KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > subversion > ui > blame > 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.subversion.ui.blame;
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.spi.diff.*;
27 import org.netbeans.modules.subversion.ui.update.RevertModifications;
28 import org.netbeans.modules.subversion.ui.update.RevertModificationsAction;
29 import org.netbeans.modules.subversion.ui.diff.DiffAction;
30 import org.netbeans.modules.subversion.RepositoryFile;
31 import org.netbeans.modules.subversion.Subversion;
32 import org.netbeans.modules.subversion.client.SvnProgressSupport;
33 import org.netbeans.modules.subversion.util.SvnUtils;
34 import org.netbeans.modules.subversion.util.Context;
35 import org.netbeans.modules.versioning.util.Utils;
36 import org.openide.*;
37 import org.openide.loaders.*;
38 import org.openide.filesystems.*;
39 import org.openide.text.*;
40 import org.openide.util.*;
41 import org.openide.xml.*;
42 import org.tigris.subversion.svnclientadapter.SVNUrl;
43 import org.tigris.subversion.svnclientadapter.SVNRevision;
44
45 import javax.swing.*;
46 import javax.swing.Timer JavaDoc;
47 import javax.swing.event.*;
48 import javax.swing.text.*;
49 import javax.accessibility.Accessible JavaDoc;
50 import java.awt.*;
51 import java.awt.event.*;
52 import java.beans.*;
53 import java.util.*;
54 import java.util.List JavaDoc;
55 import java.io.*;
56 import java.text.DateFormat JavaDoc;
57 import java.text.MessageFormat JavaDoc;
58
59 /**
60  * Represents annotation sidebar componnet in editor. It's
61  * created by {@link AnnotationBarManager}.
62  *
63  * <p>It reponds to following external signals:
64  * <ul>
65  * <li> {@link #annotate} message
66  * </ul>
67  *
68  * @author Petr Kuzel
69  */

