001package jmri.jmrit.display;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.util.Map;
006
007import javax.annotation.Nonnull;
008import javax.swing.AbstractAction;
009import javax.swing.JPopupMenu;
010import javax.swing.JSeparator;
011
012import jmri.Block;
013import jmri.InstanceManager;
014import jmri.NamedBeanHandle;
015import jmri.NamedBean.DisplayOptions;
016import jmri.jmrit.catalog.NamedIcon;
017import jmri.jmrit.throttle.ThrottleFrame;
018import jmri.jmrit.throttle.ThrottleFrameManager;
019import jmri.util.swing.JmriJOptionPane;
020import jmri.util.swing.JmriMouseEvent;
021
022/**
023 * An icon to display the value contained within a Block.
024 *
025 * @author Bob Jacobsen Copyright (c) 2004
026 */
027public class BlockContentsIcon extends MemoryIcon {
028
029    private NamedIcon defaultIcon = null;
030    private NamedBeanHandle<Block> namedBlock;
031
032    public BlockContentsIcon(String s, Editor editor) {
033        super(s, editor);
034        BlockContentsIcon.this.resetDefaultIcon();
035        _namedIcon = defaultIcon;
036        //By default all text objects are left justified
037        _popupUtil.setJustification(LEFT);
038        this.setTransferHandler(new TransferHandler());
039    }
040
041    public BlockContentsIcon(NamedIcon s, Editor editor) {
042        super(s, editor);
043        setDisplayLevel(Editor.LABELS);
044        defaultIcon = s;
045        _popupUtil.setJustification(LEFT);
046        log.debug("BlockContentsIcon ctor= {}", BlockContentsIcon.class.getName());
047        this.setTransferHandler(new TransferHandler());
048    }
049
050    @Override
051    @Nonnull
052    public Positionable deepClone() {
053        BlockContentsIcon pos = new BlockContentsIcon("", _editor);
054        return finishClone(pos);
055    }
056
057    protected Positionable finishClone(BlockContentsIcon pos) {
058        pos.setBlock(namedBlock);
059        pos.setOriginalLocation(getOriginalX(), getOriginalY());
060        if (map != null) {
061            for (Map.Entry<String, NamedIcon> entry : map.entrySet()) {
062                String url = entry.getValue().getName();
063                pos.addKeyAndIcon(NamedIcon.getIconByName(url), entry.getKey());
064            }
065        }
066        return super.finishClone(pos);
067    }
068
069    @Override
070    public void resetDefaultIcon() {
071        defaultIcon = new NamedIcon("resources/icons/misc/X-red.gif",
072                "resources/icons/misc/X-red.gif");
073    }
074
075    /**
076     * Attach a named Block to this display item.
077     *
078     * @param pName Used as a system/user name to lookup the Block object
079     */
080    public void setBlock(String pName) {
081        if (InstanceManager.getNullableDefault(jmri.BlockManager.class) != null) {
082            Block block = InstanceManager.getDefault(jmri.BlockManager.class).
083                    provideBlock(pName);
084            setBlock(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, block));
085        } else {
086            log.error("No Block Manager for this protocol, icon won't see changes");
087        }
088        updateSize();
089    }
090
091    /**
092     * Attach a named Block to this display item.
093     *
094     * @param m The Block object
095     */
096    public void setBlock(NamedBeanHandle<Block> m) {
097        if (namedBlock != null) {
098            getBlock().removePropertyChangeListener(this);
099        }
100        namedBlock = m;
101        if (namedBlock != null) {
102            getBlock().addPropertyChangeListener(this, namedBlock.getName(), "Block Icon");
103            displayState();
104            setName(namedBlock.getName());
105        }
106    }
107
108    public NamedBeanHandle<Block> getNamedBlock() {
109        return namedBlock;
110    }
111
112    public Block getBlock() {
113        if (namedBlock == null) {
114            return null;
115        }
116        return namedBlock.getBean();
117    }
118
119    @Override
120    public jmri.NamedBean getNamedBean() {
121        return getBlock();
122    }
123
124    @Override
125    public java.util.HashMap<String, NamedIcon> getMap() {
126        return map;
127    }
128
129    @Override
130    @Nonnull
131    public String getTypeString() {
132        return Bundle.getMessage("PositionableType_BlockContentsIcon");
133    }
134
135    @Override
136    @Nonnull
137    public String getNameString() {
138        String name;
139        if (namedBlock == null) {
140            name = Bundle.getMessage("NotConnected");
141        } else {
142            name = getBlock().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
143        }
144        return name;
145    }
146
147    @Override
148    public boolean showPopUp(JPopupMenu popup) {
149        if (isEditable() && selectable) {
150            popup.add(new JSeparator());
151
152            for (String key : map.keySet()) {
153                //String value = ((NamedIcon)map.get(key)).getName();
154                popup.add(new AbstractAction(key) {
155                    @Override
156                    public void actionPerformed(ActionEvent e) {
157                        String key = e.getActionCommand();
158                        setValue(key);
159                    }
160                });
161            }
162            return true;
163        } // end of selectable
164        final jmri.jmrit.dispatcher.DispatcherFrame df =
165                jmri.InstanceManager.getNullableDefault(jmri.jmrit.dispatcher.DispatcherFrame.class);
166        final jmri.jmrit.dispatcher.ActiveTrain at;
167        if (df != null) {
168            if (re != null) {
169                at = df.getActiveTrainForRoster(re);
170            } else {
171                at = df.getActiveTrainForName(this.getText());
172            }
173        } else {
174            at = null;
175        }
176        if (at != null && df != null) {
177            // we have active train, with or without auto train with or without roster entry
178            if (at.getAutoActiveTrain() != null ) {
179                if( re == null ) {
180                    popup.add(new AbstractAction("Open Throttle") {
181                        @Override
182                        public void actionPerformed(ActionEvent e) {
183                            ThrottleFrame tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
184                            tf.toFront();
185                            tf.getAddressPanel().setAddress(at.getAutoActiveTrain().getDccAddress().getNumber(),
186                                    at.getAutoActiveTrain().getDccAddress().isLongAddress());
187                        }
188                    });
189                } else {
190                    popup.add(new AbstractAction("Open Throttle") {
191                        @Override
192                        public void actionPerformed(ActionEvent e) {
193                            ThrottleFrame tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
194                            tf.toFront();
195                            tf.getAddressPanel().setAddress(at.getAutoActiveTrain().getDccAddress().getNumber(),
196                                    at.getAutoActiveTrain().getDccAddress().isLongAddress());
197                        }
198                    });
199                }
200            }
201            popup.add(new AbstractAction(Bundle.getMessage("MenuTerminateTrain")) {
202                @Override
203                public void actionPerformed(ActionEvent e) {
204                    df.terminateActiveTrain(at, true, false);
205                }
206            });
207            popup.add(new AbstractAction(Bundle.getMessage("MenuAllocateExtra")) {
208                @Override
209                public void actionPerformed(ActionEvent e) {
210                    //Just brings up the standard allocate extra frame, this could be expanded in the future
211                    //As a point and click operation.
212                    df.allocateExtraSection(e, at);
213                }
214            });
215            if (at.getStatus() == jmri.jmrit.dispatcher.ActiveTrain.DONE) {
216                popup.add(new AbstractAction("Restart") {
217                    @Override
218                    public void actionPerformed(ActionEvent e) {
219                        at.allocateAFresh();
220                    }
221                });
222            }
223            return true;
224        } else if (re != null) {
225            // No active train, but have a roster, therefore a throttle can be created
226            popup.add(new AbstractAction("Open Throttle") {
227                @Override
228                public void actionPerformed(ActionEvent e) {
229                    ThrottleFrame tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
230                    tf.toFront();
231                    tf.getAddressPanel().setRosterEntry(re);
232                }
233            });
234            // if dispatcher exists we can create a new train.
235            if (df != null) {
236                popup.add(new AbstractAction(Bundle.getMessage("MenuNewTrain")) {
237                    @Override
238                    public void actionPerformed(ActionEvent e) {
239                        if (!df.getNewTrainActive()) {
240                            df.getActiveTrainFrame().initiateTrain(e, re, getBlock());
241                            df.setNewTrainActive(true);
242                        } else {
243                            df.getActiveTrainFrame().showActivateFrame(re);
244                        }
245                    }
246
247                });
248            }
249            return true;
250        }
251        return false;
252    }
253
254    /**
255     * Text edits cannot be done to Block text - override.
256     */
257    @Override
258    public boolean setTextEditMenu(JPopupMenu popup) {
259        popup.add(new AbstractAction(Bundle.getMessage("EditBlockValue")) {
260            @Override
261            public void actionPerformed(ActionEvent e) {
262                editBlockValue();
263            }
264        });
265        return true;
266    }
267
268    /**
269     * Drive the current state of the display from the state of the Block Value.
270     */
271    @Override
272    public void displayState() {
273        log.debug("displayState");
274        if (namedBlock == null) {  // use default if not connected yet
275            setIcon(defaultIcon);
276            updateSize();
277            return;
278        }
279        if (re != null) {
280            jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this);
281            re = null;
282        }
283        Object key = getBlock().getValue();
284        displayState(key);
285    }
286
287    @Override
288    public boolean setEditIconMenu(JPopupMenu popup) {
289        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameBlock"));
290        popup.add(new AbstractAction(txt) {
291            @Override
292            public void actionPerformed(ActionEvent e) {
293                edit();
294            }
295        });
296        return true;
297    }
298
299    @Override
300    protected void edit() {
301        makeIconEditorFrame(this, "Block", true, null); // NOI18N
302        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.blockPickModelInstance());
303        ActionListener addIconAction = a -> editBlock();
304        _iconEditor.complete(addIconAction, false, true, true);
305        _iconEditor.setSelection(getBlock());
306    }
307
308    void editBlock() {
309        setBlock(_iconEditor.getTableSelection().getDisplayName());
310        updateSize();
311        _iconEditorFrame.dispose();
312        _iconEditorFrame = null;
313        _iconEditor = null;
314        invalidate();
315    }
316
317    @Override
318    public void dispose() {
319        if (getBlock() != null) {
320            getBlock().removePropertyChangeListener(this);
321        }
322        namedBlock = null;
323        if (re != null) {
324            jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this);
325            re = null;
326        }
327        super.dispose();
328    }
329
330    @Override
331    public void doMouseClicked(JmriMouseEvent e) {
332        if (e.getClickCount() == 2) { // double click?
333            if (!getEditor().isEditable() && isValueEditDisabled()) {
334                log.debug("Double click block value edit disabled");
335                return;
336            }
337            editBlockValue();
338        }
339    }
340
341    protected void editBlockValue() {
342
343        String reval = (String)JmriJOptionPane.showInputDialog(this,
344                                     Bundle.getMessage("EditCurrentBlockValue", namedBlock.getName()),
345                                     getBlock().getValue());
346
347        setValue(reval);
348        updateSize();
349    }
350
351    @Override
352    protected Object getValue() {
353        if (getBlock() == null) {
354            return null;
355        }
356        return getBlock().getValue();
357    }
358
359    @Override
360    protected void setValue(Object val) {
361        getBlock().setValue(val);
362    }
363
364    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockContentsIcon.class);
365
366}