Showing posts with label jface. Show all posts
Showing posts with label jface. Show all posts

Tuesday, January 12, 2010

Notification Listeners

To let other components in the system know about displayed notifications we use the White-Board-Pattern to consume listener services in our NotificationServiceImpl class. A listener for notifications has a very easy interface:
public interface NotificationListener extends EventListener {
  void onEvent(NotificationEvent event);
}
It extends the default Java (empty) EventListener base interface and contains a single method. This single method and its associated event object allows us to extend the kind of events we can publish without changing the interface for each new event like "Show" or "Hide". For now we will only publish "Show" events. But if in the future we decide to also notify listeners about the close/hide of a notification popup we would have to add another method to the interface "onHide" and breaking the API somehow. Using an event object we can put the type of the event inside the event object and the client would then have to decide what to do for each event type. It would still only implement a single method "onEvent" instead of having to implement a new method for each new event type such as "onShow" or "onHide". The event object itself looks like this:
public class NotificationEvent extends EventObject {
private static final long serialVersionUID = -8633958140848611658L;

private Notification notification;

private NotificationEventType type;

public NotificationEvent(NotificationService source, Notification notification, NotificationEventType type) {
  super(source);
  this.notification = notification;
  this.type = type;
}

public Notification getNotification() {
  return notification;
}

public NotificationEventType getType() {
  return type;
}
}
The event type is an enum:
public enum NotificationEventType {
Show
}
The NotificationServiceImpl is only slightly changed:
Object listeners[] = context.locateServices("NotificationListener"); //$NON-NLS-1$
if (listeners != null) {
 NotificationEvent event = new NotificationEvent(this, notification, NotificationEventType.Show);
 for (Object listener : listeners) {
   try {
     ((NotificationListener)listener).onEvent(event);
   } catch (Throwable e) {          
   }
 }
}
Right before we display the notification we get all the NotificationListener registered in the OSGi system and (safely) call their onEvent method. There are several ways to get the list of currently registered NotificationListener services. One way is to use the BundleContext.getServiceReferences(String, String) method. But this would involve getting the service object ourself and not forget to "unget" it also. Another way is to use a ServiceTracker. But since our implementation already is a Component with bindings we can just add a new binding to our service component definition XML:
<reference cardinality="0..n" interface="ui.notification.NotificationListener" name="NotificationListener" policy="dynamic"/>
We then use the ComponentContext object to get an array of services that could be bound to "NotificationListener" (as named in the component XML). This context we get from OSGi in a new protected method:
protected void activate(ComponentContext context) {
 this.context = context;
}
That's all for notifying listeners about notification display. We can now create a simple new bundle that will register a NotificationListener into the system and simply System.out.println the notifications data.
public class SysoutNotificationListener implements NotificationListener {

  public void onEvent(NotificationEvent event) {
    if (event.getType() == NotificationEventType.Show) {
      System.out.println(event.getNotification().getTitle() + ": " //$NON-NLS-1$
          + event.getNotification().getMessage());
    }
  }
}
Its component definition would look 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.listener.sysout">
 <implementation class="ui.notification.listener.sysout.internal.SysoutNotificationListener"/>
 <service>
    <provide interface="ui.notification.NotificationListener"/>
 </service>
</scr:component>
Pretty simple. Here is what the result looks like: Listeners could also be used to log a history of notifications in various ways. In the next chapter of this series we will develop a View for Eclipse RCP apps that logs a history of notifications in a table.

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

Thursday, July 23, 2009

How to replace the default JFace message icons

We all know the standard JFace icons very well. They appear in Dialogs, Wizards and Forms.

And in a form they look like this:

Now, there is a quick and easy way to replace them with your own icons if you like.

You just have to create a Fragment bundle and set org.eclipse.jface as its Bundle-Host. In your fragment bundle you include the icons you want to replace in the following folder structure:
Don't forget to add the icons folder and all of its subfolder and content into your build.properties file:
bin.includes = META-INF/,\
             .,\
             OSGI-INF/,\
             icons/
Interesting fact: You can replace the JFace icons with PNGs too. You can just rename them to have the .GIF extensions. The image file loader of SWT does not care about the extension but analyses the content of the file.