2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.actionSystem.impl;
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.impl.DataManagerImpl;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.actionSystem.ex.ActionButtonLook;
24 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
25 import com.intellij.openapi.actionSystem.ex.AnActionListener;
26 import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
31 import com.intellij.openapi.project.DumbAwareRunnable;
32 import com.intellij.openapi.ui.popup.*;
33 import com.intellij.openapi.util.ActionCallback;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.wm.IdeFocusManager;
37 import com.intellij.openapi.wm.WindowManager;
38 import com.intellij.openapi.wm.ex.WindowManagerEx;
39 import com.intellij.ui.ColorUtil;
40 import com.intellij.ui.Gray;
41 import com.intellij.ui.JBColor;
42 import com.intellij.ui.awt.RelativePoint;
43 import com.intellij.ui.awt.RelativeRectangle;
44 import com.intellij.ui.switcher.SwitchTarget;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.ui.JBUI;
47 import com.intellij.util.ui.UIUtil;
48 import com.intellij.util.ui.update.UiNotifyConnector;
49 import org.jetbrains.annotations.NotNull;
52 import javax.swing.border.EmptyBorder;
54 import java.awt.event.ComponentAdapter;
55 import java.awt.event.ComponentEvent;
56 import java.awt.event.MouseEvent;
57 import java.util.ArrayList;
58 import java.util.LinkedList;
59 import java.util.List;
61 public class ActionToolbarImpl extends JPanel implements ActionToolbar {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.impl.ActionToolbarImpl");
64 private static final List<ActionToolbarImpl> ourToolbars = new LinkedList<ActionToolbarImpl>();
65 private static final String RIGHT_ALIGN_KEY = "RIGHT_ALIGN";
67 public static void updateAllToolbarsImmediately() {
68 for (ActionToolbarImpl toolbar : new ArrayList<ActionToolbarImpl>(ourToolbars)) {
69 toolbar.updateActionsImmediately();
70 for (Component c : toolbar.getComponents()) {
71 if (c instanceof ActionButton) {
72 ((ActionButton)c).updateToolTipText();
73 ((ActionButton)c).updateIcon();
80 * This array contains Rectangles which define bounds of the corresponding
81 * components in the toolbar. This list can be consider as a cache of the
82 * Rectangle objects that are used in calculation of preferred sizes and
85 private final List<Rectangle> myComponentBounds = new ArrayList<Rectangle>();
87 private Dimension myMinimumButtonSize = JBUI.emptySize();
90 * @see ActionToolbar#getLayoutPolicy()
92 private int myLayoutPolicy;
93 private int myOrientation;
94 private final ActionGroup myActionGroup;
95 private final String myPlace;
96 protected List<AnAction> myVisibleActions;
97 private final PresentationFactory myPresentationFactory = new PresentationFactory();
98 private final boolean myDecorateButtons;
100 private final ToolbarUpdater myUpdater;
103 * @see ActionToolbar#adjustTheSameSize(boolean)
105 private boolean myAdjustTheSameSize;
107 private final ActionButtonLook myButtonLook = null;
108 private final ActionButtonLook myMinimalButtonLook = new InplaceActionButtonLook();
109 private final DataManager myDataManager;
110 protected final ActionManagerEx myActionManager;
112 private Rectangle myAutoPopupRec;
114 private final DefaultActionGroup mySecondaryActions = new DefaultActionGroup();
115 private boolean myMinimalMode;
116 private boolean myForceUseMacEnhancements;
118 public ActionButton getSecondaryActionsButton() {
119 return mySecondaryActionsButton;
122 private ActionButton mySecondaryActionsButton;
124 private int myFirstOutsideIndex = -1;
125 private JBPopup myPopup;
127 private JComponent myTargetComponent;
128 private boolean myReservePlaceAutoPopupIcon = true;
129 private boolean myAddSeparatorFirst;
131 public ActionToolbarImpl(String place,
132 @NotNull final ActionGroup actionGroup,
134 @NotNull DataManager dataManager,
135 @NotNull ActionManagerEx actionManager,
136 @NotNull KeymapManagerEx keymapManager) {
137 this(place, actionGroup, horizontal, false, dataManager, actionManager, keymapManager, false);
140 public ActionToolbarImpl(String place,
141 @NotNull ActionGroup actionGroup,
143 boolean decorateButtons,
144 @NotNull DataManager dataManager,
145 @NotNull ActionManagerEx actionManager,
146 @NotNull KeymapManagerEx keymapManager) {
147 this(place, actionGroup, horizontal, decorateButtons, dataManager, actionManager, keymapManager, false);
150 public ActionToolbarImpl(String place,
151 @NotNull ActionGroup actionGroup,
152 final boolean horizontal,
153 final boolean decorateButtons,
154 @NotNull DataManager dataManager,
155 @NotNull ActionManagerEx actionManager,
156 @NotNull KeymapManagerEx keymapManager,
157 boolean updateActionsNow) {
159 myActionManager = actionManager;
161 myActionGroup = actionGroup;
162 myVisibleActions = new ArrayList<AnAction>();
163 myDataManager = dataManager;
164 myDecorateButtons = decorateButtons;
165 myUpdater = new ToolbarUpdater(actionManager, keymapManager, this) {
167 protected void updateActionsImpl(boolean transparentOnly, boolean forced) {
168 ActionToolbarImpl.this.updateActionsImpl(transparentOnly, forced);
172 setLayout(new BorderLayout());
173 setOrientation(horizontal ? SwingConstants.HORIZONTAL : SwingConstants.VERTICAL);
175 mySecondaryActions.getTemplatePresentation().setIcon(AllIcons.General.SecondaryGroup);
176 mySecondaryActions.setPopup(true);
178 myUpdater.updateActions(updateActionsNow, false);
180 // If the panel doesn't handle mouse event then it will be passed to its parent.
181 // It means that if the panel is in sliding mode then the focus goes to the editor
182 // and panel will be automatically hidden.
183 enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.CONTAINER_EVENT_MASK);
188 public void updateUI() {
190 for (Component component : getComponents()) {
191 tweakActionComponentUI(component);
196 public void addNotify() {
198 ourToolbars.add(this);
200 // should update action right on the showing, otherwise toolbar may not be displayed at all,
201 // since by default all updates are postponed until frame gets focused.
202 updateActionsImmediately();
205 private boolean doMacEnhancementsForMainToolbar() {
206 return UIUtil.isUnderAquaLookAndFeel() && (ActionPlaces.MAIN_TOOLBAR.equals(myPlace) || myForceUseMacEnhancements);
209 public void setForceUseMacEnhancements(boolean useMacEnhancements) {
210 myForceUseMacEnhancements = useMacEnhancements;
213 private boolean isInsideNavBar() {
214 return ActionPlaces.NAVIGATION_BAR_TOOLBAR.equals(myPlace);
218 public void removeNotify() {
219 super.removeNotify();
220 ourToolbars.remove(this);
224 public JComponent getComponent() {
229 public int getLayoutPolicy() {
230 return myLayoutPolicy;
234 public void setLayoutPolicy(final int layoutPolicy) {
235 if (layoutPolicy != NOWRAP_LAYOUT_POLICY && layoutPolicy != WRAP_LAYOUT_POLICY && layoutPolicy != AUTO_LAYOUT_POLICY) {
236 throw new IllegalArgumentException("wrong layoutPolicy: " + layoutPolicy);
238 myLayoutPolicy = layoutPolicy;
242 protected void paintComponent(final Graphics g) {
243 if (doMacEnhancementsForMainToolbar()) {
244 final Rectangle r = getBounds();
245 UIUtil.drawGradientHToolbarBackground(g, r.width, r.height);
247 super.paintComponent(g);
250 if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
251 if (myAutoPopupRec != null) {
252 if (myOrientation == SwingConstants.HORIZONTAL) {
253 final int dy = myAutoPopupRec.height / 2 - AllIcons.Ide.Link.getIconHeight() / 2;
254 AllIcons.Ide.Link.paintIcon(this, g, (int)myAutoPopupRec.getMaxX() - AllIcons.Ide.Link.getIconWidth() - 1, myAutoPopupRec.y + dy);
257 final int dx = myAutoPopupRec.width / 2 - AllIcons.Ide.Link.getIconWidth() / 2;
258 AllIcons.Ide.Link.paintIcon(this, g, myAutoPopupRec.x + dx, (int)myAutoPopupRec.getMaxY() - AllIcons.Ide.Link.getIconWidth() - 1);
264 private void fillToolBar(final List<AnAction> actions, boolean layoutSecondaries) {
265 final List<AnAction> rightAligned = new ArrayList<AnAction>();
266 if (myAddSeparatorFirst) {
267 add(new MySeparator());
269 for (int i = 0; i < actions.size(); i++) {
270 final AnAction action = actions.get(i);
271 if (action instanceof RightAlignedToolbarAction) {
272 rightAligned.add(action);
275 // if (action instanceof Separator && isNavBar()) {
279 //if (action instanceof ComboBoxAction) {
280 // ((ComboBoxAction)action).setSmallVariant(true);
283 if (layoutSecondaries) {
284 if (!myActionGroup.isPrimary(action)) {
285 mySecondaryActions.add(action);
290 if (action instanceof Separator) {
291 if (i > 0 && i < actions.size() - 1) {
292 add(new MySeparator());
295 else if (action instanceof CustomComponentAction) {
296 add(getCustomComponent(action));
299 add(createToolbarButton(action));
303 if (mySecondaryActions.getChildrenCount() > 0) {
304 mySecondaryActionsButton = new ActionButton(mySecondaryActions, myPresentationFactory.getPresentation(mySecondaryActions), myPlace, getMinimumButtonSize());
305 mySecondaryActionsButton.setNoIconsInPopup(true);
306 add(mySecondaryActionsButton);
309 for (AnAction action : rightAligned) {
310 JComponent button = action instanceof CustomComponentAction ? getCustomComponent(action) : createToolbarButton(action);
311 button.putClientProperty(RIGHT_ALIGN_KEY, Boolean.TRUE);
314 //if ((ActionPlaces.MAIN_TOOLBAR.equals(myPlace) || ActionPlaces.NAVIGATION_BAR_TOOLBAR.equals(myPlace))) {
315 // final AnAction searchEverywhereAction = ActionManager.getInstance().getAction("SearchEverywhere");
316 // if (searchEverywhereAction != null) {
318 // final CustomComponentAction searchEveryWhereAction = (CustomComponentAction)searchEverywhereAction;
319 // final JComponent searchEverywhere = searchEveryWhereAction.createCustomComponent(searchEverywhereAction.getTemplatePresentation());
320 // searchEverywhere.putClientProperty("SEARCH_EVERYWHERE", Boolean.TRUE);
321 // add(searchEverywhere);
323 // catch (Exception ignore) {}
328 private JComponent getCustomComponent(AnAction action) {
329 Presentation presentation = myPresentationFactory.getPresentation(action);
330 JComponent customComponent = ((CustomComponentAction)action).createCustomComponent(presentation);
331 tweakActionComponentUI(customComponent);
332 presentation.putClientProperty(CustomComponentAction.CUSTOM_COMPONENT_PROPERTY, customComponent);
333 return customComponent;
336 private void tweakActionComponentUI(@NotNull Component actionComponent) {
337 if (ActionPlaces.EDITOR_TOOLBAR.equals(myPlace)) {
338 // tweak font & color for editor toolbar to match editor tabs style
339 actionComponent.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));
340 actionComponent.setForeground(ColorUtil.dimmer(JBColor.BLACK));
344 private Dimension getMinimumButtonSize() {
345 return isInsideNavBar() ? NAVBAR_MINIMUM_BUTTON_SIZE : DEFAULT_MINIMUM_BUTTON_SIZE;
348 public ActionButton createToolbarButton(final AnAction action, final ActionButtonLook look, final String place, final Presentation presentation, final Dimension minimumSize) {
349 if (action.displayTextInToolbar()) {
350 return new ActionButtonWithText(action, presentation, place, minimumSize);
353 final ActionButton actionButton = new ActionButton(action, presentation, place, minimumSize) {
355 protected DataContext getDataContext() {
356 return getToolbarDataContext();
359 actionButton.setLook(look);
363 private ActionButton createToolbarButton(final AnAction action) {
364 return createToolbarButton(
366 myMinimalMode ? myMinimalButtonLook : myDecorateButtons ? new MacToolbarDecoratorButtonLook() : myButtonLook,
367 myPlace, myPresentationFactory.getPresentation(action),
368 myMinimumButtonSize);
372 public void doLayout() {
374 calculateBounds(getSize(), myComponentBounds);
376 final int componentCount = getComponentCount();
377 LOG.assertTrue(componentCount <= myComponentBounds.size());
378 for (int i = componentCount - 1; i >= 0; i--) {
379 final Component component = getComponent(i);
380 component.setBounds(myComponentBounds.get(i));
385 public void validate() {
387 calculateBounds(getSize(), myComponentBounds);
392 private Dimension getChildPreferredSize(int index) {
393 Component component = getComponent(index);
394 return component.isVisible() ? component.getPreferredSize() : new Dimension();
398 * @return maximum button width
400 private int getMaxButtonWidth() {
402 for (int i = 0; i < getComponentCount(); i++) {
403 final Dimension dimension = getChildPreferredSize(i);
404 width = Math.max(width, dimension.width);
410 * @return maximum button height
413 public int getMaxButtonHeight() {
415 for (int i = 0; i < getComponentCount(); i++) {
416 final Dimension dimension = getChildPreferredSize(i);
417 height = Math.max(height, dimension.height);
422 private void calculateBoundsNowrapImpl(List<Rectangle> bounds) {
423 final int componentCount = getComponentCount();
424 LOG.assertTrue(componentCount <= bounds.size());
426 final int width = getWidth();
427 final int height = getHeight();
429 final Insets insets = getInsets();
431 if (myAdjustTheSameSize) {
432 final int maxWidth = getMaxButtonWidth();
433 final int maxHeight = getMaxButtonHeight();
435 if (myOrientation == SwingConstants.HORIZONTAL) {
436 int xOffset = insets.left;
437 for (int i = 0; i < componentCount; i++) {
438 final Rectangle r = bounds.get(i);
439 r.setBounds(xOffset, (height - maxHeight) / 2, maxWidth, maxHeight);
444 int yOffset = insets.top;
445 for (int i = 0; i < componentCount; i++) {
446 final Rectangle r = bounds.get(i);
447 r.setBounds((width - maxWidth) / 2, yOffset, maxWidth, maxHeight);
448 yOffset += maxHeight;
453 if (myOrientation == SwingConstants.HORIZONTAL) {
454 final int maxHeight = getMaxButtonHeight();
456 int xOffset = insets.left;
457 final int yOffset = insets.top;
458 for (int i = 0; i < componentCount; i++) {
459 final Dimension d = getChildPreferredSize(i);
460 final Rectangle r = bounds.get(i);
461 r.setBounds(xOffset, yOffset + (maxHeight - d.height) / 2, d.width, d.height);
466 final int maxWidth = getMaxButtonWidth();
467 final int xOffset = insets.left;
468 int yOffset = insets.top;
469 for (int i = 0; i < componentCount; i++) {
470 final Dimension d = getChildPreferredSize(i);
471 final Rectangle r = bounds.get(i);
472 r.setBounds(xOffset + (maxWidth - d.width) / 2, yOffset, d.width, d.height);
479 private void calculateBoundsAutoImp(Dimension sizeToFit, List<Rectangle> bounds) {
480 final int componentCount = getComponentCount();
481 LOG.assertTrue(componentCount <= bounds.size());
483 final boolean actualLayout = bounds == myComponentBounds;
486 myAutoPopupRec = null;
489 int autoButtonSize = AllIcons.Ide.Link.getIconWidth();
490 boolean full = false;
492 final Insets insets = getInsets();
494 if (myOrientation == SwingConstants.HORIZONTAL) {
495 int eachX = insets.left;
496 int eachY = insets.top;
498 for (int i = 0; i < componentCount; i++) {
499 final Component eachComp = getComponent(i);
500 final boolean isLast = i == componentCount - 1;
502 final Rectangle eachBound = new Rectangle(getChildPreferredSize(i));
503 maxHeight = Math.max(eachBound.height, maxHeight);
508 inside = eachX + eachBound.width <= sizeToFit.width;
510 inside = eachX + eachBound.width + autoButtonSize <= sizeToFit.width;
514 if (eachComp == mySecondaryActionsButton) {
516 if (sizeToFit.width != Integer.MAX_VALUE) {
517 eachBound.x = sizeToFit.width - eachBound.width;
518 eachX = (int)eachBound.getMaxX();
525 eachX += eachBound.width;
535 if (myAutoPopupRec == null) {
536 myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - eachX - 1, sizeToFit.height - 1);
537 myFirstOutsideIndex = i;
539 eachBound.x = Integer.MAX_VALUE;
540 eachBound.y = Integer.MAX_VALUE;
543 bounds.get(i).setBounds(eachBound);
546 for (final Rectangle r : bounds) {
547 if (r.height < maxHeight) {
548 r.y += (maxHeight - r.height) / 2;
554 int eachX = insets.left;
555 int eachY = insets.top;
556 for (int i = 0; i < componentCount; i++) {
557 final Rectangle eachBound = new Rectangle(getChildPreferredSize(i));
560 if (i < componentCount - 1) {
561 outside = eachY + eachBound.height + autoButtonSize < sizeToFit.height;
564 outside = eachY + eachBound.height < sizeToFit.height;
569 eachY += eachBound.height;
577 if (myAutoPopupRec == null) {
578 myAutoPopupRec = new Rectangle(eachX, eachY, sizeToFit.width - 1, sizeToFit.height - eachY - 1);
579 myFirstOutsideIndex = i;
581 eachBound.x = Integer.MAX_VALUE;
582 eachBound.y = Integer.MAX_VALUE;
585 bounds.get(i).setBounds(eachBound);
591 private void calculateBoundsWrapImpl(Dimension sizeToFit, List<Rectangle> bounds) {
592 // We have to graceful handle case when toolbar was not laid out yet.
593 // In this case we calculate bounds as it is a NOWRAP toolbar.
594 if (getWidth() == 0 || getHeight() == 0) {
596 setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
597 calculateBoundsNowrapImpl(bounds);
600 setLayoutPolicy(WRAP_LAYOUT_POLICY);
606 final int componentCount = getComponentCount();
607 LOG.assertTrue(componentCount <= bounds.size());
609 final Insets insets = getInsets();
611 if (myAdjustTheSameSize) {
612 if (myOrientation == SwingConstants.HORIZONTAL) {
613 final int maxWidth = getMaxButtonWidth();
614 final int maxHeight = getMaxButtonHeight();
616 // Lay components out
617 int xOffset = insets.left;
618 int yOffset = insets.top;
619 // Calculate max size of a row. It's not possible to make more than 3 row toolbar
620 final int maxRowWidth = Math.max(sizeToFit.width, componentCount * maxWidth / 3);
621 for (int i = 0; i < componentCount; i++) {
622 if (xOffset + maxWidth > maxRowWidth) { // place component at new row
623 xOffset = insets.left;
624 yOffset += maxHeight;
627 final Rectangle each = bounds.get(i);
628 each.setBounds(xOffset, yOffset, maxWidth, maxHeight);
634 final int maxWidth = getMaxButtonWidth();
635 final int maxHeight = getMaxButtonHeight();
637 // Lay components out
638 int xOffset = insets.left;
639 int yOffset = insets.top;
640 // Calculate max size of a row. It's not possible to make more then 3 column toolbar
641 final int maxRowHeight = Math.max(sizeToFit.height, componentCount * myMinimumButtonSize.height / 3);
642 for (int i = 0; i < componentCount; i++) {
643 if (yOffset + maxHeight > maxRowHeight) { // place component at new row
644 yOffset = insets.top;
648 final Rectangle each = bounds.get(i);
649 each.setBounds(xOffset, yOffset, maxWidth, maxHeight);
651 yOffset += maxHeight;
656 if (myOrientation == SwingConstants.HORIZONTAL) {
657 // Calculate row height
659 final Dimension[] dims = new Dimension[componentCount]; // we will use this dimensions later
660 for (int i = 0; i < componentCount; i++) {
661 dims[i] = getChildPreferredSize(i);
662 final int height = dims[i].height;
663 rowHeight = Math.max(rowHeight, height);
666 // Lay components out
667 int xOffset = insets.left;
668 int yOffset = insets.top;
669 // Calculate max size of a row. It's not possible to make more then 3 row toolbar
670 final int maxRowWidth = Math.max(getWidth(), componentCount * myMinimumButtonSize.width / 3);
671 for (int i = 0; i < componentCount; i++) {
672 final Dimension d = dims[i];
673 if (xOffset + d.width > maxRowWidth) { // place component at new row
674 xOffset = insets.left;
675 yOffset += rowHeight;
678 final Rectangle each = bounds.get(i);
679 each.setBounds(xOffset, yOffset + (rowHeight - d.height) / 2, d.width, d.height);
685 // Calculate row width
687 final Dimension[] dims = new Dimension[componentCount]; // we will use this dimensions later
688 for (int i = 0; i < componentCount; i++) {
689 dims[i] = getChildPreferredSize(i);
690 final int width = dims[i].width;
691 rowWidth = Math.max(rowWidth, width);
694 // Lay components out
695 int xOffset = insets.left;
696 int yOffset = insets.top;
697 // Calculate max size of a row. It's not possible to make more then 3 column toolbar
698 final int maxRowHeight = Math.max(getHeight(), componentCount * myMinimumButtonSize.height / 3);
699 for (int i = 0; i < componentCount; i++) {
700 final Dimension d = dims[i];
701 if (yOffset + d.height > maxRowHeight) { // place component at new row
702 yOffset = insets.top;
706 final Rectangle each = bounds.get(i);
707 each.setBounds(xOffset + (rowWidth - d.width) / 2, yOffset, d.width, d.height);
716 * Calculates bounds of all the components in the toolbar
718 private void calculateBounds(Dimension size2Fit, List<Rectangle> bounds) {
720 for (int i = 0; i < getComponentCount(); i++) {
721 bounds.add(new Rectangle());
724 if (myLayoutPolicy == NOWRAP_LAYOUT_POLICY) {
725 calculateBoundsNowrapImpl(bounds);
727 else if (myLayoutPolicy == WRAP_LAYOUT_POLICY) {
728 calculateBoundsWrapImpl(size2Fit, bounds);
730 else if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
731 calculateBoundsAutoImp(size2Fit, bounds);
734 throw new IllegalStateException("unknown layoutPolicy: " + myLayoutPolicy);
738 if (getComponentCount() > 0 && size2Fit.width < Integer.MAX_VALUE) {
740 for (int i = 0; i < bounds.size() - 2; i++) {
741 maxHeight = Math.max(maxHeight, bounds.get(i).height);
744 for (int i = getComponentCount() - 1, j = 1; i > 0; i--, j++) {
745 final Component component = getComponent(i);
746 if (component instanceof JComponent && ((JComponent)component).getClientProperty(RIGHT_ALIGN_KEY) == Boolean.TRUE) {
747 bounds.set(bounds.size() - j, new Rectangle(size2Fit.width - j * JBUI.scale(25), 0, JBUI.scale(25), maxHeight));
754 public Dimension getPreferredSize() {
755 final ArrayList<Rectangle> bounds = new ArrayList<Rectangle>();
756 calculateBounds(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE), bounds);
757 if (bounds.isEmpty()) return JBUI.emptySize();
758 int xLeft = Integer.MAX_VALUE;
759 int yTop = Integer.MAX_VALUE;
760 int xRight = Integer.MIN_VALUE;
761 int yBottom = Integer.MIN_VALUE;
762 for (int i = bounds.size() - 1; i >= 0; i--) {
763 final Rectangle each = bounds.get(i);
764 if (each.x == Integer.MAX_VALUE) continue;
765 xLeft = Math.min(xLeft, each.x);
766 yTop = Math.min(yTop, each.y);
767 xRight = Math.max(xRight, each.x + each.width);
768 yBottom = Math.max(yBottom, each.y + each.height);
770 final Dimension dimension = new Dimension(xRight - xLeft, yBottom - yTop);
772 if (myLayoutPolicy == AUTO_LAYOUT_POLICY && myReservePlaceAutoPopupIcon) {
773 if (myOrientation == SwingConstants.HORIZONTAL) {
774 dimension.width += AllIcons.Ide.Link.getIconWidth();
777 dimension.height += AllIcons.Ide.Link.getIconHeight();
781 final Insets i = getInsets();
783 return new Dimension(dimension.width + i.left + i.right, dimension.height + i.top + i.bottom);
787 public Dimension getMinimumSize() {
788 if (myLayoutPolicy == AUTO_LAYOUT_POLICY) {
789 final Insets i = getInsets();
790 return new Dimension(AllIcons.Ide.Link.getIconWidth() + i.left + i.right, myMinimumButtonSize.height + i.top + i.bottom);
793 return super.getMinimumSize();
797 private final class MySeparator extends JComponent {
798 private final Dimension mySize;
800 public MySeparator() {
801 if (myOrientation == SwingConstants.HORIZONTAL) {
802 mySize = JBUI.size(6, 24);
805 mySize = JBUI.size(24, 6);
810 public Dimension getPreferredSize() {
815 protected void paintComponent(final Graphics g) {
816 final Insets i = getInsets();
817 if (UIUtil.isUnderAquaBasedLookAndFeel() || UIUtil.isUnderDarcula()) {
818 if (getParent() != null) {
819 final JBColor col = new JBColor(Gray._128, Gray._111);
820 final Graphics2D g2 = (Graphics2D)g;
821 if (myOrientation == SwingConstants.HORIZONTAL) {
822 UIUtil.drawDoubleSpaceDottedLine(g2, i.top + 2, getParent().getSize().height - 2 - i.top - i.bottom, 3, col, false);
824 UIUtil.drawDoubleSpaceDottedLine(g2, i.left + 2, getParent().getSize().width - 2 - i.left - i.right, 3, col, true);
829 g.setColor(UIUtil.getSeparatorColor());
830 if (getParent() != null) {
831 if (myOrientation == SwingConstants.HORIZONTAL) {
832 UIUtil.drawLine(g, 3, 2, 3, getParent().getSize().height - 2);
835 UIUtil.drawLine(g, 2, 3, getParent().getSize().width - 2, 3);
843 public void adjustTheSameSize(final boolean value) {
844 if (myAdjustTheSameSize == value) {
847 myAdjustTheSameSize = value;
852 public void setMinimumButtonSize(@NotNull final Dimension size) {
853 myMinimumButtonSize = size;
854 for (int i = getComponentCount() - 1; i >= 0; i--) {
855 final Component component = getComponent(i);
856 if (component instanceof ActionButton) {
857 final ActionButton button = (ActionButton)component;
858 button.setMinimumButtonSize(size);
865 public void setOrientation(final int orientation) {
866 if (SwingConstants.HORIZONTAL != orientation && SwingConstants.VERTICAL != orientation) {
867 throw new IllegalArgumentException("wrong orientation: " + orientation);
869 myOrientation = orientation;
873 public void updateActionsImmediately() {
874 ApplicationManager.getApplication().assertIsDispatchThread();
875 myUpdater.updateActions(true, false);
878 private void updateActionsImpl(boolean transparentOnly, boolean forced) {
879 List<AnAction> newVisibleActions = ContainerUtil.newArrayListWithCapacity(myVisibleActions.size());
880 DataContext dataContext = getDataContext();
882 Utils.expandActionGroup(myActionGroup, newVisibleActions, myPresentationFactory, dataContext,
883 myPlace, myActionManager, transparentOnly);
885 if (forced || !newVisibleActions.equals(myVisibleActions)) {
886 boolean shouldRebuildUI = newVisibleActions.isEmpty() || myVisibleActions.isEmpty();
887 myVisibleActions = newVisibleActions;
889 Dimension oldSize = getPreferredSize();
892 mySecondaryActions.removeAll();
893 mySecondaryActionsButton = null;
894 fillToolBar(myVisibleActions, getLayoutPolicy() == AUTO_LAYOUT_POLICY && myOrientation == SwingConstants.HORIZONTAL);
896 Dimension newSize = getPreferredSize();
898 ((WindowManagerEx)WindowManager.getInstance()).adjustContainerWindow(this, oldSize, newSize);
900 if (shouldRebuildUI) {
904 Container parent = getParent();
905 if (parent != null) {
917 public boolean hasVisibleActions() {
918 return !myVisibleActions.isEmpty();
922 public void setTargetComponent(final JComponent component) {
923 myTargetComponent = component;
925 if (myTargetComponent != null) {
926 UiNotifyConnector.doWhenFirstShown(myTargetComponent, new DumbAwareRunnable() {
929 myUpdater.updateActions(false, false);
936 public DataContext getToolbarDataContext() {
937 return getDataContext();
940 protected DataContext getDataContext() {
941 return myTargetComponent != null ? myDataManager.getDataContext(myTargetComponent) : ((DataManagerImpl)myDataManager).getDataContextTest(this);
945 protected void processMouseMotionEvent(final MouseEvent e) {
946 super.processMouseMotionEvent(e);
948 if (getLayoutPolicy() != AUTO_LAYOUT_POLICY) {
951 if (myAutoPopupRec != null && myAutoPopupRec.contains(e.getPoint())) {
952 IdeFocusManager.getInstance(null).doWhenFocusSettlesDown(new Runnable() {
961 private void showAutoPopup() {
962 if (isPopupShowing()) return;
964 final ActionGroup group;
965 if (myOrientation == SwingConstants.HORIZONTAL) {
966 group = myActionGroup;
969 final DefaultActionGroup outside = new DefaultActionGroup();
970 for (int i = myFirstOutsideIndex; i < myVisibleActions.size(); i++) {
971 outside.add(myVisibleActions.get(i));
976 PopupToolbar popupToolbar = new PopupToolbar(myPlace, group, true, myDataManager, myActionManager, myUpdater.getKeymapManager(), this) {
978 protected void onOtherActionPerformed() {
983 protected DataContext getDataContext() {
984 return ActionToolbarImpl.this.getDataContext();
987 popupToolbar.setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
988 popupToolbar.updateActionsImmediately();
991 if (myOrientation == SwingConstants.HORIZONTAL) {
992 location = getLocationOnScreen();
995 location = getLocationOnScreen();
996 location.y = location.y + getHeight() - popupToolbar.getPreferredSize().height;
1000 final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(popupToolbar, null);
1001 builder.setResizable(false)
1002 .setMovable(true) // fit the screen automatically
1003 .setMayBeParent(true) // a dialog-based popup is not always on top
1004 .setRequestFocus(false)
1006 .setCancelOnClickOutside(true)
1007 .setCancelOnOtherWindowOpen(true)
1008 .setCancelCallback(new Computable<Boolean>() {
1010 public Boolean compute() {
1011 final boolean toClose = myActionManager.isActionPopupStackEmpty();
1013 myUpdater.updateActions(false, true);
1018 .setCancelOnMouseOutCallback(new MouseChecker() {
1020 public boolean check(final MouseEvent event) {
1021 return myAutoPopupRec != null &&
1022 myActionManager.isActionPopupStackEmpty() &&
1023 !new RelativeRectangle(ActionToolbarImpl.this, myAutoPopupRec).contains(new RelativePoint(event));
1027 builder.addListener(new JBPopupAdapter() {
1029 public void onClosed(LightweightWindowEvent event) {
1033 myPopup = builder.createPopup();
1034 final AnActionListener.Adapter listener = new AnActionListener.Adapter() {
1036 public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
1037 final JBPopup popup = myPopup;
1038 if (popup != null && !popup.isDisposed() && popup.isVisible()) {
1043 ActionManager.getInstance().addAnActionListener(listener);
1044 Disposer.register(myPopup, popupToolbar);
1045 Disposer.register(popupToolbar, new Disposable() {
1047 public void dispose() {
1048 ActionManager.getInstance().removeAnActionListener(listener);
1052 myPopup.showInScreenCoordinates(this, location);
1054 final Window window = SwingUtilities.getWindowAncestor(this);
1055 if (window != null) {
1056 final ComponentAdapter componentAdapter = new ComponentAdapter() {
1058 public void componentResized(final ComponentEvent e) {
1063 public void componentMoved(final ComponentEvent e) {
1068 public void componentShown(final ComponentEvent e) {
1073 public void componentHidden(final ComponentEvent e) {
1077 window.addComponentListener(componentAdapter);
1078 Disposer.register(popupToolbar, new Disposable() {
1080 public void dispose() {
1081 window.removeComponentListener(componentAdapter);
1088 private boolean isPopupShowing() {
1089 if (myPopup != null) {
1090 if (myPopup.getContent() != null) {
1097 private void hidePopup() {
1098 if (myPopup != null) {
1104 private void processClosed() {
1105 if (myPopup == null) return;
1107 Disposer.dispose(myPopup);
1110 myUpdater.updateActions(false, false);
1113 abstract static class PopupToolbar extends ActionToolbarImpl implements AnActionListener, Disposable {
1114 private final JComponent myParent;
1116 public PopupToolbar(final String place,
1117 final ActionGroup actionGroup,
1118 final boolean horizontal,
1119 final DataManager dataManager,
1120 @NotNull ActionManagerEx actionManager,
1121 final KeymapManagerEx keymapManager,
1122 JComponent parent) {
1123 super(place, actionGroup, horizontal, false, dataManager, actionManager, keymapManager, true);
1124 myActionManager.addAnActionListener(this);
1129 public Container getParent() {
1130 Container parent = super.getParent();
1131 return parent != null ? parent : myParent;
1135 public void dispose() {
1136 myActionManager.removeAnActionListener(this);
1140 public void beforeActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1144 public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
1145 if (!myVisibleActions.contains(action)) {
1146 onOtherActionPerformed();
1150 protected abstract void onOtherActionPerformed();
1153 public void beforeEditorTyping(final char c, final DataContext dataContext) {
1159 public void setReservePlaceAutoPopupIcon(final boolean reserve) {
1160 myReservePlaceAutoPopupIcon = reserve;
1164 public void setSecondaryActionsTooltip(String secondaryActionsTooltip) {
1165 mySecondaryActions.getTemplatePresentation().setDescription(secondaryActionsTooltip);
1169 public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
1170 ArrayList<SwitchTarget> result = new ArrayList<SwitchTarget>();
1172 if (getBounds().width * getBounds().height <= 0) return result;
1174 for (int i = 0; i < getComponentCount(); i++) {
1175 Component each = getComponent(i);
1176 if (each instanceof ActionButton) {
1177 result.add(new ActionTarget((ActionButton)each));
1183 private static class ActionTarget implements SwitchTarget {
1184 private final ActionButton myButton;
1186 private ActionTarget(ActionButton button) {
1191 public ActionCallback switchTo(boolean requestFocus) {
1193 return new ActionCallback.Done();
1197 public boolean isVisible() {
1198 return myButton.isVisible();
1202 public RelativeRectangle getRectangle() {
1203 return new RelativeRectangle(myButton.getParent(), myButton.getBounds());
1207 public Component getComponent() {
1212 public String toString() {
1213 return myButton.getAction().toString();
1218 public SwitchTarget getCurrentTarget() {
1223 public boolean isCycleRoot() {
1228 public List<AnAction> getActions(boolean originalProvider) {
1229 ArrayList<AnAction> result = new ArrayList<AnAction>();
1231 ArrayList<AnAction> secondary = new ArrayList<AnAction>();
1232 AnAction[] kids = myActionGroup.getChildren(null);
1233 for (AnAction each : kids) {
1234 if (myActionGroup.isPrimary(each)) {
1237 secondary.add(each);
1240 result.add(new Separator());
1241 result.addAll(secondary);
1247 public void setMiniMode(boolean minimalMode) {
1248 //if (myMinimalMode == minimalMode) return;
1250 myMinimalMode = minimalMode;
1251 if (myMinimalMode) {
1252 setMinimumButtonSize(JBUI.emptySize());
1253 setLayoutPolicy(NOWRAP_LAYOUT_POLICY);
1254 setBorder(new EmptyBorder(0, 0, 0, 0));
1257 if (isInsideNavBar()) {
1258 setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
1261 setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
1264 setMinimumButtonSize(myDecorateButtons ? new Dimension(30, 20) : DEFAULT_MINIMUM_BUTTON_SIZE);
1266 setLayoutPolicy(AUTO_LAYOUT_POLICY);
1269 myUpdater.updateActions(false, true);
1272 public void setAddSeparatorFirst(boolean addSeparatorFirst) {
1273 myAddSeparatorFirst = addSeparatorFirst;
1274 myUpdater.updateActions(false, true);