0001: /*
0002: * $Id: JGraphpadFileAction.java,v 1.12 2007/07/27 14:15:59 gaudenz Exp $
0003: * Copyright (c) 2001-2005, Gaudenz Alder
0004: *
0005: * All rights reserved.
0006: *
0007: * See LICENSE file for license details. If you are unable to locate
0008: * this file please contact info (at) jgraph (dot) com.
0009: */
0010: package com.jgraph.pad.action;
0011:
0012: import java.awt.Color;
0013: import java.awt.Component;
0014: import java.awt.KeyboardFocusManager;
0015: import java.awt.event.ActionEvent;
0016: import java.awt.geom.Rectangle2D;
0017: import java.awt.image.BufferedImage;
0018: import java.awt.print.PageFormat;
0019: import java.awt.print.PrinterException;
0020: import java.awt.print.PrinterJob;
0021: import java.io.BufferedReader;
0022: import java.io.ByteArrayOutputStream;
0023: import java.io.DataInputStream;
0024: import java.io.DataOutputStream;
0025: import java.io.File;
0026: import java.io.IOException;
0027: import java.io.InputStream;
0028: import java.io.InputStreamReader;
0029: import java.io.OutputStream;
0030: import java.net.MalformedURLException;
0031: import java.net.URL;
0032: import java.net.URLConnection;
0033: import java.net.UnknownServiceException;
0034: import java.util.ArrayList;
0035: import java.util.Enumeration;
0036: import java.util.HashSet;
0037: import java.util.Hashtable;
0038: import java.util.List;
0039: import java.util.Set;
0040: import java.util.StringTokenizer;
0041:
0042: import javax.imageio.ImageIO;
0043: import javax.swing.Action;
0044: import javax.swing.tree.TreeModel;
0045:
0046: import org.jgraph.JGraph;
0047: import org.jgraph.graph.ConnectionSet;
0048: import org.jgraph.graph.DefaultGraphModel;
0049: import org.jgraph.graph.GraphConstants;
0050: import org.jgraph.graph.GraphLayoutCache;
0051: import org.jgraph.graph.GraphModel;
0052:
0053: import com.jgraph.JGraphEditor;
0054: import com.jgraph.JGraphpad;
0055: import com.jgraph.editor.JGraphEditorAction;
0056: import com.jgraph.editor.JGraphEditorDiagram;
0057: import com.jgraph.editor.JGraphEditorFile;
0058: import com.jgraph.editor.JGraphEditorModel;
0059: import com.jgraph.editor.JGraphEditorResources;
0060: import com.jgraph.editor.factory.JGraphEditorDiagramPane;
0061: import com.jgraph.editor.factory.JGraphEditorNavigator;
0062: import com.jgraph.pad.JGraphpadDiagram;
0063: import com.jgraph.pad.JGraphpadFile;
0064: import com.jgraph.pad.JGraphpadLibrary;
0065: import com.jgraph.pad.dialog.JGraphpadDialogs;
0066: import com.jgraph.pad.factory.JGraphpadLibraryPane;
0067: import com.jgraph.pad.factory.JGraphpadOpenRecentMenu;
0068: import com.jgraph.pad.graph.JGraphpadRichTextValue;
0069: import com.jgraph.pad.util.JGraphpadImageEncoder;
0070:
0071: /**
0072: * Implements all actions of the file menu. The openRecent menu is implemented
0073: * using the {@link JGraphpadOpenRecentMenu} class, and the import/export
0074: * actions are added to the file menu by plugins, so look for their
0075: * implementations there.
0076: */
0077: public class JGraphpadFileAction extends JGraphEditorAction {
0078:
0079: /**
0080: * Defines the text/plain mime-type.
0081: */
0082: public static final String MIME_PLAINTEXT = "text/plain",
0083: MIME_HTML = "text/html";
0084:
0085: /**
0086: * Defines the (double) newline character as used in mime responses.
0087: */
0088: static String NL = "\r\n", NLNL = NL + NL;
0089:
0090: /**
0091: * Shortcut to the shared JGraphpad dialogs.
0092: */
0093: protected static JGraphpadDialogs dlgs = JGraphpadDialogs
0094: .getSharedInstance();
0095:
0096: /**
0097: * Import actions require the action value for this key to be assigned. The
0098: * object under this key will be used to create new vertices for imports.
0099: *
0100: * @see Action#putValue(java.lang.String, java.lang.Object)
0101: */
0102: public static final String KEY_VERTEXPROTOTYPE = "vertexPrototype";
0103:
0104: /**
0105: * Import actions require the action values for this key to be assigned. The
0106: * object under this key will be used to create the edges for imports.
0107: *
0108: * @see Action#putValue(java.lang.String, java.lang.Object)
0109: */
0110: public static final String KEY_EDGEPROTOTYPE = "edgePrototype";
0111:
0112: /**
0113: * Defines the key for the main windows object. This key is used to store a
0114: * reference in the editor settings for the bounds of the application
0115: * window. This class is in charge of storing the bounds in
0116: * {@link JGraphpad#PATH_USERSETTINGS} on program termination.
0117: */
0118: public static final String KEY_MAINWINDOW = JGraphpad.KEY_MAINWINDOW;
0119:
0120: /**
0121: * Defines the key for the recent files user settings. This class is in
0122: * charge of updating the list and storing it in
0123: * {@link JGraphpad#PATH_USERSETTINGS} on program termination.
0124: */
0125: public static final String KEY_RECENTFILES = "recentFiles";
0126:
0127: /**
0128: * Defines the maximum number of recent files to store under the
0129: * {@link #KEY_RECENTFILES} in the user settings file. Default is 6.
0130: */
0131: public static int MAX_RECENTFILES = 6;
0132:
0133: /**
0134: * Specifies the name for the <code>newDocument</code> action.
0135: */
0136: public static final String NAME_NEWDOCUMENT = "newDocument";
0137:
0138: /**
0139: * Specifies the name for the <code>newDiagram</code> action.
0140: */
0141: public static final String NAME_NEWDIAGRAM = "newDiagram";
0142:
0143: /**
0144: * Specifies the name for the <code>renameDiagram</code> action.
0145: */
0146: public static final String NAME_RENAMEDIAGRAM = "renameDiagram";
0147:
0148: /**
0149: * Specifies the name for the <code>removeDiagram</code> action.
0150: */
0151: public static final String NAME_REMOVEDIAGRAM = "removeDiagram";
0152:
0153: /**
0154: * Specifies the name for the <code>newLibrary</code> action.
0155: */
0156: public static final String NAME_NEWLIBRARY = "newLibrary";
0157:
0158: /**
0159: * Specifies the name for the <code>open</code> action.
0160: */
0161: public static final String NAME_OPEN = "open";
0162:
0163: /**
0164: * Specifies the name for the <code>download</code> action.
0165: */
0166: public static final String NAME_DOWNLOAD = "download";
0167:
0168: /**
0169: * Specifies the name for the <code>close</code> action.
0170: */
0171: public static final String NAME_CLOSE = "close";
0172:
0173: /**
0174: * Specifies the name for the <code>closeAll</code> action.
0175: */
0176: public static final String NAME_CLOSEALL = "closeAll";
0177:
0178: /**
0179: * Specifies the name for the <code>save</code> action.
0180: */
0181: public static final String NAME_SAVE = "save";
0182:
0183: /**
0184: * Specifies the name for the <code>saveAs</code> action.
0185: */
0186: public static final String NAME_SAVEAS = "saveAs";
0187:
0188: /**
0189: * Specifies the name for the <code>uploadAs</code> action.
0190: */
0191: public static final String NAME_UPLOADAS = "uploadAs";
0192:
0193: /**
0194: * Specifies the name for the <code>saveAll</code> action.
0195: */
0196: public static final String NAME_SAVEALL = "saveAll";
0197:
0198: /**
0199: * Specifies the name for the <code>saveImage</code> action.
0200: */
0201: public static final String NAME_SAVEIMAGE = "saveImage";
0202:
0203: /**
0204: * Specifies the name for the <code>importCSV</code> action.
0205: */
0206: public static final String NAME_IMPORTCSV = "importCSV";
0207:
0208: /**
0209: * Specifies the name for the <code>pageSetup</code> action.
0210: */
0211: public static final String NAME_PAGESETUP = "pageSetup";
0212:
0213: /**
0214: * Specifies the name for the <code>print</code> action.
0215: */
0216: public static final String NAME_PRINT = "print";
0217:
0218: /**
0219: * Specifies the name for the <code>exit</code> action.
0220: */
0221: public static final String NAME_EXIT = "exit";
0222:
0223: /**
0224: * References the enclosing editor.
0225: */
0226: protected JGraphEditor editor;
0227:
0228: /**
0229: * Holds the last directory for file operations.
0230: */
0231: protected File lastDirectory = null;
0232:
0233: /**
0234: * Constructs a new file action for the specified name and editor.
0235: *
0236: * @param name
0237: * The name of the action to be created.
0238: * @param editor
0239: * The enclosing editor for the action.
0240: */
0241: public JGraphpadFileAction(String name, JGraphEditor editor) {
0242: super (name);
0243: this .editor = editor;
0244: }
0245:
0246: /**
0247: * Executes the action based on the action name.
0248: *
0249: * @param event
0250: * The object that describes the event.
0251: */
0252: public void actionPerformed(ActionEvent event) {
0253: JGraph graph = getPermanentFocusOwnerGraph();
0254: try {
0255: if (getName().equals(NAME_NEWDOCUMENT))
0256: doNewDocument();
0257: else if (getName().equals(NAME_NEWLIBRARY))
0258: doNewLibrary();
0259: else if (getName().equals(NAME_SAVEALL))
0260: doSaveAll();
0261: else if (getName().equals(NAME_OPEN))
0262: doOpenFile(dlgs.editorFileDialog(getActiveFrame(),
0263: getString("OpenJGraphpadFile"), null, true,
0264: lastDirectory));
0265: else if (getName().equals(NAME_DOWNLOAD))
0266: doOpenFile(dlgs.valueDialog(getString("EnterURL")));
0267: else if (getName().equals(NAME_CLOSEALL))
0268: doCloseAll();
0269: else if (getName().equals(NAME_EXIT))
0270: doExit();
0271:
0272: // Actions that require a focused graph
0273: if (graph != null) {
0274: if (getName().equals(NAME_IMPORTCSV))
0275: doImportCSV(
0276: graph.getGraphLayoutCache(),
0277: dlgs
0278: .fileDialog(
0279: getPermanentFocusOwnerOrParent(),
0280: getString("OpenCSVFile"),
0281: true,
0282: ".csv",
0283: getString("CommaSeparatedFileDescription"),
0284: lastDirectory));
0285: }
0286:
0287: // Actions that require a focused file
0288: JGraphEditorFile file = getPermanentFocusOwnerFile();
0289:
0290: if (file != null && JGraphpad.INNER_LIBRARIES
0291: && file.getParent() instanceof JGraphEditorFile) {
0292: file = (JGraphEditorFile) file.getParent();
0293: }
0294:
0295: if (file != null) {
0296: if (getName().equals(NAME_NEWDIAGRAM))
0297: doNewDiagram(file);
0298: else if (getName().equals(NAME_SAVE))
0299: doSaveFile(file, false, false);
0300: else if (getName().equals(NAME_SAVEAS))
0301: doSaveFile(file, true, false);
0302: else if (getName().equals(NAME_UPLOADAS))
0303: doSaveFile(file, true, true);
0304: else if (getName().equals(NAME_CLOSE))
0305: doCloseFile(file, true);
0306: }
0307:
0308: // Actions that require a focused diagram
0309: JGraphEditorDiagram diagram = getPermanentFocusOwnerDiagram();
0310: if (diagram != null) {
0311: if (getName().equals(NAME_REMOVEDIAGRAM))
0312: doRemoveDiagram(diagram);
0313: if (getName().equals(NAME_RENAMEDIAGRAM))
0314: doRenameDiagram(diagram);
0315: }
0316:
0317: // Actions that require a focused diagram pane
0318: JGraphEditorDiagramPane diagramPane = getPermanentFocusOwnerDiagramPane();
0319: if (diagramPane != null) {
0320: if (getName().equals(NAME_SAVEIMAGE))
0321: doSaveImage(diagramPane, 5, dlgs.imageFileDialog(
0322: diagramPane, getString("SaveImage"), false,
0323: lastDirectory));
0324: else if (getName().equals(NAME_PRINT))
0325: doPrintDiagramPane(diagramPane);
0326: else if (getName().equals(NAME_PAGESETUP))
0327: doPageSetup(diagramPane);
0328:
0329: }
0330: } catch (JGraphpadDialogs.CancelException e) {
0331: // ignore
0332: } catch (Exception e) {
0333: e.printStackTrace();
0334: dlgs.errorDialog(getPermanentFocusOwner(), e.getMessage());
0335: }
0336: }
0337:
0338: /**
0339: * Saves all open files using
0340: * {@link #doSaveFile(JGraphEditorFile, boolean, boolean)} showing file
0341: * dialogs for files whose filename has not been assigned.
0342: *
0343: * @throws IOException
0344: * If there was an error saving the files.
0345: */
0346: protected void doSaveAll() throws IOException {
0347: Enumeration files = editor.getModel().roots();
0348: while (files.hasMoreElements()) {
0349: Object file = files.nextElement();
0350: if (file instanceof JGraphEditorFile)
0351: doSaveFile((JGraphEditorFile) file, false, false);
0352: }
0353: }
0354:
0355: /**
0356: * Saves the specified file displaying a filename dialog if the filename is
0357: * not set or if <code>forceFilenameDialog</code> is true. If
0358: * <code>urlDialog</code> is true, then the dialog will ask for an URL
0359: * instead of a local file.<br>
0360: * This implementation does the following additional checks:
0361: * <ul>
0362: * <li>If the file already exists it asks whether it should be overwritten.</li>
0363: * <li>It rejects to assign filenames of files which are already open.</li>
0364: * <li>If filename is an URL it tries to upload the data to that URL.</li>
0365: * </ul>
0366: * Furthermore, this implementation updates the isNew and modified state of
0367: * the file if it has successfully been saved.
0368: *
0369: * @param file
0370: * The file to be saved.
0371: * @param forceFilenameDialog
0372: * Whether to display a dialog regardless of the filename.
0373: * @param urlDialog
0374: * Whether to display an URL dialog to specifiy the filename.
0375: * @throws IOException
0376: * If the file cannot be saved.
0377: *
0378: * @see JGraphEditorModel#getOutputStream(String)
0379: * @see #postPlain(URL, String, OutputStream)
0380: */
0381: protected void doSaveFile(JGraphEditorFile file,
0382: boolean forceFilenameDialog, boolean urlDialog)
0383: throws IOException {
0384: JGraphEditorModel model = editor.getModel();
0385:
0386: // Asks for a filename, using the file's current location
0387: // as the current directory on the dialog.
0388: String filename = file.getFilename();
0389: if (file.isNew() || forceFilenameDialog) {
0390: if (urlDialog) {
0391: String defaultValue = JGraphEditor.isURL(file
0392: .getFilename()) ? file.getFilename()
0393: : "http://";
0394: filename = dlgs.valueDialog(getString("EnterURL"),
0395: defaultValue);
0396: } else {
0397: // Strip extension as it will be added by the dialog
0398: // based on the chooseable filter.
0399: if (filename.toLowerCase().endsWith(".xml.gz"))
0400: filename = filename.substring(0,
0401: filename.length() - 7);
0402: else if (filename.toLowerCase().endsWith(".xml"))
0403: filename = filename.substring(0,
0404: filename.length() - 4);
0405:
0406: filename = dlgs.editorFileDialog(
0407: getPermanentFocusOwnerOrParent(),
0408: getString("SaveJGraphpadFile"), filename,
0409: false, lastDirectory);
0410: }
0411: }
0412:
0413: // Stores the file under the given filename or URL
0414: if (filename != null) {
0415: Object check = model.getFileByFilename(filename);
0416: if (check != null && check != file) {
0417: dlgs.errorDialog(getPermanentFocusOwner(),
0418: getString("FileAlreadyOpen"));
0419: } else {
0420: OutputStream out = editor.getModel().getOutputStream(
0421: filename);
0422: model.writeObject(file, out);
0423: out.flush();
0424: out.close();
0425: if (JGraphEditor.isURL(filename)) {
0426: URL url = new URL(filename);
0427: postPlain(url, url.getFile(), out);
0428: } else
0429: lastDirectory = new File(filename).getParentFile();
0430: if (file.getFilename() == null
0431: || !file.getFilename().equals(filename))
0432: model.setFilename(file, filename);
0433: file.setNew(false);
0434: model.setModified(file, false);
0435: editor.getSettings().pushListEntryProperty(
0436: JGraphpad.NAME_USERSETTINGS, KEY_RECENTFILES,
0437: filename, MAX_RECENTFILES);
0438: }
0439: }
0440: }
0441:
0442: /**
0443: * Saves the specified graph as an image using <code>inset</code> as the
0444: * size of the empty border around the image. This implementation displays a
0445: * dialog to ask for transparency-support if the chosen fileformat supports
0446: * it (eg. PNG, GIF). If filename is an URL it tries to upload the data to
0447: * that URL.
0448: *
0449: * @param diagramPane
0450: * The diagram pane to save to the image for.
0451: * @param inset
0452: * The size of the empty border around the image.
0453: * @param filename
0454: * The filename to save or upload the image to.
0455: * @throws IOException
0456: * If the image cannot be saved.
0457: * @throws IllegalArgumentException
0458: * If the graph contains no cells.
0459: *
0460: * @see JGraph#getImage(Color, int)
0461: * @see ImageIO#write(java.awt.image.RenderedImage, java.lang.String,
0462: * java.io.OutputStream)
0463: * @see JGraphEditorModel#getOutputStream(String)
0464: * @see #post(URL, String, String, OutputStream)
0465: */
0466: protected void doSaveImage(JGraphEditorDiagramPane diagramPane,
0467: int inset, String filename) throws IOException {
0468: if (filename != null) {
0469: JGraph graph = diagramPane.getGraph();
0470: if (graph != null && graph.getModel().getRootCount() > 0) {
0471: String ext = filename.substring(
0472: filename.lastIndexOf(".") + 1).toLowerCase();
0473: Color bg = null;
0474: if (!(ext.equals("png") || ext.equals("gif"))
0475: || !dlgs.confirmDialog(JGraphEditorNavigator
0476: .getParentScrollPane(graph),
0477: getString("MakeTransparent"), true,
0478: false))
0479: bg = graph.getBackground();
0480: BufferedImage img = diagramPane.getImage(bg, inset);
0481: OutputStream out = editor.getModel().getOutputStream(
0482: filename);
0483: if (ext.equals("gif"))
0484: JGraphpadImageEncoder.writeGIF(img, out);
0485: else
0486: ImageIO.write(img, ext, out);
0487: out.flush();
0488: out.close();
0489: if (JGraphEditor.isURL(filename)) {
0490: URL url = new URL(filename);
0491: if (ext.equals("jpg"))
0492: ext = "jpeg"; // image/jpeg
0493: post(url, url.getFile(), "image/" + ext, out);
0494: } else
0495: lastDirectory = new File(filename).getParentFile();
0496: } else {
0497: throw new IllegalArgumentException(
0498: getString("DiagramIsEmpty"));
0499: }
0500: }
0501: }
0502:
0503: /**
0504: * Saves the specified byte array to the specified file.
0505: *
0506: * @param filename
0507: * The filename of the file to be written.
0508: * @param data
0509: * The array of bytes to write to the file.
0510: */
0511: public void doSave(String filename, byte[] data) throws Exception {
0512: if (filename != null) {
0513: OutputStream out = editor.getModel().getOutputStream(
0514: filename);
0515: out.write(data);
0516: out.close();
0517: if (JGraphEditor.isURL(filename)) {
0518: URL url = new URL(filename);
0519: post(url, url.getFile(), MIME_HTML, out);
0520: } else
0521: lastDirectory = new File(filename).getParentFile();
0522: }
0523: }
0524:
0525: /**
0526: * Displays a file- or URL-dialog and loads the selected file or URL into
0527: * the specified diagram as a comma separated value file (CSV) using
0528: * {@link #importCSVFile(GraphLayoutCache, InputStream, String, Object, Object, String)}.
0529: *
0530: * @param cache
0531: * The graph layout cache to import into.
0532: * @param filename
0533: * The filename to import from.
0534: * @throws IOException
0535: * If the file cannot be read.
0536: *
0537: * @see JGraphEditorModel#getInputStream(String)
0538: */
0539: protected void doImportCSV(GraphLayoutCache cache, String filename)
0540: throws IOException {
0541: if (filename != null) {
0542: InputStream in = editor.getModel().getInputStream(filename);
0543: Object vertexPrototype = getValue(KEY_VERTEXPROTOTYPE);
0544: Object edgePrototype = getValue(KEY_EDGEPROTOTYPE);
0545: importCSVFile(cache, in, ",", vertexPrototype,
0546: edgePrototype, "");
0547: in.close();
0548: lastDirectory = new File(filename).getParentFile();
0549: }
0550: }
0551:
0552: /**
0553: * Displays a system print dialog and prints the specified diagram pane.
0554: *
0555: * @param diagramPane
0556: * The diagram pane to be printed.
0557: * @throws PrinterException
0558: * If the document can not be printed.
0559: */
0560: protected void doPrintDiagramPane(
0561: JGraphEditorDiagramPane diagramPane)
0562: throws PrinterException {
0563: PrinterJob printJob = PrinterJob.getPrinterJob();
0564: printJob.setPrintable(diagramPane);
0565: if (printJob.printDialog())
0566: printJob.print();
0567: }
0568:
0569: /**
0570: * Displays the system page format dialog and updates the pageformat on the
0571: * specified diagram pane.
0572: *
0573: * @param diagramPane
0574: * The diagram pane to set the page format.
0575: */
0576: protected void doPageSetup(JGraphEditorDiagramPane diagramPane) {
0577: PageFormat format = PrinterJob.getPrinterJob().pageDialog(
0578: diagramPane.getPageFormat());
0579: if (format != null)
0580: diagramPane.setPageFormat(format);
0581: }
0582:
0583: /**
0584: * Opens the specified filename using
0585: * {@link JGraphEditorModel#addFile(String)} if a file for the specified
0586: * name is not already open.
0587: *
0588: * @param filename
0589: * The name of the file to be opened.
0590: * @throws IOException
0591: * If there was an error reading the file.
0592: * @throws MalformedURLException
0593: * If the filename is an URL but the URL is invalid.
0594: */
0595: protected void doOpenFile(String filename)
0596: throws MalformedURLException, IOException {
0597: if (filename != null) {
0598: if (editor.getModel().getFileByFilename(filename) == null) {
0599: editor.getModel().addFile(filename);
0600: editor.getSettings().pushListEntryProperty(
0601: JGraphpad.NAME_USERSETTINGS, KEY_RECENTFILES,
0602: filename, MAX_RECENTFILES);
0603: if (!JGraphEditor.isURL(filename))
0604: lastDirectory = new File(filename).getParentFile();
0605: } else
0606: dlgs.informationDialog(getPermanentFocusOwner(),
0607: getString("FileAlreadyOpen"));
0608: }
0609: }
0610:
0611: /**
0612: * Closes all open files using {@link #doCloseAll()}, saves the user
0613: * settings and terminates the program using {@link System#exit(int)}. This
0614: * implementation allows the {@link JGraphpad#PATH_USERSETTINGS} to be a
0615: * URL.
0616: *
0617: * @throws IOException
0618: * If there was an error saving unsaved changes.
0619: */
0620: protected void doExit() throws IOException {
0621: Enumeration files = editor.getModel().roots();
0622: while (files.hasMoreElements()) {
0623: Object obj = files.nextElement();
0624: if (obj instanceof JGraphEditorFile)
0625: doCloseFile((JGraphEditorFile) obj, false);
0626: }
0627: editor.exit(1);
0628: }
0629:
0630: /**
0631: * Closes all open files using
0632: * {@link #doCloseFile(JGraphEditorFile, boolean)} giving the user a chance
0633: * to save unsaved changes before closing each file.
0634: *
0635: * @throws IOException
0636: * If there was an error saving unsaved changes.
0637: */
0638: protected void doCloseAll() throws IOException {
0639: // Closes all files in a two step process to
0640: // avoid concurrent modifications and simplify
0641: // things. First gets all "roots" from the model.
0642: JGraphEditorModel model = editor.getModel();
0643: Object[] files = new Object[model
0644: .getChildCount(model.getRoot())];
0645: for (int i = 0; i < files.length; i++)
0646: files[i] = model.getChild(model.getRoot(), i);
0647:
0648: // Then closes all files, thereby removing them
0649: // from the childset of the root.
0650: for (int i = 0; i < files.length; i++)
0651: if (files[i] instanceof JGraphEditorFile)
0652: doCloseFile((JGraphEditorFile) files[i], true);
0653: }
0654:
0655: /**
0656: * Closes the specified file by removing it from the parent. This
0657: * implementation displays a dialog if there are unsaved changes and calls
0658: * {@link #doSaveFile(JGraphEditorFile, boolean, boolean)} if the user
0659: * chooses to save the changes.
0660: *
0661: * @param file
0662: * The file to be closed.
0663: * @param remove
0664: * Whether the file should be removed from the model.
0665: * @throws IOException
0666: * If the unsaved changes can not be saved.
0667: */
0668: protected void doCloseFile(JGraphEditorFile file, boolean remove)
0669: throws IOException {
0670: if (file.isModified()
0671: && dlgs.confirmDialog(getPermanentFocusOwnerOrParent(),
0672: JGraphEditorResources.getString("SaveChanges",
0673: file), true, true)) {
0674: doSaveFile(file, false, false);
0675: }
0676: if (remove)
0677: editor.getModel().removeNodeFromParent(file);
0678: }
0679:
0680: /**
0681: * Inserts a new empty document into the document model. The name is
0682: * assigned automatically using the Document resource string and the number
0683: * returned by {@link #getFileCount(boolean)}.
0684: */
0685: protected void doNewDocument() {
0686: JGraphEditorModel model = editor.getModel();
0687: int number = getFileCount(false) + 1;
0688: JGraphpadFile file = new JGraphpadFile(getString("Document")
0689: + number);
0690: model.addRoot(file);
0691: doNewDiagram(file);
0692: }
0693:
0694: /**
0695: * Inserts a new empty diagram into the specified file. The name is assigned
0696: * automatically using the Diagram resource string and the number returned
0697: * by {@link TreeModel#getChildCount(java.lang.Object)} for the file.
0698: *
0699: * @param file
0700: * The file to add the diagram to.
0701: */
0702: protected void doNewDiagram(JGraphEditorFile file) {
0703: if (!isLibrary(file)) {
0704: JGraphEditorModel model = editor.getModel();
0705: int number = model.getChildCount(file) + 1;
0706: JGraphpadDiagram newDiagram = new JGraphpadDiagram(
0707: getString("Diagram") + number);
0708: model.addChild(newDiagram, (JGraphpadFile) file);
0709: }
0710: }
0711:
0712: /**
0713: * Inserts a new library into the given file. The name is assigned
0714: * automatically using the Library resource string and the number returned
0715: * by {@link #getFileCount(boolean)}.
0716: */
0717: protected void doNewLibrary() {
0718: JGraphEditorModel model = editor.getModel();
0719:
0720: JGraphEditorFile file = null;
0721: if (JGraphpad.INNER_LIBRARIES) {
0722: file = getPermanentFocusOwnerFile();
0723: if (file instanceof JGraphpadLibrary) {
0724: file = (JGraphEditorFile) file.getParent();
0725: }
0726: }
0727:
0728: int number = getFileCount(file, true) + 1;
0729: JGraphpadLibrary library = new JGraphpadLibrary(
0730: getString("Library") + number);
0731:
0732: if (file != null) {
0733: model.addChild(library, file);
0734: } else {
0735: model.addRoot(library);
0736: }
0737: }
0738:
0739: /**
0740: * Removes the specified diagram from the parent file if the parent file
0741: * contains at least one diagram after the removal. Otherwise the diagram is
0742: * not removed. This implementation displays a confirmation dialog before
0743: * actually removing the diagram.
0744: *
0745: * @param diagram
0746: * The diagram to be removed from its parent file.
0747: *
0748: * @see JGraphpadDialogs#confirmDialog(Component, String, boolean, boolean)
0749: */
0750: protected void doRemoveDiagram(JGraphEditorDiagram diagram) {
0751: JGraphEditorFile file = JGraphEditorModel
0752: .getParentFile(diagram);
0753:
0754: // A file needs at least one diagram to be able to add new
0755: // diagrams to it. This is because in order to find the
0756: // "focused" file the system tries to first find the focused
0757: // diagram pane, and goes to the file from there. Hence, if
0758: // there is no diagram pane the focused file cannot be found.
0759: if (file.getChildCount() > 1
0760: && dlgs.confirmDialog(getPermanentFocusOwnerOrParent(),
0761: JGraphEditorResources.getString(
0762: "RemoveDiagram", diagram), true, false)) {
0763: editor.getModel().removeNodeFromParent(diagram);
0764: }
0765: }
0766:
0767: /**
0768: * Displays a dialog to enter the new name for the specified diagram and
0769: * updates the name of the diagram using
0770: * {@link JGraphEditorModel#setName(JGraphEditorDiagram, String)}.
0771: *
0772: * @param diagram
0773: * The diagram to be renamed.
0774: *
0775: * @see JGraphpadDialogs#valueDialog(String, String)
0776: */
0777: protected void doRenameDiagram(JGraphEditorDiagram diagram) {
0778: String newValue = dlgs.valueDialog(getString("EnterName"),
0779: diagram.getName());
0780: if (newValue != null)
0781: editor.getModel().setName(diagram, newValue);
0782: }
0783:
0784: /**
0785: * Uses {@link #isLibrary(JGraphEditorFile)} to find the number of roots
0786: * that are instances of {@link JGraphEditorFile}. If
0787: * <code>countLibraries</code> is true, then the number of libraries is
0788: * returned. <br>
0789: * The following always holds:
0790: * <code>getFileCount(false) + getFileCount(true) ==
0791: * getChildCount(getRoot())</code>
0792: *
0793: * @param countLibraries
0794: * Whether libraries or non-libraries should be counted.
0795: * @return Returns the number of libraries or non-libraries.
0796: */
0797: protected int getFileCount(boolean countLibraries) {
0798: return getFileCount(null, countLibraries);
0799: }
0800:
0801: /**
0802: * Uses {@link #isLibrary(JGraphEditorFile)} to find the number of roots
0803: * that are instances of {@link JGraphEditorFile}. If
0804: * <code>countLibraries</code> is true, then the number of libraries is
0805: * returned. <br>
0806: * The following always holds:
0807: * <code>getFileCount(false) + getFileCount(true) ==
0808: * getChildCount(getRoot())</code>
0809: *
0810: * @param countLibraries
0811: * Whether libraries or non-libraries should be counted.
0812: * @return Returns the number of libraries or non-libraries.
0813: */
0814: protected int getFileCount(Object root, boolean countLibraries) {
0815: JGraphEditorModel model = editor.getModel();
0816:
0817: if (root == null) {
0818: root = model.getRoot();
0819: }
0820:
0821: int result = 0;
0822: int childCount = model.getChildCount(root);
0823: for (int i = 0; i < childCount; i++) {
0824: Object child = model.getChild(root, i);
0825: if (child instanceof JGraphEditorFile) {
0826: JGraphEditorFile file = (JGraphEditorFile) child;
0827: if (countLibraries == isLibrary(file))
0828: result++;
0829: }
0830: }
0831: return result;
0832: }
0833:
0834: /**
0835: * Returns true if the specified file is a library. This implementation
0836: * returns true if file is an instance of {@link JGraphpadLibrary}.
0837: *
0838: * @param file
0839: * The file to be checked.
0840: * @return Returns true if <code>file</code> is a library.
0841: */
0842: protected boolean isLibrary(JGraphEditorFile file) {
0843: return file instanceof JGraphpadLibrary;
0844: }
0845:
0846: /**
0847: * Returns the diagram for the permanent focus owner diagram pane.
0848: */
0849: public static JGraphEditorFile getPermanentFocusOwnerFile() {
0850: JGraphEditorDiagram diagram = getPermanentFocusOwnerDiagram();
0851: if (diagram != null)
0852: return JGraphEditorModel.getParentFile(diagram);
0853: JGraphpadLibraryPane libraryPane = getPermanentFocusOwnerLibraryPane();
0854: if (libraryPane != null)
0855: return libraryPane.getLibrary();
0856: return null;
0857: }
0858:
0859: /**
0860: * Reads the specified input stream as a comma-delimeted file, using the
0861: * following format: a,b[,c] where a and b are vertices and c is the label
0862: * of the edge to be inserted between a and b. For example, to create a
0863: * triangle, use:<br>
0864: * a,b,ab<br>
0865: * b,c,bc<br>
0866: * c,a,ca<br>
0867: *
0868: * @param cache
0869: * The layout cache to import the file into.
0870: * @param fstream
0871: * The stream to import the cells from.
0872: * @param delim
0873: * The delimeter to parse the tokens.
0874: * @param vertexPrototype
0875: * The prototype to create new vertices with.
0876: * @param edgePrototype
0877: * The prototype to create new edges with.
0878: * @param defaultEdgeLabel
0879: * The default label to use for edges if none is specified.
0880: * @throws IOException
0881: */
0882: public static void importCSVFile(GraphLayoutCache cache,
0883: InputStream fstream, String delim, Object vertexPrototype,
0884: Object edgePrototype, String defaultEdgeLabel)
0885: throws IOException {
0886: GraphModel model = cache.getModel();
0887:
0888: // Convert our input stream to a
0889: // DataInputStream
0890: DataInputStream in = new DataInputStream(fstream);
0891:
0892: // Continue to read lines while
0893: // there are still some left to read
0894: // Map from keys to vertices
0895: Hashtable map = new Hashtable();
0896:
0897: // Adds the existing vertices from the graph layout cache
0898: // into the vertex map for reference by the keys in the file.
0899: Object[] items = DefaultGraphModel.getAll(model);
0900: if (items != null) {
0901: for (int i = 0; i < items.length; i++)
0902: if (items[i] != null && items[i].toString() != null
0903: && !model.isPort(items[i])
0904: && !model.isEdge(items[i])) {
0905: map.put(items[i].toString(), items[i]);
0906: }
0907: }
0908:
0909: // Geometry of the import matrix
0910: int cols = 8;
0911: int offset = 40;
0912: int w = 100;
0913: int h = 100;
0914:
0915: // Vertices and Edges to insert
0916: Hashtable adj = new Hashtable();
0917: // Make space for a minimum of 4 bytes per entry
0918: List insert = new ArrayList(in.available() / 4);
0919: ConnectionSet cs = new ConnectionSet();
0920: while (in.available() != 0) {
0921: String s = in.readLine();
0922: StringTokenizer st = new StringTokenizer(s, delim);
0923: if (st.hasMoreTokens()) {
0924: String srckey = st.nextToken().trim();
0925:
0926: // Get or create source vertex
0927: Object source = getCellForKey(model, vertexPrototype,
0928: map, srckey, cols, w, h, offset, false);
0929: if (!model.contains(source) && !insert.contains(source))
0930: insert.add(source);
0931: if (st.hasMoreTokens()) {
0932: String tgtkey = st.nextToken().trim();
0933:
0934: // Get or create source vertex
0935: Object target = getCellForKey(model,
0936: vertexPrototype, map, tgtkey, cols, w, h,
0937: offset, false);
0938: if (!model.contains(target)
0939: && !insert.contains(target))
0940: insert.add(target);
0941:
0942: // Create and insert Edge
0943: Set neighbours = (Set) adj.get(srckey);
0944: if (neighbours == null) {
0945: neighbours = new HashSet();
0946: adj.put(srckey, neighbours);
0947: }
0948: String label = (st.hasMoreTokens()) ? st
0949: .nextToken().trim() : defaultEdgeLabel;
0950: if (!(neighbours.contains(tgtkey))) {
0951: Object edge = DefaultGraphModel.cloneCell(
0952: model, edgePrototype);
0953: model.valueForCellChanged(edge, label);
0954: Object sourcePort = model.getChild(source, 0);
0955: Object targetPort = model.getChild(target, 0);
0956: if (sourcePort != null && targetPort != null) {
0957: cs.connect(edge, sourcePort, targetPort);
0958: insert.add(edge);
0959: neighbours.add(tgtkey);
0960: }
0961: }
0962: }
0963: }
0964: }
0965: in.close();
0966: cache.insert(insert.toArray(), null, cs, null, null);
0967: }
0968:
0969: /**
0970: * Utility method to return the cell stored under key in the specified map
0971: * or create the cell using the specified prototype and model and put it
0972: * into the map under key. The cells will be positioned into a matrix with
0973: * <code>cols</code> columns and entries of size (w,h).
0974: *
0975: * @param model
0976: * The model to use for cloning the prototype.
0977: * @param prototype
0978: * The prototype to use for creating new cells.
0979: * @param map
0980: * The map to check whether the cell exists for key.
0981: * @param key
0982: * The key to return the cell for.
0983: * @param cols
0984: * The number of columns for the matrix.
0985: * @param w
0986: * The width of the entries.
0987: * @param h
0988: * The height of the entries.
0989: * @param offset
0990: * The offset from the top left.
0991: * @param image
0992: * Whether to insert image or text cells.
0993: * @return Returns the cell for the specified key.
0994: */
0995: public static Object getCellForKey(GraphModel model,
0996: Object prototype, Hashtable map, String key, int cols,
0997: int w, int h, int offset, boolean image) {
0998: Object cell = map.get(key);
0999: if (cell == null) {
1000: cell = DefaultGraphModel.cloneCell(model, prototype);
1001: if (image)
1002: model.valueForCellChanged(cell, key);
1003: else
1004: model.valueForCellChanged(cell,
1005: new JGraphpadRichTextValue(key));
1006:
1007: GraphConstants.setResize(model.getAttributes(cell), true);
1008:
1009: // Set initial Location
1010: int col = map.size() / cols;
1011: int row = map.size() % cols;
1012: Rectangle2D bounds = new Rectangle2D.Double(row * w
1013: + offset, col * h + offset, 10, 10);
1014: GraphConstants.setBounds(model.getAttributes(cell), bounds);
1015: map.put(key, cell);
1016: }
1017: return cell;
1018: }
1019:
1020: /**
1021: * Posts the data to the specified url using <code>path</code> to specify
1022: * the filename in the mime response using for type {@link #MIME_PLAINTEXT}.
1023: *
1024: * @param url
1025: * The url to post the mime response to.
1026: * @param path
1027: * The filename to use in the mime response.
1028: * @param data
1029: * The binary data to send with the mime response.
1030: * @return Returns true if the data was successfuly posted.
1031: * @throws IOException
1032: */
1033: public static boolean postPlain(URL url, String path,
1034: OutputStream data) throws IOException {
1035: return post(url, path, data.toString(), MIME_PLAINTEXT);
1036: }
1037:
1038: /**
1039: * Posts the data to the specified url using <code>path</code> to specify
1040: * the filename in the mime response for the specified mime type.
1041: *
1042: * @param url
1043: * The url to post the mime response to.
1044: * @param path
1045: * The filename to use in the mime response.
1046: * @param mime
1047: * The mime type to use for the response.
1048: * @param data
1049: * The binary data to send with the mime response.
1050: * @return Returns true if the data was successfuly posted.
1051: * @throws IOException
1052: */
1053: public static boolean post(URL url, String path, String mime,
1054: OutputStream data) throws IOException {
1055: return post(url, path, mime, convert(data, mime));
1056: }
1057:
1058: /**
1059: * Converts the specified data stream into a string assuming the data stream
1060: * is of the specified mime type. This performs a byte to char conversion on
1061: * all mime types other than {@link #MIME_PLAINTEXT}.
1062: *
1063: * @param data
1064: * The data to be converted.
1065: * @param mime
1066: * The mime type to assume for the data.
1067: * @return Returns a string representation of the data in the stream.
1068: */
1069: public static String convert(OutputStream data, String mime) {
1070: String text = null;
1071: if (data instanceof ByteArrayOutputStream) {
1072: byte[] aByte = ((ByteArrayOutputStream) data).toByteArray();
1073: int size = aByte.length;
1074: char[] aChar = new char[size];
1075: for (int i = 0; i < size; i++) {
1076: aChar[i] = (char) aByte[i];
1077: }
1078: text = String.valueOf(aChar, 0, aChar.length);
1079: } else
1080: text = data.toString();
1081: return text;
1082: }
1083:
1084: /**
1085: * Posts the data to the specified url using <code>path</code> to specify
1086: * the filename in the mime response for the specified mime type.
1087: *
1088: * @param url
1089: * The url to post the mime response to.
1090: * @param path
1091: * The filename to use in the mime response.
1092: * @param mime
1093: * The mime type to use for the response.
1094: * @param data
1095: * The binary data to send with the mime response.
1096: * @return Returns true if the data was successfuly posted.
1097: * @throws IOException
1098: */
1099: public static boolean post(URL url, String path, String mime,
1100: String data) throws IOException {
1101: String sep = "89692781418184";
1102: while (data.indexOf(sep) != -1)
1103: sep += "x";
1104: String message = makeMimeForm("", mime, path, data, "", sep);
1105:
1106: // Ask for parameters
1107: URLConnection connection = url.openConnection();
1108: connection.setAllowUserInteraction(false);
1109: connection.setDoOutput(true);
1110: connection.setUseCaches(false);
1111: connection.setRequestProperty("Content-type",
1112: "multipart/form-data; boundary=" + sep);
1113: connection.setRequestProperty("Content-length", Integer
1114: .toString(message.length()));
1115:
1116: String replyString = null;
1117: try {
1118: DataOutputStream out = new DataOutputStream(connection
1119: .getOutputStream());
1120: out.writeBytes(message);
1121: out.close();
1122: try {
1123: BufferedReader in = new BufferedReader(
1124: new InputStreamReader(connection
1125: .getInputStream()));
1126: String reply = null;
1127: while ((reply = in.readLine()) != null) {
1128: if (reply.startsWith("ERROR ")) {
1129: replyString = reply
1130: .substring("ERROR ".length());
1131: }
1132: }
1133: in.close();
1134: } catch (IOException ioe) {
1135: replyString = ioe.toString();
1136: }
1137: } catch (UnknownServiceException use) {
1138: replyString = use.getMessage();
1139: }
1140: if (replyString != null) {
1141: return false;
1142: } else {
1143: return true;
1144: }
1145: }
1146:
1147: /**
1148: * Returns a mime form using the specified parameters.
1149: */
1150: public static String makeMimeForm(String fileName, String type,
1151: String path, String content, String comment, String sep) {
1152: String binary = "";
1153: if (type.startsWith("image/") || type.startsWith("application")) {
1154: binary = "Content-Transfer-Encoding: binary" + NL;
1155: }
1156: String mime_sep = NL + "--" + sep + NL;
1157: return "--"
1158: + sep
1159: + "\r\n"
1160: + "Content-Disposition: form-data; name=\"filename\""
1161: + NLNL
1162: + fileName
1163: + mime_sep
1164: + "Content-Disposition: form-data; name=\"noredirect\""
1165: + NLNL
1166: + 1
1167: + mime_sep
1168: + "Content-Disposition: form-data; name=\"filepath\"; "
1169: + "filename=\""
1170: + path
1171: + "\""
1172: + NL
1173: + "Content-Type: "
1174: + type
1175: + NL
1176: + binary
1177: + NL
1178: + content
1179: + mime_sep
1180: + "Content-Disposition: form-data; name=\"filecomment\""
1181: + NLNL + comment + NL + "--" + sep + "--" + NL;
1182: }
1183:
1184: /**
1185: * Returns the permanent focus owner library pane.
1186: */
1187: public static JGraphpadLibraryPane getPermanentFocusOwnerLibraryPane() {
1188: Component component = KeyboardFocusManager
1189: .getCurrentKeyboardFocusManager()
1190: .getPermanentFocusOwner();
1191: return JGraphpadLibraryPane.getParentLibraryPane(component);
1192: }
1193:
1194: /**
1195: * Bundle of all actions in this class.
1196: */
1197: public static class AllActions implements Bundle {
1198:
1199: /**
1200: * Holds the actions. All actions require an editor reference and are
1201: * therefore created at construction time.
1202: */
1203: public JGraphEditorAction actionNewDocument, actionNewDiagram,
1204: actionNewLibrary, actionOpen, actionDownload,
1205: actionSave, actionSaveAs, actionUploadAs,
1206: actionSaveAll, actionRenameDiagram,
1207: actionRemoveDiagram, actionClose, actionCloseAll,
1208: actionSaveImage, actionImportCSV, actionPrint,
1209: actionPageSetup, actionExit;
1210:
1211: /**
1212: * Constructs the action bundle for the specified editor.
1213: *
1214: * @param editor
1215: * The enclosing editor for this bundle.
1216: */
1217: public AllActions(JGraphEditor editor) {
1218: Object vertexPrototype = editor.getSettings().getObject(
1219: JGraphpad.KEY_VERTEXPROTOTYPE);
1220: Object edgePrototype = editor.getSettings().getObject(
1221: JGraphpad.KEY_EDGEPROTOTYPE);
1222: actionNewDocument = new JGraphpadFileAction(
1223: NAME_NEWDOCUMENT, editor);
1224: actionNewDiagram = new JGraphpadFileAction(NAME_NEWDIAGRAM,
1225: editor);
1226: actionNewLibrary = new JGraphpadFileAction(NAME_NEWLIBRARY,
1227: editor);
1228: actionOpen = new JGraphpadFileAction(NAME_OPEN, editor);
1229: actionDownload = new JGraphpadFileAction(NAME_DOWNLOAD,
1230: editor);
1231: actionSave = new JGraphpadFileAction(NAME_SAVE, editor);
1232: actionSaveAs = new JGraphpadFileAction(NAME_SAVEAS, editor);
1233: actionUploadAs = new JGraphpadFileAction(NAME_UPLOADAS,
1234: editor);
1235: actionSaveAll = new JGraphpadFileAction(NAME_SAVEALL,
1236: editor);
1237: actionRemoveDiagram = new JGraphpadFileAction(
1238: NAME_REMOVEDIAGRAM, editor);
1239: actionRenameDiagram = new JGraphpadFileAction(
1240: NAME_RENAMEDIAGRAM, editor);
1241: actionPrint = new JGraphpadFileAction(NAME_PRINT, editor);
1242: actionPageSetup = new JGraphpadFileAction(NAME_PAGESETUP,
1243: editor);
1244: actionClose = new JGraphpadFileAction(NAME_CLOSE, editor);
1245: actionCloseAll = new JGraphpadFileAction(NAME_CLOSEALL,
1246: editor);
1247: actionSaveImage = new JGraphpadFileAction(NAME_SAVEIMAGE,
1248: editor);
1249: actionExit = new JGraphpadFileAction(NAME_EXIT, editor);
1250: actionImportCSV = new JGraphpadFileAction(NAME_IMPORTCSV,
1251: editor);
1252: actionImportCSV.putValue(KEY_VERTEXPROTOTYPE,
1253: vertexPrototype);
1254: actionImportCSV.putValue(KEY_EDGEPROTOTYPE, edgePrototype);
1255: }
1256:
1257: /*
1258: * (non-Javadoc)
1259: */
1260: public JGraphEditorAction[] getActions() {
1261: return new JGraphEditorAction[] { actionNewDocument,
1262: actionNewDiagram, actionNewLibrary, actionOpen,
1263: actionDownload, actionSave, actionSaveAs,
1264: actionUploadAs, actionSaveAll, actionRemoveDiagram,
1265: actionRenameDiagram, actionPrint, actionPageSetup,
1266: actionClose, actionCloseAll, actionSaveImage,
1267: actionImportCSV, actionExit };
1268: }
1269:
1270: /*
1271: * (non-Javadoc)
1272: */
1273: public void update() {
1274: JGraphEditorDiagram diagram = getPermanentFocusOwnerDiagram();
1275: JGraphEditorFile file = getPermanentFocusOwnerFile();
1276: boolean isDiagramFocused = (diagram != null);
1277: boolean isFileModified = (file != null && (file
1278: .isModified() || file.isNew()));
1279: actionOpen.setEnabled(true);
1280: actionRemoveDiagram.setEnabled(isDiagramFocused);
1281: actionRenameDiagram.setEnabled(isDiagramFocused);
1282: actionNewDiagram.setEnabled(isDiagramFocused);
1283: actionImportCSV.setEnabled(isDiagramFocused);
1284: actionSaveImage.setEnabled(isDiagramFocused);
1285: actionClose.setEnabled(file != null);
1286: actionCloseAll.setEnabled(file != null);
1287: actionSave.setEnabled(isFileModified);
1288: actionSaveAs.setEnabled(file != null);
1289: actionSaveAll.setEnabled(file != null);
1290: actionUploadAs.setEnabled(file != null);
1291: }
1292:
1293: }
1294:
1295: };
|