001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Component; 008import java.awt.Container; 009import java.awt.Font; 010import java.awt.FontMetrics; 011import java.awt.GridBagConstraints; 012import java.awt.GridBagLayout; 013import java.awt.event.MouseWheelEvent; 014import java.awt.event.MouseWheelListener; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Collections; 018import java.util.HashSet; 019import java.util.Iterator; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.NoSuchElementException; 023import java.util.Objects; 024import java.util.OptionalInt; 025import java.util.Set; 026import java.util.function.Predicate; 027import java.util.stream.IntStream; 028 029import javax.swing.BorderFactory; 030import javax.swing.Icon; 031import javax.swing.ImageIcon; 032import javax.swing.JLabel; 033import javax.swing.JOptionPane; 034import javax.swing.JPanel; 035import javax.swing.JScrollPane; 036import javax.swing.JTabbedPane; 037import javax.swing.SwingConstants; 038import javax.swing.UIManager; 039import javax.swing.event.ChangeEvent; 040import javax.swing.event.ChangeListener; 041 042import org.openstreetmap.josm.actions.ExpertToggleAction; 043import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener; 044import org.openstreetmap.josm.actions.RestartAction; 045import org.openstreetmap.josm.gui.HelpAwareOptionPane; 046import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 047import org.openstreetmap.josm.gui.MainApplication; 048import org.openstreetmap.josm.gui.preferences.advanced.AdvancedPreference; 049import org.openstreetmap.josm.gui.preferences.audio.AudioPreference; 050import org.openstreetmap.josm.gui.preferences.display.ColorPreference; 051import org.openstreetmap.josm.gui.preferences.display.DisplayPreference; 052import org.openstreetmap.josm.gui.preferences.display.DrawingPreference; 053import org.openstreetmap.josm.gui.preferences.display.GPXPreference; 054import org.openstreetmap.josm.gui.preferences.display.LafPreference; 055import org.openstreetmap.josm.gui.preferences.display.LanguagePreference; 056import org.openstreetmap.josm.gui.preferences.imagery.ImageryPreference; 057import org.openstreetmap.josm.gui.preferences.map.BackupPreference; 058import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference; 059import org.openstreetmap.josm.gui.preferences.map.TaggingPresetPreference; 060import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference; 061import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference; 062import org.openstreetmap.josm.gui.preferences.remotecontrol.RemoteControlPreference; 063import org.openstreetmap.josm.gui.preferences.server.ProxyPreference; 064import org.openstreetmap.josm.gui.preferences.server.ServerAccessPreference; 065import org.openstreetmap.josm.gui.preferences.shortcut.ShortcutPreference; 066import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference; 067import org.openstreetmap.josm.gui.preferences.validator.ValidatorTagCheckerRulesPreference; 068import org.openstreetmap.josm.gui.preferences.validator.ValidatorTestsPreference; 069import org.openstreetmap.josm.gui.util.GuiHelper; 070import org.openstreetmap.josm.plugins.PluginDownloadTask; 071import org.openstreetmap.josm.plugins.PluginHandler; 072import org.openstreetmap.josm.plugins.PluginInformation; 073import org.openstreetmap.josm.tools.GBC; 074import org.openstreetmap.josm.tools.ImageProvider; 075import org.openstreetmap.josm.tools.Logging; 076import org.openstreetmap.josm.tools.Pair; 077import org.openstreetmap.josm.tools.Utils; 078import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler; 079 080/** 081 * The preference settings. 082 * 083 * @author imi 084 */ 085@SuppressWarnings("deprecation") 086public final class PreferenceTabbedPane extends JTabbedPane implements ExpertModeChangeListener, ChangeListener { 087 088 private final class PluginDownloadAfterTask implements Runnable { 089 private final PluginPreference preference; 090 private final PluginDownloadTask task; 091 private final Set<PluginInformation> toDownload; 092 093 private PluginDownloadAfterTask(PluginPreference preference, PluginDownloadTask task, 094 Set<PluginInformation> toDownload) { 095 this.preference = preference; 096 this.task = task; 097 this.toDownload = toDownload; 098 } 099 100 @Override 101 public void run() { 102 boolean requiresRestart = false; 103 104 for (PreferenceSetting setting : settingsInitialized) { 105 if (setting.ok()) { 106 requiresRestart = true; 107 } 108 } 109 110 // build the messages. We only display one message, including the status information from the plugin download task 111 // and - if necessary - a hint to restart JOSM 112 // 113 StringBuilder sb = new StringBuilder(); 114 sb.append("<html>"); 115 if (task != null && !task.isCanceled()) { 116 PluginHandler.refreshLocalUpdatedPluginInfo(task.getDownloadedPlugins()); 117 sb.append(PluginPreference.buildDownloadSummary(task)); 118 } 119 if (requiresRestart) { 120 sb.append(tr("You have to restart JOSM for some settings to take effect.")); 121 sb.append("<br/><br/>"); 122 sb.append(tr("Would you like to restart now?")); 123 } 124 sb.append("</html>"); 125 126 // display the message, if necessary 127 // 128 if (requiresRestart) { 129 final ButtonSpec[] options = RestartAction.getButtonSpecs(); 130 if (0 == HelpAwareOptionPane.showOptionDialog( 131 MainApplication.getMainFrame(), 132 sb.toString(), 133 tr("Restart"), 134 JOptionPane.INFORMATION_MESSAGE, 135 null, /* no special icon */ 136 options, 137 options[0], 138 null /* no special help */ 139 )) { 140 MainApplication.getMenu().restart.actionPerformed(null); 141 } 142 } else if (task != null && !task.isCanceled()) { 143 JOptionPane.showMessageDialog( 144 MainApplication.getMainFrame(), 145 sb.toString(), 146 tr("Warning"), 147 JOptionPane.WARNING_MESSAGE 148 ); 149 } 150 151 // load the plugins that can be loaded at runtime 152 List<PluginInformation> newPlugins = preference.getNewlyActivatedPlugins(); 153 if (newPlugins != null) { 154 Collection<PluginInformation> downloadedPlugins = null; 155 if (task != null && !task.isCanceled()) { 156 downloadedPlugins = task.getDownloadedPlugins(); 157 } 158 List<PluginInformation> toLoad = new ArrayList<>(); 159 for (PluginInformation pi : newPlugins) { 160 if (toDownload.contains(pi) && downloadedPlugins != null && !downloadedPlugins.contains(pi)) { 161 continue; // failed download 162 } 163 if (pi.canloadatruntime) { 164 toLoad.add(pi); 165 } 166 } 167 // check if plugin dependencies can also be loaded 168 Collection<PluginInformation> allPlugins = new HashSet<>(toLoad); 169 allPlugins.addAll(PluginHandler.getPlugins()); 170 boolean removed; 171 do { 172 removed = false; 173 Iterator<PluginInformation> it = toLoad.iterator(); 174 while (it.hasNext()) { 175 if (!PluginHandler.checkRequiredPluginsPreconditions(null, allPlugins, it.next(), requiresRestart)) { 176 it.remove(); 177 removed = true; 178 } 179 } 180 } while (removed); 181 182 if (!toLoad.isEmpty()) { 183 PluginHandler.loadPlugins(PreferenceTabbedPane.this, toLoad, null); 184 } 185 } 186 187 if (MainApplication.getMainFrame() != null) { 188 MainApplication.getMainFrame().repaint(); 189 } 190 } 191 } 192 193 /** 194 * Allows PreferenceSettings to do validation of entered values when ok was pressed. 195 * If data is invalid then event can return false to cancel closing of preferences dialog. 196 * @since 10600 (functional interface) 197 */ 198 @FunctionalInterface 199 public interface ValidationListener { 200 /** 201 * Determines if preferences can be saved. 202 * @return True if preferences can be saved 203 */ 204 boolean validatePreferences(); 205 } 206 207 private interface PreferenceTab { 208 TabPreferenceSetting getTabPreferenceSetting(); 209 210 Component getComponent(); 211 } 212 213 /** 214 * Panel used for preference settings. 215 * @since 4968 216 */ 217 public static final class PreferencePanel extends JPanel implements PreferenceTab { 218 private final transient TabPreferenceSetting preferenceSetting; 219 220 private PreferencePanel(TabPreferenceSetting preferenceSetting) { 221 super(new GridBagLayout()); 222 this.preferenceSetting = Objects.requireNonNull(preferenceSetting, "preferenceSetting"); 223 buildPanel(); 224 } 225 226 private void buildPanel() { 227 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 228 JPanel headerPanel = new JPanel(new BorderLayout()); 229 add(headerPanel, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); 230 231 JLabel label = new JLabel("<html>" + 232 "<b>" + preferenceSetting.getTitle() + "</b><br>" + 233 "<i>" + preferenceSetting.getDescription() + "</i></html>"); 234 label.setFont(label.getFont().deriveFont(Font.PLAIN)); 235 headerPanel.add(label, BorderLayout.CENTER); 236 237 ImageIcon icon = preferenceSetting.getIcon(ImageProvider.ImageSizes.SETTINGS_TAB); 238 headerPanel.add(new JLabel(icon), BorderLayout.EAST); 239 } 240 241 @Override 242 public TabPreferenceSetting getTabPreferenceSetting() { 243 return preferenceSetting; 244 } 245 246 @Override 247 public Component getComponent() { 248 return this; 249 } 250 } 251 252 /** 253 * Scroll pane used for large {@link PreferencePanel}s. 254 * @since 4968 255 */ 256 public static final class PreferenceScrollPane extends JScrollPane implements PreferenceTab { 257 private final transient TabPreferenceSetting preferenceSetting; 258 259 private PreferenceScrollPane(PreferencePanel preferencePanel) { 260 super(preferencePanel.getComponent()); 261 this.preferenceSetting = preferencePanel.getTabPreferenceSetting(); 262 GuiHelper.setDefaultIncrement(this); 263 setBorder(BorderFactory.createEmptyBorder()); 264 } 265 266 @Override 267 public TabPreferenceSetting getTabPreferenceSetting() { 268 return preferenceSetting; 269 } 270 271 @Override 272 public Component getComponent() { 273 return this; 274 } 275 } 276 277 // all created tabs 278 private final transient List<PreferenceTab> tabs = new ArrayList<>(); 279 private static final Collection<PreferenceSettingFactory> SETTINGS_FACTORIES = new LinkedList<>(); 280 private static final PreferenceSettingFactory ADVANCED_PREFERENCE_FACTORY = new AdvancedPreference.Factory(); 281 private final transient List<PreferenceSetting> settings = new ArrayList<>(); 282 283 // distinct list of tabs that have been initialized (we do not initialize tabs until they are displayed to speed up dialog startup) 284 private final transient List<PreferenceSetting> settingsInitialized = new ArrayList<>(); 285 286 final transient List<ValidationListener> validationListeners = new ArrayList<>(); 287 288 /** 289 * Add validation listener to currently open preferences dialog. Calling to removeValidationListener is not necessary, all listeners will 290 * be automatically removed when dialog is closed 291 * @param validationListener validation listener to add 292 */ 293 public void addValidationListener(ValidationListener validationListener) { 294 validationListeners.add(validationListener); 295 } 296 297 /** 298 * Construct a PreferencePanel for the preference settings. Layout is GridBagLayout 299 * and a centered title label and the description are added. 300 * @param caller Preference settings, that display a top level tab 301 * @return The created panel ready to add other controls. 302 */ 303 public PreferencePanel createPreferenceTab(TabPreferenceSetting caller) { 304 return createPreferenceTab(caller, false); 305 } 306 307 /** 308 * Construct a PreferencePanel for the preference settings. Layout is GridBagLayout 309 * and a centered title label and the description are added. 310 * @param caller Preference settings, that display a top level tab 311 * @param inScrollPane if <code>true</code> the added tab will show scroll bars 312 * if the panel content is larger than the available space 313 * @return The created panel ready to add other controls. 314 */ 315 public PreferencePanel createPreferenceTab(TabPreferenceSetting caller, boolean inScrollPane) { 316 PreferencePanel p = new PreferencePanel(Objects.requireNonNull(caller, "caller")); 317 tabs.add(inScrollPane ? new PreferenceScrollPane(p) : p); 318 return p; 319 } 320 321 private OptionalInt indexOfTab(Predicate<TabPreferenceSetting> predicate) { 322 return IntStream.range(0, getTabCount()) 323 .filter(i -> getComponentAt(i) instanceof PreferenceTab 324 && predicate.test(((PreferenceTab) getComponentAt(i)).getTabPreferenceSetting())) 325 .findFirst(); 326 } 327 328 private void selectTabBy(Predicate<TabPreferenceSetting> predicate) { 329 setSelectedIndex(indexOfTab(predicate).orElse(0)); 330 } 331 332 /** 333 * Selects a {@link TabPreferenceSetting} by its icon name 334 * @param name the icon name 335 */ 336 public void selectTabByName(String name) { 337 Objects.requireNonNull(name); 338 selectTabBy(tps -> Objects.equals(name, tps.getIconName())); 339 } 340 341 /** 342 * Selects a {@link TabPreferenceSetting} by class 343 * @param clazz preferences tab class 344 */ 345 public void selectTabByPref(Class<? extends TabPreferenceSetting> clazz) { 346 selectTabBy(clazz::isInstance); 347 } 348 349 /** 350 * Selects a {@link SubPreferenceSetting} by class 351 * @param clazz sub preferences tab class 352 * @return true if the specified preference settings have been selected, false otherwise. 353 */ 354 public boolean selectSubTabByPref(Class<? extends SubPreferenceSetting> clazz) { 355 try { 356 final SubPreferenceSetting sub = getSetting(clazz); 357 final TabPreferenceSetting tab = sub.getTabPreferenceSetting(this); 358 selectTabBy(tps -> tps.equals(tab)); 359 return tab.selectSubTab(sub); 360 } catch (NoSuchElementException ignore) { 361 Logging.trace(ignore); 362 return false; 363 } 364 } 365 366 /** 367 * Returns the currently selected preference and sub preference setting 368 * @return the currently selected preference and sub preference setting 369 */ 370 public Pair<Class<? extends TabPreferenceSetting>, Class<? extends SubPreferenceSetting>> getSelectedTab() { 371 final Component selected = getSelectedComponent(); 372 if (selected instanceof PreferenceTab) { 373 final TabPreferenceSetting setting = ((PreferenceTab) selected).getTabPreferenceSetting(); 374 return Pair.create(setting.getClass(), setting.getSelectedSubTab()); 375 } else { 376 return null; 377 } 378 } 379 380 /** 381 * Returns the {@code DisplayPreference} object. 382 * @return the {@code DisplayPreference} object. 383 */ 384 public DisplayPreference getDisplayPreference() { 385 return getSetting(DisplayPreference.class); 386 } 387 388 /** 389 * Returns the {@code PluginPreference} object. 390 * @return the {@code PluginPreference} object. 391 */ 392 public PluginPreference getPluginPreference() { 393 return getSetting(PluginPreference.class); 394 } 395 396 /** 397 * Returns the {@code ImageryPreference} object. 398 * @return the {@code ImageryPreference} object. 399 */ 400 public ImageryPreference getImageryPreference() { 401 return getSetting(ImageryPreference.class); 402 } 403 404 /** 405 * Returns the {@code ShortcutPreference} object. 406 * @return the {@code ShortcutPreference} object. 407 */ 408 public ShortcutPreference getShortcutPreference() { 409 return getSetting(ShortcutPreference.class); 410 } 411 412 /** 413 * Returns the {@code ServerAccessPreference} object. 414 * @return the {@code ServerAccessPreference} object. 415 * @since 6523 416 */ 417 public ServerAccessPreference getServerPreference() { 418 return getSetting(ServerAccessPreference.class); 419 } 420 421 /** 422 * Returns the {@code ValidatorPreference} object. 423 * @return the {@code ValidatorPreference} object. 424 * @since 6665 425 */ 426 public ValidatorPreference getValidatorPreference() { 427 return getSetting(ValidatorPreference.class); 428 } 429 430 /** 431 * Saves preferences. 432 */ 433 public void savePreferences() { 434 // create a task for downloading plugins if the user has activated, yet not downloaded, new plugins 435 final PluginPreference preference = getPluginPreference(); 436 if (preference != null) { 437 final Set<PluginInformation> toDownload = preference.getPluginsScheduledForUpdateOrDownload(); 438 final PluginDownloadTask task; 439 if (!Utils.isEmpty(toDownload)) { 440 task = new PluginDownloadTask(this, toDownload, tr("Download plugins")); 441 } else { 442 task = null; 443 } 444 445 // this is the task which will run *after* the plugins are downloaded 446 final Runnable continuation = new PluginDownloadAfterTask(preference, task, toDownload); 447 448 if (task != null) { 449 // if we have to launch a plugin download task we do it asynchronously, followed 450 // by the remaining "save preferences" activities run on the Swing EDT. 451 MainApplication.worker.submit(task); 452 MainApplication.worker.submit(() -> GuiHelper.runInEDT(continuation)); 453 } else { 454 // no need for asynchronous activities. Simply run the remaining "save preference" 455 // activities on this thread (we are already on the Swing EDT 456 continuation.run(); 457 } 458 } 459 } 460 461 /** 462 * If the dialog is closed with Ok, the preferences will be stored to the preferences- 463 * file, otherwise no change of the file happens. 464 */ 465 public PreferenceTabbedPane() { 466 super(SwingConstants.LEFT, JTabbedPane.SCROLL_TAB_LAYOUT); 467 super.addMouseWheelListener(new WheelListener(this)); 468 ExpertToggleAction.addExpertModeChangeListener(this); 469 } 470 471 /** 472 * Constructs GUI. 473 */ 474 public void buildGui() { 475 Collection<PreferenceSettingFactory> factories = new ArrayList<>(SETTINGS_FACTORIES); 476 factories.addAll(PluginHandler.getPreferenceSetting()); 477 factories.add(ADVANCED_PREFERENCE_FACTORY); 478 479 for (PreferenceSettingFactory factory : factories) { 480 if (factory != null) { 481 PreferenceSetting setting = factory.createPreferenceSetting(); 482 if (setting instanceof TabPreferenceSetting && ((TabPreferenceSetting) setting).getIconName() == null) { 483 Logging.error("Invalid setting (Icon missing): " + setting.getClass().getName()); 484 setting = null; 485 } 486 if (setting != null) { 487 settings.add(setting); 488 } 489 } 490 } 491 addGUITabs(false); 492 super.getModel().addChangeListener(this); 493 } 494 495 private void addGUITabsForSetting(Icon icon, TabPreferenceSetting tps, int maxWidth) { 496 for (PreferenceTab tab : tabs) { 497 if (tab.getTabPreferenceSetting().equals(tps)) { 498 insertGUITabsForSetting(icon, tps, tab.getComponent(), getTabCount(), maxWidth); 499 } 500 } 501 } 502 503 private int insertGUITabsForSetting(Icon icon, TabPreferenceSetting tps, int index, int maxWidth) { 504 int position = index; 505 for (PreferenceTab tab : tabs) { 506 if (tab.getTabPreferenceSetting().equals(tps)) { 507 insertGUITabsForSetting(icon, tps, tab.getComponent(), position, maxWidth); 508 position++; 509 } 510 } 511 return position - 1; 512 } 513 514 private void insertGUITabsForSetting(Icon icon, TabPreferenceSetting tps, final Component component, int position, int maxWidth) { 515 // macOS / AquaLookAndFeel does not support horizontal tabs, see https://josm.openstreetmap.de/ticket/7548#comment:80 516 String title = "Aqua".equals(UIManager.getLookAndFeel().getID()) ? null : htmlTabTitle(tps.getTitle(), maxWidth); 517 insertTab(title, icon, component, tps.getTooltip(), position); 518 } 519 520 private static String htmlTabTitle(String title, int maxWidth) { 521 // Width is set to force left alignment, see https://stackoverflow.com/a/33781096/2257172 522 return "<html><div style='padding-left:5px; width:" + maxWidth + "px'>" + title + "</div></html>"; 523 } 524 525 private void addGUITabs(boolean clear) { 526 boolean expert = ExpertToggleAction.isExpert(); 527 if (clear) { 528 removeAll(); 529 } 530 // Compute max tab length in pixels 531 int maxWidth = computeMaxTabWidth(); 532 // Inspect each tab setting 533 for (PreferenceSetting setting : settings) { 534 if (setting instanceof TabPreferenceSetting) { 535 TabPreferenceSetting tps = (TabPreferenceSetting) setting; 536 if (expert || !tps.isExpert()) { 537 ImageIcon icon = tps.getIcon(ImageProvider.ImageSizes.LARGEICON); 538 if (settingsInitialized.contains(tps)) { 539 // If it has been initialized, add corresponding tab(s) 540 addGUITabsForSetting(icon, tps, maxWidth); 541 } else { 542 // If it has not been initialized, create an empty tab with only icon and tooltip 543 insertGUITabsForSetting(icon, tps, new PreferencePanel(tps), getTabCount(), maxWidth); 544 } 545 } 546 } else if (!(setting instanceof SubPreferenceSetting)) { 547 Logging.warn("Ignoring preferences "+setting); 548 } 549 } 550 // Hide empty TabPreferenceSetting (only present for plugins) 551 for (DefaultTabPreferenceSetting tps : Utils.filteredCollection(settings, DefaultTabPreferenceSetting.class)) { 552 if (!tps.canBeHidden() || Utils.filteredCollection(settings, SubPreferenceSetting.class).stream() 553 .anyMatch(s -> s.getTabPreferenceSetting(this) == tps)) { 554 continue; 555 } 556 indexOfTab(tps::equals).ifPresent(index -> { 557 remove(index); 558 Logging.debug("{0}: hiding empty {1}", getClass().getSimpleName(), tps); 559 }); 560 } 561 setSelectedIndex(-1); 562 } 563 564 private int computeMaxTabWidth() { 565 FontMetrics fm = getFontMetrics(getFont()); 566 return settings.stream().filter(x -> x instanceof TabPreferenceSetting).map(x -> ((TabPreferenceSetting) x).getTitle()) 567 .filter(Objects::nonNull).mapToInt(fm::stringWidth).max().orElse(120); 568 } 569 570 @Override 571 public void expertChanged(boolean isExpert) { 572 Component sel = getSelectedComponent(); 573 addGUITabs(true); 574 int index = -1; 575 if (sel != null) { 576 index = indexOfComponent(sel); 577 } 578 setSelectedIndex(Math.max(index, 0)); 579 } 580 581 /** 582 * Returns a list of all preferences settings 583 * @return a list of all preferences settings 584 */ 585 public List<PreferenceSetting> getSettings() { 586 return Collections.unmodifiableList(settings); 587 } 588 589 /** 590 * Returns the preferences setting for the given class 591 * @param clazz the preference setting class 592 * @param <T> the preference setting type 593 * @return the preferences setting for the given class 594 * @throws NoSuchElementException if there is no such value 595 */ 596 public <T extends PreferenceSetting> T getSetting(Class<? extends T> clazz) { 597 return Utils.filteredCollection(settings, clazz).iterator().next(); 598 } 599 600 static { 601 // order is important! 602 SETTINGS_FACTORIES.add(new DisplayPreference.Factory()); 603 SETTINGS_FACTORIES.add(new DrawingPreference.Factory()); 604 SETTINGS_FACTORIES.add(new GPXPreference.Factory()); 605 SETTINGS_FACTORIES.add(new ColorPreference.Factory()); 606 SETTINGS_FACTORIES.add(new LafPreference.Factory()); 607 SETTINGS_FACTORIES.add(new LanguagePreference.Factory()); 608 609 SETTINGS_FACTORIES.add(new ServerAccessPreference.Factory()); 610 SETTINGS_FACTORIES.add(new ProxyPreference.Factory()); 611 SETTINGS_FACTORIES.add(new ProjectionPreference.Factory()); 612 SETTINGS_FACTORIES.add(new MapPaintPreference.Factory()); 613 SETTINGS_FACTORIES.add(new TaggingPresetPreference.Factory()); 614 SETTINGS_FACTORIES.add(new BackupPreference.Factory()); 615 SETTINGS_FACTORIES.add(new PluginPreference.Factory()); 616 SETTINGS_FACTORIES.add(MainApplication.getToolbar()); 617 SETTINGS_FACTORIES.add(new AudioPreference.Factory()); 618 SETTINGS_FACTORIES.add(new ShortcutPreference.Factory()); 619 SETTINGS_FACTORIES.add(new ValidatorPreference.Factory()); 620 SETTINGS_FACTORIES.add(new ValidatorTestsPreference.Factory()); 621 SETTINGS_FACTORIES.add(new ValidatorTagCheckerRulesPreference.Factory()); 622 SETTINGS_FACTORIES.add(new RemoteControlPreference.Factory()); 623 SETTINGS_FACTORIES.add(new ImageryPreference.Factory()); 624 } 625 626 /** 627 * This mouse wheel listener reacts when a scroll is carried out over the 628 * tab strip and scrolls one tab/down or up, selecting it immediately. 629 */ 630 static final class WheelListener implements MouseWheelListener { 631 632 final JTabbedPane tabbedPane; 633 634 WheelListener(JTabbedPane tabbedPane) { 635 this.tabbedPane = tabbedPane; 636 } 637 638 @Override 639 public void mouseWheelMoved(MouseWheelEvent wev) { 640 // Ensure the cursor is over the tab strip 641 if (tabbedPane.indexAtLocation(wev.getPoint().x, wev.getPoint().y) < 0) 642 return; 643 644 // Get currently selected tab && ensure the new tab index is sound 645 int newTab = Utils.clamp(tabbedPane.getSelectedIndex() + wev.getWheelRotation(), 646 0, tabbedPane.getTabCount() - 1); 647 648 tabbedPane.setSelectedIndex(newTab); 649 } 650 } 651 652 @Override 653 public void stateChanged(ChangeEvent e) { 654 int index = getSelectedIndex(); 655 Component sel = getSelectedComponent(); 656 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 } 668 } 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); 682 } 683 } 684 Container ancestor = getTopLevelAncestor(); 685 if (ancestor instanceof PreferenceDialog) { 686 ((PreferenceDialog) ancestor).setHelpContext(preferenceSettings.getHelpContext()); 687 } 688 } 689 } 690 691 private void addSubPreferenceSetting(TabPreferenceSetting preferenceSettings, SubPreferenceSetting sps) { 692 if (sps.getTabPreferenceSetting(this) == preferenceSettings) { 693 try { 694 sps.addGui(this); 695 } catch (SecurityException ex) { 696 Logging.error(ex); 697 } catch (RuntimeException ex) { // NOPMD 698 BugReportExceptionHandler.handleException(ex); 699 } finally { 700 settingsInitialized.add(sps); 701 } 702 } 703 } 704}