KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > diff > builtin > visualizer > editable > DiffViewManager


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-2007 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.diff.builtin.visualizer.editable;
20
21 import org.netbeans.api.diff.Difference;
22 import org.netbeans.editor.EditorUI;
23 import org.netbeans.editor.Utilities;
24 import org.netbeans.editor.BaseDocument;
25 import org.netbeans.spi.editor.highlighting.HighlightsContainer;
26 import org.netbeans.spi.diff.DiffProvider;
27 import org.openide.util.Lookup;
28
29 import javax.swing.event.ChangeListener JavaDoc;
30 import javax.swing.event.ChangeEvent JavaDoc;
31 import javax.swing.*;
32 import javax.swing.text.*;
33 import java.awt.Dimension JavaDoc;
34 import java.util.*;
35 import java.io.*;
36
37 /**
38  * Handles interaction among Diff components: editor panes, scroll bars, action bars and the split pane.
39  *
40  * @author Maros Sandor
41  */

42 class DiffViewManager implements ChangeListener JavaDoc {
43     
44     private final EditableDiffView master;
45     
46     private final DiffContentPanel leftContentPanel;
47     private final DiffContentPanel rightContentPanel;
48
49     /**
50      * True when this class caused the current scroll event, false otherwise.
51      */

52     private boolean myScrollEvent;
53     
54     private int cachedDiffSerial;
55     private DecoratedDifference [] decorationsCached = new DecoratedDifference[0];
56     private HighLight [] secondHilitesCached = new HighLight[0];
57     private HighLight [] firstHilitesCached = new HighLight[0];
58     private final ScrollMapCached scrollMap = new ScrollMapCached();
59     
60     public DiffViewManager(EditableDiffView master) {
61         this.master = master;
62         this.leftContentPanel = master.getEditorPane1();
63         this.rightContentPanel = master.getEditorPane2();
64     }
65     
66     void init() {
67         initScrolling();
68     }
69
70     private void initScrolling() {
71         leftContentPanel.getScrollPane().getVerticalScrollBar().getModel().addChangeListener(this);
72         rightContentPanel.getScrollPane().getVerticalScrollBar().getModel().addChangeListener(this);
73         // The vertical scroll bar must be there for mouse wheel to work correctly.
74
// However it's not necessary to be seen (but must be visible so that the wheel will work).
75
leftContentPanel.getScrollPane().getVerticalScrollBar().setPreferredSize(new Dimension JavaDoc(0, 0));
76     }
77
78     public void stateChanged(ChangeEvent JavaDoc e) {
79         JScrollBar leftScrollBar = leftContentPanel.getScrollPane().getVerticalScrollBar();
80         JScrollBar rightScrollBar = rightContentPanel.getScrollPane().getVerticalScrollBar();
81         if (e.getSource() == leftContentPanel.getScrollPane().getVerticalScrollBar().getModel()) {
82             int value = leftScrollBar.getValue();
83             leftContentPanel.getActionsScrollPane().getVerticalScrollBar().setValue(value);
84             if (myScrollEvent) return;
85             myScrollEvent = true;
86             rightScrollBar.setValue((int) (value / getScrollFactor()));
87         } else {
88             int value = rightScrollBar.getValue();
89             rightContentPanel.getActionsScrollPane().getVerticalScrollBar().setValue(value);
90             if (myScrollEvent) return;
91             myScrollEvent = true;
92             smartScroll();
93         }
94         master.getMyDivider().repaint();
95         myScrollEvent = false;
96     }
97     
98     public void scroll() {
99         myScrollEvent = true;
100         smartScroll();
101         master.getMyDivider().repaint();
102         myScrollEvent = false;
103     }
104     
105     EditableDiffView getMaster() {
106         return master;
107     }
108     
109     private void updateDifferences() {
110         assert Thread.holdsLock(this);
111         int mds = master.getDiffSerial();
112         if (mds <= cachedDiffSerial) return;
113         cachedDiffSerial = mds;
114         computeDecorations();
115         computeSecondHighlights();
116         computeFirstHighlights();
117     }
118
119     public synchronized DecoratedDifference [] getDecorations() {
120         updateDifferences();
121         return decorationsCached;
122     }
123
124     public synchronized HighLight[] getSecondHighlights() {
125         updateDifferences();
126         return secondHilitesCached;
127     }
128
129     public synchronized HighLight[] getFirstHighlights() {
130         updateDifferences();
131         return firstHilitesCached;
132     }
133     
134     private void computeFirstHighlights() {
135         List<HighLight> hilites = new ArrayList<HighLight>();
136         Document doc = leftContentPanel.getEditorPane().getDocument();
137         for (DecoratedDifference dd : decorationsCached) {
138             Difference diff = dd.getDiff();
139             if (dd.getBottomLeft() == -1) continue;
140             int start = getRowStartFromLineOffset(doc, diff.getFirstStart() - 1);
141             if (isOneLineChange(diff)) {
142                 CorrectRowTokenizer firstSt = new CorrectRowTokenizer(diff.getFirstText());
143                 CorrectRowTokenizer secondSt = new CorrectRowTokenizer(diff.getSecondText());
144                 for (int i = diff.getSecondStart(); i <= diff.getSecondEnd(); i++) {
145                     String JavaDoc firstRow = firstSt.nextToken();
146                     String JavaDoc secondRow = secondSt.nextToken();
147                     List<HighLight> rowhilites = computeFirstRowHilites(start, firstRow, secondRow);
148                     hilites.addAll(rowhilites);
149                     start += firstRow.length() + 1;
150                 }
151             } else {
152                 int end = getRowStartFromLineOffset(doc, diff.getFirstEnd());
153                 if (end == -1) {
154                     end = doc.getLength();
155                 }
156                 SimpleAttributeSet attrs = new SimpleAttributeSet();
157                 StyleConstants.setBackground(attrs, master.getColor(diff));
158                 attrs.addAttribute(HighlightsContainer.ATTR_EXTENDS_EOL, Boolean.TRUE);
159                 hilites.add(new HighLight(start, end, attrs));
160             }
161         }
162         firstHilitesCached = hilites.toArray(new HighLight[hilites.size()]);
163     }
164     
165     static int getRowStartFromLineOffset(Document doc, int lineIndex) {
166         if (doc instanceof BaseDocument) {
167             return Utilities.getRowStartFromLineOffset((BaseDocument) doc, lineIndex);
168         } else {
169             // TODO: find row start from line offet
170
Element element = doc.getDefaultRootElement();
171             Element line = element.getElement(lineIndex);
172             return line.getStartOffset();
173         }
174     }
175     
176     private void computeSecondHighlights() {
177         List<HighLight> hilites = new ArrayList<HighLight>();
178         Document doc = rightContentPanel.getEditorPane().getDocument();
179         for (DecoratedDifference dd : decorationsCached) {
180             Difference diff = dd.getDiff();
181             if (dd.getBottomRight() == -1) continue;
182             int start = getRowStartFromLineOffset(doc, diff.getSecondStart() - 1);
183             if (isOneLineChange(diff)) {
184                 CorrectRowTokenizer firstSt = new CorrectRowTokenizer(diff.getFirstText());
185                 CorrectRowTokenizer secondSt = new CorrectRowTokenizer(diff.getSecondText());
186                 for (int i = diff.getSecondStart(); i <= diff.getSecondEnd(); i++) {
187                     try {
188                         String JavaDoc firstRow = firstSt.nextToken();
189                         String JavaDoc secondRow = secondSt.nextToken();
190                         List<HighLight> rowhilites = computeSecondRowHilites(start, firstRow, secondRow);
191                         hilites.addAll(rowhilites);
192                         start += secondRow.length() + 1;
193                     } catch (Exception JavaDoc e) {
194                         e.printStackTrace();
195                     }
196                 }
197             } else {
198                 int end = getRowStartFromLineOffset(doc, diff.getSecondEnd());
199                 if (end == -1) {
200                     end = doc.getLength();
201                 }
202                 SimpleAttributeSet attrs = new SimpleAttributeSet();
203                 StyleConstants.setBackground(attrs, master.getColor(diff));
204                 attrs.addAttribute(HighlightsContainer.ATTR_EXTENDS_EOL, Boolean.TRUE);
205                 hilites.add(new HighLight(start, end, attrs));
206             }
207         }
208         secondHilitesCached = hilites.toArray(new HighLight[hilites.size()]);
209     }
210
211     private List<HighLight> computeFirstRowHilites(int rowStart, String JavaDoc left, String JavaDoc right) {
212         List<HighLight> hilites = new ArrayList<HighLight>(4);
213         
214         String JavaDoc leftRows = wordsToRows(left);
215         String JavaDoc rightRows = wordsToRows(right);
216
217         DiffProvider diffprovider = Lookup.getDefault().lookup(DiffProvider.class);
218         if (diffprovider == null) {
219             return hilites;
220         }
221
222         Difference[] diffs;
223         try {
224             diffs = diffprovider.computeDiff(new StringReader(leftRows), new StringReader(rightRows));
225         } catch (IOException e) {
226             return hilites;
227         }
228
229         // what we can hilite in first source
230
for (Difference diff : diffs) {
231             if (diff.getType() == Difference.ADD) continue;
232             int start = rowOffset(leftRows, diff.getFirstStart());
233             int end = rowOffset(leftRows, diff.getFirstEnd() + 1);
234             
235             SimpleAttributeSet attrs = new SimpleAttributeSet();
236             StyleConstants.setBackground(attrs, master.getColor(diff));
237             hilites.add(new HighLight(rowStart + start, rowStart + end, attrs));
238         }
239         return hilites;
240     }
241     
242     private List<HighLight> computeSecondRowHilites(int rowStart, String JavaDoc left, String JavaDoc right) {
243         List<HighLight> hilites = new ArrayList<HighLight>(4);
244         
245         String JavaDoc leftRows = wordsToRows(left);
246         String JavaDoc rightRows = wordsToRows(right);
247
248         DiffProvider diffprovider = Lookup.getDefault().lookup(DiffProvider.class);
249         if (diffprovider == null) {
250             return hilites;
251         }
252
253         Difference[] diffs;
254         try {
255             diffs = diffprovider.computeDiff(new StringReader(leftRows), new StringReader(rightRows));
256         } catch (IOException e) {
257             return hilites;
258         }
259
260         // what we can hilite in second source
261
for (Difference diff : diffs) {
262             if (diff.getType() == Difference.DELETE) continue;
263             int start = rowOffset(rightRows, diff.getSecondStart());
264             int end = rowOffset(rightRows, diff.getSecondEnd() + 1);
265             
266             SimpleAttributeSet attrs = new SimpleAttributeSet();
267             StyleConstants.setBackground(attrs, master.getColor(diff));
268             hilites.add(new HighLight(rowStart + start, rowStart + end, attrs));
269         }
270         return hilites;
271     }
272
273     /**
274      * 1-based row index.
275      *
276      * @param row
277      * @param rowIndex
278      * @return
279      */

280     private int rowOffset(String JavaDoc row, int rowIndex) {
281         if (rowIndex == 1) return 0;
282         int newLines = 0;
283         for (int i = 0; i < row.length(); i++) {
284             char c = row.charAt(i);
285             if (c == '\n') {
286                 newLines++;
287                 if (--rowIndex == 1) {
288                     return i + 1 - newLines;
289                 }
290             }
291         }
292         return row.length();
293     }
294
295     private String JavaDoc wordsToRows(String JavaDoc s) {
296         StringBuilder JavaDoc sb = new StringBuilder JavaDoc(s.length() * 2);
297         StringTokenizer st = new StringTokenizer(s, " \t\n[]{};:'\",.<>/?-=_+\\|~!@#$%^&*()", true); // NOI18N
298
while (st.hasMoreTokens()) {
299             String JavaDoc token = st.nextToken();
300             if (token.length() == 0) continue;
301             sb.append(token);
302             sb.append('\n');
303         }
304         return sb.toString();
305     }
306
307     private boolean isOneLineChange(Difference diff) {
308         return diff.getType() == Difference.CHANGE &&
309                 diff.getFirstEnd() - diff.getFirstStart() == diff.getSecondEnd() - diff.getSecondStart();
310     }
311
312     private void computeDecorations() {
313         
314         EditorUI editorUI = org.netbeans.editor.Utilities.getEditorUI(rightContentPanel.getEditorPane());
315         int lineHeight = editorUI.getLineHeight();
316         
317         Difference [] diffs = master.getDifferences();
318         decorationsCached = new DecoratedDifference[diffs.length];
319         for (int i = 0; i < diffs.length; i++) {
320             Difference difference = diffs[i];
321             DecoratedDifference dd = new DecoratedDifference(difference);
322             
323             if (difference.getType() == Difference.ADD) {
324                 dd.topRight = (difference.getSecondStart() - 1) * lineHeight;
325                 dd.bottomRight = difference.getSecondEnd() * lineHeight;
326                 dd.topLeft = difference.getFirstStart() * lineHeight;
327                 dd.floodFill = true;
328             } else if (difference.getType() == Difference.DELETE) {
329                 dd.topLeft = (difference.getFirstStart() - 1) * lineHeight;
330                 dd.bottomLeft = difference.getFirstEnd() * lineHeight;
331                 dd.topRight = difference.getSecondStart() * lineHeight;
332                 dd.floodFill = true;
333             } else {
334                 dd.topRight = (difference.getSecondStart() - 1) * lineHeight;
335                 dd.bottomRight = difference.getSecondEnd() * lineHeight;
336                 dd.topLeft = (difference.getFirstStart() - 1) * lineHeight;
337                 dd.bottomLeft = difference.getFirstEnd() * lineHeight;
338                 dd.floodFill = true;
339             }
340             decorationsCached[i] = dd;
341         }
342     }
343
344     /**
345      * 1. find the difference whose top (first line) is closest to the center of the screen. If there is no difference on screen, proceed to #5
346      * 2. find line offset of the found difference in the other document
347      * 3. scroll the other document so that the difference starts on the same visual line
348      *
349      * 5. scroll the other document proportionally
350      */

351     private synchronized void smartScroll() {
352         DiffContentPanel rightPane = master.getEditorPane2();
353         DiffContentPanel leftPane = master.getEditorPane1();
354         
355         int [] map = scrollMap.getScrollMap(rightPane.getSize().height, master.getDiffSerial());
356         
357         int rightOffet = rightPane.getScrollPane().getVerticalScrollBar().getValue();
358         if (rightOffet >= map.length) return;
359         leftPane.getScrollPane().getVerticalScrollBar().setValue(map[rightOffet]);
360     }
361
362     private int computeLeftOffsetToMatchDifference(DifferencePosition differenceMatchStart, int lineHeight, int rightOffset) {
363
364         Difference diff = differenceMatchStart.getDiff();
365         boolean matchStart = differenceMatchStart.isStart();
366         
367         int value;
368         int valueSecond;
369         if (matchStart) {
370             value = diff.getFirstStart() * lineHeight; // kde zacina prva, 180
371
valueSecond = diff.getSecondStart() * lineHeight; // kde by zacinala druha, napr. 230
372
} else {
373             if (diff.getType() == Difference.ADD) {
374                 value = diff.getFirstStart() * lineHeight; // kde zacina prva, 180
375
value -= lineHeight;
376                 valueSecond = diff.getSecondEnd() * lineHeight; // kde by zacinala druha, napr. 230
377
} else {
378                 value = diff.getFirstEnd() * lineHeight; // kde zacina prva, 180
379
if (diff.getType() == Difference.DELETE) {
380                     value += lineHeight;
381                     valueSecond = diff.getSecondStart() * lineHeight; // kde by zacinala druha, napr. 230
382
} else {
383                     valueSecond = diff.getSecondEnd() * lineHeight; // kde by zacinala druha, napr. 230
384
}
385             }
386         }
387
388         // druha je na 400
389
int secondOffset = rightOffset - valueSecond;
390         
391         value += secondOffset;
392         if (diff.getType() == Difference.ADD) value += lineHeight;
393         if (diff.getType() == Difference.DELETE) value -= lineHeight;
394         
395         return value;
396     }
397     
398     private DifferencePosition findDifferenceToMatch(int rightOffset, int rightViewportHeight) {
399         
400         DecoratedDifference candidate = null;
401         
402         DecoratedDifference [] diffs = getDecorations();
403         for (DecoratedDifference dd : diffs) {
404             if (dd.getTopRight() > rightOffset + rightViewportHeight) break;
405             if (dd.getBottomRight() != -1) {
406                 if (dd.getBottomRight() <= rightOffset) continue;
407             } else {
408                 if (dd.getTopRight() <= rightOffset) continue;
409             }
410             if (candidate != null) {
411                 if (candidate.getDiff().getType() == Difference.DELETE) {
412                     candidate = dd;
413                 } else if (candidate.getTopRight() < rightOffset) {
414                     candidate = dd;
415                 } else if (dd.getTopRight() <= rightOffset + rightViewportHeight / 2) {
416                     candidate = dd;
417                 }
418             } else {
419                 candidate = dd;
420             }
421         }
422         if (candidate == null) return null;
423         boolean matchStart = candidate.getTopRight() > rightOffset + rightViewportHeight / 2;
424         if (candidate.getDiff().getType() == Difference.DELETE && candidate.getTopRight() < rightOffset + rightViewportHeight * 4 / 5) matchStart = false;
425         if (candidate.getDiff().getType() == Difference.DELETE && candidate == diffs[diffs.length -1]) matchStart = false;
426         return new DifferencePosition(candidate.getDiff(), matchStart);
427     }
428
429     private double getScrollFactor() {
430         BoundedRangeModel m1 = leftContentPanel.getScrollPane().getVerticalScrollBar().getModel();
431         BoundedRangeModel m2 = rightContentPanel.getScrollPane().getVerticalScrollBar().getModel();
432         return ((double) m1.getMaximum() - m1.getExtent()) / (m2.getMaximum() - m2.getExtent());
433     }
434
435
436     /**
437      * The split pane needs to be repainted along with editor.
438      *
439      * @param decoratedEditorPane the pane that is currently repainting
440      */

441     void editorPainting(DecoratedEditorPane decoratedEditorPane) {
442         if (!decoratedEditorPane.isFirst()) {
443             JComponent mydivider = master.getMyDivider();
444             mydivider.paint(mydivider.getGraphics());
445         }
446     }
447     
448     public static class DifferencePosition {
449         
450         private Difference diff;
451         private boolean isStart;
452
453         public DifferencePosition(Difference diff, boolean start) {
454             this.diff = diff;
455             isStart = start;
456         }
457
458         public Difference getDiff() {
459             return diff;
460         }
461
462         public boolean isStart() {
463             return isStart;
464         }
465     }
466
467     public static class DecoratedDifference {
468         private Difference diff;
469         private int topLeft; // top line in the left pane
470
private int bottomLeft = -1; // bottom line in the left pane, -1 for ADDs
471
private int topRight;
472         private int bottomRight = -1; // bottom line in the right pane, -1 for DELETEs
473
private boolean floodFill; // should the whole difference be highlited
474

475         public DecoratedDifference(Difference difference) {
476             diff = difference;
477         }
478
479         public Difference getDiff() {
480             return diff;
481         }
482
483         public int getTopLeft() {
484             return topLeft;
485         }
486
487         public int getBottomLeft() {
488             return bottomLeft;
489         }
490
491         public int getTopRight() {
492             return topRight;
493         }
494
495         public int getBottomRight() {
496             return bottomRight;
497         }
498
499         public boolean isFloodFill() {
500             return floodFill;
501         }
502     }
503
504     public static class HighLight {
505         
506         private final int startOffset;
507         private final int endOffset;
508         private final AttributeSet attrs;
509
510         public HighLight(int startOffset, int endOffset, AttributeSet attrs) {
511             this.startOffset = startOffset;
512             this.endOffset = endOffset;
513             this.attrs = attrs;
514         }
515
516         public int getStartOffset() {
517             return startOffset;
518         }
519
520         public int getEndOffset() {
521             return endOffset;
522         }
523
524         public AttributeSet getAttrs() {
525             return attrs;
526         }
527     }
528
529     /**
530      * Java StringTokenizer does not work if the very first character is a delimiter.
531      */

532     private static class CorrectRowTokenizer {
533         
534         private final String JavaDoc s;
535         private int idx;
536
537         public CorrectRowTokenizer(String JavaDoc s) {
538             this.s = s;
539         }
540
541         public String JavaDoc nextToken() {
542             String JavaDoc token = null;
543             for (int end = idx; end < s.length(); end++) {
544                 if (s.charAt(end) == '\n') {
545                     token = s.substring(idx, end);
546                     idx = end + 1;
547                     break;
548                 }
549             }
550             return token;
551         }
552     }
553
554     private class ScrollMapCached {
555         
556         private int rightPanelHeightCached;
557         private int [] scrollMapCached;
558         private int diffSerialCached;
559
560         public synchronized int[] getScrollMap(int rightPanelHeight, int diffSerial) {
561             if (rightPanelHeight != rightPanelHeightCached || diffSerialCached != diffSerial || scrollMapCached == null) {
562                 diffSerialCached = diffSerial;
563                 rightPanelHeightCached = rightPanelHeight;
564                 scrollMapCached = compute();
565             }
566             return scrollMapCached;
567         }
568
569         private int [] compute() {
570             DiffContentPanel rightPane = master.getEditorPane2();
571
572             int rightViewportHeight = rightPane.getScrollPane().getViewport().getViewRect().height;
573             int rightHeight = rightPane.getEditorPane().getSize().height;
574         
575             EditorUI editorUI = org.netbeans.editor.Utilities.getEditorUI(leftContentPanel.getEditorPane());
576             int lineHeight = editorUI.getLineHeight();
577         
578             int [] scrollMap = new int[rightHeight];
579             int lastOffset = 0;
580             for (int rightOffset = 0; rightOffset < rightHeight; rightOffset++) {
581                 DifferencePosition dpos = findDifferenceToMatch(rightOffset, rightViewportHeight);
582                 int leftOffset;
583                 if (dpos == null) {
584                     leftOffset = lastOffset + rightOffset;
585                 } else {
586                     leftOffset = computeLeftOffsetToMatchDifference(dpos, lineHeight, rightOffset);
587                     lastOffset = leftOffset - rightOffset;
588                 }
589                 scrollMap[rightOffset] = leftOffset;
590             }
591             scrollMap = smooth(scrollMap);
592             return scrollMap;
593         }
594
595         private int[] smooth(int[] map) {
596             int [] newMap = new int [map.length];
597             int leftShift = 0;
598             float correction = 0.0f;
599             for (int i = 0; i < map.length; i++) {
600                 int leftOffset = map[i];
601                 int requestedShift = leftOffset - i;
602                 if (requestedShift > leftShift) {
603                     if (correction > requestedShift - leftShift) correction = requestedShift - leftShift;
604                     leftShift += correction;
605                     correction += 0.02f;
606                 } else if (requestedShift < leftShift) {
607                     leftShift -= 1;
608                 } else {
609                     correction = 1.0f;
610                 }
611                 newMap[i] = i + leftShift;
612             }
613             return newMap;
614         }
615     }
616 }
617
Popular Tags