cleanup (inspection "Java | Class structure | Utility class is not 'final'")
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / util / VcsLogUiUtil.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.vcs.log.util;
3
4 import com.google.common.util.concurrent.FutureCallback;
5 import com.google.common.util.concurrent.Futures;
6 import com.google.common.util.concurrent.ListenableFuture;
7 import com.intellij.ide.IdeTooltip;
8 import com.intellij.ide.IdeTooltipManager;
9 import com.intellij.openapi.Disposable;
10 import com.intellij.openapi.progress.util.ProgressWindow;
11 import com.intellij.openapi.ui.popup.Balloon;
12 import com.intellij.openapi.util.ActionCallback;
13 import com.intellij.openapi.util.Disposer;
14 import com.intellij.openapi.util.registry.Registry;
15 import com.intellij.openapi.util.text.StringUtil;
16 import com.intellij.ui.*;
17 import com.intellij.ui.components.panels.Wrapper;
18 import com.intellij.ui.navigation.History;
19 import com.intellij.ui.navigation.Place;
20 import com.intellij.util.concurrency.EdtExecutorService;
21 import com.intellij.util.containers.ContainerUtil;
22 import com.intellij.util.ui.JBUI;
23 import com.intellij.util.ui.StatusText;
24 import com.intellij.util.ui.UIUtil;
25 import com.intellij.vcs.log.CommitId;
26 import com.intellij.vcs.log.VcsLogBundle;
27 import com.intellij.vcs.log.data.VcsLogData;
28 import com.intellij.vcs.log.data.VcsLogProgress;
29 import com.intellij.vcs.log.ui.AbstractVcsLogUi;
30 import com.intellij.vcs.log.ui.VcsLogUiEx;
31 import com.intellij.vcs.log.ui.filter.VcsLogFilterUiEx;
32 import com.intellij.vcs.log.ui.frame.ProgressStripe;
33 import com.intellij.vcs.log.ui.frame.VcsLogCommitDetailsListPanel;
34 import com.intellij.vcs.log.ui.highlighters.VcsLogHighlighterFactory;
35 import com.intellij.vcs.log.ui.table.VcsLogGraphTable;
36 import com.intellij.vcs.log.visible.VisiblePackRefresherImpl;
37 import org.jetbrains.annotations.Nls;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import javax.swing.*;
43 import java.awt.*;
44 import java.util.Collection;
45 import java.util.List;
46 import java.util.function.Predicate;
47
48 import static com.intellij.vcs.log.ui.AbstractVcsLogUi.LOG_HIGHLIGHTER_FACTORY_EP;
49
50 public final class VcsLogUiUtil {
51   @NotNull
52   public static JComponent installProgress(@NotNull JComponent component,
53                                            @NotNull VcsLogData logData,
54                                            @NotNull String logId,
55                                            @NotNull Disposable disposableParent) {
56     ProgressStripe progressStripe =
57       new ProgressStripe(component, disposableParent, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) {
58         @Override
59         public void updateUI() {
60           super.updateUI();
61           if (myDecorator != null && logData.getProgress().isRunning()) startLoadingImmediately();
62         }
63       };
64     logData.getProgress().addProgressIndicatorListener(new VcsLogProgress.ProgressListener() {
65       @Override
66       public void progressStarted(@NotNull Collection<? extends VcsLogProgress.ProgressKey> keys) {
67         if (isProgressVisible(keys, logId)) {
68           progressStripe.startLoading();
69         }
70       }
71
72       @Override
73       public void progressChanged(@NotNull Collection<? extends VcsLogProgress.ProgressKey> keys) {
74         if (isProgressVisible(keys, logId)) {
75           progressStripe.startLoading();
76         }
77         else {
78           progressStripe.stopLoading();
79         }
80       }
81
82       @Override
83       public void progressStopped() {
84         progressStripe.stopLoading();
85       }
86     }, disposableParent);
87
88     return progressStripe;
89   }
90
91   public static boolean isProgressVisible(@NotNull Collection<? extends VcsLogProgress.ProgressKey> keys,
92                                           @NotNull String logId) {
93     if (keys.contains(VcsLogData.DATA_PACK_REFRESH)) {
94       return true;
95     }
96     return ContainerUtil.find(keys, key -> VisiblePackRefresherImpl.isVisibleKeyFor(key, logId)) != null;
97   }
98
99   @NotNull
100   public static JScrollPane setupScrolledGraph(@NotNull VcsLogGraphTable graphTable, int border) {
101     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(graphTable, border);
102     ComponentUtil.putClientProperty(scrollPane, UIUtil.KEEP_BORDER_SIDES, SideBorder.TOP);
103     graphTable.viewportSet(scrollPane.getViewport());
104     return scrollPane;
105   }
106
107   public static void installDetailsListeners(@NotNull VcsLogGraphTable graphTable,
108                                              @NotNull VcsLogCommitDetailsListPanel detailsPanel,
109                                              @NotNull VcsLogData logData,
110                                              @NotNull Disposable disposableParent) {
111     Runnable miniDetailsLoadedListener = () -> {
112       graphTable.reLayout();
113       graphTable.repaint();
114     };
115     Runnable containingBranchesListener = () -> {
116       detailsPanel.branchesChanged();
117       graphTable.repaint(); // we may need to repaint highlighters
118     };
119     logData.getMiniDetailsGetter().addDetailsLoadedListener(miniDetailsLoadedListener);
120     logData.getContainingBranchesGetter().addTaskCompletedListener(containingBranchesListener);
121
122     Disposer.register(disposableParent, () -> {
123       logData.getContainingBranchesGetter().removeTaskCompletedListener(containingBranchesListener);
124       logData.getMiniDetailsGetter().removeDetailsLoadedListener(miniDetailsLoadedListener);
125     });
126   }
127
128   @NotNull
129   public static SimpleTextAttributes getLinkAttributes() {
130     return new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBUI.CurrentTheme.Link.linkColor());
131   }
132
133   public static void showTooltip(@NotNull JComponent component,
134                                  @NotNull Point point,
135                                  @NotNull Balloon.Position position,
136                                  @NotNull String text) {
137     JEditorPane tipComponent = IdeTooltipManager.initPane(text, new HintHint(component, point).setAwtTooltip(true), null);
138     IdeTooltip tooltip = new IdeTooltip(component, point, new Wrapper(tipComponent)).setPreferredPosition(position).setToCenter(false)
139       .setToCenterIfSmall(false);
140     IdeTooltipManager.getInstance().show(tooltip, false);
141   }
142
143   @NotNull
144   public static History installNavigationHistory(@NotNull VcsLogUiEx ui) {
145     History history = new History(new VcsLogPlaceNavigator(ui));
146     ui.getTable().getSelectionModel().addListSelectionListener((e) -> {
147       if (!history.isNavigatingNow() && !e.getValueIsAdjusting()) {
148         history.pushQueryPlace();
149       }
150     });
151     return history;
152   }
153
154   @NotNull
155   public static String shortenTextToFit(@NotNull String text, @NotNull FontMetrics fontMetrics, int availableWidth, int maxLength,
156                                         @NotNull String symbol) {
157     if (fontMetrics.stringWidth(text) <= availableWidth) return text;
158
159     for (int i = text.length(); i > maxLength; i--) {
160       String result = StringUtil.shortenTextWithEllipsis(text, i, 0, symbol);
161       if (fontMetrics.stringWidth(result) <= availableWidth) {
162         return result;
163       }
164     }
165     return StringUtil.shortenTextWithEllipsis(text, maxLength, 0, symbol);
166   }
167
168   public static int getHorizontalTextPadding(@NotNull SimpleColoredComponent component) {
169     Insets borderInsets = component.getMyBorder().getBorderInsets(component);
170     Insets ipad = component.getIpad();
171     return borderInsets.left + borderInsets.right + ipad.left + ipad.right;
172   }
173
174   public static void appendActionToEmptyText(@Nls @NotNull StatusText emptyText, @Nls @NotNull String text, @NotNull Runnable action) {
175     emptyText.appendSecondaryText(text, getLinkAttributes(), e -> action.run());
176   }
177
178   public static void appendResetFiltersActionToEmptyText(@NotNull VcsLogFilterUiEx filterUi, @Nls @NotNull StatusText emptyText) {
179     appendActionToEmptyText(emptyText, VcsLogBundle.message("vcs.log.reset.filters.status.action"), filterUi::clearFilters);
180   }
181
182   public static boolean isDiffPreviewInEditor() {
183     return Registry.is("vcs.log.show.diff.preview.as.editor.tab");
184   }
185
186   public static void installHighlighters(@NotNull AbstractVcsLogUi logUi, @NotNull Predicate<? super VcsLogHighlighterFactory> enabled) {
187     LOG_HIGHLIGHTER_FACTORY_EP.addChangeListener(() -> {
188       updateHighlighters(logUi, enabled);
189     }, logUi);
190     updateHighlighters(logUi, enabled);
191   }
192
193   private static void updateHighlighters(@NotNull AbstractVcsLogUi logUi, @NotNull Predicate<? super VcsLogHighlighterFactory> enabled) {
194     logUi.getTable().removeAllHighlighters();
195     for (VcsLogHighlighterFactory factory : LOG_HIGHLIGHTER_FACTORY_EP.getExtensionList()) {
196       if (enabled.test(factory)) {
197         logUi.getTable().addHighlighter(factory.createHighlighter(logUi.getLogData(), logUi));
198       }
199     }
200   }
201
202   private static class VcsLogPlaceNavigator implements Place.Navigator {
203     @NonNls private static final String PLACE_KEY = "Vcs.Log.Ui.History.PlaceKey";
204     @NotNull private final VcsLogUiEx myUi;
205
206     private VcsLogPlaceNavigator(@NotNull VcsLogUiEx ui) {
207       myUi = ui;
208     }
209
210     @Override
211     public final void queryPlace(@NotNull Place place) {
212       List<CommitId> commits = myUi.getVcsLog().getSelectedCommits();
213       if (commits.size() > 0) {
214         place.putPath(PLACE_KEY, commits.get(0));
215       }
216     }
217
218     @Override
219     public final ActionCallback navigateTo(@Nullable Place place, boolean requestFocus) {
220       if (place == null) return ActionCallback.DONE;
221
222       Object value = place.getPath(PLACE_KEY);
223       if (!(value instanceof CommitId)) return ActionCallback.REJECTED;
224
225       CommitId commitId = (CommitId)value;
226       ActionCallback callback = new ActionCallback();
227
228       ListenableFuture<Boolean> future = myUi.jumpToCommit(commitId.getHash(), commitId.getRoot());
229
230       Futures.addCallback(future, new FutureCallback<Boolean>() {
231         @Override
232         public void onSuccess(Boolean success) {
233           if (success) {
234             if (requestFocus) myUi.getTable().requestFocusInWindow();
235             callback.setDone();
236           }
237           else {
238             callback.setRejected();
239           }
240         }
241
242         @Override
243         public void onFailure(Throwable t) {
244           callback.setRejected();
245         }
246       }, EdtExecutorService.getInstance());
247
248       return callback;
249     }
250   }
251 }