Ticket #15057: dialog-update-v3.patch

File dialog-update-v3.patch, 26.4 KB (added by bafonins, 7 years ago)

Added translations, refactored the code a bit, added sorting for the items by their creation/update date.

  • src/org/openstreetmap/josm/actions/OverpassDownloadAction.java

     
    77import java.awt.BorderLayout;
    88import java.awt.Component;
    99import java.awt.Dimension;
     10import java.awt.GridBagLayout;
    1011import java.awt.event.ActionEvent;
    1112import java.awt.event.FocusEvent;
    1213import java.awt.event.FocusListener;
     
    2425import javax.swing.JOptionPane;
    2526import javax.swing.JPanel;
    2627import javax.swing.JScrollPane;
     28import javax.swing.event.ListSelectionEvent;
     29import javax.swing.event.ListSelectionListener;
    2730import javax.swing.plaf.basic.BasicArrowButton;
    2831
    2932import org.openstreetmap.josm.Main;
     
    3942import org.openstreetmap.josm.gui.widgets.JosmTextArea;
    4043import org.openstreetmap.josm.io.OverpassDownloadReader;
    4144import org.openstreetmap.josm.tools.GBC;
     45import org.openstreetmap.josm.tools.ImageProvider;
    4246import org.openstreetmap.josm.tools.Shortcut;
    4347
    4448/**
     
    157161        private static OverpassDownloadDialog instance;
    158162        private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED =
    159163                new BooleanProperty("download.overpass.query-list.opened", false);
     164        private static final String ACTION_IMG_SUBDIR = "dialogs";
    160165
    161166        private OverpassDownloadDialog(Component parent) {
    162167            super(parent, ht("/Action/OverpassDownload"));
     
    176181
    177182        @Override
    178183        protected void buildMainPanelAboveDownloadSelections(JPanel pnl) {
    179             // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
    180             pnl.add(new JLabel(), GBC.eol());
    181 
    182184            DisableActionsFocusListener disableActionsFocusListener =
    183185                    new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap());
    184186
     
    190192                }
    191193            };
    192194
    193             JButton openQueryWizard = new JButton("Query Wizard");
     195            JButton openQueryWizard = new JButton(tr("Query Wizard"));
    194196            openQueryWizard.setToolTipText(tooltip);
    195197            openQueryWizard.addActionListener(queryWizardAction);
    196198
     199            // use eol() that is needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes
     200            pnl.add(openQueryWizard, GBC.eol());
     201            pnl.add(new JLabel(tr("Overpass query:")), GBC.std().insets(5, 5, 0, 0).anchor(GBC.NORTHWEST));
     202
    197203            // CHECKSTYLE.OFF: LineLength
    198204            this.overpassQuery = new JosmTextArea(
    199205                    "/*\n" +
     
    215221                }
    216222            });
    217223
     224
    218225            this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery);
    219             overpassQueryList.setToolTipText(tr("Show/hide Overpass snippet list"));
    220             overpassQueryList.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
    221             overpassQueryList.setPreferredSize(new Dimension(350, 300));
     226            this.overpassQueryList.setPreferredSize(new Dimension(350, 300));
     227
     228            EditSnippetAction edit = new EditSnippetAction();
     229            RemoveSnippetAction remove = new RemoveSnippetAction();
     230            this.overpassQueryList.addSelectionListener(edit);
     231            this.overpassQueryList.addSelectionListener(remove);
     232
     233            JPanel listPanel = new JPanel(new GridBagLayout());
     234            listPanel.add(new JLabel(tr("Your saved queries:")), GBC.eol().insets(2).anchor(GBC.CENTER));
     235            listPanel.add(this.overpassQueryList, GBC.eol().fill(GBC.BOTH));
     236            listPanel.add(new JButton(new AddSnippetAction()), GBC.std().fill(GBC.HORIZONTAL));
     237            listPanel.add(new JButton(edit), GBC.std().fill(GBC.HORIZONTAL));
     238            listPanel.add(new JButton(remove), GBC.std().fill(GBC.HORIZONTAL));
     239            listPanel.setVisible(OVERPASS_QUERY_LIST_OPENED.get());
     240
    222241            JScrollPane scrollPane = new JScrollPane(overpassQuery);
    223             BasicArrowButton arrowButton = new BasicArrowButton(overpassQueryList.isVisible()
     242            BasicArrowButton arrowButton = new BasicArrowButton(listPanel.isVisible()
    224243                ? BasicArrowButton.EAST
    225244                : BasicArrowButton.WEST);
     245            arrowButton.setToolTipText(tr("Show/hide Overpass snippet list"));
    226246            arrowButton.addActionListener(e -> {
    227                 if (overpassQueryList.isVisible()) {
    228                     overpassQueryList.setVisible(false);
     247                if (listPanel.isVisible()) {
     248                    listPanel.setVisible(false);
    229249                    arrowButton.setDirection(BasicArrowButton.WEST);
    230250                    OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
    231251                } else {
    232                     overpassQueryList.setVisible(true);
     252                    listPanel.setVisible(true);
    233253                    arrowButton.setDirection(BasicArrowButton.EAST);
    234                     OVERPASS_QUERY_LIST_OPENED.put(Boolean.FALSE);
     254                    OVERPASS_QUERY_LIST_OPENED.put(Boolean.TRUE);
    235255                }
    236256            });
    237257
     
    241261
    242262            JPanel pane = new JPanel(new BorderLayout());
    243263            pane.add(innerPanel, BorderLayout.CENTER);
    244             pane.add(overpassQueryList, BorderLayout.EAST);
     264            pane.add(listPanel, BorderLayout.EAST);
    245265
    246266            GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200;
    247             pnl.add(openQueryWizard, GBC.std().insets(5, 5, 5, 5));
    248267            pnl.add(pane, gbc);
    249268        }
    250269
    251         String getOverpassQuery() {
     270        public String getOverpassQuery() {
    252271            return overpassQuery.getText();
    253272        }
    254273
     
    280299        public void triggerDownload() {
    281300            super.btnDownload.doClick();
    282301        }
     302
     303        /**
     304         * Action that delegates snippet creation to {@link OverpassQueryList#createNewItem()}.
     305         */
     306        class AddSnippetAction extends AbstractAction {
     307
     308            /**
     309             * Constructs a new {@code AddSnippetAction}.
     310             */
     311            AddSnippetAction() {
     312                super();
     313                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "add"));
     314                putValue(SHORT_DESCRIPTION, tr("Add new snippet"));
     315            }
     316
     317            @Override
     318            public void actionPerformed(ActionEvent e) {
     319                overpassQueryList.createNewItem();
     320            }
     321        }
     322
     323        /**
     324         * Action that delegates snippet removal to {@link OverpassQueryList#removeSelectedItem()}.
     325         */
     326        class RemoveSnippetAction extends AbstractAction implements ListSelectionListener {
     327
     328            /**
     329             * Constructs a new {@code RemoveSnippetAction}.
     330             */
     331            RemoveSnippetAction() {
     332                super();
     333                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "delete"));
     334                putValue(SHORT_DESCRIPTION, tr("Delete selected snippet"));
     335                checkEnabled();
     336            }
     337
     338            @Override
     339            public void actionPerformed(ActionEvent e) {
     340                overpassQueryList.removeSelectedItem();
     341            }
     342
     343            /**
     344             * Disables the action if no items are selected.
     345             */
     346            void checkEnabled() {
     347                setEnabled(overpassQueryList.getSelectedItem().isPresent());
     348            }
     349
     350            @Override
     351            public void valueChanged(ListSelectionEvent e) {
     352                checkEnabled();
     353            }
     354        }
     355
     356        /**
     357         * Action that delegates snippet edit to {@link OverpassQueryList#editSelectedItem()}.
     358         */
     359        class EditSnippetAction extends AbstractAction implements ListSelectionListener {
     360
     361            /**
     362             * Constructs a new {@code EditSnippetAction}.
     363             */
     364            EditSnippetAction() {
     365                super();
     366                putValue(SMALL_ICON, ImageProvider.get(ACTION_IMG_SUBDIR, "edit"));
     367                putValue(SHORT_DESCRIPTION, tr("Edit selected snippet"));
     368                checkEnabled();
     369            }
     370
     371            @Override
     372            public void actionPerformed(ActionEvent e) {
     373                overpassQueryList.editSelectedItem();
     374            }
     375
     376            /**
     377             * Disables the action if no items are selected.
     378             */
     379            void checkEnabled() {
     380                setEnabled(overpassQueryList.getSelectedItem().isPresent());
     381            }
     382
     383            @Override
     384            public void valueChanged(ListSelectionEvent e) {
     385                checkEnabled();
     386            }
     387        }
    283388    }
    284389}
  • src/org/openstreetmap/josm/actions/downloadtasks/PostDownloadHandler.java

     
    1919import org.openstreetmap.josm.Main;
    2020import org.openstreetmap.josm.gui.ExceptionDialogUtil;
    2121import org.openstreetmap.josm.gui.Notification;
     22import org.openstreetmap.josm.gui.util.GuiHelper;
    2223import org.openstreetmap.josm.tools.ExceptionUtil;
    2324import org.openstreetmap.josm.tools.Utils;
    2425
     
    6768        // make sure errors are reported only once
    6869        //
    6970        Set<Object> errors = new LinkedHashSet<>(task.getErrorObjects());
     71
    7072        if (this.errorReporter != null) {
    71             errorReporter.accept(errors);
     73            GuiHelper.runInEDT(() -> errorReporter.accept(errors));
    7274        }
    7375
    7476        if (errors.isEmpty()) {
  • src/org/openstreetmap/josm/gui/download/OverpassQueryList.java

     
    1515import java.awt.event.MouseEvent;
    1616import java.time.LocalDateTime;
    1717import java.time.format.DateTimeFormatter;
     18import java.time.format.DateTimeParseException;
    1819import java.util.ArrayList;
    1920import java.util.Collection;
    2021import java.util.Collections;
    2122import java.util.HashMap;
     23import java.util.List;
    2224import java.util.Locale;
    2325import java.util.Map;
    2426import java.util.Objects;
     
    7375     */
    7476    private static final String KEY_KEY = "key";
    7577    private static final String QUERY_KEY = "query";
    76     private static final String USE_COUNT_KEY = "useCount";
     78    private static final String LAST_EDIT_KEY = "lastEdit";
    7779    private static final String PREFERENCE_ITEMS = "download.overpass.query";
    7880
     81    private static final String TRANSLATED_HISTORY = tr("history");
     82
    7983    /**
    8084     * Constructs a new {@code OverpassQueryList}.
    8185     * @param parent The parent of this component.
     
    102106     */
    103107    public synchronized Optional<SelectorItem> getSelectedItem() {
    104108        int idx = lsResult.getSelectedIndex();
    105         if (lsResultModel.getSize() == 0 || idx == -1) {
     109        if (lsResultModel.getSize() <= idx || idx == -1) {
    106110            return Optional.empty();
    107111        }
    108112
    109113        SelectorItem item = lsResultModel.getElementAt(idx);
    110         item.increaseUsageCount();
    111114
    112         this.items.values().stream()
    113                 .filter(it -> !it.getKey().equals(item.getKey()))
    114                 .forEach(SelectorItem::decreaseUsageCount);
    115 
    116115        filterItems();
    117116
    118117        return Optional.of(item);
     
    127126     */
    128127    public synchronized void saveHistoricItem(String query) {
    129128        boolean historicExist = this.items.values().stream()
    130                 .filter(it -> it.getKey().contains("history"))
    131129                .map(SelectorItem::getQuery)
    132130                .anyMatch(q -> q.equals(query));
    133131
    134132        if (!historicExist) {
    135133            SelectorItem item = new SelectorItem(
    136                     "history " + LocalDateTime.now().format(FORMAT),
    137                     query);
     134                    TRANSLATED_HISTORY + " " + LocalDateTime.now().format(FORMAT), query);
    138135
    139136            this.items.put(item.getKey(), item);
    140137
     
    147144     * Removes currently selected item, saves the current state to preferences and
    148145     * updates the view.
    149146     */
    150     private synchronized void removeSelectedItem() {
     147    public synchronized void removeSelectedItem() {
    151148        Optional<SelectorItem> it = this.getSelectedItem();
    152149
    153150        if (!it.isPresent()) {
     
    159156
    160157        SelectorItem item = it.get();
    161158        if (this.items.remove(item.getKey(), item)) {
     159            clearSelection();
    162160            savePreferences();
    163161            filterItems();
    164162        }
     
    168166     * Opens {@link EditItemDialog} for the selected item, saves the current state
    169167     * to preferences and updates the view.
    170168     */
    171     private synchronized void editSelectedItem() {
     169    public synchronized void editSelectedItem() {
    172170        Optional<SelectorItem> it = this.getSelectedItem();
    173171
    174172        if (!it.isPresent()) {
     
    184182                componentParent,
    185183                tr("Edit item"),
    186184                item,
    187                 tr("Save"));
     185                tr("Save"), tr("Cancel"));
    188186        dialog.showDialog();
    189187
    190188        Optional<SelectorItem> editedItem = dialog.getOutputItem();
     
    201199     * Opens {@link EditItemDialog}, saves the state to preferences if a new item is added
    202200     * and updates the view.
    203201     */
    204     private synchronized void createNewItem() {
     202    public synchronized void createNewItem() {
    205203        EditItemDialog dialog = new EditItemDialog(componentParent, tr("Add snippet"), tr("Add"));
    206204        dialog.showDialog();
    207205
    208206        Optional<SelectorItem> newItem = dialog.getOutputItem();
    209207        newItem.ifPresent(i -> {
    210             items.put(i.getKey(), new SelectorItem(i.getKey(), i.getQuery()));
     208            items.put(i.getKey(), i);
    211209            savePreferences();
    212210            filterItems();
    213211        });
     
    221219    @Override
    222220    protected void filterItems() {
    223221        String text = edSearchText.getText().toLowerCase(Locale.ENGLISH);
    224 
    225         super.lsResultModel.setItems(this.items.values().stream()
     222        List<SelectorItem> matchingItems = this.items.values().stream()
     223                .sorted((i1, i2) -> i2.getLastEdit().compareTo(i1.getLastEdit()))
    226224                .filter(item -> item.getKey().contains(text))
    227                 .collect(Collectors.toList()));
     225                .collect(Collectors.toList());
     226       
     227        super.lsResultModel.setItems(matchingItems);
    228228    }
    229229
    230230    private void doubleClickEvent() {
     
    247247            Map<String, String> it = new HashMap<>();
    248248            it.put(KEY_KEY, item.getKey());
    249249            it.put(QUERY_KEY, item.getQuery());
    250             it.put(USE_COUNT_KEY, Integer.toString(item.getUsageCount()));
     250            it.put(LAST_EDIT_KEY, item.getLastEdit().format(FORMAT));
    251251
    252252            toSave.add(it);
    253253        }
     
    265265        Map<String, SelectorItem> result = new HashMap<>();
    266266
    267267        for (Map<String, String> entry : toRetrieve) {
    268             String key = entry.get(KEY_KEY);
    269             String query = entry.get(QUERY_KEY);
    270             int usageCount = Integer.parseInt(entry.get(USE_COUNT_KEY));
     268            try {
     269                String key = entry.get(KEY_KEY);
     270                String query = entry.get(QUERY_KEY);
     271                LocalDateTime lastEdit = LocalDateTime.parse(entry.get(LAST_EDIT_KEY), FORMAT);
    271272
    272             result.put(key, new SelectorItem(key, query, usageCount));
     273                result.put(key, new SelectorItem(key, query, lastEdit));
     274            } catch (IllegalArgumentException | DateTimeParseException e) {
     275                // skip any corrupted item
     276                Main.error(e);
     277            }
    273278        }
    274279
    275280        return result;
     
    411416
    412417        private final JTextField name;
    413418        private final JosmTextArea query;
    414         private final int initialNameHash;
    415419
    416420        private final transient AbstractTextComponentValidator queryValidator;
    417421        private final transient AbstractTextComponentValidator nameValidator;
     
    419423        private static final int SUCCESS_BTN = 0;
    420424        private static final int CANCEL_BTN = 1;
    421425
     426        private final transient SelectorItem itemToEdit;
     427
    422428        /**
    423429         * Added/Edited object to be returned. If {@link Optional#empty()} then probably
    424430         * the user closed the dialog, otherwise {@link SelectorItem} is present.
     
    436442                String... buttonTexts) {
    437443            super(parent, title, buttonTexts);
    438444
    439             String nameToEdit = itemToEdit != null ? itemToEdit.getKey() : "";
    440             String queryToEdit = itemToEdit != null ? itemToEdit.getQuery() : "";
    441             this.initialNameHash = nameToEdit.hashCode();
     445            this.itemToEdit = itemToEdit;
    442446
     447            String nameToEdit = itemToEdit == null ? "" : itemToEdit.getKey();
     448            String queryToEdit = itemToEdit == null ? "" : itemToEdit.getQuery();
     449
    443450            this.name = new JTextField(nameToEdit);
    444451            this.query = new JosmTextArea(queryToEdit);
    445452
     
    457464                @Override
    458465                public boolean isValid() {
    459466                    String currentName = name.getText();
    460                     int currentHash = currentName.hashCode();
    461467
    462                     return !Utils.isStripEmpty(currentName) &&
    463                             !(currentHash != initialNameHash &&
    464                                     items.containsKey(currentName));
     468                    boolean notEmpty = !Utils.isStripEmpty(currentName);
     469                    boolean exist = !currentName.equals(nameToEdit) &&
     470                                        items.containsKey(currentName);
     471
     472                    return notEmpty && !exist;
    465473                }
    466474            };
    467475
     
    477485            panel.add(this.name, GBC.eol().insets(5).anchor(GBC.SOUTHEAST).fill(GBC.HORIZONTAL));
    478486            panel.add(queryScrollPane, constraint);
    479487
    480             setDefaultButton(SUCCESS_BTN);
    481             setCancelButton(CANCEL_BTN);
     488            setDefaultButton(SUCCESS_BTN + 1);
     489            setCancelButton(CANCEL_BTN + 1);
    482490            setPreferredSize(new Dimension(400, 400));
    483491            setContent(panel, false);
    484492        }
     
    500508                            tr("The item cannot be created with provided name"),
    501509                            tr("Warning"),
    502510                            JOptionPane.WARNING_MESSAGE);
     511
     512                    return;
    503513                } else if (!this.queryValidator.isValid()) {
    504514                    JOptionPane.showMessageDialog(
    505515                            componentParent,
     
    506516                            tr("The item cannot be created with an empty query"),
    507517                            tr("Warning"),
    508518                            JOptionPane.WARNING_MESSAGE);
    509                 } else {
    510                     this.outputItem = Optional.of(new SelectorItem(this.name.getText(), this.query.getText()));
    511                     super.buttonAction(buttonIndex, evt);
     519
     520                    return;
     521                } else if (this.itemToEdit != null) { // editing the item
     522                    String newKey = this.name.getText();
     523                    String newQuery = this.query.getText();
     524
     525                    String itemKey = this.itemToEdit.getKey();
     526                    String itemQuery = this.itemToEdit.getQuery();
     527
     528                    this.outputItem = Optional.of(new SelectorItem(
     529                            this.name.getText(),
     530                            this.query.getText(),
     531                            !newKey.equals(itemKey) || !newQuery.equals(itemQuery)
     532                                ? LocalDateTime.now()
     533                                : this.itemToEdit.getLastEdit()));
     534
     535                } else { // creating new
     536                    this.outputItem = Optional.of(new SelectorItem(
     537                            this.name.getText(),
     538                            this.query.getText()));
    512539                }
    513             } else {
    514                 super.buttonAction(buttonIndex, evt);
    515540            }
     541
     542            super.buttonAction(buttonIndex, evt);
    516543        }
    517544    }
    518545
     
    523550    public static class SelectorItem {
    524551        private final String itemKey;
    525552        private final String query;
    526         private int usageCount;
     553        private final LocalDateTime lastEdit;
    527554
    528555        /**
    529556         * Constructs a new {@code SelectorItem}.
     
    533560         * @exception IllegalArgumentException if any parameter is empty.
    534561         */
    535562        public SelectorItem(String key, String query) {
    536             this(key, query, 1);
     563            this(key, query, LocalDateTime.now());
    537564        }
    538565
    539566        /**
     
    540567         * Constructs a new {@code SelectorItem}.
    541568         * @param key The key of this item.
    542569         * @param query The query of the item.
    543          * @param usageCount The number of times this query was used.
     570         * @param lastEdit The latest when the item was
    544571         * @exception NullPointerException if any parameter is {@code null}.
    545572         * @exception IllegalArgumentException if any parameter is empty.
    546573         */
    547         public SelectorItem(String key, String query, int usageCount) {
    548             Objects.requireNonNull(key);
    549             Objects.requireNonNull(query);
     574        public SelectorItem(String key, String query, LocalDateTime lastEdit) {
     575            Objects.requireNonNull(key, "The name of the item cannot be null");
     576            Objects.requireNonNull(query, "The query of the item cannot be null");
     577            Objects.requireNonNull(lastEdit, "The last edit date time cannot be null");
    550578
    551579            if (Utils.isStripEmpty(key)) {
    552580                throw new IllegalArgumentException("The key of the item cannot be empty");
     
    557585
    558586            this.itemKey = key;
    559587            this.query = query;
    560             this.usageCount = usageCount;
     588            this.lastEdit = lastEdit;
    561589        }
    562590
    563591        /**
     
    577605        }
    578606
    579607        /**
    580          * Gets the number of times the query was used by the user.
    581          * @return The usage count of this item.
     608         * Gets the latest date time when the item was created/changed.
     609         * @return The latest date time when the item was created/changed.
    582610         */
    583         public int getUsageCount() {
    584             return this.usageCount;
     611        public LocalDateTime getLastEdit() {
     612            return lastEdit;
    585613        }
    586614
    587         /**
    588          * Increments the {@link SelectorItem#usageCount} by one till
    589          * it reaches {@link Integer#MAX_VALUE}.
    590          */
    591         public void increaseUsageCount() {
    592             if (this.usageCount < Integer.MAX_VALUE) {
    593                 this.usageCount++;
    594             }
    595         }
    596 
    597         /**
    598          * Decrements the {@link SelectorItem#usageCount} ny one till
    599          * it reaches 0.
    600          */
    601         public void decreaseUsageCount() {
    602             if (this.usageCount > 0) {
    603                 this.usageCount--;
    604             }
    605         }
    606 
    607615        @Override
    608616        public int hashCode() {
    609617            final int prime = 31;
  • src/org/openstreetmap/josm/gui/download/OverpassQueryWizardDialog.java

     
    3636 */
    3737public final class OverpassQueryWizardDialog extends ExtendedDialog {
    3838
     39    private final HistoryComboBox queryWizard;
    3940    private static final String HEADLINE_START = "<h3>";
    4041    private static final String HEADLINE_END = "</h3>";
    4142    private static final String TR_START = "<tr>";
     
    4243    private static final String TR_END = "</tr>";
    4344    private static final String TD_START = "<td>";
    4445    private static final String TD_END = "</td>";
    45     private final HistoryComboBox queryWizard;
    46     private final OverpassTurboQueryWizard overpassQueryBuilder;
     46    private static final String SPAN_START = "<span>";
     47    private static final String SPAN_END = "</span>";
    4748    private static final CollectionProperty OVERPASS_WIZARD_HISTORY =
    4849            new CollectionProperty("download.overpass.wizard", new ArrayList<String>());
     50    private final transient OverpassTurboQueryWizard overpassQueryBuilder;
    4951
    5052    // dialog buttons
    5153    private static final int BUILD_QUERY = 0;
     
    199201                .append("<table>").append(TR_START).append(TD_START)
    200202                .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>type:node</i>", "<i>type:relation</i>", "<i>type:way</i>")))
    201203                .append(TD_END).append(TD_START)
    202                 .append("<span>").append(tr("Download objects of a certain type.")).append("</span>")
     204                .append(SPAN_START).append(tr("Download objects of a certain type.")).append(SPAN_END)
    203205                .append(TD_END).append(TR_END)
    204206                .append(TR_START).append(TD_START)
    205207                .append(Utils.joinAsHtmlUnorderedList(
     
    214216                                "is set to 1000m, but it can be changed in the generated query.", "<i>tourism=hotel around Berlin</i> -"),
    215217                        tr("{0} all objects within the current selection that have {1} as attribute.", "<i>tourism=hotel in bbox</i> -",
    216218                                "'tourism=hotel'"))))
    217                 .append("<span>")
     219                .append(SPAN_START)
    218220                .append(tr("Instead of <i>location</i> any valid place name can be used like address, city, etc."))
    219                 .append("</span>")
     221                .append(SPAN_END)
    220222                .append(TD_END).append(TR_END)
    221223                .append(TR_START).append(TD_START)
    222224                .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>key=value</i>", "<i>key=*</i>", "<i>key~regex</i>",
     
    230232                        tr("<i>expression1 {0} expression2</i>", "or"),
    231233                        tr("<i>expression1 {0} expression2</i>", "and"))))
    232234                .append(TD_END).append(TD_START)
    233                 .append("<span>")
     235                .append(SPAN_START)
    234236                .append(tr("Basic logical operators can be used to create more sophisticated queries. Instead of \"or\" - \"|\", \"||\" " +
    235237                        "can be used, and instead of \"and\" - \"&\", \"&&\"."))
    236                 .append("</span>")
     238                .append(SPAN_END)
    237239                .append(TD_END).append(TR_END).append("</table>")
    238240                .append("</body>")
    239241                .append("</html>")