70 final class AnnotationBar extends JComponent implements Accessible JavaDoc, PropertyChangeListener, DocumentListener, ChangeListener, ActionListener, Runnable JavaDoc, ComponentListener {
71
72     /**
73      * Target text component for which the annotation bar is aiming.
74      */

75     private final JTextComponent textComponent;
76
77     /**
78      * User interface related to the target text component.
79      */

80     private final EditorUI editorUI;
81
82     /**
83      * Fold hierarchy of the text component user interface.
84      */

85     private final FoldHierarchy foldHierarchy;
86
87     /**
88      * Document related to the target text component.
89      */

90     private final BaseDocument doc;
91
92     /**
93      * Caret of the target text component.
94      */

95     private final Caret caret;
96
97     /**
98      * Caret batch timer launched on receiving
99      * annotation data structures (AnnotateLine).
100      */

101     private Timer JavaDoc caretTimer;
102
103     /**
104      * Controls annotation bar visibility.
105      */

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

117     private Map<Element, AnnotateLine> elementAnnotations;
118
119     /**
120      * Represents text that should be displayed in
121      * visible bar with yet <code>null</code> elementAnnotations.
122      */

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

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

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

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

147     private RequestProcessor.Task latestAnnotationTask = null;
148
149     /**
150      * Holds false if Rollback Changes action is NOT valid for current revision, true otherwise.
151      */

152     private boolean recentRevisionCanBeRolledBack;
153
154     /**
155      * Creates new instance initializing final fields.
156      */

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

167     /**
168      * Makes the bar visible and sensitive to
169      * LogOutoutListener events that should deliver
170      * actual content to be displayed.
171      */

172     public void annotate() {
173         annotated = true;
174         elementAnnotations = null;
175
176         doc.addDocumentListener(this);
177         textComponent.addComponentListener(this);
178         editorUI.addPropertyChangeListener(this);
179
180         revalidate(); // resize the component
181
}
182
183     public void setAnnotationMessage(String JavaDoc message) {
184         elementAnnotationsSubstitute = message;
185         revalidate();
186     }
187     
188     /**
189      * Result computed show it...
190      * Takes AnnotateLines and shows them.
191      */

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

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

295     /**
296      * Gets a the file related to the document
297      *
298      * @return the file related to the document, <code>null</code> if none
299      * exists.
300      */

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

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

341     }
342
343     private JPopupMenu createPopup() {
344         final ResourceBundle loc = NbBundle.getBundle(AnnotationBar.class);
345         final JPopupMenu popupMenu = new JPopupMenu();
346
347         final File file = getCurrentFile();
348         
349         final JMenuItem diffMenu = new JMenuItem(loc.getString("CTL_MenuItem_DiffToRevision"));
350         diffMenu.addActionListener(new ActionListener() {
351             public void actionPerformed(ActionEvent e) {
352                 if (recentRevision != null) {
353                     if (getPreviousRevision(recentRevision) != null) {
354                         DiffAction.diff(file, getPreviousRevision(recentRevision), recentRevision);
355                     }
356                 }
357             }
358         });
359         popupMenu.add(diffMenu);
360
361         JMenuItem rollbackMenu = new JMenuItem(loc.getString("CTL_MenuItem_Revert"));
362         rollbackMenu.addActionListener(new ActionListener() {
363             public void actionPerformed(ActionEvent e) {
364                 revert(file, recentRevision);
365             }
366         });
367         popupMenu.add(rollbackMenu);
368         rollbackMenu.setEnabled(recentRevisionCanBeRolledBack);
369
370         JMenuItem menu;
371         menu = new JMenuItem(loc.getString("CTL_MenuItem_CloseAnnotations"));
372         menu.addActionListener(new ActionListener() {
373             public void actionPerformed(ActionEvent e) {
374                 hideBar();
375             }
376         });
377         popupMenu.add(new JSeparator());
378         popupMenu.add(menu);
379
380         diffMenu.setVisible(false);
381         rollbackMenu.setVisible(false);
382         if (recentRevision != null) {
383             if (getPreviousRevision(recentRevision) != null) {
384                 String JavaDoc format = loc.getString("CTL_MenuItem_DiffToRevision");
385                 diffMenu.setText(MessageFormat.format(format, new Object JavaDoc [] { recentRevision, getPreviousRevision(recentRevision) }));
386                 diffMenu.setVisible(true);
387             }
388             rollbackMenu.setVisible(true);
389         }
390
391         return popupMenu;
392     }
393
394     private void revert(File file, String JavaDoc revision) {
395         final Context ctx = new Context(file);
396         final SVNUrl url = SvnUtils.getRepositoryRootUrl(file);
397         final RepositoryFile repositoryFile = new RepositoryFile(url, url, SVNRevision.HEAD);
398         
399         final RevertModifications revertModifications = new RevertModifications(repositoryFile, revision);
400         if(!revertModifications.showDialog()) {
401             return;
402         }
403
404         RequestProcessor rp = Subversion.getInstance().getRequestProcessor(url);
405         SvnProgressSupport support = new SvnProgressSupport() {
406             public void perform() {
407                 RevertModificationsAction.performRevert(ctx, revertModifications, this);
408             }
409         };
410         support.start(rp, url, NbBundle.getMessage(AnnotationBar.class, "MSG_Revert_Progress")); // NOI18N
411
}
412
413     private String JavaDoc getPreviousRevision(String JavaDoc revision) {
414         return Long.toString(Long.parseLong(revision) - 1);
415     }
416
417     /**
418      * Hides the annotation bar from user.
419      */

420     void hideBar() {
421         annotated = false;
422         revalidate();
423         release();
424     }
425
426     /**
427      * Gets a request processor which is able to cancel tasks.
428      */

429     private RequestProcessor getRequestProcessor() {
430         if (requestProcessor == null) {
431             requestProcessor = new RequestProcessor("AnnotationBarRP", 1, true); // NOI18N
432
}
433         
434         return requestProcessor;
435     }
436     
437     /**
438      * Shows commit message in status bar and or revision change repaints side
439      * bar (to highlight same revision). This process is started in a
440      * seperate thread.
441      */

