Saturday, July 31, 2010

title pic Millionth Download?

Posted by pookzilla on May 3, 2007

So… any plans to mark the millionth download of the Eclipse release this time around? Can we somehow conspire to have Denis shave his head? Can we convince Chris to get an Eclipse tattoo? I’d offer to do that myself but getting me to agree to a tattoo is hardly a worthy challenge.

title pic Security? I Prefer Herring.

Posted by pookzilla on March 12, 2007

In the past I have suggested rather… unorthodox… uses of the activities framework, transformation services, and custom splash handlers in the context of “security.” These misguided suggestions made their way into Neil Bartlett’s security talk at this years Eclipsecon. As could be expected, they were shot down.

To my own credit, I never thought these were good ideas. I tried to ensure that the standard “stupid idea” caveat was present whenever I suggested them but in my excitement to give people what they want that may not have always been apparent. In the future I’ll try and keep my off-the-wall suggestions to myself. In preparation for that I must purge myself of some ideas that have been percolating in my head:

  • With judicious use of our new menu story you may reverse unwanted male pattern baldness.
  • Rubbing Working Sets onto a fresh burn will prevent scaring and infection.
  • OSGi acts as an excellent lubricant and has very easy cleanup.
  • In the absence of Cayenne pepper sprinkle on a few Status Handlers. You wont be disappointed.
  • If you’re looking for some quick cash you’ll find that Contexts go for $5/gram on the streets of Santa Clara.
  • Views and Editors? Herb #3 and Spice #9.
  • Decorators could be to console gaming what Berber carpet is to horticulture. You heard it here first.

title pic Holding the Fort

Posted by pookzilla on March 8, 2007

To all of you enjoying EclipseCon right now all I have to say is:

I hate you so very much.

It’s almost -30C with windchill here in Ottawa! The envy I fee is palpable; I can almost taste it. In fact, I think it tastes like frostbite.

:)

title pic I Want To Tell You a Story II

Posted by pookzilla on January 31, 2007

So, the story. Well, the story is still progressing. It’s wandered off into a tangent involving the installation of GeSHi and an attempt at finding a new home for examples that aren’t destined for the build. I will show example code now but there are fully functioning example projects that will be made available just as soon as they have a home.

Example 1: An Interactive Splash

Perhaps you want to use the splash screen as a log in screen. The code for such a splash handler might look something like this:

/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others. All rights
 * reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public
 * License v1.0 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: IBM Corporation - initial API and
 * implementation
 ******************************************************************************/
 
package org.eclipse.ui.examples.splash;
 
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.splash.AbstractSplashHandler;
 
/**
 * @since 3.3
 * 
 */
