/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * VueDragTree.java * * Created on May 5, 2003, 4:08 PM */ package tufts.vue; import tufts.Util; import tufts.vue.gui.GUI; import javax.swing.*; import java.awt.*; import java.io.*; import java.net.*; import java.awt.event.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import java.util.*; import javax.swing.event.*; import osid.dr.*; import osid.filing.*; import tufts.oki.remoteFiling.*; import tufts.oki.localFiling.*; import javax.swing.tree.*; import java.util.Iterator; /** * * @version $Revision: 1.83 $ / $Date: 2010-02-03 19:17:41 $ / $Author: mike $ * @author rsaigal * @author Scott Fraize */ public class VueDragTree extends JTree implements DragGestureListener, DragSourceListener, TreeSelectionListener, ActionListener { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(VueDragTree.class); public static ResourceNode oldnode; private static final int DOUBLE_CLICK = 2; //private static final boolean FullStartup = VueUtil.isMacPlatform() && !DEBUG.Enabled; private static final boolean FullStartup = true; //private static final boolean SlowStartup = false; // protected static final ImageIcon nleafIcon = VueResources.getImageIcon("favorites.leafIcon") ; // protected static final ImageIcon inactiveIcon = VueResources.getImageIcon("favorites.inactiveIcon") ; // protected static final ImageIcon activeIcon = VueResources.getImageIcon("favorites.activeIcon") ; private final java.util.List<Resource> mResources = new ArrayList(); public VueDragTree(Iterable<Resource> iterable, String treeName) { if (DEBUG.DR) Log.debug("NEW: " + treeName + "; " + Util.tags(iterable)); //if (DEBUG.Enabled) Util.printStackTrace(Util.tags(this) + "; NEW " + getClass() + " " + treeName); setModel(createTreeModel(iterable, treeName)); setName(treeName); setRootVisible(true); if (FullStartup) { this.expandRow(0); this.expandRow(1); //this.setRootVisible(false); } implementDrag(this); createPopupMenu(); this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); addTreeSelectionListener(this); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent me){ if (me.getClickCount() != DOUBLE_CLICK) return; TreePath path = getPathForLocation(me.getX(), me.getY()); if (path == null) return; Object c = path.getLastPathComponent(); if (c instanceof CabinetNode) { CabinetNode cabNode = (CabinetNode) path.getLastPathComponent(); Resource r = cabNode.getResource(); if (r != null) r.displayContent(); // Object uo = cabNode.getUserObject(); // if (uo instanceof Resource) // ((Resource)uo).displayContent(); } } }); addListeners(); if (DEBUG.SELECTION) Util.printStackTrace(GUI.namex(this) + " constructed from obj " + iterable + " treeName " + treeName); } protected VueDragTree(FavoritesNode favoritesNode) { //Util.printStackTrace("NEW: " + getClass() + "; " + favoritesNode + "; " + favoritesNode.getResource()); if (DEBUG.Enabled) Log.debug("NEW: " + favoritesNode + "; " + favoritesNode.getResource());; setName(favoritesNode.toString()); setModel(new DefaultTreeModel(favoritesNode)); expandRow(0); createPopupMenu(); implementDrag(this); addTreeSelectionListener(this); addListeners(); if (DEBUG.SELECTION) Util.printStackTrace(GUI.namex(this) + " constructed from FavoritesNode " + favoritesNode); } protected void addListeners() { VUE.addActiveListener(Resource.class, this); } @Override public void addNotify() { super.addNotify(); // if (resourceSelection == null) { // // Only do this on addNotify, as workaround for buggy multiple // // instances of data sources being spuriously generated by // // the data source loader. // resourceSelection = VUE.getResourceSelection(); // resourceSelection.addListener(this); // } if (DEBUG.SELECTION) System.out.println(GUI.namex(this) + " addNotify"); } @Override public void removeNotify() { super.removeNotify(); if (DEBUG.SELECTION) System.out.println(GUI.namex(this) + " removeNotify"); } public void activeChanged(ActiveEvent e, Resource r) { // as we won't see events from ourself, we can always clear the selection clearSelection(); repaint(); } // /** ResourceSelection.Listener */ // public void resourceSelectionChanged(tufts.vue.ResourceSelection.Event e) { // if (e.source == this) { // return; // //if (getPicked() == e.selected) { // // ; // do nothing; already selected // } else { // // todo: if contains selected item, select it // // TODO: clearing the selection isn't working! don't know // // if is just a repaint issue. // clearSelection(); // //setSelectionRow(-1); // repaint(); // } // } private void implementDrag(VueDragTree tree){ DragSource dragSource = DragSource.getDefaultDragSource(); dragSource.createDefaultDragGestureRecognizer (tree, DnDConstants.ACTION_COPY | DnDConstants.ACTION_MOVE | DnDConstants.ACTION_LINK, (DragGestureListener) tree); addTreeExpansionListener(new TreeExpansionListener(){ public void treeCollapsed(TreeExpansionEvent e) {} public void treeExpanded(TreeExpansionEvent e) { TreePath path = e.getPath(); if(path != null) { if (path.getLastPathComponent() instanceof FileNode){ FileNode node = (FileNode)path.getLastPathComponent(); if( !node.isExplored()) { DefaultTreeModel model = (DefaultTreeModel)getModel(); node.explore(); model.nodeStructureChanged(node); } } } } }); // Add a tree will expand listener. addTreeWillExpandListener(new TreeWillExpandListener() { public void treeWillExpand(TreeExpansionEvent e) { TreePath path = e.getPath(); if (path.getLastPathComponent() instanceof CabinetNode) { CabinetNode cabNode = (CabinetNode) path.getLastPathComponent(); if (cabNode == null) return; setSelectionPath(path); if (cabNode.getCabinet() != null)cabNode.getDataModel().reload(); } } public void treeWillCollapse(TreeExpansionEvent e) {} }); VueDragTreeCellRenderer renderer = new VueDragTreeCellRenderer(this); tree.setCellRenderer(renderer); ToolTipManager.sharedInstance().registerComponent(tree); } private DefaultTreeModel createTreeModel(Iterable<Resource> iterable, String treeName) { //final Resource resourceRoot = Resource.getFactory().get(treeName); //final ResourceNode root = new ResourceNode(resourceRoot); final ResourceNode root = new RootNode(treeName); if (DEBUG.Enabled) Log.debug("createTreeModel: " + root); if (iterable != null) { boolean didFirst = false; for (Resource resource : iterable) { mResources.add(resource); if (DEBUG.RESOURCE || DEBUG.DR) Log.debug("\tchild: " + resource); if (resource instanceof CabinetResource) { CabinetResource cabRes = (CabinetResource) resource; CabinetEntry entry = cabRes.getEntry(); CabinetNode cabNode = null; if (entry instanceof RemoteCabinetEntry) cabNode = new CabinetNode(cabRes, CabinetNode.REMOTE); else cabNode = new CabinetNode(cabRes, CabinetNode.LOCAL); //------------------------------------------------------- root.add(cabNode); //------------------------------------------------------- if (FullStartup && !didFirst) { // Do NOT DO THIS AUTOMATICALLY -- it can dramaticaly slow startup times. // by tens of seconds (!) -- SMF 2007-10-10 if ((new File(cabRes.getSpec())).isDirectory()) cabNode.explore(); else if (cabNode.getCabinet() != null) cabNode.explore(); } } else { ResourceNode node = new ResourceNode(resource); //------------------------------------------------------- root.add(node); //------------------------------------------------------- } didFirst = true; } } return new DefaultTreeModel(root); } //**************************************** public void dragGestureRecognized(DragGestureEvent e) { if (getSelectionPath() != null) { TreePath path = getLeadSelectionPath(); oldnode = (ResourceNode)path.getLastPathComponent(); ResourceNode parentnode = (ResourceNode)oldnode.getParent(); Resource resource = oldnode.getResource(); if (DEBUG.DND) System.out.println(this + " dragGestureRecognized " + e); if (DEBUG.DND) System.out.println("selected node is " + oldnode.getClass() + "[" + oldnode + "] resource=" + resource); if (resource != null) GUI.startRecognizedDrag(e, resource, this); } } public void dragDropEnd(DragSourceDropEvent e) { if (tufts.vue.VUE.dropIsLocal == true){ DefaultTreeModel model = (DefaultTreeModel)this.getModel(); model.removeNodeFromParent(oldnode); tufts.vue.VUE.dropIsLocal = false; } } public void dragEnter(DragSourceDragEvent e) { } public void dragExit(DragSourceEvent e) {} public void dragOver(DragSourceDragEvent e) {} public void dropActionChanged(DragSourceDragEvent e) { if (DEBUG.DND) System.out.println("VueDragTree: dropActionChanged to " + tufts.vue.gui.GUI.dropName(e.getDropAction())); } public void valueChanged(TreeSelectionEvent e) { try { if (e.isAddedPath() && e.getPath().getLastPathComponent() != null ) { Resource resource = (Resource)((ResourceNode)e.getPath().getLastPathComponent()).getResource(); VUE.setActive(Resource.class, this, resource); //resourceSelection.setTo(resource, this); } } catch(Exception ex) { // (null,ex.toString(),"Error in VueDragTree Selection"); System.out.println("VueDragTree.valueChanged "+ex.getMessage()); ex.printStackTrace(); } } public void createPopupMenu() { JMenuItem menuItem; //Create the popup menu. JPopupMenu popup = new JPopupMenu(); menuItem = new JMenuItem(VueResources.getString("vuedragtree.menu.openresource")); menuItem.addActionListener(this); popup.add(menuItem); if (mResources.size() > 0) { menuItem = new JMenuItem(VueResources.getString("vuedragtree.menu.addalltomap")); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { VUE.getActiveViewer().getMapDropTarget() .processTransferable(new tufts.vue.gui.GUI.ResourceTransfer(mResources), null); // VUE.getActiveMap().getUndoManager().mark("Add Resources to Map"); // todo: can't override pre-existing "Drop" mark } }); } popup.add(menuItem); //Add listener to the text area so the popup menu can come up. MouseListener popupListener = new PopupListener(popup); this.addMouseListener(popupListener); } public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JMenuItem){ JMenuItem source = (JMenuItem)(e.getSource()); TreePath tp = this.getSelectionPath(); if (tp != null){ ResourceNode resNode = (ResourceNode)tp.getLastPathComponent(); resNode.getResource().displayContent(); } } } class PopupListener extends MouseAdapter { JPopupMenu popup; PopupListener(JPopupMenu popupMenu) { popup = popupMenu; } public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseClicked(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (VueDragTree.this.getSelectionPath() != null){ if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } } } class VueDragTreeCellRenderer extends DefaultTreeCellRenderer{ protected final VueDragTree tree; public VueDragTreeCellRenderer(VueDragTree vdTree) { this.tree = vdTree; //if (DEBUG.Enabled) setOpaque(true); } protected final void superGetTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); if (VUE.VUE3) colorLabel(sel, value); } private void colorLabel(boolean selected, Object value) { final ResourceNode node = (ResourceNode) value; if (!selected && node.resource != null && node.resource.equals(VUE.getActiveResource())) setForeground(Color.blue); //setForeground(VueConstants.COLOR_SELECTION); else setForeground(Color.black); } public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); if (leaf) { ResourceNode node = (ResourceNode) value; Icon icon = node.getIcon(); if (icon != null) setIcon(icon); if (VUE.VUE3) colorLabel(sel, value); } return this; } } @Override public String toString() { return getClass().getName() + "[" + getName() + "]"; } } class ResourceNode extends DefaultMutableTreeNode { protected Resource resource; protected static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(ResourceNode.class); public ResourceNode(Resource resource) { this.resource = resource; //if (resource != null) resource.setDebugProperty("TREE-NODE", getClass().getSimpleName()); setUserObject(resource); } protected ResourceNode() { resource = null; } public Resource getResource() { return resource; } public String toString() { String title = resource.getTitle(); if (title == null || title.length() == 0) return resource.getSpec(); else return title; } public Icon getIcon() { if (resource != null) { //resource.setDebugProperty("TREE-LEAF", isLeaf() ? "TRUE" : "FALSE"); return resource.getTinyIcon(); } else { return null; } } } class CabinetNode extends ResourceNode { private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(CabinetNode.class); public static CabinetNode getCabinetNode(String title, File file, ResourceNode rootNode, DefaultTreeModel model){ CabinetNode node= null; try{ osid.shared.Agent agent = null; LocalCabinetEntry cab; if(file.isDirectory()){ cab = LocalCabinet.instance(file.getAbsolutePath(),agent,null); } else { // todo: use factory, also -- should this be a bytestore? // TODO: BUG: THIS IS COMPLETELY ILLEGAL (in fact, LocalCabinetEntry // should be an abstract class) // SMF 2008-04-17 cab = new LocalCabinetEntry(file.getAbsolutePath(),agent,null); if (DEBUG.Enabled) Log.debug("new INDETERMINATE CABINET ENTRY " + cab); } CabinetResource res = CabinetResource.create(cab); //if (DEBUG.Enabled) Log.debug("new " + res); CabinetEntry entry = res.getEntry(); //if (title != null) res.setTitle(title); if (entry instanceof RemoteCabinetEntry) node = new CabinetNode(res, REMOTE); else node = new CabinetNode(res, LOCAL); model.insertNodeInto(node, rootNode, (rootNode.getChildCount())); //node.explore(); } catch(Exception ex){ System.out.println("CabinetNode: "+ex); ex.printStackTrace(); } return node; } public static final String LOCAL = "local"; public static final String REMOTE = "remote"; private final String type; private final boolean isLeaf; private final CabinetEntry entry; private boolean explored = false; CabinetNode(final CabinetResource r, String type) { super(r); this.type = type; this.entry = r.getEntry(); if (r == null) { isLeaf = false; } else if (r.getClientType() == Resource.FILE) { isLeaf = true; } else if (r.getClientType() == Resource.DIRECTORY) { isLeaf = false; } else if (entry != null) { if (type.equals(REMOTE) && ((RemoteCabinetEntry)entry).isCabinet()) isLeaf = false; else if (type.equals(LOCAL) && ((LocalCabinetEntry)entry).isCabinet()) isLeaf = false; else isLeaf = true; } else isLeaf = true; //setAllowsChildren(!isLeaf); } // public CabinetNode(final CabinetResource cabRes) { // super(cabRes); // this.type = LOCAL; // this.entry = cabRes.getEntry(); // this.isLeaf = false; // } // Will allow lazy creation of the Resource (including exact app icon fetch -- helps much on Windows) private static final boolean LazyResources = false; private CabinetNode(final LocalCabinetEntry lentry) { super(LazyResources ? null : CabinetResource.create(lentry)); this.type = LOCAL; this.entry = lentry; this.isLeaf = !lentry.isCabinet(); //setAllowsChildren(!isLeaf); } private CabinetNode(final RemoteCabinetEntry rentry) { super(LazyResources ? null : CabinetResource.create(rentry)); this.type = REMOTE; this.entry = rentry; this.isLeaf = !rentry.isCabinet(); //setAllowsChildren(!isLeaf); } @Override public final Resource getResource() { if (LazyResources) { synchronized (this) { if (resource == null) { //Util.printStackTrace("producing for " + entry); resource = CabinetResource.create(entry); } } } return resource; } public final boolean isLeaf() { return isLeaf; } // public final Icon getIcon() { // if (isLeaf && resource != null) // return resource.getTinyIcon(); // else // return null; // } /** * Return the cabinet entry associated with this tree node. If it is a cabinet, * then return it. Otherwise, return null. */ public Cabinet getCabinet() { return entry instanceof Cabinet ? ((Cabinet)entry) : null; } /* * Expand the tree (ie. find the cabinet entries below this node). * This only applies if the current node is a cabinet. */ public void explore() { if (explored) return; if (getCabinet() != null) { final String name = Util.tags(entry); try { if (DEBUG.Enabled) Log.debug(name + ": explore..."); loadEntries(); if (DEBUG.Enabled) Log.debug(name + ": explored."); } catch (FilingException e) { Log.warn("explore: " + name + ";", e); } } } private void loadEntries() throws FilingException { if (type.equals(LOCAL)) { CabinetEntryIterator i = (LocalCabinetEntryIterator) getCabinet().entries(); while (i.hasNext()) { LocalCabinetEntry ce = (LocalCabinetEntry) i.next(); if (ce.getDisplayName().startsWith(".")) // don't display dot files continue; this.add(new CabinetNode(ce)); } this.explored = true; } else if (type.equals(REMOTE)) { CabinetEntryIterator i = (RemoteCabinetEntryIterator) getCabinet().entries(); while (i.hasNext()) { RemoteCabinetEntry ce = (RemoteCabinetEntry) i.next(); if (ce.getDisplayName().startsWith(".")) // don't display dot files continue; this.add(new CabinetNode(ce)); } } } /** * Return a string version of the node. In this implementation, the display name * of the cabinet entry is returned. */ @Override public String toString() { if (resource != null) { return resource.getTitle(); } else if (entry != null) { try { return entry.getDisplayName(); } catch (Throwable t) { Log.warn(t); return entry.toString(); } } else return String.format("%s@%x(%s)", getClass().getName(), System.identityHashCode(this), type); } /** * Return the data model used. */ DefaultTreeModel getDataModel() { explore(); return new DefaultTreeModel(this); } } class FavoritesNode extends ResourceNode { private boolean explored = false; public FavoritesNode(Resource resource){ super(resource); // ensure is marked as favorites (some versions of VUE may have left marked as type NONE) resource.setClientType(Resource.FAVORITES); } public boolean isExplored() { return explored; } } class RootNode extends ResourceNode { final String name; public RootNode(String name) { super(null); this.name = name; } @Override public final String toString() { return name; } @Override public final boolean isLeaf() { return false; } } class FileNode extends ResourceNode { private boolean explored = false; public FileNode(File file) { setUserObject(file); if (DEBUG.Enabled) Log.debug("NEW FileNode: " + file); // Code w/no apparent effect commented out -- SMF 2007-10-05 //try{ // MapResource resource = new MapResource(file.toURL().toString()); //}catch (Exception ex){}; } public boolean getAllowsChildren() { return isDirectory(); } public boolean isLeaf() { return !isDirectory(); } public File getFile() { return (File)getUserObject(); } public boolean isExplored() { return explored; } public boolean isDirectory() { File file = getFile(); if (file != null) { return file.isDirectory(); } else { return false; } } public String toString() { File file = (File)getUserObject(); String filename = file.toString(); int index = filename.lastIndexOf(File.separator); return (index != -1 && index != filename.length()-1) ? filename.substring(index+1) : filename; } public void displayContent(){ try{ URL url = getFile().toURL(); VueUtil.openURL(url.toString().replaceFirst("/","")); }catch (Exception ex){System.out.println("problem opening conten");} } public void explore() { if(!isDirectory()) return; if(!isExplored()) { File file = getFile(); File[] contents = file.listFiles(); for(int i=0; i < contents.length; ++i) add(new FileNode(contents[i])); explored = true; } } }