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