Ticket #20573: 20573-0.2.diff

File 20573-0.2.diff, 83.0 KB (added by Bjoeni, 3 years ago)
  • src/org/openstreetmap/josm/data/preferences/sources/ExtendedSourceEntry.java

     
    4949    }
    5050
    5151    private static void appendRow(StringBuilder s, String th, String td) {
    52         s.append("<tr><th>").append(th).append("</th><td>").append(Utils.escapeReservedCharactersHTML(td)).append("</td</tr>");
     52        s.append("<tr><th>").append(th).append("</th><td>").append(Utils.escapeReservedCharactersHTML(td)).append("</td></tr>");
    5353    }
    5454
    5555    /**
  • src/org/openstreetmap/josm/gui/preferences/DefaultTabPreferenceSetting.java

     
    4848        this.tabpane = tabpane;
    4949        this.subSettingMap = tabpane != null ? new HashMap<>() : null;
    5050        if (tabpane != null) {
    51             tabpane.addMouseWheelListener(new PreferenceTabbedPane.WheelListener(tabpane));
     51            tabpane.addMouseWheelListener(new PreferenceTabbedPane.MouseAndWheelListener(tabpane));
    5252        }
    5353    }
    5454
  • src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java

     
    1010import java.awt.FontMetrics;
    1111import java.awt.GridBagConstraints;
    1212import java.awt.GridBagLayout;
     13import java.awt.event.MouseEvent;
     14import java.awt.event.MouseListener;
    1315import java.awt.event.MouseWheelEvent;
    1416import java.awt.event.MouseWheelListener;
    1517import java.util.ArrayList;
     
    6062import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;
    6163import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
    6264import org.openstreetmap.josm.gui.preferences.remotecontrol.RemoteControlPreference;
     65import org.openstreetmap.josm.gui.preferences.search.SearchPanel;
     66import org.openstreetmap.josm.gui.preferences.search.SearchTextField;
    6367import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
    6468import org.openstreetmap.josm.gui.preferences.server.ServerAccessPreference;
    6569import org.openstreetmap.josm.gui.preferences.shortcut.ShortcutPreference;
     
    204208        boolean validatePreferences();
    205209    }
    206210
    207     private interface PreferenceTab {
     211    public interface PreferenceTab {
    208212        TabPreferenceSetting getTabPreferenceSetting();
    209213
    210214        Component getComponent();
     
    273277        }
    274278    }
    275279
     280    private final SearchPanel searchPanel = new SearchPanel(this);
     281    private final SearchTextField searchTextField = new SearchTextField(searchPanel);
     282
    276283    // all created tabs
    277284    private final transient List<PreferenceTab> tabs = new ArrayList<>();
    278285    private static final Collection<PreferenceSettingFactory> SETTINGS_FACTORIES = new LinkedList<>();
     
    463470     */
    464471    public PreferenceTabbedPane() {
    465472        super(SwingConstants.LEFT, JTabbedPane.SCROLL_TAB_LAYOUT);
    466         super.addMouseWheelListener(new WheelListener(this));
     473        MouseAndWheelListener l = new MouseAndWheelListener(this);
     474        super.addMouseWheelListener(l);
     475        super.addMouseListener(l);
    467476        ExpertToggleAction.addExpertModeChangeListener(this);
    468477    }
    469478
     
    526535        if (clear) {
    527536            removeAll();
    528537        }
     538
     539        addTab(null, searchPanel);
     540        setTabComponentAt(0, searchTextField);
    529541        // Compute max tab length in pixels
    530542        int maxWidth = computeMaxTabWidth();
    531543        // Inspect each tab setting
     
    557569                Logging.debug("{0}: hiding empty {1}", getClass().getSimpleName(), tps);
    558570            });
    559571        }
     572        searchTextField.adjustWidth();
    560573        setSelectedIndex(-1);
    561574    }
    562575
     
    626639     * This mouse wheel listener reacts when a scroll is carried out over the
    627640     * tab strip and scrolls one tab/down or up, selecting it immediately.
    628641     */
    629     static final class WheelListener implements MouseWheelListener {
     642    static final class MouseAndWheelListener implements MouseWheelListener, MouseListener {
    630643
    631644        final JTabbedPane tabbedPane;
    632645
    633         WheelListener(JTabbedPane tabbedPane) {
     646        MouseAndWheelListener(JTabbedPane tabbedPane) {
    634647            this.tabbedPane = tabbedPane;
    635648        }
    636649
     
    646659
    647660            tabbedPane.setSelectedIndex(newTab);
    648661        }
     662
     663        @Override
     664        public void mouseClicked(MouseEvent e) {
     665            tabbedPane.requestFocus();
     666        }
     667
     668        @Override
     669        public void mouseEntered(MouseEvent e) {
     670        }
     671
     672        @Override
     673        public void mouseExited(MouseEvent e) {
     674        }
     675
     676        @Override
     677        public void mousePressed(MouseEvent e) {
     678        }
     679
     680        @Override
     681        public void mouseReleased(MouseEvent e) {
     682        }
    649683    }
    650684
    651685    @Override
     
    653687        int index = getSelectedIndex();
    654688        Component sel = getSelectedComponent();
    655689        if (index > -1 && sel instanceof PreferenceTab) {
    656             PreferenceTab tab = (PreferenceTab) sel;
    657             TabPreferenceSetting preferenceSettings = tab.getTabPreferenceSetting();
    658             if (!settingsInitialized.contains(preferenceSettings)) {
    659                 try {
    660                     getModel().removeChangeListener(this);
    661                     preferenceSettings.addGui(this);
    662                     // Add GUI for sub preferences
    663                     for (PreferenceSetting setting : settings) {
    664                         if (setting instanceof SubPreferenceSetting) {
    665                             addSubPreferenceSetting(preferenceSettings, (SubPreferenceSetting) setting);
    666                         }
     690            initializeTab(index, (PreferenceTab) sel, false);
     691        }
     692    }
     693
     694    public void initializeTab(int index, PreferenceTab tab, boolean silent) {
     695        TabPreferenceSetting preferenceSettings = tab.getTabPreferenceSetting();
     696        if (!settingsInitialized.contains(preferenceSettings)) {
     697            try {
     698                getModel().removeChangeListener(this);
     699                preferenceSettings.addGui(this);
     700                // Add GUI for sub preferences
     701                for (PreferenceSetting setting : settings) {
     702                    if (setting instanceof SubPreferenceSetting) {
     703                        addSubPreferenceSetting(preferenceSettings, (SubPreferenceSetting) setting);
    667704                    }
    668                     Icon icon = getIconAt(index);
    669                     remove(index);
    670                     if (index <= insertGUITabsForSetting(icon, preferenceSettings, index, computeMaxTabWidth())) {
    671                         setSelectedIndex(index);
    672                     }
    673                 } catch (SecurityException ex) {
    674                     Logging.error(ex);
    675                 } catch (RuntimeException ex) { // NOPMD
    676                     // allow to change most settings even if e.g. a plugin fails
    677                     BugReportExceptionHandler.handleException(ex);
    678                 } finally {
    679                     settingsInitialized.add(preferenceSettings);
    680                     getModel().addChangeListener(this);
    681705                }
     706                Icon icon = getIconAt(index);
     707                remove(index);
     708                if (index <= insertGUITabsForSetting(icon, preferenceSettings, index, computeMaxTabWidth()) && !silent) {
     709                    setSelectedIndex(index);
     710                }
     711            } catch (SecurityException ex) {
     712                Logging.error(ex);
     713            } catch (RuntimeException ex) { // NOPMD
     714                // allow to change most settings even if e.g. a plugin fails
     715                BugReportExceptionHandler.handleException(ex);
     716            } finally {
     717                settingsInitialized.add(preferenceSettings);
     718                getModel().addChangeListener(this);
    682719            }
    683             Container ancestor = getTopLevelAncestor();
    684             if (ancestor instanceof PreferenceDialog) {
    685                 ((PreferenceDialog) ancestor).setHelpContext(preferenceSettings.getHelpContext());
    686             }
    687720        }
     721        Container ancestor = getTopLevelAncestor();
     722        if (ancestor instanceof PreferenceDialog) {
     723            ((PreferenceDialog) ancestor).setHelpContext(preferenceSettings.getHelpContext());
     724        }
    688725    }
    689726
    690727    private void addSubPreferenceSetting(TabPreferenceSetting preferenceSettings, SubPreferenceSetting sps) {
  • src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java

     
    5252import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    5353import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    5454import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     55import org.openstreetmap.josm.gui.preferences.search.NotSearchablePanel;
    5556import org.openstreetmap.josm.gui.util.GuiHelper;
    5657import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
    5758import org.openstreetmap.josm.gui.widgets.JosmTextField;
     
    164165
    165166    @Override
    166167    public void addGui(final PreferenceTabbedPane gui) {
    167         JPanel p = gui.createPreferenceTab(this);
     168        JPanel panel = gui.createPreferenceTab(this);
    168169
     170        NotSearchablePanel p = new NotSearchablePanel(new GridBagLayout());
     171        panel.add(p, GBC.std().fill());
     172
    169173        final JPanel txtFilterPanel = new JPanel(new GridBagLayout());
    170174        p.add(txtFilterPanel, GBC.eol().fill(GBC.HORIZONTAL));
    171175        txtFilter = new JosmTextField();
  • src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java

     
    5454import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    5555import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    5656import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     57import org.openstreetmap.josm.gui.preferences.search.NotSearchablePanel;
    5758import org.openstreetmap.josm.gui.util.GuiHelper;
    5859import org.openstreetmap.josm.gui.widgets.FilterField;
    5960import org.openstreetmap.josm.plugins.PluginDownloadTask;
     
    165166    }
    166167
    167168    private JPanel buildSearchFieldPanel() {
    168         JPanel pnl = new JPanel(new GridBagLayout());
     169        JPanel pnl = new NotSearchablePanel(new GridBagLayout());
    169170        pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    170171        GridBagConstraints gc = new GridBagConstraints();
    171172
  • src/org/openstreetmap/josm/gui/preferences/search/ISearchableComponent.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.util.List;
     5
     6/**
     7 * Interface allowing components in the preferences to determine if and how they can be searched
     8 * @author Bjoeni
     9 */
     10public interface ISearchableComponent {
     11    /**
     12     * @return whether the component can be searched<br><br>
     13     * default: true
     14     */
     15    default boolean isSearchable() {
     16        return true;
     17    }
     18
     19    /**
     20     * @return whether the children of this component should be traversed <br><br>
     21     *
     22     * default: same as {@link #isSearchable()} unless explicitly overridden
     23     */
     24    default boolean isChildrenSearchable() {
     25        return isSearchable();
     26    }
     27
     28    /**
     29     * @return the {@link SearchItem}s that should be used. Ignored if {@link #isSearchable()} returns {@code false}.<br>
     30     * Overrides the default {@link SearchTextFinder} if returning not {@code null} (including if an empty list is returned)<br><br>
     31     * default: null
     32     */
     33    default List<SearchItem> getSearchItems() {
     34        return null;
     35    }
     36}
  • src/org/openstreetmap/josm/gui/preferences/search/NotSearchablePanel.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.LayoutManager;
     5
     6import javax.swing.JPanel;
     7
     8/**
     9 * {@link JPanel} that disallows searching its contents
     10 * @see ISearchableComponent
     11 * @author Bjoeni
     12 */
     13public class NotSearchablePanel extends JPanel implements ISearchableComponent {
     14
     15    /**
     16     * Create {@link JPanel} that disallows searching its contents
     17     */
     18    public NotSearchablePanel() {
     19        super();
     20    }
     21
     22    /**
     23     * Create {@link JPanel} that disallows searching its contents
     24     * @param layout
     25     */
     26    public NotSearchablePanel(LayoutManager layout) {
     27        super(layout);
     28    }
     29
     30    /**
     31     * Create {@link JPanel} that disallows searching its contents
     32     * @param isDoubleBuffered
     33     */
     34    public NotSearchablePanel(boolean isDoubleBuffered) {
     35        super(isDoubleBuffered);
     36    }
     37
     38    /**
     39     * Create {@link JPanel} that disallows searching its contents
     40     * @param layout
     41     * @param isDoubleBuffered
     42     */
     43    public NotSearchablePanel(LayoutManager layout, boolean isDoubleBuffered) {
     44        super(layout, isDoubleBuffered);
     45    }
     46
     47    /**
     48     * The component can not be searched
     49     * @return false
     50     */
     51    @Override
     52    public boolean isSearchable() {
     53        return false;
     54    }
     55
     56}
  • src/org/openstreetmap/josm/gui/preferences/search/SearchIndex.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.Component;
     5import java.awt.Container;
     6import java.awt.GridBagConstraints;
     7import java.awt.GridBagLayout;
     8import java.awt.LayoutManager;
     9import java.util.ArrayList;
     10import java.util.Arrays;
     11import java.util.List;
     12import java.util.Stack;
     13
     14import javax.swing.JComponent;
     15import javax.swing.JSeparator;
     16
     17import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     18import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferenceTab;
     19
     20/**
     21 * Contains all {@link SearchItem}s
     22 * @author Bjoeni
     23 */
     24public class SearchIndex {
     25    Stack<SearchItem> stack = new Stack<>();
     26    List<SearchItem> parents = new ArrayList<>();
     27    PreferenceTabbedPane prefTabs;
     28    GridBagConstraints lastConstraint;
     29
     30    public SearchIndex(PreferenceTabbedPane prefTabs) {
     31        this.prefTabs = prefTabs;
     32    }
     33
     34    void add(SearchItem item) {
     35        if (stack.isEmpty()) {
     36            item.setLevelInset(0, 0);
     37            parents.add(item);
     38            stack.push(item);
     39        } else {
     40            SearchItem lastItem = stack.lastElement();
     41            if (!lastItem.eol && lastItem.level == item.level) {
     42                lastItem.setEOL(item.eol);
     43                lastItem.addChild(item);
     44            } else if (lastItem.level < item.level || (lastItem.level == item.level && lastItem.inset < item.inset)) {
     45                stack.push(lastItem.addChild(item));
     46            } else {
     47                stack.pop();
     48                add(item);
     49            }
     50        }
     51    }
     52
     53    void addAll(List<SearchItem> items, boolean isEOL) {
     54        if (items.size() > 0) {
     55            items.get(items.size() - 1).setEOL(isEOL);
     56        }
     57        items.forEach(item -> add(item));
     58    }
     59
     60    void insertEOL() {
     61        if (!stack.isEmpty()) {
     62            stack.lastElement().setEOL();
     63        }
     64    }
     65
     66    void insertSeparator() {
     67        if (!stack.isEmpty()) {
     68            stack.lastElement().maximizeInset();
     69        }
     70    }
     71
     72    void insertNewPage() {
     73        stack.clear();
     74    }
     75
     76    public boolean isBuilt() {
     77        return !parents.isEmpty();
     78    }
     79
     80    public void build() {
     81        parents.clear();
     82        for (int i = 0; i < prefTabs.getTabCount(); i++) {
     83            Component c = prefTabs.getComponentAt(i);
     84            if (c instanceof PreferenceTab) {
     85                prefTabs.initializeTab(i, (PreferenceTab) c, true);
     86                c = prefTabs.getComponentAt(i);
     87            }
     88            insertNewPage();
     89            searchComponent(c, 1);
     90        }
     91    }
     92
     93    private String lastFilterStr;
     94
     95    public void filter(String filterStr) {
     96        if (lastFilterStr != null && filterStr.indexOf(lastFilterStr) != 0) {
     97            parents.forEach(SearchItem::showAll);
     98        }
     99
     100        if (!filterStr.isEmpty()) {
     101            parents.stream().filter(SearchItem::isVisible).forEach(item -> {
     102                item.filter(filterStr);
     103            });
     104        }
     105
     106        lastFilterStr = filterStr;
     107    }
     108
     109    private boolean searchComponent(Component comp, int level) {
     110
     111        final Component c = comp;
     112
     113        boolean isEOL = true;
     114        GridBagConstraints currentConstraint = null;
     115
     116        Container p = c.getParent();
     117        if (p != null) {
     118            LayoutManager layout = p.getLayout();
     119            if (layout != null && layout instanceof GridBagLayout) {
     120                GridBagLayout grid = (GridBagLayout) layout;
     121
     122                currentConstraint = grid.getConstraints(c);
     123                isEOL = currentConstraint.gridwidth == GridBagConstraints.REMAINDER;
     124            }
     125        }
     126
     127        if (lastConstraint != null && currentConstraint != null
     128                && (((lastConstraint.fill == GridBagConstraints.HORIZONTAL
     129                        || lastConstraint.fill == GridBagConstraints.BOTH)
     130                        && currentConstraint.fill != GridBagConstraints.HORIZONTAL
     131                        && currentConstraint.fill != GridBagConstraints.BOTH)
     132                        || lastConstraint.gridy != currentConstraint.gridy)) {
     133            insertEOL();
     134        }
     135
     136        lastConstraint = currentConstraint;
     137        boolean isChildrenSearchable = true;
     138        ISearchableComponent s = null;
     139        if (c instanceof ISearchableComponent) {
     140            s = (ISearchableComponent) c;
     141            isChildrenSearchable = s.isChildrenSearchable();
     142        }
     143
     144        if (s == null || s.isSearchable()) {
     145
     146            List<SearchItem> items = null;
     147            if (s != null) {
     148                items = s.getSearchItems();
     149            }
     150            if (items == null) {
     151                List<SearchItem> itm = new ArrayList<>();
     152                SearchTextFinder.DEFAULT_SEARCH_TEXT_FINDERS.forEach(finder -> itm.addAll(finder.getSearchItems(c)));
     153                items = itm;
     154            }
     155            if (items.size() == 0) {
     156                if (isEOL) {
     157                    insertEOL();
     158                }
     159                if (c instanceof JSeparator) {
     160                    insertSeparator();
     161                }
     162
     163            } else {
     164                items.get(0).components.add(c);
     165                if (c instanceof JComponent) {
     166                    items.get(0).tooltip = ((JComponent) c).getToolTipText();
     167                }
     168
     169                final int inset = currentConstraint == null ? 0 : currentConstraint.insets.left;
     170                items.forEach(item -> item.setLevelInset(level, inset));
     171                isChildrenSearchable = isChildrenSearchable
     172                        && items.stream().allMatch(item -> item.childComponentsSearchable);
     173
     174                addAll(items, isEOL);
     175            }
     176        }
     177
     178        if (isChildrenSearchable && c instanceof Container) {
     179            Container cont = (Container) c;
     180            List<Component> components = Arrays.asList(cont.getComponents());
     181            if (components.size() > 0) {
     182                insertEOL();
     183                for (Component component : components) {
     184                    searchComponent(component, level + 1);
     185                }
     186            }
     187        }
     188        return isEOL;
     189    }
     190
     191}
     192 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/search/SearchItem.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.Component;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.List;
     8import java.util.Locale;
     9import java.util.Objects;
     10import java.util.Optional;
     11
     12import org.openstreetmap.josm.tools.LanguageInfo;
     13
     14/**
     15 * Contains searchable items and their components
     16 * @author Bjoeni
     17 */
     18public class SearchItem {
     19    List<Component> components = new ArrayList<>();
     20    String text;
     21    String tooltip;
     22    int level;
     23    int inset = 0;
     24    boolean eol = true;
     25    List<SearchItem> children = new ArrayList<>();
     26    boolean childComponentsSearchable = true;
     27
     28    private boolean visible = true;
     29    private static final Locale CURRENT_LOCALE = LanguageInfo.getLocale(LanguageInfo.getJOSMLocaleCode());
     30
     31    public SearchItem() {
     32    }
     33
     34    public SearchItem(String text) {
     35        this.text = text;
     36    }
     37
     38    public SearchItem(Component component, String text, String tooltip, boolean eol,
     39            boolean childComponentsSearchable) {
     40        this.components.add(component);
     41        this.text = text;
     42        this.tooltip = tooltip;
     43        this.setEOL(eol);
     44        this.childComponentsSearchable = childComponentsSearchable;
     45    }
     46
     47    public SearchItem addChild(SearchItem item) {
     48        Optional<SearchItem> match = children.stream()
     49                .filter(c -> Objects.equals(c.text, item.text) && c.level == item.level && c.inset == item.inset)
     50                .findAny();
     51
     52        if (match.isPresent()) {
     53            match.get().merge(item);
     54            return match.get();
     55        } else {
     56            children.add(item);
     57            return item;
     58        }
     59    }
     60
     61    public void merge(SearchItem item) {
     62        this.components.addAll(item.components);
     63        this.setEOL(this.eol || item.eol);
     64    }
     65
     66    public boolean filter(String filterStr) {
     67        visible = this.matches(filterStr);
     68        for (SearchItem child : children) {
     69            if (child.isVisible()) {
     70                visible = child.filter(filterStr) || visible;
     71            }
     72        }
     73        return visible;
     74    }
     75
     76    public boolean matches(String filterStr) {
     77        return Arrays.asList(filterStr.toLowerCase(CURRENT_LOCALE).split(" ")).stream()
     78                .allMatch(f -> text != null && text.toLowerCase(CURRENT_LOCALE).indexOf(f) != -1
     79                        || (tooltip != null && tooltip.toLowerCase(CURRENT_LOCALE).indexOf(f) != -1));
     80    }
     81
     82    public void showAll() {
     83        visible = true;
     84        children.forEach(SearchItem::showAll);
     85    }
     86
     87    @Override
     88    public String toString() {
     89        return text + (tooltip == null ? "" : " (Tooltip: " + tooltip + ")") + " [" + level + "." + inset + "] {"
     90                + components.size() + "}";
     91    }
     92
     93    public boolean isVisible() {
     94        return visible;
     95    }
     96
     97    public void addTooltip(String tooltip) {
     98        if (this.tooltip == null || this.tooltip.trim().isEmpty()) {
     99            this.tooltip = tooltip;
     100        }
     101    }
     102
     103    public void setLevelInset(int level, int inset) {
     104        this.level = level;
     105        this.inset = inset;
     106    }
     107
     108    public void setEOL() {
     109        setEOL(true);
     110    }
     111
     112    public void setEOL(boolean eol) {
     113        this.eol = eol;
     114    }
     115
     116    public void maximizeInset() {
     117        this.inset = Integer.MAX_VALUE;
     118    }
     119
     120}
     121 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/search/SearchPanel.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.util.List;
     7import java.util.stream.Collectors;
     8
     9import javax.swing.JLabel;
     10import javax.swing.JPanel;
     11import javax.swing.JScrollPane;
     12
     13import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     14import org.openstreetmap.josm.tools.Logging;
     15import org.openstreetmap.josm.tools.Utils;
     16
     17/**
     18 * Panel displaying search results in the preferences
     19 * @author Bjoeni
     20 */
     21public class SearchPanel extends JScrollPane implements ISearchableComponent {
     22
     23    private PreferenceTabbedPane tabs;
     24    private SearchIndex searchIndex;
     25    private JLabel lbl = new JLabel("search results here");
     26    private JPanel panel = new JPanel();
     27
     28    public SearchPanel(PreferenceTabbedPane tabs) {
     29        this.tabs = tabs;
     30        searchIndex = new SearchIndex(tabs);
     31        panel.add(lbl);
     32        setViewportView(panel);
     33    }
     34
     35    public PreferenceTabbedPane getTabPane() {
     36        return tabs;
     37    }
     38
     39    public void search(String text) {
     40        if (!searchIndex.isBuilt() || "".equals(text)) {
     41            Logging.info("Building search index...");
     42            lbl.setText(tr("Building search index..."));
     43            searchIndex.build();
     44        }
     45        Logging.info("searching");
     46        lbl.setText(tr("Searching..."));
     47        searchIndex.filter(text);
     48        Logging.info("searched");
     49        searchableSettings = new StringBuilder();
     50        printSearchItems(searchIndex.parents.stream().filter(SearchItem::isVisible).collect(Collectors.toList()), "");
     51        /*try {
     52            FileUtils.writeStringToFile(new File(FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath()
     53                    + "/searchableSettings.txt"), searchableSettings.toString());
     54        } catch (IOException e) {
     55            e.printStackTrace();
     56        }*/
     57        lbl.setText("<html><pre>" + Utils.escapeReservedCharactersHTML(searchableSettings.toString()) + "<pre><html>");
     58    }
     59
     60    StringBuilder searchableSettings;
     61
     62    private void printSearchItems(List<SearchItem> items, String indent) {
     63        for (int i = 0; i < items.size(); i++) {
     64            SearchItem item = items.get(i);
     65            searchableSettings.append("\n" + indent + item.toString().replaceAll("\n", "<br>"));
     66            List<SearchItem> c = item.children.stream().filter(SearchItem::isVisible).collect(Collectors.toList());
     67            if (c.size() > 0) {
     68                searchableSettings.append("\n" + indent + ">> ");
     69                printSearchItems(c, indent + "   ");
     70                searchableSettings.append("\n" + indent + "<<");
     71                if (indent.isEmpty()) {
     72                    searchableSettings.append("\n");
     73                }
     74            } else if (item.eol && i != items.size() - 1) {
     75                searchableSettings.append("\n" + indent + "==");
     76            }
     77        }
     78
     79    }
     80
     81    @Override
     82    public boolean isSearchable() {
     83        return false;
     84    }
     85
     86}
  • src/org/openstreetmap/josm/gui/preferences/search/SearchTextField.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.Dimension;
     7
     8import javax.swing.event.DocumentEvent;
     9import javax.swing.event.DocumentListener;
     10
     11import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     12import org.openstreetmap.josm.gui.widgets.JosmTextField;
     13
     14/**
     15 * TextField for searching the preferences
     16 * @author Bjoeni
     17 */
     18public class SearchTextField extends JosmTextField {
     19    private SearchPanel panel;
     20    private PreferenceTabbedPane tabs;
     21
     22    public SearchTextField(SearchPanel panel) {
     23
     24        this.panel = panel;
     25        tabs = panel.getTabPane();
     26        setHint(tr("Search..."));
     27        getDocument().addDocumentListener(new DocumentListener() {
     28
     29            @Override
     30            public void removeUpdate(DocumentEvent e) {
     31                panel.getTabPane().setSelectedIndex(0);
     32                panel.search(getTextContent());
     33            }
     34
     35            @Override
     36            public void insertUpdate(DocumentEvent e) {
     37                panel.getTabPane().setSelectedIndex(0);
     38                panel.search(getTextContent());
     39
     40            }
     41
     42            @Override
     43            public void changedUpdate(DocumentEvent e) {
     44            }
     45        });
     46    }
     47
     48    public void adjustWidth() {
     49        int width = getPreferredSize().width;
     50        for (int i = 0; i < tabs.getTabCount(); i++) {
     51            width = Math.max(width, tabs.getBoundsAt(i).width);
     52        }
     53        setPreferredSize(new Dimension(width, getPreferredSize().height));
     54    }
     55
     56    @Override
     57    public String getText() {
     58        return tr("Search");
     59    }
     60
     61    public String getTextContent() {
     62        return super.getText();
     63    }
     64
     65}
  • src/org/openstreetmap/josm/gui/preferences/search/SearchTextFinder.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.Component;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.Collections;
     8import java.util.List;
     9import java.util.stream.Collectors;
     10
     11import javax.swing.JCheckBox;
     12import javax.swing.JComboBox;
     13import javax.swing.JLabel;
     14import javax.swing.JList;
     15import javax.swing.JRadioButton;
     16import javax.swing.JTable;
     17import javax.swing.table.TableModel;
     18
     19/**
     20 * Class for obtaining the searchable properties of arbitrary components
     21 * @param <T> type of the component
     22 * @author Bjoeni
     23 */
     24public class SearchTextFinder<T extends Component> {
     25    List<IComponentProperties<T>> properties = new ArrayList<>();
     26
     27    @FunctionalInterface
     28    private interface IComponentProperties<U extends Component> {
     29        Object getFrom(U component);
     30
     31        default List<SearchItem> getSearchItems(U component) {
     32            Object obj = getFrom(component);
     33
     34            if (obj == null) {
     35                return new ArrayList<>();
     36            }
     37
     38            if (obj instanceof String) {
     39                return Arrays.asList(new SearchItem((String) obj));
     40            }
     41
     42            if (obj instanceof String[]) {
     43                return Arrays.asList((String[]) obj).stream().map(SearchItem::new).collect(Collectors.toList());
     44            }
     45
     46            if (obj instanceof SearchItem[]) {
     47                return Arrays.asList((SearchItem[]) obj);
     48            }
     49
     50            throw new IllegalArgumentException();
     51        }
     52    }
     53
     54    @FunctionalInterface
     55    interface ComponentProperty<U extends Component> extends IComponentProperties<U> {
     56        @Override
     57        String getFrom(U component);
     58    }
     59
     60    @FunctionalInterface
     61    interface ComponentPropertyArray<U extends Component> extends IComponentProperties<U> {
     62        @Override
     63        String[] getFrom(U component);
     64    }
     65
     66    @FunctionalInterface
     67    interface ComponentPropertyItem<U extends Component> extends IComponentProperties<U> {
     68        @Override
     69        SearchItem[] getFrom(U component);
     70    }
     71
     72    /**
     73     * Instantiates {@link SearchTextFinder}
     74     */
     75    public SearchTextFinder() {
     76    }
     77
     78    /**
     79     * Instantiates {@link SearchTextFinder}
     80     * @param prop Expression returning a searchable string for the given component, e.g. {@code JLabel::getText}.
     81     * @see #add(ComponentProperty)
     82     * @see #addArray(ComponentPropertyArray)
     83     */
     84    public SearchTextFinder(ComponentProperty<T> prop) {
     85        properties.add(prop);
     86    }
     87
     88    /**
     89     * Add another property.
     90     * The compiler can't deal with generic vararg parameters properly, so just use this function instead
     91     * @param prop Expression returning a searchable string for the given component, e.g. {@code JLabel::getText}.
     92     * @return this (for chaining)
     93     */
     94    public SearchTextFinder<T> add(ComponentProperty<T> prop) {
     95        properties.add(prop);
     96        return this;
     97    }
     98
     99    /**
     100     * Add a property returning a string array.
     101     * The compiler can't deal with generic vararg parameters properly, so just use this function instead
     102     * @param prop Expression returning an array of searchable strings for the given component
     103     * @return this (for chaining)
     104     */
     105    public SearchTextFinder<T> addArray(ComponentPropertyArray<T> prop) {
     106        properties.add(prop);
     107        return this;
     108    }
     109
     110    /**
     111     * Add a property returning a string array.
     112     * The compiler can't deal with generic vararg parameters properly, so just use this function instead
     113     * @param prop Expression returning an array of searchable strings for the given component
     114     * @return this (for chaining)
     115     */
     116    public SearchTextFinder<T> addItem(ComponentPropertyItem<T> prop) {
     117        properties.add(prop);
     118        return this;
     119    }
     120
     121    /**
     122     * Get the searchable texts for the given component
     123     * @param c component
     124     * @return {@code List<String>} or an empty list if it's not the right type.
     125     */
     126    public List<SearchItem> getSearchItems(Component c) {
     127        List<SearchItem> ret = new ArrayList<>();
     128
     129        try {
     130            @SuppressWarnings("unchecked")
     131            T component = (T) c;
     132            if (component != null) {
     133                properties.forEach(property -> {
     134                    List<SearchItem> items = property.getSearchItems(component);
     135                    items.forEach(item -> {
     136                        if (item.text != null && item.text.trim().length() > 0) {
     137                            if (item.text.indexOf('<') != -1) {
     138                                item.text = stripHtml(item.text);
     139                            }
     140                            if (item.text.indexOf(':') == item.text.length() - 1) {
     141                                item.text = item.text.substring(0, item.text.length() - 1);
     142                            }
     143                            if (item.tooltip != null && item.tooltip.indexOf('<') != -1) {
     144                                item.tooltip = stripHtml(item.tooltip);
     145                            }
     146                            ret.add(item);
     147                        }
     148                    });
     149                });
     150            }
     151
     152        } catch (ClassCastException ex) {
     153            // can't use 'instanceof' here, because the type information of T will already be stripped away at runtime
     154        }
     155        return ret;
     156    }
     157
     158    private String stripHtml(String text) {
     159        return text
     160                .replaceAll("<br\s*/?>", "\n")
     161                .replaceAll("(<style>.*</style>|<[^<>]*>)", " ")
     162                .trim()
     163                .replaceAll(" +", " ");
     164    }
     165
     166    public final static List<SearchTextFinder<?>> DEFAULT_SEARCH_TEXT_FINDERS = Collections.unmodifiableList(Arrays.asList(
     167
     168                    new SearchTextFinder<>(JLabel::getText),
     169                    new SearchTextFinder<>(JRadioButton::getText),
     170                    new SearchTextFinder<>(JCheckBox::getText),
     171
     172                    new SearchTextFinder<JTable>().addItem(table -> {
     173                        List<SearchItem> lst = new ArrayList<>();
     174                        TableModel tm = table.getModel();
     175                        for (int row = 0; row < tm.getRowCount(); row++) {
     176                            for (int col = 0; col < tm.getColumnCount(); col++) {
     177                                Object obj = tm.getValueAt(row, col);
     178                                Component renderer = table.getCellRenderer(row, col)
     179                                        .getTableCellRendererComponent(table, obj, false, false, row, col);
     180                                if (renderer instanceof JLabel) {
     181                                    JLabel label = (JLabel) renderer;
     182                                    boolean isLast = col == tm.getColumnCount() - 1;
     183                                    lst.add(new SearchItem(label, label.getText(), label.getToolTipText(), isLast, false));
     184                                }
     185                            }
     186                        }
     187                        return lst.toArray(new SearchItem[0]);
     188                    }),
     189
     190                    new SearchTextFinder<JComboBox<Object>>().addItem(combobox -> {
     191                        List<SearchItem> lst = new ArrayList<>();
     192                        int size = combobox.getItemCount();
     193                        JList<Object> listStub = new JList<>();
     194                        for (int i = 0; i < size; i++) {
     195                            Component renderer = combobox.getRenderer().getListCellRendererComponent(listStub,
     196                                    combobox.getItemAt(i), i, false, false);
     197                            if (renderer instanceof JLabel) {
     198                                JLabel label = (JLabel) renderer;
     199                                lst.add(new SearchItem(renderer, label.getText(), label.getToolTipText(), false, false));
     200                            }
     201                        }
     202                        return lst.toArray(new SearchItem[0]);
     203                    })));
     204
     205}
     206 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/search/package-info.java

     
     1package org.openstreetmap.josm.gui.preferences.search;
     2 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java

     
    1515import java.awt.event.KeyEvent;
    1616import java.awt.im.InputContext;
    1717import java.lang.reflect.Field;
     18import java.util.ArrayList;
    1819import java.util.LinkedHashMap;
    1920import java.util.List;
    2021import java.util.Map;
     
    3940import javax.swing.table.TableColumnModel;
    4041
    4142import org.openstreetmap.josm.data.preferences.NamedColorProperty;
     43import org.openstreetmap.josm.gui.preferences.search.SearchItem;
     44import org.openstreetmap.josm.gui.preferences.search.ISearchableComponent;
    4245import org.openstreetmap.josm.gui.util.GuiHelper;
    4346import org.openstreetmap.josm.gui.util.TableHelper;
    4447import org.openstreetmap.josm.gui.widgets.FilterField;
     
    5053/**
    5154 * This is the keyboard preferences content.
    5255 */
    53 public class PrefJPanel extends JPanel {
     56public class PrefJPanel extends JPanel implements ISearchableComponent {
    5457
    5558    // table of shortcuts
    5659    private final AbstractTableModel model;
     
    363366            }
    364367        }
    365368    }
     369
     370    @Override
     371    public boolean isChildrenSearchable() {
     372        return false;
     373    }
     374
     375    @Override
     376    public List<SearchItem> getSearchItems() {
     377        List<SearchItem> list = new ArrayList<>();
     378        for (int row = 0; row < model.getRowCount(); row++) {
     379            list.add(new SearchItem(model.getValueAt(row, 0).toString()));
     380        }
     381        return list;
     382    }
    366383}