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