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 < 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