001package jmri.jmrit.display;
002
003import java.awt.*;
004import java.awt.datatransfer.DataFlavor;
005import java.awt.datatransfer.Transferable;
006import java.awt.datatransfer.UnsupportedFlavorException;
007import java.awt.dnd.DnDConstants;
008import java.awt.dnd.DropTarget;
009import java.awt.dnd.DropTargetDragEvent;
010import java.awt.dnd.DropTargetDropEvent;
011import java.awt.dnd.DropTargetEvent;
012import java.awt.dnd.DropTargetListener;
013import java.awt.event.ActionEvent;
014import java.awt.event.ActionListener;
015import java.awt.event.KeyAdapter;
016import java.awt.event.KeyEvent;
017import java.io.IOException;
018import java.util.ArrayList;
019import java.util.Enumeration;
020import java.util.HashMap;
021import java.util.Hashtable;
022import java.util.Iterator;
023import java.util.Map;
024import javax.swing.*;
025import javax.swing.event.ListSelectionEvent;
026import javax.swing.event.ListSelectionListener;
027import javax.swing.table.TableColumn;
028import javax.swing.table.TableColumnModel;
029import javax.swing.tree.TreeNode;
030
031import jmri.CatalogTree;
032import jmri.CatalogTreeManager;
033import jmri.InstanceManager;
034import jmri.NamedBean;
035import jmri.SignalHead;
036import jmri.jmrit.catalog.CatalogPanel;
037import jmri.CatalogTreeLeaf;
038import jmri.CatalogTreeNode;
039import jmri.jmrit.catalog.ImageIndexEditor;
040import jmri.jmrit.catalog.NamedIcon;
041import jmri.jmrit.picker.PickListModel;
042import jmri.util.swing.JmriJOptionPane;
043
044/**
045 * Provides a simple editor for selecting N NamedIcons. Class for Icon Editors
046 * implements "Drag n Drop". Allows drops from icons dragged from a Catalog
047 * preview pane.
048 * <p>
049 * See {@link SensorIcon} for an item that might want to have that type of
050 * information, and {@link jmri.jmrit.display.panelEditor.PanelEditor} for an
051 * example of how to use this.
052 *
053 * @author Pete Cressman Copyright (c) 2009, 2010
054 */
055public class IconAdder extends JPanel implements ListSelectionListener {
056
057    private int ROW_HEIGHT;
058
059    HashMap<String, JToggleButton> _iconMap;
060    ArrayList<String> _iconOrderList;
061    private JScrollPane _pickTablePane;
062
063    private PickListModel<NamedBean> _pickListModel;
064
065    CatalogTreeNode _defaultIcons;      // current set of icons user has selected
066    JPanel _iconPanel;
067    private JPanel _buttonPanel;
068    private String _type;
069    private boolean _userDefaults;
070    protected JTextField _sysNameText; // is set in IconAdderTest
071    JTable _table;
072    JButton _addButton;
073    private JButton _addTableButton;
074    private JButton _changeButton;
075    private JButton _closeButton;
076    private CatalogPanel _catalog;
077    private JFrame _parent;
078    private boolean _allowDeletes;
079    boolean _update;    // updating existing icon from popup
080
081    public IconAdder() {
082        _userDefaults = false;
083        _iconMap = new HashMap<>(10);
084        _iconOrderList = new ArrayList<>();
085        IconAdder.this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
086    }
087
088    public IconAdder(boolean allowDeletes) {
089        this();
090        _allowDeletes = allowDeletes;
091    }
092
093    public IconAdder(String type) {
094        this();
095        _type = type;
096        IconAdder.this.initDefaultIcons();
097    }
098
099    public void reset() {
100        if (_table != null) {
101            _table.clearSelection();
102        }
103        closeCatalog();
104        if (_defaultIcons != null) {
105            makeIconPanel(true);
106            log.debug("IconPanel ready");
107        }
108        this.revalidate();
109    }
110
111    public void initDefaultIcons() {
112        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
113        // unfiltered, xml-stored, default icon tree
114        CatalogTree tree = manager.getBySystemName("NXDI");
115        if (tree != null) {
116            CatalogTreeNode node = tree.getRoot();
117
118            Enumeration<TreeNode> e = node.children();
119
120            while (e.hasMoreElements()) {
121                CatalogTreeNode nChild = (CatalogTreeNode) e.nextElement();
122                if (_type.equals(nChild.toString())) {
123                    _defaultIcons = nChild; // consists of set of a NOI18N appearance name elements,
124                    // each containing an icon URL path
125                    _userDefaults = true;
126                    break;
127                }
128            }
129        }
130        log.debug("initDefaultIcons: type= {}, defaultIcons= {}", _type, _defaultIcons);
131    }
132
133    /**
134     * Replace the existing _defaultIcons TreeSet with a new set,
135     * created from the current _iconMap set of icons. Note these might have I18N labels as their keys.
136     * <p>
137     * The new _defaultIcons might be a null Node.
138     */
139    private void createDefaultIconNodeFromMap() {
140        log.debug("createDefaultIconNodeFromMap for node= {}, _iconOrderList.size()= {}", _type, _iconOrderList.size());
141        _defaultIcons = new CatalogTreeNode(_type);
142        for (Map.Entry<String, JToggleButton> entry : _iconMap.entrySet()) {
143            NamedIcon icon = (NamedIcon) entry.getValue().getIcon();
144            _defaultIcons.addLeaf(new CatalogTreeLeaf(entry.getKey(), icon.getURL(), _iconOrderList.indexOf(entry.getKey())));
145        }
146    }
147
148    public CatalogTreeNode getDefaultIconNode() {
149        log.debug("getDefaultIconNode for node= {}", _type);
150        CatalogTreeNode defaultIcons = new CatalogTreeNode(_type);
151        ArrayList<CatalogTreeLeaf> leafList = _defaultIcons.getLeaves();
152        for (int i = 0; i < leafList.size(); i++) {
153            CatalogTreeLeaf leaf = leafList.get(i);
154            defaultIcons.addLeaf(new CatalogTreeLeaf(leaf.getName(), leaf.getPath(), i));
155        }
156        return defaultIcons;
157    }
158
159    /**
160     * Build iconMap and orderArray from user's choice of defaults.
161     *
162     * @param n the root in a catalog from which icons are made
163     */
164    protected void makeIcons(CatalogTreeNode n) {
165        if (log.isDebugEnabled()) {
166            log.debug("makeIcons from node= {}, numChildren= {}, NumLeaves= {}",
167                    n.toString(), n.getChildCount(), n.getNumLeaves());
168        }
169        _iconMap = new HashMap<>(10);
170        _iconOrderList = new ArrayList<>();
171        ArrayList<CatalogTreeLeaf> leafList = n.getLeaves();
172        // adjust order of icons
173        int k = leafList.size() - 1;
174        for (int i = leafList.size() - 1; i >= 0; i--) {
175            CatalogTreeLeaf leaf = leafList.get(i);
176            String name = leaf.getName();
177            String path = leaf.getPath();
178            switch (name) {
179                case "BeanStateInconsistent":
180                    this.setIcon(0, name, new NamedIcon(path, path));
181                    break;
182                case "BeanStateUnknown":
183                    this.setIcon(1, name, new NamedIcon(path, path));
184                    break;
185                default:
186                    this.setIcon(k, name, new NamedIcon(path, path));
187                    k--;
188                    break;
189            }
190        }
191    }
192
193    /**
194     * @param order the index to icon's name and the inverse order that icons
195     *              are drawn in doIconPanel()
196     * @param label the icon name displayed in the icon panel and the key
197     *              to the icon button in _iconMap, supplied as I18N string
198     * @param icon  the icon displayed in the icon button
199     */
200    protected void setIcon(int order, String label, NamedIcon icon) {
201        // make a button to change that icon
202        log.debug("setIcon at order= {}, key= {}", order, label);
203        JToggleButton button = new IconButton(label, icon);
204        if (icon == null || icon.getIconWidth() < 1 || icon.getIconHeight() < 1) {
205            button.setText(Bundle.getMessage("invisibleIcon"));
206            button.setForeground(Color.lightGray);
207        } else {
208            icon.reduceTo(CatalogPanel.ICON_WIDTH, CatalogPanel.ICON_HEIGHT, CatalogPanel.ICON_SCALE);
209            button.setToolTipText(icon.getName());
210        }
211
212        if (_allowDeletes) {
213            String fileName = "resources/icons/misc/X-red.gif";
214            button.setSelectedIcon(new jmri.jmrit.catalog.NamedIcon(fileName, fileName));
215        }
216        if (icon != null) {
217            icon.reduceTo(CatalogPanel.ICON_WIDTH, CatalogPanel.ICON_HEIGHT, CatalogPanel.ICON_SCALE);
218        }
219
220        _iconMap.put(label, button);
221        // calls may not be in ascending order, so pad array
222        if (order > _iconOrderList.size()) {
223            for (int i = _iconOrderList.size(); i < order; i++) {
224                _iconOrderList.add(i, "placeHolder");
225            }
226        } else {
227            if (order < _iconOrderList.size()) {
228                _iconOrderList.remove(order);
229            }
230        }
231        _iconOrderList.add(order, label);
232    }
233
234    /**
235     * Install the icons used to represent all the states of the entity being
236     * edited.
237     *
238     * @param order (reverse) order of display, (0 last, to N first)
239     * @param label the state name to display. Must be unique from all other
240     *              calls to this method
241     * @param name  the resource name of the icon image to display
242     */
243    public void setIcon(int order, String label, String name) {
244        log.debug("setIcon: order= {}, label= {}, name= {}", order, label, name);
245        this.setIcon(order, label, new NamedIcon(name, name));
246    }
247
248    public void setParent(JFrame parent) {
249        _parent = parent;
250    }
251
252    void pack() {
253        _parent.pack();
254    }
255
256    public int getNumIcons() {
257        return _iconMap.size();
258    }
259
260    static int STRUT_SIZE = 3;
261
262    /**
263     * After all the calls to setIcon(...) are made, make the icon display. Two
264     * columns to save space for subsequent panels.
265     *
266     * @param useDefaults true to use user-specified defaults; false otherwise
267     */
268    public void makeIconPanel(boolean useDefaults) {
269        if (useDefaults && _userDefaults) {
270            makeIcons(_defaultIcons);
271        }
272        log.debug("makeIconPanel updating");
273        clearIconPanel();
274        doIconPanel();
275    }
276
277    private void clearIconPanel() {
278        if (_iconPanel != null) {
279            this.remove(_iconPanel);
280        }
281        _iconPanel = new JPanel();
282        _iconPanel.setLayout(new GridLayout(0,2));
283    }
284
285    protected void doIconPanel() {
286        JPanel panel;
287        for (int i = _iconOrderList.size() - 1; i >= 0; i--) {
288            log.debug("adding icon #{}", i);
289            panel = new JPanel();
290            panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
291            panel.add(Box.createHorizontalStrut(STRUT_SIZE));
292            String key = _iconOrderList.get(i); // NOI18N
293            // TODO BUG edit icon context usage in signal head; turnout etc work OK
294            JPanel p = new JPanel();
295            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
296            String labelName = key;
297            try {
298                labelName = Bundle.getMessage(key); // I18N
299            } catch (java.util.MissingResourceException mre) {
300                log.warn("doIconPanel() property key {} missing", key);
301            }
302            JLabel name = new JLabel(labelName);
303            name.setAlignmentX(Component.CENTER_ALIGNMENT);
304            p.add(name);
305            JToggleButton button = _iconMap.get(key);
306            button.setAlignmentX(Component.CENTER_ALIGNMENT);
307            p.add(button);
308            panel.add(p);
309            // TODO align button centered in GridLayout
310            _iconPanel.add(panel);
311        }
312        this.add(_iconPanel, 0);
313    }
314
315    /**
316     * After the calls to makeIconPanel(), optionally make a pick list table for
317     * managed elements. (Not all Icon Editors use pick lists).
318     *
319     * @param tableModel the model from which the table is created
320     */
321    @SuppressWarnings("unchecked")  //  cast PickListModel<? extends NamedBean> to PickListModel<NamedBean>
322    public void setPickList(PickListModel<? extends NamedBean> tableModel) {
323        _pickListModel = (PickListModel<NamedBean>) tableModel;
324        _table = new JTable(tableModel);
325        _pickListModel.makeSorter(_table);
326
327        _table.setRowSelectionAllowed(true);
328        _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
329        ROW_HEIGHT = _table.getRowHeight();
330        _table.setPreferredScrollableViewportSize(new java.awt.Dimension(200, 7 * ROW_HEIGHT));
331        _table.setDragEnabled(true);
332        TableColumnModel columnModel = _table.getColumnModel();
333
334        TableColumn sNameColumnT = columnModel.getColumn(PickListModel.SNAME_COLUMN);
335        sNameColumnT.setResizable(true);
336        sNameColumnT.setMinWidth(50);
337        sNameColumnT.setMaxWidth(200);
338
339        TableColumn uNameColumnT = columnModel.getColumn(PickListModel.UNAME_COLUMN);
340        uNameColumnT.setResizable(true);
341        uNameColumnT.setMinWidth(100);
342        uNameColumnT.setMaxWidth(300);
343
344        _pickTablePane = new JScrollPane(_table);
345        this.add(_pickTablePane);
346        this.add(Box.createVerticalStrut(STRUT_SIZE));
347        pack();
348    }
349
350    public void setSelection(NamedBean bean) {
351        int row = _pickListModel.getIndexOf(bean);
352        row = _table.convertRowIndexToView(row);
353        _table.addRowSelectionInterval(row, row);
354        _pickTablePane.getVerticalScrollBar().setValue(row * ROW_HEIGHT);
355    }
356
357    /**
358     * When a Pick list is installed, table selection controls the Add button.
359     */
360    @Override
361    public void valueChanged(ListSelectionEvent e) {
362        if (_table == null) {
363            return;
364        }
365        int row = _table.getSelectedRow();
366        log.debug("Table valueChanged: row= {}", row);
367        if (row >= 0) {
368            _addButton.setEnabled(true);
369            _addButton.setToolTipText(null);
370            if (_type != null && _type.equals("SignalHead")) {
371                // update Add Icon panel to match icons displayed to the selected signal head appearances
372                makeIconMap(_pickListModel.getBeanAt(row)); // NOI18N
373                clearIconPanel();
374                doIconPanel();
375            }
376        } else {
377            _addButton.setEnabled(false);
378            _addButton.setToolTipText(Bundle.getMessage("ToolTipPickFromTable"));
379        }
380        validate();
381    }
382
383    /**
384     * Update/Recreate the iconMap for this bean, only called for SignalHeads.
385     *
386     * @param bean the object to create the map for
387     */
388    private void makeIconMap(NamedBean bean) {
389        if (bean != null && _type != null && _type.equals("SignalHead")) {
390            _iconMap = new HashMap<>(12);
391            _iconOrderList = new ArrayList<>();
392            ArrayList<CatalogTreeLeaf> leafList = _defaultIcons.getLeaves();
393            int k = 0;
394            String[] stateKeys = ((SignalHead) bean).getValidStateKeys(); // states contains non-localized appearances
395            for (CatalogTreeLeaf leaf : leafList) {
396                String name = leaf.getName(); // NOI18N
397                log.debug("SignalHead Appearance leaf name= {}", name);
398                for (String state : stateKeys) {
399                    if (name.equals(state) || name.equals("SignalHeadStateDark")
400                            || name.equals("SignalHeadStateHeld")) {
401                        String path = leaf.getPath();
402                        this.setIcon(k++, name, new NamedIcon(path, path));
403                        break;
404                    }
405                }
406            }
407        } else { // no selection, revert to default signal head appearances
408            makeIcons(_defaultIcons);
409        }
410        log.debug("makeIconMap: _iconMap.size()= {}", _iconMap.size());
411    }
412
413    private void checkIconSizes() {
414        if (!_addButton.isEnabled()) {
415            return;
416        }
417        Iterator<JToggleButton> iter = _iconMap.values().iterator();
418        int lastWidth = 0;
419        int lastHeight = 0;
420        boolean first = true;
421        while (iter.hasNext()) {
422            JToggleButton but = iter.next();
423            if (first) {
424                lastWidth = but.getIcon().getIconWidth();
425                lastHeight = but.getIcon().getIconHeight();
426                first = false;
427                continue;
428            }
429            int nextWidth = but.getIcon().getIconWidth();
430            int nextHeight = but.getIcon().getIconHeight();
431            if ((Math.abs(lastWidth - nextWidth) > 3 || Math.abs(lastHeight - nextHeight) > 3)) {
432                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("IconSizeDiff"),
433                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
434                return;
435            }
436            lastWidth = nextWidth;
437            lastHeight = nextHeight;
438        }
439        log.debug("Size: width= {}, height= {}", lastWidth, lastHeight);
440    }
441
442    /**
443     * Used by Panel Editor to make the final installation of the icon(s) into
444     * the user's Panel.
445     * <p>
446     * Note! the selection is cleared. When two successive calls are made, the
447     * 2nd will always return null, regardless of the 1st return.
448     *
449     * @return the selected item
450     */
451    public NamedBean getTableSelection() {
452        if (InstanceManager.getDefault(CatalogTreeManager.class).isIndexChanged()) {
453            checkIconSizes();
454        }
455        int row = _table.getSelectedRow();
456        row = _table.convertRowIndexToModel(row);
457        if (row >= 0) {
458            NamedBean b = _pickListModel.getBeanAt(row);
459            _table.clearSelection();
460            _addButton.setEnabled(false);
461            _addButton.setToolTipText(null);
462            this.revalidate();
463            if (b != null) {
464                log.debug("getTableSelection: row = {}, bean = {}", row, b.getDisplayName());
465            }
466            return b;
467        } else {
468            log.debug("getTableSelection: row = 0");
469        }
470        return null;
471    }
472
473    /**
474     * Get a new NamedIcon object for your own use.
475     *
476     * @param key Name of key (label)
477     * @return Unique object
478     */
479    public NamedIcon getIcon(String key) {
480        log.debug("getIcon for key= {}", key);
481        return new NamedIcon((NamedIcon) _iconMap.get(key).getIcon());
482    }
483
484    /**
485     * Get a new Hashtable of only the icons selected for display.
486     *
487     * @return a map of icons using the icon labels as keys
488     */
489    public Hashtable<String, NamedIcon> getIconMap() {
490        log.debug("getIconMap: _allowDeletes= {}", _allowDeletes);
491        Hashtable<String, NamedIcon> iconMap = new Hashtable<>();
492        for (Map.Entry<String, JToggleButton> entry : _iconMap.entrySet()) {
493            JToggleButton button = entry.getValue();
494            log.debug("getIconMap: key= {}, button.isSelected()= {}", entry.getKey(), button.isSelected());
495            if (!_allowDeletes || !button.isSelected()) {
496                iconMap.put(entry.getKey(), new NamedIcon((NamedIcon) button.getIcon()));
497            }
498        }
499        return iconMap;
500    }
501
502    /*
503     * Support selection of NamedBean from a pick list table.
504     *
505     * @param addIconAction ActionListener that adds an icon to the panel -
506     *          representing either an entity as pick list selection, an
507     *          arbitrary image, or a value, such as a memory value
508     * @param changeIconAction ActionListener that displays sources from
509     *          which to select an image file
510     */
511    public void complete(ActionListener addIconAction, boolean changeIcon,
512            boolean addToTable, boolean update) {
513        _update = update;
514        if (_buttonPanel != null) {
515            this.remove(_buttonPanel);
516        }
517        _buttonPanel = new JPanel();
518        _buttonPanel.setLayout(new BoxLayout(_buttonPanel, BoxLayout.Y_AXIS));
519        JPanel p = new JPanel();
520        p.setLayout(new FlowLayout());
521        if (addToTable) {
522            JPanel pInner = new JPanel();
523            pInner.setLayout(new BoxLayout(pInner, BoxLayout.X_AXIS));
524            _sysNameText = new JTextField();
525            _sysNameText.setPreferredSize(
526                    new Dimension(150, _sysNameText.getPreferredSize().height + 2));
527
528            String tooltip = _pickListModel.getManager().getEntryToolTip();
529            if (tooltip!=null) {
530                StringBuilder sb = new StringBuilder();
531                sb.append("<br>");
532                sb.append(_pickListModel.getManager().getMemo().getUserName());
533                sb.append(" ");
534                sb.append(_pickListModel.getManager().getBeanTypeHandled(true));
535                sb.append(":<br>");
536                sb.append(_pickListModel.getManager().getEntryToolTip());
537                tooltip = sb.toString();
538            }
539
540            _sysNameText.setToolTipText(Bundle.getMessage("newBeanBySysNameTip",
541                _pickListModel.getManager().getBeanTypeHandled(false),
542                _pickListModel.getManager().getMemo().getUserName(),
543                InstanceManager.getDefault(jmri.jmrix.internal.InternalSystemConnectionMemo.class)
544                    .getSystemPrefix()+_pickListModel.getManager().typeLetter(),
545                (tooltip==null ? "" : tooltip)
546                ));
547            _addTableButton = new JButton(Bundle.getMessage("addToTable",_pickListModel.getManager().getBeanTypeHandled()));
548            _addTableButton.addActionListener((ActionEvent a) -> addToTable());
549            _addTableButton.setEnabled(false);
550            _addTableButton.setToolTipText(Bundle.getMessage("ToolTipWillActivate"));
551            pInner.add(_sysNameText);
552            _sysNameText.addKeyListener(new KeyAdapter() {
553                @Override
554                public void keyReleased(KeyEvent a) {
555                    if (_sysNameText.getText().length() > 0) {
556                        _addTableButton.setEnabled(true);
557                        _addTableButton.setToolTipText(null);
558                        _table.clearSelection();
559                    }
560                }
561            });
562
563            pInner.add(_addTableButton);
564            p.add(pInner);
565            _buttonPanel.add(p);
566            p = new JPanel();
567            p.setLayout(new FlowLayout());  //new BoxLayout(p, BoxLayout.Y_AXIS)
568        }
569        if (update) {
570            _addButton = new JButton(Bundle.getMessage("ButtonUpdateIcon"));
571        } else {
572            _addButton = new JButton(Bundle.getMessage("ButtonAddIcon"));
573        }
574        _addButton.addActionListener(addIconAction);
575        _addButton.setEnabled(true);
576        if (changeIcon) {
577            _changeButton = new JButton(Bundle.getMessage("ButtonChangeIcon"));
578            _changeButton.addActionListener((ActionEvent a) -> addCatalog());
579            p.add(_changeButton);
580            _closeButton = new JButton(Bundle.getMessage("ButtonCloseCatalog"));
581            _closeButton.addActionListener((ActionEvent a) -> closeCatalog());
582            _closeButton.setVisible(false);
583            p.add(_closeButton);
584        }
585        _buttonPanel.add(p);
586        if (_table != null) {
587            _addButton.setEnabled(false);
588            _addButton.setToolTipText(Bundle.getMessage("ToolTipPickFromTable"));
589        }
590        addAdditionalButtons(_buttonPanel);
591        p = new JPanel();
592        p.add(_addButton);
593        _buttonPanel.add(p);
594
595        _buttonPanel.add(Box.createVerticalStrut(STRUT_SIZE));
596        _buttonPanel.add(new JSeparator());
597        this.add(_buttonPanel);
598
599        if (changeIcon) {
600            _catalog = CatalogPanel.makeDefaultCatalog();
601            _catalog.setVisible(false);
602            _catalog.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
603            this.add(_catalog);
604        }
605        if (_type != null) {
606            createDefaultIconNodeFromMap();
607        }
608        // Allow initial row to be set without getting callback to valueChanged
609        if (_table != null) {
610            _table.getSelectionModel().addListSelectionListener(this);
611        }
612        pack();
613    }
614
615    protected void addAdditionalButtons(JPanel p) {
616    }
617
618    public boolean addIconIsEnabled() {
619        return _addButton.isEnabled();
620    }
621
622    @SuppressWarnings("unchecked") // PickList is a parameterized class, but we don't use that here
623    void addToTable() {
624        String name = _sysNameText.getText();
625        if (name != null && name.length() > 0) {
626            try {
627                NamedBean bean = _pickListModel.addBean(name);
628                if (bean != null) {
629                    int setRow = _pickListModel.getIndexOf(bean);
630                    _table.setRowSelectionInterval(setRow, setRow);
631                    _pickTablePane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
632                }
633            } catch (IllegalArgumentException ex) {
634                JmriJOptionPane.showMessageDialog(this.getParent(),
635                     ex.getLocalizedMessage(),
636                    Bundle.getMessage("WarningTitle"),  // NOI18N
637                    JmriJOptionPane.WARNING_MESSAGE);
638            }
639        }
640        _sysNameText.setText("");
641        _addTableButton.setEnabled(false);
642        _addTableButton.setToolTipText(Bundle.getMessage("ToolTipWillActivate"));
643    }
644
645    /*
646     * Add panel to change icons.
647     */
648    public void addCatalog() {
649        log.debug("addCatalog() called");
650        // add and display the catalog, so icons can be selected
651        if (_catalog == null) {
652            _catalog = CatalogPanel.makeDefaultCatalog();
653            _catalog.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
654        }
655        _catalog.setVisible(true); // display the tree view
656
657        if (_changeButton != null) {
658            _changeButton.setVisible(false);
659            _closeButton.setVisible(true);
660        }
661        if (_pickTablePane != null) {
662            _pickTablePane.setVisible(false); // hide the bean table during icon edit
663        }
664        pack();
665    }
666
667    void closeCatalog() {
668        if (_changeButton != null) {
669            _catalog.setVisible(false); // hide the tree view
670            _changeButton.setVisible(true);
671            _closeButton.setVisible(false);
672        }
673        if (_pickTablePane != null) {
674            _pickTablePane.setVisible(true);
675        }
676        pack();
677    }
678
679    public void addDirectoryToCatalog() {
680        if (_catalog == null) {
681            _catalog = CatalogPanel.makeDefaultCatalog();
682        }
683        if (_changeButton != null) {
684            _changeButton.setVisible(false);
685            _closeButton.setVisible(true);
686        }
687        this.add(_catalog);
688        this.pack();
689    }
690
691    /**
692     * If icons are changed, update global tree.
693     */
694    private void updateCatalogTree() {
695        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
696        // unfiltered, xml-stored, default icon tree
697        CatalogTree tree = manager.getBySystemName("NXDI");
698        if (tree == null) { // build a new Default Icons tree
699            tree = manager.newCatalogTree("NXDI", "Default Icons");
700        }
701        CatalogTreeNode root = tree.getRoot();
702
703        Enumeration<TreeNode> e = root.children();
704
705        String name = _defaultIcons.toString();
706        while (e.hasMoreElements()) {
707            CatalogTreeNode nChild = (CatalogTreeNode)e.nextElement();
708            if (name.equals(nChild.toString())) {
709                log.debug("Remove node {}", nChild);
710                root.remove(nChild);
711                break;
712            }
713        }
714        root.add(_defaultIcons);
715        InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
716    }
717
718    private class IconButton extends DropButton {
719
720        String key; // NOI18N
721
722        IconButton(String label, Icon icon) {  // init icon passed to avoid ref before ctor complete
723            super(icon);
724            key = label;
725        }
726    }
727
728    /**
729     * Clean up when its time to make it all go away
730     */
731    public void dispose() {
732        // clean up GUI aspects
733        this.removeAll();
734        _iconMap = null;
735        _iconOrderList = null;
736        _catalog = null;
737    }
738
739    class DropButton extends JToggleButton implements DropTargetListener {
740
741        DataFlavor dataFlavor;
742
743        DropButton(Icon icon) {
744            super(icon);
745            try {
746                dataFlavor = new DataFlavor(ImageIndexEditor.IconDataFlavorMime);
747            } catch (ClassNotFoundException cnfe) {
748                log.error("Unable to create drag and drop target.", cnfe);
749            }
750            // is the item created in this next line ever used?
751            new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this);
752            // log.debug("DropJLabel ctor");
753        }
754
755        @Override
756        public void dragExit(DropTargetEvent dte) {
757            // log.debug("DropJLabel.dragExit ");
758        }
759
760        @Override
761        public void dragEnter(DropTargetDragEvent dtde) {
762            // log.debug("DropJLabel.dragEnter ");
763        }
764
765        @Override
766        public void dragOver(DropTargetDragEvent dtde) {
767            // log.debug("DropJLabel.dragOver ");
768        }
769
770        @Override
771        public void dropActionChanged(DropTargetDragEvent dtde) {
772            // log.debug("DropJLabel.dropActionChanged ");
773        }
774
775        @Override
776        public void drop(DropTargetDropEvent e) {
777            try {
778                Transferable tr = e.getTransferable();
779                if (e.isDataFlavorSupported(dataFlavor)) {
780                    NamedIcon newIcon = (NamedIcon) tr.getTransferData(dataFlavor);
781                    if (newIcon != null) { // newIcon never null according to contract
782                        e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
783                        DropTarget target = (DropTarget) e.getSource();
784                        IconButton iconButton = (IconButton) target.getComponent();
785                        String key = iconButton.key;
786                        JToggleButton button = _iconMap.get(key);
787                        NamedIcon oldIcon = (NamedIcon) button.getIcon();
788                        button.setIcon(newIcon);
789                        if (newIcon.getIconWidth() < 1 || newIcon.getIconHeight() < 1) {
790                            button.setText(Bundle.getMessage("invisibleIcon"));
791                            button.setForeground(Color.lightGray);
792                        } else {
793                            button.setText(null);
794                        }
795                        _iconMap.put(key, button);
796                        if (!_update) {
797                            _defaultIcons.deleteLeaf(key, oldIcon.getURL());
798                            _defaultIcons.addLeaf(key, newIcon.getURL());
799                            updateCatalogTree();
800                        }
801                        e.dropComplete(true);
802                        log.debug("DropJLabel.drop COMPLETED for {}, {}", key, newIcon.getURL());
803                    } else {
804                        log.debug("DropJLabel.drop REJECTED!");
805                        e.rejectDrop();
806                    }
807                }
808            } catch (IOException ioe) {
809                log.debug("DropPanel.drop REJECTED!");
810                e.rejectDrop();
811            } catch (UnsupportedFlavorException ufe) {
812                log.debug("DropJLabel.drop REJECTED!");
813                e.rejectDrop();
814            }
815        }
816    }
817
818    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IconAdder.class);
819
820}