blob: cae0b4fd3a632074dde9b2e98263218a3c89a0a6 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.diff.impl.dir;
import com.intellij.ide.DataManager;
import com.intellij.ide.diff.DiffElement;
import com.intellij.ide.diff.DiffType;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diff.impl.dir.actions.DirDiffToolbarActions;
import com.intellij.openapi.diff.impl.dir.actions.RefreshDirDiffAction;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.ClickListener;
import com.intellij.ui.FilterComponent;
import com.intellij.ui.PopupHandler;
import com.intellij.ui.TableSpeedSearch;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBLoadingPanel;
import com.intellij.ui.components.JBLoadingPanelListener;
import com.intellij.ui.table.JBTable;
import com.intellij.util.diff.FilesTooBigForDiffException;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author Konstantin Bulenkov
*/
@SuppressWarnings({"unchecked"})
public class DirDiffPanel implements Disposable, DataProvider {
public static final String DIVIDER_PROPERTY = "dir.diff.panel.divider.location";
private JPanel myDiffPanel;
private JBTable myTable;
private JPanel myComponent;
private JSplitPane mySplitPanel;
private TextFieldWithBrowseButton mySourceDirField;
private TextFieldWithBrowseButton myTargetDirField;
private JBLabel myTargetDirLabel;
private JBLabel mySourceDirLabel;
private JPanel myToolBarPanel;
private JPanel myRootPanel;
private JPanel myFilterPanel;
private JBLabel myFilterLabel;
private JPanel myFilesPanel;
private FilterComponent myFilter;
private final DirDiffTableModel myModel;
public JLabel myErrorLabel;
private final DirDiffWindow myDiffWindow;
private JComponent myDiffPanelComponent;
private JComponent myViewComponent;
private DiffElement myCurrentElement;
private String oldFilter;
public static final DataKey<DirDiffTableModel> DIR_DIFF_MODEL = DataKey.create("DIR_DIFF_MODEL");
public static final DataKey<JTable> DIR_DIFF_TABLE = DataKey.create("DIR_DIFF_TABLE");
public DirDiffPanel(DirDiffTableModel model, DirDiffWindow wnd) {
myModel = model;
myDiffWindow = wnd;
mySourceDirField.setText(model.getSourceDir().getPath());
myTargetDirField.setText(model.getTargetDir().getPath());
mySourceDirField.setBorder(new EmptyBorder(0, 0, 0, 8));
myTargetDirField.setBorder(new EmptyBorder(0, 0, 0, 12));
mySourceDirLabel.setIcon(model.getSourceDir().getIcon());
myTargetDirLabel.setIcon(model.getTargetDir().getIcon());
myTargetDirLabel.setBorder(new EmptyBorder(0, 8, 0, 0));
myModel.setTable(myTable);
myModel.setPanel(this);
Disposer.register(this, myModel);
myTable.setModel(myModel);
new TableSpeedSearch(myTable);
final DirDiffTableCellRenderer renderer = new DirDiffTableCellRenderer();
myTable.setExpandableItemsEnabled(false);
myTable.setDefaultRenderer(Object.class, renderer);
myTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
final Project project = myModel.getProject();
myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
final int lastIndex = e.getLastIndex();
final int firstIndex = e.getFirstIndex();
final DirDiffElementImpl last = myModel.getElementAt(lastIndex);
final DirDiffElementImpl first = myModel.getElementAt(firstIndex);
if (last == null || first == null) {
update(false);
return;
}
if (last.isSeparator()) {
final int ind = lastIndex + ((lastIndex < firstIndex) ? 1 : -1);
myTable.getSelectionModel().addSelectionInterval(ind, ind);
}
else if (first.isSeparator()) {
final int ind = firstIndex + ((firstIndex < lastIndex) ? 1 : -1);
myTable.getSelectionModel().addSelectionInterval(ind, ind);
}
else {
update(false);
}
myDiffWindow.setTitle(myModel.getTitle());
}
});
if (model.isOperationsEnabled()) {
new AnAction("Change diff operation") {
@Override
public void actionPerformed(AnActionEvent e) {
changeOperationForSelection();
}
}.registerCustomShortcutSet(CustomShortcutSet.fromString("SPACE"), myTable);
new ClickListener() {
@Override
public boolean onClick(MouseEvent e, int clickCount) {
if (e.getButton() == MouseEvent.BUTTON3) return false;
if (myTable.getRowCount() > 0) {
final int row = myTable.rowAtPoint(e.getPoint());
final int col = myTable.columnAtPoint(e.getPoint());
if (row != -1 && col == ((myTable.getColumnCount() - 1) / 2)) {
changeOperationForSelection();
}
}
return true;
}
}.installOn(myTable);
}
myTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
final int keyCode = e.getKeyCode();
final int rowCount = myTable.getRowCount();
int row = myTable.getSelectionModel().getLeadSelectionIndex();
final int[] rows = myTable.getSelectedRows();
if (rows.length == 0) return;
if (keyCode == KeyEvent.VK_DOWN && row < rowCount - 1) {
row++;
final DirDiffElementImpl element = myModel.getElementAt(row);
if (element == null) return;
if (element.isSeparator()) {
row++;
}
}
else if (keyCode == KeyEvent.VK_UP && row > 0) {
row--;
final DirDiffElementImpl element = myModel.getElementAt(row);
if (element == null) return;
if (element.isSeparator()) {
row--;
}
}
else {
return;
}
final DirDiffElementImpl element = myModel.getElementAt(row);
if (element == null) return;
if (!element.isSeparator()) {
e.consume();
myTable.changeSelection(row, (myModel.getColumnCount() - 1) / 2, false, e.isShiftDown());
}
}
});
final TableColumnModel columnModel = myTable.getColumnModel();
final TableColumn operationColumn = columnModel.getColumn((columnModel.getColumnCount() - 1) / 2);
operationColumn.setMaxWidth(25);
operationColumn.setMinWidth(25);
for (int i = 0; i < columnModel.getColumnCount(); i++) {
final String name = myModel.getColumnName(i);
final TableColumn column = columnModel.getColumn(i);
if (DirDiffTableModel.COLUMN_DATE.equals(name)) {
column.setMaxWidth(90);
column.setMinWidth(90);
} else if (DirDiffTableModel.COLUMN_SIZE.equals(name)) {
column.setMaxWidth(120);
column.setMinWidth(120);
}
}
final DirDiffToolbarActions actions = new DirDiffToolbarActions(myModel, myDiffPanel);
final ActionManager actionManager = ActionManager.getInstance();
final ActionToolbar toolbar = actionManager.createActionToolbar("DirDiff", actions, true);
registerCustomShortcuts(actions, myTable);
myToolBarPanel.add(toolbar.getComponent(), BorderLayout.CENTER);
final JBLabel label = new JBLabel("Use Space button or mouse click to change operation for the selected elements. Enter to perform.", SwingConstants.CENTER);
label.setForeground(UIUtil.getInactiveTextColor());
UIUtil.applyStyle(UIUtil.ComponentStyle.MINI, label);
DataManager.registerDataProvider(myFilesPanel, this);
myTable.addMouseListener(new PopupHandler() {
@Override
public void invokePopup(Component comp, int x, int y) {
final JPopupMenu popupMenu =
actionManager.createActionPopupMenu("DirDiffPanel", (ActionGroup)actionManager.getAction("DirDiffMenu")).getComponent();
popupMenu.show(comp, x, y);
}
});
myFilesPanel.add(label, BorderLayout.SOUTH);
final JBLoadingPanel loadingPanel = new JBLoadingPanel(new BorderLayout(), wnd.getDisposable());
loadingPanel.addListener(new JBLoadingPanelListener.Adapter() {
boolean showHelp = true;
@Override
public void onLoadingFinish() {
if (showHelp && myModel.isOperationsEnabled() && myModel.getRowCount() > 0) {
final long count = PropertiesComponent.getInstance().getOrInitLong("dir.diff.space.button.info", 0);
if (count < 3) {
JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(" Use Space button to change operation"))
.setFadeoutTime(5000)
.setContentInsets(new Insets(15, 15, 15, 15))
.createBalloon().show(new RelativePoint(myTable, new Point(myTable.getWidth() / 2, 0)), Balloon.Position.above);
PropertiesComponent.getInstance().setValue("dir.diff.space.button.info", String.valueOf(count + 1));
}
}
showHelp = false;
}
});
loadingPanel.add(myComponent, BorderLayout.CENTER);
myTable.putClientProperty(myModel.DECORATOR, loadingPanel);
myTable.addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
myTable.removeComponentListener(this);
myModel.reloadModel(false);
}
});
myRootPanel.removeAll();
myRootPanel.add(loadingPanel, BorderLayout.CENTER);
myFilter = new FilterComponent("dir.diff.filter", 15, false) {
@Override
public void filter() {
fireFilterUpdated();
}
@Override
protected void onEscape(KeyEvent e) {
e.consume();
focusTable();
}
@Override
protected JComponent getPopupLocationComponent() {
return UIUtil.findComponentOfType(super.getPopupLocationComponent(), JTextComponent.class);
}
};
myModel.addModelListener(new DirDiffModelListener() {
@Override
public void updateStarted() {
myFilter.setEnabled(false);
}
@Override
public void updateFinished() {
myFilter.setEnabled(true);
}
});
myFilter.getTextEditor().setColumns(10);
myFilter.setFilter(myModel.getSettings().getFilter());
//oldFilter = myFilter.getText();
oldFilter = myFilter.getFilter();
myFilterPanel.add(myFilter, BorderLayout.CENTER);
myFilterLabel.setLabelFor(myFilter);
final Callable<DiffElement> srcChooser = myModel.getSourceDir().getElementChooser(project);
final Callable<DiffElement> trgChooser = myModel.getTargetDir().getElementChooser(project);
mySourceDirField.setEditable(false);
myTargetDirField.setEditable(false);
if (srcChooser != null && myModel.getSettings().enableChoosers) {
mySourceDirField.setButtonEnabled(true);
mySourceDirField.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
final Callable<DiffElement> chooser = myModel.getSourceDir().getElementChooser(project);
if (chooser == null) return;
final DiffElement newElement = chooser.call();
if (newElement != null) {
if (!StringUtil.equals(mySourceDirField.getText(), newElement.getPath())) {
myModel.setSourceDir(newElement);
mySourceDirField.setText(newElement.getPath());
myModel.clearWithMessage("Source or Target has been changed. Please run Refresh (" + KeymapUtil.getShortcutsText(
RefreshDirDiffAction.REFRESH_SHORTCUT.getShortcuts()) + ")");
}
}
} catch (Exception e1) {//
}
}
});
} else {
Dimension preferredSize = mySourceDirField.getPreferredSize();
mySourceDirField.setButtonEnabled(false);
mySourceDirField.getButton().setVisible(false);
mySourceDirField.setPreferredSize(preferredSize);
}
if (trgChooser != null && myModel.getSettings().enableChoosers) {
myTargetDirField.setButtonEnabled(true);
myTargetDirField.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
final Callable<DiffElement> chooser = myModel.getTargetDir().getElementChooser(project);
if (chooser == null) return;
final DiffElement newElement = chooser.call();
if (newElement != null) {
myModel.setTargetDir(newElement);
myTargetDirField.setText(newElement.getPath());
}
} catch (Exception e1) {//
}
}
});
} else {
Dimension preferredSize = myTargetDirField.getPreferredSize();
myTargetDirField.setButtonEnabled(false);
myTargetDirField.getButton().setVisible(false);
myTargetDirField.setPreferredSize(preferredSize);
}
}
private void changeOperationForSelection() {
for (int row : myTable.getSelectedRows()) {
if (row != -1) {
final DirDiffElementImpl element = myModel.getElementAt(row);
if (element != null) {
element.setNextOperation();
myModel.fireTableRowsUpdated(row, row);
}
}
}
}
public void update(boolean force) {
final Project project = myModel.getProject();
final DirDiffElementImpl element = myModel.getElementAt(myTable.getSelectedRow());
if (element == null) {
clearDiffPanel();
return;
}
if (!force
&& myCurrentElement != null
&& (myCurrentElement == element.getSource() || myCurrentElement == element.getTarget())) {
return;
}
clearDiffPanel();
if (element.getType() == DiffType.CHANGED) {
try {
myDiffPanelComponent = element.getSource().getDiffComponent(element.getTarget(), project, myDiffWindow.getWindow(), myModel);
}
catch (FilesTooBigForDiffException e) {
// todo KB: check
myDiffPanelComponent = null;
myErrorLabel = new JLabel("Can not build diff for file " + element.getTarget().getPath() + ". File is too big and there are too many changes.");
}
if (myDiffPanelComponent != null) {
myDiffPanel.add(myDiffPanelComponent, BorderLayout.CENTER);
myCurrentElement = element.getSource();
} else {
myDiffPanel.add(getErrorLabel(), BorderLayout.CENTER);
}
} else {
final DiffElement object;
final DiffElement target;
if (element.getType() == DiffType.ERROR) {
object = element.getSource() == null ? element.getTarget() : element.getSource();
target = element.getSource() == null ? element.getSource() : element.getTarget();
} else {
object = element.isSource() ? element.getSource() : element.getTarget();
target = element.isSource() ? element.getTarget() : element.getSource();
}
myViewComponent = object.getViewComponent(project, target, myModel);
if (myViewComponent != null) {
myCurrentElement = object;
myDiffPanel.add(myViewComponent, BorderLayout.CENTER);
DataProvider dataProvider = myCurrentElement.getDataProvider(project);
if (dataProvider != null) {
DataManager.registerDataProvider(myDiffPanel, dataProvider);
}
else {
DataManager.removeDataProvider(myDiffPanel);
}
} else {
myDiffPanel.add(getErrorLabel(), BorderLayout.CENTER);
}
}
myDiffPanel.revalidate();
myDiffPanel.repaint();
}
private void registerCustomShortcuts(DirDiffToolbarActions actions, JComponent component) {
for (AnAction action : actions.getChildren(null)) {
if (action instanceof ShortcutProvider) {
final ShortcutSet shortcut = ((ShortcutProvider)action).getShortcut();
if (shortcut != null) {
action.registerCustomShortcutSet(shortcut, component);
}
}
}
}
public void focusTable() {
final Project project = myModel.getProject();
final IdeFocusManager focusManager = project == null || project.isDefault()
? IdeFocusManager.getGlobalInstance() : IdeFocusManager.getInstance(project);
focusManager.doWhenFocusSettlesDown(new Runnable() {
@Override
public void run() {
focusManager.requestFocus(myTable, true);
}
});
}
public String getFilter() {
return myFilter.getFilter();
}
private void fireFilterUpdated() {
final String newFilter = myFilter.getFilter();
if (!StringUtil.equals(oldFilter, newFilter)) {
oldFilter = newFilter;
myModel.getSettings().setFilter(newFilter);
myModel.applySettings();
}
}
private JLabel getErrorLabel() {
return myErrorLabel == null ? myErrorLabel = new JLabel("Unknown or binary file type", SwingConstants.CENTER) : myErrorLabel;
}
private void clearDiffPanel() {
if (myDiffPanelComponent != null) {
myDiffPanel.remove(myDiffPanelComponent);
myDiffPanelComponent = null;
if (myCurrentElement != null) {
myCurrentElement.disposeDiffComponent();
}
}
if (myViewComponent != null) {
myDiffPanel.remove(myViewComponent);
myViewComponent = null;
if (myCurrentElement != null) {
myCurrentElement.disposeViewComponent();
}
}
myCurrentElement = null;
myDiffPanel.remove(getErrorLabel());
DataManager.removeDataProvider(myDiffPanel);
myDiffPanel.repaint();
}
public JComponent getPanel() {
return myRootPanel;
}
public JBTable getTable() {
return myTable;
}
public void dispose() {
myModel.stopUpdating();
PropertiesComponent.getInstance().setValue(DIVIDER_PROPERTY, String.valueOf(mySplitPanel.getDividerLocation()));
clearDiffPanel();
}
private void createUIComponents() {
final AtomicBoolean callUpdate = new AtomicBoolean(true);
myRootPanel = new JPanel(new BorderLayout()) {
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
if (callUpdate.get()) {
callUpdate.set(false);
myModel.reloadModel(false);
}
}
};
}
public void setupSplitter() {
mySplitPanel.setDividerLocation(Integer.valueOf(PropertiesComponent.getInstance().getValue(DIVIDER_PROPERTY, "200")));
}
@Override
public Object getData(@NonNls String dataId) {
if (CommonDataKeys.PROJECT.is(dataId)) {
return myModel.getProject();
}
if (DIR_DIFF_MODEL.is(dataId)) {
return myModel;
}
if (DIR_DIFF_TABLE.is(dataId)) {
return myTable;
}
return null;
}
}