站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Section 11.1.  Adding the Login Dialog - Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Previous Page
Next Page

11.1. Adding the Login Dialog

The first thing to decide when adding a login prompt to an application is at what point in the application's lifecycle the prompt should actually appear. In this iteration of Hyperbola, we want to be logged in as soon as possibleeven before the first Workbench window is shown. This has a number of advantages: it keeps the rest of the code simple since the connection information is assumed to be available and the connection open; the application can check licensing information; plus, the application can configure itself based on the logged-in user. For example, the name of the logged-in user can be used to determine what the UI looks like, what actions are available, and so on.

The first place that the Hyperbola application gets control is in the Application method run(Object). This method simply creates an SWT Display and opens the Workbench using that Display. We want to prompt after creating the Display but before starting the Workbench. Note that there is no magic herethe run() method is not special. You are free to do as much UI work as you like before creating the Display or starting the Workbench. The only restriction is that you have to create the Display before anything can be drawn.

Note

Think of the Display as SWT's model. When a Display is created, it does not mean that a window or dialog has been opened. The Display is the single point of contact for all UI capabilities of SWT. It provides capabilities such as running the event loop, inter-thread communication, timers, fonts, and colors.


At the end of Chapter 10, you modified the Application's run(Object) method to call login(Session). Now you have to change the login() method to prompt the user instead of automatically connecting with hard-coded connection details. You can write the login dialog yourself or you can use the BasicLoginDialog class from the sample code for this chapter. You may notice another login dialog, LoginDialog, in the code for this chapter. This is the final version of the dialog as it appears at the end of the chapter. We included the BasicLoginDialog to help you get started. If you get the BasicLoginDialog from the code for this chapter, rename it to LoginDialog.

Modify the login(Session) method to open the login dialog as shown below. Notice that whenever a dialog is shown before the first Workbench window is opened, it should not be parented. Here the BasicLoginDialogs constructor takes a null argument. Dialogs without parents are managed by the windowing system, and as such, have an entry in the task bar. This means that users can tell there is a dialog open and can easily bring it to the front if needed.

org.eclipsercp.hyperbola/Application
private boolean login(final Session session) {
  while (session.getConnection() == null ||
      !session.getConnection().isAuthenticated()) {
    LoginDialog loginDialog = new LoginDialog(null);
    if (loginDialog.open() != Window.OK)
      return false;
    session.setConnectionDetails(loginDialog.getConnectionDetails());
    connectWithProgress(session);
  }
  return true;
}

The method login(Session) loops forever, prompting the user for login information and trying to log in using that information. The details of the LoginDialog are not particularly important here. It suffices to say that if the user clicks OK in the dialog, the login code gets the required information and attempts to log in by calling the connectWithProgress(Session) method. If the user clicks Cancel, then the method returns and Hyperbola exits. If the login attempt fails, the user is prompted again.

Progress reporting during login is vital. In particular, during login, you cannot be sure how long it will take to contact the remote server. Users appreciate feedback about the status of the login and expect the opportunity to cancel if it is taking too long.

As part of the refactoring in Chapter 10, a new login method that tracks progress during the initial connection and login was added to Session. The technique for reporting progress is to add an XMPP packet listener to the connection. The Session reports progress on an IProgressMonitor and checks for cancellation as packets are sent.

It's a good habit to ensure that long-running methods have some sort of feedback. In Hyperbola, the only thing we can tell the user is whether he or she is authenticating or receiving the roster from the server. The user can cancel between these operations.

org.eclipsercp.hyperbola/Session
public void connectAndLogin(final IProgressMonitor monitor)
    throws XMPPException {
  PacketListener progressPacketListener = new PacketListener() {
    public void processPacket(Packet packet) {
      if (monitor.isCanceled())
        throw new OperationCanceledException();
      String message = null;
      if (packet instanceof Authentication)
        message = "Authenticating...";
      if (packet instanceof RosterPacket)
        message = "Receiving roster...";
      if (message != null)
        monitor.subTask(message);
    }
  };
  try {
    monitor.beginTask("Connecting...", IProgressMonitor.UNKNOWN);
    String server = connectionDetails.getServer();
    monitor.subTask("Contacting " + server);
    connection = new XMPPConnection(server, 5222);
    connection.addPacketWriterListener(progressPacketListener,
        new OrFilter(new PacketTypeFilter(Authentication.class),
        new PacketTypeFilter(RosterPacket.class)));
    connection.login(connectionDetails.getUserId(),
        connectionDetails.getPassword(),
        connectionDetails.getResource());
  } finally {
    if (connection != null)
        connection.removePacketWriterListener(progressPacketListener);
    monitor.done();
  }
}

The caller of Session.connectAndLogin(IProgressMonitor)the Application, in our caseprovides a monitor and displays the progress to the user. There's an existing dialog called ProgressMonitorDialog that shows progress that can be used for this purpose. The connectWithProgress(Session) method called from the application opens a progress dialog, then connects using the Session, as shown below:

