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