001package jmri.jmrit.display.palette;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Component;
006import java.awt.Dimension;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.datatransfer.DataFlavor;
010import java.awt.datatransfer.UnsupportedFlavorException;
011import java.awt.event.ActionListener;
012import java.io.IOException;
013import java.util.ArrayList;
014import java.util.HashMap;
015import java.util.Iterator;
016import java.util.Map.Entry;
017
018import javax.annotation.Nonnull;
019import javax.swing.Box;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022
023import jmri.NamedBean;
024import jmri.Turnout;
025import jmri.jmrit.catalog.DragJLabel;
026import jmri.jmrit.catalog.NamedIcon;
027import jmri.jmrit.display.DisplayFrame;
028import jmri.jmrit.display.Editor;
029import jmri.jmrit.display.IndicatorTurnoutIcon;
030import jmri.jmrit.picker.PickListModel;
031import jmri.util.swing.ImagePanel;
032import jmri.util.swing.JmriJOptionPane;
033
034/**
035 * JPanel for IndicatorTurnout items.
036 *
037 * @author Pete Cressman Copyright (c) 2010, 2020
038 */
039public class IndicatorTOItemPanel extends TableItemPanel<Turnout> {
040
041    private JPanel _tablePanel;
042    private HashMap<String, HashMap<String, NamedIcon>> _unstoredMaps;
043    private DetectionPanel _detectPanel;
044    protected HashMap<String, HashMap<String, NamedIcon>> _iconGroupsMap;
045
046    public IndicatorTOItemPanel(DisplayFrame parentFrame, String type, String family, PickListModel<Turnout> model) {
047        super(parentFrame, type, family, model);
048    }
049
050    @Override
051    public void init() {
052        if (!_initialized) {
053            super.init();
054            _detectPanel = new DetectionPanel(this);
055            add(_detectPanel, 1);
056        }
057        hideIcons();
058    }
059
060    /**
061     * CircuitBuilder init for conversion of plain track to indicator track.
062     */
063    @Override
064    public void init(JPanel bottomPanel) {
065        super.init(bottomPanel);
066        add(_iconFamilyPanel, 0);
067    }
068
069    /**
070     * Init for update of existing indicator turnout.
071     * _bottom3Panel has "Update Panel" button put onto _bottom1Panel.
072     *
073     * @param doneAction doneAction
074     * @param iconMaps iconMaps
075     */
076    public void initUpdate(ActionListener doneAction, HashMap<String, HashMap<String, NamedIcon>> iconMaps) {
077        _iconGroupsMap = iconMaps;
078        if (iconMaps != null) {
079            checkCurrentMaps(iconMaps); // is map in families?, does user want to add it? etc.
080        }
081        if (_family == null || _family.isEmpty()) {
082            _family = Bundle.getMessage("unNamed");
083        }
084        _detectPanel = new DetectionPanel(this);
085        super.init(doneAction, null);
086        add(_detectPanel, 1);
087        add(_iconFamilyPanel, 2);
088    }
089
090    private void checkCurrentMaps(HashMap<String, HashMap<String, NamedIcon>> iconMaps) {
091        String family = getValidFamily(_family, iconMaps);
092        if (_isUnstoredMap) {
093            _unstoredMaps = iconMaps;
094            int result = JmriJOptionPane.showConfirmDialog(_frame.getEditor(), Bundle.getMessage("UnkownFamilyName", family),
095                    Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
096                    JmriJOptionPane.QUESTION_MESSAGE);
097            if (result == JmriJOptionPane.YES_OPTION) {
098                ItemPalette.addLevel4Family(_itemType, family, iconMaps);
099            }
100            _family = family;
101        } else {
102            if (family != null) {  // icons same as a known family, maybe with another name
103                if (!family.equals(_family)) {
104                    log.info("{} icon's family \"{}\" found but is called \"{}\" in the Catalog.  Name changed to Catalog name.",
105                            _itemType, _family, family);
106                    _family = family;
107                }
108                return;
109            }
110        }
111
112    }
113
114    protected String getValidFamily(String family, HashMap<String, HashMap<String, NamedIcon>> iconMap) {
115        HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families = ItemPalette.getLevel4FamilyMaps(_itemType);
116        if (families == null || families.isEmpty()) {
117            return null;
118        }
119        String mapFamily;
120        if (iconMap != null) {
121            mapFamily = findFamilyOfMaps(null, iconMap, families);
122            if (log.isDebugEnabled()) {
123                log.debug("getValidFamily: findFamilyOfMaps {} found stored family \"{}\" for family \"{}\".", _itemType, mapFamily, family);
124            }
125            if (mapFamily == null) {
126                _isUnstoredMap = true;
127            } else {
128                _isUnstoredMap = false;
129                if (family != null) {
130                    return mapFamily;
131                }
132            }
133        }
134        mapFamily = family;
135        // check that name is not duplicate.
136        boolean nameOK = false;
137        while (!nameOK) {
138            if (mapFamily == null || mapFamily.isEmpty()) {
139                Component fr;
140                if (_dialog != null) fr = _dialog; else fr = this;
141                mapFamily = JmriJOptionPane.showInputDialog(fr, Bundle.getMessage("EnterFamilyName"),
142                        Bundle.getMessage("createNewFamily"), JmriJOptionPane.QUESTION_MESSAGE);
143                if (mapFamily == null || mapFamily.isEmpty()) {   // user quit
144                    return null;
145                }
146            }
147            Iterator<String> iter = families.keySet().iterator();
148            while (iter.hasNext()) {
149                String fam = iter.next();
150                log.debug("check names. fam={} family={} mapFamily={}", fam, family, mapFamily);
151                if (mapFamily.equals(fam)) {   // family cannot be null
152                    JmriJOptionPane.showMessageDialog(_frame,
153                            Bundle.getMessage("DuplicateFamilyName", mapFamily, _itemType, Bundle.getMessage("UseAnotherName")),
154                            Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
155                    mapFamily = null;
156                    nameOK = false;
157                    break;
158                }
159                nameOK = true;
160            }
161        }
162        return mapFamily;
163    }
164
165    /**
166     * Find the family name of the map in a families HashMap.
167     *
168     * @param exemptFamily exclude from matching
169     * @param newMap iconMap
170     * @param families families of itemType
171     * @return null if map is not in the family
172     */
173    protected String findFamilyOfMaps(String exemptFamily,
174                HashMap<String, HashMap<String, NamedIcon>> newMap,
175                HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families) {
176        for (Entry<String, HashMap<String, HashMap<String, NamedIcon>>> entry : families.entrySet()) {
177            String family = entry.getKey();
178            if (!family.equals(exemptFamily)) {
179                log.debug(" familyKey = {}", entry.getKey());
180                HashMap<String, HashMap<String, NamedIcon>> statusMaps = entry.getValue();
181                if (familiesAreEqual(newMap, statusMaps)) {
182                    return family;
183                }
184            }
185        }
186        return null;
187    }
188
189    // Test if status families are equal
190    protected boolean familiesAreEqual(
191                HashMap<String,  HashMap<String, NamedIcon>> famOne,
192                HashMap<String, HashMap<String, NamedIcon>> famTwo) {
193        if (famOne.size() != famTwo.size()) {
194            return false;
195        }
196        for (Entry<String, HashMap<String, NamedIcon>> ent : famOne.entrySet()) {
197            String statusKey = ent.getKey();
198            log.debug("  statusKey = {}", statusKey);
199            HashMap<String, NamedIcon> map = famTwo.get(statusKey);
200            if (map == null) {
201                return false;
202            }
203            if (!mapsAreEqual(ent.getValue(), map)) {
204                return false;
205            }
206            log.debug("  status {}'s are equal.", statusKey);
207        }
208        return true;
209    }
210
211    @Override
212    protected boolean namesStoredMap(String family) {
213        HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families =
214                ItemPalette.getLevel4FamilyMaps(_itemType);
215        if (families.keySet().contains(family)) {
216            return true;
217        }
218        return false;
219    }
220
221    /*
222     * Get a handle in order to change visibility.
223     */
224    @Override
225    protected JPanel initTablePanel(PickListModel<Turnout> model) {
226        _tablePanel = super.initTablePanel(model);
227        return _tablePanel;
228    }
229
230    @Override
231    public void dispose() {
232        if (_detectPanel != null) {
233            _detectPanel.dispose();
234        }
235        super.dispose();
236    }
237
238    @Override
239    protected void makeFamiliesPanel() {
240        HashMap<String, HashMap<String, HashMap<String, NamedIcon>>>
241                    families = ItemPalette.getLevel4FamilyMaps(_itemType);
242        boolean isEmpty = families.values().isEmpty();
243        if (_bottomPanel == null) {
244            makeBottomPanel(isEmpty);
245        } else {
246           if (isEmpty ^ _wasEmpty) {
247               remove(_bottomPanel);
248               makeBottomPanel(isEmpty);
249           }
250        }
251        _wasEmpty = isEmpty;
252        if (isEmpty) {
253            _iconGroupsMap = _unstoredMaps;
254            addIcons2Panel(_iconGroupsMap, _iconPanel, false);
255            if (!_suppressDragging) {
256                makeDragIconPanel();
257                makeDndIcon();
258            }
259            addFamilyPanels(false);
260         } else {
261             makeFamiliesPanel(families);
262         }
263    }
264    private void makeFamiliesPanel(@Nonnull HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families) {
265
266        makeFamilyButtons(families.keySet());  // makes _familyButtonPanel
267        if (_iconGroupsMap == null) {
268            _iconGroupsMap = families.get(_family);
269            if (_iconGroupsMap == null) {
270                _isUnstoredMap = true;
271                _iconGroupsMap = _unstoredMaps;
272            }
273        }
274        addIcons2Panel(_iconGroupsMap, _iconPanel, false); // need to have family maps identified before calling
275
276        if (!_suppressDragging) {
277            makeDragIconPanel();
278            makeDndIcon();
279        }
280        addFamilyPanels(!families.isEmpty());
281    }
282
283    @Override
284    protected String getDisplayKey() {
285        return "TurnoutStateClosed";
286    }
287
288    /**
289    * Add current family icons to Show Icons pane when _showIconsButton pressed
290    * Also, dropIcon is true, call is from Icondialog and current family icons are
291    * added for editing.
292    * @see #hideIcons()
293    *
294    * @param iconMaps family maps
295    * @param iconPanel panel to fill with icons
296    * @param dropIcon true for ability to drop new image on icon to change icon source
297    * */
298    protected void addIcons2Panel(HashMap<String, HashMap<String, NamedIcon>> iconMaps, ImagePanel iconPanel, boolean dropIcon) {
299        if (iconMaps == null) {
300            return;
301        }
302        GridBagLayout gridbag = new GridBagLayout();
303        iconPanel.setLayout(gridbag);
304        iconPanel.removeAll();
305
306        GridBagConstraints c = ItemPanel.itemGridBagConstraint();
307
308        if (iconMaps.isEmpty()) {
309            iconPanel.add(Box.createRigidArea(new Dimension(70,70)));
310        }
311
312        for (Entry<String, HashMap<String, NamedIcon>> stringHashMapEntry : iconMaps.entrySet()) {
313            c.gridx = 0;
314            c.gridy++;
315
316            String statusName = stringHashMapEntry.getKey();
317            JPanel panel = new JPanel();
318            panel.add(new JLabel(ItemPalette.convertText(statusName)));
319            panel.setOpaque(false);
320            gridbag.setConstraints(panel, c);
321            iconPanel.add(panel);
322            c.gridx++;
323            HashMap<String, NamedIcon> iconMap = stringHashMapEntry.getValue();
324            ItemPanel.checkIconMap("Turnout", iconMap); // NOI18N
325            for (Entry<String, NamedIcon> ent : iconMap.entrySet()) {
326                String key = ent.getKey();
327                panel = makeIconDisplayPanel(key, iconMap, dropIcon);
328
329                gridbag.setConstraints(panel, c);
330                iconPanel.add(panel);
331                c.gridx++;
332            }
333        }
334    }
335
336    @Override
337    protected void hideIcons() {
338        if (_tablePanel != null) {
339            _tablePanel.setVisible(true);
340            _tablePanel.invalidate();
341                    }
342        if (_detectPanel != null) {
343            _detectPanel.setVisible(true);
344            _detectPanel.invalidate();
345        }
346        super.hideIcons();
347    }
348
349    @Override
350    protected void showIcons() {
351        if (_detectPanel != null) {
352            _detectPanel.setVisible(false);
353            _detectPanel.invalidate();
354        }
355        if (_tablePanel != null) {
356            _tablePanel.setVisible(false);
357            _tablePanel.invalidate(); // force redraw
358        }
359        super.showIcons();
360    }
361
362    /**
363     * Action item for delete family.
364     */
365    @Override
366    protected void deleteFamilySet() {
367        if (JmriJOptionPane.showConfirmDialog(_frame, Bundle.getMessage("confirmDelete", _family),
368                Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE)
369                == JmriJOptionPane.YES_OPTION) {
370            ItemPalette.removeLevel4IconMap(_itemType, _family, null);
371            _family = null;
372            _tablePanel.setVisible(true);
373            updateFamiliesPanel();
374            setFamily(_family);
375        }
376    }
377
378    protected HashMap<String, HashMap<String, NamedIcon>> makeNewIconMap() {
379        HashMap<String, HashMap<String, NamedIcon>> map = new HashMap<>();
380        for (String statusKey : INDICATOR_TRACK) {
381            map.put(statusKey, makeNewIconMap("Turnout")); // NOI18N
382        }
383        return map;
384    }
385
386    protected void makeDndIcon() {
387        if (_iconGroupsMap != null) {
388            makeDndIcon(_iconGroupsMap.get("ClearTrack"));
389        } else {
390            makeDndIcon(null);
391        }
392    }
393
394    /**
395     * Needed by setFamily() change _family display
396     */
397    @Override
398    protected void setFamilyMaps() {
399        _iconGroupsMap = ItemPalette.getLevel4Family(_itemType, _family);
400        if (_iconGroupsMap == null) {
401            _isUnstoredMap = true;
402            _iconGroupsMap = _unstoredMaps;
403        }
404        if (!_suppressDragging) {
405            makeDragIconPanel();
406            makeDndIcon();
407        }
408        addIcons2Panel(_iconGroupsMap, _iconPanel, false);
409    }
410
411    @Override
412    @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "Cast follows specific Constructor")
413    protected void openDialog(String type, String family) {
414        closeDialogs();
415        hideIcons();
416        _dialog = new IndicatorTOIconDialog(type, family, this);
417        IndicatorTOIconDialog d = (IndicatorTOIconDialog)_dialog;
418        if (_family == null) {
419            d.setMaps(null);
420        } else {
421            d.setMaps(_iconGroupsMap);
422        }
423        d.pack();
424    }
425
426    protected void dialogDone(String family, HashMap<String, HashMap<String, NamedIcon>> iconMap) {
427        if (!_update && !(family.equals(_family) && familiesAreEqual(iconMap, _iconGroupsMap))) {
428            ItemPalette.removeLevel4IconMap(_itemType, _family, null);
429            ItemPalette.addLevel4Family(_itemType, family, iconMap);
430        } else {
431            _iconGroupsMap = iconMap;
432            if (!namesStoredMap(family)) {
433                _isUnstoredMap = true;
434            }
435            if (_isUnstoredMap) {
436                _unstoredMaps = _iconGroupsMap;
437            }
438        }
439        _family = family;
440        makeFamiliesPanel();
441        setFamily(family);
442        _cntlDown = false;
443        hideIcons();
444        if (log.isDebugEnabled()) {
445            log.debug("dialogDoneAction done for {} {}. {} unStored={}",
446                    _itemType, _family, (_update?"update":""), _isUnstoredMap);
447        }
448    }
449
450    /*
451     * **************** pseudo inheritance ********************
452     */
453    public boolean getShowTrainName() {
454        return _detectPanel.getShowTrainName();
455    }
456
457    public void setShowTrainName(boolean show) {
458        _detectPanel.setShowTrainName(show);
459    }
460
461    public String getOccSensor() {
462        return _detectPanel.getOccSensor();
463    }
464
465    public String getOccBlock() {
466        return _detectPanel.getOccBlock();
467    }
468
469    public void setOccDetector(String name) {
470        _detectPanel.setOccDetector(name);
471    }
472
473    public ArrayList<String> getPaths() {
474        return _detectPanel.getPaths();
475    }
476
477    public void setPaths(ArrayList<String> paths) {
478        _detectPanel.setPaths(paths);
479    }
480
481    public HashMap<String, HashMap<String, NamedIcon>> getIconMaps() {
482        if (_iconGroupsMap != null) {
483            return _iconGroupsMap;
484        }
485        _iconGroupsMap = ItemPalette.getLevel4FamilyMaps(_itemType).get(_family);
486        if (_iconGroupsMap == null) {
487            _iconGroupsMap = _unstoredMaps;
488        }
489        if (_iconGroupsMap == null) {
490            log.warn("Family \"{}\" for type \"{}\" not found.", _family, _itemType);
491            _iconGroupsMap = makeNewIconMap();
492        }
493        return _iconGroupsMap;
494    }
495
496    @Override
497    protected JLabel getDragger(DataFlavor flavor,
498            HashMap<String, NamedIcon> map, NamedIcon icon) {
499        return new IconDragJLabel(flavor, icon);
500    }
501
502    protected class IconDragJLabel extends DragJLabel {
503
504        public IconDragJLabel(DataFlavor flavor, NamedIcon icon) {
505            super(flavor, icon);
506        }
507
508        @Override
509        public boolean isDataFlavorSupported(DataFlavor flavor) {
510            return super.isDataFlavorSupported(flavor);
511        }
512
513        @Override
514        protected boolean okToDrag() {
515            NamedBean bean = getDeviceNamedBean();
516            if (bean == null) {
517                JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("noRowSelected"),
518                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
519                return false;
520            }
521            return true;
522        }
523
524       @Override
525        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
526            if (!isDataFlavorSupported(flavor)) {
527                return null;
528            }
529            NamedBean bean = getDeviceNamedBean();
530            if (bean == null) {
531                return null;
532            }
533
534            HashMap<String, HashMap<String, NamedIcon>> iconMap = getIconMaps();
535
536            if (flavor.isMimeTypeEqual(Editor.POSITIONABLE_FLAVOR)) {
537                IndicatorTurnoutIcon t = new IndicatorTurnoutIcon(_frame.getEditor());
538
539                t.setOccBlock(_detectPanel.getOccBlock());
540                t.setOccSensor(_detectPanel.getOccSensor());
541                t.setShowTrain(_detectPanel.getShowTrainName());
542                t.setTurnout(bean.getSystemName());
543                t.setFamily(_family);
544
545                for (Entry<String, HashMap<String, NamedIcon>> entry : iconMap.entrySet()) {
546                    String status = entry.getKey();
547                    for (Entry<String, NamedIcon> ent : entry.getValue().entrySet()) {
548                        t.setIcon(status, ent.getKey(), new NamedIcon(ent.getValue()));
549                    }
550                }
551                t.setLevel(Editor.TURNOUTS);
552                return t;
553            } else if (DataFlavor.stringFlavor.equals(flavor)) {
554                StringBuilder sb = new StringBuilder(_itemType);
555                sb.append(" icons for \"");
556                sb.append(bean.getDisplayName());
557                sb.append("\"");
558                return  sb.toString();
559            }
560            return null;
561        }
562    }
563
564    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IndicatorTOItemPanel.class);
565
566}