Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / actionSystem / impl / ToolbarUpdater.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.actionSystem.impl;
17
18 import com.intellij.openapi.actionSystem.TimerListener;
19 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ModalityState;
23 import com.intellij.openapi.keymap.Keymap;
24 import com.intellij.openapi.keymap.KeymapManagerListener;
25 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.wm.IdeFocusManager;
28 import com.intellij.util.ui.JBSwingUtilities;
29 import com.intellij.util.ui.update.Activatable;
30 import com.intellij.util.ui.update.UiNotifyConnector;
31 import org.jetbrains.annotations.NotNull;
32
33 import javax.swing.*;
34 import java.awt.*;
35 import java.lang.ref.WeakReference;
36
37 /**
38  * @author Konstantin Bulenkov
39  */
40 public abstract class ToolbarUpdater implements Activatable {
41   private final ActionManagerEx myActionManager;
42   private final KeymapManagerEx myKeymapManager;
43   private final JComponent myComponent;
44
45   private final KeymapManagerListener myKeymapManagerListener = new MyKeymapManagerListener();
46   private final TimerListener myTimerListener = new MyTimerListener();
47   private final WeakTimerListener myWeakTimerListener;
48
49   private boolean myListenersArmed;
50
51   public ToolbarUpdater(@NotNull JComponent component) {
52     this(ActionManagerEx.getInstanceEx(), KeymapManagerEx.getInstanceEx(), component);
53   }
54
55   public ToolbarUpdater(@NotNull ActionManagerEx actionManager, @NotNull KeymapManagerEx keymapManager, @NotNull JComponent component) {
56     myActionManager = actionManager;
57     myKeymapManager = keymapManager;
58     myComponent = component;
59     myWeakTimerListener = new WeakTimerListener(actionManager, myTimerListener);
60     new UiNotifyConnector(component, this);
61   }
62
63   @Override
64   public void showNotify() {
65     if (myListenersArmed) return;
66     myListenersArmed = true;
67     myActionManager.addTimerListener(500, myWeakTimerListener);
68     myActionManager.addTransparentTimerListener(500, myWeakTimerListener);
69     myKeymapManager.addWeakListener(myKeymapManagerListener);
70     updateActionTooltips();
71   }
72
73   @Override
74   public void hideNotify() {
75     if (!myListenersArmed) return;
76     myListenersArmed = false;
77     myActionManager.removeTimerListener(myWeakTimerListener);
78     myActionManager.removeTransparentTimerListener(myWeakTimerListener);
79     myKeymapManager.removeWeakListener(myKeymapManagerListener);
80   }
81
82   @NotNull
83   public KeymapManagerEx getKeymapManager() {
84     return myKeymapManager;
85   }
86
87   @NotNull
88   public ActionManagerEx getActionManager() {
89     return myActionManager;
90   }
91
92   public void updateActions(boolean now, boolean forced) {
93     updateActions(now, false, forced);
94   }
95
96   private void updateActions(boolean now, final boolean transparentOnly, final boolean forced) {
97     final Runnable updateRunnable = new MyUpdateRunnable(this, transparentOnly, forced);
98     final Application app = ApplicationManager.getApplication();
99
100     if (now || app.isUnitTestMode()) {
101       updateRunnable.run();
102     }
103     else {
104       final IdeFocusManager fm = IdeFocusManager.getInstance(null);
105
106       if (!app.isHeadlessEnvironment()) {
107         if (app.isDispatchThread()) {
108           fm.doWhenFocusSettlesDown(updateRunnable);
109         }
110         else {
111           UiNotifyConnector.doWhenFirstShown(myComponent, new Runnable() {
112             @Override
113             public void run() {
114               fm.doWhenFocusSettlesDown(updateRunnable);
115             }
116           });
117         }
118       }
119     }
120   }
121
122   protected abstract void updateActionsImpl(boolean transparentOnly, boolean forced);
123
124   protected void updateActionTooltips() {
125     for (ActionButton actionButton : JBSwingUtilities.uiTraverser().preOrderTraversal(myComponent).filter(ActionButton.class)) {
126       actionButton.updateToolTipText();
127     }
128   }
129
130   private final class MyKeymapManagerListener implements KeymapManagerListener {
131     @Override
132     public void activeKeymapChanged(Keymap keymap) {
133       updateActionTooltips();
134     }
135   }
136
137   private final class MyTimerListener implements TimerListener {
138
139     @Override
140     public ModalityState getModalityState() {
141       return ModalityState.stateForComponent(myComponent);
142     }
143
144     @Override
145     public void run() {
146       if (!myComponent.isShowing()) {
147         return;
148       }
149
150       // do not update when a popup menu is shown (if popup menu contains action which is also in the toolbar, it should not be enabled/disabled)
151       MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
152       MenuElement[] selectedPath = menuSelectionManager.getSelectedPath();
153       if (selectedPath.length > 0) {
154         return;
155       }
156
157       // don't update toolbar if there is currently active modal dialog
158       Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
159       if (window instanceof Dialog && ((Dialog)window).isModal() && !SwingUtilities.isDescendingFrom(myComponent, window)) {
160         return;
161       }
162
163       updateActions(false, myActionManager.isTransparentOnlyActionsUpdateNow(), false);
164     }
165   }
166
167   private static class MyUpdateRunnable implements Runnable {
168     private final boolean myTransparentOnly;
169     private final boolean myForced;
170
171     @NotNull private final WeakReference<ToolbarUpdater> myUpdaterRef;
172     private final int myHash;
173
174     public MyUpdateRunnable(@NotNull ToolbarUpdater updater, boolean transparentOnly, boolean forced) {
175       myTransparentOnly = transparentOnly;
176       myForced = forced;
177       myHash = updater.hashCode();
178
179       myUpdaterRef = new WeakReference<ToolbarUpdater>(updater);
180     }
181
182     @Override
183     public void run() {
184       ToolbarUpdater updater = myUpdaterRef.get();
185       if (updater == null) return;
186
187       if (!updater.myComponent.isVisible()) {
188         return;
189       }
190
191       updater.updateActionsImpl(myTransparentOnly, myForced);
192     }
193
194     @Override
195     public boolean equals(Object obj) {
196       if (!(obj instanceof MyUpdateRunnable)) return false;
197
198       MyUpdateRunnable that = (MyUpdateRunnable)obj;
199       if (myHash != that.myHash) return false;
200
201       ToolbarUpdater updater1 = myUpdaterRef.get();
202       ToolbarUpdater updater2 = that.myUpdaterRef.get();
203       return Comparing.equal(updater1, updater2);
204     }
205
206     @Override
207     public int hashCode() {
208       return myHash;
209     }
210   }
211 }