001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.diff.builtin.visualizer.editable;
042:
043: import org.netbeans.api.diff.Difference;
044: import org.netbeans.editor.*;
045: import org.netbeans.editor.Utilities;
046: import org.netbeans.editor.BaseDocument;
047: import org.netbeans.spi.editor.highlighting.HighlightsContainer;
048: import org.netbeans.spi.diff.DiffProvider;
049: import org.openide.util.Lookup;
050:
051: import javax.swing.event.ChangeListener;
052: import javax.swing.event.ChangeEvent;
053: import javax.swing.*;
054: import javax.swing.text.*;
055: import java.awt.Dimension;
056: import java.util.*;
057: import java.util.logging.Logger;
058: import java.util.logging.Level;
059: import java.io.*;
060:
061: /**
062: * Handles interaction among Diff components: editor panes, scroll bars, action bars and the split pane.
063: *
064: * @author Maros Sandor
065: */
066: class DiffViewManager implements ChangeListener {
067:
068: private final EditableDiffView master;
069:
070: private final DiffContentPanel leftContentPanel;
071: private final DiffContentPanel rightContentPanel;
072:
073: /**
074: * True when this class caused the current scroll event, false otherwise.
075: */
076: private boolean myScrollEvent;
077:
078: private int cachedDiffSerial;
079: private DecoratedDifference[] decorationsCached = new DecoratedDifference[0];
080: private HighLight[] secondHilitesCached = new HighLight[0];
081: private HighLight[] firstHilitesCached = new HighLight[0];
082: private final ScrollMapCached scrollMap = new ScrollMapCached();
083:
084: public DiffViewManager(EditableDiffView master) {
085: this .master = master;
086: this .leftContentPanel = master.getEditorPane1();
087: this .rightContentPanel = master.getEditorPane2();
088: }
089:
090: void init() {
091: initScrolling();
092: }
093:
094: private void initScrolling() {
095: leftContentPanel.getScrollPane().getVerticalScrollBar()
096: .getModel().addChangeListener(this );
097: rightContentPanel.getScrollPane().getVerticalScrollBar()
098: .getModel().addChangeListener(this );
099: // The vertical scroll bar must be there for mouse wheel to work correctly.
100: // However it's not necessary to be seen (but must be visible so that the wheel will work).
101: leftContentPanel.getScrollPane().getVerticalScrollBar()
102: .setPreferredSize(new Dimension(0, 0));
103: }
104:
105: private final Boolean[] smartScrollDisabled = new Boolean[] { Boolean.TRUE };
106:
107: public void runWithSmartScrollingDisabled(Runnable runnable) {
108: synchronized (smartScrollDisabled) {
109: smartScrollDisabled[0] = true;
110: }
111: try {
112: runnable.run();
113: } catch (Exception e) {
114: Logger.getLogger(DiffViewManager.class.getName()).log(
115: Level.SEVERE, "", e);
116: } finally {
117: SwingUtilities.invokeLater(new Runnable() {
118: public void run() {
119: synchronized (smartScrollDisabled) {
120: smartScrollDisabled[0] = false;
121: }
122: }
123: });
124: }
125: }
126:
127: public void stateChanged(ChangeEvent e) {
128: JScrollBar leftScrollBar = leftContentPanel.getScrollPane()
129: .getVerticalScrollBar();
130: JScrollBar rightScrollBar = rightContentPanel.getScrollPane()
131: .getVerticalScrollBar();
132: if (e.getSource() == leftContentPanel.getScrollPane()
133: .getVerticalScrollBar().getModel()) {
134: int value = leftScrollBar.getValue();
135: leftContentPanel.getActionsScrollPane()
136: .getVerticalScrollBar().setValue(value);
137: if (myScrollEvent)
138: return;
139: myScrollEvent = true;
140: } else {
141: int value = rightScrollBar.getValue();
142: rightContentPanel.getActionsScrollPane()
143: .getVerticalScrollBar().setValue(value);
144: if (myScrollEvent)
145: return;
146: myScrollEvent = true;
147: boolean doSmartScroll;
148: synchronized (smartScrollDisabled) {
149: doSmartScroll = !smartScrollDisabled[0];
150: }
151: if (doSmartScroll) {
152: smartScroll();
153: master.updateCurrentDifference();
154: }
155: }
156: master.getMyDivider().repaint();
157: myScrollEvent = false;
158: }
159:
160: public void scroll() {
161: myScrollEvent = true;
162: smartScroll();
163: master.getMyDivider().repaint();
164: myScrollEvent = false;
165: }
166:
167: EditableDiffView getMaster() {
168: return master;
169: }
170:
171: private void updateDifferences() {
172: assert Thread.holdsLock(this );
173: int mds = master.getDiffSerial();
174: if (mds <= cachedDiffSerial)
175: return;
176: cachedDiffSerial = mds;
177: computeDecorations();
178: computeSecondHighlights();
179: computeFirstHighlights();
180: }
181:
182: public synchronized DecoratedDifference[] getDecorations() {
183: updateDifferences();
184: return decorationsCached;
185: }
186:
187: public synchronized HighLight[] getSecondHighlights() {
188: updateDifferences();
189: return secondHilitesCached;
190: }
191:
192: public synchronized HighLight[] getFirstHighlights() {
193: updateDifferences();
194: return firstHilitesCached;
195: }
196:
197: private void computeFirstHighlights() {
198: List<HighLight> hilites = new ArrayList<HighLight>();
199: Document doc = leftContentPanel.getEditorPane().getDocument();
200: for (DecoratedDifference dd : decorationsCached) {
201: Difference diff = dd.getDiff();
202: if (dd.getBottomLeft() == -1)
203: continue;
204: int start = getRowStartFromLineOffset(doc, diff
205: .getFirstStart() - 1);
206: if (isOneLineChange(diff)) {
207: CorrectRowTokenizer firstSt = new CorrectRowTokenizer(
208: diff.getFirstText());
209: CorrectRowTokenizer secondSt = new CorrectRowTokenizer(
210: diff.getSecondText());
211: for (int i = diff.getSecondStart(); i <= diff
212: .getSecondEnd(); i++) {
213: String firstRow = firstSt.nextToken();
214: String secondRow = secondSt.nextToken();
215: List<HighLight> rowhilites = computeFirstRowHilites(
216: start, firstRow, secondRow);
217: hilites.addAll(rowhilites);
218: start += firstRow.length() + 1;
219: }
220: } else {
221: int end = getRowStartFromLineOffset(doc, diff
222: .getFirstEnd());
223: if (end == -1) {
224: end = doc.getLength();
225: }
226: SimpleAttributeSet attrs = new SimpleAttributeSet();
227: StyleConstants.setBackground(attrs, master
228: .getColor(diff));
229: attrs.addAttribute(
230: HighlightsContainer.ATTR_EXTENDS_EOL,
231: Boolean.TRUE);
232: hilites.add(new HighLight(start, end, attrs));
233: }
234: }
235: firstHilitesCached = hilites.toArray(new HighLight[hilites
236: .size()]);
237: }
238:
239: static int getRowStartFromLineOffset(Document doc, int lineIndex) {
240: if (doc instanceof BaseDocument) {
241: return Utilities.getRowStartFromLineOffset(
242: (BaseDocument) doc, lineIndex);
243: } else {
244: // TODO: find row start from line offet
245: Element element = doc.getDefaultRootElement();
246: Element line = element.getElement(lineIndex);
247: return line.getStartOffset();
248: }
249: }
250:
251: private void computeSecondHighlights() {
252: List<HighLight> hilites = new ArrayList<HighLight>();
253: Document doc = rightContentPanel.getEditorPane().getDocument();
254: for (DecoratedDifference dd : decorationsCached) {
255: Difference diff = dd.getDiff();
256: if (dd.getBottomRight() == -1)
257: continue;
258: int start = getRowStartFromLineOffset(doc, diff
259: .getSecondStart() - 1);
260: if (isOneLineChange(diff)) {
261: CorrectRowTokenizer firstSt = new CorrectRowTokenizer(
262: diff.getFirstText());
263: CorrectRowTokenizer secondSt = new CorrectRowTokenizer(
264: diff.getSecondText());
265: for (int i = diff.getSecondStart(); i <= diff
266: .getSecondEnd(); i++) {
267: try {
268: String firstRow = firstSt.nextToken();
269: String secondRow = secondSt.nextToken();
270: List<HighLight> rowhilites = computeSecondRowHilites(
271: start, firstRow, secondRow);
272: hilites.addAll(rowhilites);
273: start += secondRow.length() + 1;
274: } catch (Exception e) {
275: e.printStackTrace();
276: }
277: }
278: } else {
279: int end = getRowStartFromLineOffset(doc, diff
280: .getSecondEnd());
281: if (end == -1) {
282: end = doc.getLength();
283: }
284: SimpleAttributeSet attrs = new SimpleAttributeSet();
285: StyleConstants.setBackground(attrs, master
286: .getColor(diff));
287: attrs.addAttribute(
288: HighlightsContainer.ATTR_EXTENDS_EOL,
289: Boolean.TRUE);
290: hilites.add(new HighLight(start, end, attrs));
291: }
292: }
293: secondHilitesCached = hilites.toArray(new HighLight[hilites
294: .size()]);
295: }
296:
297: private List<HighLight> computeFirstRowHilites(int rowStart,
298: String left, String right) {
299: List<HighLight> hilites = new ArrayList<HighLight>(4);
300:
301: String leftRows = wordsToRows(left);
302: String rightRows = wordsToRows(right);
303:
304: DiffProvider diffprovider = Lookup.getDefault().lookup(
305: DiffProvider.class);
306: if (diffprovider == null) {
307: return hilites;
308: }
309:
310: Difference[] diffs;
311: try {
312: diffs = diffprovider.computeDiff(
313: new StringReader(leftRows), new StringReader(
314: rightRows));
315: } catch (IOException e) {
316: return hilites;
317: }
318:
319: // what we can hilite in first source
320: for (Difference diff : diffs) {
321: if (diff.getType() == Difference.ADD)
322: continue;
323: int start = rowOffset(leftRows, diff.getFirstStart());
324: int end = rowOffset(leftRows, diff.getFirstEnd() + 1);
325:
326: SimpleAttributeSet attrs = new SimpleAttributeSet();
327: StyleConstants.setBackground(attrs, master.getColor(diff));
328: hilites.add(new HighLight(rowStart + start, rowStart + end,
329: attrs));
330: }
331: return hilites;
332: }
333:
334: private List<HighLight> computeSecondRowHilites(int rowStart,
335: String left, String right) {
336: List<HighLight> hilites = new ArrayList<HighLight>(4);
337:
338: String leftRows = wordsToRows(left);
339: String rightRows = wordsToRows(right);
340:
341: DiffProvider diffprovider = Lookup.getDefault().lookup(
342: DiffProvider.class);
343: if (diffprovider == null) {
344: return hilites;
345: }
346:
347: Difference[] diffs;
348: try {
349: diffs = diffprovider.computeDiff(
350: new StringReader(leftRows), new StringReader(
351: rightRows));
352: } catch (IOException e) {
353: return hilites;
354: }
355:
356: // what we can hilite in second source
357: for (Difference diff : diffs) {
358: if (diff.getType() == Difference.DELETE)
359: continue;
360: int start = rowOffset(rightRows, diff.getSecondStart());
361: int end = rowOffset(rightRows, diff.getSecondEnd() + 1);
362:
363: SimpleAttributeSet attrs = new SimpleAttributeSet();
364: StyleConstants.setBackground(attrs, master.getColor(diff));
365: hilites.add(new HighLight(rowStart + start, rowStart + end,
366: attrs));
367: }
368: return hilites;
369: }
370:
371: /**
372: * 1-based row index.
373: *
374: * @param row
375: * @param rowIndex
376: * @return
377: */
378: private int rowOffset(String row, int rowIndex) {
379: if (rowIndex == 1)
380: return 0;
381: int newLines = 0;
382: for (int i = 0; i < row.length(); i++) {
383: char c = row.charAt(i);
384: if (c == '\n') {
385: newLines++;
386: if (--rowIndex == 1) {
387: return i + 1 - newLines;
388: }
389: }
390: }
391: return row.length();
392: }
393:
394: private String wordsToRows(String s) {
395: StringBuilder sb = new StringBuilder(s.length() * 2);
396: StringTokenizer st = new StringTokenizer(s,
397: " \t\n[]{};:'\",.<>/?-=_+\\|~!@#$%^&*()", true); // NOI18N
398: while (st.hasMoreTokens()) {
399: String token = st.nextToken();
400: if (token.length() == 0)
401: continue;
402: sb.append(token);
403: sb.append('\n');
404: }
405: return sb.toString();
406: }
407:
408: private boolean isOneLineChange(Difference diff) {
409: return diff.getType() == Difference.CHANGE
410: && diff.getFirstEnd() - diff.getFirstStart() == diff
411: .getSecondEnd()
412: - diff.getSecondStart();
413: }
414:
415: private void computeDecorations() {
416:
417: Document document = master.getEditorPane2().getEditorPane()
418: .getDocument();
419: EditorUI editorUI = org.netbeans.editor.Utilities
420: .getEditorUI(rightContentPanel.getEditorPane());
421: if (editorUI == null)
422: return;
423: int lineHeight = editorUI.getLineHeight();
424:
425: Difference[] diffs = master.getDifferences();
426: decorationsCached = new DecoratedDifference[diffs.length];
427: for (int i = 0; i < diffs.length; i++) {
428: Difference difference = diffs[i];
429: DecoratedDifference dd = new DecoratedDifference(
430: difference, canRollback(document, difference));
431:
432: if (difference.getType() == Difference.ADD) {
433: dd.topRight = (difference.getSecondStart() - 1)
434: * lineHeight;
435: dd.bottomRight = difference.getSecondEnd() * lineHeight;
436: dd.topLeft = difference.getFirstStart() * lineHeight;
437: dd.floodFill = true;
438: } else if (difference.getType() == Difference.DELETE) {
439: dd.topLeft = (difference.getFirstStart() - 1)
440: * lineHeight;
441: dd.bottomLeft = difference.getFirstEnd() * lineHeight;
442: dd.topRight = difference.getSecondStart() * lineHeight;
443: dd.floodFill = true;
444: } else {
445: dd.topRight = (difference.getSecondStart() - 1)
446: * lineHeight;
447: dd.bottomRight = difference.getSecondEnd() * lineHeight;
448: dd.topLeft = (difference.getFirstStart() - 1)
449: * lineHeight;
450: dd.bottomLeft = difference.getFirstEnd() * lineHeight;
451: dd.floodFill = true;
452: }
453: decorationsCached[i] = dd;
454: }
455: }
456:
457: private boolean canRollback(Document doc, Difference diff) {
458: if (!(doc instanceof GuardedDocument))
459: return true;
460: GuardedDocument document = (GuardedDocument) doc;
461: int start, end;
462: if (diff.getType() == Difference.DELETE) {
463: start = end = Utilities.getRowStartFromLineOffset(document,
464: diff.getSecondStart());
465: } else {
466: start = Utilities.getRowStartFromLineOffset(document, diff
467: .getSecondStart() - 1);
468: end = Utilities.getRowStartFromLineOffset(document, diff
469: .getSecondEnd());
470: }
471: MarkBlockChain mbc = ((GuardedDocument) document)
472: .getGuardedBlockChain();
473: return (mbc.compareBlock(start, end) & MarkBlock.OVERLAP) == 0;
474: }
475:
476: /**
477: * 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
478: * 2. find line offset of the found difference in the other document
479: * 3. scroll the other document so that the difference starts on the same visual line
480: *
481: * 5. scroll the other document proportionally
482: */
483: private void smartScroll() {
484: DiffContentPanel rightPane = master.getEditorPane2();
485: DiffContentPanel leftPane = master.getEditorPane1();
486:
487: int[] map = scrollMap.getScrollMap(rightPane.getEditorPane()
488: .getSize().height, master.getDiffSerial());
489:
490: int rightOffet = rightPane.getScrollPane()
491: .getVerticalScrollBar().getValue();
492: if (rightOffet >= map.length)
493: return;
494: leftPane.getScrollPane().getVerticalScrollBar().setValue(
495: map[rightOffet]);
496: }
497:
498: private int computeLeftOffsetToMatchDifference(
499: DifferencePosition differenceMatchStart, int lineHeight,
500: int rightOffset) {
501:
502: Difference diff = differenceMatchStart.getDiff();
503: boolean matchStart = differenceMatchStart.isStart();
504:
505: int value;
506: int valueSecond;
507: if (matchStart) {
508: value = diff.getFirstStart() * lineHeight; // kde zacina prva, 180
509: valueSecond = diff.getSecondStart() * lineHeight; // kde by zacinala druha, napr. 230
510: } else {
511: if (diff.getType() == Difference.ADD) {
512: value = diff.getFirstStart() * lineHeight; // kde zacina prva, 180
513: value -= lineHeight;
514: valueSecond = diff.getSecondEnd() * lineHeight; // kde by zacinala druha, napr. 230
515: } else {
516: value = diff.getFirstEnd() * lineHeight; // kde zacina prva, 180
517: if (diff.getType() == Difference.DELETE) {
518: value += lineHeight;
519: valueSecond = diff.getSecondStart() * lineHeight; // kde by zacinala druha, napr. 230
520: } else {
521: valueSecond = diff.getSecondEnd() * lineHeight; // kde by zacinala druha, napr. 230
522: }
523: }
524: }
525:
526: // druha je na 400
527: int secondOffset = rightOffset - valueSecond;
528:
529: value += secondOffset;
530: if (diff.getType() == Difference.ADD)
531: value += lineHeight;
532: if (diff.getType() == Difference.DELETE)
533: value -= lineHeight;
534:
535: return value;
536: }
537:
538: private DifferencePosition findDifferenceToMatch(int rightOffset,
539: int rightViewportHeight) {
540:
541: DecoratedDifference candidate = null;
542:
543: DecoratedDifference[] diffs = getDecorations();
544: for (DecoratedDifference dd : diffs) {
545: if (dd.getTopRight() > rightOffset + rightViewportHeight)
546: break;
547: if (dd.getBottomRight() != -1) {
548: if (dd.getBottomRight() <= rightOffset)
549: continue;
550: } else {
551: if (dd.getTopRight() <= rightOffset)
552: continue;
553: }
554: if (candidate != null) {
555: if (candidate.getDiff().getType() == Difference.DELETE) {
556: candidate = dd;
557: } else if (candidate.getTopRight() < rightOffset) {
558: candidate = dd;
559: } else if (dd.getTopRight() <= rightOffset
560: + rightViewportHeight / 2) {
561: candidate = dd;
562: }
563: } else {
564: candidate = dd;
565: }
566: }
567: if (candidate == null)
568: return null;
569: boolean matchStart = candidate.getTopRight() > rightOffset
570: + rightViewportHeight / 2;
571: if (candidate.getDiff().getType() == Difference.DELETE
572: && candidate.getTopRight() < rightOffset
573: + rightViewportHeight * 4 / 5)
574: matchStart = false;
575: if (candidate.getDiff().getType() == Difference.DELETE
576: && candidate == diffs[diffs.length - 1])
577: matchStart = false;
578: return new DifferencePosition(candidate.getDiff(), matchStart);
579: }
580:
581: double getScrollFactor() {
582: BoundedRangeModel m1 = leftContentPanel.getScrollPane()
583: .getVerticalScrollBar().getModel();
584: BoundedRangeModel m2 = rightContentPanel.getScrollPane()
585: .getVerticalScrollBar().getModel();
586: return ((double) m1.getMaximum() - m1.getExtent())
587: / (m2.getMaximum() - m2.getExtent());
588: }
589:
590: /**
591: * The split pane needs to be repainted along with editor.
592: *
593: * @param decoratedEditorPane the pane that is currently repainting
594: */
595: void editorPainting(DecoratedEditorPane decoratedEditorPane) {
596: if (!decoratedEditorPane.isFirst()) {
597: JComponent mydivider = master.getMyDivider();
598: mydivider.paint(mydivider.getGraphics());
599: }
600: }
601:
602: public static class DifferencePosition {
603:
604: private Difference diff;
605: private boolean isStart;
606:
607: public DifferencePosition(Difference diff, boolean start) {
608: this .diff = diff;
609: isStart = start;
610: }
611:
612: public Difference getDiff() {
613: return diff;
614: }
615:
616: public boolean isStart() {
617: return isStart;
618: }
619: }
620:
621: public static class DecoratedDifference {
622: private final Difference diff;
623: private final boolean canRollback;
624: private int topLeft; // top line in the left pane
625: private int bottomLeft = -1; // bottom line in the left pane, -1 for ADDs
626: private int topRight;
627: private int bottomRight = -1; // bottom line in the right pane, -1 for DELETEs
628: private boolean floodFill; // should the whole difference be highlited
629:
630: public DecoratedDifference(Difference difference,
631: boolean canRollback) {
632: diff = difference;
633: this .canRollback = canRollback;
634: }
635:
636: public boolean canRollback() {
637: return canRollback;
638: }
639:
640: public Difference getDiff() {
641: return diff;
642: }
643:
644: public int getTopLeft() {
645: return topLeft;
646: }
647:
648: public int getBottomLeft() {
649: return bottomLeft;
650: }
651:
652: public int getTopRight() {
653: return topRight;
654: }
655:
656: public int getBottomRight() {
657: return bottomRight;
658: }
659:
660: public boolean isFloodFill() {
661: return floodFill;
662: }
663: }
664:
665: public static class HighLight {
666:
667: private final int startOffset;
668: private final int endOffset;
669: private final AttributeSet attrs;
670:
671: public HighLight(int startOffset, int endOffset,
672: AttributeSet attrs) {
673: this .startOffset = startOffset;
674: this .endOffset = endOffset;
675: this .attrs = attrs;
676: }
677:
678: public int getStartOffset() {
679: return startOffset;
680: }
681:
682: public int getEndOffset() {
683: return endOffset;
684: }
685:
686: public AttributeSet getAttrs() {
687: return attrs;
688: }
689: }
690:
691: /**
692: * Java StringTokenizer does not work if the very first character is a delimiter.
693: */
694: private static class CorrectRowTokenizer {
695:
696: private final String s;
697: private int idx;
698:
699: public CorrectRowTokenizer(String s) {
700: this .s = s;
701: }
702:
703: public String nextToken() {
704: String token = null;
705: for (int end = idx; end < s.length(); end++) {
706: if (s.charAt(end) == '\n') {
707: token = s.substring(idx, end);
708: idx = end + 1;
709: break;
710: }
711: }
712: return token;
713: }
714: }
715:
716: private class ScrollMapCached {
717:
718: private int rightPanelHeightCached;
719: private int[] scrollMapCached;
720: private int diffSerialCached;
721:
722: public synchronized int[] getScrollMap(int rightPanelHeight,
723: int diffSerial) {
724: if (rightPanelHeight != rightPanelHeightCached
725: || diffSerialCached != diffSerial
726: || scrollMapCached == null) {
727: diffSerialCached = diffSerial;
728: rightPanelHeightCached = rightPanelHeight;
729: scrollMapCached = compute();
730: }
731: return scrollMapCached;
732: }
733:
734: private int[] compute() {
735: DiffContentPanel rightPane = master.getEditorPane2();
736:
737: int rightViewportHeight = rightPane.getScrollPane()
738: .getViewport().getViewRect().height;
739:
740: int[] scrollMap = new int[rightPanelHeightCached];
741:
742: EditorUI editorUI = org.netbeans.editor.Utilities
743: .getEditorUI(leftContentPanel.getEditorPane());
744: if (editorUI == null)
745: return scrollMap;
746: int lineHeight = editorUI.getLineHeight();
747:
748: int lastOffset = 0;
749: for (int rightOffset = 0; rightOffset < rightPanelHeightCached; rightOffset++) {
750: DifferencePosition dpos = findDifferenceToMatch(
751: rightOffset, rightViewportHeight);
752: int leftOffset;
753: if (dpos == null) {
754: leftOffset = lastOffset + rightOffset;
755: } else {
756: leftOffset = computeLeftOffsetToMatchDifference(
757: dpos, lineHeight, rightOffset);
758: lastOffset = leftOffset - rightOffset;
759: }
760: scrollMap[rightOffset] = leftOffset;
761: }
762: scrollMap = smooth(scrollMap);
763: return scrollMap;
764: }
765:
766: private int[] smooth(int[] map) {
767: int[] newMap = new int[map.length];
768: int leftShift = 0;
769: float correction = 0.0f;
770: for (int i = 0; i < map.length; i++) {
771: int leftOffset = map[i];
772: int requestedShift = leftOffset - i;
773: if (requestedShift > leftShift) {
774: if (correction > requestedShift - leftShift)
775: correction = requestedShift - leftShift;
776: leftShift += correction;
777: correction += 0.02f;
778: } else if (requestedShift < leftShift) {
779: leftShift -= 1;
780: } else {
781: correction = 1.0f;
782: }
783: newMap[i] = i + leftShift;
784: }
785: return newMap;
786: }
787: }
788: }
|