Return-Path: <env_7171485-492267985@hermes.sun.com>
Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id NAA21494; Thu, 20 Dec 2001 13:07:51 -0500 (EST)
Received: from hermes.sun.com (hermes.sun.com [64.124.140.169])
	by fort-point-station.mit.edu (8.9.2/8.9.2) with SMTP id NAA22090
	for <alexp@mit.edu>; Thu, 20 Dec 2001 13:07:50 -0500 (EST)
Date: Thu, 20 Dec 2001 18:07:50 GMT+00:00
From: "JDC Tech Tips" <body_7171485-492267985@hermes.sun.com>
To: alexp@mit.edu
Message-Id: <7171485-492267985@hermes.sun.com>
Subject: JDC Tech Tips December 20, 2001
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
X-Mailer: Beyond Email 

 J  D  C    T  E  C  H    T  I  P  S


                      TIPS, TECHNIQUES, AND SAMPLE CODE



WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
December 20, 2001. This issue covers two approaches for
creating modal internal frames:

         * Creating Modal Internal Frames -- Approach 1 
         * Creating Modal Internal Frames -- Approach 2 

These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3 and Java 2 Enterprise Edition, v 1.3. 

This issue of the JDC Tech Tips is written by John Zukowski, 
president of JZ Ventures, Inc. (http://www.jzventures.com).

You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt1220.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
CREATING MODAL INTERNAL FRAMES -- APPROACH 1

The Java Foundation Classes (JFC) Project Swing component set 
provides the JOptionPane component to display simple, standard 
dialogs. A dialog is a window that displays information, but also 
expects a response from the user. For example, a dialog might 
warn the user of a potential problem, and also display an OK
button so that the user can acknowledge the warning. You can 
display a dialog in either a popup window or in an internal frame, 
that is, a window within another window. 

To display a dialog in a popup window, you use one of the 
JOptionPane showXXXDialog methods such as showMessageDialog.
The popup window in this case is modal. This means that the user 
must respond to the popup before the program continues running.
But there's more -- modal also means that the user can't interact 
with other parts of the program. To display a dialog in an 
internal frame, you use one of the showInternalXXXDialog methods
in JOptionPane. These internal frame dialogs are not modal. There 
are times however when you might want a dialog in an internal 
frame to be modal. This Tech Tip will show you how to create 
a modal dialog in an internal frame.

There is limited support for modality in the internal frames 
created by a JOptionPane. To take advantage of this limited 
support, you need to place the internal frame in the glass pane 
of the frame where the desktop pane appears. 

If you've worked with internal frames, you know that normally
you add internal frames, that is, instances of JInternalFrame, to
a desktop pane, that is, an instance of JDesktopPane. A desktop
pane is a layered pane that manages multiple overlapping internal 
frames. A glass pane is part of the root pane that a top-level 
window such as a frame relies on. A root pane is comprised of 
three parts (the glass pane, layered pane, and content pane), and 
an optional fourth part (the menu bar). The content pane contains
the root pane's visible components. The optional menu bar contains
the root pane's menus. And the layered pane positions the contents
of the content pane and the optional menu bar. The glass pane 
is useful in intercepting events that would otherwise pass through 
to the underlying components.

So, to repeat, you can create a somewhat modal dialog in an 
internal frame created by JOptionPane. To do this, you put the 
internal frame in the glass pane of the frame where the desktop 
pane appears. This technique restricts input to only that 
specific frame. The internal frame in this case isn't truly 
modal. To be truly modal, an internal frame needs to block once it 
is shown. If you follow this technique, the internal frame doesn't 
do that. However, the approach does restrict input to the single 
component. 

The first step in this technique is to create a dialog within an
internal frame. To do this, you need to create the JOptionPane, 
and then use one of the createInternalXXX methods to create
and show the necessary message component. For instance, the 
following creates a message dialog within an internal frame:

  JOptionPane optionPane = new JOptionPane();
  optionPane.setMessage("Hello, World");
  optionPane.setMessageType(
    JOptionPane.INFORMATION_MESSAGE);
  JInternalFrame modal = 
    optionPane.createInternalFrame(desktop, "Modal");

The next step is to place the component in the glass pane of the 
window where the desktop pane is located. The glass pane can be 
any component. So, the easiest way to do this is to create a 
transparent JPanel:

  JPanel glass = new JPanel();
  glass.setOpaque(false);
  glass.add(modal);
  frame.setGlassPane(glass);
  glass.setVisible(true);
  modal.setVisible(true);

The last steps are to set up the glass pane to intercept events,
and to hide the glass pane when the internal frame closes. In
order for the glass pane to intercept events, you have to attach 
a MouseListener and MouseMotionListener. To hide the glass pane 
when the internal frame closes, you need to attach an 
InternalFrameListener to the internal frame:

  class ModalAdapter extends InternalFrameAdapter {
    Component glass;

    public ModalAdapter(Component glass) {
      this.glass = glass;

      // Associate dummy mouse listeners
      // Otherwise mouse events pass through
      MouseInputAdapter adapter = new MouseInputAdapter(){};
      glass.addMouseListener(adapter);
      glass.addMouseMotionListener(adapter);
    }

    public void internalFrameClosed(InternalFrameEvent e) {
      glass.setVisible(false);
    }
  }

Here's a program that puts all the pieces together. It creates 
a JDesktopPane with a single internal frame. On that frame is
a button. When that button is pressed, the blocking internal 
frame message dialog appears. While that is visible, you cannot 
press the first button. Once the OK button is pressed on the 
message window, you can then interact with the first internal 
frame.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class Modal  {

  static class ModalAdapter 
      extends InternalFrameAdapter {
    Component glass;

    public ModalAdapter(Component glass) {
      this.glass = glass;

      // Associate dummy mouse listeners
      // Otherwise mouse events pass through
      MouseInputAdapter adapter = 
        new MouseInputAdapter(){};
      glass.addMouseListener(adapter);
      glass.addMouseMotionListener(adapter);
    }

    public void internalFrameClosed(
        InternalFrameEvent e) {
      glass.setVisible(false);
    }
  }

  public static void main(String args[]) {
    final JFrame frame = new JFrame(
      "Modal Internal Frame");
    frame.setDefaultCloseOperation(
      JFrame.EXIT_ON_CLOSE);

    final JDesktopPane desktop = new JDesktopPane();

    ActionListener showModal = 
        new ActionListener() {
      public void actionPerformed(ActionEvent e) {

        // Manually construct a message frame popup
        JOptionPane optionPane = new JOptionPane();
        optionPane.setMessage("Hello, World");
        optionPane.setMessageType(
          JOptionPane.INFORMATION_MESSAGE);
        JInternalFrame modal = optionPane.
          createInternalFrame(desktop, "Modal");

        // create opaque glass pane
        JPanel glass = new JPanel();
        glass.setOpaque(false);

        // Attach modal behavior to frame
        modal.addInternalFrameListener(
          new ModalAdapter(glass));

        // Add modal internal frame to glass pane
        glass.add(modal);

        // Change glass pane to our panel
        frame.setGlassPane(glass);

        // Show glass pane, then modal dialog
        glass.setVisible(true);
        modal.setVisible(true);

        System.out.println("Returns immediately");
      }
    };

    JInternalFrame internal = 
      new JInternalFrame("Opener");
    desktop.add(internal);

    JButton button = new JButton("Open");
    button.addActionListener(showModal);

    Container iContent = internal.getContentPane();
    iContent.add(button, BorderLayout.CENTER);
    internal.setBounds(25, 25, 200, 100);
    internal.setVisible(true);

    Container content = frame.getContentPane();
    content.add(desktop, BorderLayout.CENTER);
    frame.setSize(500, 300);
    frame.setVisible(true);
  }
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CREATING MODAL INTERNAL FRAMES -- APPROACH 2

While the approach shown in the Tech Tip "Creating Modal Internal 
Frames -- Approach 1" provides internal frames that block input 
to other internal frames, the frames are not truly modal. To be
truly modal, an internal frame needs to block once it is shown.
The internal frame in the above tip doesn't do that.

In order to make an internal frame truly modal, you must
take over the event dispatching yourself when the frame is shown. 
This would be in addition to showing the frame in the glass pane. 
You can still use the JOptionPane to create the message and input 
dialogs, but you must also add some behavior that is normally 
handled for you when using one of the showInternalXXX methods. 
Because of the needed custom behavior, and when this custom 
behavior is needed, it is necessary to create a JInternalFrame 
subclass. Doing this also allows you to move into the subclass 
much of the behavior that was done in the prior ActionListener.

Most of the work you need to do in creating a truly modal 
internal frame involves completing the subclass constructor. 
Simply copying the ActionListener code from the previous tip
to the constructor provides a framework in which you can build. 
By passing in the JRootPane, you can use this modal 
JInternalFrame in both a JApplet as well as a JFrame.

  public ModalInternalFrame(String title, 
      JRootPane rootPane, Component desktop, 
      JOptionPane pane) {
    super(title);

    // create opaque glass pane
    final JPanel glass = new JPanel();
    glass.setOpaque(false);

    // Attach mouse listeners
    MouseInputAdapter adapter = 
      new MouseInputAdapter(){};
    glass.addMouseListener(adapter);
    glass.addMouseMotionListener(adapter);

    // Add in option pane
    getContentPane().add(pane, BorderLayout.CENTER);

    // *** Remaining code to be added here ***

    // Add modal internal frame to glass pane
    glass.add(this);

    // Change glass pane to our panel
    rootPane.setGlassPane(glass);

    // Show glass pane, then modal dialog
    glass.setVisible(true);
  }

Notice that the only code not copied from the ActionListener is 
the final setVisible(true) call for the internal frame.

Some of the other tasks that the showInternalXXX methods of 
JOptionPane perform include (1) setting up the closing of the 
dialog once one of the buttons is selected (or input is entered),
and (2) some appearance-related tasks mostly having to do with 
sizing. Because you aren't using the showInternalXXX method, you 
must perform these other tasks yourself.

The way to set up the closing of the internal frame is to attach 
a PropertyChangeListener to the option pane. In the JOptionPane, 
when a button is selected or input is entered, it triggers the 
generation of a PropertyChangeEvent. You can close the frame when 
that event happens. Here's what the code for that behavior looks 
like:

    // Define close behavior
    PropertyChangeListener pcl = 
        new PropertyChangeListener() {
      public void propertyChange(
          PropertyChangeEvent event) {
        if (isVisible() && 
          (event.getPropertyName().equals(
            JOptionPane.VALUE_PROPERTY) ||
           event.getPropertyName().equals(
            JOptionPane.INPUT_VALUE_PROPERTY))) {
          try {
            setClosed(true);
          } catch (PropertyVetoException ignored) {
          }
          ModalInternalFrame.this.setVisible(false);
          glass.setVisible(false);
        }
      }
    };
    pane.addPropertyChangeListener(pcl);

There are three appearance-related tasks that you need to perform. 
Internal frame dialogs are defined to have a different border 
than regular internal frames. So, you need to set a client 
property for the frame. The second task is to initialize the size 
and position of the internal frame. You could just hard-code 
a size (however, the following code centers the frame). The final 
task is to mark the internal frame as the selected one. Here's 
what the code for these appearance-related tasks looks like:

    // Change frame border
    putClientProperty("JInternalFrame.frameType", 
      "optionDialog");

    // Size frame
    Dimension size = getPreferredSize();
    Dimension rootSize = desktop.getSize();

    setBounds((rootSize.width - size.width) / 2,
              (rootSize.height - size.height) / 2,
              size.width, size.height); 
    desktop.validate(); 
    try {
      setSelected(true);
    } catch (PropertyVetoException ignored) {
    }

Adding these two code blocks to the middle of your constructor
completes the initialization of the JInternalFrame subclass.

The final thing you have to do is take over the event dispatching 
after the internal frame is shown.  Normally, event dispatching 
is handled in the EventQueue class. However, because you are 
blocking the event handling thread when you make the internal 
frame modal, the EventQueue will never see events. So you have to 
replace the functionality. 

In order to dispatch events yourself, all you have to do is copy 
the code in the dispatchEvent() method of the EventQueue. If the 
internal frame is made visible from a thread other than the event 
dispatching thread, you don't even have to copy the code in the 
dispatchEvent() method. In that case, all you have to do is 
wait() to block. Then, when the frame is closed, it needs to be 
notified. Here's what the event dispatching code looks like.

  public void setVisible(boolean value) {
    super.setVisible(value);
    if (value) {
      startModal();
    } else {
      stopModal();
    }
  }

  private synchronized void startModal() {
    try {
      if (SwingUtilities.isEventDispatchThread()) {
        EventQueue theQueue = 
          getToolkit().getSystemEventQueue();
        while (isVisible()) {
          AWTEvent event = theQueue.getNextEvent();
          Object source = event.getSource();
          if (event instanceof ActiveEvent) {
            ((ActiveEvent)event).dispatch();
          } else if (source instanceof Component) {
            ((Component)source).dispatchEvent(
              event);
          } else if (source instanceof 
              MenuComponent) {
            ((MenuComponent)source).dispatchEvent(
              event);
          } else {
            System.err.println(
              "Unable to dispatch: " + event);
          }
        }
      } else {
        while (isVisible()) {
          wait();
        }
      }
    } catch (InterruptedException ignored) {
    }
  }

  private synchronized void stopModal() {
    notifyAll();
  }

Here's an example that puts everything together. Instead of 
simply displaying a message dialog, it prompts the user to answer 
a Yes/No question when the modal internal frame is shown. Notice 
that all you have to do after creating the internal frame is show 
it.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.beans.*;

public class ModalInternalFrame extends JInternalFrame {

  public ModalInternalFrame(String title, JRootPane 
      rootPane, Component desktop, JOptionPane pane) {
    super(title);

    // create opaque glass pane
    final JPanel glass = new JPanel();
    glass.setOpaque(false);

    // Attach mouse listeners
    MouseInputAdapter adapter = 
      new MouseInputAdapter(){};
    glass.addMouseListener(adapter);
    glass.addMouseMotionListener(adapter);

    // Add in option pane
    getContentPane().add(pane, BorderLayout.CENTER);

    // Define close behavior
    PropertyChangeListener pcl = 
        new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent 
          event) {
        if (isVisible() && 
          (event.getPropertyName().equals(
            JOptionPane.VALUE_PROPERTY) ||
           event.getPropertyName().equals(
            JOptionPane.INPUT_VALUE_PROPERTY))) {
          try {
            setClosed(true);
          } catch (PropertyVetoException ignored) {
          }
          ModalInternalFrame.this.setVisible(false);
          glass.setVisible(false);
        }
      }
    };
    pane.addPropertyChangeListener(pcl);

    // Change frame border
    putClientProperty("JInternalFrame.frameType",
      "optionDialog");

    // Size frame
    Dimension size = getPreferredSize();
    Dimension rootSize = desktop.getSize();

    setBounds((rootSize.width - size.width) / 2,
              (rootSize.height - size.height) / 2,
               size.width, size.height); 
    desktop.validate(); 
    try {
      setSelected(true);
    } catch (PropertyVetoException ignored) {
    }

    // Add modal internal frame to glass pane
    glass.add(this);

    // Change glass pane to our panel
    rootPane.setGlassPane(glass);

    // Show glass pane, then modal dialog
    glass.setVisible(true);
  }

  public void setVisible(boolean value) {
    super.setVisible(value);
    if (value) {
      startModal();
    } else {
      stopModal();
    }
  }

  private synchronized void startModal() {
    try {
      if (SwingUtilities.isEventDispatchThread()) {
        EventQueue theQueue = 
          getToolkit().getSystemEventQueue();
        while (isVisible()) {
          AWTEvent event = theQueue.getNextEvent();
          Object source = event.getSource();
          if (event instanceof ActiveEvent) {
            ((ActiveEvent)event).dispatch();
          } else if (source instanceof Component) {
            ((Component)source).dispatchEvent(
              event);
          } else if (source instanceof MenuComponent) {
            ((MenuComponent)source).dispatchEvent(
              event);
          } else {
            System.err.println(
              "Unable to dispatch: " + event);
          }
        }
      } else {
        while (isVisible()) {
          wait();
        }
      }
    } catch (InterruptedException ignored) {
    }
  }

  private synchronized void stopModal() {
    notifyAll();
  }

  public static void main(String args[]) {
    final JFrame frame = new JFrame(
      "Modal Internal Frame");
    frame.setDefaultCloseOperation(
      JFrame.EXIT_ON_CLOSE);

    final JDesktopPane desktop = new JDesktopPane();

    ActionListener showModal = 
        new ActionListener() {
      Integer ZERO = new Integer(0);
      Integer ONE = new Integer(1);
      public void actionPerformed(ActionEvent e) {

        // Manually construct an input popup
        JOptionPane optionPane = new JOptionPane(
          "Print?", JOptionPane.QUESTION_MESSAGE, 
          JOptionPane.YES_NO_OPTION);

        // Construct a message internal frame popup
        JInternalFrame modal = 
          new ModalInternalFrame("Really Modal", 
          frame.getRootPane(), desktop, optionPane);

        modal.setVisible(true);

        Object value = optionPane.getValue();
        if (value.equals(ZERO)) {
          System.out.println("Selected Yes");
        } else if (value.equals(ONE)) {
          System.out.println("Selected No");
        } else {
          System.err.println("Input Error"); 
       }
      }
    };

    JInternalFrame internal = 
      new JInternalFrame("Opener");
    desktop.add(internal);

    JButton button = new JButton("Open");
    button.addActionListener(showModal);

    Container iContent = internal.getContentPane();
    iContent.add(button, BorderLayout.CENTER);
    internal.setBounds(25, 25, 200, 100);
    internal.setVisible(true);

    Container content = frame.getContentPane();
    content.add(desktop, BorderLayout.CENTER);
    frame.setSize(500, 300);
    frame.setVisible(true);
  }
}

