Changeset 2308 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2009-10-25T12:05:31+01:00 (15 years ago)
Author:
Gubaer
Message:

fixed #3762: Deleted relation still referenced when deleting former member
Clean up of Delete command. New: only one confirmation dialog for all parent relations of deleted objects, see help.
Improved infrastructure for context-sensitive help, improved internal help browser.

Location:
trunk/src/org/openstreetmap/josm
Files:
5 added
23 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/AboutAction.java

    r2081 r2308  
    9393    static public void setUserAgent() {
    9494        Properties sysProp = System.getProperties();
    95         sysProp.put("http.agent", "JOSM/1.5 ("+(version.equals(tr("UNKNOWN"))?"UNKNOWN":version)+" "+LanguageInfo.getLanguageCode()+")");
     95        sysProp.put("http.agent", "JOSM/1.5 ("+(version.equals(tr("UNKNOWN"))?"UNKNOWN":version)+" "+LanguageInfo.getJOSMLocaleCode()+")");
    9696        System.setProperties(sysProp);
    9797    }
  • trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java

    r2273 r2308  
    107107        TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
    108108
    109 
    110109        // try to build a new way which includes all the combined
    111110        // ways
  • trunk/src/org/openstreetmap/josm/actions/HelpAction.java

    r2274 r2308  
    99
    1010import javax.swing.AbstractAction;
    11 import javax.swing.AbstractButton;
    12 import javax.swing.Action;
    13 import javax.swing.JComponent;
    14 import javax.swing.JMenu;
    1511import javax.swing.SwingUtilities;
    1612
    1713import org.openstreetmap.josm.Main;
    1814import org.openstreetmap.josm.gui.help.HelpBrowserProxy;
    19 import org.openstreetmap.josm.gui.help.Helpful;
     15import org.openstreetmap.josm.gui.help.HelpUtil;
    2016import org.openstreetmap.josm.tools.ImageProvider;
    2117
     
    2319 * Open a help browser and displays lightweight online help.
    2420 *
    25  * @author imi
    2621 */
    2722public class HelpAction extends AbstractAction {
    28 
    29 
    30     private String pathhelp = Main.pref.get("help.pathhelp", "Help/");
    31     private String pathmenu = Main.pref.get("help.pathmenu", "Menu/");
    3223
    3324    public HelpAction() {
     
    4334                if (mouse != null) {
    4435                    c = SwingUtilities.getDeepestComponentAt(c, mouse.x, mouse.y);
    45                     topic = contextSensitiveHelp(c);
     36                    topic = HelpUtil.getContextSpecificHelpTopic(c);
    4637                } else {
    4738                    topic = null;
     
    4940            } else {
    5041                Point mouse = Main.parent.getMousePosition();
    51                 topic = contextSensitiveHelp(SwingUtilities.getDeepestComponentAt(Main.parent, mouse.x, mouse.y));
     42                topic = HelpUtil.getContextSpecificHelpTopic(SwingUtilities.getDeepestComponentAt(Main.parent, mouse.x, mouse.y));
    5243            }
    5344            if (topic == null) {
    54                 HelpBrowserProxy.getInstance().setUrlForHelpTopic("Help");
     45                HelpBrowserProxy.getInstance().setUrlForHelpTopic("/");
    5546            } else {
    56                 help(topic);
     47                HelpBrowserProxy.getInstance().setUrlForHelpTopic(topic);
    5748            }
    5849        } else {
    59             HelpBrowserProxy.getInstance().setUrlForHelpTopic("Help");
     50            HelpBrowserProxy.getInstance().setUrlForHelpTopic("/");
    6051        }
    6152    }
    62 
    63     /**
    64      * @return The topic of the help. <code>null</code> for "don't know"
    65      */
    66     private String contextSensitiveHelp(Object c) {
    67         if (c == null)
    68             return null;
    69         if (c instanceof Helpful)
    70             return ((Helpful)c).helpTopic();
    71         if (c instanceof JMenu) {
    72             JMenu b = (JMenu)c;
    73             if (b.getClientProperty("help") != null)
    74                 return (String)b.getClientProperty("help");
    75             return pathmenu+b.getText();
    76         }
    77         if (c instanceof AbstractButton) {
    78             AbstractButton b = (AbstractButton)c;
    79             if (b.getClientProperty("help") != null)
    80                 return (String)b.getClientProperty("help");
    81             return contextSensitiveHelp(((AbstractButton)c).getAction());
    82         }
    83         if (c instanceof Action)
    84             return (String)((Action)c).getValue("help");
    85         if (c instanceof JComponent && ((JComponent)c).getClientProperty("help") != null)
    86             return (String)((JComponent)c).getClientProperty("help");
    87         if (c instanceof Component)
    88             return contextSensitiveHelp(((Component)c).getParent());
    89         return null;
    90     }
    91 
    92     /**
    93      * Displays the help (or browse on the already open help) on the online page
    94      * with the given help topic. Use this for larger help descriptions.
    95      */
    96     public void help(String topic) {
    97         HelpBrowserProxy.getInstance().setUrlForHelpTopic(pathhelp + topic);
    98     }
    9953}
  • trunk/src/org/openstreetmap/josm/actions/OpenLocationAction.java

    r2285 r2308  
    9191                tr("Close dialog and cancel downloading")
    9292        });
    93         dialog.configureContextsensitiveHelp("Help/Action/OpenLocation", true /* show help button */);
     93        dialog.configureContextsensitiveHelp("/Action/OpenLocation", true /* show help button */);
    9494        dialog.showDialog();
    9595        if (dialog.getValue() != 1) return;
  • trunk/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java

    r2181 r2308  
    142142            c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
    143143        } else {
    144             c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt);
     144            c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
    145145        }
    146146        if (c != null) {
     
    309309     * @param e MouseEvent from which modifiers and position are taken
    310310     * @param int modifiers For explanation: @see updateCursor
    311      * @param Simulate Set to true if the user should be bugged with additional
     311     * @param silet Set to true if the user should not be bugged with additional
    312312     *        dialogs
    313313     * @return
    314314     */
    315     private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean simulate) {
     315    private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
    316316        // Note: CTRL is the only modifier that is checked in MouseMove, don't
    317317        // forget updating it there
     
    330330                    c = DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way),true);
    331331                } else {
    332                     c = DeleteCommand.delete(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way), !alt, simulate);
     332                    c = DeleteCommand.delete(getEditLayer(),Collections.singleton((OsmPrimitive)ws.way), !alt, silent);
    333333                }
    334334            }
     
    336336            c = DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(sel));
    337337        } else {
    338             c = DeleteCommand.delete(getEditLayer(),Collections.singleton(sel), !alt, simulate);
     338            c = DeleteCommand.delete(getEditLayer(),Collections.singleton(sel), !alt, silent);
    339339        }
    340340
  • trunk/src/org/openstreetmap/josm/command/Command.java

    r2284 r2308  
    33
    44import java.util.Collection;
     5import static org.openstreetmap.josm.tools.I18n.tr;
    56import java.util.HashMap;
    67import java.util.HashSet;
     
    5859     * Creates a new command in the context of a specific data layer
    5960     *
    60      * @param layer the data layer
     61     * @param layer the data layer. Must not be null.
     62     * @throws IllegalArgumentException thrown if layer is null
    6163     */
    62     public Command(OsmDataLayer layer) {
     64    public Command(OsmDataLayer layer) throws IllegalArgumentException {
     65        if (layer == null)
     66            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "layer"));
    6367        this.layer = layer;
    6468    }
  • trunk/src/org/openstreetmap/josm/command/DeleteCommand.java

    r2296 r2308  
    1010import java.util.Collection;
    1111import java.util.Collections;
    12 import java.util.HashMap;
    1312import java.util.HashSet;
    1413import java.util.Iterator;
     
    2423
    2524import org.openstreetmap.josm.Main;
     25import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
    2626import org.openstreetmap.josm.data.osm.Node;
    2727import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    3131import org.openstreetmap.josm.data.osm.Way;
    3232import org.openstreetmap.josm.data.osm.WaySegment;
     33import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
    3334import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
    3435import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
    3536import org.openstreetmap.josm.gui.DefaultNameFormatter;
    3637import org.openstreetmap.josm.gui.ExtendedDialog;
     38import org.openstreetmap.josm.gui.actionsupport.DeleteFromRelationConfirmationDialog;
    3739import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    3840import org.openstreetmap.josm.tools.ImageProvider;
    3941
     42import sun.swing.BakedArrayList;
     43
    4044/**
    4145 * A command to delete a number of primitives from the dataset.
    42  * @author imi
     46
    4347 */
    4448public class DeleteCommand extends Command {
     
    4953
    5054    /**
    51      * Constructor for a collection of data
     55     * Constructor. Deletes a collection of primitives in the current edit layer.
     56     *
     57     * @param data the primitives to delete
    5258     */
    5359    public DeleteCommand(Collection<? extends OsmPrimitive> data) {
    54         super();
     60        if (data == null) {
     61            data = Collections.emptyList();
     62        }
    5563        this.toDelete = data;
     64    }
     65
     66    /**
     67     * Constructor. Deletes a single primitive in the current edit layer.
     68     *
     69     * @param data  the primitive to delete. Must not be null.
     70     * @throws IllegalArgumentException thrown if data is null
     71     */
     72    public DeleteCommand(OsmPrimitive data) throws IllegalArgumentException {
     73        if (data == null)
     74            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
     75        this.toDelete = Collections.singleton(data);
    5676    }
    5777
     
    5979     * Constructor for a single data item. Use the collection constructor to delete multiple
    6080     * objects.
    61      */
    62     public DeleteCommand(OsmPrimitive data) {
    63         this.toDelete = Collections.singleton(data);
    64     }
    65 
    66     /**
    67      * Constructor for a single data item. Use the collection constructor to delete multiple
    68      * objects.
    69      *
    70      * @param layer the layer context for deleting this primitive
    71      * @param data the primitive to delete
    72      */
    73     public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) {
     81     *
     82     * @param layer the layer context for deleting this primitive. Must not be null.
     83     * @param data the primitive to delete. Must not be null.
     84     * @throws IllegalArgumentException thrown if data is null
     85     * @throws IllegalArgumentException thrown if layer is null
     86     */
     87    public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) throws IllegalArgumentException {
    7488        super(layer);
     89        if (data == null)
     90            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
    7591        this.toDelete = Collections.singleton(data);
    7692    }
     
    8096     * a specific layer
    8197     *
    82      * @param layer the layer context for deleting these primitives
     98     * @param layer the layer context for deleting these primitives. Must not be null.
    8399     * @param data the primitives to delete
    84      */
    85     public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) {
     100     * @throws IllegalArgumentException thrown if layer is null
     101     */
     102    public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) throws IllegalArgumentException{
    86103        super(layer);
     104        if (data == null) {
     105            data = Collections.emptyList();
     106        }
    87107        this.toDelete = data;
     108    }
     109
     110    protected void removeNewNodesFromDeletedWay(Way w) {
     111        // #2707: ways to be deleted can include new nodes (with node.id == 0).
     112        // Remove them from the way before the way is deleted. Otherwise the
     113        // deleted way is saved (or sent to the API) with a dangling reference to a node
     114        // Example:
     115        // <node id='2' action='delete' visible='true' version='1' ... />
     116        // <node id='1' action='delete' visible='true' version='1' ... />
     117        // <!-- missing node with id -1 because new deleted nodes are not persisted -->
     118        // <way id='3' action='delete' visible='true' version='1'>
     119        // <nd ref='1' />
     120        // <nd ref='-1' /> <!-- here's the problem -->
     121        // <nd ref='2' />
     122        // </way>
     123        if (w.isNew())
     124            return; // process existing ways only
     125        List<Node> nodesToKeep = new ArrayList<Node>();
     126        // lookup new nodes which have been added to the set of deleted
     127        // nodes ...
     128        Iterator<Node> it = nodesToKeep.iterator();
     129        while(it.hasNext()) {
     130            Node n = it.next();
     131            if (n.isNew()) {
     132                it.remove();
     133            }
     134        }
     135        w.setNodes(nodesToKeep);
    88136    }
    89137
     
    93141        for (OsmPrimitive osm : toDelete) {
    94142            osm.setDeleted(true);
     143            if (osm instanceof Way) {
     144                removeNewNodesFromDeletedWay((Way)osm);
     145            }
    95146        }
    96147        return true;
     
    109160            String msg = "";
    110161            switch(OsmPrimitiveType.from(primitive)) {
    111             case NODE: msg = "Delete node {0}"; break;
    112             case WAY: msg = "Delete way {0}"; break;
    113             case RELATION:msg = "Delete relation {0}"; break;
     162                case NODE: msg = "Delete node {0}"; break;
     163                case WAY: msg = "Delete way {0}"; break;
     164                case RELATION:msg = "Delete relation {0}"; break;
    114165            }
    115166
     
    130181            apiname = t.getAPIName();
    131182            switch(t) {
    132             case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
    133             case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
    134             case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
     183                case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
     184                case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
     185                case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
    135186            }
    136187        }
     
    156207     * If a way is deleted, only the way and no nodes are deleted.
    157208     *
    158      * @param layer
     209     * @param layer the {@see OsmDataLayer} in whose context primitives are deleted. Must not be null.
    159210     * @param selection The list of all object to be deleted.
    160      * @param simulate  Set to true if the user should not be bugged with additional dialogs
     211     * @param silent  Set to true if the user should not be bugged with additional dialogs
    161212     * @return command A command to perform the deletions, or null of there is nothing to delete.
    162      */
    163     public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean simulate) {
     213     * @throws IllegalArgumentException thrown if layer is null
     214     */
     215    public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean silent) throws IllegalArgumentException {
     216        if (layer == null)
     217            throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "layer"));
     218        if (selection == null || selection.isEmpty()) return null;
    164219        CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data);
    165220        v.initialize();
     
    170225        if (v.getData().isEmpty())
    171226            return null;
    172         if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !simulate)
     227        if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !silent)
    173228            return null;
    174229        return new DeleteCommand(layer,v.getData());
     
    233288     *    <li>it is not referred to by other non-deleted primitives outside of  <code>primitivesToDelete</code></li>
    234289     * <ul>
     290     * @param backreferences backreference data structure
    235291     * @param layer  the layer in whose context primitives are deleted
    236292     * @param primitivesToDelete  the primitives to delete
     
    238294     * can be deleted too
    239295     */
    240     protected static Collection<Node> computeNodesToDelete(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
     296    protected static Collection<Node> computeNodesToDelete(BackreferencedDataSet backreferences, OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
    241297        Collection<Node> nodesToDelete = new HashSet<Node>();
    242         CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
    243         for (OsmPrimitive osm : primitivesToDelete) {
    244             if (! (osm instanceof Way) ) {
    245                 continue;
    246             }
    247             for (Node n : ((Way) osm).getNodes()) {
     298        //CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
     299        for (Way way : OsmPrimitive.getFilteredList(primitivesToDelete, Way.class)) {
     300            for (Node n : way.getNodes()) {
    248301                if (n.isTagged()) {
    249302                    continue;
    250303                }
    251                 v.initialize();
    252                 n.visit(v);
    253                 Collection<OsmPrimitive> referringPrimitives = v.getData();
     304                //v.initialize();
     305                //n.visit(v);
     306                Collection<OsmPrimitive> referringPrimitives = backreferences.getParents(n);
    254307                referringPrimitives.removeAll(primitivesToDelete);
    255308                int count = 0;
     
    276329     * they are part of a relation, inform the user and do not delete.
    277330     *
    278      * @param layer the {@see OsmDataLayer} in whose context a primitive the primitives are deleted
    279      * @param selection The objects to delete.
     331     * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
     332     * @param selection the objects to delete.
    280333     * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
    281      * @param simulate Set to true if the user should not be bugged with additional questions
    282334     * @return command a command to perform the deletions, or null if there is nothing to delete.
    283335     */
    284336    public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
    285337            boolean alsoDeleteNodesInWay) {
    286         return delete(layer, selection, alsoDeleteNodesInWay, false);
    287     }
    288 
     338        return delete(layer, selection, alsoDeleteNodesInWay, false /* not silent */);
     339    }
     340
     341    /**
     342     * Try to delete all given primitives.
     343     *
     344     * If a node is used by a way, it's removed from that way. If a node or a way is used by a
     345     * relation, inform the user and do not delete.
     346     *
     347     * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
     348     * they are part of a relation, inform the user and do not delete.
     349     *
     350     * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
     351     * @param selection the objects to delete.
     352     * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
     353     * @param silent set to true if the user should not be bugged with additional questions
     354     * @return command a command to perform the deletions, or null if there is nothing to delete.
     355     */
    289356    public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
    290             boolean alsoDeleteNodesInWay, boolean simulate) {
    291         if (selection.isEmpty())
     357            boolean alsoDeleteNodesInWay, boolean silent) {
     358        if (selection == null || selection.isEmpty())
    292359            return null;
    293360
    294         Collection<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
     361        BackreferencedDataSet backreferences = new BackreferencedDataSet(layer.data);
     362        backreferences.build();
     363
     364        Set<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
    295365        Collection<Way> waysToBeChanged = new HashSet<Way>();
    296         HashMap<OsmPrimitive, Collection<OsmPrimitive>> relationsToBeChanged = new HashMap<OsmPrimitive, Collection<OsmPrimitive>>();
    297366
    298367        if (alsoDeleteNodesInWay) {
    299368            // delete untagged nodes only referenced by primitives in primitivesToDelete,
    300369            // too
    301             Collection<Node> nodesToDelete = computeNodesToDelete(layer, primitivesToDelete);
     370            Collection<Node> nodesToDelete = computeNodesToDelete(backreferences, layer, primitivesToDelete);
    302371            primitivesToDelete.addAll(nodesToDelete);
    303372        }
    304373
    305         if (!simulate && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
     374        if (!silent && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
    306375            return null;
    307376
    308         CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
    309         for (OsmPrimitive osm : primitivesToDelete) {
    310             v.initialize();
    311             osm.visit(v);
    312             for (OsmPrimitive ref : v.getData()) {
    313                 if (primitivesToDelete.contains(ref)) {
    314                     continue;
    315                 }
    316                 if (ref instanceof Way) {
    317                     waysToBeChanged.add((Way) ref);
    318                 } else if (ref instanceof Relation) {
    319                     if (testRelation((Relation) ref, osm, simulate) == 1) {
    320                         Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
    321                         if (relset == null) {
    322                             relset = new HashSet<OsmPrimitive>();
    323                         }
    324                         relset.add(osm);
    325                         relationsToBeChanged.put(ref, relset);
    326                     } else
    327                         return null;
    328                 } else
    329                     return null;
    330             }
    331         }
     377        waysToBeChanged.addAll(OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Way.class));
    332378
    333379        Collection<Command> cmds = new LinkedList<Command>();
     
    337383            if (wnew.getNodesCount() < 2) {
    338384                primitivesToDelete.add(w);
    339 
    340                 v.initialize();
    341                 w.visit(v);
    342                 for (OsmPrimitive ref : v.getData()) {
    343                     if (primitivesToDelete.contains(ref)) {
    344                         continue;
    345                     }
    346                     if (ref instanceof Relation) {
    347                         Boolean found = false;
    348                         Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
    349                         if (relset == null) {
    350                             relset = new HashSet<OsmPrimitive>();
    351                         } else {
    352                             for (OsmPrimitive m : relset) {
    353                                 if (m == w) {
    354                                     found = true;
    355                                     break;
    356                                 }
    357                             }
    358                         }
    359                         if (!found) {
    360                             if (testRelation((Relation) ref, w, simulate) == 1) {
    361                                 relset.add(w);
    362                                 relationsToBeChanged.put(ref, relset);
    363                             } else
    364                                 return null;
    365                         }
    366                     } else
    367                         return null;
    368                 }
    369385            } else {
    370386                cmds.add(new ChangeCommand(w, wnew));
     
    372388        }
    373389
    374         Iterator<OsmPrimitive> iterator = relationsToBeChanged.keySet().iterator();
     390        // get a confirmation that the objects to delete can be removed from their parent
     391        // relations
     392        //
     393        if (!silent) {
     394            Set<RelationToChildReference> references = backreferences.getRelationToChildReferences(primitivesToDelete);
     395            Iterator<RelationToChildReference> it = references.iterator();
     396            while(it.hasNext()) {
     397                RelationToChildReference ref = it.next();
     398                if (ref.getParent().isDeleted()) {
     399                    it.remove();
     400                }
     401            }
     402            if (!references.isEmpty()) {
     403                DeleteFromRelationConfirmationDialog dialog = DeleteFromRelationConfirmationDialog.getInstance();
     404                dialog.getModel().populate(references);
     405                dialog.setVisible(true);
     406                if (dialog.isCanceled())
     407                    return null;
     408            }
     409        }
     410
     411        // remove the objects from their parent relations
     412        //
     413        Iterator<Relation> iterator = OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Relation.class).iterator();
    375414        while (iterator.hasNext()) {
    376             Relation cur = (Relation) iterator.next();
     415            Relation cur = iterator.next();
    377416            Relation rel = new Relation(cur);
    378             for (OsmPrimitive osm : relationsToBeChanged.get(cur)) {
    379                 rel.removeMembersFor(osm);
    380             }
     417            rel.removeMembersFor(primitivesToDelete);
    381418            cmds.add(new ChangeCommand(cur, rel));
    382419        }
    383420
    384         // #2707: ways to be deleted can include new nodes (with node.id == 0).
    385         // Remove them from the way before the way is deleted. Otherwise the
    386         // deleted way is saved (or sent to the API) with a dangling reference to a node
    387         // Example:
    388         // <node id='2' action='delete' visible='true' version='1' ... />
    389         // <node id='1' action='delete' visible='true' version='1' ... />
    390         // <!-- missing node with id -1 because new deleted nodes are not persisted -->
    391         // <way id='3' action='delete' visible='true' version='1'>
    392         // <nd ref='1' />
    393         // <nd ref='-1' /> <!-- heres the problem -->
    394         // <nd ref='2' />
    395         // </way>
    396         for (OsmPrimitive primitive : primitivesToDelete) {
    397             if (!(primitive instanceof Way)) {
    398                 continue;
    399             }
    400             Way w = (Way) primitive;
    401             if (w.isNew()) { // new ways with id == 0 are fine,
    402                 continue; // process existing ways only
    403             }
    404             Way wnew = new Way(w);
    405             List<Node> nodesToKeep = new ArrayList<Node>();
    406             // lookup new nodes which have been added to the set of deleted
    407             // nodes ...
    408             for (Node n : wnew.getNodes()) {
    409                 if (!n.isNew() || !primitivesToDelete.contains(n)) {
    410                     nodesToKeep.add(n);
    411                 }
    412             }
    413             // .. and remove them from the way
    414             //
    415             wnew.setNodes(nodesToKeep);
    416             if (nodesToKeep.size() < w.getNodesCount()) {
    417                 cmds.add(new ChangeCommand(w, wnew));
    418             }
    419         }
    420 
     421        // build the delete command
     422        //
    421423        if (!primitivesToDelete.isEmpty()) {
    422424            cmds.add(new DeleteCommand(layer,primitivesToDelete));
     
    427429
    428430    public static Command deleteWaySegment(OsmDataLayer layer, WaySegment ws) {
    429         if (ws.way.getNodesCount() < 3) {
    430             // If the way contains less than three nodes, it can't have more
    431             // than one segment, so the way should be deleted.
    432 
     431        if (ws.way.getNodesCount() < 3)
    433432            return new DeleteCommand(layer, Collections.singleton(ws.way));
    434         }
    435433
    436434        if (ws.way.firstNode() == ws.way.lastNode()) {
  • trunk/src/org/openstreetmap/josm/data/osm/Relation.java

    r2305 r2308  
    22
    33import java.util.ArrayList;
     4import java.util.Collection;
    45import java.util.HashMap;
    56import java.util.HashSet;
     
    170171        for (RelationMemberData member:relationData.getMembers()) {
    171172            switch (member.getMemberType()) {
    172             case NODE:
    173                 nodes.put(member.getMemberId(), nodeMarker);
    174                 break;
    175             case WAY:
    176                 ways.put(member.getMemberId(), wayMarker);
    177                 break;
    178             case RELATION:
    179                 relations.put(member.getMemberId(), relationMarker);
    180                 break;
     173                case NODE:
     174                    nodes.put(member.getMemberId(), nodeMarker);
     175                    break;
     176                case WAY:
     177                    ways.put(member.getMemberId(), wayMarker);
     178                    break;
     179                case RELATION:
     180                    relations.put(member.getMemberId(), relationMarker);
     181                    break;
    181182            }
    182183        }
     
    202203            OsmPrimitive foundMember = null;
    203204            switch (member.getMemberType()) {
    204             case NODE:
    205                 foundMember = nodes.get(member.getMemberId());
    206                 if (foundMember == nodeMarker) {
    207                     throw new AssertionError("Data consistency problem - relation with missing member detected");
    208                 }
    209                 break;
    210             case WAY:
    211                 foundMember = ways.get(member.getMemberId());
    212                 if (foundMember == wayMarker) {
    213                     throw new AssertionError("Data consistency problem - relation with missing member detected");
    214                 }
    215                 break;
    216             case RELATION:
    217                 foundMember = relations.get(member.getMemberId());
    218                 if (foundMember == relationMarker) {
    219                     throw new AssertionError("Data consistency problem - relation with missing member detected");
    220                 }
    221                 break;
     205                case NODE:
     206                    foundMember = nodes.get(member.getMemberId());
     207                    if (foundMember == nodeMarker)
     208                        throw new AssertionError("Data consistency problem - relation with missing member detected");
     209                    break;
     210                case WAY:
     211                    foundMember = ways.get(member.getMemberId());
     212                    if (foundMember == wayMarker)
     213                        throw new AssertionError("Data consistency problem - relation with missing member detected");
     214                    break;
     215                case RELATION:
     216                    foundMember = relations.get(member.getMemberId());
     217                    if (foundMember == relationMarker)
     218                        throw new AssertionError("Data consistency problem - relation with missing member detected");
     219                    break;
    222220            }
    223221            newMembers.add(new RelationMember(member.getRole(), foundMember));
     
    290288    }
    291289
     290    /**
     291     * removes all members with member.member == primitive
     292     *
     293     * @param primitives the primitives to check for
     294     */
     295    public void removeMembersFor(Collection<OsmPrimitive> primitives) {
     296        if (primitives == null || primitives.isEmpty())
     297            return;
     298
     299        ArrayList<RelationMember> todelete = new ArrayList<RelationMember>();
     300        for (RelationMember member: members) {
     301            if (primitives.contains(member.getMember())) {
     302                todelete.add(member);
     303            }
     304        }
     305        members.removeAll(todelete);
     306    }
     307
    292308    @Override
    293309    public String getDisplayName(NameFormatter formatter) {
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java

    r2282 r2308  
    13701370        leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic",false);
    13711371        orderFont = new Font(Main.pref.get("mappaint.font","Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
    1372         String[] names = {"name:"+LanguageInfo.getLanguageCode(), "name", "int_name", "ref", "operator", "brand","addr:housenumber"};
     1372        String[] names = {"name:"+LanguageInfo.getJOSMLocaleCode(), "name", "int_name", "ref", "operator", "brand","addr:housenumber"};
    13731373        regionalNameOrder = Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(names));
    13741374        minEN = nc.getEastNorth(0,nc.getHeight()-1);
  • trunk/src/org/openstreetmap/josm/gui/GettingStarted.java

    r2120 r2308  
    5252
    5353        final private int myVersion = AboutAction.getVersionNumber();
    54         final private String myLang = LanguageInfo.getLanguageCodeWiki();
     54        final private String myLang = LanguageInfo.getWikiLanguagePrefix();
    5555
    5656        /**
  • trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java

    r2301 r2308  
    192192
    193193        public void actionPerformed(ActionEvent arg0) {
    194             HelpBrowserProxy.getInstance().setUrlForHelpTopic("/Help/Dialog/ConflictDialog");
     194            HelpBrowserProxy.getInstance().setUrlForHelpTopic("/Dialog/ConflictDialog");
    195195        }
    196196    }
  • trunk/src/org/openstreetmap/josm/gui/help/ContextSensitiveHelpAction.java

    r2289 r2308  
    1919    private String helpTopic;
    2020
     21    /**
     22     *
     23     * @param helpTopic
     24     */
    2125    public ContextSensitiveHelpAction(String helpTopic) {
    2226        putValue(SHORT_DESCRIPTION, tr("Show help information"));
  • trunk/src/org/openstreetmap/josm/gui/help/HelpApplication.java

    r2274 r2308  
    66import java.awt.Rectangle;
    77import java.awt.Toolkit;
     8import java.io.IOException;
     9import java.io.PrintWriter;
     10import java.io.StringWriter;
     11import java.lang.Thread.UncaughtExceptionHandler;
    812import java.util.Arrays;
    913import java.util.Collection;
     
    1216import java.util.List;
    1317import java.util.Map;
     18import java.util.logging.FileHandler;
     19import java.util.logging.Level;
     20import java.util.logging.LogManager;
     21import java.util.logging.Logger;
     22import java.util.logging.SimpleFormatter;
     23
     24import javax.swing.JOptionPane;
    1425
    1526import org.openstreetmap.josm.Main;
     
    2334 */
    2435public class HelpApplication {
     36    static private final Logger logger = Logger.getLogger(HelpApplication.class.getName());
    2537    private HelpBrowser browser;
    2638    private HelpBrowserCommandProcessor commandProcessor;
     
    8294
    8395        MainApplication.preConstructorInit(args);
     96        Thread.setDefaultUncaughtExceptionHandler(
     97                new UncaughtExceptionHandler() {
     98                    public void uncaughtException(Thread t, Throwable e) {
     99                        StringWriter sw = new StringWriter();
     100                        e.printStackTrace(new PrintWriter(sw));
     101                        logger.log(Level.SEVERE, sw.getBuffer().toString());
     102                    }
     103                }
     104        );
    84105
    85106        new HelpApplication().start();
  • trunk/src/org/openstreetmap/josm/gui/help/HelpBrowser.java

    r2276 r2308  
    22package org.openstreetmap.josm.gui.help;
    33
     4import static org.openstreetmap.josm.gui.help.HelpUtil.buildAbsoluteHelpTopic;
     5import static org.openstreetmap.josm.gui.help.HelpUtil.getHelpTopicEditUrl;
    46import static org.openstreetmap.josm.tools.I18n.tr;
    57
    68import java.awt.BorderLayout;
     9import java.awt.Rectangle;
    710import java.awt.event.ActionEvent;
    811import java.awt.event.KeyEvent;
     
    1215import java.io.IOException;
    1316import java.io.InputStreamReader;
     17import java.util.Locale;
    1418import java.util.Observable;
    1519import java.util.Observer;
     20import java.util.logging.Logger;
    1621
    1722import javax.swing.AbstractAction;
     
    2227import javax.swing.JOptionPane;
    2328import javax.swing.JPanel;
     29import javax.swing.JScrollBar;
    2430import javax.swing.JScrollPane;
    2531import javax.swing.JSeparator;
     
    2834import javax.swing.event.HyperlinkEvent;
    2935import javax.swing.event.HyperlinkListener;
     36import javax.swing.text.AttributeSet;
     37import javax.swing.text.BadLocationException;
     38import javax.swing.text.Document;
     39import javax.swing.text.Element;
     40import javax.swing.text.SimpleAttributeSet;
     41import javax.swing.text.html.HTMLDocument;
    3042import javax.swing.text.html.HTMLEditorKit;
    3143import javax.swing.text.html.StyleSheet;
     44import javax.swing.text.html.HTML.Tag;
    3245
    3346import org.openstreetmap.josm.Main;
     47import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    3448import org.openstreetmap.josm.tools.ImageProvider;
    35 import org.openstreetmap.josm.tools.LanguageInfo;
    3649import org.openstreetmap.josm.tools.OpenBrowser;
    37 import org.openstreetmap.josm.tools.WikiReader;
    3850
    3951public class HelpBrowser extends JFrame {
    40 
     52    static private final Logger logger = Logger.getLogger(HelpBrowser.class.getName());
     53
     54    /** the unique instance */
    4155    private static HelpBrowser instance;
    4256
     
    6175    static public void launchBrowser(String helpTopic) {
    6276        HelpBrowser browser = getInstance();
    63         browser.setUrlForHelpTopic(helpTopic);
     77        browser.openHelpTopic(helpTopic);
    6478        browser.setVisible(true);
    6579        browser.toFront();
     
    6882    /** the help browser */
    6983    private JEditorPane help;
     84    private JScrollPane spHelp;
     85
    7086    /** the help browser history */
    7187    private HelpBrowserHistory history;
     
    7490    private String url;
    7591
    76     private String languageCode = LanguageInfo.getLanguageCodeWiki();
    77     private String baseurl = Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
    78     private String pathbase = Main.pref.get("help.pathbase", "/wiki/");
    79     private WikiReader reader = new WikiReader(baseurl);
     92    private HelpContentReader reader;
    8093
    8194    /**
     
    126139        help.setEditorKit(kit);
    127140        help.setEditable(false);
    128         help.addHyperlinkListener(new HyperlinkListener(){
    129             public void hyperlinkUpdate(HyperlinkEvent e) {
    130                 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
    131                     return;
    132                 if (e.getURL() == null) {
    133                     help.setText("<html>404 not found</html>");
    134                 } else if (e.getURL().toString().endsWith("action=edit")) {
    135                     OpenBrowser.displayUrl(e.getURL().toString());
    136                 } else {
    137                     url = e.getURL().toString();
    138                     setUrl(e.getURL().toString());
    139                 }
    140             }
    141         });
     141        help.addHyperlinkListener(new HyperlinkHandler());
    142142        help.setContentType("text/html");
    143 
    144 
    145143        history = new HelpBrowserHistory(this);
    146144
     
    148146        setContentPane(p);
    149147
    150         p.add(new JScrollPane(help), BorderLayout.CENTER);
     148        p.add(spHelp = new JScrollPane(help), BorderLayout.CENTER);
    151149
    152150        addWindowListener(new WindowAdapter(){
     
    168166
    169167    public HelpBrowser() {
     168        reader = new HelpContentReader(HelpUtil.getWikiBaseUrl());
    170169        build();
    171170    }
    172171
     172    /**
     173     * Replies the current URL
     174     *
     175     * @return the current URL
     176     */
    173177    public String getUrl() {
    174178        return url;
    175179    }
    176180
    177     protected void loadUrl(String url) {
    178         String langurl = url;
    179         if(url.startsWith(baseurl+pathbase)){
    180             int i = pathbase.length()+baseurl.length();
    181             String title = url.substring(i);
    182             if(languageCode.length() != 0 && !title.startsWith(languageCode)) {
    183                 title = languageCode + title;
    184             }
    185             langurl = url.substring(0, i) + title;
    186         }
    187         boolean loaded = false;
    188         if(!langurl.equals(this.url) && !langurl.equals(url)){
    189             loaded = loadHelpUrl(url, langurl, true);
    190         }
    191         if(!loaded) {
    192             loaded = loadHelpUrl(url, langurl, false);
    193         }
    194         if(!loaded) {
    195             help.setText(tr("Error while loading page {0}",url));
    196         }
    197     }
    198 
    199     public void setUrl(String url) {
    200         loadUrl(url);
     181    /**
     182     * Displays a warning page when a help topic doesn't exist yet.
     183     *
     184     * @param relativeHelpTopic the help topic
     185     */
     186    protected void handleMissingHelpContent(String relativeHelpTopic) {
     187        String message = tr("<html><p class=\"warning-header\">Help content for help topic missing</p>"
     188                + "<p class=\"warning-body\">Help content for the help topic <strong>{0}</strong> is "
     189                + "not available yet. It is missing both in your local language ({1}) and in english.<br><br>"
     190                + "Please help to improve the JOSM help system and fill in the missing information."
     191                + "You can both edit the <a href=\"{2}\">help topic in your local language ({1})</a> and "
     192                + "the <a href=\"{3}\">help topic in english</a>."
     193                + "</p></html>",
     194                relativeHelpTopic,
     195                Locale.getDefault().getDisplayName(),
     196                getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic)),
     197                getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH))
     198        );
     199        help.setText(message);
     200    }
     201
     202    /**
     203     * Displays a error page if a help topic couldn't be loaded because of network or IO error.
     204     *
     205     * @param relativeHelpTopic the help topic
     206     * @param e the exception
     207     */
     208    protected void handleHelpContentReaderException(String relativeHelpTopic, HelpContentReaderException e) {
     209        String message = tr("<html><p class=\"error-header\">Error when retrieving help information</p>"
     210                + "<p class=\"error-body\">The content for the help topic <strong>{0}</strong> could "
     211                + "not be loaded. The error message is (untranslated):<br>"
     212                + "<tt>{1}</tt>"
     213                + "</p></html>",
     214                relativeHelpTopic,
     215                e.toString()
     216        );
     217        help.setText(message);
     218    }
     219
     220    protected void scrollToTop() {
     221        JScrollBar sb = spHelp.getVerticalScrollBar();
     222        sb.setValue(sb.getMinimum());
     223    }
     224
     225    /**
     226     * Loads a help topic given by a relative help topic name (i.e. "/Action/New")
     227     *
     228     * First tries to load the language specific help topic. If it is missing, tries to
     229     * load the topic in english.
     230     *
     231     * @param relativeHelpTopic the relative help topic
     232     */
     233    protected void loadRelativeHelpTopic(String relativeHelpTopic) {
     234        String url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic));
     235        String content = null;
     236        try {
     237            content = reader.fetchHelpTopicContent(url);
     238        } catch(MissingHelpContentException e) {
     239            url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH));
     240            try {
     241                logger.info("fetching url: " + url);
     242                content = reader.fetchHelpTopicContent(url);
     243            } catch(MissingHelpContentException e1) {
     244                handleMissingHelpContent(relativeHelpTopic);
     245                return;
     246            } catch(HelpContentReaderException e1) {
     247                e1.printStackTrace();
     248                handleHelpContentReaderException(relativeHelpTopic,e1);
     249                return;
     250            }
     251        } catch(HelpContentReaderException e) {
     252            e.printStackTrace();
     253            handleHelpContentReaderException(relativeHelpTopic, e);
     254            return;
     255        }
     256        help.setText(content);
     257        history.setCurrentUrl(url);
     258        this.url = url;
     259        scrollToTop();
     260    }
     261
     262    /**
     263     * Loads a help topic given by an absolute help topic name, i.e.
     264     * "/De:Help/Action/New"
     265     *
     266     * @param absoluteHelpTopic the absolute help topic name
     267     */
     268    protected void loadAbsoluteHelpTopic(String absoluteHelpTopic) {
     269        String url = HelpUtil.getHelpTopicUrl(absoluteHelpTopic);
     270        String content = null;
     271        try {
     272            content = reader.fetchHelpTopicContent(url);
     273        } catch(MissingHelpContentException e) {
     274            handleMissingHelpContent(absoluteHelpTopic);
     275            return;
     276        } catch(HelpContentReaderException e) {
     277            e.printStackTrace();
     278            handleHelpContentReaderException(absoluteHelpTopic, e);
     279            return;
     280        }
     281        help.setText(content);
     282        history.setCurrentUrl(url);
     283        this.url = url;
     284        scrollToTop();
     285    }
     286
     287    /**
     288     * Opens an URL and displays the content.
     289     *
     290     *  If the URL is the locator of an absolute help topic, help content is loaded from
     291     *  the JOSM wiki. Otherwise, the help browser loads the page from the given URL
     292     *
     293     * @param url the url
     294     */
     295    public void openUrl(String url) {
    201296        if (!isVisible()) {
    202297            setVisible(true);
     
    205300            toFront();
    206301        }
    207         history.setCurrentUrl(url);
    208     }
    209 
    210     public void setUrlForHelpTopic(String topic) {
    211         setUrl(baseurl+pathbase+ topic);
    212     }
    213 
    214     protected boolean loadHelpUrl(String url, String localizedUrl, boolean useLocalizedUrl){
    215         this.url = useLocalizedUrl ? localizedUrl : url;
    216         boolean loaded = false;
    217         try {
    218             String txt = reader.read(this.url);
    219             if(txt.length() == 0){
    220                 if(useLocalizedUrl)
    221                     throw new IOException();
    222                 if(url.equals(localizedUrl)){
    223                     txt = ("<HTML>"+tr("Help page missing. Create it in <A HREF=\"{0}\">English</A>.",
    224                             url+"?action=edit")+"</HTML>");
    225                 } else{
    226                     txt = ("<HTML>"+tr("Help page missing. Create it in <A HREF=\"{0}\">English</A> or <A HREF=\"{1}\">your language</A>.",
    227                             url+"?action=edit", localizedUrl+"?action=edit")+"</HTML>");
    228                 }
    229             }
    230             help.setText(txt);
    231             help.setCaretPosition(0);
    232             loaded = true;
    233         } catch (IOException ex) {
    234         }
    235         return loaded;
     302        String helpTopic = HelpUtil.extractAbsoluteHelpTopic(url);
     303        if (helpTopic == null) {
     304            try {
     305                this.url = url;
     306                help.setPage(url);
     307            } catch(IOException e) {
     308                HelpAwareOptionPane.showOptionDialog(
     309                        Main.parent,
     310                        tr(
     311                                "<html>Failed to open help page for url {0}.<br>"
     312                                + "This is most likely due to a network problem, please check your<br>"
     313                                + "your internet connection</html>",
     314                                url.toString()
     315                        ),
     316                        tr("Failed to open URL"),
     317                        JOptionPane.ERROR_MESSAGE,
     318                        null, /* no icon */
     319                        null, /* standard options, just OK button */
     320                        null, /* default is standard */
     321                        null /* no help context */
     322                );
     323            }
     324            history.setCurrentUrl(url);
     325        } else {
     326            loadAbsoluteHelpTopic(helpTopic);
     327        }
     328    }
     329
     330    /**
     331     * Loads and displays the help information for a help topic given
     332     * by a relative help topic name, i.e. "/Action/New"
     333     *
     334     * @param relativeHelpTopic the relative help topic
     335     */
     336    public void openHelpTopic(String relativeHelpTopic) {
     337        if (!isVisible()) {
     338            setVisible(true);
     339            toFront();
     340        } else {
     341            toFront();
     342        }
     343        loadRelativeHelpTopic(relativeHelpTopic);
    236344    }
    237345
     
    256364
    257365        public void actionPerformed(ActionEvent e) {
    258             if (!getUrl().startsWith(baseurl)) {
     366            if (!getUrl().startsWith(HelpUtil.getWikiBaseHelpUrl())) {
     367                String message = tr(
     368                        "<html>The current URL <tt>{0}</tt><br>"
     369                        + "is an external URL. Editing is only possible for help topics<br>"
     370                        + "on the help server <tt>{1}</tt>.</html>",
     371                        getUrl(),
     372                        HelpUtil.getWikiBaseUrl()
     373                );
    259374                JOptionPane.showMessageDialog(
    260375                        Main.parent,
    261                         tr("Can only edit help pages from JOSM Online Help"),
     376                        message,
    262377                        tr("Warning"),
    263378                        JOptionPane.WARNING_MESSAGE
     
    265380                return;
    266381            }
     382            String url = getUrl();
     383            url = url.replaceAll("#[^#]*$", "");
    267384            OpenBrowser.displayUrl(url+"?action=edit");
    268385        }
     
    277394
    278395        public void actionPerformed(ActionEvent e) {
    279             setUrl(url);
     396            openUrl(getUrl());
    280397        }
    281398    }
     
    328445
    329446        public void actionPerformed(ActionEvent e) {
    330             setUrlForHelpTopic("Help");
     447            openHelpTopic("/");
     448        }
     449    }
     450
     451    class HyperlinkHandler implements HyperlinkListener {
     452
     453        /**
     454         * Scrolls the help browser to the element with id <code>id</code>
     455         *
     456         * @param id the id
     457         * @return true, if an element with this id was found and scrolling was successful; false, otherwise
     458         */
     459        protected boolean scrollToElementWithId(String id) {
     460            Document d = help.getDocument();
     461            if (d instanceof HTMLDocument) {
     462                HTMLDocument doc = (HTMLDocument) d;
     463                Element element = doc.getElement(id);
     464                try {
     465                    Rectangle r = help.modelToView(element.getStartOffset());
     466                    if (r != null) {
     467                        Rectangle vis = help.getVisibleRect();
     468                        r.height = vis.height;
     469                        help.scrollRectToVisible(r);
     470                        return true;
     471                    }
     472                } catch(BadLocationException e) {
     473                    System.err.println(tr("Warning: bad location in HTML document. Exception was: " + e.toString()));
     474                    e.printStackTrace();
     475                }
     476            }
     477            return false;
     478        }
     479
     480        /**
     481         * Checks whether the hyperlink event originated on a <a ...> element with
     482         * a relative href consisting of a URL fragment only, i.e.
     483         * <a href="#thisIsALocalFragment">. If so, replies the fragment, i.e.
     484         * "thisIsALocalFragment".
     485         *
     486         * Otherwise, replies null
     487         *
     488         * @param e the hyperlink event
     489         * @return the local fragment
     490         */
     491        protected String getUrlFragment(HyperlinkEvent e) {
     492            AttributeSet set = e.getSourceElement().getAttributes();
     493            Object value = set.getAttribute(Tag.A);
     494            if (value == null || ! (value instanceof SimpleAttributeSet)) return null;
     495            SimpleAttributeSet atts = (SimpleAttributeSet)value;
     496            value = atts.getAttribute(javax.swing.text.html.HTML.Attribute.HREF);
     497            if (value == null) return null;
     498            String s = (String)value;
     499            if (s.matches("#.*"))
     500                return s.substring(1);
     501            return null;
     502        }
     503
     504        public void hyperlinkUpdate(HyperlinkEvent e) {
     505            if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
     506                return;
     507            if (e.getURL() == null) {
     508                // Probably hyperlink event on a an A-element with a href consisting of
     509                // a fragment only, i.e. "#ALocalFragment".
     510                //
     511                String fragment = getUrlFragment(e);
     512                if (fragment != null) {
     513                    // first try to scroll to an element with id==fragment. This is the way
     514                    // table of contents are built in the JOSM wiki. If this fails, try to
     515                    // scroll to a <A name="..."> element.
     516                    //
     517                    if (!scrollToElementWithId(fragment)) {
     518                        help.scrollToReference(fragment);
     519                    }
     520                } else {
     521                    HelpAwareOptionPane.showOptionDialog(
     522                            Main.parent,
     523                            tr("Failed to open help page. The target URL is empty."),
     524                            tr("Failed to open help page"),
     525                            JOptionPane.ERROR_MESSAGE,
     526                            null, /* no icon */
     527                            null, /* standard options, just OK button */
     528                            null, /* default is standard */
     529                            null /* no help context */
     530                    );
     531                }
     532            } else if (e.getURL().toString().endsWith("action=edit")) {
     533                OpenBrowser.displayUrl(e.getURL().toString());
     534            } else {
     535                url = e.getURL().toString();
     536                openUrl(e.getURL().toString());
     537            }
    331538        }
    332539    }
  • trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserCommandProcessor.java

    r2274 r2308  
    55import java.io.IOException;
    66import java.io.InputStreamReader;
     7import java.util.logging.Level;
     8import java.util.logging.Logger;
    79
    810import javax.swing.SwingUtilities;
     
    1618 */
    1719public class HelpBrowserCommandProcessor implements Runnable {
     20    private static final Logger logger = Logger.getLogger(HelpBrowserCommandProcessor.class.getName());
    1821
    1922    /** the controlled help browser*/
     
    3134     * Show the help page for help topic <code>helpTopic</code>.
    3235     *
    33      * @param helpTopics the help topic
     36     * @param helpTopic the help topic
    3437     */
    35     protected void setUrlForHelpTopics(final String helpTopics) {
     38    protected void setUrlForHelpTopic(final String helpTopic) {
    3639        Runnable r = new Runnable() {
    3740            public void run() {
    38                 browser.setUrlForHelpTopic(helpTopics);
     41                browser.openHelpTopic(helpTopic);
    3942                browser.setVisible(true);
    4043                browser.toFront();
     
    6568        while(true) {
    6669            String cmd = null;
     70
    6771            try {
    6872                cmd = reader.readLine();
     73                logger.info("got command: " + cmd);
    6974            } catch(IOException e) {
     75                logger.log(Level.SEVERE,e.toString());
    7076                System.out.println(tr("Failed to read command. Exiting help browser. Exception was:" + e.toString()));
    7177                System.exit(1);
     
    7379            if (cmd.startsWith("exit")) {
    7480                exit();
    75             } else if (cmd.startsWith("setUrlForHelpTopics ")) {
    76                 String helpTopics = cmd.substring("setUrlForHelpTopics ".length());
    77                 setUrlForHelpTopics(helpTopics);
     81            } else if (cmd.startsWith("setUrlForHelpTopic ")) {
     82                String helpTopic = cmd.substring("setUrlForHelpTopic ".length());
     83                setUrlForHelpTopic(helpTopic);
    7884            }
    7985        }
  • trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserHistory.java

    r2274 r2308  
    3535        if (historyPos < 0) return;
    3636        String url = history.get(historyPos);
    37         browser.loadUrl(url);
     37        browser.openUrl(url);
    3838        setChanged();
    3939        notifyObservers();
     
    4444        if (historyPos >= history.size()) return;
    4545        String url = history.get(historyPos);
    46         browser.loadUrl(url);
     46        browser.openUrl(url);
    4747        setChanged();
    4848        notifyObservers();
  • trunk/src/org/openstreetmap/josm/gui/help/HelpBrowserProxy.java

    r2276 r2308  
    7777    /**
    7878     * Direct the help browser to the help page for help topic
    79      * <code>helpTopic</code>
     79     * <code>relativeHelpTopic</code>
    8080     *
    81      * @param helpTopic the help topic
     81     * @param relativeHelpTopic the help topic
    8282     */
    83     public void setUrlForHelpTopic(String helpTopic) {
     83    public void setUrlForHelpTopic(String relativeHelpTopic) {
    8484        if (helpBrowserProcess == null) {
    8585            launch();
     
    9595            return;
    9696        }
    97         pw.println("setUrlForHelpTopics " + helpTopic);
     97        pw.println("setUrlForHelpTopic " + relativeHelpTopic);
    9898        pw.flush();
    9999    }
  • trunk/src/org/openstreetmap/josm/gui/help/HelpUtil.java

    r2301 r2308  
    22package org.openstreetmap.josm.gui.help;
    33
     4import java.awt.Component;
    45import java.awt.event.KeyEvent;
    5 
     6import java.util.Locale;
     7
     8import javax.swing.AbstractButton;
     9import javax.swing.Action;
    610import javax.swing.JComponent;
     11import javax.swing.JMenu;
    712import javax.swing.KeyStroke;
    813
    914import org.openstreetmap.josm.Main;
     15import org.openstreetmap.josm.tools.LanguageInfo;
    1016
    1117public class HelpUtil {
    1218
    1319    /**
     20     * Replies the base wiki URL.
     21     *
     22     * @return the base wiki URL
     23     */
     24    static public String getWikiBaseUrl() {
     25        return Main.pref.get("help.baseurl", "http://josm.openstreetmap.de");
     26    }
     27
     28    /**
     29     * Replies the base wiki URL for help pages
     30     *
     31     * @return the base wiki URL for help pages
     32     */
     33    static public String getWikiBaseHelpUrl() {
     34        return getWikiBaseUrl() + "/wiki";
     35    }
     36
     37    /**
     38     * Replies the URL on the wiki for an absolute help topic. The URL is encoded in UTF-8.
     39     *
     40     * @param absoluteHelpTopic the absolute help topic
     41     * @return the url
     42     * @see #buildAbsoluteHelpTopic(String)
     43     * @see #buildAbsoluteHelpTopic(String, Locale)
     44     */
     45    static public String getHelpTopicUrl(String absoluteHelpTopic) {
     46        String ret = getWikiBaseHelpUrl();
     47        ret = ret.replaceAll("\\/+$", "");
     48        absoluteHelpTopic  =absoluteHelpTopic.replace(" ", "%20");
     49        absoluteHelpTopic = absoluteHelpTopic.replaceAll("^\\/+", "/");
     50        return ret + absoluteHelpTopic;
     51    }
     52
     53    /**
     54     * Replies the URL to the edit page for the absolute help topic.
     55     *
     56     * @param absoluteHelpTopic the absolute help topic
     57     * @return the URL to the edit page
     58     */
     59    static public String getHelpTopicEditUrl(String absoluteHelpTopic) {
     60        String topicUrl = getHelpTopicUrl(absoluteHelpTopic);
     61        topicUrl = topicUrl.replaceAll("#[^#]*$", ""); // remove optional fragment
     62        return topicUrl + "?action=edit";
     63    }
     64
     65    /**
     66     * Extracts the relative help topic from an URL. Replies null, if
     67     * no relative help topic is found.
     68     *
     69     * @param url the url
     70     * @return the relative help topic in the URL, i.e. "/Action/New"
     71     */
     72    static public String extractRelativeHelpTopic(String url) {
     73        String topic = extractAbsoluteHelpTopic(url);
     74        if (topic == null) return null;
     75        String pattern = "/[A-Z][a-z]:" + getHelpTopicPrefix(Locale.ENGLISH).replaceAll("^\\/+", "");
     76        if (url.matches(pattern))
     77            return topic.substring(pattern.length());
     78        return null;
     79    }
     80
     81    /**
     82     * Extracts the absolute help topic from an URL. Replies null, if
     83     * no absolute help topic is found.
     84     *
     85     * @param url the url
     86     * @return the absolute help topic in the URL, i.e. "/De:Help/Action/New"
     87     */
     88    static public String extractAbsoluteHelpTopic(String url) {
     89        if (!url.startsWith(getWikiBaseHelpUrl())) return null;
     90        url = url.substring(getWikiBaseHelpUrl().length());
     91        String prefix = getHelpTopicPrefix(Locale.ENGLISH);
     92        if (url.startsWith(prefix))
     93            return url;
     94
     95        String pattern = "/[A-Z][a-z]:" + prefix.replaceAll("^\\/+", "");
     96        if (url.matches(pattern))
     97            return url;
     98
     99        return null;
     100    }
     101
     102    /**
     103     * Replies the help topic prefix for the current locale. Examples:
     104     * <ul>
     105     *   <li>/Help if the current locale is a locale with language "en"</li>
     106     *   <li>/De:Help if the current locale is a locale with language "de"</li>
     107     * </ul>
     108     *
     109     * @return the help topic prefix
     110     * @see #getHelpTopicPrefix(Locale)
     111     */
     112    static public String getHelpTopicPrefix() {
     113        return getHelpTopicPrefix(Locale.getDefault());
     114    }
     115
     116    /**
     117     * Replies the help topic prefix for the given locale. Examples:
     118     * <ul>
     119     *   <li>/Help if the  locale is a locale with language "en"</li>
     120     *   <li>/De:Help if the  locale is a locale with language "de"</li>
     121     * </ul>
     122     *
     123     * @param locale the locale. {@see Locale#ENGLISH} assumed, if null.
     124     * @return the help topic prefix
     125     * @see #getHelpTopicPrefix(Locale)
     126     */
     127    static public String getHelpTopicPrefix(Locale locale) {
     128        if (locale == null) {
     129            locale = Locale.ENGLISH;
     130        }
     131        String ret = Main.pref.get("help.pathhelp", "/Help");
     132        ret = ret.replaceAll("^\\/+", ""); // remove leading /
     133        ret = "/" + LanguageInfo.getWikiLanguagePrefix(locale) + ret;
     134        return ret;
     135    }
     136
     137    /**
     138     * Replies the absolute, localized help topic for the given topic.
     139     *
     140     * Example: for a topic "/Dialog/RelationEditor" and the locale "de", this method
     141     * replies "/De:Help/Dialog/RelationEditor"
     142     *
     143     * @param topic the relative help topic. Home help topic assumed, if null.
     144     * @param locale the locale. {@see Locale#ENGLISH} assumed, if null.
     145     * @return the absolute, localized help topic
     146     */
     147    static public String buildAbsoluteHelpTopic(String topic, Locale locale) {
     148        if (locale == null) {
     149            locale = Locale.ENGLISH;
     150        }
     151        if (topic == null || topic.trim().length() == 0 || topic.trim().equals("/"))
     152            return getHelpTopicPrefix(locale);
     153        String ret = getHelpTopicPrefix(locale);
     154        if (topic.startsWith("/")) {
     155            ret += topic;
     156        } else {
     157            ret += "/" + topic;
     158        }
     159        ret.replaceAll("\\/+", "\\/"); // just in case, collapse sequences of //
     160        return ret;
     161    }
     162
     163    /**
     164     * Replies the absolute, localized help topic for the given topic and the
     165     * current locale.
     166     *
     167     * @param topic the relative help topic. Home help topic assumed, if null.
     168     * @return the absolute, localized help topic
     169     * @see Locale#getDefault()
     170     * @see #buildAbsoluteHelpTopic(String, Locale)
     171     */
     172    static public String buildAbsoluteHelpTopic(String topic) {
     173        return buildAbsoluteHelpTopic(topic, Locale.getDefault());
     174    }
     175
     176    /**
     177     * Replies the context specific help topic configured for <code>context</code>.
     178     *
     179     * @return the help topic. null, if no context specific help topic is found
     180     */
     181    static public String getContextSpecificHelpTopic(Object context) {
     182        if (context == null)
     183            return null;
     184        if (context instanceof Helpful)
     185            return ((Helpful)context).helpTopic();
     186        if (context instanceof JMenu) {
     187            JMenu b = (JMenu)context;
     188            if (b.getClientProperty("help") != null)
     189                return (String)b.getClientProperty("help");
     190            return null;
     191        }
     192        if (context instanceof AbstractButton) {
     193            AbstractButton b = (AbstractButton)context;
     194            if (b.getClientProperty("help") != null)
     195                return (String)b.getClientProperty("help");
     196            return getContextSpecificHelpTopic(b.getAction());
     197        }
     198        if (context instanceof Action)
     199            return (String)((Action)context).getValue("help");
     200        if (context instanceof JComponent && ((JComponent)context).getClientProperty("help") != null)
     201            return (String)((JComponent)context).getClientProperty("help");
     202        if (context instanceof Component)
     203            return getContextSpecificHelpTopic(((Component)context).getParent());
     204        return null;
     205    }
     206
     207    /**
    14208     * Makes a component aware of context sensitive help.
    15209     *
    16      * @param component the component
    17      * @param topic the help topic
    18      */
    19     static public void setHelpContext(JComponent component, String topic) {
     210     * A relative help topic doesn't start with /Help and doesn't include a locale
     211     * code. Example: /Dialog/RelationEditor is a relative help topic, /De:Help/Dialog/RelationEditor
     212     * is not.
     213     *
     214     *
     215     * @param component the component  the component
     216     * @param topic the help topic. Set to the default help topic if null.
     217     */
     218    static public void setHelpContext(JComponent component, String relativeHelpTopic) {
     219        if (relativeHelpTopic == null) {
     220            relativeHelpTopic = "";
     221        }
    20222        component.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F1,0), "help");
    21223        component.getActionMap().put("help", Main.main.menu.help);
    22         component.putClientProperty("help", topic);
    23     }
    24 
     224        component.putClientProperty("help", relativeHelpTopic);
     225    }
     226
     227    /**
     228     * This is a simple marker method for help topic literals. If you declare a help
     229     * topic literal in the source you should enclose it in ht(...).
     230     *
     231     *  <strong>Example</strong>
     232     *  <pre>
     233     *     String helpTopic = ht("/Dialog/RelationEditor");
     234     *  or
     235     *     putValue("help", ht("/Dialog/RelationEditor"));
     236     *  </pre>
     237     *
     238     *
     239     * @param helpTopic
     240     */
     241    static public String ht(String helpTopic) {
     242        // this is just a marker method
     243        return helpTopic;
     244    }
    25245}
  • trunk/src/org/openstreetmap/josm/gui/help/help-browser.css

    r2274 r2308  
    1919tt {font-family: Courier New}
    2020pre {font-family: Courier New}
     21.warning-header {
     22        font-family: Arial, sans-serif;
     23        font-size:24pt;
     24        font-weight:bold
     25}
     26.warning-body {
     27        background-color:rgb(253,255,221);
     28        padding: 10pt;
     29        border-color:rgb(128,128,128);
     30        border-style: solid;
     31        border-width: 1px;
     32}
     33
     34.error-header {
     35        font-family: Arial, sans-serif;
     36        font-size:24pt;
     37        font-weight:bold
     38}
     39.error-body {
     40        background-color:rgb(254,195,190);
     41        padding: 10pt;
     42        border-color:rgb(128,128,128);
     43        border-style: solid;
     44        border-width: 1px;
     45}
  • trunk/src/org/openstreetmap/josm/gui/io/UploadDialog.java

    r2301 r2308  
    192192                JComponent.WHEN_IN_FOCUSED_WINDOW
    193193        );
    194 
    195         pnl.add(new SideButton(new ContextSensitiveHelpAction("/Help/Dialogs/UploadDialog")));
    196         HelpUtil.setHelpContext(getRootPane(),"/Help/Dialogs/UploadDialog");
     194        pnl.add(new SideButton(new ContextSensitiveHelpAction("/Dialogs/UploadDialog")));
     195        HelpUtil.setHelpContext(getRootPane(),"/Dialogs/UploadDialog");
    197196        return pnl;
    198197    }
  • trunk/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

    r2301 r2308  
    376376                        public void actionPerformed(ActionEvent e) {
    377377                            HelpBrowser b = new HelpBrowser();
    378                             b.setUrlForHelpTopic("Help/Concepts/Conflict");
     378                            b.openHelpTopic("Help/Concepts/Conflict");
    379379                            b.setVisible(true);
    380380                        }
     
    383383            dialog.setContentPane(pane);
    384384            dialog.pack();
    385             HelpUtil.setHelpContext(dialog.getRootPane(), "Concepts/Conflict");
     385            HelpUtil.setHelpContext(dialog.getRootPane(), "/Concepts/Conflict");
    386386            WindowGeometry.centerOnScreen(dialog.getSize()).applySafe(dialog);
    387387            dialog.setVisible(true);
  • trunk/src/org/openstreetmap/josm/tools/LanguageInfo.java

    r1755 r2308  
    33
    44import java.util.Locale;
     5import static org.openstreetmap.josm.tools.I18n.tr;
    56
    67public class LanguageInfo {
    7     static public String getLanguageCodeWiki()
    8     {
    9         String languageCode = getLanguageCode();
    10         if(languageCode.equals("en"))
     8
     9    /**
     10     * Replies the wiki language prefix for the given locale. The wiki language
     11     * prefix has the form 'Xy:' where 'Xy' is a ISO 639 language code in title
     12     * case.
     13     *
     14     * @param locale  the locale
     15     * @return the wiki language prefix
     16     */
     17    static public String getWikiLanguagePrefix(Locale locale) {
     18        String code = getJOSMLocaleCode(locale);
     19        if (code.length() == 2) {
     20            if (code.equals("en")) return "";
     21        } else if (code.matches("[^_]+_[^_]+")) {
     22            code = code.substring(0,2);
     23        } else {
     24            System.err.println(tr("Warning: failed to derive wiki language prefix from JOSM locale code ''{0}''. Using default code ''en''.", code));
    1125            return "";
    12         else if(languageCode.equals("pt_BR"))
    13             return "Pt:";
    14         return languageCode.substring(0,1).toUpperCase() + languageCode.substring(1) + ":";
     26        }
     27        return code.substring(0,1).toUpperCase() + code.substring(1) + ":";
    1528    }
    16     static public String getLanguageCode()
    17     {
    18         String full = Locale.getDefault().toString();
     29
     30    /**
     31     * Replies the wiki language prefix for the current locale.
     32     *
     33     * @return the wiki language prefix
     34     * @see Locale#getDefault()
     35     * @see #getWikiLanguagePrefix(Locale)
     36     */
     37    static public String getWikiLanguagePrefix() {
     38        return getWikiLanguagePrefix(Locale.getDefault());
     39    }
     40
     41    /**
     42     * Replies the JOSM locale code for the default locale.
     43     *
     44     * @return the JOSM locale code for the default locale
     45     * @see #getJOSMLocaleCode(Locale)
     46     */
     47    static public String getJOSMLocaleCode() {
     48        return getJOSMLocaleCode(Locale.getDefault());
     49    }
     50
     51    /**
     52     * Replies the local code used by JOSM for a given locale.
     53     *
     54     * In most cases JOSM uses the 2-character ISO 639 language code ({@see Locale#getLanguage()}
     55     * to identify the locale of a localized resource, but in some cases it may use the
     56     * programmatic name for locales, as replied by {@see Locale#toString()}.
     57     *
     58     * @param locale the locale. Replies "en" if null.
     59     * @return the JOSM code for the given locale
     60     */
     61    static public String getJOSMLocaleCode(Locale locale) {
     62        if (locale == null) return "en";
     63        String full = locale.toString();
    1964        if (full.equals("iw_IL"))
    2065            return "he";
     
    2267        else if (full.equals("en_GB"))
    2368            return full;
    24         return Locale.getDefault().getLanguage();
     69
     70        return locale.getLanguage();
    2571    }
     72
     73
    2674    static public String getLanguageCodeXML()
    2775    {
    28         return getLanguageCode()+".";
     76        return getJOSMLocaleCode()+".";
    2977    }
    3078    static public String getLanguageCodeManifest()
    3179    {
    32         return getLanguageCode()+"_";
     80        return getJOSMLocaleCode()+"_";
    3381    }
    3482}
  • trunk/src/org/openstreetmap/josm/tools/WikiReader.java

    r2274 r2308  
    3535     * pathes etc..
    3636     *
    37      * @return Either the string of the content of the wiki page.
     37     * @return
    3838     * @throws IOException Throws, if the page could not be loaded.
    3939     */
     
    4646
    4747    public String readLang(String text) {
    48         String languageCode = LanguageInfo.getLanguageCodeWiki();
     48        String languageCode = LanguageInfo.getWikiLanguagePrefix();
    4949        String url = baseurl + "/wiki/" + languageCode + text;
    5050        String res = "";
Note: See TracChangeset for help on using the changeset viewer.