• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 *   - Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 *
11 *   - Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 *
15 *   - Neither the name of Oracle nor the names of its
16 *     contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 * This source code is provided to illustrate the usage of a given feature
34 * or technique and has been deliberately simplified. Additional steps
35 * required for a production-quality application, such as security checks,
36 * input validation and proper error handling, might not be present in
37 * this sample code.
38 */
39
40/*
41 * This script creates a simple Notepad-like interface, which
42 * serves as a simple script editor, runner.
43 *
44 * File dependency:
45 *
46 *    gui.js -> for basic GUI functions
47 */
48
49/*
50 * globalThis is used for actionHelpGlobals() and showFrame().
51 */
52var globalThis = this;
53
54/*
55 * JavaImporter helps in avoiding pollution of JavaScript
56 * global namespace. We can import multiple Java packages
57 * with this and use the JavaImporter object with "with"
58 * statement.
59 */
60var guiPkgs = new JavaImporter(java.awt, java.awt.event,
61                               javax.swing, javax.swing.undo,
62                               javax.swing.event, javax.swing.text);
63
64// main entry point of the scriptpad application
65var main = function() {
66    function createEditor() {
67        var c = new guiPkgs.JTextArea();
68        c.setDragEnabled(true);
69        c.setFont(new guiPkgs.Font("monospaced", guiPkgs.Font.PLAIN, 12));
70        return c;
71    }
72
73    /*const*/ var titleSuffix = "- Scriptpad";
74    /*const*/ var defaultTitle = "Untitled" + titleSuffix;
75
76    // Scriptpad's main frame
77    var frame;
78    // Scriptpad's main editor
79    var editor;
80
81    // To track the current file name
82    var curFileName = null;
83
84    // To track whether the current document
85    // has been modified or not
86    var docChanged = false;
87
88    // check and alert user for unsaved
89    // but modified document
90    function checkDocChanged() {
91        if (docChanged) {
92            // ignore zero-content untitled document
93            if (curFileName == null &&
94                editor.document.length == 0) {
95                return;
96            }
97
98            if (confirm("Do you want to save the changes?",
99                        "The document has changed")) {
100                actionSave();
101            }
102        }
103    }
104
105    // set a document listener to track
106    // whether that is modified or not
107    function setDocListener() {
108        var doc = editor.getDocument();
109        docChanged = false;
110        doc.addDocumentListener( new guiPkgs.DocumentListener() {
111                                     equals: function(o) {
112                                         return this === o; },
113                                     toString: function() {
114                                         return "doc listener"; },
115                                     changeUpdate: function() {
116                                         docChanged = true; },
117                                     insertUpdate: function() {
118                                         docChanged = true; },
119                                     removeUpdate: function() {
120                                         docChanged = true; }
121                                 });
122    }
123
124    // menu action functions
125
126    // "File" menu
127
128    // create a "new" document
129    function actionNew() {
130        checkDocChanged();
131        curFileName = null;
132        editor.setDocument(new guiPkgs.PlainDocument());
133        setDocListener();
134        frame.setTitle(defaultTitle);
135        editor.revalidate();
136    }
137
138    // open an existing file
139    function actionOpen() {
140        checkDocChanged();
141        var f = fileDialog();
142        if (f == null) {
143            return;
144        }
145
146        if (f.isFile() && f.canRead()) {
147            frame.setTitle(f.getName() + titleSuffix);
148            editor.setDocument(new guiPkgs.PlainDocument());
149            var progress = new guiPkgs.JProgressBar();
150            progress.setMinimum(0);
151            progress.setMaximum(f.length());
152            var doc = editor.getDocument();
153            var inp = new java.io.FileReader(f);
154            var buff = java.lang.reflect.Array.newInstance(
155                java.lang.Character.TYPE, 4096);
156            var nch;
157            while ((nch = inp.read(buff, 0, buff.length)) != -1) {
158                doc.insertString(doc.getLength(),
159                                 new java.lang.String(buff, 0, nch), null);
160                progress.setValue(progress.getValue() + nch);
161            }
162            inp.close();
163            curFileName = f.getAbsolutePath();
164            setDocListener();
165        } else {
166            error("Can not open file: " + f,
167                  "Error opening file: " + f);
168        }
169    }
170
171    // open script from a URL
172    function actionOpenURL() {
173        checkDocChanged();
174        var url = prompt("Address:");
175        if (url == null) {
176            return;
177        }
178
179        try {
180            var u = new java.net.URL(url);
181            editor.setDocument(new guiPkgs.PlainDocument());
182            frame.setTitle(url + titleSuffix);
183            var progress = new guiPkgs.JProgressBar();
184            progress.setMinimum(0);
185            progress.setIndeterminate(true);
186            var doc = editor.getDocument();
187            var inp = new java.io.InputStreamReader(u.openStream());
188            var buff = java.lang.reflect.Array.newInstance(
189                java.lang.Character.TYPE, 4096);
190            var nch;
191            while ((nch = inp.read(buff, 0, buff.length)) != -1) {
192                doc.insertString(doc.getLength(),
193                                 new java.lang.String(buff, 0, nch), null);
194                progress.setValue(progress.getValue() + nch);
195            }
196            curFileName = null;
197            setDocListener();
198        } catch (e) {
199            error("Error opening URL: " + e,
200                  "Can not open URL: " + url);
201        }
202    }
203
204    // factored out "save" function used by
205    // save, save as menu actions
206    function save(file) {
207        var doc = editor.getDocument();
208        frame.setTitle(file.getName() + titleSuffix);
209        curFileName = file;
210        var progress = new guiPkgs.JProgressBar();
211        progress.setMinimum(0);
212        progress.setMaximum(file.length());
213        var out = new java.io.FileWriter(file);
214        var text = new guiPkgs.Segment();
215        text.setPartialReturn(true);
216        var charsLeft = doc.getLength();
217        var offset = 0;
218        var min;
219
220        while (charsLeft > 0) {
221            doc.getText(offset, Math.min(4096, charsLeft), text);
222            out.write(text.array, text.offset, text.count);
223            charsLeft -= text.count;
224            offset += text.count;
225            progress.setValue(offset);
226            java.lang.Thread.sleep(10);
227        }
228
229        out.flush();
230        out.close();
231        docChanged = false;
232    }
233
234    // file-save as menu
235    function actionSaveAs() {
236        var ret = fileDialog(null, true);
237        if (ret == null) {
238            return;
239        }
240        save(ret);
241    }
242
243    // file-save menu
244    function actionSave() {
245        if (curFileName) {
246            save(new java.io.File(curFileName));
247        } else {
248            actionSaveAs();
249        }
250    }
251
252    // exit from scriptpad
253    function actionExit() {
254        checkDocChanged();
255        java.lang.System.exit(0);
256    }
257
258    // "Edit" menu
259
260    // cut the currently selected text
261    function actionCut() {
262        editor.cut();
263    }
264
265    // copy the currently selected text to clipboard
266    function actionCopy() {
267        editor.copy();
268    }
269
270    // paste clipboard content to document
271    function actionPaste() {
272        editor.paste();
273    }
274
275    // select all the text in editor
276    function actionSelectAll() {
277        editor.selectAll();
278    }
279
280    // "Tools" menu
281
282    // run the current document as JavaScript
283    function actionRun() {
284        var doc = editor.getDocument();
285        var script = doc.getText(0, doc.getLength());
286        var oldFile = engine.get(javax.script.ScriptEngine.FILENAME);
287        try {
288            if (engine == undefined) {
289                var m = new javax.script.ScriptEngineManager();
290                engine = m.getEngineByName("nashorn");
291            }
292            engine.put(javax.script.ScriptEngine.FILENAME, frame.title);
293            engine.eval(script, context);
294        } catch (e) {
295            error(e, "Script Error");
296            e.printStackTrace();
297        } finally {
298            engine.put(javax.script.ScriptEngine.FILENAME, oldFile);
299        }
300    }
301
302    // "Examples" menu
303
304    // show given script as new document
305    function showScript(title, str) {
306        actionNew();
307        frame.setTitle("Example - " + title + titleSuffix);
308        var doc = editor.document;
309        doc.insertString(0, str, null);
310    }
311
312    // "hello world"
313    function actionHello() {
314        showScript(actionEval.title,
315                   "alert('Hello, world');");
316    }
317    actionHello.title = "Hello, World";
318
319    // eval the "hello world"!
320    function actionEval() {
321        showScript(actionEval.title,
322                   "eval(\"alert('Hello, world')\");");
323    }
324    actionEval.title = "Eval";
325
326    // show how to access Java static methods
327    function actionJavaStatic() {
328        showScript(arguments.callee.title,
329                   "// Just use Java syntax\n" +
330                   "var props = java.lang.System.getProperties();\n" +
331                   "alert(props.get('os.name'));");
332    }
333    actionJavaStatic.title = "Java Static Calls";
334
335    // show how to access Java classes, methods
336    function actionJavaAccess() {
337        showScript(arguments.callee.title,
338                   "// just use new JavaClass();\n" +
339                   "var fr = new javax.swing.JFrame();\n" +
340                   "// call all public methods as in Java\n" +
341                   "fr.setTitle('hello');\n" +
342                   "fr.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);\n" +
343                   "fr.setSize(200, 200);\n" +
344                   "fr.setVisible(true);");
345    }
346    actionJavaAccess.title = "Java Object Access";
347
348    // show how to use Java bean conventions
349    function actionJavaBean() {
350        showScript(arguments.callee.title,
351                   "var fr = new javax.swing.JFrame();\n" +
352                   "fr.setSize(200, 200);\n" +
353                   "// access public get/set methods as fields\n" +
354                   "fr.defaultCloseOperation = javax.swing.WindowConstants.DISPOSE_ON_CLOSE;\n" +
355                   "fr.title = 'hello';\n" +
356                   "fr.visible = true;");
357    }
358    actionJavaBean.title = "Java Beans";
359
360    // show how to implement Java interface
361    function actionJavaInterface() {
362        showScript(arguments.callee.title,
363                   "// use Java anonymizer class-like syntax!\n" +
364                   "var r = new java.lang.Runnable() {\n" +
365                   "            run: function() {\n" +
366                   "                    alert('hello');\n" +
367                   "            }\n" +
368                   "    };\n" +
369                   "// use the above Runnable to create a Thread\n" +
370                   "new java.lang.Thread(r).start();\n" +
371                   "// For simple one method interfaces, just pass script function\n" +
372                   "new java.lang.Thread(function() { alert('world'); }).start();");
373    }
374    actionJavaInterface.title = "Java Interfaces";
375
376    // show how to import Java classes, packages
377    function actionJavaImport() {
378        showScript(arguments.callee.title,
379                   "// use Java-like import *...\n" +
380                   "//    importPackage(java.io);\n" +
381                   "// or import a specific class\n" +
382                   "//    importClass(java.io.File);\n" +
383                   "// or better - import just within a scope!\n" +
384                   "var ioPkgs = JavaImporter(java.io);\n" +
385                   "with (ioPkgs) { alert(new File('.').absolutePath); }");
386    }
387    actionJavaImport.title = "Java Import";
388
389    // "Help" menu
390
391    /*
392     * Shows a one liner help message for each
393     * global function. Note that this function
394     * depends on docString meta-data for each
395     * function.
396     */
397    function actionHelpGlobals() {
398        var names = new java.util.ArrayList();
399        for (var i in globalThis) {
400            var func = globalThis[i];
401            if (typeof(func) == "function" &&
402                ("docString" in func)) {
403                names.add(i);
404            }
405        }
406        java.util.Collections.sort(names);
407        var helpDoc = new java.lang.StringBuffer();
408        helpDoc.append("<table border='1'>");
409        var itr = names.iterator();
410        while (itr.hasNext()) {
411            var name = itr.next();
412            helpDoc.append("<tr><td>");
413            helpDoc.append(name);
414            helpDoc.append("</td><td>");
415            helpDoc.append(globalThis[name].docString);
416            helpDoc.append("</td></tr>");
417        }
418        helpDoc.append("</table>");
419
420        var helpEditor = new guiPkgs.JEditorPane();
421        helpEditor.setContentType("text/html");
422        helpEditor.setEditable(false);
423        helpEditor.setText(helpDoc.toString());
424
425        var scroller = new guiPkgs.JScrollPane();
426        var port = scroller.getViewport();
427        port.add(helpEditor);
428
429        var helpFrame = new guiPkgs.JFrame("Help - Global Functions");
430        helpFrame.getContentPane().add("Center", scroller);
431        helpFrame.setDefaultCloseOperation(guiPkgs.WindowConstants.DISPOSE_ON_CLOSE);
432        helpFrame.pack();
433        helpFrame.setSize(500, 600);
434        helpFrame.setVisible(true);
435    }
436
437    // show a simple about message for scriptpad
438    function actionAbout() {
439        alert("Scriptpad\nVersion 1.1", "Scriptpad");
440    }
441
442    /*
443     * This data is used to construct menu bar.
444     * This way adding a menu is easier. Just add
445     * top level menu or add an item to an existing
446     * menu. "action" should be a function that is
447     * called back on clicking the correponding menu.
448     */
449    var menuData = [
450        {
451            menu: "File",
452            items: [
453                { name: "New",  action: actionNew , accel: guiPkgs.KeyEvent.VK_N },
454                { name: "Open...", action: actionOpen, accel: guiPkgs.KeyEvent.VK_O },
455                { name: "Open URL...", action: actionOpenURL, accel: guiPkgs.KeyEvent.VK_U },
456                { name: "Save", action: actionSave, accel: guiPkgs.KeyEvent.VK_S },
457                { name: "Save As...", action: actionSaveAs },
458                { name: "-" },
459                { name: "Exit", action: actionExit, accel: guiPkgs.KeyEvent.VK_Q }
460            ]
461        },
462
463        {
464            menu: "Edit",
465            items: [
466                { name: "Cut", action: actionCut, accel: guiPkgs.KeyEvent.VK_X },
467                { name: "Copy", action: actionCopy, accel: guiPkgs.KeyEvent.VK_C },
468                { name: "Paste", action: actionPaste, accel: guiPkgs.KeyEvent.VK_V },
469                { name: "-" },
470                { name: "Select All", action: actionSelectAll, accel: guiPkgs.KeyEvent.VK_A }
471            ]
472        },
473
474        {
475            menu: "Tools",
476            items: [
477                { name: "Run", action: actionRun, accel: guiPkgs.KeyEvent.VK_R }
478            ]
479        },
480
481        {
482            menu: "Examples",
483            items: [
484                { name: actionHello.title, action: actionHello },
485                { name: actionEval.title, action: actionEval },
486                { name: actionJavaStatic.title, action: actionJavaStatic },
487                { name: actionJavaAccess.title, action: actionJavaAccess },
488                { name: actionJavaBean.title, action: actionJavaBean },
489                { name: actionJavaInterface.title, action: actionJavaInterface },
490                { name: actionJavaImport.title, action: actionJavaImport }
491            ]
492        },
493
494        {
495            menu: "Help",
496            items: [
497                { name: "Global Functions", action: actionHelpGlobals },
498                { name: "-" },
499                { name: "About Scriptpad", action: actionAbout }
500            ]
501        }
502    ];
503
504    function setMenuAccelerator(mi, accel) {
505        var keyStroke = guiPkgs.KeyStroke.getKeyStroke(accel,
506                                                       guiPkgs.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false);
507        mi.setAccelerator(keyStroke);
508    }
509
510    // create a menubar using the above menu data
511    function createMenubar() {
512        var mb = new guiPkgs.JMenuBar();
513        for (var m in menuData) {
514            var items = menuData[m].items;
515            var menu = new guiPkgs.JMenu(menuData[m].menu);
516
517            for (var i in items) {
518                if (items[i].name.equals("-")) {
519                    menu.addSeparator();
520                } else {
521                    var mi = new guiPkgs.JMenuItem(items[i].name);
522                    var action = items[i].action;
523                    mi.addActionListener(action);
524                    var accel = items[i].accel;
525                    if (accel) {
526                        setMenuAccelerator(mi, accel);
527                    }
528                    menu.add(mi);
529                }
530	        }
531
532	        mb.add(menu);
533        }
534
535        return mb;
536    }
537
538    // function to add a new menu item under "Tools" menu
539    function addTool(menuItem, action, accel) {
540        if (typeof(action) != "function") {
541            return;
542        }
543
544        var toolsIndex = -1;
545        // find the index of the "Tools" menu
546        for (var i in menuData) {
547            if (menuData[i].menu.equals("Tools")) {
548                toolsIndex = i;
549                break;
550            }
551        }
552        if (toolsIndex == -1) {
553            return;
554        }
555        var toolsMenu = frame.getJMenuBar().getMenu(toolsIndex);
556        var mi = new guiPkgs.JMenuItem(menuItem);
557        mi.addActionListener(action);
558        if (accel) {
559            setMenuAccelerator(mi, accel);
560        }
561        toolsMenu.add(mi);
562    }
563
564    // create Scriptpad frame
565    function createFrame() {
566        frame = new guiPkgs.JFrame();
567        frame.setTitle(defaultTitle);
568        frame.setBackground(guiPkgs.Color.lightGray);
569        frame.getContentPane().setLayout(new guiPkgs.BorderLayout());
570
571        // create notepad panel
572        var notepad = new guiPkgs.JPanel();
573        notepad.setBorder(guiPkgs.BorderFactory.createEtchedBorder());
574        notepad.setLayout(new guiPkgs.BorderLayout());
575
576        // create editor
577        editor = createEditor();
578        var scroller = new guiPkgs.JScrollPane();
579        var port = scroller.getViewport();
580        port.add(editor);
581
582        // add editor to notepad panel
583        var panel = new guiPkgs.JPanel();
584        panel.setLayout(new guiPkgs.BorderLayout());
585        panel.add("Center", scroller);
586        notepad.add("Center", panel);
587
588        // add notepad panel to frame
589        frame.getContentPane().add("Center", notepad);
590
591        // set menu bar to frame and show the frame
592        frame.setJMenuBar(createMenubar());
593        frame.setDefaultCloseOperation(guiPkgs.JFrame.EXIT_ON_CLOSE);
594        frame.pack();
595        frame.setSize(500, 600);
596    }
597
598    // show Scriptpad frame
599    function showFrame() {
600        // set global variable by the name "window"
601        globalThis.window = frame;
602
603        // open new document
604        actionNew();
605
606        frame.setVisible(true);
607    }
608
609    // create and show Scriptpad frame
610    createFrame();
611    showFrame();
612
613    /*
614     * Application object has two fields "frame", "editor"
615     * which are current JFrame and editor and a method
616     * called "addTool" to add new menu item to "Tools" menu.
617     */
618    return {
619        frame: frame,
620        editor: editor,
621        addTool: addTool
622    };
623};
624
625/*
626 * Call the main and store Application object
627 * in a global variable named "application".
628 */
629var application = main();
630
631if (this.load == undefined) {
632    function load(file) {
633        var ioPkgs = new JavaImporter(java.io);
634        with (ioPkgs) {
635            var stream = new FileInputStream(file);
636            var bstream = new BufferedInputStream(stream);
637            var reader = new BufferedReader(new InputStreamReader(bstream));
638            var oldFilename = engine.get(engine.FILENAME);
639            engine.put(engine.FILENAME, file);
640            try {
641                engine.eval(reader, context);
642            } finally {
643                engine.put(engine.FILENAME, oldFilename);
644            }
645            stream.close();
646        }
647    }
648    load.docString = "loads the given script file";
649}
650
651/*
652 * Load user specific init file under home dir, if found.
653 */
654function loadUserInit() {
655    var home = java.lang.System.getProperty("user.home");
656    var f = new java.io.File(home, "scriptpad.js");
657    if (f.exists()) {
658        engine.eval(new java.io.FileReader(f));
659    }
660}
661
662loadUserInit();
663