001: /*
002: * VisualDebuggerView.java
003: *
004: * Copyright (c) 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * See the file "LICENSE.txt" for information on usage and redistribution of
007: * this file, and for a DISCLAIMER OF ALL WARRANTIES.
008: */
009: package pnuts.tools;
010:
011: import java.awt.BorderLayout;
012: import java.awt.Color;
013: import java.awt.Component;
014: import java.awt.Container;
015: import java.awt.Font;
016: import java.awt.Insets;
017: import java.awt.Point;
018: import java.awt.event.ActionEvent;
019: import java.awt.event.MouseAdapter;
020: import java.awt.event.MouseEvent;
021: import java.awt.event.WindowAdapter;
022: import java.awt.event.WindowEvent;
023: import java.io.BufferedReader;
024: import java.io.File;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.InputStreamReader;
028: import java.io.PrintWriter;
029: import java.io.StringWriter;
030: import java.net.URL;
031: import java.util.Enumeration;
032: import java.util.Hashtable;
033: import java.util.Locale;
034: import java.util.MissingResourceException;
035: import java.util.ResourceBundle;
036: import java.util.StringTokenizer;
037: import java.util.Vector;
038: import javax.swing.AbstractAction;
039: import javax.swing.Action;
040: import javax.swing.Box;
041: import javax.swing.ImageIcon;
042: import javax.swing.JButton;
043: import javax.swing.JDialog;
044: import javax.swing.JFileChooser;
045: import javax.swing.JFrame;
046: import javax.swing.JMenu;
047: import javax.swing.JMenuBar;
048: import javax.swing.JMenuItem;
049: import javax.swing.JScrollPane;
050: import javax.swing.JTextArea;
051: import javax.swing.JToolBar;
052: import javax.swing.KeyStroke;
053: import javax.swing.text.BadLocationException;
054: import javax.swing.text.DefaultHighlighter;
055: import javax.swing.text.Element;
056: import javax.swing.text.Highlighter;
057: import javax.swing.UIManager;
058: import org.pnuts.awt.DialogOutputStream;
059: import pnuts.lang.Context;
060: import pnuts.lang.Runtime;
061: import pnuts.lang.SimpleNode;
062:
063: public class VisualDebuggerView {
064: public static final String imageSuffix = ".image";
065: public static final String labelSuffix = ".label";
066: public static final String actionSuffix = ".action";
067: public static final String tipSuffix = ".tooltip";
068: public static final String shortcutSuffix = ".shortcut";
069: public static final String openAction = "open";
070: public static final String stepAction = "step";
071: public static final String stepUpAction = "stepUp";
072: public static final String nextAction = "next";
073: public static final String contAction = "cont";
074: public static final String closeAction = "close";
075: public static final String inspectAction = "inspect";
076: public static final String clearAction = "clear";
077:
078: static final int DEFAULT_WIDTH = 500;
079: static final int DEFAULT_HEIGHT = 600;
080: static final Color DEFALT_CURRENT_POSITION_COLOR = Color.cyan;
081: static final Color DEFALT_BREAK_POINT_COLOR = Color.orange;
082:
083: private static String actionNames[] = { openAction, stepAction,
084: stepUpAction, nextAction, contAction, closeAction,
085: clearAction, inspectAction };
086:
087: Action[] defaultActions = new Action[] {
088: new MenuAction(VisualDebuggerModel.OPEN),
089: new MenuAction(VisualDebuggerModel.STEP),
090: new MenuAction(VisualDebuggerModel.STEP_UP),
091: new MenuAction(VisualDebuggerModel.NEXT),
092: new MenuAction(VisualDebuggerModel.CONT),
093: new MenuAction(VisualDebuggerModel.CLOSE),
094: new MenuAction(VisualDebuggerModel.CLEAR_BP),
095: new MenuAction(VisualDebuggerModel.INSPECT) };
096:
097: private ResourceBundle resources;
098:
099: static {
100: try {
101: UIManager.setLookAndFeel(UIManager
102: .getSystemLookAndFeelClassName());
103: } catch (Exception e) {
104: e.printStackTrace();
105: }
106: }
107:
108: static ResourceBundle getDefaultResourceBundle() {
109: try {
110: return ResourceBundle.getBundle("pnuts.tools.dbg", Locale
111: .getDefault());
112: } catch (MissingResourceException mre) {
113: throw new RuntimeException("resource not found");
114: }
115: }
116:
117: private Color currentPositionColor;
118: private Color breakPointColor;
119: private Hashtable menuItems;
120: private Hashtable commands;
121: private boolean guiStarted = false;
122: private Hashtable highlights; // source -> Highlighter
123: private Hashtable tags; // source:line -> highlight tag
124: private Object traceTag;
125: private Element lineMap;
126: private JFrame jfr;
127: private JTextArea jta;
128: private JToolBar toolbar;
129: private JMenuBar jmb;
130: private ContextView contextView;
131: private JDialog inspector;
132: private PrintWriter errorStream;
133: private int windowWidth;
134: private int windowHeight;
135: protected VisualDebuggerModel model;
136:
137: public VisualDebuggerView() {
138: this (getDefaultResourceBundle());
139: }
140:
141: public VisualDebuggerView(ResourceBundle resourceBundle) {
142: this .highlights = new Hashtable();
143: this .tags = new Hashtable();
144: this .commands = new Hashtable();
145: for (int i = 0; i < actionNames.length; i++) {
146: commands.put(actionNames[i], new MenuAction(i + 1));
147: }
148: this .resources = resourceBundle;
149: }
150:
151: public VisualDebuggerModel getModel() {
152: return model;
153: }
154:
155: /**
156: * Returns a JFrame
157: *
158: * The default behavior of this method creates and returns a JFrame object.
159: * Subclasses may override this method to define a different way of getting JFrame.
160: */
161: protected JFrame getJFrame() {
162: return new JFrame();
163: }
164:
165: public void startGUI() {
166: if (guiStarted) {
167: return;
168: }
169: initializeLineColors();
170: jfr = getJFrame();
171: jfr.addWindowListener(new WindowAdapter() {
172: public void windowClosing(WindowEvent e) {
173: model.do_close();
174: exitGUI();
175: }
176: });
177: jta = createTextArea();
178: menuItems = new Hashtable();
179: jmb = createMenubar();
180: if (jmb != null) {
181: jfr.setJMenuBar(jmb);
182: }
183: Container contentPane = jfr.getContentPane();
184: contentPane.setLayout(new BorderLayout());
185: Component tb = createToolbar();
186: if (tb != null) {
187: contentPane.add("North", tb);
188: }
189: contentPane.add(new JScrollPane(jta));
190:
191: int w = getIntResource("width");
192: if (w < 0) {
193: w = DEFAULT_WIDTH;
194: }
195: int h = getIntResource("height");
196: if (h < 0) {
197: h = DEFAULT_HEIGHT;
198: }
199: jfr.setSize(w, h);
200: jfr.setVisible(true);
201:
202: contextView = new ContextView(this );
203: inspector = new JDialog(jfr);
204: inspector.setLocation(jfr.getX() + jfr.getWidth() + 1, jfr
205: .getY());
206: inspector.getContentPane().add(contextView.getContainer());
207: inspector.setSize(
208: (int) inspector.getPreferredSize().getWidth() + 20,
209: (int) inspector.getPreferredSize().getHeight() + 20);
210:
211: errorStream = new PrintWriter(new DialogOutputStream(jfr), true);
212: guiStarted = true;
213: }
214:
215: void initializeLineColors() {
216: int i = getIntResource("currentPositionColor");
217: if (i < 0) {
218: this .currentPositionColor = DEFALT_CURRENT_POSITION_COLOR;
219: } else {
220: this .currentPositionColor = new Color(i);
221: }
222: i = getIntResource("breakPointColor");
223: if (i < 0) {
224: this .breakPointColor = DEFALT_BREAK_POINT_COLOR;
225: } else {
226: this .breakPointColor = new Color(i);
227: }
228: }
229:
230: public void exitGUI() {
231: jfr.dispose();
232: jfr = null;
233: jta = null;
234: guiStarted = false;
235: }
236:
237: /**
238: * Returns the title string Subclasses may override this method to customize
239: * the window title.
240: *
241: * @param source
242: * the script source
243: * @return the title string
244: */
245: protected String getTitleString(Object source) {
246: return (source == null) ? "?" : source.toString();
247: }
248:
249: void update(Object source, int beginLine) {
250: update(source, beginLine, null, null);
251: }
252:
253: /**
254: * Updates the view
255: */
256: public void update(Object source, int beginLine, SimpleNode node,
257: Context c) {
258: startGUI();
259: c.setErrorWriter(errorStream);
260:
261: if (source == null) {
262: if (node != null) {
263: jta.setText(Runtime.unparse(node, null));
264: } else {
265: jta.setText("");
266: }
267: }
268:
269: contextView.setContext(c);
270: try {
271: Highlighter highlighter = null;
272: if (source != null) {
273: highlighter = (Highlighter) highlights.get(source);
274: if (highlighter == null) {
275: highlighter = new DefaultHighlighter();
276: highlights.put(source, highlighter);
277: }
278: } else {
279: highlighter = new DefaultHighlighter();
280: jfr.setTitle(getTitleString(source));
281: }
282: boolean sourceChanged = false;
283:
284: if (source != null
285: && !source.equals(model.getCurrentSource())) {
286: sourceChanged = true;
287: jfr.setTitle(getTitleString(source));
288: StringWriter sw = new StringWriter();
289: if (source instanceof URL) {
290: URL url = (URL) source;
291: InputStream in = url.openStream();
292: BufferedReader reader = new BufferedReader(
293: new InputStreamReader(in));
294: try {
295: char[] buf = new char[512];
296: int n = 0;
297: while ((n = reader.read(buf)) != -1) {
298: sw.write(buf, 0, n);
299: }
300: jta.setText(sw.toString());
301: } finally {
302: reader.close();
303: }
304:
305: } else if (source instanceof Runtime) {
306: jta.setText("");
307: return;
308: } else if (source instanceof String) {
309: jta.setText((String) source);
310: } else {
311: throw new RuntimeException("invalid source:"
312: + source);
313: }
314: }
315:
316: if (source != null) {
317: try {
318: if (sourceChanged) {
319: jta.setHighlighter(highlighter);
320: }
321:
322: lineMap = jta.getDocument().getDefaultRootElement();
323:
324: Vector lines = model.getBreakPoints(source);
325: if (lines != null) {
326: for (Enumeration e = lines.elements(); e
327: .hasMoreElements();) {
328: int line = ((Integer) e.nextElement())
329: .intValue();
330: Element pos = lineMap.getElement(line - 1);
331: if (pos != null) {
332: int start = pos.getStartOffset();
333: int end = pos.getEndOffset() - 1;
334:
335: String key = source + ":" + (line - 1);
336: if (tags.get(key) == null) {
337: Object tag = highlighter
338: .addHighlight(
339: start,
340: end,
341: new DefaultHighlighter.DefaultHighlightPainter(
342: breakPointColor));
343: tags.put(key, tag);
344: }
345: }
346: }
347: }
348: if (beginLine > 0) {
349: Element pos = lineMap.getElement(beginLine - 1);
350: if (pos != null) {
351: int start = pos.getStartOffset();
352: int end = pos.getEndOffset() - 1;
353:
354: jta.select(start, start); // to make it visible
355:
356: if (traceTag != null && !sourceChanged) {
357: highlighter.changeHighlight(traceTag,
358: start, end);
359: } else {
360: traceTag = highlighter
361: .addHighlight(
362: start,
363: end,
364: new DefaultHighlighter.DefaultHighlightPainter(
365: currentPositionColor));
366: }
367: }
368: } else {
369: if (beginLine < 0) {
370: jta.setText("");
371: }
372: }
373: } catch (BadLocationException ex) {
374: ex.printStackTrace();
375: return;
376: }
377:
378: } else { // source == null
379: if (beginLine > 0) {
380: lineMap = jta.getDocument().getDefaultRootElement();
381:
382: if (lineMap != null) {
383: Element pos = lineMap.getElement(beginLine - 1);
384: if (pos != null) {
385: int start = pos.getStartOffset();
386: int end = pos.getEndOffset() - 1;
387: try {
388: jta.select(start, start); // to make it visible
389: jta.setHighlighter(highlighter);
390: if (traceTag != null) {
391: highlighter.changeHighlight(
392: traceTag, start, end);
393: } else {
394: traceTag = highlighter
395: .addHighlight(
396: start,
397: end,
398: new DefaultHighlighter.DefaultHighlightPainter(
399: currentPositionColor));
400: }
401:
402: } catch (BadLocationException badLocation) {
403: }
404: }
405: }
406: } else {
407: if (beginLine < 0) {
408: jta.setText("");
409: }
410: }
411: }
412: jta.repaint();
413: jfr.setVisible(true);
414:
415: } catch (IOException e) {
416: e.printStackTrace();
417: System.exit(-1);
418: }
419: }
420:
421: int getElementIndex(int pos) {
422: Element lineMap = jta.getDocument().getDefaultRootElement();
423: return lineMap.getElementIndex(pos);
424: }
425:
426: Element getElement(int pos) {
427: Element lineMap = jta.getDocument().getDefaultRootElement();
428: return lineMap.getElement(pos);
429: }
430:
431: void showInspector() {
432: inspector.setVisible(true);
433: }
434:
435: void open() {
436: synchronized (this ) {
437: notifyAll();
438: }
439: JFileChooser chooser = new JFileChooser(System
440: .getProperty("user.dir"));
441: int ret = chooser.showOpenDialog(jfr);
442: if (ret == JFileChooser.APPROVE_OPTION) {
443: final File file = chooser.getSelectedFile();
444:
445: Thread t = new Thread(new Runnable() {
446: public void run() {
447: open(file.getPath());
448: }
449: });
450: t.start();
451: }
452: }
453:
454: /**
455: * Opens a local file in a window
456: *
457: * @param filename
458: * the file name
459: */
460: public void open(String filename) {
461: File file = new File(filename);
462: try {
463: if (file.exists()) {
464: update(Runtime.fileToURL(file), 0);
465: } else {
466: errorStream.println(filename + " is not found");
467: }
468: } catch (IOException e) {
469: }
470: }
471:
472: /**
473: * Create the toolbar. By default this reads the resource file for the
474: * definition of the toolbar.
475: */
476: private Component createToolbar() {
477: String toolbarDef = getResourceString("toolbar");
478: if (toolbarDef == null) {
479: return null;
480: }
481: toolbar = new JToolBar();
482: String[] toolKeys = tokenize(toolbarDef);
483: for (int i = 0; i < toolKeys.length; i++) {
484: if (toolKeys[i].equals("-")) {
485: toolbar.add(Box.createHorizontalStrut(5));
486: } else {
487: toolbar.add(createToolbarButton(toolKeys[i]));
488: }
489: }
490: toolbar.add(Box.createHorizontalGlue());
491: return toolbar;
492: }
493:
494: /**
495: * Create a button to go inside of the toolbar. By default this will load an
496: * image resource. The image filename is relative to the classpath
497: * (including the '.' directory if its a part of the classpath), and may
498: * either be in a JAR file or a separate file.
499: *
500: * @param key
501: * The key in the resource file to serve as the basis of lookups.
502: */
503: protected JButton createToolbarButton(String key) {
504: URL url = getResource(key + imageSuffix);
505: JButton b;
506: if (url != null) {
507: b = new JButton(new ImageIcon(url)) {
508: public float getAlignmentY() {
509: return 0.5f;
510: }
511: };
512: } else {
513: b = new JButton(getResourceString(key + labelSuffix)) {
514: public float getAlignmentY() {
515: return 0.5f;
516: }
517: };
518: }
519: b.setRequestFocusEnabled(false);
520: b.setMargin(new Insets(1, 1, 1, 1));
521:
522: String astr = getResourceString(key + actionSuffix);
523: if (astr == null) {
524: astr = key;
525: }
526: Action a = getAction(astr);
527: if (a != null) {
528: b.setActionCommand(astr);
529: b.addActionListener(a);
530: } else {
531: b.setEnabled(false);
532: }
533:
534: String tip = getResourceString(key + tipSuffix);
535: if (tip != null) {
536: b.setToolTipText(tip);
537: }
538:
539: return b;
540: }
541:
542: String[] tokenize(String input) {
543: Vector v = new Vector();
544: StringTokenizer t = new StringTokenizer(input);
545: String cmd[];
546:
547: while (t.hasMoreTokens()) {
548: v.addElement(t.nextToken());
549: }
550: cmd = new String[v.size()];
551: for (int i = 0; i < cmd.length; i++) {
552: cmd[i] = (String) v.elementAt(i);
553: }
554: return cmd;
555: }
556:
557: /**
558: * Create the menubar for the app. By default this pulls the definition of
559: * the menu from the associated resource file.
560: *
561: * @return a JMenuBar
562: */
563: protected JMenuBar createMenubar() {
564: JMenuItem mi;
565: JMenuBar mb = new JMenuBar();
566: String mbdef = getResourceString("menubar");
567: if (mbdef == null) {
568: return null;
569: }
570: String[] menuKeys = tokenize(mbdef);
571: for (int i = 0; i < menuKeys.length; i++) {
572: JMenu m = createMenu(menuKeys[i]);
573: if (m != null) {
574: mb.add(m);
575: }
576: }
577: return mb;
578: }
579:
580: /**
581: * Create a menu for the app. By default this pulls the definition of the
582: * menu from the associated resource file.
583: *
584: * @param key
585: * name of a menu group
586: */
587: protected JMenu createMenu(String key) {
588: String[] itemKeys = tokenize(getResourceString(key));
589: JMenu menu = new JMenu(getResourceString(key + labelSuffix));
590: for (int i = 0; i < itemKeys.length; i++) {
591: if (itemKeys[i].equals("-")) {
592: menu.addSeparator();
593: } else {
594: JMenuItem mi = createMenuItem(itemKeys[i]);
595: menu.add(mi);
596: }
597: }
598: return menu;
599: }
600:
601: /**
602: * Create a menu item for the specified command
603: *
604: * @param cmd
605: * the command name
606: * @return the JMenuItem
607: */
608: protected JMenuItem createMenuItem(String cmd) {
609: JMenuItem mi = new JMenuItem(getResourceString(cmd
610: + labelSuffix));
611: KeyStroke ks = KeyStroke.getKeyStroke(getResourceString(cmd
612: + shortcutSuffix));
613: if (ks != null) {
614: mi.setAccelerator(ks);
615: }
616: String astr = getResourceString(cmd + actionSuffix);
617: if (astr == null) {
618: astr = cmd;
619: }
620: mi.setActionCommand(astr);
621: Action a = getAction(astr);
622: if (a != null) {
623: mi.addActionListener(a);
624: mi.setEnabled(a.isEnabled());
625: } else {
626: mi.setEnabled(false);
627: }
628: menuItems.put(cmd, mi);
629: return mi;
630: }
631:
632: int getIntResource(String nm) {
633: String s = getResourceString(nm);
634: if (s != null) {
635: try {
636: return Integer.decode(s).intValue();
637: } catch (NumberFormatException e) {
638: }
639: }
640: return -1;
641: }
642:
643: protected String getResourceString(String nm) {
644: String str;
645: try {
646: str = resources.getString(nm);
647: } catch (MissingResourceException mre) {
648: str = null;
649: }
650: return str;
651: }
652:
653: protected URL getResource(String key) {
654: String name = getResourceString(key);
655: if (name != null) {
656: URL url = VisualDebuggerView.class.getResource(name);
657: return url;
658: }
659: return null;
660: }
661:
662: Action getAction(String cmd) {
663: return (Action) commands.get(cmd);
664: }
665:
666: /**
667: * Create an editor to represent the given document.
668: */
669: protected JTextArea createTextArea() {
670: JTextArea jta = new JTextArea();
671: jta.setFont(new Font("monospaced", Font.PLAIN, 12));
672: jta.addMouseListener(new MouseHandler());
673: jta.setEditable(false);
674: return jta;
675: }
676:
677: class MenuAction extends AbstractAction {
678: private int id;
679:
680: MenuAction(int id) {
681: this .id = id;
682: }
683:
684: public void actionPerformed(ActionEvent e) {
685: switch (id) {
686: case VisualDebuggerModel.OPEN:
687: open();
688: break;
689: case VisualDebuggerModel.STEP:
690: model.do_step(1);
691: break;
692: case VisualDebuggerModel.STEP_UP:
693: model.do_stepup();
694: break;
695: case VisualDebuggerModel.NEXT:
696: model.do_next(1);
697: break;
698: case VisualDebuggerModel.CONT:
699: model.do_cont();
700: break;
701: case VisualDebuggerModel.CLOSE:
702: model.do_close();
703: exitGUI();
704: break;
705: case VisualDebuggerModel.CLEAR_BP: {
706: int start = -1;
707: int end = -1;
708: Highlighter.HighlightPainter p = null;
709: Highlighter.Highlight currentLineHighlight = (Highlighter.Highlight) traceTag;
710: if (currentLineHighlight != null) {
711: start = currentLineHighlight.getStartOffset();
712: end = currentLineHighlight.getEndOffset();
713: p = currentLineHighlight.getPainter();
714: }
715: model.clearBreakPoints();
716: highlights = new Hashtable();
717: tags = new Hashtable();
718: traceTag = null;
719: jta.setHighlighter(jta.getHighlighter());
720: highlights.put(model.getCurrentSource(), jta
721: .getHighlighter());
722: if (currentLineHighlight != null) {
723: try {
724: traceTag = jta.getHighlighter().addHighlight(
725: start, end, p);
726: } catch (BadLocationException e1) {
727: e1.printStackTrace();
728: }
729: }
730: }
731: break;
732: case VisualDebuggerModel.INSPECT:
733: inspector.setVisible(!inspector.isVisible());
734: inspector.pack();
735: break;
736: }
737: }
738: }
739:
740: class MouseHandler extends MouseAdapter {
741: public void mousePressed(MouseEvent e) {
742: maybeShowPopup(e);
743: }
744:
745: public void mouseReleased(MouseEvent e) {
746: maybeShowPopup(e);
747: }
748:
749: private void maybeShowPopup(MouseEvent e) {
750: if (!e.isPopupTrigger()) {
751: return;
752: }
753:
754: int x = e.getX();
755: int y = e.getY();
756: Point pt = new Point(x, y);
757: int pos = jta.viewToModel(pt);
758: if (pos > 0) {
759: int line = getElementIndex(pos); // zero origin
760: Element elem = getElement(line);
761:
762: try {
763: Object currentSource = model.getCurrentSource();
764: String key = currentSource + ":" + line;
765: Object tag = tags.get(key);
766:
767: Highlighter highlighter = jta.getHighlighter();
768: if (tag == null) {
769: tag = highlighter
770: .addHighlight(
771: elem.getStartOffset(),
772: elem.getEndOffset() - 1,
773: new DefaultHighlighter.DefaultHighlightPainter(
774: breakPointColor));
775: tags.put(key, tag);
776:
777: model.setBreakPoint(currentSource, line);
778: } else {
779: highlighter.removeHighlight(tag);
780: tags.remove(key);
781: model.removeBreakPoint(currentSource, line);
782: }
783: } catch (BadLocationException ex) {
784: ex.printStackTrace();
785: }
786: }
787: }
788: }
789: }
|