442     private void onCurrentLine() {
443         if (latestAnnotationTask != null) {
444             latestAnnotationTask.cancel();
445         }
446         
447         latestAnnotationTask = getRequestProcessor().post(this);
448     }
449
450     // latestAnnotationTask business logic
451
public void run() {
452         // get resource bundle
453
ResourceBundle loc = NbBundle.getBundle(AnnotationBar.class);
454         // give status bar "wait" indication
455
StatusBar statusBar = editorUI.getStatusBar();
456         recentStatusMessage = loc.getString("CTL_StatusBar_WaitFetchAnnotation");
457         statusBar.setText(StatusBar.CELL_MAIN, recentStatusMessage);
458         
459         recentRevisionCanBeRolledBack = false;
460         // determine current line
461
int line = -1;
462         int offset = caret.getDot();
463         try {
464             line = Utilities.getLineOffset(doc, offset);
465         } catch (BadLocationException ex) {
466             ErrorManager err = ErrorManager.getDefault();
467             err.annotate(ex, "Can not get line for caret at offset " + offset); // NOI18N
468
err.notify(ex);
469             clearRecentFeedback();
470             return;
471         }
472
473         // handle locally modified lines
474
AnnotateLine al = getAnnotateLine(line);
475         if (al == null) {
476             AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
477             if (amp != null) {
478                 amp.setMarks(Collections.<AnnotationMark>emptyList());
479             }
480             clearRecentFeedback();
481             if (recentRevision != null) {
482                 recentRevision = null;
483                 repaint();
484             }
485             return;
486         }
487
488         // handle unchanged lines
489
String JavaDoc revision = al.getRevision();
490         if (revision.equals(recentRevision) == false) {
491             recentRevision = revision;
492             recentRevisionCanBeRolledBack = al.canBeRolledBack();
493             repaint();
494
495             AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
496             if (amp != null) {
497             
498                 List JavaDoc<AnnotationMark> marks = new ArrayList<AnnotationMark>(elementAnnotations.size());
499                 // I cannot affort to lock elementAnnotations for long time
500
// it's accessed from editor thread too
501
Iterator<Map.Entry<Element, AnnotateLine>> it2;
502                 synchronized(elementAnnotations) {
503                     it2 = new HashSet<Map.Entry<Element, AnnotateLine>>(elementAnnotations.entrySet()).iterator();
504                 }
505                 while (it2.hasNext()) {
506                     Map.Entry<Element, AnnotateLine> next = it2.next();
507                     AnnotateLine annotateLine = next.getValue();
508                     if (revision.equals(annotateLine.getRevision())) {
509                         Element element = next.getKey();
510                         if (elementAnnotations.containsKey(element) == false) {
511                             continue;
512                         }
513                         int elementOffset = element.getStartOffset();
514                         int lineNumber = NbDocument.findLineNumber((StyledDocument)doc, elementOffset);
515                         AnnotationMark mark = new AnnotationMark(lineNumber, revision);
516                         marks.add(mark);
517                     }
518
519                     if (Thread.interrupted()) {
520                         clearRecentFeedback();
521                         return;
522                     }
523                 }
524                 amp.setMarks(marks);
525             }
526         }
527
528         if (al.getCommitMessage() != null) {
529             recentStatusMessage = al.getCommitMessage();
530             statusBar.setText(StatusBar.CELL_MAIN, al.getAuthor() + ": " + recentStatusMessage); // NOI18N
531
} else {
532             clearRecentFeedback();
533         };
534     }
535     
536     /**
537      * Clears the status bar if it contains the latest status message
538      * displayed by this annotation bar.
539      */

540     private void clearRecentFeedback() {
541         StatusBar statusBar = editorUI.getStatusBar();
542         if (statusBar.getText(StatusBar.CELL_MAIN) == recentStatusMessage) {
543             statusBar.setText(StatusBar.CELL_MAIN, ""); // NOI18N
544
}
545     }
546
547     /**
548      * Components created by SibeBarFactory are positioned
549      * using a Layout manager that determines componnet size
550      * by retireving preferred size.
551      *
552      * <p>Once componnet needs resizing it simply calls
553      * {@link #revalidate} that triggers new layouting
554      * that consults prefered size.
555      */

556     public Dimension getPreferredSize() {
557         Dimension dim = textComponent.getSize();
558         int width = annotated ? getBarWidth() : 0;
559         dim.width = width;
560         dim.height *=2; // XXX
561
return dim;
562     }
563
564     /**
565      * Gets the maximum size of this component.
566      *
567      * @return the maximum size of this component
568      */

