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.ThrottleFrameManager;
018import jmri.jmrit.throttle.interfaces.ThrottleControllerUI;
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        // This is a little different
165        // jmri.jmrit.dispatcher.DispatcherFrame.class is AutoCreate so getNullableDefault creates it 
166        // if it doesnt exist. So we look at the count of instances.
167        final jmri.jmrit.dispatcher.DispatcherFrame df;
168        if (jmri.InstanceManager.getList(jmri.jmrit.dispatcher.DispatcherFrame.class).isEmpty()) {
169            df = null;
170        } else {
171            df = jmri.InstanceManager.getNullableDefault(jmri.jmrit.dispatcher.DispatcherFrame.class);
172        }
173        final jmri.jmrit.dispatcher.ActiveTrain at;
174        if (df != null) {
175            if (re != null) {
176                at = df.getActiveTrainForRoster(re);
177            } else {
178                at = df.getActiveTrainForName(this.getText());
179            }
180        } else {
181            at = null;
182        }
183        if (at != null && df != null) {
184            // we have active train, with or without auto train with or without roster entry
185            if (at.getAutoActiveTrain() != null ) {
186                if( re == null ) {
187                    popup.add(new AbstractAction("Open Throttle") {
188                        @Override
189                        public void actionPerformed(ActionEvent e) {
190                            ThrottleControllerUI tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
191                            tf.toFront();
192                            tf.setAddress(at.getAutoActiveTrain().getDccAddress());
193                        }
194                    });
195                } else {
196                    popup.add(new AbstractAction("Open Throttle") {
197                        @Override
198                        public void actionPerformed(ActionEvent e) {
199                            ThrottleControllerUI tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
200                            tf.toFront();
201                            tf.setAddress(at.getAutoActiveTrain().getDccAddress());
202                        }
203                    });
204                }
205            }
206            popup.add(new AbstractAction(Bundle.getMessage("MenuTerminateTrain")) {
207                @Override
208                public void actionPerformed(ActionEvent e) {
209                    df.terminateActiveTrain(at, true, false);
210                }
211            });
212            popup.add(new AbstractAction(Bundle.getMessage("MenuAllocateExtra")) {
213                @Override
214                public void actionPerformed(ActionEvent e) {
215                    //Just brings up the standard allocate extra frame, this could be expanded in the future
216                    //As a point and click operation.
217                    df.allocateExtraSection(e, at);
218                }
219            });
220            if (at.getStatus() == jmri.jmrit.dispatcher.ActiveTrain.DONE) {
221                popup.add(new AbstractAction("Restart") {
222                    @Override
223                    public void actionPerformed(ActionEvent e) {
224                        at.allocateAFresh();
225                    }
226                });
227            }
228            if (isEditable()) {
229                popup.add(new JSeparator());
230            }
231            return true;
232        } else if (re != null) {
233            // No active train, but have a roster, therefore a throttle can be created
234            popup.add(new AbstractAction("Open Throttle") {
235                @Override
236                public void actionPerformed(ActionEvent e) {
237                    ThrottleControllerUI tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame();
238                    tf.toFront();
239                    tf.setRosterEntry(re);
240                }
241            });
242            // if dispatcher exists we can create a new train.
243            if (df != null) {
244                popup.add(new AbstractAction(Bundle.getMessage("MenuNewTrain")) {
245                    @Override
246                    public void actionPerformed(ActionEvent e) {
247                        if (!df.getNewTrainActive()) {
248                            df.getActiveTrainFrame().initiateTrain(e, re, getBlock());
249                            df.setNewTrainActive(true);
250                        } else {
251                            df.getActiveTrainFrame().showActivateFrame(re);
252                        }
253                    }
254
255                });
256            }
257            if (isEditable()) {
258                popup.add(new JSeparator());
259            }
260            return true;
261        }
262        return false;
263    }
264
265    /**
266     * Text edits cannot be done to Block text - override.
267     */
268    @Override
269    public boolean setTextEditMenu(JPopupMenu popup) {
270        popup.add(new AbstractAction(Bundle.getMessage("EditBlockValue")) {
271            @Override
272            public void actionPerformed(ActionEvent e) {
273                editBlockValue();
274            }
275        });
276        return true;
277    }
278
279    /**
280     * Drive the current state of the display from the state of the Block Value.
281     */
282    @Override
283    public void displayState() {
284        log.debug("displayState");
285        if (namedBlock == null) {  // use default if not connected yet
286            setIcon(defaultIcon);
287            updateSize();
288            return;
289        }
290        if (re != null) {
291            jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this);
292            re = null;
293        }
294        Object key = getBlock().getValue();
295        displayState(key);
296    }
297
298    @Override
299    public boolean setEditIconMenu(JPopupMenu popup) {
300        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameBlock"));
301        popup.add(new AbstractAction(txt) {
302            @Override
303            public void actionPerformed(ActionEvent e) {
304                edit();
305            }
306        });
307        return true;
308    }
309
310    @Override
311    protected void edit() {
312        makeIconEditorFrame(this, "Block", true, null); // NOI18N
313        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.blockPickModelInstance());
314        ActionListener addIconAction = a -> editBlock();
315        _iconEditor.complete(addIconAction, false, true, true);
316        _iconEditor.setSelection(getBlock());
317    }
318
319    void editBlock() {
320        setBlock(_iconEditor.getTableSelection().getDisplayName());
321        updateSize();
322        _iconEditorFrame.dispose();
323        _iconEditorFrame = null;
324        _iconEditor = null;
325        invalidate();
326    }
327
328    @Override
329    public void dispose() {
330        if (getBlock() != null) {
331            getBlock().removePropertyChangeListener(this);
332        }
333        namedBlock = null;
334        if (re != null) {
335            jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this);
336            re = null;
337        }
338        super.dispose();
339    }
340
341    @Override
342    public void doMouseClicked(JmriMouseEvent e) {
343        if (e.getClickCount() == 2) { // double click?
344            if (!getEditor().isEditable() && isValueEditDisabled()) {
345                log.debug("Double click block value edit disabled");
346                return;
347            }
348            editBlockValue();
349        }
350    }
351
352    protected void editBlockValue() {
353
354        String reval = (String)JmriJOptionPane.showInputDialog(this,
355                                     Bundle.getMessage("EditCurrentBlockValue", namedBlock.getName()),
356                                     getBlock().getValue());
357
358        setValue(reval);
359        updateSize();
360    }
361
362    @Override
363    protected Object getValue() {
364        if (getBlock() == null) {
365            return null;
366        }
367        return getBlock().getValue();
368    }
369
370    @Override
371    protected void setValue(Object val) {
372        getBlock().setValue(val);
373    }
374
375    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockContentsIcon.class);
376
377}