f7fa5cc529d8ff2f8e19005e7af319c9e1fe3482
[fedora-idea.git] / platform / platform-impl / src / com / intellij / notification / impl / ui / NotificationsListPanel.java
blobf7fa5cc529d8ff2f8e19005e7af319c9e1fe3482
1 /*
2 * Copyright 2000-2009 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.notification.impl.ui;
18 import com.intellij.notification.Notification;
19 import com.intellij.notification.NotificationType;
20 import com.intellij.notification.impl.NotificationModelListener;
21 import com.intellij.notification.impl.NotificationsManagerImpl;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.ui.popup.*;
26 import com.intellij.openapi.ui.popup.util.MinimizeButton;
27 import com.intellij.openapi.util.Disposer;
28 import com.intellij.openapi.wm.impl.content.GraphicsConfig;
29 import com.intellij.ui.components.panels.Wrapper;
30 import com.intellij.util.NotNullFunction;
31 import com.intellij.util.Processor;
32 import com.intellij.util.ui.UIUtil;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 import javax.swing.*;
37 import javax.swing.event.HyperlinkListener;
38 import javax.swing.event.ListDataListener;
39 import javax.swing.event.ListDataEvent;
40 import java.awt.*;
41 import java.awt.event.*;
42 import java.awt.geom.RoundRectangle2D;
43 import java.lang.ref.WeakReference;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.List;
47 import java.util.Enumeration;
49 /**
50 * @author spleaner
52 public class NotificationsListPanel extends JPanel implements NotificationModelListener, Disposable {
53 private static final Logger LOG = Logger.getInstance("#com.intellij.notification.impl.ui.NotificationsListPanel");
54 private static final String REMOVE_KEY = "REMOVE";
56 private WeakReference<JBPopup> myPopupRef;
58 private Project myProject;
59 private Wrapper myWrapper;
60 private JComponent myActiveComponent;
62 private JComponent myEmptyComponent;
63 private JComponent myListComponent;
65 public NotificationsListPanel(@Nullable final Project project) {
66 setLayout(new BorderLayout());
67 myProject = project;
69 myEmptyComponent = new JLabel("No new notifications.", JLabel.CENTER);
70 myListComponent = ItemsList.create(project, this);
72 myWrapper = new Wrapper();
73 myWrapper.setContent(getCurrentComponent(project));
75 add(myWrapper, BorderLayout.CENTER);
78 public void notificationsAdded(@NotNull Notification... notification) {
79 switchView(myProject);
82 public void notificationsRemoved(@NotNull Notification... notification) {
83 switchView(myProject);
86 public void notificationsRead(@NotNull Notification... notification) {
87 switchView(myProject);
90 @Override
91 public void addNotify() {
92 super.addNotify();
93 getManager().addListener(this);
96 @Override
97 public void removeNotify() {
98 getManager().removeListener(this);
99 super.removeNotify();
102 private JComponent getCurrentComponent(@Nullable final Project project) {
103 final boolean empty = getManager().count(project) == 0;
104 final JComponent component = empty ? myEmptyComponent : myListComponent;
105 if (myActiveComponent == component) return null;
107 myActiveComponent = component;
108 return myActiveComponent;
111 protected void switchView(@Nullable final Project project) {
112 final JComponent component = getCurrentComponent(project);
113 if (component != null) {
114 myWrapper.setContent(component);
115 myWrapper.revalidate();
116 myWrapper.repaint();
120 public void dispose() {
121 getManager().markRead();
123 myProject = null;
126 private static NotificationsManagerImpl getManager() {
127 return NotificationsManagerImpl.getNotificationsManagerImpl();
130 static Dimension getMinSize() {
131 final Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
132 size.width *= 0.1d;
133 size.height *= 0.1d;
134 return size;
137 public void clear() {
138 if (myPopupRef != null) {
139 final JBPopup jbPopup = myPopupRef.get();
140 if (jbPopup != null) {
141 jbPopup.cancel();
144 myPopupRef = null;
148 public JComponent getPreferredFocusedComponent() {
149 return myWrapper.getTargetComponent();
152 public static JBPopup show(@Nullable final Project project, @NotNull final JComponent parent) {
153 final NotificationsListPanel panel = new NotificationsListPanel(project);
154 final ComponentPopupBuilder builder =
155 JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel.getPreferredFocusedComponent());
156 final JBPopup popup = builder.setResizable(true).setMinSize(getMinSize()).setDimensionServiceKey(null, "NotificationsPopup", true)
157 .setCancelOnClickOutside(false).setBelongsToGlobalPopupStack(false).setCancelButton(new MinimizeButton("Hide")).setMovable(true)
158 .setRequestFocus(true).setTitle("Notifications").createPopup();
160 popup.addListener(new JBPopupListener.Adapter() {
161 @Override
162 public void onClosed(LightweightWindowEvent event) {
163 Disposer.dispose(panel);
167 popup.showInCenterOf(SwingUtilities.getRootPane(parent));
168 return popup;
171 private static class NotificationsListRenderer extends JComponent implements ListCellRenderer {
172 private JTextPane myText;
173 private boolean mySelected;
174 private boolean myHasFocus;
175 private JLabel myIconLabel;
176 private Processor<Cursor> myProc;
177 private boolean myWasRead;
179 private NotificationsListRenderer() {
180 setLayout(new BorderLayout());
181 setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));
183 setOpaque(false);
185 myIconLabel = new JLabel();
186 myIconLabel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
187 myIconLabel.setOpaque(false);
189 myText = new JTextPane() {
190 @Override
191 public void setCursor(Cursor cursor) {
192 super.setCursor(cursor);
193 onCursorChanged(cursor);
197 myText.setBorder(BorderFactory.createEmptyBorder(3, 0, 3, 0));
198 myText.setOpaque(false);
199 myText.setEditable(false);
200 myText.setEditorKit(UIUtil.getHTMLEditorKit());
202 final Wrapper.North comp = new Wrapper.North(myIconLabel);
203 comp.setOpaque(false);
204 add(comp, BorderLayout.WEST);
205 add(myText, BorderLayout.CENTER);
208 public JTextPane getText() {
209 return myText;
212 public void setCursorHandler(Processor<Cursor> proc) {
213 myProc = proc;
216 public void resetCursorHandler() {
217 myProc = null;
220 public void onCursorChanged(Cursor cursor) {
221 if (myProc != null) myProc.process(cursor);
224 @Override
225 protected void paintComponent(Graphics g) {
226 final Graphics2D g2d = (Graphics2D)g;
228 final Rectangle bounds = getBounds();
229 final Insets insets = getInsets();
231 final GraphicsConfig cfg = new GraphicsConfig(g);
232 cfg.setAntialiasing(true);
234 final Shape shape = new RoundRectangle2D.Double(insets.left, insets.top, bounds.width - 1 - insets.left - insets.right,
235 bounds.height - 1 - insets.top - insets.bottom, 6, 6);
237 if (mySelected) {
238 g2d.setColor(UIUtil.getListSelectionBackground());
239 g2d.fillRect(0, 0, bounds.width, bounds.height);
242 g2d.setColor(Color.WHITE);
243 g2d.fill(shape);
246 Color bgColor = getBackground();
247 if (myWasRead) {
248 bgColor = new Color(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue(), 60);
251 g2d.setColor(bgColor);
252 g2d.fill(shape);
254 g2d.setColor(myHasFocus || mySelected ? getBackground().darker().darker() : myWasRead ? getBackground() : getBackground().darker());
255 g2d.draw(shape);
256 cfg.restore();
258 super.paintComponent(g);
261 public Component getListCellRendererComponent(final JList list,
262 final Object value,
263 final int index,
264 final boolean isSelected,
265 final boolean cellHasFocus) {
266 LOG.assertTrue(value instanceof Notification);
267 final Notification notification = (Notification)value;
269 mySelected = isSelected;
270 myHasFocus = cellHasFocus;
272 myText.setText(NotificationsUtil.buildHtml(notification));
273 myIconLabel.setIcon(NotificationsUtil.getIcon(notification));
274 myWasRead = NotificationsManagerImpl.getNotificationsManagerImpl().wasRead(notification);
276 setBackground(NotificationsUtil.getBackground(notification));
278 return this;
282 private static class NotificationsListModel extends AbstractListModel implements NotificationModelListener, Disposable {
283 private List<Notification> myNotifications = new ArrayList<Notification>();
284 private NotificationType myType;
285 private Project myProject;
286 private NotNullFunction<Project, Collection<Notification>> myRebuildFunction;
287 private boolean myArchive;
289 private NotificationsListModel(@Nullable Project project) {
290 myProject = project;
292 myRebuildFunction = new NotNullFunction<Project, Collection<Notification>>() {
293 @NotNull
294 public Collection<Notification> fun(Project project) {
295 return getManager().getByType(myType, project);
299 getManager().addListener(this);
300 rebuildList();
303 public void dispose() {
304 getManager().removeListener(this);
305 myProject = null;
308 public int getSize() {
309 return myNotifications.size();
312 public Object getElementAt(final int index) {
313 return myNotifications.get(index);
316 private void rebuildList() {
317 myNotifications.clear();
318 myNotifications.addAll(myRebuildFunction.fun(myProject));
319 fireContentsChanged(this, 0, myNotifications.size() - 1);
322 public void filter(final NotificationType type) {
323 myType = type;
324 rebuildList();
327 public void notificationsAdded(@NotNull Notification... notification) {
328 rebuildList();
331 public void notificationsRemoved(@NotNull Notification... notification) {
332 rebuildList();
335 public void notificationsRead(@NotNull Notification... notification) {
336 rebuildList();
340 private static class ItemsList extends JList {
341 private ItemsList(final NotificationsListModel model) {
342 super(model);
343 setOpaque(false);
345 setCellRenderer(new NotificationsListRenderer());
346 getSelectionModel().setSelectionInterval(0, 0);
348 getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), REMOVE_KEY);
349 getActionMap().put(REMOVE_KEY, new AbstractAction() {
350 public void actionPerformed(final ActionEvent e) {
351 removeSelected();
355 setBackground(UIUtil.getPanelBackgound());
357 addMouseMotionListener(new MouseMotionListener() {
358 public void mouseMoved(MouseEvent e) {
359 processMouse(e, false);
362 public void mouseDragged(MouseEvent e) {
366 addMouseListener(new MouseAdapter() {
367 @Override
368 public void mouseClicked(final MouseEvent e) {
369 if (!e.isPopupTrigger()) {
370 processMouse(e, true);
376 private void processMouse(final MouseEvent e, final boolean click) {
377 final int index = locationToIndex(e.getPoint());
378 if (index > -1) {
379 final Object value = getModel().getElementAt(index);
380 if (value != null && value instanceof Notification) {
381 final Notification notification = (Notification)value;
382 final Component renderer = getCellRenderer().getListCellRendererComponent(this, value, index, false, false);
383 if (renderer instanceof NotificationsListRenderer) {
384 final Rectangle bounds = getCellBounds(index, index);
385 renderer.setBounds(bounds);
386 renderer.doLayout();
388 final JTextPane text = ((NotificationsListRenderer)renderer).getText();
390 Processor<Cursor> processor;
391 HyperlinkListener listener = null;
392 if (click) {
393 listener = NotificationsUtil.wrapListener(notification);
394 if (listener != null) text.addHyperlinkListener(listener);
396 else {
397 processor = new Processor<Cursor>() {
398 public boolean process(Cursor cursor) {
399 ItemsList.this.setCursor(cursor);
400 return true;
404 ((NotificationsListRenderer)renderer).setCursorHandler(processor);
407 final Point point = e.getPoint();
408 point.translate(-bounds.x, -bounds.y);
410 final Rectangle r = text.getBounds();
411 point.translate(-r.x, -r.y);
413 final MouseEvent newEvent =
414 new MouseEvent(text, e.getID(), e.getWhen(), e.getModifiers(), point.x, point.y, e.getClickCount(), e.isPopupTrigger(),
415 e.getButton());
417 text.dispatchEvent(newEvent);
419 ((NotificationsListRenderer)renderer).resetCursorHandler();
420 if (listener != null) {
421 text.removeHyperlinkListener(listener);
428 @SuppressWarnings({"ConstantConditions"})
429 @Override
430 public NotificationsListModel getModel() {
431 final ListModel listModel = super.getModel();
432 return listModel instanceof NotificationsListModel ? (NotificationsListModel)listModel : null;
435 public void removeSelected() {
436 final ListSelectionModel model = getSelectionModel();
437 final NotificationsListModel listModel = getModel();
438 if (!model.isSelectionEmpty()) {
439 final int min = model.getMinSelectionIndex();
440 final int max = model.getMaxSelectionIndex();
442 final List<Notification> tbr = new ArrayList<Notification>();
443 for (int i = min; i <= max; i++) {
444 if (model.isSelectedIndex(i)) {
445 final Notification notification = (Notification)listModel.getElementAt(i);
446 if (notification != null) {
447 tbr.add(notification);
452 if (tbr.size() > 0) {
453 getManager().remove(tbr.toArray(new Notification[tbr.size()]));
455 final int toSelect = Math.min(min, listModel.getSize() - 1);
456 model.clearSelection();
457 if (toSelect >= 0) {
458 model.setSelectionInterval(toSelect, toSelect);
459 scrollRectToVisible(getCellBounds(toSelect, toSelect));
464 revalidate();
465 repaint();
468 private static void createFilterButton(final JPanel parent,
469 final ButtonGroup group,
470 final String title,
471 final ActionListener listener,
472 final NotNullFunction<MyButton, String> titleCallback,
473 final char mnemonic,
474 final boolean active) {
475 final StickyButton b = new MyButton(title, listener) {
476 @Override
477 public void updateTitle() {
478 setText(titleCallback.fun(this));
482 parent.add(b);
483 group.add(b);
485 b.setFocusable(false);
486 b.setSelected(active);
487 b.setMnemonic(mnemonic);
490 private static void updateButtons(@NotNull final JComponent filterBar) {
491 final Component[] components = filterBar.getComponents();
492 for (final Component c : components) {
493 if (c instanceof MyButton) {
494 ((MyButton)c).updateTitle();
499 private static JComponent buildFilterBar(final ItemsList list, final Project project) {
500 final JPanel box = new JPanel();
501 box.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
502 box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));
504 final ButtonGroup buttonGroup = new ButtonGroup();
506 createFilterButton(box, buttonGroup, "All", new ActionListener() {
507 public void actionPerformed(final ActionEvent e) {
508 list.filter(null);
510 }, new NotNullFunction<MyButton, String>() {
511 @NotNull
512 public String fun(MyButton myButton) {
513 final int i = count(null, project);
514 if (i > 0) {
515 return String.format("All (%s)", i);
518 return "All";
520 }, 'A', true);
522 createFilterButton(box, buttonGroup, "Error", new ActionListener() {
523 public void actionPerformed(final ActionEvent e) {
524 list.filter(NotificationType.ERROR);
526 }, new NotNullFunction<MyButton, String>() {
527 @NotNull
528 public String fun(MyButton myButton) {
529 final int i = count(NotificationType.ERROR, project);
530 myButton.setVisible(i > 0);
531 if (i > 0) {
532 return String.format("Error (%s)", i);
533 } else if (myButton.isSelected()) {
534 switchToAll(buttonGroup);
537 return "Error";
539 }, 'E', false);
541 createFilterButton(box, buttonGroup, "Warning", new ActionListener() {
542 public void actionPerformed(final ActionEvent e) {
543 list.filter(NotificationType.WARNING);
545 }, new NotNullFunction<MyButton, String>() {
546 @NotNull
547 public String fun(MyButton myButton) {
548 final int i = count(NotificationType.WARNING, project);
549 myButton.setVisible(i > 0);
550 if (i > 0) {
551 return String.format("Warning (%s)", i);
552 } else if (myButton.isSelected()) {
553 switchToAll(buttonGroup);
556 return "Warning";
558 }, 'W', false);
560 createFilterButton(box, buttonGroup, "Information", new ActionListener() {
561 public void actionPerformed(final ActionEvent e) {
562 list.filter(NotificationType.INFORMATION);
564 }, new NotNullFunction<MyButton, String>() {
565 @NotNull
566 public String fun(MyButton myButton) {
567 final int i = count(NotificationType.INFORMATION, project);
568 myButton.setVisible(i > 0);
569 if (i > 0) {
570 return String.format("Information (%s)", i);
571 } else if (myButton.isSelected()) {
572 switchToAll(buttonGroup);
575 return "Information";
577 }, 'I', false);
579 return box;
582 private static void switchToAll(final ButtonGroup buttonGroup) {
583 final Enumeration<AbstractButton> enumeration = buttonGroup.getElements();
584 while (enumeration.hasMoreElements()) {
585 final AbstractButton button = enumeration.nextElement();
586 if (button.getText().startsWith("All")) {
587 button.doClick();
588 return;
593 private static int count(@Nullable final NotificationType type, @Nullable final Project project) {
594 return NotificationsManagerImpl.getNotificationsManagerImpl().getByType(type, project).size();
597 private void filter(@Nullable final NotificationType type) {
598 final NotificationsListModel listModel = getModel();
599 listModel.filter(type);
600 if (listModel.getSize() > 0) setSelectedIndex(0);
603 public static JComponent create(final Project project, final Disposable parentDisposable) {
604 final NotificationsListModel model = new NotificationsListModel(project);
605 Disposer.register(parentDisposable, model);
607 // TODO: switch filter if removed all of the notifications from current one!
609 final ItemsList list = new ItemsList(model);
610 final JScrollPane scrollPane = new JScrollPane(list);
611 scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
612 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
614 scrollPane.setBorder(null);
615 scrollPane.getViewport().setBackground(UIUtil.getPanelBackgound());
617 final JComponent buttonBar = buildFilterBar(list, project);
618 model.addListDataListener(new ListDataListener() {
619 public void intervalAdded(ListDataEvent e) {
620 updateButtons(buttonBar);
623 public void intervalRemoved(ListDataEvent e) {
624 updateButtons(buttonBar);
627 public void contentsChanged(ListDataEvent e) {
628 updateButtons(buttonBar);
632 final JPanel panel = new JPanel(new BorderLayout()) {
633 @Override
634 public void requestFocus() {
635 updateButtons(buttonBar);
636 list.requestFocus();
640 panel.add(buttonBar, BorderLayout.NORTH);
641 panel.add(scrollPane, BorderLayout.CENTER);
643 return panel;
647 private abstract static class MyButton extends StickyButton {
648 private MyButton(String text, ActionListener listener) {
649 super(text, listener);
652 private MyButton(String text) {
653 super(text);
656 public abstract void updateTitle();