package org.freeplane.plugin.workspace.mindmapmode; import java.awt.Container; import java.awt.Dimension; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDropEvent; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeSelectionEvent; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.IMenuContributor; import org.freeplane.core.ui.MenuBuilder; import org.freeplane.core.ui.components.JResizer.Direction; import org.freeplane.core.ui.components.OneTouchCollapseResizer; import org.freeplane.core.ui.components.OneTouchCollapseResizer.CollapseDirection; import org.freeplane.core.ui.components.OneTouchCollapseResizer.ComponentCollapseListener; import org.freeplane.core.ui.components.ResizeEvent; import org.freeplane.core.ui.components.ResizerListener; import org.freeplane.core.ui.ribbon.RibbonMapChangeAdapter; import org.freeplane.core.util.Compat; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.link.LinkController; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.mode.mindmapmode.MModeController; import org.freeplane.features.ui.INodeViewLifeCycleListener; import org.freeplane.features.ui.ViewController; import org.freeplane.features.url.UrlManager; import org.freeplane.plugin.workspace.URIUtils; import org.freeplane.plugin.workspace.WorkspaceController; import org.freeplane.plugin.workspace.actions.FileNodeDeleteAction; import org.freeplane.plugin.workspace.actions.FileNodeNewFileAction; import org.freeplane.plugin.workspace.actions.FileNodeNewMindmapAction; import org.freeplane.plugin.workspace.actions.NodeCopyAction; import org.freeplane.plugin.workspace.actions.NodeCutAction; import org.freeplane.plugin.workspace.actions.NodeNewFolderAction; import org.freeplane.plugin.workspace.actions.NodeNewLinkAction; import org.freeplane.plugin.workspace.actions.NodeOpenLocationAction; import org.freeplane.plugin.workspace.actions.NodePasteAction; import org.freeplane.plugin.workspace.actions.NodeRefreshAction; import org.freeplane.plugin.workspace.actions.NodeRemoveAction; import org.freeplane.plugin.workspace.actions.NodeRenameAction; import org.freeplane.plugin.workspace.actions.PhysicalFolderSortOrderAction; import org.freeplane.plugin.workspace.actions.ProjectOpenLocationAction; import org.freeplane.plugin.workspace.actions.ProjectRenameAction; import org.freeplane.plugin.workspace.actions.WorkspaceCollapseAction; import org.freeplane.plugin.workspace.actions.WorkspaceExpandAction; import org.freeplane.plugin.workspace.actions.WorkspaceImportProjectAction; import org.freeplane.plugin.workspace.actions.WorkspaceNewMapAction; import org.freeplane.plugin.workspace.actions.WorkspaceNewProjectAction; import org.freeplane.plugin.workspace.actions.WorkspaceProjectOpenLocationAction; import org.freeplane.plugin.workspace.actions.WorkspaceRemoveProjectAction; import org.freeplane.plugin.workspace.components.IWorkspaceView; import org.freeplane.plugin.workspace.components.TreeView; import org.freeplane.plugin.workspace.creator.DefaultFileNodeCreator; import org.freeplane.plugin.workspace.dnd.WorkspaceTransferable; import org.freeplane.plugin.workspace.features.AWorkspaceModeExtension; import org.freeplane.plugin.workspace.features.IWorkspaceNodeSelectionListener; import org.freeplane.plugin.workspace.features.IWorkspaceSettingsHandler; import org.freeplane.plugin.workspace.handler.DefaultFileNodeIconHandler; import org.freeplane.plugin.workspace.handler.DirectoryMergeConflictDialog; import org.freeplane.plugin.workspace.handler.FileExistsConflictDialog; import org.freeplane.plugin.workspace.handler.LinkTypeFileIconHandler; import org.freeplane.plugin.workspace.io.AFileNodeCreator; import org.freeplane.plugin.workspace.io.FileReadManager; import org.freeplane.plugin.workspace.io.FileSystemManager; import org.freeplane.plugin.workspace.model.WorkspaceModel; import org.freeplane.plugin.workspace.model.WorkspaceModelEvent; import org.freeplane.plugin.workspace.model.WorkspaceModelListener; 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.DefaultFileNode; import org.freeplane.plugin.workspace.nodes.LinkTypeFileNode; import org.freeplane.view.swing.map.NodeView; import org.freeplane.view.swing.ui.mindmapmode.MNodeDropListener; public class MModeWorkspaceController extends AWorkspaceModeExtension { abstract class ResizerEventAdapter implements ResizerListener, ComponentCollapseListener { } private FileReadManager fileTypeManager; private TreeView view; private IWorkspaceSettingsHandler settings; private volatile WorkspaceModel wsModel; private AWorkspaceProject selectedProject = null; private IProjectSelectionListener projectSelectionListener; private Runnable viewUpdater; public MModeWorkspaceController(ModeController modeController) { super(modeController); setupController(modeController); } public void start(ModeController modeController) { setupActions(modeController); setupModel(modeController); setupView(modeController); } private void setupController(ModeController modeController) { modeController.removeExtension(UrlManager.class); UrlManager.install(new MModeWorkspaceUrlManager()); modeController.removeExtension(LinkController.class); LinkController.install(MModeWorkspaceLinkController.getController()); //add link type entry to the chooser MModeWorkspaceLinkController.getController().prepareOptionPanelBuilder(((MModeController)modeController).getOptionPanelBuilder()); modeController.addINodeViewLifeCycleListener(new INodeViewLifeCycleListener() { public void onViewCreated(Container nodeView) { NodeView node = (NodeView) nodeView; final DropTarget dropTarget = new DropTarget(node.getMainView(), new MNodeDropListener() { public void drop(final DropTargetDropEvent dtde) { DropTargetDropEvent evt = dtde; if(dtde.getTransferable().isDataFlavorSupported(WorkspaceTransferable.WORKSPACE_NODE_FLAVOR)) { evt = new DropTargetDropEvent(dtde.getDropTargetContext(), dtde.getLocation(), dtde.getDropAction(), dtde.getSourceActions(), false); } super.drop(evt); } }); dropTarget.setActive(true); } public void onViewRemoved(Container nodeView) { } }); modeController.addMenuContributor(new IMenuContributor() { public void updateMenus(ModeController modeController, MenuBuilder builder) { final String MENU_PROJECT_KEY = "/menu_bar/project"; //insert project menu into main menu JMenu projectMenu = new JMenu(TextUtils.getText("menu.project.entry.label")); projectMenu.setMnemonic('o'); builder.addMenuItem("/menu_bar/format", projectMenu, MENU_PROJECT_KEY, MenuBuilder.AFTER); builder.addAction(MENU_PROJECT_KEY, WorkspaceController.getAction(WorkspaceNewProjectAction.KEY), MenuBuilder.AS_CHILD); builder.addAction(MENU_PROJECT_KEY, WorkspaceController.getAction(WorkspaceImportProjectAction.KEY), MenuBuilder.AS_CHILD); builder.addSeparator(MENU_PROJECT_KEY, MenuBuilder.AS_CHILD); final String MENU_PROJECT_ADD_KEY = builder.getMenuKey(MENU_PROJECT_KEY, "new"); final JMenu addMenu = new JMenu(TextUtils.getText("workspace.action.new.label")); builder.addMenuItem(MENU_PROJECT_KEY, addMenu, MENU_PROJECT_ADD_KEY, MenuBuilder.AS_CHILD); builder.addAction(MENU_PROJECT_ADD_KEY, new NodeNewFolderAction(), MenuBuilder.AS_CHILD); builder.addAction(MENU_PROJECT_ADD_KEY, new NodeNewLinkAction(), MenuBuilder.AS_CHILD); final WorkspaceRemoveProjectAction rmProjectAction = new WorkspaceRemoveProjectAction(); builder.addAction(MENU_PROJECT_KEY, rmProjectAction, MenuBuilder.AS_CHILD); builder.addSeparator(MENU_PROJECT_KEY, MenuBuilder.AS_CHILD); setDefaultAccelerator(builder.getShortcutKey(builder.getMenuKey(MENU_PROJECT_KEY,WorkspaceProjectOpenLocationAction.KEY)), "control alt L"); final WorkspaceProjectOpenLocationAction openLocAction = new WorkspaceProjectOpenLocationAction(); builder.addAction(MENU_PROJECT_KEY, openLocAction, MenuBuilder.AS_CHILD); projectMenu.getPopupMenu().addPopupMenuListener(new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent e) { rmProjectAction.setEnabled(); openLocAction.setEnabled(); if(WorkspaceController.getSelectedProject() == null) { addMenu.setEnabled(false); } else { addMenu.setEnabled(true); } } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {} public void popupMenuCanceled(PopupMenuEvent e) {} }); } private void setDefaultAccelerator(final String shortcutKey, String accelerator) { if (accelerator != null) { if (null == ResourceController.getResourceController().getProperty(shortcutKey, null)) { if (Compat.isMacOsX()) { accelerator = accelerator.replaceFirst("CONTROL", "META").replaceFirst("control", "meta"); } ResourceController.getResourceController().setDefaultProperty(shortcutKey, accelerator); } } } }); //RIBBONS - workspace final RibbonMapChangeAdapter adapter = modeController.getUserInputListenerFactory().getRibbonBuilder().getMapChangeAdapter(); getView().addProjectSelectionListener(getWSSelectionListener(adapter)); getView().getNodeSelectionHandler().add(new IWorkspaceNodeSelectionListener() { public void selectionChanged(TreeSelectionEvent event) { adapter.selectionChanged(event); } }); modeController.getUserInputListenerFactory().getRibbonBuilder().registerContributorFactory("project_band_main", new WorkspaceProjectBandContributorFactory(this)); File file = new File(Compat.getApplicationUserDirectory(), "workspace_ribbon.xml"); if (file.exists()) { LogUtils.info("using alternative ribbon configuration file: "+file.getAbsolutePath()); try { modeController.getUserInputListenerFactory().getRibbonBuilder().updateRibbon(file.toURI().toURL()); } catch (MalformedURLException e) { LogUtils.severe("MModeControllerFactory.createStandardControllers(): "+e.getMessage()); } } else { modeController.getUserInputListenerFactory().getRibbonBuilder().updateRibbon(MModeWorkspaceController.class.getResource("/xml/ribbons.xml")); } } // private void setupSettings(ModeController modeController) { // loadSettings(getSettingsPath()); // } private void setupModel(ModeController modeController) { load(); } private void setupView(ModeController modeController) { FileSystemManager.setDirectoryConflictHandler(new DirectoryMergeConflictDialog()); FileSystemManager.setFileConflictHandler(new FileExistsConflictDialog()); final OneTouchCollapseResizer otcr = new OneTouchCollapseResizer(Direction.LEFT, CollapseDirection.COLLAPSE_LEFT); otcr.addCollapseListener(getWorkspaceView()); ResizerEventAdapter adapter = new ResizerEventAdapter() { public void componentResized(ResizeEvent event) { if(event.getComponent().equals(getView())) { getWorkspaceSettings().setProperty(WorkspaceSettings.WORKSPACE_VIEW_WIDTH, String.valueOf(((JComponent) event.getComponent()).getPreferredSize().width)); } } public void componentCollapsed(ResizeEvent event) { if(event.getComponent().equals(getView())) { getWorkspaceSettings().setProperty(WorkspaceSettings.WORKSPACE_VIEW_COLLAPSED, "true"); } } public void componentExpanded(ResizeEvent event) { if(event.getComponent().equals(getView())) { getWorkspaceSettings().setProperty(WorkspaceSettings.WORKSPACE_VIEW_COLLAPSED, "false"); } } }; otcr.addResizerListener(adapter); otcr.addCollapseListener(adapter); Box resizableTools = Box.createHorizontalBox(); resizableTools.add(getWorkspaceView()); this.viewUpdater = new Runnable() { public void run() { boolean expanded = true; try { int width = Integer.parseInt(getWorkspaceSettings().getProperty(WorkspaceSettings.WORKSPACE_VIEW_WIDTH, "250")); getWorkspaceView().setPreferredSize(new Dimension(width, 100)); } catch (Exception e) { // blindly accept } try { expanded = !Boolean.parseBoolean(getWorkspaceSettings().getProperty(WorkspaceSettings.WORKSPACE_VIEW_COLLAPSED, "false")); } catch (Exception e) { // default is set true -> ignore exception } otcr.setExpanded(expanded); } }; resizableTools.add(otcr); modeController.getUserInputListenerFactory().addToolBar("workspace", ViewController.LEFT, resizableTools); this.viewUpdater.run(); getWorkspaceView().setModel(getModel()); getView().expandPath(getModel().getRoot().getTreePath()); for(AWorkspaceProject project : getModel().getProjects()) { getView().expandPath(project.getModel().getRoot().getTreePath()); } getView().getNodeTypeIconManager().addNodeTypeIconHandler(LinkTypeFileNode.class, new LinkTypeFileIconHandler()); getView().getNodeTypeIconManager().addNodeTypeIconHandler(DefaultFileNode.class, new DefaultFileNodeIconHandler()); getView().refreshView(); } private void setupActions(ModeController modeController) { ResourceController res = ResourceController.getResourceController(); res.setDefaultProperty(WorkspaceRemoveProjectAction.KEY+".icon", "/images/docear/project/Project-RemoveProject.png"); res.setDefaultProperty(ProjectOpenLocationAction.KEY+".icon", "/images/docear/project/Project-OpenLocation.png"); res.setDefaultProperty(ProjectRenameAction.KEY+".icon", "/images/docear/project/Project-Rename.png"); WorkspaceController.addAction(new WorkspaceExpandAction()); WorkspaceController.addAction(new WorkspaceCollapseAction()); WorkspaceController.addAction(new WorkspaceNewProjectAction()); WorkspaceController.addAction(new WorkspaceImportProjectAction()); WorkspaceController.addAction(new NodeNewFolderAction()); WorkspaceController.addAction(new NodeNewLinkAction()); WorkspaceController.addAction(new NodeOpenLocationAction()); //WORKSPACE - fixed: #332 WorkspaceController.addAction(new NodeCutAction()); WorkspaceController.addAction(new NodeCopyAction()); WorkspaceController.addAction(new NodePasteAction()); WorkspaceController.addAction(new NodeRenameAction()); WorkspaceController.addAction(new NodeRemoveAction()); WorkspaceController.addAction(new NodeRefreshAction()); WorkspaceController.addAction(new WorkspaceRemoveProjectAction()); WorkspaceController.addAction(new ProjectOpenLocationAction()); WorkspaceController.addAction(new ProjectRenameAction()); WorkspaceController.replaceAction(new WorkspaceNewMapAction()); WorkspaceController.addAction(new FileNodeNewMindmapAction()); WorkspaceController.addAction(new FileNodeNewFileAction()); WorkspaceController.addAction(new FileNodeDeleteAction()); WorkspaceController.addAction(new PhysicalFolderSortOrderAction()); } private IProjectSelectionListener getWSSelectionListener(final RibbonMapChangeAdapter mapChangeAdapter) { if(projectSelectionListener == null) { projectSelectionListener = new IProjectSelectionListener() { public void selectionChanged(ProjectSelectionEvent evt) { if(mapChangeAdapter != null) { mapChangeAdapter.selectionChanged(evt.getSelectedProject()); } selectedProject = evt.getSelectedProject(); } }; } return projectSelectionListener; } private void saveSettings() { // clear old settings String[] projectsIds = getWorkspaceSettings().getProperty(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS, "").split(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS_SEPARATOR); for (String projectID : projectsIds) { getWorkspaceSettings().removeProperty(projectID); } // build new project stack List<String> projectIDs = new ArrayList<String>(); synchronized (getModel().getProjects()) { for(AWorkspaceProject project : getModel().getProjects()) { saveProject(project); if(projectIDs.contains(project.getProjectID())) { continue; } projectIDs.add(project.getProjectID()); getWorkspaceSettings().setProperty(project.getProjectID(), project.getProjectHome().toString()); } } StringBuilder sb = new StringBuilder(); for (String prjId : projectIDs) { if(sb.length()>0) { sb.append(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS_SEPARATOR); } sb.append(prjId); } getWorkspaceSettings().setProperty(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS, sb.toString()); try { getWorkspaceSettings().store(); } catch (final Exception ex) { LogUtils.severe("could not store workspace settings.", ex); } } private void saveProject(AWorkspaceProject project) { try { getProjectLoader().storeProject(project); } catch (IOException e) { LogUtils.severe(e); } } private TreeView getWorkspaceView() { if (this.view == null) { this.view = new TreeView(); this.view.setMinimumSize(new Dimension(100, 100)); this.view.setPreferredSize(new Dimension(150, 100)); this.view.addProjectSelectionListener(getProjectSelectionListener()); getModel(); } return this.view; } public IWorkspaceSettingsHandler getWorkspaceSettings() { if(settings == null) { settings = new WorkspaceSettings(); try { settings.load(); } catch (IOException e) { LogUtils.info("Workspace settings not loaded: "+e.getMessage()); } } return settings; } public void setWorkspaceSettings(IWorkspaceSettingsHandler settings) { this.settings = settings; } public WorkspaceModel getModel() { if(wsModel == null) { wsModel = WorkspaceModel.createDefaultModel(); setModel(wsModel); } return wsModel; } public void setModel(WorkspaceModel model) { wsModel = model; if(wsModel != null) { wsModel.addWorldModelListener(new WorkspaceModelListener() { public void treeStructureChanged(TreeModelEvent arg0) {} public void treeNodesRemoved(TreeModelEvent arg0) {} public void treeNodesInserted(TreeModelEvent arg0) {} public void treeNodesChanged(TreeModelEvent arg0) {} public void projectRemoved(WorkspaceModelEvent event) { if(event.getProject().equals(getSelectedProject())) { selectedProject = null; final RibbonMapChangeAdapter adapter = Controller.getCurrentModeController().getUserInputListenerFactory().getRibbonBuilder().getMapChangeAdapter(); adapter.selectionChanged(selectedProject); } } public void projectAdded(WorkspaceModelEvent event) {} }); } } @Override public IWorkspaceView getView() { return getWorkspaceView(); } public FileReadManager getFileTypeManager() { if (this.fileTypeManager == null) { this.fileTypeManager = new FileReadManager(); Properties props = new Properties(); try { props.load(this.getClass().getResourceAsStream("/conf/filenodetypes.properties")); Class<?>[] args = {}; for (Object key : props.keySet()) { try { Class<?> clazz = DefaultFileNodeCreator.class; clazz = this.getClass().getClassLoader().loadClass(key.toString()); AFileNodeCreator handler = (AFileNodeCreator) clazz.getConstructor(args).newInstance(); handler.setFileTypeList(props.getProperty(key.toString(), ""), "\\|"); this.fileTypeManager.addFileHandler(handler); } catch (ClassNotFoundException e) { LogUtils.warn("Class not found [" + key + "]", e); } catch (ClassCastException e) { LogUtils.warn("Class [" + key + "] is not of type: PhysicalNode", e); } catch (Exception e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } return this.fileTypeManager; } public URI getDefaultProjectHome() { File home = URIUtils.getAbsoluteFile(WorkspaceController.getApplicationHome()); home = new File(home, "projects"); return home.toURI(); } public void shutdown() { save(); } private IProjectSelectionListener getProjectSelectionListener() { if(this.projectSelectionListener == null) { this.projectSelectionListener = new IProjectSelectionListener() { public void selectionChanged(ProjectSelectionEvent event) { selectedProject = event.getSelectedProject(); } }; } return this.projectSelectionListener; } @Override public AWorkspaceProject getSelectedProject() { return selectedProject; } @Override public void save() { saveSettings(); } @Override public void load() { clear(); if(this.viewUpdater != null) { this.viewUpdater.run(); } String[] projectsIds = getWorkspaceSettings().getProperty(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS, "").split(WorkspaceSettings.WORKSPACE_MODEL_PROJECTS_SEPARATOR); for (final String projectID : projectsIds) { final String projectHome = getWorkspaceSettings().getProperty(projectID); if(projectHome == null) { continue; } try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { AWorkspaceProject project = null; try { project = AWorkspaceProject.create(projectID, URIUtils.createURI(projectHome)); getModel().addProject(project); getProjectLoader().loadProject(project); } catch (Exception e) { LogUtils.severe(e); if(project != null) { getModel().removeProject(project); } } } }); } catch (Exception e) { LogUtils.severe(e); } } getWorkspaceView().repaint(); } @Override public void clear() { getView().setPaintingEnabled(false); AWorkspaceProject[] projects = getModel().getProjects().toArray(new AWorkspaceProject[0]); for (AWorkspaceProject project : projects) { getModel().removeProject(project); } getView().setPaintingEnabled(true); } }