001package jmri.jmrit.display.palette;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.datatransfer.DataFlavor;
010import java.awt.event.ActionEvent;
011import java.awt.event.ActionListener;
012import java.util.Enumeration;
013import java.util.HashMap;
014import java.util.Map.Entry;
015
016import javax.swing.AbstractAction;
017import javax.swing.AbstractButton;
018import javax.swing.Action;
019import javax.swing.BorderFactory;
020import javax.swing.Box;
021import javax.swing.BoxLayout;
022import javax.swing.ButtonGroup;
023import javax.swing.JButton;
024import javax.swing.JComponent;
025import javax.swing.JLabel;
026import javax.swing.JPanel;
027import javax.swing.JRadioButton;
028import javax.swing.KeyStroke;
029
030import jmri.jmrit.catalog.CatalogPanel;
031import jmri.jmrit.catalog.NamedIcon;
032import jmri.jmrit.display.DisplayFrame;
033import jmri.jmrit.display.Editor;
034import jmri.util.swing.ImagePanel;
035import jmri.util.swing.JmriJOptionPane;
036
037/**
038 * ItemPanel general implementation for placement of CPE items having sets of
039 * icons (families). The "family" is the set of icons that represent the various
040 * states and/or status of the item.
041 *
042 * @see ItemPanel palette class diagram
043 * @author Pete Cressman Copyright (c) 2010, 2011, 2018
044 * @author Egbert Broerse 2017
045 */
046public abstract class FamilyItemPanel extends ItemPanel {
047
048    protected String _family;
049    // _iconPanel (from ItemPanel) contains all the images of the icons for a
050    // selected family.
051    // _previewPanel (from ItemPanel) contains _iconPanel and optionally
052    // _dragIconPanel.
053    //protected JPanel _iconFamilyPanel; // Holds _previewPanel, _familyButtonPanel.
054    protected ImagePanel _dragIconPanel; // panel to drag to the icons to the
055                                         // control panel, hidden upon [Show Icons]
056    protected JPanel _familyButtonPanel; // panel of radioButtons to select icon family
057    protected JButton _showIconsButton;
058    protected JButton _updateButton;
059    protected HashMap<String, NamedIcon> _unstoredMap;
060    protected IconDialog _dialog;
061    protected ButtonGroup _familyButtonGroup;
062
063    protected boolean _isUnstoredMap;
064    protected boolean _cntlDown;    // when true, edit dialog will add default sets
065
066    /**
067     * Constructor types with multiple families and multiple icon families.
068     *
069     * @param parentFrame enclosing parentFrame
070     * @param type        bean type
071     * @param family      icon family
072     */
073    public FamilyItemPanel(DisplayFrame parentFrame, String type, String family) {
074        super(parentFrame, type);
075        _family = family;
076    }
077
078    @Override
079    public void init() {
080        if (!_initialized) {
081            makeShowIconsButton();
082            super.init();
083        }
084        hideIcons();
085    }
086
087    /**
088     * Init for update of existing palette item type.
089     *
090     * @param doneAction doneAction
091     * @param iconMap    iconMap
092     */
093    public void init(ActionListener doneAction, HashMap<String, NamedIcon> iconMap) {
094        _update = true;
095        _doneAction = doneAction;
096        _suppressDragging = true; // no dragging when updating
097        if (iconMap != null) {
098            checkCurrentMap(iconMap); // is map in catalog?
099        }
100        if (_family == null || _family.isEmpty()) {
101            _family = Bundle.getMessage("unNamed");
102        }
103        initIconFamiliesPanel();
104        _initialized = true;
105    }
106
107    /**
108     * Initialization for conversion of plain track to indicator track by
109     * CircuitBuilder.
110     *
111     * @param bottomPanel button panel
112     */
113    public void init(JPanel bottomPanel) {
114        _update = false;
115        _suppressDragging = true; // no dragging in circuitBuilder
116        initIconFamiliesPanel();
117        remove(_bottomPanel);
118        bottomPanel.add(makeShowIconsButton(), 0);
119        add(bottomPanel);
120        _initialized = true;
121        hideIcons();
122    }
123
124    /**
125     * Needed by CPE ConvertDialog.java
126     *
127     * @return JPanel
128     */
129    public JPanel getBottomPanel() {
130        return _bottomPanel;
131    }
132
133    public JButton getUpdateButton() {
134        return _updateButton;
135    }
136
137    /**
138     * Add [Update] button to _bottomPanel.
139     * @param doneAction Action for button
140     */
141    @Override
142    protected JButton makeUpdateButton(ActionListener doneAction) {
143        _updateButton = new JButton(Bundle.getMessage("updateButton"));
144        _updateButton.addActionListener(doneAction);
145        _updateButton.setToolTipText(Bundle.getMessage("ToolTipPickFromTable"));
146        return _updateButton;
147    }
148
149    @Override
150    protected JPanel makeItemButtonPanel() {
151        JPanel panel = new JPanel();
152        panel.add(makeShowIconsButton());
153        panel.add(makeEditButton());
154        if (!_update) {
155            addCreateDeleteFamilyButtons(panel);
156        }
157        return panel;
158    }
159
160    protected JButton makeShowIconsButton() {
161        if (_showIconsButton == null) {
162            _showIconsButton = new JButton(Bundle.getMessage("ShowIcons"));
163            _showIconsButton.addActionListener(a -> {
164                if (_iconPanel.isVisible()) {
165                    hideIcons();
166                } else {
167                    showIcons();
168                }
169            });
170            _showIconsButton.setToolTipText(Bundle.getMessage("ToolTipShowIcons"));
171        }
172        return _showIconsButton;
173    }
174
175    private JButton makeEditButton() {
176        JButton editButton = new JButton(Bundle.getMessage("ButtonEditIcons"));
177        editButton.addActionListener(a -> openDialog(_itemType, _family));
178        editButton.setToolTipText(Bundle.getMessage("ToolTipEditIcons"));
179        makeControlKeyBinder(editButton);
180        return editButton;
181    }
182
183    private JButton makeNewFamilyButton() {
184        JButton button = new JButton(Bundle.getMessage("createNewFamily"));
185        button.addActionListener(a -> newFamilyDialog());
186        button.setToolTipText(Bundle.getMessage("ToolTipAddFamily"));
187        makeControlKeyBinder(button);
188        return button;
189    }
190
191    /**
192     * Replacement panel for _bottomPanel when no icon families exist for
193     * _itemType.
194     */
195    @Override
196    protected JPanel makeSpecialBottomPanel(boolean update) {
197        JPanel _bottom2Panel = new JPanel();
198        if (update) {
199            _bottom2Panel.add(makeEditButton());
200        } else {
201            _bottom2Panel.add(makeNewFamilyButton());
202        }
203        JButton button = new JButton(Bundle.getMessage("RestoreDefault"));
204        button.addActionListener(a -> loadDefaultType());
205        _bottom2Panel.add(button);
206        return _bottom2Panel;
207    }
208
209    protected void addCreateDeleteFamilyButtons(JPanel panel) {
210        panel.add(makeNewFamilyButton());
211        JButton deleteButton = new JButton(Bundle.getMessage("deleteFamily"));
212        deleteButton.addActionListener(a -> deleteFamilySet());
213        deleteButton.setToolTipText(Bundle.getMessage("ToolTipDeleteFamily"));
214        panel.add(deleteButton);
215    }
216
217    /**
218     * Check whether map is one of the families. If so, return. If not, does
219     * user want to add it to families? If so, add. If not, save for return when
220     * updated. update ctor has entered a name for _family.
221     *
222     * @param iconMap existing map of the icon
223     */
224    private void checkCurrentMap(HashMap<String, NamedIcon> iconMap) {
225        if (_itemType.equals("SignalMast")) {
226            return;
227        }
228        String family = getValidFamilyName(_family, iconMap);
229
230        if (_isUnstoredMap) {
231            _unstoredMap = iconMap;
232            int result = JmriJOptionPane.showConfirmDialog(_frame.getEditor(), Bundle.getMessage("UnkownFamilyName", family),
233                    Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
234                    JmriJOptionPane.QUESTION_MESSAGE);
235            if (result == JmriJOptionPane.YES_OPTION) {
236                ItemPalette.addFamily(_itemType, family, iconMap);
237            }
238            _family = family;
239        } else {
240            if (family != null) { // icons same as a known family, maybe with
241                                  // another name
242                if (!family.equals(_family)) {
243                    log.info(
244                            "{} icon's family \"{}\" found but is called \"{}\" in the Catalog.  Name changed to Catalog name.",
245                            _itemType, _family, family);
246                    _family = family;
247                }
248            }
249        }
250    }
251
252    /**
253     * Check that family name proposed by user for an icon family 1. name is not
254     * a duplicate key 2. icon family is already stored. (Sets "_isUnstoredMap"
255     * flag.)
256     *
257     * @param family  name for icon set
258     * @param iconMap map the family name refers to.
259     * @return valid family name or null if user declines to provide a valid
260     *         name.
261     */
262    protected String getValidFamilyName(String family, HashMap<String, NamedIcon> iconMap) {
263        HashMap<String, HashMap<String, NamedIcon>> families = ItemPalette.getFamilyMaps(_itemType);
264        String mapFamily;
265        if (iconMap != null) {
266            mapFamily = findFamilyOfMap(null, iconMap, families);
267            if (mapFamily == null) {
268                _isUnstoredMap = true;
269            } else {
270                log.debug("getValidFamilyName: findFamilyOfMap {} found stored family \"{}\" for family \"{}\".",
271                        _itemType, mapFamily, family);
272                _isUnstoredMap = false;
273                if (family != null) {
274                    return mapFamily;
275                }
276            }
277        }
278        mapFamily = family;
279        // check that name is not duplicate.
280        boolean nameOK = false;
281        while (!nameOK) {
282            if (mapFamily == null || mapFamily.isEmpty()) {
283                Component fr;
284                if (_dialog != null) {
285                    fr = _dialog;
286                } else {
287                    fr = this;
288                }
289                mapFamily = JmriJOptionPane.showInputDialog(fr, Bundle.getMessage("EnterFamilyName"), 
290                    Bundle.getMessage("createNewFamily"), JmriJOptionPane.QUESTION_MESSAGE );
291                if (mapFamily == null) { // user quit
292                    return null;
293                }
294            }
295            if (families.isEmpty()) {
296                break;
297            }
298            for (String fam : families.keySet()) {
299                if (mapFamily.equals(fam)) {
300                    if (_update) {
301                        String thisType = NAME_MAP.get(_itemType);
302                        JmriJOptionPane.showMessageDialog(_frame,
303                            Bundle.getMessage("DuplicateFamilyName", mapFamily, Bundle.getMessage(thisType),
304                            Bundle.getMessage("UseAnotherName")), Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
305                        mapFamily = null;
306                        nameOK = false;
307                        break;
308                    } else {
309                        return mapFamily;
310                    }
311                }
312                nameOK = true;
313            }
314            if (!nameOK && _update) {
315                break;
316            }
317        }
318        return mapFamily;
319    }
320
321    /**
322     * Find the family name of the map in a families HashMap.
323     *
324     * @param exemptFamily exclude from matching
325     * @param newMap       iconMap
326     * @param families     families of itemType
327     * @return null if map is not in the family
328     */
329    protected String findFamilyOfMap(String exemptFamily, HashMap<String, NamedIcon> newMap,
330            HashMap<String, HashMap<String, NamedIcon>> families) {
331        for (Entry<String, HashMap<String, NamedIcon>> entry : families.entrySet()) {
332            String family = entry.getKey();
333            // log.debug("FamilyKey = {}", entry.getKey());
334            if (!family.equals(exemptFamily)) {
335                if (mapsAreEqual(entry.getValue(), newMap)) {
336                    // log.debug("findFamilyOfMap: Map found with name \"{}\"",
337                    // entry.getKey());
338                    return entry.getKey();
339                }
340            }
341        }
342        return null;
343    }
344
345    protected boolean namesStoredMap(String family) {
346        HashMap<String, HashMap<String, NamedIcon>> families = ItemPalette.getFamilyMaps(_itemType);
347        return families.containsKey(family);
348    }
349
350    /*
351     * Entry point when returning from result of familiesMissing() call
352     */
353    @Override
354    protected void makeFamiliesPanel() {
355        HashMap<String, HashMap<String, NamedIcon>> families = ItemPalette.getFamilyMaps(_itemType);
356        boolean isEmpty = families.values().isEmpty();
357        if (_bottomPanel == null) {
358            makeBottomPanel(isEmpty);
359        } else {
360           if (isEmpty ^ _wasEmpty) {
361               remove(_bottomPanel);
362               makeBottomPanel(isEmpty);
363               log.debug("REMAKE BOTTOM PANEL {}", _itemType);
364           }
365        }
366        if (!isEmpty) {
367            makeFamilyButtons(families.keySet()); // makes _familyButtonPanel
368            if (_currentIconMap == null || _currentIconMap.isEmpty()) {
369                _currentIconMap = families.get(_family);
370            }
371        }
372
373        if (_currentIconMap == null) {
374            if (_isUnstoredMap) {
375                _currentIconMap = _unstoredMap;
376            } else {
377                _currentIconMap = new HashMap<>();
378            }
379        }
380        _wasEmpty = isEmpty;
381
382        if (!_suppressDragging) {
383            makeDragIconPanel();
384            makeDndIcon(_currentIconMap);
385        }
386
387        addIconsToPanel(_currentIconMap, _iconPanel, false);
388        addFamilyPanels(!isEmpty);
389    }
390
391    @Override
392    protected JPanel instructions() {
393        JPanel blurb = new JPanel();
394        blurb.setLayout(new BoxLayout(blurb, BoxLayout.Y_AXIS));
395        String thisType = NAME_MAP.get(_itemType);
396        blurb.add(new JLabel(Bundle.getMessage("PickRowBean", Bundle.getMessage(thisType))));
397        blurb.add(new JLabel(Bundle.getMessage("DragBean")));
398        JPanel panel = new JPanel();
399        panel.add(blurb);
400        return panel;
401    }
402
403    protected void addFamilyPanels(boolean hasMaps) {
404        makePreviewPanel(hasMaps, _dragIconPanel);
405
406        if (_familyButtonPanel != null) {
407            _iconFamilyPanel.add(_familyButtonPanel);
408        }
409        _iconPanel.setVisible(false);
410    }
411
412    // the null checks are needed for several rare cases where nulls can occur
413    protected void updateFamiliesPanel() {
414        log.debug("updateFamiliesPanel for {}", _itemType);
415        if (_iconPanel != null) {
416            _iconPanel.removeAll();
417        }
418        if (_dragIconPanel != null) {
419            _dragIconPanel.removeAll();
420        }
421        if (_previewPanel != null) {
422            _previewPanel.setVisible(false);
423        }
424        if (_familyButtonPanel != null) {
425            _familyButtonPanel.removeAll();
426        }
427        makeFamiliesPanel();
428        _iconFamilyPanel.invalidate();
429    }
430
431    /**
432     * Make the _familyButtonPanel panel of buttons to select a family. Create
433     * and set actions of radioButtons to change family on pane.
434     *
435     * @param keySet of icon family names
436     */
437    protected void makeFamilyButtons(java.util.Set<String> keySet) {
438        if (_familyButtonPanel == null) {
439            _familyButtonPanel = new JPanel(); // this is only a local object
440            _familyButtonPanel.setLayout(new BoxLayout(_familyButtonPanel, BoxLayout.Y_AXIS));
441        } else {
442            _familyButtonPanel.removeAll();
443        }
444        log.debug("makeFamilyButtons for {} family= {}", _itemType, _family);
445        String thisType = NAME_MAP.get(_itemType);
446        String txt = Bundle.getMessage("IconFamiliesLabel", Bundle.getMessage(thisType));
447
448        JPanel p = new JPanel(new FlowLayout());
449        p.add(new JLabel(txt));
450        p.setOpaque(false);
451        _familyButtonPanel.add(p);
452        _familyButtonGroup = new ButtonGroup();
453
454        GridBagLayout gridbag = new GridBagLayout();
455        GridBagConstraints c = ItemPanel.itemGridBagConstraint();
456
457        int numCol = 4;
458        JPanel buttonPanel = new JPanel();
459        buttonPanel.setLayout(gridbag);
460        String family = "";
461        int length = 0;
462        JRadioButton button = null;
463
464        for (String s : keySet) {
465            family = s;
466            length += family.length();
467            button = new JRadioButton(family);
468            addFamilyButtonListener(button, family);
469            if (family.equals(_family)) {
470                button.setSelected(true);
471            }
472            log.debug("{} ActionListener and button for family \"{}\" at gridx= {} gridy= {}", _itemType, family, c.gridx, c.gridy);
473            gridbag.setConstraints(button, c);
474            buttonPanel.add(button, c);
475            if (c.gridx >= numCol || length > 50) { // start next row
476                c.gridy++;
477                c.gridx = 0;
478                length = 0;
479            }
480            c.gridx++;
481        }
482        if (button != null && _family == null) {
483            button.setSelected(true);
484            _family = family;
485        } else if (_family != null && !keySet.contains(_family)) {
486            button = new JRadioButton(_family);
487            addFamilyButtonListener(button, _family);
488            log.debug("\"{}\" ActionListener and button for family \"{}\" at gridx= {} gridy= {}", _itemType, _family,
489                    c.gridx, c.gridy);
490            gridbag.setConstraints(button, c);
491            buttonPanel.add(button, c);
492            button.setSelected(true);
493        }
494        _familyButtonPanel.add(buttonPanel);
495    }
496
497    private void addFamilyButtonListener(JRadioButton button, String family) {
498        button.addActionListener(new ActionListener() {
499            String fam;
500
501            @Override
502            public void actionPerformed(ActionEvent e) {
503                setFamily(fam);
504            }
505
506            ActionListener init(String f) {
507                fam = f;
508                return this;
509            }
510        }.init(family));
511        _familyButtonGroup.add(button);
512    }
513
514    /**
515     * Position initial Preview component on _iconFamilyPanel. If already
516     * present, keep and clear it.
517     */
518    protected void makeDragIconPanel() {
519        if (_dragIconPanel == null) {
520            _dragIconPanel = new ImagePanel();
521            _dragIconPanel.setOpaque(true); // to show background color/squares
522            _dragIconPanel.setLayout(new FlowLayout());
523            _dragIconPanel.setBorder(BorderFactory.createLineBorder(Color.black));
524            _dragIconPanel.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
525        } else {
526            _dragIconPanel.removeAll();
527        }
528        _dragIconPanel.setImage(_frame.getPreviewBackground());
529        if (_iconPanel != null) {
530            _iconPanel.setImage(_frame.getPreviewBackground());
531        }
532        _dragIconPanel.setVisible(true);
533    }
534
535    @Override
536    protected JPanel makeIconDisplayPanel(String key, HashMap<String, NamedIcon> iconMap, boolean dropIcon) {
537        NamedIcon icon = iconMap.get(key);
538        JPanel panel = new JPanel();
539        JLabel image;
540        if (dropIcon) {
541            image = new DropJLabel(icon, iconMap);
542        } else {
543            image = new JLabel(icon);
544        }
545        wrapIconImage(icon, image, panel, key);
546        return panel;
547    }
548
549    protected JLabel getDragger(DataFlavor flavor, HashMap<String, NamedIcon> map, NamedIcon icon) {
550        return null;
551    }
552
553    protected void makeDndIcon(HashMap<String, NamedIcon> iconMap) {
554        if (iconMap == null) {
555            return;
556        }
557        JLabel label;
558        NamedIcon icon;
559        String scaleText;
560        if (!iconMap.isEmpty()) {
561            String displayKey = getDisplayKey();
562            if (iconMap.get(displayKey) == null) {
563                displayKey = (String) iconMap.keySet().toArray()[0];
564            }
565            icon = iconMap.get(displayKey);
566            if (log.isDebugEnabled()) {
567                log.debug("makeDndIcon for {}, {}. displayKey \"{}\" has icon {}",
568                        _itemType, _family, displayKey, (icon != null));
569            }
570             if (icon != null) {
571                icon = new NamedIcon(icon);
572                double scale = icon.reduceTo(CatalogPanel.ICON_WIDTH,
573                        CatalogPanel.ICON_HEIGHT, CatalogPanel.ICON_SCALE);
574                scaleText = java.text.MessageFormat.format(Bundle.getMessage("scale"),
575                        CatalogPanel.printDbl(scale, 2));
576            } else {
577                scaleText = Bundle.getMessage("noIcon");
578            }
579            try {
580                label = getDragger(new DataFlavor(Editor.POSITIONABLE_FLAVOR), iconMap, icon);
581            } catch (java.lang.ClassNotFoundException cnfe) {
582                log.warn("no DndIconPanel for {}, {} created", _itemType, displayKey, cnfe);
583                label = new JLabel(scaleText);
584            }
585        } else {
586            label = new JLabel("- - - - - -");
587            scaleText = Bundle.getMessage("noIcon");
588        }
589        JPanel panel = makeDragIconPanel(label, scaleText);
590        _dragIconPanel.add(panel);
591    }
592
593    /**
594     * Get the key to display the icon to be used for dragging to the panel
595     *
596     * @return key for desired icon
597     */
598    abstract protected String getDisplayKey();
599
600    private JPanel makeDragIconPanel(JLabel label, String scaleText) {
601        JPanel panel = new JPanel();
602        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
603        String borderName = Bundle.getMessage("dragToPanel");
604        panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black),
605                borderName));
606        panel.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
607        panel.setOpaque(false);
608        panel.add(Box.createVerticalGlue());
609        if (label != null) {
610            label.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
611            // label.setIcon(icon);
612            label.setName(borderName);
613            label.setOpaque(false);
614            JPanel p = new JPanel();
615            p.add(label);
616            p.setOpaque(false);
617            panel.add(p);
618        }
619        JPanel p = new JPanel();
620        p.setOpaque(false);
621        p.add(new JLabel(scaleText));
622        panel.add(p);
623        Dimension dim = panel.getPreferredSize();
624        dim.width = Math.max(CatalogPanel.ICON_WIDTH, dim.width) + 10;
625        panel.setPreferredSize(dim);
626        return panel;
627    }
628
629    @Override
630    protected void hideIcons() {
631        boolean isPalette = (_frame instanceof ItemPalette);
632        Dimension totalDim = _frame.getSize();
633        Dimension oldDim = getSize();
634        if (_iconPanel != null) {
635            _iconPanel.setVisible(false);
636            _iconPanel.invalidate(); // force redraw
637            if (_update) {
638                _previewPanel.setVisible(false);
639            }
640            if (!_suppressDragging) {
641                _dragIconPanel.setVisible(true);
642                _dragIconPanel.invalidate();
643                _previewPanel.setVisible(true);
644                _instructions.setVisible(true);
645            } else {
646                _previewPanel.setVisible(false);
647            }
648            _previewPanel.invalidate(); // force redraw
649        }
650        reSizeDisplay(isPalette, oldDim, totalDim);
651        _showIconsButton.setText(Bundle.getMessage("ShowIcons"));
652        closeDialogs();
653    }
654
655    protected void showIcons() {
656        boolean isPalette = (_frame instanceof ItemPalette);
657        Dimension totalDim = _frame.getSize();
658        Dimension oldDim = getSize();
659        if (_iconPanel != null) {
660            _iconPanel.setVisible(true);
661            _iconPanel.invalidate(); // force redraw
662            if (_update) {
663                _previewPanel.setVisible(true);
664            }
665            if (!_suppressDragging) {
666                _dragIconPanel.setVisible(false);
667                _dragIconPanel.invalidate();
668                _instructions.setVisible(false);
669            } else {
670                _previewPanel.setVisible(true);
671            }
672            _previewPanel.invalidate(); // force redraw
673        }
674        reSizeDisplay(isPalette, oldDim, totalDim);
675        _showIconsButton.setText(Bundle.getMessage("HideIcons"));
676        closeDialogs();
677    }
678
679    /**
680     * Action item for deletion of an icon family.
681     */
682    protected void deleteFamilySet() {
683        if (JmriJOptionPane.showConfirmDialog(_frame, Bundle.getMessage("confirmDelete", _family),
684                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
685                JmriJOptionPane.QUESTION_MESSAGE) == JmriJOptionPane.YES_OPTION) {
686            ItemPalette.removeIconMap(_itemType, _family);
687            _family = null;
688            _currentIconMap = null;
689            updateFamiliesPanel();
690        }
691    }
692
693    protected void setControlDown(boolean b) {
694        _cntlDown = b;
695    }
696    Action pressed = new AbstractAction() {
697        @Override
698        public void actionPerformed(ActionEvent e) {
699            setControlDown(true);
700        }
701    };
702    Action released = new AbstractAction() {
703        @Override
704        public void actionPerformed(ActionEvent e) {
705            setControlDown(false);
706        }
707    };
708
709    private void makeControlKeyBinder(JComponent comp) {
710        comp.getInputMap().put(KeyStroke.getKeyStroke("control A"), "pressed");
711        comp.getActionMap().put("pressed", pressed);
712        comp.getInputMap().put(KeyStroke.getKeyStroke("control released A"), "released");
713        comp.getActionMap().put("released", released);
714    }
715
716    private boolean newFamilyDialog() {
717        if (_cntlDown) {
718            loadDefaultType();
719            _cntlDown = false;
720            return true;
721        }
722        String family = JmriJOptionPane.showInputDialog(_frame, Bundle.getMessage("EnterFamilyName"),
723            Bundle.getMessage("createNewFamily"), JmriJOptionPane.QUESTION_MESSAGE );
724        if (family == null) {
725            return false;
726        }
727        _family = null; // don't delete current family when adding new
728        openDialog(_itemType, family);
729        return true;
730    }
731
732    protected void openDialog(String type, String family) {
733        closeDialogs();
734        hideIcons();
735        _dialog = new IconDialog(type, family, this);
736        if (_family == null) {
737            _dialog.setMap(null);
738        } else {
739            _dialog.setMap(_currentIconMap);
740        }
741    }
742
743    /**
744     * IconDialog calls this method to make any changes 'permanent'. It is
745     * responsible for testing that the changes are valid.
746     *
747     * @param family  family name, possibly changed
748     * @param iconMap family map, possibly changed
749     */
750    protected void dialogDoneAction(String family, HashMap<String, NamedIcon> iconMap) {
751        if (!_update && !(family.equals(_family) && mapsAreEqual(iconMap, _currentIconMap))) {
752            ItemPalette.removeIconMap(_itemType, _family);
753            ItemPalette.addFamily(_itemType, family, iconMap);
754        } else {
755            _currentIconMap = iconMap;
756            if (!namesStoredMap(family)) {
757                _isUnstoredMap = true;
758            }
759            if (_isUnstoredMap) {
760                _unstoredMap = iconMap;
761            }
762        }
763        _family = family;
764        makeFamiliesPanel();
765        setFamily(family);
766        _cntlDown = false;
767        hideIcons();
768        if (log.isDebugEnabled()) {
769            log.debug("dialogDoneAction done for {} {} update={}. unStored={}",
770                    _itemType, _family, _update, _isUnstoredMap);
771        }
772    }
773
774    protected boolean isUpdate() {
775        return _update;
776    }
777
778    @Override
779    public void closeDialogs() {
780        if (_dialog != null) {
781            _dialog.closeDialogs();
782            _dialog.dispose();
783            _dialog = null;
784        }
785    }
786
787    public void dispose() {
788        closeDialogs();
789    }
790
791    /**
792     * Recover from cancelled Add Family dialog
793     */
794    protected void setFamily() {
795        if (_familyButtonGroup == null) {
796            return;
797        }
798        Enumeration<AbstractButton> en = _familyButtonGroup.getElements();
799        while (en.hasMoreElements()) {
800            AbstractButton button = en.nextElement();
801            if (button.isSelected()) {
802                _family = button.getText();
803                break;
804            }
805        }
806    }
807    /**
808     * Action of family radio button. MultiSensorItemPanel and
809     * IndicatorTOItemPanel must override.
810     *
811     * @param family icon family name
812     */
813    protected void setFamily(String family) {
814        _family = family;
815        setFamilyMaps();
816        _iconFamilyPanel.invalidate(); // force redraw
817        hideIcons();
818        setFamilyButton();
819    }
820
821    protected void setFamilyMaps() {
822        _currentIconMap = ItemPalette.getIconMap(_itemType, _family);
823        if (_currentIconMap == null) {
824            _isUnstoredMap = true;
825            _currentIconMap = _unstoredMap;
826        }
827        if (!_suppressDragging) {
828            makeDragIconPanel();
829            makeDndIcon(_currentIconMap);
830        }
831        addIconsToPanel(_currentIconMap, _iconPanel, false);
832    }
833
834    protected void setFamilyButton() {
835        if (_familyButtonGroup == null) {
836            return;
837        }
838        Enumeration<AbstractButton> en = _familyButtonGroup.getElements();
839        while (en.hasMoreElements()) {
840            AbstractButton button =  en.nextElement();
841            if (_family != null && _family.equals(button.getText())) {
842                button.setSelected(true);
843                break;
844            }
845        }
846    }
847
848    @Override
849    protected void previewColorChange() {
850        if (_dialog != null) {
851            ImagePanel iconPanel = _dialog.getIconEditPanel();
852            if (iconPanel != null) {
853                iconPanel.setImage(_frame.getPreviewBackground());
854            }
855            iconPanel = _dialog.getCatalogPreviewPanel();
856            if (iconPanel != null) {
857                iconPanel.setImage(_frame.getPreviewBackground());
858            }
859        }
860        log.debug("FamilyItemPanel.super stores previewColorChange");
861        super.previewColorChange();
862    }
863
864    /**
865     * Create icon set to panel icon display class.
866     *
867     * @return updated icon map
868     */
869    public HashMap<String, NamedIcon> getIconMap() {
870        if (_currentIconMap != null) {
871            return _currentIconMap;
872        }
873        HashMap<String, NamedIcon> map = ItemPalette.getIconMap(_itemType, _family);
874        if (map == null) {
875            map = _unstoredMap;
876        }
877        if (map == null) {
878            log.warn("Family \"{}\" for type \"{}\" not found.", _family, _itemType);
879            map = makeNewIconMap(_itemType);
880        }
881        return map;
882    }
883
884    public String getFamilyName() {
885        return _family;
886    }
887
888    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FamilyItemPanel.class);
889
890}