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 mainText = StringUtil.replace(mainText, " ", " ");
137 int nlIndex = eolIndex(mainText);
139 mainText = mainText.substring(0, nlIndex);
143 List<Pair<TextRange, HyperlinkInfo>> links = new ArrayList<Pair<TextRange, HyperlinkInfo>>();
147 Matcher tagMatcher = TAG_PATTERN.matcher(mainText);
148 if (!tagMatcher.find()) {
152 message += mainText.substring(0, tagMatcher.start());
153 Matcher aMatcher = A_PATTERN.matcher(tagMatcher.group());
154 if (aMatcher.matches()) {
155 final String href = aMatcher.group(2);
156 int linkEnd = mainText.indexOf(A_CLOSING, tagMatcher.end());
158 String linkText = mainText.substring(tagMatcher.end(), linkEnd).replaceAll(TAG_PATTERN.pattern(), "");
160 links.add(new Pair<TextRange, HyperlinkInfo>(TextRange.from(message.length(), linkText.length()), new NotificationHyperlinkInfo(notification, href)));
163 mainText = mainText.substring(linkEnd + A_CLOSING.length());
167 mainText = mainText.substring(tagMatcher.end());
170 message = StringUtil.unescapeXml(StringUtil.convertLineSeparators(message));
172 String status = message;
176 links.add(new Pair<TextRange, HyperlinkInfo>(TextRange.from(message.length() - 5, 4), new ShowBalloon(notification)));
179 return new LogEntry(message, status, links);
182 public static class LogEntry {
183 public final String message;
184 public final String status;
185 public final List<Pair<TextRange, HyperlinkInfo>> links;
187 public LogEntry(String message, String status, List<Pair<TextRange, HyperlinkInfo>> links) {
188 this.message = message;
189 this.status = status;
194 private static int eolIndex(String mainText) {
195 TreeSet<Integer> indices = new TreeSet<Integer>();
196 indices.add(mainText.indexOf("<br>", 1));
197 indices.add(mainText.indexOf("<br/>", 1));
198 indices.add(mainText.indexOf("<p/>", 1));
199 indices.add(mainText.indexOf("<p>", 1));
200 indices.add(mainText.indexOf("\n"));
202 return indices.isEmpty() ? -1 : indices.iterator().next();
205 public static boolean isEventLogVisible(Project project) {
206 final ToolWindow window = getEventLog(project);
207 return window != null && window.isVisible();
211 public static ToolWindow getEventLog(Project project) {
212 return project == null ? null : ToolWindowManager.getInstance(project).getToolWindow(LOG_TOOL_WINDOW_ID);
215 public static void toggleLog(final Project project) {
216 final ToolWindow eventLog = getEventLog(project);
217 if (eventLog != null) {
218 if (!eventLog.isVisible()) {
219 eventLog.activate(null, true);
220 getLogModel(project).logShown();
227 public static class ProjectTracker extends AbstractProjectComponent {
228 private volatile EventLogConsole myConsole;
229 private final List<Notification> myInitial = new CopyOnWriteArrayList<Notification>();
230 private final LogModel myProjectModel;
232 public ProjectTracker(@NotNull final Project project) {
235 myProjectModel = new LogModel(project, project);
237 for (Notification notification : getApplicationComponent().myModel.takeNotifications()) {
238 printNotification(notification);
241 project.getMessageBus().connect(project).subscribe(Notifications.TOPIC, new Notifications() {
243 public void notify(@NotNull Notification notification) {
244 printNotification(notification);
248 public void register(@NotNull String groupDisplayName, @NotNull NotificationDisplayType defaultDisplayType) {
252 public void register(@NotNull String groupDisplayName,
253 @NotNull NotificationDisplayType defaultDisplayType,
261 public void projectOpened() {
262 myConsole = new EventLogConsole(myProjectModel);
264 for (Notification notification : myInitial) {
265 printNotification(notification);
271 public void projectClosed() {
272 getApplicationComponent().myModel.setStatusMessage(null, 0);
275 private void printNotification(final Notification notification) {
276 final EventLogConsole console = myConsole;
277 if (console == null) {
278 myInitial.add(notification);
282 if (!NotificationsConfigurationImpl.getSettings(notification.getGroupId()).isShouldLog()) {
286 myProjectModel.addNotification(notification);
288 ApplicationManager.getApplication().invokeLater(new Runnable() {
291 if (!ShutDownTracker.isShutdownHookRunning() && !myProject.isDisposed()) {
292 console.doPrintNotification(notification);
300 private static ProjectTracker getProjectComponent(Project project) {
301 return project.getComponent(ProjectTracker.class);
303 public static class FactoryItself implements ToolWindowFactory, DumbAware {
304 public void createToolWindowContent(final Project project, ToolWindow toolWindow) {
305 final Editor editor = getProjectComponent(project).myConsole.getConsoleEditor();
307 SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true) {
309 public Object getData(@NonNls String dataId) {
310 return PlatformDataKeys.HELP_ID.is(dataId) ? HELP_ID : super.getData(dataId);
313 panel.setContent(editor.getComponent());
315 DefaultActionGroup group = new DefaultActionGroup();
316 group.add(new DumbAwareAction("Settings", "Edit notification settings", IconLoader.getIcon("/actions/showSettings.png")) {
318 public void actionPerformed(AnActionEvent e) {
319 ShowSettingsUtil.getInstance().editConfigurable(project, new NotificationsConfigurable());
322 group.add(new DisplayBalloons());
323 group.add(new ToggleUseSoftWrapsToolbarAction(SoftWrapAppliancePlaces.CONSOLE) {
325 protected Editor getEditor(AnActionEvent e) {
329 group.add(new ScrollToTheEndToolbarAction(editor));
330 group.add(new DumbAwareAction("Mark all as read", "Mark all unread notifications as read", IconLoader.getIcon("/general/reset.png")) {
332 public void update(AnActionEvent e) {
333 if (project.isDisposed()) return;
334 e.getPresentation().setEnabled(!getProjectComponent(project).myProjectModel.getNotifications().isEmpty());
338 public void actionPerformed(AnActionEvent e) {
339 LogModel model = getProjectComponent(project).myProjectModel;
340 for (Notification notification : model.getNotifications()) {
341 model.removeNotification(notification);
342 notification.expire();
346 group.add(new ContextHelpAction(HELP_ID));
348 ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, false);
349 toolbar.setTargetComponent(panel);
350 panel.setToolbar(toolbar.getComponent());
352 final Content content = ContentFactory.SERVICE.getInstance().createContent(panel, "", false);
353 toolWindow.getContentManager().addContent(content);
356 private static class DisplayBalloons extends ToggleAction implements DumbAware {
357 public DisplayBalloons() {
358 super("Show balloons", "Enable or suppress notification balloons", IconLoader.getIcon("/general/balloon.png"));
362 public boolean isSelected(AnActionEvent e) {
363 return NotificationsConfigurationImpl.getNotificationsConfigurationImpl().SHOW_BALLOONS;
367 public void setSelected(AnActionEvent e, boolean state) {
368 NotificationsConfigurationImpl.getNotificationsConfigurationImpl().SHOW_BALLOONS = state;
373 private static class NotificationHyperlinkInfo implements HyperlinkInfo {
374 private final Notification myNotification;
375 private final String myHref;
377 public NotificationHyperlinkInfo(Notification notification, String href) {
378 myNotification = notification;
383 public void navigate(Project project) {
384 NotificationListener listener = myNotification.getListener();
385 if (listener != null) {
386 EventLogConsole console = EventLog.getProjectComponent(project).myConsole;
389 url = new URL(null, myHref);
391 catch (MalformedURLException ignored) {
393 listener.hyperlinkUpdate(myNotification, new HyperlinkEvent(console.getConsoleEditor().getContentComponent(), HyperlinkEvent.EventType.ACTIVATED, url, myHref));
398 private static class ShowBalloon implements HyperlinkInfo {
399 private final Notification myNotification;
401 public ShowBalloon(Notification notification) {
402 myNotification = notification;
406 public void navigate(Project project) {
407 hideBalloon(myNotification);
409 for (Notification notification : getLogModel(project).getNotifications()) {
410 hideBalloon(notification);
413 RelativePoint target = EventLog.getProjectComponent(project).myConsole.getHyperlinkLocation(this);
414 if (target != null) {
415 Balloon balloon = NotificationsManagerImpl.createBalloon(myNotification, true, true);
416 Disposer.register(project, balloon);
417 balloon.show(target, Balloon.Position.above);
421 private static void hideBalloon(Notification notification1) {
422 Balloon balloon = notification1.getBalloon();
423 if (balloon != null) {