001package jmri.jmrit.display.configurexml; 002 003import jmri.util.gui.GuiLafPreferencesManager; 004 005import java.awt.Color; 006import java.awt.Font; 007 008import jmri.InstanceManager; 009import jmri.configurexml.AbstractXmlAdapter; 010import jmri.configurexml.JmriConfigureXmlException; 011import jmri.jmrit.catalog.NamedIcon; 012import jmri.jmrit.display.Editor; 013import jmri.jmrit.display.Positionable; 014import jmri.jmrit.display.PositionableLabel; 015import jmri.jmrit.display.PositionablePopupUtil; 016import jmri.jmrit.display.ToolTip; 017import jmri.jmrit.logixng.LogixNG_Manager; 018 019import org.jdom2.Attribute; 020import org.jdom2.DataConversionException; 021import org.jdom2.Element; 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025/** 026 * Handle configuration for display.PositionableLabel objects 027 * 028 * @author Bob Jacobsen Copyright: Copyright (c) 2002 029 */ 030public class PositionableLabelXml extends AbstractXmlAdapter { 031 032 public PositionableLabelXml() { 033 } 034 035 /** 036 * Default implementation for storing the contents of a PositionableLabel 037 * 038 * @param o Object to store, of type PositionableLabel 039 * @return Element containing the complete info 040 */ 041 @Override 042 public Element store(Object o) { 043 PositionableLabel p = (PositionableLabel) o; 044 045 if (!p.isActive()) { 046 return null; // if flagged as inactive, don't store 047 } 048 Element element = new Element("positionablelabel"); 049 storeCommonAttributes(p, element); 050 051 if (p.isText()) { 052 if (p.getUnRotatedText() != null) { 053 element.setAttribute("text", p.getUnRotatedText()); 054 } 055 storeTextInfo(p, element); 056 } 057 058 if (p.isIcon() && p.getIcon() != null) { 059 element.setAttribute("icon", "yes"); 060 element.addContent(storeIcon("icon", (NamedIcon) p.getIcon())); 061 } 062 063 storeLogixNG_Data(p, element); 064 065 element.setAttribute("class", "jmri.jmrit.display.configurexml.PositionableLabelXml"); 066 return element; 067 } 068 069 /** 070 * Store the text formatting information. 071 * <p> 072 * This is always stored, even if the icon isn't in text mode, because some 073 * uses (subclasses) of PositionableLabel flip back and forth between icon 074 * and text, and want to remember their formatting. 075 * 076 * @param p the icon to store 077 * @param element the XML representation of the icon 078 */ 079 protected void storeTextInfo(Positionable p, Element element) { 080 //if (p.getText()!=null) element.setAttribute("text", p.getText()); 081 PositionablePopupUtil util = p.getPopupUtility(); 082 083 GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class); 084 String defaultFontName = manager.getDefaultFont().getFontName(); 085 086 String fontName = util.getFont().getFontName(); 087 if (!fontName.equals(defaultFontName)) { 088 element.setAttribute("fontFamily", "" + util.getFont().getFamily()); 089 element.setAttribute("fontname", "" + fontName); 090 } 091 092 element.setAttribute("size", "" + util.getFontSize()); 093 element.setAttribute("style", "" + util.getFontStyle()); 094 095 // always write the foreground (text) color 096 element.setAttribute("red", "" + util.getForeground().getRed()); 097 element.setAttribute("green", "" + util.getForeground().getGreen()); 098 element.setAttribute("blue", "" + util.getForeground().getBlue()); 099 100 element.setAttribute("hasBackground", util.hasBackground() ? "yes" : "no"); 101 if (util.hasBackground()) { 102 element.setAttribute("redBack", "" + util.getBackground().getRed()); 103 element.setAttribute("greenBack", "" + util.getBackground().getGreen()); 104 element.setAttribute("blueBack", "" + util.getBackground().getBlue()); 105 } 106 107 if (util.getMargin() != 0) { 108 element.setAttribute("margin", "" + util.getMargin()); 109 } 110 if (util.getBorderSize() != 0) { 111 element.setAttribute("borderSize", "" + util.getBorderSize()); 112 element.setAttribute("redBorder", "" + util.getBorderColor().getRed()); 113 element.setAttribute("greenBorder", "" + util.getBorderColor().getGreen()); 114 element.setAttribute("blueBorder", "" + util.getBorderColor().getBlue()); 115 } 116 if (util.getFixedWidth() != 0) { 117 element.setAttribute("fixedWidth", "" + util.getFixedWidth()); 118 } 119 if (util.getFixedHeight() != 0) { 120 element.setAttribute("fixedHeight", "" + util.getFixedHeight()); 121 } 122 123 String just; 124 switch (util.getJustification()) { 125 case 0x02: 126 just = "right"; 127 break; 128 case 0x04: 129 just = "centre"; 130 break; 131 default: 132 just = "left"; 133 break; 134 } 135 element.setAttribute("justification", just); 136 137 if (util.getOrientation() != PositionablePopupUtil.HORIZONTAL) { 138 String ori; 139 switch (util.getOrientation()) { 140 case PositionablePopupUtil.VERTICAL_DOWN: 141 ori = "vertical_down"; 142 break; 143 case PositionablePopupUtil.VERTICAL_UP: 144 ori = "vertical_up"; 145 break; 146 default: 147 ori = "horizontal"; 148 break; 149 } 150 element.setAttribute("orientation", ori); 151 } 152 //return element; 153 } 154 155 /** 156 * Default implementation for storing the common contents of an Icon 157 * 158 * @param p the icon to store 159 * @param element the XML representation of the icon 160 */ 161 public void storeCommonAttributes(Positionable p, Element element) { 162 163 if (p.getId() != null) element.setAttribute("id", p.getId()); 164 165 var classes = p.getClasses(); 166 if (!classes.isEmpty()) { 167 StringBuilder classNames = new StringBuilder(); 168 for (String className : classes) { 169 if (className.contains(",")) { 170 throw new UnsupportedOperationException("Comma is not allowed in class names"); 171 } 172 if (classNames.length() > 0) classNames.append(","); 173 classNames.append(className); 174 } 175 element.setAttribute("classes", classNames.toString()); 176 } 177 178 element.setAttribute("x", "" + p.getX()); 179 element.setAttribute("y", "" + p.getY()); 180 element.setAttribute("level", String.valueOf(p.getDisplayLevel())); 181 element.setAttribute("forcecontroloff", !p.isControlling() ? "true" : "false"); 182 element.setAttribute("hidden", p.isHidden() ? "yes" : "no"); 183 if (p.isEmptyHidden()) { 184 element.setAttribute("emptyHidden", "yes"); 185 } 186 element.setAttribute("positionable", p.isPositionable() ? "true" : "false"); 187 element.setAttribute("showtooltip", p.showToolTip() ? "true" : "false"); 188 element.setAttribute("editable", p.isEditable() ? "true" : "false"); 189 ToolTip tip = p.getToolTip(); 190 if (tip != null) { 191 if (tip.getPrependToolTipWithDisplayName()) { 192 element.addContent( 193 new Element("tooltip_prependWithDisplayName") 194 .addContent("yes")); 195 } 196 String txt = tip.getText(); 197 if (txt != null) { 198 Element elem = new Element("tooltip").addContent(txt); // was written as "toolTip" 3.5.1 and before 199 element.addContent(elem); 200 } 201 } 202 if (p.getDegrees() != 0) { 203 element.setAttribute("degrees", "" + p.getDegrees()); 204 } 205 } 206 207 public Element storeIcon(String elemName, NamedIcon icon) { 208 if (icon == null) { 209 return null; 210 } 211 Element element = new Element(elemName); 212 element.setAttribute("url", icon.getURL()); 213 element.setAttribute("degrees", String.valueOf(icon.getDegrees())); 214 element.setAttribute("scale", String.valueOf(icon.getScale())); 215 216 // the "rotate" attribute was deprecated in 2.9.4, replaced by the "rotation" element 217 element.addContent(new Element("rotation").addContent(String.valueOf(icon.getRotation()))); 218 219 return element; 220 } 221 222 public void storeLogixNG_Data(Positionable p, Element element) { 223 if (p.getLogixNG() == null) return; 224 225 // Don't save LogixNG data if we don't have any ConditionalNGs 226 if (p.getLogixNG().getNumConditionalNGs() == 0) return; 227 Element logixNG_Element = new Element("LogixNG"); 228 logixNG_Element.addContent(new Element("InlineLogixNG_SystemName").addContent(p.getLogixNG().getSystemName())); 229 element.addContent(logixNG_Element); 230 } 231 232 @Override 233 public boolean load(Element shared, Element perNode) { 234 log.error("Invalid method called"); 235 return false; 236 } 237 238 /** 239 * Create a PositionableLabel, then add to a target JLayeredPane 240 * 241 * @param element Top level Element to unpack. 242 * @param o Editor as an Object 243 * @throws JmriConfigureXmlException when a error prevents creating the objects as as 244 * required by the input XML 245 */ 246 @Override 247 public void load(Element element, Object o) throws JmriConfigureXmlException { 248 // create the objects 249 PositionableLabel l = null; 250 251 // get object class and determine editor being used 252 Editor editor = (Editor) o; 253 if (element.getAttribute("icon") != null) { 254 NamedIcon icon; 255 String name = element.getAttribute("icon").getValue(); 256// if (log.isDebugEnabled()) log.debug("icon attribute= "+name); 257 if (name.equals("yes")) { 258 icon = getNamedIcon("icon", element, "PositionableLabel ", editor); 259 } else { 260 icon = NamedIcon.getIconByName(name); 261 if (icon == null) { 262 icon = editor.loadFailed("PositionableLabel", name); 263 if (icon == null) { 264 log.info("PositionableLabel icon removed for url= {}", name); 265 return; 266 } 267 } 268 } 269 // abort if name != yes and have null icon 270 if (icon == null && !name.equals("yes")) { 271 log.info("PositionableLabel icon removed for url= {}", name); 272 return; 273 } 274 l = new PositionableLabel(icon, editor); 275 try { 276 Attribute a = element.getAttribute("rotate"); 277 if (a != null && icon != null) { 278 int rotation = element.getAttribute("rotate").getIntValue(); 279 icon.setRotation(rotation, l); 280 } 281 } catch (org.jdom2.DataConversionException e) { 282 } 283 284 if (name.equals("yes")) { 285 NamedIcon nIcon = loadIcon(l, "icon", element, "PositionableLabel ", editor); 286 if (nIcon != null) { 287 l.updateIcon(nIcon); 288 } else { 289 log.info("PositionableLabel icon removed for url= {}", name); 290 return; 291 } 292 } else { 293 l.updateIcon(icon); 294 } 295 } 296 297 if (element.getAttribute("text") != null) { 298 if (l == null) { 299 l = new PositionableLabel(element.getAttribute("text").getValue(), editor); 300 } 301 loadTextInfo(l, element); 302 303 } else if (l == null) { 304 log.error("PositionableLabel is null!"); 305 if (log.isDebugEnabled()) { 306 java.util.List<Attribute> attrs = element.getAttributes(); 307 log.debug("\tElement Has {} Attributes:", attrs.size()); 308 for (Attribute a : attrs) { 309 log.debug(" attribute: {} = {}", a.getName(), a.getValue()); 310 } 311 java.util.List<Element> kids = element.getChildren(); 312 log.debug("\tElementHas {} children:", kids.size()); 313 for (Element e : kids) { 314 log.debug(" child: {} = \"{}\"", e.getName(), e.getValue()); 315 } 316 } 317 editor.loadFailed(); 318 return; 319 } 320 try { 321 editor.putItem(l); 322 } catch (Positionable.DuplicateIdException e) { 323 // This should never happen 324 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 325 } 326 327 loadLogixNG_Data(l, element); 328 329 // load individual item's option settings after editor has set its global settings 330 loadCommonAttributes(l, Editor.LABELS, element); 331 } 332 333 protected void loadTextInfo(Positionable l, Element element) { 334 if (log.isDebugEnabled()) { 335 log.debug("loadTextInfo"); 336 } 337 jmri.jmrit.display.PositionablePopupUtil util = l.getPopupUtility(); 338 if (util == null) { 339 log.warn("PositionablePopupUtil is null! {}", element); 340 return; 341 } 342 343 Attribute a = element.getAttribute("size"); 344 try { 345 if (a != null) { 346 util.setFontSize(a.getFloatValue()); 347 } 348 } catch (DataConversionException ex) { 349 log.warn("invalid size attribute value"); 350 } 351 352 a = element.getAttribute("style"); 353 try { 354 if (a != null) { 355 int style = a.getIntValue(); 356 int drop = 0; 357 switch (style) { 358 case 0: //0 Normal 359 case 2: // italic 360 drop = 1; 361 break; 362 default: 363 break; 364 } 365 util.setFontStyle(style, drop); 366 } 367 } catch (DataConversionException ex) { 368 log.warn("invalid style attribute value"); 369 } 370 371 a = element.getAttribute("fontname"); 372 try { 373 if (a != null) { 374 util.setFont(new Font(a.getValue(), util.getFontStyle(), util.getFontSize())); 375 // Reset util to the new instance 376 // The setFont process clones the current util instance but the rest of loadTextInfo used the orignal instance. 377 util = l.getPopupUtility(); 378 } 379 } catch (NullPointerException e) { // considered normal if the attributes are not present 380 } 381 382 // set color if needed 383 try { 384 int red = element.getAttribute("red").getIntValue(); 385 int blue = element.getAttribute("blue").getIntValue(); 386 int green = element.getAttribute("green").getIntValue(); 387 util.setForeground(new Color(red, green, blue)); 388 } catch (org.jdom2.DataConversionException e) { 389 log.warn("Could not parse color attributes!"); 390 } catch (NullPointerException e) { // considered normal if the attributes are not present 391 } 392 393 a = element.getAttribute("hasBackground"); 394 if (a != null) { 395 util.setHasBackground("yes".equals(a.getValue())); 396 } else { 397 util.setHasBackground(true); 398 } 399 if (util.hasBackground()) { 400 try { 401 int red = element.getAttribute("redBack").getIntValue(); 402 int blue = element.getAttribute("blueBack").getIntValue(); 403 int green = element.getAttribute("greenBack").getIntValue(); 404 util.setBackgroundColor(new Color(red, green, blue)); 405 } catch (org.jdom2.DataConversionException e) { 406 log.warn("Could not parse background color attributes!"); 407 } catch (NullPointerException e) { 408 util.setHasBackground(false);// if the attributes are not listed, we consider the background as clear. 409 } 410 } 411 412 int fixedWidth = 0; 413 int fixedHeight = 0; 414 try { 415 fixedHeight = element.getAttribute("fixedHeight").getIntValue(); 416 } catch (org.jdom2.DataConversionException e) { 417 log.warn("Could not parse fixed Height attributes!"); 418 } catch (NullPointerException e) { // considered normal if the attributes are not present 419 } 420 421 try { 422 fixedWidth = element.getAttribute("fixedWidth").getIntValue(); 423 } catch (org.jdom2.DataConversionException e) { 424 log.warn("Could not parse fixed Width attribute!"); 425 } catch (NullPointerException e) { // considered normal if the attributes are not present 426 } 427 if (!(fixedWidth == 0 && fixedHeight == 0)) { 428 util.setFixedSize(fixedWidth, fixedHeight); 429 } 430 if ((util.getFixedWidth() == 0) || (util.getFixedHeight() == 0)) { 431 try { 432 util.setMargin(element.getAttribute("margin").getIntValue()); 433 } catch (org.jdom2.DataConversionException e) { 434 log.warn("Could not parse margin attribute!"); 435 } catch (NullPointerException e) { // considered normal if the attributes are not present 436 } 437 } 438 try { 439 util.setBorderSize(element.getAttribute("borderSize").getIntValue()); 440 int red = element.getAttribute("redBorder").getIntValue(); 441 int blue = element.getAttribute("blueBorder").getIntValue(); 442 int green = element.getAttribute("greenBorder").getIntValue(); 443 util.setBorderColor(new Color(red, green, blue)); 444 } catch (org.jdom2.DataConversionException e) { 445 log.warn("Could not parse border attributes!"); 446 } catch (NullPointerException e) { // considered normal if the attribute not present 447 } 448 449 a = element.getAttribute("justification"); 450 if (a != null) { 451 util.setJustification(a.getValue()); 452 } else { 453 util.setJustification("left"); 454 } 455 a = element.getAttribute("orientation"); 456 if (a != null) { 457 util.setOrientation(a.getValue()); 458 } else { 459 util.setOrientation("horizontal"); 460 } 461 462 int deg = 0; 463 try { 464 a = element.getAttribute("degrees"); 465 if (a != null) { 466 deg = a.getIntValue(); 467 l.rotate(deg); 468 } 469 } catch (DataConversionException ex) { 470 log.warn("invalid 'degrees' value (non integer)"); 471 } 472 if (deg == 0 && util.hasBackground()) { 473 l.setOpaque(true); 474 } 475 } 476 477 public void loadCommonAttributes(Positionable l, int defaultLevel, Element element) 478 throws JmriConfigureXmlException { 479 480 if (element.getAttribute("id") != null) { 481 try { 482 l.setId(element.getAttribute("id").getValue()); 483 } catch (Positionable.DuplicateIdException e) { 484 throw new JmriConfigureXmlException("Positionable id is not unique", e); 485 } 486 } 487 488 if (element.getAttribute("classes") != null) { 489 String classes = element.getAttribute("classes").getValue(); 490 for (String className : classes.split(",")) { 491 if (!className.isBlank()) { 492 l.addClass(className); 493 } 494 } 495 } 496 497 try { 498 l.setControlling(!element.getAttribute("forcecontroloff").getBooleanValue()); 499 } catch (DataConversionException e1) { 500 log.warn("unable to convert positionable label forcecontroloff attribute"); 501 } catch (Exception e) { 502 } 503 504 // find coordinates 505 int x = 0; 506 int y = 0; 507 try { 508 x = element.getAttribute("x").getIntValue(); 509 y = element.getAttribute("y").getIntValue(); 510 } catch (org.jdom2.DataConversionException e) { 511 log.error("failed to convert positional attribute"); 512 } 513 l.setLocation(x, y); 514 515 // find display level 516 int level = defaultLevel; 517 try { 518 level = element.getAttribute("level").getIntValue(); 519 } catch (org.jdom2.DataConversionException e) { 520 log.warn("Could not parse level attribute!"); 521 } catch (NullPointerException e) { 522 // considered normal if the attribute not present 523 } 524 l.setDisplayLevel(level); 525 526 try { 527 boolean value = element.getAttribute("hidden").getBooleanValue(); 528 l.setHidden(value); 529 l.setVisible(!value); 530 } catch (DataConversionException e) { 531 log.warn("unable to convert positionable label hidden attribute"); 532 } catch (NullPointerException e) { 533 // considered normal if the attribute not present 534 } 535 536 try { 537 boolean value = element.getAttribute("emptyHidden").getBooleanValue(); 538 l.setEmptyHidden(value); 539 } catch (DataConversionException e) { 540 log.warn("unable to convert positionable label emptyHidden attribute"); 541 } catch (NullPointerException e) { 542 // considered normal if the attribute not present 543 } 544 545 try { 546 l.setPositionable(element.getAttribute("positionable").getBooleanValue()); 547 } catch (DataConversionException e) { 548 log.warn("unable to convert positionable label positionable attribute"); 549 } catch (NullPointerException e) { 550 // considered normal if the attribute not present 551 } 552 try { 553 l.setShowToolTip(element.getAttribute("showtooltip").getBooleanValue()); 554 } catch (DataConversionException e) { 555 log.warn("unable to convert positionable label showtooltip attribute"); 556 } catch (NullPointerException e) { 557 // considered normal if the attribute not present 558 } 559 try { 560 l.setEditable(element.getAttribute("editable").getBooleanValue()); 561 } catch (DataConversionException e) { 562 log.warn("unable to convert positionable label editable attribute"); 563 } catch (NullPointerException e) { 564 // considered normal if the attribute not present 565 } 566 567 Attribute a = element.getAttribute("degrees"); 568 if (a != null && l instanceof PositionableLabel) { 569 try { 570 int deg = a.getIntValue(); 571 ((PositionableLabel) l).setDegrees(deg); 572 } catch (org.jdom2.DataConversionException dce) { 573 } 574 } 575 576 Element elem = element.getChild("tooltip_prependWithDisplayName"); 577 if (elem != null) { 578 ToolTip tip = l.getToolTip(); 579 if (tip != null) { 580 tip.setPrependToolTipWithDisplayName("yes".equals(elem.getText())); 581 } 582 } 583 584 elem = element.getChild("tooltip"); 585 if (elem == null) { 586 elem = element.getChild("toolTip"); // pre JMRI 3.5.2 587 } 588 if (elem != null) { 589 ToolTip tip = l.getToolTip(); 590 if (tip != null) { 591 tip.setText(elem.getText()); 592 } 593 } 594 } 595 596 public NamedIcon loadIcon(PositionableLabel l, String attrName, Element element, 597 String name, Editor ed) { 598 NamedIcon icon = getNamedIcon(attrName, element, name, ed); 599 if (icon != null) { 600 try { 601 int deg = 0; 602 double scale = 1.0; 603 Element elem = element.getChild(attrName); 604 if (elem != null) { 605 Attribute a = elem.getAttribute("degrees"); 606 if (a != null) { 607 deg = a.getIntValue(); 608 } 609 a = elem.getAttribute("scale"); 610 if (a != null) { 611 scale = elem.getAttribute("scale").getDoubleValue(); 612 } 613 icon.setLoad(deg, scale, l); 614 if (deg == 0) { 615 // "rotate" attribute is JMRI 2.9.3 and before 616 a = elem.getAttribute("rotate"); 617 if (a != null) { 618 int rotation = a.getIntValue(); 619 // 2.9.3 and before, only unscaled icons rotate 620 if (scale == 1.0) { 621 icon.setRotation(rotation, l); 622 } 623 } 624 // "rotation" element is JMRI 2.9.4 and after 625 Element e = elem.getChild("rotation"); 626 if (e != null) { 627 // ver 2.9.4 allows orthogonal rotations of scaled icons 628 int rotation = Integer.parseInt(e.getText()); 629 icon.setRotation(rotation, l); 630 } 631 } 632 } 633 } catch (org.jdom2.DataConversionException dce) { 634 } 635 } 636 return icon; 637 } 638 639 protected NamedIcon getNamedIcon(String childName, Element element, 640 String name, Editor ed) { 641 NamedIcon icon = null; 642 Element elem = element.getChild(childName); 643 if (elem != null) { 644 String iconName = elem.getAttribute("url").getValue(); 645 icon = NamedIcon.getIconByName(iconName); 646 if (icon == null) { 647 icon = ed.loadFailed(name, iconName); 648 if (icon == null) { 649 log.info("{} removed for url= {}", name, iconName); 650 } 651 } 652 } else { 653 log.debug("getNamedIcon: child element \"{}\" not found in element {}", childName, element.getName()); 654 } 655 return icon; 656 } 657 658 public void loadLogixNG_Data(Positionable p, Element element) { 659 Element logixNG_Element = element.getChild("LogixNG"); 660 if (logixNG_Element == null) return; 661 Element inlineLogixNG = logixNG_Element.getChild("InlineLogixNG_SystemName"); 662 if (inlineLogixNG != null) { 663 String systemName = inlineLogixNG.getTextTrim(); 664 p.setLogixNG_SystemName(systemName); 665 InstanceManager.getDefault(LogixNG_Manager.class).registerSetupTask(() -> { 666 p.setupLogixNG(); 667 }); 668 } 669 } 670 671 private final static Logger log = LoggerFactory.getLogger(PositionableLabelXml.class); 672}