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