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