001package jmri.jmrit.display.palette;
002
003import java.awt.Color;
004import java.awt.FlowLayout;
005import java.awt.event.WindowAdapter;
006import java.awt.event.WindowEvent;
007import java.util.HashMap;
008import java.util.Map.Entry;
009
010import javax.swing.BorderFactory;
011import javax.swing.Box;
012import javax.swing.BoxLayout;
013import javax.swing.JButton;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JScrollPane;
017
018import jmri.jmrit.catalog.CatalogPanel;
019import jmri.jmrit.catalog.NamedIcon;
020import jmri.util.swing.ImagePanel;
021import jmri.util.swing.JmriJOptionPane;
022
023/**
024 * This class is used when FamilyItemPanel classes add, modify or delete icon
025 * families.
026 *
027 * Note this class cannot be used with super classes of FamilyItemPanel
028 * (ItemPanel etc) since there are several casts to FamilyItemPanel.
029 *
030 * @author Pete Cressman Copyright (c) 2010, 2011, 2020
031 */
032public class IconDialog extends ItemDialog {
033
034    protected String _family;
035    protected HashMap<String, NamedIcon> _iconMap;
036    protected ImagePanel _iconEditPanel;
037    protected CatalogPanel _catalog;
038    protected final JLabel _nameLabel;
039
040    /**
041     * Constructor for an existing family to change icons, add/delete icons, or to
042     * delete the family entirely.
043     * @param type itemType
044     * @param family icon family name
045     * @param parent the ItemPanel calling this class
046     */
047    public IconDialog(String type, String family, FamilyItemPanel parent) {
048        super(type, Bundle.getMessage("ShowIconsTitle", family), parent);
049        _family = family;
050        JPanel panel = new JPanel();
051        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
052        panel.add(Box.createVerticalStrut(ItemPalette.STRUT_SIZE));
053
054        JPanel p = new JPanel();
055        _nameLabel = new JLabel(Bundle.getMessage("FamilyName", family));
056        p.add(_nameLabel);
057        panel.add(p);
058
059        _iconEditPanel = new ImagePanel();
060        _iconEditPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black, 1),
061                Bundle.getMessage("PreviewBorderTitle")));
062        if (!_parent.isUpdate()) {
063            _iconEditPanel.setImage(_parent._frame.getPreviewBackground());
064        } else {
065            _iconEditPanel.setImage(_parent._frame.getBackground(0));   //update always should be the panel background
066        }
067        panel.add(_iconEditPanel); // put icons above buttons
068
069        JPanel buttonPanel = new JPanel();
070        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));
071        makeDoneButtonPanel(buttonPanel, "ButtonDone");
072        panel.add(buttonPanel);
073
074        p = new JPanel();
075        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
076        p.add(panel);
077        _catalog = makeCatalog();
078        p.add(_catalog);
079
080        JScrollPane sp = new JScrollPane(p);
081        setContentPane(sp);
082        addWindowListener(new WindowAdapter() {
083            @Override
084            public void windowClosing(WindowEvent e) {
085                cancel();
086            }
087        });
088    }
089
090    protected void setMap(HashMap<String, NamedIcon> iconMap) {
091        if (iconMap != null) {
092            _iconMap = IconDialog.clone(iconMap);
093        } else {
094            _iconMap = _parent.makeNewIconMap(_type);
095        }        
096        if (!(_type.equals("MultiSensor") || _type.equals("SignalHead"))) {
097            ItemPanel.checkIconMap(_type, _iconMap);
098        }
099        _parent.addIconsToPanel(_iconMap, _iconEditPanel, true);
100        setLocationRelativeTo(_parent);
101        setVisible(true);
102        pack();
103        log.debug("setMap: initialization done.");
104    }
105
106    private CatalogPanel makeCatalog() {
107        CatalogPanel catalog = CatalogPanel.makeDefaultCatalog(false, false, true);
108        catalog.setToolTipText(Bundle.getMessage("ToolTipDragIcon"));
109        ImagePanel panel = catalog.getPreviewPanel();
110        if (!_parent.isUpdate()) {
111            panel.setImage(_parent._frame.getPreviewBackground());
112        } else {
113            panel.setImage(_parent._frame.getBackground(0));   //update always should be the panel background
114        }
115        return catalog;
116    }
117
118    // for _parent to update when background is changed
119    protected ImagePanel getIconEditPanel() {
120        return _iconEditPanel;
121    }
122
123    // for _parent to update when background is changed
124    protected ImagePanel getCatalogPreviewPanel() {
125        return _catalog.getPreviewPanel();
126    }
127
128    /**
129     * Action for both create new family and change existing family.
130     * @return true if success
131     */
132    protected boolean doDoneAction() {
133        if (log.isDebugEnabled()) {
134            log.debug("doDoneAction: {} for {} family= {}", (_parent._update?"Update":""), _type, _family);
135        }
136        if (_family == null || _family.isEmpty()) {
137            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NoFamilyName"),
138                    Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
139            return false;
140        }
141        HashMap<String, HashMap<String, NamedIcon>> families = ItemPalette.getFamilyMaps(_type);
142        String family;
143        HashMap<String, NamedIcon> catalogSet = families.get(_family);
144        boolean nameUsed;
145        if (catalogSet == null) {
146            family = _parent.findFamilyOfMap(null, _iconMap, families);
147            nameUsed = false;
148        } else {
149            family = _parent.findFamilyOfMap(_family, _iconMap, families);
150            nameUsed = true;        // the map is found under another name than _family
151        }
152        if (family != null ) {  // "new" map is stored
153            boolean sameMap = _parent.mapsAreEqual(_iconMap, families.get(family));
154            if (!mapInCatalogOK(sameMap, nameUsed, _family, family)) {
155                return false;
156            }
157        } else {
158            boolean sameMap;
159            if (catalogSet == null) {
160                sameMap = false;
161            } else {
162                sameMap = _parent.mapsAreEqual(catalogSet, _iconMap);
163            }
164            if (!mapNotInCatalogOK(sameMap, nameUsed, _family)) {
165                return false;
166            }
167        }
168        _parent.dialogDoneAction(_family, _iconMap);
169        return true;
170    }
171
172    /**
173     * 
174     * @param sameMap   Map edited in dialog is the same as map found in catalog
175     * @param nameUsed  Name as edited in dialog is the same as name found in catalog
176     * @param editFamily Map name as edited in this dialog
177     * @param catalogFamily Map name as found in the catalog
178     * @return false if not OK
179     */
180    protected boolean mapInCatalogOK(boolean sameMap, boolean nameUsed, String editFamily, String catalogFamily) {
181        log.debug("doDoneAction: map of {} in storage with name= {}", editFamily, catalogFamily);
182        if (_parent._update) {
183            if (!catalogFamily.equals(editFamily)) {
184                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("CannotChangeName", editFamily, catalogFamily),
185                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
186                return false;
187            }
188        } else {
189            log.debug("doDoneAction: name {} {} used and map {} same as {}",
190                    editFamily, (nameUsed?"is":"NOT"), (sameMap?"":"NOT"), catalogFamily);
191            if (catalogFamily.equals(editFamily)) {
192                if (!sameMap) {
193                    JmriJOptionPane.showMessageDialog(this, 
194                            Bundle.getMessage("DuplicateFamilyName", editFamily, _type, Bundle.getMessage("UseAnotherName")),
195                            Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
196                    return false;
197                }             
198            } else {
199                if (sameMap) {
200                    String oldFamily = _parent.getFamilyName(); // if oldFamily != null, this is an edit, not new set
201                    if (oldFamily != null) {    // editing an catalog set
202                        if (nameUsed) {
203                            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("SameNameSet", editFamily, catalogFamily),
204                                    Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
205                            return false;
206                        }
207                    } else {
208                        if (!nameUsed) {
209                            JmriJOptionPane.showMessageDialog(this, 
210                                    Bundle.getMessage("DuplicateFamilyName", editFamily,
211                                            _type, Bundle.getMessage("CannotUseName", catalogFamily)),
212                                    Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
213                            return false;
214                        }
215                    }
216                }
217           }
218        }
219        return true;
220    }
221
222    /**
223     * Edited map is not in the catalog.
224     * 
225     * @param sameMap  Map edited in dialog is the same as map currently held in parent item panel
226     * @param nameUsed Name as edited in dialog is the same as a name found in catalog
227     * @param editFamily Map name as edited in this dialog
228     * @return false if not OK
229     */
230    protected boolean mapNotInCatalogOK(boolean sameMap, boolean nameUsed, String editFamily) {
231        String oldFamily = _parent.getFamilyName();
232        if (_parent._update) {
233            if (nameUsed) {    // name is a key to stored map
234                log.debug("{} keys a stored map. name is used", editFamily); 
235                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("NeedDifferentName", editFamily),
236                        Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
237                return false;
238            }
239        } else {
240            if (oldFamily != null) {    // editing an catalog set from parent
241                log.debug("Editing set {}. {} {} a stored map.", oldFamily, editFamily, (nameUsed?"is":"NOT")); 
242                if (nameUsed) { // map in catalog under another name
243                    if (!editFamily.equals(oldFamily)) { // named changed
244                        if (!sameMap) { // also map changed
245                            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("badReplaceIconSet", oldFamily, editFamily),
246                                    Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
247                            return false;
248                        }
249                    }
250                } else {
251                    int result = JmriJOptionPane.showOptionDialog(this,
252                            Bundle.getMessage("ReplaceFamily", oldFamily, editFamily),
253                            Bundle.getMessage("QuestionTitle"), JmriJOptionPane.DEFAULT_OPTION, 
254                            JmriJOptionPane.QUESTION_MESSAGE, null,
255                            new Object[] {oldFamily, editFamily, Bundle.getMessage("ButtonCancel")},
256                            Bundle.getMessage("ButtonCancel"));
257                    switch (result) {
258                        case 0: // array position 0, oldFamily
259                            _family = oldFamily;
260                            break;
261                        case 2: // array position 2 Cancel Button
262                        case JmriJOptionPane.CLOSED_OPTION: // Dialog closed
263                            return true;
264                        case 1: // array position 1 editFamily
265                        default:
266                            break;
267                    }
268                }
269            } else {
270                if (nameUsed) { // map in catalog under another name
271                    JmriJOptionPane.showMessageDialog(this, 
272                            Bundle.getMessage("DuplicateFamilyName", editFamily, _type, Bundle.getMessage("UseAnotherName")),
273                            Bundle.getMessage("MessageTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
274                    return false;
275                }
276            }
277        }
278        return true;
279    }
280
281    /**
282     * Action item to rename an icon family.
283     */
284    protected void renameFamily() {
285        String family = _parent.getValidFamilyName(null, _iconMap);
286        if (family != null) {
287            _family = family;
288            _nameLabel.setText(Bundle.getMessage("FamilyName", _family));
289            invalidate();
290        }
291    }
292    
293    protected void makeDoneButtonPanel(JPanel buttonPanel, String text) {
294        JPanel panel = new JPanel();
295        panel.setLayout(new FlowLayout());
296        JButton doneButton = new JButton(Bundle.getMessage(text));
297        doneButton.addActionListener(a -> {
298            if (doDoneAction()) {
299                dispose();
300            }
301        });
302        panel.add(doneButton);
303
304        JButton renameButton = new JButton(Bundle.getMessage("renameFamily"));
305        renameButton.addActionListener(a -> renameFamily());
306        panel.add(renameButton);
307
308        JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel"));
309        cancelButton.addActionListener(a -> cancel());
310        panel.add(cancelButton);
311        buttonPanel.add(panel);
312    }
313
314    protected void cancel() {
315        _parent.setFamily();
316        _parent._cntlDown = false;
317        super.dispose();
318    }
319    static protected HashMap<String, NamedIcon> clone(HashMap<String, NamedIcon> map) {
320        HashMap<String, NamedIcon> clone = null;
321        if (map != null) {
322            clone = new HashMap<>();
323            for (Entry<String, NamedIcon> entry : map.entrySet()) {
324                clone.put(entry.getKey(), new NamedIcon(entry.getValue()));
325            }
326        }
327        return clone;
328    }
329
330    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IconDialog.class);
331
332}