1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.javascript.tools.debugger;
7 
8 import javax.swing.*;
9 import javax.swing.text.*;
10 import javax.swing.event.*;
11 import javax.swing.table.*;
12 import java.awt.EventQueue;
13 import java.awt.ActiveEvent;
14 import java.awt.AWTEvent;
15 import java.awt.BorderLayout;
16 import java.awt.Color;
17 import java.awt.Component;
18 import java.awt.Container;
19 import java.awt.Dimension;
20 import java.awt.Event;
21 import java.awt.Font;
22 import java.awt.FontMetrics;
23 import java.awt.Frame;
24 import java.awt.Graphics;
25 import java.awt.GridBagConstraints;
26 import java.awt.GridBagLayout;
27 import java.awt.GridLayout;
28 import java.awt.MenuComponent;
29 import java.awt.Point;
30 import java.awt.Polygon;
31 import java.awt.Rectangle;
32 import java.awt.Toolkit;
33 import java.awt.event.*;
34 
35 import java.util.List;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.Comparator;
40 import java.util.EventListener;
41 import java.util.EventObject;
42 import java.util.Map;
43 import java.util.HashMap;
44 import java.util.Properties;
45 import java.io.*;
46 import javax.swing.tree.DefaultTreeCellRenderer;
47 import javax.swing.tree.TreePath;
48 import java.lang.reflect.Method;
49 
50 import org.mozilla.javascript.Kit;
51 import org.mozilla.javascript.SecurityUtilities;
52 
53 import org.mozilla.javascript.tools.shell.ConsoleTextArea;
54 
55 import org.mozilla.javascript.tools.debugger.treetable.JTreeTable;
56 import org.mozilla.javascript.tools.debugger.treetable.TreeTableModel;
57 import org.mozilla.javascript.tools.debugger.treetable.TreeTableModelAdapter;
58 
59 /**
60  * GUI for the Rhino debugger.
61  */
62 public class SwingGui extends JFrame implements GuiCallback {
63 
64     /**
65      * Serializable magic number.
66      */
67     private static final long serialVersionUID = -8217029773456711621L;
68 
69     /**
70      * The debugger.
71      */
72     Dim dim;
73 
74     /**
75      * The action to run when the 'Exit' menu item is chosen or the
76      * frame is closed.
77      */
78     private Runnable exitAction;
79 
80     /**
81      * The {@link JDesktopPane} that holds the script windows.
82      */
83     private JDesktopPane desk;
84 
85     /**
86      * The {@link JPanel} that shows information about the context.
87      */
88     private ContextWindow context;
89 
90     /**
91      * The menu bar.
92      */
93     private Menubar menubar;
94 
95     /**
96      * The tool bar.
97      */
98     private JToolBar toolBar;
99 
100     /**
101      * The console that displays I/O from the script.
102      */
103     private JSInternalConsole console;
104 
105     /**
106      * The {@link JSplitPane} that separates {@link #desk} from
107      * {@link org.mozilla.javascript.Context}.
108      */
109     private JSplitPane split1;
110 
111     /**
112      * The status bar.
113      */
114     private JLabel statusBar;
115 
116     /**
117      * Hash table of internal frame names to the internal frames themselves.
118      */
119     private final Map<String,JFrame> toplevels =
120         Collections.synchronizedMap(new HashMap<String,JFrame>());
121 
122     /**
123      * Hash table of script URLs to their internal frames.
124      */
125     private final Map<String,FileWindow> fileWindows =
126         Collections.synchronizedMap(new HashMap<String,FileWindow>());
127 
128 
129     /**
130      * The {@link FileWindow} that last had the focus.
131      */
132     private FileWindow currentWindow;
133 
134     /**
135      * File choose dialog for loading a script.
136      */
137     JFileChooser dlg;
138 
139     /**
140      * The AWT EventQueue.  Used for manually pumping AWT events from
141      * {@link #dispatchNextGuiEvent()}.
142      */
143     private EventQueue awtEventQueue;
144 
145     /**
146      * Creates a new SwingGui.
147      */
SwingGui(Dim dim, String title)148     public SwingGui(Dim dim, String title) {
149         super(title);
150         this.dim = dim;
151         init();
152         dim.setGuiCallback(this);
153     }
154 
155     /**
156      * Returns the Menubar of this debugger frame.
157      */
getMenubar()158     public Menubar getMenubar() {
159         return menubar;
160     }
161 
162     /**
163      * Sets the {@link Runnable} that will be run when the "Exit" menu
164      * item is chosen.
165      */
setExitAction(Runnable r)166     public void setExitAction(Runnable r) {
167         exitAction = r;
168     }
169 
170     /**
171      * Returns the debugger console component.
172      */
getConsole()173     public JSInternalConsole getConsole() {
174         return console;
175     }
176 
177     /**
178      * Sets the visibility of the debugger GUI.
179      */
180     @Override
setVisible(boolean b)181     public void setVisible(boolean b) {
182         super.setVisible(b);
183         if (b) {
184             // this needs to be done after the window is visible
185             console.consoleTextArea.requestFocus();
186             context.split.setDividerLocation(0.5);
187             try {
188                 console.setMaximum(true);
189                 console.setSelected(true);
190                 console.show();
191                 console.consoleTextArea.requestFocus();
192             } catch (Exception exc) {
193             }
194         }
195     }
196 
197     /**
198      * Records a new internal frame.
199      */
addTopLevel(String key, JFrame frame)200     void addTopLevel(String key, JFrame frame) {
201         if (frame != this) {
202             toplevels.put(key, frame);
203         }
204     }
205 
206     /**
207      * Constructs the debugger GUI.
208      */
init()209     private void init() {
210         menubar = new Menubar(this);
211         setJMenuBar(menubar);
212         toolBar = new JToolBar();
213         JButton button;
214         JButton breakButton, goButton, stepIntoButton,
215             stepOverButton, stepOutButton;
216         String [] toolTips = {"Break (Pause)",
217                               "Go (F5)",
218                               "Step Into (F11)",
219                               "Step Over (F7)",
220                               "Step Out (F8)"};
221         int count = 0;
222         button = breakButton = new JButton("Break");
223         button.setToolTipText("Break");
224         button.setActionCommand("Break");
225         button.addActionListener(menubar);
226         button.setEnabled(true);
227         button.setToolTipText(toolTips[count++]);
228 
229         button = goButton = new JButton("Go");
230         button.setToolTipText("Go");
231         button.setActionCommand("Go");
232         button.addActionListener(menubar);
233         button.setEnabled(false);
234         button.setToolTipText(toolTips[count++]);
235 
236         button = stepIntoButton = new JButton("Step Into");
237         button.setToolTipText("Step Into");
238         button.setActionCommand("Step Into");
239         button.addActionListener(menubar);
240         button.setEnabled(false);
241         button.setToolTipText(toolTips[count++]);
242 
243         button = stepOverButton = new JButton("Step Over");
244         button.setToolTipText("Step Over");
245         button.setActionCommand("Step Over");
246         button.setEnabled(false);
247         button.addActionListener(menubar);
248         button.setToolTipText(toolTips[count++]);
249 
250         button = stepOutButton = new JButton("Step Out");
251         button.setToolTipText("Step Out");
252         button.setActionCommand("Step Out");
253         button.setEnabled(false);
254         button.addActionListener(menubar);
255         button.setToolTipText(toolTips[count++]);
256 
257         Dimension dim = stepOverButton.getPreferredSize();
258         breakButton.setPreferredSize(dim);
259         breakButton.setMinimumSize(dim);
260         breakButton.setMaximumSize(dim);
261         breakButton.setSize(dim);
262         goButton.setPreferredSize(dim);
263         goButton.setMinimumSize(dim);
264         goButton.setMaximumSize(dim);
265         stepIntoButton.setPreferredSize(dim);
266         stepIntoButton.setMinimumSize(dim);
267         stepIntoButton.setMaximumSize(dim);
268         stepOverButton.setPreferredSize(dim);
269         stepOverButton.setMinimumSize(dim);
270         stepOverButton.setMaximumSize(dim);
271         stepOutButton.setPreferredSize(dim);
272         stepOutButton.setMinimumSize(dim);
273         stepOutButton.setMaximumSize(dim);
274         toolBar.add(breakButton);
275         toolBar.add(goButton);
276         toolBar.add(stepIntoButton);
277         toolBar.add(stepOverButton);
278         toolBar.add(stepOutButton);
279 
280         JPanel contentPane = new JPanel();
281         contentPane.setLayout(new BorderLayout());
282         getContentPane().add(toolBar, BorderLayout.NORTH);
283         getContentPane().add(contentPane, BorderLayout.CENTER);
284         desk = new JDesktopPane();
285         desk.setPreferredSize(new Dimension(600, 300));
286         desk.setMinimumSize(new Dimension(150, 50));
287         desk.add(console = new JSInternalConsole("JavaScript Console"));
288         context = new ContextWindow(this);
289         context.setPreferredSize(new Dimension(600, 120));
290         context.setMinimumSize(new Dimension(50, 50));
291 
292         split1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, desk,
293                                           context);
294         split1.setOneTouchExpandable(true);
295         SwingGui.setResizeWeight(split1, 0.66);
296         contentPane.add(split1, BorderLayout.CENTER);
297         statusBar = new JLabel();
298         statusBar.setText("Thread: ");
299         contentPane.add(statusBar, BorderLayout.SOUTH);
300         dlg = new JFileChooser();
301 
302         javax.swing.filechooser.FileFilter filter =
303             new javax.swing.filechooser.FileFilter() {
304                     @Override
305                     public boolean accept(File f) {
306                         if (f.isDirectory()) {
307                             return true;
308                         }
309                         String n = f.getName();
310                         int i = n.lastIndexOf('.');
311                         if (i > 0 && i < n.length() -1) {
312                             String ext = n.substring(i + 1).toLowerCase();
313                             if (ext.equals("js")) {
314                                 return true;
315                             }
316                         }
317                         return false;
318                     }
319 
320                     @Override
321                     public String getDescription() {
322                         return "JavaScript Files (*.js)";
323                     }
324                 };
325         dlg.addChoosableFileFilter(filter);
326         addWindowListener(new WindowAdapter() {
327                 @Override
328                 public void windowClosing(WindowEvent e) {
329                     exit();
330                 }
331             });
332     }
333 
334     /**
335      * Runs the {@link #exitAction}.
336      */
exit()337     private void exit() {
338         if (exitAction != null) {
339             SwingUtilities.invokeLater(exitAction);
340         }
341         dim.setReturnValue(Dim.EXIT);
342     }
343 
344     /**
345      * Returns the {@link FileWindow} for the given URL.
346      */
getFileWindow(String url)347     FileWindow getFileWindow(String url) {
348         if (url == null || url.equals("<stdin>")) {
349             return null;
350         }
351         return fileWindows.get(url);
352     }
353 
354     /**
355      * Returns a short version of the given URL.
356      */
getShortName(String url)357     static String getShortName(String url) {
358         int lastSlash = url.lastIndexOf('/');
359         if (lastSlash < 0) {
360             lastSlash = url.lastIndexOf('\\');
361         }
362         String shortName = url;
363         if (lastSlash >= 0 && lastSlash + 1 < url.length()) {
364             shortName = url.substring(lastSlash + 1);
365         }
366         return shortName;
367     }
368 
369     /**
370      * Closes the given {@link FileWindow}.
371      */
removeWindow(FileWindow w)372     void removeWindow(FileWindow w) {
373         fileWindows.remove(w.getUrl());
374         JMenu windowMenu = getWindowMenu();
375         int count = windowMenu.getItemCount();
376         JMenuItem lastItem = windowMenu.getItem(count -1);
377         String name = getShortName(w.getUrl());
378         for (int i = 5; i < count; i++) {
379             JMenuItem item = windowMenu.getItem(i);
380             if (item == null) continue; // separator
381             String text = item.getText();
382             //1 D:\foo.js
383             //2 D:\bar.js
384             int pos = text.indexOf(' ');
385             if (text.substring(pos + 1).equals(name)) {
386                 windowMenu.remove(item);
387                 // Cascade    [0]
388                 // Tile       [1]
389                 // -------    [2]
390                 // Console    [3]
391                 // -------    [4]
392                 if (count == 6) {
393                     // remove the final separator
394                     windowMenu.remove(4);
395                 } else {
396                     int j = i - 4;
397                     for (;i < count -1; i++) {
398                         JMenuItem thisItem = windowMenu.getItem(i);
399                         if (thisItem != null) {
400                             //1 D:\foo.js
401                             //2 D:\bar.js
402                             text = thisItem.getText();
403                             if (text.equals("More Windows...")) {
404                                 break;
405                             } else {
406                                 pos = text.indexOf(' ');
407                                 thisItem.setText((char)('0' + j) + " " +
408                                                  text.substring(pos + 1));
409                                 thisItem.setMnemonic('0' + j);
410                                 j++;
411                             }
412                         }
413                     }
414                     if (count - 6 == 0 && lastItem != item) {
415                         if (lastItem.getText().equals("More Windows...")) {
416                             windowMenu.remove(lastItem);
417                         }
418                     }
419                 }
420                 break;
421             }
422         }
423         windowMenu.revalidate();
424     }
425 
426     /**
427      * Shows the line at which execution in the given stack frame just stopped.
428      */
showStopLine(Dim.StackFrame frame)429     void showStopLine(Dim.StackFrame frame) {
430         String sourceName = frame.getUrl();
431         if (sourceName == null || sourceName.equals("<stdin>")) {
432             if (console.isVisible()) {
433                 console.show();
434             }
435         } else {
436             showFileWindow(sourceName, -1);
437             int lineNumber = frame.getLineNumber();
438             FileWindow w = getFileWindow(sourceName);
439             if (w != null) {
440                 setFilePosition(w, lineNumber);
441             }
442         }
443     }
444 
445     /**
446      * Shows a {@link FileWindow} for the given source, creating it
447      * if it doesn't exist yet. if <code>lineNumber</code> is greater
448      * than -1, it indicates the line number to select and display.
449      * @param sourceUrl the source URL
450      * @param lineNumber the line number to select, or -1
451      */
showFileWindow(String sourceUrl, int lineNumber)452     protected void showFileWindow(String sourceUrl, int lineNumber) {
453         FileWindow w = getFileWindow(sourceUrl);
454         if (w == null) {
455             Dim.SourceInfo si = dim.sourceInfo(sourceUrl);
456             createFileWindow(si, -1);
457             w = getFileWindow(sourceUrl);
458         }
459         if (lineNumber > -1) {
460             int start = w.getPosition(lineNumber-1);
461             int end = w.getPosition(lineNumber)-1;
462             w.textArea.select(start);
463             w.textArea.setCaretPosition(start);
464             w.textArea.moveCaretPosition(end);
465         }
466         try {
467             if (w.isIcon()) {
468                 w.setIcon(false);
469             }
470             w.setVisible(true);
471             w.moveToFront();
472             w.setSelected(true);
473             requestFocus();
474             w.requestFocus();
475             w.textArea.requestFocus();
476         } catch (Exception exc) {
477         }
478     }
479 
480     /**
481      * Creates and shows a new {@link FileWindow} for the given source.
482      */
createFileWindow(Dim.SourceInfo sourceInfo, int line)483     protected void createFileWindow(Dim.SourceInfo sourceInfo, int line) {
484         boolean activate = true;
485 
486         String url = sourceInfo.url();
487         FileWindow w = new FileWindow(this, sourceInfo);
488         fileWindows.put(url, w);
489         if (line != -1) {
490             if (currentWindow != null) {
491                 currentWindow.setPosition(-1);
492             }
493             try {
494                 w.setPosition(w.textArea.getLineStartOffset(line-1));
495             } catch (BadLocationException exc) {
496                 try {
497                     w.setPosition(w.textArea.getLineStartOffset(0));
498                 } catch (BadLocationException ee) {
499                     w.setPosition(-1);
500                 }
501             }
502         }
503         desk.add(w);
504         if (line != -1) {
505             currentWindow = w;
506         }
507         menubar.addFile(url);
508         w.setVisible(true);
509 
510         if (activate) {
511             try {
512                 w.setMaximum(true);
513                 w.setSelected(true);
514                 w.moveToFront();
515             } catch (Exception exc) {
516             }
517         }
518     }
519 
520     /**
521      * Update the source text for <code>sourceInfo</code>. This returns true
522      * if a {@link FileWindow} for the given source exists and could be updated.
523      * Otherwise, this does nothing and returns false.
524      * @param sourceInfo the source info
525      * @return true if a {@link FileWindow} for the given source exists
526      *              and could be updated, false otherwise.
527      */
updateFileWindow(Dim.SourceInfo sourceInfo)528     protected boolean updateFileWindow(Dim.SourceInfo sourceInfo) {
529         String fileName = sourceInfo.url();
530         FileWindow w = getFileWindow(fileName);
531         if (w != null) {
532             w.updateText(sourceInfo);
533             w.show();
534             return true;
535         }
536         return false;
537     }
538 
539     /**
540      * Moves the current position in the given {@link FileWindow} to the
541      * given line.
542      */
setFilePosition(FileWindow w, int line)543     private void setFilePosition(FileWindow w, int line) {
544         boolean activate = true;
545         JTextArea ta = w.textArea;
546         try {
547             if (line == -1) {
548                 w.setPosition(-1);
549                 if (currentWindow == w) {
550                     currentWindow = null;
551                 }
552             } else {
553                 int loc = ta.getLineStartOffset(line-1);
554                 if (currentWindow != null && currentWindow != w) {
555                     currentWindow.setPosition(-1);
556                 }
557                 w.setPosition(loc);
558                 currentWindow = w;
559             }
560         } catch (BadLocationException exc) {
561             // fix me
562         }
563         if (activate) {
564             if (w.isIcon()) {
565                 desk.getDesktopManager().deiconifyFrame(w);
566             }
567             desk.getDesktopManager().activateFrame(w);
568             try {
569                 w.show();
570                 w.toFront();  // required for correct frame layering (JDK 1.4.1)
571                 w.setSelected(true);
572             } catch (Exception exc) {
573             }
574         }
575     }
576 
577     /**
578      * Handles script interruption.
579      */
enterInterruptImpl(Dim.StackFrame lastFrame, String threadTitle, String alertMessage)580     void enterInterruptImpl(Dim.StackFrame lastFrame,
581                             String threadTitle, String alertMessage) {
582         statusBar.setText("Thread: " + threadTitle);
583 
584         showStopLine(lastFrame);
585 
586         if (alertMessage != null) {
587             MessageDialogWrapper.showMessageDialog(this,
588                                                    alertMessage,
589                                                    "Exception in Script",
590                                                    JOptionPane.ERROR_MESSAGE);
591         }
592 
593         updateEnabled(true);
594 
595         Dim.ContextData contextData = lastFrame.contextData();
596 
597         JComboBox ctx = context.context;
598         List<String> toolTips = context.toolTips;
599         context.disableUpdate();
600         int frameCount = contextData.frameCount();
601         ctx.removeAllItems();
602         // workaround for JDK 1.4 bug that caches selected value even after
603         // removeAllItems() is called
604         ctx.setSelectedItem(null);
605         toolTips.clear();
606         for (int i = 0; i < frameCount; i++) {
607             Dim.StackFrame frame = contextData.getFrame(i);
608             String url = frame.getUrl();
609             int lineNumber = frame.getLineNumber();
610             String shortName = url;
611             if (url.length() > 20) {
612                 shortName = "..." + url.substring(url.length() - 17);
613             }
614             String location = "\"" + shortName + "\", line " + lineNumber;
615             ctx.insertItemAt(location, i);
616             location = "\"" + url + "\", line " + lineNumber;
617             toolTips.add(location);
618         }
619         context.enableUpdate();
620         ctx.setSelectedIndex(0);
621         ctx.setMinimumSize(new Dimension(50, ctx.getMinimumSize().height));
622     }
623 
624     /**
625      * Returns the 'Window' menu.
626      */
getWindowMenu()627     private JMenu getWindowMenu() {
628         return menubar.getMenu(3);
629     }
630 
631     /**
632      * Displays a {@link JFileChooser} and returns the selected filename.
633      */
chooseFile(String title)634     private String chooseFile(String title) {
635         dlg.setDialogTitle(title);
636         File CWD = null;
637         String dir = SecurityUtilities.getSystemProperty("user.dir");
638         if (dir != null) {
639             CWD = new File(dir);
640         }
641         if (CWD != null) {
642             dlg.setCurrentDirectory(CWD);
643         }
644         int returnVal = dlg.showOpenDialog(this);
645         if (returnVal == JFileChooser.APPROVE_OPTION) {
646             try {
647                 String result = dlg.getSelectedFile().getCanonicalPath();
648                 CWD = dlg.getSelectedFile().getParentFile();
649                 Properties props = System.getProperties();
650                 props.put("user.dir", CWD.getPath());
651                 System.setProperties(props);
652                 return result;
653             } catch (IOException ignored) {
654             } catch (SecurityException ignored) {
655             }
656         }
657         return null;
658     }
659 
660     /**
661      * Returns the current selected internal frame.
662      */
getSelectedFrame()663     private JInternalFrame getSelectedFrame() {
664        JInternalFrame[] frames = desk.getAllFrames();
665        for (int i = 0; i < frames.length; i++) {
666            if (frames[i].isShowing()) {
667                return frames[i];
668            }
669        }
670        return frames[frames.length - 1];
671     }
672 
673     /**
674      * Enables or disables the menu and tool bars with respect to the
675      * state of script execution.
676      */
updateEnabled(boolean interrupted)677     private void updateEnabled(boolean interrupted) {
678         ((Menubar)getJMenuBar()).updateEnabled(interrupted);
679         for (int ci = 0, cc = toolBar.getComponentCount(); ci < cc; ci++) {
680             boolean enableButton;
681             if (ci == 0) {
682                 // Break
683                 enableButton = !interrupted;
684             } else {
685                 enableButton = interrupted;
686             }
687             toolBar.getComponent(ci).setEnabled(enableButton);
688         }
689         if (interrupted) {
690             toolBar.setEnabled(true);
691             // raise the debugger window
692             int state = getExtendedState();
693             if (state == Frame.ICONIFIED) {
694                 setExtendedState(Frame.NORMAL);
695             }
696             toFront();
697             context.setEnabled(true);
698         } else {
699             if (currentWindow != null) currentWindow.setPosition(-1);
700             context.setEnabled(false);
701         }
702     }
703 
704     /**
705      * Calls {@link JSplitPane#setResizeWeight} via reflection.
706      * For compatibility, since JDK &lt; 1.3 does not have this method.
707      */
setResizeWeight(JSplitPane pane, double weight)708     static void setResizeWeight(JSplitPane pane, double weight) {
709         try {
710             Method m = JSplitPane.class.getMethod("setResizeWeight",
711                                                   new Class[]{double.class});
712             m.invoke(pane, new Object[]{new Double(weight)});
713         } catch (NoSuchMethodException exc) {
714         } catch (IllegalAccessException exc) {
715         } catch (java.lang.reflect.InvocationTargetException exc) {
716         }
717     }
718 
719     /**
720      * Reads the file with the given name and returns its contents as a String.
721      */
readFile(String fileName)722     private String readFile(String fileName) {
723         String text;
724         try {
725             Reader r = new FileReader(fileName);
726             try {
727                 text = Kit.readReader(r);
728             } finally {
729                 r.close();
730             }
731         } catch (IOException ex) {
732             MessageDialogWrapper.showMessageDialog(this,
733                                                    ex.getMessage(),
734                                                    "Error reading "+fileName,
735                                                    JOptionPane.ERROR_MESSAGE);
736             text = null;
737         }
738         return text;
739     }
740 
741     // GuiCallback
742 
743     /**
744      * Called when the source text for a script has been updated.
745      */
updateSourceText(Dim.SourceInfo sourceInfo)746     public void updateSourceText(Dim.SourceInfo sourceInfo) {
747         RunProxy proxy = new RunProxy(this, RunProxy.UPDATE_SOURCE_TEXT);
748         proxy.sourceInfo = sourceInfo;
749         SwingUtilities.invokeLater(proxy);
750     }
751 
752     /**
753      * Called when the interrupt loop has been entered.
754      */
enterInterrupt(Dim.StackFrame lastFrame, String threadTitle, String alertMessage)755     public void enterInterrupt(Dim.StackFrame lastFrame,
756                                String threadTitle,
757                                String alertMessage) {
758         if (SwingUtilities.isEventDispatchThread()) {
759             enterInterruptImpl(lastFrame, threadTitle, alertMessage);
760         } else {
761             RunProxy proxy = new RunProxy(this, RunProxy.ENTER_INTERRUPT);
762             proxy.lastFrame = lastFrame;
763             proxy.threadTitle = threadTitle;
764             proxy.alertMessage = alertMessage;
765             SwingUtilities.invokeLater(proxy);
766         }
767     }
768 
769     /**
770      * Returns whether the current thread is the GUI event thread.
771      */
isGuiEventThread()772     public boolean isGuiEventThread() {
773         return SwingUtilities.isEventDispatchThread();
774     }
775 
776     /**
777      * Processes the next GUI event.
778      */
dispatchNextGuiEvent()779     public void dispatchNextGuiEvent() throws InterruptedException {
780         EventQueue queue = awtEventQueue;
781         if (queue == null) {
782             queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
783             awtEventQueue = queue;
784         }
785         AWTEvent event = queue.getNextEvent();
786         if (event instanceof ActiveEvent) {
787             ((ActiveEvent)event).dispatch();
788         } else {
789             Object source = event.getSource();
790             if (source instanceof Component) {
791                 Component comp = (Component)source;
792                 comp.dispatchEvent(event);
793             } else if (source instanceof MenuComponent) {
794                 ((MenuComponent)source).dispatchEvent(event);
795             }
796         }
797     }
798 
799     // ActionListener
800 
801     /**
802      * Performs an action from the menu or toolbar.
803      */
actionPerformed(ActionEvent e)804     public void actionPerformed(ActionEvent e) {
805         String cmd = e.getActionCommand();
806         int returnValue = -1;
807         if (cmd.equals("Cut") || cmd.equals("Copy") || cmd.equals("Paste")) {
808             JInternalFrame f = getSelectedFrame();
809             if (f != null && f instanceof ActionListener) {
810                 ((ActionListener)f).actionPerformed(e);
811             }
812         } else if (cmd.equals("Step Over")) {
813             returnValue = Dim.STEP_OVER;
814         } else if (cmd.equals("Step Into")) {
815             returnValue = Dim.STEP_INTO;
816         } else if (cmd.equals("Step Out")) {
817             returnValue = Dim.STEP_OUT;
818         } else if (cmd.equals("Go")) {
819             returnValue = Dim.GO;
820         } else if (cmd.equals("Break")) {
821             dim.setBreak();
822         } else if (cmd.equals("Exit")) {
823             exit();
824         } else if (cmd.equals("Open")) {
825             String fileName = chooseFile("Select a file to compile");
826             if (fileName != null) {
827                 String text = readFile(fileName);
828                 if (text != null) {
829                     RunProxy proxy = new RunProxy(this, RunProxy.OPEN_FILE);
830                     proxy.fileName = fileName;
831                     proxy.text = text;
832                     new Thread(proxy).start();
833                 }
834             }
835         } else if (cmd.equals("Load")) {
836             String fileName = chooseFile("Select a file to execute");
837             if (fileName != null) {
838                 String text = readFile(fileName);
839                 if (text != null) {
840                     RunProxy proxy = new RunProxy(this, RunProxy.LOAD_FILE);
841                     proxy.fileName = fileName;
842                     proxy.text = text;
843                     new Thread(proxy).start();
844                 }
845             }
846         } else if (cmd.equals("More Windows...")) {
847             MoreWindows dlg = new MoreWindows(this, fileWindows,
848                                               "Window", "Files");
849             dlg.showDialog(this);
850         } else if (cmd.equals("Console")) {
851             if (console.isIcon()) {
852                 desk.getDesktopManager().deiconifyFrame(console);
853             }
854             console.show();
855             desk.getDesktopManager().activateFrame(console);
856             console.consoleTextArea.requestFocus();
857         } else if (cmd.equals("Cut")) {
858         } else if (cmd.equals("Copy")) {
859         } else if (cmd.equals("Paste")) {
860         } else if (cmd.equals("Go to function...")) {
861             FindFunction dlg = new FindFunction(this, "Go to function",
862                                                 "Function");
863             dlg.showDialog(this);
864         } else if (cmd.equals("Tile")) {
865             JInternalFrame[] frames = desk.getAllFrames();
866             int count = frames.length;
867             int rows, cols;
868             rows = cols = (int)Math.sqrt(count);
869             if (rows*cols < count) {
870                 cols++;
871                 if (rows * cols < count) {
872                     rows++;
873                 }
874             }
875             Dimension size = desk.getSize();
876             int w = size.width/cols;
877             int h = size.height/rows;
878             int x = 0;
879             int y = 0;
880             for (int i = 0; i < rows; i++) {
881                 for (int j = 0; j < cols; j++) {
882                     int index = (i*cols) + j;
883                     if (index >= frames.length) {
884                         break;
885                     }
886                     JInternalFrame f = frames[index];
887                     try {
888                         f.setIcon(false);
889                         f.setMaximum(false);
890                     } catch (Exception exc) {
891                     }
892                     desk.getDesktopManager().setBoundsForFrame(f, x, y,
893                                                                w, h);
894                     x += w;
895                 }
896                 y += h;
897                 x = 0;
898             }
899         } else if (cmd.equals("Cascade")) {
900             JInternalFrame[] frames = desk.getAllFrames();
901             int count = frames.length;
902             int x, y, w, h;
903             x = y = 0;
904             h = desk.getHeight();
905             int d = h / count;
906             if (d > 30) d = 30;
907             for (int i = count -1; i >= 0; i--, x += d, y += d) {
908                 JInternalFrame f = frames[i];
909                 try {
910                     f.setIcon(false);
911                     f.setMaximum(false);
912                 } catch (Exception exc) {
913                 }
914                 Dimension dimen = f.getPreferredSize();
915                 w = dimen.width;
916                 h = dimen.height;
917                 desk.getDesktopManager().setBoundsForFrame(f, x, y, w, h);
918             }
919         } else {
920             Object obj = getFileWindow(cmd);
921             if (obj != null) {
922                 FileWindow w = (FileWindow)obj;
923                 try {
924                     if (w.isIcon()) {
925                         w.setIcon(false);
926                     }
927                     w.setVisible(true);
928                     w.moveToFront();
929                     w.setSelected(true);
930                 } catch (Exception exc) {
931                 }
932             }
933         }
934         if (returnValue != -1) {
935             updateEnabled(false);
936             dim.setReturnValue(returnValue);
937         }
938     }
939 }
940 
941 /**
942  * Helper class for showing a message dialog.
943  */
944 class MessageDialogWrapper {
945 
946     /**
947      * Shows a message dialog, wrapping the <code>msg</code> at 60
948      * columns.
949      */
showMessageDialog(Component parent, String msg, String title, int flags)950     public static void showMessageDialog(Component parent, String msg,
951                                          String title, int flags) {
952         if (msg.length() > 60) {
953             StringBuffer buf = new StringBuffer();
954             int len = msg.length();
955             int j = 0;
956             int i;
957             for (i = 0; i < len; i++, j++) {
958                 char c = msg.charAt(i);
959                 buf.append(c);
960                 if (Character.isWhitespace(c)) {
961                     int k;
962                     for (k = i + 1; k < len; k++) {
963                         if (Character.isWhitespace(msg.charAt(k))) {
964                             break;
965                         }
966                     }
967                     if (k < len) {
968                         int nextWordLen = k - i;
969                         if (j + nextWordLen > 60) {
970                             buf.append('\n');
971                             j = 0;
972                         }
973                     }
974                 }
975             }
976             msg = buf.toString();
977         }
978         JOptionPane.showMessageDialog(parent, msg, title, flags);
979     }
980 }
981 
982 /**
983  * Extension of JTextArea for script evaluation input.
984  */
985 class EvalTextArea
986     extends JTextArea
987     implements KeyListener, DocumentListener {
988 
989     /**
990      * Serializable magic number.
991      */
992     private static final long serialVersionUID = -3918033649601064194L;
993 
994     /**
995      * The debugger GUI.
996      */
997     private SwingGui debugGui;
998 
999     /**
1000      * History of expressions that have been evaluated
1001      */
1002     private List<String> history;
1003 
1004     /**
1005      * Index of the selected history item.
1006      */
1007     private int historyIndex = -1;
1008 
1009     /**
1010      * Position in the display where output should go.
1011      */
1012     private int outputMark;
1013 
1014     /**
1015      * Creates a new EvalTextArea.
1016      */
EvalTextArea(SwingGui debugGui)1017     public EvalTextArea(SwingGui debugGui) {
1018         this.debugGui = debugGui;
1019         history = Collections.synchronizedList(new ArrayList<String>());
1020         Document doc = getDocument();
1021         doc.addDocumentListener(this);
1022         addKeyListener(this);
1023         setLineWrap(true);
1024         setFont(new Font("Monospaced", 0, 12));
1025         append("% ");
1026         outputMark = doc.getLength();
1027     }
1028 
1029     /**
1030      * Selects a subrange of the text.
1031      */
1032     @Override
select(int start, int end)1033     public void select(int start, int end) {
1034         //requestFocus();
1035         super.select(start, end);
1036     }
1037 
1038     /**
1039      * Called when Enter is pressed.
1040      */
returnPressed()1041     private synchronized void returnPressed() {
1042         Document doc = getDocument();
1043         int len = doc.getLength();
1044         Segment segment = new Segment();
1045         try {
1046             doc.getText(outputMark, len - outputMark, segment);
1047         } catch (javax.swing.text.BadLocationException ignored) {
1048             ignored.printStackTrace();
1049         }
1050         String text = segment.toString();
1051         if (debugGui.dim.stringIsCompilableUnit(text)) {
1052             if (text.trim().length() > 0) {
1053                history.add(text);
1054                historyIndex = history.size();
1055             }
1056             append("\n");
1057             String result = debugGui.dim.eval(text);
1058             if (result.length() > 0) {
1059                 append(result);
1060                 append("\n");
1061             }
1062             append("% ");
1063             outputMark = doc.getLength();
1064         } else {
1065             append("\n");
1066         }
1067     }
1068 
1069     /**
1070      * Writes output into the text area.
1071      */
write(String str)1072     public synchronized void write(String str) {
1073         insert(str, outputMark);
1074         int len = str.length();
1075         outputMark += len;
1076         select(outputMark, outputMark);
1077     }
1078 
1079     // KeyListener
1080 
1081     /**
1082      * Called when a key is pressed.
1083      */
keyPressed(KeyEvent e)1084     public void keyPressed(KeyEvent e) {
1085         int code = e.getKeyCode();
1086         if (code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_LEFT) {
1087             if (outputMark == getCaretPosition()) {
1088                 e.consume();
1089             }
1090         } else if (code == KeyEvent.VK_HOME) {
1091            int caretPos = getCaretPosition();
1092            if (caretPos == outputMark) {
1093                e.consume();
1094            } else if (caretPos > outputMark) {
1095                if (!e.isControlDown()) {
1096                    if (e.isShiftDown()) {
1097                        moveCaretPosition(outputMark);
1098                    } else {
1099                        setCaretPosition(outputMark);
1100                    }
1101                    e.consume();
1102                }
1103            }
1104         } else if (code == KeyEvent.VK_ENTER) {
1105             returnPressed();
1106             e.consume();
1107         } else if (code == KeyEvent.VK_UP) {
1108             historyIndex--;
1109             if (historyIndex >= 0) {
1110                 if (historyIndex >= history.size()) {
1111                     historyIndex = history.size() -1;
1112                 }
1113                 if (historyIndex >= 0) {
1114                     String str = history.get(historyIndex);
1115                     int len = getDocument().getLength();
1116                     replaceRange(str, outputMark, len);
1117                     int caretPos = outputMark + str.length();
1118                     select(caretPos, caretPos);
1119                 } else {
1120                     historyIndex++;
1121                 }
1122             } else {
1123                 historyIndex++;
1124             }
1125             e.consume();
1126         } else if (code == KeyEvent.VK_DOWN) {
1127             int caretPos = outputMark;
1128             if (history.size() > 0) {
1129                 historyIndex++;
1130                 if (historyIndex < 0) {historyIndex = 0;}
1131                 int len = getDocument().getLength();
1132                 if (historyIndex < history.size()) {
1133                     String str = history.get(historyIndex);
1134                     replaceRange(str, outputMark, len);
1135                     caretPos = outputMark + str.length();
1136                 } else {
1137                     historyIndex = history.size();
1138                     replaceRange("", outputMark, len);
1139                 }
1140             }
1141             select(caretPos, caretPos);
1142             e.consume();
1143         }
1144     }
1145 
1146     /**
1147      * Called when a key is typed.
1148      */
keyTyped(KeyEvent e)1149     public void keyTyped(KeyEvent e) {
1150         int keyChar = e.getKeyChar();
1151         if (keyChar == 0x8 /* KeyEvent.VK_BACK_SPACE */) {
1152             if (outputMark == getCaretPosition()) {
1153                 e.consume();
1154             }
1155         } else if (getCaretPosition() < outputMark) {
1156             setCaretPosition(outputMark);
1157         }
1158     }
1159 
1160     /**
1161      * Called when a key is released.
1162      */
keyReleased(KeyEvent e)1163     public synchronized void keyReleased(KeyEvent e) {
1164     }
1165 
1166     // DocumentListener
1167 
1168     /**
1169      * Called when text was inserted into the text area.
1170      */
insertUpdate(DocumentEvent e)1171     public synchronized void insertUpdate(DocumentEvent e) {
1172         int len = e.getLength();
1173         int off = e.getOffset();
1174         if (outputMark > off) {
1175             outputMark += len;
1176         }
1177     }
1178 
1179     /**
1180      * Called when text was removed from the text area.
1181      */
removeUpdate(DocumentEvent e)1182     public synchronized void removeUpdate(DocumentEvent e) {
1183         int len = e.getLength();
1184         int off = e.getOffset();
1185         if (outputMark > off) {
1186             if (outputMark >= off + len) {
1187                 outputMark -= len;
1188             } else {
1189                 outputMark = off;
1190             }
1191         }
1192     }
1193 
1194     /**
1195      * Attempts to clean up the damage done by {@link #updateUI()}.
1196      */
postUpdateUI()1197     public synchronized void postUpdateUI() {
1198         //requestFocus();
1199         setCaret(getCaret());
1200         select(outputMark, outputMark);
1201     }
1202 
1203     /**
1204      * Called when text has changed in the text area.
1205      */
changedUpdate(DocumentEvent e)1206     public synchronized void changedUpdate(DocumentEvent e) {
1207     }
1208 }
1209 
1210 /**
1211  * An internal frame for evaluating script.
1212  */
1213 class EvalWindow extends JInternalFrame implements ActionListener {
1214 
1215     /**
1216      * Serializable magic number.
1217      */
1218     private static final long serialVersionUID = -2860585845212160176L;
1219 
1220     /**
1221      * The text area into which expressions can be typed.
1222      */
1223     private EvalTextArea evalTextArea;
1224 
1225     /**
1226      * Creates a new EvalWindow.
1227      */
EvalWindow(String name, SwingGui debugGui)1228     public EvalWindow(String name, SwingGui debugGui) {
1229         super(name, true, false, true, true);
1230         evalTextArea = new EvalTextArea(debugGui);
1231         evalTextArea.setRows(24);
1232         evalTextArea.setColumns(80);
1233         JScrollPane scroller = new JScrollPane(evalTextArea);
1234         setContentPane(scroller);
1235         //scroller.setPreferredSize(new Dimension(600, 400));
1236         pack();
1237         setVisible(true);
1238     }
1239 
1240     /**
1241      * Sets whether the text area is enabled.
1242      */
1243     @Override
setEnabled(boolean b)1244     public void setEnabled(boolean b) {
1245         super.setEnabled(b);
1246         evalTextArea.setEnabled(b);
1247     }
1248 
1249     // ActionListener
1250 
1251     /**
1252      * Performs an action on the text area.
1253      */
actionPerformed(ActionEvent e)1254     public void actionPerformed(ActionEvent e) {
1255         String cmd = e.getActionCommand();
1256         if (cmd.equals("Cut")) {
1257             evalTextArea.cut();
1258         } else if (cmd.equals("Copy")) {
1259             evalTextArea.copy();
1260         } else if (cmd.equals("Paste")) {
1261             evalTextArea.paste();
1262         }
1263     }
1264 }
1265 
1266 /**
1267  * Internal frame for the console.
1268  */
1269 class JSInternalConsole extends JInternalFrame implements ActionListener {
1270 
1271     /**
1272      * Serializable magic number.
1273      */
1274     private static final long serialVersionUID = -5523468828771087292L;
1275 
1276     /**
1277      * Creates a new JSInternalConsole.
1278      */
JSInternalConsole(String name)1279     public JSInternalConsole(String name) {
1280         super(name, true, false, true, true);
1281         consoleTextArea = new ConsoleTextArea(null);
1282         consoleTextArea.setRows(24);
1283         consoleTextArea.setColumns(80);
1284         JScrollPane scroller = new JScrollPane(consoleTextArea);
1285         setContentPane(scroller);
1286         pack();
1287         addInternalFrameListener(new InternalFrameAdapter() {
1288                 @Override
1289                 public void internalFrameActivated(InternalFrameEvent e) {
1290                     // hack
1291                     if (consoleTextArea.hasFocus()) {
1292                         consoleTextArea.getCaret().setVisible(false);
1293                         consoleTextArea.getCaret().setVisible(true);
1294                     }
1295                 }
1296             });
1297     }
1298 
1299     /**
1300      * The console text area.
1301      */
1302     ConsoleTextArea consoleTextArea;
1303 
1304     /**
1305      * Returns the input stream of the console text area.
1306      */
getIn()1307     public InputStream getIn() {
1308         return consoleTextArea.getIn();
1309     }
1310 
1311     /**
1312      * Returns the output stream of the console text area.
1313      */
getOut()1314     public PrintStream getOut() {
1315         return consoleTextArea.getOut();
1316     }
1317 
1318     /**
1319      * Returns the error stream of the console text area.
1320      */
getErr()1321     public PrintStream getErr() {
1322         return consoleTextArea.getErr();
1323     }
1324 
1325     // ActionListener
1326 
1327     /**
1328      * Performs an action on the text area.
1329      */
actionPerformed(ActionEvent e)1330     public void actionPerformed(ActionEvent e) {
1331         String cmd = e.getActionCommand();
1332         if (cmd.equals("Cut")) {
1333             consoleTextArea.cut();
1334         } else if (cmd.equals("Copy")) {
1335             consoleTextArea.copy();
1336         } else if (cmd.equals("Paste")) {
1337             consoleTextArea.paste();
1338         }
1339     }
1340 }
1341 
1342 /**
1343  * Popup menu class for right-clicking on {@link FileTextArea}s.
1344  */
1345 class FilePopupMenu extends JPopupMenu {
1346 
1347     /**
1348      * Serializable magic number.
1349      */
1350     private static final long serialVersionUID = 3589525009546013565L;
1351 
1352     /**
1353      * The popup x position.
1354      */
1355     int x;
1356 
1357     /**
1358      * The popup y position.
1359      */
1360     int y;
1361 
1362     /**
1363      * Creates a new FilePopupMenu.
1364      */
FilePopupMenu(FileTextArea w)1365     public FilePopupMenu(FileTextArea w) {
1366         JMenuItem item;
1367         add(item = new JMenuItem("Set Breakpoint"));
1368         item.addActionListener(w);
1369         add(item = new JMenuItem("Clear Breakpoint"));
1370         item.addActionListener(w);
1371         add(item = new JMenuItem("Run"));
1372         item.addActionListener(w);
1373     }
1374 
1375     /**
1376      * Displays the menu at the given coordinates.
1377      */
show(JComponent comp, int x, int y)1378     public void show(JComponent comp, int x, int y) {
1379         this.x = x;
1380         this.y = y;
1381         super.show(comp, x, y);
1382     }
1383 }
1384 
1385 /**
1386  * Text area to display script source.
1387  */
1388 class FileTextArea
1389     extends JTextArea
1390     implements ActionListener, PopupMenuListener, KeyListener, MouseListener {
1391 
1392     /**
1393      * Serializable magic number.
1394      */
1395     private static final long serialVersionUID = -25032065448563720L;
1396 
1397     /**
1398      * The owning {@link FileWindow}.
1399      */
1400     private FileWindow w;
1401 
1402     /**
1403      * The popup menu.
1404      */
1405     private FilePopupMenu popup;
1406 
1407     /**
1408      * Creates a new FileTextArea.
1409      */
FileTextArea(FileWindow w)1410     public FileTextArea(FileWindow w) {
1411         this.w = w;
1412         popup = new FilePopupMenu(this);
1413         popup.addPopupMenuListener(this);
1414         addMouseListener(this);
1415         addKeyListener(this);
1416         setFont(new Font("Monospaced", 0, 12));
1417     }
1418 
1419     /**
1420      * Moves the selection to the given offset.
1421      */
select(int pos)1422     public void select(int pos) {
1423         if (pos >= 0) {
1424             try {
1425                 int line = getLineOfOffset(pos);
1426                 Rectangle rect = modelToView(pos);
1427                 if (rect == null) {
1428                     select(pos, pos);
1429                 } else {
1430                     try {
1431                         Rectangle nrect =
1432                             modelToView(getLineStartOffset(line + 1));
1433                         if (nrect != null) {
1434                             rect = nrect;
1435                         }
1436                     } catch (Exception exc) {
1437                     }
1438                     JViewport vp = (JViewport)getParent();
1439                     Rectangle viewRect = vp.getViewRect();
1440                     if (viewRect.y + viewRect.height > rect.y) {
1441                         // need to scroll up
1442                         select(pos, pos);
1443                     } else {
1444                         // need to scroll down
1445                         rect.y += (viewRect.height - rect.height)/2;
1446                         scrollRectToVisible(rect);
1447                         select(pos, pos);
1448                     }
1449                 }
1450             } catch (BadLocationException exc) {
1451                 select(pos, pos);
1452                 //exc.printStackTrace();
1453             }
1454         }
1455     }
1456 
1457     /**
1458      * Checks if the popup menu should be shown.
1459      */
checkPopup(MouseEvent e)1460     private void checkPopup(MouseEvent e) {
1461         if (e.isPopupTrigger()) {
1462             popup.show(this, e.getX(), e.getY());
1463         }
1464     }
1465 
1466     // MouseListener
1467 
1468     /**
1469      * Called when a mouse button is pressed.
1470      */
mousePressed(MouseEvent e)1471     public void mousePressed(MouseEvent e) {
1472         checkPopup(e);
1473     }
1474 
1475     /**
1476      * Called when the mouse is clicked.
1477      */
mouseClicked(MouseEvent e)1478     public void mouseClicked(MouseEvent e) {
1479         checkPopup(e);
1480         requestFocus();
1481         getCaret().setVisible(true);
1482     }
1483 
1484     /**
1485      * Called when the mouse enters the component.
1486      */
mouseEntered(MouseEvent e)1487     public void mouseEntered(MouseEvent e) {
1488     }
1489 
1490     /**
1491      * Called when the mouse exits the component.
1492      */
mouseExited(MouseEvent e)1493     public void mouseExited(MouseEvent e) {
1494     }
1495 
1496     /**
1497      * Called when a mouse button is released.
1498      */
mouseReleased(MouseEvent e)1499     public void mouseReleased(MouseEvent e) {
1500         checkPopup(e);
1501     }
1502 
1503     // PopupMenuListener
1504 
1505     /**
1506      * Called before the popup menu will become visible.
1507      */
popupMenuWillBecomeVisible(PopupMenuEvent e)1508     public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
1509     }
1510 
1511     /**
1512      * Called before the popup menu will become invisible.
1513      */
popupMenuWillBecomeInvisible(PopupMenuEvent e)1514     public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
1515     }
1516 
1517     /**
1518      * Called when the popup menu is cancelled.
1519      */
popupMenuCanceled(PopupMenuEvent e)1520     public void popupMenuCanceled(PopupMenuEvent e) {
1521     }
1522 
1523     // ActionListener
1524 
1525     /**
1526      * Performs an action.
1527      */
actionPerformed(ActionEvent e)1528     public void actionPerformed(ActionEvent e) {
1529         int pos = viewToModel(new Point(popup.x, popup.y));
1530         popup.setVisible(false);
1531         String cmd = e.getActionCommand();
1532         int line = -1;
1533         try {
1534             line = getLineOfOffset(pos);
1535         } catch (Exception exc) {
1536         }
1537         if (cmd.equals("Set Breakpoint")) {
1538             w.setBreakPoint(line + 1);
1539         } else if (cmd.equals("Clear Breakpoint")) {
1540             w.clearBreakPoint(line + 1);
1541         } else if (cmd.equals("Run")) {
1542             w.load();
1543         }
1544     }
1545 
1546     // KeyListener
1547 
1548     /**
1549      * Called when a key is pressed.
1550      */
keyPressed(KeyEvent e)1551     public void keyPressed(KeyEvent e) {
1552         switch (e.getKeyCode()) {
1553         case KeyEvent.VK_BACK_SPACE:
1554         case KeyEvent.VK_ENTER:
1555         case KeyEvent.VK_DELETE:
1556         case KeyEvent.VK_TAB:
1557             e.consume();
1558             break;
1559         }
1560     }
1561 
1562     /**
1563      * Called when a key is typed.
1564      */
keyTyped(KeyEvent e)1565     public void keyTyped(KeyEvent e) {
1566         e.consume();
1567     }
1568 
1569     /**
1570      * Called when a key is released.
1571      */
keyReleased(KeyEvent e)1572     public void keyReleased(KeyEvent e) {
1573         e.consume();
1574     }
1575 }
1576 
1577 /**
1578  * Dialog to list the available windows.
1579  */
1580 class MoreWindows extends JDialog implements ActionListener {
1581 
1582     /**
1583      * Serializable magic number.
1584      */
1585     private static final long serialVersionUID = 5177066296457377546L;
1586 
1587     /**
1588      * Last selected value.
1589      */
1590     private String value;
1591 
1592     /**
1593      * The list component.
1594      */
1595     private JList list;
1596 
1597     /**
1598      * Our parent frame.
1599      */
1600     private SwingGui swingGui;
1601 
1602     /**
1603      * The "Select" button.
1604      */
1605     private JButton setButton;
1606 
1607     /**
1608      * The "Cancel" button.
1609      */
1610     private JButton cancelButton;
1611 
1612     /**
1613      * Creates a new MoreWindows.
1614      */
MoreWindows(SwingGui frame, Map<String,FileWindow> fileWindows, String title, String labelText)1615     MoreWindows(SwingGui frame, Map<String,FileWindow> fileWindows, String title,
1616                 String labelText) {
1617         super(frame, title, true);
1618         this.swingGui = frame;
1619         //buttons
1620         cancelButton = new JButton("Cancel");
1621         setButton = new JButton("Select");
1622         cancelButton.addActionListener(this);
1623         setButton.addActionListener(this);
1624         getRootPane().setDefaultButton(setButton);
1625 
1626         //dim part of the dialog
1627         list = new JList(new DefaultListModel());
1628         DefaultListModel model = (DefaultListModel)list.getModel();
1629         model.clear();
1630         //model.fireIntervalRemoved(model, 0, size);
1631         for (String data: fileWindows.keySet()) {
1632             model.addElement(data);
1633         }
1634         list.setSelectedIndex(0);
1635         //model.fireIntervalAdded(model, 0, data.length);
1636         setButton.setEnabled(true);
1637         list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
1638         list.addMouseListener(new MouseHandler());
1639         JScrollPane listScroller = new JScrollPane(list);
1640         listScroller.setPreferredSize(new Dimension(320, 240));
1641         //XXX: Must do the following, too, or else the scroller thinks
1642         //XXX: it's taller than it is:
1643         listScroller.setMinimumSize(new Dimension(250, 80));
1644         listScroller.setAlignmentX(LEFT_ALIGNMENT);
1645 
1646         //Create a container so that we can add a title around
1647         //the scroll pane.  Can't add a title directly to the
1648         //scroll pane because its background would be white.
1649         //Lay out the label and scroll pane from top to button.
1650         JPanel listPane = new JPanel();
1651         listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS));
1652         JLabel label = new JLabel(labelText);
1653         label.setLabelFor (list);
1654         listPane.add(label);
1655         listPane.add(Box.createRigidArea(new Dimension(0,5)));
1656         listPane.add(listScroller);
1657         listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
1658 
1659         //Lay out the buttons from left to right.
1660         JPanel buttonPane = new JPanel();
1661         buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
1662         buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
1663         buttonPane.add(Box.createHorizontalGlue());
1664         buttonPane.add(cancelButton);
1665         buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
1666         buttonPane.add(setButton);
1667 
1668         //Put everything together, using the content pane's BorderLayout.
1669         Container contentPane = getContentPane();
1670         contentPane.add(listPane, BorderLayout.CENTER);
1671         contentPane.add(buttonPane, BorderLayout.SOUTH);
1672         pack();
1673         addKeyListener(new KeyAdapter() {
1674                 @Override
1675                 public void keyPressed(KeyEvent ke) {
1676                     int code = ke.getKeyCode();
1677                     if (code == KeyEvent.VK_ESCAPE) {
1678                         ke.consume();
1679                         value = null;
1680                         setVisible(false);
1681                     }
1682                 }
1683             });
1684     }
1685 
1686     /**
1687      * Shows the dialog.
1688      */
showDialog(Component comp)1689     public String showDialog(Component comp) {
1690         value = null;
1691         setLocationRelativeTo(comp);
1692         setVisible(true);
1693         return value;
1694     }
1695 
1696     // ActionListener
1697 
1698     /**
1699      * Performs an action.
1700      */
actionPerformed(ActionEvent e)1701     public void actionPerformed(ActionEvent e) {
1702         String cmd = e.getActionCommand();
1703         if (cmd.equals("Cancel")) {
1704             setVisible(false);
1705             value = null;
1706         } else if (cmd.equals("Select")) {
1707             value = (String)list.getSelectedValue();
1708             setVisible(false);
1709             swingGui.showFileWindow(value, -1);
1710         }
1711     }
1712 
1713     /**
1714      * MouseListener implementation for {@link #list}.
1715      */
1716     private class MouseHandler extends MouseAdapter {
1717         @Override
mouseClicked(MouseEvent e)1718         public void mouseClicked(MouseEvent e) {
1719             if (e.getClickCount() == 2) {
1720                 setButton.doClick();
1721             }
1722         }
1723     }
1724 }
1725 
1726 /**
1727  * Find function dialog.
1728  */
1729 class FindFunction extends JDialog implements ActionListener {
1730 
1731     /**
1732      * Serializable magic number.
1733      */
1734     private static final long serialVersionUID = 559491015232880916L;
1735 
1736     /**
1737      * Last selected function.
1738      */
1739     private String value;
1740 
1741     /**
1742      * List of functions.
1743      */
1744     private JList list;
1745 
1746     /**
1747      * The debug GUI frame.
1748      */
1749     private SwingGui debugGui;
1750 
1751     /**
1752      * The "Select" button.
1753      */
1754     private JButton setButton;
1755 
1756     /**
1757      * The "Cancel" button.
1758      */
1759     private JButton cancelButton;
1760 
1761     /**
1762      * Creates a new FindFunction.
1763      */
FindFunction(SwingGui debugGui, String title, String labelText)1764     public FindFunction(SwingGui debugGui, String title, String labelText) {
1765         super(debugGui, title, true);
1766         this.debugGui = debugGui;
1767 
1768         cancelButton = new JButton("Cancel");
1769         setButton = new JButton("Select");
1770         cancelButton.addActionListener(this);
1771         setButton.addActionListener(this);
1772         getRootPane().setDefaultButton(setButton);
1773 
1774         list = new JList(new DefaultListModel());
1775         DefaultListModel model = (DefaultListModel)list.getModel();
1776         model.clear();
1777 
1778         String[] a = debugGui.dim.functionNames();
1779         java.util.Arrays.sort(a);
1780         for (int i = 0; i < a.length; i++) {
1781             model.addElement(a[i]);
1782         }
1783         list.setSelectedIndex(0);
1784 
1785         setButton.setEnabled(a.length > 0);
1786         list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
1787         list.addMouseListener(new MouseHandler());
1788         JScrollPane listScroller = new JScrollPane(list);
1789         listScroller.setPreferredSize(new Dimension(320, 240));
1790         listScroller.setMinimumSize(new Dimension(250, 80));
1791         listScroller.setAlignmentX(LEFT_ALIGNMENT);
1792 
1793         //Create a container so that we can add a title around
1794         //the scroll pane.  Can't add a title directly to the
1795         //scroll pane because its background would be white.
1796         //Lay out the label and scroll pane from top to button.
1797         JPanel listPane = new JPanel();
1798         listPane.setLayout(new BoxLayout(listPane, BoxLayout.Y_AXIS));
1799         JLabel label = new JLabel(labelText);
1800         label.setLabelFor (list);
1801         listPane.add(label);
1802         listPane.add(Box.createRigidArea(new Dimension(0,5)));
1803         listPane.add(listScroller);
1804         listPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
1805 
1806         //Lay out the buttons from left to right.
1807         JPanel buttonPane = new JPanel();
1808         buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
1809         buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
1810         buttonPane.add(Box.createHorizontalGlue());
1811         buttonPane.add(cancelButton);
1812         buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
1813         buttonPane.add(setButton);
1814 
1815         //Put everything together, using the content pane's BorderLayout.
1816         Container contentPane = getContentPane();
1817         contentPane.add(listPane, BorderLayout.CENTER);
1818         contentPane.add(buttonPane, BorderLayout.SOUTH);
1819         pack();
1820         addKeyListener(new KeyAdapter() {
1821                 @Override
1822                 public void keyPressed(KeyEvent ke) {
1823                     int code = ke.getKeyCode();
1824                     if (code == KeyEvent.VK_ESCAPE) {
1825                         ke.consume();
1826                         value = null;
1827                         setVisible(false);
1828                     }
1829                 }
1830             });
1831     }
1832 
1833     /**
1834      * Shows the dialog.
1835      */
showDialog(Component comp)1836     public String showDialog(Component comp) {
1837         value = null;
1838         setLocationRelativeTo(comp);
1839         setVisible(true);
1840         return value;
1841     }
1842 
1843     // ActionListener
1844 
1845     /**
1846      * Performs an action.
1847      */
actionPerformed(ActionEvent e)1848     public void actionPerformed(ActionEvent e) {
1849         String cmd = e.getActionCommand();
1850         if (cmd.equals("Cancel")) {
1851             setVisible(false);
1852             value = null;
1853         } else if (cmd.equals("Select")) {
1854             if (list.getSelectedIndex() < 0) {
1855                 return;
1856             }
1857             try {
1858                 value = (String)list.getSelectedValue();
1859             } catch (ArrayIndexOutOfBoundsException exc) {
1860                 return;
1861             }
1862             setVisible(false);
1863             Dim.FunctionSource item = debugGui.dim.functionSourceByName(value);
1864             if (item != null) {
1865                 Dim.SourceInfo si = item.sourceInfo();
1866                 String url = si.url();
1867                 int lineNumber = item.firstLine();
1868                 debugGui.showFileWindow(url, lineNumber);
1869             }
1870         }
1871     }
1872 
1873     /**
1874      * MouseListener implementation for {@link #list}.
1875      */
1876     class MouseHandler extends MouseAdapter {
1877         @Override
mouseClicked(MouseEvent e)1878         public void mouseClicked(MouseEvent e) {
1879             if (e.getClickCount() == 2) {
1880                 setButton.doClick();
1881             }
1882         }
1883     }
1884 }
1885 
1886 /**
1887  * Gutter for FileWindows.
1888  */
1889 class FileHeader extends JPanel implements MouseListener {
1890 
1891     /**
1892      * Serializable magic number.
1893      */
1894     private static final long serialVersionUID = -2858905404778259127L;
1895 
1896     /**
1897      * The line that the mouse was pressed on.
1898      */
1899     private int pressLine = -1;
1900 
1901     /**
1902      * The owning FileWindow.
1903      */
1904     private FileWindow fileWindow;
1905 
1906     /**
1907      * Creates a new FileHeader.
1908      */
FileHeader(FileWindow fileWindow)1909     public FileHeader(FileWindow fileWindow) {
1910         this.fileWindow = fileWindow;
1911         addMouseListener(this);
1912         update();
1913     }
1914 
1915     /**
1916      * Updates the gutter.
1917      */
update()1918     public void update() {
1919         FileTextArea textArea = fileWindow.textArea;
1920         Font font = textArea.getFont();
1921         setFont(font);
1922         FontMetrics metrics = getFontMetrics(font);
1923         int h = metrics.getHeight();
1924         int lineCount = textArea.getLineCount() + 1;
1925         String dummy = Integer.toString(lineCount);
1926         if (dummy.length() < 2) {
1927             dummy = "99";
1928         }
1929         Dimension d = new Dimension();
1930         d.width = metrics.stringWidth(dummy) + 16;
1931         d.height = lineCount * h + 100;
1932         setPreferredSize(d);
1933         setSize(d);
1934     }
1935 
1936     /**
1937      * Paints the component.
1938      */
1939     @Override
paint(Graphics g)1940     public void paint(Graphics g) {
1941         super.paint(g);
1942         FileTextArea textArea = fileWindow.textArea;
1943         Font font = textArea.getFont();
1944         g.setFont(font);
1945         FontMetrics metrics = getFontMetrics(font);
1946         Rectangle clip = g.getClipBounds();
1947         g.setColor(getBackground());
1948         g.fillRect(clip.x, clip.y, clip.width, clip.height);
1949         int ascent = metrics.getMaxAscent();
1950         int h = metrics.getHeight();
1951         int lineCount = textArea.getLineCount() + 1;
1952         String dummy = Integer.toString(lineCount);
1953         if (dummy.length() < 2) {
1954             dummy = "99";
1955         }
1956         int startLine = clip.y / h;
1957         int endLine = (clip.y + clip.height) / h + 1;
1958         int width = getWidth();
1959         if (endLine > lineCount) endLine = lineCount;
1960         for (int i = startLine; i < endLine; i++) {
1961             String text;
1962             int pos = -2;
1963             try {
1964                 pos = textArea.getLineStartOffset(i);
1965             } catch (BadLocationException ignored) {
1966             }
1967             boolean isBreakPoint = fileWindow.isBreakPoint(i + 1);
1968             text = Integer.toString(i + 1) + " ";
1969             int y = i * h;
1970             g.setColor(Color.blue);
1971             g.drawString(text, 0, y + ascent);
1972             int x = width - ascent;
1973             if (isBreakPoint) {
1974                 g.setColor(new Color(0x80, 0x00, 0x00));
1975                 int dy = y + ascent - 9;
1976                 g.fillOval(x, dy, 9, 9);
1977                 g.drawOval(x, dy, 8, 8);
1978                 g.drawOval(x, dy, 9, 9);
1979             }
1980             if (pos == fileWindow.currentPos) {
1981                 Polygon arrow = new Polygon();
1982                 int dx = x;
1983                 y += ascent - 10;
1984                 int dy = y;
1985                 arrow.addPoint(dx, dy + 3);
1986                 arrow.addPoint(dx + 5, dy + 3);
1987                 for (x = dx + 5; x <= dx + 10; x++, y++) {
1988                     arrow.addPoint(x, y);
1989                 }
1990                 for (x = dx + 9; x >= dx + 5; x--, y++) {
1991                     arrow.addPoint(x, y);
1992                 }
1993                 arrow.addPoint(dx + 5, dy + 7);
1994                 arrow.addPoint(dx, dy + 7);
1995                 g.setColor(Color.yellow);
1996                 g.fillPolygon(arrow);
1997                 g.setColor(Color.black);
1998                 g.drawPolygon(arrow);
1999             }
2000         }
2001     }
2002 
2003     // MouseListener
2004 
2005     /**
2006      * Called when the mouse enters the component.
2007      */
mouseEntered(MouseEvent e)2008     public void mouseEntered(MouseEvent e) {
2009     }
2010 
2011     /**
2012      * Called when a mouse button is pressed.
2013      */
mousePressed(MouseEvent e)2014     public void mousePressed(MouseEvent e) {
2015         Font font = fileWindow.textArea.getFont();
2016         FontMetrics metrics = getFontMetrics(font);
2017         int h = metrics.getHeight();
2018         pressLine = e.getY() / h;
2019     }
2020 
2021     /**
2022      * Called when the mouse is clicked.
2023      */
mouseClicked(MouseEvent e)2024     public void mouseClicked(MouseEvent e) {
2025     }
2026 
2027     /**
2028      * Called when the mouse exits the component.
2029      */
mouseExited(MouseEvent e)2030     public void mouseExited(MouseEvent e) {
2031     }
2032 
2033     /**
2034      * Called when a mouse button is released.
2035      */
mouseReleased(MouseEvent e)2036     public void mouseReleased(MouseEvent e) {
2037         if (e.getComponent() == this
2038                 && (e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
2039             int y = e.getY();
2040             Font font = fileWindow.textArea.getFont();
2041             FontMetrics metrics = getFontMetrics(font);
2042             int h = metrics.getHeight();
2043             int line = y/h;
2044             if (line == pressLine) {
2045                 fileWindow.toggleBreakPoint(line + 1);
2046             } else {
2047                 pressLine = -1;
2048             }
2049         }
2050     }
2051 }
2052 
2053 /**
2054  * An internal frame for script files.
2055  */
2056 class FileWindow extends JInternalFrame implements ActionListener {
2057 
2058     /**
2059      * Serializable magic number.
2060      */
2061     private static final long serialVersionUID = -6212382604952082370L;
2062 
2063     /**
2064      * The debugger GUI.
2065      */
2066     private SwingGui debugGui;
2067 
2068     /**
2069      * The SourceInfo object that describes the file.
2070      */
2071     private Dim.SourceInfo sourceInfo;
2072 
2073     /**
2074      * The FileTextArea that displays the file.
2075      */
2076     FileTextArea textArea;
2077 
2078     /**
2079      * The FileHeader that is the gutter for {@link #textArea}.
2080      */
2081     private FileHeader fileHeader;
2082 
2083     /**
2084      * Scroll pane for containing {@link #textArea}.
2085      */
2086     private JScrollPane p;
2087 
2088     /**
2089      * The current offset position.
2090      */
2091     int currentPos;
2092 
2093     /**
2094      * Loads the file.
2095      */
load()2096     void load() {
2097         String url = getUrl();
2098         if (url != null) {
2099             RunProxy proxy = new RunProxy(debugGui, RunProxy.LOAD_FILE);
2100             proxy.fileName = url;
2101             proxy.text = sourceInfo.source();
2102             new Thread(proxy).start();
2103         }
2104     }
2105 
2106     /**
2107      * Returns the offset position for the given line.
2108      */
getPosition(int line)2109     public int getPosition(int line) {
2110         int result = -1;
2111         try {
2112             result = textArea.getLineStartOffset(line);
2113         } catch (javax.swing.text.BadLocationException exc) {
2114         }
2115         return result;
2116     }
2117 
2118     /**
2119      * Returns whether the given line has a breakpoint.
2120      */
isBreakPoint(int line)2121     public boolean isBreakPoint(int line) {
2122         return sourceInfo.breakableLine(line) && sourceInfo.breakpoint(line);
2123     }
2124 
2125     /**
2126      * Toggles the breakpoint on the given line.
2127      */
toggleBreakPoint(int line)2128     public void toggleBreakPoint(int line) {
2129         if (!isBreakPoint(line)) {
2130             setBreakPoint(line);
2131         } else {
2132             clearBreakPoint(line);
2133         }
2134     }
2135 
2136     /**
2137      * Sets a breakpoint on the given line.
2138      */
setBreakPoint(int line)2139     public void setBreakPoint(int line) {
2140         if (sourceInfo.breakableLine(line)) {
2141             boolean changed = sourceInfo.breakpoint(line, true);
2142             if (changed) {
2143                 fileHeader.repaint();
2144             }
2145         }
2146     }
2147 
2148     /**
2149      * Clears a breakpoint from the given line.
2150      */
clearBreakPoint(int line)2151     public void clearBreakPoint(int line) {
2152         if (sourceInfo.breakableLine(line)) {
2153             boolean changed = sourceInfo.breakpoint(line, false);
2154             if (changed) {
2155                 fileHeader.repaint();
2156             }
2157         }
2158     }
2159 
2160     /**
2161      * Creates a new FileWindow.
2162      */
FileWindow(SwingGui debugGui, Dim.SourceInfo sourceInfo)2163     public FileWindow(SwingGui debugGui, Dim.SourceInfo sourceInfo) {
2164         super(SwingGui.getShortName(sourceInfo.url()),
2165               true, true, true, true);
2166         this.debugGui = debugGui;
2167         this.sourceInfo = sourceInfo;
2168         updateToolTip();
2169         currentPos = -1;
2170         textArea = new FileTextArea(this);
2171         textArea.setRows(24);
2172         textArea.setColumns(80);
2173         p = new JScrollPane();
2174         fileHeader = new FileHeader(this);
2175         p.setViewportView(textArea);
2176         p.setRowHeaderView(fileHeader);
2177         setContentPane(p);
2178         pack();
2179         updateText(sourceInfo);
2180         textArea.select(0);
2181     }
2182 
2183     /**
2184      * Updates the tool tip contents.
2185      */
updateToolTip()2186     private void updateToolTip() {
2187         // Try to set tool tip on frame. On Mac OS X 10.5,
2188         // the number of components is different, so try to be safe.
2189         int n = getComponentCount() - 1;
2190         if (n > 1) {
2191             n = 1;
2192         } else if (n < 0) {
2193             return;
2194         }
2195         Component c = getComponent(n);
2196         // this will work at least for Metal L&F
2197         if (c != null && c instanceof JComponent) {
2198             ((JComponent)c).setToolTipText(getUrl());
2199         }
2200     }
2201 
2202     /**
2203      * Returns the URL of the source.
2204      */
getUrl()2205     public String getUrl() {
2206         return sourceInfo.url();
2207     }
2208 
2209     /**
2210      * Called when the text of the script has changed.
2211      */
updateText(Dim.SourceInfo sourceInfo)2212     public void updateText(Dim.SourceInfo sourceInfo) {
2213         this.sourceInfo = sourceInfo;
2214         String newText = sourceInfo.source();
2215         if (!textArea.getText().equals(newText)) {
2216             textArea.setText(newText);
2217             int pos = 0;
2218             if (currentPos != -1) {
2219                 pos = currentPos;
2220             }
2221             textArea.select(pos);
2222         }
2223         fileHeader.update();
2224         fileHeader.repaint();
2225     }
2226 
2227     /**
2228      * Sets the cursor position.
2229      */
setPosition(int pos)2230     public void setPosition(int pos) {
2231         textArea.select(pos);
2232         currentPos = pos;
2233         fileHeader.repaint();
2234     }
2235 
2236     /**
2237      * Selects a range of characters.
2238      */
select(int start, int end)2239     public void select(int start, int end) {
2240         int docEnd = textArea.getDocument().getLength();
2241         textArea.select(docEnd, docEnd);
2242         textArea.select(start, end);
2243     }
2244 
2245     /**
2246      * Disposes this FileWindow.
2247      */
2248     @Override
dispose()2249     public void dispose() {
2250         debugGui.removeWindow(this);
2251         super.dispose();
2252     }
2253 
2254     // ActionListener
2255 
2256     /**
2257      * Performs an action.
2258      */
actionPerformed(ActionEvent e)2259     public void actionPerformed(ActionEvent e) {
2260         String cmd = e.getActionCommand();
2261         if (cmd.equals("Cut")) {
2262             // textArea.cut();
2263         } else if (cmd.equals("Copy")) {
2264             textArea.copy();
2265         } else if (cmd.equals("Paste")) {
2266             // textArea.paste();
2267         }
2268     }
2269 }
2270 
2271 /**
2272  * Table model class for watched expressions.
2273  */
2274 class MyTableModel extends AbstractTableModel {
2275 
2276     /**
2277      * Serializable magic number.
2278      */
2279     private static final long serialVersionUID = 2971618907207577000L;
2280 
2281     /**
2282      * The debugger GUI.
2283      */
2284     private SwingGui debugGui;
2285 
2286     /**
2287      * List of watched expressions.
2288      */
2289     private List<String> expressions;
2290 
2291     /**
2292      * List of values from evaluated from {@link #expressions}.
2293      */
2294     private List<String> values;
2295 
2296     /**
2297      * Creates a new MyTableModel.
2298      */
MyTableModel(SwingGui debugGui)2299     public MyTableModel(SwingGui debugGui) {
2300         this.debugGui = debugGui;
2301         expressions = Collections.synchronizedList(new ArrayList<String>());
2302         values = Collections.synchronizedList(new ArrayList<String>());
2303         expressions.add("");
2304         values.add("");
2305     }
2306 
2307     /**
2308      * Returns the number of columns in the table (2).
2309      */
getColumnCount()2310     public int getColumnCount() {
2311         return 2;
2312     }
2313 
2314     /**
2315      * Returns the number of rows in the table.
2316      */
getRowCount()2317     public int getRowCount() {
2318         return expressions.size();
2319     }
2320 
2321     /**
2322      * Returns the name of the given column.
2323      */
2324     @Override
getColumnName(int column)2325     public String getColumnName(int column) {
2326         switch (column) {
2327         case 0:
2328             return "Expression";
2329         case 1:
2330             return "Value";
2331         }
2332         return null;
2333     }
2334 
2335     /**
2336      * Returns whether the given cell is editable.
2337      */
2338     @Override
isCellEditable(int row, int column)2339     public boolean isCellEditable(int row, int column) {
2340         return true;
2341     }
2342 
2343     /**
2344      * Returns the value in the given cell.
2345      */
getValueAt(int row, int column)2346     public Object getValueAt(int row, int column) {
2347         switch (column) {
2348         case 0:
2349             return expressions.get(row);
2350         case 1:
2351             return values.get(row);
2352         }
2353         return "";
2354     }
2355 
2356     /**
2357      * Sets the value in the given cell.
2358      */
2359     @Override
setValueAt(Object value, int row, int column)2360     public void setValueAt(Object value, int row, int column) {
2361         switch (column) {
2362         case 0:
2363             String expr = value.toString();
2364             expressions.set(row, expr);
2365             String result = "";
2366             if (expr.length() > 0) {
2367                 result = debugGui.dim.eval(expr);
2368                 if (result == null) result = "";
2369             }
2370             values.set(row, result);
2371             updateModel();
2372             if (row + 1 == expressions.size()) {
2373                 expressions.add("");
2374                 values.add("");
2375                 fireTableRowsInserted(row + 1, row + 1);
2376             }
2377             break;
2378         case 1:
2379             // just reset column 2; ignore edits
2380             fireTableDataChanged();
2381         }
2382     }
2383 
2384     /**
2385      * Re-evaluates the expressions in the table.
2386      */
updateModel()2387     void updateModel() {
2388         for (int i = 0; i < expressions.size(); ++i) {
2389             String expr = expressions.get(i);
2390             String result = "";
2391             if (expr.length() > 0) {
2392                 result = debugGui.dim.eval(expr);
2393                 if (result == null) result = "";
2394             } else {
2395                 result = "";
2396             }
2397             result = result.replace('\n', ' ');
2398             values.set(i, result);
2399         }
2400         fireTableDataChanged();
2401     }
2402 }
2403 
2404 /**
2405  * A table for evaluated expressions.
2406  */
2407 class Evaluator extends JTable {
2408 
2409     /**
2410      * Serializable magic number.
2411      */
2412     private static final long serialVersionUID = 8133672432982594256L;
2413 
2414     /**
2415      * The {@link TableModel} for this table.
2416      */
2417     MyTableModel tableModel;
2418 
2419     /**
2420      * Creates a new Evaluator.
2421      */
Evaluator(SwingGui debugGui)2422     public Evaluator(SwingGui debugGui) {
2423         super(new MyTableModel(debugGui));
2424         tableModel = (MyTableModel)getModel();
2425     }
2426 }
2427 
2428 /**
2429  * Tree model for script object inspection.
2430  */
2431 class VariableModel implements TreeTableModel {
2432 
2433     /**
2434      * Serializable magic number.
2435      */
2436     private static final String[] cNames = { " Name", " Value" };
2437 
2438     /**
2439      * Tree column types.
2440      */
2441     private static final Class<?>[] cTypes =
2442         { TreeTableModel.class, String.class };
2443 
2444     /**
2445      * Empty {@link VariableNode} array.
2446      */
2447     private static final VariableNode[] CHILDLESS = new VariableNode[0];
2448 
2449     /**
2450      * The debugger.
2451      */
2452     private Dim debugger;
2453 
2454     /**
2455      * The root node.
2456      */
2457     private VariableNode root;
2458 
2459     /**
2460      * Creates a new VariableModel.
2461      */
VariableModel()2462     public VariableModel() {
2463     }
2464 
2465     /**
2466      * Creates a new VariableModel.
2467      */
VariableModel(Dim debugger, Object scope)2468     public VariableModel(Dim debugger, Object scope) {
2469         this.debugger = debugger;
2470         this.root = new VariableNode(scope, "this");
2471     }
2472 
2473     // TreeTableModel
2474 
2475     /**
2476      * Returns the root node of the tree.
2477      */
getRoot()2478     public Object getRoot() {
2479         if (debugger == null) {
2480             return null;
2481         }
2482         return root;
2483     }
2484 
2485     /**
2486      * Returns the number of children of the given node.
2487      */
getChildCount(Object nodeObj)2488     public int getChildCount(Object nodeObj) {
2489         if (debugger == null) {
2490             return 0;
2491         }
2492         VariableNode node = (VariableNode) nodeObj;
2493         return children(node).length;
2494     }
2495 
2496     /**
2497      * Returns a child of the given node.
2498      */
getChild(Object nodeObj, int i)2499     public Object getChild(Object nodeObj, int i) {
2500         if (debugger == null) {
2501             return null;
2502         }
2503         VariableNode node = (VariableNode) nodeObj;
2504         return children(node)[i];
2505     }
2506 
2507     /**
2508      * Returns whether the given node is a leaf node.
2509      */
isLeaf(Object nodeObj)2510     public boolean isLeaf(Object nodeObj) {
2511         if (debugger == null) {
2512             return true;
2513         }
2514         VariableNode node = (VariableNode) nodeObj;
2515         return children(node).length == 0;
2516     }
2517 
2518     /**
2519      * Returns the index of a node under its parent.
2520      */
getIndexOfChild(Object parentObj, Object childObj)2521     public int getIndexOfChild(Object parentObj, Object childObj) {
2522         if (debugger == null) {
2523             return -1;
2524         }
2525         VariableNode parent = (VariableNode) parentObj;
2526         VariableNode child = (VariableNode) childObj;
2527         VariableNode[] children = children(parent);
2528         for (int i = 0; i != children.length; i++) {
2529             if (children[i] == child) {
2530                 return i;
2531             }
2532         }
2533         return -1;
2534     }
2535 
2536     /**
2537      * Returns whether the given cell is editable.
2538      */
isCellEditable(Object node, int column)2539     public boolean isCellEditable(Object node, int column) {
2540         return column == 0;
2541     }
2542 
2543     /**
2544      * Sets the value at the given cell.
2545      */
setValueAt(Object value, Object node, int column)2546     public void setValueAt(Object value, Object node, int column) { }
2547 
2548     /**
2549      * Adds a TreeModelListener to this tree.
2550      */
addTreeModelListener(TreeModelListener l)2551     public void addTreeModelListener(TreeModelListener l) { }
2552 
2553     /**
2554      * Removes a TreeModelListener from this tree.
2555      */
removeTreeModelListener(TreeModelListener l)2556     public void removeTreeModelListener(TreeModelListener l) { }
2557 
valueForPathChanged(TreePath path, Object newValue)2558     public void valueForPathChanged(TreePath path, Object newValue) { }
2559 
2560     // TreeTableNode
2561 
2562     /**
2563      * Returns the number of columns.
2564      */
getColumnCount()2565     public int getColumnCount() {
2566         return cNames.length;
2567     }
2568 
2569     /**
2570      * Returns the name of the given column.
2571      */
getColumnName(int column)2572     public String getColumnName(int column) {
2573         return cNames[column];
2574     }
2575 
2576     /**
2577      * Returns the type of value stored in the given column.
2578      */
getColumnClass(int column)2579     public Class<?> getColumnClass(int column) {
2580         return cTypes[column];
2581     }
2582 
2583     /**
2584      * Returns the value at the given cell.
2585      */
getValueAt(Object nodeObj, int column)2586     public Object getValueAt(Object nodeObj, int column) {
2587         if (debugger == null) { return null; }
2588         VariableNode node = (VariableNode)nodeObj;
2589         switch (column) {
2590         case 0: // Name
2591             return node.toString();
2592         case 1: // Value
2593             String result;
2594             try {
2595                 result = debugger.objectToString(getValue(node));
2596             } catch (RuntimeException exc) {
2597                 result = exc.getMessage();
2598             }
2599             StringBuffer buf = new StringBuffer();
2600             int len = result.length();
2601             for (int i = 0; i < len; i++) {
2602                 char ch = result.charAt(i);
2603                 if (Character.isISOControl(ch)) {
2604                     ch = ' ';
2605                 }
2606                 buf.append(ch);
2607             }
2608             return buf.toString();
2609         }
2610         return null;
2611     }
2612 
2613     /**
2614      * Returns an array of the children of the given node.
2615      */
children(VariableNode node)2616     private VariableNode[] children(VariableNode node) {
2617         if (node.children != null) {
2618             return node.children;
2619         }
2620 
2621         VariableNode[] children;
2622 
2623         Object value = getValue(node);
2624         Object[] ids = debugger.getObjectIds(value);
2625         if (ids == null || ids.length == 0) {
2626             children = CHILDLESS;
2627         } else {
2628             Arrays.sort(ids, new Comparator<Object>() {
2629                     public int compare(Object l, Object r)
2630                     {
2631                         if (l instanceof String) {
2632                             if (r instanceof Integer) {
2633                                 return -1;
2634                             }
2635                             return ((String)l).compareToIgnoreCase((String)r);
2636                         } else {
2637                             if (r instanceof String) {
2638                                 return 1;
2639                             }
2640                             int lint = ((Integer)l).intValue();
2641                             int rint = ((Integer)r).intValue();
2642                             return lint - rint;
2643                         }
2644                     }
2645             });
2646             children = new VariableNode[ids.length];
2647             for (int i = 0; i != ids.length; ++i) {
2648                 children[i] = new VariableNode(value, ids[i]);
2649             }
2650         }
2651         node.children = children;
2652         return children;
2653     }
2654 
2655     /**
2656      * Returns the value of the given node.
2657      */
getValue(VariableNode node)2658     public Object getValue(VariableNode node) {
2659         try {
2660             return debugger.getObjectProperty(node.object, node.id);
2661         } catch (Exception exc) {
2662             return "undefined";
2663         }
2664     }
2665 
2666     /**
2667      * A variable node in the tree.
2668      */
2669     private static class VariableNode {
2670 
2671         /**
2672          * The script object.
2673          */
2674         private Object object;
2675 
2676         /**
2677          * The object name.  Either a String or an Integer.
2678          */
2679         private Object id;
2680 
2681         /**
2682          * Array of child nodes.  This is filled with the properties of
2683          * the object.
2684          */
2685         private VariableNode[] children;
2686 
2687         /**
2688          * Creates a new VariableNode.
2689          */
VariableNode(Object object, Object id)2690         public VariableNode(Object object, Object id) {
2691             this.object = object;
2692             this.id = id;
2693         }
2694 
2695         /**
2696          * Returns a string representation of this node.
2697          */
2698         @Override
toString()2699         public String toString() {
2700             return id instanceof String
2701                 ? (String) id : "[" + ((Integer) id).intValue() + "]";
2702         }
2703     }
2704 }
2705 
2706 /**
2707  * A tree table for browsing script objects.
2708  */
2709 class MyTreeTable extends JTreeTable {
2710 
2711     /**
2712      * Serializable magic number.
2713      */
2714     private static final long serialVersionUID = 3457265548184453049L;
2715 
2716     /**
2717      * Creates a new MyTreeTable.
2718      */
MyTreeTable(VariableModel model)2719     public MyTreeTable(VariableModel model) {
2720         super(model);
2721     }
2722 
2723     /**
2724      * Initializes a tree for this tree table.
2725      */
resetTree(TreeTableModel treeTableModel)2726     public JTree resetTree(TreeTableModel treeTableModel) {
2727         tree = new TreeTableCellRenderer(treeTableModel);
2728 
2729         // Install a tableModel representing the visible rows in the tree.
2730         super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
2731 
2732         // Force the JTable and JTree to share their row selection models.
2733         ListToTreeSelectionModelWrapper selectionWrapper = new
2734             ListToTreeSelectionModelWrapper();
2735         tree.setSelectionModel(selectionWrapper);
2736         setSelectionModel(selectionWrapper.getListSelectionModel());
2737 
2738         // Make the tree and table row heights the same.
2739         if (tree.getRowHeight() < 1) {
2740             // Metal looks better like this.
2741             setRowHeight(18);
2742         }
2743 
2744         // Install the tree editor renderer and editor.
2745         setDefaultRenderer(TreeTableModel.class, tree);
2746         setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
2747         setShowGrid(true);
2748         setIntercellSpacing(new Dimension(1,1));
2749         tree.setRootVisible(false);
2750         tree.setShowsRootHandles(true);
2751         DefaultTreeCellRenderer r = (DefaultTreeCellRenderer)tree.getCellRenderer();
2752         r.setOpenIcon(null);
2753         r.setClosedIcon(null);
2754         r.setLeafIcon(null);
2755         return tree;
2756     }
2757 
2758     /**
2759      * Returns whether the cell under the coordinates of the mouse
2760      * in the {@link EventObject} is editable.
2761      */
isCellEditable(EventObject e)2762     public boolean isCellEditable(EventObject e) {
2763         if (e instanceof MouseEvent) {
2764             MouseEvent me = (MouseEvent)e;
2765             // If the modifiers are not 0 (or the left mouse button),
2766             // tree may try and toggle the selection, and table
2767             // will then try and toggle, resulting in the
2768             // selection remaining the same. To avoid this, we
2769             // only dispatch when the modifiers are 0 (or the left mouse
2770             // button).
2771             if (me.getModifiers() == 0 ||
2772                 ((me.getModifiers() & (InputEvent.BUTTON1_MASK|1024)) != 0 &&
2773                  (me.getModifiers() &
2774                   (InputEvent.SHIFT_MASK |
2775                    InputEvent.CTRL_MASK |
2776                    InputEvent.ALT_MASK |
2777                    InputEvent.BUTTON2_MASK |
2778                    InputEvent.BUTTON3_MASK |
2779                    64   | //SHIFT_DOWN_MASK
2780                    128  | //CTRL_DOWN_MASK
2781                    512  | // ALT_DOWN_MASK
2782                    2048 | //BUTTON2_DOWN_MASK
2783                    4096   //BUTTON3_DOWN_MASK
2784                    )) == 0)) {
2785                 int row = rowAtPoint(me.getPoint());
2786                 for (int counter = getColumnCount() - 1; counter >= 0;
2787                      counter--) {
2788                     if (TreeTableModel.class == getColumnClass(counter)) {
2789                         MouseEvent newME = new MouseEvent
2790                             (MyTreeTable.this.tree, me.getID(),
2791                              me.getWhen(), me.getModifiers(),
2792                              me.getX() - getCellRect(row, counter, true).x,
2793                              me.getY(), me.getClickCount(),
2794                              me.isPopupTrigger());
2795                         MyTreeTable.this.tree.dispatchEvent(newME);
2796                         break;
2797                     }
2798                 }
2799             }
2800             if (me.getClickCount() >= 3) {
2801                 return true;
2802             }
2803             return false;
2804         }
2805         if (e == null) {
2806             return true;
2807         }
2808         return false;
2809     }
2810 }
2811 
2812 /**
2813  * Panel that shows information about the context.
2814  */
2815 class ContextWindow extends JPanel implements ActionListener {
2816 
2817     /**
2818      * Serializable magic number.
2819      */
2820     private static final long serialVersionUID = 2306040975490228051L;
2821 
2822     /**
2823      * The debugger GUI.
2824      */
2825     private SwingGui debugGui;
2826 
2827     /**
2828      * The combo box that holds the stack frames.
2829      */
2830     JComboBox context;
2831 
2832     /**
2833      * Tool tips for the stack frames.
2834      */
2835     List<String> toolTips;
2836 
2837     /**
2838      * Tabbed pane for "this" and "locals".
2839      */
2840     private JTabbedPane tabs;
2841 
2842     /**
2843      * Tabbed pane for "watch" and "evaluate".
2844      */
2845     private JTabbedPane tabs2;
2846 
2847     /**
2848      * The table showing the "this" object.
2849      */
2850     private MyTreeTable thisTable;
2851 
2852     /**
2853      * The table showing the stack local variables.
2854      */
2855     private MyTreeTable localsTable;
2856 
2857     /**
2858      * The {@link #evaluator}'s table model.
2859      */
2860     private MyTableModel tableModel;
2861 
2862     /**
2863      * The script evaluator table.
2864      */
2865     private Evaluator evaluator;
2866 
2867     /**
2868      * The script evaluation text area.
2869      */
2870     private EvalTextArea cmdLine;
2871 
2872     /**
2873      * The split pane.
2874      */
2875     JSplitPane split;
2876 
2877     /**
2878      * Whether the ContextWindow is enabled.
2879      */
2880     private boolean enabled;
2881 
2882     /**
2883      * Creates a new ContextWindow.
2884      */
ContextWindow(final SwingGui debugGui)2885     public ContextWindow(final SwingGui debugGui) {
2886         this.debugGui = debugGui;
2887         enabled = false;
2888         JPanel left = new JPanel();
2889         JToolBar t1 = new JToolBar();
2890         t1.setName("Variables");
2891         t1.setLayout(new GridLayout());
2892         t1.add(left);
2893         JPanel p1 = new JPanel();
2894         p1.setLayout(new GridLayout());
2895         JPanel p2 = new JPanel();
2896         p2.setLayout(new GridLayout());
2897         p1.add(t1);
2898         JLabel label = new JLabel("Context:");
2899         context = new JComboBox();
2900         context.setLightWeightPopupEnabled(false);
2901         toolTips = Collections.synchronizedList(new java.util.ArrayList<String>());
2902         label.setBorder(context.getBorder());
2903         context.addActionListener(this);
2904         context.setActionCommand("ContextSwitch");
2905         GridBagLayout layout = new GridBagLayout();
2906         left.setLayout(layout);
2907         GridBagConstraints lc = new GridBagConstraints();
2908         lc.insets.left = 5;
2909         lc.anchor = GridBagConstraints.WEST;
2910         lc.ipadx = 5;
2911         layout.setConstraints(label, lc);
2912         left.add(label);
2913         GridBagConstraints c = new GridBagConstraints();
2914         c.gridwidth = GridBagConstraints.REMAINDER;
2915         c.fill = GridBagConstraints.HORIZONTAL;
2916         c.anchor = GridBagConstraints.WEST;
2917         layout.setConstraints(context, c);
2918         left.add(context);
2919         tabs = new JTabbedPane(SwingConstants.BOTTOM);
2920         tabs.setPreferredSize(new Dimension(500,300));
2921         thisTable = new MyTreeTable(new VariableModel());
2922         JScrollPane jsp = new JScrollPane(thisTable);
2923         jsp.getViewport().setViewSize(new Dimension(5,2));
2924         tabs.add("this", jsp);
2925         localsTable = new MyTreeTable(new VariableModel());
2926         localsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
2927         localsTable.setPreferredSize(null);
2928         jsp = new JScrollPane(localsTable);
2929         tabs.add("Locals", jsp);
2930         c.weightx  = c.weighty = 1;
2931         c.gridheight = GridBagConstraints.REMAINDER;
2932         c.fill = GridBagConstraints.BOTH;
2933         c.anchor = GridBagConstraints.WEST;
2934         layout.setConstraints(tabs, c);
2935         left.add(tabs);
2936         evaluator = new Evaluator(debugGui);
2937         cmdLine = new EvalTextArea(debugGui);
2938         //cmdLine.requestFocus();
2939         tableModel = evaluator.tableModel;
2940         jsp = new JScrollPane(evaluator);
2941         JToolBar t2 = new JToolBar();
2942         t2.setName("Evaluate");
2943         tabs2 = new JTabbedPane(SwingConstants.BOTTOM);
2944         tabs2.add("Watch", jsp);
2945         tabs2.add("Evaluate", new JScrollPane(cmdLine));
2946         tabs2.setPreferredSize(new Dimension(500,300));
2947         t2.setLayout(new GridLayout());
2948         t2.add(tabs2);
2949         p2.add(t2);
2950         evaluator.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
2951         split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
2952                                p1, p2);
2953         split.setOneTouchExpandable(true);
2954         SwingGui.setResizeWeight(split, 0.5);
2955         setLayout(new BorderLayout());
2956         add(split, BorderLayout.CENTER);
2957 
2958         final JToolBar finalT1 = t1;
2959         final JToolBar finalT2 = t2;
2960         final JPanel finalP1 = p1;
2961         final JPanel finalP2 = p2;
2962         final JSplitPane finalSplit = split;
2963         final JPanel finalThis = this;
2964 
2965         ComponentListener clistener = new ComponentListener() {
2966                 boolean t2Docked = true;
2967                 void check(Component comp) {
2968                     Component thisParent = finalThis.getParent();
2969                     if (thisParent == null) {
2970                         return;
2971                     }
2972                     Component parent = finalT1.getParent();
2973                     boolean leftDocked = true;
2974                     boolean rightDocked = true;
2975                     boolean adjustVerticalSplit = false;
2976                     if (parent != null) {
2977                         if (parent != finalP1) {
2978                             while (!(parent instanceof JFrame)) {
2979                                 parent = parent.getParent();
2980                             }
2981                             JFrame frame = (JFrame)parent;
2982                             debugGui.addTopLevel("Variables", frame);
2983 
2984                             // We need the following hacks because:
2985                             // - We want an undocked toolbar to be
2986                             //   resizable.
2987                             // - We are using JToolbar as a container of a
2988                             //   JComboBox. Without this JComboBox's popup
2989                             //   can get left floating when the toolbar is
2990                             //   re-docked.
2991                             //
2992                             // We make the frame resizable and then
2993                             // remove JToolbar's window listener
2994                             // and insert one of our own that first ensures
2995                             // the JComboBox's popup window is closed
2996                             // and then calls JToolbar's window listener.
2997                             if (!frame.isResizable()) {
2998                                 frame.setResizable(true);
2999                                 frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
3000                                 final EventListener[] l =
3001                                     frame.getListeners(WindowListener.class);
3002                                 frame.removeWindowListener((WindowListener)l[0]);
3003                                 frame.addWindowListener(new WindowAdapter() {
3004                                         @Override
3005                                         public void windowClosing(WindowEvent e) {
3006                                             context.hidePopup();
3007                                             ((WindowListener)l[0]).windowClosing(e);
3008                                         }
3009                                     });
3010                                 //adjustVerticalSplit = true;
3011                             }
3012                             leftDocked = false;
3013                         } else {
3014                             leftDocked = true;
3015                         }
3016                     }
3017                     parent = finalT2.getParent();
3018                     if (parent != null) {
3019                         if (parent != finalP2) {
3020                             while (!(parent instanceof JFrame)) {
3021                                 parent = parent.getParent();
3022                             }
3023                             JFrame frame = (JFrame)parent;
3024                             debugGui.addTopLevel("Evaluate", frame);
3025                             frame.setResizable(true);
3026                             rightDocked = false;
3027                         } else {
3028                             rightDocked = true;
3029                         }
3030                     }
3031                     if (leftDocked && t2Docked && rightDocked && t2Docked) {
3032                         // no change
3033                         return;
3034                     }
3035                     t2Docked = rightDocked;
3036                     JSplitPane split = (JSplitPane)thisParent;
3037                     if (leftDocked) {
3038                         if (rightDocked) {
3039                             finalSplit.setDividerLocation(0.5);
3040                         } else {
3041                             finalSplit.setDividerLocation(1.0);
3042                         }
3043                         if (adjustVerticalSplit) {
3044                             split.setDividerLocation(0.66);
3045                         }
3046 
3047                     } else if (rightDocked) {
3048                             finalSplit.setDividerLocation(0.0);
3049                             split.setDividerLocation(0.66);
3050                     } else {
3051                         // both undocked
3052                         split.setDividerLocation(1.0);
3053                     }
3054                 }
3055                 public void componentHidden(ComponentEvent e) {
3056                     check(e.getComponent());
3057                 }
3058                 public void componentMoved(ComponentEvent e) {
3059                     check(e.getComponent());
3060                 }
3061                 public void componentResized(ComponentEvent e) {
3062                     check(e.getComponent());
3063                 }
3064                 public void componentShown(ComponentEvent e) {
3065                     check(e.getComponent());
3066                 }
3067             };
3068         p1.addContainerListener(new ContainerListener() {
3069             public void componentAdded(ContainerEvent e) {
3070                 Component thisParent = finalThis.getParent();
3071                 JSplitPane split = (JSplitPane)thisParent;
3072                 if (e.getChild() == finalT1) {
3073                     if (finalT2.getParent() == finalP2) {
3074                         // both docked
3075                         finalSplit.setDividerLocation(0.5);
3076                     } else {
3077                         // left docked only
3078                         finalSplit.setDividerLocation(1.0);
3079                     }
3080                     split.setDividerLocation(0.66);
3081                 }
3082             }
3083             public void componentRemoved(ContainerEvent e) {
3084                 Component thisParent = finalThis.getParent();
3085                 JSplitPane split = (JSplitPane)thisParent;
3086                 if (e.getChild() == finalT1) {
3087                     if (finalT2.getParent() == finalP2) {
3088                         // right docked only
3089                         finalSplit.setDividerLocation(0.0);
3090                         split.setDividerLocation(0.66);
3091                     } else {
3092                         // both undocked
3093                         split.setDividerLocation(1.0);
3094                     }
3095                 }
3096             }
3097             });
3098         t1.addComponentListener(clistener);
3099         t2.addComponentListener(clistener);
3100         setEnabled(false);
3101     }
3102 
3103     /**
3104      * Enables or disables the component.
3105      */
3106     @Override
setEnabled(boolean enabled)3107     public void setEnabled(boolean enabled) {
3108         context.setEnabled(enabled);
3109         thisTable.setEnabled(enabled);
3110         localsTable.setEnabled(enabled);
3111         evaluator.setEnabled(enabled);
3112         cmdLine.setEnabled(enabled);
3113     }
3114 
3115     /**
3116      * Disables updating of the component.
3117      */
disableUpdate()3118     public void disableUpdate() {
3119         enabled = false;
3120     }
3121 
3122     /**
3123      * Enables updating of the component.
3124      */
enableUpdate()3125     public void enableUpdate() {
3126         enabled = true;
3127     }
3128 
3129     // ActionListener
3130 
3131     /**
3132      * Performs an action.
3133      */
actionPerformed(ActionEvent e)3134     public void actionPerformed(ActionEvent e) {
3135         if (!enabled) return;
3136         if (e.getActionCommand().equals("ContextSwitch")) {
3137             Dim.ContextData contextData = debugGui.dim.currentContextData();
3138             if (contextData == null) { return; }
3139             int frameIndex = context.getSelectedIndex();
3140             context.setToolTipText(toolTips.get(frameIndex));
3141             int frameCount = contextData.frameCount();
3142             if (frameIndex >= frameCount) {
3143                 return;
3144             }
3145             Dim.StackFrame frame = contextData.getFrame(frameIndex);
3146             Object scope = frame.scope();
3147             Object thisObj = frame.thisObj();
3148             thisTable.resetTree(new VariableModel(debugGui.dim, thisObj));
3149             VariableModel scopeModel;
3150             if (scope != thisObj) {
3151                 scopeModel = new VariableModel(debugGui.dim, scope);
3152             } else {
3153                 scopeModel = new VariableModel();
3154             }
3155             localsTable.resetTree(scopeModel);
3156             debugGui.dim.contextSwitch(frameIndex);
3157             debugGui.showStopLine(frame);
3158             tableModel.updateModel();
3159         }
3160     }
3161 }
3162 
3163 /**
3164  * The debugger frame menu bar.
3165  */
3166 class Menubar extends JMenuBar implements ActionListener {
3167 
3168     /**
3169      * Serializable magic number.
3170      */
3171     private static final long serialVersionUID = 3217170497245911461L;
3172 
3173     /**
3174      * Items that are enabled only when interrupted.
3175      */
3176     private List<JMenuItem> interruptOnlyItems =
3177         Collections.synchronizedList(new ArrayList<JMenuItem>());
3178 
3179     /**
3180      * Items that are enabled only when running.
3181      */
3182     private List<JMenuItem> runOnlyItems =
3183         Collections.synchronizedList(new ArrayList<JMenuItem>());
3184 
3185     /**
3186      * The debugger GUI.
3187      */
3188     private SwingGui debugGui;
3189 
3190     /**
3191      * The menu listing the internal frames.
3192      */
3193     private JMenu windowMenu;
3194 
3195     /**
3196      * The "Break on exceptions" menu item.
3197      */
3198     private JCheckBoxMenuItem breakOnExceptions;
3199 
3200     /**
3201      * The "Break on enter" menu item.
3202      */
3203     private JCheckBoxMenuItem breakOnEnter;
3204 
3205     /**
3206      * The "Break on return" menu item.
3207      */
3208     private JCheckBoxMenuItem breakOnReturn;
3209 
3210     /**
3211      * Creates a new Menubar.
3212      */
Menubar(SwingGui debugGui)3213     Menubar(SwingGui debugGui) {
3214         super();
3215         this.debugGui = debugGui;
3216         String[] fileItems  = {"Open...", "Run...", "", "Exit"};
3217         String[] fileCmds  = {"Open", "Load", "", "Exit"};
3218         char[] fileShortCuts = {'0', 'N', 0, 'X'};
3219         int[] fileAccelerators = {KeyEvent.VK_O,
3220                                   KeyEvent.VK_N,
3221                                   0,
3222                                   KeyEvent.VK_Q};
3223         String[] editItems = {"Cut", "Copy", "Paste", "Go to function..."};
3224         char[] editShortCuts = {'T', 'C', 'P', 'F'};
3225         String[] debugItems = {"Break", "Go", "Step Into", "Step Over", "Step Out"};
3226         char[] debugShortCuts = {'B', 'G', 'I', 'O', 'T'};
3227         String[] plafItems = {"Metal", "Windows", "Motif"};
3228         char [] plafShortCuts = {'M', 'W', 'F'};
3229         int[] debugAccelerators = {KeyEvent.VK_PAUSE,
3230                                    KeyEvent.VK_F5,
3231                                    KeyEvent.VK_F11,
3232                                    KeyEvent.VK_F7,
3233                                    KeyEvent.VK_F8,
3234                                    0, 0};
3235 
3236         JMenu fileMenu = new JMenu("File");
3237         fileMenu.setMnemonic('F');
3238         JMenu editMenu = new JMenu("Edit");
3239         editMenu.setMnemonic('E');
3240         JMenu plafMenu = new JMenu("Platform");
3241         plafMenu.setMnemonic('P');
3242         JMenu debugMenu = new JMenu("Debug");
3243         debugMenu.setMnemonic('D');
3244         windowMenu = new JMenu("Window");
3245         windowMenu.setMnemonic('W');
3246         for (int i = 0; i < fileItems.length; ++i) {
3247             if (fileItems[i].length() == 0) {
3248                 fileMenu.addSeparator();
3249             } else {
3250                 JMenuItem item = new JMenuItem(fileItems[i],
3251                                                fileShortCuts[i]);
3252                 item.setActionCommand(fileCmds[i]);
3253                 item.addActionListener(this);
3254                 fileMenu.add(item);
3255                 if (fileAccelerators[i] != 0) {
3256                     KeyStroke k = KeyStroke.getKeyStroke(fileAccelerators[i], Event.CTRL_MASK);
3257                     item.setAccelerator(k);
3258                 }
3259             }
3260         }
3261         for (int i = 0; i < editItems.length; ++i) {
3262             JMenuItem item = new JMenuItem(editItems[i],
3263                                            editShortCuts[i]);
3264             item.addActionListener(this);
3265             editMenu.add(item);
3266         }
3267         for (int i = 0; i < plafItems.length; ++i) {
3268             JMenuItem item = new JMenuItem(plafItems[i],
3269                                            plafShortCuts[i]);
3270             item.addActionListener(this);
3271             plafMenu.add(item);
3272         }
3273         for (int i = 0; i < debugItems.length; ++i) {
3274             JMenuItem item = new JMenuItem(debugItems[i],
3275                                            debugShortCuts[i]);
3276             item.addActionListener(this);
3277             if (debugAccelerators[i] != 0) {
3278                 KeyStroke k = KeyStroke.getKeyStroke(debugAccelerators[i], 0);
3279                 item.setAccelerator(k);
3280             }
3281             if (i != 0) {
3282                 interruptOnlyItems.add(item);
3283             } else {
3284                 runOnlyItems.add(item);
3285             }
3286             debugMenu.add(item);
3287         }
3288         breakOnExceptions = new JCheckBoxMenuItem("Break on Exceptions");
3289         breakOnExceptions.setMnemonic('X');
3290         breakOnExceptions.addActionListener(this);
3291         breakOnExceptions.setSelected(false);
3292         debugMenu.add(breakOnExceptions);
3293 
3294         breakOnEnter = new JCheckBoxMenuItem("Break on Function Enter");
3295         breakOnEnter.setMnemonic('E');
3296         breakOnEnter.addActionListener(this);
3297         breakOnEnter.setSelected(false);
3298         debugMenu.add(breakOnEnter);
3299 
3300         breakOnReturn = new JCheckBoxMenuItem("Break on Function Return");
3301         breakOnReturn.setMnemonic('R');
3302         breakOnReturn.addActionListener(this);
3303         breakOnReturn.setSelected(false);
3304         debugMenu.add(breakOnReturn);
3305 
3306         add(fileMenu);
3307         add(editMenu);
3308         //add(plafMenu);
3309         add(debugMenu);
3310         JMenuItem item;
3311         windowMenu.add(item = new JMenuItem("Cascade", 'A'));
3312         item.addActionListener(this);
3313         windowMenu.add(item = new JMenuItem("Tile", 'T'));
3314         item.addActionListener(this);
3315         windowMenu.addSeparator();
3316         windowMenu.add(item = new JMenuItem("Console", 'C'));
3317         item.addActionListener(this);
3318         add(windowMenu);
3319 
3320         updateEnabled(false);
3321     }
3322 
3323     /**
3324      * Returns the "Break on exceptions" menu item.
3325      */
getBreakOnExceptions()3326     public JCheckBoxMenuItem getBreakOnExceptions() {
3327         return breakOnExceptions;
3328     }
3329 
3330     /**
3331      * Returns the "Break on enter" menu item.
3332      */
getBreakOnEnter()3333     public JCheckBoxMenuItem getBreakOnEnter() {
3334         return breakOnEnter;
3335     }
3336 
3337     /**
3338      * Returns the "Break on return" menu item.
3339      */
getBreakOnReturn()3340     public JCheckBoxMenuItem getBreakOnReturn() {
3341         return breakOnReturn;
3342     }
3343 
3344     /**
3345      * Returns the "Debug" menu.
3346      */
getDebugMenu()3347     public JMenu getDebugMenu() {
3348         return getMenu(2);
3349     }
3350 
3351     // ActionListener
3352 
3353     /**
3354      * Performs an action.
3355      */
actionPerformed(ActionEvent e)3356     public void actionPerformed(ActionEvent e) {
3357         String cmd = e.getActionCommand();
3358         String plaf_name = null;
3359         if (cmd.equals("Metal")) {
3360             plaf_name = "javax.swing.plaf.metal.MetalLookAndFeel";
3361         } else if (cmd.equals("Windows")) {
3362             plaf_name = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
3363         } else if (cmd.equals("Motif")) {
3364             plaf_name = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
3365         } else {
3366             Object source = e.getSource();
3367             if (source == breakOnExceptions) {
3368                 debugGui.dim.setBreakOnExceptions(breakOnExceptions.isSelected());
3369             } else if (source == breakOnEnter) {
3370                 debugGui.dim.setBreakOnEnter(breakOnEnter.isSelected());
3371             } else if (source == breakOnReturn) {
3372                 debugGui.dim.setBreakOnReturn(breakOnReturn.isSelected());
3373             } else {
3374                 debugGui.actionPerformed(e);
3375             }
3376             return;
3377         }
3378         try {
3379             UIManager.setLookAndFeel(plaf_name);
3380             SwingUtilities.updateComponentTreeUI(debugGui);
3381             SwingUtilities.updateComponentTreeUI(debugGui.dlg);
3382         } catch (Exception ignored) {
3383             //ignored.printStackTrace();
3384         }
3385     }
3386 
3387     /**
3388      * Adds a file to the window menu.
3389      */
addFile(String url)3390     public void addFile(String url) {
3391         int count = windowMenu.getItemCount();
3392         JMenuItem item;
3393         if (count == 4) {
3394             windowMenu.addSeparator();
3395             count++;
3396         }
3397         JMenuItem lastItem = windowMenu.getItem(count -1);
3398         boolean hasMoreWin = false;
3399         int maxWin = 5;
3400         if (lastItem != null &&
3401            lastItem.getText().equals("More Windows...")) {
3402             hasMoreWin = true;
3403             maxWin++;
3404         }
3405         if (!hasMoreWin && count - 4 == 5) {
3406             windowMenu.add(item = new JMenuItem("More Windows...", 'M'));
3407             item.setActionCommand("More Windows...");
3408             item.addActionListener(this);
3409             return;
3410         } else if (count - 4 <= maxWin) {
3411             if (hasMoreWin) {
3412                 count--;
3413                 windowMenu.remove(lastItem);
3414             }
3415             String shortName = SwingGui.getShortName(url);
3416 
3417             windowMenu.add(item = new JMenuItem((char)('0' + (count-4)) + " " + shortName, '0' + (count - 4)));
3418             if (hasMoreWin) {
3419                 windowMenu.add(lastItem);
3420             }
3421         } else {
3422             return;
3423         }
3424         item.setActionCommand(url);
3425         item.addActionListener(this);
3426     }
3427 
3428     /**
3429      * Updates the enabledness of menu items.
3430      */
updateEnabled(boolean interrupted)3431     public void updateEnabled(boolean interrupted) {
3432         for (int i = 0; i != interruptOnlyItems.size(); ++i) {
3433             JMenuItem item = interruptOnlyItems.get(i);
3434             item.setEnabled(interrupted);
3435         }
3436 
3437         for (int i = 0; i != runOnlyItems.size(); ++i) {
3438             JMenuItem item = runOnlyItems.get(i);
3439             item.setEnabled(!interrupted);
3440         }
3441     }
3442 }
3443 
3444 /**
3445  * Class to consolidate all cases that require to implement Runnable
3446  * to avoid class generation bloat.
3447  */
3448 class RunProxy implements Runnable {
3449 
3450     // Constants for 'type'.
3451     static final int OPEN_FILE = 1;
3452     static final int LOAD_FILE = 2;
3453     static final int UPDATE_SOURCE_TEXT = 3;
3454     static final int ENTER_INTERRUPT = 4;
3455 
3456     /**
3457      * The debugger GUI.
3458      */
3459     private SwingGui debugGui;
3460 
3461     /**
3462      * The type of Runnable this object is.  Takes one of the constants
3463      * defined in this class.
3464      */
3465     private int type;
3466 
3467     /**
3468      * The name of the file to open or load.
3469      */
3470     String fileName;
3471 
3472     /**
3473      * The source text to update.
3474      */
3475     String text;
3476 
3477     /**
3478      * The source for which to update the text.
3479      */
3480     Dim.SourceInfo sourceInfo;
3481 
3482     /**
3483      * The frame to interrupt in.
3484      */
3485     Dim.StackFrame lastFrame;
3486 
3487     /**
3488      * The name of the interrupted thread.
3489      */
3490     String threadTitle;
3491 
3492     /**
3493      * The message of the exception thrown that caused the thread
3494      * interruption, if any.
3495      */
3496     String alertMessage;
3497 
3498     /**
3499      * Creates a new RunProxy.
3500      */
RunProxy(SwingGui debugGui, int type)3501     public RunProxy(SwingGui debugGui, int type) {
3502         this.debugGui = debugGui;
3503         this.type = type;
3504     }
3505 
3506     /**
3507      * Runs this Runnable.
3508      */
run()3509     public void run() {
3510         switch (type) {
3511           case OPEN_FILE:
3512             try {
3513                 debugGui.dim.compileScript(fileName, text);
3514             } catch (RuntimeException ex) {
3515                 MessageDialogWrapper.showMessageDialog(
3516                     debugGui, ex.getMessage(), "Error Compiling "+fileName,
3517                     JOptionPane.ERROR_MESSAGE);
3518             }
3519             break;
3520 
3521           case LOAD_FILE:
3522             try {
3523                 debugGui.dim.evalScript(fileName, text);
3524             } catch (RuntimeException ex) {
3525                 MessageDialogWrapper.showMessageDialog(
3526                     debugGui, ex.getMessage(), "Run error for "+fileName,
3527                     JOptionPane.ERROR_MESSAGE);
3528             }
3529             break;
3530 
3531           case UPDATE_SOURCE_TEXT:
3532             {
3533                 String fileName = sourceInfo.url();
3534                 if (!debugGui.updateFileWindow(sourceInfo) &&
3535                         !fileName.equals("<stdin>")) {
3536                     debugGui.createFileWindow(sourceInfo, -1);
3537                 }
3538             }
3539             break;
3540 
3541           case ENTER_INTERRUPT:
3542             debugGui.enterInterruptImpl(lastFrame, threadTitle, alertMessage);
3543             break;
3544 
3545           default:
3546             throw new IllegalArgumentException(String.valueOf(type));
3547 
3548         }
3549     }
3550 }
3551