KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > versioning > system > cvss > ui > actions > annotate > AnnotationBar


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.versioning.system.cvss.ui.actions.annotate;
21
22 import org.netbeans.editor.*;
23 import org.netbeans.editor.Utilities;
24 import org.netbeans.api.editor.fold.*;
25 import org.netbeans.api.diff.*;
26 import org.netbeans.api.project.*;
27 import org.netbeans.api.project.ui.OpenProjects;
28 import org.netbeans.modules.versioning.system.cvss.ui.actions.log.*;
29 import org.netbeans.modules.versioning.system.cvss.ui.actions.diff.*;
30 import org.netbeans.modules.versioning.system.cvss.ui.actions.update.GetCleanAction;
31 import org.netbeans.modules.versioning.system.cvss.util.*;
32 import org.netbeans.lib.cvsclient.command.annotate.*;
33 import org.netbeans.spi.diff.*;
34 import org.openide.*;
35 import org.openide.loaders.*;
36 import org.openide.filesystems.*;
37 import org.openide.text.*;
38 import org.openide.util.*;
39 import org.openide.xml.*;
40
41 import javax.swing.*;
42 import javax.swing.Timer JavaDoc;
43 import javax.swing.event.*;
44 import javax.swing.text.*;
45 import javax.accessibility.Accessible JavaDoc;
46 import java.awt.*;
47 import java.awt.event.*;
48 import java.beans.*;
49 import java.util.*;
50 import java.util.List JavaDoc;
51 import java.io.*;
52 import java.text.MessageFormat JavaDoc;
53
54 /**
55  * Represents annotation sidebar componnet in editor. It's
56  * created by {@link AnnotationBarManager}.
57  *
58  * <p>It reponds to following external signals:
59  * <ul>
60  * <li> {@link #annotate} message
61  * <li> {@link LogOutputListener} events
62  * </ul>
63  *
64  * @author Petr Kuzel
65  */

