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.subversion.ui.diff;
0043:
0044: import org.netbeans.modules.versioning.util.DelegatingUndoRedo;
0045: import org.netbeans.modules.versioning.util.VersioningEvent;
0046: import org.netbeans.modules.versioning.util.VersioningListener;
0047: import org.netbeans.modules.versioning.util.NoContentPanel;
0048: import org.netbeans.modules.subversion.util.Context;
0049: import org.netbeans.modules.subversion.util.SvnUtils;
0050: import org.netbeans.modules.subversion.Subversion;
0051: import org.netbeans.modules.subversion.FileStatusCache;
0052: import org.netbeans.modules.subversion.FileInformation;
0053: import org.netbeans.modules.subversion.client.PropertiesClient;
0054: import org.netbeans.modules.subversion.client.SvnProgressSupport;
0055: import org.netbeans.modules.subversion.ui.commit.CommitAction;
0056: import org.netbeans.modules.subversion.ui.status.StatusAction;
0057: import org.netbeans.modules.subversion.ui.update.UpdateAction;
0058: import org.netbeans.api.diff.DiffController;
0059: import org.netbeans.api.diff.StreamSource;
0060: import org.netbeans.api.diff.Difference;
0061: import org.netbeans.spi.diff.DiffProvider;
0062: import org.openide.util.RequestProcessor;
0063: import org.openide.util.NbBundle;
0064: import org.openide.util.Lookup;
0065: import org.openide.util.lookup.Lookups;
0066: import org.openide.awt.UndoRedo;
0067: import org.openide.windows.TopComponent;
0068: import org.openide.windows.WindowManager;
0069: import org.openide.nodes.Node;
0070: import org.openide.nodes.AbstractNode;
0071: import org.openide.nodes.Children;
0072: import org.openide.filesystems.FileObject;
0073: import org.openide.filesystems.FileUtil;
0074: import org.openide.LifecycleManager;
0075: import javax.swing.*;
0076: import java.io.*;
0077: import java.awt.*;
0078: import java.awt.event.ActionEvent;
0079: import java.awt.event.ActionListener;
0080: import java.util.*;
0081: import java.util.List;
0082: import java.beans.PropertyChangeListener;
0083: import java.beans.PropertyChangeEvent;
0084: import java.util.logging.Level;
0085: import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
0086: import org.netbeans.modules.subversion.ui.actions.ContextAction;
0087: import org.tigris.subversion.svnclientadapter.SVNClientException;
0088: import org.tigris.subversion.svnclientadapter.SVNUrl;
0089:
0090: /**
0091: *
0092: * @author Maros Sandor
0093: */
0094: class MultiDiffPanel extends javax.swing.JPanel implements
0095: ActionListener, VersioningListener, DiffSetupSource,
0096: PropertyChangeListener {
0097:
0098: /**
0099: * Array of DIFF setups that we show in the DIFF view. Contents of this array is changed if
0100: * the user switches DIFF types.
0101: */
0102: private Setup[] setups;
0103:
0104: private final DelegatingUndoRedo delegatingUndoRedo = new DelegatingUndoRedo();
0105:
0106: /**
0107: * Context in which to DIFF.
0108: */
0109: private final Context context;
0110:
0111: private int displayStatuses;
0112:
0113: /**
0114: * Display name of the context of this diff.
0115: */
0116: private final String contextName;
0117:
0118: private int currentType;
0119: private int currentIndex = -1;
0120: private int currentModelIndex = -1;
0121:
0122: private RequestProcessor.Task prepareTask;
0123: private DiffPrepareTask dpt;
0124:
0125: private AbstractAction nextAction;
0126: private AbstractAction prevAction;
0127:
0128: /**
0129: * null for view that are not
0130: */
0131: private RequestProcessor.Task refreshTask;
0132:
0133: private JComponent diffView;
0134: private DiffFileTable fileTable;
0135: private boolean dividerSet;
0136:
0137: private SvnProgressSupport executeStatusSupport;
0138:
0139: /**
0140: * Creates diff panel and immediatelly starts loading...
0141: */
0142: public MultiDiffPanel(Context context, int initialType,
0143: String contextName) {
0144: this .context = context;
0145: this .contextName = contextName;
0146: currentType = initialType;
0147: initComponents();
0148: setupComponents();
0149: refreshSetups();
0150: refreshComponents();
0151: refreshTask = org.netbeans.modules.versioning.util.Utils
0152: .createTask(new RefreshViewTask());
0153: }
0154:
0155: /**
0156: * Construct diff component showing just one file.
0157: * It hides All, Local, Remote toggles and file chooser combo.
0158: */
0159: public MultiDiffPanel(File file, String rev1, String rev2) {
0160: context = null;
0161: contextName = file.getName();
0162: initComponents();
0163: setupComponents();
0164: localToggle.setVisible(false);
0165: remoteToggle.setVisible(false);
0166: allToggle.setVisible(false);
0167: fileTable.getComponent().setVisible(false);
0168: commitButton.setVisible(false);
0169:
0170: // mimics refreshSetups()
0171: setups = new Setup[] { new Setup(file, rev1, rev2) };
0172: setDiffIndex(0, 0);
0173: dpt = new DiffPrepareTask(setups);
0174: prepareTask = RequestProcessor.getDefault().post(dpt);
0175: }
0176:
0177: private boolean fileTableSetSelectedIndexContext;
0178:
0179: public void setSelectedIndex(int viewIndex) {
0180: if (fileTableSetSelectedIndexContext)
0181: return;
0182: setDiffIndex(viewIndex, 0);
0183: }
0184:
0185: UndoRedo getUndoRedo() {
0186: return delegatingUndoRedo;
0187: }
0188:
0189: private void cancelBackgroundTasks() {
0190: if (prepareTask != null) {
0191: prepareTask.cancel();
0192: }
0193: if (executeStatusSupport != null) {
0194: executeStatusSupport.cancel();
0195: }
0196: }
0197:
0198: /**
0199: * Called by the enclosing TopComponent to interrupt the fetching task.
0200: */
0201: void componentClosed() {
0202: setups = null;
0203: cancelBackgroundTasks();
0204: }
0205:
0206: void requestActive() {
0207: if (diffView != null) {
0208: diffView.requestFocusInWindow();
0209: }
0210: }
0211:
0212: private void setupComponents() {
0213: fileTable = new DiffFileTable(this );
0214: splitPane.setTopComponent(fileTable.getComponent());
0215: splitPane.setBottomComponent(new NoContentPanel(NbBundle
0216: .getMessage(MultiDiffPanel.class,
0217: "MSG_DiffPanel_NoContent")));
0218: commitButton.addActionListener(this );
0219: localToggle.addActionListener(this );
0220: remoteToggle.addActionListener(this );
0221: allToggle.addActionListener(this );
0222:
0223: commitButton.setToolTipText(NbBundle.getMessage(
0224: MultiDiffPanel.class, "MSG_CommitDiff_Tooltip",
0225: contextName));
0226: updateButton.setToolTipText(NbBundle.getMessage(
0227: MultiDiffPanel.class, "MSG_UpdateDiff_Tooltip",
0228: contextName));
0229: ButtonGroup grp = new ButtonGroup();
0230: grp.add(localToggle);
0231: grp.add(remoteToggle);
0232: grp.add(allToggle);
0233: if (currentType == Setup.DIFFTYPE_LOCAL)
0234: localToggle.setSelected(true);
0235: else if (currentType == Setup.DIFFTYPE_REMOTE)
0236: remoteToggle.setSelected(true);
0237: else if (currentType == Setup.DIFFTYPE_ALL)
0238: allToggle.setSelected(true);
0239:
0240: nextAction = new AbstractAction(
0241: null,
0242: new javax.swing.ImageIcon(
0243: getClass()
0244: .getResource(
0245: "/org/netbeans/modules/subversion/resources/icons/diff-next.png"))) { // NOI18N
0246: {
0247: putValue(
0248: Action.SHORT_DESCRIPTION,
0249: java.util.ResourceBundle
0250: .getBundle(
0251: "org/netbeans/modules/subversion/ui/diff/Bundle")
0252: .getString("CTL_DiffPanel_Next_Tooltip"));
0253: }
0254:
0255: public void actionPerformed(ActionEvent e) {
0256: onNextButton();
0257: }
0258: };
0259: prevAction = new AbstractAction(
0260: null,
0261: new javax.swing.ImageIcon(
0262: getClass()
0263: .getResource(
0264: "/org/netbeans/modules/subversion/resources/icons/diff-prev.png"))) { // NOI18N
0265: {
0266: putValue(
0267: Action.SHORT_DESCRIPTION,
0268: java.util.ResourceBundle
0269: .getBundle(
0270: "org/netbeans/modules/subversion/ui/diff/Bundle")
0271: .getString("CTL_DiffPanel_Prev_Tooltip"));
0272: }
0273:
0274: public void actionPerformed(ActionEvent e) {
0275: onPrevButton();
0276: }
0277: };
0278: nextButton.setAction(nextAction);
0279: prevButton.setAction(prevAction);
0280: }
0281:
0282: private void refreshComponents() {
0283: DiffController view = setups != null && currentModelIndex != -1 ? setups[currentModelIndex]
0284: .getView()
0285: : null;
0286: int currentDifferenceIndex = view != null ? view
0287: .getDifferenceIndex() : -1;
0288: if (view != null) {
0289: nextAction.setEnabled(currentIndex < setups.length - 1
0290: || currentDifferenceIndex < view
0291: .getDifferenceCount() - 1);
0292: } else {
0293: nextAction.setEnabled(false);
0294: }
0295: prevAction.setEnabled(currentIndex > 0
0296: || currentDifferenceIndex > 0);
0297: dividerSet = false;
0298: }
0299:
0300: public void addNotify() {
0301: super .addNotify();
0302: if (refreshTask != null) {
0303: Subversion.getInstance().getStatusCache()
0304: .addVersioningListener(this );
0305: }
0306: JComponent parent = (JComponent) getParent();
0307: parent.getActionMap().put("jumpNext", nextAction); // NOI18N
0308: parent.getActionMap().put("jumpPrev", prevAction); // NOI18N
0309: }
0310:
0311: private void updateSplitLocation() {
0312: if (dividerSet)
0313: return;
0314: JComponent parent = (JComponent) getParent();
0315: Dimension dim = parent == null ? new Dimension() : parent
0316: .getSize();
0317: if (dim.width <= 0 || dim.height <= 0) {
0318: SwingUtilities.invokeLater(new Runnable() {
0319: public void run() {
0320: updateSplitLocation();
0321: }
0322: });
0323: return;
0324: }
0325: dividerSet = true;
0326: JTable jt = fileTable.getTable();
0327: int optimalLocation = jt.getPreferredSize().height
0328: + jt.getTableHeader().getPreferredSize().height;
0329: if (optimalLocation > dim.height / 3) {
0330: optimalLocation = dim.height / 3;
0331: }
0332: if (optimalLocation <= jt.getTableHeader().getPreferredSize().height) {
0333: optimalLocation = jt.getTableHeader().getPreferredSize().height * 3;
0334: }
0335: splitPane.setDividerLocation(optimalLocation);
0336: }
0337:
0338: public void removeNotify() {
0339: Subversion.getInstance().getStatusCache()
0340: .removeVersioningListener(this );
0341: super .removeNotify();
0342: }
0343:
0344: public void versioningEvent(VersioningEvent event) {
0345: if (event.getId() == FileStatusCache.EVENT_FILE_STATUS_CHANGED) {
0346: if (!affectsView(event)) {
0347: return;
0348: }
0349: refreshTask.schedule(200);
0350: }
0351: }
0352:
0353: private boolean affectsView(VersioningEvent event) {
0354: File file = (File) event.getParams()[0];
0355: FileInformation oldInfo = (FileInformation) event.getParams()[1];
0356: FileInformation newInfo = (FileInformation) event.getParams()[2];
0357: if (oldInfo == null) {
0358: if ((newInfo.getStatus() & displayStatuses) == 0)
0359: return false;
0360: } else {
0361: if ((oldInfo.getStatus() & displayStatuses)
0362: + (newInfo.getStatus() & displayStatuses) == 0)
0363: return false;
0364: }
0365: return context.contains(file);
0366: }
0367:
0368: private void setDiffIndex(int idx, int location) {
0369: currentIndex = idx;
0370: DiffController view = null;
0371:
0372: if (currentIndex != -1) {
0373: currentModelIndex = showingFileTable() ? fileTable
0374: .getModelIndex(currentIndex) : 0;
0375: view = setups[currentModelIndex].getView();
0376:
0377: // enable Select in .. action
0378: TopComponent tc = (TopComponent) getClientProperty(TopComponent.class);
0379: if (tc != null) {
0380: Node node = Node.EMPTY;
0381: File baseFile = setups[currentModelIndex].getBaseFile();
0382: if (baseFile != null) {
0383: FileObject fo = FileUtil.toFileObject(baseFile);
0384: if (fo != null) {
0385: node = new AbstractNode(Children.LEAF, Lookups
0386: .singleton(fo));
0387: }
0388: }
0389: tc.setActivatedNodes(new Node[] { node });
0390: }
0391:
0392: diffView = null;
0393: boolean focus = false;
0394: if (view != null) {
0395: if (showingFileTable()) {
0396: fileTableSetSelectedIndexContext = true;
0397: fileTable.setSelectedIndex(currentIndex);
0398: fileTableSetSelectedIndexContext = false;
0399: }
0400: diffView = view.getJComponent();
0401: diffView.getActionMap().put("jumpNext", nextAction); // NOI18N
0402: diffView.getActionMap().put("jumpPrev", prevAction); // NOI18N
0403: setBottomComponent();
0404: if (location == -1) {
0405: location = view.getDifferenceCount() - 1;
0406: }
0407: if (location >= 0
0408: && location < view.getDifferenceCount()) {
0409: view
0410: .setLocation(
0411: DiffController.DiffPane.Modified,
0412: DiffController.LocationType.DifferenceIndex,
0413: location);
0414: }
0415: Component toc = WindowManager.getDefault()
0416: .getRegistry().getActivated();
0417: if (SwingUtilities.isDescendingFrom(this , toc)) {
0418: // focus = true;
0419: }
0420: } else {
0421: diffView = new NoContentPanel(NbBundle
0422: .getMessage(MultiDiffPanel.class,
0423: "MSG_DiffPanel_NoContent"));
0424: }
0425: } else {
0426: currentModelIndex = -1;
0427: diffView = new NoContentPanel(NbBundle.getMessage(
0428: MultiDiffPanel.class,
0429: "MSG_DiffPanel_NoFileSelected"));
0430: setBottomComponent();
0431: }
0432:
0433: delegatingUndoRedo.setDiffView(diffView);
0434:
0435: refreshComponents();
0436: // if (focus) {
0437: // diffView.requestFocusInWindow();
0438: // }
0439: }
0440:
0441: private boolean showingFileTable() {
0442: return fileTable.getComponent().isVisible();
0443: }
0444:
0445: private void setBottomComponent() {
0446: int gg = splitPane.getDividerLocation();
0447: splitPane.setBottomComponent(diffView);
0448: splitPane.setDividerLocation(gg);
0449: }
0450:
0451: public void actionPerformed(ActionEvent e) {
0452: Object source = e.getSource();
0453: if (source == commitButton)
0454: onCommitButton();
0455: else if (source == localToggle || source == remoteToggle
0456: || source == allToggle)
0457: onDiffTypeChanged();
0458: }
0459:
0460: private void onRefreshButton() {
0461: if (context == null || context.getRoots().size() == 0) {
0462: return;
0463: }
0464:
0465: if (executeStatusSupport != null) {
0466: executeStatusSupport.cancel();
0467: executeStatusSupport = null;
0468: }
0469:
0470: LifecycleManager.getDefault().saveAll();
0471: RequestProcessor rp = Subversion.getInstance()
0472: .getRequestProcessor();
0473: executeStatusSupport = new SvnProgressSupport() {
0474: public void perform() {
0475: StatusAction.executeStatus(context, this );
0476: SwingUtilities.invokeLater(new Runnable() {
0477: public void run() {
0478: refreshSetups();
0479: }
0480:
0481: });
0482: }
0483: };
0484: SVNUrl url;
0485: try {
0486: url = ContextAction.getSvnUrl(context);
0487: } catch (SVNClientException ex) {
0488: SvnClientExceptionHandler.notifyException(ex, true, true);
0489: return;
0490: }
0491: executeStatusSupport.start(rp, url, NbBundle.getMessage(
0492: MultiDiffPanel.class, "MSG_Refresh_Progress"));
0493: }
0494:
0495: private void onUpdateButton() {
0496: UpdateAction.performUpdate(context, contextName);
0497: }
0498:
0499: private void onCommitButton() {
0500: LifecycleManager.getDefault().saveAll();
0501: CommitAction.commit(contextName, context);
0502: }
0503:
0504: /** Next that is driven by visibility. It continues to next not yet visible difference. */
0505: private void onNextButton() {
0506: if (showingFileTable()) {
0507: currentIndex = fileTable.getSelectedIndex();
0508: currentModelIndex = fileTable.getSelectedModelIndex();
0509: }
0510:
0511: DiffController view = setups[currentModelIndex].getView();
0512: int currentDifferenceIndex = view != null ? view
0513: .getDifferenceIndex() : -1;
0514: if (view != null) {
0515: int visibleDiffernce = view.getDifferenceIndex();
0516: if (visibleDiffernce < view.getDifferenceCount() - 1) {
0517: currentDifferenceIndex = Math.max(
0518: currentDifferenceIndex, visibleDiffernce);
0519: }
0520: if (++currentDifferenceIndex >= view.getDifferenceCount()) {
0521: if (++currentIndex >= setups.length) {
0522: currentIndex--;
0523: } else {
0524: setDiffIndex(currentIndex, 0);
0525: }
0526: } else {
0527: view.setLocation(DiffController.DiffPane.Modified,
0528: DiffController.LocationType.DifferenceIndex,
0529: currentDifferenceIndex);
0530: }
0531: } else {
0532: if (++currentIndex >= setups.length)
0533: currentIndex = 0;
0534: setDiffIndex(currentIndex, 0);
0535: }
0536: refreshComponents();
0537: }
0538:
0539: private void onPrevButton() {
0540: DiffController view = setups[currentModelIndex].getView();
0541: if (view != null) {
0542: int currentDifferenceIndex = view.getDifferenceIndex();
0543: if (--currentDifferenceIndex < 0) {
0544: if (--currentIndex < 0) {
0545: currentIndex++;
0546: } else {
0547: setDiffIndex(currentIndex, -1);
0548: }
0549: } else {
0550: view.setLocation(DiffController.DiffPane.Modified,
0551: DiffController.LocationType.DifferenceIndex,
0552: currentDifferenceIndex);
0553: }
0554: } else {
0555: if (--currentIndex < 0)
0556: currentIndex = setups.length - 1;
0557: setDiffIndex(currentIndex, -1);
0558: }
0559: refreshComponents();
0560: }
0561:
0562: /**
0563: * @return setups, takes into account Local, Remote, All switch
0564: */
0565: public Collection<Setup> getSetups() {
0566: if (setups == null) {
0567: return Collections.emptySet();
0568: } else {
0569: return Arrays.asList(setups);
0570: }
0571: }
0572:
0573: public String getSetupDisplayName() {
0574: return contextName;
0575: }
0576:
0577: private void refreshSetups() {
0578: if (dpt != null) {
0579: prepareTask.cancel();
0580: }
0581:
0582: File[] files;
0583: switch (currentType) {
0584: case Setup.DIFFTYPE_LOCAL:
0585: displayStatuses = FileInformation.STATUS_LOCAL_CHANGE;
0586: break;
0587: case Setup.DIFFTYPE_REMOTE:
0588: displayStatuses = FileInformation.STATUS_REMOTE_CHANGE;
0589: break;
0590: case Setup.DIFFTYPE_ALL:
0591: displayStatuses = FileInformation.STATUS_LOCAL_CHANGE
0592: | FileInformation.STATUS_REMOTE_CHANGE;
0593: break;
0594: default:
0595: throw new IllegalStateException("Unknown DIFF type:"
0596: + currentType); // NOI18N
0597: }
0598: files = SvnUtils.getModifiedFiles(context, displayStatuses);
0599:
0600: setups = computeSetups(files);
0601: boolean propertyColumnVisible = false;
0602: for (Setup setup : setups) {
0603: if (setup.getPropertyName() != null) {
0604: propertyColumnVisible = true;
0605: break;
0606: }
0607: }
0608: fileTable.setColumns(propertyColumnVisible ? new String[] {
0609: DiffNode.COLUMN_NAME_NAME,
0610: DiffNode.COLUMN_NAME_PROPERTY,
0611: DiffNode.COLUMN_NAME_STATUS,
0612: DiffNode.COLUMN_NAME_LOCATION } : new String[] {
0613: DiffNode.COLUMN_NAME_NAME, DiffNode.COLUMN_NAME_STATUS,
0614: DiffNode.COLUMN_NAME_LOCATION });
0615: fileTable.setTableModel(setupToNodes(setups));
0616:
0617: if (setups.length == 0) {
0618: String noContentLabel;
0619: switch (currentType) {
0620: case Setup.DIFFTYPE_LOCAL:
0621: noContentLabel = NbBundle.getMessage(
0622: MultiDiffPanel.class,
0623: "MSG_DiffPanel_NoLocalChanges");
0624: break;
0625: case Setup.DIFFTYPE_REMOTE:
0626: noContentLabel = NbBundle.getMessage(
0627: MultiDiffPanel.class,
0628: "MSG_DiffPanel_NoRemoteChanges");
0629: break;
0630: case Setup.DIFFTYPE_ALL:
0631: noContentLabel = NbBundle.getMessage(
0632: MultiDiffPanel.class,
0633: "MSG_DiffPanel_NoAllChanges");
0634: break;
0635: default:
0636: throw new IllegalStateException("Unknown DIFF type:"
0637: + currentType); // NOI18N
0638: }
0639: setups = null;
0640: fileTable.setTableModel(new Node[0]);
0641: fileTable.getComponent().setEnabled(false);
0642: fileTable.getComponent().setPreferredSize(null);
0643: Dimension dim = fileTable.getComponent().getPreferredSize();
0644: fileTable.getComponent().setPreferredSize(
0645: new Dimension(dim.width + 1, dim.height));
0646: diffView = null;
0647: diffView = new NoContentPanel(noContentLabel);
0648: setBottomComponent();
0649: nextAction.setEnabled(false);
0650: prevAction.setEnabled(false);
0651: revalidate();
0652: repaint();
0653: } else {
0654: fileTable.getComponent().setEnabled(true);
0655: fileTable.getComponent().setPreferredSize(null);
0656: Dimension dim = fileTable.getComponent().getPreferredSize();
0657: fileTable.getComponent().setPreferredSize(
0658: new Dimension(dim.width + 1, dim.height));
0659: setDiffIndex(0, 0);
0660: dpt = new DiffPrepareTask(setups);
0661: prepareTask = RequestProcessor.getDefault().post(dpt);
0662: }
0663: }
0664:
0665: private Setup[] computeSetups(File[] files) {
0666: List<Setup> newSetups = new ArrayList<Setup>(files.length);
0667: for (int i = 0; i < files.length; i++) {
0668: File file = files[i];
0669: if (!file.isDirectory()) {
0670: Setup setup = new Setup(file, null, currentType);
0671: setup.setNode(new DiffNode(setup, displayStatuses));
0672: newSetups.add(setup);
0673: }
0674: addPropertiesSetups(file, newSetups);
0675: }
0676: Collections.sort(newSetups, new SetupsComparator());
0677: return newSetups.toArray(new Setup[newSetups.size()]);
0678: }
0679:
0680: private void addPropertiesSetups(File base, List<Setup> newSetups) {
0681: if (currentType == Setup.DIFFTYPE_REMOTE)
0682: return;
0683:
0684: DiffProvider diffAlgorithm = (DiffProvider) Lookup.getDefault()
0685: .lookup(DiffProvider.class);
0686: PropertiesClient client = new PropertiesClient(base);
0687: try {
0688: Map<String, byte[]> baseProps = client.getBaseProperties();
0689: Map<String, byte[]> localProps = client.getProperties();
0690:
0691: Set<String> allProps = new TreeSet<String>(localProps
0692: .keySet());
0693: allProps.addAll(baseProps.keySet());
0694: for (String key : allProps) {
0695: boolean isBase = baseProps.containsKey(key);
0696: boolean isLocal = localProps.containsKey(key);
0697: boolean propertiesDiffer = true;
0698: if (isBase && isLocal) {
0699: Property p1 = new Property(baseProps.get(key));
0700: Property p2 = new Property(localProps.get(key));
0701: Difference[] diffs = diffAlgorithm.computeDiff(p1
0702: .toReader(), p2.toReader());
0703: propertiesDiffer = (diffs.length != 0);
0704: }
0705: if (propertiesDiffer) {
0706: Setup setup = new Setup(base, key, currentType);
0707: setup.setNode(new DiffNode(setup, displayStatuses));
0708: newSetups.add(setup);
0709: }
0710: }
0711: } catch (IOException e) {
0712: Subversion.LOG.log(Level.INFO, null, e);
0713: }
0714: }
0715:
0716: private Node[] setupToNodes(Setup[] setups) {
0717: List<Node> nodes = new ArrayList<Node>(setups.length);
0718: for (Setup setup : setups) {
0719: nodes.add(setup.getNode());
0720: }
0721: return (Node[]) nodes.toArray(new Node[nodes.size()]);
0722: }
0723:
0724: private void onDiffTypeChanged() {
0725: if (localToggle.isSelected()) {
0726: if (currentType == Setup.DIFFTYPE_LOCAL)
0727: return;
0728: currentType = Setup.DIFFTYPE_LOCAL;
0729: } else if (remoteToggle.isSelected()) {
0730: if (currentType == Setup.DIFFTYPE_REMOTE)
0731: return;
0732: currentType = Setup.DIFFTYPE_REMOTE;
0733: } else if (allToggle.isSelected()) {
0734: if (currentType == Setup.DIFFTYPE_ALL)
0735: return;
0736: currentType = Setup.DIFFTYPE_ALL;
0737: }
0738: refreshSetups();
0739: }
0740:
0741: public void propertyChange(PropertyChangeEvent evt) {
0742: if (DiffController.PROP_DIFFERENCES.equals(evt
0743: .getPropertyName())) {
0744: refreshComponents();
0745: }
0746: }
0747:
0748: private class DiffPrepareTask implements Runnable {
0749:
0750: private final Setup[] prepareSetups;
0751:
0752: public DiffPrepareTask(Setup[] prepareSetups) {
0753: this .prepareSetups = prepareSetups;
0754: }
0755:
0756: public void run() {
0757: for (int i = 0; i < prepareSetups.length; i++) {
0758: if (prepareSetups != setups)
0759: return;
0760: try {
0761: prepareSetups[i].initSources(); // slow network I/O
0762: final int fi = i;
0763: StreamSource ss1 = prepareSetups[fi]
0764: .getFirstSource();
0765: StreamSource ss2 = prepareSetups[fi]
0766: .getSecondSource();
0767: final DiffController view = DiffController.create(
0768: ss1, ss2); // possibly executing slow external diff
0769: view.addPropertyChangeListener(MultiDiffPanel.this );
0770: SwingUtilities.invokeLater(new Runnable() {
0771: public void run() {
0772: prepareSetups[fi].setView(view);
0773: if (prepareSetups != setups) {
0774: return;
0775: }
0776: if (currentModelIndex == fi) {
0777: setDiffIndex(currentIndex, 0);
0778: }
0779: updateSplitLocation();
0780: }
0781: });
0782: } catch (IOException e) {
0783: Subversion.LOG.log(Level.SEVERE, null, e);
0784: }
0785: }
0786: }
0787: }
0788:
0789: private static class SetupsComparator implements Comparator<Setup> {
0790:
0791: private SvnUtils.ByImportanceComparator delegate = new SvnUtils.ByImportanceComparator();
0792: private FileStatusCache cache;
0793:
0794: public SetupsComparator() {
0795: cache = Subversion.getInstance().getStatusCache();
0796: }
0797:
0798: public int compare(Setup setup1, Setup setup2) {
0799: int cmp = delegate.compare(cache.getStatus(setup1
0800: .getBaseFile()), cache.getStatus(setup2
0801: .getBaseFile()));
0802: if (cmp == 0) {
0803: return setup1.getBaseFile().getName()
0804: .compareToIgnoreCase(
0805: setup2.getBaseFile().getName());
0806: }
0807: return cmp;
0808: }
0809: }
0810:
0811: private class RefreshViewTask implements Runnable {
0812: public void run() {
0813: SwingUtilities.invokeLater(new Runnable() {
0814: public void run() {
0815: refreshSetups();
0816: }
0817: });
0818: }
0819: }
0820:
0821: /** This method is called from within the constructor to
0822: * initialize the form.
0823: * WARNING: Do NOT modify this code. The content of this method is
0824: * always regenerated by the Form Editor.
0825: */
0826: // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
0827: private void initComponents() {
0828:
0829: controlsToolBar = new javax.swing.JToolBar();
0830: allToggle = new javax.swing.JToggleButton();
0831: jPanel3 = new javax.swing.JPanel();
0832: localToggle = new javax.swing.JToggleButton();
0833: jPanel4 = new javax.swing.JPanel();
0834: remoteToggle = new javax.swing.JToggleButton();
0835: jPanel1 = new javax.swing.JPanel();
0836: nextButton = new javax.swing.JButton();
0837: prevButton = new javax.swing.JButton();
0838: jPanel2 = new javax.swing.JPanel();
0839: refreshButton = new javax.swing.JButton();
0840: updateButton = new javax.swing.JButton();
0841: jPanel5 = new javax.swing.JPanel();
0842: commitButton = new javax.swing.JButton();
0843: splitPane = new javax.swing.JSplitPane();
0844:
0845: controlsToolBar.setFloatable(false);
0846: controlsToolBar.setRollover(true);
0847:
0848: allToggle
0849: .setIcon(new javax.swing.ImageIcon(
0850: getClass()
0851: .getResource(
0852: "/org/netbeans/modules/subversion/resources/icons/remote_vs_local.png"))); // NOI18N
0853: allToggle.setToolTipText(org.openide.util.NbBundle.getMessage(
0854: MultiDiffPanel.class, "CTL_DiffPanel_All_Tooltip")); // NOI18N
0855: allToggle.setFocusable(false);
0856: allToggle
0857: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0858: allToggle
0859: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0860: controlsToolBar.add(allToggle);
0861:
0862: jPanel3.setMaximumSize(new java.awt.Dimension(12, 32767));
0863:
0864: org.jdesktop.layout.GroupLayout jPanel3Layout = new org.jdesktop.layout.GroupLayout(
0865: jPanel3);
0866: jPanel3.setLayout(jPanel3Layout);
0867: jPanel3Layout.setHorizontalGroup(jPanel3Layout
0868: .createParallelGroup(
0869: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0870: 12, Short.MAX_VALUE));
0871: jPanel3Layout.setVerticalGroup(jPanel3Layout
0872: .createParallelGroup(
0873: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0874: 23, Short.MAX_VALUE));
0875:
0876: controlsToolBar.add(jPanel3);
0877:
0878: localToggle
0879: .setIcon(new javax.swing.ImageIcon(
0880: getClass()
0881: .getResource(
0882: "/org/netbeans/modules/subversion/resources/icons/local_vs_local.png"))); // NOI18N
0883: localToggle.setToolTipText(org.openide.util.NbBundle
0884: .getMessage(MultiDiffPanel.class,
0885: "CTL_DiffPanel_Local_Tooltip")); // NOI18N
0886: localToggle.setFocusable(false);
0887: localToggle
0888: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0889: localToggle
0890: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0891: controlsToolBar.add(localToggle);
0892:
0893: jPanel4.setMaximumSize(new java.awt.Dimension(12, 32767));
0894:
0895: org.jdesktop.layout.GroupLayout jPanel4Layout = new org.jdesktop.layout.GroupLayout(
0896: jPanel4);
0897: jPanel4.setLayout(jPanel4Layout);
0898: jPanel4Layout.setHorizontalGroup(jPanel4Layout
0899: .createParallelGroup(
0900: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0901: 12, Short.MAX_VALUE));
0902: jPanel4Layout.setVerticalGroup(jPanel4Layout
0903: .createParallelGroup(
0904: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0905: 23, Short.MAX_VALUE));
0906:
0907: controlsToolBar.add(jPanel4);
0908:
0909: remoteToggle
0910: .setIcon(new javax.swing.ImageIcon(
0911: getClass()
0912: .getResource(
0913: "/org/netbeans/modules/subversion/resources/icons/remote_vs_remote.png"))); // NOI18N
0914: remoteToggle.setToolTipText(org.openide.util.NbBundle
0915: .getMessage(MultiDiffPanel.class,
0916: "CTL_DiffPanel_Remote_Tooltip")); // NOI18N
0917: remoteToggle.setFocusable(false);
0918: remoteToggle
0919: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0920: remoteToggle
0921: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0922: controlsToolBar.add(remoteToggle);
0923:
0924: jPanel1.setMaximumSize(new java.awt.Dimension(80, 32767));
0925:
0926: org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(
0927: jPanel1);
0928: jPanel1.setLayout(jPanel1Layout);
0929: jPanel1Layout.setHorizontalGroup(jPanel1Layout
0930: .createParallelGroup(
0931: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0932: 80, Short.MAX_VALUE));
0933: jPanel1Layout.setVerticalGroup(jPanel1Layout
0934: .createParallelGroup(
0935: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0936: 23, Short.MAX_VALUE));
0937:
0938: controlsToolBar.add(jPanel1);
0939:
0940: nextButton
0941: .setIcon(new javax.swing.ImageIcon(
0942: getClass()
0943: .getResource(
0944: "/org/netbeans/modules/subversion/resources/icons/diff-next.png"))); // NOI18N
0945: nextButton.setToolTipText(org.openide.util.NbBundle.getMessage(
0946: MultiDiffPanel.class, "CTL_DiffPanel_Next_Tooltip")); // NOI18N
0947: nextButton.setFocusable(false);
0948: nextButton
0949: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0950: nextButton
0951: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0952: controlsToolBar.add(nextButton);
0953:
0954: prevButton
0955: .setIcon(new javax.swing.ImageIcon(
0956: getClass()
0957: .getResource(
0958: "/org/netbeans/modules/subversion/resources/icons/diff-prev.png"))); // NOI18N
0959: prevButton.setToolTipText(org.openide.util.NbBundle.getMessage(
0960: MultiDiffPanel.class, "CTL_DiffPanel_Prev_Tooltip")); // NOI18N
0961: prevButton.setFocusable(false);
0962: prevButton
0963: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0964: prevButton
0965: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0966: controlsToolBar.add(prevButton);
0967:
0968: jPanel2.setMaximumSize(new java.awt.Dimension(30, 32767));
0969:
0970: org.jdesktop.layout.GroupLayout jPanel2Layout = new org.jdesktop.layout.GroupLayout(
0971: jPanel2);
0972: jPanel2.setLayout(jPanel2Layout);
0973: jPanel2Layout.setHorizontalGroup(jPanel2Layout
0974: .createParallelGroup(
0975: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0976: 30, Short.MAX_VALUE));
0977: jPanel2Layout.setVerticalGroup(jPanel2Layout
0978: .createParallelGroup(
0979: org.jdesktop.layout.GroupLayout.LEADING).add(0,
0980: 23, Short.MAX_VALUE));
0981:
0982: controlsToolBar.add(jPanel2);
0983:
0984: refreshButton
0985: .setIcon(new javax.swing.ImageIcon(
0986: getClass()
0987: .getResource(
0988: "/org/netbeans/modules/subversion/resources/icons/refresh.png"))); // NOI18N
0989: refreshButton.setToolTipText(org.openide.util.NbBundle
0990: .getMessage(MultiDiffPanel.class,
0991: "refreshButton.toolTipText")); // NOI18N
0992: refreshButton.setFocusable(false);
0993: refreshButton
0994: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
0995: refreshButton
0996: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
0997: refreshButton
0998: .addActionListener(new java.awt.event.ActionListener() {
0999: public void actionPerformed(
1000: java.awt.event.ActionEvent evt) {
1001: refreshButtonActionPerformed(evt);
1002: }
1003: });
1004: controlsToolBar.add(refreshButton);
1005:
1006: updateButton
1007: .setIcon(new javax.swing.ImageIcon(
1008: getClass()
1009: .getResource(
1010: "/org/netbeans/modules/subversion/resources/icons/update.png"))); // NOI18N
1011: updateButton.setFocusable(false);
1012: updateButton
1013: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1014: updateButton
1015: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1016: updateButton
1017: .addActionListener(new java.awt.event.ActionListener() {
1018: public void actionPerformed(
1019: java.awt.event.ActionEvent evt) {
1020: updateButtonActionPerformed(evt);
1021: }
1022: });
1023: controlsToolBar.add(updateButton);
1024:
1025: jPanel5.setMaximumSize(new java.awt.Dimension(20, 32767));
1026:
1027: org.jdesktop.layout.GroupLayout jPanel5Layout = new org.jdesktop.layout.GroupLayout(
1028: jPanel5);
1029: jPanel5.setLayout(jPanel5Layout);
1030: jPanel5Layout.setHorizontalGroup(jPanel5Layout
1031: .createParallelGroup(
1032: org.jdesktop.layout.GroupLayout.LEADING).add(0,
1033: 20, Short.MAX_VALUE));
1034: jPanel5Layout.setVerticalGroup(jPanel5Layout
1035: .createParallelGroup(
1036: org.jdesktop.layout.GroupLayout.LEADING).add(0,
1037: 23, Short.MAX_VALUE));
1038:
1039: controlsToolBar.add(jPanel5);
1040:
1041: commitButton
1042: .setIcon(new javax.swing.ImageIcon(
1043: getClass()
1044: .getResource(
1045: "/org/netbeans/modules/subversion/resources/icons/commit.png"))); // NOI18N
1046: commitButton.setToolTipText(org.openide.util.NbBundle
1047: .getMessage(MultiDiffPanel.class,
1048: "MSG_CommitDiff_Tooltip")); // NOI18N
1049: commitButton.setFocusable(false);
1050: commitButton
1051: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
1052: commitButton
1053: .setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
1054: controlsToolBar.add(commitButton);
1055:
1056: splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
1057:
1058: org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(
1059: this );
1060: this .setLayout(layout);
1061: layout.setHorizontalGroup(layout.createParallelGroup(
1062: org.jdesktop.layout.GroupLayout.LEADING).add(
1063: controlsToolBar,
1064: org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 716,
1065: Short.MAX_VALUE).add(splitPane,
1066: org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 716,
1067: Short.MAX_VALUE));
1068: layout
1069: .setVerticalGroup(layout
1070: .createParallelGroup(
1071: org.jdesktop.layout.GroupLayout.LEADING)
1072: .add(
1073: layout
1074: .createSequentialGroup()
1075: .add(
1076: controlsToolBar,
1077: org.jdesktop.layout.GroupLayout.PREFERRED_SIZE,
1078: 25,
1079: org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
1080: .addPreferredGap(
1081: org.jdesktop.layout.LayoutStyle.RELATED)
1082: .add(
1083: splitPane,
1084: org.jdesktop.layout.GroupLayout.DEFAULT_SIZE,
1085: 331, Short.MAX_VALUE)));
1086: }// </editor-fold>//GEN-END:initComponents
1087:
1088: private void updateButtonActionPerformed(
1089: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateButtonActionPerformed
1090: onUpdateButton();
1091: }//GEN-LAST:event_updateButtonActionPerformed
1092:
1093: private void refreshButtonActionPerformed(
1094: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
1095: onRefreshButton();
1096: }//GEN-LAST:event_refreshButtonActionPerformed
1097:
1098: // Variables declaration - do not modify//GEN-BEGIN:variables
1099: private javax.swing.JToggleButton allToggle;
1100: private javax.swing.JButton commitButton;
1101: private javax.swing.JToolBar controlsToolBar;
1102: private javax.swing.JPanel jPanel1;
1103: private javax.swing.JPanel jPanel2;
1104: private javax.swing.JPanel jPanel3;
1105: private javax.swing.JPanel jPanel4;
1106: private javax.swing.JPanel jPanel5;
1107: private javax.swing.JToggleButton localToggle;
1108: private javax.swing.JButton nextButton;
1109: private javax.swing.JButton prevButton;
1110: private javax.swing.JButton refreshButton;
1111: private javax.swing.JToggleButton remoteToggle;
1112: private javax.swing.JSplitPane splitPane;
1113: private javax.swing.JButton updateButton;
1114:
1115: // End of variables declaration//GEN-END:variables
1116:
1117: /** Interprets property blob. */
1118: static final class Property {
1119: final byte[] value;
1120:
1121: Property(Object value) {
1122: this .value = (byte[]) value;
1123: }
1124:
1125: String getMIME() {
1126: return "text/plain"; // NOI18N
1127: }
1128:
1129: Reader toReader() {
1130: if (SvnUtils.isBinary(value)) {
1131: return new StringReader(NbBundle.getMessage(
1132: MultiDiffPanel.class, "LBL_Diff_NoBinaryDiff")); // hexa-flexa txt? // NOI18N
1133: } else {
1134: try {
1135: return new InputStreamReader(
1136: new ByteArrayInputStream(value), "utf8"); // NOI18N
1137: } catch (UnsupportedEncodingException ex) {
1138: Subversion.LOG.log(Level.SEVERE, null, ex);
1139: return new StringReader("[ERROR: "
1140: + ex.getLocalizedMessage() + "]"); // NOI18N
1141: }
1142: }
1143: }
1144: }
1145: }
|