001package apps.gui3.tabbedpreferences; 002 003import apps.AppConfigBase; 004import apps.ConfigBundle; 005import java.awt.BorderLayout; 006import java.awt.CardLayout; 007import java.awt.Dimension; 008import java.awt.event.ActionEvent; 009import java.util.ArrayList; 010import java.util.List; 011import javax.swing.BorderFactory; 012import javax.swing.BoxLayout; 013import javax.swing.ImageIcon; 014import javax.swing.JButton; 015import javax.swing.JComponent; 016import javax.swing.JLabel; 017import javax.swing.JList; 018import javax.swing.JPanel; 019import javax.swing.JScrollPane; 020import javax.swing.JSeparator; 021import javax.swing.JTabbedPane; 022import javax.swing.ListSelectionModel; 023import javax.swing.event.ListSelectionEvent; 024import jmri.InstanceManager; 025import jmri.ShutDownManager; 026import jmri.swing.PreferencesPanel; 027import jmri.util.FileUtil; 028import jmri.util.ThreadingUtil; 029import jmri.util.swing.JmriJOptionPane; 030 031import org.jdom2.Element; 032 033/** 034 * Provide access to the connection preferences via a tabbed pane. 035 * 036 * @author Bob Jacobsen Copyright 2010, 2019 037 * @author Randall Wood 2012, 2016 038 */ 039public class EditConnectionPreferences extends AppConfigBase { 040 041 @Override 042 public String getHelpTarget() { 043 return "package.apps.TabbedPreferences"; 044 } 045 046 @Override 047 public String getTitle() { 048 return Bundle.getMessage("TitlePreferences"); 049 } 050 // Preferences Window Title 051 052 @Override 053 public boolean isMultipleInstances() { 054 return false; 055 } // only one of these! 056 057 ArrayList<Element> preferencesElements = new ArrayList<>(); 058 059 JPanel detailpanel = new JPanel(); 060 { 061 // The default panel needs to have a CardLayout 062 detailpanel.setLayout(new CardLayout()); 063 } 064 065 /** 066 * The dialog that displays the preferences. 067 * Used by the quit button to dispose the dialog. 068 */ 069 final EditConnectionPreferencesDialog dialog; 070 071 ArrayList<PreferencesCatItems> preferencesArray = new ArrayList<>(); 072 JPanel buttonpanel; 073 JList<String> list; 074 JButton save; 075 JButton quit = null; 076 JScrollPane listScroller; 077 078 public EditConnectionPreferences(EditConnectionPreferencesDialog dialog) { 079 080 this.dialog = dialog; 081 082 /* 083 * Adds the place holders for the menu managedPreferences so that any managedPreferences added by 084 * third party code is added to the end 085 */ 086 preferencesArray.add(new PreferencesCatItems("CONNECTIONS", rb 087 .getString("MenuConnections"), 100)); 088 } 089 090 public void init() { 091 list = new JList<>(); 092 listScroller = new JScrollPane(list); 093 listScroller.setPreferredSize(new Dimension(100, 100)); 094 095 buttonpanel = new JPanel(); 096 buttonpanel.setLayout(new BoxLayout(buttonpanel, BoxLayout.Y_AXIS)); 097 buttonpanel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 3)); 098 099 detailpanel = new JPanel(); 100 detailpanel.setLayout(new CardLayout()); 101 detailpanel.setBorder(BorderFactory.createEmptyBorder(6, 3, 6, 6)); 102 103 save = new JButton( 104 ConfigBundle.getMessage("ButtonSave"), 105 new ImageIcon(FileUtil.findURL("program:resources/icons/misc/gui3/SaveIcon.png", FileUtil.Location.INSTALLED))); 106 save.addActionListener((ActionEvent e) -> { 107 dialog.restartProgram = true; 108 savePressed(invokeSaveOptions()); 109 }); 110 111 quit = new JButton( 112 ConfigBundle.getMessage("ButtonQuit")); 113// new ImageIcon(FileUtil.findURL("program:resources/icons/misc/gui3/SaveIcon.png", FileUtil.Location.INSTALLED))); 114 quit.addActionListener((ActionEvent e) -> { 115 if (dialog != null) { 116 dialog.restartProgram = false; 117 dialog.dispose(); 118 } 119 }); 120 121 setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); 122 getTabbedPreferences().preferencesArray.stream().forEach((preferences) -> { 123 detailpanel.add(preferences.getPanel(), preferences.getPrefItem()); 124 }); 125 126 updateJList(); 127 add(buttonpanel); 128 add(new JSeparator(JSeparator.VERTICAL)); 129 add(detailpanel); 130 131 list.setSelectedIndex(0); 132 selection(preferencesArray.get(0).getPrefItem()); 133 } 134 135 // package only - for EditConnectionPreferencesDialog 136 boolean isDirty() { 137 // if not for the debug statements, this method could be the one line: 138 // return this.getPreferencesPanels().values.stream().anyMatch((panel) -> (panel.isDirty())); 139 return this.getPreferencesPanels().values().stream().map((panel) -> { 140 // wrapped in isDebugEnabled test to prevent overhead of assembling message 141 if (log.isDebugEnabled()) { 142 log.debug("PreferencesPanel {} ({}) is {}.", 143 panel.getClass().getName(), 144 (panel.getTabbedPreferencesTitle() != null) ? panel.getTabbedPreferencesTitle() : panel.getPreferencesItemText(), 145 (panel.isDirty()) ? "dirty" : "clean"); 146 } 147 return panel; 148 }).anyMatch((panel) -> (panel.isDirty())); 149 } 150 151 // package only - for EditConnectionPreferencesDialog 152 boolean invokeSaveOptions() { 153 boolean restartRequired = false; 154 for (PreferencesPanel panel : this.getPreferencesPanels().values()) { 155 // wrapped in isDebugEnabled test to prevent overhead of assembling message 156 if (log.isDebugEnabled()) { 157 log.debug("PreferencesPanel {} ({}) is {}.", 158 panel.getClass().getName(), 159 (panel.getTabbedPreferencesTitle() != null) ? panel.getTabbedPreferencesTitle() : panel.getPreferencesItemText(), 160 (panel.isDirty()) ? "dirty" : "clean"); 161 } 162 panel.savePreferences(); 163 // wrapped in isDebugEnabled test to prevent overhead of assembling message 164 if (log.isDebugEnabled()) { 165 log.debug("PreferencesPanel {} ({}) restart is {}required.", 166 panel.getClass().getName(), 167 (panel.getTabbedPreferencesTitle() != null) ? panel.getTabbedPreferencesTitle() : panel.getPreferencesItemText(), 168 (panel.isRestartRequired()) ? "" : "not "); 169 } 170 if (!restartRequired) { 171 restartRequired = panel.isRestartRequired(); 172 } 173 } 174 return restartRequired; 175 } 176 177 void selection(String view) { 178 CardLayout cl = (CardLayout) (detailpanel.getLayout()); 179 cl.show(detailpanel, view); 180 } 181 182 public void addPreferencesPanel(PreferencesPanel panel) { 183 this.getPreferencesPanels().put(panel.getClass().getName(), panel); 184 addItem(panel.getPreferencesItem(), 185 panel.getPreferencesItemText(), 186 panel.getTabbedPreferencesTitle(), 187 panel.getLabelKey(), 188 panel, 189 panel.getPreferencesTooltip(), 190 panel.getSortOrder() 191 ); 192 } 193 194 private void addItem(String prefItem, String itemText, String tabTitle, 195 String labelKey, PreferencesPanel item, String tooltip, int sortOrder) { 196 PreferencesCatItems itemBeingAdded = null; 197 for (PreferencesCatItems preferences : preferencesArray) { 198 if (preferences.getPrefItem().equals(prefItem)) { 199 itemBeingAdded = preferences; 200 // the lowest sort order of any panel sets the sort order for 201 // the preferences category 202 if (sortOrder < preferences.sortOrder) { 203 preferences.sortOrder = sortOrder; 204 } 205 break; 206 } 207 } 208 if (itemBeingAdded == null) { 209 itemBeingAdded = new PreferencesCatItems(prefItem, itemText, sortOrder); 210 preferencesArray.add(itemBeingAdded); 211 // As this is a new item in the selection list, we need to update 212 // the JList. 213 updateJList(); 214 } 215 if (tabTitle == null) { 216 tabTitle = itemText; 217 } 218 itemBeingAdded.addPreferenceItem(tabTitle, labelKey, item.getPreferencesComponent(), tooltip, sortOrder); 219 } 220 221 /* Method allows for the preference to goto a specific list item */ 222 public void gotoPreferenceItem(String selection, String subCategory) { 223 224 selection(selection); 225 list.setSelectedIndex(getCategoryIndexFromString(selection)); 226 if (subCategory == null || subCategory.isEmpty()) { 227 return; 228 } 229 preferencesArray.get(getCategoryIndexFromString(selection)) 230 .gotoSubCategory(subCategory); 231 } 232 233 /* 234 * Returns a List of existing Preference Categories. 235 */ 236 public List<String> getPreferenceMenuList() { 237 ArrayList<String> choices = new ArrayList<>(); 238 for (PreferencesCatItems preferences : preferencesArray) { 239 choices.add(preferences.getPrefItem()); 240 } 241 return choices; 242 } 243 244 int getCategoryIndexFromString(String category) { 245 for (int x = 0; x < preferencesArray.size(); x++) { 246 if (preferencesArray.get(x).getPrefItem().equals(category)) { 247 return (x); 248 } 249 } 250 return -1; 251 } 252 253 protected ArrayList<String> getChoices() { 254 ArrayList<String> choices = new ArrayList<>(); 255 for (PreferencesCatItems preferences : preferencesArray) { 256 choices.add(preferences.getItemString()); 257 } 258 return choices; 259 } 260 261 void updateJList() { 262 buttonpanel.removeAll(); 263 if (list.getListSelectionListeners().length > 0) { 264 list.removeListSelectionListener(list.getListSelectionListeners()[0]); 265 } 266 List<String> choices = this.getChoices(); 267 list = new JList<>(choices.toArray(new String[choices.size()])); 268 listScroller = new JScrollPane(list); 269 listScroller.setPreferredSize(new Dimension(100, 100)); 270 271 list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); 272 list.setLayoutOrientation(JList.VERTICAL); 273 list.addListSelectionListener((ListSelectionEvent e) -> { 274 PreferencesCatItems item = preferencesArray.get(list.getSelectedIndex()); 275 selection(item.getPrefItem()); 276 }); 277 buttonpanel.add(listScroller); 278 buttonpanel.add(save); 279 280 if (quit != null) { 281 buttonpanel.add(quit); 282 } 283 } 284 285 public boolean isPreferencesValid() { 286 return this.getPreferencesPanels().values().stream().allMatch((panel) -> (panel.isPreferencesValid())); 287 } 288 289 @Override 290 public void savePressed(boolean restartRequired) { 291 ShutDownManager sdm = InstanceManager.getDefault(ShutDownManager.class); 292 if (!this.isPreferencesValid() && !sdm.isShuttingDown()) { 293 for (PreferencesPanel panel : this.getPreferencesPanels().values()) { 294 if (!panel.isPreferencesValid()) { 295 switch (JmriJOptionPane.showConfirmDialog(this, 296 Bundle.getMessage("InvalidPreferencesMessage", panel.getTabbedPreferencesTitle()), 297 Bundle.getMessage("InvalidPreferencesTitle"), 298 JmriJOptionPane.YES_NO_OPTION, 299 JmriJOptionPane.ERROR_MESSAGE)) { 300 case JmriJOptionPane.YES_OPTION: 301 // abort save and return to broken preferences 302 this.gotoPreferenceItem(panel.getPreferencesItem(), panel.getTabbedPreferencesTitle()); 303 return; 304 default: 305 // do nothing 306 break; 307 } 308 } 309 } 310 } 311 super.savePressed(restartRequired); 312 } 313 314 static class PreferencesCatItems implements java.io.Serializable { 315 316 /* 317 * This contains details of all list managedPreferences to be displayed in the 318 * preferences 319 */ 320 String itemText; 321 String prefItem; 322 int sortOrder = Integer.MAX_VALUE; 323 JTabbedPane tabbedPane = new JTabbedPane(); 324 ArrayList<String> disableItemsList = new ArrayList<>(); 325 326 private final ArrayList<TabDetails> tabDetailsArray = new ArrayList<>(); 327 328 PreferencesCatItems(String pref, String title, int sortOrder) { 329 prefItem = pref; 330 itemText = title; 331 this.sortOrder = sortOrder; 332 } 333 334 void addPreferenceItem(String title, String labelkey, JComponent item, 335 String tooltip, int sortOrder) { 336 for (TabDetails tabDetails : tabDetailsArray) { 337 if (tabDetails.getTitle().equals(title)) { 338 // If we have a match then we do not need to add it back in. 339 return; 340 } 341 } 342 TabDetails tab = new TabDetails(labelkey, title, item, tooltip, sortOrder); 343 tabDetailsArray.add(tab); 344 tabDetailsArray.sort((TabDetails o1, TabDetails o2) -> { 345 int comparison = Integer.compare(o1.sortOrder, o2.sortOrder); 346 return (comparison != 0) ? comparison : o1.tabTitle.compareTo(o2.tabTitle); 347 }); 348 JScrollPane scroller = new JScrollPane(tab.getPanel()); 349 scroller.setBorder(BorderFactory.createEmptyBorder()); 350 ThreadingUtil.runOnGUI(() -> { 351 352 tabbedPane.addTab(tab.getTitle(), null, scroller, tab.getToolTip()); 353 354 for (String disableItem : disableItemsList) { 355 if (item.getClass().getName().equals(disableItem)) { 356 tabbedPane.setEnabledAt(tabbedPane.indexOfTab(tab.getTitle()), false); 357 return; 358 } 359 } 360 }); 361 } 362 363 String getPrefItem() { 364 return prefItem; 365 } 366 367 String getItemString() { 368 return itemText; 369 } 370 371 /* 372 * This returns a JPanel if only one item is configured for a menu item 373 * or it returns a JTabbedFrame if there are multiple managedPreferences for the menu 374 */ 375 JComponent getPanel() { 376 if (tabDetailsArray.size() == 1) { 377 return tabDetailsArray.get(0).getPanel(); 378 } else { 379 if (tabbedPane.getTabCount() == 0) { 380 for (TabDetails tab : tabDetailsArray) { 381 ThreadingUtil.runOnGUI(() -> { 382 JScrollPane scroller = new JScrollPane(tab.getPanel()); 383 scroller.setBorder(BorderFactory.createEmptyBorder()); 384 385 tabbedPane.addTab(tab.getTitle(), null, scroller, tab.getToolTip()); 386 387 for (String disableItem : disableItemsList) { 388 if (tab.getItem().getClass().getName().equals(disableItem)) { 389 tabbedPane.setEnabledAt(tabbedPane.indexOfTab(tab.getTitle()), false); 390 return; 391 } 392 } 393 }); 394 } 395 } 396 return tabbedPane; 397 } 398 } 399 400 void gotoSubCategory(String sub) { 401 if (tabDetailsArray.size() == 1) { 402 return; 403 } 404 for (int i = 0; i < tabDetailsArray.size(); i++) { 405 if (tabDetailsArray.get(i).getTitle().equals(sub)) { 406 tabbedPane.setSelectedIndex(i); 407 return; 408 } 409 } 410 } 411 412 static class TabDetails implements java.io.Serializable { 413 414 /* This contains all the JPanels that make up a preferences menus */ 415 JComponent tabItem; 416 String tabTooltip; 417 String tabTitle; 418 JPanel tabPanel = new JPanel(); 419 private final int sortOrder; 420 421 TabDetails(String labelkey, String tabTit, JComponent item, 422 String tooltip, int sortOrder) { 423 tabItem = item; 424 tabTitle = tabTit; 425 tabTooltip = tooltip; 426 this.sortOrder = sortOrder; 427 428 JComponent p = new JPanel(); 429 p.setLayout(new BorderLayout()); 430 if (labelkey != null) { 431 // insert label at top 432 // As this can be multi-line, embed the text within <html> 433 // tags and replace newlines with <br> tag 434 JLabel t = new JLabel("<html>" 435 + labelkey.replace(String.valueOf('\n'), "<br>") 436 + "</html>"); 437 t.setHorizontalAlignment(JLabel.CENTER); 438 t.setAlignmentX(0.5f); 439 t.setPreferredSize(t.getMinimumSize()); 440 t.setMaximumSize(t.getMinimumSize()); 441 t.setOpaque(false); 442 p.add(t, BorderLayout.NORTH); 443 } 444 p.add(item, BorderLayout.CENTER); 445 ThreadingUtil.runOnGUI(() -> { 446 tabPanel.setLayout(new BorderLayout()); 447 tabPanel.add(p, BorderLayout.CENTER); 448 }); 449 } 450 451 String getToolTip() { 452 return tabTooltip; 453 } 454 455 String getTitle() { 456 return tabTitle; 457 } 458 459 JPanel getPanel() { 460 return tabPanel; 461 } 462 463 JComponent getItem() { 464 return tabItem; 465 } 466 467 int getSortOrder() { 468 return sortOrder; 469 } 470 } 471 } 472 473 /** 474 * Ensure a TabbedPreferences instance is always available. 475 * 476 * @return the default TabbedPreferences instance, creating it if needed 477 */ 478 private TabbedPreferences getTabbedPreferences() { 479 return InstanceManager.getOptionalDefault(TabbedPreferences.class).orElseGet(() -> { 480 return InstanceManager.setDefault(TabbedPreferences.class, new TabbedPreferences()); 481 }); 482 } 483 484 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EditConnectionPreferences.class); 485 486}