/******************************************************************************* * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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. ******************************************************************************/ package hr.fer.zemris.vhdllab.view.explorer; import hr.fer.zemris.vhdllab.applets.texteditor.ViewVhdlEditorMetadata; import hr.fer.zemris.vhdllab.entity.File; import hr.fer.zemris.vhdllab.entity.FileType; import hr.fer.zemris.vhdllab.entity.Project; import hr.fer.zemris.vhdllab.platform.manager.editor.EditorIdentifier; import hr.fer.zemris.vhdllab.platform.manager.editor.EditorManagerFactory; import hr.fer.zemris.vhdllab.platform.manager.simulation.SimulationManager; import hr.fer.zemris.vhdllab.platform.manager.workspace.IdentifierToInfoObjectMapper; import hr.fer.zemris.vhdllab.platform.manager.workspace.WorkspaceListener; import hr.fer.zemris.vhdllab.platform.manager.workspace.WorkspaceManager; import hr.fer.zemris.vhdllab.platform.manager.workspace.support.WorkspaceInitializationListener; import hr.fer.zemris.vhdllab.platform.ui.MouseClickAdapter; import hr.fer.zemris.vhdllab.service.MetadataExtractionService; import hr.fer.zemris.vhdllab.service.hierarchy.Hierarchy; import hr.fer.zemris.vhdllab.service.hierarchy.HierarchyNode; import hr.fer.zemris.vhdllab.service.result.Result; import hr.fer.zemris.vhdllab.service.workspace.FileReport; import hr.fer.zemris.vhdllab.service.workspace.Workspace; import hr.fer.zemris.vhdllab.util.BeanUtil; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import java.util.Map.Entry; import java.util.prefs.Preferences; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.apache.commons.collections.IteratorUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.lf5.viewer.categoryexplorer.TreeModelAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.richclient.application.PageComponentContext; import org.springframework.richclient.application.support.AbstractView; import org.springframework.richclient.command.ActionCommand; import org.springframework.richclient.command.CommandGroup; import org.springframework.richclient.command.CommandManager; import org.springframework.richclient.command.support.GlobalCommandIds; import org.springframework.richclient.dialog.ConfirmationDialog; import org.springframework.richclient.tree.FocusableTreeCellRenderer; public class ProjectExplorerView extends AbstractView implements WorkspaceInitializationListener, WorkspaceListener { private static final String PREFERENCES_HIERARCHY_MODE = "project.explorere.hierarchy.mode"; public static final int HIERARCHY_X_USES_Y = 0; public static final int HIERARCHY_X_USED_BY_Y = 1; public static final int HIERARCHY_FLAT = 2; protected final ActionCommand openCommand = new OpenCommand(); protected final ActionCommand deleteCommand = new DeleteCommand(); protected final ActionCommand compileCommand = new CompileCommand(); protected final ActionCommand simulateCommand = new SimulateCommand(); protected final ActionCommand viewVhdlCommand = new ViewVhdlCommand(); protected final ActionCommand hierarchyXUsesYCommand = new HierarchyXUsesYCommand(); protected final ActionCommand hierarchyXUsedByYCommand = new HierarchyXUsedByYCommand(); protected final ActionCommand hierarchyFlatCommand = new HierarchyFlatCommand(); @Autowired private IdentifierToInfoObjectMapper mapper; @Autowired protected WorkspaceManager workspaceManager; @Autowired protected SimulationManager simulationManager; @Autowired protected MetadataExtractionService metadataExtractionService; @Autowired protected EditorManagerFactory editorManagerFactory; private DefaultMutableTreeNode root; private DefaultTreeModel model; protected JTree tree; protected JPopupMenu popupMenu; private Integer hierarchyMode; @Override protected JComponent createControl() { CommandManager commandManager = getActiveWindow().getCommandManager(); commandManager.createCommandGroup("hierarchyMenu", new String[] { hierarchyXUsesYCommand.getId(), hierarchyXUsedByYCommand.getId(), hierarchyFlatCommand.getId() }); CommandGroup commandGroup = commandManager.createCommandGroup( "treePopupMenu", new String[] { "newMenu", "separator", openCommand.getId(), "separator", compileCommand.getId(), simulateCommand.getId(), viewVhdlCommand.getId(), "separator", deleteCommand.getId(), "separator", "hierarchyMenu" }); popupMenu = commandGroup.createPopupMenu(); root = new DefaultMutableTreeNode("vhdllab-root"); model = new DefaultTreeModel(root); model.addTreeModelListener(new TreeModelAdapter() { @Override public void treeNodesInserted(TreeModelEvent e) { Object[] children = e.getChildren(); if (children.length > 0) { Object child = children[children.length - 1]; TreePath path = e.getTreePath().pathByAddingChild(child); tree.setSelectionPath(path); } } }); tree = new JTree(model); tree.setRootVisible(false); tree.setShowsRootHandles(true); // tree is expanded manually in OpenFileOrShowPopupMenuListener tree.setToggleClickCount(0); tree.setCellRenderer(new WorkspaceCellRenderer()); tree.addMouseListener(new OpenFileOrShowPopupMenuListener()); tree.addTreeSelectionListener(new CommandGuard()); tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); JPanel hierarchyPanel = new JPanel(); hierarchyPanel.add(createHierarchyButton(hierarchyXUsesYCommand)); hierarchyPanel.add(createHierarchyButton(hierarchyXUsedByYCommand)); hierarchyPanel.add(createHierarchyButton(hierarchyFlatCommand)); JPanel borderHierarchyPanel = new JPanel(new BorderLayout()); borderHierarchyPanel.add(hierarchyPanel, BorderLayout.WEST); JPanel control = new JPanel(new BorderLayout()); control.add(borderHierarchyPanel, BorderLayout.NORTH); control.add(new JScrollPane(tree), BorderLayout.CENTER); return control; } private AbstractButton createHierarchyButton(ActionCommand command) { AbstractButton button = command.createButton(); button.setText(null); String iconKey = BeanUtil.beanName(command.getClass()) + ".bigicon"; button.setIcon(getIconSource().getIcon(iconKey)); return button; } @Override protected void registerLocalCommandExecutors(PageComponentContext context) { CommandManager commandManager = getActiveWindow().getCommandManager(); commandManager.registerCommand(openCommand); commandManager.registerCommand(compileCommand); commandManager.registerCommand(simulateCommand); commandManager.registerCommand(viewVhdlCommand); commandManager.registerCommand(deleteCommand); commandManager.registerCommand(hierarchyXUsesYCommand); commandManager.registerCommand(hierarchyXUsedByYCommand); commandManager.registerCommand(hierarchyFlatCommand); context.register(deleteCommand.getId(), deleteCommand); } @Override public void initialize(Workspace workspace) { for (Project project : workspace.getProjects()) { MutableTreeNode projectNode = addProject(project); Hierarchy hierarchy = workspace.getHierarchy(project); if (hierarchy != null) { addFiles(hierarchy, null, projectNode); } } model.reload(); } @Override public void projectCreated(Project project) { addProject(project); } @Override public void projectDeleted(Project project) { for (int i = 0; i < root.getChildCount(); i++) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) root .getChildAt(i); if (project.equals(node.getUserObject())) { root.remove(i); model.nodesWereRemoved(root, new int[] { i }, new Object[] { node }); break; } } } private void updateHierarchy(DefaultMutableTreeNode parentNode, Hierarchy hierarchy, HierarchyNode hierarchyNode) { Map<Object, DefaultMutableTreeNode> map = new HashMap<Object, DefaultMutableTreeNode>( parentNode.getChildCount()); for (int i = 0; i < parentNode.getChildCount(); i++) { DefaultMutableTreeNode c = (DefaultMutableTreeNode) parentNode .getChildAt(i); map.put(c.getUserObject(), c); } Iterator<HierarchyNode> iterator = getHierarchyIterator(hierarchy, hierarchyNode); while (iterator.hasNext()) { HierarchyNode next = iterator.next(); File file = next.getFile(); DefaultMutableTreeNode nextParentNode; if (map.containsKey(file)) { nextParentNode = map.remove(file); } else { nextParentNode = (DefaultMutableTreeNode) insertNode( parentNode, file); } updateHierarchy(nextParentNode, hierarchy, next); } // model.nodesWereRemoved expected ordered indices Map<Integer, DefaultMutableTreeNode> sortedMap = new TreeMap<Integer, DefaultMutableTreeNode>(); for(DefaultMutableTreeNode n : map.values()) { int index = parentNode.getIndex(n); sortedMap.put(Integer.valueOf(index), n); } // construct arguments for model.nodesWereRemoved int[] childIndices = new int[sortedMap.size()]; Object[] removedChildren = new Object[sortedMap.size()]; int i = 0; for (Entry<Integer, DefaultMutableTreeNode> entry : sortedMap.entrySet()) { childIndices[i] = entry.getKey(); removedChildren[i] = entry.getValue(); i++; parentNode.remove(entry.getValue()); } model.nodesWereRemoved(parentNode, childIndices, removedChildren); } @Override public void fileCreated(FileReport report) { Hierarchy hierarchy = report.getHierarchy(); DefaultMutableTreeNode treeNode = getNodeFor(hierarchy.getProject()); updateHierarchy(treeNode, hierarchy, null); } @Override public void fileSaved(FileReport report) { fileCreated(report); } @Override public void fileDeleted(FileReport report) { fileCreated(report); } protected void refereshProjectExplorer() { root.removeAllChildren(); initialize(workspaceManager.getWorkspace()); } public Integer getHierarchyMode() { if (hierarchyMode == null) { Preferences preferences = Preferences .userNodeForPackage(ProjectExplorerView.class); hierarchyMode = preferences.getInt(PREFERENCES_HIERARCHY_MODE, HIERARCHY_X_USES_Y); } if (hierarchyMode != HIERARCHY_X_USES_Y && hierarchyMode != HIERARCHY_X_USED_BY_Y && hierarchyMode != HIERARCHY_FLAT) { hierarchyMode = HIERARCHY_X_USES_Y; } return hierarchyMode; } public void setHierarchyMode(Integer newHierarchyMode) { Integer oldHierarchyMode = this.hierarchyMode; this.hierarchyMode = newHierarchyMode; if (!ObjectUtils.equals(this.hierarchyMode, oldHierarchyMode)) { logger.debug("Hierarchy changed to: " + this.hierarchyMode); refereshProjectExplorer(); } } @SuppressWarnings("unchecked") private Iterator<HierarchyNode> getHierarchyIterator(Hierarchy hierarchy, HierarchyNode node) { Integer mode = getHierarchyMode(); switch (mode) { case 0: return hierarchy.iteratorXUsesYHierarchy(node); case 1: return hierarchy.iteratorXUsedByYHierarchy(node); case 2: if (node == null) { return hierarchy.iteratorFlatHierarchy(); } return IteratorUtils.emptyIterator(); default: throw new IllegalStateException(mode.toString()); } } private void addFiles(Hierarchy hierarchy, HierarchyNode hierarchyNode, MutableTreeNode parentNode) { Iterator<HierarchyNode> iterator = getHierarchyIterator(hierarchy, hierarchyNode); while (iterator.hasNext()) { HierarchyNode next = iterator.next(); MutableTreeNode parentFileNode = addFile(parentNode, next.getFile()); addFiles(hierarchy, next, parentFileNode); } } private MutableTreeNode addFile(MutableTreeNode projectNode, File file) { return insertNode(projectNode, file); } private MutableTreeNode addProject(Project project) { MutableTreeNode projectNode = insertNode(root, project); tree.requestFocusInWindow(); tree.setSelectionPath(new TreePath(new Object[] { root, projectNode })); return projectNode; } private MutableTreeNode insertNode(MutableTreeNode parentNode, Object childObject) { MutableTreeNode childNode = new DefaultMutableTreeNode(childObject); model.insertNodeInto(childNode, parentNode, parentNode.getChildCount()); return childNode; } protected Project getSelectedProject() { DefaultMutableTreeNode node = getLastSelectedNode(); return (Project) node.getUserObject(); } protected File getSelectedFile() { DefaultMutableTreeNode node = getLastSelectedNode(); File hierarchyFile = (File) node.getUserObject(); File loadedFile = mapper.getFile(hierarchyFile); loadedFile.setProject(getProjectForSelectedFile()); return loadedFile; } private DefaultMutableTreeNode getNodeFor(Project project) { for (int i = 0; i < root.getChildCount(); i++) { DefaultMutableTreeNode projectNode = (DefaultMutableTreeNode) root .getChildAt(i); if (project.equals(projectNode.getUserObject())) { return projectNode; } } return null; } private Project getProjectForSelectedFile() { DefaultMutableTreeNode node = getLastSelectedNode(); TreeNode[] path = node.getPath(); DefaultMutableTreeNode projectNode = (DefaultMutableTreeNode) path[1]; return (Project) projectNode.getUserObject(); } protected DefaultMutableTreeNode getLastSelectedNode() { TreePath path = tree.getSelectionPath(); if (path == null) { return null; } return (DefaultMutableTreeNode) path.getLastPathComponent(); } protected boolean lastSelectedNodeIsProject() { return isProject(getLastSelectedNode()); } protected boolean lastSelectedNodeIsFile() { return isFile(getLastSelectedNode()); } protected boolean isProject(DefaultMutableTreeNode node) { if (node == null) { return false; } return node.getUserObject() instanceof Project; } protected boolean isFile(DefaultMutableTreeNode node) { if (node == null) { return false; } return node.getUserObject() instanceof File; } public class OpenCommand extends ActionCommand { public OpenCommand() { super(BeanUtil.beanName(OpenCommand.class)); setEnabled(false); } @Override protected void doExecuteCommand() { File file = getSelectedFile(); logger.debug("Opening " + file); editorManagerFactory.get(file).open(); } } public class CompileCommand extends ActionCommand { public CompileCommand() { super(BeanUtil.beanName(CompileCommand.class)); setEnabled(false); } @Override protected void doExecuteCommand() { File file = getSelectedFile(); logger.debug("Compiling " + file); simulationManager.compile(file); } } public class SimulateCommand extends ActionCommand { public SimulateCommand() { super(BeanUtil.beanName(SimulateCommand.class)); setEnabled(false); } @Override protected void doExecuteCommand() { File file = getSelectedFile(); logger.debug("Simulating " + file); simulationManager.simulate(file); } } public class ViewVhdlCommand extends ActionCommand { public ViewVhdlCommand() { super(BeanUtil.beanName(ViewVhdlCommand.class)); setEnabled(false); } @Override protected void doExecuteCommand() { File file = getSelectedFile(); logger.debug("Opening view vhdl for " + file); Result result = metadataExtractionService.generateVhdl(file.getId()); String vhdl = getData(result); File viewVhdlFile = new File(file.getName() + ":vhdl", FileType.SOURCE, vhdl); viewVhdlFile.setProject(file.getProject()); editorManagerFactory.get( new EditorIdentifier(new ViewVhdlEditorMetadata(), viewVhdlFile)).open(); } private String getData(Result result) { String data = result.getData(); if (data == null) { data = "Errors:\n" + StringUtils.join(result.getMessages(), '\n'); } return data; } } public class DeleteCommand extends ActionCommand { private static final String DELETE_PROJECT_TITLE = "deleteProjectDialog.title"; private static final String DELETE_PROJECT_MESSAGE = "deleteProjectDialog.message"; private static final String DELETE_FILE_TITLE = "deleteFileDialog.title"; private static final String DELETE_FILE_MESSAGE = "deleteFileDialog.message"; private static final String DELETE_COMMAND_ID = "deleteResourceCommand"; public DeleteCommand() { super(GlobalCommandIds.DELETE); setEnabled(false); } @SuppressWarnings("synthetic-access") @Override protected void doExecuteCommand() { if (lastSelectedNodeIsProject()) { final Project project = getSelectedProject(); String title = getMessage(DELETE_PROJECT_TITLE); String message = getMessage(DELETE_PROJECT_MESSAGE, new Object[] { project.getName() }); new ConfirmationDialog(title, message) { @Override protected String getFinishCommandId() { return DELETE_COMMAND_ID; } @Override protected String getCancelCommandId() { return DEFAULT_CANCEL_COMMAND_ID; } @Override protected void onConfirm() { logger.debug("Deleting " + project); workspaceManager.delete(project); } }.showDialog(); } else if (lastSelectedNodeIsFile()) { final File file = getSelectedFile(); String title = getMessage(DELETE_FILE_TITLE); String message = getMessage(DELETE_FILE_MESSAGE, new Object[] { file.getName(), file.getProject().getName() }); new ConfirmationDialog(title, message) { @Override protected String getFinishCommandId() { return DELETE_COMMAND_ID; } @Override protected String getCancelCommandId() { return DEFAULT_CANCEL_COMMAND_ID; } @Override protected void onConfirm() { logger.debug("Deleting " + file); workspaceManager.delete(file); } }.showDialog(); } } } public class HierarchyXUsesYCommand extends ActionCommand { public HierarchyXUsesYCommand() { super(BeanUtil.beanName(HierarchyXUsesYCommand.class)); } @Override protected void doExecuteCommand() { setHierarchyMode(HIERARCHY_X_USES_Y); } } public class HierarchyXUsedByYCommand extends ActionCommand { public HierarchyXUsedByYCommand() { super(BeanUtil.beanName(HierarchyXUsedByYCommand.class)); } @Override protected void doExecuteCommand() { setHierarchyMode(HIERARCHY_X_USED_BY_Y); } } public class HierarchyFlatCommand extends ActionCommand { public HierarchyFlatCommand() { super(BeanUtil.beanName(HierarchyFlatCommand.class)); } @Override protected void doExecuteCommand() { setHierarchyMode(HIERARCHY_FLAT); } } protected class CommandGuard implements TreeSelectionListener { @Override public void valueChanged(TreeSelectionEvent e) { openCommand.setEnabled(lastSelectedNodeIsFile()); compileCommand.setEnabled(lastSelectedNodeIsFile() && getSelectedFileType().isCompilable()); simulateCommand.setEnabled(lastSelectedNodeIsFile() && getSelectedFileType().isSimulatable()); viewVhdlCommand.setEnabled(lastSelectedNodeIsFile() && getSelectedFileType().canViewVhdl()); deleteCommand.setEnabled(lastSelectedNodeIsProject() || (lastSelectedNodeIsFile() && !getSelectedFileType() .equals(FileType.PREDEFINED))); } private FileType getSelectedFileType() { return getSelectedFile().getType(); } } protected class OpenFileOrShowPopupMenuListener extends MouseClickAdapter { @SuppressWarnings("synthetic-access") @Override public void mouseClicked(MouseEvent e) { getActiveWindow().getPage().setActiveComponent( ProjectExplorerView.this); super.mouseClicked(e); } @Override protected void onDoubleClick(MouseEvent e) { if (openCommand.isEnabled()) { openCommand.execute(); } else { TreePath path = setSelectionPath(e); if (tree.isExpanded(path)) { tree.collapsePath(path); } else { tree.expandPath(path); } } } @Override protected void onRightMouseClick(MouseEvent e) { setSelectionPath(e); logger.debug("About to show project explorer popup menu"); popupMenu.show(tree, e.getX(), e.getY()); } private TreePath setSelectionPath(MouseEvent e) { TreePath path = tree.getClosestPathForLocation(e.getX(), e.getY()); tree.setSelectionPath(path); return path; } } protected class WorkspaceCellRenderer extends FocusableTreeCellRenderer { private static final long serialVersionUID = 1L; private static final String PROJECT_ICON = "project.icon"; private static final String SOURCE_ICON = "source.icon"; private static final String SCHEMA_ICON = "schema.icon"; private static final String AUTOMATON_ICON = "automaton.icon"; private static final String TESTBENCH_ICON = "testbench.icon"; private static final String PREDEFINED_ICON = "predefined.icon"; private static final String SIMULATION_ICON = "simulation.icon"; @SuppressWarnings("synthetic-access") @Override public Component getTreeCellRendererComponent( @SuppressWarnings("hiding") JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, @SuppressWarnings("hiding") boolean hasFocus) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; String stringValue; if (isProject(node)) { Project project = (Project) node.getUserObject(); stringValue = project.getName(); setIcon(getIconSource().getIcon(PROJECT_ICON)); } else if (isFile(node)) { File file = (File) node.getUserObject(); stringValue = file.getName(); switch (file.getType()) { case SOURCE: setIcon(getIconSource().getIcon(SOURCE_ICON)); break; case SCHEMA: setIcon(getIconSource().getIcon(SCHEMA_ICON)); break; case AUTOMATON: setIcon(getIconSource().getIcon(AUTOMATON_ICON)); break; case TESTBENCH: setIcon(getIconSource().getIcon(TESTBENCH_ICON)); break; case PREDEFINED: setIcon(getIconSource().getIcon(PREDEFINED_ICON)); break; case SIMULATION: setIcon(getIconSource().getIcon(SIMULATION_ICON)); break; default: throw new IllegalStateException(file.getType().toString()); } } else { stringValue = node.getUserObject().toString(); } return super.getTreeCellRendererComponent(tree, stringValue, sel, expanded, leaf, row, hasFocus); } @Override public void setIcon(Icon icon) { setLeafIcon(icon); setOpenIcon(icon); setClosedIcon(icon); super.setIcon(icon); } } }