001    package nl.cwi.sen1.tide.tool.srcviewer;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Color;
005    import java.awt.Component;
006    import java.awt.Dimension;
007    import java.awt.Font;
008    import java.awt.FontMetrics;
009    import java.awt.Point;
010    import java.awt.Rectangle;
011    import java.awt.event.ActionEvent;
012    import java.awt.event.ActionListener;
013    import java.awt.event.InputEvent;
014    import java.awt.event.MouseEvent;
015    import java.awt.event.MouseListener;
016    import java.awt.event.MouseMotionListener;
017    import java.io.BufferedReader;
018    import java.io.FileReader;
019    import java.io.IOException;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.Map;
023    
024    import javax.swing.JLayeredPane;
025    import javax.swing.JOptionPane;
026    import javax.swing.JPanel;
027    import javax.swing.JPopupMenu;
028    import javax.swing.JScrollPane;
029    import javax.swing.JViewport;
030    import javax.swing.OverlayLayout;
031    import javax.swing.Scrollable;
032    import javax.swing.SwingConstants;
033    import javax.swing.event.PopupMenuEvent;
034    import javax.swing.event.PopupMenuListener;
035    import javax.swing.text.BadLocationException;
036    import javax.swing.text.DefaultHighlighter;
037    import javax.swing.text.Highlighter;
038    
039    import nl.cwi.sen1.tide.PreferenceListener;
040    import nl.cwi.sen1.tide.PreferenceSet;
041    import nl.cwi.sen1.tide.tool.ToolManager;
042    import nl.cwi.sen1.tide.tool.support.DebugProcess;
043    import nl.cwi.sen1.tide.tool.support.DebugProcessListener;
044    import nl.cwi.sen1.tide.tool.support.Expr;
045    import nl.cwi.sen1.tide.tool.support.Port;
046    import nl.cwi.sen1.tide.tool.support.Rule;
047    
048    class SourceFileViewer
049            extends JScrollPane
050            implements
051                    MouseListener,
052                    MouseMotionListener,
053                    PopupMenuListener,
054                    ActionListener,
055                    DebugProcessListener,
056                    PreferenceListener {
057            private static final String ITEM_ADD_BREAKPOINT = "Add Breakpoint";
058            private static final String ITEM_ADD_WATCHPOINT = "Add Watchpoint";
059            private static final String ITEM_EDIT_RULE = "Edit Rule";
060            private static final String ITEM_DELETE_RULE = "Delete Rule";
061    
062            static final Color COLOR_CPE = new Color(0xDD, 0xDD, 0xFF);
063            static final Color COLOR_VARIABLE = new Color(0xEE, 0xEE, 0x00);
064            static final Color COLOR_BREAK = new Color(0xFF, 0xCC, 0xCC);
065            static final Color COLOR_BREAKPOINT = new Color(0xCC, 0x00, 0x00);
066            static final Color COLOR_WATCH = new Color(0xCC, 0xFF, 0xCC);
067            static final Color COLOR_WATCHPOINT = new Color(0x00, 0x99, 0x00);
068            static final Color COLOR_LINEBG = new Color(0xF0, 0xF0, 0xF0);
069    
070            private static final String TAG_BREAKPOINT = "sv-breakpoint";
071            private static final String TAG_WATCHPOINT = "sv-watchpoint";
072    
073            private ToolManager manager;
074            private PreferenceSet prefs;
075            private DebugProcess process;
076            private String file;
077            private String tag_view_var;
078    
079            private JPopupMenu menu;
080    
081            private String tag_breakpoint;
082            private String tag_watchpoint;
083    
084            private LineNumberCanvas lineNumbers;
085            private SourceBrowser text;
086            private JPanel glass;
087    
088            private DefaultHighlighter highlighter;
089    
090            private Highlighter.HighlightPainter cpePainter;
091            private Object cpeHighlight;
092    
093            private Highlighter.HighlightPainter variablePainter;
094    
095            private Map<ValuePopup, Object> variableHighlights;
096    
097            private int lastSelected;
098            private Rule lastSelectedRule;
099            private ValuePopup draggedPopup;
100    
101            public SourceFileViewer(
102                    ToolManager manager,
103                    DebugProcess process,
104                    String file,
105                    int id,
106                    String tag_view_var) {
107                    this.manager = manager;
108                    this.process = process;
109                    this.file = file;
110                    this.tag_view_var = tag_view_var;
111                    process.addDebugProcessListener(this);
112    
113                    prefs = manager.getPreferences();
114                    prefs.addPreferenceListener(this);
115    
116                    tag_breakpoint = TAG_BREAKPOINT + "-" + id;
117                    tag_watchpoint = TAG_WATCHPOINT + "-" + id;
118    
119                    glass = new JPanel();
120                    glass.setOpaque(false);
121                    glass.setLayout(null);
122    
123                    text = new SourceBrowser();
124    
125                    lineNumbers = new LineNumberCanvas(text);
126                    lineNumbers.setBackground(COLOR_LINEBG);
127    
128                    preferencesChanged(prefs);
129    
130                    JPanel textPanel = new JPanel();
131                    textPanel.setLayout(new BorderLayout());
132                    textPanel.add("Center", text);
133                    textPanel.add("West", lineNumbers);
134    
135                    ScrollablePane pane = new ScrollablePane(textPanel, glass, text);
136                    setViewportView(pane);
137    
138                    glass.addMouseListener(this);
139                    glass.addMouseMotionListener(this);
140    
141                    prepareHighlightingStuff();
142    
143                    try {
144                            text.read(new BufferedReader(new FileReader(file)), null);
145                    } catch (IOException e) {
146                            System.err.println("cannot find source file " + file);
147                    }
148            }
149    
150            private void prepareHighlightingStuff() {
151                    highlighter = new DefaultHighlighter();
152                    highlighter.setDrawsLayeredHighlights(true);
153                    text.setHighlighter(highlighter);
154                    text.setEditable(false);
155    
156                    cpePainter = new DefaultHighlighter.DefaultHighlightPainter(COLOR_CPE);
157    
158                    variablePainter = new DefaultHighlighter.DefaultHighlightPainter(COLOR_VARIABLE);
159    
160                    variableHighlights = new HashMap<ValuePopup, Object>();
161            }
162    
163            public String getFile() {
164                    return file;
165            }
166    
167            protected void cleanup() {
168                    process.removeDebugProcessListener(this);
169                    prefs.removePreferenceListener(this);
170            }
171    
172            protected void highlightRules(Iterator<Rule> rules) {
173                    while (rules.hasNext()) {
174                            Rule rule = rules.next();
175                            highlightRule(rule);
176                    }
177            }
178    
179            private void highlightRule(Rule rule) {
180                    Expr location = rule.getLocation();
181                    if (location != null
182                            && location.isLocation()
183                            && location.getLocationFileName().equals(file)) {
184    
185                            lineNumbers.addLocationRule(rule, location.getLocationStartLine());
186    
187                            if (rule.isBreakpoint()) {
188                                    text.addBreakpoint(rule, location);
189                            } else {
190                                    text.addWatchpoint(rule, location);
191                            }
192                            repaint();
193                    }
194            }
195    
196            private void unhighlightRule(Rule rule) {
197                    text.removeWatchpoint(rule);
198                    text.removeBreakpoint(rule);
199                    lineNumbers.removeLocationRule(rule);
200                    repaint();
201            }
202    
203            protected void highlightCpe() {
204                    if (cpeHighlight != null) {
205                            unhighlightCpe();
206                    }
207                    Expr location = process.getLastLocation();
208                    cpeHighlight = addHighlight(location, cpePainter, false, true);
209                    centerAround(location);
210            }
211    
212            protected void unhighlightCpe() {
213                    if (cpeHighlight != null) {
214                            highlighter.removeHighlight(cpeHighlight);
215                            cpeHighlight = null;
216                            lineNumbers.clearCpe();
217                            repaint();
218                    }
219            }
220    
221            private void showVariable(int x, int y) {
222                    try {
223                            int pos = text.viewToModel(new Point(x, y));
224                            int linenr = text.getLine(pos);
225                            int col = text.getColumn(pos);
226                            int start = text.getLineStartOffset(linenr - 1);
227                            int end = text.getLineEndOffset(linenr - 1);
228                            String line = text.getText(start, end - start);
229    
230                            if (line.equals("") || line.equals("\n")) {
231                                    return;
232                            }
233    
234                            Expr expr = Expr.makeSourceVar(file, pos, linenr, col, line);
235                            process.requestEvaluation(expr, tag_view_var);
236                    } catch (BadLocationException e) {
237                            System.err.println(e.getMessage());
238                    }
239            }
240    
241            void removeValuePopup(ValuePopup popup) {
242                    popup.closePopup();
243                    Object highlight = variableHighlights.remove(popup);
244                    highlighter.removeHighlight(highlight);
245                    glass.remove(popup);
246                    repaint();
247            }
248    
249            private void centerAround(Expr location) {
250                    try {
251                            int start =
252                                    text.getLineStartOffset(location.getLocationStartLine() - 1);
253                            Rectangle rect = text.modelToView(start);
254                            if (rect != null) {
255                                    JViewport viewport = getViewport();
256    
257                                    // Make rectangle relative to viewport, and add
258                                    // some extra context to make behaviour more intuitive
259                                    rect.x -= (viewport.getViewPosition().x + 20);
260                                    rect.y -= (viewport.getViewPosition().y + 20);
261                                    rect.height += 40;
262                                    rect.width += 40;
263                                    viewport.scrollRectToVisible(rect);
264                            }
265                    } catch (BadLocationException e) {
266                            System.out.println("bad location of cpe: " + e.getMessage());
267                    }
268            }
269    
270            protected Object addHighlight(
271                    Expr location,
272                    Highlighter.HighlightPainter painter,
273                    boolean whole_line,
274                    boolean is_cpe) {
275                    String file = location.getLocationFileName();
276                    
277                    if (file.equals(this.file)) {
278                            try {
279                                    int start =
280                                            text.getLineStartOffset(
281                                                    location.getLocationStartLine() - 1);
282    
283                                    if (!whole_line) {
284                                            start += location.getLocationStartColumn();
285                                    }
286    
287                                    int end;
288                                    int end_line = location.getLocationEndLine();
289                                    int end_col = location.getLocationEndColumn();
290    
291                                    if (is_cpe) {
292                                            lineNumbers.setCpe(end_line);
293                                    }
294    
295                                    if (whole_line || end_col == -1) {
296                                            end = text.getLineEndOffset(end_line - 1);
297                                    } else {
298                                            end = text.getLineStartOffset(end_line - 1) + end_col;
299                                    }
300    
301                                    return highlighter.addHighlight(start, end, painter);
302                            } catch (BadLocationException e) {
303                                    System.err.println(
304                                            "bad coordinates: " + location + ", source changed ???");
305                            }
306    
307                    }
308    
309                    return null;
310            }
311    
312            protected void highlightVariable(
313                    int start,
314                    int length,
315                    ValuePopup varWindow) {
316                    try {
317                            Object highlight =
318                                    highlighter.addHighlight(
319                                            start,
320                                            start + length,
321                                            variablePainter);
322                            variableHighlights.put(varWindow, highlight);
323                            Rectangle rect = text.modelToView(start + length);
324                            varWindow.show(
325                                    glass,
326                                    text.getX() + rect.x + 10,
327                                    text.getY() + rect.y - 10);
328                    } catch (BadLocationException e) {
329                            System.err.println("bad location: " + e.getMessage());
330                    }
331            }
332    
333            private void showMenu(int x, int y) {
334                    lastSelected = text.viewToModel(new Point(x, y));
335                    text.setSelectedPosition(lastSelected);
336    
337                    menu = new JPopupMenu("Location Rules");
338                    menu.addPopupMenuListener(this);
339    
340                    Rule rule = text.getRuleAt(x, y);
341                    lastSelectedRule = rule;
342                    if (rule == null) {
343                            menu.add(ITEM_ADD_BREAKPOINT).addActionListener(this);
344                            menu.add(ITEM_ADD_WATCHPOINT).addActionListener(this);
345                    } else {
346                            menu.add(ITEM_DELETE_RULE).addActionListener(this);
347                            menu.add(ITEM_EDIT_RULE).addActionListener(this);
348                    }
349                    menu.show(glass, x, y);
350            }
351    
352            public void popupMenuWillBecomeVisible(PopupMenuEvent event) {}
353    
354            public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
355                    text.clearSelectedPosition();
356            }
357    
358            public void popupMenuCanceled(PopupMenuEvent event) {}
359    
360            public void mouseDragged(MouseEvent event) {
361                    int x = event.getX() - text.getX();
362                    int y = event.getY() - text.getY();
363    
364                    if ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
365                            if (draggedPopup != null) {
366                                    x += draggedPopup.getDragTransX();
367                                    y += draggedPopup.getDragTransY();
368                                    draggedPopup.setLocation(x, y);
369                                    repaint();
370                            } else {
371                                    Component comp =
372                                            glass.findComponentAt(event.getX(), event.getY());
373                                    if (comp instanceof ValuePopup) {
374                                            ValuePopup popup = (ValuePopup) comp;
375                                            popup.startDragging(x, y);
376                                            draggedPopup = popup;
377                                    }
378                            }
379                    }
380            }
381    
382            public void mouseMoved(MouseEvent event) {}
383    
384            public void mouseClicked(MouseEvent event) {
385                    if (!event.isShiftDown()
386                            && !event.isMetaDown()
387                            && !event.isControlDown()) {
388                            Component comp = glass.findComponentAt(event.getX(), event.getY());
389                            if (comp instanceof ValuePopup) {
390                                    ValuePopup popup = (ValuePopup) comp;
391                                    if (event.getX() - popup.getX() < ValuePopup.ICON_SIZE) {
392                                            process.requestRuleDeletion(popup.getUpdateRule());
393                                    } else {
394                                            popup.updateValue();
395                                    }
396                            }
397                    }
398            }
399    
400            public void mouseEntered(MouseEvent e) {}
401    
402            public void mouseExited(MouseEvent e) {}
403    
404            public void mousePressed(MouseEvent event) {
405                    Component comp = glass.findComponentAt(event.getX(), event.getY());
406                    if (comp instanceof ValuePopup) {
407                            ValuePopup popup = (ValuePopup) comp;
408                            if (event.isPopupTrigger()) {
409                                    popup.showMenu(glass, event.getX(), event.getY());
410                            }
411                    } else {
412                            if (event.isPopupTrigger() && event.getX() > text.getX()) {
413                                    showMenu(event.getX() - text.getX(), event.getY());
414                            } else {
415                                    if (event.getClickCount() > 1
416                                            && comp == glass
417                                            && event.getX() > text.getX()) {
418                                            showVariable(event.getX() - text.getX(), event.getY());
419                                    }
420                            }
421                    }
422            }
423    
424            public void mouseReleased(MouseEvent e) {
425                    draggedPopup = null;
426            }
427    
428            public void actionPerformed(ActionEvent event) {
429                    if (event.getActionCommand().equals(ITEM_ADD_BREAKPOINT)) {
430                            handleAddBreakpoint();
431                    } else if (event.getActionCommand().equals(ITEM_ADD_WATCHPOINT)) {
432                            handleAddWatchpoint();
433                    } else if (event.getActionCommand().equals(ITEM_EDIT_RULE)) {
434                            manager.editRule(process, lastSelectedRule);
435                    } else if (event.getActionCommand().equals(ITEM_DELETE_RULE)) {
436                            process.requestRuleDeletion(lastSelectedRule);
437                    }
438            }
439            private void handleAddWatchpoint() {
440                    Expr location =
441                            Expr.makeLocation(
442                                    file,
443                                    text.getLine(lastSelected),
444                                    text.getColumn(lastSelected));
445                    Expr value = null;
446                    String expr = "";
447                    String title = "What value do you want to watch?";
448                    while (value == null) {
449                            expr =
450                                    (String) JOptionPane.showInternalInputDialog(
451                                            this,
452                                            title,
453                                            "Watchpoint",
454                                            JOptionPane.PLAIN_MESSAGE,
455                                            null,
456                                            null,
457                                            expr);
458                            if (expr == null) {
459                                    return;
460                            }
461                            try {
462                                    value = Expr.make(expr);
463                            } catch (aterm.ParseError e) {
464                                    title = "Please enter a valid expression";
465                            }
466                    }
467                    process.requestRuleCreation(
468                            Port.makeStep(),
469                            location,
470                            value,
471                            tag_watchpoint,
472                            true);
473                    return;
474            }
475    
476            private void handleAddBreakpoint() {
477                    Expr location =
478                            Expr.makeLocation(
479                                    file,
480                                    text.getLine(lastSelected),
481                                    text.getColumn(lastSelected));
482                    process.requestRuleCreation(
483                            Port.makeStep(),
484                            location,
485                            Expr.makeBreak(),
486                            tag_breakpoint,
487                            true);
488            }
489    
490            public void ruleCreated(DebugProcess process, Rule rule) {
491                    highlightRule(rule);
492            }
493    
494            public void ruleDeleted(DebugProcess process, Rule rule) {
495                    unhighlightRule(rule);
496            }
497    
498            public void ruleModified(DebugProcess process, Rule rule) {
499                    unhighlightRule(rule);
500                    highlightRule(rule);
501            }
502    
503            public void ruleTriggered(DebugProcess process, Rule rule, Expr value){}
504    
505            public void evaluationResult(DebugProcess process, Expr expr, Expr value, String tag){}
506    
507            public void preferencesChanged(PreferenceSet prefs) {
508                    String prefName = SourceViewerFactory.PREF_SOURCECODE_FONT;
509                    Font font = prefs.getFontPreference(prefName);
510                    text.setFont(font);
511                    getVerticalScrollBar().setUnitIncrement(
512                            getFontMetrics(font).getHeight());
513                    prefName = SourceViewerFactory.PREF_LINENUMBER_FONT;
514                    lineNumbers.setFont(prefs.getFontPreference(prefName));
515                    repaint();
516            }
517    
518            public void preferenceChanged(PreferenceSet prefs, String name, String oldValue, String newValue) {
519                    if (name.equals(SourceViewerFactory.PREF_SOURCECODE_FONT)
520                            || name.equals(SourceViewerFactory.PREF_LINENUMBER_FONT)) {
521                            preferencesChanged(prefs);
522                    }
523            }
524    
525            public void preferencesStatusChanged(PreferenceSet set, boolean clean){}
526    }
527    
528    class ScrollablePane extends JLayeredPane implements Scrollable {
529            private SourceBrowser text;
530    
531            public ScrollablePane(JPanel textPanel, JPanel glass, SourceBrowser text) {
532                    this.text = text;
533    
534                    setLayout(new OverlayLayout(this));
535                    add(textPanel, new Integer(1));
536                    add(glass, new Integer(2));
537            }
538    
539            public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
540                    if (orientation == SwingConstants.HORIZONTAL) {
541                            return visibleRect.width;
542                    }
543            return visibleRect.height;
544            }
545    
546            public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
547                    Font font = text.getFont();
548                    FontMetrics metrics = text.getFontMetrics(font);
549    
550                    if (orientation == SwingConstants.HORIZONTAL) {
551                            return metrics.getMaxAdvance();
552                    }
553            return metrics.getHeight();
554            }
555    
556            public Dimension getPreferredScrollableViewportSize() {
557                    return text.getPreferredSize();
558            }
559    
560            public boolean getScrollableTracksViewportHeight() {
561                    return false;
562            }
563    
564            public boolean getScrollableTracksViewportWidth() {
565                    return false;
566            }
567    }