569     public Dimension getMaximumSize() {
570         return getPreferredSize();
571     }
572
573     /**
574      * Gets the preferred width of this component.
575      *
576      * @return the preferred width of this component
577      */

578     private int getBarWidth() {
579         String JavaDoc longestString = ""; // NOI18N
580
if (elementAnnotations == null) {
581             longestString = elementAnnotationsSubstitute;
582         } else {
583             synchronized(elementAnnotations) {
584                 Iterator<AnnotateLine> it = elementAnnotations.values().iterator();
585                 while (it.hasNext()) {
586                     AnnotateLine line = it.next();
587                     String JavaDoc displayName = getDisplayName(line); // NOI18N
588
if (displayName.length() > longestString.length()) {
589                         longestString = displayName;
590                     }
591                 }
592             }
593         }
594         char[] data = longestString.toCharArray();
595         int w = getGraphics().getFontMetrics().charsWidth(data, 0, data.length);
596         return w + 4;
597     }
598
599     private String JavaDoc getDisplayName(AnnotateLine line) {
600         return line.getRevision() + " " + line.getAuthor(); // NOI18N
601
}
602
603     /**
604      * Pair method to {@link #annotate}. It releases
605      * all resources.
606      */

607     private void release() {
608         editorUI.removePropertyChangeListener(this);
609         textComponent.removeComponentListener(this);
610         doc.removeDocumentListener(this);
611         caret.removeChangeListener(this);
612         if (caretTimer != null) {
613             caretTimer.removeActionListener(this);
614         }
615         elementAnnotations = null;
616         // cancel running annotation task if active
617
if(latestAnnotationTask != null) {
618             latestAnnotationTask.cancel();
619         }
620         AnnotationMarkProvider amp = AnnotationMarkInstaller.getMarkProvider(textComponent);
621         if (amp != null) {
622             amp.setMarks(Collections.<AnnotationMark>emptyList());
623         }
624
625         clearRecentFeedback();
626     }
627
628     /**
629      * Paints one view that corresponds to a line (or
630      * multiple lines if folding takes effect).
631      */

632     private void paintView(View view, Graphics g, int yBase) {
633         JTextComponent component = editorUI.getComponent();
634         if (component == null) return;
635         BaseTextUI textUI = (BaseTextUI)component.getUI();
636
637         Element rootElem = textUI.getRootView(component).getElement();
638         int line = rootElem.getElementIndex(view.getStartOffset());
639
640         String JavaDoc annotation = ""; // NOI18N
641
AnnotateLine al = null;
642         if (elementAnnotations != null) {
643             al = getAnnotateLine(line);
644             if (al != null) {
645                 annotation = getDisplayName(al); // NOI18N
646
}
647         } else {
648             annotation = elementAnnotationsSubstitute;
649         }
650
651         if (al != null && al.getRevision().equals(recentRevision)) {
652             g.setColor(selectedColor());
653         } else {
654             g.setColor(foregroundColor());
655         }
656         g.drawString(annotation, 2, yBase + editorUI.getLineAscent());
657     }
658
659     /**
660      * Presents commit message as tooltips.
661      */

