| /* |
| * 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 git4idea.history.wholeTree; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.actions.ContextHelpAction; |
| import com.intellij.ide.actions.RefreshAction; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.actionSystem.impl.SimpleDataContext; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.impl.CaptionIcon; |
| import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; |
| import com.intellij.openapi.ide.CopyPasteManager; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.DumbAwareAction; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.MessageType; |
| import com.intellij.openapi.ui.Splitter; |
| import com.intellij.openapi.ui.popup.*; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.changes.Change; |
| import com.intellij.openapi.vcs.changes.actions.CreatePatchFromChangesAction; |
| import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; |
| import com.intellij.openapi.vcs.changes.committed.RepositoryChangesBrowser; |
| import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer; |
| import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; |
| import com.intellij.openapi.vcs.ui.SearchFieldAction; |
| import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; |
| import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.ui.*; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.ui.components.JBList; |
| import com.intellij.ui.table.JBTable; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.containers.MultiMap; |
| import com.intellij.util.text.DateFormatUtil; |
| import com.intellij.util.ui.AdjustComponentWhenShown; |
| import com.intellij.util.ui.ColumnInfo; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.vcsUtil.MoreAction; |
| import git4idea.GitUtil; |
| import git4idea.GitVcs; |
| import git4idea.branch.GitBranchUtil; |
| import git4idea.branch.GitBrancher; |
| import git4idea.changes.GitChangeUtils; |
| import git4idea.history.browser.*; |
| import git4idea.repo.GitRepository; |
| import icons.Git4ideaIcons; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import javax.swing.event.ListSelectionEvent; |
| import javax.swing.event.ListSelectionListener; |
| import javax.swing.table.TableCellRenderer; |
| import javax.swing.table.TableColumnModel; |
| import java.awt.*; |
| import java.awt.datatransfer.StringSelection; |
| import java.awt.event.KeyAdapter; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.MouseAdapter; |
| import java.awt.event.MouseEvent; |
| import java.io.File; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author irengrig |
| */ |
| public class GitLogUI implements Disposable { |
| private final static Logger LOG = Logger.getInstance("#git4idea.history.wholeTree.GitLogUI"); |
| public static final SimpleTextAttributes HIGHLIGHT_TEXT_ATTRIBUTES = |
| new SimpleTextAttributes(SimpleTextAttributes.STYLE_SEARCH_MATCH, UIUtil.getTableForeground()); |
| public static final String GIT_LOG_TABLE_PLACE = "git log table"; |
| private final Project myProject; |
| private final GitVcs myVcs; |
| |
| private BigTableTableModel myTableModel; |
| private DetailsCache myDetailsCache; |
| private final Mediator myMediator; |
| private Splitter mySplitter; |
| private GitTableScrollChangeListener myMyChangeListener; |
| private List<VirtualFile> myRootsUnderVcs; |
| private final Map<VirtualFile, CachedRefs> myRefs; |
| private final SymbolicRefs myRecalculatedCommon; |
| private UIRefresh myUIRefresh; |
| private JBTable myJBTable; |
| private GraphGutter myGraphGutter; |
| private RepositoryChangesBrowser myRepositoryChangesBrowser; |
| final List<CommitI> myCommitsInRepositoryChangesBrowser; |
| private boolean myDataBeingAdded; |
| private boolean myStarted; |
| private String myPreviousFilter; |
| private final CommentSearchContext myCommentSearchContext; |
| private final List<String> myUsersSearchContext; |
| private String mySelectedBranch; |
| private BranchSelectorAction myBranchSelectorAction; |
| private final DescriptionRenderer myDescriptionRenderer; |
| |
| private GenericDetailsLoader<CommitI, GitHeavyCommit> myDetailsLoader; |
| private GenericDetailsLoader<CommitI, List<String>> myBranchesLoader; |
| private GitLogDetailsPanel myDetailsPanel; |
| |
| private StepType myState; |
| private MoreAction myMoreAction; |
| private UsersFilterAction myUsersFilterAction; |
| private MyFilterUi myUserFilterI; |
| private MyRefreshAction myRefreshAction; |
| private MyStructureFilter myStructureFilter; |
| private StructureFilterAction myStructureFilterAction; |
| private DatesFilterAction myDatesFilterAction; |
| private AnAction myCopyHashAction; |
| // todo group somewhere?? |
| private Consumer<CommitI> myDetailsLoaderImpl; |
| private Consumer<CommitI> myBranchesLoaderImpl; |
| private final RequestsMerger mySelectionRequestsMerger; |
| |
| private final TableCellRenderer myAuthorRenderer; |
| private MyRootsAction myRootsAction; |
| private JPanel myEqualToHeadr; |
| private boolean myThereIsDisordering; |
| private final GitLogUI.MyShowTreeAction myMyShowTreeAction; |
| private final GitLogShowOnlyStarredCommitsAction myFilterStarredAction; |
| private boolean myIsFilterByStarOn; |
| private JLabel myOrderLabel; |
| private boolean myProjectScope; |
| private ActionPopupMenu myContextMenu; |
| private final Map<AbstractHash, Long> myMarked; |
| private final Runnable myRefresh; |
| private JViewport myTableViewPort; |
| private GitLogUI.MyGotoCommitAction myMyGotoCommitAction; |
| private final Set<VirtualFile> myClearedHighlightingRoots; |
| private final Splitter myDetailsSplitter; |
| private JScrollPane myTableScrollPane; |
| private GitLogUI.MyTextFieldAction myTextFieldAction; |
| private DatesFilterI myDatesFilter; |
| private final GitLogSettings mySettings; |
| |
| public GitLogUI(Project project, final Mediator mediator) { |
| myProject = project; |
| myVcs = GitVcs.getInstance(project); |
| mySettings = GitLogSettings.getInstance(myProject); |
| myMediator = mediator; |
| myCommentSearchContext = new CommentSearchContext(); |
| myUsersSearchContext = new ArrayList<String>(); |
| myRefs = new HashMap<VirtualFile, CachedRefs>(); |
| myRecalculatedCommon = new SymbolicRefs(); |
| myPreviousFilter = ""; |
| myDescriptionRenderer = new DescriptionRenderer(); |
| myCommentSearchContext.addHighlighter(myDescriptionRenderer.myInner.myWorker); |
| myCommitsInRepositoryChangesBrowser = new ArrayList<CommitI>(); |
| myMarked = new HashMap<AbstractHash, Long>(); |
| myClearedHighlightingRoots = new HashSet<VirtualFile>(); |
| |
| mySelectionRequestsMerger = new RequestsMerger(new Runnable() { |
| @Override |
| public void run() { |
| selectionChanged(); |
| } |
| }, new Consumer<Runnable>() { |
| @Override |
| public void consume(Runnable runnable) { |
| SwingUtilities.invokeLater(runnable); |
| } |
| }); |
| createTableModel(); |
| myState = StepType.CONTINUE; |
| |
| initUiRefresh(); |
| myAuthorRenderer = new HighLightingRenderer(HIGHLIGHT_TEXT_ATTRIBUTES, SimpleTextAttributes.REGULAR_ATTRIBUTES); |
| myMyShowTreeAction = new MyShowTreeAction(); |
| myIsFilterByStarOn = false; |
| myFilterStarredAction = new GitLogShowOnlyStarredCommitsAction(createOnOffForFilterStarred()); |
| myRefresh = new Runnable() { |
| @Override |
| public void run() { |
| reloadRequest(); |
| } |
| }; |
| myDetailsSplitter = new Splitter(true, 0.6f); |
| myDetailsSplitter.setShowDividerControls(true); |
| } |
| |
| private OnOff createOnOffForFilterStarred() { |
| return new OnOff() { |
| @Override |
| public boolean isOn() { |
| return myIsFilterByStarOn; |
| } |
| |
| @Override |
| public void on() { |
| myIsFilterByStarOn = true; |
| toggleOtherFiltersPresentation(); |
| reloadRequest(); |
| } |
| |
| @Override |
| public void off() { |
| myIsFilterByStarOn = false; |
| toggleOtherFiltersPresentation(); |
| reloadRequest(); |
| } |
| |
| private void toggleOtherFiltersPresentation() { |
| myDatesFilterAction.greyPanelFg(myIsFilterByStarOn); |
| myStructureFilterAction.greyPanelFg(myIsFilterByStarOn); |
| myBranchSelectorAction.greyPanelFg(myIsFilterByStarOn); |
| myUsersFilterAction.greyPanelFg(myIsFilterByStarOn); |
| myTextFieldAction.setTextFieldFg(myIsFilterByStarOn); |
| } |
| }; |
| } |
| |
| public void initFromSettings() { |
| mySelectedBranch = mySettings.getSelectedBranch(); |
| myUserFilterI.myFilter = mySettings.getSelectedUser(); |
| myUserFilterI.myMeSelected = mySettings.isSelectedUserMe(); |
| myUsersFilterAction.setSelectedPresets(mySettings.getSelectedUser(), mySettings.isSelectedUserMe()); |
| myBranchSelectorAction.setPreset(mySelectedBranch); |
| |
| final List<String> selectedPaths = mySettings.getSelectedPaths(); |
| if (selectedPaths != null && ! selectedPaths.isEmpty()){ |
| final ArrayList<VirtualFile> paths = new ArrayList<VirtualFile>(); |
| final LocalFileSystem lfs = LocalFileSystem.getInstance(); |
| for (String path : selectedPaths) { |
| final VirtualFile vf = lfs.refreshAndFindFileByIoFile(new File(path)); |
| if (vf == null) return; // do not keep filter if any file can not be found |
| paths.add(vf); |
| } |
| myStructureFilter.myAllSelected = false; |
| myStructureFilter.getSelected().addAll(paths); |
| } |
| myStructureFilterAction.setPreset(); |
| |
| if (mySettings.getDateState().mySelectedTime) { |
| myDatesFilterAction.setPreset(mySettings.getDateState()); |
| } |
| final HashSet<VirtualFile> activeRoots = new HashSet<VirtualFile>(); |
| final Set<String> saved = mySettings.getActiveRoots(); |
| if (! saved.isEmpty()) { |
| for (VirtualFile vf : myRootsUnderVcs) { |
| if (saved.contains(vf.getPath())) { |
| activeRoots.add(vf); |
| } |
| } |
| if (! activeRoots.isEmpty()) { |
| myTableModel.setActiveRoots(activeRoots); |
| } |
| } |
| } |
| |
| private void setOrderText(boolean topoOrder) { |
| myOrderLabel.setText(topoOrder ? "Topo Order" : "Date Order"); |
| } |
| |
| private void initUiRefresh() { |
| myUIRefresh = new UIRefresh() { |
| @Override |
| public void detailsLoaded() { |
| tryRefreshDetails(); |
| fireTableRepaint(); |
| } |
| |
| @Override |
| public void linesReloaded(boolean drawMore) { |
| if ((! StepType.STOP.equals(myState)) && (! StepType.FINISHED.equals(myState))) { |
| myState = drawMore ? StepType.PAUSE : StepType.CONTINUE; |
| } |
| fireTableRepaint(); |
| updateMoreVisibility(); |
| orderLabelVisibility(); |
| } |
| |
| @Override |
| public void acceptException(Exception e) { |
| LOG.info(e); |
| if (myVcs.getExecutableValidator().isExecutableValid()) { |
| VcsBalloonProblemNotifier.showOverChangesView(myProject, e.getMessage(), MessageType.ERROR); |
| } |
| } |
| |
| @Override |
| public void finished() { |
| myState = StepType.FINISHED; |
| updateMoreVisibility(); |
| } |
| |
| @Override |
| public void reportStash(VirtualFile root, @Nullable Couple<AbstractHash> hash) { |
| myTableModel.stashFor(root, hash); |
| } |
| |
| @Override |
| public void reportSymbolicRefs(VirtualFile root, CachedRefs symbolicRefs) { |
| myRefs.put(root, symbolicRefs); |
| myTableModel.setHeadIfEmpty(root, symbolicRefs.getHeadHash()); |
| |
| myRecalculatedCommon.clear(); |
| if (myRefs.isEmpty()) return; |
| |
| final CheckSamePattern<String> currentUser = new CheckSamePattern<String>(); |
| final CheckSamePattern<String> currentBranch = new CheckSamePattern<String>(); |
| for (CachedRefs refs : myRefs.values()) { |
| myRecalculatedCommon.addLocals(refs.getLocalBranches()); |
| myRecalculatedCommon.addRemotes(refs.getRemoteBranches()); |
| final String currentFromRefs = refs.getCurrent() == null ? null : refs.getCurrent().getFullName(); |
| currentBranch.iterate(currentFromRefs); |
| currentUser.iterate(refs.getUsername()); |
| } |
| if (currentBranch.isSame()) { |
| myRecalculatedCommon.setCurrent(myRefs.values().iterator().next().getCurrent()); |
| } |
| if (currentUser.isSame()) { |
| final String username = currentUser.getSameValue(); |
| myRecalculatedCommon.setUsername(username); |
| myUserFilterI.setMe(username); |
| } |
| |
| myBranchSelectorAction.setSymbolicRefs(myRecalculatedCommon); |
| myBranchSelectorAction.asTextAction(); |
| } |
| }; |
| } |
| |
| private void orderLabelVisibility() { |
| myOrderLabel.setVisible(myGraphGutter.getComponent().getWidth() > 60); |
| } |
| |
| private void fireTableRepaint() { |
| final TableSelectionKeeper keeper = new TableSelectionKeeper(myJBTable, myTableModel); |
| keeper.put(); |
| myDataBeingAdded = true; |
| myTableModel.fireTableDataChanged(); |
| keeper.restore(); |
| myDataBeingAdded = false; |
| myJBTable.revalidate(); |
| myJBTable.repaint(); |
| ((JComponent) myEqualToHeadr.getParent()).revalidate(); |
| myEqualToHeadr.getParent().repaint(); |
| } |
| |
| private void start() { |
| myStarted = true; |
| myMyChangeListener.start(); |
| myGraphGutter.setRowHeight(myJBTable.getRowHeight()); |
| myGraphGutter.start(); |
| rootsChanged(myRootsUnderVcs); |
| } |
| |
| private static class TableSelectionKeeper { |
| private final List<Pair<Integer, AbstractHash>> myData; |
| private final JBTable myTable; |
| private final BigTableTableModel myModel; |
| private int[] mySelectedRows; |
| |
| private TableSelectionKeeper(final JBTable table, final BigTableTableModel model) { |
| myTable = table; |
| myModel = model; |
| myData = new ArrayList<Pair<Integer,AbstractHash>>(); |
| } |
| |
| public void put() { |
| mySelectedRows = myTable.getSelectedRows(); |
| for (int row : mySelectedRows) { |
| final CommitI commitI = myModel.getCommitAt(row); |
| if (commitI != null) { |
| myData.add(Pair.create(commitI.selectRepository(SelectorList.getInstance()), commitI.getHash())); |
| } |
| } |
| } |
| |
| public void restore() { |
| final int rowCount = myModel.getRowCount(); |
| final ListSelectionModel selectionModel = myTable.getSelectionModel(); |
| for (int row : mySelectedRows) { |
| final CommitI commitI = myModel.getCommitAt(row); |
| if (commitI != null) { |
| final Pair<Integer, AbstractHash> pair = |
| Pair.create(commitI.selectRepository(SelectorList.getInstance()), commitI.getHash()); |
| if (myData.remove(pair)) { |
| selectionModel.addSelectionInterval(row, row); |
| if (myData.isEmpty()) return; |
| } |
| } |
| } |
| if (myData.isEmpty()) return; |
| for (int i = 0; i < rowCount; i++) { |
| final CommitI commitI = myModel.getCommitAt(i); |
| if (commitI == null) continue; |
| final Pair<Integer, AbstractHash> pair = |
| Pair.create(commitI.selectRepository(SelectorList.getInstance()), commitI.getHash()); |
| if (myData.remove(pair)) { |
| selectionModel.addSelectionInterval(i, i); |
| if (myData.isEmpty()) break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| public UIRefresh getUIRefresh() { |
| return myUIRefresh; |
| } |
| |
| public void createMe() { |
| mySplitter = new Splitter(false, 0.7f); |
| |
| final JPanel wrapper = createMainTable(); |
| mySplitter.setFirstComponent(wrapper); |
| |
| final JComponent component = createRepositoryBrowserDetails(); |
| mySplitter.setSecondComponent(component); |
| |
| Disposer.register(this, new Disposable() { |
| @Override |
| public void dispose() { |
| if (myMyChangeListener != null) { |
| myMyChangeListener.stop(); |
| } |
| } |
| }); |
| |
| createDetailLoaders(); |
| } |
| |
| private void createDetailLoaders() { |
| myDetailsLoaderImpl = new Consumer<CommitI>() { |
| @Override |
| public void consume(final CommitI commitI) { |
| if (commitI == null || commitI.holdsDecoration()) return; |
| final GitHeavyCommit gitCommit = fullCommitPresentation(commitI); |
| |
| if (gitCommit == null) { |
| final MultiMap<VirtualFile, AbstractHash> question = new MultiMap<VirtualFile, AbstractHash>(); |
| question.putValue(commitI.selectRepository(myRootsUnderVcs), commitI.getHash()); |
| myDetailsCache.acceptQuestion(question); |
| } else { |
| try { |
| myDetailsLoader.take(commitI, gitCommit); |
| } |
| catch (Details.AlreadyDisposedException e) { |
| // |
| } |
| } |
| } |
| }; |
| myDetailsLoader = new GenericDetailsLoader<CommitI, GitHeavyCommit>(myDetailsLoaderImpl, new PairConsumer<CommitI, GitHeavyCommit>() { |
| @Override |
| public void consume(CommitI commitI, GitHeavyCommit commit) { |
| myDetailsPanel.setData(commitI.selectRepository(myRootsUnderVcs), commit); |
| } |
| }); |
| |
| myBranchesLoaderImpl = new Consumer<CommitI>() { |
| private final Processor<AbstractHash> myRecheck; |
| |
| { |
| myRecheck = new Processor<AbstractHash>() { |
| @Override |
| public boolean process(AbstractHash abstractHash) { |
| if (myBranchesLoader.getCurrentlySelected() == null) return false; |
| return Comparing.equal(myBranchesLoader.getCurrentlySelected().getHash(), abstractHash); |
| } |
| }; |
| } |
| |
| @Override |
| public void consume(final CommitI commitI) { |
| if (commitI == null) return; |
| final VirtualFile root = commitI.selectRepository(myRootsUnderVcs); |
| final List<String> branches = myDetailsCache.getBranches(root, commitI.getHash()); |
| if (branches != null) { |
| try { |
| myBranchesLoader.take(commitI, branches); |
| } |
| catch (Details.AlreadyDisposedException e) { |
| // |
| } |
| return; |
| } |
| |
| myDetailsCache.loadAndPutBranches(root, commitI.getHash(), new Consumer<List<String>>() { |
| @Override |
| public void consume(List<String> strings) { |
| if (myProject.isDisposed() || strings == null) return; |
| try { |
| myBranchesLoader.take(commitI, strings); |
| } |
| catch (Details.AlreadyDisposedException e) { |
| // |
| } |
| } |
| }, myRecheck); |
| } |
| }; |
| myBranchesLoader = new GenericDetailsLoader<CommitI, List<String>>(myBranchesLoaderImpl, new PairConsumer<CommitI, List<String>>() { |
| @Override |
| public void consume(CommitI commitI, List<String> strings) { |
| myDetailsPanel.setBranches(strings); |
| } |
| }); |
| } |
| |
| private JComponent createRepositoryBrowserDetails() { |
| myRepositoryChangesBrowser = new RepositoryChangesBrowser(myProject, Collections.<CommittedChangeList>emptyList(), Collections.<Change>emptyList(), null); |
| myRepositoryChangesBrowser.getDiffAction().registerCustomShortcutSet(CommonShortcuts.getDiff(), myJBTable); |
| myRepositoryChangesBrowser.getViewer().setScrollPaneBorder(IdeBorderFactory.createBorder(SideBorder.LEFT | SideBorder.TOP)); |
| myJBTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { |
| @Override |
| public void valueChanged(ListSelectionEvent e) { |
| myGraphGutter.getComponent().repaint(); |
| mySelectionRequestsMerger.request(); |
| } |
| }); |
| return myRepositoryChangesBrowser; |
| } |
| |
| private void selectionChanged() { |
| if (myDataBeingAdded) { |
| mySelectionRequestsMerger.request(); |
| return; |
| } |
| final int[] rows = myJBTable.getSelectedRows(); |
| selectionChangedForDetails(rows); |
| |
| if (rows.length == 0) { |
| myRepositoryChangesBrowser.getViewer().setEmptyText("Nothing selected"); |
| return; |
| } else if (rows.length >= 10) { |
| myRepositoryChangesBrowser.getViewer().setEmptyText("Too many rows selected"); |
| return; |
| } |
| if (! myDataBeingAdded && ! gatherNotLoadedData()) { |
| myRepositoryChangesBrowser.getViewer().setEmptyText("Loading..."); |
| } |
| } |
| |
| private static class MeaningfulSelection { |
| private CommitI myCommitI; |
| private int myMeaningfulRows; |
| |
| private MeaningfulSelection(int[] rows, final BigTableTableModel tableModel) { |
| myMeaningfulRows = 0; |
| for (int row : rows) { |
| final CommitI commitAt = tableModel.getCommitAt(row); |
| if (! commitAt.holdsDecoration()) { |
| myCommitI = commitAt; |
| ++ myMeaningfulRows; |
| if (myMeaningfulRows > 1) break; |
| } |
| } |
| } |
| |
| @Nullable |
| public CommitI getCommit() { |
| return myCommitI; |
| } |
| |
| public int getMeaningfulRows() { |
| return myMeaningfulRows; |
| } |
| } |
| |
| private void tryRefreshDetails() { |
| MeaningfulSelection meaningfulSelection = new MeaningfulSelection(myJBTable.getSelectedRows(), myTableModel); |
| if (meaningfulSelection.getMeaningfulRows() == 1) { |
| // still have one item selected which probably was not loaded |
| final CommitI commit = meaningfulSelection.getCommit(); |
| myDetailsLoaderImpl.consume(commit); |
| myBranchesLoaderImpl.consume(commit); |
| } |
| } |
| |
| private void selectionChangedForDetails(int[] rows) { |
| MeaningfulSelection meaningfulSelection = new MeaningfulSelection(rows, myTableModel); |
| int meaningfulRows = meaningfulSelection.getMeaningfulRows(); |
| CommitI commitAt = meaningfulSelection.getCommit(); |
| |
| if (meaningfulRows == 0) { |
| myDetailsPanel.nothingSelected(); |
| myDetailsLoader.updateSelection(null, false); |
| myBranchesLoader.updateSelection(null, false); |
| } else if (meaningfulRows == 1) { |
| final GitHeavyCommit commit = fullCommitPresentation(commitAt); |
| if (commit == null) { |
| myDetailsPanel.loading(commitAt.selectRepository(myRootsUnderVcs)); |
| } |
| myDetailsLoader.updateSelection(commitAt, false); |
| myBranchesLoader.updateSelection(commitAt, false); |
| } else { |
| myDetailsPanel.severalSelected(); |
| myDetailsLoader.updateSelection(null, false); |
| myBranchesLoader.updateSelection(null, false); |
| } |
| } |
| |
| private GitHeavyCommit fullCommitPresentation(CommitI commitAt) { |
| return myDetailsCache.convert(commitAt.selectRepository(myRootsUnderVcs), commitAt.getHash()); |
| } |
| |
| public void updateByScroll() { |
| gatherNotLoadedData(); |
| } |
| |
| private boolean gatherNotLoadedData() { |
| if (myDataBeingAdded) return false; |
| final int[] rows = myJBTable.getSelectedRows(); |
| final List<GitHeavyCommit> commits = new ArrayList<GitHeavyCommit>(); |
| final List<CommitI> forComparison = new ArrayList<CommitI>(); |
| |
| final MultiMap<VirtualFile,AbstractHash> missingHashes = new MultiMap<VirtualFile, AbstractHash>(); |
| for (int i = rows.length - 1; i >= 0; --i) { |
| final int row = rows[i]; |
| final CommitI commitI = myTableModel.getCommitAt(row); |
| if (commitI == null || commitI.holdsDecoration()) continue; |
| final GitHeavyCommit details = fullCommitPresentation(commitI); |
| if (details == null) { |
| missingHashes.putValue(commitI.selectRepository(myRootsUnderVcs), commitI.getHash()); |
| } else if (missingHashes.isEmpty()) { // no sense in collecting commits when s |
| forComparison.add(commitI); |
| commits.add(details); |
| } |
| } |
| if (! missingHashes.isEmpty()) { |
| myDetailsCache.acceptQuestion(missingHashes); |
| return false; |
| } |
| if (Comparing.equal(myCommitsInRepositoryChangesBrowser, forComparison)) return true; |
| myCommitsInRepositoryChangesBrowser.clear(); |
| myCommitsInRepositoryChangesBrowser.addAll(forComparison); |
| |
| final List<Change> changes = new ArrayList<Change>(); |
| for (GitHeavyCommit commit : commits) { |
| changes.addAll(commit.getChanges()); |
| } |
| final List<Change> zipped = CommittedChangesTreeBrowser.zipChanges(changes); |
| myRepositoryChangesBrowser.setChangesToDisplay(zipped); |
| return true; |
| } |
| |
| private JPanel createMainTable() { |
| myJBTable = new JBTable(myTableModel) { |
| @Override |
| public TableCellRenderer getCellRenderer(int row, int column) { |
| final TableCellRenderer custom = myTableModel.getColumnInfo(column).getRenderer(myTableModel.getValueAt(row, column)); |
| return custom == null ? super.getCellRenderer(row, column) : custom; |
| } |
| }; |
| |
| if (UIUtil.isUnderDarcula()) { |
| myJBTable.setStriped(true); |
| myJBTable.setShowGrid(false); |
| } |
| final TableLinkMouseListener tableLinkListener = new TableLinkMouseListener() { |
| @Override |
| protected Object tryGetTag(MouseEvent e, JTable table, int row, int column) { |
| myDescriptionRenderer.getTableCellRendererComponent(table, table.getValueAt(row, column), false, false, row, column); |
| final Rectangle rc = table.getCellRect(row, column, false); |
| return myDescriptionRenderer.myInner.getFragmentTag(e.getPoint().x - rc.x - myDescriptionRenderer.getCurrentWidth()); |
| } |
| }; |
| final ActionToolbar actionToolbar = createToolbar(); |
| tableLinkListener.installOn(myJBTable); |
| myJBTable.getExpandableItemsHandler().setEnabled(false); |
| myJBTable.setShowGrid(false); |
| myJBTable.setModel(myTableModel); |
| myJBTable.setBorder(null); |
| final PopupHandler popupHandler = new PopupHandler() { |
| @Override |
| public void invokePopup(Component comp, int x, int y) { |
| createContextMenu(); |
| myContextMenu.getComponent().show(comp, x, y); |
| } |
| }; |
| myJBTable.addMouseListener(popupHandler); |
| |
| myTableScrollPane = ScrollPaneFactory.createScrollPane(myJBTable); |
| myTableScrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT | SideBorder.BOTTOM)); |
| myGraphGutter = new GraphGutter(myTableModel); |
| myGraphGutter.setJBTable(myJBTable); |
| myTableViewPort = myTableScrollPane.getViewport(); |
| myGraphGutter.setTableViewPort(myTableViewPort); |
| myGraphGutter.getComponent().addMouseListener(popupHandler); |
| |
| new AdjustComponentWhenShown() { |
| @Override |
| protected boolean init() { |
| return adjustColumnSizes(myTableScrollPane); |
| } |
| |
| @Override |
| protected boolean canExecute() { |
| return myStarted; |
| } |
| }.install(myJBTable); |
| |
| myMyChangeListener = new GitTableScrollChangeListener(myJBTable, myDetailsCache, myTableModel, new Runnable() { |
| @Override |
| public void run() { |
| updateByScroll(); |
| } |
| }, new Runnable() { |
| @Override |
| public void run() { |
| if (myGraphGutter.getComponent().isVisible()) { |
| myGraphGutter.getComponent().repaint(); |
| } |
| } |
| } |
| ); |
| myTableScrollPane.getViewport().addChangeListener(myMyChangeListener); |
| |
| final JPanel wrapper = new DataProviderPanel(new BorderLayout()); |
| wrapper.add(actionToolbar.getComponent(), BorderLayout.NORTH); |
| final JPanel mainBorderWrapper = new JPanel(new BorderLayout()); |
| final JPanel wrapperGutter = new JPanel(new BorderLayout()); |
| //myGraphGutter.getComponent().setVisible(false); |
| createTreeUpperComponent(); |
| wrapperGutter.add(myEqualToHeadr, BorderLayout.NORTH); |
| wrapperGutter.add(myGraphGutter.getComponent(), BorderLayout.CENTER); |
| mainBorderWrapper.add(wrapperGutter, BorderLayout.WEST); |
| mainBorderWrapper.add(myTableScrollPane, BorderLayout.CENTER); |
| //mainBorderWrapper.setBorder(BorderFactory.createLineBorder(UIUtil.getBorderColor())); |
| wrapper.add(mainBorderWrapper, BorderLayout.CENTER); |
| |
| myDetailsPanel = new GitLogDetailsPanel(myProject, myDetailsCache, new Convertor<VirtualFile, CachedRefs>() { |
| @Override |
| public CachedRefs convert(VirtualFile o) { |
| return myRefs.get(o); |
| } |
| }, new Processor<AbstractHash>() { |
| @Override |
| public boolean process(AbstractHash hash) { |
| return myMarked.containsKey(hash); |
| } |
| }); |
| |
| myDetailsSplitter.setFirstComponent(wrapper); |
| JPanel details = myDetailsPanel.getComponent(); |
| details.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT)); |
| setupDetailsSplitter(mySettings.isShowDetails()); |
| return myDetailsSplitter; |
| } |
| |
| private void createTreeUpperComponent() { |
| final MyTreeSettings treeSettings = new MyTreeSettings(); |
| myEqualToHeadr = new JPanel(new BorderLayout()) { |
| @Override |
| public Dimension getPreferredSize() { |
| return getMySize(); |
| } |
| |
| @Override |
| public Dimension getMaximumSize() { |
| return getMySize(); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| return getMySize(); |
| } |
| |
| public Dimension getMySize() { |
| final int height = myJBTable.getTableHeader().getHeight(); |
| final int width = myGraphGutter.getComponent().getPreferredSize().width; |
| return new Dimension(width, height); |
| } |
| }; |
| myEqualToHeadr.setBorder(BorderFactory.createMatteBorder(1,0,1,0, UIUtil.getBorderColor())); |
| final JPanel wr2 = new JPanel(new BorderLayout()); |
| myOrderLabel = new JLabel(); |
| myOrderLabel.setForeground(UIUtil.getInactiveTextColor()); |
| myOrderLabel.setBorder(new EmptyBorder(0, 1, 0, 0)); |
| wr2.add(myOrderLabel, BorderLayout.WEST); |
| wr2.add(treeSettings.getLabel(), BorderLayout.EAST); |
| myEqualToHeadr.add(wr2, BorderLayout.CENTER); |
| treeSettings.getLabel().setBorder(BorderFactory.createLineBorder(UIUtil.getLabelBackground())); |
| final MouseAdapter mouseAdapter = new MouseAdapter() { |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| treeSettings.execute(e); |
| } |
| |
| @Override |
| public void mouseExited(MouseEvent e) { |
| treeSettings.getLabel().setBackground(UIUtil.getLabelBackground()); |
| treeSettings.getLabel().setBorder(BorderFactory.createLineBorder(UIUtil.getLabelBackground())); |
| } |
| |
| @Override |
| public void mouseEntered(MouseEvent e) { |
| treeSettings.getLabel().setBackground(UIUtil.getLabelBackground().darker()); |
| treeSettings.getLabel().setBorder(BorderFactory.createLineBorder(Color.black)); |
| } |
| }; |
| myEqualToHeadr.addMouseListener(mouseAdapter); |
| treeSettings.getLabel().addMouseListener(mouseAdapter); |
| } |
| |
| private void createContextMenu() { |
| if (myContextMenu == null) { |
| final DefaultActionGroup group = new DefaultActionGroup(); |
| final Point location = MouseInfo.getPointerInfo().getLocation(); |
| SwingUtilities.convertPointFromScreen(location, myJBTable); |
| final int row = myJBTable.rowAtPoint(location); |
| if (row >= 0) { |
| final GitHeavyCommit commit = getCommitAtRow(row); |
| if (commit != null) { |
| myUsersFilterAction.setPreselectedUser(commit.getCommitter()); |
| } |
| } |
| group.add(getCherryPickAction()); |
| group.add(new CreatePatchFromChangesAction() { |
| @Override |
| public void update(AnActionEvent e) { |
| Change[] changes; |
| e.getPresentation().setEnabled((changes = e.getData(VcsDataKeys.CHANGES)) != null && changes.length > 0); |
| } |
| }); |
| group.add(new MyCheckoutRevisionAction()); |
| group.add(new MyCheckoutNewBranchAction()); |
| group.add(new MyCreateNewTagAction()); |
| |
| group.add(new Separator()); |
| group.add(myCopyHashAction); |
| group.add(myMyShowTreeAction); |
| group.add(myMyGotoCommitAction); |
| final CustomShortcutSet shortcutSet = |
| new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); |
| myMyGotoCommitAction.registerCustomShortcutSet(shortcutSet, myJBTable); |
| myMyGotoCommitAction.registerCustomShortcutSet(shortcutSet, myGraphGutter.getComponent()); |
| final MyHighlightCurrent myHighlightCurrent = new MyHighlightCurrent(); |
| final CustomShortcutSet customShortcutSet = new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_W, SystemInfo.isMac |
| ? KeyEvent.META_DOWN_MASK |
| : KeyEvent.CTRL_DOWN_MASK)); |
| myHighlightCurrent.registerCustomShortcutSet(customShortcutSet, myJBTable); |
| myHighlightCurrent.registerCustomShortcutSet(customShortcutSet, myGraphGutter.getComponent()); |
| |
| group.add(myHighlightCurrent); |
| group.add(new MyHighlightActionGroup()); |
| final MyToggleCommitMark toggleCommitMark = new MyToggleCommitMark(); |
| toggleCommitMark.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0)), myJBTable); |
| toggleCommitMark.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0)), |
| myGraphGutter.getComponent()); |
| group.add(toggleCommitMark); |
| group.add(new Separator()); |
| |
| group.add(myBranchSelectorAction.asTextAction()); |
| group.add(myUsersFilterAction.asTextAction()); |
| group.add(myStructureFilterAction.asTextAction()); |
| group.add(myDatesFilterAction.asTextAction()); |
| group.add(new Separator()); |
| group.add(myRefreshAction); |
| |
| group.addAll(getCustomActions()); |
| |
| myRefreshAction.registerShortcutOn(myJBTable); |
| myRefreshAction.registerShortcutOn(myGraphGutter.getComponent()); |
| |
| myContextMenu = ActionManager.getInstance().createActionPopupMenu(GIT_LOG_TABLE_PLACE, group); |
| } |
| } |
| |
| // TODO move all actions in this group, so that they are independent from this class, and would be accessible from anywhere |
| @NotNull |
| private static ActionGroup getCustomActions() { |
| return (ActionGroup)ActionManager.getInstance().getAction("Git.LogContextMenu"); |
| } |
| |
| private ActionToolbar createToolbar() { |
| final DefaultActionGroup group = new DefaultActionGroup(); |
| myBranchSelectorAction = new BranchSelectorAction(myProject, new Consumer<String>() { |
| @Override |
| public void consume(String s) { |
| mySelectedBranch = s; |
| reloadRequest(); |
| } |
| }); |
| myUserFilterI = new MyFilterUi(myRefresh); |
| myUsersFilterAction = new UsersFilterAction(myProject, myUserFilterI); |
| myTextFieldAction = new MyTextFieldAction(); |
| group.add(myTextFieldAction); |
| group.add(myBranchSelectorAction); |
| group.add(myUsersFilterAction); |
| Getter<List<VirtualFile>> rootsGetter = new Getter<List<VirtualFile>>() { |
| @Override |
| public List<VirtualFile> get() { |
| return myRootsUnderVcs; |
| } |
| }; |
| myStructureFilter = new MyStructureFilter(myRefresh, rootsGetter); |
| myStructureFilterAction = new StructureFilterAction(myProject, myStructureFilter); |
| group.add(myStructureFilterAction); |
| myDatesFilter = new DatesFilterI() { |
| @Override |
| public long getBefore() { |
| return mySettings.getDateState().mySelectedTime ? mySettings.getDateState().myTimeBefore : -1; |
| } |
| |
| @Override |
| public long getAfter() { |
| return mySettings.getDateState().mySelectedTime ? mySettings.getDateState().myTimeAfter : -1; |
| } |
| |
| @Override |
| public boolean isAll() { |
| return ! mySettings.getDateState().mySelectedTime; |
| } |
| |
| @Override |
| public long getCommitTimeIfOne() { |
| final CommitI commitAt = getCommitIfOneRealSelected(); |
| return commitAt == null ? -1 : commitAt.getTime(); |
| } |
| |
| @Override |
| public void selectAll() { |
| mySettings.getDateState().mySelectedTime = false; |
| myRefresh.run(); |
| } |
| |
| @Override |
| public void filter(long before, long after, String presetFilterName) { |
| final GitLogSettings.MyDateState state = mySettings.getDateState(); |
| state.myTimeBefore = before; |
| state.myTimeAfter = after; |
| state.mySelectedTime = true; |
| state.myPresetFilter = presetFilterName; |
| myRefresh.run(); |
| } |
| }; |
| myDatesFilterAction = new DatesFilterAction(myProject, myDatesFilter); |
| group.add(myDatesFilterAction); |
| group.add(myFilterStarredAction); |
| group.add(getCherryPickAction()); |
| group.add(ActionManager.getInstance().getAction("ChangesView.CreatePatchFromChanges")); |
| myRefreshAction = new MyRefreshAction(); |
| myRootsAction = new MyRootsAction(rootsGetter, myJBTable); |
| group.add(myRootsAction); |
| group.add(myMyShowTreeAction); |
| |
| group.add(new ToggleAction("Show Details", "Display details panel", AllIcons.Actions.Preview) { |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return !myProject.isDisposed() && mySettings.isShowDetails(); |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| setupDetailsSplitter(state); |
| if (!myProject.isDisposed()) { |
| mySettings.setShowDetails(state); |
| } |
| } |
| }); |
| |
| myMyGotoCommitAction = new MyGotoCommitAction(); |
| group.add(myMyGotoCommitAction); |
| group.add(myRefreshAction); |
| group.add(new ContextHelpAction("reference.changesToolWindow.log")); |
| |
| //group.add(new TestIndexAction()); |
| myMoreAction = new MoreAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| myMediator.continueLoading(); |
| myState = StepType.CONTINUE; |
| updateMoreVisibility(); |
| } |
| }; |
| group.add(myMoreAction); |
| // just created here |
| myCopyHashAction = new AnAction("Copy Hash") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| final StringBuilder sb = new StringBuilder(); |
| for (int row : selectedRows) { |
| final CommitI commitAt = myTableModel.getCommitAt(row); |
| if (commitAt == null) continue; |
| if (sb.length() > 0) { |
| sb.append(' '); |
| } |
| sb.append(commitAt.getHash().getString()); |
| } |
| CopyPasteManager.getInstance().setContents(new StringSelection(sb.toString())); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| e.getPresentation().setEnabled(myJBTable.getSelectedRowCount() > 0); |
| } |
| }; |
| return ActionManager.getInstance().createActionToolbar("Git log", group, true); |
| } |
| |
| private static AnAction getCherryPickAction() { |
| return ActionManager.getInstance().getAction("Git.CherryPick"); |
| } |
| |
| private void setupDetailsSplitter(boolean state) { |
| myDetailsSplitter.setSecondComponent(state ? myDetailsPanel.getComponent() : null); |
| if (state) { |
| myTableScrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT | SideBorder.BOTTOM)); |
| } |
| else { |
| myTableScrollPane.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT)); |
| } |
| } |
| |
| private class DataProviderPanel extends JPanel implements TypeSafeDataProvider { |
| |
| private final GitCommitDetailsProvider myCommitDetailsProvider; |
| |
| private DataProviderPanel(LayoutManager layout) { |
| super(layout); |
| myCommitDetailsProvider = new GitCommitDetailsProvider() { |
| @NotNull |
| @Override |
| public List<String> getContainingBranches(@NotNull VirtualFile root, @NotNull AbstractHash commitHash) { |
| List<String> branches = myDetailsCache.getBranches(root, commitHash); |
| return branches == null ? Collections.<String>emptyList() : branches; |
| } |
| }; |
| } |
| |
| @Override |
| public void calcData(DataKey key, DataSink sink) { |
| if (VcsDataKeys.CHANGES.equals(key) || GitVcs.GIT_COMMIT.equals(key)) { |
| final int[] rows = myJBTable.getSelectedRows(); |
| if (rows.length != 1) return; |
| int row = rows[0]; |
| final GitHeavyCommit gitCommit = getCommitAtRow(row); |
| if (gitCommit == null) return; |
| |
| if (VcsDataKeys.CHANGES.equals(key)) { |
| final List<Change> changes = new ArrayList<Change>(gitCommit.getChanges()); |
| sink.put(key, changes.toArray(new Change[changes.size()])); |
| } |
| else if (GitVcs.GIT_COMMIT.equals(key)) { |
| sink.put(key, gitCommit); |
| } |
| } |
| else if (GitVcs.SELECTED_COMMITS.equals(key)) { |
| sink.put(key, getSelectedCommits()); |
| } |
| else if (GitVcs.COMMIT_DETAILS_PROVIDER.equals(key)) { |
| sink.put(key, myCommitDetailsProvider); |
| } |
| else if (VcsDataKeys.PRESET_COMMIT_MESSAGE.equals(key)) { |
| final int[] rows = myJBTable.getSelectedRows(); |
| if (rows.length != 1) return; |
| final CommitI commitAt = myTableModel.getCommitAt(rows[0]); |
| if (commitAt == null) return; |
| final GitHeavyCommit gitCommit = fullCommitPresentation(commitAt); |
| if (gitCommit == null) return; |
| sink.put(key, gitCommit.getDescription()); |
| } |
| } |
| } |
| |
| /** |
| * Returns the list of selected commits. The list is sorted so that newest commits go first (because they are above in the log). |
| */ |
| @NotNull |
| private List<GitHeavyCommit> getSelectedCommits() { |
| if (myJBTable == null) { |
| return Collections.emptyList(); |
| } |
| |
| final List<GitHeavyCommit> commits = new ArrayList<GitHeavyCommit>(); |
| for (int row : myJBTable.getSelectedRows()) { |
| final CommitI commitI = myTableModel.getCommitAt(row); |
| if (commitI == null) { |
| return Collections.emptyList(); |
| } |
| if (commitI.holdsDecoration()) { |
| continue; |
| } |
| final VirtualFile root = commitI.selectRepository(myRootsUnderVcs); |
| final GitHeavyCommit gitCommit = myDetailsCache.convert(root, commitI.getHash()); |
| if (gitCommit == null) { |
| return Collections.emptyList(); |
| } |
| commits.add(gitCommit); |
| } |
| return commits; |
| } |
| |
| @Nullable |
| private GitHeavyCommit getCommitAtRow(int row) { |
| final CommitI commitAt = myTableModel.getCommitAt(row); |
| if (commitAt == null) return null; |
| final GitHeavyCommit gitCommit = fullCommitPresentation(commitAt); |
| if (gitCommit == null) return null; |
| return gitCommit; |
| } |
| |
| private boolean adjustColumnSizes(JScrollPane scrollPane) { |
| if (myJBTable.getWidth() <= 0) return false; |
| createContextMenu(); |
| //myJBTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); |
| final TableColumnModel columnModel = myJBTable.getColumnModel(); |
| final FontMetrics metrics = myJBTable.getFontMetrics(myJBTable.getFont()); |
| final int height = metrics.getHeight(); |
| myJBTable.setRowHeight((int) (height * 1.3) + 1); |
| myGraphGutter.setRowHeight(myJBTable.getRowHeight()); |
| myGraphGutter.setHeaderHeight(myJBTable.getTableHeader().getHeight()); |
| final int dateWidth = metrics.stringWidth("Yesterday 00:00:00 " + scrollPane.getVerticalScrollBar().getWidth()) + columnModel.getColumnMargin(); |
| final int nameWidth = metrics.stringWidth("Somelong W. UsernameToDisplay"); |
| int widthWas = 0; |
| for (int i = 0; i < columnModel.getColumnCount(); i++) { |
| widthWas += columnModel.getColumn(i).getWidth(); |
| } |
| |
| columnModel.getColumn(1).setWidth(nameWidth); |
| columnModel.getColumn(1).setPreferredWidth(nameWidth); |
| |
| columnModel.getColumn(2).setWidth(dateWidth); |
| columnModel.getColumn(2).setPreferredWidth(dateWidth); |
| |
| final int nullWidth = widthWas - dateWidth - nameWidth - columnModel.getColumnMargin() * 3; |
| columnModel.getColumn(0).setWidth(nullWidth); |
| columnModel.getColumn(0).setPreferredWidth(nullWidth); |
| |
| return true; |
| } |
| |
| private static class CommentSearchContext { |
| private final List<HighlightingRendererBase> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| private final List<String> mySearchContext; |
| |
| private CommentSearchContext() { |
| mySearchContext = new ArrayList<String>(); |
| } |
| |
| public void addHighlighter(final HighlightingRendererBase renderer) { |
| myListeners.add(renderer); |
| } |
| |
| public void clear() { |
| mySearchContext.clear(); |
| for (HighlightingRendererBase listener : myListeners) { |
| listener.setSearchContext(Collections.<String>emptyList()); |
| } |
| } |
| |
| public String preparse(String previousFilter) { |
| final String[] strings = previousFilter.split("[\\s]"); |
| StringBuilder sb = new StringBuilder(); |
| mySearchContext.clear(); |
| for (String string : strings) { |
| if (string.trim().length() == 0) continue; |
| mySearchContext.add(string.toLowerCase()); |
| final String word = StringUtil.escapeToRegexp(string); |
| sb.append(word).append(".*"); |
| } |
| new SubstringsFilter().doFilter(mySearchContext); |
| for (HighlightingRendererBase listener : myListeners) { |
| listener.setSearchContext(mySearchContext); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| public static class SubstringsFilter extends AbstractFilterChildren<String> { |
| @Override |
| protected boolean isAncestor(String parent, String child) { |
| return parent.startsWith(child); |
| } |
| |
| @Override |
| protected void sortAscending(List<String> strings) { |
| Collections.sort(strings, new ComparableComparator.Descending<String>()); |
| } |
| } |
| |
| public JComponent getPanel() { |
| return mySplitter; |
| } |
| |
| public void rootsChanged(List<VirtualFile> rootsUnderVcs) { |
| final RootsHolder wasRootsHolder = myTableModel.getRootsHolder(); |
| final HashSet<VirtualFile> activeRoots = new HashSet<VirtualFile>(rootsUnderVcs); |
| |
| if (wasRootsHolder != null) { |
| final HashSet<VirtualFile> excludedRoots = new HashSet<VirtualFile>(wasRootsHolder.getRoots()); |
| excludedRoots.removeAll(myTableModel.getActiveRoots()); |
| activeRoots.removeAll(excludedRoots); |
| } |
| |
| myRootsUnderVcs = rootsUnderVcs; |
| final RootsHolder rootsHolder = new RootsHolder(rootsUnderVcs); |
| myTableModel.setRootsHolder(rootsHolder); |
| myTableModel.setActiveRoots(activeRoots); |
| myDetailsCache.rootsChanged(rootsUnderVcs); |
| |
| if (myStarted) { |
| reloadRequest(); |
| } |
| } |
| |
| public UIRefresh getRefreshObject() { |
| return myUIRefresh; |
| } |
| |
| public BigTableTableModel getTableModel() { |
| return myTableModel; |
| } |
| |
| private void createTableModel() { |
| myTableModel = new BigTableTableModel(columns(), new Runnable() { |
| @Override |
| public void run() { |
| start(); |
| } |
| }); |
| } |
| |
| List<ColumnInfo> columns() { |
| initAuthor(); |
| return Arrays.asList((ColumnInfo)COMMENT, AUTHOR, DATE); |
| } |
| |
| private final ColumnInfo<Object, Object> COMMENT = new ColumnInfo<Object, Object>("Comment") { |
| private final TableCellRenderer mySimpleRenderer = new SimpleRenderer(SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true); |
| |
| @Override |
| public Object valueOf(Object o) { |
| if (o instanceof GitHeavyCommit) { |
| return o; |
| } |
| if (BigTableTableModel.LOADING == o) return o; |
| return o == null ? "" : o.toString(); |
| } |
| |
| @Override |
| public TableCellRenderer getRenderer(Object o) { |
| return o instanceof GitHeavyCommit ? myDescriptionRenderer : mySimpleRenderer; |
| } |
| }; |
| |
| private class HighLightingRenderer extends ColoredTableCellRenderer { |
| private final SimpleTextAttributes myHighlightAttributes; |
| private final SimpleTextAttributes myUsualAttributes; |
| private SimpleTextAttributes myUsualAttributesForRun; |
| protected final HighlightingRendererBase myWorker; |
| |
| public HighLightingRenderer(SimpleTextAttributes highlightAttributes, SimpleTextAttributes usualAttributes) { |
| myHighlightAttributes = highlightAttributes; |
| myUsualAttributes = usualAttributes == null ? SimpleTextAttributes.REGULAR_ATTRIBUTES : usualAttributes; |
| myUsualAttributesForRun = myUsualAttributes; |
| myWorker = new HighlightingRendererBase() { |
| @Override |
| protected void usual(String s) { |
| append(s, myUsualAttributesForRun); |
| } |
| |
| @Override |
| protected void highlight(String s) { |
| append(s, SimpleTextAttributes.merge(myUsualAttributesForRun, myHighlightAttributes)); |
| } |
| }; |
| } |
| |
| public HighlightingRendererBase getWorker() { |
| return myWorker; |
| } |
| |
| @Override |
| protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { |
| setBackground(getRowBg(row)); |
| //setBackground(getLogicBackground(selected, row)); |
| if (BigTableTableModel.LOADING == value) { |
| return; |
| } |
| final String text = value.toString(); |
| myUsualAttributesForRun = isCurrentUser(row, text) ? |
| SimpleTextAttributes.merge(myUsualAttributes, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES) : |
| new SimpleTextAttributes(myUsualAttributes.getBgColor(), myUsualAttributes.getFgColor(), |
| myUsualAttributes.getWaveColor(), myUsualAttributes.getStyle()); |
| if (myWorker.isEmpty()) { |
| append(text, myUsualAttributesForRun); |
| return; |
| } |
| myWorker.tryHighlight(text); |
| } |
| |
| private boolean isCurrentUser(final int row, final String text) { |
| final CommitI commitAt = myTableModel.getCommitAt(row); |
| if (commitAt == null) return false; |
| final SymbolicRefsI symbolicRefs = myRefs.get(commitAt.selectRepository(myRootsUnderVcs)); |
| if (symbolicRefs == null) return false; |
| return Comparing.equal(symbolicRefs.getUsername(), text); |
| } |
| } |
| |
| private class SimpleRenderer extends ColoredTableCellRenderer { |
| private final SimpleTextAttributes myAtt; |
| private final boolean myShowLoading; |
| |
| public SimpleRenderer(SimpleTextAttributes att, boolean showLoading) { |
| myAtt = att; |
| myShowLoading = showLoading; |
| } |
| |
| @Override |
| protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { |
| setBackground(getRowBg(row)); |
| if (BigTableTableModel.LOADING == value) { |
| if (myShowLoading) { |
| append("Loading..."); |
| } |
| return; |
| } |
| append(value.toString(), myAtt); |
| } |
| } |
| |
| private class DescriptionRenderer implements TableCellRenderer { |
| private final Map<String, Icon> myTagMap; |
| private final Map<String, Icon> myBranchMap; |
| private final JPanel myPanel; |
| private final Inner myInner; |
| private int myCurrentWidth; |
| |
| private DescriptionRenderer() { |
| myInner = new Inner(); |
| myTagMap = new HashMap<String, Icon>(); |
| myBranchMap = new HashMap<String, Icon>(); |
| myPanel = new JPanel(); |
| myPanel.setBackground(UIUtil.getTableBackground()); |
| final BoxLayout layout = new BoxLayout(myPanel, BoxLayout.X_AXIS); |
| myPanel.setLayout(layout); |
| myCurrentWidth = 0; |
| } |
| |
| public void resetIcons() { |
| myBranchMap.clear(); |
| myTagMap.clear(); |
| } |
| |
| public int getCurrentWidth() { |
| return myCurrentWidth; |
| } |
| |
| @Override |
| public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
| myCurrentWidth = 0; |
| final Color bg = isSelected ? UIUtil.getTableSelectionBackground() : getRowBg(row); |
| if (value instanceof GitHeavyCommit) { |
| myPanel.removeAll(); |
| myPanel.setBackground(bg); |
| final GitHeavyCommit commit = (GitHeavyCommit)value; |
| |
| final boolean marked = myMarked.containsKey(commit.getShortHash()); |
| final int localSize = commit.getLocalBranches() == null ? 0 : commit.getLocalBranches().size(); |
| final int remoteSize = commit.getRemoteBranches() == null ? 0 : commit.getRemoteBranches().size(); |
| final int tagsSize = commit.getTags().size(); |
| |
| if (marked) { |
| myPanel.add(new JLabel(Git4ideaIcons.Star)); |
| myCurrentWidth += Git4ideaIcons.Star.getIconWidth(); |
| } |
| if (localSize + remoteSize > 0) { |
| final CommitI commitI = myTableModel.getCommitAt(row); |
| final List<Trinity<String, Boolean, Color>> display = getBranchesToDisplay(commit, commitI); |
| boolean containsHead = commit.getTags().contains("HEAD"); |
| |
| final boolean plus = localSize + remoteSize + tagsSize > (display.size() + (containsHead ? 1 : 0)); |
| for (int i = 0; i < display.size(); i++) { |
| final Trinity<String, Boolean, Color> trinity = display.get(i); |
| boolean withContionuation = containsHead ? false : (plus && (i == display.size() - 1)); |
| String key = trinity.getFirst() + (withContionuation ? "@" : ""); |
| Icon icon = myBranchMap.get(key); |
| if (icon == null) { |
| icon = new CaptionIcon(trinity.getThird(), table.getFont().deriveFont((float) table.getFont().getSize() - 1), |
| trinity.getFirst(), table, CaptionIcon.Form.SQUARE, |
| withContionuation, trinity.getSecond()); |
| myBranchMap.put(key, icon); |
| } |
| addOneIcon(table, value, isSelected, hasFocus, row, column, icon); |
| } |
| if (tagsSize > 0 && containsHead) { |
| addTagIcon(table, value, isSelected, hasFocus, row, column, "HEAD", plus); |
| } |
| |
| myInner.setBackground(bg); |
| return myPanel; |
| } |
| if ((localSize + remoteSize == 0) && (tagsSize > 0)) { |
| final String tag = commit.getTags().get(0); |
| addTagIcon(table, value, isSelected, hasFocus, row, column, tag, tagsSize > 1); |
| myInner.setBackground(bg); |
| return myPanel; |
| } |
| if (marked) { |
| myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); |
| myPanel.add(myInner); |
| myInner.setBackground(bg); |
| return myPanel; |
| } |
| } |
| myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); |
| myInner.setBackground(bg); |
| return myInner; |
| } |
| |
| private void addTagIcon(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column, String tag, final boolean plus) { |
| String key = tag + (plus ? "@" : ""); |
| Icon icon = myTagMap.get(key); |
| if (icon == null) { |
| icon = new CaptionIcon(Colors.tag, table.getFont().deriveFont((float) table.getFont().getSize() - 1), |
| tag, table, CaptionIcon.Form.ROUNDED, plus, false); |
| myTagMap.put(key, icon); |
| } |
| addOneIcon(table, value, isSelected, hasFocus, row, column, icon); |
| } |
| |
| private List<Trinity<String, Boolean, Color>> getBranchesToDisplay(final GitHeavyCommit commit, final CommitI commitI) { |
| final List<Trinity<String, Boolean, Color>> result = new ArrayList<Trinity<String, Boolean, Color>>(); |
| |
| final List<String> localBranches = commit.getLocalBranches(); |
| final SymbolicRefsI symbolicRefs = myRefs.get(commitI.selectRepository(myRootsUnderVcs)); |
| final String currentName = symbolicRefs.getCurrentName(); |
| final String trackedRemoteName = symbolicRefs.getTrackedRemoteName(); |
| |
| if (currentName != null && localBranches.contains(currentName)) { |
| result.add(new Trinity<String, Boolean, Color>(currentName, true, Colors.local)); |
| } |
| final List<String> remoteBranches = commit.getRemoteBranches(); |
| if (trackedRemoteName != null && remoteBranches.contains(trackedRemoteName)) { |
| result.add(new Trinity<String, Boolean, Color>(trackedRemoteName, true, Colors.remote)); |
| } |
| if (result.isEmpty()) { |
| boolean remote = localBranches.isEmpty(); |
| result.add(new Trinity<String, Boolean, Color>(remote ? (remoteBranches.get(0)) : localBranches.get(0), false, |
| remote ? Colors.remote : Colors.local)); |
| } |
| return result; |
| } |
| |
| private void addOneIcon(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column, Icon icon) { |
| myCurrentWidth += icon.getIconWidth(); |
| //myPanel.removeAll(); |
| //myPanel.setBackground(getLogicBackground(isSelected, row)); |
| myPanel.add(new JLabel(icon)); |
| myInner.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); |
| myPanel.add(myInner); |
| } |
| |
| private class Inner extends HighLightingRenderer { |
| private final IssueLinkRenderer myIssueLinkRenderer; |
| private final Consumer<String> myConsumer; |
| |
| private Inner() { |
| super(HIGHLIGHT_TEXT_ATTRIBUTES, null); |
| myIssueLinkRenderer = new IssueLinkRenderer(myProject, this); |
| myConsumer = new Consumer<String>() { |
| @Override |
| public void consume(String s) { |
| myWorker.tryHighlight(s); |
| } |
| }; |
| } |
| @Override |
| protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { |
| //setBackground(getLogicBackground(selected, row)); |
| if (value instanceof GitHeavyCommit) { |
| final GitHeavyCommit gitCommit = (GitHeavyCommit)value; |
| myIssueLinkRenderer.appendTextWithLinks(gitCommit.getSubject(), SimpleTextAttributes.REGULAR_ATTRIBUTES, myConsumer); |
| //super.customizeCellRenderer(table, ((GitCommit) value).getDescription(), selected, hasFocus, row, column); |
| } else { |
| super.customizeCellRenderer(table, value, selected, hasFocus, row, column); |
| } |
| } |
| } |
| } |
| |
| private Color getRowBg(int row) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows != null && selectedRows.length > 0) { |
| for (int selectedRow : selectedRows) { |
| if (selectedRow == row) { |
| return UIUtil.getTableSelectionBackground(); |
| } |
| } |
| } |
| if (myClearedHighlightingRoots.isEmpty()) { |
| return myTableModel.isInCurrentBranch(row) ? Colors.highlighted : UIUtil.isUnderDarcula() ? UIUtil.getTableBackground().darker() : UIUtil.getTableBackground(); |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(row); |
| if (commitAt.holdsDecoration()) { |
| return UIUtil.getTableBackground(); |
| } |
| final VirtualFile virtualFile = commitAt.selectRepository(myRootsUnderVcs); |
| return myClearedHighlightingRoots.contains(virtualFile) ? UIUtil.getTableBackground() : |
| (myTableModel.isInCurrentBranch(row) ? Colors.highlighted : UIUtil.getTableBackground()); |
| } |
| |
| //private Color getLogicBackground(final boolean isSelected, final int row) { |
| // Color bkgColor; |
| // final CommitI commitAt = myTableModel.getCommitAt(row); |
| // GitCommit gitCommit = null; |
| // if (commitAt != null && (! commitAt.holdsDecoration())) { |
| // gitCommit = fullCommitPresentation(commitAt); |
| // } |
| // |
| // if (isSelected) { |
| // bkgColor = UIUtil.getTableSelectionBackground(); |
| // } else { |
| // bkgColor = UIUtil.getTableBackground(); |
| // if (gitCommit != null) { |
| // if (myDetailsCache.getStashName(commitAt.selectRepository(myRootsUnderVcs), gitCommit.getShortHash()) != null) { |
| // bkgColor = Colors.stashed; |
| // } else if (gitCommit.isOnLocal() && gitCommit.isOnTracked()) { |
| // bkgColor = Colors.commonThisBranch; |
| // } else if (gitCommit.isOnLocal()) { |
| // bkgColor = Colors.ownThisBranch; |
| // } |
| // } |
| // } |
| // return bkgColor; |
| //} |
| |
| private ColumnInfo<Object, String> AUTHOR; |
| |
| private void initAuthor() { |
| AUTHOR = new ColumnInfo<Object, String>("Author") { |
| @Override |
| public String valueOf(Object o) { |
| if (o instanceof GitHeavyCommit) { |
| return ((GitHeavyCommit)o).getAuthor(); |
| } |
| return ""; |
| } |
| |
| @Override |
| public TableCellRenderer getRenderer(Object o) { |
| return myAuthorRenderer; |
| } |
| }; |
| } |
| |
| private final ColumnInfo<Object, String> DATE = new ColumnInfo<Object, String>("Date") { |
| private final TableCellRenderer myRenderer = new SimpleRenderer(SimpleTextAttributes.REGULAR_ATTRIBUTES, false); |
| |
| @Override |
| public String valueOf(Object o) { |
| if (o instanceof GitHeavyCommit) { |
| return DateFormatUtil.formatPrettyDateTime(((GitHeavyCommit)o).getDate()); |
| } |
| return ""; |
| } |
| |
| @Override |
| public TableCellRenderer getRenderer(Object o) { |
| return myRenderer; |
| } |
| }; |
| |
| public void setDetailsCache(DetailsCache detailsCache) { |
| myDetailsCache = detailsCache; |
| } |
| |
| // todo test action |
| private class TestIndexAction extends DumbAwareAction { |
| private TestIndexAction() { |
| super("Test Index", "Test Index", PlatformIcons.CHECK_ICON); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| myTableModel.printNavigation(); |
| //final BigTableTableModel.WiresGroupIterator iterator = myTableModel.getGroupIterator(myTableModel.getRowCount() - 1); |
| } |
| } |
| |
| private class MyRefreshAction extends RefreshAction { |
| private MyRefreshAction() { |
| super("Refresh", "Refresh", AllIcons.Actions.Refresh); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| rootsChanged(myRootsUnderVcs); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| e.getPresentation().setEnabled(true); |
| } |
| } |
| |
| private class MyTextFieldAction extends SearchFieldAction { |
| private MyTextFieldAction() { |
| super("Filter:"); |
| |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| checkIfFilterChanged(); |
| } |
| |
| private void checkIfFilterChanged() { |
| final String newValue = getText().trim(); |
| if (! Comparing.equal(myPreviousFilter, newValue)) { |
| myPreviousFilter = newValue; |
| |
| reloadRequest(); |
| } |
| } |
| } |
| |
| private void reloadRequest() { |
| // store state |
| mySettings.setSelectedBranch(mySelectedBranch); |
| mySettings.setSelectedUser(myUserFilterI.myFilter); |
| mySettings.setSelectedUserIsMe(myUserFilterI.isMeSelected()); |
| mySettings.setSelectedPaths(myStructureFilter.myAllSelected ? null : myStructureFilter.getSelected()); |
| |
| myState = StepType.CONTINUE; |
| final int was = myTableModel.getRowCount(); |
| myDetailsCache.resetAsideCaches(); |
| final Collection<String> startingPoints = mySelectedBranch == null ? Collections.<String>emptyList() : Collections.singletonList(mySelectedBranch); |
| final GitLogSettings.MyDateState dateState = mySettings.getDateState(); |
| Set<ChangesFilter.Filter> dateFilters = null; |
| if (dateState.mySelectedTime) { |
| dateFilters = applyDateFilter(); |
| } |
| myDescriptionRenderer.resetIcons(); |
| final boolean commentFilterEmpty = StringUtil.isEmptyOrSpaces(myPreviousFilter); |
| myCommentSearchContext.clear(); |
| myUsersSearchContext.clear(); |
| |
| final boolean haveFilters = (! (commentFilterEmpty && (myUserFilterI.myFilter == null) && myStructureFilter.myAllSelected && |
| (! mySettings.getDateState().mySelectedTime))) || myIsFilterByStarOn; |
| //myThereIsDisordering = (! (commentFilterEmpty && (myUserFilterI.myFilter == null) && myStructureFilter.myAllSelected)) || myIsFilterByStarOn; |
| myThereIsDisordering = haveFilters; |
| final boolean topoOrder = (!myThereIsDisordering) && myRootsUnderVcs.size() == 1 ? mySettings.isTopoOrder() : false; |
| myOrderLabel.setVisible(false); |
| setOrderText(topoOrder); |
| if (topoOrder) { |
| myTableModel.useNoGroupingStrategy(); |
| } else { |
| myTableModel.useDateGroupingStrategy(); |
| } |
| |
| myEqualToHeadr.getParent().setVisible(! myThereIsDisordering && myMyShowTreeAction.isSelected(null)); |
| if (! haveFilters) { |
| /*if (myMyShowTreeAction.isSelected(null)) { |
| myEqualToHeadr.getParent().setVisible(true); |
| }*/ |
| myUsersSearchContext.clear(); |
| myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, null, new GitLogFilters(), topoOrder); |
| } else if (myIsFilterByStarOn) { |
| myUsersSearchContext.clear(); |
| myMediator.reloadSetFixed(myMarked, new RootsHolder(myRootsUnderVcs)); |
| } else { |
| ChangesFilter.Comment comment = null; |
| if (! commentFilterEmpty) { |
| final String commentFilter = myCommentSearchContext.preparse(myPreviousFilter); |
| comment = new ChangesFilter.Comment(commentFilter); |
| } |
| Set<ChangesFilter.Filter> userFilters = null; |
| if (myUserFilterI.myFilter != null) { |
| userFilters = applyUserFilter(); |
| } |
| Map<VirtualFile, ChangesFilter.Filter> structureFilters = null; |
| if (! myStructureFilter.myAllSelected) { |
| structureFilters = applyStructureFilter(); |
| } |
| |
| final List<String> possibleReferencies = commentFilterEmpty ? null : Arrays.asList(myPreviousFilter.split("[\\s]")); |
| myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, null, new GitLogFilters(comment, userFilters, dateFilters, |
| structureFilters, possibleReferencies), topoOrder); |
| } |
| myCommentSearchContext.addHighlighter(myDetailsPanel.getHtmlHighlighter()); |
| updateMoreVisibility(); |
| mySelectionRequestsMerger.request(); |
| fireTableRepaint(); |
| myTableModel.fireTableRowsDeleted(0, was); |
| myGraphGutter.getComponent().revalidate(); |
| myGraphGutter.getComponent().repaint(); |
| } |
| |
| private Set<ChangesFilter.Filter> applyDateFilter() { |
| final Set<ChangesFilter.Filter> result = new HashSet<ChangesFilter.Filter>(); |
| final long timeBefore = mySettings.getDateState().myTimeBefore; |
| if (timeBefore > 0) { |
| result.add(new ChangesFilter.BeforeTime(timeBefore)); |
| } |
| final long timeAfter = mySettings.getDateState().myTimeAfter; |
| if (timeAfter > 0) { |
| result.add(new ChangesFilter.AfterTime(timeAfter)); |
| } |
| return result; |
| } |
| |
| private Map<VirtualFile, ChangesFilter.Filter> applyStructureFilter() { |
| Map<VirtualFile, ChangesFilter.Filter> structureFilters; |
| structureFilters = new HashMap<VirtualFile, ChangesFilter.Filter>(); |
| final Collection<VirtualFile> selected = new ArrayList<VirtualFile>(myStructureFilter.getSelected()); |
| final ArrayList<VirtualFile> copy = new ArrayList<VirtualFile>(myRootsUnderVcs); |
| Collections.sort(copy, FilePathComparator.getInstance()); |
| Collections.reverse(copy); |
| for (VirtualFile root : copy) { |
| final Collection<VirtualFile> selectedForRoot = new SmartList<VirtualFile>(); |
| final Iterator<VirtualFile> iterator = selected.iterator(); |
| while (iterator.hasNext()) { |
| VirtualFile next = iterator.next(); |
| if (VfsUtil.isAncestor(root, next, false)) { |
| selectedForRoot.add(next); |
| iterator.remove(); |
| } |
| } |
| if (! selectedForRoot.isEmpty()) { |
| final ChangesFilter.StructureFilter structureFilter = new ChangesFilter.StructureFilter(); |
| structureFilter.addFiles(selectedForRoot); |
| structureFilters.put(root, structureFilter); |
| } |
| } |
| return structureFilters; |
| } |
| |
| private Set<ChangesFilter.Filter> applyUserFilter() { |
| Set<ChangesFilter.Filter> userFilters; |
| final String[] strings = myUserFilterI.myFilter.split(","); |
| userFilters = new HashSet<ChangesFilter.Filter>(); |
| for (String string : strings) { |
| string = string.trim(); |
| if (string.length() == 0) continue; |
| myUsersSearchContext.add(string.toLowerCase()); |
| final String regexp = StringUtil.escapeToRegexp(string); |
| userFilters.add(new ChangesFilter.Committer(regexp)); |
| userFilters.add(new ChangesFilter.Author(regexp)); |
| } |
| return userFilters; |
| } |
| |
| interface Colors { |
| Color tag = new JBColor(new Color(0xf1ef9e), new Color(113, 111, 64)); |
| Color remote = new JBColor(new Color(0xbcbcfc), new Color(0xbcbcfc).darker().darker()); |
| Color local = new JBColor(new Color(0x75eec7), new Color(0x0D6D4F)); |
| Color highlighted = new JBColor(new Color(210,255,233), UIUtil.getTableBackground()); |
| } |
| |
| private void updateMoreVisibility() { |
| if (StepType.PAUSE.equals(myState)) { |
| myMoreAction.setEnabled(true); |
| myMoreAction.setVisible(true); |
| } else if (StepType.CONTINUE.equals(myState)) { |
| myMoreAction.setVisible(true); |
| myMoreAction.setEnabled(false); |
| } else { |
| myMoreAction.setVisible(false); |
| } |
| } |
| |
| private class MyFilterUi implements UserFilterI { |
| private boolean myMeIsKnown; |
| private String myMe; |
| private String myFilter; |
| private boolean myMeSelected; |
| private final Runnable myReloadCallback; |
| |
| public MyFilterUi(Runnable reloadCallback) { |
| myReloadCallback = reloadCallback; |
| } |
| |
| @Override |
| public void allSelected() { |
| myFilter = null; |
| myMeSelected = false; |
| myReloadCallback.run(); |
| } |
| |
| @Override |
| public void meSelected() { |
| myFilter = myMe; |
| myMeSelected = true; |
| myReloadCallback.run(); |
| } |
| |
| @Override |
| public void filter(String s) { |
| myMeSelected = false; |
| myFilter = s; |
| myReloadCallback.run(); |
| } |
| |
| @Override |
| public boolean isMeKnown() { |
| return myMeIsKnown; |
| } |
| |
| public boolean isMeSelected() { |
| return myMeSelected; |
| } |
| |
| @Override |
| public String getMe() { |
| return myMe; |
| } |
| |
| @Override |
| public String getUserIfOne() { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows != null && selectedRows.length > 0) { |
| for (int row : selectedRows) { |
| final CommitI commitAt = myTableModel.getCommitAt(row); |
| if (commitAt.holdsDecoration()) continue; |
| final GitHeavyCommit atRow = getCommitAtRow(row); |
| if (atRow != null && atRow.getCommitter() != null) return atRow.getCommitter(); |
| } |
| } |
| return null; |
| } |
| |
| public void setMe(final String me) { |
| myMeIsKnown = ! StringUtil.isEmptyOrSpaces(me); |
| myMe = me == null ? "" : me.trim(); |
| } |
| } |
| |
| private static class MyStructureFilter implements StructureFilterI { |
| private boolean myAllSelected; |
| private final List<VirtualFile> myFiles; |
| private final Runnable myReloadCallback; |
| private final Getter<List<VirtualFile>> myGetter; |
| |
| private MyStructureFilter(Runnable reloadCallback, final Getter<List<VirtualFile>> getter) { |
| myReloadCallback = reloadCallback; |
| myGetter = getter; |
| myFiles = new ArrayList<VirtualFile>(); |
| myAllSelected = true; |
| } |
| |
| @Override |
| public void allSelected() { |
| if (myAllSelected) return; |
| myAllSelected = true; |
| myReloadCallback.run(); |
| } |
| |
| @Override |
| public void select(Collection<VirtualFile> files) { |
| myAllSelected = false; |
| if (Comparing.haveEqualElements(files, myFiles)) return; |
| myFiles.clear(); |
| myFiles.addAll(files); |
| myReloadCallback.run(); |
| } |
| |
| @Override |
| public Collection<VirtualFile> getSelected() { |
| return myFiles; |
| } |
| |
| @Override |
| public List<VirtualFile> getRoots() { |
| return myGetter.get(); |
| } |
| |
| public boolean isAllSelected() { |
| return myAllSelected; |
| } |
| } |
| |
| public void setProjectScope(boolean projectScope) { |
| myProjectScope = projectScope; |
| myRootsAction.setEnabled(! projectScope); |
| } |
| |
| private static class MyRootsAction extends AnAction { |
| private boolean myEnabled; |
| private final Getter<List<VirtualFile>> myRootsGetter; |
| private final JComponent myComponent; |
| |
| private MyRootsAction(final Getter<List<VirtualFile>> rootsGetter, final JComponent component) { |
| super("Show roots", "Show roots", AllIcons.General.BalloonInformation); |
| myRootsGetter = rootsGetter; |
| myComponent = component; |
| myEnabled = false; |
| } |
| |
| public void setEnabled(boolean enabled) { |
| myEnabled = enabled; |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| e.getPresentation().setEnabled(myEnabled); |
| e.getPresentation().setVisible(myEnabled); |
| super.update(e); |
| } |
| |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| List<VirtualFile> virtualFiles = myRootsGetter.get(); |
| assert virtualFiles != null && virtualFiles.size() > 0; |
| SortedListModel sortedListModel = new SortedListModel(null); |
| final JBList jbList = new JBList(sortedListModel); |
| sortedListModel.add("Roots:"); |
| for (VirtualFile virtualFile : virtualFiles) { |
| sortedListModel.add(virtualFile.getPath()); |
| } |
| |
| JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(jbList, jbList).setRequestFocus(true).createPopup(); |
| if (e.getInputEvent() instanceof MouseEvent) { |
| popup.show(new RelativePoint((MouseEvent)e.getInputEvent())); |
| } else { |
| popup.showInBestPositionFor(e.getDataContext()); |
| } |
| } |
| } |
| |
| public class MyShowTreeAction extends ToggleAction implements DumbAware { |
| private static final String SHOW_GRAPH_TITLE = "Show Graph"; |
| private static final String HIDE_GRAPH_TITLE = "Hide Graph"; |
| private static final String SHOW_GRAPH_DESCRIPTION = "Display commit graph"; |
| private static final String HIDE_GRAPH_DESCRIPTION = "Hide commit graph"; |
| |
| private boolean myIsSelected; |
| |
| public MyShowTreeAction() { |
| super(SHOW_GRAPH_TITLE, SHOW_GRAPH_DESCRIPTION, Git4ideaIcons.Branch); |
| myIsSelected = mySettings.isShowTree(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setEnabled(!myThereIsDisordering); |
| e.getPresentation().setText(isSelected(e) ? HIDE_GRAPH_TITLE : SHOW_GRAPH_TITLE); |
| e.getPresentation().setDescription(isSelected(e) ? HIDE_GRAPH_DESCRIPTION : SHOW_GRAPH_DESCRIPTION); |
| } |
| |
| @Override |
| public boolean isSelected(AnActionEvent e) { |
| return myIsSelected; |
| } |
| |
| @Override |
| public void setSelected(AnActionEvent e, boolean state) { |
| myIsSelected = state; |
| mySettings.setShowTree(state); |
| if (!myThereIsDisordering) { |
| myEqualToHeadr.getParent().setVisible(state); |
| } |
| } |
| } |
| |
| public class MyTreeSettings { |
| private final DumbAwareAction myMultiColorAction; |
| private final DumbAwareAction myCalmAction; |
| private final Icon myIcon; |
| private final JLabel myLabel; |
| private final GitLogUI.MySelectRootsForTreeAction myRootsForTreeAction; |
| private final DumbAwareAction myDateOrder; |
| private final DumbAwareAction myTopoOrder; |
| private final Icon myMarkIcon; |
| |
| public MyTreeSettings() { |
| myIcon = AllIcons.General.ComboArrow; |
| myMarkIcon = PlatformIcons.CHECK_ICON; |
| |
| myMultiColorAction = new DumbAwareAction("Multicolour") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| myGraphGutter.setStyle(GraphGutter.PresentationStyle.multicolour); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setIcon(GraphGutter.PresentationStyle.multicolour.equals(myGraphGutter.getStyle()) ? AllIcons.General.Mdot : AllIcons.General.Mdot_empty); |
| } |
| }; |
| myCalmAction = new DumbAwareAction("Two Colors") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| myGraphGutter.setStyle(GraphGutter.PresentationStyle.calm); |
| } |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setIcon(GraphGutter.PresentationStyle.multicolour.equals(myGraphGutter.getStyle()) ? AllIcons.General.Mdot_empty |
| : AllIcons.General.Mdot); |
| } |
| }; |
| |
| myDateOrder = new DumbAwareAction("Date Order") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| if (! mySettings.isTopoOrder()) return; |
| mySettings.setTopoOrder(false); |
| reloadRequest(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setIcon(mySettings.isTopoOrder() ? AllIcons.General.Mdot_empty : AllIcons.General.Mdot); |
| } |
| }; |
| myTopoOrder = new DumbAwareAction("Topo Order") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| if (mySettings.isTopoOrder()) return; |
| mySettings.setTopoOrder(true); |
| reloadRequest(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setIcon(mySettings.isTopoOrder() ? AllIcons.General.Mdot : AllIcons.General.Mdot_empty); |
| } |
| }; |
| |
| myRootsForTreeAction = new MySelectRootsForTreeAction(); |
| myLabel = new JLabel(myIcon); |
| myLabel.setOpaque(false); |
| } |
| |
| public JLabel getLabel() { |
| return myLabel; |
| } |
| |
| public Icon getIcon() { |
| return myIcon; |
| } |
| |
| public void execute(final MouseEvent e) { |
| final DefaultActionGroup group = createActionGroup(); |
| final DataContext parent = DataManager.getInstance().getDataContext(myEqualToHeadr); |
| final DataContext dataContext = SimpleDataContext.getSimpleContext(CommonDataKeys.PROJECT.getName(), myProject, parent); |
| final JBPopup popup = JBPopupFactory.getInstance() |
| .createActionGroupPopup(null, group, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true, |
| new Runnable() { |
| @Override |
| public void run() { |
| } |
| }, 20); |
| popup.show(new RelativePoint(e)); |
| } |
| |
| private DefaultActionGroup createActionGroup() { |
| final DefaultActionGroup dab = new DefaultActionGroup(); |
| if (myRootsUnderVcs.size() == 1) { |
| dab.add(myDateOrder); |
| dab.add(myTopoOrder); |
| dab.add(new Separator()); |
| } |
| dab.add(myMultiColorAction); |
| dab.add(myCalmAction); |
| dab.add(new Separator()); |
| dab.add(myRootsForTreeAction); |
| return dab; |
| } |
| } |
| |
| public class MySelectRootsForTreeAction extends DumbAwareAction { |
| public MySelectRootsForTreeAction() { |
| super("Repositories..."); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final CheckBoxList<String> checkBoxList = new CheckBoxList<String>(); |
| |
| final List<VirtualFile> order = myTableModel.getOrder(); |
| final Set<VirtualFile> activeRoots = myTableModel.getActiveRoots(); |
| |
| final TreeMap<String, Boolean> map = new TreeMap<String, Boolean>(); |
| for (VirtualFile virtualFile : order) { |
| map.put(virtualFile.getPath(), activeRoots.contains(virtualFile)); |
| } |
| checkBoxList.setStringItems(map); |
| |
| final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(checkBoxList, checkBoxList). |
| setRequestFocus(true). |
| addListener(new JBPopupListener() { |
| @Override |
| public void beforeShown(LightweightWindowEvent event) { |
| checkBoxList.setSelectedIndex(0); |
| IdeFocusManager.getInstance(myProject).requestFocus(checkBoxList, true); |
| } |
| |
| @Override |
| public void onClosed(LightweightWindowEvent event) { |
| if (event.isOk()) { |
| final Set<String> paths = |
| new HashSet<String>(ContainedInBranchesConfigDialog.gatherSelected((DefaultListModel)checkBoxList.getModel())); |
| if (paths.isEmpty()) { |
| myMyShowTreeAction.setSelected(null, false); |
| return; |
| } |
| final HashSet<VirtualFile> set = new HashSet<VirtualFile>(order); |
| final Iterator<VirtualFile> iterator = set.iterator(); |
| while (iterator.hasNext()) { |
| VirtualFile file = iterator.next(); |
| if (!paths.contains(file.getPath())) { |
| iterator.remove(); |
| } |
| } |
| |
| if (myProjectScope) { |
| mySettings.setActiveRoots(paths); |
| } |
| |
| myTableModel.setActiveRoots(set); |
| myGraphGutter.getComponent().revalidate(); |
| myGraphGutter.getComponent().repaint(); |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| orderLabelVisibility(); |
| } |
| }); |
| } |
| } |
| }).setTitle("Show graph for:"). |
| setAdText("Press Enter to complete"). |
| createPopup(); |
| |
| final AnAction ok = new AnAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| popup.closeOk(e.getInputEvent()); |
| } |
| }; |
| ok.registerCustomShortcutSet(CommonShortcuts.CTRL_ENTER, checkBoxList); |
| ok.registerCustomShortcutSet(CommonShortcuts.ENTER, checkBoxList); |
| |
| if (e != null && e.getInputEvent() instanceof MouseEvent) { |
| popup.show(new RelativePoint((MouseEvent)e.getInputEvent())); |
| } else { |
| final Dimension dimension = popup.getContent().getPreferredSize(); |
| final Point at = new Point(20,0); |
| popup.show(new RelativePoint(myEqualToHeadr, at)); |
| } |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| final boolean enabled = myRootsUnderVcs.size() > 1; |
| e.getPresentation().setEnabled(enabled); |
| e.getPresentation().setVisible(enabled); |
| } |
| } |
| |
| public class MyHighlightCurrent extends DumbAwareAction { |
| public MyHighlightCurrent() { |
| super("Highlight subgraph"); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final CommitI commitAt = getCommitIfOneRealSelected(); |
| if (commitAt == null) { |
| return; |
| } |
| final VirtualFile root = commitAt.selectRepository(myRootsUnderVcs); |
| myTableModel.setHead(root, commitAt.getHash()); |
| myClearedHighlightingRoots.remove(root); |
| myJBTable.repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| if (myThereIsDisordering) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| weNeedOneCommitSelected(e); |
| } |
| } |
| |
| private CommitI getCommitIfOneRealSelected() { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| return null; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration()) { |
| return null; |
| } |
| return commitAt; |
| } |
| |
| private void weNeedOneCommitSelected(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration()) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| e.getPresentation().setEnabled(true); |
| } |
| |
| public class MyHighlightActionGroup extends ActionGroup { |
| private final DumbAwareAction myAllHeads; |
| private final DumbAwareAction myClearAll; |
| private final DumbAwareAction myHead; |
| //private final DumbAwareAction myClear; |
| // private final DumbAwareAction myCurrent; |
| private final AnAction[] myAnActions; |
| |
| public MyHighlightActionGroup() { |
| super("Highlight...", true); |
| |
| myAllHeads = new DumbAwareAction("All HEADs subgraphs") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| for (VirtualFile root : myTableModel.getActiveRoots()) { |
| final SymbolicRefsI symbolicRefs = myRefs.get(root); |
| if (symbolicRefs == null) continue; |
| final AbstractHash headHash = symbolicRefs.getHeadHash(); |
| if (headHash == null) continue; |
| myTableModel.setHead(root, headHash); |
| myClearedHighlightingRoots.removeAll(myRootsUnderVcs); |
| } |
| myJBTable.repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| if (myThereIsDisordering) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| e.getPresentation().setVisible(myTableModel.getActiveRoots().size() > 1); |
| } |
| }; |
| myClearAll = new DumbAwareAction("Clear") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| for (VirtualFile root : myTableModel.getActiveRoots()) { |
| myTableModel.setDumbHighlighter(root); |
| } |
| myClearedHighlightingRoots.addAll(myRootsUnderVcs); |
| myJBTable.repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| if (myThereIsDisordering) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| //e.getPresentation().setVisible(myTableModel.getActiveRoots().size() > 1); |
| } |
| }; |
| myHead = new DumbAwareAction("HEAD subgraph") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| return; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration()) { |
| return; |
| } |
| final VirtualFile root = commitAt.selectRepository(myRootsUnderVcs); |
| final SymbolicRefsI symbolicRefs = myRefs.get(root); |
| if (symbolicRefs == null) return; |
| final AbstractHash headHash = symbolicRefs.getHeadHash(); |
| if (headHash == null) return; |
| myTableModel.setHead(root, headHash); |
| myClearedHighlightingRoots.remove(root); |
| myJBTable.repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| if (myThereIsDisordering) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| weNeedOneCommitSelected(e); |
| } |
| }; |
| /*myClear = new DumbAwareAction("Clear") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| return; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration()) { |
| return; |
| } |
| final VirtualFile root = commitAt.selectRepository(myRootsUnderVcs); |
| myTableModel.setDumbHighlighter(root); |
| myClearedHighlightingRoots.add(root); |
| myJBTable.repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| if (myThereAreFilters) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| weNeedOneCommitSelected(e); |
| } |
| };*/ |
| /*myCurrent = new DumbAwareAction("Current") { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| return; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration()) { |
| return; |
| } |
| final VirtualFile root = commitAt.selectRepository(myRootsUnderVcs); |
| myTableModel.setHead(root, commitAt.getHash()); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| weNeedOneCommitSelected(e); |
| } |
| };*/ |
| |
| myAnActions = new AnAction[]{myHead, myAllHeads, myClearAll}; |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setEnabled(!myThereIsDisordering); |
| } |
| |
| @NotNull |
| @Override |
| public AnAction[] getChildren(@Nullable AnActionEvent e) { |
| return myAnActions; |
| } |
| } |
| |
| public class MyToggleCommitMark extends DumbAwareAction { |
| public MyToggleCommitMark() { |
| super("Mark", "Mark", Git4ideaIcons.Star); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final Action action = checkSelection(); |
| if (Action.disabled.equals(action)) return; |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (Action.unselect.equals(action)) { |
| for (int selectedRow : selectedRows) { |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRow); |
| if (commitAt.holdsDecoration()) continue; |
| myMarked.remove(commitAt.getHash()); |
| } |
| } |
| if (Action.select.equals(action)) { |
| for (int selectedRow : selectedRows) { |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRow); |
| if (commitAt.holdsDecoration()) continue; |
| myMarked.put(commitAt.getHash(), commitAt.getTime()); |
| } |
| } |
| myJBTable.repaint(); |
| myGraphGutter.getComponent().repaint(); |
| myDetailsPanel.redrawBranchLabels(); |
| myDetailsPanel.getComponent().revalidate(); |
| myDetailsPanel.getComponent().repaint(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| final Action action = checkSelection(); |
| e.getPresentation().setEnabled(! Action.disabled.equals(action)); |
| e.getPresentation().setVisible(! Action.disabled.equals(action)); |
| |
| e.getPresentation().setText(Action.select.equals(action) ? "Mark" : "Clear mark"); |
| } |
| |
| private Action checkSelection() { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length == 0) return Action.disabled; |
| boolean haveSelected = false; |
| boolean haveUnSelected = false; |
| for (int selectedRow : selectedRows) { |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRow); |
| if (commitAt.holdsDecoration()) continue; |
| if (myMarked.containsKey(commitAt.getHash())) { |
| haveSelected = true; |
| } else { |
| haveUnSelected = true; |
| } |
| } |
| if (! haveSelected && ! haveUnSelected) return Action.disabled; |
| if (haveSelected) return Action.unselect; |
| return Action.select; |
| } |
| } |
| |
| private static enum Action { |
| disabled, |
| select, |
| unselect |
| } |
| |
| private class MyCheckoutRevisionAction extends DumbAwareAction { |
| private MyCheckoutRevisionAction() { |
| super("Checkout Revision"); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) return; |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration() || myTableModel.isStashed(commitAt)) return; |
| |
| final GitRepository repository = |
| GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(commitAt.selectRepository(myRootsUnderVcs)); |
| if (repository == null) return; |
| GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); |
| brancher.checkout(commitAt.getHash().getString(), |
| Collections.singletonList(repository), myRefresh); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| commitCanBeUsedForCheckout(e); |
| } |
| } |
| |
| private class MyCheckoutNewBranchAction extends DumbAwareAction { |
| private MyCheckoutNewBranchAction() { |
| super("New Branch", "Create new branch starting from the selected commit", IconUtil.getAddIcon()); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) return; |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration() || myTableModel.isStashed(commitAt)) return; |
| |
| final GitRepository repository = |
| GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(commitAt.selectRepository(myRootsUnderVcs)); |
| if (repository == null) return; |
| |
| String reference = commitAt.getHash().getString(); |
| final String name = GitBranchUtil |
| .getNewBranchNameFromUser(myProject, Collections.singleton(repository), "Checkout New Branch From " + reference); |
| if (name != null) { |
| GitBrancher brancher = ServiceManager.getService(myProject, GitBrancher.class); |
| brancher.checkoutNewBranchStartingFrom(name, reference, Collections.singletonList(repository), myRefresh); |
| } |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| commitCanBeUsedForCheckout(e); |
| } |
| } |
| |
| private void commitCanBeUsedForCheckout(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| boolean enabled = ! commitAt.holdsDecoration() && ! myTableModel.isStashed(commitAt); |
| e.getPresentation().setEnabled(enabled); |
| } |
| |
| private class MyCreateNewTagAction extends DumbAwareAction { |
| private MyCreateNewTagAction() { |
| super("New Tag"); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| final int[] selectedRows = myJBTable.getSelectedRows(); |
| if (selectedRows.length != 1) return; |
| final CommitI commitAt = myTableModel.getCommitAt(selectedRows[0]); |
| if (commitAt.holdsDecoration() || myTableModel.isStashed(commitAt)) return; |
| |
| final GitRepository repository = |
| GitUtil.getRepositoryManager(myProject).getRepositoryForRoot(commitAt.selectRepository(myRootsUnderVcs)); |
| if (repository == null) return; |
| new GitCreateNewTag(myProject, repository, commitAt.getHash().getString(), myRefresh).execute(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| commitCanBeUsedForCheckout(e); |
| } |
| } |
| |
| public void selectCommit(String commitId) { |
| myJBTable.getSelectionModel().clearSelection(); |
| myMyGotoCommitAction.tryFind(commitId); |
| } |
| |
| public class MyGotoCommitAction extends DumbAwareAction { |
| public MyGotoCommitAction() { |
| super("Find Commit", "Find commit by hash, reference or description fragment (in loaded part)", AllIcons.Actions.Menu_find); |
| } |
| |
| @Override |
| public void actionPerformed(final AnActionEvent e) { |
| final JTextField field = new JTextField(30); |
| |
| final String[] gotoString = new String[1]; |
| final JBPopup[] popup = new JBPopup[1]; |
| field.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (KeyEvent.VK_ENTER == e.getKeyCode()) { |
| gotoString[0] = field.getText(); |
| if (gotoString[0] != null) { |
| tryFind(gotoString[0]); |
| } |
| if (popup[0] != null) { |
| popup[0].cancel(); |
| } |
| } |
| } |
| }); |
| final JPanel panel = new JPanel(new BorderLayout()); |
| panel.add(field, BorderLayout.CENTER); |
| final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, field); |
| popup[0] = builder.setTitle("Goto") |
| .setResizable(true) |
| .setFocusable(true) |
| .setRequestFocus(true) |
| .setMovable(true) |
| .setModalContext(true) |
| .setAdText("Commit hash, or reference, or regexp for commit message") |
| .setDimensionServiceKey(myProject, "Git.Log.Tree.Goto", true) |
| .setCancelOnClickOutside(true) |
| .addListener(new JBPopupListener() { |
| public void beforeShown(LightweightWindowEvent event) { |
| IdeFocusManager.findInstanceByContext(e.getDataContext()).requestFocus(field, true); |
| } |
| |
| public void onClosed(LightweightWindowEvent event) { |
| } |
| }) |
| .createPopup(); |
| UIUtil.installPopupMenuColorAndFonts(popup[0].getContent()); |
| UIUtil.installPopupMenuBorder(popup[0].getContent()); |
| popup[0].showInBestPositionFor(e.getDataContext()); |
| } |
| |
| private void tryFind(String reference) { |
| reference = reference.trim(); |
| final int idx = getIdx(reference); |
| if (idx == -1) { |
| VcsBalloonProblemNotifier.showOverChangesView(myProject, "Nothing found for: \"" + reference + "\"", MessageType.WARNING); |
| } else { |
| myJBTable.getSelectionModel().addSelectionInterval(idx, idx); |
| final int scrollOffsetTop = myJBTable.getRowHeight() * idx - myTableViewPort.getHeight()/2; |
| if (scrollOffsetTop > 0) { |
| myTableViewPort.setViewPosition(new Point(0, scrollOffsetTop)); |
| } |
| } |
| } |
| |
| private int getIdx(String reference) { |
| if (! StringUtil.containsWhitespaces(reference)) { |
| final int commitByIteration = findCommitByIteration(reference); |
| if (commitByIteration != -1) return commitByIteration; |
| } |
| for (VirtualFile root : myRootsUnderVcs) { |
| final SHAHash shaHash = GitChangeUtils.commitExists(myProject, root, reference, null); |
| if (shaHash != null) { |
| final int commitByIteration = findCommitByIteration(shaHash.getValue()); |
| if (commitByIteration != -1) return commitByIteration; |
| } |
| } |
| final Set<AbstractHash> hashes = new HashSet<AbstractHash>(); |
| for (VirtualFile root : myRootsUnderVcs) { |
| final List<AbstractHash> abstractHashs = GitChangeUtils.commitExistsByComment(myProject, root, reference); |
| if (abstractHashs != null) { |
| hashes.addAll(abstractHashs); |
| } |
| } |
| if (! hashes.isEmpty()) { |
| final int commitByIteration = findCommitByIteration(hashes); |
| if (commitByIteration != -1) return commitByIteration; |
| } |
| return -1; |
| } |
| |
| private int findCommitByIteration(Set<AbstractHash> references) { |
| for (int i = 0; i < myTableModel.getRowCount(); i++) { |
| final CommitI commitAt = myTableModel.getCommitAt(i); |
| if (commitAt.holdsDecoration()) continue; |
| if (references.contains(commitAt.getHash())) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private int findCommitByIteration(String reference) { |
| for (int i = 0; i < myTableModel.getRowCount(); i++) { |
| final CommitI commitAt = myTableModel.getCommitAt(i); |
| if (commitAt.holdsDecoration()) continue; |
| final String string = commitAt.getHash().getString(); |
| if (string.startsWith(reference) || reference.startsWith(string)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| } |
| } |