diff --git a/platform/platform-api/src/com/intellij/ui/components/labels/LinkLabel.java b/platform/platform-api/src/com/intellij/ui/components/labels/LinkLabel.java index 7e8341c184ca..4c8785c7f029 100644 --- a/platform/platform-api/src/com/intellij/ui/components/labels/LinkLabel.java +++ b/platform/platform-api/src/com/intellij/ui/components/labels/LinkLabel.java @@ -188,7 +188,7 @@ public class LinkLabel extends JLabel { private final JBRectangle textR = new JBRectangle(); private final JBRectangle viewR = new JBRectangle(); - private boolean isInClickableArea(Point pt) { + protected boolean isInClickableArea(Point pt) { iconR.clear(); textR.clear(); final Insets insets = getInsets(null); diff --git a/platform/platform-impl/src/com/intellij/notification/EventLog.java b/platform/platform-impl/src/com/intellij/notification/EventLog.java index 892c88868c7d..1266efb86f53 100644 --- a/platform/platform-impl/src/com/intellij/notification/EventLog.java +++ b/platform/platform-impl/src/com/intellij/notification/EventLog.java @@ -34,7 +34,6 @@ import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.*; -import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.ui.BalloonLayoutData; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.content.Content; @@ -49,9 +48,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; -import java.awt.*; import java.util.*; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -93,14 +90,8 @@ public class EventLog { } } - public static void showNotification(@NotNull String groupId, @NotNull String id) { - Window frame = IdeFrameImpl.getActiveFrame(); - if (frame instanceof IdeFrame) { - Project project = ((IdeFrame)frame).getProject(); - if (project != null) { - getProjectComponent(project).showNotification(groupId, id); - } - } + public static void showNotification(@NotNull Project project, @NotNull String groupId, @NotNull String id) { + getProjectComponent(project).showNotification(groupId, id); } private static EventLog getApplicationComponent() { @@ -478,7 +469,7 @@ public class EventLog { }); } - void showNotification(@NotNull final String groupId, @NotNull final String id) { + private void showNotification(@NotNull final String groupId, @NotNull final String id) { ToolWindow eventLog = getEventLog(myProject); if (eventLog != null) { activate(eventLog, groupId, new Runnable() { diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationGroupBean.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationGroupBean.java index d80e0ec006be..17eda4fcdb43 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationGroupBean.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationGroupBean.java @@ -30,4 +30,7 @@ public class NotificationGroupBean extends AbstractExtensionPointBean { @Attribute("replaceTitle") public String replaceTitle; + + @Attribute("shortTitle") + public String shortTitle; } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/notification/impl/NotificationParentGroup.java b/platform/platform-impl/src/com/intellij/notification/impl/NotificationParentGroup.java index 2301db0ed102..ca7ea37e7964 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationParentGroup.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationParentGroup.java @@ -35,6 +35,7 @@ public class NotificationParentGroup { private static Map> myChildren; private static Map myGroupToParent; private static Map myReplaceTitles; + private static Map myShortTitles; private static void prepareInfo() { if (myParents == null) { @@ -60,6 +61,7 @@ public class NotificationParentGroup { myGroupToParent = new HashMap<>(); myReplaceTitles = new HashMap<>(); + myShortTitles = new HashMap<>(); for (NotificationGroupBean bean : EP_CHILD_NAME.getExtensions()) { NotificationParentGroupBean parent = myParents.get(bean.parentId); if (parent != null) { @@ -68,6 +70,9 @@ public class NotificationParentGroup { if (bean.replaceTitle != null) { myReplaceTitles.put(bean.groupId, bean.replaceTitle); } + if (bean.shortTitle != null) { + myShortTitles.put(bean.groupId, bean.shortTitle); + } } } } @@ -79,6 +84,12 @@ public class NotificationParentGroup { return myReplaceTitles.get(groupId); } + @Nullable + public static String getShortTitle(@NotNull String groupId) { + prepareInfo(); + return myShortTitles.get(groupId); + } + @NotNull public static List getChildren(@NotNull NotificationParentGroupBean parent) { prepareInfo(); 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 3051d3482ba4..705c99265d2e 100644 --- a/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/notification/impl/NotificationsManagerImpl.java @@ -50,8 +50,10 @@ import com.intellij.ui.components.labels.LinkListener; import com.intellij.ui.components.panels.HorizontalLayout; import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.util.ArrayUtil; +import com.intellij.util.FontUtil; import com.intellij.util.Function; import com.intellij.util.IconUtil; +import com.intellij.util.ui.AbstractLayoutManager; import com.intellij.util.ui.ButtonlessScrollBarUI; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; @@ -68,6 +70,7 @@ import javax.swing.text.BadLocationException; import java.awt.*; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; +import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -249,7 +252,7 @@ public class NotificationsManagerImpl extends NotificationsManager { layoutDataRef.set(layoutData); } else { - List mergeData = ((BalloonLayoutImpl)layout).preMerge(notification); + BalloonLayoutData.MergeInfo mergeData = ((BalloonLayoutImpl)layout).preMerge(notification); if (mergeData != null) { BalloonLayoutData layoutData = new BalloonLayoutData(); layoutData.mergeData = mergeData; @@ -494,7 +497,6 @@ public class NotificationsManagerImpl extends NotificationsManager { if (NotificationsConfigurationImpl.getSettings(notification.getGroupId()).isShouldLog()) { layoutData.groupId = notification.getGroupId(); layoutData.id = notification.id; - layoutData.status = EventLog.formatForLog(notification, "").status; } } else { @@ -502,7 +504,7 @@ public class NotificationsManagerImpl extends NotificationsManager { } layoutDataRef.set(layoutData); - boolean actions = !notification.getActions().isEmpty() || layoutData.mergeData != null; + boolean actions = !notification.getActions().isEmpty(); boolean showFullContent = layoutData.showFullContent || notification instanceof NotificationActionProvider; Color foregroundR = Gray._0; @@ -732,7 +734,11 @@ public class NotificationsManagerImpl extends NotificationsManager { } if (buttons == null && actions) { - createActionPanel(notification, layoutData.mergeData, centerPanel, layoutData.configuration.actionGap); + createActionPanel(notification, centerPanel, layoutData.configuration.actionGap); + } + + if (layoutData.mergeData != null) { + createMergeAction(layoutData, content); } text.setSize(text.getPreferredSize()); @@ -775,10 +781,7 @@ public class NotificationsManagerImpl extends NotificationsManager { return balloon; } - private static void createActionPanel(@NotNull final Notification notification, - @Nullable final List mergeData, - @NotNull JPanel centerPanel, - int gap) { + 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); @@ -815,45 +818,61 @@ public class NotificationsManagerImpl extends NotificationsManager { } }, action)); } + } - if (mergeData != null) { - final int size = mergeData.size(); - if (size == 1) { - actionPanel.add( - HorizontalLayout.RIGHT, - new LinkLabel("History", null, new LinkListener() { - @Override - public void linkSelected(LinkLabel aSource, BalloonLayoutData.MergeInfo info) { - EventLog.showNotification(info.groupId, info.id); - } - }, mergeData.get(0)) { - @Override - protected String getStatusBarText() { - return getLinkData().status; - } - }); - } - else { - actionPanel.add( - HorizontalLayout.RIGHT, - new DropDownAction("History", new LinkListener() { - @Override - public void linkSelected(LinkLabel link, Void aLinkData) { - DefaultActionGroup group = new DefaultActionGroup(); - for (int i = 0; i < size; i++) { - BalloonLayoutData.MergeInfo info = mergeData.get(i); - group.add(new AnAction(String.valueOf(i + 1), info.status, null) { - @Override - public void actionPerformed(AnActionEvent e) { - EventLog.showNotification(info.groupId, info.id); - } - }); - } - showPopup(link, group); - } - })); - } + private static void createMergeAction(@NotNull final BalloonLayoutData layoutData, @NotNull JPanel panel) { + StringBuilder title = new StringBuilder().append(layoutData.mergeData.count).append(" more"); + String shortTitle = NotificationParentGroup.getShortTitle(layoutData.groupId); + if (shortTitle != null) { + title.append(" from ").append(shortTitle); } + + LinkLabel action = new LinkLabel( + title.toString(), null, + new LinkListener() { + @Override + public void linkSelected(LinkLabel aSource, BalloonLayoutData layoutData) { + EventLog.showNotification(layoutData.project, layoutData.groupId, layoutData.mergeData.linkId); + } + }, layoutData) { + @Override + protected boolean isInClickableArea(Point pt) { + return true; + } + + @Override + protected Color getTextColor() { + return new JBColor(0x666666, 0x8C8C8C); + } + }; + + action.setFont(FontUtil.minusOne(action.getFont())); + action.setHorizontalAlignment(SwingConstants.CENTER); + action.setPaintUnderline(false); + + AbstractLayoutManager layout = new AbstractLayoutManager() { + @Override + public Dimension preferredLayoutSize(Container parent) { + return new Dimension(parent.getWidth(), JBUI.scale(20) + 2); + } + + @Override + public void layoutContainer(Container parent) { + parent.getComponent(0).setBounds(2, 1, parent.getWidth() - 4, JBUI.scale(20)); + } + }; + JPanel mergePanel = new NonOpaquePanel(layout) { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.setColor(new JBColor(0xE3E3E3, 0x3A3C3D)); + ((Graphics2D)g).fill(new Rectangle2D.Double(1.5, 1, getWidth() - 2.5, getHeight() - 2)); + g.setColor(new JBColor(0xDBDBDB, 0x353738)); + ((Graphics2D)g).draw(new Rectangle2D.Double(2, 0, getWidth() - 3.5, 0.5)); + } + }; + mergePanel.add(action); + panel.add(BorderLayout.SOUTH, mergePanel); } public static int calculateContentHeight(int lines) { diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java index 1dcfce83554b..70995736060d 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutData.java @@ -17,8 +17,7 @@ package com.intellij.ui; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; - -import java.util.List; +import org.jetbrains.annotations.NotNull; /** * @author Alexander Lobas @@ -26,8 +25,7 @@ import java.util.List; public class BalloonLayoutData { public String groupId; public String id; - public String status; - public List mergeData; + public MergeInfo mergeData; public boolean showFullContent; @@ -50,17 +48,18 @@ public class BalloonLayoutData { public Runnable lafHandler; - public MergeInfo getMergeInfo() { - MergeInfo info = new MergeInfo(); - info.groupId = groupId; - info.id = id; - info.status = status; - return info; + @NotNull + public MergeInfo merge() { + return mergeData == null ? new MergeInfo(id, 1) : new MergeInfo(mergeData.linkId, mergeData.count + 1); } public static class MergeInfo { - public String groupId; - public String id; - public String status; + public String linkId; + public int count; + + public MergeInfo(@NotNull String linkId, int count) { + this.linkId = linkId; + this.count = count; + } } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutImpl.java b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutImpl.java index d2665133f457..36cffac4a717 100644 --- a/platform/platform-impl/src/com/intellij/ui/BalloonLayoutImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/BalloonLayoutImpl.java @@ -109,6 +109,7 @@ public class BalloonLayoutImpl implements BalloonLayout { Balloon merge = merge(layoutData); if (merge == null) { if (NotificationsManagerImpl.newEnabled() && + getVisibleCount() > 0 && layoutData instanceof BalloonLayoutData && ((BalloonLayoutData)layoutData).groupId != null) { int index = -1; @@ -123,7 +124,7 @@ public class BalloonLayoutImpl implements BalloonLayout { } } - if (count == getVisibleCount()) { + if (count > 0 && count == getVisibleCount()) { remove(myBalloons.get(index)); } } @@ -186,17 +187,12 @@ public class BalloonLayoutImpl implements BalloonLayout { } @Nullable - public List preMerge(@NotNull Notification notification) { + public BalloonLayoutData.MergeInfo preMerge(@NotNull Notification notification) { Balloon balloon = merge(notification.getGroupId()); if (balloon != null) { BalloonLayoutData layoutData = myLayoutData.get(balloon); if (layoutData != null) { - List mergeData = new ArrayList<>(); - if (layoutData.mergeData != null) { - mergeData.addAll(layoutData.mergeData); - } - mergeData.add(layoutData.getMergeInfo()); - return mergeData; + return layoutData.merge(); } } return null; @@ -218,9 +214,8 @@ public class BalloonLayoutImpl implements BalloonLayout { myBalloons.remove(balloon); BalloonLayoutData layoutData = myLayoutData.remove(balloon); if (layoutData != null) { - layoutData.id = null; layoutData.groupId = null; - layoutData.status = null; + layoutData.id = null; layoutData.mergeData = null; } if (hide) { diff --git a/platform/util/src/com/intellij/util/FontUtil.java b/platform/util/src/com/intellij/util/FontUtil.java index 4e4fb9c4cd16..8419be2c9944 100644 --- a/platform/util/src/com/intellij/util/FontUtil.java +++ b/platform/util/src/com/intellij/util/FontUtil.java @@ -40,4 +40,9 @@ public class FontUtil { public static String spaceAndThinSpace() { return " " + canDisplay(UIUtil.getLabelFont(), '\u2009', " "); } + + @NotNull + public static Font minusOne(@NotNull Font font) { + return font.deriveFont(font.getSize() - 1f); + } }