cleanup
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / status / ui / HgIncomingOutgoingWidget.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 org.zmlx.hg4idea.status.ui;
3
4 import com.intellij.dvcs.repo.VcsRepositoryManager;
5 import com.intellij.dvcs.repo.VcsRepositoryMappingListener;
6 import com.intellij.icons.AllIcons;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.fileEditor.FileEditorManager;
9 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.util.Disposer;
12 import com.intellij.openapi.util.IconLoader;
13 import com.intellij.openapi.vfs.VirtualFile;
14 import com.intellij.openapi.wm.StatusBar;
15 import com.intellij.openapi.wm.StatusBarWidget;
16 import com.intellij.openapi.wm.StatusBarWidgetFactory;
17 import com.intellij.openapi.wm.impl.status.EditorBasedWidget;
18 import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager;
19 import com.intellij.util.Consumer;
20 import com.intellij.util.messages.MessageBusConnection;
21 import org.jetbrains.annotations.CalledInAny;
22 import org.jetbrains.annotations.Nls;
23 import org.jetbrains.annotations.NotNull;
24 import org.zmlx.hg4idea.HgBundle;
25 import org.zmlx.hg4idea.HgVcs;
26 import org.zmlx.hg4idea.status.HgChangesetStatus;
27 import org.zmlx.hg4idea.status.HgRemoteStatusUpdater;
28
29 import javax.swing.*;
30 import java.awt.event.MouseEvent;
31 import java.util.Objects;
32
33 final class HgIncomingOutgoingWidget extends EditorBasedWidget implements StatusBarWidget.IconPresentation, StatusBarWidget.Multiframe {
34   private static final String INCOMING_WIDGET_ID = "InHgIncomingOutgoingWidget";
35   private static final String OUTGOING_WIDGET_ID = "OutHgIncomingOutgoingWidget";
36
37   @NotNull private final HgVcs myVcs;
38   private final boolean myIsIncoming;
39   @NotNull private final Icon myEnabledIcon;
40   @NotNull private final Icon myDisabledIcon;
41
42   private volatile String myTooltip = "";
43   private Icon myCurrentIcon;
44
45   public HgIncomingOutgoingWidget(@NotNull HgVcs vcs, boolean isIncoming) {
46     super(vcs.getProject());
47     myIsIncoming = isIncoming;
48     myVcs = vcs;
49     myEnabledIcon = myIsIncoming ? AllIcons.Ide.IncomingChangesOn : AllIcons.Ide.OutgoingChangesOn;
50     myDisabledIcon = IconLoader.getDisabledIcon(myEnabledIcon);
51     myCurrentIcon = myDisabledIcon;
52
53     MessageBusConnection busConnection = myProject.getMessageBus().connect();
54     busConnection.subscribe(VcsRepositoryManager.VCS_REPOSITORY_MAPPING_UPDATED, () -> updateLater());
55     busConnection.subscribe(HgVcs.STATUS_TOPIC, (project, root) -> updateLater());
56     busConnection.subscribe(HgVcs.REMOTE_TOPIC, (project, root) -> updateLater());
57     busConnection.subscribe(HgVcs.INCOMING_OUTGOING_CHECK_TOPIC, new HgWidgetUpdater() {
58       @Override
59       public void update() {
60         updateLater();
61       }
62     });
63     updateLater();
64   }
65
66   @Override
67   public StatusBarWidget copy() {
68     return new HgIncomingOutgoingWidget(myVcs, myIsIncoming);
69   }
70
71   @NotNull
72   @Override
73   public String ID() {
74     return myIsIncoming ? INCOMING_WIDGET_ID : OUTGOING_WIDGET_ID;
75   }
76
77   @Override
78   public WidgetPresentation getPresentation() {
79     return this;
80   }
81
82   @Override
83   public void selectionChanged(@NotNull FileEditorManagerEvent event) {
84     updateLater();
85   }
86
87   @Override
88   public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
89     updateLater();
90   }
91
92   @Override
93   public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
94     updateLater();
95   }
96
97   @Override
98   public String getTooltipText() {
99     return myTooltip;
100   }
101
102   @Override
103   // Updates branch information on click
104   public Consumer<MouseEvent> getClickConsumer() {
105     return mouseEvent -> updateLater();
106   }
107
108   @CalledInAny
109   public void updateLater() {
110     ApplicationManager.getApplication().invokeLater(() -> {
111       if (Disposer.isDisposed(this)) return;
112
113       HgRemoteStatusUpdater statusUpdater = myVcs.getRemoteStatusUpdater();
114       if (statusUpdater == null) return;
115       HgChangesetStatus status = statusUpdater.getStatus(myIsIncoming);
116       boolean changesAvailable = status.getNumChanges() > 0;
117       myCurrentIcon = changesAvailable ? myEnabledIcon : myDisabledIcon;
118       myTooltip = changesAvailable ? "\n" + status.getToolTip() : HgBundle.message("no.changes.available");
119       if (myStatusBar != null) myStatusBar.updateWidget(ID());
120     });
121   }
122
123   @NotNull
124   @Override
125   public Icon getIcon() {
126     return myCurrentIcon;
127   }
128
129   //if smb call hide widget then it removed from status bar ans dispose method called.
130   // if we do not override dispose IDE call EditorWidget dispose method and set connection to null.
131   //next, if we repeat hide/show dispose eth will be callees several times,but connection will be null -> NPE or already disposed message.
132   @Override
133   public void dispose() {
134     if (!isDisposed()) {
135       super.dispose();
136     }
137   }
138   public static class Listener implements VcsRepositoryMappingListener, HgWidgetUpdater {
139     private final Project myProject;
140
141     public Listener(@NotNull Project project) {
142       myProject = project;
143     }
144
145     @Override
146     public void mappingChanged() {
147       updateVisibility();
148     }
149
150     @Override
151     public void updateVisibility() {
152       StatusBarWidgetsManager widgetsManager = myProject.getService(StatusBarWidgetsManager.class);
153       widgetsManager.updateWidget(HgIncomingOutgoingWidget.IncomingFactory.class);
154       widgetsManager.updateWidget(HgIncomingOutgoingWidget.OutgoingFactory.class);
155     }
156   }
157
158   public static class IncomingFactory extends MyWidgetFactory {
159     public IncomingFactory() {
160       super(true);
161     }
162   }
163
164   public static class OutgoingFactory extends MyWidgetFactory {
165     public OutgoingFactory() {
166       super(false);
167     }
168   }
169
170   private static abstract class MyWidgetFactory implements StatusBarWidgetFactory {
171     private final boolean myIsIncoming;
172
173     protected MyWidgetFactory(boolean isIncoming) {
174       myIsIncoming = isIncoming;
175     }
176
177     @Override
178     public boolean isAvailable(@NotNull Project project) {
179       return HgRemoteStatusUpdater.isCheckingEnabled(project);
180     }
181
182     @Override
183     public @NotNull StatusBarWidget createWidget(@NotNull Project project) {
184       HgVcs hgVcs = Objects.requireNonNull(HgVcs.getInstance(project));
185       return new HgIncomingOutgoingWidget(hgVcs, myIsIncoming);
186     }
187
188     @Override
189     public void disposeWidget(@NotNull StatusBarWidget widget) {
190       Disposer.dispose(widget);
191     }
192
193     @Override
194     public boolean canBeEnabledOn(@NotNull StatusBar statusBar) {
195       return true;
196     }
197
198     @Override
199     public @NotNull String getId() {
200       return myIsIncoming ? INCOMING_WIDGET_ID : OUTGOING_WIDGET_ID;
201     }
202
203     @Override
204     public @Nls @NotNull String getDisplayName() {
205       return myIsIncoming ? HgBundle.message("hg4idea.status.bar.incoming.widget.name")
206                           : HgBundle.message("hg4idea.status.bar.outgoing.widget.name");
207     }
208   }
209 }
210