001: package tide.editor.linemessages;
002:
003: import tide.editor.Accelerators;
004: import snow.utils.gui.CustomEtchedBorder;
005: import tide.editor.debugger.BreakpointLineMessage;
006: import java.awt.EventQueue;
007: import snow.utils.gui.Icons;
008: import tide.editor.SharedUtils;
009: import tide.editor.EditorPanel;
010: import tide.editor.EditorDocument;
011: import tide.sources.FileItem;
012: import javax.swing.*;
013: import javax.swing.event.*;
014: import java.awt.Graphics2D;
015: import java.awt.Graphics;
016: import java.awt.Dimension;
017: import java.awt.Rectangle;
018: import java.awt.Point;
019: import java.awt.Color;
020: import java.awt.Toolkit;
021: import java.awt.geom.*;
022: import tide.editor.MainEditorFrame;
023: import tide.editor.bookmarks.SourceBookmark;
024: import java.awt.event.*;
025: import snow.texteditor.DocumentUtils;
026: import java.util.*;
027:
028: /** The panel showing the informations for source lines, synchronizes its view with the current
029: * viewed portion of the source.
030: * Displays the line messages and bookmarks.
031: * TODO: show an overwiew of errors when mouse's over. (tooltip?)
032: * TODO: show CTRL+UP and DOWN shortcuts in the popup when necessary
033: */
034: public final class LinePanel extends JPanel {
035: transient FileItem editedFile = null;
036: final EditorPanel editorPanel;
037:
038: private int fromLine = -1;
039: private int toLine = -1;
040:
041: // where the source is shown first
042: private int originalCaretLine = -1;
043:
044: private List<LineMessage> linesMessages = new ArrayList<LineMessage>();
045: final public List<SourceBookmark> bookmarks = new ArrayList<SourceBookmark>();
046:
047: final int w = 16;
048:
049: public LinePanel(EditorPanel editorPanel) {
050:
051: setBorder(new CustomEtchedBorder(false, false, false, true));
052:
053: // called from editorPanel constructor => don't access members now, cause they are not initialized, even final ones !!!
054: this .editorPanel = editorPanel;
055:
056: this .setMaximumSize(new Dimension(w, (int) Toolkit
057: .getDefaultToolkit().getScreenSize().getHeight()));
058: this .setPreferredSize(new Dimension(w, 50));
059: this .setMinimumSize(new Dimension(w, 50));
060:
061: addMouseListener(new MouseAdapter() {
062: @Override
063: public void mousePressed(MouseEvent me) {
064: if (me.isPopupTrigger()) {
065: showPopup(me);
066: return;
067: }
068: }
069:
070: @Override
071: public void mouseReleased(MouseEvent me) {
072: if (me.isPopupTrigger()) {
073: showPopup(me);
074: return;
075: }
076: }
077: });
078:
079: this .addMouseMotionListener(new MouseMotionAdapter() {
080: @Override
081: public void mouseMoved(MouseEvent me) {
082: //...
083: }
084: });
085: }
086:
087: int maxShowedInPopup = 10;
088:
089: // TODO: limit popup width
090: private void showPopup(MouseEvent me) {
091: final FileItem editedFile = editorPanel
092: .getActualDisplayedFile();
093: if (editedFile == null)
094: return;
095:
096: JPopupMenu popup = new JPopupMenu("line panel");
097: popup.setMaximumSize(new Dimension(500, 1024)); // Works ??
098:
099: /// determine the line at clicked point
100: final int lineNb = getLineForMouseYPos(me.getY());
101:
102: final List<LineMessage> messL = getMessagesForLine(lineNb + 1);
103: final List<SourceBookmark> books = getBookmarksForLine(lineNb + 1);
104:
105: boolean hasActions = false;
106: for (final LineMessage mess : messL) {
107: List<AbstractAction> acts = mess.lookForAvailableActions();
108: if (acts != null && acts.size() > 0) {
109: hasActions = true;
110: for (AbstractAction a : acts) {
111: popup.add(a);
112: }
113: }
114: }
115:
116: if (hasActions) {
117: popup.addSeparator();
118: }
119:
120: // max 10
121: int nShowed = 0;
122: for (final LineMessage mess : messL) {
123: if (nShowed >= maxShowedInPopup) {
124: popup.add(" ... " + (messL.size() - maxShowedInPopup)
125: + " further messages...");
126: break;
127: }
128:
129: // selects it in the messages table if clicked.
130: JMenuItem mi = new JMenuItem(mess.toStringHTML());
131: popup.add(mi);
132: mi.addActionListener(new ActionListener() {
133: public void actionPerformed(ActionEvent ae) {
134: MessagesTable.getInstance().selectMessage(mess);
135: MainEditorFrame.instance.outputPanels
136: .select_LineMessages();
137: }
138: });
139:
140: nShowed++;
141: }
142:
143: if (messL.isEmpty() && lineNb >= 0) {
144: popup.add("Line " + (lineNb + 1));
145: }
146:
147: if (messL.size() > 0) {
148: JMenuItem remove = new JMenuItem(
149: "remove all the messages for line " + (lineNb + 1),
150: Icons.sharedCross);
151: popup.addSeparator();
152: popup.add(remove);
153: remove.addActionListener(new ActionListener() {
154: public void actionPerformed(ActionEvent ae) {
155: for (LineMessage mess : messL) {
156: LineMessagesManager.getInstance().remove(mess);
157: LineMessagesManager.getInstance().refreshView();
158: }
159: }
160: });
161: /*JMenuItem print = new JMenuItem("Print them in execution tab");
162: popup.addSeparator();
163: popup.add(print);
164: print.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae)
165: {
166: MainEditorFrame.instance.outputPanels.selectOutputTab();
167: for(LineMessage mess : messL)
168: {
169: MainEditorFrame.instance.outputPanels.executionOutputPanel.executionDoc.append("\n"+mess.getMessage()+"\n");
170: }
171: } });*/
172: }
173:
174: //always show, si si, may appear in another message... and also teatches
175: // the shortcut
176: // if(linesMessages.size()>1) // no sense if 1...
177: {
178: popup.addSeparator();
179: JMenuItem viewAll = new JMenuItem("View all message"
180: + (linesMessages.size() == 1 ? "" : "s")
181: + " for this source");
182: viewAll.setAccelerator(Accelerators.viewMessagesOfSelected);
183:
184: popup.add(viewAll);
185: viewAll.addActionListener(new ActionListener() {
186: public void actionPerformed(ActionEvent ae) {
187: if (editedFile != null) {
188: MainEditorFrame.instance.outputPanels
189: .filterMessages(editedFile
190: .getJavaName());
191: }
192: }
193: });
194: }
195:
196: if (!books.isEmpty()) {
197: popup.addSeparator();
198: for (final SourceBookmark bm : books) {
199: if (bm.getRemark().length() > 0) {
200: popup.add("Bookmark: " + bm.getRemark());
201: }
202:
203: JMenuItem rem = new JMenuItem(
204: " remove this bookmark", Icons.sharedCross); // indent...
205: popup.add(rem);
206: rem.addActionListener(new ActionListener() {
207: public void actionPerformed(ActionEvent ae) {
208: MainEditorFrame.instance.getActualProject()
209: .getAllBookmarks_REF().remove(bm);
210: LineMessagesManager.getInstance().refreshView();
211: }
212: });
213: }
214: }
215:
216: popup.addSeparator();
217:
218: SharedUtils.addBookmarksPopup(popup, editedFile.getJavaName()); // standard
219: JMenuItem add = new JMenuItem("Add a bookmark here",
220: Icons.sharedPlus);
221: popup.add(add);
222: add.addActionListener(new ActionListener() {
223: public void actionPerformed(ActionEvent ae) {
224: String tt = JOptionPane.showInputDialog(LinePanel.this ,
225: "Bookmark description (optional)",
226: "Add a bookmark", JOptionPane.QUESTION_MESSAGE);
227: if (tt == null)
228: return;
229: MainEditorFrame.instance
230: .getActualProject()
231: .addBookmark(
232: new SourceBookmark(editorPanel
233: .getActualDisplayedFile()
234: .getJavaName(), lineNb, -1, tt));
235: }
236: });
237:
238: if (MainEditorFrame.enableExperimental) {
239: JMenuItem addBP = new JMenuItem(
240: "Add a breakpoint here (for jdb)", Icons.sharedPlus); //? new Icons.DiskIcon(7, 7, Color.red, true)
241: popup.addSeparator();
242: popup.add(addBP);
243: addBP.addActionListener(new ActionListener() {
244: public void actionPerformed(ActionEvent ae) {
245: String tt = JOptionPane.showInputDialog(
246: LinePanel.this ,
247: "Breakpoint description (optional)", "");
248: if (tt == null)
249: return;
250: LineMessagesManager.getInstance().add(
251: new BreakpointLineMessage(editorPanel
252: .getActualDisplayedFile()
253: .getJavaName(), lineNb, tt, System
254: .currentTimeMillis()));
255: LineMessagesManager.getInstance().refreshView(); // also calls this...
256: }
257: });
258: }
259:
260: if (editorPanel.getActualDisplayedFile() != null) {
261: final int cursorPosi = editorPanel.getTextPane()
262: .getCaretPosition();
263: final int cursorLine = DocumentUtils.getLineNumber(
264: editorPanel.getDocument(), cursorPosi);
265: popup.addSeparator();
266: JMenuItem cursorPos = new JMenuItem(
267: "Go to cursor position (line " + cursorLine + ")",
268: Icons.sharedRightArrow);
269: popup.add(cursorPos);
270: cursorPos.addActionListener(new ActionListener() {
271: public void actionPerformed(ActionEvent ae) {
272: if (editorPanel.getActualDisplayedFile() == null)
273: return;
274: //editorPanel.getActualDisplayedFile().
275: editorPanel.scrollPosMiddle(cursorPosi, false);
276:
277: }
278: });
279: }
280:
281: // if messages are present, shodt down to see the line and the message.
282: popup.show(this , me.getX(), me.getY()
283: + (messL.size() > 0 ? 10 : 0));
284:
285: }
286:
287: /** line 1 also take the messages for line -1, ...
288: */
289: private List<LineMessage> getMessagesForLine(int line) {
290: List<LineMessage> hits = new ArrayList<LineMessage>();
291: for (LineMessage mess : this .linesMessages) {
292: if (mess.getLine() == line)
293: hits.add(mess);
294: if (line == 1 && mess.getLine() <= 0)
295: hits.add(mess);
296: }
297: return hits;
298: }
299:
300: private List<SourceBookmark> getBookmarksForLine(int line) {
301: List<SourceBookmark> hits = new ArrayList<SourceBookmark>();
302: for (SourceBookmark sb : this .bookmarks) {
303: int lp = sb.getLinePosition() + 1;
304: if (lp == line)
305: hits.add(sb);
306: if (line == 1 && lp <= 0)
307: hits.add(sb);
308: }
309: return hits;
310: }
311:
312: private int getLineForMouseYPos(int my) {
313: /// determine the line at clicked point
314: JTextPane editorTextPane = editorPanel.getTextPane();
315: JScrollPane editorScrollPane = editorPanel.getScrollPane();
316:
317: // determine upper left position
318: Point upLeft = editorScrollPane.getViewport().getViewPosition();
319: int modPos = editorTextPane.viewToModel(new Point(0,
320: (int) upLeft.getY() + my));
321: EditorDocument doc = editorPanel.getDocument();
322: int lineNb = DocumentUtils.getLineNumber(doc, modPos);
323: return lineNb;
324: }
325:
326: /** resets the view and install lines of interrest
327: */
328: public void setSourceFileToEdit(FileItem src) {
329:
330: if (editedFile == src) {
331: refreshMessages();
332: return; // nothing to do.
333: }
334:
335: editedFile = src;
336:
337: if (editedFile != null) {
338: originalCaretLine = editedFile.getCaretLinePosition();
339: } else {
340: originalCaretLine = -1;
341: }
342:
343: refreshMessages();
344:
345: fromLine = -1;
346: toLine = -1;
347:
348: repaint();
349: }
350:
351: /** Called at compilation end or on known changes from the messages manager.
352: */
353: public void refreshMessages() {
354: linesMessages.clear();
355: bookmarks.clear();
356:
357: if (editedFile != null) {
358: String ejn = editedFile.getJavaName();
359: List<LineMessage> messs = LineMessagesManager.getInstance()
360: .getMessages(ejn);
361: if (messs != null) {
362: linesMessages.addAll(messs);
363: }
364:
365: bookmarks.addAll(MainEditorFrame.instance
366: .getActualProject().getBookmarksFor(ejn));
367: }
368:
369: EventQueue.invokeLater(new Runnable() {
370: public void run() {
371:
372: if (linesMessages.size() > 0) {
373: setBackground(new Color(255, 245, 245)); // a very pale red
374: } else {
375: setBackground(UIManager
376: .getColor("Label.background"));
377: }
378: }
379: });
380:
381: repaint();
382: }
383:
384: /** called when the view bounds changes
385: */
386: public void viewBoundsChanged(int from, int to) {
387: /*fromPos = from;
388: toPos = to;*/
389: //System.out.println("LinePanel: "+from+" "+to);
390: EditorDocument doc = editorPanel.getDocument();
391: int newFromLine = DocumentUtils.getLineNumber(doc, from);
392: int newToLine = DocumentUtils.getLineNumber(doc, to);
393:
394: if (newFromLine != fromLine || newToLine != toLine) {
395: fromLine = newFromLine;
396: toLine = newToLine;
397:
398: //System.out.println("Bounds changed: "+fromLine+" "+toLine);
399:
400: repaint();
401: }
402: }
403:
404: @Override
405: public void paintComponent(Graphics gr) {
406: //System.out.println("Paint: "+fromLine+" "+toLine+": "+linesMessages.size()+" messages");
407: super .paintComponent(gr); // background, ...
408: //System.out.println(" "+fromLine+" "+toLine);
409: //if(fromLine==-1 || toLine==-1) return;
410:
411: try {
412: JTextPane editorTextPane = editorPanel.getTextPane();
413: JScrollPane editorScrollPane = editorPanel.getScrollPane();
414:
415: // determine upper left position
416: Point upLeft = editorScrollPane.getViewport()
417: .getViewPosition();
418: //System.out.println("\nupleft="+upLeft);
419:
420: int upLeftMod = editorTextPane.viewToModel(new Point(0,
421: (int) upLeft.getY()));
422: //System.out.println("mod="+upLeftMod);
423:
424: //Rectangle fromR = editorTextPane.modelToView(upLeftMod);
425:
426: EditorDocument doc = editorPanel.getDocument();
427: //int lineFrom = DocumentUtils.getLineNumber(doc, upLeftMod);
428: //int lineTo =
429: //System.out.println("line="+lineFrom);
430:
431: //
432: Graphics2D g2 = (Graphics2D) gr;
433: g2.setColor(Color.darkGray);
434:
435: if (originalCaretLine >= 0 && originalCaretLine >= fromLine
436: && originalCaretLine <= toLine) {
437: int posDoci = DocumentUtils.getDocPositionFor(doc,
438: originalCaretLine, 0);
439: Rectangle reci = editorTextPane.modelToView(posDoci);
440: g2.setColor(Color.blue);
441: int y = (int) (reci.getY() - upLeft.getY()) + 7;
442: int r = 5;
443:
444: GeneralPath gp = new GeneralPath();
445: gp.moveTo(0, y - r);
446: gp.lineTo(r - 1, y);
447: gp.lineTo(0, y + r);
448: gp.closePath();
449: g2.fill(gp);
450: //g2.fill( new Arc2D.Double(-7, y-r+7, 2*r, 2*r, 0, 360, Arc2D.CHORD) );
451:
452: }
453:
454: boolean hasBeforeVisibleView = false;
455: boolean hasAfterVisibleView = false;
456:
457: lm: for (LineMessage mess : linesMessages) {
458: int li = mess.getLine() - 1; // li is 0 based
459: if (li < 0)
460: li = 0;
461:
462: if (li < fromLine - 1) {
463: hasBeforeVisibleView = true;
464: continue lm;
465: } else if (li > toLine + 1) {
466: hasAfterVisibleView = true;
467: continue lm;
468: }
469:
470: g2.setColor(mess.getColor());
471:
472: int posDoci = DocumentUtils.getDocPositionFor(doc, li,
473: 0);
474: Rectangle reci = editorTextPane.modelToView(posDoci);
475:
476: int y = (int) (reci.getY() - upLeft.getY());
477: int r = 3;
478:
479: String letter = mess.getLetter();
480: if (letter == null) {
481: g2
482: .fill(new Arc2D.Double(
483: 2 + mess.getShiftX(), y - r + 7,
484: 2 * r, 2 * r, 0, 360, Arc2D.CHORD));
485: } else {
486: g2.drawString(letter, 2 + mess.getShiftX(), y + 12);
487: }
488:
489: } // for messages
490:
491: bm: for (SourceBookmark sb : bookmarks) {
492: int li = sb.getLinePosition();
493: if (li < 0)
494: li = 0;
495:
496: if (li < fromLine - 1) {
497: continue bm;
498: } else if (li > toLine + 1) {
499: continue bm;
500: }
501: g2.setColor(Color.blue);
502:
503: int posDoci = DocumentUtils.getDocPositionFor(doc, li,
504: 0);
505: Rectangle reci = editorTextPane.modelToView(posDoci);
506: int y = (int) (reci.getY() - upLeft.getY());
507: g2.drawString("B", 6, y + 12);
508: }
509:
510: // show small arrows showing that there are some warnings above or below.
511: if (hasBeforeVisibleView) {
512: GeneralPath gp = new GeneralPath();
513: gp.moveTo(8, 0);
514: gp.lineTo(13, 5);
515: gp.lineTo(3, 5);
516: gp.closePath();
517:
518: g2.setColor(Color.orange);
519: g2.fill(gp);
520: }
521:
522: if (hasAfterVisibleView) {
523: int h = this .getHeight();
524: GeneralPath gp = new GeneralPath();
525: gp.moveTo(8, h);
526: gp.lineTo(13, h - 5);
527: gp.lineTo(3, h - 5);
528: gp.closePath();
529:
530: g2.setColor(Color.orange);
531: g2.fill(gp);
532: }
533: } catch (Exception e) {
534: e.printStackTrace();
535: }
536: }
537:
538: }
|