package com.intellij.vcs.log.ui.frame; import com.google.common.primitives.Ints; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.VcsDataKeys; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.committed.RepositoryChangesBrowser; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.ui.components.JBLoadingPanel; import com.intellij.ui.components.panels.Wrapper; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.table.ComponentsListFocusTraversalPolicy; import com.intellij.vcs.CommittedChangeListForRevision; import com.intellij.vcs.log.*; import com.intellij.vcs.log.data.VcsLogData; import com.intellij.vcs.log.impl.CommonUiProperties; import com.intellij.vcs.log.impl.MainVcsLogUiProperties; import com.intellij.vcs.log.impl.VcsLogUtil; import com.intellij.vcs.log.ui.VcsLogActionPlaces; import com.intellij.vcs.log.ui.VcsLogInternalDataKeys; import com.intellij.vcs.log.ui.VcsLogUiImpl; import com.intellij.vcs.log.ui.actions.IntelliSortChooserPopupAction; import com.intellij.vcs.log.ui.filter.VcsLogClassicFilterUi; import com.intellij.vcs.log.ui.table.VcsLogGraphTable; import com.intellij.vcs.log.util.BekUtil; import com.intellij.vcs.log.util.VcsLogUiUtil; import com.intellij.vcs.log.visible.VisiblePack; import net.miginfocom.swing.MigLayout; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.*; import java.util.List; import static com.intellij.openapi.vfs.VfsUtilCore.toVirtualFileArray; import static com.intellij.util.ObjectUtils.assertNotNull; import static com.intellij.util.ObjectUtils.chooseNotNull; import static com.intellij.util.containers.ContainerUtil.getFirstItem; public class MainFrame extends JPanel implements DataProvider, Disposable { private static final String HELP_ID = "reference.changesToolWindow.log"; @NotNull private final VcsLogData myLogData; @NotNull private final VcsLogUi myUi; @NotNull private final VcsLog myLog; @NotNull private final VcsLogClassicFilterUi myFilterUi; @NotNull private final JBLoadingPanel myChangesLoadingPane; @NotNull private final VcsLogGraphTable myGraphTable; @NotNull private final DetailsPanel myDetailsPanel; @NotNull private final Splitter myDetailsSplitter; @NotNull private final JComponent myToolbar; @NotNull private final RepositoryChangesBrowser myChangesBrowser; @NotNull private final Splitter myChangesBrowserSplitter; @NotNull private final SearchTextField myTextFilter; @NotNull private final MainVcsLogUiProperties myUiProperties; public MainFrame(@NotNull VcsLogData logData, @NotNull VcsLogUiImpl ui, @NotNull Project project, @NotNull MainVcsLogUiProperties uiProperties, @NotNull VcsLog log, @NotNull VisiblePack initialDataPack) { // collect info myLogData = logData; myUi = ui; myLog = log; myUiProperties = uiProperties; myFilterUi = new VcsLogClassicFilterUi(ui, logData, myUiProperties, initialDataPack); // initialize components myGraphTable = new VcsLogGraphTable(ui, logData, initialDataPack); myGraphTable.setCompactReferencesView(myUiProperties.get(MainVcsLogUiProperties.COMPACT_REFERENCES_VIEW)); myGraphTable.setShowTagNames(myUiProperties.get(MainVcsLogUiProperties.SHOW_TAG_NAMES)); PopupHandler.installPopupHandler(myGraphTable, VcsLogActionPlaces.POPUP_ACTION_GROUP, VcsLogActionPlaces.VCS_LOG_TABLE_PLACE); myDetailsPanel = new DetailsPanel(logData, ui.getColorManager(), this); myChangesBrowser = new RepositoryChangesBrowser(project, null, Collections.emptyList(), null) { @Override protected void buildToolBar(DefaultActionGroup toolBarGroup) { super.buildToolBar(toolBarGroup); toolBarGroup.add(ActionManager.getInstance().getAction(VcsLogActionPlaces.VCS_LOG_SHOW_DETAILS_ACTION)); } }; myChangesBrowser.getViewerScrollPane().setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); myChangesBrowser.getDiffAction().registerCustomShortcutSet(myChangesBrowser.getDiffAction().getShortcutSet(), getGraphTable()); myChangesBrowser.getEditSourceAction().registerCustomShortcutSet(CommonShortcuts.getEditSource(), getGraphTable()); myChangesBrowser.getViewer().setEmptyText(""); myChangesLoadingPane = new JBLoadingPanel(new BorderLayout(), this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS); myChangesLoadingPane.add(myChangesBrowser); myDetailsSplitter = new OnePixelSplitter(true, "vcs.log.details.splitter.proportion", 0.7f); myDetailsSplitter.setFirstComponent(myChangesLoadingPane); setupDetailsSplitter(myUiProperties.get(CommonUiProperties.SHOW_DETAILS)); myGraphTable.getSelectionModel().addListSelectionListener(new MyCommitSelectionListenerForDiff()); myDetailsPanel.installCommitSelectionListener(myGraphTable); VcsLogUiUtil.installDetailsListeners(myGraphTable, myDetailsPanel, myLogData, this); myTextFilter = myFilterUi.createTextFilter(); myToolbar = createActionsToolbar(); JComponent toolbars = new JPanel(new BorderLayout()); toolbars.add(myToolbar, BorderLayout.NORTH); JComponent toolbarsAndTable = new JPanel(new BorderLayout()); toolbarsAndTable.add(toolbars, BorderLayout.NORTH); toolbarsAndTable.add(VcsLogUiUtil.installProgress(VcsLogUiUtil.setupScrolledGraph(myGraphTable, SideBorder.TOP), myLogData, this), BorderLayout.CENTER); myChangesBrowserSplitter = new OnePixelSplitter(false, "vcs.log.changes.splitter.proportion", 0.7f); myChangesBrowserSplitter.setFirstComponent(toolbarsAndTable); myChangesBrowserSplitter.setSecondComponent(myDetailsSplitter); setLayout(new BorderLayout()); add(myChangesBrowserSplitter); Disposer.register(ui, this); myGraphTable.resetDefaultFocusTraversalKeys(); setFocusCycleRoot(true); setFocusTraversalPolicy(new MyFocusPolicy()); } /** * Informs components that the actual DataPack has been updated (e.g. due to a log refresh). <br/> * Components may want to update their fields and/or rebuild. * * @param dataPack new data pack. * @param permGraphChanged true if permanent graph itself was changed. */ public void updateDataPack(@NotNull VisiblePack dataPack, boolean permGraphChanged) { myFilterUi.updateDataPack(dataPack); myGraphTable.updateDataPack(dataPack, permGraphChanged); } public void setupDetailsSplitter(boolean state) { myDetailsSplitter.setSecondComponent(state ? myDetailsPanel : null); } @NotNull public VcsLogGraphTable getGraphTable() { return myGraphTable; } @NotNull public VcsLogFilterUi getFilterUi() { return myFilterUi; } private JComponent createActionsToolbar() { DefaultActionGroup toolbarGroup = new DefaultActionGroup(); toolbarGroup.add(ActionManager.getInstance().getAction(VcsLogActionPlaces.TOOLBAR_ACTION_GROUP)); DefaultActionGroup mainGroup = new DefaultActionGroup(); mainGroup.add(ActionManager.getInstance().getAction(VcsLogActionPlaces.VCS_LOG_TEXT_FILTER_SETTINGS_ACTION)); mainGroup.add(new Separator()); mainGroup.add(myFilterUi.createActionGroup()); mainGroup.addSeparator(); if (BekUtil.isBekEnabled()) { if (BekUtil.isLinearBekEnabled()) { mainGroup.add(new IntelliSortChooserPopupAction()); // can not register both of the actions in xml file, choosing to register an action for the "outer world" // I can of course if linear bek is enabled replace the action on start but why bother } else { mainGroup.add(ActionManager.getInstance().getAction(VcsLogActionPlaces.VCS_LOG_INTELLI_SORT_ACTION)); } } mainGroup.add(toolbarGroup); ActionToolbar toolbar = createActionsToolbar(mainGroup); Wrapper textFilter = new Wrapper(myTextFilter); textFilter.setVerticalSizeReferent(toolbar.getComponent()); textFilter.setBorder(JBUI.Borders.emptyLeft(5)); ActionToolbar settings = createActionsToolbar(new DefaultActionGroup(ActionManager.getInstance().getAction(VcsLogActionPlaces.VCS_LOG_QUICK_SETTINGS_ACTION))); settings.setReservePlaceAutoPopupIcon(false); settings.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY); JPanel panel = new JPanel(new MigLayout("ins 0, fill", "[left]0[left, fill]push[right]", "center")); panel.add(textFilter); panel.add(toolbar.getComponent()); panel.add(settings.getComponent()); return panel; } @NotNull private ActionToolbar createActionsToolbar(@NotNull DefaultActionGroup mainGroup) { ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(VcsLogActionPlaces.VCS_LOG_TOOLBAR_PLACE, mainGroup, true); toolbar.setTargetComponent(this); return toolbar; } @NotNull public JComponent getMainComponent() { return this; } @Nullable @Override public Object getData(@NonNls String dataId) { if (VcsDataKeys.CHANGES.is(dataId) || VcsDataKeys.SELECTED_CHANGES.is(dataId)) { return ArrayUtil.toObjectArray(myChangesBrowser.getCurrentDisplayedChanges(), Change.class); } else if (VcsDataKeys.CHANGE_LISTS.is(dataId)) { List<VcsFullCommitDetails> details = myLog.getSelectedDetails(); if (details.size() > VcsLogUtil.MAX_SELECTED_COMMITS) return null; return ContainerUtil.map2Array(details, CommittedChangeListForRevision.class, VcsLogUtil::createCommittedChangeList); } else if (VcsDataKeys.VCS.is(dataId)) { Set<VirtualFile> roots = getSelectedRoots(); if (roots == null) return null; if (roots.size() == 1) { return myLogData.getLogProvider(assertNotNull(getFirstItem(roots))).getSupportedVcs(); } } else if (VcsLogDataKeys.VCS_LOG_BRANCHES.is(dataId)) { int[] selectedRows = myGraphTable.getSelectedRows(); if (selectedRows.length != 1) return null; return myGraphTable.getModel().getBranchesAtRow(selectedRows[0]); } else if (PlatformDataKeys.HELP_ID.is(dataId)) { return HELP_ID; } else if (VcsLogInternalDataKeys.LOG_UI_PROPERTIES.is(dataId)) { return myUiProperties; } else if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) { Collection<VirtualFile> roots = chooseNotNull(getSelectedRoots(), VcsLogUtil.getVisibleRoots(myUi)); return toVirtualFileArray(roots); } return null; } @Nullable private Set<VirtualFile> getSelectedRoots() { int[] selectedRows = myGraphTable.getSelectedRows(); if (selectedRows.length == 0 || selectedRows.length > VcsLogUtil.MAX_SELECTED_COMMITS) return null; return ContainerUtil.map2Set(Ints.asList(selectedRows), row -> myGraphTable.getModel().getRoot(row)); } @NotNull public JComponent getToolbar() { return myToolbar; } @NotNull public SearchTextField getTextFilter() { return myTextFilter; } public void showDetails(boolean state) { myDetailsSplitter.setSecondComponent(state ? myDetailsPanel : null); } @Override public void dispose() { myDetailsSplitter.dispose(); myChangesBrowserSplitter.dispose(); } private class MyCommitSelectionListenerForDiff extends CommitSelectionListenerForDiff { protected MyCommitSelectionListenerForDiff() { super(myLogData, MainFrame.this.myGraphTable); } @Override protected void onEmptySelection() { super.onEmptySelection(); myChangesBrowser.getViewer().setEmptyText("No commits selected"); } @Override protected void setChangesToDisplay(@NotNull List<Change> changes) { myChangesBrowser.setChangesToDisplay(changes); } @Override protected void clearChanges() { // just reset and wait for details to be loaded myChangesBrowser.setChangesToDisplay(Collections.emptyList()); myChangesBrowser.getViewer().setEmptyText(""); } @Override protected void startLoading() { myChangesLoadingPane.startLoading(); } @Override protected void stopLoading() { myChangesLoadingPane.stopLoading(); } } private class MyFocusPolicy extends ComponentsListFocusTraversalPolicy { @NotNull @Override protected List<Component> getOrderedComponents() { return Arrays.asList(myGraphTable, myChangesBrowser.getPreferredFocusedComponent(), myTextFilter.getTextEditor()); } } }