001package jmri.jmrit.display; 002 003import java.awt.Color; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.util.Map; 007 008import javax.annotation.Nonnull; 009import javax.swing.AbstractAction; 010import javax.swing.JPopupMenu; 011import javax.swing.JSeparator; 012 013import jmri.InstanceManager; 014import jmri.NamedBeanHandle; 015import jmri.Reportable; 016import jmri.NamedBean.DisplayOptions; 017import jmri.jmrit.catalog.NamedIcon; 018import jmri.jmrit.logixng.GlobalVariable; 019import jmri.jmrit.logixng.GlobalVariableManager; 020import jmri.util.swing.JmriJOptionPane; 021import jmri.util.swing.JmriMouseEvent; 022 023/** 024 * An icon to display a status of a GlobalVariable. 025 * <p> 026 * The value of the global variable can't be changed with this icon. 027 * 028 * @author Bob Jacobsen Copyright (c) 2004 029 * @author Daniel Bergqvist Copyright (C) 2022 030 */ 031public class GlobalVariableIcon extends MemoryOrGVIcon implements java.beans.PropertyChangeListener/*, DropTargetListener*/ { 032 033 NamedIcon defaultIcon = null; 034 // the map of icons 035 java.util.HashMap<String, NamedIcon> map = null; 036 private NamedBeanHandle<GlobalVariable> namedGlobalVariable; 037 038 public GlobalVariableIcon(String s, Editor editor) { 039 super(s, editor); 040 resetDefaultIcon(); 041 _namedIcon = defaultIcon; 042 //By default all content is left justified 043 _popupUtil.setJustification(LEFT); 044 } 045 046 public GlobalVariableIcon(NamedIcon s, Editor editor) { 047 super(s, editor); 048 setDisplayLevel(Editor.LABELS); 049 defaultIcon = s; 050 _popupUtil.setJustification(LEFT); 051 log.debug("GlobalVariableIcon ctor= {}", GlobalVariableIcon.class.getName()); 052 } 053 054 @Override 055 public Positionable deepClone() { 056 GlobalVariableIcon pos = new GlobalVariableIcon("", _editor); 057 return finishClone(pos); 058 } 059 060 protected Positionable finishClone(GlobalVariableIcon pos) { 061 pos.setGlobalVariable(namedGlobalVariable.getName()); 062 pos.setOriginalLocation(getOriginalX(), getOriginalY()); 063 if (map != null) { 064 for (Map.Entry<String, NamedIcon> entry : map.entrySet()) { 065 String url = entry.getValue().getName(); 066 pos.addKeyAndIcon(NamedIcon.getIconByName(url), entry.getKey()); 067 } 068 } 069 return super.finishClone(pos); 070 } 071 072 public void resetDefaultIcon() { 073 defaultIcon = new NamedIcon("resources/icons/misc/X-red.gif", 074 "resources/icons/misc/X-red.gif"); 075 } 076 077 public void setDefaultIcon(NamedIcon n) { 078 defaultIcon = n; 079 } 080 081 public NamedIcon getDefaultIcon() { 082 return defaultIcon; 083 } 084 085 private void setMap() { 086 if (map == null) { 087 map = new java.util.HashMap<>(); 088 } 089 } 090 091 /** 092 * Attach a named GlobalVariable to this display item. 093 * 094 * @param pName Used as a system/user name to lookup the GlobalVariable object 095 */ 096 public void setGlobalVariable(String pName) { 097 if (InstanceManager.getNullableDefault(GlobalVariableManager.class) != null) { 098 try { 099 GlobalVariable globalVariable = InstanceManager.getDefault(GlobalVariableManager.class).getGlobalVariable(pName); 100 setGlobalVariable(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, globalVariable)); 101 } catch (IllegalArgumentException e) { 102 log.error("GlobalVariable '{}' not available, icon won't see changes", pName); 103 } 104 } else { 105 log.error("No GlobalVariableManager for this protocol, icon won't see changes"); 106 } 107 updateSize(); 108 } 109 110 /** 111 * Attach a named GlobalVariable to this display item. 112 * 113 * @param m The GlobalVariable object 114 */ 115 public void setGlobalVariable(NamedBeanHandle<GlobalVariable> m) { 116 if (namedGlobalVariable != null) { 117 getGlobalVariable().removePropertyChangeListener(this); 118 } 119 namedGlobalVariable = m; 120 if (namedGlobalVariable != null) { 121 getGlobalVariable().addPropertyChangeListener(this, namedGlobalVariable.getName(), "GlobalVariable Icon"); 122 displayState(); 123 setName(namedGlobalVariable.getName()); 124 } 125 } 126 127 public NamedBeanHandle<GlobalVariable> getNamedGlobalVariable() { 128 return namedGlobalVariable; 129 } 130 131 public GlobalVariable getGlobalVariable() { 132 if (namedGlobalVariable == null) { 133 return null; 134 } 135 return namedGlobalVariable.getBean(); 136 } 137 138 @Override 139 public jmri.NamedBean getNamedBean() { 140 return getGlobalVariable(); 141 } 142 143 public java.util.HashMap<String, NamedIcon> getMap() { 144 return map; 145 } 146 147 // display icons 148 public void addKeyAndIcon(NamedIcon icon, String keyValue) { 149 if (map == null) { 150 setMap(); // initialize if needed 151 } 152 map.put(keyValue, icon); 153 // drop size cache 154 //height = -1; 155 //width = -1; 156 displayState(); // in case changed 157 } 158 159 // update icon as state of GlobalVariable changes 160 @Override 161 public void propertyChange(java.beans.PropertyChangeEvent e) { 162 if (log.isDebugEnabled()) { 163 log.debug("property change: {} is now {}", 164 e.getPropertyName(), e.getNewValue()); 165 } 166 if (e.getPropertyName().equals("value")) { 167 displayState(); 168 } 169 if (e.getSource() instanceof jmri.Throttle) { 170 if (e.getPropertyName().equals(jmri.Throttle.ISFORWARD)) { 171 Boolean boo = (Boolean) e.getNewValue(); 172 if (boo) { 173 flipIcon(NamedIcon.NOFLIP); 174 } else { 175 flipIcon(NamedIcon.HORIZONTALFLIP); 176 } 177 } 178 } 179 } 180 181 @Override 182 @Nonnull 183 public String getTypeString() { 184 return Bundle.getMessage("PositionableType_GlobalVariableIcon"); 185 } 186 187 @Override 188 public String getNameString() { 189 String name; 190 if (namedGlobalVariable == null) { 191 name = Bundle.getMessage("NotConnected"); 192 } else { 193 name = getGlobalVariable().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME); 194 } 195 return name; 196 } 197 198 public void setSelectable(boolean b) { 199 selectable = b; 200 } 201 202 public boolean isSelectable() { 203 return selectable; 204 } 205 boolean selectable = false; 206 207 @Override 208 public boolean showPopUp(JPopupMenu popup) { 209 if (isEditable() && selectable) { 210 popup.add(new JSeparator()); 211 212 for (String key : map.keySet()) { 213 //String value = ((NamedIcon)map.get(key)).getName(); 214 popup.add(new AbstractAction(key) { 215 216 @Override 217 public void actionPerformed(ActionEvent e) { 218 String key = e.getActionCommand(); 219 setValue(key); 220 } 221 }); 222 } 223 return true; 224 } // end of selectable 225 return false; 226 } 227 228 /** 229 * Text edits cannot be done to GlobalVariable text - override 230 */ 231 @Override 232 public boolean setTextEditMenu(JPopupMenu popup) { 233 popup.add(new AbstractAction(Bundle.getMessage("EditGlobalVariableValue")) { 234 235 @Override 236 public void actionPerformed(ActionEvent e) { 237 editGlobalVariableValue(); 238 } 239 }); 240 return true; 241 } 242 243 protected void flipIcon(int flip) { 244 if (_namedIcon != null) { 245 _namedIcon.flip(flip, this); 246 } 247 updateSize(); 248 repaint(); 249 } 250 Color _saveColor; 251 252 /** 253 * Drive the current state of the display from the state of the GlobalVariable. 254 */ 255 @Override 256 public void displayState() { 257 log.debug("displayState()"); 258 259 if (namedGlobalVariable == null) { // use default if not connected yet 260 setIcon(defaultIcon); 261 updateSize(); 262 return; 263 } 264 Object key = getGlobalVariable().getValue(); 265 displayState(key); 266 } 267 268 /** 269 * Special method to transfer a setAttributes call from the LE version of 270 * GlobalVariableIcon. This eliminates the need to change references to public. 271 * 272 * @since 4.11.6 273 * @param util The LE popup util object. 274 * @param that The current positional object (this). 275 */ 276 public void setAttributes(PositionablePopupUtil util, Positionable that) { 277 _editor.setAttributes(util, that); 278 } 279 280 protected void displayState(Object key) { 281 log.debug("displayState({})", key); 282 if (key != null) { 283 if (map == null) { 284 Object val = key; 285 // no map, attempt to show object directly 286 if (val instanceof String) { 287 String str = (String) val; 288 _icon = false; 289 _text = true; 290 setText(str); 291 updateIcon(null); 292 if (log.isDebugEnabled()) { 293 log.debug("String str= \"{}\" str.trim().length()= {}", str, str.trim().length()); 294 log.debug(" maxWidth()= {}, maxHeight()= {}", maxWidth(), maxHeight()); 295 log.debug(" getBackground(): {}", getBackground()); 296 log.debug(" _editor.getTargetPanel().getBackground(): {}", _editor.getTargetPanel().getBackground()); 297 log.debug(" setAttributes to getPopupUtility({}) with", getPopupUtility()); 298 log.debug(" hasBackground() {}", getPopupUtility().hasBackground()); 299 log.debug(" getBackground() {}", getPopupUtility().getBackground()); 300 log.debug(" on editor {}", _editor); 301 } 302 _editor.setAttributes(getPopupUtility(), this); 303 } else if (val instanceof javax.swing.ImageIcon) { 304 _icon = true; 305 _text = false; 306 setIcon((javax.swing.ImageIcon) val); 307 setText(null); 308 } else if (val instanceof Number) { 309 _icon = false; 310 _text = true; 311 setText(val.toString()); 312 setIcon(null); 313 } else if (val instanceof jmri.IdTag){ 314 // most IdTags are Reportable objects, so 315 // this needs to be before Reportable 316 _icon = false; 317 _text = true; 318 setIcon(null); 319 setText(((jmri.IdTag)val).getDisplayName()); 320 } else if (val instanceof Reportable) { 321 _icon = false; 322 _text = true; 323 setText(((Reportable)val).toReportString()); 324 setIcon(null); 325 } else { 326 // don't recognize the type, do our best with toString 327 log.debug("display current value of {} as String, val= {} of Class {}", 328 getNameString(), val, val.getClass().getName()); 329 _icon = false; 330 _text = true; 331 setText(val.toString()); 332 setIcon(null); 333 } 334 } else { 335 // map exists, use it 336 NamedIcon newicon = map.get(key.toString()); 337 if (newicon != null) { 338 339 setText(null); 340 super.setIcon(newicon); 341 } else { 342 // no match, use default 343 _icon = true; 344 _text = false; 345 setIcon(defaultIcon); 346 setText(null); 347 } 348 } 349 } else { 350 log.debug("object null"); 351 _icon = true; 352 _text = false; 353 setIcon(defaultIcon); 354 setText(null); 355 } 356 updateSize(); 357 } 358 359 /*As the size of a global variable label can change we want to adjust 360 the position of the x,y if the width is fixed*/ 361 @SuppressWarnings("hiding") // OVerrides a value in SwingConstants 362 static final int LEFT = 0x00; 363 @SuppressWarnings("hiding") // OVerrides a value in SwingConstants 364 static final int RIGHT = 0x02; 365 static final int CENTRE = 0x04; 366 367 @Override 368 public void updateSize() { 369 if (_popupUtil.getFixedWidth() == 0) { 370 //setSize(maxWidth(), maxHeight()); 371 switch (_popupUtil.getJustification()) { 372 case LEFT: 373 super.setLocation(getOriginalX(), getOriginalY()); 374 break; 375 case RIGHT: 376 super.setLocation(getOriginalX() - maxWidth(), getOriginalY()); 377 break; 378 case CENTRE: 379 super.setLocation(getOriginalX() - (maxWidth() / 2), getOriginalY()); 380 break; 381 default: 382 log.warn("Unhandled justification code: {}", _popupUtil.getJustification()); 383 break; 384 } 385 setSize(maxWidth(), maxHeight()); 386 } else { 387 super.updateSize(); 388 if (_icon && _namedIcon != null) { 389 _namedIcon.reduceTo(maxWidthTrue(), maxHeightTrue(), 0.2); 390 } 391 } 392 } 393 394 /*Stores the original location of the memory, this is then used to calculate 395 the position of the text dependant upon the justification*/ 396 private int originalX = 0; 397 private int originalY = 0; 398 399 public void setOriginalLocation(int x, int y) { 400 originalX = x; 401 originalY = y; 402 updateSize(); 403 } 404 405 @Override 406 public int getOriginalX() { 407 return originalX; 408 } 409 410 @Override 411 public int getOriginalY() { 412 return originalY; 413 } 414 415 @Override 416 public void setLocation(int x, int y) { 417 if (_popupUtil.getFixedWidth() == 0) { 418 setOriginalLocation(x, y); 419 } else { 420 super.setLocation(x, y); 421 } 422 } 423 424 @Override 425 public boolean setEditIconMenu(JPopupMenu popup) { 426 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameGlobalVariable")); 427 popup.add(new AbstractAction(txt) { 428 @Override 429 public void actionPerformed(ActionEvent e) { 430 edit(); 431 } 432 }); 433 return true; 434 } 435 436 @Override 437 protected void edit() { 438 makeIconEditorFrame(this, "GlobalVariable", true, null); 439 _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.globalVariablePickModelInstance()); 440 ActionListener addIconAction = (ActionEvent a) -> editGlobalVariable(); 441 _iconEditor.complete(addIconAction, false, false, true); 442 _iconEditor.setSelection(getGlobalVariable()); 443 } 444 445 void editGlobalVariable() { 446 setGlobalVariable(_iconEditor.getTableSelection().getDisplayName()); 447 updateSize(); 448 _iconEditorFrame.dispose(); 449 _iconEditorFrame = null; 450 _iconEditor = null; 451 invalidate(); 452 } 453 454 @Override 455 public void dispose() { 456 if (getGlobalVariable() != null) { 457 getGlobalVariable().removePropertyChangeListener(this); 458 } 459 namedGlobalVariable = null; 460 super.dispose(); 461 } 462 463 @Override 464 public void doMouseClicked(JmriMouseEvent e) { 465 if (e.getClickCount() == 2) { // double click? 466 if (!getEditor().isEditable() && isValueEditDisabled()) { 467 log.debug("Double click global variable value edit is disabled"); 468 return; 469 } 470 editGlobalVariableValue(); 471 } 472 } 473 474 protected void editGlobalVariableValue() { 475 476 String reval = (String)JmriJOptionPane.showInputDialog(this, 477 Bundle.getMessage("EditCurrentGlobalVariableValue", namedGlobalVariable.getName()), 478 getGlobalVariable().getValue()); 479 480 setValue(reval); 481 updateSize(); 482 } 483 484 protected Object getValue() { 485 if (getGlobalVariable() == null) { 486 return null; 487 } 488 return getGlobalVariable().getValue(); 489 } 490 491 protected void setValue(Object val) { 492 getGlobalVariable().setValue(val); 493 } 494 495 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GlobalVariableIcon.class); 496 497}