/** * Copyright 1999-2009 The Pegadi Team * * 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 org.pegadi.storysketch; import org.apache.xerces.parsers.DOMParser; import org.apache.xml.serialize.DOMSerializer; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.SerializerFactory; import org.jgraph.JGraph; import org.jgraph.event.GraphModelEvent; import org.jgraph.event.GraphModelListener; import org.jgraph.event.GraphSelectionEvent; import org.jgraph.event.GraphSelectionListener; import org.jgraph.graph.*; import org.jgraph.plaf.basic.BasicGraphUI; import org.pegadi.storysketch.cells.*; import org.pegadi.storysketch.views.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import javax.swing.*; import javax.swing.event.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDropEvent; import java.awt.event.*; import java.awt.print.PageFormat; import java.awt.print.Paper; import java.awt.print.Printable; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.URL; import java.text.DateFormat; import java.util.*; public class StorySketchPanel extends JPanel implements GraphSelectionListener, KeyListener, Printable { private final Logger log = LoggerFactory.getLogger(getClass()); JGraph graph; JTree tree; StorySketchApplication application; public static BrowserLauncher launcher; // Undo Manager protected GraphUndoManager undoManager; // Actions which Change State protected Action undo, redo, remove, group, ungroup, tofront, toback, cut, copy, paste, xmlSave,xmlLoad, print, feedback; ButtonGroup buttonGroup = new ButtonGroup(); JToggleButton selectButton; JToggleButton personButton; JToggleButton noteButton; JToggleButton quoteButton; JToggleButton eventButton; JToggleButton appointmentButton; JToggleButton archiveButton; JToggleButton conflictButton; JToggleButton placeButton; JToggleButton edgeButton; JEditorPane info = new JEditorPane("text/html","<html></html>"); String helpText = "<html><body bgcolor=\"#FFFFFF\"><b>Saksskisse</b><br>Klikk på et objekt for å velge det<br>Dobbeltklikk for å redigere<br>Velg verktøy på toppen for å sette inn nytt objekt</body></html>"; OverviewPanel overview; public StorySketchPanel() { setLayout(new BorderLayout()); GraphModel model = new StorySketchGraphModel(); graph = new StorySketchGraph(model); //graph.setTransferHandler(new StorySketchTransferHandler()); graph.setDropTarget(new DropTarget() { public void drop(DropTargetDropEvent dtde) { graph_drop(dtde); } }); graph.setSelectNewCells(true); setStorySketchApplication(new DefaultStorySketchApplication()); JScrollPane graphScroll = new JScrollPane(graph); overview = new OverviewPanel(graph, graphScroll); overview.setMinimumSize(new Dimension(70, 80)); overview.setPreferredSize(new Dimension(70, 80)); graph.setAntiAliased(true); graph.setDoubleBuffered(false); graph.setMarqueeColor(Color.DARK_GRAY); graph.getSelectionModel().addGraphSelectionListener(new GraphSelectionListener() { public void valueChanged(GraphSelectionEvent e) { updateInfo(e); } }); info.setEditable(false); info.setText(helpText); info.setCaretPosition(0); info.setPreferredSize(new Dimension(0,0)); info.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { info_hyperlinkUpdate(e); } }); // Set up the graph tree //GraphTreeModel gtModel = new GraphTreeModel(graph.getModel()); //tree = new JTree(gtModel); //graph.getModel().addGraphModelListener(gtModel); //tree.setRootVisible(false); graph.setInvokesStopCellEditing(true); graph.setGridEnabled(true); graph.setGridVisible(true); graph.setGridSize(8); graph.setGridColor(new Color(225,225,225)); graph.setGridMode(JGraph.LINE_GRID_MODE); //graph.setBackground(new Color(240,240,240)); undoManager = new GraphUndoManager() { // Override Superclass public void undoableEditHappened(UndoableEditEvent e) { // First Invoke Superclass super.undoableEditHappened(e); // Then Update Undo/Redo Buttons updateHistoryButtons(); } }; // Update ToolBar based on Selection Changes graph.getSelectionModel().addGraphSelectionListener(this); // Listen for Delete Keystroke when the Graph has Focus graph.addKeyListener(this); // Construct Panel JPanel graphPanel = new JPanel(new BorderLayout()); // Add a ToolBar graphPanel.add(createInsertBar(), BorderLayout.NORTH); JToolBar tb = createToolBar(); tb.setOrientation(SwingConstants.VERTICAL); graphPanel.add(tb, BorderLayout.EAST); graphPanel.add(graphScroll, BorderLayout.CENTER); // Add the Graph as Center Component JSplitPane bottomSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(info), overview); JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, graphPanel, bottomSplit); split.setDividerLocation(0.7); split.setResizeWeight(1); bottomSplit.setDividerLocation(0.5); bottomSplit.setResizeWeight(1); add(split, BorderLayout.CENTER); setModel(model); } public void graph_drop(DropTargetDropEvent dtde) { Transferable t = dtde.getTransferable(); DataFlavor[] flavors = t.getTransferDataFlavors(); boolean canImport = false; for (DataFlavor flavor : flavors) { if (DataFlavor.stringFlavor.equals(flavor)) { canImport = true; } } if(!canImport) { return; } try { dtde.acceptDrop(DnDConstants.ACTION_COPY); String str = (String)t.getTransferData(DataFlavor.stringFlavor); JMenu insert = createInsertMenu(dtde.getLocation(), str); JPopupMenu menu = new JPopupMenu(); menu.add(insert); menu.show(graph, (int) dtde.getLocation().getX(), (int) dtde.getLocation().getY()); }catch (UnsupportedFlavorException ignored) { } catch (IOException ignored) { } } public GraphModel getModel() { return graph.getModel(); } public void setModel(GraphModel model) { graph.setModel(model); overview.setModel(model); model.addUndoableEditListener(undoManager); updateHistoryButtons(); } public void updateInfo(GraphSelectionEvent e) { Object c = e.getCell(); if(graph.getSelectionCell() == null) { info.setText(helpText); info.setCaretPosition(0); } else if(c instanceof StorySketchCell) { StorySketchCell sc = (StorySketchCell) c; info.setText(sc.toHTML()); info.setCaretPosition(0); } } // // PopupMenu // public JPopupMenu createPopupMenu(final MouseEvent event, final Object cell) { final Point pt = event.getPoint(); JPopupMenu menu = new JPopupMenu(); if (cell != null) { // Edit menu.add(new AbstractAction("Rediger") { public void actionPerformed(ActionEvent e) { graph.startEditingAtCell(cell); } }); if(cell instanceof PersonCell) { PersonCell pc = (PersonCell) cell; final Person p = (Person) pc.getUserObject(); if(p.getEmail().length() > 0) { final String address = (p.getName().length() > 1) ? p.getName() +" <" +p.getEmail() +">" : p.getEmail(); menu.add(new AbstractAction("Send epost") { public void actionPerformed(ActionEvent e) { application.sendMail(address, "", ""); } }); menu.add(new AbstractAction("Send artikkel") { public void actionPerformed(ActionEvent e) { application.sendArticleAsMail(address); } }); } } } else { JMenu insert = createInsertMenu(pt,""); menu.add(insert); } // Remove if (!graph.isSelectionEmpty()) { menu.add(new AbstractAction("Slett") { public void actionPerformed(ActionEvent e) { remove.actionPerformed(e); } }); } return menu; } public JMenu createInsertMenu(final Point pt, final String str) { JMenu insert = new JMenu("Sett inn"); // Person insert.add(new AbstractAction("Person") { public void actionPerformed(ActionEvent ev) { Person p = new Person(); p.setName(str); insertPerson(pt, p); } }); // Note insert.add(new AbstractAction("Notat") { public void actionPerformed(ActionEvent ev) { Note n = new Note(); n.setNotes(str); insertNote(pt, n); } }); // Quote insert.add(new AbstractAction("Sitat") { public void actionPerformed(ActionEvent ev) { Quote q = new Quote(); q.setQuote(str); insertQuote(pt, q); } }); // Avtale insert.add(new AbstractAction("Avtale") { public void actionPerformed(ActionEvent ev) { Appointment a = new Appointment(); a.setWho(str); insertAppointment(pt, a); } }); // Archive insert.add(new AbstractAction("Arkivsaker") { public void actionPerformed(ActionEvent ev) { insertArchive(pt); } }); // Conflict insert.add(new AbstractAction("Konflikt") { public void actionPerformed(ActionEvent ev) { insertConflict(pt); } }); return insert; } public JToolBar createToolBar() { JToolBar toolbar = new JToolBar(); toolbar.setFloatable(true); // Undo toolbar.addSeparator(); URL undoUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/undo.gif"); ImageIcon undoIcon = new ImageIcon(undoUrl); undo = new AbstractAction("", undoIcon) { public void actionPerformed(ActionEvent e) { undo(); } }; undo.setEnabled(false); toolbar.add(undo).setToolTipText("Angre"); // Redo URL redoUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/redo.gif"); ImageIcon redoIcon = new ImageIcon(redoUrl); redo = new AbstractAction("", redoIcon) { public void actionPerformed(ActionEvent e) { redo(); } }; redo.setEnabled(false); toolbar.add(redo).setToolTipText("Utfør likevel"); // // Edit Block // toolbar.addSeparator(); Action action; URL url; // Copy action = TransferHandler.getCopyAction(); url = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/copy.gif"); action.putValue(Action.SMALL_ICON, new ImageIcon(url)); toolbar.add(copy = new EventRedirector(action)).setToolTipText("Kopier"); // Paste action = TransferHandler.getPasteAction(); url = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/paste.gif"); action.putValue(Action.SMALL_ICON, new ImageIcon(url)); toolbar.add(paste = new EventRedirector(action)).setToolTipText("Lim inn"); // Cut action = TransferHandler.getCutAction(); url = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/cut.gif"); action.putValue(Action.SMALL_ICON, new ImageIcon(url)); toolbar.add(cut = new EventRedirector(action)).setToolTipText("Klipp ut"); // Remove URL removeUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/delete.gif"); ImageIcon removeIcon = new ImageIcon(removeUrl); remove = new AbstractAction("", removeIcon) { public void actionPerformed(ActionEvent e) { if (!graph.isSelectionEmpty()) { Object[] cells = graph.getSelectionCells(); cells = graph.getDescendants(cells); graph.getModel().remove(cells); } } }; remove.setEnabled(false); toolbar.add(remove).setToolTipText("Slett"); // Zoom Std toolbar.addSeparator(); URL zoomUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/zoom100.gif"); ImageIcon zoomIcon = new ImageIcon(zoomUrl); toolbar.add(new AbstractAction("", zoomIcon) { public void actionPerformed(ActionEvent e) { graph.setScale(1.0); } }).setToolTipText("Standard zoom"); // Zoom In URL zoomInUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/zoomin.gif"); ImageIcon zoomInIcon = new ImageIcon(zoomInUrl); toolbar.add(new AbstractAction("", zoomInIcon) { public void actionPerformed(ActionEvent e) { graph.setScale(1.2*graph.getScale()); } }).setToolTipText("Zoom inn"); // Zoom Out URL zoomOutUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/zoomout.gif"); ImageIcon zoomOutIcon = new ImageIcon(zoomOutUrl); toolbar.add(new AbstractAction("", zoomOutIcon) { public void actionPerformed(ActionEvent e) { graph.setScale(graph.getScale()/1.2); } }).setToolTipText("Zoom ut"); // Group //toolbar.addSeparator(); URL groupUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/group.gif"); ImageIcon groupIcon = new ImageIcon(groupUrl); group = new AbstractAction("", groupIcon) { public void actionPerformed(ActionEvent e) { group(graph.getSelectionCells()); } }; group.setEnabled(false); //toolbar.add(group); // XMLSAVE URL xmlSaveUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/ungroup.gif"); ImageIcon xmlSaveIcon = new ImageIcon(xmlSaveUrl); xmlSave = new AbstractAction("", xmlSaveIcon) { public void actionPerformed(ActionEvent e) { saveAsXML(); } }; //toolbar.add(xmlSave).setToolTipText("Lagre som XML"); // XML Load URL xmlLoadUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/ungroup.gif"); ImageIcon xmlLoadIcon = new ImageIcon(xmlLoadUrl); xmlLoad = new AbstractAction("", xmlLoadIcon) { public void actionPerformed(ActionEvent e) { loadXML(); } }; //toolbar.add(xmlLoad).setToolTipText("Åpne XML"); // XML Load toolbar.addSeparator(); URL printUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/print.gif"); ImageIcon printIcon = new ImageIcon(printUrl); print = new AbstractAction("", printIcon) { public void actionPerformed(ActionEvent e) { printGraph(); } }; toolbar.add(print).setToolTipText("Skriv ut skisse"); toolbar.addSeparator(); URL feedbackUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/feedback.png"); ImageIcon feedbackIcon = new ImageIcon(feedbackUrl); feedback = new AbstractAction("", feedbackIcon) { public void actionPerformed(ActionEvent e) { sendFeedback(); } }; toolbar.add(feedback).setToolTipText("Send tilbakeMelding"); // Ungroup URL ungroupUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/ungroup.gif"); ImageIcon ungroupIcon = new ImageIcon(ungroupUrl); ungroup = new AbstractAction("", ungroupIcon) { public void actionPerformed(ActionEvent e) { ungroup(graph.getSelectionCells()); } }; ungroup.setEnabled(false); //toolbar.add(ungroup); // To Front toolbar.addSeparator(); URL toFrontUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/tofront.gif"); ImageIcon toFrontIcon = new ImageIcon(toFrontUrl); tofront = new AbstractAction("", toFrontIcon) { public void actionPerformed(ActionEvent e) { if (!graph.isSelectionEmpty()) toFront(graph.getSelectionCells()); } }; tofront.setEnabled(false); //toolbar.add(tofront); // To Back URL toBackUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/toback.gif"); ImageIcon toBackIcon = new ImageIcon(toBackUrl); toback = new AbstractAction("", toBackIcon) { public void actionPerformed(ActionEvent e) { if (!graph.isSelectionEmpty()) toBack(graph.getSelectionCells()); } }; toback.setEnabled(false); //toolbar.add(toback); return toolbar; } public JToolBar createInsertBar() { JToolBar toolbar = new JToolBar(); toolbar.setFloatable(true); // Select URL selectUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/selector.gif"); ImageIcon selectIcon = new ImageIcon(selectUrl); selectButton = new JToggleButton(new AbstractAction("", selectIcon) { public void actionPerformed(ActionEvent e) { // } }); selectButton.setToolTipText("Velg"); buttonGroup.add(selectButton); selectButton.setSelected(true); toolbar.add(selectButton); // Person URL personUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/person.png"); ImageIcon personIcon = new ImageIcon(personUrl); personButton = new JToggleButton(new AbstractAction("", personIcon) { public void actionPerformed(ActionEvent e) { // } }); personButton.setToolTipText("Legg til person"); buttonGroup.add(personButton); toolbar.add(personButton); // Note URL noteUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/NewNote16.gif"); ImageIcon noteIcon = new ImageIcon(noteUrl); noteButton = new JToggleButton(new AbstractAction("", noteIcon) { public void actionPerformed(ActionEvent e) { // } }); noteButton.setToolTipText("Legg til notat"); buttonGroup.add(noteButton); toolbar.add(noteButton); // Quote URL quoteUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/quote.png"); ImageIcon quoteIcon = new ImageIcon(quoteUrl); quoteButton = new JToggleButton(new AbstractAction("", quoteIcon) { public void actionPerformed(ActionEvent e) { // } }); quoteButton.setToolTipText("Legg til sitat"); buttonGroup.add(quoteButton); toolbar.add(quoteButton); // Event URL eventUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/NewNote16.gif"); ImageIcon eventIcon = new ImageIcon(eventUrl); eventButton = new JToggleButton(new AbstractAction("", eventIcon) { public void actionPerformed(ActionEvent e) { // } }); eventButton.setToolTipText("Legg inn ny hendelse"); buttonGroup.add(eventButton); //toolbar.add(eventButton); // Archive URL archiveUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/archive.png"); ImageIcon archiveIcon = new ImageIcon(archiveUrl); archiveButton = new JToggleButton(new AbstractAction("", archiveIcon) { public void actionPerformed(ActionEvent e) { // } }); archiveButton.setToolTipText("Legg til arkivsak"); buttonGroup.add(archiveButton); toolbar.add(archiveButton); // Appointment URL appointmentUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/appointment.png"); ImageIcon appointmentIcon = new ImageIcon(appointmentUrl); appointmentButton = new JToggleButton(new AbstractAction("", appointmentIcon) { public void actionPerformed(ActionEvent e) { // } }); appointmentButton.setToolTipText("Legg til avtale"); buttonGroup.add(appointmentButton); toolbar.add(appointmentButton); // Conflict URL conflictUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/conflict.png"); ImageIcon conflictIcon = new ImageIcon(conflictUrl); conflictButton = new JToggleButton(new AbstractAction("", conflictIcon) { public void actionPerformed(ActionEvent e) { // } }); conflictButton.setToolTipText("Legg til en konflikt/debatt"); buttonGroup.add(conflictButton); toolbar.add(conflictButton); // Place URL placeUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/NewNote16.gif"); ImageIcon placeIcon = new ImageIcon(placeUrl); placeButton = new JToggleButton(new AbstractAction("", placeIcon) { public void actionPerformed(ActionEvent e) { // } }); placeButton.setToolTipText("Legg inn et sted"); buttonGroup.add(placeButton); //toolbar.add(placeButton); toolbar.addSeparator(); // Edge URL edgeUrl = getClass().getClassLoader().getResource("images/org/pegadi/storysketch/edge.gif"); ImageIcon edgeIcon = new ImageIcon(edgeUrl); edgeButton = new JToggleButton(new AbstractAction("", edgeIcon) { public void actionPerformed(ActionEvent e) { } }); edgeButton.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { updatePortsVisible(); } }); edgeButton.setToolTipText("Lag relasjon (sammenkobling)"); buttonGroup.add(edgeButton); toolbar.add(edgeButton); return toolbar; } public void updatePortsVisible() { graph.setPortsVisible(edgeButton.isSelected()); } // This will change the source of the actionevent to graph. protected class EventRedirector extends AbstractAction { protected Action action; // Construct the "Wrapper" Action public EventRedirector(Action a) { super("", (ImageIcon) a.getValue(Action.SMALL_ICON)); this.action = a; } // Redirect the Actionevent public void actionPerformed(ActionEvent e) { e = new ActionEvent(graph, e.getID(), e.getActionCommand(), e.getModifiers()); action.actionPerformed(e); } } // Update Undo/Redo Button State based on Undo Manager protected void updateHistoryButtons() { // The View Argument Defines the Context undo.setEnabled(undoManager.canUndo(graph.getGraphLayoutCache())); redo.setEnabled(undoManager.canRedo(graph.getGraphLayoutCache())); } // Insert a new Vertex at point public void insert(Point point) { // Construct Vertex with no Label DefaultGraphCell vertex = new DefaultGraphCell(); // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(50,60); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map GraphConstants.setBorderColor(map, Color.black); // Add a White Background GraphConstants.setBackground(map, Color.white); // Make Vertex Opaque GraphConstants.setOpaque(map, true); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } // Insert a new Vertex at point public void insertPerson(Point point) { insertPerson(point, null); } public void insertPerson(Point point, Person person) { // Construct Vertex with no Label PersonCell vertex; if(person == null) { vertex = new PersonCell(); } else { vertex = new PersonCell(person); } // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(55,70); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map //point.translate(-(int)size.getWidth()/2, -(int)size.getHeight()/2); GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map //GraphConstants.setBorderColor(map, Color.black); // Add a White Background //GraphConstants.setBackground(map, Color.white); // Make Vertex Opaque //GraphConstants.setOpaque(map, true); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } // Insert a new Vertex at point public void insertNote(Point point) { insertNote(point, null); } // Insert a new Vertex at point public void insertNote(Point point, Note note) { // Construct Vertex with no Label NoteCell vertex; if(note == null) { vertex = new NoteCell(); } else { vertex = new NoteCell(note); } // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(50,65); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } // Insert a new Vertex at point public void insertQuote(Point point) { insertQuote(point, null); } // Insert a new Vertex at point public void insertQuote(Point point, Quote quote) { // Construct Vertex with no Label QuoteCell vertex; if(quote == null) { vertex = new QuoteCell(); } else { vertex = new QuoteCell(quote); } // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(70,55); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map //point.translate(-(int) (size.getWidth()/2), - (int) (size.getHeight()/2)); GraphConstants.setBounds(map, new Rectangle(point, size)); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } // Insert a new Vertex at point public void insertEvent(Point point) { // Construct Vertex with no Label EventCell vertex = new EventCell(); // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(50,65); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map GraphConstants.setBorderColor(map, Color.black); // Add a White Background GraphConstants.setBackground(map, Color.decode("#E1FCAE")); // Make Vertex Opaque GraphConstants.setOpaque(map, true); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } public void insertAppointment(Point point) { insertAppointment(point, null); } // Insert a new Vertex at point public void insertAppointment(Point point, Appointment a) { // Construct Vertex with no Label AppointmentCell vertex; if(a == null) { vertex = new AppointmentCell(); } else { vertex = new AppointmentCell(a); } // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(100,60); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } public void insertArchive(Point point) { insertArchive(point, null); } // Insert a new Vertex at point public void insertArchive(Point point, Archive archive) { // Construct Vertex with no Label ArchiveCell vertex; if(archive == null) { vertex = new ArchiveCell(); } else { vertex = new ArchiveCell(archive); } // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(110,55); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map GraphConstants.setBorderColor(map, Color.black); // Add a White Background GraphConstants.setBackground(map, Color.decode("#E1ACAE")); // Make Vertex Opaque GraphConstants.setOpaque(map, true); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } public void insertConflict(Point point) { insertConflict(point, null); } // Insert a new Vertex at point public void insertConflict(Point point, Conflict conflict) { java.util.List toInsert = new LinkedList(); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Construct Vertex with no Label ConflictCell vertex; if(conflict == null) { vertex = new ConflictCell(); } else { vertex = new ConflictCell(conflict); } // Add ports DefaultPort leftPort = new DefaultPort(); toInsert.add(leftPort); vertex.add(leftPort); Map portMap = GraphConstants.createMap(); GraphConstants.setOffset(portMap,new Point(0,750)); attributes.put(leftPort,portMap); DefaultPort rightPort = new DefaultPort(); toInsert.add(rightPort); vertex.add(rightPort); portMap = GraphConstants.createMap(); GraphConstants.setOffset(portMap,new Point(1000,750)); attributes.put(rightPort,portMap); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(65,70); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map GraphConstants.setBorderColor(map, Color.black); // Add a White Background GraphConstants.setBackground(map, Color.decode("#ffffaa")); // Make Vertex Opaque GraphConstants.setOpaque(map, true); toInsert.add(vertex); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(toInsert.toArray(), attributes, null, null, null); } // Insert a new Vertex at point public void insertPlace(Point point) { // Construct Vertex with no Label PlaceCell vertex = new PlaceCell(); // Add one Floating Port vertex.add(new DefaultPort()); // Snap the Point to the Grid point = graph.snap(new Point(point)); // Default Size for the new Vertex Dimension size = new Dimension(50,65); // Create a Map that holds the attributes for the Vertex Map map = GraphConstants.createMap(); // Add a Bounds Attribute to the Map GraphConstants.setBounds(map, new Rectangle(point, size)); // Add a Border Color Attribute to the Map GraphConstants.setBorderColor(map, Color.black); // Add a White Background GraphConstants.setBackground(map, Color.decode("#b2d9f7")); // Make Vertex Opaque GraphConstants.setOpaque(map, true); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Vertex with its Attributes attributes.put(vertex, map); // Insert the Vertex and its Attributes graph.getModel().insert(new Object[]{vertex}, attributes, null, null, null); } // Insert a new Edge between source and target public void connect(Port source, Port target) { // Connections that will be inserted into the Model ConnectionSet cs = new ConnectionSet(); // Construct Edge with no label DefaultEdge edge = new DefaultEdge(); // Create Connection between source and target using edge cs.connect(edge, source, target); // Create a Map thath holds the attributes for the edge Map map = GraphConstants.createMap(); // Add a Line End Attribute //GraphConstants.setLineEnd(map, GraphConstants.ARROW_SIMPLE); //Set style GraphConstants.setRouting(map, GraphConstants.ROUTING_SIMPLE); GraphConstants.setLineStyle(map, GraphConstants.STYLE_BEZIER); // Construct a Map from cells to Maps (for insert) Hashtable attributes = new Hashtable(); // Associate the Edge with its Attributes attributes.put(edge, map); // Insert the Edge and its Attributes graph.getModel().insert(new Object[]{edge}, attributes, cs, null, null); } // Create a Group that Contains the Cells public void group(Object[] cells) { // Order Cells by View Layering cells = graph.getGraphLayoutCache().order(cells); // If Any Cells in View if (cells != null && cells.length > 0) { // Create Group Cell int count = getCellCount(graph); DefaultGraphCell group = new DefaultGraphCell(count - 1); // Create Change Information ParentMap map = new ParentMap(); // Insert Child Parent Entries for (Object cell : cells) map.addEntry(cell, group); // Insert into model graph.getModel().insert(new Object[]{group}, null, null, map, null); } } // Returns the total number of cells in a graph protected int getCellCount(JGraph graph) { Object[] cells = graph.getDescendants(graph.getRoots()); return cells.length; } public void loadXML() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(this); if(returnVal != JFileChooser.APPROVE_OPTION) { return; } File file = chooser.getSelectedFile(); DOMParser parser = new DOMParser(); Document doc = null; try { parser.parse(file.getAbsolutePath()); } catch (SAXException se) { log.error("Error parsing Story Sketch", se); } catch (java.io.IOException ioe) { log.error("Error parsing Story Sketch", ioe); } GraphModel newModel = XMLUtil.getGraphModel(parser.getDocument().getDocumentElement()); setModel(newModel); } public void saveAsXML() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showSaveDialog(this); if(returnVal != JFileChooser.APPROVE_OPTION) { return; } File file = chooser.getSelectedFile(); DocumentBuilderFactory fac; DocumentBuilder db = null; Document doc; try { fac = DocumentBuilderFactory.newInstance(); db = fac.newDocumentBuilder(); } catch (FactoryConfigurationError e) { log.error("Error saving", e); } catch (ParserConfigurationException e) { log.error("Error saving", e); } doc = db.newDocument(); Element root = doc.createElement("storysketch"); doc.appendChild(root); root.appendChild(XMLUtil.createModelElement(getModel(), doc)); SerializerFactory sFac = SerializerFactory.getSerializerFactory("xml"); OutputFormat format = new OutputFormat(doc, "iso-8859-1", true); format.setLineWidth(0); // To disable line wrapping. DOMSerializer ser; try { ser = sFac.makeSerializer(new java.io.FileOutputStream(file.getAbsolutePath()), format).asDOMSerializer(); ser.serialize(doc); } catch (Exception e) { e.printStackTrace(); } } // Ungroup the Groups in Cells and Select the Children public void ungroup(Object[] cells) { // If any Cells if (cells != null && cells.length > 0) { // List that Holds the Groups ArrayList groups = new ArrayList(); // List that Holds the Children ArrayList children = new ArrayList(); // Loop Cells for (Object cell : cells) { // If Cell is a Group if (isGroup(cell)) { // Add to List of Groups groups.add(cell); // Loop Children of Cell for (int j = 0; j < graph.getModel().getChildCount(cell); j++) { // Get Child from Model Object child = graph.getModel().getChild(cell, j); // If Not Port if (!(child instanceof Port)) // Add to Children List children.add(child); } } } // Remove Groups from Model (Without Children) graph.getModel().remove(groups.toArray()); // Select Children graph.setSelectionCells(children.toArray()); } } // Determines if a Cell is a Group public boolean isGroup(Object cell) { // Map the Cell to its View CellView view = graph.getGraphLayoutCache().getMapping(cell, false); return view != null && !view.isLeaf(); } // Brings the Specified Cells to Front public void toFront(Object[] c) { graph.getGraphLayoutCache().toFront(c); } // Sends the Specified Cells to Back public void toBack(Object[] c) { graph.getGraphLayoutCache().toBack(c); } // Undo the last Change to the Model or the View public void undo() { try { undoManager.undo(graph.getGraphLayoutCache()); } catch (Exception ex) { log.error("Error undoing", ex); } finally { updateHistoryButtons(); } } // Redo the last Change to the Model or the View public void redo() { try { undoManager.redo(graph.getGraphLayoutCache()); } catch (Exception ex) { log.error("Error redoing", ex); } finally { updateHistoryButtons(); } } // From GraphSelectionListener Interface public void valueChanged(GraphSelectionEvent e) { // Group Button only Enabled if more than One Cell Selected group.setEnabled(graph.getSelectionCount() > 1); // Update Button States based on Current Selection boolean enabled = !graph.isSelectionEmpty(); remove.setEnabled(enabled); ungroup.setEnabled(enabled); tofront.setEnabled(enabled); toback.setEnabled(enabled); copy.setEnabled(enabled); cut.setEnabled(enabled); } // // KeyListener for Delete KeyStroke // public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } public void keyPressed(KeyEvent e) { // Listen for Delete Key Press if (e.getKeyCode() == KeyEvent.VK_DELETE) // Execute Remove Action on Delete Key Press remove.actionPerformed(null); } public void info_hyperlinkUpdate(HyperlinkEvent e) { if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { log.info("Link is: " + e.getURL()); if(e.getURL().toString().startsWith("mailto:")) { application.sendMail(e.getURL().toString().substring(7), "", ""); } else if (e.getURL().toString().startsWith("http://underdusken.no:8888")) { try { info.setPage(e.getURL()); } catch (Exception ex) { JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(info), "Kunne ikke laste side", "Feil ved lasting", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } } else { log.info("Launching URL in browser"); if(launcher != null) launcher.browserLaunch(e.getURL().toString()); } } } public void sendFeedback() { application.sendMail("bjorsnos@underdusken.no", "Tilbakemelding ang. saksskisse", "Hei, \n\nSaksskissa er veldig bra, men:"); } public void printGraph() { PrinterJob printJob = PrinterJob.getPrinterJob(); PageFormat format = printJob.defaultPage(); Paper paper = new Paper(); log.info("Default size is : {}/{}", paper.getWidth(), paper.getHeight()); double A4_WIDTH = (210/25.4)*72; double A4_HEIGHT = (297/25.4)*72; double MARGIN = (10/25.4)*72; paper.setSize(A4_WIDTH, A4_HEIGHT); paper.setImageableArea(MARGIN, MARGIN, A4_WIDTH - MARGIN, A4_HEIGHT - MARGIN); log.info("New size is : {}/{}", paper.getWidth(), paper.getHeight()); format.setPaper(paper); Dimension d = graph.getPreferredSize(); double d1 = (d.getHeight()/graph.getScale()) / (d.getWidth()/graph.getScale()); double d2 = format.getImageableHeight()/format.getImageableHeight(); if(d1 < d2 && (format.getImageableWidth() < (d.getWidth()/graph.getScale()) || format.getImageableHeight() < (d.getHeight()/graph.getScale()))) { log.info("setting landscape orientation"); format.setOrientation(PageFormat.LANDSCAPE); } printJob.setPrintable(this, format); if (printJob.printDialog()) { try { printJob.print(); } catch (Exception printException) { printException.printStackTrace(); } } } public int print(Graphics g, PageFormat format, int page) { if(page > 0) { return NO_SUCH_PAGE; } else { Dimension d = graph.getPreferredSize(); double d1 = d.getHeight()/(d.getWidth()/graph.getScale()); double d2 = format.getImageableHeight()/ format.getImageableHeight(); double sx = format.getImageableWidth() / (d.getWidth()/graph.getScale()) ; double sy = format.getImageableHeight() / (d.getHeight()/graph.getScale()); double scale = Math.min(sx, sy); if(scale > 1) scale = 1; g.translate((int)format.getImageableX(), (int)format.getImageableY()); StorySketchGraph printGraph = new StorySketchGraph(graph.getModel()); printGraph.setScale(scale); JScrollPane p = new JScrollPane(printGraph); JFrame f = new JFrame(); f.getContentPane().add(p); f.pack(); printGraph.paint(g); String label = "Saksskisse skrevet ut av Pegadi - " + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(new Date()); FontMetrics fm = g.getFontMetrics(); int lw = fm.stringWidth(label); g.drawString(label, (int) (format.getImageableWidth()-lw), //0+fm.getHeight()); (int) (format.getImageableHeight() -format.getImageableY() -fm.getMaxDescent())); return PAGE_EXISTS; } } public void setStorySketchApplication(StorySketchApplication application) { this.application = application; } public class StorySketchGraph extends JGraph { public StorySketchGraph(GraphModel model) { super(model); setMarqueeHandler(new StorySketchMarqueeHandler()); } /** * Override parent method with custom GraphUI. */ public void updateUI() { setUI(new EditorGraphUI()); invalidate(); } // Overrides JGraph.createVertexView protected VertexView createVertexView(Object v, CellMapper cm) { if (v instanceof PersonCell) { return new PersonView(v, this, cm); } else if (v instanceof NoteCell) { return new NoteView(v, this, cm); } else if (v instanceof QuoteCell) { return new QuoteView(v, this, cm); } else if (v instanceof EventCell) { return new EventView(v, this, cm); } else if (v instanceof AppointmentCell) { return new AppointmentView(v, this, cm); } else if (v instanceof ArchiveCell) { return new ArchiveView(v, this, cm); } else if (v instanceof ConflictCell) { return new ConflictView(v, this, cm); } else if (v instanceof PlaceCell) { return new PlaceView(v, this, cm); } // Else Call Superclass return super.createVertexView(v,cm); } // Override Superclass Method to Return Custom EdgeView protected EdgeView createEdgeView(Object e, CellMapper cm) { // Return Custom EdgeView return new EdgeView(e, this, cm) { // Override Superclass Method public boolean isAddPointEvent(MouseEvent event) { // Points are Added using Shift-Click return event.isShiftDown(); } // Override Superclass Method public boolean isRemovePointEvent(MouseEvent event) { // Points are Removed using Shift-Click return event.isShiftDown(); } }; } } public class StorySketchMarqueeHandler extends BasicMarqueeHandler { // Holds the Start and the Current Point protected Point start, current; // Holds the First and the Current Port protected PortView port, firstPort; // Override to Gain Control (for PopupMenu and ConnectMode) public boolean isForceMarqueeEvent(MouseEvent e) { // If Right Mouse Button we want to Display the PopupMenu if (SwingUtilities.isRightMouseButton(e)) // Return Immediately return true; // Find and Remember Port port = getSourcePortAt(e.getPoint()); // If Port Found and in ConnectMode (=Ports Visible) if (port != null && graph.isPortsVisible()) return true; // Else Call Superclass return super.isForceMarqueeEvent(e); } // Display PopupMenu or Remember Start Location and First Port public void mousePressed(final MouseEvent e) { Point loc = graph.fromScreen(e.getPoint()); // If Right Mouse Button if (SwingUtilities.isRightMouseButton(e)) { // Scale From Screen to Model // Find Cell in Model Coordinates Object cell = graph.getFirstCellForLocation(e.getPoint().x, e.getPoint().y); // Create PopupMenu for the Cell JPopupMenu menu = createPopupMenu(e, cell); // Display PopupMenu menu.show(graph, e.getX(), e.getY()); e.consume(); // Else if in ConnectMode and Remembered Port is Valid } else if (personButton.isSelected()) { insertPerson(loc); selectButton.setSelected(true); e.consume(); } else if (noteButton.isSelected()) { insertNote(loc); selectButton.setSelected(true); e.consume(); } else if (quoteButton.isSelected()) { insertQuote(loc); selectButton.setSelected(true); e.consume(); } else if (eventButton.isSelected()) { insertEvent(loc); selectButton.setSelected(true); e.consume(); } else if (appointmentButton.isSelected()) { insertAppointment(loc); selectButton.setSelected(true); e.consume(); } else if (archiveButton.isSelected()) { insertArchive(loc); selectButton.setSelected(true); e.consume(); } else if (conflictButton.isSelected()) { insertConflict(loc); selectButton.setSelected(true); e.consume(); } else if (placeButton.isSelected()) { insertPlace(loc); selectButton.setSelected(true); e.consume(); } else if (port != null && !e.isConsumed() && graph.isPortsVisible()) { // Remember Start Location start = graph.toScreen(port.getLocation(null)); // Remember First Port firstPort = port; // Consume Event e.consume(); } else { // Call Superclass super.mousePressed(e); } } // Find Port under Mouse and Repaint Connector public void mouseDragged(MouseEvent e) { // If remembered Start Point is Valid if (start != null && !e.isConsumed()) { // Fetch Graphics from Graph Graphics g = graph.getGraphics(); // Xor-Paint the old Connector (Hide old Connector) paintConnector(Color.black, graph.getBackground(), g); // Reset Remembered Port port = getTargetPortAt(e.getPoint()); // If Port was found then Point to Port Location if (port != null) current = graph.toScreen(port.getLocation(null)); // Else If no Port was found then Point to Mouse Location else current = graph.snap(e.getPoint()); // Xor-Paint the new Connector paintConnector(graph.getBackground(), Color.black, g); // Consume Event e.consume(); } // Call Superclass super.mouseDragged(e); } public PortView getSourcePortAt(Point point) { // Scale from Screen to Model Point tmp = graph.fromScreen(new Point(point)); // Find a Port View in Model Coordinates and Remember return graph.getPortViewAt(tmp.x, tmp.y); } // Find a Cell at point and Return its first Port as a PortView protected PortView getTargetPortAt(Point point) { // Find Cell at point (No scaling needed here) Object cell = graph.getFirstCellForLocation(point.x, point.y); // Loop Children to find PortView for (int i = 0; i < graph.getModel().getChildCount(cell); i++) { // Get Child from Model Object tmp = graph.getModel().getChild(cell, i); // Get View for Child using the Graph's View as a Cell Mapper tmp = graph.getGraphLayoutCache().getMapping(tmp, false); // If Child View is a Port View and not equal to First Port if (tmp instanceof PortView && tmp != firstPort) // Return as PortView return (PortView) tmp; } // No Port View found return getSourcePortAt(point); } // Connect the First Port and the Current Port in the Graph or Repaint public void mouseReleased(MouseEvent e) { // If Valid Event, Current and First Port if (e != null && !e.isConsumed() && port != null && firstPort != null && firstPort != port) { // Then Establish Connection connect((Port) firstPort.getCell(), (Port) port.getCell()); selectButton.setSelected(true); // Consume Event e.consume(); // Else Repaint the Graph } else graph.repaint(); // Reset Global Vars firstPort = port = null; start = current = null; // Call Superclass super.mouseReleased(e); } // Show Special Cursor if Over Port public void mouseMoved(MouseEvent e) { // Check Mode and Find Port if (e != null && getSourcePortAt(e.getPoint()) != null && !e.isConsumed() && graph.isPortsVisible()) { // Set Cusor on Graph (Automatically Reset) graph.setCursor(new Cursor(Cursor.HAND_CURSOR)); // Consume Event e.consume(); } super.mouseMoved(e); } // Use Xor-Mode on Graphics to Paint Connector protected void paintConnector(Color fg, Color bg, Graphics g) { // Set Foreground g.setColor(fg); // Set Xor-Mode Color g.setXORMode(bg); // Highlight the Current Port paintPort(graph.getGraphics()); // If Valid First Port, Start and Current Point if (firstPort != null && start != null && current != null) // Then Draw A Line From Start to Current Point g.drawLine(start.x, start.y, current.x, current.y); } // Use the Preview Flag to Draw a Highlighted Port protected void paintPort(Graphics g) { // If Current Port is Valid if (port != null) { // If Not Floating Port... boolean o = (GraphConstants.getOffset(port.getAttributes()) != null); // ...Then use Parent's Bounds Rectangle r = (o) ? port.getBounds() : port.getParentView().getBounds(); // Scale from Model to Screen r = graph.toScreen(new Rectangle(r)); // Add Space For the Highlight Border r.setBounds(r.x-3, r.y-3, r.width+6, r.height+6); // Paint Port in Preview (=Highlight) Mode graph.getUI().paintCell(g, port, r, true); } } } public class StorySketchTransferHandler extends TransferHandler { public StorySketchTransferHandler() { } public boolean canImport(JComponent c, DataFlavor[] flavors) { for (DataFlavor flavor : flavors) { if (DataFlavor.stringFlavor.equals(flavor)) { return true; } } return false; } public boolean importData(JComponent c, Transferable t) { if (canImport(c, t.getTransferDataFlavors())) { try { String str = (String)t.getTransferData(DataFlavor.stringFlavor); log.info("Transfered {}", str); return true; } catch (UnsupportedFlavorException ignored) { } catch (IOException ignored) { } } return false; } } class OverviewPanel extends JPanel implements ComponentListener, GraphModelListener, MouseListener, MouseMotionListener, ChangeListener, PropertyChangeListener { //PannerViewfinder v; Rectangle r; JGraph graph; JGraph originalGraph; JScrollPane scroll; double scale = 1; Point last; Rectangle rect = new Rectangle(); double zoom = 1; public OverviewPanel(JGraph g, JScrollPane scroll) { originalGraph = g; this.scroll=scroll; graph = new StorySketchGraph(g.getModel()); graph.setAntiAliased(true); graph.setEnabled(false); setLayout(new BorderLayout()); add(graph, BorderLayout.CENTER); g.getModel().addGraphModelListener(this); addComponentListener(this); graph.addMouseListener(this); graph.addMouseMotionListener(this); scroll.getViewport().addChangeListener(this); g.addPropertyChangeListener(JGraph.SCALE_PROPERTY, this); } public void propertyChange(PropertyChangeEvent evt) { zoom = (Double) evt.getNewValue(); componentResized(null); } public GraphModel getModel() { return graph.getModel(); } public void setModel(GraphModel model) { graph.setModel(model); } public void graphChanged(GraphModelEvent e) { componentResized(null); repaint(); } public void stateChanged(ChangeEvent e) { updatePannerFromView(); repaint(); } public void componentResized(ComponentEvent e) { Dimension d = scroll.getViewport().getView().getSize(); Dimension s = getSize(); double sx = s.getWidth() / d.getWidth(); double sy = s.getHeight() / d.getHeight(); scale = Math.min(sx, sy); graph.setScale(scale*zoom); updatePannerFromView(); repaint(); } public void componentShown(ComponentEvent e) { componentResized(e); } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void mousePressed(MouseEvent e) { last = e.getPoint(); } public void mouseMoved(MouseEvent e) { if(rect.contains(e.getPoint())) { setCursor(new Cursor(Cursor.HAND_CURSOR)); } else { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } public void mouseDragged(MouseEvent e) { //updatePanner(e); updateViewport(e); updatePannerFromView(); repaint(); } public void mouseReleased(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } public void mouseExited(MouseEvent e) { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } public void mouseEntered(MouseEvent e) { } public void updatePannerFromView() { Rectangle r = scroll.getViewport().getViewRect(); rect = new Rectangle((int) (r.getX() * scale), (int) (r.getY() * scale), (int) (r.getWidth() * scale)-1, (int) (r.getHeight() * scale)-1); } public void updatePanner(MouseEvent e) { int tx = (int) (e.getX() - last.getX()); int ty = (int) (e.getY() - last.getY()); Rectangle newRect = new Rectangle(rect); newRect.translate(tx,ty); int x1 = (int) newRect.getX(); int y1 = (int) newRect.getY(); int x2 = x1 + (int) newRect.getWidth(); int y2 = y1 + (int) newRect.getHeight(); if(x1 >= 0 && y1 >= 0 && x2 <= getSize().getWidth() && y2 < getSize().getHeight()) { rect = newRect; } last = e.getPoint(); } public void updateViewport(MouseEvent e) { double dx = e.getX() - last.getX(); double dy = e.getY() - last.getY(); double dxV = dx/scale; double dyV = dy/scale; Rectangle r = scroll.getViewport().getViewRect(); int x = (int) (r.getLocation().getX() + dxV); int y = (int) (r.getLocation().getY() + dyV); Dimension d = scroll.getViewport().getViewSize(); if(x + r.getWidth() > d.getWidth()) x = (int) (d.getWidth() -r.getWidth()); if(y + r.getHeight() > d.getHeight()) y = (int) (d.getHeight() -r.getHeight()); if(x < 0) { x=0; } if(y < 0) { y=0; } Point p = new Point(x,y); p.setLocation(x, y); scroll.getViewport().setViewPosition(p); last = e.getPoint(); } public void paintChildren(Graphics g) { super.paintChildren(g); Graphics2D g2 = (Graphics2D) g; g2.setColor(Color.RED); g2.draw(rect); } } /** * Definition of the custom GraphUI. */ public class EditorGraphUI extends BasicGraphUI { protected CellEditorListener cellEditorListener; protected JFrame editDialog = null; /** * Create the dialog using the cell's editing component. */ protected void createEditDialog(Object cell, MouseEvent event) { Dimension editorSize = editingComponent.getPreferredSize(); editDialog = new JFrame(graph.convertValueToString(cell)); editDialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { graph.stopEditing(); } }); editDialog.getContentPane().setLayout(new GridLayout(1, 1)); editDialog.getContentPane().add(editingComponent); Point p = null; if(event != null) { p = event.getPoint(); } else { if(cell instanceof DefaultGraphCell) { DefaultGraphCell c = (DefaultGraphCell) cell; p = GraphConstants.getBounds(c.getAttributes()).getLocation(); } } SwingUtilities.convertPointToScreen(p, graph); editingComponent.validate(); editDialog.pack(); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); if(p.getX() + editingComponent.getSize().getWidth() +50 > screen.getWidth()) { p.translate(-(int) (p.getX() +editingComponent.getSize().getWidth() +50 -screen.getWidth()),0); } if(p.getY() + editingComponent.getSize().getHeight() +50 > screen.getHeight()) { p.translate(0, -(int) (p.getY() +editingComponent.getSize().getHeight() +50 -screen.getHeight())); } editDialog.setLocation(p); //editDialog.setResizable(false); editDialog.setVisible(true); } protected void completeEditing() { /* If should invoke stopCellEditing, try that */ if (graph.getInvokesStopCellEditing() && stopEditingInCompleteEditing && editingComponent != null) { cellEditor.stopCellEditing(); } /* Invoke cancelCellEditing, this will do nothing if stopCellEditing * was successful. */ completeEditing(false, true, true); } /** * Stops the editing session. If messageStop is true the editor * is messaged with stopEditing, if messageCancel is true the * editor is messaged with cancelEditing. If messageGraph is true * the graphModel is messaged with valueForCellChanged. */ protected void completeEditing(boolean messageStop, boolean messageCancel, boolean messageGraph) { if(editingCell instanceof Edge) { super.completeEditing(messageStop, messageCancel, messageGraph); return; } if (stopEditingInCompleteEditing && editingComponent != null && editDialog != null) { Component oldComponent = editingComponent; Object oldCell = editingCell; GraphCellEditor oldEditor = cellEditor; Object newValue = oldEditor.getCellEditorValue(); Rectangle editingBounds = graph.getCellBounds(editingCell); boolean requestFocus = ((graph.hasFocus() || editingComponent.hasFocus())); editingCell = null; editingComponent = null; if (messageStop) oldEditor.stopCellEditing(); else if (messageCancel) oldEditor.cancelCellEditing(); editDialog.dispose(); if (requestFocus) graph.requestFocus(); if (messageGraph) { Map map = GraphConstants.createMap(); GraphConstants.setValue(map, newValue); Map nested = new Hashtable(); nested.put(oldCell, map); graphLayoutCache.edit(nested, null, null, null); } updateSize(); // Remove Editor Listener if (oldEditor != null && cellEditorListener != null) oldEditor.removeCellEditorListener(cellEditorListener); cellEditor = null; editDialog = null; } } /** * Will start editing for cell if there is a cellEditor and * shouldSelectCell returns true.<p> * This assumes that cell is valid and visible. */ protected boolean startEditing(Object cell, MouseEvent event) { if(cell instanceof Edge) { return super.startEditing(cell, event); } completeEditing(); if (graph.isCellEditable(cell) && editDialog == null) { // Create Editing Component **** ***** CellView tmp = graphLayoutCache.getMapping(cell, false); cellEditor = tmp.getEditor(); editingComponent = cellEditor.getGraphCellEditorComponent( graph, cell, graph.isCellSelected(cell)); if (cellEditor.isCellEditable(event)) { editingCell = cell; // Create Wrapper Dialog **** ***** createEditDialog(cell, event); // Add Editor Listener if (cellEditorListener == null) cellEditorListener = createCellEditorListener(); if (cellEditor != null && cellEditorListener != null) cellEditor.addCellEditorListener(cellEditorListener); if (cellEditor.shouldSelectCell(event)) { stopEditingInCompleteEditing = false; try { graph.setSelectionCell(cell); } catch (Exception e) { log.error("Error setting selection", e); } stopEditingInCompleteEditing = true; } if (event != null) { /* Find the component that will get forwarded all the mouse events until mouseReleased. */ Point componentPoint = SwingUtilities.convertPoint( graph, new Point(event.getX(), event.getY()), editingComponent); /* Create an instance of BasicTreeMouseListener to handle passing the mouse/motion events to the necessary component. */ // We really want similiar behavior to getMouseEventTarget, // but it is package private. Component activeComponent = SwingUtilities.getDeepestComponentAt( editingComponent, componentPoint.x, componentPoint.y); if (activeComponent != null) { new MouseInputHandler( graph, activeComponent, event); } } return true; } else editingComponent = null; } return false; } } }