
nl.tudelft.goal.SimpleIDE.FilePanel Maven / Gradle / Ivy
/**
* GOAL interpreter that facilitates developing and executing GOAL multi-agent
* programs. Copyright (C) 2011 K.V. Hindriks, W. Pasman
*
* This program 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.
*
* This program 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
* this program. If not, see .
*/
package nl.tudelft.goal.SimpleIDE;
import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import goal.tools.errorhandling.Resources;
import goal.tools.errorhandling.Warning;
import goal.tools.errorhandling.WarningStrings;
import goal.util.Observer;
import nl.tudelft.goal.SimpleIDE.actions.CloseAndRemoveAction;
import nl.tudelft.goal.SimpleIDE.actions.DeleteAction;
import nl.tudelft.goal.SimpleIDE.actions.EditAction;
import nl.tudelft.goal.SimpleIDE.actions.InfoAction;
import nl.tudelft.goal.SimpleIDE.actions.NewFileAction;
import nl.tudelft.goal.SimpleIDE.actions.OpenFileAction;
import nl.tudelft.goal.SimpleIDE.actions.QuitAction;
import nl.tudelft.goal.SimpleIDE.actions.ReloadFileAction;
import nl.tudelft.goal.SimpleIDE.actions.RenameAction;
import nl.tudelft.goal.SimpleIDE.actions.RunAction;
import nl.tudelft.goal.SimpleIDE.actions.SaveFileAction;
import nl.tudelft.goal.SimpleIDE.filenodes.AbstractFileNode;
import nl.tudelft.goal.SimpleIDE.filenodes.FileRootNode;
import nl.tudelft.goal.SimpleIDE.filenodes.NodeRenderer;
/**
* Panel that displays the contents of the {@link IdeFiles}.
*
* @author W.Pasman 25aug15
*
*/
public class FilePanel extends JPanel {
private static final long serialVersionUID = 1085401469943214372L;
/**
* The tree-visualizer for the files.
*/
private final JTree fileTree;
private final TreeModel model;
/**
*
* @param mainp the main panel, for centering dialogs.
* @param files the {@link IdeFiles}
*/
FilePanel(final JPanel mainp, final IdeFiles files) {
this.model = new DefaultTreeModel(new FileRootNode(files));
this.fileTree = new MyTree(this.model, files);
// add the various event listeners
addListeners();
// double-click should not toggle the tree
this.fileTree.setToggleClickCount(-1);
// set the layout
setLayout(new BorderLayout());
this.fileTree.setEditable(false);
this.fileTree.setRootVisible(false);
this.fileTree.setShowsRootHandles(true);
this.fileTree.setCellRenderer(new NodeRenderer());
// include tree view in pane
final JScrollPane fileTreeView = new JScrollPane(this.fileTree);
this.add(new JLabel("Files"), BorderLayout.NORTH); //$NON-NLS-1$
this.add(fileTreeView, BorderLayout.CENTER);
}
private JPopupMenu createPopupMenu() throws ReflectiveOperationException {
final JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem(ActionFactory.getAction(EditAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(SaveFileAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(ReloadFileAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(CloseAndRemoveAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(RenameAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(InfoAction.class)));
popup.add(new JSeparator());
popup.add(new JMenuItem(ActionFactory.getAction(OpenFileAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(NewFileAction.class)));
popup.add(new JMenuItem(ActionFactory.getAction(DeleteAction.class)));
popup.add(new JSeparator());
popup.add(new JMenuItem(ActionFactory.getAction(RunAction.class)));
popup.add(new JSeparator());
popup.add(new JMenuItem(ActionFactory.getAction(QuitAction.class)));
return popup;
}
/**
* Adds all relevant listeners to this FilePanel's tree.
*/
private void addListeners() {
// add mouse listener
this.fileTree.addMouseListener(new myMouseListener());
// add tree selection listener
this.fileTree.addTreeSelectionListener(e -> ActionFactory.broadcastStateChange());
// tree expansion listener
this.fileTree.addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(final TreeExpansionEvent event) {
// theIDE.refreshMenuItemsAndButtons();
ActionFactory.broadcastStateChange();
}
@Override
public void treeCollapsed(final TreeExpansionEvent event) {
// theIDE.refreshMenuItemsAndButtons();
ActionFactory.broadcastStateChange();
}
});
}
/**
* This object handles mouse clicks in the panel. Its jobs:
*
* - Create popup menu
*
- Handle double click to open editor.
*
* @author W.Pasman 18jul2011
*
*/
private class myMouseListener extends MouseAdapter {
/**
* Handles double click events on file nodes, in order to open editor panels for
* the file(s) that are selected. Single click events are handled by the tree
* selection listener.
*
* @see http ://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JTree.html for
* code.
*/
@Override
public void mousePressed(final MouseEvent event) {
if (event.isPopupTrigger()) {
try {
createPopupMenu().show(FilePanel.this.fileTree, event.getX(), event.getY());
} catch (final Exception e) {
new Warning(Resources.get(WarningStrings.FAILED_POPUP_WINDOW_CREATE), e).emit();
}
}
final TreePath selPath = FilePanel.this.fileTree.getPathForLocation(event.getX(), event.getY());
// path is null if nothing is selected
if (selPath == null) {
return;
}
final AbstractFileNode node = (AbstractFileNode) selPath.getLastPathComponent();
if (node != null && event.getClickCount() == 2) {
// user double clicked process node
try {
ActionFactory.getAction(EditAction.class).Execute(node, null);
} catch (final Exception e) {
new Warning(Resources.get(WarningStrings.FAILED_FILE_EDIT), e).emit();
}
}
}
@Override
public void mouseReleased(final MouseEvent event) {
if (event.isPopupTrigger()) {
try {
createPopupMenu().show(FilePanel.this.fileTree, event.getX(), event.getY());
} catch (final Exception e) {
new Warning(Resources.get(WarningStrings.FAILED_POPUP_WINDOW_CREATE), e).emit();
}
}
}
}
/**
* Get ALL selected {@link AbstractFileNode}s in the panel's tree, on
* top-to-bottom order. See #545. If nothing is selected, returns list with the
* root node only.
*
* @return list of all selected nodes.
*/
public List
getSelectedNodes() {
final TreePath[] paths = this.fileTree.getSelectionPaths();
final List nodes = new ArrayList<>();
if (paths == null) {
nodes.add((AbstractFileNode) this.model.getRoot());
} else {
for (final TreePath path : paths) {
nodes.add((AbstractFileNode) path.getLastPathComponent());
}
}
return nodes;
}
}
/**
* A JTree that re-loads the structure when a change occurs in the underlying
* model.
* Technical details
*
* The default JTree needs to be *exactly* informed by the model about changes.
* A model can issue a {@link TreeModelEvent} to the listeners (a.o. the JTree).
* However, this event can only pass the object that has changed (e.g. the
* removed or added file) and then JTree still has to figure out the
* consequences for the entire tree. This is pretty complex and the default
* JTree only collapses the entire tree in such events.
*
*
* @author W.Pasman 31aug15
*/
class MyTree extends JTree implements Observer {
private static final long serialVersionUID = -5366113509674951407L;
public MyTree(final TreeModel model, final IdeFiles files) {
super(model);
files.addObserver(this);
}
@Override
public void eventOccured(final IdeFiles source, final File evt) {
/**
* Computing what exactly happened is very complex. We would have to compare the
* current tree with the tree on the screen. Instead, it showed much easier to
* reload the entire model and re-open the nodes that were open before the
* reload.
*/
SwingUtilities.invokeLater(() -> {
final TreePath rootpath = new TreePath(getModel().getRoot());
final Enumeration oldState = getExpandedDescendants(rootpath);
((DefaultTreeModel) MyTree.this.treeModel).reload();
// re-open the old state
while (oldState.hasMoreElements()) {
setExpandedState(oldState.nextElement(), true);
}
});
}
}