001package jmri.jmrit.display.palette;
002
003import java.awt.Color;
004import java.awt.Dimension;
005import java.awt.datatransfer.DataFlavor;
006import java.awt.datatransfer.Transferable;
007import java.awt.datatransfer.UnsupportedFlavorException;
008import java.awt.dnd.DnDConstants;
009import java.awt.dnd.DropTarget;
010import java.awt.dnd.DropTargetAdapter;
011import java.awt.dnd.DropTargetDropEvent;
012import java.awt.event.ActionListener;
013import java.io.IOException;
014import java.util.HashMap;
015
016import javax.annotation.Nonnull;
017import javax.swing.BorderFactory;
018import javax.swing.Box;
019import javax.swing.BoxLayout;
020import javax.swing.Icon;
021import javax.swing.JButton;
022import javax.swing.JLabel;
023import javax.swing.JPanel;
024
025import jmri.jmrit.catalog.CatalogPanel;
026import jmri.jmrit.catalog.DragJLabel;
027import jmri.jmrit.catalog.ImageIndexEditor;
028import jmri.jmrit.catalog.NamedIcon;
029import jmri.jmrit.display.DisplayFrame;
030import jmri.jmrit.display.Editor;
031import jmri.jmrit.display.LinkingLabel;
032import jmri.jmrit.display.PositionableLabel;
033import jmri.util.swing.ImagePanel;
034import jmri.util.swing.JmriJOptionPane;
035import jmri.util.swing.JmriMouseAdapter;
036import jmri.util.swing.JmriMouseEvent;
037import jmri.util.swing.JmriMouseListener;
038
039/**
040 * ItemPanel for plain Icons and Backgrounds.
041 * Does NOT use IconDialog class to add, replace or delete icons.
042 * @see ItemPanel palette class diagram
043 */
044public class IconItemPanel extends ItemPanel {
045
046    protected JButton _catalogButton;
047    protected JButton _deleteIconButton;
048    protected CatalogPanel _catalog;
049    protected IconDisplayPanel _selectedIcon;
050    protected DataFlavor _positionableDataFlavor;
051    protected DataFlavor _namedIconDataFlavor;
052    protected int _level = Editor.ICONS; // sub classes can override (e.g. Background)
053    private NamedIcon _updateIcon;
054
055    /**
056     * Constructor for plain icons and backgrounds.
057     *
058     * @param type type
059     * @param parentFrame parentFrame
060     */
061    public IconItemPanel(DisplayFrame parentFrame, String type) {
062        super(parentFrame, type);
063        setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
064    }
065
066    @Override
067    public void init() {
068        if (!_initialized) {
069            super.init();
070            initLinkPanel();
071            _catalog = makeCatalog();
072            add(_catalog);
073        }
074    }
075
076    /**
077     * Init for update of existing palette item type.
078     *
079     * @param doneAction doneAction
080     */
081    public void init(ActionListener doneAction) {
082        _update = true;
083        _doneAction = doneAction;
084        _suppressDragging = true; // no dragging when updating
085        initIconFamiliesPanel();
086        add(_iconPanel);
087        _catalog = makeCatalog();
088        add(_catalog);
089    }
090
091    @Override
092    protected JPanel instructions() {
093        JPanel blurb = new JPanel();
094        blurb.setLayout(new BoxLayout(blurb, BoxLayout.Y_AXIS));
095        blurb.add(new JLabel(Bundle.getMessage("DragIconPanel")));
096        blurb.add(new JLabel(Bundle.getMessage("DragIconCatalog")));
097        JPanel panel = new JPanel();
098        panel.add(blurb);
099        return panel;
100    }
101
102    protected CatalogPanel makeCatalog() {
103        CatalogPanel catalog = CatalogPanel.makeDefaultCatalog(false, false, true);
104        ImagePanel panel = catalog.getPreviewPanel();
105        if (!_update) {
106            panel.setImage(_frame.getPreviewBackground());
107        } else {
108            panel.setImage(_frame.getBackground(0));   //update always should be the panel background
109            catalog.setParent(this);
110        }
111        catalog.setToolTipText(Bundle.getMessage("ToolTipDragCatalog"));
112        catalog.setVisible(false);
113        return catalog;
114    }
115
116    @Override
117    protected void previewColorChange() {
118        if (_initialized) {
119            ImagePanel iconPanel = _catalog.getPreviewPanel();
120            if (iconPanel != null) {
121                iconPanel.setImage(_frame.getPreviewBackground());
122            }
123        }
124        super.previewColorChange();
125    }
126
127    /*
128     * Plain icons have only one family, usually named "set".
129     * Override for plain icon and background and put all icons here.
130     */
131    @Override
132    protected void initIconFamiliesPanel() {
133        super.initIconFamiliesPanel();
134        if (!_update) {
135            _iconPanel.addMouseListener(JmriMouseListener.adapt(new IconListener()));
136        }
137    }
138
139    @Override
140    protected void makeFamiliesPanel() {
141        if (!_update) {
142            HashMap<String, HashMap<String, NamedIcon>> families = ItemPalette.getFamilyMaps(_itemType);
143            log.debug("makeFamiliesPanel Num families= {}", families.size());
144            _currentIconMap = families.get("set");
145            if (_currentIconMap == null) {
146                _currentIconMap = new HashMap<>();
147                if (families.size() != 0) {
148                    log.error("{} Unknown families found for {}", families.size(), _itemType);
149                }
150            }
151            makeDataFlavors();
152        } else {
153            _currentIconMap = new HashMap<>();
154        }
155        addIconsToPanel();
156        makePreviewPanel(true, null);
157    }
158    private void addIconsToPanel() {
159        boolean isEmpty = _currentIconMap.isEmpty();
160        if (_bottomPanel == null) {
161            makeBottomPanel(isEmpty);
162        } else {
163           if (isEmpty ^ _wasEmpty) {
164               remove(_bottomPanel);
165               makeBottomPanel(isEmpty);
166           }
167        }
168        _wasEmpty = isEmpty;
169        addIconsToPanel(_currentIconMap, _iconPanel, _update);
170    }
171
172    private void makeDataFlavors() {
173        try {
174            _positionableDataFlavor = new DataFlavor(Editor.POSITIONABLE_FLAVOR);
175            _namedIconDataFlavor = new DataFlavor(ImageIndexEditor.IconDataFlavorMime);
176        } catch (ClassNotFoundException cnfe) {
177            log.error("Unable to find class supporting {}", ImageIndexEditor.IconDataFlavorMime, cnfe);
178        }
179        if (!_update) {
180            new DropTarget(_iconPanel, DnDConstants.ACTION_COPY_OR_MOVE, new ADropTargetListener());
181        }
182    }
183
184    @Override
185    protected JPanel makeIconDisplayPanel(String key, HashMap<String, NamedIcon> iconMap, boolean dropIcon) {
186        NamedIcon icon = iconMap.get(key);
187        return new IconDisplayPanel(key, icon, dropIcon);
188    }
189
190    @Override
191    protected JPanel makeItemButtonPanel() {
192        JPanel panel = new JPanel();
193        panel.add(makeCatalogButton());
194        if (_update) {
195            return panel;
196        }
197        JButton renameButton = new JButton(Bundle.getMessage("RenameIcon"));
198        renameButton.addActionListener(a -> renameIcon());
199        panel.add(renameButton);
200
201        _deleteIconButton = new JButton(Bundle.getMessage("deleteIcon"));
202        _deleteIconButton.addActionListener(a -> deleteIcon());
203        _deleteIconButton.setToolTipText(Bundle.getMessage("ToolTipDeleteIcon"));
204        panel.add(_deleteIconButton);
205        return panel;
206    }
207
208    private JButton makeCatalogButton() {
209        if (_catalogButton == null) {
210            _catalogButton = new JButton(Bundle.getMessage("ButtonShowCatalog"));
211            _catalogButton.addActionListener(a -> {
212                if (_catalog.isVisible()) {
213                    hideCatalog();
214                } else {
215                    showCatalog();
216                }
217            });
218            _catalogButton.setToolTipText(Bundle.getMessage("ToolTipCatalog"));
219        }
220        return _catalogButton;
221    }
222
223    /**
224     * Replacement panel for _bottomPanel when no icon families exist for
225     * _itemType.
226     */
227    @Override
228    protected JPanel makeSpecialBottomPanel(boolean update) {
229        JPanel _bottom2Panel = new JPanel();
230        _bottom2Panel.add(makeCatalogButton());
231
232        if(!update) {
233            JButton button = new JButton(Bundle.getMessage("RestoreDefault"));
234            button.addActionListener(a -> loadDefaultType());
235            _bottom2Panel.add(button);
236        }
237        return _bottom2Panel;
238    }
239
240    protected void hideCatalog() {
241        Dimension oldDim = getSize();
242        boolean isPalette = (_frame instanceof ItemPalette);
243        Dimension totalDim = _frame.getSize();
244        _catalog.setVisible(false);
245        _catalog.invalidate();
246        reSizeDisplay(isPalette, oldDim, totalDim);
247        _catalogButton.setText(Bundle.getMessage("ButtonShowCatalog"));
248    }
249
250    protected void showCatalog() {
251        Dimension oldDim = getSize();
252        boolean isPalette = (_frame instanceof ItemPalette);
253        Dimension totalDim = _frame.getSize();
254        _catalog.setVisible(true);
255        _catalog.invalidate();
256        reSizeDisplay(isPalette, oldDim, totalDim);
257        _catalogButton.setText(Bundle.getMessage("HideCatalog"));
258    }
259
260    protected void putIcon(String name, NamedIcon icon) {
261        _currentIconMap.put(name, icon);
262        log.debug("putIcon {}", name);
263        updateIcons();
264    }
265
266    /**
267     * Action item for makeBottomPanel.
268     */
269    protected void deleteIcon() {
270        if (_selectedIcon == null) {
271            JmriJOptionPane.showMessageDialog(_frame, Bundle.getMessage("ToSelectIcon"),
272                    Bundle.getMessage("ReminderTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
273            return;
274        }
275        log.debug("deleteIcon {}", _selectedIcon._key);
276        _currentIconMap.remove(_selectedIcon._key);
277        updateIcons();
278    }
279
280    @Override
281    protected void hideIcons() {
282    }
283
284    /*
285     * Family 'set' has changed
286     */
287    private void updateIcons() {
288        _previewPanel.setVisible(false);    // necessary to guarantee _iconPanel gets refreshed
289        addIconsToPanel();
290        Dimension oldDim = getSize();
291        boolean isPalette = (_frame instanceof ItemPalette);
292        Dimension totalDim = _frame.getSize();
293        _previewPanel.setVisible(true);
294        if (!_update) {
295            ItemPalette.removeIconMap(_itemType, "set");
296            ItemPalette.addFamily(_itemType, "set", _currentIconMap);
297        }
298        _iconPanel.invalidate();
299        reSizeDisplay(isPalette, oldDim, totalDim);
300    }
301
302    private void renameIcon() {
303        if (_selectedIcon != null) {
304            String name = JmriJOptionPane.showInputDialog(_frame, Bundle.getMessage("NoIconName"),
305                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.QUESTION_MESSAGE);
306            if (name != null) {
307                _currentIconMap.remove(_selectedIcon._key);
308                putIcon(name, _selectedIcon._icon);
309                _selectedIcon._key = name;
310                deselectIcon();
311            }
312        } else {
313            JmriJOptionPane.showMessageDialog(_frame, Bundle.getMessage("ToSelectIcon"),
314                    Bundle.getMessage("ReminderTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
315        }
316    }
317
318    protected void setSelection(@Nonnull IconDisplayPanel panel) {
319        if (_selectedIcon != null && !panel.equals(_selectedIcon)) {
320            deselectIcon();
321        }
322        String borderName = ItemPalette.convertText(panel._key);
323        panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.red, 2), borderName));
324        _selectedIcon = panel;
325        _catalog.deselectIcon();
326    }
327
328    public void deselectIcon() {
329        if (_selectedIcon != null) {
330            String borderName = ItemPalette.convertText(_selectedIcon._key);
331            _selectedIcon.setBorder(BorderFactory.createTitledBorder(
332                    BorderFactory.createLineBorder(Color.black, 1), borderName));
333            _selectedIcon = null;
334        }
335    }
336
337    protected String setIconName(String name) {
338        name = JmriJOptionPane.showInputDialog(this,
339                Bundle.getMessage("NoIconName"), name);
340        if (name == null || name.trim().length() == 0) {
341            return null;
342        }
343        while (_currentIconMap.get(name) != null) {
344            JmriJOptionPane.showMessageDialog(this,
345                    Bundle.getMessage("DuplicateIconName", name),
346                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
347            name = JmriJOptionPane.showInputDialog(this,
348                    Bundle.getMessage("NoIconName"), name);
349            if (name == null || name.trim().length() == 0) {
350                return null;
351            }
352        }
353        return name;
354    }
355
356    protected void initLinkPanel() {
357        JPanel blurb = new JPanel();
358        blurb.setLayout(new BoxLayout(blurb, BoxLayout.Y_AXIS));
359        blurb.add(Box.createVerticalStrut(ItemPalette.STRUT_SIZE));
360        blurb.add(new JLabel(Bundle.getMessage("ToLinkToURL", "Text")));
361        blurb.add(new JLabel(Bundle.getMessage("enterPanel")));
362        blurb.add(new JLabel(Bundle.getMessage("enterURL")));
363        blurb.add(Box.createVerticalStrut(ItemPalette.STRUT_SIZE));
364        blurb.add(new JLabel(Bundle.getMessage("LinkName")));
365        blurb.add(_linkName);
366        _linkName.setToolTipText(Bundle.getMessage("ToolTipLink"));
367        blurb.setToolTipText(Bundle.getMessage("ToolTipLink"));
368        JPanel panel = new JPanel();
369        panel.add(blurb);
370        JPanel linkPanel = new JPanel();
371        linkPanel.add(panel);
372        add(linkPanel);
373    }
374
375    /*
376     * edit a PositionableLabel's icon
377     * @param icon the icon to be update/changed
378     */
379    public void setUpdateIcon(NamedIcon icon) {
380        if (!_update || icon == null) {
381            return;
382        }
383        _updateIcon = icon;
384        String name = icon.getName();
385        if (name == null) {
386            name =Bundle.getMessage("unNamed");
387        } else {
388            java.io.File f = new java.io.File(name);
389            name = f.getName();
390            int index = name.indexOf('.');
391            if (index > 0) {
392                name = name.substring(0, index);
393            }
394        }
395        _currentIconMap.put(name, icon);
396        updateIcons();
397    }
398
399    /*
400     * edit a PositionableLabel's icon
401     * @return return update/changed icon
402     */
403    public NamedIcon getUpdateIcon() {
404        return _updateIcon;
405    }
406
407    class ADropTargetListener extends DropTargetAdapter {
408        ADropTargetListener() {
409            super();
410        }
411
412        @Override
413        public void drop(DropTargetDropEvent e) {
414            boolean accepted = false;
415            try {
416                Transferable tr = e.getTransferable();
417                if (e.isDataFlavorSupported(_positionableDataFlavor)) {
418                    PositionableLabel label = (PositionableLabel)tr.getTransferData(_positionableDataFlavor);
419                    NamedIcon newIcon = new NamedIcon((NamedIcon)label.getIcon());
420                    accepted = accept(label.getName(), newIcon);
421                } else if (e.isDataFlavorSupported(_namedIconDataFlavor)) {
422                    NamedIcon icon = (NamedIcon) tr.getTransferData(_namedIconDataFlavor);
423                    NamedIcon newIcon = new NamedIcon(icon);
424                    accepted = accept(icon.getName(), newIcon);
425                } else if (e.isDataFlavorSupported(DataFlavor.stringFlavor)) {
426                    String text = (String) tr.getTransferData(DataFlavor.stringFlavor);
427                    log.debug("drop for stringFlavor {}", text);
428                    NamedIcon newIcon = new NamedIcon(text, text);
429                    accepted = accept(Bundle.getMessage("unNamed"), newIcon);
430                 } else {
431                    log.debug("IconDragJLabel.drop REJECTED!");
432                    e.rejectDrop();
433                }
434            } catch (IOException | UnsupportedFlavorException ioe) {
435            }
436            if (!accepted) {
437                e.rejectDrop();
438                log.debug("IconDragJLabel.drop REJECTED!");
439            } else {
440                e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
441            }
442        }
443
444        private boolean accept(String name, NamedIcon newIcon) {
445            if (name == null || newIcon == null) {
446                return false;
447            }
448            putIcon(name, newIcon);
449            if (log.isDebugEnabled()) {
450                log.debug("IconDragJLabel.drop COMPLETED for {}, {}", name, newIcon.getURL());
451            }
452            return true;
453        }
454
455    }
456
457    public class IconDragJLabel extends DragJLabel /*implements DropTargetListener*/ {
458
459        int level;
460
461        public IconDragJLabel(DataFlavor flavor, NamedIcon icon, int zLevel) {
462            super(flavor, icon);
463            level = zLevel;
464        }
465
466        @Override
467        public boolean isDataFlavorSupported(DataFlavor flavor) {
468            return _dataFlavor.equals(flavor);
469        }
470
471        @Override
472        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
473            if (!isDataFlavorSupported(flavor)) {
474                return null;
475            }
476            String url = ((NamedIcon) getIcon()).getURL();
477            log.debug("DragJLabel.getTransferData url= {}", url);
478            if (flavor.isMimeTypeEqual(Editor.POSITIONABLE_FLAVOR)) {
479                String link = _linkName.getText().trim();
480                PositionableLabel l;
481                if (link.length() == 0) {
482                    l = new PositionableLabel(NamedIcon.getIconByName(url), _frame.getEditor());
483                } else {
484                    l = new LinkingLabel(NamedIcon.getIconByName(url), _frame.getEditor(), link);
485                }
486                l.setLevel(level);
487                return l;
488            } else if (DataFlavor.stringFlavor.equals(flavor)) {
489                return _itemType + " for \"" + url + "\"";
490            }
491            return null;
492        }
493    }
494
495    class ADropJLabel extends DropJLabel {
496        ADropJLabel(Icon icon) {
497            super(icon);
498        }
499        ADropJLabel(Icon icon, HashMap<String, NamedIcon> iconMap) {
500            super(icon, iconMap);
501        }
502
503        @Override
504        protected void accept(DropTargetDropEvent e, NamedIcon newIcon) {
505            super.accept(e, newIcon);
506            _updateIcon = newIcon;
507        }
508    }
509
510    public class IconDisplayPanel extends JPanel implements JmriMouseListener {
511        String _key;
512        NamedIcon _icon;
513
514        public IconDisplayPanel(String key, NamedIcon icon, boolean dropIcon) {
515            super();
516            _key = key;
517            _icon = icon;
518            JLabel image;
519            if (dropIcon) {
520                image = new ADropJLabel(icon);
521            } else {
522                image = new IconDragJLabel(_positionableDataFlavor, icon, _level);
523                image.addMouseListener(JmriMouseListener.adapt(this));
524                addMouseListener(JmriMouseListener.adapt(new IconListener()));
525            }
526            wrapIconImage(icon, image, this, key);
527        }
528
529        @Override
530        public void mouseClicked(JmriMouseEvent event) {
531            if (event.getSource() instanceof JLabel) {
532                setSelection(this);
533            } else if (event.getSource() instanceof IconDisplayPanel) {
534                IconDisplayPanel panel = (IconDisplayPanel)event.getSource();
535                setSelection(panel);
536            }
537        }
538        @Override
539        public void mousePressed(JmriMouseEvent event) {
540        }
541        @Override
542        public void mouseReleased(JmriMouseEvent event) {
543        }
544        @Override
545        public void mouseEntered(JmriMouseEvent event) {
546        }
547        @Override
548        public void mouseExited(JmriMouseEvent event) {
549        }
550    }
551
552    class IconListener extends JmriMouseAdapter  {
553        @Override
554        public void mouseClicked(JmriMouseEvent event) {
555            if (event.getSource() instanceof IconDisplayPanel) {
556                IconDisplayPanel panel = (IconDisplayPanel)event.getSource();
557                setSelection(panel);
558            } else {
559                deselectIcon();
560            }
561        }
562    }
563
564    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IconItemPanel.class);
565
566}