001: /*
002: * $Id: JGraphpadEditAction.java,v 1.2 2005/10/15 16:36:17 gaudenz Exp $
003: * Copyright (c) 2001-2005, Gaudenz Alder
004: *
005: * All rights reserved.
006: *
007: * See LICENSE file for license details. If you are unable to locate
008: * this file please contact info (at) jgraph (dot) com.
009: */
010: package com.jgraph.pad.action;
011:
012: import java.awt.Component;
013: import java.awt.event.ActionEvent;
014: import java.util.ArrayList;
015: import java.util.List;
016: import java.util.regex.Matcher;
017: import java.util.regex.Pattern;
018: import java.util.regex.PatternSyntaxException;
019:
020: import javax.swing.Action;
021: import javax.swing.TransferHandler;
022: import javax.swing.text.DefaultEditorKit;
023:
024: import org.jgraph.JGraph;
025: import org.jgraph.graph.CellView;
026: import org.jgraph.graph.GraphLayoutCache;
027: import org.jgraph.graph.GraphUndoManager;
028:
029: import com.jgraph.editor.JGraphEditorAction;
030: import com.jgraph.editor.JGraphEditorResources;
031: import com.jgraph.editor.factory.JGraphEditorDiagramPane;
032: import com.jgraph.pad.dialog.JGraphpadDialogs;
033: import com.jgraph.pad.factory.JGraphpadLibraryPane;
034:
035: /**
036: * Implements all actions of the edit menu. The selectPath and selectTree
037: * actions are implemented by plugins.
038: */
039: public class JGraphpadEditAction extends JGraphEditorAction {
040:
041: /**
042: * Holds the last search expression. Note: In a multi application
043: * environment you may have to put this into the application instance.
044: */
045: protected static Pattern lastSearchPattern = null;
046:
047: /**
048: * Holds the last found cell for a search.
049: */
050: protected static Object lastFoundCell = null;
051:
052: /**
053: * Specifies the name for the <code>cut</code> action.
054: */
055: public static final String NAME_CUT = "cut";
056:
057: /**
058: * Specifies the name for the <code>copy</code> action.
059: */
060: public static final String NAME_COPY = "copy";
061:
062: /**
063: * Specifies the name for the <code>paste</code> action.
064: */
065: public static final String NAME_PASTE = "paste";
066:
067: /**
068: * Specifies the name for the <code>delete</code> action.
069: */
070: public static final String NAME_DELETE = "delete";
071:
072: /**
073: * Specifies the name for the <code>edit</code> action.
074: */
075: public static final String NAME_EDIT = "edit";
076:
077: /**
078: * Specifies the name for the <code>find</code> action.
079: */
080: public static final String NAME_FIND = "find";
081:
082: /**
083: * Specifies the name for the <code>findAgain</code> action.
084: */
085: public static final String NAME_FINDAGAIN = "findAgain";
086:
087: /**
088: * Specifies the name for the <code>undo</code> action.
089: */
090: public static final String NAME_UNDO = "undo";
091:
092: /**
093: * Specifies the name for the <code>redo</code> action.
094: */
095: public static final String NAME_REDO = "redo";
096:
097: /**
098: * Specifies the name for the <code>selectAll</code> action.
099: */
100: public static final String NAME_SELECTALL = "selectAll";
101:
102: /**
103: * Specifies the name for the <code>clearSelection</code> action.
104: */
105: public static final String NAME_CLEARSELECTION = "clearSelection";
106:
107: /**
108: * Specifies the name for the <code>selectVertices</code> action.
109: */
110: public static final String NAME_SELECTVERTICES = "selectVertices";
111:
112: /**
113: * Specifies the name for the <code>selectEdges</code> action.
114: */
115: public static final String NAME_SELECTEDGES = "selectEdges";
116:
117: /**
118: * Specifies the name for the <code>deselectVertices</code> action.
119: */
120: public static final String NAME_DESELECTVERTICES = "deselectVertices";
121:
122: /**
123: * Specifies the name for the <code>deselectEdges</code> action.
124: */
125: public static final String NAME_DESELECTEDGES = "deselectEdges";
126:
127: /**
128: * Specifies the name for the <code>invertSelection</code> action.
129: */
130: public static final String NAME_INVERTSELECTION = "invertSelection";
131:
132: /**
133: * Fallback action if the focus-owner is not a graph. This is assigned
134: * internally for special action names, namely cut, copy, paste and delete
135: * for text components.
136: */
137: protected Action fallbackAction = null;
138:
139: /**
140: * Constructs a new edit action for the specified name. This constructs the
141: * {@link #fallbackAction} for text components in case of supported actions.
142: *
143: * @param name
144: * The name of the action to be created.
145: */
146: public JGraphpadEditAction(String name) {
147: super (name);
148: if (getName().equals(NAME_COPY))
149: fallbackAction = new DefaultEditorKit.CopyAction();
150: else if (getName().equals(NAME_PASTE))
151: fallbackAction = new DefaultEditorKit.PasteAction();
152: else if (getName().equals(NAME_CUT))
153: fallbackAction = new DefaultEditorKit.CutAction();
154: else if (getName().equals(NAME_DELETE))
155: fallbackAction = new DefaultEditorKit.CutAction();
156: }
157:
158: /**
159: * Executes the action based on the action name.
160: *
161: * @param e
162: * The object that describes the event.
163: */
164: public void actionPerformed(ActionEvent e) {
165: Component component = getPermanentFocusOwner();
166: JGraphpadLibraryPane libraryPane = JGraphpadFileAction
167: .getPermanentFocusOwnerLibraryPane();
168:
169: // Calls the fallback action if one has been defined
170: // and the focus owner is not graph
171: if (!(component instanceof JGraph) && libraryPane == null
172: && fallbackAction != null) {
173: fallbackAction.actionPerformed(e);
174:
175: // Actions that require a focused graph
176: } else if (component instanceof JGraph) {
177: JGraph graph = (JGraph) component;
178:
179: // Creates a new event with the graph as the source.
180: // This is required for the transfer handler to work
181: // correctly when called from outside the graph ui.
182: ActionEvent newEvent = new ActionEvent(graph, e.getID(), e
183: .getActionCommand());
184: if (getName().equals(NAME_COPY))
185: TransferHandler.getCopyAction().actionPerformed(
186: newEvent);
187: else if (getName().equals(NAME_PASTE))
188: TransferHandler.getPasteAction().actionPerformed(
189: newEvent);
190: else if (getName().equals(NAME_CUT))
191: TransferHandler.getCutAction()
192: .actionPerformed(newEvent);
193: else if (getName().equals(NAME_DELETE))
194: graph.getGraphLayoutCache()
195: .remove(
196: graph.getDescendants(graph
197: .getSelectionCells()));
198: else if (getName().equals(NAME_FIND)) {
199: lastSearchPattern = null;
200: lastFoundCell = null;
201: doFindAgain(graph);
202: } else if (getName().equals(NAME_FINDAGAIN))
203: doFindAgain(graph);
204: else if (getName().equals(NAME_EDIT))
205: doEdit(graph);
206: else if (getName().equals(NAME_SELECTALL))
207: doSelect(graph, true, false, false);
208: else if (getName().equals(NAME_CLEARSELECTION))
209: graph.clearSelection();
210: else if (getName().equals(NAME_SELECTVERTICES))
211: doSelect(graph, false, false, false);
212: else if (getName().equals(NAME_SELECTEDGES))
213: doSelect(graph, false, true, false);
214: else if (getName().equals(NAME_DESELECTVERTICES))
215: doSelect(graph, false, false, true);
216: else if (getName().equals(NAME_DESELECTEDGES))
217: doSelect(graph, false, true, true);
218: else if (getName().equals(NAME_INVERTSELECTION))
219: doInvertSelection(graph);
220: } else if (libraryPane != null && !libraryPane.isReadOnly()) {
221: ActionEvent newEvent = new ActionEvent(libraryPane
222: .getBackingGraph(), e.getID(), e.getActionCommand());
223:
224: if (getName().equals(NAME_COPY))
225: TransferHandler.getCopyAction().actionPerformed(
226: newEvent);
227: else if (getName().equals(NAME_PASTE))
228: TransferHandler.getPasteAction().actionPerformed(
229: newEvent);
230: else if (getName().equals(NAME_CUT))
231: TransferHandler.getCutAction()
232: .actionPerformed(newEvent);
233: else if (getName().equals(NAME_DELETE)
234: && JGraphpadDialogs.getSharedInstance()
235: .confirmDialog(libraryPane,
236: getString("DeleteEntry"), true,
237: false))
238: libraryPane.removeEntry();
239: }
240:
241: // Actions that require a focused diagram pane
242: JGraphEditorDiagramPane diagramPane = getPermanentFocusOwnerDiagramPane();
243: if (diagramPane != null) {
244: if (getName().equals(NAME_UNDO))
245: doUndo(diagramPane);
246: else if (getName().equals(NAME_REDO))
247: doRedo(diagramPane);
248: }
249: }
250:
251: /**
252: * Adds or removes the specified cells to/from the selection. If
253: * <code>all</code> is true then <code>edges</code> is ignored.
254: *
255: * @param graph
256: * The graph to perform the operation in.
257: * @param all
258: * Whether all cells should be selected.
259: * @param edges
260: * Whether edges or vertices should be selected.
261: * @param deselect
262: * Whether to remove the cells from the selection.
263: */
264: protected void doSelect(JGraph graph, boolean all, boolean edges,
265: boolean deselect) {
266: Object[] cells = (all) ? graph.getRoots() : graph
267: .getGraphLayoutCache().getCells(false, !edges, false,
268: edges);
269: if (deselect)
270: graph.getSelectionModel().removeSelectionCells(cells);
271: else
272: graph.addSelectionCells(cells);
273: }
274:
275: /**
276: * Inverts the selection in the specified graph by selecting all cells for
277: * which {@link #isParentSelected(JGraph, Object)} returns false.
278: *
279: * @param graph
280: * The graph to perform the operation in.
281: */
282: public void doInvertSelection(JGraph graph) {
283: GraphLayoutCache cache = graph.getGraphLayoutCache();
284: CellView[] views = cache.getAllDescendants(cache.getRoots());
285: List result = new ArrayList(views.length);
286: for (int i = 0; i < views.length; i++)
287: if (views[i].isLeaf()
288: && !graph.getModel().isPort(views[i].getCell())
289: && !isParentSelected(graph, views[i].getCell()))
290: result.add(views[i].getCell());
291: graph.setSelectionCells(result.toArray());
292: }
293:
294: /**
295: * Helper method that returns true if either the cell or one of its parent
296: * is selected in <code>graph</code>.
297: *
298: * @param graph
299: * The graph to check if the cell is selected in.
300: * @param cell
301: * The cell that is to be tested for selection.
302: * @return Returns true if cell or one of its ancestors is selected.
303: */
304: protected boolean isParentSelected(JGraph graph, Object cell) {
305: do {
306: if (graph.isCellSelected(cell))
307: return true;
308: cell = graph.getModel().getParent(cell);
309: } while (cell != null);
310: return false;
311: }
312:
313: /**
314: * Undos the last operation in the specified diagram pane.
315: *
316: * @param diagramPane
317: * The diagram pane to perform the operation in.
318: */
319: protected void doUndo(JGraphEditorDiagramPane diagramPane) {
320: GraphUndoManager manager = diagramPane.getGraphUndoManager();
321: GraphLayoutCache cache = diagramPane.getGraph()
322: .getGraphLayoutCache();
323: if (manager.canUndo(cache))
324: manager.undo(cache);
325: }
326:
327: /**
328: * Undos the last operation in the specified diagram pane.
329: *
330: * @param diagramPane
331: * The diagram pane to perform the operation in.
332: */
333: protected void doRedo(JGraphEditorDiagramPane diagramPane) {
334: GraphUndoManager manager = diagramPane.getGraphUndoManager();
335: GraphLayoutCache cache = diagramPane.getGraph()
336: .getGraphLayoutCache();
337: if (manager.canRedo(cache))
338: manager.redo(cache);
339: }
340:
341: /**
342: * Starts editing the selection cell in the specified graph.
343: *
344: * @param graph
345: * The graph to perform the operation in.
346: */
347: protected void doEdit(JGraph graph) {
348: if (!graph.isSelectionEmpty())
349: graph.startEditingAtCell(graph.getSelectionCell());
350: }
351:
352: /**
353: * Displays a dialog for {@link #lastSearchPattern} if it is null and
354: * performs a search using regular expression matching starting at
355: * {@link #lastFoundCell}. The found cell is selected and scrolled to. If
356: * no cell is found an error message is displayed.
357: *
358: * @param graph
359: * The graph to perform the operation in.
360: */
361: public void doFindAgain(JGraph graph) {
362: if (lastSearchPattern == null) {
363: String exp = JGraphpadDialogs.getSharedInstance()
364: .valueDialog(getString("EnterSearchPattern"), "");
365: if (exp != null && exp.length() > 0) {
366: try {
367: lastSearchPattern = Pattern.compile(".*" + exp
368: + ".*");
369: } catch (PatternSyntaxException e) {
370: JGraphpadDialogs.getSharedInstance().errorDialog(
371: graph, e.getLocalizedMessage());
372: }
373: }
374: }
375: if (lastSearchPattern != null) {
376: Object[] cells = graph.getRoots();
377: boolean active = (lastFoundCell == null);
378: Object match = null;
379: if (cells != null && cells.length > 0) {
380:
381: // Searches for the regular expression in cells
382: for (int i = 0; i < cells.length; i++) {
383: if (active || match == null) {
384: String s = String.valueOf(
385: graph.getModel().getValue(cells[i]))
386: .replaceAll("\n", "");
387: Matcher m = lastSearchPattern.matcher(s);
388: if (m.matches()) {
389: match = cells[i];
390: if (active)
391: break;
392: }
393: }
394: active = active || cells[i] == lastFoundCell;
395: }
396: }
397: lastFoundCell = match;
398: if (lastFoundCell != null) {
399: graph.scrollCellToVisible(lastFoundCell);
400: graph.setSelectionCell(lastFoundCell);
401: } else {
402: JGraphpadDialogs.getSharedInstance().errorDialog(
403: getActiveFrame(),
404: JGraphEditorResources.getString("NotFound",
405: lastSearchPattern.toString()));
406: }
407: }
408: }
409:
410: /**
411: * Returns the fallback action.
412: *
413: * @return Returns the fallbackAction.
414: */
415: public Action getFallbackAction() {
416: return fallbackAction;
417: }
418:
419: /**
420: * Bundle of all actions in this class.
421: */
422: public static class AllActions implements Bundle {
423:
424: /**
425: * Holds the actions.
426: */
427: public JGraphEditorAction actionEdit = new JGraphpadEditAction(
428: NAME_EDIT), actionSelectAll = new JGraphpadEditAction(
429: NAME_SELECTALL),
430: actionClearSelection = new JGraphpadEditAction(
431: NAME_CLEARSELECTION),
432: actionSelectVertices = new JGraphpadEditAction(
433: NAME_SELECTVERTICES),
434: actionSelectEdges = new JGraphpadEditAction(
435: NAME_SELECTEDGES),
436: actionDeselectVertices = new JGraphpadEditAction(
437: NAME_DESELECTVERTICES),
438: actionDeselectEdges = new JGraphpadEditAction(
439: NAME_DESELECTEDGES),
440: actionInvertSelection = new JGraphpadEditAction(
441: NAME_INVERTSELECTION),
442: actionFind = new JGraphpadEditAction(NAME_FIND),
443: actionFindAgain = new JGraphpadEditAction(
444: NAME_FINDAGAIN),
445: actionUndo = new JGraphpadEditAction(NAME_UNDO),
446: actionRedo = new JGraphpadEditAction(NAME_REDO),
447: actionCut = new JGraphpadEditAction(NAME_CUT),
448: actionCopy = new JGraphpadEditAction(NAME_COPY),
449: actionPaste = new JGraphpadEditAction(NAME_PASTE),
450: actionDelete = new JGraphpadEditAction(NAME_DELETE);
451:
452: /*
453: * (non-Javadoc)
454: */
455: public JGraphEditorAction[] getActions() {
456: return new JGraphEditorAction[] { actionEdit,
457: actionSelectAll, actionClearSelection,
458: actionSelectVertices, actionSelectEdges,
459: actionDeselectVertices, actionDeselectEdges,
460: actionInvertSelection, actionFind, actionUndo,
461: actionRedo, actionFindAgain, actionCut, actionCopy,
462: actionPaste, actionDelete };
463: }
464:
465: /*
466: * (non-Javadoc)
467: */
468: public void update() {
469: JGraph graph = getPermanentFocusOwnerGraph();
470: JGraphEditorDiagramPane diagramPane = getPermanentFocusOwnerDiagramPane();
471: if (diagramPane != null) {
472: GraphUndoManager manager = diagramPane
473: .getGraphUndoManager();
474: GraphLayoutCache glc = diagramPane.getGraph()
475: .getGraphLayoutCache();
476: actionUndo.setEnabled(manager.canUndo(glc));
477: actionRedo.setEnabled(manager.canRedo(glc));
478: } else {
479: actionUndo.setEnabled(false);
480: actionRedo.setEnabled(false);
481: }
482: boolean isGraphFocused = graph != null;
483: boolean isGraphEditable = isGraphFocused
484: && graph.isEditable();
485: boolean isSelectionEmpty = !isGraphFocused
486: || graph.isSelectionEmpty();
487: JGraphpadLibraryPane libraryPane = JGraphpadFileAction
488: .getPermanentFocusOwnerLibraryPane();
489: boolean isEntrySelected = libraryPane != null
490: && !libraryPane.isSelectionEmpty();
491:
492: actionEdit.setEnabled(isGraphEditable);
493: actionFind.setEnabled(isGraphFocused);
494: actionFindAgain.setEnabled(isGraphFocused
495: && lastSearchPattern != null);
496: actionCut.setEnabled(!isSelectionEmpty || isEntrySelected);
497: actionCopy.setEnabled(!isSelectionEmpty || isEntrySelected);
498: actionPaste.setEnabled(isGraphFocused
499: || libraryPane != null);
500: actionDelete.setEnabled(!isSelectionEmpty
501: || isEntrySelected);
502: }
503:
504: }
505:
506: };
|