Wednesday, December 16, 2009

Notification Framework

In this series of tutorials I will describe a small, extensible notification framework for JFace/Eclipse applications. I will make heavy use of OSGi services and Eclipse Extensions. So before you continue to read, I suggest you make yourself familiar with those concepts to be able to follow the series.
  1. Basic Notification Service
  2. Notify listeners about notifications
  3. Showing all notifications in a view
  4. Notifications via OSGi EventAdmin
  5. Adding links to notifications
  6. Let the notify console command quote "The Big Lebowski" from the IMDB with links
  7. Declarative colour customization for Notifications
  8. Add preferences which notifications should be shown
  9. Showing notifications after a "copy" command was executed
There is git repository accompanied with this tutorial series. You can clone it directly from git://github.com/pke/Notifications.git

Inspiration

The inspiration for using notifications in my own programs came from Mylyn that shows neat little notifications upon changes in the watched buglists. I was further inspired by the great blog post over at Hexapixel: Creating a Notification Popup Widget. We will use this widget as the base of this tutorial series.

Creating the service

First we create a simple service interface for our NotificationService. Its intentionally simple and does not contain any query methods to enumerate notifications or such. It simply provides a method to show a single notification. But as you will see in the course of this series that's enough to drive a powerful notification system. So the interface will look like this:
public interface NotifyService {
 /**
  * Shows a notification. Re-arranges already visible notifications.
  *
  * @param notification
  */
 void show(Notification notification);
}
For the Notification interface itself we will use JFace's IMessageProvider as a base as it provides already a message text and type for our notification. All that is left is a title. So our Notification interface looks pretty simple too:
interface Notification extends IMessageProvider {
 /**
  * @return the title of this notification or null if none.
  */
 String getTitle();
}
Thats basically all we need for a simple notification. As the base for the implementation of our Notification Service we will use Hexapixel's code for now. That makes the implementation of the NotificationService really simple:
public void show(final Notification notification) {
  if (notification != null) {
    Display.getDefault().asyncExec(new Runnable() {
      public void run() {
        NotificationType type;
        switch (notification.getMessageType()) {
        case IMessageProvider.WARNING:
          type = NotificationType.WARN;
          break;
        case IMessageProvider.ERROR:
          type = NotificationType.ERROR;
          break;
        default:
          type = NotificationType.INFO;
          break;
        }
        NotifierDialog.notify(notification.getTitle(), notification.getMessage(), type);
      }
    });
  }
}
I only modified the original NotifierDialog class so that it no longer requires an active shell for operating. There is one more little thing we also need to do, to actually see the notification on screen. We need a thread that polls the SWT message loop. This is usually done for us when we run inside the Eclipse workbench, but since we are going to test this notification framework in an OSGi environment (for now) we need a thread that polls the SWT message loop for us. The code is also inside the NotificationService component implementation:
Runnable runnable = new Runnable() {
  public void run() {
    while (!Display.getDefault().isDisposed()) {
      if (!Display.getDefault().readAndDispatch()) {
        Display.getDefault().sleep();
      }
    }
  }
};
new Thread(runnable, "SWT").start();

Equinox Console Command to show notifications

Basically we have everything in place now to show notifications programmatically. We can have other components use our notification service to display messages. So we will create a console command for the Equinox OSGi console and implement a simple CommandProvider. It will expose the command "notify" to the Equinox console if you start the OSGi configuration with the "-console" command. The notify command will take at least one parameter, which will be the notifications message. If you specify another parameter, then this will become the notifications title. Otherwise a default title is choosen.
public class NotifyCommand implements CommandProvider {

 NotificationService service;

 protected void activate(ComponentContext context) {
   service = (NotificationService) context.locateService("NotificationService");
 }

 public void _notify(CommandInterpreter ci) {
   final String message = ci.nextArgument();
   if (null == message) {
     ci.println("You need to specify a message");
     return;
   }
   String title = ci.nextArgument();
   if (title == null) {
     title = "Notification";
   }
  
   final String finalTitle = title;
   service.show(new Notification() {
     public String getTitle() {
       return finalTitle;
     }
    
     public String getMessage() {
       return message;
     }

     public int getMessageType() {
       return IMessageProvider.INFORMATION;
     }
   });
 }

 public String getHelp() {
   return "--- Notification ---\n\tnotify message [title]";
 }
}