To learn more about internal frames, see the Java Tutorial lesson 
"How to Use Internal Frames" at 
http://java.sun.com/tutorial/uiswing/components/internalframe.html.

To learn more about the root pane and its glass pane, read the 
Java Tutorial lesson "How to Use Root Panes" at 
http://java.sun.com/tutorial/uiswing/components/rootpane.html.

.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

IMPORTANT: Please read our Terms of Use and Privacy policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/ 

* FEEDBACK
  Comments? Send your feedback on the JDC Tech Tips to: 
  jdc-webmaster@sun.com

* SUBSCRIBE/UNSUBSCRIBE
  - To subscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), choose
    the newsletters you want to subscribe to and click "Update".
  - To unsubscribe, go to the subscriptions page,
    (http://developer.java.sun.com/subscription/), uncheck the
    appropriate checkbox, and click "Update".
  - To use our one-click unsubscribe facility, see the link at 
    the end of this email:
    
- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://java.sun.com/jdc/TechTips/index.html


- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://java.sun.com/jdc/copyright.html


JDC Tech Tips 
December 20, 2001

Sun, Sun Microsystems, Java, and Java Developer Connection are
trademarks or registered trademarks of Sun Microsystems, Inc. 
in the United States and other countries.


To use our one-click unsubscribe facility, select the following URL:
http://sunmail.sun.com/unsubscribe?7171485-492267985
