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; } }