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 if (p.isValueEditDisabled()) { 187 element.setAttribute("valueEditDisabled", "yes"); 188 } 189 element.setAttribute("positionable", p.isPositionable() ? "true" : "false"); 190 element.setAttribute("showtooltip", p.showToolTip() ? "true" : "false"); 191 element.setAttribute("editable", p.isEditable() ? "true" : "false"); 192 ToolTip tip = p.getToolTip(); 193 if (tip != null) { 194 if (tip.getPrependToolTipWithDisplayName()) { 195 element.addContent( 196 new Element("tooltip_prependWithDisplayName") 197 .addContent("yes")); 198 } 199 String txt = tip.getText(); 200 if (txt != null) { 201 Element elem = new Element("tooltip").addContent(txt); // was written as "toolTip" 3.5.1 and before 202 element.addContent(elem); 203 } 204 } 205 if (p.getDegrees() != 0) { 206 element.setAttribute("degrees", "" + p.getDegrees()); 207 } 208 } 209 210 public Element storeIcon(String elemName, NamedIcon icon) { 211 if (icon == null) { 212 return null; 213 } 214 Element element = new Element(elemName); 215 element.setAttribute("url", icon.getURL()); 216 element.setAttribute("degrees", String.valueOf(icon.getDegrees())); 217 element.setAttribute("scale", String.valueOf(icon.getScale())); 218 219 // the "rotate" attribute was deprecated in 2.9.4, replaced by the "rotation" element 220 element.addContent(new Element("rotation").addContent(String.valueOf(icon.getRotation()))); 221 222 return element; 223 } 224 225 public void storeLogixNG_Data(Positionable p, Element element) { 226 if (p.getLogixNG() == null) return; 227 228 // Don't save LogixNG data if we don't have any ConditionalNGs 229 if (p.getLogixNG().getNumConditionalNGs() == 0) return; 230 Element logixNG_Element = new Element("LogixNG"); 231 logixNG_Element.addContent(new Element("InlineLogixNG_SystemName").addContent(p.getLogixNG().getSystemName())); 232 element.addContent(logixNG_Element); 233 } 234 235 @Override 236 public boolean load(Element shared, Element perNode) { 237 log.error("Invalid method called"); 238 return false; 239 } 240 241 /** 242 * Create a PositionableLabel, then add to a target JLayeredPane 243 * 244 * @param element Top level Element to unpack. 245 * @param o Editor as an Object 246 * @throws JmriConfigureXmlException when a error prevents creating the objects as as 247 * required by the input XML 248 */ 249 @Override 250 public void load(Element element, Object o) throws JmriConfigureXmlException { 251 // create the objects 252 PositionableLabel l = null; 253 254 // get object class and determine editor being used 255 Editor editor = (Editor) o; 256 if (element.getAttribute("icon") != null) { 257 NamedIcon icon; 258 String name = element.getAttribute("icon").getValue(); 259// if (log.isDebugEnabled()) log.debug("icon attribute= "+name); 260 if (name.equals("yes")) { 261 icon = getNamedIcon("icon", element, "PositionableLabel ", editor); 262 } else { 263 icon = NamedIcon.getIconByName(name); 264 if (icon == null) { 265 icon = editor.loadFailed("PositionableLabel", name); 266 if (icon == null) { 267 log.info("PositionableLabel icon removed for url= {}", name); 268 return; 269 } 270 } 271 } 272 // abort if name != yes and have null icon 273 if (icon == null && !name.equals("yes")) { 274 log.info("PositionableLabel icon removed for url= {}", name); 275 return; 276 } 277 l = new PositionableLabel(icon, editor); 278 try { 279 Attribute a = element.getAttribute("rotate"); 280 if (a != null && icon != null) { 281 int rotation = element.getAttribute("rotate").getIntValue(); 282 icon.setRotation(rotation, l); 283 } 284 } catch (org.jdom2.DataConversionException e) { 285 } 286 287 if (name.equals("yes")) { 288 NamedIcon nIcon = loadIcon(l, "icon", element, "PositionableLabel ", editor); 289 if (nIcon != null) { 290 l.updateIcon(nIcon); 291 } else { 292 log.info("PositionableLabel icon removed for url= {}", name); 293 return; 294 } 295 } else { 296 l.updateIcon(icon); 297 } 298 } 299 300 if (element.getAttribute("text") != null) { 301 if (l == null) { 302 l = new PositionableLabel(element.getAttribute("text").getValue(), editor); 303 } 304 loadTextInfo(l, element); 305 306 } else if (l == null) { 307 log.error("PositionableLabel is null!"); 308 if (log.isDebugEnabled()) { 309 java.util.List<Attribute> attrs = element.getAttributes(); 310 log.debug("\tElement Has {} Attributes:", attrs.size()); 311 for (Attribute a : attrs) { 312 log.debug(" attribute: {} = {}", a.getName(), a.getValue()); 313 } 314 java.util.List<Element> kids = element.getChildren(); 315 log.debug("\tElementHas {} children:", kids.size()); 316 for (Element e : kids) { 317 log.debug(" child: {} = \"{}\"", e.getName(), e.getValue()); 318 } 319 } 320 editor.loadFailed(); 321 return; 322 } 323 try { 324 editor.putItem(l); 325 } catch (Positionable.DuplicateIdException e) { 326 // This should never happen 327 log.error("Editor.putItem() with null id has thrown DuplicateIdException", e); 328 } 329 330 loadLogixNG_Data(l, element); 331 332 // load individual item's option settings after editor has set its global settings 333 loadCommonAttributes(l, Editor.LABELS, element); 334 } 335 336 protected void loadTextInfo(Positionable l, Element element) { 337 if (log.isDebugEnabled()) { 338 log.debug("loadTextInfo"); 339 } 340 jmri.jmrit.display.PositionablePopupUtil util = l.getPopupUtility(); 341 if (util == null) { 342 log.warn("PositionablePopupUtil is null! {}", element); 343 return; 344 } 345 346 Attribute a = element.getAttribute("size"); 347 try { 348 if (a != null) { 349 util.setFontSize(a.getFloatValue()); 350 } 351 } catch (DataConversionException ex) { 352 log.warn("invalid size attribute value"); 353 } 354 355 a = element.getAttribute("style"); 356 try { 357 if (a != null) { 358 int style = a.getIntValue(); 359 int drop = 0; 360 switch (style) { 361 case 0: //0 Normal 362 case 2: // italic 363 drop = 1; 364 break; 365 default: 366 break; 367 } 368 util.setFontStyle(style, drop); 369 } 370 } catch (DataConversionException ex) { 371 log.warn("invalid style attribute value"); 372 } 373 374 a = element.getAttribute("fontname"); 375 try { 376 if (a != null) { 377 util.setFont(new Font(a.getValue(), util.getFontStyle(), util.getFontSize())); 378 // Reset util to the new instance 379 // The setFont process clones the current util instance but the rest of loadTextInfo used the orignal instance. 380 util = l.getPopupUtility(); 381 } 382 } catch (NullPointerException e) { // considered normal if the attributes are not present 383 } 384 385 // set color if needed 386 try { 387 int red = element.getAttribute("red").getIntValue(); 388 int blue = element.getAttribute("blue").getIntValue(); 389 int green = element.getAttribute("green").getIntValue(); 390 util.setForeground(new Color(red, green, blue)); 391 } catch (org.jdom2.DataConversionException e) { 392 log.warn("Could not parse color attributes!"); 393 } catch (NullPointerException e) { // considered normal if the attributes are not present 394 } 395 396 a = element.getAttribute("hasBackground"); 397 if (a != null) { 398 util.setHasBackground("yes".equals(a.getValue())); 399 } else { 400 util.setHasBackground(true); 401 } 402 if (util.hasBackground()) { 403 try { 404 int red = element.getAttribute("redBack").getIntValue(); 405 int blue = element.getAttribute("blueBack").getIntValue(); 406 int green = element.getAttribute("greenBack").getIntValue(); 407 util.setBackgroundColor(new Color(red, green, blue)); 408 } catch (org.jdom2.DataConversionException e) { 409 log.warn("Could not parse background color attributes!"); 410 } catch (NullPointerException e) { 411 util.setHasBackground(false);// if the attributes are not listed, we consider the background as clear. 412 } 413 } 414 415 int fixedWidth = 0; 416 int fixedHeight = 0; 417 try { 418 fixedHeight = element.getAttribute("fixedHeight").getIntValue(); 419 } catch (org.jdom2.DataConversionException e) { 420 log.warn("Could not parse fixed Height attributes!"); 421 } catch (NullPointerException e) { // considered normal if the attributes are not present 422 } 423 424 try { 425 fixedWidth = element.getAttribute("fixedWidth").getIntValue(); 426 } catch (org.jdom2.DataConversionException e) { 427 log.warn("Could not parse fixed Width attribute!"); 428 } catch (NullPointerException e) { // considered normal if the attributes are not present 429 } 430 if (!(fixedWidth == 0 && fixedHeight == 0)) { 431 util.setFixedSize(fixedWidth, fixedHeight); 432 } 433 if ((util.getFixedWidth() == 0) || (util.getFixedHeight() == 0)) { 434 try { 435 util.setMargin(element.getAttribute("margin").getIntValue()); 436 } catch (org.jdom2.DataConversionException e) { 437 log.warn("Could not parse margin attribute!"); 438 } catch (NullPointerException e) { // considered normal if the attributes are not present 439 } 440 } 441 try { 442 util.setBorderSize(element.getAttribute("borderSize").getIntValue()); 443 int red = element.getAttribute("redBorder").getIntValue(); 444 int blue = element.getAttribute("blueBorder").getIntValue(); 445 int green = element.getAttribute("greenBorder").getIntValue(); 446 util.setBorderColor(new Color(red, green, blue)); 447 } catch (org.jdom2.DataConversionException e) { 448 log.warn("Could not parse border attributes!"); 449 } catch (NullPointerException e) { // considered normal if the attribute not present 450 } 451 452 a = element.getAttribute("justification"); 453 if (a != null) { 454 util.setJustification(a.getValue()); 455 } else { 456 util.setJustification("left"); 457 } 458 a = element.getAttribute("orientation"); 459 if (a != null) { 460 util.setOrientation(a.getValue()); 461 } else { 462 util.setOrientation("horizontal"); 463 } 464 465 int deg = 0; 466 try { 467 a = element.getAttribute("degrees"); 468 if (a != null) { 469 deg = a.getIntValue(); 470 l.rotate(deg); 471 } 472 } catch (DataConversionException ex) { 473 log.warn("invalid 'degrees' value (non integer)"); 474 } 475 if (deg == 0 && util.hasBackground()) { 476 l.setOpaque(true); 477 } 478 } 479 480 public void loadCommonAttributes(Positionable l, int defaultLevel, Element element) 481 throws JmriConfigureXmlException { 482 483 if (element.getAttribute("id") != null) { 484 try { 485 l.setId(element.getAttribute("id").getValue()); 486 } catch (Positionable.DuplicateIdException e) { 487 throw new JmriConfigureXmlException("Positionable id is not unique", e); 488 } 489 } 490 491 if (element.getAttribute("classes") != null) { 492 String classes = element.getAttribute("classes").getValue(); 493 for (String className : classes.split(",")) { 494 if (!className.isBlank()) { 495 l.addClass(className); 496 } 497 } 498 } 499 500 try { 501 l.setControlling(!element.getAttribute("forcecontroloff").getBooleanValue()); 502 } catch (DataConversionException e1) { 503 log.warn("unable to convert positionable label forcecontroloff attribute"); 504 } catch (Exception e) { 505 } 506 507 // find coordinates 508 int x = 0; 509 int y = 0; 510 try { 511 x = element.getAttribute("x").getIntValue(); 512 y = element.getAttribute("y").getIntValue(); 513 } catch (org.jdom2.DataConversionException e) { 514 log.error("failed to convert positional attribute"); 515 } 516 l.setLocation(x, y); 517 518 // find display level 519 int level = defaultLevel; 520 try { 521 level = element.getAttribute("level").getIntValue(); 522 } catch (org.jdom2.DataConversionException e) { 523 log.warn("Could not parse level attribute!"); 524 } catch (NullPointerException e) { 525 // considered normal if the attribute not present 526 } 527 l.setDisplayLevel(level); 528 529 try { 530 boolean value = element.getAttribute("hidden").getBooleanValue(); 531 l.setHidden(value); 532 l.setVisible(!value); 533 } catch (DataConversionException e) { 534 log.warn("unable to convert positionable label hidden attribute"); 535 } catch (NullPointerException e) { 536 // considered normal if the attribute not present 537 } 538 539 try { 540 boolean value = element.getAttribute("emptyHidden").getBooleanValue(); 541 l.setEmptyHidden(value); 542 } catch (DataConversionException e) { 543 log.warn("unable to convert positionable label emptyHidden attribute"); 544 } catch (NullPointerException e) { 545 // considered normal if the attribute not present 546 } 547 548 try { 549 boolean value = element.getAttribute("valueEditDisabled").getBooleanValue(); 550 l.setValueEditDisabled(value); 551 } catch (DataConversionException e) { 552 log.warn("unable to convert positionable label valueEditDisabled attribute"); 553 } catch (NullPointerException e) { 554 // considered normal if the attribute not present 555 } 556 557 try { 558 l.setPositionable(element.getAttribute("positionable").getBooleanValue()); 559 } catch (DataConversionException e) { 560 log.warn("unable to convert positionable label positionable attribute"); 561 } catch (NullPointerException e) { 562 // considered normal if the attribute not present 563 } 564 try { 565 l.setShowToolTip(element.getAttribute("showtooltip").getBooleanValue()); 566 } catch (DataConversionException e) { 567 log.warn("unable to convert positionable label showtooltip attribute"); 568 } catch (NullPointerException e) { 569 // considered normal if the attribute not present 570 } 571 try { 572 l.setEditable(element.getAttribute("editable").getBooleanValue()); 573 } catch (DataConversionException e) { 574 log.warn("unable to convert positionable label editable attribute"); 575 } catch (NullPointerException e) { 576 // considered normal if the attribute not present 577 } 578 579 Attribute a = element.getAttribute("degrees"); 580 if (a != null && l instanceof PositionableLabel) { 581 try { 582 int deg = a.getIntValue(); 583 ((PositionableLabel) l).setDegrees(deg); 584 } catch (org.jdom2.DataConversionException dce) { 585 } 586 } 587 588 Element elem = element.getChild("tooltip_prependWithDisplayName"); 589 if (elem != null) { 590 ToolTip tip = l.getToolTip(); 591 if (tip != null) { 592 tip.setPrependToolTipWithDisplayName("yes".equals(elem.getText())); 593 } 594 } 595 596 elem = element.getChild("tooltip"); 597 if (elem == null) { 598 elem = element.getChild("toolTip"); // pre JMRI 3.5.2 599 } 600 if (elem != null) { 601 ToolTip tip = l.getToolTip(); 602 if (tip != null) { 603 tip.setText(elem.getText()); 604 } 605 } 606 } 607 608 public NamedIcon loadIcon(PositionableLabel l, String attrName, Element element, 609 String name, Editor ed) { 610 NamedIcon icon = getNamedIcon(attrName, element, name, ed); 611 if (icon != null) { 612 try { 613 int deg = 0; 614 double scale = 1.0; 615 Element elem = element.getChild(attrName); 616 if (elem != null) { 617 Attribute a = elem.getAttribute("degrees"); 618 if (a != null) { 619 deg = a.getIntValue(); 620 } 621 a = elem.getAttribute("scale"); 622 if (a != null) { 623 scale = elem.getAttribute("scale").getDoubleValue(); 624 } 625 icon.setLoad(deg, scale, l); 626 if (deg == 0) { 627 // "rotate" attribute is JMRI 2.9.3 and before 628 a = elem.getAttribute("rotate"); 629 if (a != null) { 630 int rotation = a.getIntValue(); 631 // 2.9.3 and before, only unscaled icons rotate 632 if (scale == 1.0) { 633 icon.setRotation(rotation, l); 634 } 635 } 636 // "rotation" element is JMRI 2.9.4 and after 637 Element e = elem.getChild("rotation"); 638 if (e != null) { 639 // ver 2.9.4 allows orthogonal rotations of scaled icons 640 int rotation = Integer.parseInt(e.getText()); 641 icon.setRotation(rotation, l); 642 } 643 } 644 } 645 } catch (org.jdom2.DataConversionException dce) { 646 } 647 } 648 return icon; 649 } 650 651 protected NamedIcon getNamedIcon(String childName, Element element, 652 String name, Editor ed) { 653 NamedIcon icon = null; 654 Element elem = element.getChild(childName); 655 if (elem != null) { 656 String iconName = elem.getAttribute("url").getValue(); 657 icon = NamedIcon.getIconByName(iconName); 658 if (icon == null) { 659 icon = ed.loadFailed(name, iconName); 660 if (icon == null) { 661 log.info("{} removed for url= {}", name, iconName); 662 } 663 } 664 } else { 665 log.debug("getNamedIcon: child element \"{}\" not found in element {}", childName, element.getName()); 666 } 667 return icon; 668 } 669 670 public void loadLogixNG_Data(Positionable p, Element element) { 671 Element logixNG_Element = element.getChild("LogixNG"); 672 if (logixNG_Element == null) return; 673 Element inlineLogixNG = logixNG_Element.getChild("InlineLogixNG_SystemName"); 674 if (inlineLogixNG != null) { 675 String systemName = inlineLogixNG.getTextTrim(); 676 p.setLogixNG_SystemName(systemName); 677 InstanceManager.getDefault(LogixNG_Manager.class).registerSetupTask(() -> { 678 p.setupLogixNG(); 679 }); 680 } 681 } 682 683 private final static Logger log = LoggerFactory.getLogger(PositionableLabelXml.class); 684}