/* * Copyright 2000-2014 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.vcs.history; import com.intellij.history.LocalHistory; import com.intellij.history.LocalHistoryAction; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.PanelWithActionsAndCloseButton; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.annotate.AnnotationProvider; import com.intellij.openapi.vcs.annotate.FileAnnotation; import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction; import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer; import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.openapi.vcs.impl.BackgroundableActionEnabledHandler; import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; import com.intellij.openapi.vcs.impl.VcsBackgroundableActions; import com.intellij.openapi.vcs.ui.ReplaceFileConfirmationDialog; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vcs.vfs.VcsFileSystem; import com.intellij.openapi.vcs.vfs.VcsVirtualFile; import com.intellij.openapi.vcs.vfs.VcsVirtualFolder; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.ui.content.ContentManager; import com.intellij.ui.dualView.*; import com.intellij.ui.table.TableView; import com.intellij.util.*; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.*; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.InputEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; import java.util.List; /** * author: lesya */ public class FileHistoryPanelImpl extends PanelWithActionsAndCloseButton { private static final Logger LOG = Logger.getInstance("#com.intellij.cvsSupport2.ui.FileHistoryDialog"); @NotNull private final Project myProject; private final JEditorPane myComments; private JComponent myAdditionalDetails; private Consumer myListener; private String myOriginalComment = ""; private final DefaultActionGroup myPopupActions; private final AbstractVcs myVcs; private final VcsHistoryProvider myProvider; private final AnnotationProvider myAnnotationProvider; private VcsHistorySession myHistorySession; private final FilePath myFilePath; private final FileHistoryRefresherI myRefresherI; private VcsFileRevision myBottomRevisionForShowDiff; private final DualView myDualView; @NotNull private final DiffFromHistoryHandler myDiffHandler; private final Alarm myUpdateAlarm; private volatile boolean myInRefresh; private List myTargetSelection; private final AsynchConsumer myHistoryPanelRefresh; private static final String COMMIT_MESSAGE_TITLE = VcsBundle.message("label.selected.revision.commit.message"); @NonNls private static final String VCS_HISTORY_ACTIONS_GROUP = "VcsHistoryActionsGroup"; private final Map myRevisionsOrder; private boolean myIsStaticAndEmbedded; private final Splitter myDetailsSplitter = new Splitter(false, 0.5f); private final Comparator myRevisionsInOrderComparator = new Comparator() { @Override public int compare(VcsFileRevision o1, VcsFileRevision o2) { // descending return Comparing.compare(myRevisionsOrder.get(o2.getRevisionNumber()), myRevisionsOrder.get(o1.getRevisionNumber())); } }; private final DualViewColumnInfo REVISION = new VcsColumnInfo(VcsBundle.message("column.name.revision.version")) { protected VcsRevisionNumber getDataOf(VcsFileRevision object) { return object.getRevisionNumber(); } @Override public Comparator getComparator() { return myRevisionsInOrderComparator; } public String valueOf(VcsFileRevision object) { final VcsRevisionNumber revisionNumber = object.getRevisionNumber(); return revisionNumber instanceof ShortVcsRevisionNumber ? ((ShortVcsRevisionNumber)revisionNumber).toShortString() : revisionNumber.asString(); } @Override public String getPreferredStringValue() { return "123.4567"; } }; private final DualViewColumnInfo DATE = new VcsColumnInfo(VcsBundle.message("column.name.revision.date")) { protected String getDataOf(VcsFileRevision object) { Date date = object.getRevisionDate(); if (date == null) return ""; return DateFormatUtil.formatPrettyDateTime(date); } public int compare(VcsFileRevision o1, VcsFileRevision o2) { return Comparing.compare(o1.getRevisionDate(), o2.getRevisionDate()); } @Override public String getPreferredStringValue() { return DateFormatUtil.formatPrettyDateTime(Clock.getTime()); } }; private boolean myColumnSizesSet; public void scheduleRefresh(final boolean canUseLastRevision) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { refreshImpl(canUseLastRevision); } }); } private static class AuthorCellRenderer extends DefaultTableCellRenderer { private String myTooltipText; public void setTooltipText(final String text) { myTooltipText = text; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (c instanceof JComponent) { ((JComponent)c).setToolTipText(myTooltipText); } if (isSelected || hasFocus) { c.setBackground(table.getSelectionBackground()); c.setForeground(table.getSelectionForeground()); } else { c.setBackground(table.getBackground()); c.setForeground(table.getForeground()); } return c; } } private static final TableCellRenderer AUTHOR_RENDERER = new AuthorCellRenderer(); private final DualViewColumnInfo AUTHOR = new VcsColumnInfo(VcsBundle.message("column.name.revision.list.author")) { protected String getDataOf(VcsFileRevision object) { VcsFileRevision rev = object; if (object instanceof TreeNodeOnVcsRevision) { rev = ((TreeNodeOnVcsRevision)object).getRevision(); } if (rev instanceof VcsFileRevisionEx) { if (!rev.getAuthor().equals(((VcsFileRevisionEx)rev).getCommitterName())) return object.getAuthor() + "*"; } return object.getAuthor(); } @Override public TableCellRenderer getRenderer(VcsFileRevision revision) { return AUTHOR_RENDERER; } @Override public TableCellRenderer getCustomizedRenderer(VcsFileRevision value, TableCellRenderer renderer) { if (renderer instanceof AuthorCellRenderer) { VcsFileRevision revision = value; if (value instanceof TreeNodeOnVcsRevision) { revision = ((TreeNodeOnVcsRevision)value).getRevision(); } if (revision instanceof VcsFileRevisionEx) { final VcsFileRevisionEx ex = (VcsFileRevisionEx)revision; final StringBuilder sb = new StringBuilder(ex.getAuthor()); if (ex.getAuthorEmail() != null) sb.append(" <").append(ex.getAuthorEmail()).append(">"); if (ex.getCommitterName() != null && !ex.getAuthor().equals(ex.getCommitterName())) { sb.append(", via ").append(ex.getCommitterName()); if (ex.getCommitterEmail() != null) sb.append(" <").append(ex.getCommitterEmail()).append(">"); } ((AuthorCellRenderer)renderer).setTooltipText(sb.toString()); } } return renderer; } @Override @NonNls public String getPreferredStringValue() { return "author_author"; } }; private Splitter mySplitter; private static class MessageRenderer extends ColoredTableCellRenderer { private final IssueLinkRenderer myIssueLinkRenderer; public MessageRenderer(Project project) { myIssueLinkRenderer = new IssueLinkRenderer(project, this); } protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { setOpaque(selected); if (value instanceof String) { String message = (String) value; myIssueLinkRenderer.appendTextWithLinks(message); } } } private static class MessageColumnInfo extends VcsColumnInfo { private final MessageRenderer myRenderer; public MessageColumnInfo(Project project) { super(FileHistoryPanelImpl.COMMIT_MESSAGE_TITLE); myRenderer = new MessageRenderer(project); } protected String getDataOf(VcsFileRevision object) { final String originalMessage = object.getCommitMessage(); if (originalMessage != null) { String commitMessage = originalMessage.trim(); int index13 = commitMessage.indexOf('\r'); int index10 = commitMessage.indexOf('\n'); if (index10 < 0 && index13 < 0) { return commitMessage; } else { return commitMessage.substring(0, getSuitableIndex(index10, index13)) + "..."; } } else { return ""; } } @Override public String getPreferredStringValue() { return StringUtil.repeatSymbol('a', 125); } public TableCellRenderer getRenderer(VcsFileRevision p0) { return myRenderer; } } private static int getSuitableIndex(int index10, int index13) { if (index10 < 0) { return index13; } else if (index13 < 0) { return index10; } else { return Math.min(index10, index13); } } private final Map myRevisionToVirtualFile = new HashMap(); public FileHistoryPanelImpl(AbstractVcs vcs, FilePath filePath, VcsHistorySession session, VcsHistoryProvider provider, ContentManager contentManager, final FileHistoryRefresherI refresherI) { this(vcs, filePath, session, provider, contentManager, refresherI, false); } public FileHistoryPanelImpl(AbstractVcs vcs, FilePath filePath, VcsHistorySession session, VcsHistoryProvider provider, ContentManager contentManager, final FileHistoryRefresherI refresherI, final boolean isStaticEmbedded) { super(contentManager, provider.getHelpId() != null ? provider.getHelpId() : "reference.versionControl.toolwindow.history", ! isStaticEmbedded); myProject = vcs.getProject(); myIsStaticAndEmbedded = false; myVcs = vcs; myProvider = provider; myAnnotationProvider = myVcs.getCachingAnnotationProvider(); myRefresherI = refresherI; myHistorySession = session; myFilePath = filePath; DiffFromHistoryHandler customDiffHandler = provider.getHistoryDiffHandler(); myDiffHandler = customDiffHandler == null ? new StandardDiffFromHistoryHandler() : customDiffHandler; final DualViewColumnInfo[] columns = createColumnList(myVcs.getProject(), provider, session); myComments = new JEditorPane(UIUtil.HTML_MIME, ""); myComments.setPreferredSize(new Dimension(150, 100)); myComments.setEditable(false); myComments.setBackground(UIUtil.getComboBoxDisabledBackground()); myComments.addHyperlinkListener(new BrowserHyperlinkListener()); myRevisionsOrder = new HashMap(); refreshRevisionsOrder(); replaceTransferable(); myUpdateAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myProject); final HistoryAsTreeProvider treeHistoryProvider = myHistorySession.getHistoryAsTreeProvider(); @NonNls String storageKey = "FileHistory." + provider.getClass().getName(); if (treeHistoryProvider != null) { myDualView = new DualView(new TreeNodeOnVcsRevision(null, treeHistoryProvider.createTreeOn(myHistorySession.getRevisionList())), columns, storageKey, myVcs.getProject()); } else { myDualView = new DualView(new TreeNodeOnVcsRevision(null, wrapWithTreeElements(myHistorySession.getRevisionList())), columns, storageKey, myVcs.getProject()); myDualView.switchToTheFlatMode(); } new TableSpeedSearch(myDualView.getFlatView()).setComparator(new SpeedSearchComparator(false)); final TableLinkMouseListener listener = new TableLinkMouseListener(); listener.installOn(myDualView.getFlatView()); listener.installOn(myDualView.getTreeView()); createDualView(); if (isStaticEmbedded) { setIsStaticAndEmbedded(isStaticEmbedded); } myPopupActions = createPopupActions(); myHistoryPanelRefresh = new AsynchConsumer() { public void finished() { if (treeHistoryProvider != null) { // scroll tree view to most recent change final TreeTableView treeView = myDualView.getTreeView(); final int lastRow = treeView.getRowCount() - 1; if (lastRow >= 0) { treeView.scrollRectToVisible(treeView.getCellRect(lastRow, 0, true)); } } myInRefresh = false; myTargetSelection = null; mySplitter.revalidate(); mySplitter.repaint(); } public void consume(VcsHistorySession vcsHistorySession) { FileHistoryPanelImpl.this.refresh(vcsHistorySession); } }; // todo react to event? myUpdateAlarm.addRequest(new Runnable() { public void run() { if (myVcs.getProject().isDisposed()) { return; } boolean refresh = ApplicationManager.getApplication().isActive() && !myInRefresh && myHistorySession.shouldBeRefreshed(); myUpdateAlarm.cancelAllRequests(); if (myUpdateAlarm.isDisposed()) return; myUpdateAlarm.addRequest(this, 20000); if (refresh) { refreshImpl(true); } } }, 20000); init(); chooseView(); } private void replaceTransferable() { final TransferHandler originalTransferHandler = myComments.getTransferHandler(); final TransferHandler newHandler = new TransferHandler("copy") { @Override public void exportAsDrag(final JComponent comp, final InputEvent e, final int action) { originalTransferHandler.exportAsDrag(comp, e, action); } @Override public void exportToClipboard(final JComponent comp, final Clipboard clip, final int action) throws IllegalStateException { if ((action == COPY || action == MOVE) && (getSourceActions(comp) & action) != 0) { String selectedText = myComments.getSelectedText(); final Transferable t; if (selectedText == null) { t = new TextTransferable(myComments.getText(), myOriginalComment); } else { t = new TextTransferable(selectedText); } try { clip.setContents(t, null); exportDone(comp, t, action); return; } catch (IllegalStateException ise) { exportDone(comp, t, NONE); throw ise; } } exportDone(comp, null, NONE); } @Override public boolean importData(final JComponent comp, final Transferable t) { return originalTransferHandler.importData(comp, t); } @Override public boolean canImport(final JComponent comp, final DataFlavor[] transferFlavors) { return originalTransferHandler.canImport(comp, transferFlavors); } @Override public int getSourceActions(final JComponent c) { return originalTransferHandler.getSourceActions(c); } @Override public Icon getVisualRepresentation(final Transferable t) { return originalTransferHandler.getVisualRepresentation(t); } }; myComments.setTransferHandler(newHandler); } private DualViewColumnInfo[] createColumnList(Project project, VcsHistoryProvider provider, final VcsHistorySession session) { final VcsDependentHistoryComponents components = provider.getUICustomization(session, this); myAdditionalDetails = components.getDetailsComponent(); myListener = components.getRevisionListener(); ArrayList columns = new ArrayList(); if (provider.isDateOmittable()) { columns.addAll(Arrays.asList(REVISION, AUTHOR)); } else { columns.addAll(Arrays.asList(REVISION, DATE, AUTHOR)); } columns.addAll(wrapAdditionalColumns(components.getColumns())); columns.add(new MessageColumnInfo(project)); return columns.toArray(new DualViewColumnInfo[columns.size()]); } private Collection wrapAdditionalColumns(ColumnInfo[] additionalColumns) { ArrayList result = new ArrayList(); if (additionalColumns != null) { for (ColumnInfo additionalColumn : additionalColumns) { result.add(new MyColumnWrapper(additionalColumn)); } } return result; } private static List> wrapWithTreeElements(List revisions) { ArrayList> result = new ArrayList>(); for (final VcsFileRevision revision : revisions) { result.add(new TreeItem(revision)); } return result; } private void refresh(final VcsHistorySession session) { myHistorySession = session; refreshRevisionsOrder(); HistoryAsTreeProvider treeHistoryProvider = session.getHistoryAsTreeProvider(); if (myHistorySession.getRevisionList().isEmpty()) { adjustEmptyText(); } if (treeHistoryProvider != null) { myDualView.setRoot(new TreeNodeOnVcsRevision(null, treeHistoryProvider.createTreeOn(myHistorySession.getRevisionList())), myTargetSelection); } else { myDualView.setRoot(new TreeNodeOnVcsRevision(null, wrapWithTreeElements(myHistorySession.getRevisionList())), myTargetSelection); } columnSizesOnce(); myDualView.expandAll(); myDualView.repaint(); } private void columnSizesOnce() { if (! myColumnSizesSet) { myDualView.getFlatView().updateColumnSizes(); myColumnSizesSet = true; } } private void adjustEmptyText() { VirtualFile virtualFile = myFilePath.getVirtualFile(); if (virtualFile == null || !virtualFile.isValid()) { if (!myFilePath.getIOFile().exists()) { String emptyText = "File " + myFilePath.getName() + " not found"; setEmptyText(emptyText); return; } } setEmptyText(StatusText.DEFAULT_EMPTY_TEXT); } private void setEmptyText(String emptyText) { myDualView.getFlatView().getEmptyText().setText(emptyText); myDualView.getTreeView().getEmptyText().setText(emptyText); } protected void addActionsTo(DefaultActionGroup group) { addToGroup(false, group); } private void createDualView() { myDualView.setShowGrid(true); myDualView.getTreeView().addMouseListener(new PopupHandler() { public void invokePopup(Component comp, int x, int y) { ActionPopupMenu popupMenu = ActionManager.getInstance() .createActionPopupMenu(ActionPlaces.UPDATE_POPUP, myPopupActions); popupMenu.getComponent().show(comp, x, y); } }); myDualView.getFlatView().addMouseListener(new PopupHandler() { public void invokePopup(Component comp, int x, int y) { ActionPopupMenu popupMenu = ActionManager.getInstance() .createActionPopupMenu(ActionPlaces.UPDATE_POPUP, myPopupActions); popupMenu.getComponent().show(comp, x, y); } }); myDualView.requestFocus(); myDualView.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { updateMessage(); } }); myDualView.setRootVisible(false); myDualView.expandAll(); final TreeCellRenderer defaultCellRenderer = myDualView.getTree().getCellRenderer(); final Getter sessionGetter = new Getter() { public VcsHistorySession get() { return myHistorySession; } }; myDualView.setTreeCellRenderer(new MyTreeCellRenderer(defaultCellRenderer, sessionGetter)); myDualView.setCellWrapper(new MyCellWrapper(sessionGetter)); final TableView flatView = myDualView.getFlatView(); TableViewModel sortableModel = flatView.getTableViewModel(); sortableModel.setSortable(true); final RowSorter rowSorter = flatView.getRowSorter(); if (rowSorter != null) { rowSorter.setSortKeys(Arrays.asList(new RowSorter.SortKey(0, SortOrder.DESCENDING))); } } private static void makeBold(Component component) { if (component instanceof JComponent) { JComponent jComponent = (JComponent)component; Font font = jComponent.getFont(); if (font != null) { jComponent.setFont(font.deriveFont(Font.BOLD)); } } else if (component instanceof Container) { Container container = (Container)component; for (int i = 0; i < container.getComponentCount(); i++) { makeBold(container.getComponent(i)); } } } private void updateMessage() { List selection = getSelection(); final VcsFileRevision revision; if (selection.size() != 1) { revision = null; myComments.setText(""); myOriginalComment = ""; } else { revision = getFirstSelectedRevision(); if (revision != null) { final String message = revision.getCommitMessage(); myOriginalComment = message; @NonNls final String text = IssueLinkHtmlRenderer.formatTextIntoHtml(myVcs.getProject(), message); myComments.setText(text); myComments.setCaretPosition(0); } } if (myListener != null) { myListener.consume(revision); } } protected JComponent createCenterPanel() { mySplitter = new Splitter(true, getSplitterProportion()); mySplitter.setDividerWidth(4); //splitter.getDivider().setBackground(UIUtil.getBgFillColor(splitter.getDivider()).brighter()); mySplitter.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) { setSplitterProportionTo((Float)evt.getNewValue()); } } }); JPanel commentGroup = new JPanel(new BorderLayout()); final JLabel commentLabel = new JLabel(COMMIT_MESSAGE_TITLE + ":"); commentGroup.add(commentLabel, BorderLayout.NORTH); JScrollPane pane = ScrollPaneFactory.createScrollPane(myComments); pane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.LEFT | (myAdditionalDetails == null ? 0 : SideBorder.RIGHT))); commentGroup.add(pane, BorderLayout.CENTER); myDetailsSplitter.setFirstComponent(commentGroup); myDetailsSplitter.setSecondComponent(myAdditionalDetails); mySplitter.setFirstComponent(myDualView); setupDetails(); return mySplitter; } private void setupDetails() { boolean showDetails = ! myIsStaticAndEmbedded && getConfiguration().SHOW_FILE_HISTORY_DETAILS; if (showDetails) { myDualView.setViewBorder(IdeBorderFactory.createBorder(SideBorder.LEFT | SideBorder.BOTTOM)); } else { myDualView.setViewBorder(IdeBorderFactory.createBorder(SideBorder.LEFT)); } mySplitter.setSecondComponent(showDetails ? myDetailsSplitter : null); } private void chooseView() { if (showTree()) { myDualView.switchToTheTreeMode(); } else { myDualView.switchToTheFlatMode(); } } private boolean showTree() { return getConfiguration().SHOW_FILE_HISTORY_AS_TREE; } private void setSplitterProportionTo(Float newProportion) { getConfiguration().FILE_HISTORY_SPLITTER_PROPORTION = newProportion.floatValue(); } protected float getSplitterProportion() { return getConfiguration().FILE_HISTORY_SPLITTER_PROPORTION; } private VcsConfiguration getConfiguration() { return VcsConfiguration.getInstance(myVcs.getProject()); } private DefaultActionGroup createPopupActions() { return addToGroup(true, new DefaultActionGroup(null, false)); } private DefaultActionGroup addToGroup(boolean popup, DefaultActionGroup result) { if (popup) { result.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EDIT_SOURCE)); } final MyDiffAction diffAction = new MyDiffAction(); result.add(diffAction); if (!popup) { diffAction.registerCustomShortcutSet(new CustomShortcutSet( CommonShortcuts.getDiff().getShortcuts() [0], CommonShortcuts.DOUBLE_CLICK_1.getShortcuts() [0]), myDualView.getFlatView()); diffAction.registerCustomShortcutSet(new CustomShortcutSet( CommonShortcuts.getDiff().getShortcuts() [0], CommonShortcuts.DOUBLE_CLICK_1.getShortcuts() [0]), myDualView.getTreeView()); } else { diffAction.registerCustomShortcutSet(CommonShortcuts.getDiff(), this); } final MyShowDiffWithLocalAction showDiffWithLocalAction = new MyShowDiffWithLocalAction(); result.add(showDiffWithLocalAction); final AnAction diffGroup = ActionManager.getInstance().getAction(VCS_HISTORY_ACTIONS_GROUP); if (diffGroup != null) result.add(diffGroup); result.add(new MyCreatePatch()); result.add(new MyGetVersionAction()); result.add(new MyAnnotateAction()); AnAction[] additionalActions = myProvider.getAdditionalActions(new Runnable() { public void run() { refreshImpl(true); } }); if (additionalActions != null) { for (AnAction additionalAction : additionalActions) { if (popup || additionalAction.getTemplatePresentation().getIcon() != null) { result.add(additionalAction); } } } result.add(new RefreshFileHistoryAction()); if (! myIsStaticAndEmbedded) { result.add(new MyToggleAction()); } if (!popup && supportsTree()) { result.add(new MyShowAsTreeAction()); } return result; } private void refreshImpl(final boolean useLastRevision) { new AbstractCalledLater(myVcs.getProject(), ModalityState.NON_MODAL) { public void run() { if (myInRefresh) return; myInRefresh = true; myTargetSelection = myDualView.getFlatView().getSelectedObjects(); mySplitter.revalidate(); mySplitter.repaint(); myRefresherI.run(true, useLastRevision); columnSizesOnce(); } }.callMe(); } public AsynchConsumer getHistoryPanelRefresh() { return myHistoryPanelRefresh; } private boolean supportsTree() { return myHistorySession != null && myHistorySession.getHistoryAsTreeProvider() != null; } private class MyShowAsTreeAction extends ToggleAction implements DumbAware { public MyShowAsTreeAction() { super(VcsBundle.message("action.name.show.files.as.tree"), null, PlatformIcons.SMALL_VCS_CONFIGURABLE); } public boolean isSelected(AnActionEvent e) { return getConfiguration().SHOW_FILE_HISTORY_AS_TREE; } public void setSelected(AnActionEvent e, boolean state) { getConfiguration().SHOW_FILE_HISTORY_AS_TREE = state; chooseView(); } } private class MyDiffAction extends AbstractActionForSomeSelection { public MyDiffAction() { super(VcsBundle.message("action.name.compare"), VcsBundle.message("action.description.compare"), "diff", 2, FileHistoryPanelImpl.this); } protected void executeAction(AnActionEvent e) { List sel = getSelection(); int selectionSize = sel.size(); if (selectionSize > 1) { myDiffHandler.showDiffForTwo(myFilePath, sel.get(0).getRevision(), sel.get(sel.size() - 1).getRevision()); } else if (selectionSize == 1) { final TableView flatView = myDualView.getFlatView(); final int selectedRow = flatView.getSelectedRow(); VcsFileRevision revision = getFirstSelectedRevision(); VcsFileRevision previousRevision; if (selectedRow == (flatView.getRowCount() - 1)) { // no previous previousRevision = myBottomRevisionForShowDiff != null ? myBottomRevisionForShowDiff : VcsFileRevision.NULL; } else { previousRevision = flatView.getRow(selectedRow + 1).getRevision(); } if (revision != null) { myDiffHandler.showDiffForOne(e, myFilePath, previousRevision, revision); } } } public void update(final AnActionEvent e) { super.update(e); final int selectionSize = getSelection().size(); e.getPresentation().setEnabled(selectionSize > 0 && isEnabled()); } public boolean isEnabled() { final int selectionSize = getSelection().size(); if (selectionSize == 1) { List sel = getSelection(); return myHistorySession.isContentAvailable(sel.get(0)); } else if (selectionSize > 1) { return isDiffEnabled(); } return false; } private boolean isDiffEnabled() { List sel = getSelection(); return myHistorySession.isContentAvailable(sel.get(0)) && myHistorySession.isContentAvailable(sel.get(sel.size() - 1)); } } private class MyShowDiffWithLocalAction extends AbstractActionForSomeSelection { private MyShowDiffWithLocalAction() { super(VcsBundle.message("action.name.compare.with.local"), VcsBundle.message("action.description.compare.with.local"), "diffWithCurrent", 1, FileHistoryPanelImpl.this); } @Override protected void executeAction(AnActionEvent e) { final List selection = getSelection(); if (selection.size() != 1) return; if (ChangeListManager.getInstance(myVcs.getProject()).isFreezedWithNotification(null)) return; final VcsRevisionNumber currentRevisionNumber = myHistorySession.getCurrentRevisionNumber(); VcsFileRevision selectedRevision = getFirstSelectedRevision(); if (currentRevisionNumber != null && selectedRevision != null) { myDiffHandler.showDiffForTwo(myFilePath, selectedRevision, new CurrentRevision(myFilePath.getVirtualFile(), currentRevisionNumber)); } } private boolean isDiffWithCurrentEnabled() { if (myHistorySession.getCurrentRevisionNumber() == null) return false; if (myFilePath.getVirtualFile() == null) return false; if (!myHistorySession.isContentAvailable(getFirstSelectedRevision())) return false; return true; } @Override public boolean isEnabled() { final int size = getSelection().size(); return size == 1 && isDiffWithCurrentEnabled(); } } private class MyGetVersionAction extends AbstractActionForSomeSelection { public MyGetVersionAction() { super(VcsBundle.message("action.name.get.file.content.from.repository"), VcsBundle.message("action.description.get.file.content.from.repository"), "get", 1, FileHistoryPanelImpl.this); } @Override public boolean isEnabled() { return super.isEnabled() && getVirtualParent() != null && myHistorySession.isContentAvailable(getFirstSelectedRevision()) && !myFilePath.isDirectory(); } protected void executeAction(AnActionEvent e) { if (ChangeListManager.getInstance(myVcs.getProject()).isFreezedWithNotification(null)) return; final VcsFileRevision revision = getFirstSelectedRevision(); if (getVirtualFile() != null) { if (!new ReplaceFileConfirmationDialog(myVcs.getProject(), VcsBundle.message("acton.name.get.revision")) .confirmFor(new VirtualFile[]{getVirtualFile()})) { return; } } getVersion(revision); refreshFile(revision); } private void refreshFile(VcsFileRevision revision) { Runnable refresh = null; final VirtualFile vf = getVirtualFile(); if (vf == null) { final LocalHistoryAction action = startLocalHistoryAction(revision); final VirtualFile vp = getVirtualParent(); if (vp != null) { refresh = new Runnable() { public void run() { vp.refresh(false, true, new Runnable() { public void run() { myFilePath.refresh(); action.finish(); } }); } }; } } else { refresh = new Runnable() { public void run() { vf.refresh(false, false); } }; } if (refresh != null) { ProgressManager.getInstance().runProcessWithProgressSynchronously(refresh, "Refreshing files...", false, myVcs.getProject()); } } private void getVersion(final VcsFileRevision revision) { final VirtualFile file = getVirtualFile(); final Project project = myVcs.getProject(); new Task.Backgroundable(project, VcsBundle.message("show.diff.progress.title")) { @Override public void run(@NotNull ProgressIndicator indicator) { final LocalHistoryAction action = file != null ? startLocalHistoryAction(revision) : LocalHistoryAction.NULL; final byte[] revisionContent; try { revisionContent = VcsHistoryUtil.loadRevisionContent(revision); } catch (final IOException e) { LOG.info(e); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { Messages.showMessageDialog(VcsBundle.message("message.text.cannot.load.revision", e.getLocalizedMessage()), VcsBundle.message("message.title.get.revision.content"), Messages.getInformationIcon()); } }); return; } catch (final VcsException e) { LOG.info(e); ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { Messages.showMessageDialog(VcsBundle.message("message.text.cannot.load.revision", e.getLocalizedMessage()), VcsBundle.message("message.title.get.revision.content"), Messages.getInformationIcon()); } }); return; } catch (ProcessCanceledException ex) { return; } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { try { new WriteCommandAction.Simple(project) { @Override protected void run() throws Throwable { if (file != null && !file.isWritable() && ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(file).hasReadonlyFiles()) { return; } try { write(revisionContent); } catch (IOException e) { Messages.showMessageDialog(VcsBundle.message("message.text.cannot.save.content", e.getLocalizedMessage()), VcsBundle.message("message.title.get.revision.content"), Messages.getErrorIcon()); } } }.execute(); if (file != null) { VcsDirtyScopeManager.getInstance(project).fileDirty(file); } } finally { action.finish(); } } }); } }.queue(); } private LocalHistoryAction startLocalHistoryAction(final VcsFileRevision revision) { return LocalHistory.getInstance().startAction(createGetActionTitle(revision)); } private String createGetActionTitle(final VcsFileRevision revision) { return VcsBundle.message("action.name.for.file.get.version", getIOFile().getAbsolutePath(), revision.getRevisionNumber()); } private File getIOFile() { return myFilePath.getIOFile(); } private void write(byte[] revision) throws IOException { if (getVirtualFile() == null) { writeContentToIOFile(revision); } else { Document document = myFilePath.getDocument(); if (document == null) { writeContentToFile(revision); } else { writeContentToDocument(document, revision); } } } private void writeContentToIOFile(byte[] revisionContent) throws IOException { FileOutputStream outputStream = new FileOutputStream(getIOFile()); try { outputStream.write(revisionContent); } finally { outputStream.close(); } } private void writeContentToFile(final byte[] revision) throws IOException { getVirtualFile().setBinaryContent(revision); } private void writeContentToDocument(final Document document, byte[] revisionContent) throws IOException { final String content = StringUtil.convertLineSeparators(new String(revisionContent, myFilePath.getCharset().name())); CommandProcessor.getInstance().executeCommand(myVcs.getProject(), new Runnable() { public void run() { document.replaceString(0, document.getTextLength(), content); } }, VcsBundle.message("message.title.get.version"), null); } } private class MyAnnotateAction extends AnAction implements DumbAware { public MyAnnotateAction() { super(VcsBundle.message("annotate.action.name"), VcsBundle.message("annotate.action.description"), AllIcons.Actions.Annotate); } private String key(final VirtualFile vf) { return vf.getPath(); } public void update(AnActionEvent e) { VirtualFile revVFile = e.getData( VcsDataKeys.VCS_VIRTUAL_FILE ); VcsFileRevision revision = e.getData( VcsDataKeys.VCS_FILE_REVISION ); final Boolean nonLocal = e.getData(VcsDataKeys.VCS_NON_LOCAL_HISTORY_SESSION); boolean isFile = revVFile != null && !revVFile.isDirectory(); FileType fileType = isFile ? revVFile.getFileType() : null; boolean enabled = revision != null && isFile && !fileType.isBinary() && !Boolean.TRUE.equals(nonLocal); if (enabled) { final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(myVcs.getProject()); enabled = (! (((ProjectLevelVcsManagerImpl) plVcsManager).getBackgroundableActionHandler( VcsBackgroundableActions.ANNOTATE).isInProgress(key(revVFile)))); } e.getPresentation() .setEnabled(enabled && myHistorySession.isContentAvailable(revision) && myAnnotationProvider != null && myAnnotationProvider.isAnnotationValid(revision)); } public void actionPerformed(AnActionEvent e) { final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); final VirtualFile revisionVirtualFile = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); final Boolean nonLocal = e.getData(VcsDataKeys.VCS_NON_LOCAL_HISTORY_SESSION); if ((revision == null) || (revisionVirtualFile == null) || Boolean.TRUE.equals(nonLocal)) return; final BackgroundableActionEnabledHandler handler = ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myVcs.getProject())). getBackgroundableActionHandler(VcsBackgroundableActions.ANNOTATE); handler.register(key(revisionVirtualFile)); final Ref fileAnnotationRef = new Ref(); final Ref exceptionRef = new Ref(); ProgressManager.getInstance().run(new Task.Backgroundable(myVcs.getProject(), VcsBundle.message("retrieving.annotations"), true, BackgroundFromStartOption.getInstance()) { public void run(@NotNull ProgressIndicator indicator) { try { fileAnnotationRef.set(myAnnotationProvider.annotate(revisionVirtualFile, revision)); } catch (VcsException e) { exceptionRef.set(e); } } @Override public void onCancel() { onSuccess(); } @Override public void onSuccess() { handler.completed(key(revisionVirtualFile)); if (! exceptionRef.isNull()) { AbstractVcsHelper.getInstance(myProject).showError(exceptionRef.get(), VcsBundle.message("operation.name.annotate")); } if (fileAnnotationRef.isNull()) return; AbstractVcsHelper.getInstance(myProject).showAnnotation(fileAnnotationRef.get(), revisionVirtualFile, myVcs); } }); } } public Object getData(String dataId) { VcsFileRevision firstSelectedRevision = getFirstSelectedRevision(); if (CommonDataKeys.NAVIGATABLE.is(dataId)) { List selectedItems = getSelection(); if (selectedItems.size() != 1) return null; if (!myHistorySession.isContentAvailable(firstSelectedRevision)) { return null; } VirtualFile virtualFileForRevision = createVirtualFileForRevision(firstSelectedRevision); if (virtualFileForRevision != null) { return new OpenFileDescriptor(myVcs.getProject(), virtualFileForRevision); } else { return null; } } else if (CommonDataKeys.PROJECT.is(dataId)) { return myVcs.getProject(); } else if (VcsDataKeys.VCS_FILE_REVISION.is(dataId)) { return firstSelectedRevision; } else if (VcsDataKeys.VCS_NON_LOCAL_HISTORY_SESSION.is(dataId) && myHistorySession != null) { return ! myHistorySession.hasLocalSource(); } else if (VcsDataKeys.VCS.is(dataId)) { return myVcs.getKeyInstanceMethod(); } else if (VcsDataKeys.VCS_FILE_REVISIONS.is(dataId)) { return getSelectedRevisions(); } else if (VcsDataKeys.REMOTE_HISTORY_CHANGED_LISTENER.is(dataId)) { return new Consumer() { @Override public void consume(String s) { myDualView.rebuild(); } }; } else if (VcsDataKeys.CHANGES.is(dataId)) { return getChanges(); } else if (VcsDataKeys.VCS_VIRTUAL_FILE.is(dataId)) { if (firstSelectedRevision == null) return null; return createVirtualFileForRevision(firstSelectedRevision); } else if (VcsDataKeys.FILE_PATH.is(dataId)) { return myFilePath; } else if (VcsDataKeys.IO_FILE.is(dataId)) { return myFilePath.getIOFile(); } else if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) { if (getVirtualFile() == null) return null; if (getVirtualFile().isValid()) { return getVirtualFile(); } else { return null; } } else if (VcsDataKeys.FILE_HISTORY_PANEL.is(dataId)) { return this; } else { return super.getData(dataId); } } @Nullable private Change[] getChanges() { final VcsFileRevision[] revisions = getSelectedRevisions(); if (revisions.length > 0) { Arrays.sort(revisions, new Comparator() { public int compare(final VcsFileRevision o1, final VcsFileRevision o2) { return o1.getRevisionNumber().compareTo(o2.getRevisionNumber()); } }); for (VcsFileRevision revision : revisions) { if (! myHistorySession.isContentAvailable(revision)) { return null; } } final ContentRevision startRevision = new LoadedContentRevision(myFilePath, revisions[0], myVcs.getProject()); final ContentRevision endRevision = (revisions.length == 1) ? new CurrentContentRevision(myFilePath) : new LoadedContentRevision(myFilePath, revisions[revisions.length - 1], myVcs.getProject()); return new Change[]{new Change(startRevision, endRevision)}; } return null; } private static class LoadedContentRevision implements ContentRevision { private final FilePath myFile; private final VcsFileRevision myRevision; private final Project myProject; private LoadedContentRevision(final FilePath file, final VcsFileRevision revision, final Project project) { myFile = file; myRevision = revision; myProject = project; } public String getContent() throws VcsException { try { return VcsHistoryUtil.loadRevisionContentGuessEncoding(myRevision, myFile.getVirtualFile(), myProject); } catch (IOException e) { throw new VcsException(VcsBundle.message("message.text.cannot.load.revision", e.getLocalizedMessage())); } } @NotNull public FilePath getFile() { return myFile; } @NotNull public VcsRevisionNumber getRevisionNumber() { return myRevision.getRevisionNumber(); } } private VirtualFile createVirtualFileForRevision(VcsFileRevision revision) { if (!myRevisionToVirtualFile.containsKey(revision)) { FilePath filePath = (revision instanceof VcsFileRevisionEx ? ((VcsFileRevisionEx)revision).getPath() : myFilePath); myRevisionToVirtualFile.put(revision, filePath.isDirectory() ? new VcsVirtualFolder(filePath.getPath(), null, VcsFileSystem.getInstance()) : new VcsVirtualFile(filePath.getPath(), revision, VcsFileSystem.getInstance())); } return myRevisionToVirtualFile.get(revision); } private List getSelection() { //noinspection unchecked return myDualView.getSelection(); } @Nullable private VcsFileRevision getFirstSelectedRevision() { List selection = getSelection(); if (selection.isEmpty()) return null; return ((TreeNodeOnVcsRevision)selection.get(0)).myRevision; } public VcsFileRevision[] getSelectedRevisions() { List selection = getSelection(); VcsFileRevision[] result = new VcsFileRevision[selection.size()]; for(int i=0; i> roots) { myRevision = revision == null ? VcsFileRevision.NULL : revision; for (final TreeItem root : roots) { add(new TreeNodeOnVcsRevision(root.getData(), root.getChildren())); } } @Nullable @Override public RepositoryLocation getChangedRepositoryPath() { return myRevision.getChangedRepositoryPath(); } public VcsFileRevision getRevision() { return myRevision; } public String getAuthor() { return myRevision.getAuthor(); } public String getCommitMessage() { return myRevision.getCommitMessage(); } public byte[] loadContent() throws IOException, VcsException { return myRevision.loadContent(); } public VcsRevisionNumber getRevisionNumber() { return myRevision.getRevisionNumber(); } public Date getRevisionDate() { return myRevision.getRevisionDate(); } public String getBranchName() { return myRevision.getBranchName(); } public byte[] getContent() throws IOException, VcsException { return myRevision.getContent(); } public String toString() { return getRevisionNumber().asString(); } public boolean shouldBeInTheFlatView() { return myRevision != VcsFileRevision.NULL; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TreeNodeOnVcsRevision that = (TreeNodeOnVcsRevision)o; if (myRevision != null ? !myRevision.getRevisionNumber().equals(that.myRevision.getRevisionNumber()) : that.myRevision != null) return false; return true; } @Override public int hashCode() { return myRevision != null ? myRevision.getRevisionNumber().hashCode() : 0; } } public void dispose() { super.dispose(); myDualView.dispose(); myUpdateAlarm.dispose(); } abstract class AbstractActionForSomeSelection extends AnAction implements DumbAware { private final int mySuitableSelectedElements; private final FileHistoryPanelImpl mySelectionProvider; public AbstractActionForSomeSelection(String name, String description, @NonNls String iconName, int suitableSelectionSize, FileHistoryPanelImpl tableProvider) { super(name, description, IconLoader.getIcon("/actions/" + iconName + ".png")); mySuitableSelectedElements = suitableSelectionSize; mySelectionProvider = tableProvider; } protected abstract void executeAction(AnActionEvent e); public boolean isEnabled() { return mySelectionProvider.getSelection().size() == mySuitableSelectedElements; } public void actionPerformed(AnActionEvent e) { if (!isEnabled()) return; executeAction(e); } public void update(AnActionEvent e) { Presentation presentation = e.getPresentation(); presentation.setVisible(true); presentation.setEnabled(isEnabled()); } } abstract static class VcsColumnInfo extends DualViewColumnInfo implements Comparator { public VcsColumnInfo(String name) { super(name); } protected abstract T getDataOf(VcsFileRevision o); public Comparator getComparator() { return this; } public String valueOf(VcsFileRevision object) { T result = getDataOf(object); return result == null ? "" : result.toString(); } public int compare(VcsFileRevision o1, VcsFileRevision o2) { return compareObjects(getDataOf(o1), getDataOf(o2)); } private static int compareObjects(Comparable data1, Comparable data2) { if (data1 == data2) return 0; if (data1 == null) return -1; if (data2 == null) return 1; return data1.compareTo(data2); } public boolean shouldBeShownIsTheTree() { return true; } public boolean shouldBeShownIsTheTable() { return true; } } private class MyColumnWrapper extends DualViewColumnInfo { private final ColumnInfo myBaseColumn; public Comparator getComparator() { final Comparator comparator = myBaseColumn.getComparator(); if (comparator == null) return null; return new Comparator() { public int compare(TreeNodeOnVcsRevision o1, TreeNodeOnVcsRevision o2) { if (o1 == null) return -1; if (o2 == null) return 1; VcsFileRevision revision1 = o1.myRevision; VcsFileRevision revision2 = o2.myRevision; if (revision1 == null) return -1; if (revision2 == null) return 1; return comparator.compare(revision1, revision2); } }; } public String getName() { return myBaseColumn.getName(); } public Class getColumnClass() { return myBaseColumn.getColumnClass(); } public boolean isCellEditable(TreeNodeOnVcsRevision o) { return myBaseColumn.isCellEditable(o.myRevision); } public void setValue(TreeNodeOnVcsRevision o, Object aValue) { //noinspection unchecked myBaseColumn.setValue(o.myRevision, (T)aValue); } public TableCellRenderer getRenderer(TreeNodeOnVcsRevision p0) { return myBaseColumn.getRenderer(p0.myRevision); } public TableCellEditor getEditor(TreeNodeOnVcsRevision item) { return myBaseColumn.getEditor(item.myRevision); } public String getMaxStringValue() { final String superValue = myBaseColumn.getMaxStringValue(); if (superValue != null) return superValue; return getMaxValue(myBaseColumn.getName()); } public int getAdditionalWidth() { return myBaseColumn.getAdditionalWidth(); } public int getWidth(JTable table) { return myBaseColumn.getWidth(table); } public void setName(String s) { myBaseColumn.setName(s); } public MyColumnWrapper(ColumnInfo additionalColunm) { super(additionalColunm.getName()); myBaseColumn = additionalColunm; } public boolean shouldBeShownIsTheTree() { return true; } public boolean shouldBeShownIsTheTable() { return true; } public Object valueOf(TreeNodeOnVcsRevision o) { return myBaseColumn.valueOf(o.myRevision); } } private VirtualFile getVirtualFile() { return myFilePath.getVirtualFile(); } private VirtualFile getVirtualParent() { return myFilePath.getVirtualFileParent(); } private String getMaxValue(String name) { if (myDualView == null) return null; TableView table = myDualView.getFlatView(); if (table.getRowCount() == 0) return null; final Enumeration columns = table.getColumnModel().getColumns(); int idx = 0; while (columns.hasMoreElements()) { TableColumn column = columns.nextElement(); if (name.equals(column.getHeaderValue())) { break; } ++ idx; } if (idx >= table.getColumnModel().getColumnCount() - 1) return null; final FontMetrics fm = table.getFontMetrics(table.getFont().deriveFont(Font.BOLD)); final Object header = table.getColumnModel().getColumn(idx).getHeaderValue(); double maxValue = fm.stringWidth((String)header); String value = (String)header; for (int i = 0; i < table.getRowCount(); i++) { final Object at = table.getValueAt(i, idx); if (at instanceof String) { final int newWidth = fm.stringWidth((String)at); if (newWidth > maxValue) { maxValue = newWidth; value = (String) at; } } } return value + "ww"; } private class MyTreeCellRenderer implements TreeCellRenderer { private final TreeCellRenderer myDefaultCellRenderer; private final Getter myHistorySession; public MyTreeCellRenderer(final TreeCellRenderer defaultCellRenderer, final Getter historySession) { myDefaultCellRenderer = defaultCellRenderer; myHistorySession = historySession; } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { final Component result = myDefaultCellRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); final TreePath path = tree.getPathForRow(row); if (path == null) return result; final VcsFileRevision revision = row >= 0 ? (VcsFileRevision)path.getLastPathComponent() : null; if (revision != null) { if (myHistorySession.get().isCurrentRevision(revision.getRevisionNumber())) { makeBold(result); } if (!selected && myHistorySession.get().isCurrentRevision(revision.getRevisionNumber())) { result.setBackground(new Color(188, 227, 231)); } ((JComponent)result).setOpaque(false); } else if (selected) { result.setBackground(UIUtil.getTableSelectionBackground()); } else { result.setBackground(UIUtil.getTableBackground()); } return result; } } private static class MyCellWrapper implements CellWrapper { private final Getter myHistorySession; public MyCellWrapper(final Getter historySession) { myHistorySession = historySession; } public void wrap(Component component, JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column, Object treeNode) { VcsFileRevision revision = (VcsFileRevision)treeNode; if (revision == null) return; if (myHistorySession.get().isCurrentRevision(revision.getRevisionNumber())) { makeBold(component); } } } private class RefreshFileHistoryAction extends AnAction implements DumbAware { public RefreshFileHistoryAction() { super(VcsBundle.message("action.name.refresh"), VcsBundle.message("action.desctiption.refresh"), AllIcons.Actions.Refresh); } public void actionPerformed(AnActionEvent e) { if (myInRefresh) return; refreshImpl(false); } @Override public void update(AnActionEvent e) { super.update(e); e.getPresentation().setEnabled(! myInRefresh); } } private void refreshRevisionsOrder() { final List list = myHistorySession.getRevisionList(); myRevisionsOrder.clear(); int cnt = 0; for (VcsFileRevision revision : list) { myRevisionsOrder.put(revision.getRevisionNumber(), cnt); ++ cnt; } } public void setIsStaticAndEmbedded(boolean isStaticAndEmbedded) { myIsStaticAndEmbedded = isStaticAndEmbedded; myDualView.setZipByHeight(isStaticAndEmbedded); myDualView.getFlatView().updateColumnSizes(); if (myIsStaticAndEmbedded) { disableClose(); myDualView.getFlatView().getTableHeader().setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); myDualView.getTreeView().getTableHeader().setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); myDualView.getFlatView().setBorder(null); myDualView.getTreeView().setBorder(null); } } public void setBottomRevisionForShowDiff(VcsFileRevision bottomRevisionForShowDiff) { myBottomRevisionForShowDiff = bottomRevisionForShowDiff; } private static class FolderPatchCreationTask extends Task.Backgroundable { @Nullable private final AbstractVcs myVcs; private final TreeNodeOnVcsRevision myRevision; private CommittedChangeList myList; private VcsException myException; private FolderPatchCreationTask(@Nullable AbstractVcs vcs, final TreeNodeOnVcsRevision revision) { super(vcs.getProject(), VcsBundle.message("create.patch.loading.content.progress"), true); myVcs = vcs; myRevision = revision; } @Override public void run(@NotNull ProgressIndicator indicator) { final CommittedChangesProvider provider = myVcs.getCommittedChangesProvider(); if (provider == null) return; final RepositoryLocation changedRepositoryPath = myRevision.getChangedRepositoryPath(); if (changedRepositoryPath == null) return; final VcsVirtualFile vf = new VcsVirtualFile(changedRepositoryPath.toPresentableString(), myRevision.getRevision(), VcsFileSystem.getInstance()); try { myList = ShowAllAffectedGenericAction.getRemoteList(myVcs, myRevision.getRevisionNumber(), vf); //myList = provider.getOneList(vf, myRevision.getRevisionNumber()); } catch (VcsException e1) { myException = e1; } } @Override public void onSuccess() { final AbstractVcsHelper helper = AbstractVcsHelper.getInstance(myProject); if (myException != null) { helper.showError(myException, VcsBundle.message("create.patch.error.title", myException.getMessage())); } else { if (myList == null) { helper.showError(myException, "Can not load changelist contents"); return; } CreatePatchFromChangesAction.createPatch(myProject, myList.getComment(), new ArrayList(myList.getChanges())); } } } public class MyCreatePatch extends DumbAwareAction { private final CreatePatchFromChangesAction myUsualDelegate; public MyCreatePatch() { super(VcsBundle.message("action.name.create.patch.for.selected.revisions"), VcsBundle.message("action.description.create.patch.for.selected.revisions"), AllIcons.Actions.CreatePatch); myUsualDelegate = new CreatePatchFromChangesAction(); } @Override public void actionPerformed(AnActionEvent e) { if (myFilePath.isDirectory()) { final List selection = getSelection(); if (selection.size() != 1) return; ProgressManager.getInstance().run(new FolderPatchCreationTask(myVcs, selection.get(0))); } else { myUsualDelegate.actionPerformed(e); } } @Override public void update(AnActionEvent e) { e.getPresentation().setVisible(true); if (myFilePath.isNonLocal()) { e.getPresentation().setEnabled(false); return; } boolean enabled = (! myFilePath.isDirectory()) || myProvider.supportsHistoryForDirectories(); final int selectionSize = getSelection().size(); if (enabled && (! myFilePath.isDirectory())) { // in order to do not load changes only for action update enabled = (selectionSize > 0) && (selectionSize < 3); } else if (enabled) { enabled = selectionSize == 1 && getSelection().get(0).getChangedRepositoryPath() != null; } e.getPresentation().setEnabled(enabled); } } /** * @author Kirill Likhodedov */ private class StandardDiffFromHistoryHandler implements DiffFromHistoryHandler { @Override public void showDiffForOne(@NotNull AnActionEvent e, @NotNull FilePath filePath, @NotNull VcsFileRevision previousRevision, @NotNull VcsFileRevision revision) { VcsHistoryUtil.showDifferencesInBackground(myVcs.getProject(), myFilePath, previousRevision, revision, true); } @Override public void showDiffForTwo(@NotNull FilePath filePath, @NotNull VcsFileRevision revision1, @NotNull VcsFileRevision revision2) { VcsHistoryUtil.showDifferencesInBackground(myProject, myFilePath, revision1, revision2, true); } } private class MyToggleAction extends ToggleAction implements DumbAware { public MyToggleAction() { super("Show Details", "Display details panel", AllIcons.Actions.Preview); } @Override public boolean isSelected(AnActionEvent e) { return getConfiguration().SHOW_FILE_HISTORY_DETAILS; } @Override public void setSelected(AnActionEvent e, boolean state) { getConfiguration().SHOW_FILE_HISTORY_DETAILS = state; setupDetails(); } } }