org.eclipsercp.hyperbola/Application
private void connectWithProgress(final Session session) {
  ProgressMonitorDialog progress = new ProgressMonitorDialog(null);
  progress.setCancelable(true);
  try {
    progress.run(true, true, new IRunnableWithProgress() {
      public void run(IProgressMonitor monitor)
          throws InvocationTargetException {
        try {
          session.connectAndLogin(monitor);
        } catch (XMPPException e) {
          throw new InvocationTargetException(e);
        }
      }
    });
  } catch (InvocationTargetException e) {
  } catch (InterruptedException e) {
  }
}

Now when you run Hyperbola, it should appear as shown in Figure 11-1. Enter the user information used in Chapter 10, that is, the user is "reader", the server is "eclipsercp.org", and use "secret" as the password. Click Login and you should once again be able to chat with Eliza. Of course, now you can log into your own XMPP account and chat with your friends if you like.

Figure 11-1. Login dialog


You might have noticed that while the login dialog was up, the Hyperbola splash screen was showing. That seems a bit strange. Splash screens are there to reassure the user that something is happening. Once the Hyperbola application is up and interacting with the user, the splash screen is no longer needed. You can fix that by adding the following code just before calling login(Session) in Application.run(Object):

org.eclipsercp.hyperbola/Application
Platform.endSplash();

Note that the Workbench automatically closes the splash screen, if it is still up, when the startup sequence is completed. Once the splash screen is down, it cannot be redisplayed.

Note

It's actually possible to run code before the application is run. If you need to perform more advanced configuration, such as determining which plug-ins are loaded, see Chapter 26, "OSGi Essentials," for information about starting plug-ins from within the config.ini file using start levels.


11.1.1. Branding the Dialog

The best practice of setting the dialog's parent to null means that it does not inherit the icon set by the Workbench on the top-level window. Actually, there isn't even a Workbench or a top-level window. As a result, the Hyperbola icon is missing from the login dialog and the task bar entry, as shown in Figure 11-2.

Figure 11-2. Task bar and dialog missing the Hyperbola icon


The quick fix for this is to directly access a Hyperbola icon and call Dialog.setImage(Image). Since you've already configured the product icon via the product configuration, a more elegant solution is to use the icon from the Hyperbola product description. This isolates the login dialog from changes to the product's branding.

Every running Eclipse has at most one productthe IProduct returned from Platform.getProduct(). The product contains all of the information provided in the related extension to the org.eclipse.runtime.product extension point. Of course, getProduct() can return null since you can run an Eclipse application directly without having a product.

Most of the information related to a product comes as free-form key/value pairs. Eclipse defines some canonical keys such as the application name, the about text, and so on in org.eclipse.ui.branding.IProductConstants. The IProductConstants.WINDOW_IMAGES property is of particular interest here. The value is a comma-separated list of image paths for different sized images that can be used to represent product windows. Update the LoginDialog method configureShell(Shell) to get the window images and use them for the dialog, as shown in the snippet below:

org.eclipsercp.hyperbola/LoginDialog
protected void configureShell(Shell newShell) {
  super.configureShell(newShell);
  newShell.setText("Hyperbola Login");
  IProduct product = Platform.getProduct();
  if (product != null) {
    String bundleId = product.getDefiningBundle().getSymbolicName();
    String[] imageURLs = HyperbolaUtils.parseCSL(
       product.getProperty(IProductConstants.WINDOW_IMAGES));
    if (imageURLs.length > 0) {
      images = new Image[imageURLs.length];
      for (int i = 0; i < imageURLs.length; i++) {
        ImageDescriptor descriptor =
             AbstractUIPlugin.imageDescriptorFromPlugin(
                 bundleId, imageURLs[i]);
        images[i] = descriptor.createImage(true);
      }
      newShell.setImages(images);
    }
  }
}
public boolean close() {
  if (images != null) {
    for (int i = 0; i < images.length; i++)
      images[i].dispose();
  }
  return super.close();
}

This code is doing more than you think. In particular, the AbstractUIPlugin static method imageDescriptorFromPlugin(String, String) isolates you from the form of the image paths. Contributed image paths can be a fully qualified uniform resource locator (URL) to the image or a relative path from within the declaring product plug-in. Here is an example of a fully qualified window image URL:

org.eclipsercp.hyperbola/plugin.xml
<property
     name="windowImages"
     value="platform:/plugin/org.eclipsercp.hyperbola.ui/icons/alt16.gif"/>

Whereas this example shows the value as a relative image path:

org.eclipsercp.hyperbola/plugin.xml
<property
    name="windowImages"
    value="icons/alt16.gif"/>

Most of the images and icons contributed to UI extension points can take either relative or absolute URLs. This is useful as it allows you to put all your images and branding in one spot and then refer to it from wherever it is needed. Note, however, that if you are forcing or expecting other plug-ins to access your image, this effectively makes the image locations and names APIif you change them, the other plug-ins break.


Previous Page
Next Page