001package jmri.jmrit.display.palette;
002
003import java.awt.BorderLayout;
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.GraphicsEnvironment;
007import java.awt.event.ActionEvent;
008import java.net.URL;
009import java.util.Enumeration;
010import java.util.HashMap;
011import java.util.Iterator;
012import java.util.List;
013import java.util.Map.Entry;
014import javax.annotation.Nonnull;
015import javax.swing.JMenu;
016import javax.swing.JMenuBar;
017import javax.swing.JMenuItem;
018import javax.swing.JOptionPane;
019import javax.swing.JScrollPane;
020import javax.swing.JTabbedPane;
021import javax.swing.event.ChangeEvent;
022import javax.swing.event.ChangeListener;
023import javax.swing.tree.TreeNode;
024import jmri.CatalogTree;
025import jmri.CatalogTreeManager;
026import jmri.InstanceManager;
027import jmri.CatalogTreeLeaf;
028import jmri.CatalogTreeNode;
029import jmri.jmrit.catalog.DirectorySearcher;
030import jmri.jmrit.catalog.ImageIndexEditor;
031import jmri.jmrit.catalog.NamedIcon;
032import jmri.jmrit.display.DisplayFrame;
033import jmri.jmrit.display.Editor;
034import jmri.jmrit.picker.PickListModel;
035import jmri.util.FileUtil;
036import org.jdom2.Element;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
041
042/**
043 * Container for adding items to Control Panels. Starting point for palette package.
044 * <p>
045 * Loads and stores icons used in Control Panel Editor panels.
046 * For background colors to work on a particular editor instance, select the
047 * 'Item Palette' item under 'Add Items' menu and configure the 'Backgrounds' tab
048 * ItemPalette for that editor. Otherwise any item can be dragged and
049 * dropped to any editor.
050 * <p>
051 * The icons are displayed on the background of the last editor to call the
052 * ItemPalette instance. In session the user can set it to another color or a white/gray
053 * squares pattern using the "View on:" combo. This choice is shared across tabs
054 * as a field on the {@link jmri.jmrit.display.DisplayFrame} parent frame.
055 * <p>
056 * <a href="doc-files/ItemPalette-ClassDiagram.png"><img src="doc-files/ItemPalette-ClassDiagram.png" alt="UML Class diagram" height="50%" width="50%"></a>
057 *
058 * @author Pete Cressman Copyright (c) 2010, 2018
059 * @author Egbert Broerse Copyright (c) 2017
060 */
061/*
062@startuml jmri/jmrit/display/palette/doc-files/ItemPalette-ClassDiagram.png
063
064abstract class JPanel
065package "jmri.util.swing.ImagePanel" {
066   class ImagePanel {
067-BufferedImage back
068+setImage()
069+paintComponent()
070}
071}
072package "jmri.util.swing.DrawSquares" {
073   class "DrawSquares" {
074+DrawSquares()
075}
076}
077abstract class ItemPanel {
078-String type
079#int previewBgSet
080#BufferedImage[] _backgrounds
081#MakeBgCombo()
082}
083JPanel --|> ItemPanel
084abstract class FamilyItemPanel
085class TableItemPanel
086class IndicatorItemPanel
087IndicatorItemPanel : type = "Indicator"
088object viewOnCombo
089viewOnCombo : -int choice
090viewOnCombo : +EventListener InitListener
091object preview
092preview : -image = 1
093preview : +EventListener comboListener
094object TurnoutItemPanel
095TurnoutItemPanel : type = "Turnout"
096TableItemPanel -- TurnoutItemPanel
097object SensorItemPanel
098SensorItemPanel : type = "Sensor"
099TableItemPanel -- SensorItemPanel
100class SignalMastItemPanel
101SignalMastItemPanel : type = "SignalMast"
102TableItemPanel --|> SignalMastItemPanel
103class MultiSensorItemPanel
104MultiSensorItemPanel : type = "MultiSensor"
105TableItemPanel --|> MultiSensorItemPanel
106class IconItemPanel
107class BackgroundItemPanel
108BackgroundItemPanel : type = "Background"
109IconItemPanel --|> BackgroundItemPanel
110class ClockItemPanel
111ClockItemPanel : type = "Clock"
112IconItemPanel --|> ClockItemPanel
113class DecoratorPanel
114DecoratorPanel : #int previewBgSet
115DecoratorPanel : #BufferedImage[] _backgrounds
116JPanel --|> DecoratorPanel
117abstract class DragJComponent
118JPanel --|> DragJComponent
119class TextItemPanel
120TextItemPanel : type = "Text"
121
122ItemPanel --|> FamilyItemPanel
123FamilyItemPanel --|> TableItemPanel
124FamilyItemPanel --|> IndicatorItemPanel
125DecoratorPanel *-- viewOnCombo
126FamilyItemPanel *-- viewOnCombo : if != SignalMast
127FamilyItemPanel *-- preview
128IconItemPanel *-- viewOnCombo : if != Background
129SignalMastItemPanel *-- viewOnCombo
130viewOnCombo ..> preview: setImage[n]
131viewOnCombo -- DrawSquares
132ItemPanel --|> IconItemPanel
133ItemPanel --|> TextItemPanel
134DecoratorPanel -- TextItemPanel
135ImagePanel -- preview
136DragJComponent --|> ReporterItemPanel
137ReporterItemPanel *-- preview
138' MemoryItemPanel not shown
139
140@enduml
141*/
142
143public class ItemPalette extends DisplayFrame implements ChangeListener {
144
145    public static final int STRUT_SIZE = 5;
146    static final String RED_X = "resources/icons/misc/X-red.gif";
147
148    protected static JTabbedPane _tabPane;
149    protected static HashMap<String, ItemPanel> _tabIndex;
150
151    private static volatile HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> _iconMaps;
152    // for now, special case 4 level maps since IndicatorTO is the only case.
153    private static volatile HashMap<String, HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>> _indicatorTOMaps;
154    private ItemPanel _currentItemPanel;
155
156    /**
157     * Store palette icons in preferences file catalogTrees.xml
158     */
159    public static void storeIcons() {
160        if (_iconMaps == null) {
161            return;     // never loaded
162        }
163        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
164        // unfiltered, xml-stored, item palate icon tree
165        CatalogTree tree = manager.getBySystemName("NXPI");
166        // discard old version
167        if (tree != null) {
168            manager.deregister(tree);
169        }
170        tree = manager.newCatalogTree("NXPI", "Item Palette");
171        CatalogTreeNode root = tree.getRoot();
172
173        for (Entry<String, HashMap<String, HashMap<String, NamedIcon>>> entry : _iconMaps.entrySet()) {
174            root.add(store3levelMap(entry.getKey(), entry.getValue()));
175            if (log.isDebugEnabled()) {
176                log.debug("Add type node {}", entry.getKey());
177            }
178        }
179
180        for (Entry<String, HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>> entry : _indicatorTOMaps.entrySet()) {
181            CatalogTreeNode typeNode = new CatalogTreeNode(entry.getKey());
182            for (Entry<String, HashMap<String, HashMap<String, NamedIcon>>> ent : entry.getValue().entrySet()) {
183                typeNode.add(store3levelMap(ent.getKey(), ent.getValue()));
184                log.debug("Add IndicatorTO node {}", ent.getKey());
185            }
186            root.add(typeNode);
187            log.debug("Add IndicatorTO node {}", entry.getKey());
188        }
189    }
190
191    static CatalogTreeNode store3levelMap(String type, HashMap<String, HashMap<String, NamedIcon>> familyMap) {
192        CatalogTreeNode typeNode = new CatalogTreeNode(type);
193        for (Entry<String, HashMap<String, NamedIcon>> mapEntry : familyMap.entrySet()) {
194            String family = mapEntry.getKey();
195            CatalogTreeNode familyNode = new CatalogTreeNode(family);
196            HashMap<String, NamedIcon> iconMap = mapEntry.getValue();
197            for (Entry<String, NamedIcon> iconEntry : iconMap.entrySet()) {
198                String state = iconEntry.getKey();
199                String path = iconEntry.getValue().getURL();
200                familyNode.addLeaf(state, path);
201            }
202            typeNode.add(familyNode);
203            log.debug("Add familyNode {}", familyNode);
204        }
205        return typeNode;
206    }
207
208    public static void loadIcons() {
209        if (_iconMaps == null) {
210            // long t = System.currentTimeMillis();
211            InstanceManager.getDefault(jmri.CatalogTreeManager.class).loadImageIndex();
212            _iconMaps = new HashMap<>();
213            _indicatorTOMaps = new HashMap<>();
214
215            if (!loadSavedIcons()) {
216                loadDefaultIcons();
217            }
218        }
219    }
220
221    static boolean loadSavedIcons() {
222        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
223        CatalogTree tree = manager.getBySystemName("NXPI");
224        if (tree != null) {
225            CatalogTreeNode root = tree.getRoot();
226            Enumeration<TreeNode> e = root.children();
227            while (e.hasMoreElements()) {
228                CatalogTreeNode node = (CatalogTreeNode)e.nextElement();
229                String typeName = (String) node.getUserObject();
230                // detect this is a 4 level map collection.
231                // not very elegant (i.e. extensible), but maybe all that's needed.
232                if (typeName.equals("IndicatorTO")) {
233                    HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyTOMap
234                            = loadIndicatorFamilyMap(node);
235                    log.debug("Add {} indicatorTO families to item type {} for _indicatorTOMaps.",
236                            familyTOMap.size(), typeName );
237                    _indicatorTOMaps.put(typeName, familyTOMap);
238                } else {
239                    HashMap<String, HashMap<String, NamedIcon>> familyMap
240                            = loadFamilyMap(node);
241                    _iconMaps.put(typeName, familyMap);
242                    log.debug("Add item type {} to _iconMaps.", typeName);
243                }
244            }
245            log.debug("Icon Map has {} members", _iconMaps.size());
246            return true;
247        }
248        return false;
249    }
250
251    static HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>
252            loadIndicatorFamilyMap(CatalogTreeNode node) {
253        HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyMap
254                = new HashMap<>();
255        Enumeration<TreeNode> ee = node.children();
256        while (ee.hasMoreElements()) {
257            CatalogTreeNode famNode = (CatalogTreeNode)ee.nextElement();
258            String name = (String) famNode.getUserObject();
259            familyMap.put(name, loadFamilyMap(famNode));
260        }
261        return familyMap;
262    }
263
264    static HashMap<String, HashMap<String, NamedIcon>> loadFamilyMap(CatalogTreeNode node) {
265        HashMap<String, HashMap<String, NamedIcon>> familyMap = new HashMap<>();
266        Enumeration<TreeNode> ee = node.children();
267        while (ee.hasMoreElements()) {
268            CatalogTreeNode famNode = (CatalogTreeNode)ee.nextElement();
269            String familyName = (String)famNode.getUserObject();
270            HashMap<String, NamedIcon> iconMap = new HashMap<>();
271            List<CatalogTreeLeaf> list = famNode.getLeaves();
272            for (CatalogTreeLeaf catalogTreeLeaf : list) {
273                String iconName = catalogTreeLeaf.getName();
274                String path = catalogTreeLeaf.getPath();
275                NamedIcon icon = NamedIcon.getIconByName(path);
276                if (icon == null) {
277                    log.warn("loadFamilyMap cannot find icon \"{}\" in family\"{}\" at path \"{}\"", iconName, familyName, path);
278                    String fileName = RED_X;
279                    icon = new NamedIcon(fileName, fileName);
280                }
281                iconMap.put(iconName, icon);
282                log.debug("Add {} icon to family \"{}\"", iconName, familyName);
283            }
284            familyMap.put(familyName, iconMap);
285        }
286        return familyMap;
287    }
288
289    static List<Element> getDefaultIconItemTypes() throws org.jdom2.JDOMException, java.io.IOException {
290        URL file = FileUtil.findURL("xml/defaultPanelIcons.xml");
291        if (file == null) {
292            throw new IllegalArgumentException("defaultPanelIcons file (xml/defaultPanelIcons.xml) doesn't exist.");
293        }
294        jmri.jmrit.XmlFile xf = new jmri.jmrit.XmlFile() {
295        };
296        Element root = xf.rootFromURL(file);
297        return (root.getChild("ItemTypes").getChildren());
298    }
299
300    static void loadDefaultIcons() {
301        try {
302            List<Element> typeList = getDefaultIconItemTypes();
303            for (Element type : typeList) {
304                String typeName = type.getName();
305                List<Element> families = type.getChildren();
306                loadFamilies(typeName, families);
307            }
308        } catch (org.jdom2.JDOMException | java.io.IOException e) {
309            log.error("error reading file \"defaultPanelIcons.xml\" due to: ", e);
310        }
311    }
312
313    static void loadFamilies(String typeName, List<Element> families) {
314        // detect this is a 4 level map collection.
315        // not very elegant (i.e. extensible), but maybe all that's needed.
316        if (typeName.equals("IndicatorTO")) {
317            HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyTOMap
318                    = loadDefaultIndicatorTOMap(families, null);
319            _indicatorTOMaps.put(typeName, familyTOMap);
320            log.debug("Add {} indicatorTO families to item type {} to _indicatorTOMaps.",
321                    familyTOMap.size(), typeName);
322        } else {
323            HashMap<String, HashMap<String, NamedIcon>> familyMap = loadDefaultFamilyMap(families, null);
324            _iconMaps.put(typeName, familyMap);
325            log.debug("Add {} families to item type \"{}\" to _iconMaps.",
326                    familyMap.size(), typeName);
327        }
328    }
329
330    @SuppressFBWarnings(value="DLS_DEAD_LOCAL_STORE",justification="Stores are not dead. Both statements APPEND additional items into their maps")
331    static public void loadMissingItemType(String itemType) {
332        try {
333            Element thisType = null;
334            List<Element> typeList = getDefaultIconItemTypes();
335            for (Element type : typeList) {
336                String typeName = type.getName();
337                if (typeName.equals(itemType)) {
338                    thisType = type;
339                    break;
340                }
341            }
342            if (thisType == null) {
343                log.error("No type \"{}\" in file \"defaultPanelIcons.xml\"", itemType);
344                return;
345            }
346            List<Element> families = thisType.getChildren();
347            if (itemType.equals("IndicatorTO")) {
348                HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyMaps = _indicatorTOMaps.get(itemType);
349                familyMaps = loadDefaultIndicatorTOMap(families, familyMaps);
350            } else {
351                HashMap<String, HashMap<String, NamedIcon>> familyMap = ItemPalette.getFamilyMaps(itemType);
352                familyMap = loadDefaultFamilyMap(families, familyMap);
353            }
354            InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
355        } catch (org.jdom2.JDOMException | java.io.IOException ex) {
356            log.error("error reading file \"defaultPanelIcons.xml\" due to: {}", ex);
357        }
358    }
359
360    static HashMap<String, HashMap<String, NamedIcon>> loadDefaultFamilyMap(
361            List<Element> families, HashMap<String, HashMap<String, NamedIcon>> familyMap) {
362        if (familyMap == null) {
363            familyMap = new HashMap<>();
364        }
365        for (Element family : families) {
366            String familyName = family.getName();
367            HashMap<String, NamedIcon> iconMap = new HashMap<>();
368            // Map of all icons of in family, familyName
369            List<Element> iconfiles = family.getChildren();
370            for (Element iconfile : iconfiles) {
371                String iconName = iconfile.getName();
372                String fileName = iconfile.getText().trim();
373                if (fileName.length() == 0) {
374                    log.warn("loadDefaultFamilyMap: icon \"{}\" in family \"{}\" has no image file.", iconName, familyName);
375                    fileName = RED_X;
376                }
377                NamedIcon icon = NamedIcon.getIconByName(fileName);
378                if (icon == null) {
379                    log.warn("loadDefaultFamilyMap: icon \"{}\" in family \"{}\" cannot get icon from file \"{}\".", iconName, familyName, fileName);
380                    fileName = RED_X;
381                    icon = new NamedIcon(fileName, fileName);
382                }
383                iconMap.put(iconName, icon);
384            }
385            familyMap.put(familyName, iconMap);
386            if (log.isDebugEnabled()) {
387                log.debug("Add {}  icons to family \"{}\"", iconMap.size(), familyName);
388            }
389        }
390        return familyMap;
391    }
392
393    static HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>
394            loadDefaultIndicatorTOMap(List<Element> typeList,  HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyTOMap) {
395        if (familyTOMap == null) {
396            familyTOMap = new HashMap<>();
397        }
398      //  HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> familyTOMap = new HashMap<>();
399        // Map of all families of type, typeName
400        for (Element element : typeList) {
401            String familyName = element.getName();
402            List<Element> types = element.getChildren();
403            HashMap<String, HashMap<String, NamedIcon>> familyMap = loadDefaultFamilyMap(types, null);
404            familyTOMap.put(familyName, familyMap);
405            if (log.isDebugEnabled()) {
406                log.debug("Add {} IndicatorTO sub-families to item type {}  to IndicatorTO families.", familyMap.size(), familyName);
407            }
408        }
409        return familyTOMap;
410    }
411
412    public static ItemPalette getDefault(String title, @Nonnull Editor ed) {
413        if (GraphicsEnvironment.isHeadless()) {
414            return null;
415        }
416        ItemPalette instance = InstanceManager.getOptionalDefault(ItemPalette.class).orElseGet(() -> InstanceManager.setDefault(ItemPalette.class, new ItemPalette(title, ed)));
417        if (!ed.equals(instance.getEditor())) {
418            instance.updateBackground(ed);
419            InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(ed, null, instance);
420        }
421        // pack before setLocation
422        instance.pack();
423        InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(ed, null, instance);
424        instance.setVisible(true);
425        return instance;
426    }
427    
428    public void setEditor(Editor ed) {
429        updateBackground(ed);
430        InstanceManager.getDefault(jmri.util.PlaceWindow.class).nextTo(ed, null, this);
431    }
432
433    public ItemPalette(String title, Editor ed) {
434        super(title, ed);
435        init();
436        setTitle(Bundle.getMessage("ItemPaletteTitle", Bundle.getMessage(ItemPanel.NAME_MAP.get("Turnout"))));
437    }
438
439    private void init() {
440        loadIcons();
441        addWindowListener(new java.awt.event.WindowAdapter() {
442            @Override
443            public void windowClosing(java.awt.event.WindowEvent e) {
444                closePanels(e);
445            }
446        });
447
448        makeMenus();
449        buildTabPane(this);
450
451        setLayout(new BorderLayout(5, 5));
452        add(_tabPane, BorderLayout.CENTER);
453        JScrollPane sp = (JScrollPane) _tabPane.getSelectedComponent();
454        _currentItemPanel = (ItemPanel) sp.getViewport().getView();
455        _currentItemPanel.hideIcons();
456    }
457
458    /*
459     * Add the tabs on the Control Panel Editor.
460     */
461    static void buildTabPane(ItemPalette palette) {
462        _tabPane = new JTabbedPane();
463        _tabIndex = new HashMap<>();
464
465        ItemPanel itemPanel = new TableItemPanel<>(palette, "Turnout", null,
466                PickListModel.turnoutPickModelInstance());
467        addItemTab(itemPanel, "Turnout", "BeanNameTurnout");
468        itemPanel.init();  // show panel on start
469
470        itemPanel = new TableItemPanel<>(palette, "Sensor", null,
471                PickListModel.sensorPickModelInstance());
472        addItemTab(itemPanel, "Sensor", "BeanNameSensor");
473
474        itemPanel = new SignalHeadItemPanel(palette, "SignalHead", null,
475                PickListModel.signalHeadPickModelInstance());
476        addItemTab(itemPanel, "SignalHead", "BeanNameSignalHead");
477
478        itemPanel = new SignalMastItemPanel(palette, "SignalMast", null,
479                PickListModel.signalMastPickModelInstance());
480        addItemTab(itemPanel, "SignalMast", "BeanNameSignalMast");
481
482        itemPanel = new MemoryItemPanel(palette, "Memory", null,
483                PickListModel.memoryPickModelInstance());
484        addItemTab(itemPanel, "Memory", "BeanNameMemory");
485
486        itemPanel = new ReporterItemPanel(palette, "Reporter", null,
487                PickListModel.reporterPickModelInstance());
488        addItemTab(itemPanel, "Reporter", "BeanNameReporter");
489
490        itemPanel = new TableItemPanel<>(palette, "Light", null,
491                PickListModel.lightPickModelInstance());
492        addItemTab(itemPanel, "Light", "BeanNameLight");
493
494        itemPanel = new MultiSensorItemPanel(palette, "MultiSensor", null,
495                PickListModel.multiSensorPickModelInstance());
496        addItemTab(itemPanel, "MultiSensor", "MultiSensor");
497
498        itemPanel = new IconItemPanel(palette, "Icon");
499        addItemTab(itemPanel, "Icon", "Icon");
500
501        itemPanel = new BackgroundItemPanel(palette, "Background");
502        addItemTab(itemPanel, "Background", "Background");
503
504        itemPanel = new TextItemPanel(palette, "Text");
505        addItemTab(itemPanel, "Text", "Text");
506
507        itemPanel = new RPSItemPanel(palette, "RPSReporter", null);
508        addItemTab(itemPanel, "RPSReporter", "RPSreporter");
509
510        itemPanel = new ClockItemPanel(palette, "FastClock");
511        addItemTab(itemPanel, "FastClock", "FastClock");
512
513        itemPanel = new IndicatorItemPanel(palette, "IndicatorTrack", null);
514        addItemTab(itemPanel, "IndicatorTrack", "IndicatorTrack");
515
516        itemPanel = new IndicatorTOItemPanel(palette, "IndicatorTO", null,
517                PickListModel.turnoutPickModelInstance());
518        addItemTab(itemPanel, "IndicatorTO", "IndicatorTO");
519
520        itemPanel = new PortalItemPanel(palette, "Portal", null);
521        addItemTab(itemPanel, "Portal", "BeanNamePortal");
522
523        _tabPane.addChangeListener(palette);
524    }
525
526    static void addItemTab(ItemPanel itemPanel, String key, String tabTitle) {
527        JScrollPane scrollPane = new JScrollPane(itemPanel);
528        _tabPane.add(Bundle.getMessage(tabTitle), scrollPane);
529        _tabIndex.put(key, itemPanel);
530    }
531
532    @Override
533    public void setPreviewBg(int index) {
534        super.setPreviewBg(index);
535        if (_currentItemPanel != null) {    // wait until tab panels are created
536            for (ItemPanel panel : _tabIndex.values()) {
537                panel.previewColorChange();
538            }
539        }
540    }
541
542    @Override
543    public void stateChanged(ChangeEvent e) {
544        JTabbedPane tp = (JTabbedPane) e.getSource();
545        JScrollPane sp = (JScrollPane) tp.getSelectedComponent();
546        ItemPanel p = (ItemPanel) sp.getViewport().getView();
547        p.closeDialogs();
548        p.init(); // (re)initialize tab pane
549        p.invalidate();
550        Dimension newTabDim = p.getPreferredSize();
551        Dimension oldTabDim = null;
552        if (_currentItemPanel != null) {
553            _currentItemPanel.closeDialogs();
554            oldTabDim = _currentItemPanel.getSize();
555        } else {
556            oldTabDim = newTabDim;
557        }
558        setTitle(Bundle.getMessage("ItemPaletteTitle", Bundle.getMessage(ItemPanel.NAME_MAP.get(p._itemType))));
559        Dimension totalDim = _tabPane.getSize();
560        Dimension deltaDim;
561        if (log.isDebugEnabled()) {
562            deltaDim = new Dimension(totalDim.width - oldTabDim.width, totalDim.height - oldTabDim.height);
563            log.debug(" old _tabPane Dim= ({}, {}) oldType=({})= ({}, {})newType=({})= ({}, {}). diff= ({}, {})",
564                    totalDim.width, totalDim.height, _currentItemPanel._itemType, oldTabDim.width, oldTabDim.height,
565                    p._itemType, newTabDim.width, newTabDim.height, deltaDim.width, deltaDim.height);
566        }
567        deltaDim = p.shellDimension(p);
568        reSize(_tabPane, deltaDim, newTabDim);
569        _currentItemPanel = p;
570    }
571
572    private void makeMenus() {
573        JMenuBar menuBar = new JMenuBar();
574        JMenu findIcon = new JMenu(Bundle.getMessage("findIconMenu"));
575        menuBar.add(findIcon);
576
577        JMenuItem editItem = new JMenuItem(Bundle.getMessage("editIndexMenu"));
578        editItem.addActionListener((ActionEvent e) -> {
579                ImageIndexEditor ii = InstanceManager.getDefault(ImageIndexEditor.class);
580                ii.pack();
581                ii.setVisible(true);
582        });
583        findIcon.add(editItem);
584        findIcon.addSeparator();
585
586        JMenuItem openItem = new JMenuItem(Bundle.getMessage("openDirMenu"));
587        openItem.addActionListener((ActionEvent e) -> InstanceManager.getDefault(DirectorySearcher.class).openDirectory());
588        findIcon.add(openItem);
589
590         JMenuItem searchItem = new JMenuItem(Bundle.getMessage("searchFSMenu"));
591         searchItem.addActionListener((ActionEvent e) -> DirectorySearcher.instance().searchFS());
592         findIcon.add(searchItem);
593
594        setJMenuBar(menuBar);
595        addHelpMenu("package.jmri.jmrit.display.ItemPalette", true);
596    }
597
598    private void closePanels(java.awt.event.WindowEvent e) {
599        java.awt.Component[] comps = _tabPane.getComponents();
600        if (log.isDebugEnabled()) {
601            log.debug("closePanels: tab count= {}", _tabPane.getTabCount());
602        }
603        for (Component comp : comps) {
604            javax.swing.JViewport vp = (javax.swing.JViewport) ((JScrollPane) comp).getComponent(0);
605            Component ip = vp.getView();
606            if (ip instanceof ItemPanel) {
607                ((ItemPanel) ip).closeDialogs();
608            }
609        }
610        super.windowClosing(e);
611    }
612
613    /*
614     * Look for duplicate name of family in the iterated set.
615     */
616    static private boolean familyNameOK(String type, String family, Iterator<String> it) {
617        if (family == null || family.length() == 0) {
618            JOptionPane.showMessageDialog(null,
619                    Bundle.getMessage("EnterFamilyName"),
620                    Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
621            return false;
622        }
623        while (it.hasNext()) {
624            String f = it.next();
625            if (family.equals(f)) {
626                JOptionPane.showMessageDialog(null,
627                        java.text.MessageFormat.format(Bundle.getMessage("DuplicateFamilyName"),
628                                new Object[]{family, type}),
629                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
630                return false;
631            }
632        }
633        return true;
634    }
635
636    /**
637     * Add a new Family of icons to the device type.
638     *
639     * @param type type
640     * @param family family
641     * @param iconMap iconMap
642     * @return result
643     */
644    static protected boolean addFamily(String type, String family, HashMap<String, NamedIcon> iconMap) {
645        if (family == null) {
646            return false;
647        }
648        HashMap<String, HashMap<String, NamedIcon>> typeMap = ItemPalette.getFamilyMaps(type);
649        typeMap.put(family, iconMap);
650        InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
651        return true;
652    }
653
654    /**
655     * Get all the Families of icons for a given device type.
656     *
657     * @param type type
658     * @return map of families
659     */
660    static public @Nonnull HashMap<String, HashMap<String, NamedIcon>> getFamilyMaps(String type) {
661        HashMap<String, HashMap<String, NamedIcon>> families = _iconMaps.get(type);
662        if (families == null) {
663            families = new HashMap<>();
664            _iconMaps.put(type, families);
665        }
666        return families;
667    }
668
669    /**
670     * Remove a Family of icons from the device type.
671     *
672     * @param type type
673     * @param family family
674     */
675    static protected void removeIconMap(String type, String family) {
676        if (log.isDebugEnabled()) {
677            log.debug("removeIconMap for family \"{}\" in type \"{}\"", family, type);
678        }
679        HashMap<String, HashMap<String, NamedIcon>> families = getFamilyMaps(type);
680        families.remove(family);
681        InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
682        if (log.isDebugEnabled()) {
683            for (String s : families.keySet()) {
684                log.debug("removeIconMap remaining Keys: family \"{}\" in type \"{}\"", s, type);
685            }
686        }
687    }
688
689    /*
690     * Get a clone of the Family of icons for a given device type and family.
691     */
692    static public HashMap<String, NamedIcon> getIconMap(String type, String family) {
693        HashMap<String, HashMap<String, NamedIcon>> itemMap = _iconMaps.get(type);
694        if (itemMap == null) {
695            log.error("getIconMap failed. item type \"{}\" not found.", type);
696            return null;
697        }
698        HashMap<String, NamedIcon> iconMap = itemMap.get(family);
699        if (iconMap == null) {
700            log.warn("getIconMap failed. family \"{}\" not found in item type \"{}\"", family, type);
701            return null;
702        }
703        return cloneMap(iconMap);
704    }
705
706    /**
707     * ************ Currently only needed for IndicatorTO type **************
708     * @param type type
709     * @param family family
710     * @param iconMap iconMap
711     * @return result
712     */
713    // add entire family
714    static protected boolean addLevel4Family(String type, String family,
715            HashMap<String, HashMap<String, NamedIcon>> iconMap) {
716        Iterator<String> iter = getLevel4FamilyMaps(type).keySet().iterator();
717        if (familyNameOK(type, family, iter)) {
718            getLevel4FamilyMaps(type).put(family, iconMap);
719            InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
720            return true;
721        }
722        return false;
723    }
724
725    // add entire family
726    static protected void addLevel4FamilyMap(String type, String family,
727            String key, HashMap<String, NamedIcon> iconMap) {
728        HashMap<String, HashMap<String, NamedIcon>> familyMap = getLevel4Family(type, family);
729        familyMap.put(key, iconMap);
730        InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
731    }
732
733    // Currently only needed for IndicatorTO type
734    static protected HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>
735            getLevel4FamilyMaps(String type) {
736        return _indicatorTOMaps.get(type);
737    }
738
739    // Currently only needed for IndicatorTO type
740    static protected HashMap<String, HashMap<String, NamedIcon>>
741            getLevel4Family(String type, String family) {
742        HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> map = _indicatorTOMaps.get(type);
743        return map.get(family);
744    }
745
746    // Currently only needed for IndicatorTO type
747    static protected void removeLevel4IconMap(String type, String family, String key) {
748        if (log.isDebugEnabled()) {
749            log.debug("removeLevel4IconMap for indicator family \"{}\" in type \"{}\" with key \"{}\"",
750                    family, type, key);
751        }
752        if (key != null) {
753            _indicatorTOMaps.get(type).get(family).remove(key);
754        } else {
755            _indicatorTOMaps.get(type).remove(family);
756        }
757        InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
758    }
759
760    ///////////////////////////////////////////////////////////////////////////////
761
762    static protected HashMap<String, NamedIcon> cloneMap(HashMap<String, NamedIcon> map) {
763        HashMap<String, NamedIcon> clone = new HashMap<>();
764        if (map != null) {
765            for (Entry<String, NamedIcon> entry : map.entrySet()) {
766                String name = entry.getKey();
767                NamedIcon icon = new NamedIcon(entry.getValue());
768                clone.put(name, icon);
769            }
770        }
771        return clone;
772    }
773
774    /**
775     * Default key names as listed in defaultPanelIcons.xml are Bundle keys and
776     * nodes in the CatalogTree. However users also define icon sets and store
777     * them in the CatalogTree. The names the user has defined for these sets
778     * (i.e.family" name) are also nodes in the CatalogTree. So it is expected
779     * that such names will fall through as an Exception.  Thus these names are
780     * returned as the user has entered them.  There is no failure of I18N here.
781     * @param name key name
782     * @return usable UI display name
783     */
784    static public String convertText(String name) {
785        String cName = null;
786        try {
787            // NOI18N
788            cName = Bundle.getMessage(name);
789        } catch (Exception e) {
790            cName = name;
791        }
792        return cName;
793    }
794
795    private final static Logger log = LoggerFactory.getLogger(ItemPalette.class);
796
797}