Create the component definitions

All that is left to make those 2 services run as Declerative Services is to create the required component XML files in /OSGI-INF/. For the NotificationService it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ui.notification.NotificationService">
  <implementation class="ui.notification.internal.NotificationServiceImpl"/>
  <service>
     <provide interface="ui.notification.NotificationService"/>
  </service>
</scr:component>
And for the NotifyCommand it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ui.notification.NotifyCommand">
  <implementation class="ui.notification.command.internal.NotifyCommand"/>
  <service>
     <provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
  </service>
  <reference cardinality="1..1" interface="ui.notification.NotificationService" name="NotificationService" policy="static"/>
</scr:component>

Run configuration

To run this we create an OSGi run configuration and add the following bundles to it:
  • ui.notification
  • ui.notification.command
  • org.eclipse.osgi
  • org.eclipse.osgi.services
  • org.eclipse.equinox.ds
  • org.eclipse.equinox.util
  • org.eclipse.equinox.common
  • org.eclipse.core.commands
  • org.eclipse.swt
  • org.eclipse.swt.win32.win32.x86
  • org.eclipse.jface
Make sure the org.eclipse.equinox.ds bundle is started, as well as our two ui.notification* bundles. If we start the configuration now, we will be able to type: notify "Hello World" and see a notification coming up at the right bottom of our primary screen as seen in this video: That's all for now. Stay tuned for the next part where we will notify listener services about notifications using the White-Board pattern. Feedback is always welcome, especially about how to poll the SWT message loop in OSGi apps.

Source:

Check out the source code at github or clone directly from git://github.com/pke/Notifications.git

5 comments:

  1. Hi Phil, looks cool, just gave it a try, worked without a problem.
    Mylyn's notification seem to be more sophisticated graphically already and support stacking. Maybe that code could be reused?
    Also wondering if this could be changed to support existing notification systems like growl or the gtk one :)

    ReplyDelete
  2. Thank you Ralf for your feedback. I am aware of the Mylyn notifications. By the end of this tutorial series we will have more sophisticated notifications. It seems that the Mylyn notifications are not public API (yet).

    We will have custom icons, custom colouring, controls and links in the notifications and probably stacking. If by stacking you mean merging several notifications into one and providing a summary.
    Otherwise if you mean stacking on the screen try to call notify more quickly and it should stack up already.

    Since I am on Win32 here I cannot directly provide support for growl, but I suppose using JNA it should be possible to create a GrowlNotificationService implementation. This service would then have higher priority then the default one (presented here) if running on a MacOSX.

    ReplyDelete
  3. Nice Article and very useful. I am also a very enthusiastic developer and keep on creating a custom controls same like you did here .
    I saw in your code that you are using lots of FONTS, COLOR and IMAGES. so I am not sure if you are following MSAA standards (Microsoft Active Accessibility Support). Did you tested your custom widget with different screen resolution?
    If this is not working how would you handle the Accessibility events for different OS screen resolution. Kepping in mind that we are not using SWT/JFACE provided fonts and color and creating our new instance of these.

    I am currently working in a RCP application which is going to use by more than 50k users worldwide (and keeo growing). Our Testing team has tested new created custom controls by IBM JAW’s and unfortunately they failed in the test.

    So can you please advise me how we can create custom controls with our own fonts and Colors which should also work on accessibility point of view (In other words if we can use JFACEResource/Font/Color Registry in appropriate way).

    Would really appreciate if you can help me out. Thanks in bunch

    Vikash

    ReplyDelete
  4. Thanks for your comment Vikash. In this blog post I simply re-used the notification component by fellow blogger "hexapixel" as mentioned at the beginning of the article.
    Maybe you should get in contact with him (http://hexapixel.com/2009/06/30/creating-a-notification-popup-widget) about the accessibility issues the control could create. I have no experience on MSAA or any other standard for that matter. Good luck on your app!

    ReplyDelete
  5. Hello Philipp,

    First of all thank you for the article and the work you've done.

    We are thinking to implement this feature in our application.

    In your comment of bug 229823 (https://bugs.eclipse.org/bugs/show_bug.cgi?id=229823#c7) you mentioned that you added the option to adapt the framework using extension points. Is this code available?

    I cloned the git repository but didn't see those features.

    Thanks in advance,
    Davy

    ReplyDelete