public class InteractiveSplashHandler
    extends
    AbstractSplashHandler {
  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.ui.splash.AbstractSplashHandler#init(org
   * .eclipse.swt.widgets .Shell)
   */
  public void init(final Shell splash) {
    super.init(splash);
    splash.setLayout(new FillLayout());
    // make our composite inherit the splash background
    splash.setBackgroundMode(SWT.INHERIT_DEFAULT);
 
    final String[] username = new String[1], password = new String[1];
    final boolean[] loggedIn = new boolean[] { false };
    Composite panel = new Composite(splash, SWT.BORDER);
    panel.setLayout(new GridLayout(2, false));
    {
      Composite spanner = new Composite(panel, SWT.NONE);
      GridData data = new GridData(SWT.FILL, SWT.FILL,
          true, true);
      data.horizontalSpan = 2;
      spanner.setLayoutData(data);
    }
    {
      Label label = new Label(panel, SWT.NONE);
      label.setText("Name:"); //$NON-NLS-1$
      final Text text = new Text(panel, SWT.BORDER);
      text.addVerifyListener(new VerifyListener() {
 
        public void verifyText(VerifyEvent e) {
          username[0] = e.text;
        }
      });
      text.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
          true, false));
    }
    {
      Label label = new Label(panel, SWT.NONE);
      label.setText("Password:"); //$NON-NLS-1$
      final Text text = new Text(panel, SWT.PASSWORD
          | SWT.BORDER);
      text.addVerifyListener(new VerifyListener() {
 
        public void verifyText(VerifyEvent e) {
          password[0] = e.text;
        }
      });
      text.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
          true, false));
    }
    {
      Button logIn = new Button(panel, SWT.PUSH);
      logIn.setText("Log In"); //$NON-NLS-1$
      logIn.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
          true, false, 2, 1));
      logIn.addSelectionListener(new SelectionAdapter() {
        /*
         * (non-Javadoc)
         * 
         * @seeorg.eclipse.swt.events.SelectionAdapter#
         * widgetSelected(org.eclipse
         * .swt.events.SelectionEvent)
         */
        public void widgetSelected(SelectionEvent e) {
          if (username[0] != null
              && !username[0].equals("") //$NON-NLS-1$
              && password[0] != null 
              && !password[0].equals("")) { //$NON-NLS-1$
            loggedIn[0] = true;
          } else {
            MessageDialog
                .openError(
                    splash,
                    "Could not Authenticate", "There was a problem logging in."); //$NON-NLS-1$//$NON-NLS-2$
          }
        }
 
      });
    }
    {
      Button quit = new Button(panel, SWT.PUSH);
      quit.setText("Quit"); //$NON-NLS-1$
      quit.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
          true, false, 2, 1));
      quit.addSelectionListener(new SelectionAdapter() {
        /*
         * (non-Javadoc)
         * 
         * @seeorg.eclipse.swt.events.SelectionAdapter#
         * widgetSelected(org.eclipse
         * .swt.events.SelectionEvent)
         */
        public void widgetSelected(SelectionEvent e) {
          // quick and dirty exit. If people really need
          // this feature
          // we can talk about new API to abort startup down
          // the road
          splash.getDisplay().close();
          System.exit(0);
        }
 
      });
    }
    // layout the new controls
    splash.layout(true);
 
    // spin the loop until we're logged in
    while (loggedIn[0] == false) {
      if (!splash.getDisplay().readAndDispatch()) {
        splash.getDisplay().sleep();
      }
    }
  }
}

Resulting in:

Example 2: A Browser Splash

Perhaps there is some elaborate flash content you want to show in your browser. The code could look something like this. Note that while the code only points to static old eclipse.org I have tested this with flash animation. Having your Eclipse come up to the tune of “I’ve Got a Big Bag of Crabs” is priceless.

/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others. All rights
 * reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public
 * License v1.0 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: IBM Corporation - initial API and
 * implementation
 ******************************************************************************/
 
package org.eclipse.ui.examples.splash;
 
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.splash.AbstractSplashHandler;
 
/**
 * @since 3.3
 * 
 */
public class BrowserSplashHandler
    extends
    AbstractSplashHandler {
 
  private boolean movieLoaded = false, done = false;
 
  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.ui.internal.splash.AbstractSplashHandler
   * #init(org.eclipse.swt.widgets.Shell,
   * org.eclipse.ui.IWorkbench)
   */
  public void init(final Shell splash) {
    super.init(splash);
    splash.setLayout(new GridLayout(1, true));
    final Browser browser = new Browser(splash, SWT.NONE);
    browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
        true, true));
    final Button button = new Button(splash, SWT.PUSH);
    button.setText("Press to close"); //$NON-NLS-1$
    button.setBounds(570 / 2 - button.computeSize(
        SWT.DEFAULT, SWT.DEFAULT).x / 2,
        splash.getSize().y - 20, splash.getSize().x, 20);
    button.setVisible(false);
    button.setLayoutData(new GridData(SWT.CENTER, SWT.FILL,
        false, false));
    browser.addProgressListener(new ProgressListener() {
 
      public void changed(ProgressEvent event) {
      }
 
      public void completed(ProgressEvent event) {
        movieLoaded = true;
        button.setVisible(true);
        button
            .addSelectionListener(new SelectionListener() {
 
              public void widgetDefaultSelected(
                  SelectionEvent e) {
 
              }
 
              public void widgetSelected(SelectionEvent e) {
                done = true;
              }
            });
      }
    });
    browser.setUrl("http://eclipse.org"); //$NON-NLS-1$		
    splash.layout(true);
    while (!movieLoaded)
      while (getSplash().getDisplay().readAndDispatch())
        ;
  }
 
  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.ui.internal.splash.AbstractSplashHandler
   * #endSplash()
   */
  public void dispose() {
    getSplash().setActive();
    if (movieLoaded) {
      while (!done)
        getSplash().getDisplay().readAndDispatch();
    }
    super.dispose();
  }
}

