package pipe.views;
import pipe.actions.gui.PipeApplicationModel;
import pipe.actions.gui.ZoomManager;
import pipe.constants.GUIConstants;
import pipe.controllers.PetriNetController;
import pipe.controllers.SelectionManager;
import pipe.controllers.application.PipeApplicationController;
import pipe.gui.*;
import pipe.handlers.PetriNetMouseHandler;
import pipe.utilities.gui.GuiUtils;
import uk.ac.imperial.pipe.exceptions.PetriNetComponentNotFoundException;
import uk.ac.imperial.pipe.models.manager.PetriNetManagerImpl;
import uk.ac.imperial.pipe.models.petrinet.PetriNet;
import uk.ac.imperial.pipe.models.petrinet.Token;
import uk.ac.imperial.pipe.models.petrinet.name.PetriNetName;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Main class application view
*/
@SuppressWarnings("serial")
public class PipeApplicationView extends JFrame implements ActionListener, Observer {
/**
* Class logger
*/
private static final Logger LOGGER = Logger.getLogger(PipeApplicationView.class.getName());
/**
* Status bar for useful messages
*/
public final StatusBar statusBar;
/**
* Zoom managager
*/
private final ZoomManager zoomManager;
private final JSplitPane moduleAndAnimationHistoryFrame;
private final JTabbedPane frameForPetriNetTabs = new JTabbedPane();
private final List<PetriNetTab> petriNetTabs = new ArrayList<>();
private final PipeApplicationController applicationController;
private final PipeApplicationModel applicationModel;
public JComboBox<String> zoomComboBox;
public JComboBox<String> tokenClassComboBox;
private UndoableEditListener undoListener;
private JScrollPane scroller;
private List<JLayer<JComponent>> wrappedPetrinetTabs = new ArrayList<>();
private Map<PetriNetTab, AnimationHistoryView> histories = new HashMap<>();
public PipeApplicationView(ZoomManager zoomManager, final PipeApplicationController applicationController,
PipeApplicationModel applicationModel) {
this.zoomManager = zoomManager;
this.applicationModel = applicationModel;
this.applicationController = applicationController;
applicationController.registerToManager(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(PetriNetManagerImpl.NEW_PETRI_NET_MESSAGE)) {
PetriNet petriNet = (PetriNet) evt.getNewValue();
registerNewPetriNet(petriNet);
} else if (evt.getPropertyName().equals(PetriNetManagerImpl.REMOVE_PETRI_NET_MESSAGE)) {
removeCurrentTab();
}
}
});
applicationModel.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(PipeApplicationModel.TOGGLE_ANIMATION_MODE)) {
boolean oldMode = (boolean) evt.getOldValue();
boolean newMode = (boolean) evt.getNewValue();
if (oldMode != newMode) {
setAnimationMode(newMode);
}
} else if (evt.getPropertyName().equals(PipeApplicationModel.TYPE_ACTION_CHANGE_MESSAGE)) {
PetriNetTab petriNetTab = getCurrentTab();
if (petriNetTab != null) {
petriNetTab.setCursorType("crosshair");
SelectionManager selectionManager =
applicationController.getActivePetriNetController().getSelectionManager();
selectionManager.disableSelection();
}
}
}
});
setTitle(null);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (IllegalAccessException | ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException e) {
LOGGER.log(Level.SEVERE, e.getMessage());
}
this.setIconImage(new ImageIcon(getImageURL("icon")).getImage());
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setSize(screenSize.width * 80 / 100, screenSize.height * 80 / 100);
this.setLocationRelativeTo(null);
// Status bar...
statusBar = new StatusBar();
getContentPane().add(statusBar, BorderLayout.PAGE_END);
this.setForeground(java.awt.Color.BLACK);
this.setBackground(java.awt.Color.WHITE);
ModuleManager moduleManager = new ModuleManager(this, applicationController);
JTree moduleTree = moduleManager.getModuleTree();
moduleAndAnimationHistoryFrame = new JSplitPane(JSplitPane.VERTICAL_SPLIT, moduleTree, null);
moduleAndAnimationHistoryFrame.setContinuousLayout(true);
moduleAndAnimationHistoryFrame.setDividerSize(0);
JSplitPane pane =
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, moduleAndAnimationHistoryFrame, frameForPetriNetTabs);
pane.setContinuousLayout(true);
pane.setOneTouchExpandable(true);
// avoid multiple borders
pane.setBorder(null);
pane.setDividerSize(8);
getContentPane().add(pane);
setVisible(true);
applicationModel.setMode(GUIConstants.SELECT);
//TODO: DO YOU NEED TO DO THIS?
// selectAction.actionPerformed(null);
setTabChangeListener();
setZoomChangeListener();
}
public void setUndoListener(UndoableEditListener listener) {
undoListener = listener;
}
@Override
public final void setTitle(String title) {
String name = applicationModel.getName();
super.setTitle((title == null) ? name : name + ": " + title);
}
// set tabbed pane properties and add change listener that updates tab with
// linked model and view
private void setTabChangeListener() {
frameForPetriNetTabs.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
PetriNetTab petriNetTab = getCurrentTab();
applicationController.setActiveTab(petriNetTab);
if (areAnyTabsDisplayed()) {
PetriNetController controller = applicationController.getActivePetriNetController();
if (controller.isCopyInProgress()) {
controller.cancelPaste();
}
petriNetTab.setVisible(true);
petriNetTab.repaint();
updateZoomCombo();
setTitle(petriNetTab.getName());
applicationModel.setInAnimationMode(controller.isInAnimationMode());
}
refreshTokenClassChoices();
}
});
}
public void setTabChangeListener(ChangeListener listener) {
frameForPetriNetTabs.addChangeListener(listener);
}
public PetriNetTab getCurrentTab() {
int index = frameForPetriNetTabs.getSelectedIndex();
return getTab(index);
}
PetriNetTab getTab(int index) {
if (index < 0 || index >= petriNetTabs.size()) {
return null;
}
return petriNetTabs.get(index);
}
/**
* Refreshes the combo box that presents the Tokens available for use.
* If there are no Petri nets being displayed this clears it
*/
public void refreshTokenClassChoices() {
if (areAnyTabsDisplayed()) {
String[] tokenClassChoices = buildTokenClassChoices();
ComboBoxModel<String> model = new DefaultComboBoxModel<>(tokenClassChoices);
tokenClassComboBox.setModel(model);
if (tokenClassChoices.length > 0) {
try {
PetriNetController controller = applicationController.getActivePetriNetController();
controller.selectToken(getSelectedTokenName());
} catch (PetriNetComponentNotFoundException petriNetComponentNotFoundException) {
GuiUtils.displayErrorMessage(this, petriNetComponentNotFoundException.getMessage());
}
}
} else {
tokenClassComboBox.setModel(new DefaultComboBoxModel<String>());
}
}
public String getSelectedTokenName() {
ComboBoxModel<String> model = tokenClassComboBox.getModel();
Object selected = model.getSelectedItem();
return selected.toString();
}
/**
* @return names of Tokens for the combo box
*/
protected String[] buildTokenClassChoices() {
if (areAnyTabsDisplayed()) {
PetriNetController petriNetController = applicationController.getActivePetriNetController();
Collection<Token> tokens = petriNetController.getNetTokens();
String[] tokenClassChoices = new String[tokens.size()];
int index = 0;
for (Token token : tokens) {
tokenClassChoices[index] = token.getId();
index++;
}
return tokenClassChoices;
}
return new String[0];
}
/**
* @return true if any tabs are displayed
*/
public boolean areAnyTabsDisplayed() {
return applicationController.getActivePetriNetController() != null;
}
/**
* Remove the listener from the zoomComboBox, so that when
* the box's selected item is updated to keep track of ZoomActions
* called from other sources, a duplicate ZoomAction is not called
*/
public void updateZoomCombo() {
ActionListener zoomComboListener = zoomComboBox.getActionListeners()[0];
zoomComboBox.removeActionListener(zoomComboListener);
String zoomPercentage = zoomManager.getPercentageZoom() + "%";
zoomComboBox.setSelectedItem(zoomPercentage);
zoomComboBox.addActionListener(zoomComboListener);
}
public void setAnimationMode(boolean animateMode) {
if (animateMode) {
statusBar.changeText(statusBar.TEXT_FOR_ANIMATION);
createAnimationViewPane();
} else {
statusBar.changeText(statusBar.TEXT_FOR_DRAWING);
removeAnimationViewPlane();
}
}
void removeAnimationViewPlane() {
if (scroller != null) {
moduleAndAnimationHistoryFrame.remove(scroller);
moduleAndAnimationHistoryFrame.setDividerLocation(0);
moduleAndAnimationHistoryFrame.setDividerSize(0);
}
}
/**
* Creates a new currentAnimationView text area, and returns a reference to it
*/
private void createAnimationViewPane() {
AnimationHistoryView animationHistoryView = histories.get(getCurrentTab());
scroller = new JScrollPane(animationHistoryView);
scroller.setBorder(new EmptyBorder(0, 0, 0, 0));
moduleAndAnimationHistoryFrame.setBottomComponent(scroller);
moduleAndAnimationHistoryFrame.setDividerLocation(0.5);
moduleAndAnimationHistoryFrame.setDividerSize(8);
}
public void setToolBar(JToolBar toolBar) {
getContentPane().add(toolBar, BorderLayout.PAGE_START);
}
/**
* Creates and adds the token view combo box to the view
*
* @param toolBar the JToolBar to add the combo box to
* @param action the action that the tokenClassComboBox performs when selected
*/
protected void addTokenClassComboBox(JToolBar toolBar, Action action) {
String[] tokenClassChoices = new String[]{"Default"};
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>(tokenClassChoices);
tokenClassComboBox = new JComboBox<>(model);
tokenClassComboBox.setEditable(true);
tokenClassComboBox.setSelectedItem(tokenClassChoices[0]);
tokenClassComboBox.setMaximumRowCount(100);
// tokenClassComboBox.setMaximumSize(new Dimension(125, 100));
tokenClassComboBox.setEditable(false);
tokenClassComboBox.setAction(action);
toolBar.add(tokenClassComboBox);
}
/**
* Sets pipes menu
*
* @param menu for PIPE
*/
public void setMenu(JMenuBar menu) {
setJMenuBar(menu);
}
private void setZoomChangeListener() {
zoomManager.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
getTabComponent().repaint();
updateZoomCombo();
}
});
}
private JComponent getTabComponent() {
return wrappedPetrinetTabs.get(frameForPetriNetTabs.getSelectedIndex());
}
/**
* Sets the default behaviour for exit for both Windows/Linux/Mac OS X
* @param adapter for exit action
*/
public void setExitAction(WindowListener adapter) {
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
addWindowListener(adapter);
}
/**
* Displays contributors
*/
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(this, "PIPE: Platform Independent Petri Net Ediror\n\n" + "Authors:\n" +
"2003: Jamie Bloom, Clare Clark, Camilla Clifford, Alex Duncan, Haroun Khan and Manos Papantoniou\n"
+
"2004: Tom Barnwell, Michael Camacho, Matthew Cook, Maxim Gready, Peter Kyme and Michail Tsouchlaris\n"
+
"2005: Nadeem Akharware\n" + "????: Tim Kimber, Ben Kirby, Thomas Master, Matthew Worthington\n"
+
"????: Pere Bonet Bonet (Universitat de les Illes Balears)\n" +
"????: Marc Meli\u00E0 Aguil\u00F3 (Universitat de les Illes Balears)\n" +
"2010: Alex Charalambous (Imperial College London)\n" +
"2011: Jan Vlasak (Imperial College London)\n\n" + "http://pipe2.sourceforge.net/",
"About PIPE", JOptionPane.INFORMATION_MESSAGE
);
}
//TODO: Find out if this actually ever gets called
@Override
public void update(Observable o, Object obj) {
}
/**
* Adds the tab to the main application view in the tabbed view frame
*
* @param name name of tab
* @param tab tab to add
*/
//TODO: ADD ZOOMING
public void addNewTab(String name, PetriNetTab tab) {
JScrollPane tabScroller = new JScrollPane(tab, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
tabScroller.setBorder(new BevelBorder(BevelBorder.LOWERED));
// JLayer<JComponent> jLayer = new JLayer<>(tab, zoomUI);
// wrappedPetrinetTabs.add(jLayer);
petriNetTabs.add(tab);
frameForPetriNetTabs.addTab(name, tabScroller);
frameForPetriNetTabs.setSelectedIndex(petriNetTabs.size() - 1);
}
public File getFile() {
PetriNetTab petriNetTab = petriNetTabs.get(frameForPetriNetTabs.getSelectedIndex());
return petriNetTab.appFile;
}
public void removeCurrentTab() {
removeTab(frameForPetriNetTabs.getSelectedIndex());
}
public void removeTab(int index) {
if (frameForPetriNetTabs.getTabCount() > 0) {
petriNetTabs.remove(index);
if (index > 0) {
applicationController.setActiveTab(petriNetTabs.get(index - 1));
} else {
applicationController.setActiveTab(null);
}
frameForPetriNetTabs.remove(index);
}
}
public void updateSelectedTabName(String title) {
int index = frameForPetriNetTabs.getSelectedIndex();
frameForPetriNetTabs.setTitleAt(index, title);
}
public void registerNewPetriNet(PetriNet petriNet) {
petriNet.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String msg = evt.getPropertyName();
if (msg.equals(PetriNet.PETRI_NET_NAME_CHANGE_MESSAGE)) {
PetriNetName name = (PetriNetName) evt.getNewValue();
updateSelectedTabName(name.getName());
} else if (msg.equals(PetriNet.NEW_TOKEN_CHANGE_MESSAGE) || msg.equals(
PetriNet.DELETE_TOKEN_CHANGE_MESSAGE)) {
refreshTokenClassChoices();
}
}
});
AnimationHistoryView animationHistoryView;
try {
animationHistoryView = new AnimationHistoryView("Animation History");
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
PetriNetTab petriNetTab = new PetriNetTab();
histories.put(petriNetTab, animationHistoryView);
PropertyChangeListener zoomListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
updateZoomCombo();
}
};
applicationController.registerTab(petriNet, petriNetTab, animationHistoryView, undoListener, zoomListener);
PetriNetController petriNetController = applicationController.getActivePetriNetController();
petriNetTab.setMouseHandler(
new PetriNetMouseHandler(applicationModel, petriNetController, petriNetTab));
petriNetTab.updatePreferredSize();
addNewTab(petriNet.getNameValue(), petriNetTab);
}
private URL getImageURL(String name) {
PipeResourceLocator locator = new PipeResourceLocator();
return locator.getImage(name);
}
public void register(JComboBox<String> tokenClassComboBox) {
this.tokenClassComboBox = tokenClassComboBox;
}
public void registerZoom(JComboBox<String> zoomComboBox) {
this.zoomComboBox = zoomComboBox;
}
}