66 final class AnnotationBar extends JComponent implements Accessible JavaDoc, PropertyChangeListener, LogOutputListener, DocumentListener, ChangeListener, ActionListener, Runnable JavaDoc, ComponentListener {
67
68     /**
69      * Target text component for which the annotation bar is aiming.
70      */

71     private final JTextComponent textComponent;
72
73     /**
74      * User interface related to the target text component.
75      */

76     private final EditorUI editorUI;
77
78     /**
79      * Fold hierarchy of the text component user interface.
80      */

81     private final FoldHierarchy foldHierarchy;
82
83     /**
84      * Document related to the target text component.
85      */

86     private final BaseDocument doc;
87
88     /**
89      * Caret of the target text component.
90      */

91     private final Caret caret;
92
93     /**
94      * Caret batch timer launched on receiving
95      * annotation data structures (AnnotateLine).
96      */

97     private Timer JavaDoc caretTimer;
98
99     /**
100      * Controls annotation bar visibility.
101      */

102     private boolean annotated;
103
104     /**
105      * Maps document {@link Element}s (representing lines) to
106      * {@link AnnotateLine}. <code>null</code> means that
107      * no data are available, yet. So alternative
108      * {@link #elementAnnotationsSubstitute} text shoudl be used.
109      *
110      * @thread it is accesed from multiple threads all mutations
111      * and iterations must be under elementAnnotations lock,
112      */

113     private Map elementAnnotations;
114
115     /**
116      * Maps revision number (strings) to raw commit
117      * messages (strings).
118      */

119     private Map commitMessages;
120
121     /**
122      * Represents text that should be displayed in
123      * visible bar with yet <code>null</code> elementAnnotations.
124      */

125     private String JavaDoc elementAnnotationsSubstitute;
126     
127     private Color backgroundColor = Color.WHITE;
128     private Color foregroundColor = Color.BLACK;
129     private Color selectedColor = Color.BLUE;
130
131     /**
132      * Most recent status message.
133      */

134     private String JavaDoc recentStatusMessage;
135     
136     /**
137      * Revision associated with caret line.
138      */

139     private String JavaDoc recentRevision;
140     
141     /**
142      * Request processor to create threads that may be cancelled.
143      */

144     RequestProcessor requestProcessor = null;
145     
146     /**
147      * Latest annotation comment fetching task launched.
148      */

149     private RequestProcessor.Task latestAnnotationTask = null;
150
151     /**
152      * Creates new instance initializing final fields.
153      */

154     public AnnotationBar(JTextComponent target) {
155         this.textComponent = target;
156         this.editorUI = Utilities.getEditorUI(target);
157         this.foldHierarchy = FoldHierarchy.get(editorUI.getComponent());
158         this.doc = editorUI.getDocument();
159         this.caret = textComponent.getCaret();
160     }
161     
162     // public contract ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
163

164     /**
165      * Makes the bar visible and sensitive to
166      * LogOutoutListener events that should deliver
167      * actual content to be displayed.
168      */

169     public void annotate() {
170         annotated = true;
171         elementAnnotations = null;
172         commitMessages = null;
173         ResourceBundle loc = NbBundle.getBundle(AnnotationBar.class);
174         elementAnnotationsSubstitute = loc.getString("CTL_AnnotationSubstitute");
175
176         doc.addDocumentListener(this);
177         textComponent.addComponentListener(this);
178         editorUI.addPropertyChangeListener(this);
179
180         revalidate(); // resize the component
181
}
182
183     /**
184      * Result computed show it...
185      * Takes AnnotateLines and shows them.
186      */

187     public void annotationLines(File file, List JavaDoc annotateLines) {
188         List JavaDoc lines = new LinkedList(annotateLines);
189         int lineCount = lines.size();
190         /** 0 based line numbers => 1 based line numbers*/
191         int ann2editorPermutation[] = new int[lineCount];
192         for (int i = 0; i< lineCount; i++) {
193             ann2editorPermutation[i] = i+1;
194         }
195
196         DiffProvider diff = (DiffProvider) Lookup.getDefault().lookup(DiffProvider.class);
197         if (diff != null) {
198             Reader r = new LinesReader(lines);
199             Reader docReader = org.netbeans.modules.versioning.util.Utils.getDocumentReader(doc);
200             try {
201
202                 Difference[] differences = diff.computeDiff(r, docReader);
203
204                 // customize annotation line numbers to match different reality
205
// compule line permutation
206

207                 for (int i = 0; i < differences.length; i++) {
208                     Difference d = differences[i];
209                     if (d.getType() == Difference.ADD) continue;
210
211                     int editorStart;
212                     int firstShift = d.getFirstEnd() - d.getFirstStart() +1;
213                     if (d.getType() == Difference.CHANGE) {
214                         int firstLen = d.getFirstEnd() - d.getFirstStart();
215                         int secondLen = d.getSecondEnd() - d.getSecondStart();
216                         if (secondLen >= firstLen) continue; // ADD or pure CHANGE
217
editorStart = d.getSecondStart();
218                         firstShift = firstLen - secondLen;
219                     } else { // DELETE
220
editorStart = d.getSecondStart() + 1;
221                     }
222
223                     for (int c = editorStart + firstShift -1; c<lineCount; c++) {
224                         ann2editorPermutation[c] -= firstShift;
225                     }
226                 }
227
228                 for (int i = differences.length -1; i >= 0; i--) {
229                     Difference d = differences[i];
230                     if (d.getType() == Difference.DELETE) continue;
231
232                     int firstStart;
233                     int firstShift = d.getSecondEnd() - d.getSecondStart() +1;
234                     if (d.getType() == Difference.CHANGE) {
235                         int firstLen = d.getFirstEnd() - d.getFirstStart();
236                         int secondLen = d.getSecondEnd() - d.getSecondStart();
237                         if (secondLen <= firstLen) continue; // REMOVE or pure CHANGE
238
firstShift = secondLen - firstLen;
239                         firstStart = d.getFirstStart();
240                     } else {
241                         firstStart = d.getFirstStart() + 1;
242                     }
243
244                     for (int k = firstStart-1; k<lineCount; k++) {
245                         ann2editorPermutation[k] += firstShift;
246                     }
247                 }
248
249             } catch (IOException e) {
250                 ErrorManager err = ErrorManager.getDefault();
251                 err.annotate(e, "Cannot compute local diff required for annotations, ignoring..."); // NOI18N
252
err.notify(ErrorManager.INFORMATIONAL, e);
253             }
254         }
255
256         try {
257             doc.atomicLock();
258             StyledDocument sd = (StyledDocument) doc;
259             Iterator it = lines.iterator();
260             elementAnnotations = Collections.synchronizedMap(new HashMap(lines.size()));
261             while (it.hasNext()) {
262                 AnnotateLine line = (AnnotateLine) it.next();
263                 int lineNum = ann2editorPermutation[line.getLineNum() -1];
264                 try {
265                     int lineOffset = NbDocument.findLineOffset(sd, lineNum -1);
266                     Element element = sd.getParagraphElement(lineOffset);
267                     elementAnnotations.put(element, line);
268                 } catch (IndexOutOfBoundsException JavaDoc ex) {
269                     // TODO how could I get line behind document end?
270
// furtunately user does not spot it
271
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
272                 }
273             }
274         } finally {
275             doc.atomicUnlock();
276         }
277
278         // lazy listener registration
279
caret.addChangeListener(this);
280         this.caretTimer = new Timer JavaDoc(500, this);
281         caretTimer.setRepeats(false);
282
283         onCurrentLine();
284         revalidate();
285         repaint();
286     }
287
288     /**
289      * Takes commit messages and shows them as tooltips.
290      *
291      * @param a hashmap containing commit messages
292      */

293     public void commitMessages(Map messages) {
294         this.commitMessages = messages;
295     }
296
297     // implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
298

299     /**
300      * Gets a the file related to the document
301      *
302      * @return the file related to the document, <code>null</code> if none
303      * exists.
304      */

305     private File getCurrentFile() {
306         File result = null;
307         
308         DataObject dobj = (DataObject)doc.getProperty(Document.StreamDescriptionProperty);
309         if (dobj != null) {
310             FileObject fo = dobj.getPrimaryFile();
311             result = FileUtil.toFile(fo);
312         }
313         
314         return result;
315     }
316     
317     /**
318      * Registers "close" popup menu, tooltip manager
319      * and repaint on documet change manager.
320      */

321     public void addNotify() {
322         super.addNotify();
323
324
325         this.addMouseListener(new MouseAdapter() {
326             public void mousePressed(MouseEvent e) {
327                 maybeShowPopup(e);
328             }
329
330             public void mouseReleased(MouseEvent e) {
331                 maybeShowPopup(e);
332             }
333
334             private void maybeShowPopup(MouseEvent e) {
335                 if (e.isPopupTrigger()) {
336                     createPopup().show(e.getComponent(),
337                                e.getX(), e.getY());
338                 }
339             }
340         });
341
342         // register with tooltip manager
343
setToolTipText(""); // NOI18N
344

345     }
346
347     private JPopupMenu createPopup() {
348         final ResourceBundle loc = NbBundle.getBundle(AnnotationBar.class);
349         final JPopupMenu popupMenu = new JPopupMenu();
350         final JMenuItem diffMenu = new JMenuItem(loc.getString("CTL_MenuItem_DiffToRevision"));
351         diffMenu.addActionListener(new ActionListener() {
352             public void actionPerformed(ActionEvent e) {
353                 if (recentRevision != null) {
354                     String JavaDoc prevRevision = Utils.previousRevision(recentRevision);
355                     if (prevRevision != null) {
356                         File file = getCurrentFile();
357                         if (file != null) {
358                             DiffExecutor diffExecutor = new DiffExecutor(file.getName());
359                             diffExecutor.showDiff(file, prevRevision, recentRevision);
360                         }
361                     }
362                 }
363             }
364         });
365         popupMenu.add(diffMenu);
366
367         final JMenuItem rollbackMenu = new JMenuItem(loc.getString("CTL_MenuItem_RollbackToRevision"));
368         rollbackMenu.addActionListener(new ActionListener() {
369             public void actionPerformed(ActionEvent e) {
370                 File file = getCurrentFile();
371                 GetCleanAction.rollback(file, recentRevision);
372             }
373         });
374         popupMenu.add(rollbackMenu);
375
376         Project prj = Utils.getProject(getCurrentFile());
377         if (prj != null) {
378             String JavaDoc prjName = ProjectUtils.getInformation(prj).getDisplayName();
379             JMenuItem menu = new JMenuItem(NbBundle.getMessage(AnnotationBar.class, "CTL_MenuItem_FindCommitInProject", prjName));
380             menu.addActionListener(new ActionListener() {
381                 public void actionPerformed(ActionEvent e) {
382                     int line = getCurrentLine();
383                     if (line == -1) return;
384                     AnnotateLine al = getAnnotateLine(line);
385                     if (al == null || commitMessages == null) return;
386                     String JavaDoc message = (String JavaDoc) commitMessages.get(al.getRevision());
387                     File file = getCurrentFile();
388                     Project project = Utils.getProject(file);
389                     Context context = Utils.getProjectsContext(new Project[] { project });
390                     SearchHistoryAction.openSearch(
391                             context,
392                             ProjectUtils.getInformation(project).getDisplayName(),
393                             message, al.getAuthor(), al.getDate());
394                 }
395             });
396             popupMenu.add(menu);
397         }
398
399         JMenuItem menu = new JMenuItem(loc.getString("CTL_MenuItem_FindCommitInProjects"));
400         menu.addActionListener(new ActionListener() {
401             public void actionPerformed(ActionEvent e) {
402                 int line = getCurrentLine();
403                 if (line == -1) return;
404                 AnnotateLine al = getAnnotateLine(line);
405                 if (al == null || commitMessages == null) return;
406                 String JavaDoc message = (String JavaDoc) commitMessages.get(al.getRevision());
407                 Project [] projects = OpenProjects.getDefault().getOpenProjects();
408                 int n = projects.length;
409                 SearchHistoryAction.openSearch(
410                         (n == 1) ? ProjectUtils.getInformation(projects[0]).getDisplayName() :
411                         NbBundle.getMessage(AnnotationBar.class, "CTL_FindAssociateChanges_OpenProjects_Title", Integer.toString(n)),
412                         message, al.getAuthor(), al.getDate());
413             }
414         });
415         popupMenu.add(menu);
416
417         menu = new JMenuItem(loc.getString("CTL_MenuItem_CloseAnnotations"));
418         menu.addActionListener(new ActionListener() {
419             public void actionPerformed(ActionEvent e) {
420                 hideBar();
421             }
422         });
423         popupMenu.add(new JSeparator());
424         popupMenu.add(menu);
425
426         // dynamic labels an dvisibility
427

428         diffMenu.setVisible(false);
429         rollbackMenu.setVisible(false);
430         if (recentRevision != null) {
431             String JavaDoc prevRevision = Utils.previousRevision(recentRevision);
432             if (prevRevision != null) {
433                 String JavaDoc format = loc.getString("CTL_MenuItem_DiffToRevision");
434                 diffMenu.setText(MessageFormat.format(format, new Object JavaDoc [] { recentRevision, prevRevision }));
435                 diffMenu.setVisible(true);
436             }
437             String JavaDoc format = loc.getString("CTL_MenuItem_RollbackToRevision");
438             rollbackMenu.setText(MessageFormat.format(format, new Object JavaDoc [] { recentRevision }));
439             rollbackMenu.setVisible(true);
440         }
441
442         return popupMenu;
443     }
444
445     /**
446      * Hides the annotation bar from user.
447      */

448     void hideBar() {
449         annotated = false;
450         revalidate();
451         release();
452     }
453
454     /**
455      * Gets the line number of the caret's current position. The first line
456      * will return a line number of 0 (zero). If it's impossible to determine
457      * the caret's current line number -1 will be returned.
458      *
459      * @return the line number of the caret's current position
460      */

461     private int getCurrentLine() {
462         int result = 0;
463         
464         int offset = caret.getDot();
465         try {
466             result = Utilities.getLineOffset(doc, offset);
467         } catch (BadLocationException ex) {
468             result = -1;
469         }
470         
471         return result;
472      }
473     /**
474      * Gets a request processor which is able to cancel tasks.
475      */

476     private RequestProcessor getRequestProcessor() {
477         if (requestProcessor == null) {
478             requestProcessor = new RequestProcessor("AnnotationBarRP", 1, true); // NOI18N
479
}
480         
481         return requestProcessor;
482     }
483     
484     /**
485      * Shows commit message in status bar and or revision change repaints side
486      * bar (to highlight same revision). This process is started in a
487      * seperate thread.
488      */

489     private void onCurrentLine() {
490         if (latestAnnotationTask != null) {
491             latestAnnotationTask.cancel();
492         }
493         
494         latestAnnotationTask = getRequestProcessor().post(this);
495     }
496
497     // latestAnnotationTask business logic
498
public void run() {
499         // get resource bundle
500
ResourceBundle loc = NbBundle.getBundle(AnnotationBar.class);
501         // give status bar "wait" indication
502
StatusBar statusBar = editorUI.getStatusBar();
503         recentStatusMessage = loc.getString("CTL_StatusBar_WaitFetchAnnotation");
504         statusBar.setText(StatusBar.CELL_MAIN, recentStatusMessage);
505         
506         // determine current line
507
int line = -1;
508         int offset = caret.getDot();
509         try {
510             line = Utilities.getLineOffset(doc, offset);
511         } catch (BadLocationException ex) {
512             ErrorManager err = ErrorManager.getDefault();
513             err.annotate(ex, "Can not get line for caret at offset " + offset); // NOI18N
514
err.notify(ex);
515             clearRecentFeedback();
516             return;
517         }
518
519         // handle locally modified lines
520
AnnotateLine al = getAnnotateLine(line);
521         if (al == null) {
522             AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
523             if (amp != null) {
524                 amp.setMarks(Collections.EMPTY_LIST);
525             }
526             clearRecentFeedback();
527             if (recentRevision != null) {
528                 recentRevision = null;
529                 repaint();
530             }
531             return;
532         }
533
534         // handle unchanged lines
535
String JavaDoc revision = al.getRevision();
536         if (revision.equals(recentRevision) == false) {
537             recentRevision = revision;
538             repaint();
539
540             AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
541             if (amp != null) {
542             
543                 List JavaDoc marks = new ArrayList(elementAnnotations.size());
544                 // I cannot affort to lock elementAnnotations for long time
545
// it's accessed from editor thread too
546
Iterator it2;
547                 synchronized(elementAnnotations) {
548                     it2 = new HashSet(elementAnnotations.entrySet()).iterator();
549                 }
550                 while (it2.hasNext()) {
551                     Map.Entry next = (Map.Entry) it2.next();
552                     AnnotateLine annotateLine = (AnnotateLine) next.getValue();
553                     if (revision.equals(annotateLine.getRevision())) {
554                         Element element = (Element) next.getKey();
555                         if (elementAnnotations.containsKey(element) == false) {
556                             continue;
557                         }
558                         int elementOffset = element.getStartOffset();
559                         int lineNumber = NbDocument.findLineNumber((StyledDocument)doc, elementOffset);
560                         AnnotationMark mark = new AnnotationMark(lineNumber, revision);
561                         marks.add(mark);
562                     }
563
564                     if (Thread.interrupted()) {
565                         clearRecentFeedback();
566                         return;
567                     }
568                 }
569                 amp.setMarks(marks);
570             }
571         }
572
573         if (commitMessages != null) {
574             String JavaDoc message = (String JavaDoc) commitMessages.get(revision);
575             if (message != null) {
576                 recentStatusMessage = message;
577                 statusBar.setText(StatusBar.CELL_MAIN, al.getAuthor() + ": " + recentStatusMessage); // NOI18N
578
} else {
579                 clearRecentFeedback();
580             }
581         } else {
582             clearRecentFeedback();
583         };
584     }
585     
586     /**
587      * Clears the status bar if it contains the latest status message
588      * displayed by this annotation bar.
589      */

590     private void clearRecentFeedback() {
591         StatusBar statusBar = editorUI.getStatusBar();
592         if (statusBar.getText(StatusBar.CELL_MAIN) == recentStatusMessage) {
593             statusBar.setText(StatusBar.CELL_MAIN, ""); // NOI18N
594
}
595     }
596
597     /**
598      * Components created by SibeBarFactory are positioned
599      * using a Layout manager that determines componnet size
600      * by retireving preferred size.
601      *
602      * <p>Once componnet needs resizing it simply calls
603      * {@link #revalidate} that triggers new layouting
604      * that consults prefered size.
605      */

606     public Dimension getPreferredSize() {
607         Dimension dim = textComponent.getSize();
608         int width = annotated ? getBarWidth() : 0;
609         dim.width = width;
610         dim.height *=2; // XXX
611
return dim;
612     }
613
614     /**
615      * Gets the maximum size of this component.
616      *
617      * @return the maximum size of this component
618      */

619     public Dimension getMaximumSize() {
620         return getPreferredSize();
621     }
622
623     /**
624      * Gets the preferred width of this component.
625      *
626      * @return the preferred width of this component
627      */

628     private int getBarWidth() {
629         String JavaDoc longestString = ""; // NOI18N
630
if (elementAnnotations == null) {
631             longestString = elementAnnotationsSubstitute;
632         } else {
633             synchronized(elementAnnotations) {
634                 Iterator it = elementAnnotations.values().iterator();
635                 while (it.hasNext()) {
636                     AnnotateLine line = (AnnotateLine) it.next();
637                     String JavaDoc displayName = line.getRevision() + " " + line.getAuthor(); // NOI18N
638
if (displayName.length() > longestString.length()) {
639                         longestString = displayName;
640                     }
641                 }
642             }
643         }
644         char[] data = longestString.toCharArray();
645         int w = getGraphics().getFontMetrics().charsWidth(data, 0, data.length);
646         return w;
647     }
648     
649     /**
650      * Pair method to {@link #annotate}. It releases
651      * all resources.
652      */

653     private void release() {
654         editorUI.removePropertyChangeListener(this);
655         textComponent.removeComponentListener(this);
656         doc.removeDocumentListener(this);
657         caret.removeChangeListener(this);
658         if (caretTimer != null) {
659             caretTimer.removeActionListener(this);
660         }
661         commitMessages = null;
662         elementAnnotations = null;
663         // cancel running annotation task if active
664
if(latestAnnotationTask != null) {
665             latestAnnotationTask.cancel();
666         }
667         AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
668         if (amp != null) {
669             amp.setMarks(Collections.EMPTY_LIST);
670         }
671
672         clearRecentFeedback();
673     }
674
675     /**
676      * Paints one view that corresponds to a line (or
677      * multiple lines if folding takes effect).
678      */

679     private void paintView(View view, Graphics g, int yBase) {
680         JTextComponent component = editorUI.getComponent();
681         if (component == null) return;
682         BaseTextUI textUI = (BaseTextUI)component.getUI();
683
684         Element rootElem = textUI.getRootView(component).getElement();
685         int line = rootElem.getElementIndex(view.getStartOffset());
686
687         String JavaDoc annotation = ""; // NOI18N
688
AnnotateLine al = null;
689         if (elementAnnotations != null) {
690             al = getAnnotateLine(line);
691             if (al != null) {
692                 annotation = al.getRevision() + " " + al.getAuthor(); // NOI18N
693
}
694         } else {
695             annotation = elementAnnotationsSubstitute;
696         }
697
698         if (al != null && al.getRevision().equals(recentRevision)) {
699             g.setColor(selectedColor());
700         } else {
701             g.setColor(foregroundColor());
702         }
703         g.drawString(annotation, 0, yBase + editorUI.getLineAscent());
704     }
705
706     /**
707      * Presents commit message as tooltips.
708      */

709     public String JavaDoc getToolTipText (MouseEvent e) {
710         if (editorUI == null)
711             return null;
712         int line = getLineFromMouseEvent(e);
713
714         StringBuffer JavaDoc annotation = new StringBuffer JavaDoc();
715         if (elementAnnotations != null) {
716             AnnotateLine al = getAnnotateLine(line);
717
718             if (al != null) {
719                 String JavaDoc escapedAuthor = NbBundle.getMessage(AnnotationBar.class, "BK0001");
720                 try {
721                     escapedAuthor = XMLUtil.toElementContent(al.getAuthor());
722                 } catch (CharConversionException e1) {
723                     ErrorManager err = ErrorManager.getDefault();
724                     err.annotate(e1, "CVS.AB: can not HTML escape: " + al.getAuthor()); // NOI18N
725
err.notify(ErrorManager.INFORMATIONAL, e1);
726                 }
727
728                 // always return unique string to avoid tooltip sharing on mouse move over same revisions -->
729
annotation.append("<html><!-- line=" + line++ + " -->" + al.getRevision() + " <b>" + escapedAuthor + "</b> " + al.getDateString()); // NOI18N
730
if (commitMessages != null) {
731                     String JavaDoc message = (String JavaDoc) commitMessages.get(al.getRevision());
732                     if (message != null) {
733                         String JavaDoc escaped = null;
734                         try {
735                             escaped = XMLUtil.toElementContent(message);
736                         } catch (CharConversionException e1) {
737                             ErrorManager err = ErrorManager.getDefault();
738                             err.annotate(e1, "CVS.AB: can not HTML escape: " + message); // NOI18N
739
err.notify(ErrorManager.INFORMATIONAL, e1);
740                         }
741                         if (escaped != null) {
742                             String JavaDoc lined = escaped.replaceAll(System.getProperty("line.separator"), "<br>"); // NOI18N
743
annotation.append("<p>" + lined); // NOI18N
744
}
745                     }
746                 }
747             }
748         } else {
749             annotation.append(elementAnnotationsSubstitute);
750         }
751
752         return annotation.toString();
753     }
754
755     /**
756      * Locates AnnotateLine associated with given line. The
757      * line is translated to Element that is used as map lookup key.
758      * The map is initially filled up with Elements sampled on
759      * annotate() method.
760      *
761      * <p>Key trick is that Element's identity is maintained
762      * until line removal (and is restored on undo).
763      *
764      * @param line
765      * @return found AnnotateLine or <code>null</code>
766      */

767     private AnnotateLine getAnnotateLine(int line) {
768         StyledDocument sd = (StyledDocument) doc;
769         int lineOffset = NbDocument.findLineOffset(sd, line);
770         Element element = sd.getParagraphElement(lineOffset);
771         AnnotateLine al = (AnnotateLine) elementAnnotations.get(element);
772
773         if (al != null) {
774             int startOffset = element.getStartOffset();
775             int endOffset = element.getEndOffset();
776             try {
777                 int len = endOffset - startOffset;
778                 String JavaDoc text = doc.getText(startOffset, len -1);
779                 String JavaDoc content = al.getContent();
780                 if (text.equals(content)) {
781                     return al;
782                 }
783             } catch (BadLocationException e) {
784                 ErrorManager err = ErrorManager.getDefault();
785                 err.annotate(e, "CVS.AB: can not locate line annotation."); // NOI18N
786
err.notify(ErrorManager.INFORMATIONAL, e);
787             }
788         }
789
790         return null;
791     }
792
793     /**
794      * GlyphGutter copy pasted bolerplate method.
795      * It invokes {@link #paintView} that contains
796      * actual business logic.
797      */

798     public void paintComponent(Graphics g) {
799         super.paintComponent(g);
800
801         Rectangle clip = g.getClipBounds();
802
803         JTextComponent component = editorUI.getComponent();
804         if (component == null) return;
805
806         BaseTextUI textUI = (BaseTextUI)component.getUI();
807         View rootView = Utilities.getDocumentView(component);
808         if (rootView == null) return;
809
810         g.setColor(backgroundColor());
811         g.fillRect(clip.x, clip.y, clip.width, clip.height);
812
813         AbstractDocument doc = (AbstractDocument)component.getDocument();
814         doc.readLock();
815         try{
816             foldHierarchy.lock();
817             try{
818                 int startPos = textUI.getPosFromY(clip.y);
819                 int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward);
820                 int rootViewCount = rootView.getViewCount();
821
822                 if (startViewIndex >= 0 && startViewIndex < rootViewCount) {
823                     // find the nearest visible line with an annotation
824
Rectangle rec = textUI.modelToView(component, rootView.getView(startViewIndex).getStartOffset());
825                     int y = (rec == null) ? 0 : rec.y;
826
827                     int clipEndY = clip.y + clip.height;
828                     for (int i = startViewIndex; i < rootViewCount; i++){
829                         View view = rootView.getView(i);
830                         paintView(view, g, y);
831                         y += editorUI.getLineHeight();
832                         if (y >= clipEndY) {
833                             break;
834                         }
835                     }
836                 }
837
838             } finally {
839                 foldHierarchy.unlock();
840             }
841         } catch (BadLocationException ble){
842             ErrorManager.getDefault().notify(ble);
843         } finally {
844             doc.readUnlock();
845         }
846     }
847
848     private Color backgroundColor() {
849         if (textComponent != null) {
850             return textComponent.getBackground();
851         }
852         return backgroundColor;
853     }
854
855     private Color foregroundColor() {
856         if (textComponent != null) {
857             return textComponent.getForeground();
858         }
859         return foregroundColor;
860     }
861
862     private Color selectedColor() {
863         if (backgroundColor == backgroundColor()) {
864             return selectedColor;
865         }
866         if (textComponent != null) {
867             return textComponent.getForeground();
868         }
869         return selectedColor;
870
871     }
872
873
874     /** GlyphGutter copy pasted utility method. */
875     private int getLineFromMouseEvent(MouseEvent e){
876         int line = -1;
877         if (editorUI != null) {
878             try{
879                 JTextComponent component = editorUI.getComponent();
880                 BaseTextUI textUI = (BaseTextUI)component.getUI();
881                 int clickOffset = textUI.viewToModel(component, new Point(0, e.getY()));
882                 line = Utilities.getLineOffset(doc, clickOffset);
883             }catch (BadLocationException ble){
884             }
885         }
886         return line;
887     }
888
889     /** Implementation */
890     public void propertyChange(PropertyChangeEvent evt) {
891         if (evt == null) return;
892         String JavaDoc id = evt.getPropertyName();
893         if (EditorUI.COMPONENT_PROPERTY.equals(id)) { // NOI18N
894
if (evt.getNewValue() == null){
895                 // component deinstalled, lets uninstall all isteners
896
release();
897             }
898         }
899
900     }
901
902     /** Implementation */
903     public void changedUpdate(DocumentEvent e) {
904     }
905
906     /** Implementation */
907     public void insertUpdate(DocumentEvent e) {
908         // handle new lines, Enter hit at end of line changes
909
// the line element instance
910
// XXX Actually NB document implementation triggers this method two times
911
// - first time with one removed and two added lines
912
// - second time with two removed and two added lines
913
if (elementAnnotations != null) {
914             Element[] elements = e.getDocument().getRootElements();
915             synchronized(elementAnnotations) { // atomic change
916
for (int i = 0; i < elements.length; i++) {
917                     Element element = elements[i];
918                     DocumentEvent.ElementChange change = e.getChange(element);
919                     if (change == null) continue;
920                     Element[] removed = change.getChildrenRemoved();
921                     Element[] added = change.getChildrenAdded();
922
923                     if (removed.length == added.length) {
924                         for (int c = 0; c<removed.length; c++) {
925                             Object JavaDoc recent = elementAnnotations.get(removed[c]);
926                             if (recent != null) {
927                                 elementAnnotations.remove(removed[c]);
928                                 elementAnnotations.put(added[c], recent);
929                             }
930                         }
931                     } else if (removed.length == 1 && added.length > 0) {
932                         Element key = removed[0];
933                         Object JavaDoc recent = elementAnnotations.get(key);
934                         if (recent != null) {
935                             elementAnnotations.remove(key);
936                             elementAnnotations.put(added[0], recent);
937                         }
938                     }
939                 }
940             }
941         }
942         repaint();
943     }
944
945     /** Implementation */
946     public void removeUpdate(DocumentEvent e) {
947         if (e.getDocument().getLength() == 0) { // external reload
948
hideBar();
949         }
950         repaint();
951     }
952
953     /** Caret */
954     public void stateChanged(ChangeEvent e) {
955         assert e.getSource() == caret;
956         caretTimer.restart();
957     }
958
959     /** Timer */
960     public void actionPerformed(ActionEvent e) {
961         assert e.getSource() == caretTimer;
962         onCurrentLine();
963     }
964
965     /** on JTextPane */
966     public void componentHidden(ComponentEvent e) {
967     }
968
969     /** on JTextPane */
970     public void componentMoved(ComponentEvent e) {
971     }
972
973     /** on JTextPane */
974     public void componentResized(ComponentEvent e) {
975         revalidate();
976     }
977
978     /** on JTextPane */
979     public void componentShown(ComponentEvent e) {
980     }
981
982     private static class CvsAnnotation extends Annotation {
983         
984         private final String JavaDoc text;
985
986         private Line line;
987
988         public CvsAnnotation(String JavaDoc tooltip, Line line) {
989             text = tooltip;
990             this.line = line;
991         }
992
993         public void attach() {
994             attach(line);
995             line = null;
996         }
997
998         public String JavaDoc getShortDescription() {
999             return text;
1000        }
1001
1002        public String JavaDoc getAnnotationType() {
1003            return "org-netbeans-modules-versioning-system-cvss-Annotation"; // NOI18N
1004
}
1005    }
1006}
1007
Popular Tags