package net.sourceforge.fidocadj.macropicker.model;
import java.util.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.*;
import javax.swing.plaf.metal.MetalIconFactory;
import net.sourceforge.fidocadj.librarymodel.LibraryModel;
import net.sourceforge.fidocadj.librarymodel.Library;
import net.sourceforge.fidocadj.librarymodel.Category;
import net.sourceforge.fidocadj.librarymodel.event.LibraryListener;
import net.sourceforge.fidocadj.librarymodel.event.AddEvent;
import net.sourceforge.fidocadj.librarymodel.event.KeyChangeEvent;
import net.sourceforge.fidocadj.librarymodel.event.RemoveEvent;
import net.sourceforge.fidocadj.librarymodel.event.RenameEvent;
import net.sourceforge.fidocadj.primitives.MacroDesc;
/** JTree model for showing macro library.
<pre>
This file is part of FidoCadJ.
FidoCadJ is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FidoCadJ is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FidoCadJ. If not,
@see <a href=http://www.gnu.org/licenses/>http://www.gnu.org/licenses/</a>.
Copyright 2014-2016 Kohta Ozaki, Davide Bucci
</pre>
@author Kohta Ozaki, Davide Bucci
*/
public class MacroTreeModel implements TreeModel,LibraryListener
{
public static final int ROOT=0;
public static final int LIBRARY=1;
public static final int CATEGORY=2;
public static final int MACRO=3;
private RootNode rootNode;
final private LibraryModel libraryModel;
final private List<TreeModelListener> listeners;
private HashMap<TreePath, AbstractMacroTreeNode> libraryNodeMap;
private String filterWord;
/** Constructor.
@param libraryModel the library model to be associated to this class.
*/
public MacroTreeModel(LibraryModel libraryModel)
{
this.libraryModel = libraryModel;
listeners = new ArrayList<TreeModelListener>();
createMap();
synchronizeTree(null);
fireChanged();
}
/** Set filtering word.
@param filterWord words separated by space.
*/
public void setFilterWord(String filterWord)
{
this.filterWord = filterWord;
if(filterWord == null || filterWord.length()==0) {
synchronizeTree(null);
this.filterWord = null;
fireChanged();
} else {
final String chainedWord =
filterWord.toLowerCase(new Locale("en"));
synchronizeTree(new NodeFilterInterface() {
public boolean accept(MacroTreeNode node)
{
String[] words = chainedWord.trim().split(" ");
int matched=0;
for(String word:words) {
if(0<=node.toString().toLowerCase().indexOf(word)) {
matched++;
} else if(word.length()==0) {
matched++;
}
}
return words.length==matched;
}
});
fireChanged();
}
}
/** Reset the current search mode.
*/
private void resetSearchMode()
{
filterWord = null;
}
/** Check if a search is currently being made.
@return true if a search is active.
*/
public boolean isSearchMode()
{
return filterWord != null;
}
/** Create the map of nodes constituting the library.
*/
private void createMap()
{
libraryNodeMap = new HashMap<TreePath, AbstractMacroTreeNode>();
}
/** Get the type of the specified node.
@param path the node to analyze.
@return the kind of the node: ROOT, LIBRARY, CATEGORY, MACRO or -1
if the type could not be retrieved.
*/
public int getNodeType(TreePath path)
{
Object o;
int type;
if(path==null) return -1;
o = path.getLastPathComponent();
if(o instanceof RootNode) {
return ROOT;
} else if(o instanceof LibraryNode) {
return LIBRARY;
} else if(o instanceof CategoryNode) {
return CATEGORY;
} else if(o instanceof MacroNode) {
return MACRO;
}
return -1;
}
/** Return filtering word.
@return null or filtering word.
*/
public String getFilterWord()
{
return filterWord;
}
/** Implements TreeModel interface.
Add a tree model listener.
@param l the tree model listener.
*/
public void addTreeModelListener(TreeModelListener l)
{
listeners.add(l);
}
/** Implements TreeModel interface .
Get the child object.
@param parent the parent object.
@param index the index of the child.
@return the child object retrieved.
*/
public Object getChild(Object parent, int index)
{
return ((TreeNode)parent).getChildAt(index);
}
/** Implements TreeModel interface. Get the number of children.
@param parent the parent object.
@return the number of the children.
*/
public int getChildCount(Object parent)
{
return ((TreeNode)parent).getChildCount();
}
/** Implements TreeModel interface. Get the index of the given child.
@param parent the parent object.
@param child the child to search for.
@return the index of the child or -1 if the child has not been found.
TODO: check if it is true that it is -1...
*/
public int getIndexOfChild(Object parent, Object child)
{
return ((TreeNode)parent).getIndex((TreeNode)child);
}
/** Implements TreeModel interface.
Get the root node.
@return the root node.
*/
public Object getRoot()
{
return rootNode;
}
/** Implements TreeModel interface.
Check if the given node is a leaf.
@param node the node to check.
@return true if the node is a leaf (in this context, a macro).
*/
public boolean isLeaf(Object node)
{
return ((TreeNode)node).isLeaf();
}
/** Implements TreeModel interface.
Remove the given listener.
@param l the listener to remove.
*/
public void removeTreeModelListener(TreeModelListener l)
{
listeners.remove(l);
}
/** Implements TreeModel interface.
TODO: improve the documentation. What is that supposed to do? Is the
implementation complete in the code?
@param path the path.
@param newValue the new value.
*/
public void valueForPathChanged(TreePath path, Object newValue)
{
// NOP
}
/** Get a certain macro (leaf in this context).
@param path the path where the macro has to be searched for.
@return the macro.
*/
public MacroDesc getMacro(TreePath path)
{
Object o;
if(path==null) return null;
o = getPathComponentAt(path,3);
if(o instanceof MacroNode) {
return ((MacroNode)o).getMacro();
} else {
return null;
}
}
/** Get a certain category.
@param path the path where the category has to be searched for.
@return the category.
*/
public Category getCategory(TreePath path)
{
Object o;
if(path==null) return null;
o = getPathComponentAt(path,2);
if(o instanceof CategoryNode) {
return ((CategoryNode)o).getCategory();
} else {
return null;
}
}
/** Get a certain library.
@param path the path where the library has to be searched for.
@return the library.
*/
public Library getLibrary(TreePath path)
{
Object o;
if(path==null) return null;
o = getPathComponentAt(path,1);
if(o instanceof LibraryNode) {
return ((LibraryNode)o).getLibrary();
} else {
return null;
}
}
private Object getPathComponentAt(TreePath path,int index)
{
if(index <= path.getPathCount()-1) {
return path.getPathComponent(index);
} else {
return null;
}
}
/** Notify that the tree has changed and it requires a refresh.
*/
private void fireChanged()
{
for(TreeModelListener l:listeners) {
l.treeStructureChanged(new TreeModelEvent((Object)this,
new TreePath(rootNode),null,null));
}
}
private TreePath createAbsolutePath(TreeNode lastNode)
{
TreeNode parentNode = lastNode.getParent();
if(parentNode==null){
return new TreePath(lastNode);
} else {
return createAbsolutePath(parentNode).pathByAddingChild(lastNode);
}
}
/** Called when a library node has to be renamed.
@param e the rename event.
*/
public void libraryNodeRenamed(RenameEvent e)
{
Object renamedNode = e.getRenamedNode();
TreePath renamedPath;
TreeNode renamedMacroTreeNode;
if(renamedNode==null) {
fireTreeNodeChanged(new TreePath(rootNode));
} else {
for(TreePath path:(Set<TreePath>)libraryNodeMap.keySet()){
if(path.getLastPathComponent()==renamedNode){
renamedMacroTreeNode = (TreeNode)libraryNodeMap.get(path);
renamedPath = createAbsolutePath(renamedMacroTreeNode);
fireTreeNodeChanged(renamedPath);
}
}
}
}
/** Called when a library node has to be removed.
@param e the ermove event.
*/
public void libraryNodeRemoved(RemoveEvent e)
{
Object parentNode;
TreePath parentPath;
TreeNode parentMacroTreeNode;
resetSearchMode();
synchronizeTree(null);
parentNode = e.getParentNode();
if(parentNode==null) {
fireTreeStructureChanged(new TreePath(rootNode));
} else {
for(TreePath path:(Set<TreePath>)libraryNodeMap.keySet()){
if(path.getLastPathComponent()==parentNode){
parentMacroTreeNode = (TreeNode)libraryNodeMap.get(path);
parentPath = createAbsolutePath(parentMacroTreeNode);
fireTreeStructureChanged(parentPath);
}
}
}
}
/** Called when a library node has to be daded.
@param e the add event.
*/
public void libraryNodeAdded(AddEvent e)
{
Object parentNode;
TreePath parentPath;
TreeNode parentMacroTreeNode;
resetSearchMode();
synchronizeTree(null);
parentNode = e.getParentNode();
if(parentNode==null) {
fireTreeStructureChanged(new TreePath(rootNode));
} else {
for(TreePath path:(Set<TreePath>)libraryNodeMap.keySet()){
if(path.getLastPathComponent()==parentNode){
parentMacroTreeNode = (TreeNode)libraryNodeMap.get(path);
parentPath = createAbsolutePath(parentMacroTreeNode);
fireTreeStructureChanged(parentPath);
}
}
}
}
/** Called when a library node has to be changed.
TODO: is this unimplemented?
@param e the changed event.
*/
public void libraryNodeKeyChanged(KeyChangeEvent e)
{
// Nothing to do here
}
/** To be called when a new library has been loaded.
*/
public void libraryLoaded()
{
resetSearchMode();
synchronizeTree(null);
fireChanged();
}
private void fireTreeNodeChanged(TreePath path)
{
if(path!=null) {
for(TreeModelListener l:listeners) {
l.treeNodesChanged(new TreeModelEvent(this, path));
}
}
}
private void fireTreeStructureChanged(TreePath path)
{
if(path!=null){
for(TreeModelListener l:listeners) {
l.treeStructureChanged(new TreeModelEvent(this, path));
}
}
}
/** Performs a synchronization of the library tree with the current
contents of the library model. It can be called when a research is
done, to obtain the results shown in the tree.
@param filter filtering rules to be applied.
*/
private void synchronizeTree(NodeFilterInterface filter)
{
LibraryNode ln;
CategoryNode cn;
MacroNode mn;
TreePath libraryPath;
TreePath categoryPath;
TreePath macroPath;
// Save a copy of the current library note
HashMap<TreePath,AbstractMacroTreeNode> tmpMap =libraryNodeMap;
/*
(HashMap<TreePath,AbstractMacroTreeNode>)libraryNodeMap.clone();
libraryNodeMap.clear(); */
libraryNodeMap = new HashMap<TreePath, AbstractMacroTreeNode>();
if(rootNode==null){
rootNode = new RootNode();
}
if(filter==null) {
rootNode.setLabel("FidoCadJ");
} else {
rootNode.setLabel("Search results...");
}
rootNode.clearChildNodes();
for(Library library:libraryModel.getAllLibraries()) {
libraryPath = new TreePath(library);
if(libraryNodeMap.containsKey(libraryPath)){
ln = (LibraryNode)tmpMap.get(libraryPath);
ln.clearChildNodes();
} else {
ln = new LibraryNode(library);
}
for(Category category:library.getAllCategories()) {
if(category.isHidden()) {
continue;
}
categoryPath = libraryPath.pathByAddingChild(category);
if(tmpMap.containsKey(categoryPath)){
cn = (CategoryNode)tmpMap.get(categoryPath);
cn.clearChildNodes();
} else {
cn = new CategoryNode(category);
}
for(MacroDesc macro:category.getAllMacros()) {
macroPath = categoryPath.pathByAddingChild(macro);
if(tmpMap.containsKey(macroPath)){
mn = (MacroNode)tmpMap.get(macroPath);
} else {
mn = new MacroNode(macro);
}
if(filter!=null && !filter.accept(mn)) {
continue;
}
cn.addMacroNode(mn);
libraryNodeMap.put(macroPath,mn);
}
if(filter!=null && cn.getChildCount()==0) {
continue;
}
ln.addCategoryNode(cn);
libraryNodeMap.put(categoryPath,cn);
}
if(filter!=null && ln.getChildCount()==0) {
continue;
}
rootNode.addLibraryNode(ln);
libraryNodeMap.put(libraryPath,ln);
}
rootNode.sortTree();
}
private class RootNode extends AbstractMacroTreeNode
{
RootNode()
{
parent = null;
label = "FidoCadJ";
icon = MetalIconFactory.getTreeComputerIcon();
}
RootNode(String label)
{
parent=null;
this.label=label;
icon = MetalIconFactory.getTreeComputerIcon();
}
RootNode(String label,Icon icon)
{
parent=null;
this.label=label;
this.icon = icon;
}
public void addLibraryNode(LibraryNode node)
{
childNodes.add(node);
node.setParent((TreeNode)this);
}
public void setLabel(String label)
{
this.label = label;
}
}
private static class LibraryNode extends AbstractMacroTreeNode
implements Comparable<LibraryNode>
{
final private Library library;
LibraryNode(Library library)
{
this.library = library;
if(library.isStdLib()) {
icon = MetalIconFactory.getTreeHardDriveIcon();
} else {
icon = MetalIconFactory.getTreeFloppyDriveIcon();
}
}
public Library getLibrary()
{
return library;
}
public void addCategoryNode(CategoryNode node)
{
childNodes.add(node);
node.setParent((TreeNode)this);
}
public int compareTo(LibraryNode node)
{
Library l1 = this.library;
Library l2 = node.getLibrary();
if(l1.isStdLib() == l2.isStdLib()) {
return l1.getName().compareToIgnoreCase(l2.getName());
} else {
if(l1.isStdLib()) {
return -1;
} else {
return 1;
}
}
}
/** Convert to string.
@return the name of the node.
*/
public String toString()
{
return library.getName();
}
/** Inherit the behavior of compareTo.
*/
public boolean equals(Object node)
{
if(node instanceof LibraryNode)
return compareTo((LibraryNode)node)==0;
else
return false;
}
/** No implementation of the hashCode for the moment
@return 42, because it is The Answer.
*/
public int hashCode()
{
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
}
private static class CategoryNode extends AbstractMacroTreeNode
implements Comparable<CategoryNode>
{
final private Category category;
CategoryNode(Category category)
{
this.category = category;
icon = null;
}
public Category getCategory()
{
return category;
}
public void addMacroNode(MacroNode node)
{
childNodes.add(node);
node.setParent((TreeNode)this);
}
public int compareTo(CategoryNode node)
{
Category c1 = this.category;
Category c2 = node.getCategory();
return c1.getName().compareToIgnoreCase(c2.getName());
}
public String toString()
{
return category.getName();
}
/** Inherit the behavior of compareTo.
*/
public boolean equals(Object node)
{
if(node instanceof CategoryNode)
return compareTo((CategoryNode)node)==0;
else
return false;
}
/** No implementation of the hashCode for the moment
@return 42, because it is The Answer.
*/
public int hashCode()
{
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
}
private class MacroNode extends AbstractMacroTreeNode
implements Comparable<MacroNode>
{
MacroDesc macro;
MacroNode(MacroDesc macroDesc)
{
this.macro = macroDesc;
icon = null;
}
public MacroDesc getMacro()
{
return macro;
}
@Override
public boolean isLeaf()
{
return true;
}
/** Compare two nodes. The comparison is done with respect to the name
and if the name is equal, then the key is compared too.
*/
public int compareTo(MacroNode node)
{
MacroDesc m1 = this.macro;
MacroDesc m2 = node.getMacro();
// At first, compare the two nodes using their name
int r=m1.name.compareToIgnoreCase(m2.name);
// If they have the same name, look at the keys.
if(r==0) {
r=m1.key.compareToIgnoreCase(m2.key);
}
return r;
}
public String toString()
{
return macro.name;
}
/** Inherit the behavior of compareTo.
*/
public boolean equals(Object node)
{
if(node instanceof MacroNode)
return compareTo((MacroNode)node)==0;
else
return false;
}
/** No implementation of the hashCode for the moment
@return 42, because it is The Answer.
*/
public int hashCode()
{
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
}
}