switcher - auto selection + fixes
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / actionSystem / impl / ActionButton.java
1 /*
2  * Copyright 2000-2009 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.ide.DataManager;
19 import com.intellij.openapi.actionSystem.*;
20 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
21 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
22 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
23 import com.intellij.openapi.actionSystem.ex.ActionUtil;
24 import com.intellij.openapi.util.IconLoader;
25 import com.intellij.util.ui.EmptyIcon;
26 import org.jetbrains.annotations.NonNls;
27 import org.jetbrains.annotations.NotNull;
28
29 import javax.swing.*;
30 import java.awt.*;
31 import java.awt.event.MouseEvent;
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34
35 public class ActionButton extends JComponent implements ActionButtonComponent {
36   private static final Insets ICON_INSETS = new Insets(2, 2, 2, 2);
37
38   private static final Icon ourEmptyIcon = new EmptyIcon(18, 18);
39
40   private Dimension myMinimumButtonSize;
41   private PropertyChangeListener myActionButtonSynchronizer;
42   private Icon myDisabledIcon;
43   private Icon myIcon;
44   protected final Presentation myPresentation;
45   protected final AnAction myAction;
46   private final String myPlace;
47   private ActionButtonLook myLook = ActionButtonLook.IDEA_LOOK;
48   private boolean myMouseDown;
49   private boolean myRollover;
50   private static boolean ourGlobalMouseDown = false;
51
52   private static final Icon myDropdownIcon = IconLoader.getIcon("/general/dropdown.png");
53
54   private boolean myNoIconsInPopup = false;
55
56   public ActionButton(
57     final AnAction action,
58     final Presentation presentation,
59     final String place,
60     @NotNull final Dimension minimumSize
61     ) {
62     setMinimumButtonSize(minimumSize);
63     myRollover = false;
64     myMouseDown = false;
65     myAction = action;
66     myPresentation = presentation;
67     myPlace = place;
68     setFocusable(false);
69     enableEvents(AWTEvent.MOUSE_EVENT_MASK);
70     myMinimumButtonSize = minimumSize;
71   }
72
73   public void setNoIconsInPopup(boolean noIconsInPopup) {
74     myNoIconsInPopup = noIconsInPopup;
75   }
76
77   public void setMinimumButtonSize(@NotNull Dimension size) {
78     myMinimumButtonSize = size;
79   }
80
81   public void paintChildren(Graphics g) {}
82
83   public int getPopState() {
84     if (myAction instanceof Toggleable) {
85       Boolean selected = (Boolean)myPresentation.getClientProperty(Toggleable.SELECTED_PROPERTY);
86       boolean flag1 = selected != null && selected.booleanValue();
87       return getPopState(flag1);
88     }
89     else {
90       return getPopState(false);
91     }
92   }
93
94   protected boolean isButtonEnabled() {
95     return myPresentation.isEnabled();
96   }
97
98   private void onMousePresenceChanged(boolean setInfo) {
99     ActionMenu.showDescriptionInStatusBar(setInfo, this, myPresentation.getDescription());
100   }
101
102   public void click() {
103     performAction(new MouseEvent(this, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), 0, 0, 0, 1, false));
104   }
105
106   private void performAction(MouseEvent e) {
107     AnActionEvent event = new AnActionEvent(
108       e, getDataContext(),
109       myPlace,
110       myPresentation,
111       ActionManager.getInstance(),
112       e.getModifiers()
113     );
114     if (!ActionUtil.lastUpdateAndCheckDumb(myAction, event, false)) {
115       return;
116     }
117
118     if (isButtonEnabled()) {
119       final ActionManagerEx manager = ActionManagerEx.getInstanceEx();
120       final DataContext dataContext = event.getDataContext();
121       manager.fireBeforeActionPerformed(myAction, dataContext, event);
122       Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
123       if (component != null && !component.isShowing()) {
124         return;
125       }
126       actionPerfomed(event);
127       manager.queueActionPerformedEvent(myAction, dataContext, event);
128     }
129   }
130
131   protected DataContext getDataContext() {
132     return DataManager.getInstance().getDataContext();
133   }
134
135   private void actionPerfomed(final AnActionEvent event) {
136     if (myAction instanceof ActionGroup && !(myAction instanceof CustomComponentAction) && ((ActionGroup)myAction).isPopup()) {
137       final ActionManagerImpl am = (ActionManagerImpl)ActionManager.getInstance();
138       ActionPopupMenu popupMenu = am.createActionPopupMenu(event.getPlace(), (ActionGroup)myAction, new MenuItemPresentationFactory() {
139         @Override
140         protected Presentation processPresentation(Presentation presentation) {
141           if (myNoIconsInPopup) {
142             presentation.setIcon(null);
143             presentation.setHoveredIcon(null);
144           }
145           return presentation;
146         }
147       });
148       popupMenu.getComponent().show(this, getWidth(), 0);
149     } else {
150       myAction.actionPerformed(event);
151     }
152   }
153
154   public void removeNotify() {
155     if (myActionButtonSynchronizer != null) {
156       myPresentation.removePropertyChangeListener(myActionButtonSynchronizer);
157       myActionButtonSynchronizer = null;
158     }
159     super.removeNotify();
160   }
161
162   public void addNotify() {
163     super.addNotify();
164     if (myActionButtonSynchronizer == null) {
165       myActionButtonSynchronizer = new ActionButtonSynchronizer();
166       myPresentation.addPropertyChangeListener(myActionButtonSynchronizer);
167     }
168     updateToolTipText();
169     updateIcon();
170   }
171
172   public void setToolTipText(String s) {
173     String tooltipText = AnAction.createTooltipText(s, myAction);
174     super.setToolTipText(tooltipText.length() > 0 ? tooltipText : null);
175   }
176
177   public Dimension getPreferredSize() {
178     Icon icon = getIcon();
179     if (
180       icon.getIconWidth() < myMinimumButtonSize.width &&
181       icon.getIconHeight() < myMinimumButtonSize.height
182     ) {
183       return myMinimumButtonSize;
184     }
185     else {
186       return new Dimension(
187         icon.getIconWidth() + ICON_INSETS.left + ICON_INSETS.right,
188         icon.getIconHeight() + ICON_INSETS.top + ICON_INSETS.bottom
189       );
190     }
191   }
192
193   public Dimension getMinimumSize() {
194     return getPreferredSize();
195   }
196
197   /**
198    * @return button's icon. Icon depends on action's state. It means that the method returns
199    *         disabled icon if action is disabled. If the action's icon is <code>null</code> then it returns
200    *         an empty icon.
201    */
202   protected Icon getIcon() {
203     Icon icon = isButtonEnabled() ? myIcon : myDisabledIcon;
204     if (icon == null) {
205       icon = ourEmptyIcon;
206     }
207     return icon;
208   }
209
210   private void updateIcon() {
211     myIcon = myPresentation.getIcon();
212     if (myPresentation.getDisabledIcon() != null) { // set disabled icon if it is specified
213       myDisabledIcon = myPresentation.getDisabledIcon();
214     }
215     else {
216       myDisabledIcon = IconLoader.getDisabledIcon(myIcon);
217     }
218   }
219
220   private void setDisabledIcon(Icon icon) {
221     myDisabledIcon = icon;
222   }
223
224   void updateToolTipText() {
225     String text = myPresentation.getText();
226     setToolTipText(text == null ? myPresentation.getDescription() : text);
227   }
228
229   public void paintComponent(Graphics g) {
230     super.paintComponent(g);
231
232     paintButtonLook(g);
233
234     if (myAction instanceof ActionGroup && ((ActionGroup)myAction).isPopup()) {
235
236       int x = 5;
237       int y = 4;
238
239       if (getPopState() == PUSHED) {
240         x++;
241         y++;
242       }
243
244       myDropdownIcon.paintIcon(this, g, x, y);
245     }
246   }
247
248   protected void paintButtonLook(Graphics g) {
249     ActionButtonLook look = getButtonLook();
250     look.paintBackground(g, this);
251     look.paintIcon(g, this, getIcon());
252     look.paintBorder(g, this);
253   }
254
255   protected ActionButtonLook getButtonLook() {
256     return myLook;
257   }
258
259   public void setLook(ActionButtonLook look) {
260     if (look != null) {
261       myLook = look;
262     }
263     else {
264       myLook = ActionButtonLook.IDEA_LOOK;
265     }
266     repaint();
267   }
268
269   protected void processMouseEvent(MouseEvent e) {
270     super.processMouseEvent(e);
271     if (e.isConsumed()) return;
272     boolean skipPress = e.isMetaDown() || e.getButton() != MouseEvent.BUTTON1;
273     switch (e.getID()) {
274       case MouseEvent.MOUSE_PRESSED:
275         if (skipPress || !isButtonEnabled()) return;
276         myMouseDown = true;
277         ourGlobalMouseDown = true;
278         repaint();
279         break;
280
281       case MouseEvent.MOUSE_RELEASED:
282         if (skipPress || !isButtonEnabled()) return;
283         myMouseDown = false;
284         ourGlobalMouseDown = false;
285         if (myRollover) {
286           performAction(e);
287         }
288         repaint();
289         break;
290
291       case MouseEvent.MOUSE_ENTERED:
292         if (!myMouseDown && ourGlobalMouseDown) break;
293         myRollover = true;
294         repaint();
295         onMousePresenceChanged(true);
296         break;
297
298       case MouseEvent.MOUSE_EXITED:
299         myRollover = false;
300         if (!myMouseDown && ourGlobalMouseDown) break;
301         repaint();
302         onMousePresenceChanged(false);
303         break;
304     }
305   }
306
307   private int getPopState(boolean isPushed) {
308     if (isPushed || myRollover && myMouseDown && isButtonEnabled()) {
309       return PUSHED;
310     }
311     else {
312       return !myRollover || !isButtonEnabled() ? NORMAL : POPPED;
313     }
314   }
315
316   public AnAction getAction() {
317     return myAction;
318   }
319
320   private class ActionButtonSynchronizer implements PropertyChangeListener {
321     @NonNls protected static final String SELECTED_PROPERTY_NAME = "selected";
322
323     public void propertyChange(PropertyChangeEvent e) {
324       String propertyName = e.getPropertyName();
325       if (Presentation.PROP_TEXT.equals(propertyName)) {
326         updateToolTipText();
327       }
328       else if (Presentation.PROP_ENABLED.equals(propertyName)) {
329         repaint();
330       }
331       else if (Presentation.PROP_ICON.equals(propertyName)) {
332         updateIcon();
333         repaint();
334       }
335       else if (Presentation.PROP_DISABLED_ICON.equals(propertyName)) {
336         setDisabledIcon(myPresentation.getDisabledIcon());
337         repaint();
338       }
339       else if (Presentation.PROP_VISIBLE.equals(propertyName)) {
340       }
341       else if (SELECTED_PROPERTY_NAME.equals(propertyName)) {
342         repaint();
343       }
344     }
345   }
346 }