Resulting in:

Example 3: An Extensible Splash

Perhaps you want to have a splash screen that plug-ins can contribute to via an extension mechanism. The code for this may look something like the following:

/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others. All rights
 * reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public
 * License v1.0 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: IBM Corporation - initial API and
 * implementation
 ******************************************************************************/
 
package org.eclipse.ui.examples.splash;
 
import java.util.ArrayList;
import java.util.Iterator;
 
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.splash.AbstractSplashHandler;
 
public class ExtensibleSplashHandler
    extends
    AbstractSplashHandler {
  private ArrayList list = new ArrayList();
 
  public void init(Shell splash) {
    super.init(splash);
    IExtension[] extensions = Platform
        .getExtensionRegistry()
        .getExtensionPoint(
            "org.eclipse.ui.examples.splash.splashExtension")
        .getExtensions();
 
    ArrayList tooltips = new ArrayList();
    for (int i = 0; i < extensions.length; i++) {
      IExtension extension = extensions[i];
      IConfigurationElement[] elements = extension
          .getConfigurationElements();
      for (int j = 0; j < elements.length; j++) {
        IConfigurationElement configurationElement = elements[j];
        String iconName = configurationElement
            .getAttribute("icon");
        if (iconName == null)
          continue;
        ImageDescriptor imageDesc = AbstractUIPlugin
            .imageDescriptorFromPlugin(configurationElement
                .getNamespaceIdentifier(), iconName);
        Image image = imageDesc.createImage();
        if (image.getBounds().width != 50
            || image.getBounds().height != 50)
          image.dispose();
        else {
          list.add(image);
          tooltips.add(configurationElement
              .getAttribute("tooltip"));
        }
      }
    }
    if (list.isEmpty())
      return;
 
    // determine the number of images we can show per row
    int numPerRow = splash.getSize().x / 50;
 
    Composite iconPanel = new Composite(splash, SWT.NONE);
    iconPanel.setBackground(splash.getDisplay()
        .getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
    GridLayout gridLayout = new GridLayout(Math.min(list
        .size(), numPerRow), true);
    gridLayout.horizontalSpacing = gridLayout.verticalSpacing = gridLayout.marginHeight = gridLayout.marginWidth = 0;
    iconPanel.setLayout(gridLayout);
    for (Iterator i = list.iterator(), k = tooltips
        .iterator(); i.hasNext();) {
      Image image = (Image) i.next();
      Label label = new Label(iconPanel, SWT.NONE);
      label.setImage(image);
      label.setToolTipText((String) k.next());
    }
    // create a new background panel to avoid scaling
    Composite backgroundPanel = new Composite(splash,
        SWT.NONE);
    backgroundPanel.setBackgroundImage(splash
        .getBackgroundImage());
    backgroundPanel.setBounds(0, 0, splash.getSize().x,
        splash.getSize().y);
 
    // determine the size for the new icon panel and
    // position it at the
    // bottom of the splash.
    Point panelSize = iconPanel.computeSize(SWT.DEFAULT,
        SWT.DEFAULT, true);
    iconPanel.setBounds(splash.getSize().x / 2
        - panelSize.x / 2, splash.getSize().y, panelSize.x,
        panelSize.y);
 
    // resize the splash to contain the icon panel
    splash.setSize(splash.getSize().x, splash.getSize().y
        + panelSize.y);
    iconPanel.layout(true);
    iconPanel.update();
 
    while (iconPanel.getDisplay().readAndDispatch())
      ;
  }
 
  public void dispose() {
    super.dispose();
    if (!list.isEmpty())
      for (Iterator i = list.iterator(); i.hasNext();) {
        Image image = (Image) i.next();
        image.dispose();
      }
  }
}

Resulting in:

title pic I Want To Tell You a Story I

Posted by pookzilla on January 29, 2007

As Paul Webster likes to say, “I want to tell you a story.” Unlike Paul’s, my story has nothing to do with commands, key bindings, or Ketchup. Mine is about the splash screen.

No, wait. Come back! I promise it’s at least kind of interesting!

Over the past few milestones the SWT and runtime teams have been working on a way to get the splash screen re-implemented in SWT. There are all kinds of reasons why this is hard to do but in the end they got it done and in a most creative way at that. The basic gist of it is that the java process starts the Eclipse framework which uses native code to bring up a window. The window handle of this shell is published as a system property and later (once some heavy lifting has been done by the framework) the workbench takes that handle and wraps it in an SWT shell. We then Do Horrible Things To It such as smear it with progress bars, progress messages, and build ids.

Now for the exciting part – you too can do Horrible Things to the splash!

As of I20070130 there is a new extension point in org.eclipse.ui – splashHandler. Splash handler is like Intro in that there can only be one intro implementation per product. The extension point is quite simple. There are two possible elements – splashHandler and splashHandlerProductBinding. The first describes a splash handler while the later binds a given splash handler to a product. For example:

<extension         point="org.eclipse.ui.splashHandler">      <splashHandler            class="com.xyz.splash.Handler"            id="com.xyz.splash">      </splashHandler>      <splashHandlerProductBinding            productId="com.xyz.product"            splashId="com.xyz.splash">      </splashHandlerProductBinding></extension>

Nothing special, nothing complicated. The only interesting part is the class attribute of the splashHandler element. The value of this attribute must point to a class that extends org.eclipse.ui.splash.AbstractSplashHandler. This class has three interesting methods that you can override:

        /**  * Initialize this splash implementation. This is called very early in the  * workbench lifecycle before any window is created. The provided shell will  * already have a background image provided to it but subclasses are free to  * customize the shell in whatever way they see fit. Subclasses should  * ensure that they call the base implementation of this method at some  * point after their own method is invoked.  *  * Calls to this method will be made from the UI thread  *   * @param splash  *            the splash shell  */ public void init(Shell splash);

 /**  * Signal the handler to end the splash and dispose of any resources.  * Subclasses should ensure that they call the base implementation of this  * method at some point after their own method is invoked.  *  * Calls to this method will be made from the UI thread.  */ public void dispose();

 /**  * Return the progress monitor responsible for showing bundle loading.  * Default implementation returns a null progress monitor.  *   * Calls made to methods on this progress monitor may be made from non-UI  * threads so implementors must take care to ensure proper synchronization  * with the UI thread if necessary.  *   * @return the progress monitor  * @see NullProgressMonitor  */ public IProgressMonitor getBundleProgressMonitor();

The first, init(), is where you can add custom controls to your splash screen. For instance, you can add progress bars, animation, icons, or even custom controls. Additionally, you could have a splash that contains user name and password fields. This splash implementation would block until a user enters something in both boxes.

The second method, dispose(), can be used to perform custom cleanup. This method will be invoked sometime after the workbench has finished initializing itself. In addition to cleanup, this method could be used to block closure of the splash until some condition has been satisfied. For instance, you could have a splash screen that didn’t return from this method until the user hit the space bar.

The final method, getBundleProgressMonitor is where you can hook progress controls created in the init method to a progress monitor. This monitor will be called by the workbench when plug-ins are loaded.

The story continues tomorrow (provided anyone is still awake).

title pic Splash Build ID

Posted by pookzilla on January 12, 2007

Now that we can wrap our native splash window in an SWT Shell I’ve started to work on some API to allow an easily extensible workbench splash. As a small test of that I’ve added a touch to our splash screen – providing that you have the org.eclipse.ui.workbench/SHOW_BUILDID_ON_STARTUP preference set to true the build ID will appear just below the word “Europa” in the splash screen. This is not API yet and it’s hard-coded only to work with our splash screen at the moment but it’s enabled in the SDK product for the time being. Depending on the reception I’ll look at making it an API constant along with the progress location/message location constants in org.eclipse.ui.branding.IProductConstants

title pic Discussion Topic

Posted by pookzilla on January 9, 2007

iPhone is to sexy as Zune is to a pig in a dress. Discuss.

title pic New Working Set API

Posted by pookzilla on December 18, 2006

Near the end of M4 I added API to working sets that allowed them to express interest in certain types of objects. The intent was that you could ask the working set if a given object was suitable for containment. This would allow us to write UIs where the selection of objects could be added to working sets of a particular type only. Ie: you couldn’t add breakpoints to the resource working set. I didn’t advertise the API at the time because I wasn’t convinced that it was right and after some thought and discussion with downstream clients decided it wasn’t.

Rather than answering whether or not a given item was applicable to a working set it would be much more useful for the working set to return an object that WAS applicable. For instance, attempting to add a breakpoint to a resource working set would instead add the resource that the breakpoint corresponded to. The following API was added to address this issue.

First, a new attribute was added to the working set plugin.xml schema. This optional attribute, “elementAdapterClass”, takes a class that implements IWorkingSetElementAdapter as a value. This interface has the following definition:

/**
* Interface that describes a mechanism that may be provided by working set
* implementors to help manage the addition of elements to working sets.
* Instances of this class are capable of transforming possible working set
* content into the most applicable form.
*
* @since 3.3
*/
public interface IWorkingSetElementAdapter {

/**
* Converts the given elements for addition to /removal from the working
* set.
*
* @return the (possibly adapted) elements to add to /remove from the
* working set
*/
IAdaptable[] adaptElements(IWorkingSet ws, IAdaptable[] elements);

/**
* Disposes of this element adaptor.
*/
void dispose();
}

It is the responsibility of this interface to inspect IAdaptables and return IAdaptables derived from the originals that are suitable for inclusion in working sets of a given type. The adaptables may be the same objects, new objects, or no objects at all if the original adaptables are not suitable.

This interface is accessed indirectly via a new method on IWorkingSet:

/**
* Transforms the supplied elements into elements that are suitable for
* containment in this working set. This is useful for UI elements which
* wish to filter contributions to working sets based on applicability. This
* is a hint, however, and is not considered when the
* {@link #setElements(IAdaptable[])} method is invoked.
*
* @param objects
* the objects to transform
* @return an array of transformed elements that be empty if no elements
* from the original array are suitable
* @since 3.3
*/
public IAdaptable[] adaptElements(IAdaptable[] objects);

Note the comment in this method – this is only a hint for IWorkingSet clients, not a definitive filter. IWorkingSet.setElements() will continue to take any array of IAdaptable objects you throw at it.

This new interface behaves similarly to the existing IWorkingSetUpdater interface in that its specification will not cause eager plug-in loading. If your plug-in defines an IWorkingSetElementAdapter class it will not be used until your plug-in is loaded via some other means. Until that time the IWorkingSet.adaptElements() method will return the passed array of IAdaptables when invoked. To help get around this limitation a default implementation of IWorkingSetElementAdapter has been provided in the UI plugin. This class, BasicWorkingSetElementAdapter, is an IExecutableExtension and may be customized by client plug-ins in their plugin.xml file. The format for this customization is as follows:


<workingSet elementAdapterClass="org.eclipse.ui.BasicWorkingSetElementAdapter:some.package.ClassName1[;adapt=true|false],some.package.ClassName2[;adapt=true|false],..."
... />

From the documentation for BasicWorkingSetElementAdapter.adaptElements():

“When invoked this method will iterate over all classes specified as IExecutableExtension arguements to this class in order and compare with the elements. If the element is directly assignable to the provided class then it is added to the result array as is. If the class has specified “adapt=true” as an argument and there is an available adapter in the platform IAdapterManager then it is returned. Finally, if “adapt=true” and the class is already loaded (determined by inspecting exported bundles via the platform PackageAdmin) a direct query for the adapter is made on the object and if it is not null then it is returned.

A consequence of the above is that it is possible for this method to return differing results based on the state of bundles loaded within the system.”

For example, here is the declaration for the IDE Resource Working Set elementAdapterClass:

org.eclipse.ui.BasicWorkingSetElementAdapter:org.eclipse.core.resources.IResource;adapt=true

This means that the Resource Working Set will only accept elements that are IResources and these may be obtained either by direct assignment or by checking for an adapter.

If there are any comments on this API please log defects and I’ll respond to them ASAP. The API deadline is fast approaching so if you do have issues it’s better to express them now rather than later. Thanks!

(crossposted to platform-ui-dev@eclipse.org)

title pic Toolbar Toggle buttons on OS X

Posted by pookzilla on October 30, 2006

Another question from the inbox:

I have an SWT app on OS X that wants to have the “toggle toolbar” button in the upper right corner of the window trim. I see that Eclipse has this button but I don’t see API in SWT to make it happen. How can I have this button in my app?”

Unfortunately, there is no API for this feature. Back in 3.2 we added this feature by adding code to the org.eclipse.ui.carbon fragment that called SWT internals. The code to do this for your own SWT app would look something like this:


package scratch;

import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.Callback;
import org.eclipse.swt.internal.carbon.OS;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;

public class ToolbarShellHandler {

public static void main(String[] args) {
Display display = new Display();

Shell shell = new Shell();
shell.setLayout(new GridLayout());
Button button = new Button(shell, SWT.PUSH);
button.setText(“Toolbar”);

Text text = new Text(shell, SWT.READ_ONLY);
text.setText(“Shell contents”);

ToolbarShellHandler target = new ToolbarShellHandler();
target.hookHandler(shell);

shell.open();

while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}

private void hookHandler(Shell shell) {
// create a callback that will be invoked when the button is pressed.
final Callback commandCallback = new Callback(this, “toolbarProc”, 3); //$NON-NLS-1$
int commandProc = commandCallback.getAddress();
if (commandProc == 0) {
commandCallback.dispose();
return; // give up
}
shell.getDisplay().disposeExec(new Runnable() {
public void run() {
commandCallback.dispose();
}
});

// add the button to the window trim
int windowHandle = OS.GetControlOwner(shell.handle);
OS.ChangeWindowAttributes(windowHandle, OS.kWindowToolbarButtonAttribute, 0);

int[] mask = new int[] {
OS.kEventClassWindow, OS.kEventWindowToolbarSwitchMode
};
// register the handler with the OS
OS.InstallEventHandler(OS.GetApplicationEventTarget(), commandProc, mask.length / 2, mask, 0, null);
}

public int toolbarProc(int nextHandler, int theEvent, int userData) {
int eventKind = OS.GetEventKind(theEvent);
if (eventKind != OS.kEventWindowToolbarSwitchMode) // only handle the
// toolbar events
return OS.eventNotHandledErr;

int[] theWindow = new int[1];
// get the even window
OS.GetEventParameter(theEvent, OS.kEventParamDirectObject, OS.typeWindowRef, null, 4, null, theWindow);

int[] theRoot = new int[1];
// find the root control for the window
OS.GetRootControl(theWindow[0], theRoot);
Widget widget = Display.getCurrent().findWidget(theRoot[0]);

if (!(widget instanceof Shell)) { // only proceed if its a shell we’ve
// found
return OS.eventNotHandledErr;
}
Shell shellAffected = (Shell) widget; // do cool stuff with the shell

boolean visible = !shellAffected.getChildren()[0].getVisible();
shellAffected.getChildren()[0].setVisible(visible);
return OS.noErr;
}
}

There are two basic steps to the process. First you tweak the application shell to have the correct style bits. This causes the button to appear. This is accomplished by the OS call OS.ChangeWindowAttributes(windowHandle, OS.kWindowToolbarButtonAttribute, 0). Next, you register with the OS a handler that will be called in the event that the kEventClassWindow/
kEventWindowToolbarSwitchMode
Apple events are fired. We do this by creating a Callback class that points at a target object (in this case, the ToolbarShellHandler instance) and is given a method name (“toolbarProc”). When this even is received the Callback object reflectively calls the provided method. In this method is where you do your interesting work. In our example above this method looks for the window control responsible for generating the event, finds its corresponding SWT Shell object, and hides/un-hides the first control as appropriate.

You can use such hacks for more than just the toolbar toggle button, however. Look at the CarbonUIEnhancer class for code that hooks not only the toogle button but also the application preference and about commands.

title pic Link To Editor

Posted by pookzilla on October 27, 2006

Another question from the inbox (paraphrased just a little):

Yo, Kim. What’s the deal with Link To Editor functionality? How do I make my view exhibit this behavior? PS: I love your hat.

In case you aren’t familiar with “Link To Editor”, it is the notion that certain views can repopulate/select content within themselves based on the active editor. You see it often in views such as the Package Explorer and the History View. When an editor is activate the file is selected and scrolled to in the Package Explorer and the History View refreshes and shows new history.

There isn’t really any “deal” with this functionality. Despite its pervasiveness there isn’t any official API support for Link To Editor behavior. In all cases the support is home rolled. Thankfully, it’s pretty easy to accomplish.

Step one, define a “Link To Editor” action in your view. This often appears on the view Toolbar as a toggle action can be added in your views createPartControl method as follows:

public void createPartControl(Composite parent) {
// add interesting controls
getViewSite().getActionBars().getToolBarManager().add(
new Action("Link With Editor", IAction.AS_CHECK_BOX) {
public void run() {
toggleLinking(isChecked());
}
});
}

The toggleLinking method will look something like this:

protected void toggleLinking(boolean checked) {
this.linking = checked;
if (checked)
editorActivated(getSite().getPage().getActiveEditor());
}

This method is pretty straight forward. It preserves the linking state and then if we’re now linking calls editorActivated with the currently active editor (if any). This method does the bulk of our work and is called here to refresh the state of our view based on the active editor. It looks like this:

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null) {
// do something interesting
}
}

