001:/*
002: * MiniEdit.java
003: *
004: * Created on January 25, 2004, 7:55 PM
005: */
006:
007:package org.netbeans.actions.examples;
008:
009:import java.awt.Component;
010:import java.awt.Dimension;
011:import java.awt.FileDialog;
012:import java.awt.FlowLayout;
013:import java.awt.KeyboardFocusManager;
014:import java.awt.event.FocusListener;
015:import java.awt.event.KeyEvent;
016:import java.awt.event.MouseListener;
017:import java.io.File;
018:import java.io.FileInputStream;
019:import java.io.FileWriter;
020:import java.io.Writer;
021:import java.net.URL;
022:import java.util.ArrayList;
023:import java.util.Arrays;
024:import java.util.Enumeration;
025:import java.util.HashMap;
026:import java.util.HashSet;
027:import java.util.Iterator;
028:import java.util.Map;
029:import java.util.ResourceBundle;
030:import java.util.Set;
031:import javax.swing.BorderFactory;
032:import javax.swing.Icon;
033:import javax.swing.JComponent;
034:import javax.swing.JOptionPane;
035:import javax.swing.JPopupMenu;
036:import javax.swing.JScrollPane;
037:import javax.swing.JTextPane;
038:import javax.swing.JToolBar;
039:import javax.swing.event.CaretListener;
040:import javax.swing.event.DocumentListener;
041:import javax.swing.event.UndoableEditListener;
042:import javax.swing.text.Document;
043:import javax.swing.tree.DefaultTreeModel;
044:import javax.swing.tree.TreeModel;
045:import javax.swing.tree.TreeNode;
046:import javax.swing.tree.TreePath;
047:import javax.swing.undo.UndoManager;
048:import org.netbeans.actions.api.ContextProvider;
049:import org.netbeans.actions.api.Engine;
050:import org.netbeans.actions.spi.ProxyContextProvider;
051:import org.netbeans.actions.simple.SimpleEngine;
052:import org.netbeans.actions.spi.ContextProviderSupport;
053:import org.openide.util.Utilities;
054:import org.openide.util.enum.AlterEnumeration;
055:import org.openide.util.enum.ArrayEnumeration;
056:
057:/**
058: *
059: * @author tim
060: */
061:public class MiniEdit extends javax.swing.JFrame implements ContextProvider, FocusListener, MouseListener {
062: private Engine engine;
063: /** Creates new form MiniEdit */
064: public MiniEdit() {
065: initComponents();
066:
067: //Hopefully avoid the floppy drive on Windows by setting index to 1.
068: int rootIdx = Utilities.isWindows() ? 1 : 0;
069: //Aim our file tree at some filesystem root
070: fileTree.setModel(new FileTreeModel(
071: File.listRoots()[rootIdx]));
072:
073: //Get a URL for our actions definition URL file
074: URL url = MiniEdit.class.getClassLoader().getResource(
075: "org/netbeans/actions/examples/actiondefs.xml");
076:
077: //Get a resource bundle with localized names for our actions
078: ResourceBundle bundle = ResourceBundle.getBundle(
079: "org/netbeans/actions/examples/actions");
080:
081: //Pass the XML file and the resource bundle to create an instance of
082: //Engine which can create menus, toolbars, keymaps, etc., from it
083: engine = SimpleEngine.createEngine(url, bundle);
084: engine.setContextProvider(this );
085:
086: //Install the menu bar the engine supplies
087: setJMenuBar(engine.createMenuBar());
088:
089: //Get an array of toolbars to install
090: JToolBar[] toolbars = engine.createToolbars();
091:
092: //Install the toolbars
093: for (int i=0; i < toolbars.length; i++) {
094: toolbars[i].setFloatable(false);
095: toolbarPanel.add(toolbars[i]);
096: }
097:
098: //Very simply set up keyboard mappings for the entire app window from
099: //those defined in the engine's XML file
100: getRootPane().setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW,
101: engine.createInputMap(getRootPane()));
102: getRootPane().setActionMap(engine.createActionMap());
103:
104:
105: //Add focus listeners that will advise the engine to check enablement
106: //of actions
107: toolbarPanel.setLayout (new FlowLayout());
108: fileTree.setLargeModel(true);
109: fileTree.addFocusListener(this );
110: toolbarPanel.addFocusListener(this );
111: //Misc ui stuff
112: fileTree.setAutoscrolls(true);
113: fileTree.addMouseListener(this );
114: pack();
115: }
116:
117: /** This method is called from within the constructor to
118: * initialize the form.
119: * WARNING: Do NOT modify this code. The content of this method is
120: * always regenerated by the Form Editor.
121: */
122: private void initComponents() {//GEN-BEGIN:initComponents
123: jSplitPane1 = new javax.swing.JSplitPane();
124: documentTabs = new javax.swing.JTabbedPane();
125: jScrollPane1 = new javax.swing.JScrollPane();
126: fileTree = new javax.swing.JTree();
127: toolbarPanel = new javax.swing.JPanel();
128:
129: setTitle("MiniEdit");
130: addWindowListener(new java.awt.event.WindowAdapter() {
131: public void windowClosing(java.awt.event.WindowEvent evt) {
132: exitForm(evt);
133: }
134: });
135:
136: documentTabs.setMinimumSize(new java.awt.Dimension(400, 200));
137: jSplitPane1.setRightComponent(documentTabs);
138:
139: jScrollPane1.setMinimumSize(new java.awt.Dimension(200, 200));
140: fileTree.addKeyListener(new java.awt.event.KeyAdapter() {
141: public void keyReleased(java.awt.event.KeyEvent evt) {
142: fileTreeKeyReleased(evt);
143: }
144: });
145: fileTree.addMouseListener(new java.awt.event.MouseAdapter() {
146: public void mouseClicked(java.awt.event.MouseEvent evt) {
147: fileTreeMouseClicked(evt);
148: }
149: });
150: fileTree.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
151: public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
152: fileTreeValueChanged(evt);
153: }
154: });
155:
156: jScrollPane1.setViewportView(fileTree);
157:
158: jSplitPane1.setLeftComponent(jScrollPane1);
159:
160: getContentPane().add(jSplitPane1, java.awt.BorderLayout.CENTER);
161:
162: getContentPane().add(toolbarPanel, java.awt.BorderLayout.NORTH);
163:
164: }//GEN-END:initComponents
165:
166: private void fileTreeValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_fileTreeValueChanged
167: engine.recommendUpdate();
168:
169: }//GEN-LAST:event_fileTreeValueChanged
170:
171: private void fileTreeKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_fileTreeKeyReleased
172: if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
173: open();
174: }
175: }//GEN-LAST:event_fileTreeKeyReleased
176:
177: private void fileTreeMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_fileTreeMouseClicked
178: if (evt.getClickCount() == 2 && !evt.isPopupTrigger()) {
179: open();
180: }
181: }//GEN-LAST:event_fileTreeMouseClicked
182:
183: /** Method listed as the invoker for the open action in the XML file */
184: public void open() {
185: Enumeration files = getSelectedFilesEnumeration();
186: while (files.hasMoreElements()) {
187: openFile((File) files.nextElement());
188: }
189: }
190:
191: public void delete() {
192: Enumeration files = getSelectedFilesEnumeration();
193: StringBuffer sb = new StringBuffer("Really delete ");
194: while (files.hasMoreElements()) {
195: sb.append(files.nextElement().toString());
196: if (files.hasMoreElements()) {
197: sb.append (", ");
198: }
199: }
200: sb.append ("?");
201: int opt = JOptionPane.showConfirmDialog(this , sb.toString(), "Delete", JOptionPane.YES_NO_OPTION);
202: if (opt == JOptionPane.YES_OPTION) {
203: files = getSelectedFilesEnumeration();
204: while (files.hasMoreElements()) {
205: File f = (File) files.nextElement();
206: f.delete();
207: }
208: }
209:
210: }
211:
212: /** Utility method to open a file */
213: private void openFile(File file) {
214: Doc doc = (Doc) openDocs.get(file);
215: if (doc == null) {
216: doc = new Doc(file);
217: JTextPane pane = doc.getTextPane();
218: if (pane != null) {
219: //otherwise there was an exception
220: pane.addFocusListener(this );
221: synchronized (getTreeLock()) {
222: Component c = documentTabs.add(new JScrollPane(pane));
223: openDocs.put(file, doc);
224: documentTabs.setTitleAt(documentTabs.getTabCount()-1, doc.getTitle());
225: documentTabs.setToolTipTextAt(documentTabs.getTabCount()-1, file.toString());
226: pane.requestFocus();
227: documentTabs.setSelectedComponent(c);
228: }
229: }
230: } else {
231: doc.getTextPane().requestFocus();
232: }
233: }
234:
235: /** Method invoked by the declarative action "new" */
236: public void newFile() {
237: openFile (new NewFile());
238: }
239:
240: /** A map of the open documents */
241: private HashMap openDocs = new HashMap();
242:
243: /** Wraps a file, its associated editor component and undo support for it */
244: public class Doc implements UndoableEditListener, DocumentListener, ContextProvider, CaretListener {
245: private JTextPane pane = null;
246: private File file;
247: private boolean modified = false;
248: public Doc (File file) {
249: this .file = file;
250: }
251:
252: public JTextPane getTextPane() {
253: try {
254: if (pane == null) {
255: pane = new JTextPane();
256: if (!isNewFile()) {
257: pane.read(new FileInputStream(file), "foo");
258: }
259: pane.getDocument().addUndoableEditListener(this );
260: pane.getDocument().addDocumentListener(this );
261: pane.addCaretListener(this );
262: pane.putClientProperty("doc", this );
263: pane.addMouseListener(MiniEdit.this );
264: }
265: return pane;
266: } catch (Exception e) {
267: e.printStackTrace();
268: return null;
269: }
270: }
271:
272: /** Invoker for the revert action */
273: public void revert() {
274: if (pane == null) {
275: return;
276: }
277: try {
278: pane.read (new FileInputStream(file), "foo");
279: setModified (false);
280: if (um != null) {
281: um.discardAllEdits();
282: }
283: } catch (Exception e) {
284: e.printStackTrace();
285: }
286: }
287:
288: /** Supplies a title including a * if the file is modified */
289: public String getTitle () {
290: String result = file.getName();
291: if (isModified()) {
292: result = result + "*";
293: }
294: return result;
295: }
296:
297: /** Check if the current file is modified */
298: public boolean isModified() {
299: boolean result = modified;
300: if (result & um != null) {
301: if (!um.canUndo()) {
302: return false;
303: }
304: }
305: return result;
306: }
307:
308: /** Invoker for the saveas action */
309: public void saveAs() {
310: Doc doc = getSelectedDoc();
311: if (doc == null) {
312: throw new NullPointerException ("no doc");
313: }
314:
315:
316: FileDialog fd = new FileDialog (MiniEdit.this , "Save as");
317: fd.setMode(fd.SAVE);
318: fd.show();
319: if (fd.getFile() != null) {
320: File nue = new File (fd.getDirectory() + fd.getFile());
321: try {
322: boolean success = nue.createNewFile();
323: if (success) {
324: FileWriter w = new FileWriter (nue);
325: doc.getTextPane().write(w);
326: file = nue;
327: documentTabs.setTitleAt(documentTabs.getSelectedIndex(), nue.getName());
328: documentTabs.setToolTipTextAt(documentTabs.getSelectedIndex(), nue.toString());
329: }
330: } catch (Exception e) {
331: e.printStackTrace();
332: }
333: }
334: }
335:
336: public boolean isNewFile () {
337: return file instanceof NewFile;
338: }
339:
340: /** Invoked method by the save action */
341: public void save() {
342: if (isNewFile()) {
343: saveAs();
344: return;
345: }
346: try {
347: Writer w = new FileWriter (file);
348: pane.write(w);
349: modified = false;
350: documentTabs.setTitleAt(documentTabs.getSelectedIndex(), getTitle());
351: } catch (Exception e) {
352: e.printStackTrace();
353: }
354: }
355:
356: UndoManager um = null;
357: private UndoManager getUndoSupport() {
358: if (um == null) {
359: um = new UndoManager();
360: um.setLimit(50);
361: }
362: return um;
363: }
364:
365: public boolean canUndo() {
366: if (um != null) {
367: return um.canUndo();
368: }
369: return false;
370: }
371:
372: public boolean canRedo() {
373: if (um != null) {
374: return um.canRedo();
375: }
376: return false;
377: }
378:
379: public boolean canUndoOrRedo() {
380: if (um != null) {
381: return um.canUndoOrRedo();
382: }
383: return false;
384: }
385:
386: public void undo() {
387: if (um != null) {
388: try {
389: um.undo();
390: } catch (Exception e) {
391: e.printStackTrace();
392: }
393: }
394: }
395:
396: private void redo() {
397: if (um != null) {
398: try {
399: um.undo();
400: } catch (Exception e) {
401: e.printStackTrace();
402: }
403: }
404: }
405:
406: private void setModified (boolean val) {
407: if (modified != val) {
408: modified = val;
409: if (pane != null) {
410: documentTabs.setTitleAt(documentTabs.getSelectedIndex(), getTitle());
411: }
412: engine.recommendUpdate();
413: }
414: }
415:
416: public void changedUpdate(javax.swing.event.DocumentEvent e) {
417: setModified(true);
418: }
419:
420: public void insertUpdate(javax.swing.event.DocumentEvent e) {
421: setModified(true);
422: }
423:
424: public void removeUpdate(javax.swing.event.DocumentEvent e) {
425: setModified(true);
426: }
427:
428: public void undoableEditHappened(javax.swing.event.UndoableEditEvent e) {
429: e.getEdit();
430: getUndoSupport().addEdit(e.getEdit());
431: }
432:
433: public Map getContext() {
434: HashMap result = new HashMap();
435: result.put (getClass(), this );
436: if (isModified()) {
437: result.put ("save", Boolean.TRUE);
438: result.put ("modified", Boolean.TRUE);
439: }
440: if (isNewFile()) {
441: result.put ("isNewFile", Boolean.TRUE);
442: }
443: if (canUndo()) {
444: result.put ("undo", Boolean.TRUE);
445: }
446: if (canRedo()) {
447: result.put ("redo", Boolean.TRUE);
448: }
449: result.put ("doc", Boolean.TRUE);
450:
451: Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
452: if (documentTabs.isAncestorOf(owner)) {
453: if (canPaste()) {
454: result.put ("clipboardcontents", clipboard);
455: }
456: if (hasSelection()) {
457: result.put ("selection", Boolean.TRUE);
458: }
459: }
460: return result;
461: }
462:
463: public boolean hadSel = false;
464: public void caretUpdate(javax.swing.event.CaretEvent e) {
465: boolean hasSel = e.getDot() != e.getMark();
466: if (hasSel != hadSel) {
467: engine.recommendUpdate();
468: }
469: hadSel = hasSel;
470: }
471:
472: }
473:
474: private Doc getSelectedDoc() {
475: JComponent comp = (JComponent) documentTabs.getSelectedComponent();
476: Doc result = null;
477: if (comp != null) {
478: JTextPane jtp = (JTextPane) ((JScrollPane) comp).getViewport().getView();
479:
480: result = (Doc) jtp.getClientProperty("doc");
481: }
482:
483: return result;
484: }
485:
486: private Doc[] getModifiedDocs() {
487: ArrayList result = new ArrayList();
488: Iterator i = openDocs.keySet().iterator();
489: while (i.hasNext()) {
490: Doc doc = (Doc) openDocs.get(i.next());
491: if (doc.isModified()) {
492: result.add(doc);
493: }
494: }
495: Doc[] docs = new Doc[result.size()];
496: docs = (Doc[]) result.toArray(docs);
497: return docs;
498: }
499:
500: public void saveAll() {
501: Doc[] docs = getModifiedDocs();
502: for (int i=0; i < docs.length; i++) {
503: docs[i].save();
504: }
505: }
506:
507: private Enumeration getSelectedFilesEnumeration() {
508: TreePath paths[] = fileTree.getSelectionPaths();
509: if (paths != null) {
510: Enumeration files = new PathFileEnumeration (
511: new ArrayEnumeration(fileTree.getSelectionPaths()));
512: return files;
513: } else {
514: return new ArrayEnumeration(new TreePath[0]);
515: }
516: }
517:
518: private class PathFileEnumeration extends AlterEnumeration {
519: public PathFileEnumeration (Enumeration en) {
520: super (en);
521: }
522:
523: protected Object alter(Object o) {
524: TreePath path = (TreePath) o;
525: File f = (File) path.getLastPathComponent();
526: return f;
527: }
528: }
529:
530: private String clipboard = null;
531: public void cut() {
532: if (doCopy()) {
533: Doc doc = getSelectedDoc();
534: JTextPane pane = doc.getTextPane();
535: int start = pane.getSelectionStart();
536: int end = pane.getSelectionEnd();
537: if (start == end) {
538: throw new IllegalStateException ("no selection");
539: }
540: try {
541: pane.getDocument().remove(start, end-start);
542: } catch (Exception e) {
543: e.printStackTrace();
544: }
545: } else {
546: throw new NullPointerException ("no document");
547: }
548: }
549:
550: private boolean hasSelection() {
551: Doc doc = getSelectedDoc();
552: if (doc == null) {
553: return false;
554: }
555: JTextPane pane = doc.getTextPane();
556: return pane.getSelectionStart() != pane.getSelectionEnd();
557: }
558:
559: public boolean canPaste() {
560: return clipboard != null;
561: }
562:
563: public void paste() {
564: Doc doc = getSelectedDoc();
565: if (doc != null) {
566: JTextPane pane = doc.getTextPane();
567: int start = pane.getSelectionStart();
568: if (start == -1) {
569: throw new IllegalStateException("no selection");
570: }
571: try {
572: int end = pane.getSelectionEnd();
573: if (start != end) {
574: pane.getDocument().remove(start, end-start);
575: }
576: pane.getDocument().insertString(start, clipboard, null);
577: } catch (Exception e) {
578: e.printStackTrace();
579: }
580: } else {
581: throw new NullPointerException ("no document");
582: }
583: }
584:
585: public void copy() {
586: if (!doCopy()) {
587: throw new NullPointerException ("no document");
588: }
589: }
590:
591: private boolean doCopy() {
592: Doc doc = getSelectedDoc();
593: if (doc != null) {
594: JTextPane pane = doc.getTextPane();
595: clipboard = pane.getSelectedText();
596: return true;
597: } else {
598: return false;
599: }
600: }
601:
602: /** Exit the Application */
603: private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
604: System.exit(0);
605: }//GEN-LAST:event_exitForm
606:
607: /**
608: * @param args the command line arguments
609: */
610: public static void main(String args[]) {
611: System.setProperty ("apple.laf.useScreenMenuBar", "true");
612: new MiniEdit().show();
613: }
614:
615: public void exit() {
616: System.exit(0);
617: }
618:
619: public Map getContext() {
620: HashMap result = new HashMap();
621: result.put (getClass(), this );
622: Doc[] mods = getModifiedDocs();
623: if (mods.length > 1) {
624: result.put ("anymodified", Boolean.TRUE);
625: }
626:
627: Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
628: result.put ("activecomponent", fileTree.hasFocus() ? "filetree" : "editor");
629:
630: if (owner == fileTree || fileTree.isAncestorOf(owner)) {
631: Enumeration en = getSelectedFilesEnumeration();
632: if (en != null && en.hasMoreElements()) {
633: boolean fileSelected = false;
634: boolean dirSelected = false;
635: boolean allFilesOpen = true;
636: while (en.hasMoreElements()) {
637: File f = (File) en.nextElement();
638: fileSelected |= !f.isDirectory();
639: dirSelected |= f.isDirectory();
640: allFilesOpen &= openDocs.keySet().contains(f);
641: }
642: if (fileSelected) {
643: result.put("fileSelected", Boolean.TRUE);
644: }
645: if (!allFilesOpen && fileSelected) {
646: result.put ("closedFileSelected", Boolean.TRUE);
647: }
648: if (dirSelected) {
649: result.put("dirSelected", Boolean.TRUE);
650: }
651: }
652: }
653:
654: Doc doc = getSelectedDoc();
655: if (doc != null) {
656: result.put ("close", Boolean.TRUE);
657: result.putAll(doc.getContext());
658: }
659: return result;
660: }
661:
662: public void close() {
663: Doc doc = getSelectedDoc();
664: if (doc != null) {
665: if (doc.isModified()) {
666: /*
667: int opt = JOptionPane.showOptionDialog(this,
668: "Document is modified. Save?", "Unsaved changes", null,
669: //JOptionPane.YES_NO_CANCEL_OPTION,
670: JOptionPane.QUESTION_MESSAGE, (Icon) null,
671: new String[] {"OK", "Cancel"},
672: "Close");
673: */
674: int opt = JOptionPane.showConfirmDialog(this ,
675: "Document is modified. Save?", "Unsaved changes",
676: JOptionPane.YES_NO_CANCEL_OPTION,
677: JOptionPane.QUESTION_MESSAGE);
678: if (opt == JOptionPane.CANCEL_OPTION) {
679: return;
680: }
681: if (opt == JOptionPane.YES_OPTION) {
682: doc.save();
683: }
684: }
685: documentTabs.remove(doc.getTextPane().getParent().getParent());
686: openDocs.remove(doc);
687: }
688: }
689:
690: public void focusGained(java.awt.event.FocusEvent e) {
691: System.err.println("Telling engine to update");
692: engine.recommendUpdate();
693: }
694:
695: public void focusLost(java.awt.event.FocusEvent e) {
696: }
697:
698: public Map getContribution() {
699: return getContext();
700: }
701:
702: public void mouseClicked(java.awt.event.MouseEvent e) {
703: }
704:
705: public void mouseEntered(java.awt.event.MouseEvent e) {
706: }
707:
708: public void mouseExited(java.awt.event.MouseEvent e) {
709: }
710:
711: public void mousePressed(java.awt.event.MouseEvent e) {
712: if (e.isPopupTrigger()) {
713: JPopupMenu menu = engine.createPopupMenu();
714: menu.show((Component) e.getSource(), e.getX(), e.getY());
715: }
716: }
717:
718: public void mouseReleased(java.awt.event.MouseEvent e) {
719: if (e.isPopupTrigger()) {
720: JPopupMenu menu = engine.createPopupMenu();
721: menu.show((Component) e.getSource(), e.getX(), e.getY());
722: }
723: }
724:
725: // Variables declaration - do not modify//GEN-BEGIN:variables
726: private javax.swing.JTabbedPane documentTabs;
727: private javax.swing.JTree fileTree;
728: private javax.swing.JScrollPane jScrollPane1;
729: private javax.swing.JSplitPane jSplitPane1;
730: private javax.swing.JPanel toolbarPanel;
731: // End of variables declaration//GEN-END:variables
732:
733: private static class FileTreeModel implements TreeModel {
734: private File root;
735: public FileTreeModel (File root) {
736: this .root = root;
737: }
738:
739: public void addTreeModelListener(javax.swing.event.TreeModelListener l) {
740: //do nothing
741: }
742:
743: public Object getChild(Object parent, int index) {
744: File f = (File) parent;
745: return f.listFiles()[index];
746: }
747:
748: public int getChildCount(Object parent) {
749: File f = (File) parent;
750: if (!f.isDirectory()) {
751: return 0;
752: } else {
753: return f.list().length;
754: }
755: }
756:
757: public int getIndexOfChild(Object parent, Object child) {
758: File par = (File) parent;
759: File ch = (File) child;
760: return Arrays.asList(par.listFiles()).indexOf(ch);
761: }
762:
763: public Object getRoot() {
764: return root;
765: }
766:
767: public boolean isLeaf(Object node) {
768: File f = (File) node;
769: return !f.isDirectory();
770: }
771:
772: public void removeTreeModelListener(javax.swing.event.TreeModelListener l) {
773: //do nothing
774: }
775:
776: public void valueForPathChanged(javax.swing.tree.TreePath path, Object newValue) {
777: //do nothing
778: }
779: }
780:
781: private int newCount = 0;
782: private class NewFile extends File {
783: int index;
784: public NewFile () {
785: super ("Untitled " + (newCount++));
786: index = newCount;
787: }
788:
789: public String getName() {
790: return "Untitled " + index;
791: }
792: }
793:}
|