/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions). * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.nbgit.ui.log; import java.awt.Component; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.JComponent; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import org.netbeans.api.diff.Diff; import org.netbeans.api.diff.DiffView; import org.nbgit.ui.diff.DiffSetupSource; import org.nbgit.ui.diff.DiffStreamSource; import org.netbeans.modules.versioning.util.NoContentPanel; import org.openide.ErrorManager; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.windows.TopComponent; /** * Shows Search History results in a table with Diff pane below it. * * @author Maros Sandor */ class DiffResultsView implements AncestorListener, PropertyChangeListener, DiffSetupSource { private final SearchHistoryPanel parent; private DiffTreeTable treeView; private JSplitPane diffView; private ShowDiffTask currentTask; private RequestProcessor.Task currentShowDiffTask; private DiffView currentDiff; private int currentDifferenceIndex; private int currentIndex; private boolean dividerSet; private List<RepositoryRevision> results; private static final RequestProcessor rp = new RequestProcessor("GitDiff", 1, true); // NOI18N public DiffResultsView(SearchHistoryPanel parent, List<RepositoryRevision> results) { this.parent = parent; this.results = results; treeView = new DiffTreeTable(parent); treeView.setResults(results); treeView.addAncestorListener(this); diffView = new JSplitPane(JSplitPane.VERTICAL_SPLIT); diffView.setTopComponent(treeView); setBottomComponent(new NoContentPanel(NbBundle.getMessage(DiffResultsView.class, "MSG_DiffPanel_NoRevisions"))); // NOI18N } public void ancestorAdded(AncestorEvent event) { ExplorerManager em = ExplorerManager.find(treeView); em.addPropertyChangeListener(this); if (!dividerSet) { SwingUtilities.invokeLater(new Runnable() { public void run() { dividerSet = true; diffView.setDividerLocation(0.33); } }); } } public void ancestorMoved(AncestorEvent event) { } public void ancestorRemoved(AncestorEvent event) { ExplorerManager em = ExplorerManager.find(treeView); em.removePropertyChangeListener(this); cancelBackgroundTasks(); } public void propertyChange(PropertyChangeEvent evt) { if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) { final Node[] nodes = (Node[]) evt.getNewValue(); currentDifferenceIndex = 0; if (nodes.length == 0) { showDiffError(NbBundle.getMessage(DiffResultsView.class, "MSG_DiffPanel_NoRevisions")); // NOI18N parent.refreshComponents(false); return; } else if (nodes.length > 2) { showDiffError(NbBundle.getMessage(DiffResultsView.class, "MSG_DiffPanel_TooManyRevisions")); // NOI18N parent.refreshComponents(false); return; } // invoked asynchronously becase treeView.getSelection() may not be ready yet Runnable runnable = new Runnable() { public void run() { RepositoryRevision container1 = nodes[0].getLookup().lookup(RepositoryRevision.class); RepositoryRevision.Event r1 = nodes[0].getLookup().lookup(RepositoryRevision.Event.class); try { currentIndex = treeView.getSelection()[0]; if (nodes.length == 1) { if (container1 != null) { showContainerDiff(container1, onSelectionshowLastDifference); } else if (r1 != null) { showRevisionDiff(r1, onSelectionshowLastDifference); } } else if (nodes.length == 2) { RepositoryRevision.Event r2 = nodes[1].getLookup().lookup(RepositoryRevision.Event.class); if (r2.getFile() == null || !r2.getFile().equals(r1.getFile())) { throw new Exception(); } showDiff(r1, r1.getLogInfoHeader().getRevision(), r2.getLogInfoHeader().getRevision(), false); } } catch (Exception e) { showDiffError(NbBundle.getMessage(DiffResultsView.class, "MSG_DiffPanel_IllegalSelection")); // NOI18N parent.refreshComponents(false); return; } } }; SwingUtilities.invokeLater(runnable); } } public Collection getSetups() { Node[] nodes = TopComponent.getRegistry().getActivatedNodes(); if (nodes.length == 0) { return parent.getSetups(results.toArray(new RepositoryRevision[results.size()]), new RepositoryRevision.Event[0]); } Set<RepositoryRevision.Event> events = new HashSet<RepositoryRevision.Event>(); Set<RepositoryRevision> revisions = new HashSet<RepositoryRevision>(); for (Node n : nodes) { RevisionNode node = (RevisionNode) n; if (node.getEvent() != null) { events.add(node.getEvent()); } else { revisions.add(node.getContainer()); } } return parent.getSetups(revisions.toArray(new RepositoryRevision[revisions.size()]), events.toArray(new RepositoryRevision.Event[events.size()])); } public String getSetupDisplayName() { return null; } private void showDiffError(String s) { setBottomComponent(new NoContentPanel(s)); } private void setBottomComponent(Component component) { int dl = diffView.getDividerLocation(); diffView.setBottomComponent(component); diffView.setDividerLocation(dl); } private void showDiff(RepositoryRevision.Event header, String revision1, String revision2, boolean showLastDifference) { synchronized (this) { cancelBackgroundTasks(); currentTask = new ShowDiffTask(header, revision1, revision2, showLastDifference); currentShowDiffTask = rp.create(currentTask); currentShowDiffTask.schedule(0); } } private synchronized void cancelBackgroundTasks() { if (currentShowDiffTask != null && !currentShowDiffTask.isFinished()) { currentShowDiffTask.cancel(); // it almost always late it's enqueued, so: currentTask.cancel(); } } private boolean onSelectionshowLastDifference; private void setDiffIndex(int idx, boolean showLastDifference) { currentIndex = idx; onSelectionshowLastDifference = showLastDifference; treeView.setSelection(idx); } private void showRevisionDiff(RepositoryRevision.Event rev, boolean showLastDifference) { if (rev.getFile() == null) { return; } String id = rev.getLogInfoHeader().getRevision(); showDiff(rev, id + "^", id, showLastDifference); } private void showContainerDiff(RepositoryRevision container, boolean showLastDifference) { List<RepositoryRevision.Event> revs = container.getEvents(); RepositoryRevision.Event newest = null; //try to get the root File[] roots = parent.getRoots(); for (File root : roots) { for (RepositoryRevision.Event evt : revs) { if (root.equals(evt.getFile())) { newest = evt; } } } if (newest == null) { newest = revs.get(0); } if (newest.getFile() == null) { return; } String rev = newest.getLogInfoHeader().getRevision(); showDiff(newest, rev + "^", rev, showLastDifference); } void onNextButton() { if (currentDiff != null) { if (++currentDifferenceIndex >= currentDiff.getDifferenceCount()) { if (++currentIndex >= treeView.getRowCount()) { currentIndex = 0; } setDiffIndex(currentIndex, false); } else { currentDiff.setCurrentDifference(currentDifferenceIndex); } } else { if (++currentIndex >= treeView.getRowCount()) { currentIndex = 0; } setDiffIndex(currentIndex, false); } } void onPrevButton() { if (currentDiff != null) { if (--currentDifferenceIndex < 0) { if (--currentIndex < 0) { currentIndex = treeView.getRowCount() - 1; } setDiffIndex(currentIndex, true); } else { currentDiff.setCurrentDifference(currentDifferenceIndex); } } else { if (--currentIndex < 0) { currentIndex = treeView.getRowCount() - 1; } setDiffIndex(currentIndex, true); } } boolean isNextEnabled() { if (currentDiff != null) { return currentIndex < treeView.getRowCount() - 1 || currentDifferenceIndex < currentDiff.getDifferenceCount() - 1; } else { return false; } } boolean isPrevEnabled() { return currentIndex > 0 || currentDifferenceIndex > 0; } /** * Selects given revision in the view as if done by the user. * * @param revision revision to select */ void select(RepositoryRevision.Event revision) { treeView.requestFocusInWindow(); treeView.setSelection(revision); } void select(RepositoryRevision container) { treeView.requestFocusInWindow(); treeView.setSelection(container); } private class ShowDiffTask implements Runnable, Cancellable { private final RepositoryRevision.Event header; private final String revision1; private final String revision2; private boolean showLastDifference; private volatile boolean cancelled; public ShowDiffTask(RepositoryRevision.Event header, String revision1, String revision2, boolean showLastDifference) { this.header = header; this.revision1 = revision1; this.revision2 = revision2; this.showLastDifference = showLastDifference; } public void run() { final Diff diff = Diff.getDefault(); final DiffStreamSource s1 = new DiffStreamSource(header.getFile(), revision1, revision1); final DiffStreamSource s2 = new DiffStreamSource(header.getFile(), revision2, revision2); // it's enqueued at ClientRuntime queue and does not return until previous request handled s1.getMIMEType(); // triggers s1.init() if (cancelled) { return; } s2.getMIMEType(); // triggers s2.init() if (cancelled) { return; } if (currentTask != this) { return; } SwingUtilities.invokeLater(new Runnable() { public void run() { try { if (cancelled) { return; } final DiffView view = diff.createDiff(s1, s2); if (currentTask == ShowDiffTask.this) { currentDiff = view; setBottomComponent(currentDiff.getComponent()); if (currentDiff.getDifferenceCount() > 0) { currentDifferenceIndex = showLastDifference ? currentDiff.getDifferenceCount() - 1 : 0; currentDiff.setCurrentDifference(currentDifferenceIndex); } parent.refreshComponents(false); } } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } }); } public boolean cancel() { cancelled = true; return true; } } public JComponent getComponent() { return diffView; } }