2 * Copyright 2000-2011 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.
17 package com.intellij.notification;
19 import com.intellij.execution.filters.HyperlinkInfo;
20 import com.intellij.ide.actions.ContextHelpAction;
21 import com.intellij.notification.impl.NotificationsConfigurable;
22 import com.intellij.notification.impl.NotificationsConfigurationImpl;
23 import com.intellij.notification.impl.NotificationsManagerImpl;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.components.AbstractProjectComponent;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.actions.ScrollToTheEndToolbarAction;
29 import com.intellij.openapi.editor.actions.ToggleUseSoftWrapsToolbarAction;
30 import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
31 import com.intellij.openapi.options.ShowSettingsUtil;
32 import com.intellij.openapi.project.DumbAware;
33 import com.intellij.openapi.project.DumbAwareAction;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.project.ProjectManager;
36 import com.intellij.openapi.ui.SimpleToolWindowPanel;
37 import com.intellij.openapi.ui.popup.Balloon;
38 import com.intellij.openapi.util.*;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.openapi.wm.ToolWindow;
41 import com.intellij.openapi.wm.ToolWindowFactory;
42 import com.intellij.openapi.wm.ToolWindowManager;
43 import com.intellij.ui.awt.RelativePoint;
44 import com.intellij.ui.content.Content;
45 import com.intellij.ui.content.ContentFactory;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
50 import javax.swing.event.HyperlinkEvent;
51 import java.net.MalformedURLException;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.TreeSet;
56 import java.util.concurrent.CopyOnWriteArrayList;
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
63 public class EventLog implements Notifications {
64 public static final String LOG_REQUESTOR = "Internal log requestor";
65 public static final String LOG_TOOL_WINDOW_ID = "Event Log";
66 public static final String HELP_ID = "reference.toolwindows.event.log";
67 private final LogModel myModel = new LogModel(null, ApplicationManager.getApplication());
68 private static final String A_CLOSING = "</a>";
69 private static final Pattern TAG_PATTERN = Pattern.compile("<[^>]*>");
70 private static final Pattern A_PATTERN = Pattern.compile("<a ([^>]* )?href=[\"\']([^>]*)[\"\'][^>]*>");
73 ApplicationManager.getApplication().getMessageBus().connect().subscribe(Notifications.TOPIC, this);
77 public void notify(@NotNull Notification notification) {
78 final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
79 if (openProjects.length == 0) {
80 myModel.addNotification(notification);
82 for (Project p : openProjects) {
83 getProjectComponent(p).printNotification(notification);
87 public static void expire(@NotNull Notification notification) {
88 getApplicationComponent().myModel.removeNotification(notification);
89 for (Project p : ProjectManager.getInstance().getOpenProjects()) {
90 getProjectComponent(p).myProjectModel.removeNotification(notification);
94 private static EventLog getApplicationComponent() {
95 return ApplicationManager.getApplication().getComponent(EventLog.class);
99 public void register(@NotNull String groupDisplayName, @NotNull NotificationDisplayType defaultDisplayType) {
103 public void register(@NotNull String groupDisplayName,
104 @NotNull NotificationDisplayType defaultDisplayType,
109 public static LogModel getLogModel(@Nullable Project project) {
110 return project != null ? getProjectComponent(project).myProjectModel : getApplicationComponent().myModel;
114 public static Pair<Notification, Long> getStatusMessage(@Nullable Project project) {
115 return getLogModel(project).getStatusMessage();
118 public static LogEntry formatForLog(@NotNull final Notification notification) {
119 String content = notification.getContent();
120 String mainText = notification.getTitle();
121 boolean showMore = false;
122 if (StringUtil.isNotEmpty(content)) {
123 if (content.startsWith("<p>")) {
124 content = content.substring("<p>".length());
127 if (content.startsWith("<") && !content.startsWith("<a ")) {
130 if (StringUtil.isNotEmpty(mainText)) {
136 int nlIndex = eolIndex(mainText);
138 mainText = mainText.substring(0, nlIndex);
142 List<Pair<TextRange, HyperlinkInfo>> links = new ArrayList<Pair<TextRange, HyperlinkInfo>>();
146 Matcher tagMatcher = TAG_PATTERN.matcher(mainText);
147 if (!tagMatcher.find()) {
151 message += mainText.substring(0, tagMatcher.start());
152 Matcher aMatcher = A_PATTERN.matcher(tagMatcher.group());
153 if (aMatcher.matches()) {
154 final String href = aMatcher.group(2);
155 int linkEnd = mainText.indexOf(A_CLOSING, tagMatcher.end());
157 String linkText = mainText.substring(tagMatcher.end(), linkEnd).replaceAll(TAG_PATTERN.pattern(), "");
159 links.add(new Pair<TextRange, HyperlinkInfo>(TextRange.from(message.length(), linkText.length()), new NotificationHyperlinkInfo(notification, href)));
162 mainText = mainText.substring(linkEnd + A_CLOSING.length());
166 mainText = mainText.substring(tagMatcher.end());
169 message = StringUtil.unescapeXml(StringUtil.convertLineSeparators(message));
171 String status = message;
175 links.add(new Pair<TextRange, HyperlinkInfo>(TextRange.from(message.length() - 5, 4), new ShowBalloon(notification)));
178 return new LogEntry(message, status, links);
181 public static class LogEntry {
182 public final String message;
183 public final String status;
184 public final List<Pair<TextRange, HyperlinkInfo>> links;
186 public LogEntry(String message, String status, List<Pair<TextRange, HyperlinkInfo>> links) {
187 this.message = message;
188 this.status = status;
193 private static int eolIndex(String mainText) {
194 TreeSet<Integer> indices = new TreeSet<Integer>();
195 indices.add(mainText.indexOf("<br>", 1));
196 indices.add(mainText.indexOf("<br/>", 1));
197 indices.add(mainText.indexOf("<p/>", 1));
198 indices.add(mainText.indexOf("<p>", 1));
199 indices.add(mainText.indexOf("\n"));
201 return indices.isEmpty() ? -1 : indices.iterator().next();
204 public static boolean isEventLogVisible(Project project) {
205 final ToolWindow window = getEventLog(project);
206 return window != null && window.isVisible();
210 public static ToolWindow getEventLog(Project project) {
211 return project == null ? null : ToolWindowManager.getInstance(project).getToolWindow(LOG_TOOL_WINDOW_ID);
214 public static void toggleLog(final Project project) {
215 final ToolWindow eventLog = getEventLog(project);
216 if (eventLog != null) {
217 if (!eventLog.isVisible()) {
218 eventLog.activate(null, true);
219 getLogModel(project).logShown();
226 public static class ProjectTracker extends AbstractProjectComponent {
227 private volatile EventLogConsole myConsole;
228 private final List<Notification> myInitial = new CopyOnWriteArrayList<Notification>();
229 private final LogModel myProjectModel;
231 public ProjectTracker(@NotNull final Project project) {
234 myProjectModel = new LogModel(project, project);
236 for (Notification notification : getApplicationComponent().myModel.takeNotifications()) {
237 printNotification(notification);
240 project.getMessageBus().connect(project).subscribe(Notifications.TOPIC, new Notifications() {
242 public void notify(@NotNull Notification notification) {
243 printNotification(notification);
247 public void register(@NotNull String groupDisplayName, @NotNull NotificationDisplayType defaultDisplayType) {
251 public void register(@NotNull String groupDisplayName,
252 @NotNull NotificationDisplayType defaultDisplayType,
260 public void projectOpened() {
261 myConsole = new EventLogConsole(myProjectModel);
263 for (Notification notification : myInitial) {
264 printNotification(notification);
270 public void projectClosed() {
271 getApplicationComponent().myModel.setStatusMessage(null, 0);
274 private void printNotification(final Notification notification) {
275 final EventLogConsole console = myConsole;
276 if (console == null) {
277 myInitial.add(notification);
281 if (!NotificationsConfigurationImpl.getSettings(notification.getGroupId()).isShouldLog()) {
285 myProjectModel.addNotification(notification);
287 ApplicationManager.getApplication().invokeLater(new Runnable() {
290 if (!ShutDownTracker.isShutdownHookRunning() && !myProject.isDisposed()) {
291 console.doPrintNotification(notification);
299 private static ProjectTracker getProjectComponent(Project project) {
300 return project.getComponent(ProjectTracker.class);
302 public static class FactoryItself implements ToolWindowFactory, DumbAware {
303 public void createToolWindowContent(final Project project, ToolWindow toolWindow) {
304 final Editor editor = getProjectComponent(project).myConsole.getConsoleEditor();
306 SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true) {
308 public Object getData(@NonNls String dataId) {
309 return PlatformDataKeys.HELP_ID.is(dataId) ? HELP_ID : super.getData(dataId);
312 panel.setContent(editor.getComponent());
314 DefaultActionGroup group = new DefaultActionGroup();
315 group.add(new DumbAwareAction("Settings", "Edit notification settings", IconLoader.getIcon("/actions/showSettings.png")) {
317 public void actionPerformed(AnActionEvent e) {
318 ShowSettingsUtil.getInstance().editConfigurable(project, new NotificationsConfigurable());
321 group.add(new DisplayBalloons());
322 group.add(new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
324 protected Editor getEditor(AnActionEvent e) {
328 group.add(new ScrollToTheEndToolbarAction(editor));
329 group.add(new DumbAwareAction("Mark all as read", "Mark all unread notifications as read", IconLoader.getIcon("/general/reset.png")) {
331 public void update(AnActionEvent e) {
332 if (project.isDisposed()) return;
333 e.getPresentation().setEnabled(!getProjectComponent(project).myProjectModel.getNotifications().isEmpty());
337 public void actionPerformed(AnActionEvent e) {
338 LogModel model = getProjectComponent(project).myProjectModel;
339 for (Notification notification : model.getNotifications()) {
340 model.removeNotification(notification);
341 notification.expire();
345 group.add(new ContextHelpAction(HELP_ID));
347 ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, false);
348 toolbar.setTargetComponent(panel);
349 panel.setToolbar(toolbar.getComponent());
351 final Content content = ContentFactory.SERVICE.getInstance().createContent(panel, "", false);
352 toolWindow.getContentManager().addContent(content);
355 private static class DisplayBalloons extends ToggleAction implements DumbAware {
356 public DisplayBalloons() {
357 super("Show balloons", "Enable or suppress notification balloons", IconLoader.getIcon("/general/balloon.png"));
361 public boolean isSelected(AnActionEvent e) {
362 return NotificationsConfigurationImpl.getNotificationsConfigurationImpl().SHOW_BALLOONS;
366 public void setSelected(AnActionEvent e, boolean state) {
367 NotificationsConfigurationImpl.getNotificationsConfigurationImpl().SHOW_BALLOONS = state;
372 private static class NotificationHyperlinkInfo implements HyperlinkInfo {
373 private final Notification myNotification;
374 private final String myHref;
376 public NotificationHyperlinkInfo(Notification notification, String href) {
377 myNotification = notification;
382 public void navigate(Project project) {
383 NotificationListener listener = myNotification.getListener();
384 if (listener != null) {
385 EventLogConsole console = EventLog.getProjectComponent(project).myConsole;
388 url = new URL(null, myHref);
390 catch (MalformedURLException ignored) {
392 listener.hyperlinkUpdate(myNotification, new HyperlinkEvent(console.getConsoleEditor().getContentComponent(), HyperlinkEvent.EventType.ACTIVATED, url, myHref));
397 private static class ShowBalloon implements HyperlinkInfo {
398 private final Notification myNotification;
400 public ShowBalloon(Notification notification) {
401 myNotification = notification;
405 public void navigate(Project project) {
406 hideBalloon(myNotification);
408 for (Notification notification : getLogModel(project).getNotifications()) {
409 hideBalloon(notification);
412 RelativePoint target = EventLog.getProjectComponent(project).myConsole.getHyperlinkLocation(this);
413 if (target != null) {
414 Balloon balloon = NotificationsManagerImpl.createBalloon(myNotification, true, true);
415 Disposer.register(project, balloon);
416 balloon.show(target, Balloon.Position.above);
420 private static void hideBalloon(Notification notification1) {
421 Balloon balloon = notification1.getBalloon();
422 if (balloon != null) {