662     public String JavaDoc getToolTipText (MouseEvent e) {
663         if (editorUI == null)
664             return null;
665         int line = getLineFromMouseEvent(e);
666
667         StringBuffer JavaDoc annotation = new StringBuffer JavaDoc();
668         if (elementAnnotations != null) {
669             AnnotateLine al = getAnnotateLine(line);
670
671             if (al != null) {
672                 String JavaDoc escapedAuthor = NbBundle.getMessage(AnnotationBar.class, "TT_Annotation"); // NOI18N
673
try {
674                     escapedAuthor = XMLUtil.toElementContent(al.getAuthor());
675                 } catch (CharConversionException e1) {
676                     ErrorManager err = ErrorManager.getDefault();
677                     err.annotate(e1, "CVS.AB: can not HTML escape: " + al.getAuthor()); // NOI18N
678
err.notify(ErrorManager.INFORMATIONAL, e1);
679                 }
680
681                 // always return unique string to avoid tooltip sharing on mouse move over same revisions -->
682
annotation.append("<html><!-- line=" + line++ + " -->" + al.getRevision() + " - <b>" + escapedAuthor + "</b>"); // NOI18N
683
if (al.getDate() != null) {
684                     annotation.append(" " + DateFormat.getDateInstance().format(al.getDate())); // NOI18N
685
}
686                 if (al.getCommitMessage() != null) {
687                     String JavaDoc escaped = null;
688                     try {
689                         escaped = XMLUtil.toElementContent(al.getCommitMessage());
690                     } catch (CharConversionException e1) {
691                         ErrorManager err = ErrorManager.getDefault();
692                         err.annotate(e1, "CVS.AB: can not HTML escape: " + al.getCommitMessage()); // NOI18N
693
err.notify(ErrorManager.INFORMATIONAL, e1);
694                     }
695                     if (escaped != null) {
696                         String JavaDoc lined = escaped.replaceAll(System.getProperty("line.separator"), "<br>"); // NOI18N
697
annotation.append("<p>" + lined); // NOI18N
698
}
699                 }
700             }
701         } else {
702             annotation.append(elementAnnotationsSubstitute);
703         }
704
705         return annotation.toString();
706     }
707
708     /**
709      * Locates AnnotateLine associated with given line. The
710      * line is translated to Element that is used as map lookup key.
711      * The map is initially filled up with Elements sampled on
712      * annotate() method.
713      *
714      * <p>Key trick is that Element's identity is maintained
715      * until line removal (and is restored on undo).
716      *
717      * @param line
718      * @return found AnnotateLine or <code>null</code>
719      */

720     private AnnotateLine getAnnotateLine(int line) {
721         StyledDocument sd = (StyledDocument) doc;
722         int lineOffset = NbDocument.findLineOffset(sd, line);
723         Element element = sd.getParagraphElement(lineOffset);
724         AnnotateLine al = elementAnnotations.get(element);
725
726         if (al != null) {
727             int startOffset = element.getStartOffset();
728             int endOffset = element.getEndOffset();
729             try {
730                 int len = endOffset - startOffset;
731                 String JavaDoc text = doc.getText(startOffset, len -1);
732                 String JavaDoc content = al.getContent();
733                 if (text.equals(content)) {
734                     return al;
735                 }
736             } catch (BadLocationException e) {
737                 ErrorManager err = ErrorManager.getDefault();
738                 err.annotate(e, "CVS.AB: can not locate line annotation."); // NOI18N
739
err.notify(ErrorManager.INFORMATIONAL, e);
740             }
741         }
742
743         return null;
744     }
745
746     /**
747      * GlyphGutter copy pasted bolerplate method.
748      * It invokes {@link #paintView} that contains
749      * actual business logic.
750      */

