package org.freeplane.plugin.workspace.components; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.event.ComponentListener; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.freeplane.core.ui.components.OneTouchCollapseResizer.ComponentCollapseListener; import org.freeplane.core.ui.components.ResizeEvent; import org.freeplane.core.util.LogUtils; import org.freeplane.plugin.workspace.WorkspaceController; import org.freeplane.plugin.workspace.dnd.DnDController; import org.freeplane.plugin.workspace.dnd.WorkspaceTransferHandler; import org.freeplane.plugin.workspace.event.IWorkspaceNodeActionListener; import org.freeplane.plugin.workspace.event.WorkspaceActionEvent; import org.freeplane.plugin.workspace.features.WorkspaceNodeSelectionHandler; import org.freeplane.plugin.workspace.handler.DefaultNodeTypeIconManager; import org.freeplane.plugin.workspace.handler.INodeTypeIconManager; import org.freeplane.plugin.workspace.listener.DefaultTreeExpansionListener; import org.freeplane.plugin.workspace.listener.DefaultWorkspaceSelectionListener; import org.freeplane.plugin.workspace.mindmapmode.DefaultFileDropHandler; import org.freeplane.plugin.workspace.mindmapmode.FileFolderDropHandler; import org.freeplane.plugin.workspace.mindmapmode.InputController; import org.freeplane.plugin.workspace.mindmapmode.VirtualFolderDropHandler; import org.freeplane.plugin.workspace.model.AWorkspaceTreeNode; import org.freeplane.plugin.workspace.model.WorkspaceModel; import org.freeplane.plugin.workspace.model.project.AWorkspaceProject; import org.freeplane.plugin.workspace.model.project.IProjectSelectionListener; import org.freeplane.plugin.workspace.model.project.ProjectSelectionEvent; import org.freeplane.plugin.workspace.nodes.AFolderNode; import org.freeplane.plugin.workspace.nodes.DefaultFileNode; import org.freeplane.plugin.workspace.nodes.FolderFileNode; import org.freeplane.plugin.workspace.nodes.FolderLinkNode; import org.freeplane.plugin.workspace.nodes.FolderTypeMyFilesNode; import org.freeplane.plugin.workspace.nodes.FolderVirtualNode; import org.freeplane.plugin.workspace.nodes.LinkTypeFileNode; import org.freeplane.plugin.workspace.nodes.ProjectRootNode; import org.freeplane.plugin.workspace.nodes.WorkspaceRootNode; public class TreeView extends JPanel implements IWorkspaceView, ComponentCollapseListener { private static final long serialVersionUID = 1L; private static final int view_margin = 3; protected JTree mTree; protected JTextField m_display; private WorkspaceTransferHandler transferHandler; private INodeTypeIconManager nodeTypeIconManager; private List<IProjectSelectionListener> projectSelectionListeners = new ArrayList<IProjectSelectionListener>(); private AWorkspaceProject lastSelectedProject; private InputController inputController; private ExpandedStateHandler expandedStateHandler; private boolean paintingEnabled; private WorkspaceNodeSelectionHandler nodeSelectionHandler; public TreeView() { this.setLayout(new BorderLayout()); mTree = new JTree() { private static final long serialVersionUID = 1L; public void paint(Graphics g) { if(isPaintingEnabled()) { try { super.paint(g); } catch (Exception e) { } } } }; mTree.setBorder(BorderFactory.createEmptyBorder(2, view_margin, view_margin, view_margin)); mTree.putClientProperty("JTree.lineStyle", "Angled"); mTree.setCellRenderer(new WorkspaceNodeRenderer()); mTree.setCellEditor(new WorkspaceCellEditor(mTree, (DefaultTreeCellRenderer) mTree.getCellRenderer())); mTree.addTreeExpansionListener(new DefaultTreeExpansionListener()); mTree.addTreeExpansionListener(getExpandedStateHandler()); mTree.addTreeSelectionListener(getNodeSelectionHandler().getTreeSelectionListener()); mTree.addTreeSelectionListener(new DefaultWorkspaceSelectionListener()); mTree.addTreeSelectionListener(getProjectSelectionHandler()); //WORKSPACE - impl(later): enable multi selection mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); mTree.addMouseListener(getInputController()); mTree.addMouseMotionListener(getInputController()); mTree.addKeyListener(getInputController()); mTree.setRowHeight(18); mTree.setShowsRootHandles(false); mTree.setEditable(true); this.transferHandler = WorkspaceTransferHandler.configureDragAndDrop(mTree); initTransferHandler(); workspaceBox = Box.createVerticalBox(); workspaceBox.add(new JScrollPane(mTree)); this.add(workspaceBox, BorderLayout.CENTER); } public void addBottomBanner(Component comp) { workspaceBox.remove(comp); workspaceBox.add(comp); workspaceBox.validate(); } public void removeBottomBanner(Component comp) { workspaceBox.remove(comp); workspaceBox.validate(); } private ExpandedStateHandler getExpandedStateHandler() { if(expandedStateHandler == null) { expandedStateHandler = new ExpandedStateHandler(mTree); } return expandedStateHandler; } private void initTransferHandler() { getTransferHandler().registerNodeDropHandler(DefaultFileNode.class, new DefaultFileDropHandler()); getTransferHandler().registerNodeDropHandler(FolderFileNode.class, new FileFolderDropHandler()); getTransferHandler().registerNodeDropHandler(FolderLinkNode.class, new FileFolderDropHandler()); getTransferHandler().registerNodeDropHandler(FolderTypeMyFilesNode.class, new FileFolderDropHandler()); getTransferHandler().registerNodeDropHandler(FolderVirtualNode.class, new VirtualFolderDropHandler()); getTransferHandler().registerNodeDropHandler(ProjectRootNode.class, new VirtualFolderDropHandler()); //default fallback for folder getTransferHandler().registerNodeDropHandler(AFolderNode.class, new VirtualFolderDropHandler()); DnDController.excludeFromDND(WorkspaceRootNode.class); DnDController.excludeFromDND(LinkTypeFileNode.class); DnDController.excludeFromDND(DefaultFileNode.class); } public InputController getInputController() { if(inputController == null) { inputController = new InputController(); } return inputController; } private TreeSelectionListener getProjectSelectionHandler() { return new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { try { AWorkspaceProject selected = WorkspaceController.getCurrentModel().getProject(((AWorkspaceTreeNode) e.getNewLeadSelectionPath().getLastPathComponent()).getModel()); if(selected != lastSelectedProject) { fireProjectSelectionChanged(selected); } } catch (Exception ex) { // just for convenience, ignore everything } } }; } public void addTreeMouseListener(MouseListener l) { this.mTree.addMouseListener(l); } public void addTreeComponentListener(ComponentListener l) { this.mTree.addComponentListener(l); } public void setPreferredSize(Dimension size) { super.setPreferredSize(new Dimension(Math.max(size.width, getMinimumSize().width), Math.max(size.height, getMinimumSize().height))); } public void expandPath(final TreePath treePath) { if(EventQueue.isDispatchThread()) { try { mTree.expandPath(treePath); } catch(Exception e) { LogUtils.warn("TreeView.expandPath(): ", e); } } else { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { try { mTree.expandPath(treePath); } catch(Exception e) { LogUtils.warn("TreeView.expandPath(): ", e); } } }); } catch (Exception e) { LogUtils.warn(e); } } } public void collapsePath(TreePath treePath) { mTree.collapsePath(treePath); } public void refreshView() { paintingEnabled = true; getExpandedStateHandler().setExpandedStates(((AWorkspaceTreeNode)mTree.getModel().getRoot()).getModel(), true); repaint(); } public void setModel(WorkspaceModel model) { if(model instanceof TreeModel) { mTree.setModel((TreeModel) model); } else { mTree.setModel(new TreeModelProxy(model)); } getExpandedStateHandler().registerModel(model); } public WorkspaceTransferHandler getTransferHandler() { return this.transferHandler; } public void addSelectionPath(TreePath path) { mTree.addSelectionPath(path); } public boolean isPaintingEnabled() { return paintingEnabled; } public void setPaintingEnabled(boolean enabled) { this.paintingEnabled = enabled; } public class TreeModelProxy implements TreeModel { private final WorkspaceModel model; public TreeModelProxy(WorkspaceModel model) { this.model = model; } public Object getRoot() { if(model == null) return null; return model.getRoot(); } public Object getChild(Object parent, int index) { if(parent == null) return null; return ((AWorkspaceTreeNode) parent).getChildAt(index); } public int getChildCount(Object parent) { if(parent == null) return 0; return ((AWorkspaceTreeNode) parent).getChildCount(); } public boolean isLeaf(Object node) { return ((AWorkspaceTreeNode) node).isLeaf(); } public void valueForPathChanged(TreePath path, Object newValue) { AWorkspaceTreeNode node = (AWorkspaceTreeNode) path.getLastPathComponent(); if (node instanceof IWorkspaceNodeActionListener) { ((IWorkspaceNodeActionListener) node).handleAction(new WorkspaceActionEvent(node, WorkspaceActionEvent.WSNODE_CHANGED, newValue)); //nodeChanged(node); } else { node.setName(newValue.toString()); } } public int getIndexOfChild(Object parent, Object child) { return ((AWorkspaceTreeNode) parent).getIndex((TreeNode) child); } public void addTreeModelListener(TreeModelListener l) { this.model.addTreeModelListener(l); } public void removeTreeModelListener(TreeModelListener l) { this.model.removeTreeModelListener(l); } } public boolean containsComponent(Component comp) { if(this.equals(comp)) { return true; } else if(mTree.equals(comp)) { return true; } return false; } public TreePath getSelectionPath() { return mTree.getSelectionPath(); } public TreePath getPathForLocation(int x, int y) { return mTree.getClosestPathForLocation(x, y); } public INodeTypeIconManager getNodeTypeIconManager() { if(nodeTypeIconManager == null) { nodeTypeIconManager = new DefaultNodeTypeIconManager(); } return nodeTypeIconManager; } public void componentCollapsed(ResizeEvent event) { if(this.equals(event.getComponent())) { super.setPreferredSize(new Dimension(0, getPreferredSize().height)); } } public void componentExpanded(ResizeEvent event) { if(this.equals(event.getComponent())) { // nothing } } public AWorkspaceTreeNode getNodeForLocation(int x, int y) { TreePath path = mTree.getPathForLocation(x, y); if(path == null) { return null; } return (AWorkspaceTreeNode) path.getLastPathComponent(); } public void addProjectSelectionListener(IProjectSelectionListener listener) { if(listener == null) { return; } synchronized (projectSelectionListeners ) { projectSelectionListeners.add(listener); } } private void fireProjectSelectionChanged(AWorkspaceProject selected) { // if(selected == null) { // return; // } ProjectSelectionEvent event = new ProjectSelectionEvent(this, selected, this.lastSelectedProject); this.lastSelectedProject = selected; synchronized (projectSelectionListeners ) { for (IProjectSelectionListener listener : projectSelectionListeners) { listener.selectionChanged(event); } } } public void expandAll(AWorkspaceTreeNode nodeFromActionEvent) { for (int i = 1; i < mTree.getRowCount(); i++) { mTree.expandRow(i); } } public final static String BOTTOM_TOOLBAR_STACK = BorderLayout.SOUTH; public final static String TOP_TOOLBAR_STACK = BorderLayout.NORTH; private Box workspaceBox; public void addToolBar(Component comp, String toolBarStack) { this.add(comp, toolBarStack); } public WorkspaceNodeSelectionHandler getNodeSelectionHandler() { if(nodeSelectionHandler == null) { nodeSelectionHandler = new WorkspaceNodeSelectionHandler(); } return nodeSelectionHandler; } public Component getComponent() { return this; } }