Ticket #20573: 20573-0.3.diff

File 20573-0.3.diff, 127.5 KB (added by Bjoeni, 4 years ago)
  • src/org/openstreetmap/josm/data/validation/Test.java

    ### Eclipse Workspace Patch 1.0
    #P josm-search
     
    274274    public boolean ok() {
    275275        enabled = checkEnabled.isSelected();
    276276        testBeforeUpload = checkBeforeUpload.isSelected();
     277        checkBeforeUpload = null; //prevent memory leak (keeps reference to parent dialog)
    277278        return false;
    278279    }
    279280
  • src/org/openstreetmap/josm/gui/preferences/PreferenceDialog.java

     
    4141 */
    4242public class PreferenceDialog extends JDialog {
    4343
    44     private final PreferenceTabbedPane tpPreferences = new PreferenceTabbedPane();
     44    private PreferenceTabbedPane tpPreferences = new PreferenceTabbedPane();
    4545    private final ContextSensitiveHelpAction helpAction = new ContextSensitiveHelpAction();
    4646    private final WindowEventHandler windowEventHandler = new WindowEventHandler();
    4747    private boolean canceled;
     
    154154        } else if (previouslySelected != null && previouslySelected.a != null) {
    155155            tpPreferences.selectTabByPref(previouslySelected.a);
    156156        } else {
    157             tpPreferences.setSelectedIndex(0);
     157            tpPreferences.setSelectedIndex(1);
    158158        }
    159159    }
    160160
     
    232232        previouslySelected = tpPreferences.getSelectedTab();
    233233        removeWindowListener(windowEventHandler);
    234234        setVisible(false); // save current geometry
     235        //removeAll();
     236        tpPreferences.searchPanel.destroyIndex();
    235237        super.dispose();
    236238    }
    237239}
  • 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.Color;
     5import java.awt.Component;
     6import java.awt.Rectangle;
     7import java.lang.reflect.InvocationTargetException;
     8import java.util.ArrayList;
     9import java.util.List;
     10import java.util.Objects;
     11import java.util.Optional;
     12
     13import javax.swing.ImageIcon;
     14import javax.swing.JComponent;
     15import javax.swing.SwingUtilities;
     16import javax.swing.Timer;
     17
     18import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     19import org.openstreetmap.josm.gui.util.GuiHelper;
     20import org.openstreetmap.josm.tools.ImageProvider;
     21import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
     22import org.openstreetmap.josm.tools.Logging;
     23
     24/**
     25 * Contains searchable items and their components
     26 * @author Bjoeni
     27 */
     28public class SearchItem {
     29    int level;
     30    int inset;
     31    private String text;
     32    private String tooltip;
     33    private String highlight;
     34    private List<SearchItem> children = new ArrayList<>();
     35    private List<Component> originalComponents = new ArrayList<>();
     36
     37    private int tabIndex;
     38    private boolean eol = true;
     39    private boolean visible = true;
     40    private boolean hasVisibleChildren;
     41    private boolean childComponentsSearchable = true;
     42    private String iconName;
     43    private String textNormalized;
     44    private String tooltipNormalized;
     45    private PreferenceTabbedPane tabs;
     46    private SearchItemComponent comp;
     47
     48    public SearchItem(String text) {
     49        this.setText(text);
     50    }
     51
     52    public SearchItem(Component originalComponent, String text, String tooltip, boolean eol,
     53            boolean childComponentsSearchable) {
     54        this.originalComponents.add(originalComponent);
     55        this.setText(text);
     56        this.setTooltip(tooltip);
     57        this.setEOL(eol);
     58        this.childComponentsSearchable = childComponentsSearchable;
     59    }
     60
     61    public SearchItem addChild(SearchItem item) {
     62        synchronized (children) {
     63            Optional<SearchItem> match = children.stream()
     64                    .filter(c -> Objects.equals(c.text, item.text) && c.level == item.level && c.inset == item.inset)
     65                    .findAny();
     66            if (match.isPresent()) {
     67                match.get().merge(item);
     68                return match.get();
     69            } else {
     70                children.add(item);
     71                GuiHelper.runInEDT(() -> {
     72                    getComponent().addChild(item.getComponent());
     73                });
     74                return item;
     75            }
     76        }
     77
     78    }
     79
     80    public void merge(SearchItem item) {
     81        this.originalComponents.addAll(item.originalComponents);
     82        this.setEOL(this.isEOL() || item.isEOL());
     83    }
     84
     85    public boolean filter(SearchFilters filters) {
     86        visible = this.matches(filters);
     87        hasVisibleChildren = false;
     88        for (SearchItem child : children) {
     89            if (child.isVisible()) {
     90                hasVisibleChildren = child.filter(filters) || hasVisibleChildren;
     91            }
     92        }
     93        visible = visible || hasVisibleChildren;
     94
     95        if (visible && text != null && textNormalized != null) {
     96            highlight = filters.highlightString(text, textNormalized);
     97        } else {
     98            highlight = "<html><span style=\"color: red;\"><b>ERROR</b></span></html>";
     99        }
     100        return visible;
     101    }
     102
     103    public boolean matches(SearchFilters filters) {
     104        return filters.getNormalized().stream()
     105                .allMatch(str -> textNormalized != null && textNormalized.indexOf(str) != -1
     106                        || (tooltipNormalized != null && tooltipNormalized.indexOf(str) != -1));
     107    }
     108
     109    public void showAll() {
     110        visible = true;
     111        children.forEach(SearchItem::showAll);
     112    }
     113
     114    @Override
     115    public String toString() {
     116        return text + (tooltip == null ? "" : " (Tooltip: " + tooltip + ")") + " [" + level + "." + inset
     117                + (eol ? ":EOL" : "") + "] {" + originalComponents.size() + "}";
     118    }
     119
     120    public ImageIcon getIcon() {
     121        if (iconName == null)
     122            return null;
     123
     124        return iconName == null || iconName.isEmpty() ? null
     125                : iconName.contains("/") ? ImageProvider.get(iconName, ImageSizes.SMALLICON)
     126                        : ImageProvider.get("preferences", iconName, ImageSizes.SMALLICON);
     127    }
     128
     129    public boolean isVisible() {
     130        return visible;
     131    }
     132
     133    public boolean isEOL() {
     134        return eol;
     135    }
     136
     137    public void setEOL() {
     138        setEOL(true);
     139    }
     140
     141    public void setEOL(boolean eol) {
     142        this.eol = eol;
     143    }
     144
     145    public void setLevelInset(int level, int inset) {
     146        this.level = level;
     147        this.inset = inset;
     148    }
     149
     150    public void maximizeInset() {
     151        this.inset = Integer.MAX_VALUE;
     152    }
     153
     154    public void setTabIndex(PreferenceTabbedPane tabs, int tabIndex) {
     155        this.tabs = tabs;
     156        this.tabIndex = tabIndex;
     157    }
     158
     159    public void setIconName(String iconName) {
     160        this.iconName = iconName;
     161    }
     162
     163    public void addOriginalComponent(Component c) {
     164        this.originalComponents.add(c);
     165    }
     166
     167    public void showOriginalComponent() {
     168        if (tabIndex > -1) {
     169            tabs.setSelectedIndex(tabIndex);
     170        }
     171        originalComponents.stream().filter(Objects::nonNull).forEach(comp -> {
     172            //Color bg = comp.getBackground();
     173            Color fg = comp.getForeground();
     174            //Logging.debug("BG:" + bg.toString());
     175            Logging.debug("FG:" + fg.toString());
     176            //comp.setBackground(SystemColor.text);
     177            comp.setForeground(SearchPanel.DARK_MODE ? Color.yellow : Color.red);
     178            comp.requestFocus();
     179            if (comp instanceof JComponent) {
     180                JComponent jcomp = (JComponent) comp;
     181                Rectangle bounds = new Rectangle(jcomp.getBounds());
     182                jcomp.scrollRectToVisible(bounds);
     183            }
     184            Timer timer = new Timer(3000, l -> {
     185                //comp.setBackground(bg);
     186                comp.setForeground(fg);
     187            });
     188            timer.setRepeats(false);
     189            timer.start();
     190        });
     191        if (originalComponents.stream().filter(Objects::nonNull).noneMatch(Component::isVisible)) {
     192            Logging.warn("INVIS!!");
     193        }
     194    }
     195
     196    /**
     197     * @return the component
     198     */
     199    public SearchItemComponent getComponent() {
     200        if (comp == null) {
     201            comp = GuiHelper.runInEDTAndWaitAndReturn(() -> new SearchItemComponent(this));
     202        }
     203        return comp;
     204    }
     205
     206    public void updateComponents() throws InvocationTargetException, InterruptedException {
     207        SwingUtilities.invokeAndWait(() -> {
     208            //invokeLater is faster but causes too many calls at once and blocks the UI
     209            SearchItemComponent c = getComponent();
     210            c.setVisible(this.isVisible());
     211            if (isVisible()) {
     212                c.setToolTipText(toString());
     213                c.setText(highlight);
     214                c.setExpanded(true);
     215            }
     216        });
     217        if (isVisible()) {
     218            for (SearchItem child : children) {
     219                child.updateComponents();
     220            }
     221        }
     222    }
     223
     224    /**
     225     * @return the hasVisibleChildren
     226     */
     227    public boolean hasVisibleChildren() {
     228        return hasVisibleChildren;
     229    }
     230
     231    /**
     232     * @return the childComponentsSearchable
     233     */
     234    public boolean isChildComponentsSearchable() {
     235        return childComponentsSearchable;
     236    }
     237
     238    /**
     239     * @return the text2
     240     */
     241    public String getText() {
     242        return text;
     243    }
     244
     245    /**
     246     * @param text the text to set
     247     */
     248    public void setText(String text) {
     249        this.text = stripHtml(text);
     250        this.textNormalized = SearchFilters.normalize(this.text);
     251    }
     252
     253    /**
     254     * @return the tooltip
     255     */
     256    public String getTooltip() {
     257        return tooltip;
     258    }
     259
     260    public void addTooltip(String tooltip) {
     261        if (this.getTooltip() == null || this.getTooltip().trim().isEmpty()) {
     262            this.setTooltip(tooltip);
     263        }
     264    }
     265
     266    /**
     267     * @param tooltip the tooltip to set
     268     */
     269    public void setTooltip(String tooltip) {
     270        this.tooltip = stripHtml(tooltip);
     271        this.tooltipNormalized = SearchFilters.normalize(this.tooltip);
     272    }
     273
     274    private static String stripHtml(String str) {
     275        if (str == null || str.trim().length() == 0)
     276            return null;
     277
     278        if (str.indexOf('<') > -1) {
     279            str = str.replaceAll("(?i)(<br\\s/>\\s*)+", ": ").replaceAll("(<style>.*</style>|<[^<>]*>)", " ").trim()
     280                    .replaceAll(" +", " ");
     281        }
     282
     283        if (str.indexOf(':') == str.length() - 1) {
     284            str = str.substring(0, str.length() - 1);
     285        }
     286
     287        return str;
     288    }
     289
     290}
     291 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java

     
    1313import java.awt.event.KeyEvent;
    1414import java.awt.im.InputContext;
    1515import java.lang.reflect.Field;
     16import java.util.ArrayList;
    1617import java.util.LinkedHashMap;
    1718import java.util.List;
    1819import java.util.Map;
     
    3637import javax.swing.table.TableColumnModel;
    3738
    3839import org.openstreetmap.josm.data.preferences.NamedColorProperty;
     40import org.openstreetmap.josm.gui.preferences.search.SearchItem;
     41import org.openstreetmap.josm.gui.preferences.search.ISearchableComponent;
    3942import org.openstreetmap.josm.gui.util.GuiHelper;
    4043import org.openstreetmap.josm.gui.util.TableHelper;
    4144import org.openstreetmap.josm.gui.widgets.FilterField;
     
    4851/**
    4952 * This is the keyboard preferences content.
    5053 */
    51 public class PrefJPanel extends JPanel {
     54public class PrefJPanel extends JPanel implements ISearchableComponent {
    5255
    5356    // table of shortcuts
    5457    private final AbstractTableModel model;
     
    350353            }
    351354        }
    352355    }
     356
     357    @Override
     358    public boolean isChildrenSearchable() {
     359        return false;
     360    }
     361
     362    @Override
     363    public List<SearchItem> getSearchItems() {
     364        List<SearchItem> list = new ArrayList<>();
     365        for (int row = 0; row < model.getRowCount(); row++) {
     366            list.add(new SearchItem(model.getValueAt(row, 0).toString()));
     367        }
     368        return list;
     369    }
    353370}
  • src/org/openstreetmap/josm/gui/preferences/SourceEditor.java

     
    8383import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    8484import org.openstreetmap.josm.gui.MainApplication;
    8585import org.openstreetmap.josm.gui.PleaseWaitRunnable;
     86import org.openstreetmap.josm.gui.preferences.search.ISearchableComponent;
     87import org.openstreetmap.josm.gui.preferences.search.SearchItem;
    8688import org.openstreetmap.josm.gui.util.DocumentAdapter;
    8789import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
    8890import org.openstreetmap.josm.gui.util.GuiHelper;
     
    110112 * Editor for JOSM extensions source entries.
    111113 * @since 1743
    112114 */
    113 public abstract class SourceEditor extends JPanel {
     115public abstract class SourceEditor extends JPanel implements ISearchableComponent {
    114116
    115117    /** the type of source entry **/
    116118    protected final SourceType sourceType;
     
    563565        sourcesInitiallyLoaded = true;
    564566    }
    565567
     568    @Override
     569    public List<SearchItem> getSearchItemsAsync() {
     570        if (!sourcesInitiallyLoaded && !NetworkManager.isOffline(OnlineResource.CACHE_UPDATES)) {
     571            new SourceLoader(availableSourcesUrl, sourceProviders, true).run();
     572        }
     573        sourcesInitiallyLoaded = true;
     574        return null;
     575    }
     576
    566577    /**
    567578     * List model of available sources.
    568579     */
     
    13491360        private CachedFile cachedFile;
    13501361        private boolean canceled;
    13511362        private final List<ExtendedSourceEntry> sources = new ArrayList<>();
     1363        private boolean silent;
    13521364
    13531365        SourceLoader(String url, List<SourceProvider> sourceProviders) {
    1354             super(tr(getStr(I18nString.LOADING_SOURCES_FROM), url));
     1366            this(url, sourceProviders, false);
     1367        }
     1368
     1369        SourceLoader(String url, List<SourceProvider> sourceProviders, boolean silent) {
     1370            super(tr(getStr(I18nString.LOADING_SOURCES_FROM), url), true, silent);
    13551371            this.url = url;
    13561372            this.sourceProviders = sourceProviders;
     1373            this.silent = silent;
    13571374        }
    13581375
    13591376        @Override
     
    13631380        }
    13641381
    13651382        protected void warn(Exception e) {
    1366             String emsg = Utils.escapeReservedCharactersHTML(e.getMessage() != null ? e.getMessage() : e.toString());
    1367             final String msg = tr(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM), url, emsg);
     1383            if (silent) {
     1384                Logging.warn(e);
     1385            } else {
     1386                String emsg = Utils
     1387                        .escapeReservedCharactersHTML(e.getMessage() != null ? e.getMessage() : e.toString());
     1388                final String msg = tr(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM), url, emsg);
    13681389
    1369             GuiHelper.runInEDT(() -> HelpAwareOptionPane.showOptionDialog(
    1370                     MainApplication.getMainFrame(),
    1371                     msg,
    1372                     tr("Error"),
    1373                     JOptionPane.ERROR_MESSAGE,
    1374                     ht(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC))
    1375                     ));
     1390                GuiHelper.runInEDT(() -> HelpAwareOptionPane.showOptionDialog(MainApplication.getMainFrame(), msg,
     1391                        tr("Error"), JOptionPane.ERROR_MESSAGE,
     1392                        ht(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC))));
     1393            }
    13761394        }
    13771395
    13781396        @Override
  • 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     *
     14     * default: true
     15     */
     16    default boolean isSearchable() {
     17        return true;
     18    }
     19
     20    /**
     21     * @return whether the children of this component should be traversed <br><br>
     22     *
     23     * default: same as {@link #isSearchable()} unless explicitly overridden
     24     */
     25    default boolean isChildrenSearchable() {
     26        return isSearchable();
     27    }
     28
     29    /**
     30     * Called from the EventDispatchThread (GUI), should not be blocked.
     31     *
     32     * @return the {@link SearchItem}s that should be used. Ignored if {@link #isSearchable()} returns {@code false}.<br>
     33     * Overrides the default {@link SearchTextFinder} if returning not {@code null} (including if an empty list is returned)<br><br>
     34     *
     35     * default: null
     36     */
     37    default List<SearchItem> getSearchItems() {
     38        return null;
     39    }
     40
     41    /**
     42     * Called from the worker thread, can be blocked as long as necessary. Calls to the GUI must be placed in the EventDispatchThread.
     43     *
     44     * @return the {@link SearchItem}s that should be used. Ignored if {@link #isSearchable()} returns {@code false}.<br>
     45     * Overrides the default {@link SearchTextFinder} if returning not {@code null} (including if an empty list is returned)<br><br>
     46     *
     47     * default: null
     48     */
     49    default List<SearchItem> getSearchItemsAsync() {
     50        return null;
     51    }
     52
     53    /**
     54     * @return
     55     */
     56    default String getIconName() {
     57        return null;
     58    }
     59
     60}
  • 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/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.awt.GridBagLayout;
     7import java.awt.SystemColor;
     8import java.lang.reflect.InvocationTargetException;
     9
     10import javax.swing.BorderFactory;
     11import javax.swing.JLabel;
     12import javax.swing.JPanel;
     13import javax.swing.JScrollPane;
     14import javax.swing.SwingConstants;
     15import javax.swing.SwingUtilities;
     16
     17import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     18import org.openstreetmap.josm.tools.GBC;
     19import org.openstreetmap.josm.tools.JosmRuntimeException;
     20import org.openstreetmap.josm.tools.Logging;
     21
     22/**
     23 * Panel displaying search results in the preferences
     24 * @author Bjoeni
     25 */
     26public class SearchPanel extends JScrollPane implements ISearchableComponent {
     27
     28    public static final boolean DARK_MODE = 130 < (Math.sqrt(Math.pow(SystemColor.text.getRed(), 2) * .241
     29            + Math.pow(SystemColor.text.getGreen(), 2) * .691 + Math.pow(SystemColor.text.getBlue(), 2) * .068));
     30
     31    private PreferenceTabbedPane tabs;
     32    private SearchIndex searchIndex;
     33    private JLabel lblStatus = new JLabel(tr("Enter something in the text fied to start searching"));
     34    private JPanel panel = new JPanel(new GridBagLayout());
     35    private SearchThread thread;
     36
     37    public SearchPanel(PreferenceTabbedPane tabs) {
     38        super(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
     39        this.tabs = tabs;
     40        searchIndex = new SearchIndex(tabs);
     41        GBC gbc = GBC.eol().fill(GBC.HORIZONTAL);
     42        gbc.anchor = GBC.NORTHWEST;
     43        gbc.weightx = 1;
     44        panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
     45        panel.add(searchIndex.getPanel(), gbc);
     46        gbc.gridy = 1;
     47        lblStatus.setHorizontalAlignment(SwingConstants.CENTER);
     48        gbc.fill();
     49        gbc.anchor = GBC.CENTER;
     50        panel.add(lblStatus, gbc);
     51        //setAlignmentX(LEFT_ALIGNMENT);
     52        setViewportView(panel);
     53    }
     54
     55    public void search(String text) {
     56        if (thread != null && thread.isAlive()) {
     57            Logging.debug("alive");
     58            thread.awaitBuildAndRestart(text);
     59        } else {
     60            Logging.debug("dead");
     61            thread = new SearchThread(text);
     62            thread.start();
     63        }
     64    }
     65
     66    public PreferenceTabbedPane getTabPane() {
     67        return tabs;
     68    }
     69
     70    public void destroyIndex() {
     71        removeAll();
     72        panel = null;
     73        lblStatus = null;
     74        setViewport(null);
     75        this.searchIndex = null;
     76    }
     77
     78    @Override
     79    public boolean isSearchable() {
     80        return false;
     81    }
     82
     83    class SearchThread extends Thread {
     84        private String text;
     85        private StringBuilder searchableSettings;
     86        private boolean building;
     87        private boolean interrupted;
     88        private String nextText;
     89        private boolean justBuiltIndex;
     90
     91        public SearchThread(String text) {
     92            this.text = text;
     93        }
     94
     95        public SearchThread(String text, boolean justBuiltIndex) {
     96            this.text = text;
     97            this.justBuiltIndex = justBuiltIndex;
     98        }
     99
     100        @Override
     101        public void run() {
     102            try {
     103                boolean buildIndex = !searchIndex.isBuilt(); // || "".equals(text);
     104                if (buildIndex) {
     105                    building = true;
     106                    Logging.info("Building search index...");
     107                    setText(tr("Building search index..."));
     108                    searchIndex.build();
     109                    building = false;
     110                    if (interrupted) {
     111                        startNext(true);
     112                        return;
     113                    }
     114                }
     115
     116                if (buildIndex || justBuiltIndex) {
     117                    setText(tr("Searching..."));
     118                }
     119
     120                //if (!"".equals(text)) {
     121                Logging.debug("searching \"" + text + "\"");
     122                boolean found = searchIndex.filter(text);
     123                Logging.debug("searched \"" + text + "\"");
     124                //}
     125
     126                Logging.debug("before \"" + text + "\"");
     127                searchIndex.updateComponents();
     128                Logging.debug("done \"" + text + "\"");
     129
     130                String txt = "";
     131                if (!found) {
     132                    txt += tr("No results found.") + "<br><br>";
     133                }
     134                if (false) { //expertmode
     135                    txt += tr("You might get more results by enabling expert mode.");
     136                }
     137                setText(txt);
     138
     139            } catch (InterruptedException e) {
     140                Logging.debug("Interrupted search thread \"" + text + "\"");
     141                // to be expected
     142            } catch (InvocationTargetException e) {
     143                throw new JosmRuntimeException(e);
     144            }
     145        }
     146
     147        public void awaitBuildAndRestart(String nextText) {
     148            this.nextText = nextText;
     149            if (building) {
     150                interrupted = true;
     151            } else {
     152                interrupt();
     153                startNext(false);
     154            }
     155        }
     156
     157        private void startNext(boolean justBuiltIndex) {
     158            thread = new SearchThread(nextText, justBuiltIndex);
     159            thread.start();
     160        }
     161
     162        private void setText(String txt) {
     163            SwingUtilities.invokeLater(() -> {
     164                lblStatus.setText(txt);
     165            });
     166        }
     167    }
     168
     169}
  • 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.lang.reflect.InvocationTargetException;
     10import java.util.ArrayList;
     11import java.util.Arrays;
     12import java.util.List;
     13import java.util.Locale;
     14import java.util.Stack;
     15import java.util.stream.Collectors;
     16
     17import javax.swing.JComponent;
     18import javax.swing.JPanel;
     19import javax.swing.JSeparator;
     20import javax.swing.SwingUtilities;
     21
     22import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     23import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferenceTab;
     24import org.openstreetmap.josm.gui.util.GuiHelper;
     25import org.openstreetmap.josm.tools.GBC;
     26import org.openstreetmap.josm.tools.LanguageInfo;
     27
     28/**
     29 * Contains all {@link SearchItem}s
     30 * @author Bjoeni
     31 */
     32public class SearchIndex {
     33    public static final Locale CURRENT_LOCALE = LanguageInfo.getLocale(LanguageInfo.getJOSMLocaleCode());
     34    public static final GBC EOL = GBC.eol();
     35
     36    Stack<SearchItem> stack = new Stack<>();
     37    List<SearchItem> parents = new ArrayList<>();
     38    PreferenceTabbedPane prefTabs;
     39    GridBagConstraints lastConstraint;
     40    private boolean built;
     41    private String lastFilterStr;
     42    private int currentTabIndex = -1;
     43
     44    private JPanel panel = GuiHelper.runInEDTAndWaitAndReturn(() -> new JPanel(new GridBagLayout()));
     45
     46    GridBagConstraints gbc_nw;
     47
     48    public SearchIndex(PreferenceTabbedPane prefTabs) {
     49        this.prefTabs = prefTabs;
     50        gbc_nw = GBC.eol().fill(GBC.HORIZONTAL);
     51        gbc_nw.anchor = GBC.NORTHWEST;
     52    }
     53
     54    void add(SearchItem item) {
     55        if (item.getText() == null)
     56            return;
     57
     58        item.setTabIndex(prefTabs, currentTabIndex);
     59        if (stack.isEmpty()) {
     60            item.setLevelInset(0, 0);
     61            parents.add(item);
     62            GuiHelper.runInEDT(() -> {
     63                getPanel().add(item.getComponent(), gbc_nw);
     64            });
     65            stack.push(item);
     66        } else {
     67            SearchItem lastItem = stack.lastElement();
     68            if (!lastItem.isEOL() && lastItem.level == item.level) {
     69                lastItem.setEOL(item.isEOL());
     70                lastItem.addChild(item);
     71            } else if (lastItem.level < item.level
     72                    || (lastItem.level == item.level && lastItem.inset + 15 < item.inset)) {
     73                stack.push(lastItem.addChild(item));
     74            } else {
     75                stack.pop();
     76                add(item);
     77            }
     78        }
     79    }
     80
     81    void addAll(List<SearchItem> items, boolean isEOL) {
     82        if (items.size() > 0) {
     83            items.get(items.size() - 1).setEOL(isEOL);
     84        }
     85        items.forEach(item -> add(item));
     86    }
     87
     88    void insertEOL() {
     89        if (!stack.isEmpty()) {
     90            stack.lastElement().setEOL();
     91        }
     92    }
     93
     94    void insertSeparator() {
     95        if (!stack.isEmpty()) {
     96            stack.lastElement().maximizeInset();
     97        }
     98    }
     99
     100    void insertNewPage() {
     101        stack.clear();
     102    }
     103
     104    public boolean isBuilt() {
     105        return built;
     106    }
     107
     108    /**
     109     * @throws InvocationTargetException exception while initializing tab
     110     * @throws InterruptedException thread interrupted
     111     */
     112    public void build() throws InvocationTargetException, InterruptedException {
     113        //setVisible(false);
     114        built = false;
     115        parents.clear();
     116        getPanel().removeAll();
     117        for (int i = 0; i < prefTabs.getTabCount(); i++) {
     118            Component c = prefTabs.getComponentAt(i);
     119            currentTabIndex = i;
     120            String iconName = null;
     121            if (c instanceof PreferenceTab) {
     122                iconName = ((PreferenceTab) c).getTabPreferenceSetting().getIconName();
     123                final int index = i;
     124                final Component comp = c;
     125                SwingUtilities.invokeAndWait(() -> prefTabs.initializeTab(index, (PreferenceTab) comp, true));
     126                c = prefTabs.getComponentAt(i);
     127            }
     128            insertNewPage();
     129            searchComponent(c, 1, iconName);
     130        }
     131
     132        built = true;
     133        //setVisible(true);
     134    }
     135
     136    public boolean filter(String filterStr) {
     137        boolean found = false;
     138
     139        if (lastFilterStr != null && filterStr.indexOf(lastFilterStr) != 0) {
     140            parents.forEach(SearchItem::showAll);
     141        }
     142
     143        if (!filterStr.isEmpty()) {
     144            SearchFilters filters = new SearchFilters(filterStr);
     145            found = parents.stream()
     146                    .filter(SearchItem::isVisible)
     147                    .map(item -> item.filter(filters))
     148                    .collect(Collectors.toList()) //force all visible elements to be evaluated
     149                    .contains(true);
     150        }
     151
     152        lastFilterStr = filterStr;
     153        return found;
     154    }
     155
     156    /*public DefaultMutableTreeNode getTreeView() {
     157        return tree;
     158    }
     159
     160    public synchronized DefaultMutableTreeNode createTreeView(DefaultTreeModel model) {
     161        tree.removeAllChildren();
     162        parents.forEach(child -> {
     163            MutableTreeNode t = child.createTreeView(model);
     164            if (t != null) {
     165                model.insertNodeInto(t, tree, tree.getChildCount());
     166                //tree.add(t);
     167            }
     168        });
     169        return tree;
     170    }*/
     171
     172    private boolean searchComponent(Component comp, int level, String iconName)
     173            throws InvocationTargetException, InterruptedException {
     174
     175        final Component c = comp;
     176
     177        boolean isEOL = true;
     178        GridBagConstraints currentConstraint = null;
     179
     180        Container p = c.getParent();
     181        if (p != null) {
     182            LayoutManager layout = p.getLayout();
     183            if (layout != null && layout instanceof GridBagLayout) {
     184                GridBagLayout grid = (GridBagLayout) layout;
     185
     186                currentConstraint = grid.getConstraints(c);
     187                isEOL = currentConstraint.gridwidth == GridBagConstraints.REMAINDER;
     188            }
     189        }
     190
     191        if (lastConstraint != null && currentConstraint != null
     192                && (((lastConstraint.fill == GridBagConstraints.HORIZONTAL
     193                        || lastConstraint.fill == GridBagConstraints.BOTH)
     194                        && currentConstraint.fill != GridBagConstraints.HORIZONTAL
     195                        && currentConstraint.fill != GridBagConstraints.BOTH)
     196                        || lastConstraint.gridy != currentConstraint.gridy)) {
     197            insertEOL();
     198        }
     199
     200        lastConstraint = currentConstraint;
     201        boolean isChildrenSearchable = true;
     202        ISearchableComponent s = null;
     203        if (c instanceof ISearchableComponent) {
     204            s = (ISearchableComponent) c;
     205            isChildrenSearchable = s.isChildrenSearchable();
     206        }
     207
     208        if (s == null || s.isSearchable()) {
     209
     210            List<SearchItem> items = null;
     211            if (s != null) {
     212                items = s.getSearchItemsAsync();
     213                final ISearchableComponent s2 = s;
     214                List<SearchItem> itm2 = GuiHelper.runInEDTAndWaitAndReturn(() -> s2.getSearchItems());
     215                if (itm2 != null) {
     216                    if (items == null) {
     217                        items = itm2;
     218                    } else {
     219                        items.addAll(itm2);
     220                    }
     221                }
     222
     223                if (iconName == null) {
     224                    iconName = s.getIconName();
     225                }
     226
     227                /*final SwingTransferItem swing = new SwingTransferItem(s);
     228                SwingUtilities.invokeAndWait(() -> {
     229                    swing.list = swing.component.getSearchItems();
     230                });
     231
     232                if (swing.list != null) {
     233                    items = swing.list;
     234                }*/
     235            }
     236            if (items == null) {
     237                final List<SearchItem> itm = new ArrayList<>();
     238                SwingUtilities.invokeAndWait(() -> {
     239                    SearchTextFinder.DEFAULT_SEARCH_TEXT_FINDERS
     240                            .forEach(finder -> itm.addAll(finder.getSearchItems(c)));
     241                });
     242
     243                items = itm;
     244            }
     245            if (iconName != null) {
     246                final String iconName2 = iconName;
     247                items.forEach(item -> item.setIconName(iconName2));
     248            }
     249            if (items.size() == 0) {
     250                if (isEOL) {
     251                    insertEOL();
     252                }
     253                if (c instanceof JSeparator) {
     254                    insertSeparator();
     255                }
     256
     257            } else {
     258                items.get(0).addOriginalComponent(c);
     259                if (c instanceof JComponent) {
     260                    items.get(0).setTooltip(((JComponent) c).getToolTipText());
     261                }
     262
     263                final int inset = currentConstraint == null ? 0 : currentConstraint.insets.left;
     264                items.forEach(item -> item.setLevelInset(level, inset));
     265                isChildrenSearchable = isChildrenSearchable
     266                        && items.stream().allMatch(item -> item.isChildComponentsSearchable());
     267
     268                addAll(items, isEOL);
     269            }
     270        }
     271
     272        if (isChildrenSearchable && c instanceof Container) {
     273            Container cont = (Container) c;
     274            List<Component> components = Arrays.asList(cont.getComponents());
     275            if (components.size() > 0) {
     276                insertEOL();
     277                for (Component component : components) {
     278                    searchComponent(component, level + 1, iconName);
     279                }
     280            }
     281        }
     282        return isEOL;
     283    }
     284
     285    /**
     286     * @return the panel
     287     */
     288    public JPanel getPanel() {
     289        return panel;
     290    }
     291
     292    public void setVisible(boolean visible) {
     293        GuiHelper.runInEDT(() -> {
     294            panel.setVisible(visible);
     295        });
     296    }
     297
     298    public void updateComponents() throws InvocationTargetException, InterruptedException {
     299        for (SearchItem item : parents) {
     300            item.updateComponents();
     301        }
     302    }
     303
     304}
     305 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.preferences.plugin;
    33
     4import static java.awt.GridBagConstraints.BOTH;
    45import static java.awt.GridBagConstraints.HORIZONTAL;
    56import static org.openstreetmap.josm.tools.I18n.tr;
    67import static org.openstreetmap.josm.tools.I18n.trc;
     
    5152import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    5253import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    5354import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     55import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel;
     56import org.openstreetmap.josm.gui.preferences.search.NotSearchablePanel;
    5457import org.openstreetmap.josm.gui.util.GuiHelper;
    5558import org.openstreetmap.josm.gui.widgets.FilterField;
    5659import org.openstreetmap.josm.plugins.PluginDownloadTask;
     
    162165    }
    163166
    164167    private JPanel buildSearchFieldPanel() {
    165         JPanel pnl = new JPanel(new GridBagLayout());
     168        JPanel pnl = new NotSearchablePanel(new GridBagLayout());
    166169        pnl.add(GBC.glue(0, 0));
    167170
    168171        ButtonGroup bg = new ButtonGroup();
     
    231234
    232235    @Override
    233236    public void addGui(final PreferenceTabbedPane gui) {
     237        PreferencePanel tab = gui.createPreferenceTab(this);
    234238        JTabbedPane pane = getTabPane();
     239        JPanel pnlPluginList = buildPluginListPanel();
    235240        pnlPluginUpdatePolicy = new PluginUpdatePolicyPanel();
    236         pane.addTab(tr("Plugins"), buildPluginListPanel());
     241        pane.addTab(tr("Plugins"), pnlPluginList);
    237242        pane.addTab(tr("Plugin update policy"), pnlPluginUpdatePolicy);
    238         super.addGui(gui);
    239         readLocalPluginInformation();
     243        tab.add(pane, GBC.eol().fill(BOTH));
     244        gui.addChangeListener(e -> {
     245            if (gui.getSelectedComponent() == tab) {
     246                readLocalPluginInformation();
     247            }
     248        });
    240249        pluginPreferencesActivated = true;
    241250    }
    242251
  • 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/search/SearchItemComponent.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.BasicStroke;
     5import java.awt.Color;
     6import java.awt.Cursor;
     7import java.awt.Graphics;
     8import java.awt.Graphics2D;
     9import java.awt.GridBagLayout;
     10import java.awt.RenderingHints;
     11import java.awt.event.MouseAdapter;
     12import java.awt.event.MouseEvent;
     13
     14import javax.swing.BorderFactory;
     15import javax.swing.ImageIcon;
     16import javax.swing.JLabel;
     17import javax.swing.JPanel;
     18
     19import org.openstreetmap.josm.tools.GBC;
     20import org.openstreetmap.josm.tools.ImageProvider;
     21import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
     22
     23public class SearchItemComponent extends JPanel {
     24
     25    private final GridBagLayout own_gbl = new GridBagLayout();
     26    private final JLabel lbl = new JLabel();
     27    private final JLabel expander = new JLabel();
     28    private JPanel childPanel;
     29    private final SearchItem item;
     30
     31    private GridBagLayout parent_gbl;
     32    private boolean expanded = true;
     33    private GBC gbc_nw;
     34    private GBC gbc_nw_fill;
     35
     36    private static final ImageIcon ICON_EXPANDED = ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON);
     37    private static final ImageIcon ICON_COLLAPSED = ImageProvider.get("dialogs", "next", ImageSizes.SMALLICON);
     38
     39    private static final Cursor CURSOR_HAND = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
     40    private static final Cursor CURSOR_DEFAULT = Cursor.getDefaultCursor();
     41
     42    public SearchItemComponent(SearchItem item) {
     43        super(new GridBagLayout());
     44        setVisible(false);
     45        this.item = item;
     46        add(expander);
     47
     48        gbc_nw = GBC.eol();
     49        gbc_nw.anchor = GBC.NORTHWEST;
     50        gbc_nw_fill = GBC.eol().fill(GBC.HORIZONTAL);
     51        gbc_nw_fill.anchor = GBC.NORTHWEST;
     52        add(lbl, gbc_nw_fill);
     53        lbl.setCursor(CURSOR_HAND);
     54        lbl.setBackground(Color.cyan);
     55        //lbl.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
     56        setBorder(BorderFactory.createEmptyBorder(10, item.level == 0 ? 10 : 17, item.level == 0 ? 25 : 0, 10));
     57
     58        expander.addMouseListener(new MouseAdapter() {
     59            @Override
     60            public void mouseClicked(MouseEvent e) {
     61                setExpanded(!isExpanded());
     62            }
     63        });
     64
     65        addMouseListener(new MouseAdapter() {
     66            @Override
     67            public void mouseClicked(MouseEvent e) {
     68                item.showOriginalComponent();
     69            }
     70        });
     71    }
     72
     73    public void setText(String txt) {
     74        lbl.setText(txt);
     75    }
     76
     77    @Override
     78    protected void paintComponent(Graphics graphics) {
     79        super.paintComponent(graphics);
     80
     81        if (item.level != 0)
     82            return;
     83
     84        Graphics2D g = (Graphics2D) graphics;
     85        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     86
     87        int width = getWidth(),
     88                height = getHeight(),
     89                stroke = 1,
     90                offset = 5,
     91                radius = 15;
     92
     93
     94        g.setColor(new Color(0, 0, 0, 125));
     95        g.fillRoundRect(offset, offset, width - stroke - offset, height - stroke - offset - 10, radius, radius);
     96
     97        g.setColor(getBackground());
     98        g.fillRoundRect(0, 0, width - 5, height - 15, radius, radius);
     99
     100        g.setColor(getForeground());
     101        g.setStroke(new BasicStroke(stroke));
     102        g.drawRoundRect(0, 0, width - 5, height - 15, radius, radius);
     103
     104        g.setStroke(new BasicStroke());
     105    }
     106
     107    /**
     108     * @return the expanded
     109     */
     110    public boolean isExpanded() {
     111        return expanded;
     112    }
     113
     114    /**
     115     * @param expanded the expanded to set
     116     */
     117    public void setExpanded(boolean expanded) {
     118        if (item.hasVisibleChildren()) {
     119            this.expanded = expanded;
     120            expander.setIcon(expanded ? ICON_EXPANDED : ICON_COLLAPSED);
     121            expander.setCursor(CURSOR_HAND);
     122            getChildPanel().setVisible(expanded);
     123        } else {
     124            expander.setIcon(ICON_COLLAPSED);
     125        }
     126    }
     127
     128    /**
     129     * @return the childPanel
     130     */
     131    public JPanel getChildPanel() {
     132        if (childPanel == null) {
     133            childPanel = new JPanel(own_gbl);
     134            add(childPanel, gbc_nw_fill);
     135        }
     136        return childPanel;
     137    }
     138
     139    public void addChild(SearchItemComponent comp) {
     140        getChildPanel().add(comp, gbc_nw_fill);
     141        comp.setParentGBL(own_gbl);
     142    }
     143
     144    /**
     145     * @param parent_gbl the parent_gbl to set
     146     */
     147    public void setParentGBL(GridBagLayout parent_gbl) {
     148        this.parent_gbl = parent_gbl;
     149    }
     150
     151}
  • src/org/openstreetmap/josm/gui/PleaseWaitRunnable.java

     
    1818import org.xml.sax.SAXException;
    1919
    2020/**
    21  * Instanced of this thread will display a "Please Wait" message in middle of JOSM
     21 * Instanced of this thread can display a "Please Wait" message in middle of JOSM
    2222 * to indicate a progress being executed.
    2323 *
    2424 * @author Imi
     
    3030    /** progress monitor */
    3131    protected final ProgressMonitor progressMonitor;
    3232
     33    protected PleaseWaitRunnable(String title, boolean ignoreException, boolean silent) {
     34        this.title = title;
     35        this.ignoreException = ignoreException;
     36        this.progressMonitor = new PleaseWaitProgressMonitor(title, silent);
     37    }
     38
    3339    /**
    3440     * Create the runnable object with a given message for the user.
    3541     * @param title message for the user
  • src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java

     
    99import java.awt.Rectangle;
    1010import java.awt.event.MouseAdapter;
    1111import java.awt.event.MouseEvent;
     12import java.util.Collection;
     13import java.util.HashMap;
    1214import java.util.HashSet;
    1315import java.util.List;
     16import java.util.Map;
    1417import java.util.Set;
     18import java.util.stream.Collectors;
    1519
    1620import javax.swing.JComponent;
    1721import javax.swing.JLabel;
     
    1822import javax.swing.SwingConstants;
    1923import javax.swing.SwingUtilities;
    2024
     25import org.openstreetmap.josm.data.Preferences;
     26import org.openstreetmap.josm.gui.preferences.search.ISearchableComponent;
     27import org.openstreetmap.josm.gui.preferences.search.SearchItem;
     28import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
     29import org.openstreetmap.josm.gui.util.GuiHelper;
    2130import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    2231import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
    2332import org.openstreetmap.josm.plugins.PluginInformation;
     33import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
     34import org.openstreetmap.josm.plugins.ReadRemotePluginInformationTask;
    2435
    2536/**
    2637 * A panel displaying the list of known plugins.
    2738 */
    28 public class PluginListPanel extends VerticallyScrollablePanel {
     39public class PluginListPanel extends VerticallyScrollablePanel implements ISearchableComponent {
    2940    static final class PluginCheckBoxMouseAdapter extends MouseAdapter {
    3041        private final PluginCheckBox cbPlugin;
    3142
     
    118129    /**
    119130     * Displays a list of plugins.
    120131     * @param displayedPlugins list of plugins
     132     * @return the added labels
    121133     * @since 13799
    122134     */
    123     public void displayPluginList(List<PluginInformation> displayedPlugins) {
     135    public Map<PluginInformation, JLabel> displayPluginList(List<PluginInformation> displayedPlugins) {
     136        Map<PluginInformation, JLabel> lbls = new HashMap<>();
     137
    124138        GridBagConstraints gbc = new GridBagConstraints();
    125139        gbc.gridx = 0;
    126140        gbc.anchor = GridBagConstraints.NORTHWEST;
     
    143157                    pi.getScaledIcon(),
    144158                    SwingConstants.LEADING);
    145159            lblPlugin.addMouseListener(new PluginCheckBoxMouseAdapter(cbPlugin));
     160            lbls.put(pi, lblPlugin);
    146161
    147162            gbc.gridx = 0;
    148163            gbc.gridy = ++row;
     
    170185            add(description, gbc);
    171186        }
    172187        pluginListInitialized = true;
     188        return lbls;
    173189    }
    174190
    175191    /**
     
    236252    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
    237253        return visibleRect.height;
    238254    }
     255
     256    @Override
     257    public List<SearchItem> getSearchItemsAsync() {
     258
     259        //if (model.getAvailablePlugins().isEmpty()) {
     260
     261        final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask(new PleaseWaitProgressMonitor(true));
     262        task.run();
     263        model.setAvailablePlugins(task.getAvailablePlugins());
     264
     265        if (!task.hasSiteCacheFile()) {
     266            Collection<String> pluginSites = Preferences.main().getOnlinePluginSites();
     267            if (!pluginSites.isEmpty()) {
     268                final ReadRemotePluginInformationTask remoteTask = new ReadRemotePluginInformationTask(
     269                        new PleaseWaitProgressMonitor(true), pluginSites, false);
     270                remoteTask.run();
     271                model.updateAvailablePlugins(remoteTask.getAvailablePlugins());
     272            }
     273        }
     274
     275        Map<PluginInformation, JLabel> map = GuiHelper.runInEDTAndWaitAndReturn(() -> {
     276            removeAll();
     277            displayEmptyPluginListInformation();
     278            return displayPluginList(model.getAvailablePlugins());
     279        });
     280        pluginListInitialized = true;
     281
     282        return map.entrySet().stream().map((entry) -> {
     283            String v = "";
     284            PluginInformation plugin = entry.getKey();
     285            if (plugin.localversion != null && !plugin.localversion.trim().isEmpty()) {
     286                v = " (local: " + plugin.localversion + ")";
     287            }
     288            return new SearchItem(entry.getValue(), plugin.getName() + v, plugin.description, true, false);
     289        }).collect(Collectors.toList());
     290
     291        //}
     292        /*return model.getAvailablePlugins().stream().map(plugin -> {
     293            String v = "";
     294            if (plugin.localversion != null && !plugin.localversion.trim().isEmpty()) {
     295                v = " (local: " + plugin.localversion + ")";
     296            }
     297            return new SearchItem(null, plugin.getName() + v, plugin.description, true, false);
     298        }).collect(Collectors.toList());*/
     299    }
     300
    239301}
  • src/org/openstreetmap/josm/gui/preferences/imagery/ImageryProvidersPanel.java

     
    778778        }
    779779        return false;
    780780    }
     781
     782    /*@Override
     783    public List<SearchItem> getSearchItemsAsync() {
     784        //layerInfo.load(true);
     785        return null;
     786    }*/
    781787}
  • 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/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 = 1; i < tabs.getTabCount(); i++) {
     51            width = Math.max(width, tabs.getBoundsAt(i).width); //TODO all width the same, only check one?
     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/SearchFilters.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.text.Normalizer;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.List;
     8import java.util.regex.Matcher;
     9import java.util.regex.Pattern;
     10import java.util.stream.Collectors;
     11
     12public class SearchFilters extends ArrayList<String> {
     13    private final List<String> searchStrsNormalized;
     14
     15    public SearchFilters(String filterStr) {
     16        String[] arr = filterStr.split(" ");
     17        this.addAll(Arrays.asList(arr));
     18        this.searchStrsNormalized = Arrays.stream(arr).map(SearchFilters::normalize).collect(Collectors.toList());
     19    }
     20
     21    public List<String> getNormalized() {
     22        return searchStrsNormalized;
     23    }
     24
     25    /**
     26     * Highlights the search string in the given item text.
     27     * Needs to be done manually to deal with accents/umlauts
     28     * @param itemStr
     29     * @param itemStrNormalized
     30     * @return
     31     */
     32    public String highlightString(String itemStr, String itemStrNormalized) {
     33        Pattern p = Pattern.compile("(?i)(" + String.join("|", searchStrsNormalized) + ")");
     34        Matcher m = p.matcher(itemStrNormalized);
     35        StringBuilder sb = null;
     36        int i = 0;
     37
     38        while (m.find()) {
     39            if (sb == null) {
     40                sb = new StringBuilder("<html>");
     41            }
     42            sb.append(itemStr.substring(i, m.start()))
     43                .append("<span style=\"color: ")
     44                .append(SearchPanel.DARK_MODE ? "yellow" : "red")
     45                .append("\">")
     46                .append(itemStr.substring(m.start(), m.end()))
     47                .append("</span>");
     48            i = m.end();
     49        }
     50
     51        if (sb == null) {
     52            return itemStr;
     53        }
     54
     55        sb.append(itemStr.substring(i))
     56            .append("</html>");
     57        return sb.toString();
     58    }
     59
     60    public static String normalize(String str) {
     61        if (str == null)
     62            return null;
     63
     64        String strLower = str.toLowerCase(SearchIndex.CURRENT_LOCALE);
     65
     66        String ret = Normalizer.normalize(strLower, Normalizer.Form.NFD)
     67                .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
     68
     69        if (ret.length() != str.length()) {
     70            return strLower;
     71        }
     72
     73        return ret;
     74    }
     75}
     76 No newline at end of file
  • src/org/openstreetmap/josm/gui/progress/swing/PleaseWaitProgressMonitor.java

     
    9696
    9797    private boolean cancelable;
    9898
     99    private boolean silent;
     100
    99101    /**
    100102     * Returns the progress monitor being currently displayed.
    101103     * @return the progress monitor being currently displayed
     
    197199        this.windowTitle = windowTitle;
    198200    }
    199201
     202    public PleaseWaitProgressMonitor(boolean silent) {
     203        this();
     204        this.silent = silent;
     205    }
     206
     207    public PleaseWaitProgressMonitor(String title, boolean silent) {
     208        this(title);
     209        this.silent = silent;
     210    }
     211
    200212    private final ActionListener cancelListener = e -> cancel();
    201213
    202214    private final ActionListener inBackgroundListener = e -> {
     
    234246    public void doBeginTask() {
    235247        doInEDT(() -> {
    236248            currentProgressMonitor = this;
    237             if (GraphicsEnvironment.isHeadless()) {
     249            if (silent || GraphicsEnvironment.isHeadless()) {
    238250                return;
    239251            }
    240252            if (dialogParent != null && dialog == null) {
  • src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java

     
    4949import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
    5050import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
    5151import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     52import org.openstreetmap.josm.gui.preferences.search.NotSearchablePanel;
    5253import org.openstreetmap.josm.gui.util.DocumentAdapter;
    5354import org.openstreetmap.josm.gui.util.GuiHelper;
    5455import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
     
    163164
    164165    @Override
    165166    public void addGui(final PreferenceTabbedPane gui) {
    166         JPanel p = gui.createPreferenceTab(this);
     167        JPanel panel = gui.createPreferenceTab(this);
    167168
     169        NotSearchablePanel p = new NotSearchablePanel(new GridBagLayout());
     170        panel.add(p, GBC.std().fill());
     171
    168172        final JPanel txtFilterPanel = new JPanel(new GridBagLayout());
    169173        p.add(txtFilterPanel, GBC.eol().fill(GBC.HORIZONTAL));
    170174        txtFilter = new FilterField();
  • 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/plugins/ReadLocalPluginInformationTask.java

     
    3939public class ReadLocalPluginInformationTask extends PleaseWaitRunnable {
    4040    private final Map<String, PluginInformation> availablePlugins;
    4141    private boolean canceled;
     42    private boolean hasSiteCacheFile;
    4243
    4344    /**
    4445     * Constructs a new {@code ReadLocalPluginInformationTask}.
     
    9697            monitor.setCustomText(tr("Processing file ''{0}''", fname));
    9798            try {
    9899                processLocalPluginInformationFile(f);
     100                hasSiteCacheFile = true;
    99101            } catch (PluginListParseException e) {
    100102                Logging.warn(tr("Failed to scan file ''{0}'' for plugin information. Skipping.", fname));
    101103                Logging.error(e);
     
    219221    }
    220222
    221223    /**
     224     * @return true if a local cache file was parsed
     225     */
     226    public boolean hasSiteCacheFile() {
     227        return hasSiteCacheFile;
     228    }
     229
     230    /**
    222231     * Replies true if the task was canceled by the user
    223232     *
    224233     * @return true if the task was canceled by the user
  • 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();
     
    280284    private static final PreferenceSettingFactory ADVANCED_PREFERENCE_FACTORY = new AdvancedPreference.Factory();
    281285    private final transient List<PreferenceSetting> settings = new ArrayList<>();
    282286
     287    public final SearchPanel searchPanel = new SearchPanel(this);
     288    private final SearchTextField searchTextField = new SearchTextField(searchPanel);
     289
    283290    // distinct list of tabs that have been initialized (we do not initialize tabs until they are displayed to speed up dialog startup)
    284291    private final transient List<PreferenceSetting> settingsInitialized = new ArrayList<>();
    285292
     
    464471     */
    465472    public PreferenceTabbedPane() {
    466473        super(SwingConstants.LEFT, JTabbedPane.SCROLL_TAB_LAYOUT);
    467         super.addMouseWheelListener(new WheelListener(this));
     474        MouseAndWheelListener l = new MouseAndWheelListener(this);
     475        super.addMouseWheelListener(l);
     476        super.addMouseListener(l);
    468477        ExpertToggleAction.addExpertModeChangeListener(this);
    469478    }
    470479
     
    525534    private void addGUITabs(boolean clear) {
    526535        boolean expert = ExpertToggleAction.isExpert();
    527536        if (clear) {
     537            /*for (int i = 1; i < getTabCount(); i++) {
     538                remove(i);
     539            }*/
    528540            removeAll();
    529541        }
     542
     543        addTab(null, searchPanel);
     544        setTabComponentAt(0, searchTextField);
    530545        // Compute max tab length in pixels
    531546        int maxWidth = computeMaxTabWidth();
    532547        // Inspect each tab setting
     
    558573                Logging.debug("{0}: hiding empty {1}", getClass().getSimpleName(), tps);
    559574            });
    560575        }
    561         setSelectedIndex(-1);
     576        searchTextField.adjustWidth();
     577        //setSelectedIndex(2);
    562578    }
    563579
    564580    private int computeMaxTabWidth() {
     
    627643     * This mouse wheel listener reacts when a scroll is carried out over the
    628644     * tab strip and scrolls one tab/down or up, selecting it immediately.
    629645     */
    630     static final class WheelListener implements MouseWheelListener {
     646    static final class MouseAndWheelListener implements MouseWheelListener, MouseListener {
    631647
    632648        final JTabbedPane tabbedPane;
    633649
    634         WheelListener(JTabbedPane tabbedPane) {
     650        MouseAndWheelListener(JTabbedPane tabbedPane) {
    635651            this.tabbedPane = tabbedPane;
    636652        }
    637653
     
    647663
    648664            tabbedPane.setSelectedIndex(newTab);
    649665        }
     666
     667        @Override
     668        public void mouseClicked(MouseEvent e) {
     669            tabbedPane.requestFocus();
     670        }
     671
     672        @Override
     673        public void mouseEntered(MouseEvent e) {
     674        }
     675
     676        @Override
     677        public void mouseExited(MouseEvent e) {
     678        }
     679
     680        @Override
     681        public void mousePressed(MouseEvent e) {
     682        }
     683
     684        @Override
     685        public void mouseReleased(MouseEvent e) {
     686        }
    650687    }
    651688
    652689    @Override
     
    654691        int index = getSelectedIndex();
    655692        Component sel = getSelectedComponent();
    656693        if (index > -1 && sel instanceof PreferenceTab) {
    657             PreferenceTab tab = (PreferenceTab) sel;
    658             TabPreferenceSetting preferenceSettings = tab.getTabPreferenceSetting();
    659             if (!settingsInitialized.contains(preferenceSettings)) {
    660                 try {
    661                     getModel().removeChangeListener(this);
    662                     preferenceSettings.addGui(this);
    663                     // Add GUI for sub preferences
    664                     for (PreferenceSetting setting : settings) {
    665                         if (setting instanceof SubPreferenceSetting) {
    666                             addSubPreferenceSetting(preferenceSettings, (SubPreferenceSetting) setting);
    667                         }
     694            initializeTab(index, (PreferenceTab) sel, false);
     695        }
     696    }
     697
     698    public void initializeTab(int index, PreferenceTab tab, boolean silent) {
     699        TabPreferenceSetting preferenceSettings = tab.getTabPreferenceSetting();
     700        if (!settingsInitialized.contains(preferenceSettings)) {
     701            try {
     702                getModel().removeChangeListener(this);
     703                preferenceSettings.addGui(this);
     704                // Add GUI for sub preferences
     705                for (PreferenceSetting setting : settings) {
     706                    if (setting instanceof SubPreferenceSetting) {
     707                        addSubPreferenceSetting(preferenceSettings, (SubPreferenceSetting) setting);
    668708                    }
    669                     Icon icon = getIconAt(index);
    670                     remove(index);
    671                     if (index <= insertGUITabsForSetting(icon, preferenceSettings, index, computeMaxTabWidth())) {
    672                         setSelectedIndex(index);
    673                     }
    674                 } catch (SecurityException ex) {
    675                     Logging.error(ex);
    676                 } catch (RuntimeException ex) { // NOPMD
    677                     // allow to change most settings even if e.g. a plugin fails
    678                     BugReportExceptionHandler.handleException(ex);
    679                 } finally {
    680                     settingsInitialized.add(preferenceSettings);
    681                     getModel().addChangeListener(this);
    682709                }
     710                Icon icon = getIconAt(index);
     711                remove(index);
     712                if (index <= insertGUITabsForSetting(icon, preferenceSettings, index, computeMaxTabWidth()) && !silent) {
     713                    setSelectedIndex(index);
     714                }
     715            } catch (SecurityException ex) {
     716                Logging.error(ex);
     717            } catch (RuntimeException ex) { // NOPMD
     718                // allow to change most settings even if e.g. a plugin fails
     719                BugReportExceptionHandler.handleException(ex);
     720            } finally {
     721                settingsInitialized.add(preferenceSettings);
     722                getModel().addChangeListener(this);
    683723            }
    684             Container ancestor = getTopLevelAncestor();
    685             if (ancestor instanceof PreferenceDialog) {
    686                 ((PreferenceDialog) ancestor).setHelpContext(preferenceSettings.getHelpContext());
    687             }
    688724        }
     725        Container ancestor = getTopLevelAncestor();
     726        if (ancestor instanceof PreferenceDialog) {
     727            ((PreferenceDialog) ancestor).setHelpContext(preferenceSettings.getHelpContext());
     728        }
    689729    }
    690730
    691731    private void addSubPreferenceSetting(TabPreferenceSetting preferenceSettings, SubPreferenceSetting sps) {
  • 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     *
     14     * default: true
     15     */
     16    default boolean isSearchable() {
     17        return true;
     18    }
     19
     20    /**
     21     * @return whether the children of this component should be traversed <br><br>
     22     *
     23     * default: same as {@link #isSearchable()} unless explicitly overridden
     24     */
     25    default boolean isChildrenSearchable() {
     26        return isSearchable();
     27    }
     28
     29    /**
     30     * Called from the EventDispatchThread (GUI), should not be blocked.
     31     *
     32     * @return the {@link SearchItem}s that should be used. Ignored if {@link #isSearchable()} returns {@code false}.<br>
     33     * Overrides the default {@link SearchTextFinder} if returning not {@code null} (including if an empty list is returned)<br><br>
     34     *
     35     * default: null
     36     */
     37    default List<SearchItem> getSearchItems() {
     38        return null;
     39    }
     40
     41    /**
     42     * Called from the worker thread, can be blocked as long as necessary. Calls to the GUI must be placed in the EventDispatchThread.
     43     *
     44     * @return the {@link SearchItem}s that should be used. Ignored if {@link #isSearchable()} returns {@code false}.<br>
     45     * Overrides the default {@link SearchTextFinder} if returning not {@code null} (including if an empty list is returned)<br><br>
     46     *
     47     * default: null
     48     */
     49    default List<SearchItem> getSearchItemsAsync() {
     50        return null;
     51    }
     52
     53    /**
     54     * @return
     55     */
     56    default String getIconName() {
     57        return null;
     58    }
     59
     60}
  • 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/SearchFilters.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.text.Normalizer;
     5import java.util.ArrayList;
     6import java.util.Arrays;
     7import java.util.List;
     8import java.util.regex.Matcher;
     9import java.util.regex.Pattern;
     10import java.util.stream.Collectors;
     11
     12public class SearchFilters extends ArrayList<String> {
     13    private final List<String> searchStrsNormalized;
     14
     15    public SearchFilters(String filterStr) {
     16        String[] arr = filterStr.split(" ");
     17        this.addAll(Arrays.asList(arr));
     18        this.searchStrsNormalized = Arrays.stream(arr).map(SearchFilters::normalize).collect(Collectors.toList());
     19    }
     20
     21    public List<String> getNormalized() {
     22        return searchStrsNormalized;
     23    }
     24
     25    /**
     26     * Highlights the search string in the given item text.
     27     * Needs to be done manually to deal with accents/umlauts
     28     * @param itemStr
     29     * @param itemStrNormalized
     30     * @return
     31     */
     32    public String highlightString(String itemStr, String itemStrNormalized) {
     33        Pattern p = Pattern.compile("(?i)(" + String.join("|", searchStrsNormalized) + ")");
     34        Matcher m = p.matcher(itemStrNormalized);
     35        StringBuilder sb = null;
     36        int i = 0;
     37
     38        while (m.find()) {
     39            if (sb == null) {
     40                sb = new StringBuilder("<html>");
     41            }
     42            sb.append(itemStr.substring(i, m.start()))
     43                .append("<span style=\"color: ")
     44                .append(SearchPanel.DARK_MODE ? "yellow" : "red")
     45                .append("\">")
     46                .append(itemStr.substring(m.start(), m.end()))
     47                .append("</span>");
     48            i = m.end();
     49        }
     50
     51        if (sb == null) {
     52            return itemStr;
     53        }
     54
     55        sb.append(itemStr.substring(i))
     56            .append("</html>");
     57        return sb.toString();
     58    }
     59
     60    public static String normalize(String str) {
     61        if (str == null)
     62            return null;
     63
     64        String strLower = str.toLowerCase(SearchIndex.CURRENT_LOCALE);
     65
     66        String ret = Normalizer.normalize(strLower, Normalizer.Form.NFD)
     67                .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
     68
     69        if (ret.length() != str.length()) {
     70            return strLower;
     71        }
     72
     73        return ret;
     74    }
     75}
     76 No newline at end of file
  • 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.lang.reflect.InvocationTargetException;
     10import java.util.ArrayList;
     11import java.util.Arrays;
     12import java.util.List;
     13import java.util.Locale;
     14import java.util.Stack;
     15import java.util.stream.Collectors;
     16
     17import javax.swing.JComponent;
     18import javax.swing.JPanel;
     19import javax.swing.JSeparator;
     20import javax.swing.SwingUtilities;
     21
     22import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     23import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferenceTab;
     24import org.openstreetmap.josm.gui.util.GuiHelper;
     25import org.openstreetmap.josm.tools.GBC;
     26import org.openstreetmap.josm.tools.LanguageInfo;
     27
     28/**
     29 * Contains all {@link SearchItem}s
     30 * @author Bjoeni
     31 */
     32public class SearchIndex {
     33    public static final Locale CURRENT_LOCALE = LanguageInfo.getLocale(LanguageInfo.getJOSMLocaleCode());
     34    public static final GBC EOL = GBC.eol();
     35
     36    Stack<SearchItem> stack = new Stack<>();
     37    List<SearchItem> parents = new ArrayList<>();
     38    PreferenceTabbedPane prefTabs;
     39    GridBagConstraints lastConstraint;
     40    private boolean built;
     41    private String lastFilterStr;
     42    private int currentTabIndex = -1;
     43
     44    private JPanel panel = GuiHelper.runInEDTAndWaitAndReturn(() -> new JPanel(new GridBagLayout()));
     45
     46    GridBagConstraints gbc_nw;
     47
     48    public SearchIndex(PreferenceTabbedPane prefTabs) {
     49        this.prefTabs = prefTabs;
     50        gbc_nw = GBC.eol().fill(GBC.HORIZONTAL);
     51        gbc_nw.anchor = GBC.NORTHWEST;
     52    }
     53
     54    void add(SearchItem item) {
     55        if (item.getText() == null)
     56            return;
     57
     58        item.setTabIndex(prefTabs, currentTabIndex);
     59        if (stack.isEmpty()) {
     60            item.setLevelInset(0, 0);
     61            parents.add(item);
     62            GuiHelper.runInEDT(() -> {
     63                getPanel().add(item.getComponent(), gbc_nw);
     64            });
     65            stack.push(item);
     66        } else {
     67            SearchItem lastItem = stack.lastElement();
     68            if (!lastItem.isEOL() && lastItem.level == item.level) {
     69                lastItem.setEOL(item.isEOL());
     70                lastItem.addChild(item);
     71            } else if (lastItem.level < item.level
     72                    || (lastItem.level == item.level && lastItem.inset + 15 < item.inset)) {
     73                stack.push(lastItem.addChild(item));
     74            } else {
     75                stack.pop();
     76                add(item);
     77            }
     78        }
     79    }
     80
     81    void addAll(List<SearchItem> items, boolean isEOL) {
     82        if (items.size() > 0) {
     83            items.get(items.size() - 1).setEOL(isEOL);
     84        }
     85        items.forEach(item -> add(item));
     86    }
     87
     88    void insertEOL() {
     89        if (!stack.isEmpty()) {
     90            stack.lastElement().setEOL();
     91        }
     92    }
     93
     94    void insertSeparator() {
     95        if (!stack.isEmpty()) {
     96            stack.lastElement().maximizeInset();
     97        }
     98    }
     99
     100    void insertNewPage() {
     101        stack.clear();
     102    }
     103
     104    public boolean isBuilt() {
     105        return built;
     106    }
     107
     108    /**
     109     * @throws InvocationTargetException exception while initializing tab
     110     * @throws InterruptedException thread interrupted
     111     */
     112    public void build() throws InvocationTargetException, InterruptedException {
     113        //setVisible(false);
     114        built = false;
     115        parents.clear();
     116        getPanel().removeAll();
     117        for (int i = 0; i < prefTabs.getTabCount(); i++) {
     118            Component c = prefTabs.getComponentAt(i);
     119            currentTabIndex = i;
     120            String iconName = null;
     121            if (c instanceof PreferenceTab) {
     122                iconName = ((PreferenceTab) c).getTabPreferenceSetting().getIconName();
     123                final int index = i;
     124                final Component comp = c;
     125                SwingUtilities.invokeAndWait(() -> prefTabs.initializeTab(index, (PreferenceTab) comp, true));
     126                c = prefTabs.getComponentAt(i);
     127            }
     128            insertNewPage();
     129            searchComponent(c, 1, iconName);
     130        }
     131
     132        built = true;
     133        //setVisible(true);
     134    }
     135
     136    public boolean filter(String filterStr) {
     137        boolean found = false;
     138
     139        if (lastFilterStr != null && filterStr.indexOf(lastFilterStr) != 0) {
     140            parents.forEach(SearchItem::showAll);
     141        }
     142
     143        if (!filterStr.isEmpty()) {
     144            SearchFilters filters = new SearchFilters(filterStr);
     145            found = parents.stream()
     146                    .filter(SearchItem::isVisible)
     147                    .map(item -> item.filter(filters))
     148                    .collect(Collectors.toList()) //force all visible elements to be evaluated
     149                    .contains(true);
     150        }
     151
     152        lastFilterStr = filterStr;
     153        return found;
     154    }
     155
     156    /*public DefaultMutableTreeNode getTreeView() {
     157        return tree;
     158    }
     159
     160    public synchronized DefaultMutableTreeNode createTreeView(DefaultTreeModel model) {
     161        tree.removeAllChildren();
     162        parents.forEach(child -> {
     163            MutableTreeNode t = child.createTreeView(model);
     164            if (t != null) {
     165                model.insertNodeInto(t, tree, tree.getChildCount());
     166                //tree.add(t);
     167            }
     168        });
     169        return tree;
     170    }*/
     171
     172    private boolean searchComponent(Component comp, int level, String iconName)
     173            throws InvocationTargetException, InterruptedException {
     174
     175        final Component c = comp;
     176
     177        boolean isEOL = true;
     178        GridBagConstraints currentConstraint = null;
     179
     180        Container p = c.getParent();
     181        if (p != null) {
     182            LayoutManager layout = p.getLayout();
     183            if (layout != null && layout instanceof GridBagLayout) {
     184                GridBagLayout grid = (GridBagLayout) layout;
     185
     186                currentConstraint = grid.getConstraints(c);
     187                isEOL = currentConstraint.gridwidth == GridBagConstraints.REMAINDER;
     188            }
     189        }
     190
     191        if (lastConstraint != null && currentConstraint != null
     192                && (((lastConstraint.fill == GridBagConstraints.HORIZONTAL
     193                        || lastConstraint.fill == GridBagConstraints.BOTH)
     194                        && currentConstraint.fill != GridBagConstraints.HORIZONTAL
     195                        && currentConstraint.fill != GridBagConstraints.BOTH)
     196                        || lastConstraint.gridy != currentConstraint.gridy)) {
     197            insertEOL();
     198        }
     199
     200        lastConstraint = currentConstraint;
     201        boolean isChildrenSearchable = true;
     202        ISearchableComponent s = null;
     203        if (c instanceof ISearchableComponent) {
     204            s = (ISearchableComponent) c;
     205            isChildrenSearchable = s.isChildrenSearchable();
     206        }
     207
     208        if (s == null || s.isSearchable()) {
     209
     210            List<SearchItem> items = null;
     211            if (s != null) {
     212                items = s.getSearchItemsAsync();
     213                final ISearchableComponent s2 = s;
     214                List<SearchItem> itm2 = GuiHelper.runInEDTAndWaitAndReturn(() -> s2.getSearchItems());
     215                if (itm2 != null) {
     216                    if (items == null) {
     217                        items = itm2;
     218                    } else {
     219                        items.addAll(itm2);
     220                    }
     221                }
     222
     223                if (iconName == null) {
     224                    iconName = s.getIconName();
     225                }
     226
     227                /*final SwingTransferItem swing = new SwingTransferItem(s);
     228                SwingUtilities.invokeAndWait(() -> {
     229                    swing.list = swing.component.getSearchItems();
     230                });
     231
     232                if (swing.list != null) {
     233                    items = swing.list;
     234                }*/
     235            }
     236            if (items == null) {
     237                final List<SearchItem> itm = new ArrayList<>();
     238                SwingUtilities.invokeAndWait(() -> {
     239                    SearchTextFinder.DEFAULT_SEARCH_TEXT_FINDERS
     240                            .forEach(finder -> itm.addAll(finder.getSearchItems(c)));
     241                });
     242
     243                items = itm;
     244            }
     245            if (iconName != null) {
     246                final String iconName2 = iconName;
     247                items.forEach(item -> item.setIconName(iconName2));
     248            }
     249            if (items.size() == 0) {
     250                if (isEOL) {
     251                    insertEOL();
     252                }
     253                if (c instanceof JSeparator) {
     254                    insertSeparator();
     255                }
     256
     257            } else {
     258                items.get(0).addOriginalComponent(c);
     259                if (c instanceof JComponent) {
     260                    items.get(0).setTooltip(((JComponent) c).getToolTipText());
     261                }
     262
     263                final int inset = currentConstraint == null ? 0 : currentConstraint.insets.left;
     264                items.forEach(item -> item.setLevelInset(level, inset));
     265                isChildrenSearchable = isChildrenSearchable
     266                        && items.stream().allMatch(item -> item.isChildComponentsSearchable());
     267
     268                addAll(items, isEOL);
     269            }
     270        }
     271
     272        if (isChildrenSearchable && c instanceof Container) {
     273            Container cont = (Container) c;
     274            List<Component> components = Arrays.asList(cont.getComponents());
     275            if (components.size() > 0) {
     276                insertEOL();
     277                for (Component component : components) {
     278                    searchComponent(component, level + 1, iconName);
     279                }
     280            }
     281        }
     282        return isEOL;
     283    }
     284
     285    /**
     286     * @return the panel
     287     */
     288    public JPanel getPanel() {
     289        return panel;
     290    }
     291
     292    public void setVisible(boolean visible) {
     293        GuiHelper.runInEDT(() -> {
     294            panel.setVisible(visible);
     295        });
     296    }
     297
     298    public void updateComponents() throws InvocationTargetException, InterruptedException {
     299        for (SearchItem item : parents) {
     300            item.updateComponents();
     301        }
     302    }
     303
     304}
     305 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.Color;
     5import java.awt.Component;
     6import java.awt.Rectangle;
     7import java.lang.reflect.InvocationTargetException;
     8import java.util.ArrayList;
     9import java.util.List;
     10import java.util.Objects;
     11import java.util.Optional;
     12
     13import javax.swing.ImageIcon;
     14import javax.swing.JComponent;
     15import javax.swing.SwingUtilities;
     16import javax.swing.Timer;
     17
     18import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     19import org.openstreetmap.josm.gui.util.GuiHelper;
     20import org.openstreetmap.josm.tools.ImageProvider;
     21import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
     22import org.openstreetmap.josm.tools.Logging;
     23
     24/**
     25 * Contains searchable items and their components
     26 * @author Bjoeni
     27 */
     28public class SearchItem {
     29    int level;
     30    int inset;
     31    private String text;
     32    private String tooltip;
     33    private String highlight;
     34    private List<SearchItem> children = new ArrayList<>();
     35    private List<Component> originalComponents = new ArrayList<>();
     36
     37    private int tabIndex;
     38    private boolean eol = true;
     39    private boolean visible = true;
     40    private boolean hasVisibleChildren;
     41    private boolean childComponentsSearchable = true;
     42    private String iconName;
     43    private String textNormalized;
     44    private String tooltipNormalized;
     45    private PreferenceTabbedPane tabs;
     46    private SearchItemComponent comp;
     47
     48    public SearchItem(String text) {
     49        this.setText(text);
     50    }
     51
     52    public SearchItem(Component originalComponent, String text, String tooltip, boolean eol,
     53            boolean childComponentsSearchable) {
     54        this.originalComponents.add(originalComponent);
     55        this.setText(text);
     56        this.setTooltip(tooltip);
     57        this.setEOL(eol);
     58        this.childComponentsSearchable = childComponentsSearchable;
     59    }
     60
     61    public SearchItem addChild(SearchItem item) {
     62        synchronized (children) {
     63            Optional<SearchItem> match = children.stream()
     64                    .filter(c -> Objects.equals(c.text, item.text) && c.level == item.level && c.inset == item.inset)
     65                    .findAny();
     66            if (match.isPresent()) {
     67                match.get().merge(item);
     68                return match.get();
     69            } else {
     70                children.add(item);
     71                GuiHelper.runInEDT(() -> {
     72                    getComponent().addChild(item.getComponent());
     73                });
     74                return item;
     75            }
     76        }
     77
     78    }
     79
     80    public void merge(SearchItem item) {
     81        this.originalComponents.addAll(item.originalComponents);
     82        this.setEOL(this.isEOL() || item.isEOL());
     83    }
     84
     85    public boolean filter(SearchFilters filters) {
     86        visible = this.matches(filters);
     87        hasVisibleChildren = false;
     88        for (SearchItem child : children) {
     89            if (child.isVisible()) {
     90                hasVisibleChildren = child.filter(filters) || hasVisibleChildren;
     91            }
     92        }
     93        visible = visible || hasVisibleChildren;
     94
     95        if (visible && text != null && textNormalized != null) {
     96            highlight = filters.highlightString(text, textNormalized);
     97        } else {
     98            highlight = "<html><span style=\"color: red;\"><b>ERROR</b></span></html>";
     99        }
     100        return visible;
     101    }
     102
     103    public boolean matches(SearchFilters filters) {
     104        return filters.getNormalized().stream()
     105                .allMatch(str -> textNormalized != null && textNormalized.indexOf(str) != -1
     106                        || (tooltipNormalized != null && tooltipNormalized.indexOf(str) != -1));
     107    }
     108
     109    public void showAll() {
     110        visible = true;
     111        children.forEach(SearchItem::showAll);
     112    }
     113
     114    @Override
     115    public String toString() {
     116        return text + (tooltip == null ? "" : " (Tooltip: " + tooltip + ")") + " [" + level + "." + inset
     117                + (eol ? ":EOL" : "") + "] {" + originalComponents.size() + "}";
     118    }
     119
     120    public ImageIcon getIcon() {
     121        if (iconName == null)
     122            return null;
     123
     124        return iconName == null || iconName.isEmpty() ? null
     125                : iconName.contains("/") ? ImageProvider.get(iconName, ImageSizes.SMALLICON)
     126                        : ImageProvider.get("preferences", iconName, ImageSizes.SMALLICON);
     127    }
     128
     129    public boolean isVisible() {
     130        return visible;
     131    }
     132
     133    public boolean isEOL() {
     134        return eol;
     135    }
     136
     137    public void setEOL() {
     138        setEOL(true);
     139    }
     140
     141    public void setEOL(boolean eol) {
     142        this.eol = eol;
     143    }
     144
     145    public void setLevelInset(int level, int inset) {
     146        this.level = level;
     147        this.inset = inset;
     148    }
     149
     150    public void maximizeInset() {
     151        this.inset = Integer.MAX_VALUE;
     152    }
     153
     154    public void setTabIndex(PreferenceTabbedPane tabs, int tabIndex) {
     155        this.tabs = tabs;
     156        this.tabIndex = tabIndex;
     157    }
     158
     159    public void setIconName(String iconName) {
     160        this.iconName = iconName;
     161    }
     162
     163    public void addOriginalComponent(Component c) {
     164        this.originalComponents.add(c);
     165    }
     166
     167    public void showOriginalComponent() {
     168        if (tabIndex > -1) {
     169            tabs.setSelectedIndex(tabIndex);
     170        }
     171        originalComponents.stream().filter(Objects::nonNull).forEach(comp -> {
     172            //Color bg = comp.getBackground();
     173            Color fg = comp.getForeground();
     174            //Logging.debug("BG:" + bg.toString());
     175            Logging.debug("FG:" + fg.toString());
     176            //comp.setBackground(SystemColor.text);
     177            comp.setForeground(SearchPanel.DARK_MODE ? Color.yellow : Color.red);
     178            comp.requestFocus();
     179            if (comp instanceof JComponent) {
     180                JComponent jcomp = (JComponent) comp;
     181                Rectangle bounds = new Rectangle(jcomp.getBounds());
     182                jcomp.scrollRectToVisible(bounds);
     183            }
     184            Timer timer = new Timer(3000, l -> {
     185                //comp.setBackground(bg);
     186                comp.setForeground(fg);
     187            });
     188            timer.setRepeats(false);
     189            timer.start();
     190        });
     191        if (originalComponents.stream().filter(Objects::nonNull).noneMatch(Component::isVisible)) {
     192            Logging.warn("INVIS!!");
     193        }
     194    }
     195
     196    /**
     197     * @return the component
     198     */
     199    public SearchItemComponent getComponent() {
     200        if (comp == null) {
     201            comp = GuiHelper.runInEDTAndWaitAndReturn(() -> new SearchItemComponent(this));
     202        }
     203        return comp;
     204    }
     205
     206    public void updateComponents() throws InvocationTargetException, InterruptedException {
     207        SwingUtilities.invokeAndWait(() -> {
     208            //invokeLater is faster but causes too many calls at once and blocks the UI
     209            SearchItemComponent c = getComponent();
     210            c.setVisible(this.isVisible());
     211            if (isVisible()) {
     212                c.setToolTipText(toString());
     213                c.setText(highlight);
     214                c.setExpanded(true);
     215            }
     216        });
     217        if (isVisible()) {
     218            for (SearchItem child : children) {
     219                child.updateComponents();
     220            }
     221        }
     222    }
     223
     224    /**
     225     * @return the hasVisibleChildren
     226     */
     227    public boolean hasVisibleChildren() {
     228        return hasVisibleChildren;
     229    }
     230
     231    /**
     232     * @return the childComponentsSearchable
     233     */
     234    public boolean isChildComponentsSearchable() {
     235        return childComponentsSearchable;
     236    }
     237
     238    /**
     239     * @return the text2
     240     */
     241    public String getText() {
     242        return text;
     243    }
     244
     245    /**
     246     * @param text the text to set
     247     */
     248    public void setText(String text) {
     249        this.text = stripHtml(text);
     250        this.textNormalized = SearchFilters.normalize(this.text);
     251    }
     252
     253    /**
     254     * @return the tooltip
     255     */
     256    public String getTooltip() {
     257        return tooltip;
     258    }
     259
     260    public void addTooltip(String tooltip) {
     261        if (this.getTooltip() == null || this.getTooltip().trim().isEmpty()) {
     262            this.setTooltip(tooltip);
     263        }
     264    }
     265
     266    /**
     267     * @param tooltip the tooltip to set
     268     */
     269    public void setTooltip(String tooltip) {
     270        this.tooltip = stripHtml(tooltip);
     271        this.tooltipNormalized = SearchFilters.normalize(this.tooltip);
     272    }
     273
     274    private static String stripHtml(String str) {
     275        if (str == null || str.trim().length() == 0)
     276            return null;
     277
     278        if (str.indexOf('<') > -1) {
     279            str = str.replaceAll("(?i)(<br\\s/>\\s*)+", ": ").replaceAll("(<style>.*</style>|<[^<>]*>)", " ").trim()
     280                    .replaceAll(" +", " ");
     281        }
     282
     283        if (str.indexOf(':') == str.length() - 1) {
     284            str = str.substring(0, str.length() - 1);
     285        }
     286
     287        return str;
     288    }
     289
     290}
     291 No newline at end of file
  • src/org/openstreetmap/josm/gui/preferences/search/SearchItemComponent.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.preferences.search;
     3
     4import java.awt.BasicStroke;
     5import java.awt.Color;
     6import java.awt.Cursor;
     7import java.awt.Graphics;
     8import java.awt.Graphics2D;
     9import java.awt.GridBagLayout;
     10import java.awt.RenderingHints;
     11import java.awt.event.MouseAdapter;
     12import java.awt.event.MouseEvent;
     13
     14import javax.swing.BorderFactory;
     15import javax.swing.ImageIcon;
     16import javax.swing.JLabel;
     17import javax.swing.JPanel;
     18
     19import org.openstreetmap.josm.tools.GBC;
     20import org.openstreetmap.josm.tools.ImageProvider;
     21import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
     22
     23public class SearchItemComponent extends JPanel {
     24
     25    private final GridBagLayout own_gbl = new GridBagLayout();
     26    private final JLabel lbl = new JLabel();
     27    private final JLabel expander = new JLabel();
     28    private JPanel childPanel;
     29    private final SearchItem item;
     30
     31    private GridBagLayout parent_gbl;
     32    private boolean expanded = true;
     33    private GBC gbc_nw;
     34    private GBC gbc_nw_fill;
     35
     36    private static final ImageIcon ICON_EXPANDED = ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON);
     37    private static final ImageIcon ICON_COLLAPSED = ImageProvider.get("dialogs", "next", ImageSizes.SMALLICON);
     38
     39    private static final Cursor CURSOR_HAND = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
     40    private static final Cursor CURSOR_DEFAULT = Cursor.getDefaultCursor();
     41
     42    public SearchItemComponent(SearchItem item) {
     43        super(new GridBagLayout());
     44        setVisible(false);
     45        this.item = item;
     46        add(expander);
     47
     48        gbc_nw = GBC.eol();
     49        gbc_nw.anchor = GBC.NORTHWEST;
     50        gbc_nw_fill = GBC.eol().fill(GBC.HORIZONTAL);
     51        gbc_nw_fill.anchor = GBC.NORTHWEST;
     52        add(lbl, gbc_nw_fill);
     53        lbl.setCursor(CURSOR_HAND);
     54        lbl.setBackground(Color.cyan);
     55        //lbl.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
     56        setBorder(BorderFactory.createEmptyBorder(10, item.level == 0 ? 10 : 17, item.level == 0 ? 25 : 0, 10));
     57
     58        expander.addMouseListener(new MouseAdapter() {
     59            @Override
     60            public void mouseClicked(MouseEvent e) {
     61                setExpanded(!isExpanded());
     62            }
     63        });
     64
     65        addMouseListener(new MouseAdapter() {
     66            @Override
     67            public void mouseClicked(MouseEvent e) {
     68                item.showOriginalComponent();
     69            }
     70        });
     71    }
     72
     73    public void setText(String txt) {
     74        lbl.setText(txt);
     75    }
     76
     77    @Override
     78    protected void paintComponent(Graphics graphics) {
     79        super.paintComponent(graphics);
     80
     81        if (item.level != 0)
     82            return;
     83
     84        Graphics2D g = (Graphics2D) graphics;
     85        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     86
     87        int width = getWidth(),
     88                height = getHeight(),
     89                stroke = 1,
     90                offset = 5,
     91                radius = 15;
     92
     93
     94        g.setColor(new Color(0, 0, 0, 125));
     95        g.fillRoundRect(offset, offset, width - stroke - offset, height - stroke - offset - 10, radius, radius);
     96
     97        g.setColor(getBackground());
     98        g.fillRoundRect(0, 0, width - 5, height - 15, radius, radius);
     99
     100        g.setColor(getForeground());
     101        g.setStroke(new BasicStroke(stroke));
     102        g.drawRoundRect(0, 0, width - 5, height - 15, radius, radius);
     103
     104        g.setStroke(new BasicStroke());
     105    }
     106
     107    /**
     108     * @return the expanded
     109     */
     110    public boolean isExpanded() {
     111        return expanded;
     112    }
     113
     114    /**
     115     * @param expanded the expanded to set
     116     */
     117    public void setExpanded(boolean expanded) {
     118        if (item.hasVisibleChildren()) {
     119            this.expanded = expanded;
     120            expander.setIcon(expanded ? ICON_EXPANDED : ICON_COLLAPSED);
     121            expander.setCursor(CURSOR_HAND);
     122            getChildPanel().setVisible(expanded);
     123        } else {
     124            expander.setIcon(ICON_COLLAPSED);
     125        }
     126    }
     127
     128    /**
     129     * @return the childPanel
     130     */
     131    public JPanel getChildPanel() {
     132        if (childPanel == null) {
     133            childPanel = new JPanel(own_gbl);
     134            add(childPanel, gbc_nw_fill);
     135        }
     136        return childPanel;
     137    }
     138
     139    public void addChild(SearchItemComponent comp) {
     140        getChildPanel().add(comp, gbc_nw_fill);
     141        comp.setParentGBL(own_gbl);
     142    }
     143
     144    /**
     145     * @param parent_gbl the parent_gbl to set
     146     */
     147    public void setParentGBL(GridBagLayout parent_gbl) {
     148        this.parent_gbl = parent_gbl;
     149    }
     150
     151}
  • 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.awt.GridBagLayout;
     7import java.awt.SystemColor;
     8import java.lang.reflect.InvocationTargetException;
     9
     10import javax.swing.BorderFactory;
     11import javax.swing.JLabel;
     12import javax.swing.JPanel;
     13import javax.swing.JScrollPane;
     14import javax.swing.SwingConstants;
     15import javax.swing.SwingUtilities;
     16
     17import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
     18import org.openstreetmap.josm.tools.GBC;
     19import org.openstreetmap.josm.tools.JosmRuntimeException;
     20import org.openstreetmap.josm.tools.Logging;
     21
     22/**
     23 * Panel displaying search results in the preferences
     24 * @author Bjoeni
     25 */
     26public class SearchPanel extends JScrollPane implements ISearchableComponent {
     27
     28    public static final boolean DARK_MODE = 130 < (Math.sqrt(Math.pow(SystemColor.text.getRed(), 2) * .241
     29            + Math.pow(SystemColor.text.getGreen(), 2) * .691 + Math.pow(SystemColor.text.getBlue(), 2) * .068));
     30
     31    private PreferenceTabbedPane tabs;
     32    private SearchIndex searchIndex;
     33    private JLabel lblStatus = new JLabel(tr("Enter something in the text fied to start searching"));
     34    private JPanel panel = new JPanel(new GridBagLayout());
     35    private SearchThread thread;
     36
     37    public SearchPanel(PreferenceTabbedPane tabs) {
     38        super(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
     39        this.tabs = tabs;
     40        searchIndex = new SearchIndex(tabs);
     41        GBC gbc = GBC.eol().fill(GBC.HORIZONTAL);
     42        gbc.anchor = GBC.NORTHWEST;
     43        gbc.weightx = 1;
     44        panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
     45        panel.add(searchIndex.getPanel(), gbc);
     46        gbc.gridy = 1;
     47        lblStatus.setHorizontalAlignment(SwingConstants.CENTER);
     48        gbc.fill();
     49        gbc.anchor = GBC.CENTER;
     50        panel.add(lblStatus, gbc);
     51        //setAlignmentX(LEFT_ALIGNMENT);
     52        setViewportView(panel);
     53    }
     54
     55    public void search(String text) {
     56        if (thread != null && thread.isAlive()) {
     57            Logging.debug("alive");
     58            thread.awaitBuildAndRestart(text);
     59        } else {
     60            Logging.debug("dead");
     61            thread = new SearchThread(text);
     62            thread.start();
     63        }
     64    }
     65
     66    public PreferenceTabbedPane getTabPane() {
     67        return tabs;
     68    }
     69
     70    public void destroyIndex() {
     71        removeAll();
     72        panel = null;
     73        lblStatus = null;
     74        setViewport(null);
     75        this.searchIndex = null;
     76    }
     77
     78    @Override
     79    public boolean isSearchable() {
     80        return false;
     81    }
     82
     83    class SearchThread extends Thread {
     84        private String text;
     85        private StringBuilder searchableSettings;
     86        private boolean building;
     87        private boolean interrupted;
     88        private String nextText;
     89        private boolean justBuiltIndex;
     90
     91        public SearchThread(String text) {
     92            this.text = text;
     93        }
     94
     95        public SearchThread(String text, boolean justBuiltIndex) {
     96            this.text = text;
     97            this.justBuiltIndex = justBuiltIndex;
     98        }
     99
     100        @Override
     101        public void run() {
     102            try {
     103                boolean buildIndex = !searchIndex.isBuilt(); // || "".equals(text);
     104                if (buildIndex) {
     105                    building = true;
     106                    Logging.info("Building search index...");
     107                    setText(tr("Building search index..."));
     108                    searchIndex.build();
     109                    building = false;
     110                    if (interrupted) {
     111                        startNext(true);
     112                        return;
     113                    }
     114                }
     115
     116                if (buildIndex || justBuiltIndex) {
     117                    setText(tr("Searching..."));
     118                }
     119
     120                //if (!"".equals(text)) {
     121                Logging.debug("searching \"" + text + "\"");
     122                boolean found = searchIndex.filter(text);
     123                Logging.debug("searched \"" + text + "\"");
     124                //}
     125
     126                Logging.debug("before \"" + text + "\"");
     127                searchIndex.updateComponents();
     128                Logging.debug("done \"" + text + "\"");
     129
     130                String txt = "";
     131                if (!found) {
     132                    txt += tr("No results found.") + "<br><br>";
     133                }
     134                if (false) { //expertmode
     135                    txt += tr("You might get more results by enabling expert mode.");
     136                }
     137                setText(txt);
     138
     139            } catch (InterruptedException e) {
     140                Logging.debug("Interrupted search thread \"" + text + "\"");
     141                // to be expected
     142            } catch (InvocationTargetException e) {
     143                throw new JosmRuntimeException(e);
     144            }
     145        }
     146
     147        public void awaitBuildAndRestart(String nextText) {
     148            this.nextText = nextText;
     149            if (building) {
     150                interrupted = true;
     151            } else {
     152                interrupt();
     153                startNext(false);
     154            }
     155        }
     156
     157        private void startNext(boolean justBuiltIndex) {
     158            thread = new SearchThread(nextText, justBuiltIndex);
     159            thread.start();
     160        }
     161
     162        private void setText(String txt) {
     163            SwingUtilities.invokeLater(() -> {
     164                lblStatus.setText(txt);
     165            });
     166        }
     167    }
     168
     169}
  • 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 = 1; i < tabs.getTabCount(); i++) {
     51            width = Math.max(width, tabs.getBoundsAt(i).width); //TODO all width the same, only check one?
     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                    ret.addAll(items);
     136                    /*items.forEach(item -> {
     137                        if (item.getText() != null && item.getText().trim().length() > 0) {
     138                            String txt = item.getText();
     139                            if (txt.indexOf('<') != -1) {
     140                                txt = stripHtml(txt);
     141                            }
     142                            if (txt.indexOf(':') == txt.length() - 1) {
     143                                txt = txt.substring(0, item.getText().length() - 1);
     144                            }
     145                            item.setText(txt);
     146                            if (item.getTooltip() != null && item.getTooltip().indexOf('<') != -1) {
     147                                item.setTooltip(stripHtml(item.getTooltip()));
     148                            }
     149                            ret.add(item);
     150                        }
     151                    });*/
     152                });
     153            }
     154
     155        } catch (ClassCastException ex) {
     156            // can't use 'instanceof' here, because the type information of T will already be stripped away at runtime
     157        }
     158        return ret;
     159    }
     160
     161    public final static List<SearchTextFinder<?>> DEFAULT_SEARCH_TEXT_FINDERS = Collections.unmodifiableList(Arrays.asList(
     162
     163                    new SearchTextFinder<>(JLabel::getText),
     164                    new SearchTextFinder<>(JRadioButton::getText),
     165                    new SearchTextFinder<>(JCheckBox::getText),
     166
     167                    new SearchTextFinder<JTable>().addItem(table -> {
     168                        List<SearchItem> lst = new ArrayList<>();
     169                        TableModel tm = table.getModel();
     170                        for (int row = 0; row < tm.getRowCount(); row++) {
     171                            for (int col = 0; col < tm.getColumnCount(); col++) {
     172                                Object obj = tm.getValueAt(row, col);
     173                                Component renderer = table.getCellRenderer(row, col)
     174                                        .getTableCellRendererComponent(table, obj, false, false, row, col);
     175                                if (renderer instanceof JLabel) {
     176                                    JLabel label = (JLabel) renderer;
     177                                    boolean isLast = col == tm.getColumnCount() - 1;
     178                                    lst.add(new SearchItem(label, label.getText(), label.getToolTipText(), isLast, false));
     179                                }
     180                            }
     181                        }
     182                        return lst.toArray(new SearchItem[0]);
     183                    }),
     184
     185                    new SearchTextFinder<JComboBox<Object>>().addItem(combobox -> {
     186                        List<SearchItem> lst = new ArrayList<>();
     187                        int size = combobox.getItemCount();
     188                        JList<Object> listStub = new JList<>();
     189                        for (int i = 0; i < size; i++) {
     190                            Component renderer = combobox.getRenderer().getListCellRendererComponent(listStub,
     191                                    combobox.getItemAt(i), i, false, false);
     192                            if (renderer instanceof JLabel) {
     193                                JLabel label = (JLabel) renderer;
     194                                lst.add(new SearchItem(renderer, label.getText(), label.getToolTipText(), false, false));
     195                            }
     196                        }
     197                        return lst.toArray(new SearchItem[0]);
     198                    })));
     199
     200}
     201 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/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                    ret.addAll(items);
     136                    /*items.forEach(item -> {
     137                        if (item.getText() != null && item.getText().trim().length() > 0) {
     138                            String txt = item.getText();
     139                            if (txt.indexOf('<') != -1) {
     140                                txt = stripHtml(txt);
     141                            }
     142                            if (txt.indexOf(':') == txt.length() - 1) {
     143                                txt = txt.substring(0, item.getText().length() - 1);
     144                            }
     145                            item.setText(txt);
     146                            if (item.getTooltip() != null && item.getTooltip().indexOf('<') != -1) {
     147                                item.setTooltip(stripHtml(item.getTooltip()));
     148                            }
     149                            ret.add(item);
     150                        }
     151                    });*/
     152                });
     153            }
     154
     155        } catch (ClassCastException ex) {
     156            // can't use 'instanceof' here, because the type information of T will already be stripped away at runtime
     157        }
     158        return ret;
     159    }
     160
     161    public final static List<SearchTextFinder<?>> DEFAULT_SEARCH_TEXT_FINDERS = Collections.unmodifiableList(Arrays.asList(
     162
     163                    new SearchTextFinder<>(JLabel::getText),
     164                    new SearchTextFinder<>(JRadioButton::getText),
     165                    new SearchTextFinder<>(JCheckBox::getText),
     166
     167                    new SearchTextFinder<JTable>().addItem(table -> {
     168                        List<SearchItem> lst = new ArrayList<>();
     169                        TableModel tm = table.getModel();
     170                        for (int row = 0; row < tm.getRowCount(); row++) {
     171                            for (int col = 0; col < tm.getColumnCount(); col++) {
     172                                Object obj = tm.getValueAt(row, col);
     173                                Component renderer = table.getCellRenderer(row, col)
     174                                        .getTableCellRendererComponent(table, obj, false, false, row, col);
     175                                if (renderer instanceof JLabel) {
     176                                    JLabel label = (JLabel) renderer;
     177                                    boolean isLast = col == tm.getColumnCount() - 1;
     178                                    lst.add(new SearchItem(label, label.getText(), label.getToolTipText(), isLast, false));
     179                                }
     180                            }
     181                        }
     182                        return lst.toArray(new SearchItem[0]);
     183                    }),
     184
     185                    new SearchTextFinder<JComboBox<Object>>().addItem(combobox -> {
     186                        List<SearchItem> lst = new ArrayList<>();
     187                        int size = combobox.getItemCount();
     188                        JList<Object> listStub = new JList<>();
     189                        for (int i = 0; i < size; i++) {
     190                            Component renderer = combobox.getRenderer().getListCellRendererComponent(listStub,
     191                                    combobox.getItemAt(i), i, false, false);
     192                            if (renderer instanceof JLabel) {
     193                                JLabel label = (JLabel) renderer;
     194                                lst.add(new SearchItem(renderer, label.getText(), label.getToolTipText(), false, false));
     195                            }
     196                        }
     197                        return lst.toArray(new SearchItem[0]);
     198                    })));
     199
     200}
     201 No newline at end of file