| /* |
| * 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.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.vfs.ReadonlyStatusHandler; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.ui.*; |
| import com.intellij.ui.content.ContentManager; |
| import com.intellij.ui.dualView.CellWrapper; |
| import com.intellij.ui.dualView.DualTreeElement; |
| import com.intellij.ui.dualView.DualView; |
| import com.intellij.ui.dualView.DualViewColumnInfo; |
| 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<VcsFileRevision> 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<Object> myTargetSelection; |
| private final AsynchConsumer<VcsHistorySession> 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<VcsRevisionNumber, Integer> myRevisionsOrder; |
| private boolean myIsStaticAndEmbedded; |
| private final Splitter myDetailsSplitter = new Splitter(false, 0.5f); |
| |
| private final Comparator<VcsFileRevision> myRevisionsInOrderComparator = new Comparator<VcsFileRevision>() { |
| @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<VcsRevisionNumber>(VcsBundle.message("column.name.revision.version")) { |
| protected VcsRevisionNumber getDataOf(VcsFileRevision object) { |
| return object.getRevisionNumber(); |
| } |
| |
| @Override |
| public Comparator<VcsFileRevision> 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<String>(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<String>(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<String> { |
| 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<VcsFileRevision, VirtualFile> myRevisionToVirtualFile = new HashMap<VcsFileRevision, VirtualFile>(); |
| |
| 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<VcsRevisionNumber, Integer>(); |
| 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<VcsHistorySession>() { |
| public void finished() { |
| 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 TextTransferrable(myComments.getText(), myOriginalComment); |
| } |
| else { |
| t = new TextTransferrable(selectedText, 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<DualViewColumnInfo> columns = new ArrayList<DualViewColumnInfo>(); |
| 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<DualViewColumnInfo> wrapAdditionalColumns(ColumnInfo[] additionalColumns) { |
| ArrayList<DualViewColumnInfo> result = new ArrayList<DualViewColumnInfo>(); |
| if (additionalColumns != null) { |
| for (ColumnInfo additionalColumn : additionalColumns) { |
| result.add(new MyColumnWrapper(additionalColumn)); |
| } |
| } |
| return result; |
| } |
| |
| private static List<TreeItem<VcsFileRevision>> wrapWithTreeElements(List<VcsFileRevision> revisions) { |
| ArrayList<TreeItem<VcsFileRevision>> result = new ArrayList<TreeItem<VcsFileRevision>>(); |
| for (final VcsFileRevision revision : revisions) { |
| result.add(new TreeItem<VcsFileRevision>(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<VcsHistorySession> sessionGetter = new Getter<VcsHistorySession>() { |
| 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<? extends TableModel> 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 ToggleAction("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(); |
| } |
| }); |
| } |
| |
| 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<VcsHistorySession> 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<TreeNodeOnVcsRevision> 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<TreeNodeOnVcsRevision> 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<TreeNodeOnVcsRevision> sel = getSelection(); |
| return myHistorySession.isContentAvailable(sel.get(0)); |
| } |
| else if (selectionSize > 1) { |
| return isDiffEnabled(); |
| } |
| return false; |
| } |
| |
| private boolean isDiffEnabled() { |
| List<TreeNodeOnVcsRevision> 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<TreeNodeOnVcsRevision> 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 { |
| 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); |
| |
| FileType fileType = revVFile == null ? null : revVFile.getFileType(); |
| boolean enabled = revision != null && revVFile != null && !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<FileAnnotation> fileAnnotationRef = new Ref<FileAnnotation>(); |
| final Ref<VcsException> exceptionRef = new Ref<VcsException>(); |
| |
| 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<String>() { |
| @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<VcsFileRevision>() { |
| 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, new VcsVirtualFile(filePath.getPath(), revision, VcsFileSystem.getInstance())); |
| } |
| return myRevisionToVirtualFile.get(revision); |
| } |
| |
| private List<TreeNodeOnVcsRevision> 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<TreeNodeOnVcsRevision> selection = getSelection(); |
| VcsFileRevision[] result = new VcsFileRevision[selection.size()]; |
| for(int i=0; i<selection.size(); i++) { |
| result [i] = selection.get(i).myRevision; |
| } |
| return result; |
| } |
| |
| static class TreeNodeOnVcsRevision extends DefaultMutableTreeNode implements VcsFileRevision, DualTreeElement { |
| private final VcsFileRevision myRevision; |
| |
| public TreeNodeOnVcsRevision(VcsFileRevision revision, List<TreeItem<VcsFileRevision>> roots) { |
| myRevision = revision == null ? VcsFileRevision.NULL : revision; |
| for (final TreeItem<VcsFileRevision> 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<T extends Comparable> extends DualViewColumnInfo<VcsFileRevision, String> |
| implements Comparator<VcsFileRevision> { |
| public VcsColumnInfo(String name) { |
| super(name); |
| } |
| |
| protected abstract T getDataOf(VcsFileRevision o); |
| |
| public Comparator<VcsFileRevision> 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<T> extends DualViewColumnInfo<TreeNodeOnVcsRevision, Object> { |
| private final ColumnInfo<VcsFileRevision, T> myBaseColumn; |
| |
| public Comparator<TreeNodeOnVcsRevision> getComparator() { |
| final Comparator comparator = myBaseColumn.getComparator(); |
| if (comparator == null) return null; |
| return new Comparator<TreeNodeOnVcsRevision>() { |
| 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<VcsFileRevision, T> 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<TableColumn> 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<VcsHistorySession> myHistorySession; |
| |
| public MyTreeCellRenderer(final TreeCellRenderer defaultCellRenderer, final Getter<VcsHistorySession> 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<VcsHistorySession> myHistorySession; |
| |
| public MyCellWrapper(final Getter<VcsHistorySession> 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<VcsFileRevision> 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<Change>(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<TreeNodeOnVcsRevision> 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); |
| } |
| } |
| |
| } |