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