The above implementation of editorActivated assumes that our view is a resource view of some kind but that doesn’t have to be the case. You can implement linked views based on any editor input you want. For example, where “// do something interesting” is above you could create a org.eclipse.jface.viewers.StructuredSelection and set your views TreeViewer selection to that object. What the linking itself means is up to your view but the above pattern will establish the behaviour. Also note the eager return at the beginning of the method. If we aren’t currently in linking mode or we’re not visible then we shouldn’t do the linking work. Good citizens of the Eclipse ecosystem are coded to only do work when necessary. If we’re not visible then there’s no need for us to do the heavy work of linking editor content to our own.

We now have a link that is refreshed whenever the action is toggled. To make it useful we’ll need to hook the editorActivated method up to editor activation. First, add the following field to your view class:

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

In the createPartControl method add the following code sometime after the addition of the toggle action:

getSite().getPage().addPartListener(partListener2);

Finally, in the views dispose method add the following:

getSite().getPage().removePartListener(partListener2);

The listener above perform the following actions. When an editor is opened, the editorActivated method is called and our view may be updated (if linking is enabled and it’s visible). Also, if a part is opened and it’s our view then the editorActivated method is called. This refreshes the linked state on startup and delayed opening of our view. Finally, if our part is made visible the linked state is refreshed. This can happen in certain circumstances when plug-in code calls the org.eclipse.ui.IWorkbenchPage.showView method with the IWorkbenchPage.VIEW_VISIBLE flag as an argument. We now have a view that implements remedial Link To Editor behavior. The one thing that this example is missing is the persistence of the link state between restarts. This could be done via preferences or via the org.eclipse.ui.IMemento object passed to the init and saveState methods.

