From 4a148f1cebd9b74d79ea235b4fb67447a5cb4450 Mon Sep 17 00:00:00 2001 From: Alexander Lobas Date: Fri, 15 Apr 2016 21:13:12 +0300 Subject: [PATCH] IDEA-153807 Welcome screen notifications --- .../impl/NotificationsManagerImpl.java | 102 ++++--- .../impl/welcomeScreen/FlatWelcomeFrame.java | 71 ++++- .../WelcomeBalloonLayoutImpl.java | 248 ++++++++++++++++++ .../src/com/intellij/ui/BalloonImpl.java | 25 +- .../ui/BalloonLayoutConfiguration.java | 28 +- .../com/intellij/ui/BalloonLayoutData.java | 4 + 6 files changed, 423 insertions(+), 55 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java index 51d4bbe6cd17..2359ace2ffda 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java @@ -43,6 +43,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeBalloonLayoutImpl; import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame; import com.intellij.ui.*; import com.intellij.ui.components.GradientViewport; @@ -81,6 +82,9 @@ import java.util.List; * @author spleaner */ public class NotificationsManagerImpl extends NotificationsManager { + public static final Color FILL_COLOR = new JBColor(Gray._242, new Color(78, 80, 82)); + public static final Color BORDER_COLOR = new JBColor(Gray._178.withAlpha(205), new Color(86, 90, 92, 205)); + public NotificationsManagerImpl() { ApplicationManager.getApplication().getMessageBus().connect().subscribe(Notifications.TOPIC, new MyNotificationListener(null)); } @@ -180,6 +184,9 @@ public class NotificationsManagerImpl extends NotificationsManager { case BALLOON: default: Balloon balloon = notifyByBalloon(notification, type, project); + if (project == null || project.isDefault()) { + return; + } if (!settings.isShouldLog() || type == NotificationDisplayType.STICKY_BALLOON) { if (balloon == null) { notification.expire(); @@ -250,6 +257,8 @@ public class NotificationsManagerImpl extends NotificationsManager { if (project == null || project.isDefault()) { BalloonLayoutData layoutData = new BalloonLayoutData(); layoutData.groupId = ""; + layoutData.welcomeScreen = layout instanceof WelcomeBalloonLayoutImpl; + layoutData.type = notification.getType(); layoutDataRef.set(layoutData); } else { @@ -286,7 +295,7 @@ public class NotificationsManagerImpl extends NotificationsManager { ((BalloonImpl)balloon).setHideOnClickOutside(true); } else //noinspection ConstantConditions - if (noProjects) { + if (noProjects && !newEnabled()) { projectManager.addProjectManagerListener(new ProjectManagerAdapter() { @Override public void projectOpened(Project project) { @@ -512,8 +521,6 @@ public class NotificationsManagerImpl extends NotificationsManager { Color foregroundD = Gray._191; final Color foreground = new JBColor(foregroundR, foregroundD); - final JBColor fillColor = new JBColor(Gray._242, new Color(78, 80, 82)); - final JEditorPane text = new JEditorPane() { @Override protected void paintComponent(Graphics g) { @@ -563,8 +570,7 @@ public class NotificationsManagerImpl extends NotificationsManager { text.setCaretPosition(0); } - final JScrollPane pane = ScrollPaneFactory.createScrollPane(text, true); - pane.setOpaque(false); + final JScrollPane pane = createBalloonScrollPane(text, false); pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() { @Override @@ -593,12 +599,15 @@ public class NotificationsManagerImpl extends NotificationsManager { layoutData.maxScrollHeight = Math.min(layoutData.fullHeight, calculateContentHeight(10)); layoutData.configuration = BalloonLayoutConfiguration.create(notification, layoutData, actions); - if (!showFullContent && layoutData.maxScrollHeight != layoutData.fullHeight) { + if (layoutData.welcomeScreen) { + layoutData.maxScrollHeight = layoutData.fullHeight; + } + else if (!showFullContent && layoutData.maxScrollHeight != layoutData.fullHeight) { pane.setViewport(new GradientViewport(text, JBUI.insets(10, 0), true) { @Nullable @Override protected Color getViewColor() { - return fillColor; + return FILL_COLOR; } @Override @@ -610,13 +619,7 @@ public class NotificationsManagerImpl extends NotificationsManager { }); } - pane.getViewport().setOpaque(false); - if (!Registry.is("ide.scroll.new.layout")) { - pane.getVerticalScrollBar().setUI(ButtonlessScrollBarUI.createTransparent()); - } - pane.setBackground(fillColor); - pane.getViewport().setBackground(fillColor); - pane.getVerticalScrollBar().setBackground(fillColor); + configureBalloonScrollPane(pane); if (!showFullContent && layoutData.twoLineHeight < layoutData.fullHeight) { text.setPreferredSize(null); @@ -680,12 +683,12 @@ public class NotificationsManagerImpl extends NotificationsManager { int height = title instanceof JEditorPane ? getFirstLineHeight((JEditorPane)title) : title.getHeight(); - g.setColor(fillColor); + g.setColor(FILL_COLOR); g.fillRect(x, y, width, height); width = layoutData.configuration.beforeGearSpace; x -= width; - ((Graphics2D)g).setPaint(new GradientPaint(x, y, ColorUtil.withAlpha(fillColor, 0.2), x + width, y, fillColor)); + ((Graphics2D)g).setPaint(new GradientPaint(x, y, ColorUtil.withAlpha(FILL_COLOR, 0.2), x + width, y, FILL_COLOR)); g.fillRect(x, y, width, height); } } @@ -711,22 +714,24 @@ public class NotificationsManagerImpl extends NotificationsManager { } if (notification.isContent()) { - centerPanel.add(pane, BorderLayout.CENTER); + centerPanel.add(layoutData.welcomeScreen ? text : pane, BorderLayout.CENTER); } - final Icon icon = NotificationsUtil.getIcon(notification); - JComponent iconComponent = new JComponent() { - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - icon.paintIcon(this, g, layoutData.configuration.iconOffset.width, layoutData.configuration.iconOffset.height); - } - }; - iconComponent.setOpaque(false); - iconComponent.setPreferredSize( - new Dimension(layoutData.configuration.iconPanelWidth, 2 * layoutData.configuration.iconOffset.height + icon.getIconHeight())); + if (!layoutData.welcomeScreen) { + final Icon icon = NotificationsUtil.getIcon(notification); + JComponent iconComponent = new JComponent() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + icon.paintIcon(this, g, layoutData.configuration.iconOffset.width, layoutData.configuration.iconOffset.height); + } + }; + iconComponent.setOpaque(false); + iconComponent.setPreferredSize( + new Dimension(layoutData.configuration.iconPanelWidth, 2 * layoutData.configuration.iconOffset.height + icon.getIconHeight())); - content.add(iconComponent, BorderLayout.WEST); + content.add(iconComponent, BorderLayout.WEST); + } JPanel buttons = createButtons(notification, content, listener); if (buttons != null) { @@ -753,10 +758,8 @@ public class NotificationsManagerImpl extends NotificationsManager { pane.setPreferredSize(new Dimension(maxWidth, paneSize.height + UIUtil.getScrollBarWidth())); } - JBColor borderColor = new JBColor(Gray._178.withAlpha(205), new Color(86, 90, 92, 205)); - final BalloonBuilder builder = JBPopupFactory.getInstance().createBalloonBuilder(content); - builder.setFillColor(fillColor) + builder.setFillColor(FILL_COLOR) .setCloseButtonEnabled(buttons == null) .setShowCallout(showCallout) .setShadow(false) @@ -764,16 +767,16 @@ public class NotificationsManagerImpl extends NotificationsManager { .setHideOnAction(hideOnClickOutside) .setHideOnKeyOutside(hideOnClickOutside) .setHideOnFrameResize(false) - .setBorderColor(borderColor) + .setBorderColor(BORDER_COLOR) .setBorderInsets(new Insets(0, 0, 0, 0)); final BalloonImpl balloon = (BalloonImpl)builder.createBalloon(); balloon.setAnimationEnabled(false); notification.setBalloon(balloon); - balloon.setShadowBorderProvider(new NotificationBalloonShadowBorderProvider(fillColor, borderColor)); + balloon.setShadowBorderProvider(new NotificationBalloonShadowBorderProvider(FILL_COLOR, BORDER_COLOR)); - if (buttons == null) { + if (!layoutData.welcomeScreen && buttons == null) { balloon.setActionProvider( new NotificationBalloonActionProvider(balloon, layout.getTitle(), layoutData, notification.getGroupId())); } @@ -782,6 +785,26 @@ public class NotificationsManagerImpl extends NotificationsManager { return balloon; } + @NotNull + public static JScrollPane createBalloonScrollPane(@NotNull Component content, boolean configure) { + JScrollPane pane = ScrollPaneFactory.createScrollPane(content, true); + if (configure) { + configureBalloonScrollPane(pane); + } + return pane; + } + + public static void configureBalloonScrollPane(@NotNull JScrollPane pane) { + pane.setOpaque(false); + pane.getViewport().setOpaque(false); + if (!Registry.is("ide.scroll.new.layout")) { + pane.getVerticalScrollBar().setUI(ButtonlessScrollBarUI.createTransparent()); + } + pane.setBackground(FILL_COLOR); + pane.getViewport().setBackground(FILL_COLOR); + pane.getVerticalScrollBar().setBackground(FILL_COLOR); + } + private static void createActionPanel(@NotNull final Notification notification, @NotNull JPanel centerPanel, int gap) { JPanel actionPanel = new NonOpaquePanel(new HorizontalLayout(gap, SwingConstants.CENTER)); centerPanel.add(BorderLayout.SOUTH, actionPanel); @@ -1006,7 +1029,7 @@ public class NotificationsManagerImpl extends NotificationsManager { private final JEditorPane myText; private final BalloonLayoutData myLayoutData; private Component myTitleComponent; - private JScrollPane myCenteredComponent; + private Component myCenteredComponent; private JPanel myActionPanel; private Component myExpandAction; @@ -1021,7 +1044,10 @@ public class NotificationsManagerImpl extends NotificationsManager { return myTitleComponent; } if (myCenteredComponent != null) { - return myCenteredComponent.getViewport().getView(); + if (myCenteredComponent instanceof JScrollPane) { + return ((JScrollPane)myCenteredComponent).getViewport().getView(); + } + return myCenteredComponent; } return null; } @@ -1032,7 +1058,7 @@ public class NotificationsManagerImpl extends NotificationsManager { myTitleComponent = comp; } else if (BorderLayout.CENTER.equals(constraints)) { - myCenteredComponent = (JScrollPane)comp; + myCenteredComponent = comp; } else if (BorderLayout.SOUTH.equals(constraints)) { myActionPanel = (JPanel)comp; diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java index a1286aa84919..6c2e042e00ae 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/FlatWelcomeFrame.java @@ -19,6 +19,8 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.RecentProjectsManager; import com.intellij.internal.statistic.UsageTrigger; +import com.intellij.notification.NotificationType; +import com.intellij.notification.impl.IdeNotificationArea; import com.intellij.openapi.Disposable; import com.intellij.openapi.MnemonicHelper; import com.intellij.openapi.actionSystem.*; @@ -49,6 +51,7 @@ import com.intellij.ui.components.labels.ActionLink; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.ui.popup.PopupFactoryImpl; import com.intellij.ui.popup.list.GroupedItemsListRenderer; +import com.intellij.util.ParameterizedRunnable; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MouseEventAdapter; @@ -124,7 +127,7 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont } }); - myBalloonLayout = new BalloonLayoutImpl(rootPane, JBUI.insets(8)); + myBalloonLayout = new WelcomeBalloonLayoutImpl(rootPane, JBUI.insets(8), myScreen.myEventListener, myScreen.myEventLocation); WelcomeFrame.setupCloseAction(this); MnemonicHelper.init(this); @@ -140,6 +143,7 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont public void dispose() { saveLocation(getBounds()); super.dispose(); + ((WelcomeBalloonLayoutImpl)myBalloonLayout).dispose(); Disposer.dispose(myScreen); WelcomeFrame.resetInstance(); } @@ -185,6 +189,8 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont private class FlatWelcomeScreen extends JPanel implements WelcomeScreen { private JBSlidingPanel mySlidingPanel = new JBSlidingPanel(); + public ParameterizedRunnable> myEventListener; + public Computable myEventLocation; public FlatWelcomeScreen() { super(new BorderLayout()); @@ -273,6 +279,7 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont } toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS)); + toolbar.add(createEventsLink()); toolbar.add(createActionLink("Configure", IdeActions.GROUP_WELCOME_SCREEN_CONFIGURE, AllIcons.General.GearPlain, !registeredVisible)); toolbar.add(createActionLink("Get Help", IdeActions.GROUP_WELCOME_SCREEN_DOC, null, false)); @@ -283,6 +290,46 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont return panel; } + private JComponent createEventsLink() { + final Ref actionLinkRef = new Ref<>(); + final JComponent panel = createActionLink("Events", AllIcons.Ide.Notification.NoEvents, actionLinkRef, new AnAction() { + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + ((WelcomeBalloonLayoutImpl)myBalloonLayout).showPopup(); + } + }); + panel.setVisible(false); + myEventListener = new ParameterizedRunnable>() { + @Override + public void run(List types) { + NotificationType type = null; + for (NotificationType t : types) { + if (NotificationType.ERROR == t) { + type = NotificationType.ERROR; + break; + } + if (NotificationType.WARNING == t) { + type = NotificationType.WARNING; + } + else if (type == null && NotificationType.INFORMATION == t) { + type = NotificationType.INFORMATION; + } + } + + actionLinkRef.get().setIcon(IdeNotificationArea.createIconWithNotificationCount(actionLinkRef.get(), type, types.size())); + panel.setVisible(true); + } + }; + myEventLocation = new Computable() { + @Override + public Point compute() { + Point location = SwingUtilities.convertPoint(panel, 0, 0, getRootPane().getLayeredPane()); + return new Point(location.x, location.y + 5); + } + }; + return panel; + } + private JComponent createActionLink(final String text, final String groupId, Icon icon, boolean focusListOnLeft) { final Ref ref = new Ref(null); AnAction action = new AnAction() { @@ -296,16 +343,22 @@ public class FlatWelcomeFrame extends JFrame implements IdeFrame, AccessibleCont UsageTrigger.trigger("welcome.screen." + groupId); } }; - ref.set(new ActionLink(text, icon, action)); - ref.get().setPaintUnderline(false); - ref.get().setNormalColor(getLinkNormalColor()); + JComponent panel = createActionLink(text, icon, ref, action); + installFocusable(panel, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, focusListOnLeft); + return panel; + } + + private JComponent createActionLink(String text, Icon icon, Ref ref, AnAction action) { + ActionLink link = new ActionLink(text, icon, action); + ref.set(link); + link.setPaintUnderline(false); + link.setNormalColor(getLinkNormalColor()); NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout()); panel.setBorder(JBUI.Borders.empty(4, 6, 4, 6)); - panel.add(ref.get()); - AccessibleContextUtil.setName(panel, ref.get()); - AccessibleContextUtil.setDescription(panel, ref.get()); - panel.add(createArrow(ref.get()), BorderLayout.EAST); - installFocusable(panel, action, KeyEvent.VK_UP, KeyEvent.VK_DOWN, focusListOnLeft); + panel.add(link); + AccessibleContextUtil.setName(panel, link); + AccessibleContextUtil.setDescription(panel, link); + panel.add(createArrow(link), BorderLayout.EAST); return panel; } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java new file mode 100644 index 000000000000..107bc596e5fe --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/WelcomeBalloonLayoutImpl.java @@ -0,0 +1,248 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.wm.impl.welcomeScreen; + +import com.intellij.icons.AllIcons; +import com.intellij.notification.NotificationType; +import com.intellij.notification.impl.NotificationsManagerImpl; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.ui.*; +import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.util.Consumer; +import com.intellij.util.ParameterizedRunnable; +import com.intellij.util.ui.AbstractLayoutManager; +import com.intellij.util.ui.JBUI; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.intellij.notification.impl.NotificationsManagerImpl.BORDER_COLOR; +import static com.intellij.notification.impl.NotificationsManagerImpl.FILL_COLOR; + +/** + * @author Alexander Lobas + */ +public class WelcomeBalloonLayoutImpl extends BalloonLayoutImpl { + private static final String TYPE_KEY = "Type"; + + private final ParameterizedRunnable> myListener; + private final Computable myButtonLocation; + private BalloonImpl myPopupBalloon; + private final BalloonPanel myBalloonPanel = new BalloonPanel(); + private boolean myVisible; + private List myDisposableList = new ArrayList<>(); + + public WelcomeBalloonLayoutImpl(@NotNull JRootPane parent, + @NotNull Insets insets, + @NotNull ParameterizedRunnable> listener, + @NotNull Computable buttonLocation) { + super(parent, insets); + myListener = listener; + myButtonLocation = buttonLocation; + } + + public void dispose() { + if (myPopupBalloon != null) { + for (Disposable disposable : new ArrayList<>(myDisposableList)) { + Disposer.dispose(disposable); + } + Disposer.dispose(myPopupBalloon); + myPopupBalloon = null; + } + } + + @Override + public void add(@NotNull Balloon balloon, @Nullable Object layoutData) { + if (layoutData instanceof BalloonLayoutData && ((BalloonLayoutData)layoutData).welcomeScreen) { + addToPopup((BalloonImpl)balloon, (BalloonLayoutData)layoutData); + } + else { + super.add(balloon, layoutData); + } + } + + private void addToPopup(@NotNull BalloonImpl balloon, @NotNull BalloonLayoutData layoutData) { + layoutData.doLayout = this::layoutPopup; + layoutData.configuration = layoutData.configuration.replace(JBUI.scale(myPopupBalloon == null ? 7 : 5), JBUI.scale(12)); + + if (myPopupBalloon == null) { + final JScrollPane pane = NotificationsManagerImpl.createBalloonScrollPane(myBalloonPanel, true); + pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + pane.getVerticalScrollBar().addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + pane.setBorder(IdeBorderFactory.createEmptyBorder(SystemInfo.isMac ? 2 : 1, 0, 1, 1)); + } + + @Override + public void componentHidden(ComponentEvent e) { + pane.setBorder(IdeBorderFactory.createEmptyBorder()); + } + }); + + myPopupBalloon = + new BalloonImpl(pane, BORDER_COLOR, new Insets(0, 0, 0, 0), FILL_COLOR, true, false, false, false, true, 0, false, false, null, + false, 0, 0, 0, 0, false, null, null, false, false, false, null, false); + myPopupBalloon.setAnimationEnabled(false); + myPopupBalloon.setShadowBorderProvider( + new NotificationBalloonShadowBorderProvider(FILL_COLOR, BORDER_COLOR)); + myPopupBalloon.setHideListener(new Runnable() { + @Override + public void run() { + myPopupBalloon.getComponent().setVisible(false); + } + }); + myPopupBalloon.setActionProvider(new BalloonImpl.ActionProvider() { + private BalloonImpl.ActionButton myAction; + + @NotNull + @Override + public List createActions() { + myAction = myPopupBalloon.new ActionButton(AllIcons.Ide.Notification.Close, null, null, Consumer.EMPTY_CONSUMER); + return Collections.singletonList(myAction); + } + + @Override + public void layout(@NotNull Rectangle bounds) { + myAction.setBounds(0, 0, 0, 0); + } + }); + } + + myBalloonPanel.add(balloon.getContent()); + balloon.getContent().putClientProperty(TYPE_KEY, layoutData.type); + Disposable disposable = new Disposable() { + @Override + public void dispose() { + myDisposableList.remove(this); + myBalloonPanel.remove(balloon.getContent()); + updatePopup(); + } + }; + myDisposableList.add(disposable); + Disposer.register(balloon, disposable); + + updatePopup(); + } + + public void showPopup() { + layoutPopup(); + if (myVisible) { + myPopupBalloon.getComponent().setVisible(true); + } + else { + myPopupBalloon.show(myLayeredPane); + myVisible = true; + } + } + + private void layoutPopup() { + Dimension layeredSize = myLayeredPane.getSize(); + Dimension size = new Dimension(myPopupBalloon.getPreferredSize()); + int x = layeredSize.width - size.width - 5; + int fullHeight = myButtonLocation.compute().y; + + if (size.height > fullHeight) { + size.height = fullHeight; + } + + myPopupBalloon.setBounds(new Rectangle(x, fullHeight - size.height, size.width, size.height)); + } + + private void updatePopup() { + int count = myBalloonPanel.getComponentCount(); + List types = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + types.add((NotificationType)((JComponent)myBalloonPanel.getComponent(i)).getClientProperty(TYPE_KEY)); + } + myListener.run(types); + + if (myVisible) { + layoutPopup(); + } + } + + private static class BalloonPanel extends NonOpaquePanel { + public BalloonPanel() { + super(new AbstractLayoutManager() { + @Override + public Dimension preferredLayoutSize(Container parent) { + int count = parent.getComponentCount(); + int width = 0; + int height = 0; + for (int i = 0; i < count; i++) { + Dimension size = parent.getComponent(i).getPreferredSize(); + width = Math.max(width, size.width); + height += size.height; + } + height += count - 1; + return new Dimension(width + JBUI.scale(32), height); + } + + @Override + public void layoutContainer(Container parent) { + int count = parent.getComponentCount(); + int width = parent.getWidth() - JBUI.scale(32); + int height = parent.getHeight(); + if (count == 1) { + parent.getComponent(0).setBounds(JBUI.scale(16), 0, width, height); + } + else { + int y = 0; + for (int i = 0; i < count; i++) { + Component component = parent.getComponent(i); + Dimension size = component.getPreferredSize(); + component.setBounds(JBUI.scale(16), y, width, size.height); + y += size.height + JBUI.scale(2); + } + } + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + int count = getComponentCount() - 1; + if (count > 0) { + int x2 = getWidth() - JBUI.scale(16); + int y = 0; + + g.setColor(new JBColor(0xD0D0D0, 0x717375)); + + for (int i = 0; i < count; i++) { + Dimension size = getComponent(i).getPreferredSize(); + y += size.height + 1; + g.drawLine(JBUI.scale(16), y, x2, y); + } + } + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java b/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java index 09dc168aa116..627571db0ddd 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonImpl.java @@ -114,7 +114,12 @@ public class BalloonImpl implements Balloon, IdeTooltip.Ui { if (myHideOnMouse && id == MouseEvent.MOUSE_PRESSED) { if (!insideBalloon && !hasModalDialog(me) && !isWithinChildWindow(me)) { - hide(); + if (myHideListener == null) { + hide(); + } + else { + myHideListener.run(); + } } return; } @@ -153,8 +158,14 @@ public class BalloonImpl implements Balloon, IdeTooltip.Ui { } } - if (myHideOnKey && e instanceof KeyEvent && id == KeyEvent.KEY_PRESSED) { + if ((myHideOnKey || myHideListener != null) && e instanceof KeyEvent && id == KeyEvent.KEY_PRESSED) { final KeyEvent ke = (KeyEvent)e; + if (myHideListener != null) { + if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { + myHideListener.run(); + } + return; + } if (ke.getKeyCode() != KeyEvent.VK_SHIFT && ke.getKeyCode() != KeyEvent.VK_CONTROL && ke.getKeyCode() != KeyEvent.VK_ALT && @@ -266,6 +277,7 @@ public class BalloonImpl implements Balloon, IdeTooltip.Ui { private boolean myDisposed; private final JComponent myContent; private boolean myHideOnMouse; + private Runnable myHideListener; private final boolean myHideOnKey; private final boolean myHideOnAction; private final boolean myRequestFocus; @@ -666,6 +678,10 @@ public class BalloonImpl implements Balloon, IdeTooltip.Ui { } } + public JComponent getContent() { + return myContent; + } + public Component getComponent() { return myComp; } @@ -990,6 +1006,11 @@ public class BalloonImpl implements Balloon, IdeTooltip.Ui { myHideOnMouse = hideOnMouse; } + public void setHideListener(@NotNull Runnable listener) { + myHideListener = listener; + myHideOnMouse = true; + } + public void setShowPointer(final boolean show) { myShowPointer = show; } diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutConfiguration.java b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutConfiguration.java index 1b4223b5e2f3..68deb8a9bdca 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutConfiguration.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutConfiguration.java @@ -22,6 +22,7 @@ import com.intellij.openapi.util.SystemInfo; import com.intellij.util.ui.JBDimension; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; @@ -98,6 +99,12 @@ public class BalloonLayoutConfiguration { return twoLines(); } + @NotNull + public BalloonLayoutConfiguration replace(int topSpaceHeight, int bottomSpaceHeight) { + return new BalloonLayoutConfiguration(iconPanelWidth, iconOffset, topSpaceHeight, titleContentSpaceHeight, contentActionsSpaceHeight, + titleActionsSpaceHeight, bottomSpaceHeight, actionGap, null, 0, 0, 0); + } + @NotNull private static BalloonLayoutConfiguration twoLines() { return new BalloonLayoutConfiguration(new JBDimension(10, 11), @@ -130,7 +137,7 @@ public class BalloonLayoutConfiguration { int titleActionsSpaceHeight, int bottomSpaceHeight, int actionGap, - @NotNull Dimension rightActionsOffset, + @Nullable Dimension rightActionsOffset, int afterGearSpace, int beforeCloseSpace, int beforeGearSpace) { @@ -142,11 +149,20 @@ public class BalloonLayoutConfiguration { this.titleActionsSpaceHeight = titleActionsSpaceHeight; this.bottomSpaceHeight = bottomSpaceHeight; this.actionGap = actionGap; - this.rightActionsOffset = rightActionsOffset; - this.closeOffset = beforeCloseSpace + AllIcons.Ide.Notification.Close.getIconWidth() + rightActionsOffset.width; - this.gearCloseSpace = afterGearSpace + beforeCloseSpace; - this.allActionsOffset = closeOffset + afterGearSpace + AllIcons.Ide.Notification.Gear.getIconWidth(); - this.beforeGearSpace = beforeGearSpace; + if (rightActionsOffset == null) { + this.rightActionsOffset = new Dimension(); + this.closeOffset = 0; + this.gearCloseSpace = 0; + this.allActionsOffset = 0; + this.beforeGearSpace = 0; + } + else { + this.rightActionsOffset = rightActionsOffset; + this.closeOffset = beforeCloseSpace + AllIcons.Ide.Notification.Close.getIconWidth() + rightActionsOffset.width; + this.gearCloseSpace = afterGearSpace + beforeCloseSpace; + this.allActionsOffset = closeOffset + afterGearSpace + AllIcons.Ide.Notification.Gear.getIconWidth(); + this.beforeGearSpace = beforeGearSpace; + } } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java index 70995736060d..e1681e64e283 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java @@ -15,6 +15,7 @@ */ package com.intellij.ui; +import com.intellij.notification.NotificationType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import org.jetbrains.annotations.NotNull; @@ -29,6 +30,9 @@ public class BalloonLayoutData { public boolean showFullContent; + public boolean welcomeScreen; + public NotificationType type; + public int height; public int twoLineHeight; public int fullHeight;