Thu. Mar 28th, 2024

I have always thought one of the most useful pieces of software was a software bus.  What is a software bus?  A small piece of code that would take up no/limited screen real estate that you can develop pieces of software that interact or work independently from each other.  Here are the requirements I have put together for such a piece of code:

  • Limited UI – The UI limited to when I want it displayed, and even then it must be minimal.
  • Desktop Accessible – Directly accessable from the desktop.
  • Update/Install – Simple, and easy automatic updates required.
  • Portable – Support multiple operating systems

Eclipse is extremely portable, and has the update/install abilities needed.  However it has a UI and while desktop accessible it takes up the desktop.  Now if I add in the System Tray Icon and minimizing an Eclipse RCP to the system tray that reduces the UI.  If I want minimal UI I just need to limit what is opened.  So let’s begin building an Eclipse RCP Software Bus.  In general I assume your familiar enough with Eclipse to do most things your self.  If not there are tons of great tutorials to walk you through steps I don’t fully explain.

  1. Create a RCP Plug-in Project based on the Hello RCP template
  2. Open MANIFEST.MF  in the editor.  Modify Require-Bundle to the following:
    Require-Bundle: org.eclipse.ui,
    org.eclipse.core.runtime,
    org.eclipse.equinox.p2.ui;bundle-version="2.0.0",
    org.eclipse.equinox.p2.metadata;bundle-version="1.0.0",
    org.eclipse.equinox.p2.metadata.repository;bundle-version="1.0.0",
    org.eclipse.equinox.p2.artifact.repository;bundle-version="1.0.0",
    org.eclipse.equinox.p2.extensionlocation;bundle-version="1.0.100",
    org.eclipse.equinox.p2.updatesite;bundle-version="1.0.0",
    org.eclipse.equinox.p2.director;bundle-version="1.0.100",
    org.eclipse.equinox.p2.engine;bundle-version="1.0.100",
    org.eclipse.equinox.p2.core;bundle-version="1.0.100",
    org.eclipse.ecf;bundle-version="3.0.0",
    org.eclipse.ecf.filetransfer;bundle-version="3.0.0",
    org.eclipse.ecf.identity;bundle-version="3.0.0",
    org.eclipse.ecf.provider.filetransfer;bundle-version="3.0.0",
    org.eclipse.ecf.provider.filetransfer.httpclient;bundle-version="3.0.0",
    org.apache.commons.codec;bundle-version="1.2.0",
    org.apache.commons.httpclient;bundle-version="3.0.1",
    org.eclipse.equinox.p2.ui.sdk;bundle-version="1.0.0"
  3. Open ApplicationActionBarAdvisor.java and modify it as below:
    import org.eclipse.jface.action.GroupMarker;
    import org.eclipse.jface.action.IMenuManager;
    import org.eclipse.jface.action.MenuManager;
    import org.eclipse.jface.action.Separator;
    import org.eclipse.ui.IWorkbenchActionConstants;
    import org.eclipse.ui.IWorkbenchWindow;
    import org.eclipse.ui.actions.ActionFactory;
    import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
    import org.eclipse.ui.application.ActionBarAdvisor;
    import org.eclipse.ui.application.IActionBarConfigurer;
    
    public class ApplicationActionBarAdvisor extends ActionBarAdvisor
    {
    	/**
    	 * Exit Action for cleanly exiting the application.
    	 */
    private IWorkbenchAction exitAction;
    	
    	/**
    	 * About Action, always need to show license information
    	 */
    private IWorkbenchAction aboutAction;
    	
    	/**
    * Preferences Action, we may need to access our preferences good to make available.
    	 */
    private IWorkbenchAction preferencesAction;
    
    public ApplicationActionBarAdvisor(final IActionBarConfigurer configurer)
    	{
    super(configurer);
    	}
    
    	/**
    	 * Add your actions to the menu bar.
    	 */
    	@Override
    protected void fillMenuBar(final IMenuManager menuBar)
    	{
    		/**
    		 * We need to have a Window menu just for consistency for the preferences
    		 * menu
    		 */
    final MenuManager windowMenu = new MenuManager("&Window",
    				IWorkbenchActionConstants.M_WINDOW);
    		/**
    		 * We need to have a Help menu item for the P2 install/update
    		 * menu items to attach to.
    		 */
    final MenuManager helpMenu = new MenuManager("&Help",
    				IWorkbenchActionConstants.M_HELP);
    
    menuBar.add(windowMenu);
    		/**
    		 * Allow a spot on the menu for additions from other plug-ins
    		 */
    menuBar.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
    menuBar.add(helpMenu);
    
    		windowMenu.add(this.preferencesAction);
    
    		/**
    		 * Allow a spot on the menu for additions from other plug-ins
    		 */
    helpMenu.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
    		helpMenu.add(new Separator());
    		helpMenu.add(this.aboutAction);
    	}
    
    	/**
    	 * Always make sure you create the actions here, otherwise you risk creating 
    * mulitple instances of them.
    	 */
    	@Override
    protected void makeActions(final IWorkbenchWindow window)
    	{
    
    		this.exitAction = ActionFactory.QUIT.create(window);
    		this.register(this.exitAction);
    
    		this.aboutAction = ActionFactory.ABOUT.create(window);
    		this.register(this.aboutAction);
    
    		this.preferencesAction = ActionFactory.PREFERENCES.create(window);
    		this.register(this.preferencesAction);
    	}
    
    }
  4. Now open ApplicationWorkbenchWindowAdvisor.java and modify it to the below:
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.MenuDetectEvent;
    import org.eclipse.swt.events.MenuDetectListener;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.events.ShellAdapter;
    import org.eclipse.swt.events.ShellEvent;
    import org.eclipse.swt.graphics.Image;
    import org.eclipse.swt.graphics.Point;
    import org.eclipse.swt.widgets.Menu;
    import org.eclipse.swt.widgets.MenuItem;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.swt.widgets.Tray;
    import org.eclipse.swt.widgets.TrayItem;
    import org.eclipse.ui.IWorkbenchWindow;
    import org.eclipse.ui.PlatformUI;
    import org.eclipse.ui.application.ActionBarAdvisor;
    import org.eclipse.ui.application.IActionBarConfigurer;
    import org.eclipse.ui.application.IWorkbenchWindowConfigurer;
    import org.eclipse.ui.application.WorkbenchWindowAdvisor;
    import org.eclipse.ui.handlers.IHandlerService;
    
    public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor
    {
    	/**
    	 * Reference for accessing the window when we need to.
    	 */
    private IWorkbenchWindow window;
    	/**
    	 * The System Tray item, for our icon.
    	 */
    private TrayItem trayIcon;
    	/**
    	 * The Image object for our icon.
    	 */
    private Image trayImage;
    
    public ApplicationWorkbenchWindowAdvisor(
    final IWorkbenchWindowConfigurer configurer)
    	{
    super(configurer);
    	}
    
    	@Override
    public ActionBarAdvisor createActionBarAdvisor(
    final IActionBarConfigurer configurer)
    	{
    return new ApplicationActionBarAdvisor(configurer);
    	}
    
    	/**
    	 * Remember to dispose of our Image we create
    	 */
    	@Override
    	public void dispose()
    	{
    		if (this.trayImage != null)
    		{
    			this.trayImage.dispose();
    		}
    		if (this.trayIcon != null)
    		{
    			this.trayIcon.dispose();
    		}
    	}
    
    	/**
    * We are create a Popup menu, this is our hook for adding items to the menu.
    	 */
    private void hookPopupMenu()
    	{
    this.trayIcon.addMenuDetectListener(new MenuDetectListener()
    		{
    
    			@Override
    public void menuDetected(final MenuDetectEvent e)
    			{
    final Menu menu = new Menu(
    						ApplicationWorkbenchWindowAdvisor.this.window
    .getShell(), SWT.POP_UP);
    
    				
    // Create an About action to access the Eclipse About Action
    final MenuItem about = new MenuItem(menu, SWT.NONE);
    				about.setText("About");
    about.addSelectionListener(new SelectionAdapter()
    				{
    					@Override
    public void widgetSelected(final SelectionEvent e)
    					{
    final IHandlerService handlerService = (IHandlerService) PlatformUI
    .getWorkbench().getService(
    IHandlerService.class);
    						try
    						{
    							handlerService.executeCommand(
    									"org.eclipse.ui.help.aboutAction", null);
    						}
    
    catch (final Exception ex)
    						{
    throw new RuntimeException(
    									"org.eclipse.ui.help.aboutAction not found");
    						}
    					}
    				});
    
    				
    //Create the Preferences action on the popup menu.
    final MenuItem preferences = new MenuItem(menu, SWT.NONE);
    				preferences.setText("Preferences");
    preferences.addSelectionListener(new SelectionAdapter()
    				{
    					@Override
    public void widgetSelected(final SelectionEvent e)
    					{
    final IHandlerService handlerService = (IHandlerService) PlatformUI
    .getWorkbench().getService(
    IHandlerService.class);
    						try
    						{
    							handlerService.executeCommand(
    									"org.eclipse.ui.window.preferences", null);
    						}
    
    catch (final Exception ex)
    						{
    throw new RuntimeException(
    									"org.eclipse.ui.window.preferences not found");
    						}
    					}
    				});
    
    new MenuItem(menu, SWT.SEPARATOR);
    
    				// Create a link to the Install Software action.
    final MenuItem installSoftware = new MenuItem(menu, SWT.NONE);
    				installSoftware.setText("Install New Software");
    installSoftware.addSelectionListener(new SelectionAdapter()
    				{
    					@Override
    public void widgetSelected(final SelectionEvent e)
    					{
    final IHandlerService handlerService = (IHandlerService) PlatformUI
    .getWorkbench().getService(
    										IHandlerService.class);
    						try
    						{
    							handlerService.executeCommand(
    									"org.eclipse.equinox.p2.ui.sdk.install",
    									null);
    						}
    
    catch (final Exception ex)
    						{
    throw new RuntimeException(
    									"org.eclipse.equinox.p2.ui.sdk.install not found");
    						}
    					}
    				});
    
    				// Create a link on the menu for the Check for Updates actions
    final MenuItem checkUpdates = new MenuItem(menu, SWT.NONE);
    				checkUpdates.setText("Check for Updates");
    checkUpdates.addSelectionListener(new SelectionAdapter()
    				{
    					@Override
    public void widgetSelected(final SelectionEvent e)
    					{
    final IHandlerService handlerService = (IHandlerService) PlatformUI
    .getWorkbench().getService(
    										IHandlerService.class);
    						try
    						{
    							handlerService.executeCommand(
    									"org.eclipse.equinox.p2.ui.sdk.update",
    									null);
    						}
    
    catch (final Exception ex)
    						{
    throw new RuntimeException(
    									"org.eclipse.equinox.p2.ui.sdk.install not found");
    						}
    					}
    				});
    new MenuItem(menu, SWT.SEPARATOR);
    				
    // Create an Exi Action so that we can terminate the program when we need to.
    final MenuItem exit = new MenuItem(menu, SWT.NONE);
    				exit.setText("Exit");
    exit.addSelectionListener(new SelectionAdapter()
    				{
    					@Override
    public void widgetSelected(final SelectionEvent e)
    					{
    						// From a view you get the site which allow to get the
    						// service
    final IHandlerService handlerService = (IHandlerService) PlatformUI
    .getWorkbench().getService(
    										IHandlerService.class);
    						try
    						{
    							handlerService.executeCommand(
    									"org.eclipse.ui.file.exit", null);
    						}
    
    catch (final Exception ex)
    						{
    throw new RuntimeException(
    									"org.eclipse.ui.file.exit not found");
    						}
    					}
    				});
    				// We need to make the menu visible
    menu.setVisible(true);
    			}
    		});
    	}
    
    	@Override
    public void postWindowOpen()
    	{
    		super.postWindowOpen();
    this.window = this.getWindowConfigurer().getWindow();
    		
    // Create the trayIcon and verify the System Tray is available on 
    		// this platform.
    final Tray tray = window.getShell().getDisplay().getSystemTray();
    this.trayIcon = new TrayItem(tray, SWT.NONE);
    		this.trayImage = Activator.getImageDescriptor("/icons/icon_16x16.png")
    .createImage();
    		this.trayIcon.setImage(this.trayImage);
    		this.trayIcon.setToolTipText("TrayItem");
    		
    		// Some OS might not support tray items
    		if (this.trayIcon != null)
    		{
    			/*
    * Hide the window when it is minimized.
    			 */
    this.window.getShell().addShellListener(new ShellAdapter()
    			{
    // If the window is minimized hide the window
    				@Override
    public void shellIconified(final ShellEvent e)
    				{
    					ApplicationWorkbenchWindowAdvisor.this.window.getShell()
    .setVisible(false);
    				}
    			});
    			//Hook the menu into the tray icon
    			this.hookPopupMenu();
    			
    			// Hide the window
    this.getWindowConfigurer().getWindow().getShell().setVisible(false);
    		}
    
    // minimize the window, this will trigger the listener
    		// above and hide the window.  We won't see it anymore.
    final Shell shell = PlatformUI.getWorkbench()
    .getActiveWorkbenchWindow().getShell();
    		shell.setMinimized(true);
    	}
    
    	@Override
    public void preWindowOpen()
    	{
    final IWorkbenchWindowConfigurer configurer = this
    				.getWindowConfigurer();
    		configurer.setInitialSize(new Point(400, 300));
    		// We have to show the progress indicator and status line
    		// for the Update and Install pieces to attach to.
    		configurer.setShowStatusLine(true);
    		configurer.setShowProgressIndicator(true);
    		configurer.setShowCoolBar(false);
    configurer.setTitle("MyMiller Service Panel"); 
    	}
    }

Now just complete your Product configuration and build your RCP.  You will have a system tray based Eclipse RCP, with a menu on right click, that allows you to update, install or set preferences for installed plug-in.  You might wonder why you have two create two different menus.  The P2 plug-ins for updating/installing software require the Window menu to attach actions to.  If it is missing then it will fail to load and will not function properly.  In my next blog I’ll explain how to add automatic updating of installed plug-ins to this app.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d