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