/*
* 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.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.nbgit.Git;
import org.nbgit.GitModuleConfig;
import org.nbgit.ui.diff.DiffSetupSource;
import org.nbgit.ui.diff.Setup;
import org.netbeans.modules.versioning.util.NoContentPanel;
import org.openide.awt.Mnemonics;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.windows.TopComponent;
/**
* Contains all components of the Search History panel.
*
* @author Maros Sandor
*/
class SearchHistoryPanel extends javax.swing.JPanel implements ExplorerManager.Provider, PropertyChangeListener, ActionListener, DiffSetupSource, DocumentListener {
private final File[] roots;
private final String repositoryUrl;
private final SearchCriteriaPanel criteria;
private Divider divider;
private Action searchAction;
private SearchExecutor currentSearch;
private RequestProcessor.Task currentSearchTask;
private boolean criteriaVisible;
private boolean searchInProgress;
private List<RepositoryRevision> results;
private SummaryView summaryView;
private DiffResultsView diffView;
private boolean bOutSearch;
private boolean bIncomingSearch;
private AbstractAction nextAction;
private AbstractAction prevAction;
/** Creates new form SearchHistoryPanel */
public SearchHistoryPanel(File[] roots, SearchCriteriaPanel criteria) {
this.bOutSearch = false;
this.bIncomingSearch = false;
this.roots = roots;
this.repositoryUrl = null;
this.criteria = criteria;
criteriaVisible = true;
explorerManager = new ExplorerManager();
initComponents();
setupComponents();
refreshComponents(true);
}
public SearchHistoryPanel(String repositoryUrl, File localRoot, SearchCriteriaPanel criteria) {
this.bOutSearch = false;
this.bIncomingSearch = false;
this.repositoryUrl = repositoryUrl;
this.roots = new File[]{localRoot};
this.criteria = criteria;
criteriaVisible = true;
explorerManager = new ExplorerManager();
initComponents();
setupComponents();
refreshComponents(true);
}
void setOutSearch() {
criteria.setForOut();
bOutSearch = true;
divider.setVisible(false);
tbSummary.setToolTipText(NbBundle.getMessage(SearchHistoryPanel.class, "TT_OutSummary"));
showMergesChkBox.setToolTipText(NbBundle.getMessage(SearchHistoryPanel.class, "TT_OutShowMerges"));
tbDiff.setToolTipText(NbBundle.getMessage(SearchHistoryPanel.class, "TT_OutShowDiff"));
}
boolean isOutSearch() {
return bOutSearch;
}
boolean isShowMerges() {
return showMergesChkBox.isSelected();
}
void setIncomingSearch() {
criteria.setForIncoming();
bIncomingSearch = true;
tbDiff.setVisible(false);
bNext.setVisible(false);
bPrev.setVisible(false);
showMergesChkBox.setToolTipText(NbBundle.getMessage(SearchHistoryPanel.class, "TT_IncomingShowMerges"));
tbSummary.setToolTipText(NbBundle.getMessage(SearchHistoryPanel.class, "TT_IncomingSummary"));
}
boolean isIncomingSearch() {
return bIncomingSearch;
}
void setSearchCriteria(boolean b) {
criteriaVisible = b;
refreshComponents(false);
}
private void setupComponents() {
remove(jPanel1);
divider = new Divider(this);
java.awt.GridBagConstraints gridBagConstraints;
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridy = 2;
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 0);
add(divider, gridBagConstraints);
searchCriteriaPanel.add(criteria);
searchAction = new AbstractAction(NbBundle.getMessage(SearchHistoryPanel.class, "CTL_Search")) { // NOI18N
{
putValue(Action.SHORT_DESCRIPTION, NbBundle.getMessage(SearchHistoryPanel.class, "TT_Search")); // NOI18N
}
public void actionPerformed(ActionEvent e) {
search();
}
};
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "search"); // NOI18N
getActionMap().put("search", searchAction); // NOI18N
bSearch.setAction(searchAction);
Mnemonics.setLocalizedText(bSearch, NbBundle.getMessage(SearchHistoryPanel.class, "CTL_Search")); // NOI18N
Dimension d1 = tbSummary.getPreferredSize();
Dimension d2 = tbDiff.getPreferredSize();
if (d1.width > d2.width) {
tbDiff.setPreferredSize(d1);
}
nextAction = new AbstractAction(null, new javax.swing.ImageIcon(getClass().getResource("/org/nbgit/resources/icons/diff-next.png"))) { // NOI18N
{
putValue(Action.SHORT_DESCRIPTION, java.util.ResourceBundle.getBundle("org/nbgit/ui/diff/Bundle"). // NOI18N
getString("CTL_DiffPanel_Next_Tooltip")); // NOI18N
}
public void actionPerformed(ActionEvent e) {
diffView.onNextButton();
}
};
prevAction = new AbstractAction(null, new javax.swing.ImageIcon(getClass().getResource("/org/nbgit/resources/icons/diff-prev.png"))) { // NOI18N
{
putValue(Action.SHORT_DESCRIPTION, java.util.ResourceBundle.getBundle("org/nbgit/ui/diff/Bundle"). // NOI18N
getString("CTL_DiffPanel_Prev_Tooltip")); // NOI18N
}
public void actionPerformed(ActionEvent e) {
diffView.onPrevButton();
}
};
bNext.setAction(nextAction);
bPrev.setAction(prevAction);
criteria.tfFrom.getDocument().addDocumentListener(this);
criteria.tfTo.getDocument().addDocumentListener(this);
getActionMap().put("jumpNext", nextAction); // NOI18N
getActionMap().put("jumpPrev", prevAction); // NOI18N
showMergesChkBox.setSelected(GitModuleConfig.getDefault().getShowHistoryMerges());
showMergesChkBox.setOpaque(false);
}
public void actionPerformed(ActionEvent e) {
if (e.getID() == Divider.DIVIDER_CLICKED) {
criteriaVisible = !criteriaVisible;
refreshComponents(false);
}
}
private ExplorerManager explorerManager;
public void propertyChange(PropertyChangeEvent evt) {
if (ExplorerManager.PROP_SELECTED_NODES.equals(evt.getPropertyName())) {
TopComponent tc = (TopComponent) SwingUtilities.getAncestorOfClass(TopComponent.class, this);
if (tc == null) {
return;
}
tc.setActivatedNodes((Node[]) evt.getNewValue());
}
}
public void addNotify() {
super.addNotify();
explorerManager.addPropertyChangeListener(this);
}
public void removeNotify() {
explorerManager.removePropertyChangeListener(this);
super.removeNotify();
}
public ExplorerManager getExplorerManager() {
return explorerManager;
}
final void refreshComponents(boolean refreshResults) {
if (refreshResults) {
resultsPanel.removeAll();
if (results == null) {
if (searchInProgress) {
resultsPanel.add(new NoContentPanel(NbBundle.getMessage(SearchHistoryPanel.class, "LBL_SearchHistory_Searching"))); // NOI18N
} else {
resultsPanel.add(new NoContentPanel(NbBundle.getMessage(SearchHistoryPanel.class, "LBL_SearchHistory_NoResults"))); // NOI18N
}
} else {
if (tbSummary.isSelected()) {
if (summaryView == null) {
summaryView = new SummaryView(this, results);
}
resultsPanel.add(summaryView.getComponent());
} else {
if (diffView == null) {
diffView = new DiffResultsView(this, results);
}
resultsPanel.add(diffView.getComponent());
}
}
resultsPanel.revalidate();
resultsPanel.repaint();
}
nextAction.setEnabled(!tbSummary.isSelected() && diffView != null && diffView.isNextEnabled());
prevAction.setEnabled(!tbSummary.isSelected() && diffView != null && diffView.isPrevEnabled());
divider.setArrowDirection(criteriaVisible ? Divider.UP : Divider.DOWN);
searchCriteriaPanel.setVisible(criteriaVisible);
bSearch.setVisible(criteriaVisible);
revalidate();
repaint();
}
public void setResults(List<RepositoryRevision> newResults) {
setResults(newResults, false);
}
private void setResults(List<RepositoryRevision> newResults, boolean searching) {
this.results = newResults;
this.searchInProgress = searching;
summaryView = null;
diffView = null;
refreshComponents(true);
}
public String getRepositoryUrl() {
return repositoryUrl;
}
public String getSearchRepositoryRootUrl() {
if (repositoryUrl != null) {
return repositoryUrl;
}
File root = Git.getInstance().getTopmostManagedParent(roots[0]);
return root.toString();
}
public File[] getRoots() {
return roots;
}
public SearchCriteriaPanel getCriteria() {
return criteria;
}
private synchronized void search() {
if (currentSearchTask != null) {
currentSearchTask.cancel();
}
setResults(null, true);
currentSearch = new SearchExecutor(this);
currentSearchTask = RequestProcessor.getDefault().create(currentSearch);
currentSearchTask.schedule(0);
}
void executeSearch() {
search();
}
void showDiff(RepositoryRevision.Event revision) {
tbDiff.setSelected(true);
refreshComponents(true);
diffView.select(revision);
}
public void showDiff(RepositoryRevision container) {
tbDiff.setSelected(true);
refreshComponents(true);
diffView.select(container);
}
/**
* Return diff setup describing shown history.
* It return empty collection on non-atomic
* revision ranges. XXX move this logic to clients?
*/
public Collection getSetups() {
if (results == null) {
return Collections.EMPTY_SET;
}
if (tbDiff.isSelected()) {
return diffView.getSetups();
} else {
return summaryView.getSetups();
}
}
Collection getSetups(RepositoryRevision[] revisions, RepositoryRevision.Event[] events) {
long fromRevision = Long.MAX_VALUE;
long toRevision = Long.MIN_VALUE;
Set<File> filesToDiff = new HashSet<File>();
for (RepositoryRevision revision : revisions) {
long rev = 0; //Long.parseLong(revision.getLog().getRevision());
if (rev > toRevision) {
toRevision = rev;
}
if (rev < fromRevision) {
fromRevision = rev;
}
List<RepositoryRevision.Event> evs = revision.getEvents();
for (RepositoryRevision.Event event : evs) {
File file = event.getFile();
if (file != null) {
filesToDiff.add(file);
}
}
}
for (RepositoryRevision.Event event : events) {
long rev = 0; //Long.parseLong(event.getLogInfoHeader().getLog().getRevision());
if (rev > toRevision) {
toRevision = rev;
}
if (rev < fromRevision) {
fromRevision = rev;
}
if (event.getFile() != null) {
filesToDiff.add(event.getFile());
}
}
List<Setup> setups = new ArrayList<Setup>();
for (File file : filesToDiff) {
Setup setup = new Setup(file, Long.toString(fromRevision - 1), Long.toString(toRevision));
setups.add(setup);
}
return setups;
}
public String getSetupDisplayName() {
return null;
}
public static int compareRevisions(String r1, String r2) {
StringTokenizer st1 = new StringTokenizer(r1, "."); // NOI18N
StringTokenizer st2 = new StringTokenizer(r2, "."); // NOI18N
for (;;) {
if (!st1.hasMoreTokens()) {
return st2.hasMoreTokens() ? -1 : 0;
}
if (!st2.hasMoreTokens()) {
return st1.hasMoreTokens() ? 1 : 0;
}
int n1 = Integer.parseInt(st1.nextToken());
int n2 = Integer.parseInt(st2.nextToken());
if (n1 != n2) {
return n2 - n1;
}
}
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
buttonGroup1 = new javax.swing.ButtonGroup();
searchCriteriaPanel = new javax.swing.JPanel();
bSearch = new javax.swing.JButton();
jPanel1 = new javax.swing.JPanel();
jToolBar1 = new javax.swing.JToolBar();
tbSummary = new javax.swing.JToggleButton();
tbDiff = new javax.swing.JToggleButton();
jSeparator2 = new javax.swing.JSeparator();
bNext = new javax.swing.JButton();
bPrev = new javax.swing.JButton();
jSeparator3 = new javax.swing.JToolBar.Separator();
showMergesChkBox = new javax.swing.JCheckBox();
resultsPanel = new javax.swing.JPanel();
setBorder(javax.swing.BorderFactory.createEmptyBorder(8, 8, 0, 8));
setLayout(new java.awt.GridBagLayout());
searchCriteriaPanel.setLayout(new java.awt.BorderLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
gridBagConstraints.weightx = 1.0;
add(searchCriteriaPanel, gridBagConstraints);
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/nbgit/ui/log/Bundle"); // NOI18N
bSearch.setToolTipText(bundle.getString("TT_Search")); // NOI18N
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START;
add(bSearch, gridBagConstraints);
jPanel1.setPreferredSize(new java.awt.Dimension(10, 6));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridy = 2;
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.insets = new java.awt.Insets(0, 0, 2, 0);
add(jPanel1, gridBagConstraints);
jToolBar1.setFloatable(false);
jToolBar1.setRollover(true);
buttonGroup1.add(tbSummary);
tbSummary.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(tbSummary, bundle.getString("CTL_ShowSummary")); // NOI18N
tbSummary.setToolTipText(bundle.getString("TT_Summary")); // NOI18N
tbSummary.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
onViewToggle(evt);
}
});
jToolBar1.add(tbSummary);
tbSummary.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(SearchHistoryPanel.class, "CTL_ShowSummary")); // NOI18N
buttonGroup1.add(tbDiff);
org.openide.awt.Mnemonics.setLocalizedText(tbDiff, bundle.getString("CTL_ShowDiff")); // NOI18N
tbDiff.setToolTipText(bundle.getString("TT_ShowDiff")); // NOI18N
tbDiff.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
onViewToggle(evt);
}
});
jToolBar1.add(tbDiff);
jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL);
jSeparator2.setMaximumSize(new java.awt.Dimension(2, 32767));
jToolBar1.add(jSeparator2);
jToolBar1.add(bNext);
bNext.getAccessibleContext().setAccessibleName("null");
bNext.getAccessibleContext().setAccessibleDescription("null");
jToolBar1.add(bPrev);
bPrev.getAccessibleContext().setAccessibleName("null");
bPrev.getAccessibleContext().setAccessibleDescription("null");
jToolBar1.add(jSeparator3);
showMergesChkBox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(showMergesChkBox, org.openide.util.NbBundle.getMessage(SearchHistoryPanel.class, "CTL_ShowMerge")); // NOI18N
showMergesChkBox.setToolTipText(org.openide.util.NbBundle.getMessage(SearchHistoryPanel.class, "TT_ShowMerges")); // NOI18N
showMergesChkBox.setFocusable(false);
showMergesChkBox.setHorizontalTextPosition(javax.swing.SwingConstants.RIGHT);
showMergesChkBox.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
showMergesChkBox.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
showMergesChkBoxStateChanged(evt);
}
});
jToolBar1.add(showMergesChkBox);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridy = 3;
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
add(jToolBar1, gridBagConstraints);
resultsPanel.setLayout(new java.awt.BorderLayout());
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 4;
gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(8, 0, 8, 0);
add(resultsPanel, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
private void onViewToggle(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onViewToggle
refreshComponents(true);
}//GEN-LAST:event_onViewToggle
private void showMergesChkBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_showMergesChkBoxStateChanged
GitModuleConfig.getDefault().setShowHistoryMerges(showMergesChkBox.isSelected());
}//GEN-LAST:event_showMergesChkBoxStateChanged
public void insertUpdate(DocumentEvent e) {
validateUserInput();
}
public void removeUpdate(DocumentEvent e) {
validateUserInput();
}
public void changedUpdate(DocumentEvent e) {
validateUserInput();
}
private void validateUserInput() {
String from = criteria.getFrom();
if(from == null && criteria.tfFrom.getText().trim().length() > 0) {
bSearch.setEnabled(false);
return;
}
String to = criteria.getTo();
if(to == null && criteria.tfTo.getText().trim().length() > 0) {
bSearch.setEnabled(false);
return;
}
bSearch.setEnabled(true);
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton bNext;
private javax.swing.JButton bPrev;
private javax.swing.JButton bSearch;
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JPanel jPanel1;
private javax.swing.JSeparator jSeparator2;
private javax.swing.JToolBar.Separator jSeparator3;
private javax.swing.JToolBar jToolBar1;
private javax.swing.JPanel resultsPanel;
private javax.swing.JPanel searchCriteriaPanel;
private javax.swing.JCheckBox showMergesChkBox;
private javax.swing.JToggleButton tbDiff;
private javax.swing.JToggleButton tbSummary;
// End of variables declaration//GEN-END:variables
}