751     public void paintComponent(Graphics g) {
752         super.paintComponent(g);
753
754         Rectangle clip = g.getClipBounds();
755
756         JTextComponent component = editorUI.getComponent();
757         if (component == null) return;
758
759         BaseTextUI textUI = (BaseTextUI)component.getUI();
760         View rootView = Utilities.getDocumentView(component);
761         if (rootView == null) return;
762
763         g.setColor(backgroundColor());
764         g.fillRect(clip.x, clip.y, clip.width, clip.height);
765
766         AbstractDocument doc = (AbstractDocument)component.getDocument();
767         doc.readLock();
768         try{
769             foldHierarchy.lock();
770             try{
771                 int startPos = textUI.getPosFromY(clip.y);
772                 int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward);
773                 int rootViewCount = rootView.getViewCount();
774
775                 if (startViewIndex >= 0 && startViewIndex < rootViewCount) {
776                     // find the nearest visible line with an annotation
777
Rectangle rec = textUI.modelToView(component, rootView.getView(startViewIndex).getStartOffset());
778                     int y = (rec == null) ? 0 : rec.y;
779
780                     int clipEndY = clip.y + clip.height;
781                     for (int i = startViewIndex; i < rootViewCount; i++){
782                         View view = rootView.getView(i);
783                         paintView(view, g, y);
784                         y += editorUI.getLineHeight();
785                         if (y >= clipEndY) {
786                             break;
787                         }
788                     }
789                 }
790
791             } finally {
792                 foldHierarchy.unlock();
793             }
794         } catch (BadLocationException ble){
795             ErrorManager.getDefault().notify(ble);
796         } finally {
797             doc.readUnlock();
798         }
799     }
800
801     private Color backgroundColor() {
802         if (textComponent != null) {
803             return textComponent.getBackground();
804         }
805         return backgroundColor;
806     }
807
808     private Color foregroundColor() {
809         if (textComponent != null) {
810             return textComponent.getForeground();
811         }
812         return foregroundColor;
813     }
814
815     private Color selectedColor() {
816         if (backgroundColor == backgroundColor()) {
817             return selectedColor;
818         }
819         if (textComponent != null) {
820             return textComponent.getForeground();
821         }
822         return selectedColor;
823
824     }
825
826
827     /** GlyphGutter copy pasted utility method. */
828     private int getLineFromMouseEvent(MouseEvent e){
829         int line = -1;
830         if (editorUI != null) {
831             try{
832                 JTextComponent component = editorUI.getComponent();
833                 BaseTextUI textUI = (BaseTextUI)component.getUI();
834                 int clickOffset = textUI.viewToModel(component, new Point(0, e.getY()));
835                 line = Utilities.getLineOffset(doc, clickOffset);
836             }catch (BadLocationException ble){
837             }
838         }
839         return line;
840     }
841
842     /** Implementation */
843     public void propertyChange(PropertyChangeEvent evt) {
844         if (evt == null) return;
845         String JavaDoc id = evt.getPropertyName();
846         if (EditorUI.COMPONENT_PROPERTY.equals(id)) { // NOI18N
847
if (evt.getNewValue() == null){
848                 // component deinstalled, lets uninstall all isteners
849
release();
850             }
851         }
852
853     }
854
855     /** Implementation */
856     public void changedUpdate(DocumentEvent e) {
857     }
858
859     /** Implementation */
860     public void insertUpdate(DocumentEvent e) {
861         // handle new lines, Enter hit at end of line changes
862
// the line element instance
863
// XXX Actually NB document implementation triggers this method two times
864
// - first time with one removed and two added lines
865
// - second time with two removed and two added lines
866
if (elementAnnotations != null) {
867             Element[] elements = e.getDocument().getRootElements();
868             synchronized(elementAnnotations) { // atomic change
869
for (int i = 0; i < elements.length; i++) {
870                     Element element = elements[i];
871                     DocumentEvent.ElementChange change = e.getChange(element);
872                     if (change == null) continue;
873                     Element[] removed = change.getChildrenRemoved();
874                     Element[] added = change.getChildrenAdded();
875
876                     if (removed.length == added.length) {
877                         for (int c = 0; c<removed.length; c++) {
878                             AnnotateLine recent = elementAnnotations.get(removed[c]);
879                             if (recent != null) {
880                                 elementAnnotations.remove(removed[c]);
881                                 elementAnnotations.put(added[c], recent);
882                             }
883                         }
884                     } else if (removed.length == 1 && added.length > 0) {
885                         Element key = removed[0];
886                         AnnotateLine recent = elementAnnotations.get(key);
887                         if (recent != null) {
888                             elementAnnotations.remove(key);
889                             elementAnnotations.put(added[0], recent);
890                         }
891                     }
892                 }
893             }
894         }
895         repaint();
896     }
897
898     /** Implementation */
899     public void removeUpdate(DocumentEvent e) {
900         if (e.getDocument().getLength() == 0) { // external reload
901
hideBar();
902         }
903         repaint();
904     }
905
906     /** Caret */
907     public void stateChanged(ChangeEvent e) {
908         assert e.getSource() == caret;
909         caretTimer.restart();
910     }
911
912     /** Timer */
913     public void actionPerformed(ActionEvent e) {
914         assert e.getSource() == caretTimer;
915         onCurrentLine();
916     }
917
918     /** on JTextPane */
919     public void componentHidden(ComponentEvent e) {
920     }
921
922     /** on JTextPane */
923     public void componentMoved(ComponentEvent e) {
924     }
925
926     /** on JTextPane */
927     public void componentResized(ComponentEvent e) {
928         revalidate();
929     }
930
931     /** on JTextPane */
932     public void componentShown(ComponentEvent e) {
933     }
934 }
935
Popular Tags