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 }