Something I want to point out (that isn’t specific to this example) is the use of abstract inner class listeners on global collections (such as workbench page). In the above example you could easily make the mistake of creating the IPartListener2 implementations in the createPartControl method and neglect to dispose of them. This is a VERY BAD THING. Without the explicit disposal your view will never go away. Even if the user closes the view it will still reside in memory, forever reacting to part events that are no longer relevant to it. Abstract inner classes retain a reference to their host class and without proper disposal the leak of an object of an inner class also leaks the host object. Always take out your trash!

Here is view code in its entirety that simply spits out the name of the current file if Link To Editor is enabled:

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.ViewPart;

public class LinkedWithEditorView extends ViewPart {

private Text text;

private boolean linking;

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart) {
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

public LinkedWithEditorView() { }

public void createPartControl(Composite parent) {
text = new Text(parent, SWT.READ_ONLY);
getViewSite().getActionBars().getToolBarManager().add(
new Action(“Link With Editor”, IAction.AS_CHECK_BOX) {

public void run() {
toggleLinking(isChecked());
}
});

getSite().getPage().addPartListener(partListener2);
}

public void dispose() {
getSite().getPage().removePartListener(partListener2);
}

protected void toggleLinking(boolean checked) {
this.linking = checked;

if (checked)
editorActivated(getSite().getPage().getActiveEditor());

}

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null)
text.setText(file.getLocation().toOSString());

}

public void setFocus() {
text.setFocus();
}
}

Edit: based on feedback I’ve changed the solution to only use one listener instead of two.