0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.diff.builtin.visualizer.editable;
0043:
0044: import java.awt.Component;
0045: import java.awt.*;
0046: import java.beans.PropertyChangeListener;
0047: import java.beans.PropertyChangeEvent;
0048: import java.util.*;
0049: import java.util.List;
0050: import java.util.prefs.PreferenceChangeListener;
0051: import java.util.prefs.PreferenceChangeEvent;
0052: import java.util.logging.Logger;
0053: import java.util.logging.Level;
0054: import java.io.*;
0055: import java.lang.reflect.InvocationTargetException;
0056: import java.lang.reflect.Method;
0057: import javax.swing.*;
0058: import javax.swing.plaf.basic.BasicSplitPaneUI;
0059: import javax.swing.plaf.basic.BasicSplitPaneDivider;
0060: import javax.swing.event.DocumentListener;
0061: import javax.swing.event.DocumentEvent;
0062: import javax.swing.event.AncestorListener;
0063: import javax.swing.event.AncestorEvent;
0064: import javax.swing.text.*;
0065: import org.netbeans.api.editor.fold.FoldHierarchy;
0066: import org.netbeans.api.editor.fold.FoldUtilities;
0067: import org.netbeans.api.editor.fold.FoldHierarchyListener;
0068: import org.netbeans.api.editor.fold.FoldHierarchyEvent;
0069: import org.netbeans.modules.diff.DiffModuleConfig;
0070: import org.netbeans.modules.editor.errorstripe.privatespi.MarkProvider;
0071: import org.netbeans.modules.editor.errorstripe.privatespi.Mark;
0072:
0073: import org.openide.util.RequestProcessor;
0074: import org.openide.util.NbBundle;
0075: import org.openide.ErrorManager;
0076: import org.openide.nodes.CookieSet;
0077: import org.openide.awt.UndoRedo;
0078: import org.openide.filesystems.FileObject;
0079: import org.openide.text.CloneableEditorSupport;
0080: import org.openide.cookies.SaveCookie;
0081: import org.openide.cookies.EditorCookie;
0082: import org.openide.loaders.DataObject;
0083: import org.openide.loaders.MultiDataObject;
0084: import org.netbeans.api.diff.Difference;
0085: import org.netbeans.api.diff.StreamSource;
0086: import org.netbeans.api.diff.DiffView;
0087: import org.netbeans.api.diff.DiffController;
0088: import org.netbeans.spi.diff.DiffProvider;
0089: import org.netbeans.spi.diff.DiffControllerImpl;
0090: import org.netbeans.editor.EditorUI;
0091: import org.openide.text.NbDocument;
0092:
0093: /**
0094: * Panel that shows differences between two files. The code here was originally distributed among DiffPanel and
0095: * DiffComponent classes.
0096: *
0097: * @author Maros Sandor
0098: */
0099: public class EditableDiffView extends DiffControllerImpl implements
0100: DiffView, DocumentListener, AncestorListener,
0101: PropertyChangeListener, PreferenceChangeListener {
0102:
0103: private static final int INITIAL_DIVIDER_SIZE = 32;
0104:
0105: private Stroke boldStroke = new BasicStroke(3);
0106:
0107: // === Default Diff Colors ===========================================================
0108: private Color colorMissing;
0109: private Color colorAdded;
0110: private Color colorChanged;
0111: private Color colorLines = Color.DARK_GRAY;
0112: private Color COLOR_READONLY_BG = new Color(240, 240, 240);
0113:
0114: private final Difference[] NO_DIFFERENCES = new Difference[0];
0115:
0116: /**
0117: * Left (first) half of the Diff view, contains the editor pane, actions bar and line numbers bar.
0118: */
0119: private DiffContentPanel jEditorPane1;
0120:
0121: /**
0122: * Right (second) half of the Diff view, contains the editor pane, actions bar and line numbers bar.
0123: */
0124: private DiffContentPanel jEditorPane2;
0125:
0126: private boolean secondSourceAvailable;
0127: private boolean firstSourceAvailable;
0128: private final boolean binaryDiff;
0129:
0130: private JViewport jViewport2;
0131:
0132: final JLabel fileLabel1 = new JLabel();
0133: final JLabel fileLabel2 = new JLabel();
0134: final JPanel filePanel1 = new JPanel();
0135: final JPanel filePanel2 = new JPanel();
0136: final JSplitPane jSplitPane1 = new JSplitPane();
0137:
0138: private int diffSerial;
0139: private Difference[] diffs = NO_DIFFERENCES;
0140:
0141: private boolean ignoredUpdateEvents;
0142:
0143: private int horizontalScroll1ChangedValue = -1;
0144: private int horizontalScroll2ChangedValue = -1;
0145:
0146: private RequestProcessor.Task refreshDiffTask;
0147: private DiffViewManager manager;
0148:
0149: private boolean actionsEnabled;
0150: private DiffSplitPaneUI spui;
0151:
0152: /**
0153: * The right pane is editable IFF editableCookie is not null.
0154: */
0155: private EditorCookie.Observable editableCookie;
0156: private Document editableDocument;
0157: private UndoRedo.Manager editorUndoRedo;
0158: private EditableDiffMarkProvider diffMarkprovider;
0159:
0160: public EditableDiffView(final StreamSource ss1,
0161: final StreamSource ss2) throws IOException {
0162: refreshDiffTask = RequestProcessor.getDefault().create(
0163: new RefreshDiffTask());
0164: initColors();
0165: String title1 = ss1.getTitle();
0166: if (title1 == null)
0167: title1 = NbBundle.getMessage(EditableDiffView.class,
0168: "CTL_DiffPanel_NoTitle"); // NOI18N
0169: String title2 = ss2.getTitle();
0170: if (title2 == null)
0171: title2 = NbBundle.getMessage(EditableDiffView.class,
0172: "CTL_DiffPanel_NoTitle"); // NOI18N
0173: String mimeType1 = ss1.getMIMEType();
0174: String mimeType2 = ss2.getMIMEType();
0175: if (mimeType1 == null)
0176: mimeType1 = mimeType2;
0177: if (mimeType2 == null)
0178: mimeType2 = mimeType1;
0179: binaryDiff = mimeType1 == null || mimeType2 == null
0180: || mimeType1.equals("application/octet-stream")
0181: || mimeType2.equals("application/octet-stream");
0182:
0183: actionsEnabled = ss2.isEditable();
0184: diffMarkprovider = new EditableDiffMarkProvider();
0185:
0186: initComponents();
0187:
0188: if (!binaryDiff) {
0189: jEditorPane2.getEditorPane().putClientProperty(
0190: DiffMarkProviderCreator.MARK_PROVIDER_KEY,
0191: diffMarkprovider);
0192: }
0193: jSplitPane1.setName(org.openide.util.NbBundle.getMessage(
0194: EditableDiffView.class, "DiffComponent.title", ss1
0195: .getName(), ss2.getName())); // NOI18N
0196: spui = new DiffSplitPaneUI(jSplitPane1);
0197: jSplitPane1.setUI(spui);
0198: jSplitPane1.setResizeWeight(0.5);
0199: jSplitPane1.setDividerSize(INITIAL_DIVIDER_SIZE);
0200: jSplitPane1.putClientProperty("PersistenceType", "Never"); // NOI18N
0201: jSplitPane1.getAccessibleContext().setAccessibleName(
0202: org.openide.util.NbBundle
0203: .getMessage(EditableDiffView.class,
0204: "ACS_DiffPanelA11yName")); // NOI18N
0205: jSplitPane1.getAccessibleContext().setAccessibleDescription(
0206: org.openide.util.NbBundle
0207: .getMessage(EditableDiffView.class,
0208: "ACS_DiffPanelA11yDesc")); // NOI18N
0209:
0210: setSourceTitle(fileLabel1, title1);
0211: setSourceTitle(fileLabel2, title2);
0212:
0213: final String f1 = mimeType1;
0214: final String f2 = mimeType2;
0215: try {
0216: Runnable awtTask = new Runnable() {
0217: public void run() {
0218: Color borderColor = UIManager
0219: .getColor("scrollpane_border"); // NOI18N
0220: if (borderColor == null)
0221: borderColor = UIManager
0222: .getColor("controlShadow"); // NOI18N
0223: jSplitPane1
0224: .setBorder(BorderFactory.createMatteBorder(
0225: 1, 0, 0, 0, borderColor));
0226:
0227: if (binaryDiff) {
0228: adjustPreferredSizes();
0229: return;
0230: }
0231:
0232: jEditorPane1.getScrollPane().setBorder(null);
0233: jEditorPane2.getScrollPane().setBorder(null);
0234: jEditorPane1
0235: .setBorder(BorderFactory.createMatteBorder(
0236: 1, 0, 0, 0, borderColor));
0237: jEditorPane2
0238: .setBorder(BorderFactory.createMatteBorder(
0239: 1, 0, 0, 0, borderColor));
0240:
0241: jEditorPane1.getEditorPane().setEditorKit(
0242: CloneableEditorSupport.getEditorKit(f1));
0243: jEditorPane2.getEditorPane().setEditorKit(
0244: CloneableEditorSupport.getEditorKit(f2));
0245:
0246: try {
0247: setSource1(ss1);
0248: setSource2(ss2);
0249: } catch (IOException ioex) {
0250: org.openide.ErrorManager.getDefault().notify(
0251: ioex);
0252: }
0253:
0254: if (!secondSourceAvailable) {
0255: filePanel2.remove(jEditorPane2);
0256: NoContentPanel ncp = new NoContentPanel(
0257: NbBundle.getMessage(
0258: EditableDiffView.class,
0259: "CTL_DiffPanel_NoContent")); // NOI18N
0260: ncp.setPreferredSize(new Dimension(jEditorPane1
0261: .getPreferredSize().width, ncp
0262: .getPreferredSize().height));
0263: filePanel2.add(ncp);
0264: actionsEnabled = false;
0265: }
0266: if (!firstSourceAvailable) {
0267: filePanel1.remove(jEditorPane1);
0268: NoContentPanel ncp = new NoContentPanel(
0269: NbBundle.getMessage(
0270: EditableDiffView.class,
0271: "CTL_DiffPanel_NoContent")); // NOI18N
0272: ncp.setPreferredSize(new Dimension(jEditorPane2
0273: .getPreferredSize().width, ncp
0274: .getPreferredSize().height));
0275: filePanel1.add(ncp);
0276: actionsEnabled = false;
0277: }
0278: adjustPreferredSizes();
0279:
0280: int bgRGB = jEditorPane2.getEditorPane()
0281: .getBackground().getRGB() & 0xFFFFFF;
0282: if (jEditorPane2.getEditorPane().isEditable()
0283: && bgRGB == 0xFFFFFF
0284: && System
0285: .getProperty("netbeans.experimental.diff.ReadonlyBg") == null) {
0286: jEditorPane1.getEditorPane().setBackground(
0287: COLOR_READONLY_BG);
0288: }
0289: if (bgRGB == 0) {
0290: colorLines = Color.WHITE;
0291: }
0292: }
0293: };
0294: if (SwingUtilities.isEventDispatchThread()) {
0295: awtTask.run();
0296: } else {
0297: SwingUtilities.invokeAndWait(awtTask);
0298: }
0299: } catch (InterruptedException e) {
0300: ErrorManager err = ErrorManager.getDefault();
0301: err.notify(e);
0302: } catch (InvocationTargetException e) {
0303: ErrorManager err = ErrorManager.getDefault();
0304: err.notify(e);
0305: }
0306:
0307: if (binaryDiff) {
0308: return;
0309: }
0310:
0311: jSplitPane1.addAncestorListener(this );
0312:
0313: refreshDiffTask.run();
0314:
0315: manager = new DiffViewManager(this );
0316: manager.init();
0317: }
0318:
0319: private void adjustPreferredSizes() {
0320: // Make sure split pane opens with divider in the center
0321: Dimension pf1 = fileLabel1.getPreferredSize();
0322: Dimension pf2 = fileLabel2.getPreferredSize();
0323: if (pf1.width > pf2.width) {
0324: fileLabel2.setPreferredSize(new Dimension(pf1.width,
0325: pf2.height));
0326: } else {
0327: fileLabel1.setPreferredSize(new Dimension(pf2.width,
0328: pf1.height));
0329: }
0330: }
0331:
0332: public void setLocation(final DiffController.DiffPane pane,
0333: final DiffController.LocationType type, final int location) {
0334: manager.runWithSmartScrollingDisabled(new Runnable() {
0335: public void run() {
0336: if (type == DiffController.LocationType.DifferenceIndex) {
0337: setDifferenceImpl(location);
0338: } else {
0339: if (pane == DiffController.DiffPane.Base) {
0340: setBaseLineNumberImpl(location);
0341: } else {
0342: setModifiedLineNumberImpl(location);
0343: }
0344: }
0345: }
0346: });
0347: }
0348:
0349: private void setModifiedLineNumberImpl(int line) {
0350:
0351: }
0352:
0353: private void setBaseLineNumberImpl(int line) {
0354: initGlobalSizes(); // The window might be resized in the mean time.
0355: try {
0356: EditorUI editorUI = org.netbeans.editor.Utilities
0357: .getEditorUI(jEditorPane1.getEditorPane());
0358: if (editorUI == null)
0359: return;
0360: int lineHeight = editorUI.getLineHeight();
0361:
0362: int offset = jEditorPane1.getScrollPane().getViewport()
0363: .getViewRect().height / 5;
0364: int lineOffset = lineHeight * line - offset;
0365:
0366: double scrollFactor = manager.getScrollFactor();
0367:
0368: int off1 = org.openide.text.NbDocument.findLineOffset(
0369: (StyledDocument) jEditorPane1.getEditorPane()
0370: .getDocument(), line);
0371: jEditorPane1.getEditorPane().setCaretPosition(off1);
0372:
0373: JScrollBar leftScrollBar = jEditorPane1.getScrollPane()
0374: .getVerticalScrollBar();
0375: JScrollBar rightScrollBar = jEditorPane2.getScrollPane()
0376: .getVerticalScrollBar();
0377:
0378: leftScrollBar.setValue(lineOffset);
0379: rightScrollBar.setValue((int) (lineOffset / scrollFactor));
0380:
0381: updateCurrentDifference();
0382: } catch (IndexOutOfBoundsException ex) {
0383: ErrorManager.getDefault().notify(ex);
0384: }
0385: }
0386:
0387: private void setDifferenceImpl(int location) {
0388: if (location < -1 || location >= diffs.length)
0389: throw new IllegalArgumentException(
0390: "Illegal difference number: " + location); // NOI18N
0391: if (location == -1) {
0392: } else {
0393: setDifferenceIndex(location);
0394: SwingUtilities.invokeLater(new Runnable() {
0395: public void run() {
0396: ignoredUpdateEvents = true;
0397: showCurrentDifference();
0398: SwingUtilities.invokeLater(new Runnable() {
0399: public void run() {
0400: ignoredUpdateEvents = false;
0401: }
0402: });
0403: }
0404: });
0405: }
0406: }
0407:
0408: public JComponent getJComponent() {
0409: return jSplitPane1;
0410: }
0411:
0412: /**
0413: * @return true if Move, Replace, Insert and Move All actions should be visible and enabled, false otherwise
0414: */
0415: public boolean isActionsEnabled() {
0416: return actionsEnabled;
0417: }
0418:
0419: private void initColors() {
0420: colorMissing = DiffModuleConfig.getDefault().getDeletedColor();
0421: colorAdded = DiffModuleConfig.getDefault().getAddedColor();
0422: colorChanged = DiffModuleConfig.getDefault().getChangedColor();
0423: }
0424:
0425: public void ancestorAdded(AncestorEvent event) {
0426: DiffModuleConfig.getDefault().getPreferences()
0427: .addPreferenceChangeListener(this );
0428: expandFolds();
0429: initGlobalSizes();
0430: addChangeListeners();
0431: refreshDiff(50);
0432:
0433: if (editableCookie == null)
0434: return;
0435: refreshEditableDocument();
0436: editableCookie.addPropertyChangeListener(this );
0437: }
0438:
0439: private void refreshEditableDocument() {
0440: Document doc = null;
0441: try {
0442: doc = editableCookie.openDocument();
0443: } catch (IOException e) {
0444: Logger.getLogger(EditableDiffView.class.getName()).log(
0445: Level.INFO,
0446: "Getting new Document from EditorCookie", e); // NOI18N
0447: return;
0448: }
0449: editableDocument.removeDocumentListener(this );
0450: if (doc != editableDocument) {
0451: editableDocument = doc;
0452: jEditorPane2.getEditorPane().setDocument(editableDocument);
0453: refreshDiff(20);
0454: }
0455: editableDocument.addDocumentListener(this );
0456: }
0457:
0458: public void ancestorRemoved(AncestorEvent event) {
0459: DiffModuleConfig.getDefault().getPreferences()
0460: .removePreferenceChangeListener(this );
0461: if (editableCookie != null) {
0462: editableDocument.removeDocumentListener(this );
0463: saveModifiedDocument();
0464: editableCookie.removePropertyChangeListener(this );
0465: if (editableCookie.getOpenedPanes() == null) {
0466: editableCookie.close();
0467: }
0468: }
0469: }
0470:
0471: public void preferenceChange(PreferenceChangeEvent evt) {
0472: initColors();
0473: diffChanged(); // trigger re-calculation of hightlights in case diff stays the same
0474: refreshDiff(20);
0475: }
0476:
0477: private void saveModifiedDocument() {
0478: DataObject dao = (DataObject) editableDocument
0479: .getProperty(Document.StreamDescriptionProperty);
0480: if (dao != null) {
0481: SaveCookie sc = dao.getCookie(SaveCookie.class);
0482: if (sc != null) {
0483: try {
0484: sc.save();
0485: } catch (IOException e) {
0486: Logger.getLogger(EditableDiffView.class.getName())
0487: .log(Level.INFO,
0488: "Error saving Diff document", e); // NOI18N
0489: }
0490: }
0491: }
0492: }
0493:
0494: public void ancestorMoved(AncestorEvent event) {
0495: }
0496:
0497: public void insertUpdate(DocumentEvent e) {
0498: refreshDiff(50);
0499: }
0500:
0501: public void removeUpdate(DocumentEvent e) {
0502: refreshDiff(50);
0503: }
0504:
0505: public void changedUpdate(DocumentEvent e) {
0506: refreshDiff(50);
0507: }
0508:
0509: Color getColor(Difference ad) {
0510: if (ad.getType() == Difference.ADD)
0511: return colorAdded;
0512: if (ad.getType() == Difference.CHANGE)
0513: return colorChanged;
0514: return colorMissing;
0515: }
0516:
0517: JComponent getMyDivider() {
0518: return spui.divider.getDivider();
0519: }
0520:
0521: DiffContentPanel getEditorPane1() {
0522: return jEditorPane1;
0523: }
0524:
0525: DiffContentPanel getEditorPane2() {
0526: return jEditorPane2;
0527: }
0528:
0529: public DiffViewManager getManager() {
0530: return manager;
0531: }
0532:
0533: Difference[] getDifferences() {
0534: return diffs;
0535: }
0536:
0537: /**
0538: * Rolls back a difference in the second document.
0539: *
0540: * @param diff a difference to roll back, null to remove all differences
0541: */
0542: void rollback(Difference diff) {
0543: if (diff == null) {
0544: try {
0545: Document dest = getEditorPane2().getEditorPane()
0546: .getDocument();
0547: Document src = getEditorPane1().getEditorPane()
0548: .getDocument();
0549: dest.remove(0, dest.getLength());
0550: dest.insertString(0, src.getText(0, src.getLength()),
0551: null);
0552: } catch (BadLocationException e) {
0553: ErrorManager.getDefault().notify(e);
0554: }
0555: return;
0556: }
0557: try {
0558: Document document = getEditorPane2().getEditorPane()
0559: .getDocument();
0560: if (diff.getType() == Difference.ADD) {
0561: int start = DiffViewManager.getRowStartFromLineOffset(
0562: document, diff.getSecondStart() - 1);
0563: int end = DiffViewManager.getRowStartFromLineOffset(
0564: document, diff.getSecondEnd());
0565: if (end == -1) {
0566: end = document.getLength();
0567: }
0568: document.remove(start, end - start);
0569: } else if (diff.getType() == Difference.DELETE) {
0570: int start = DiffViewManager.getRowStartFromLineOffset(
0571: document, diff.getSecondStart());
0572: document.insertString(start, diff.getFirstText(), null);
0573: } else {
0574: int start = DiffViewManager.getRowStartFromLineOffset(
0575: document, diff.getSecondStart() - 1);
0576: int end = DiffViewManager.getRowStartFromLineOffset(
0577: document, diff.getSecondEnd());
0578: if (end == -1) {
0579: end = document.getLength();
0580: }
0581: document.remove(start, end - start);
0582: document.insertString(start, diff.getFirstText(), null);
0583: }
0584: } catch (BadLocationException e) {
0585: ErrorManager.getDefault().notify(e);
0586: }
0587: }
0588:
0589: Stroke getBoldStroke() {
0590: return boldStroke;
0591: }
0592:
0593: class DiffSplitPaneUI extends BasicSplitPaneUI {
0594:
0595: final DiffSplitPaneDivider divider;
0596:
0597: public DiffSplitPaneUI(JSplitPane splitPane) {
0598: this .splitPane = splitPane;
0599: divider = new DiffSplitPaneDivider(this ,
0600: EditableDiffView.this );
0601: }
0602:
0603: public BasicSplitPaneDivider createDefaultDivider() {
0604: return divider;
0605: }
0606: }
0607:
0608: public boolean requestFocusInWindow() {
0609: return jEditorPane1.requestFocusInWindow();
0610: }
0611:
0612: public JComponent getComponent() {
0613: return jSplitPane1;
0614: }
0615:
0616: public int getDifferenceCount() {
0617: return diffs.length;
0618: }
0619:
0620: public boolean canSetCurrentDifference() {
0621: return true;
0622: }
0623:
0624: public void setCurrentDifference(int diffNo)
0625: throws UnsupportedOperationException {
0626: setLocation(null, DiffController.LocationType.DifferenceIndex,
0627: diffNo);
0628: }
0629:
0630: public int getCurrentDifference() {
0631: return getDifferenceIndex();
0632: }
0633:
0634: private int computeCurrentDifference() {
0635: if (manager == null)
0636: return 0;
0637: Rectangle viewRect = jViewport2.getViewRect();
0638: int bottom = viewRect.y + viewRect.height * 4 / 5;
0639: DiffViewManager.DecoratedDifference[] ddiffs = manager
0640: .getDecorations();
0641: for (int i = 0; i < ddiffs.length; i++) {
0642: int startLine = ddiffs[i].getTopRight();
0643: int endLine = ddiffs[i].getBottomRight();
0644: if (endLine > bottom || endLine == -1 && startLine > bottom)
0645: return Math.max(0, i - 1);
0646: }
0647: return ddiffs.length - 1;
0648: }
0649:
0650: /**
0651: * Notifies the Diff View that it should update the current difference index. If the update is called in the scope
0652: * of setCurrentDifference() method, this method does nothing. If not, it computes current difference base on
0653: * current view. This is to ensure the following workflow:
0654: * 1) If user only pushes Next/Previous buttons in Diff, he wants to review changes one by one
0655: * 2) If user touches the scrollbar, 'current difference' changes accordingly
0656: */
0657: void updateCurrentDifference() {
0658: assert SwingUtilities.isEventDispatchThread();
0659: if (ignoredUpdateEvents) {
0660: return;
0661: }
0662: int cd = computeCurrentDifference();
0663: setDifferenceIndex(cd);
0664: }
0665:
0666: public JToolBar getToolBar() {
0667: return null;
0668: }
0669:
0670: private void showCurrentDifference() {
0671: Difference diff = diffs[getDifferenceIndex()];
0672:
0673: int off1, off2;
0674: initGlobalSizes(); // The window might be resized in the mean time.
0675: try {
0676: off1 = org.openide.text.NbDocument.findLineOffset(
0677: (StyledDocument) jEditorPane1.getEditorPane()
0678: .getDocument(),
0679: diff.getFirstStart() > 0 ? diff.getFirstStart() - 1
0680: : 0);
0681: off2 = org.openide.text.NbDocument
0682: .findLineOffset((StyledDocument) jEditorPane2
0683: .getEditorPane().getDocument(), diff
0684: .getSecondStart() > 0 ? diff
0685: .getSecondStart() - 1 : 0);
0686:
0687: jEditorPane1.getEditorPane().setCaretPosition(off1);
0688: jEditorPane2.getEditorPane().setCaretPosition(off2);
0689:
0690: DiffViewManager.DecoratedDifference ddiff = manager
0691: .getDecorations()[getDifferenceIndex()];
0692: int offset;
0693: if (ddiff.getDiff().getType() == Difference.DELETE) {
0694: offset = jEditorPane2.getScrollPane().getViewport()
0695: .getViewRect().height / 2 + 1;
0696: } else {
0697: offset = jEditorPane2.getScrollPane().getViewport()
0698: .getViewRect().height / 5;
0699: }
0700: jEditorPane2.getScrollPane().getVerticalScrollBar()
0701: .setValue(ddiff.getTopRight() - offset);
0702: } catch (IndexOutOfBoundsException ex) {
0703: ErrorManager.getDefault().notify(ex);
0704: }
0705:
0706: // scroll the left pane accordingly
0707: manager.scroll();
0708: }
0709:
0710: /** This method is called from within the constructor to initialize the form.
0711: */
0712: private void initComponents() {
0713: fileLabel1.setBorder(BorderFactory
0714: .createEmptyBorder(4, 4, 4, 4));
0715: fileLabel1
0716: .setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
0717: filePanel1.setLayout(new BorderLayout());
0718: filePanel1.add(fileLabel1, BorderLayout.PAGE_START);
0719:
0720: fileLabel2.setBorder(BorderFactory
0721: .createEmptyBorder(4, 4, 4, 4));
0722: fileLabel2
0723: .setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
0724: filePanel2.setLayout(new BorderLayout());
0725: filePanel2.add(fileLabel2, BorderLayout.PAGE_START);
0726:
0727: if (binaryDiff) {
0728: NoContentPanel ncp1 = new NoContentPanel(NbBundle
0729: .getMessage(EditableDiffView.class,
0730: "CTL_DiffPanel_BinaryFile"));
0731: fileLabel1.setLabelFor(ncp1);
0732: filePanel1.add(ncp1);
0733: NoContentPanel ncp2 = new NoContentPanel(NbBundle
0734: .getMessage(EditableDiffView.class,
0735: "CTL_DiffPanel_BinaryFile"));
0736: fileLabel2.setLabelFor(ncp2);
0737: filePanel2.add(ncp2);
0738: } else {
0739: jEditorPane1 = new DiffContentPanel(this , true);
0740: jEditorPane2 = new DiffContentPanel(this , false);
0741: jEditorPane1.getAccessibleContext().setAccessibleName(
0742: org.openide.util.NbBundle.getMessage(
0743: EditableDiffView.class,
0744: "ACS_EditorPane1A11yName")); // NOI18N
0745: jEditorPane1.getAccessibleContext()
0746: .setAccessibleDescription(
0747: org.openide.util.NbBundle.getMessage(
0748: EditableDiffView.class,
0749: "ACS_EditorPane1A11yDescr")); // NOI18N
0750: jEditorPane2.getAccessibleContext().setAccessibleName(
0751: org.openide.util.NbBundle.getMessage(
0752: EditableDiffView.class,
0753: "ACS_EditorPane2A11yName")); // NOI18N
0754: jEditorPane2.getAccessibleContext()
0755: .setAccessibleDescription(
0756: org.openide.util.NbBundle.getMessage(
0757: EditableDiffView.class,
0758: "ACS_EditorPane2A11yDescr")); // NOI18N
0759: fileLabel1.setLabelFor(jEditorPane1);
0760: filePanel1.add(jEditorPane1);
0761: fileLabel2.setLabelFor(jEditorPane2);
0762: filePanel2.add(jEditorPane2);
0763: }
0764:
0765: jSplitPane1.setLeftComponent(filePanel1);
0766: jSplitPane1.setRightComponent(filePanel2);
0767: }
0768:
0769: // Code for dispatching events from components to event handlers.
0770: private void expandFolds(JEditorPane pane) {
0771: final FoldHierarchy fh = FoldHierarchy.get(pane);
0772: FoldUtilities.expandAll(fh);
0773: fh.addFoldHierarchyListener(new FoldHierarchyListener() {
0774: public void foldHierarchyChanged(FoldHierarchyEvent evt) {
0775: FoldUtilities.expandAll(fh);
0776: }
0777: });
0778: }
0779:
0780: private void expandFolds() {
0781: expandFolds(jEditorPane1.getEditorPane());
0782: expandFolds(jEditorPane2.getEditorPane());
0783: }
0784:
0785: private void initGlobalSizes() {
0786: StyledDocument doc1 = (StyledDocument) jEditorPane1
0787: .getEditorPane().getDocument();
0788: StyledDocument doc2 = (StyledDocument) jEditorPane2
0789: .getEditorPane().getDocument();
0790: int numLines1 = org.openide.text.NbDocument.findLineNumber(
0791: doc1, doc1.getEndPosition().getOffset());
0792: int numLines2 = org.openide.text.NbDocument.findLineNumber(
0793: doc2, doc2.getEndPosition().getOffset());
0794:
0795: int numLines = Math.max(numLines1, numLines2);
0796: if (numLines < 1)
0797: numLines = 1;
0798: int totHeight = jEditorPane1.getSize().height;
0799: int value = jEditorPane2.getSize().height;
0800: if (value > totHeight)
0801: totHeight = value;
0802: }
0803:
0804: private void joinScrollBars() {
0805: final JScrollBar scrollBarH1 = jEditorPane1.getScrollPane()
0806: .getHorizontalScrollBar();
0807: final JScrollBar scrollBarH2 = jEditorPane2.getScrollPane()
0808: .getHorizontalScrollBar();
0809:
0810: scrollBarH1.getModel().addChangeListener(
0811: new javax.swing.event.ChangeListener() {
0812: public void stateChanged(
0813: javax.swing.event.ChangeEvent e) {
0814: int value = scrollBarH1.getValue();
0815: if (value == horizontalScroll1ChangedValue)
0816: return;
0817: int max1 = scrollBarH1.getMaximum();
0818: int max2 = scrollBarH2.getMaximum();
0819: int ext1 = scrollBarH1.getModel().getExtent();
0820: int ext2 = scrollBarH2.getModel().getExtent();
0821: if (max1 == ext1)
0822: horizontalScroll2ChangedValue = 0;
0823: else
0824: horizontalScroll2ChangedValue = (value * (max2 - ext2))
0825: / (max1 - ext1);
0826: horizontalScroll1ChangedValue = -1;
0827: scrollBarH2
0828: .setValue(horizontalScroll2ChangedValue);
0829: }
0830: });
0831: scrollBarH2.getModel().addChangeListener(
0832: new javax.swing.event.ChangeListener() {
0833: public void stateChanged(
0834: javax.swing.event.ChangeEvent e) {
0835: int value = scrollBarH2.getValue();
0836: if (value == horizontalScroll2ChangedValue)
0837: return;
0838: int max1 = scrollBarH1.getMaximum();
0839: int max2 = scrollBarH2.getMaximum();
0840: int ext1 = scrollBarH1.getModel().getExtent();
0841: int ext2 = scrollBarH2.getModel().getExtent();
0842: if (max2 == ext2)
0843: horizontalScroll1ChangedValue = 0;
0844: else
0845: horizontalScroll1ChangedValue = (value * (max1 - ext1))
0846: / (max2 - ext2);
0847: horizontalScroll2ChangedValue = -1;
0848: scrollBarH1
0849: .setValue(horizontalScroll1ChangedValue);
0850: }
0851: });
0852: }
0853:
0854: private void customizeEditor(JEditorPane editor) {
0855: StyledDocument doc;
0856: Document document = editor.getDocument();
0857: try {
0858: doc = (StyledDocument) editor.getDocument();
0859: } catch (ClassCastException e) {
0860: doc = new DefaultStyledDocument();
0861: try {
0862: doc.insertString(0, document.getText(0, document
0863: .getLength()), null);
0864: } catch (BadLocationException ble) {
0865: // leaving the document empty
0866: }
0867: editor.setDocument(doc);
0868: }
0869: }
0870:
0871: private void addChangeListeners() {
0872: jEditorPane1.getEditorPane().addPropertyChangeListener("font",
0873: new java.beans.PropertyChangeListener() { // NOI18N
0874: public void propertyChange(
0875: java.beans.PropertyChangeEvent evt) {
0876: javax.swing.SwingUtilities
0877: .invokeLater(new Runnable() {
0878: public void run() {
0879: diffChanged();
0880: initGlobalSizes();
0881: jEditorPane1
0882: .onUISettingsChanged();
0883: getComponent().revalidate();
0884: getComponent().repaint();
0885: }
0886: });
0887: }
0888: });
0889: jEditorPane2.getEditorPane().addPropertyChangeListener("font",
0890: new java.beans.PropertyChangeListener() { // NOI18N
0891: public void propertyChange(
0892: java.beans.PropertyChangeEvent evt) {
0893: javax.swing.SwingUtilities
0894: .invokeLater(new Runnable() {
0895: public void run() {
0896: diffChanged();
0897: initGlobalSizes();
0898: jEditorPane2
0899: .onUISettingsChanged();
0900: getComponent().revalidate();
0901: getComponent().repaint();
0902: }
0903: });
0904: }
0905: });
0906: }
0907:
0908: private synchronized void diffChanged() {
0909: diffSerial++; // we need to re-compute decorations, font size changed
0910: }
0911:
0912: private void setSource1(StreamSource ss) throws IOException {
0913: firstSourceAvailable = false;
0914: EditorKit kit = jEditorPane1.getEditorPane().getEditorKit();
0915: if (kit == null)
0916: throw new IOException("Missing Editor Kit"); // NOI18N
0917:
0918: Document sdoc = getSourceDocument(ss);
0919: Document doc = sdoc != null ? sdoc : kit
0920: .createDefaultDocument();
0921: if (!(doc instanceof StyledDocument)) {
0922: doc = new DefaultStyledDocument(new StyleContext());
0923: kit = new StyledEditorKit();
0924: jEditorPane1.getEditorPane().setEditorKit(kit);
0925: }
0926: if (sdoc == null) {
0927: Reader r = ss.createReader();
0928: if (r != null) {
0929: firstSourceAvailable = true;
0930: try {
0931: kit.read(r, doc, 0);
0932: } catch (javax.swing.text.BadLocationException e) {
0933: throw new IOException(
0934: "Can not locate the beginning of the document."); // NOI18N
0935: } finally {
0936: r.close();
0937: }
0938: }
0939: } else {
0940: firstSourceAvailable = true;
0941: }
0942: jEditorPane1.initActions();
0943: jEditorPane1.getEditorPane().setDocument(doc);
0944: customizeEditor(jEditorPane1.getEditorPane());
0945: }
0946:
0947: private Document getSourceDocument(StreamSource ss) {
0948: Document sdoc = null;
0949: FileObject fo = ss.getLookup().lookup(FileObject.class);
0950: if (fo != null) {
0951: try {
0952: DataObject dao = DataObject.find(fo);
0953: EditorCookie ec = dao.getCookie(EditorCookie.class);
0954: if (ec != null) {
0955: sdoc = ec.openDocument();
0956: }
0957: } catch (Exception e) {
0958: // fallback to other means of obtaining the source
0959: }
0960: } else {
0961: sdoc = ss.getLookup().lookup(Document.class);
0962: }
0963: return sdoc;
0964: }
0965:
0966: private void setSource2(StreamSource ss) throws IOException {
0967: secondSourceAvailable = false;
0968: EditorKit kit = jEditorPane2.getEditorPane().getEditorKit();
0969: if (kit == null)
0970: throw new IOException("Missing Editor Kit"); // NOI18N
0971:
0972: Document sdoc = getSourceDocument(ss);
0973: if (sdoc != null && ss.isEditable()) {
0974: DataObject dao = (DataObject) sdoc
0975: .getProperty(Document.StreamDescriptionProperty);
0976: if (dao != null) {
0977: if (dao instanceof MultiDataObject) {
0978: MultiDataObject mdao = (MultiDataObject) dao;
0979: for (MultiDataObject.Entry entry : mdao
0980: .secondaryEntries()) {
0981: if (entry instanceof CookieSet.Factory) {
0982: CookieSet.Factory factory = (CookieSet.Factory) entry;
0983: EditorCookie ec = factory
0984: .createCookie(EditorCookie.class);
0985: Document entryDocument = ec.getDocument();
0986: if (entryDocument == sdoc
0987: && ec instanceof EditorCookie.Observable) {
0988: editableCookie = (EditorCookie.Observable) ec;
0989: editableDocument = sdoc;
0990: editorUndoRedo = getUndoRedo(ec);
0991: }
0992: }
0993: }
0994: }
0995: if (editableCookie == null) {
0996: EditorCookie cookie = dao
0997: .getCookie(EditorCookie.class);
0998: if (cookie instanceof EditorCookie.Observable) {
0999: editableCookie = (EditorCookie.Observable) cookie;
1000: editableDocument = sdoc;
1001: editorUndoRedo = getUndoRedo(cookie);
1002: }
1003: }
1004: }
1005: }
1006: Document doc = sdoc != null ? sdoc : kit
1007: .createDefaultDocument();
1008: if (!(doc instanceof StyledDocument)) {
1009: doc = new DefaultStyledDocument(new StyleContext());
1010: kit = new StyledEditorKit();
1011: jEditorPane2.getEditorPane().setEditorKit(kit);
1012: }
1013: if (sdoc == null) {
1014: Reader r = ss.createReader();
1015: if (r != null) {
1016: secondSourceAvailable = true;
1017: try {
1018: kit.read(r, doc, 0);
1019: } catch (javax.swing.text.BadLocationException e) {
1020: throw new IOException(
1021: "Can not locate the beginning of the document."); // NOI18N
1022: } finally {
1023: r.close();
1024: }
1025: }
1026: } else {
1027: secondSourceAvailable = true;
1028: }
1029: jEditorPane2.initActions();
1030: jSplitPane1.putClientProperty(UndoRedo.class, editorUndoRedo);
1031: jEditorPane2.getEditorPane().setDocument(doc);
1032: jEditorPane2.getEditorPane()
1033: .setEditable(editableCookie != null);
1034: if (doc instanceof NbDocument.CustomEditor) {
1035: Component c = ((NbDocument.CustomEditor) doc)
1036: .createEditor(jEditorPane2.getEditorPane());
1037: if (c instanceof JComponent) {
1038: jEditorPane2.setCustomEditor((JComponent) c);
1039: }
1040: }
1041:
1042: customizeEditor(jEditorPane2.getEditorPane());
1043: jViewport2 = jEditorPane2.getScrollPane().getViewport();
1044: joinScrollBars();
1045: }
1046:
1047: private UndoRedo.Manager getUndoRedo(EditorCookie cookie) {
1048: // TODO: working around #96543
1049: try {
1050: Method method = CloneableEditorSupport.class
1051: .getDeclaredMethod("getUndoRedo"); // NOI18N
1052: method.setAccessible(true);
1053: return (UndoRedo.Manager) method.invoke(cookie);
1054: } catch (Exception e) {
1055: e.printStackTrace();
1056: }
1057: return null;
1058: }
1059:
1060: public void propertyChange(final PropertyChangeEvent evt) {
1061: if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt
1062: .getPropertyName())) {
1063: SwingUtilities.invokeLater(new Runnable() {
1064: public void run() {
1065: refreshEditableDocument();
1066: }
1067: });
1068: }
1069: }
1070:
1071: public void setSourceTitle(JLabel label, String title) {
1072: label.setText(title);
1073: // Set the minimum size in 'x' direction to a low value, so that the splitter can be moved to corner locations
1074: label.setMinimumSize(new Dimension(3,
1075: label.getMinimumSize().height));
1076: }
1077:
1078: public void setDocument1(Document doc) {
1079: if (doc != null) {
1080: jEditorPane1.getEditorPane().setDocument(doc);
1081: }
1082: }
1083:
1084: public void setDocument2(Document doc) {
1085: if (doc != null) {
1086: jEditorPane2.getEditorPane().setDocument(doc);
1087: }
1088: }
1089:
1090: private void refreshDiff(int delayMillis) {
1091: refreshDiffTask.schedule(delayMillis);
1092: }
1093:
1094: public class RefreshDiffTask implements Runnable {
1095:
1096: public void run() {
1097: synchronized (EditableDiffView.this ) {
1098: computeDiff();
1099: SwingUtilities.invokeLater(new Runnable() {
1100: public void run() {
1101: if (getDifferenceIndex() >= diffs.length)
1102: updateCurrentDifference();
1103: support.firePropertyChange(
1104: DiffController.PROP_DIFFERENCES, null,
1105: null);
1106: jEditorPane1.setCurrentDiff(diffs);
1107: jEditorPane2.setCurrentDiff(diffs);
1108: refreshDividerSize();
1109: jSplitPane1.repaint();
1110: diffMarkprovider.refresh();
1111: }
1112: });
1113: }
1114: }
1115:
1116: private void computeDiff() {
1117: if (!secondSourceAvailable || !firstSourceAvailable) {
1118: diffs = NO_DIFFERENCES;
1119: return;
1120: }
1121:
1122: Reader first = null;
1123: Reader second = null;
1124: try {
1125: first = new StringReader(jEditorPane1.getEditorPane()
1126: .getDocument().getText(
1127: 0,
1128: jEditorPane1.getEditorPane()
1129: .getDocument().getLength()));
1130: second = new StringReader(jEditorPane2.getEditorPane()
1131: .getDocument().getText(
1132: 0,
1133: jEditorPane2.getEditorPane()
1134: .getDocument().getLength()));
1135: } catch (BadLocationException e) {
1136: ErrorManager.getDefault().notify(e);
1137: }
1138:
1139: DiffProvider diff = DiffModuleConfig.getDefault()
1140: .getDefaultDiffProvider();
1141: try {
1142: diffs = diff.computeDiff(first, second);
1143: diffChanged();
1144: } catch (IOException e) {
1145: diffs = NO_DIFFERENCES;
1146: }
1147: }
1148: }
1149:
1150: private void refreshDividerSize() {
1151: Font font = jSplitPane1.getFont();
1152: if (font == null)
1153: return;
1154: FontMetrics fm = jSplitPane1.getFontMetrics(jSplitPane1
1155: .getFont());
1156: String maxDiffNumber = Integer.toString(Math.max(1,
1157: diffs.length));
1158: int neededWidth = fm.stringWidth(maxDiffNumber + " /"
1159: + maxDiffNumber);
1160: jSplitPane1.setDividerSize(Math.max(neededWidth,
1161: INITIAL_DIVIDER_SIZE));
1162: }
1163:
1164: synchronized int getDiffSerial() {
1165: return diffSerial;
1166: }
1167:
1168: static Difference getFirstDifference(Difference[] diff, int line) {
1169: if (line < 0)
1170: return null;
1171: for (int i = 0; i < diff.length; i++) {
1172: Difference difference = diff[i];
1173: if (line < difference.getFirstStart())
1174: return null;
1175: if (difference.getType() == Difference.ADD
1176: && line == difference.getFirstStart())
1177: return difference;
1178: if (line <= difference.getFirstEnd())
1179: return difference;
1180: }
1181: return null;
1182: }
1183:
1184: static Difference getSecondDifference(Difference[] diff, int line) {
1185: if (line < 0)
1186: return null;
1187: for (int i = 0; i < diff.length; i++) {
1188: Difference difference = diff[i];
1189: if (line < difference.getSecondStart())
1190: return null;
1191: if (difference.getType() == Difference.DELETE
1192: && line == difference.getSecondStart())
1193: return difference;
1194: if (line <= difference.getSecondEnd())
1195: return difference;
1196: }
1197: return null;
1198: }
1199:
1200: Color getColorLines() {
1201: return colorLines;
1202: }
1203:
1204: /**
1205: * Integration provider for the error stripe.
1206: */
1207: private class EditableDiffMarkProvider extends MarkProvider {
1208:
1209: private List<Mark> marks;
1210:
1211: public EditableDiffMarkProvider() {
1212: marks = getMarksForDifferences();
1213: }
1214:
1215: public List<Mark> getMarks() {
1216: return marks;
1217: }
1218:
1219: void refresh() {
1220: List<Mark> oldMarks = marks;
1221: marks = getMarksForDifferences();
1222: firePropertyChange(PROP_MARKS, oldMarks, marks);
1223: }
1224:
1225: private List<Mark> getMarksForDifferences() {
1226: if (diffs == null)
1227: return Collections.emptyList();
1228: List<Mark> marks = new ArrayList<Mark>(diffs.length);
1229: for (int i = 0; i < diffs.length; i++) {
1230: Difference difference = diffs[i];
1231: marks
1232: .add(new DiffMark(difference,
1233: getColor(difference)));
1234: }
1235: return marks;
1236: }
1237: }
1238: }
|