1 /* 2 * Copyright (c) 2017 Helmut Neemann 3 * Use of this source code is governed by the GPL v3 license 4 * that can be found in the LICENSE file. 5 */ 6 package de.neemann.digital.draw.library; 7 8 import de.neemann.digital.core.basic.Not; 9 import de.neemann.digital.core.element.ElementAttributes; 10 import de.neemann.digital.core.element.ElementTypeDescription; 11 import de.neemann.digital.core.element.Keys; 12 import de.neemann.digital.draw.elements.Circuit; 13 import de.neemann.digital.draw.elements.VisualElement; 14 import de.neemann.digital.draw.shapes.ShapeFactory; 15 import de.neemann.digital.gui.Settings; 16 import de.neemann.digital.lang.Lang; 17 import de.neemann.gui.IconCreator; 18 import de.neemann.gui.LineBreaker; 19 import org.slf4j.Logger; 20 import org.slf4j.LoggerFactory; 21 22 import javax.swing.*; 23 import java.io.File; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.Iterator; 27 28 /** 29 * A node in the components library 30 */ 31 public class LibraryNode implements Iterable<LibraryNode> { 32 private static final Logger LOGGER = LoggerFactory.getLogger(LibraryNode.class); 33 private static final Icon ICON_NOT_UNIQUE = IconCreator.create("testFailed.png"); 34 35 private final ArrayList<LibraryNode> children; 36 private final String translatedName; 37 private final String name; 38 private final File file; 39 private final boolean isHidden; 40 private ElementTypeDescription description; 41 private String toolTipText; 42 private ImageIcon icon; 43 private ElementLibrary library; 44 private LibraryNode parent; 45 private boolean unique; 46 private boolean descriptionImportError = false; 47 48 /** 49 * Creates a new node with the given name. 50 * The node can have children 51 * 52 * @param name name of the node 53 */ LibraryNode(String name)54 LibraryNode(String name) { 55 this.name = name; 56 this.translatedName = name; 57 this.children = new ArrayList<>(); 58 this.description = null; 59 this.toolTipText = null; 60 this.file = null; 61 this.isHidden = false; 62 } 63 64 /** 65 * Creates a new leaf 66 * 67 * @param description the description 68 */ LibraryNode(ElementTypeDescription description)69 private LibraryNode(ElementTypeDescription description) { 70 this.children = null; 71 this.description = description; 72 this.toolTipText = null; 73 this.name = description.getName(); 74 this.translatedName = description.getTranslatedName(); 75 this.file = null; 76 this.isHidden = false; 77 } 78 79 /** 80 * Creates a new leaf 81 * 82 * @param file the file containing the leaf 83 */ LibraryNode(File file, boolean isLibrary)84 LibraryNode(File file, boolean isLibrary) { 85 children = null; 86 name = file.getName(); 87 if (name.toLowerCase().endsWith(".dig")) 88 translatedName = name.substring(0, name.length() - 4); 89 else 90 translatedName = name; 91 92 isHidden = isLibrary && name.endsWith("-inc.dig"); 93 94 this.file = file; 95 } 96 97 /** 98 * Adds a node. 99 * Throws an exception if this node is a leaf 100 * 101 * @param node the node to add 102 * @return this for chained calls 103 */ add(LibraryNode node)104 LibraryNode add(LibraryNode node) { 105 children.add(node); 106 node.parent = this; 107 node.setLibrary(library); 108 return this; 109 } 110 add(ElementTypeDescription node)111 LibraryNode add(ElementTypeDescription node) { 112 add(new LibraryNode(node)); 113 return this; 114 } 115 116 /** 117 * Traverse the tree 118 * 119 * @param v a visitor 120 * @param <V> the type of the visitor 121 * @return the visitor 122 */ traverse(V v)123 public <V extends Visitor> V traverse(V v) { 124 v.visit(this); 125 if (children != null) { 126 for (LibraryNode tn : children) 127 tn.traverse(v); 128 } 129 return v; 130 } 131 132 /** 133 * @return true if this is a leaf 134 */ isLeaf()135 public boolean isLeaf() { 136 return description != null || file != null; 137 } 138 139 /** 140 * @return true if the description is already loaded 141 */ isDescriptionLoaded()142 public boolean isDescriptionLoaded() { 143 return description != null; 144 } 145 146 147 /** 148 * Returns the description of the element 149 * 150 * @return the description, null if not available 151 **/ getDescriptionOrNull()152 public ElementTypeDescription getDescriptionOrNull() { 153 return description; 154 } 155 156 /** 157 * Returns the description of the element 158 * 159 * @return the description 160 * @throws IOException IOException 161 */ getDescription()162 public ElementTypeDescription getDescription() throws IOException { 163 if (description == null) { 164 if (!unique) 165 throw new IOException(Lang.get("err_file_N0_ExistsTwiceBelow_N1", file.getName(), library.getRootFilePath())); 166 try { 167 description = library.importElement(file); 168 } catch (IOException e) { 169 descriptionImportError = true; 170 throw e; 171 } 172 library.fireLibraryChanged(this); 173 } 174 return description; 175 } 176 177 /** 178 * @return the translated name of the element 179 */ getTranslatedName()180 public String getTranslatedName() { 181 return translatedName; 182 } 183 184 /** 185 * @return the name od id of this element 186 */ getName()187 public String getName() { 188 return name; 189 } 190 191 @Override iterator()192 public Iterator<LibraryNode> iterator() { 193 return children.iterator(); 194 } 195 196 /** 197 * all children are removed 198 */ removeAll()199 public void removeAll() { 200 children.clear(); 201 } 202 203 /** 204 * @return true if this node is empty 205 */ isEmpty()206 public boolean isEmpty() { 207 if (isLeaf()) 208 return false; 209 210 return children.isEmpty(); 211 } 212 213 /** 214 * @return returns the description if present, null otherwise 215 */ isCustom()216 public boolean isCustom() { 217 return file != null; 218 } 219 220 /** 221 * get the child with index i 222 * 223 * @param i the index 224 * @return the child 225 */ getChild(int i)226 public LibraryNode getChild(int i) { 227 return children.get(i); 228 } 229 230 /** 231 * get the child with the given name 232 * 233 * @param name the name 234 * @return the child 235 */ getChild(String name)236 public LibraryNode getChild(String name) { 237 for (LibraryNode n : children) 238 if (n.getName().equals(name)) 239 return n; 240 return null; 241 } 242 243 /** 244 * @return the number of children 245 */ size()246 public int size() { 247 return children == null ? 0 : children.size(); 248 } 249 250 /** 251 * Returns the index of the gicen child 252 * 253 * @param node the node 254 * @return the nodes index 255 */ indexOf(LibraryNode node)256 public int indexOf(LibraryNode node) { 257 return children.indexOf(node); 258 } 259 260 @Override toString()261 public String toString() { 262 return translatedName; 263 } 264 265 /** 266 * Returns the icon. 267 * If icon not available the icon is created 268 * 269 * @param shapeFactory the shape factory to create the icon 270 * @return the icon 271 * @throws IOException IOException 272 */ getIcon(ShapeFactory shapeFactory)273 public Icon getIcon(ShapeFactory shapeFactory) throws IOException { 274 if (descriptionImportError) 275 return ICON_NOT_UNIQUE; 276 277 getDescription(); 278 return getIconOrNull(shapeFactory); 279 } 280 281 /** 282 * Returns the icon. 283 * If icon not available null is returned 284 * 285 * @param shapeFactory the shape factory to create the icon 286 * @return the icon or null 287 */ getIconOrNull(ShapeFactory shapeFactory)288 public Icon getIconOrNull(ShapeFactory shapeFactory) { 289 if (unique) { 290 if (icon == null && description != null) 291 icon = setWideShapeFlagTo( 292 new VisualElement(description.getName()) 293 .setShapeFactory(shapeFactory) 294 ).createIcon(75); 295 return icon; 296 } else 297 return ICON_NOT_UNIQUE; 298 } 299 300 /** 301 * Sets the wide shape flag to this element if necessary 302 * 303 * @param visualElement the visual element 304 * @return the given visual element 305 */ setWideShapeFlagTo(VisualElement visualElement)306 public VisualElement setWideShapeFlagTo(VisualElement visualElement) { 307 // set the wide shape option to the element 308 try { 309 if (Settings.getInstance().get(Keys.SETTINGS_IEEE_SHAPES) 310 && getDescription().hasAttribute(Keys.WIDE_SHAPE) 311 && !visualElement.equalsDescription(Not.DESCRIPTION)) 312 visualElement.setAttribute(Keys.WIDE_SHAPE, true); 313 } catch (IOException e1) { 314 // do nothing on error 315 } 316 return visualElement; 317 } 318 319 /** 320 * Removes the given child. 321 * 322 * @param child the element to remove 323 */ remove(LibraryNode child)324 public void remove(LibraryNode child) { 325 children.remove(child); 326 } 327 328 /** 329 * Sets the library this node belongs to 330 * 331 * @param library the library 332 * @return this for chained calls 333 */ setLibrary(ElementLibrary library)334 public LibraryNode setLibrary(ElementLibrary library) { 335 if (this.library != library) { 336 this.library = library; 337 if (children != null) 338 for (LibraryNode c : children) 339 c.setLibrary(library); 340 } 341 return this; 342 } 343 344 /** 345 * returns the tree path 346 * 347 * @return the path 348 */ getPath()349 public Object[] getPath() { 350 ArrayList<Object> path = new ArrayList<>(); 351 LibraryNode n = this; 352 while (n != null) { 353 path.add(0, n); 354 n = n.parent; 355 } 356 return path.toArray(new Object[0]); 357 } 358 359 /** 360 * Invalidate this node 361 */ invalidate()362 public void invalidate() { 363 description = null; 364 toolTipText = null; 365 icon = null; 366 library.fireLibraryChanged(this); 367 } 368 369 /** 370 * @return the tool tip text 371 */ getToolTipText()372 public String getToolTipText() { 373 if (isCustom()) { 374 if (isUnique()) { 375 if (description == null) { 376 if (toolTipText == null) { 377 try { 378 LOGGER.debug("load tooltip from " + file); 379 Circuit c = Circuit.loadCircuit(file, null); 380 toolTipText = new LineBreaker().toHTML().breakLines(Lang.evalMultilingualContent(c.getAttributes().get(Keys.DESCRIPTION))); 381 } catch (Exception e) { 382 toolTipText = Lang.get("msg_fileNotImportedYet"); 383 } 384 } 385 return toolTipText; 386 } else 387 return new LineBreaker().toHTML().breakLines(description.getDescription(new ElementAttributes())); 388 } else 389 return new LineBreaker().toHTML().breakLines(Lang.get("msg_fileIsNotUnique")); 390 } else 391 return new LineBreaker().toHTML().breakLines(Lang.getNull("elem_" + getName() + "_tt")); 392 } 393 394 /** 395 * sets the unique state of this node 396 * 397 * @param unique true if this node is unique 398 */ setUnique(boolean unique)399 void setUnique(boolean unique) { 400 this.unique = unique; 401 } 402 403 /** 404 * @return true if element is unique 405 */ isUnique()406 public boolean isUnique() { 407 return unique; 408 } 409 410 /** 411 * @return the file containing this circuit 412 */ getFile()413 public File getFile() { 414 return file; 415 } 416 417 /** 418 * If the hidden flag is set, this circuit should not appear in the select menus 419 * 420 * @return the hidden flag 421 */ isHidden()422 public boolean isHidden() { 423 return isHidden; 424 } 425 426 /** 427 * Checks if both files are equal. 428 * If one of the files is null, false is returned. 429 * 430 * @param other the other file 431 * @return true if both files are equal. 432 */ equalsFile(LibraryNode other)433 public boolean equalsFile(LibraryNode other) { 434 if (file == null || other.file == null) 435 return false; 436 437 return file.equals(other.file); 438 } 439 } 440