Chapter 16. Desktops and Internal Frames
In this Chapter:
16.1 JDesktopPane and JInternalFrame
16.1.1 JDesktopPane
class javax.swing.JDesktopPane
JDesktopPane
is a powerful extension of JLayeredPane built specifically to manage JInternalFrame children. This is Swing’s version of a multiple document interface, a feature common to most modern operating system desktops. In the last chapter we created our own MDI from scratch. Both our MDI and the JDesktopPane/JInternalFrame prebuilt MDI are quite powerful. This chapter focuses mostly on the latter, but we will relate the discussion of it to our own often.16.1.2 JInternalFrame
class javax.swing.JInternalFrame
The purpose of
JDesktopPane is to provide a specialized container for JInternalFrames. We can access its contents identically to JLayeredPane. There are several additional convenience methods defined in JDesktopPane for accessing JInternalFrame children (see API docs) and attaching a DesktopManager implementation (see below).JInternalFrame
s are very similar to our custom InnerFrames of chapter 15. They can be dragged, resized, iconified, maximized, and closed. JInternalFrame contains a JRootPane as its main container and implements the RootPaneContainer interface. We can access a JInternalFrame’s rootPane and its associated glassPane, contentPane, layeredPane, and menuBar the same way we access them in JFrame and in our InnerFrame.16.1.3 JInternalFrame.JDesktopIcon
class javax.swing.JInternalFrame.JDesktopIcon
This represents a
JInternalFrame in its iconified state. We are warned against using this class as it will disappear in future versions of Swing: "This API should NOT BE USED by Swing applications, as it will go away in future versions of Swing as its functionality is moved into JInternalFrame."API Currently when a JInternalFrame is iconified it is removed from its JDesktopPane and a JDesktopIcon instance is added to represent it. In future versions of Swing JInternalFrame will have JDesktopIcon functionality built into it.16.1.4 DefaultDesktopManager
class javax.swing.DefaultDesktopManager
This is the concrete default implementation of the
DesktopManager interface. An instance of this class is attached to each JDesktopPane if a custom DesktopManager implementation is not specified.16.1.5 The DesktopManager interface
abstract interface javax.swing.DesktopManager
Each
JDesktopPane has a DesktopManager object attached to it whose job it is to manage all operations performed on JInternalFrames within the desktop. DesktopManager methods are automatically called from the associated JDesktopPane when an action is invoked on a JInternalFrame within that desktop. These are usually invoked when the user performs some action on a JInternalFrame with the mouse:activateFrame(JInternalFrame f)
beginDraggingFrame(JComponent f)
beginResizingFrame(JComponent f, int direction)
closeFrame(JInternalFrame f)
deactivateFrame(JInternalFrame f)
deiconifyFrame(JInternalFrame f)
dragFrame(JComponent f, int newX, int newY)
endDraggingFrame(JComponent f)
endResizingFrame(JComponent f)
iconifyFrame(JInternalFrame
f)maximizeFrame(JInternalFrame
f)minimizeFrame(JInternalFrame
f)openFrame(JIntenerlFrame
f)resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight)
setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight)
Note that if we want to manually invoke say, iconification, on a
JInternalFrame we should do the following:myJInternalFrame.getDesktopPane().getDesktopManager().
iconifyFrame(myJInternalFrame);
We could also directly call
setIcon(true) on a JInternalFrame, but we are discouraged from doing so because it is not good practice to bypass the DesktopManager. The reason this is not good practice is that there may be necessary actions defined within the DesktopManager’s iconifyFrame() method that would not be invoked. So, in general, all calls to methods of JInternalFrame that have DesktopManager counterparts s! hould be delegated to the DesktopManager.We have written an animated demo that shows when and how often each
DesktopManager method is called. See \Chapter16\4, and execute the DesktopManagerDemo class. Figure 16.1 illustrates.Figure 16.1 DesktopManager animated demo.
<<file figure16-1.gif>>
16.1.6 The WindowConstnats interface
abstract interface javax.swing.WindowConstants
Refer to chapter 3 for a description of this interface.
Bug Alert: using
DO_NOTHING_ON_CLOSE with setDefaultCloseOperation() on a JInternalFrame does not work as expected. See bug #4176136 at the Java Developer Connection Bug Parade: http://developer.javasoft.com/developer/bugParade/bugs/4176136.html. This will most likely be fixed in the next release of Java 2.To capture the closing of a
JInternalFrame and display a confirmation dialog we can construct the following JInternalFrame subclass:class ConfirmJInternalFrame extends JInternalFrame implements VetoableChangeListener { public ConfirmJInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) { super(title, resizable, closable, maximizable, iconifiable); addVetoableChangeListener(this); } public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException { if (pce.getPropertyName().equals(IS_CLOSED_PROPERTY)) { boolean changed = ((Boolean) pce.getNewValue()).booleanValue(); if (changed) { int confirm = JOptionPane.showOptionDialog(this, "Close " + getTitle() + "?", "Close Confirmation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); if (confirm == 0) { m_desktop.remove(this); m_desktop.repaint(); } else throw new PropertyVetoException("Cancelled",null); } } } }
Using this class in place of
JInternalFrame will always display a confirmation dialog when the close button is pressed. This code checks to see if the closed property has changed from its previous state. This is a constrained property which we can veto if desired (see chapter 2). Luckily this comes in real handy for working around the DO_NOTHING_ON_CLOSE bug.If the confirmation dialog is displayed and then cancelled (i.e.either the "NO"button or the close dialog button is pressed) a
PropertyVetoException is thrown which vetos the property change and the internal frame will not be closed. Figure 16.2 illustrates.Figure 16.2 Handling internal frame closing with a close confirmation dialog.
<<file figure16-2.gif>>
16.1.7 The InternalFrameListener interface
abstract interface javax.swing.event.InternalFrameListener
Each
JInternalFrame can have one or more InternalFrameListeners attached. An InternalFrameListener will receive InternalFrameEvents allowing us to capture and handle them however we like with the following methods:internalFrameActivated(InternalFrameEve
nt e)internalFrameClosed(InternalFrameEvent e)
internalFrameClosing(InternalFrameEvent e)
internalFrameDeactivated(InternalFrameEvent e)
internalFrameDeiconified(InternalFrameEvent e)
internalFrameIconified(InternalFrameEvent e)
internalFrameOpened(InternalFrameEvent e)
InternalFrameListener
and DesktopManager both exist to process changes in a JInternalFrame’s state. However, they can both be used to achieve different ends. DesktopManager allows us to define internal frame handling methods for all JInternalFrames within a given JDesktopPane, whereas InternalFrameListener allows us to define InternalFrameEvent handling unique to each individua! l JInternalFrame. We can attach a different InternalFrameListener implementation to each instance of JInternalFrame, whereas only one DesktopManager implementation can be attached to any instance of JDesktopPane (and thus, each of its children).We have written an animated demo that shows when and how often each
InternalFrameListener method is called. See \Chapter16\5, and execute the InternalFrameListenerDemo class. Figure 16.3 illustrates.Figure 16.3 InternalFrameListener animated demo.
<<file figure16-3.gif>>
16.1.8 InternalFrameEvent
class javax.swing.event.InternalFrameEvent
InternalFrameEvent
s are sent to InternalFrameListeners whenever a JInternalFrame is activated, closed, about to close, deactivated, deiconified, iconified, and opened. The following static int IDs designate which type of action an InternalFrameEvent corresponds to:INTERNAL_FRAME_ACTIVATED
INTERNAL_FRAME_CLOSED
INTERNAL_FRAME_CLOSING
INTERNAL_FRAME_DEACTIVATED
INTERNAL_FRAME_DEICONIFIED
INTERNAL_FRAME_ICONIFIED
INTERNAL_FRAME_OPENED
InternalFrameEvent
extends AWTEvent, and thus encapsultes its source and the associated event ID (retrievable with getSource() and getID() respectively).16.1.9 InternalFrameAdapter
class javax.swing.event.InternalFrameAdapter
This is a concrete implementation of the
InternalFrameListener interface. It is intended to be extended for use by InternalFrameListener implementations that need to define only a subset of the InternalFrameListener methods. All methods defined within this adapter class have empty bodies.16.1.10 Outline dragging mode
JDesktopPane
supports an outline dragging mode to help with JInternalFrame dragging performance bottlenecks. To enable this mode on any JDesktopPane we must set the JDesktopPane.dragMode client property:myDesktopPane.putClientProperty(
"JDesktopPane.dragMode","outline");
Instead of actually moving and painting the frame whenever it is dragged, an XOR’d rectangle is drawn in its place until the drag ends. The example in the next section shows outline dragging mode in action.
16.2 Internalizable/externalizable frames
Most often in Java applets and applications we do not work in full-screen mode. Because of this
JDesktopPanes can often become very cluttered. We may, at some point, want to have the option of bringing an internal frame outside of the desktop. We call this externalizing a frame, for lack of a given name. (Please do not confuse the use of "externalizable" here with Java’s Externalizable interface, an extension of the Serializable interface.) Superfically, externalizing is the process of transforming a JInternalFrame into a JFrame.Now consider an application in which a maximized
JFrame is used. When this maximized frame gains the focus it hides all other existing frames and dialogs behind it. In situations where we need to switch back and forth between frames or dialogs this can be quite annoying. In order to accomodate for this problem we can think of bringing dialogs and frames inside the maximized frame to a JDesktopPane. We call this internalizing a frame. Superficially, internalizing is the process of transforming a JFrame into a JInternalFrame.Figure 16.2 Internalizable/externalizable frames
<<file figure16-2.gif>>
Although there is no real transformation that occurs, this is what appears to happen from the user’s perspective. Internalizing and externalizing is actually achieved by moving the
contentPane from a JFrame to a JInternalFrame and vice versa, respectively. The process is simple to implement:For externalization do the following:
1. Hide a
JInternalFrame with setVisible(false).2. Replace the
contentPane of a hidden JFrame with that of the hidden JInternalFrame.3. Reveal the
JFrame using setVisible(true).Internalization is just the opposite.
Reference: We constructed a small demo application showing how to do this in a Swing Connection ‘Tips and Tricks’ article. See http://java.sun.com/products/jfc/tsc/friends/tips/tips.html
16.3 Cascading and outline dragging mode
You are most likely familiar with the cascading layout that occurs as new windows are opened in MDI environments. In fact, if you have looked at any of the custom MDI examples of chapter 15 you will have seen that when you start each demo the
InnerFrames are arranged in a cascaded fashion. This example shows how to control cascading for an arbitrary number of internal frames. Additionaly, the ability to switch between any pluggable L&F available on your system is added, and outline dragging mode is enabled in our desktop.Figure 16.3 Cascading Internal Frames
<<file figure16-3.gif>>
The Code: CascadeDemo.java
see \Chapter16\1
import java.beans.PropertyVetoException; import javax.swing.*; import java.awt.event.*; import java.awt.*; public class CascadeDemo extends JFrame implements ActionListener { private static ImageIcon EARTH; private int m_count; private int m_tencount; private JButton m_newFrame; private JDesktopPane m_desktop; private JComboBox m_UIBox; private UIManager.LookAndFeelInfo[] m_infos; public CascadeDemo() { super("CascadeDemo"); EARTH = new ImageIcon("earth.jpg"); m_count = m_tencount = 0; m_desktop = new JDesktopPane(); m_desktop.putClientProperty("JDesktopPane.dragMode","outline"); m_newFrame = new JButton("New Frame"); m_newFrame.addActionListener(this); m_infos = UIManager.getInstalledLookAndFeels(); String[] LAFNames = new String[m_infos.length]; for(int i=0; i < m_infos.length; i++) { LAFNames[i] = m_infos[i].getName(); } m_UIBox = new JComboBox(LAFNames); m_UIBox.addActionListener(this); JPanel topPanel = new JPanel(true); topPanel.add(m_newFrame); topPanel.add(new JLabel("Look & Feel:",SwingConstants.RIGHT)); topPanel.add(m_UIBox); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", topPanel); getContentPane().add("Center", m_desktop); setSize(570,400); Dimension dim = getToolkit().getScreenSize(); setLocation(dim.width/2-getWidth()/2, dim.height/2-getHeight()/2); setVisible(true); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(l); } public void newFrame() { JInternalFrame jif = new JInternalFrame("Frame " + m_count, true, true, true, true); jif.setBounds(20*(m_count%10) + m_tencount*80, 20*(m_count%10), 200, 200); JLabel label = new JLabel(EARTH); jif.getContentPane().add(new JScrollPane(label)); m_desktop.add(jif); try { jif.setSelected(true); } catch (PropertyVetoException pve) { System.out.println("Could not select " + jif.getTitle()); } m_count++; if (m_count%10 == 0) { if (m_tencount < 3) m_tencount++; else m_tencount = 0; } } public void actionPerformed(ActionEvent e) { if (e.getSource() == m_newFrame) newFrame(); else if (e.getSource() == m_UIBox) { m_UIBox.hidePopup(); // BUG WORKAROUND try { UIManager.setLookAndFeel(m_infos[m_UIBox.getSelectedIndex()].getClassName()); SwingUtilities.updateComponentTreeUI(this); } catch(Exception ex) { System.out.println("Could not load " + m_infos[m_UIBox.getSelectedIndex()].getClassName()); } } } public static void main(String[] args) { new CascadeDemo(); } }
Understanding the Code
Class CascadeDemo
CascadeDemo
extends JFrame to provide the main container for this example. The constructor is responsible for initializing and laying out all GUI components. One class variable, EARTH, and several instance variables are needed:ImageIcon EARTH
: image used in each JLabel.int m_count
: keeps track of the number of internal frames that exist within the desktop.int m_tencount
: incremented every time ten internal frames are added to the desktop.JButton m_newFrame
: used to add new JInternalFrames to m_desktop.JDesktopPane m_desktop
: container for our JInternalFrames.JComboBox m_UIBox
: used for L&F selection.UIManager.LookAndFeelInfo[] m_infos
: An array of LookAndFeelInfo objects used in changing L&Fs.The only code that may look unfamiliar to you in the constructor is the following:
m_infos = UIManager.getInstalledLookAndFeels(); String[] LAFNames = new String[m_infos.length]; for (int i=0; i<m_infos.length; i++) { LAFNames[i] = m_infos[i].getName(); } m_UIBox = new JComboBox(LAFNames);
The
UIManager class is in charge of keeping track of the current look and feel as well as providing us with a way to query information about the different look and feels available on our system. Its static getInstalledLookAndFeels() method returns an array of UIManager.LookAndFeelInfo objects and we assign this array to m_infos.Each
UIManager.LookAndFeelInfo object represents a different look-and-feel that is currently installed on our system. Its getName() method returns a short name representing its associated look and feel (e.g. "Metal", "CDE/Motif", "Windows", etc.). We create an array of these Strings, LAFNames, with indices corresponding to those of m_infos.Finally we create a
JComboBox, m_UIBox, using this array of Strings. In the actionPerformed() method (see below) when an entry in m_UIBox is selected we match it with its corresponding UIManager.LookAndFeelInfo object in m_infos and load the associated look-and-feel.The
newFrame method is invoked whenever m_NewButton is pressed. First this method creates a new JInternalFrame with resizable, closable, maximizable, and iconifiable properties, and a unique title based on the current frame count:JInternalFrame jif = new JInternalFrame("Frame " + m_count, true, true, true, true);
The frame is then sized to 200 x 200 and its initial position within the our desktop is calculated based on the value of
m_count and m_tencount. The value of m_tencount is periodically reset so that each new internal frame lies within our desktop view (assuming we do not resize our desktop to have a smaller width than the maximum of 20*(m_count%10) + m_tencount*80, and a smaller height than the maximum of 20*(m_count%10). This turns out to be 420 x 180, where the maximum of m_count%10 is 9 and the maximum of m_tencount is 3).jif.setBounds(20*(m_count%10) + m_tencount*80, 20*(m_count%10), 200, 200);
Note: You might imagine a more flexible cascading scheme that positions internal frames based on the current size of the desktop. In general a rigid cascading routine is sufficient, but we are certainly not limited to this.
A
JLabel with an image is added to a JScrollPane, which is then added to the contentPane of each internal frame. Each frame is added to the desktop in layer 0 (the default layer when none is specified). Note that adding an internal frame to the desktop does not automatically place that frame at the frontmost position within the specified layer, and it is not automatically selected. To force both of these things to occur we use the JInternalFrame setSelected() method (which requires us to catch a java.beans.Property! VetoException).Finally the
newFrame() method increments m_count and determines whether to increment m_tencount or reset it to 0. m_tencount is only incremented after a group of 10 frames has been added (m_count%10 == 0) and is only reset after it has reached a value of 3. So 40 internal frames are created for each cycle of m_tencount (10 for m_tencount = 0, 1, 2, and 3).m_count++; if (m_count%10 == 0) { if (m_tencount < 3) m_tencount++; else m_tencount = 0; }
The
actionPerformed() method handles m_newFrame button presses and m_UIBox selections. The m_newFrame button invokes the newFrame() method and selecting a look and feel from m_UIBox changes the application to use that L&F. Look-and-feel switching is done by calling the UIManager setLookAndFeel() method and passing it the classname of the L&F to use (which ! we stored in the m_infos array in the constructor). Calling SwingUtilities.updateComponentTreeUI(this) changes the look and feel of everything contained within the CascadeDemo frame (refer back to chapter 2).
Bug Alert: The call to
m_UIBox.hidePopup() is added to avoid a null pointer exception bug that is caused when changing the look-and-feel of an active JComboBox. We expect this to be fixed in a future Java 2 release.Running the Code:
Figure 16.2 shows
CascadeDemo in action. This figure shows a JInternalFrame in the process of being dragged in outline dragging mode. Try creating plenty of frames to make sure that cascading is working properly. Experiment with different L&Fs. As a final test comment out the m_UIBox.hidePopup() call to check if this bug has been fixed in your version of Java.16.4 An X-like desktop environment
Some X windows systems (specifically fvwm, Panorama (SCO), and TED (TriTeal)) provide what is referred to as a pager. This is a small window that sits in the desktop (usually at the top of the screen) and shows the positions of all windows contained in the desktop. By clicking or dragging the mouse inside the pager the user’s view is moved to the associated location within the desktop. This is very helpful to X windows users because these systems often support very large desktops. Often they are larger than four times the actual size of the screen. Figure 16.4 shows a pager running on a Linux system with a desktop nine times the size of the screen.
Figure 16.4 A Linux pager
<<file figure16-4.gif>>
In this example we develop our own partial implementation of a pager for use with
JDesktopPane and its JInternalFrame children. We show it in use with a fairly large JDesktopPane (1600x1200). This desktop is scrollable and the pager will always stay in the user’s current view, even when scrolling.
Note: In this example we use a custom package called
resize. The classes contained in this package were introduced in chapter 15 as our XXResizeEdge components. Refer back to chapter 15 if you have any questions regarding this code. We do not explain how it works in this chapter. However, you should know that the classes contained in this package can be wrapped around any JComponent in a BorderLayout to make that component resizable. The thickness and mininum dimension properties associated with each class have been hard-coded as constants. If you plan to work with these classes we suggest adding a pair of set()/get() accessors to modify and retrieve these values.The main purpose of presenting this example here is to show how
DesktopManager can be customized to our needs. In the next section we will expand on this example to build a networked, multi-user desktop environment.Figure 16.5 JavaXWin with WindowWatcher
<<file figure16-5.gif>>
Figure 16.6 WindowWatcher with XXResizeEdges
<<file figure16-6.gif>>
The Code: JavaXWin.java
see \Chapter16\2
import java.beans.PropertyVetoException; import javax.swing.*; import java.awt.event.*; import java.io.*; import java.awt.*; public class JavaXWin extends JFrame { protected int m_count; protected int m_tencount; protected int m_wmX, m_wmY; protected JButton m_newFrame; protected JDesktopPane m_desktop; protected WindowManager m_wm; protected JViewport viewport; public JavaXWin() { setTitle("JavaXWin"); m_count = m_tencount = 0; m_desktop = new JDesktopPane(); JScrollPane scroller = new JScrollPane(); m_wm = new WindowManager(m_desktop); m_desktop.setDesktopManager(m_wm); m_desktop.add(m_wm.getWindowWatcher(), JLayeredPane.PALETTE_LAYER); m_wm.getWindowWatcher().setBounds(555,5,200,150); viewport = new JViewport() { public void setViewPosition(Point p) { super.setViewPosition(p); m_wm.getWindowWatcher().setLocation( m_wm.getWindowWatcher().getX() + (getViewPosition().x-m_wmX), m_wm.getWindowWatcher().getY() + (getViewPosition().y-m_wmY)); m_wmX = getViewPosition().x; m_wmY = getViewPosition().y; } }; viewport.setView(m_desktop); scroller.setViewport(viewport); ComponentAdapter ca = new ComponentAdapter() { JViewport view = viewport; public void componentResized(ComponentEvent e) { m_wm.getWindowWatcher().setLocation( view.getViewPosition().x + view.getWidth()- m_wm.getWindowWatcher().getWidth()-15, view.getViewPosition().y + 5); } }; viewport.addComponentListener(ca); m_newFrame = new JButton("New Frame"); m_newFrame.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { newFrame(); } }); JPanel topPanel = new JPanel(true); topPanel.setLayout(new FlowLayout()); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", topPanel); getContentPane().add("Center", scroller); topPanel.add(m_newFrame); Dimension dim = getToolkit().getScreenSize(); setSize(800,600); setLocation(dim.width/2-getWidth()/2, dim.height/2-getHeight()/2); m_desktop.setPreferredSize(new Dimension(1600,1200)); setVisible(true); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(l); } public void newFrame() { JInternalFrame jif = new JInternalFrame("Frame " + m_count, true, true, true, true); jif.setBounds(20*(m_count%10) + m_tencount*80, 20*(m_count%10), 200, 200); JTextArea text = new JTextArea(); JScrollPane scroller = new JScrollPane(); scroller.getViewport().add(text); try { FileReader fileStream = new FileReader("JavaLinux.txt"); text.read(fileStream, "JavaLinux.txt"); } catch (Exception e) { text.setText("* Could not read JavaLinux.txt *"); } jif.getContentPane().add(scroller); m_desktop.add(jif); try { jif.setSelected(true); } catch (PropertyVetoException pve) { System.out.println("Could not select " + jif.getTitle()); } m_count++; if (m_count%10 == 0) { if (m_tencount < 3) m_tencount++; else m_tencount = 0; } } public static void main(String[] args) { new JavaXWin(); } } class WindowManager extends DefaultDesktopManager { protected WindowWatcher ww; public WindowManager(JDesktopPane desktop) { ww = new WindowWatcher(desktop); } public WindowWatcher getWindowWatcher() { return ww; } public void activateFrame(JInternalFrame f) { super.activateFrame(f); ww.repaint(); } public void beginDraggingFrame(JComponent f) { super.beginDraggingFrame(f); ww.repaint(); } public void beginResizingFrame(JComponent f, int direction) { super.beginResizingFrame(f,direction); ww.repaint(); } public void closeFrame(JInternalFrame f) { super.closeFrame(f); ww.repaint(); } public void deactivateFrame(JInternalFrame f) { super.deactivateFrame(f); ww.repaint(); } public void deiconifyFrame(JInternalFrame f) { super.deiconifyFrame(f); ww.repaint(); } public void dragFrame(JComponent f, int newX, int newY) { f.setLocation(newX, newY); ww.repaint(); } public void endDraggingFrame(JComponent f) { super.endDraggingFrame(f); ww.repaint(); } public void endResizingFrame(JComponent f) { super.endResizingFrame(f); ww.repaint(); } public void iconifyFrame(JInternalFrame f) { super.iconifyFrame(f); ww.repaint(); } public void maximizeFrame(JInternalFrame f) { super.maximizeFrame(f); ww.repaint(); } public void minimizeFrame(JInternalFrame f) { super.minimizeFrame(f); ww.repaint(); } public void openFrame(JInternalFrame f) { super.openFrame(f); ww.repaint(); } public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) { f.setBounds(newX, newY, newWidth, newHeight); ww.repaint(); } public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) { f.setBounds(newX, newY, newWidth, newHeight); ww.repaint(); } }
The Code: WindowWatcher.java
see \Chapter16\2
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import resize.*; public class WindowWatcher extends JPanel { protected static final Color C_UNSELECTED = new Color(123, 123, 123); protected static final Color C_SELECTED = new Color(243, 232, 165); protected static final Color C_BACKGROUND = new Color(5,165,165); protected static final Color C_WWATCHER = new Color(203,226,0); protected float m_widthratio, m_heightratio; protected int m_width, m_height, m_XDifference, m_YDifference; protected JDesktopPane m_desktop; protected NorthResizeEdge m_northResizer; protected SouthResizeEdge m_southResizer; protected EastResizeEdge m_eastResizer; protected WestResizeEdge m_westResizer; public WindowWatcher(JDesktopPane desktop) { m_desktop = desktop; setOpaque(true); m_northResizer = new NorthResizeEdge(this); m_southResizer = new SouthResizeEdge(this); m_eastResizer = new EastResizeEdge(this); m_westResizer = new WestResizeEdge(this); setLayout(new BorderLayout()); add(m_northResizer, BorderLayout.NORTH); add(m_southResizer, BorderLayout.SOUTH); add(m_eastResizer, BorderLayout.EAST); add(m_westResizer, BorderLayout.WEST); MouseInputAdapter ma = new MouseInputAdapter() { public void mousePressed(MouseEvent e) { m_XDifference = e.getX(); m_YDifference = e.getY(); } public void mouseDragged(MouseEvent e) { int vx = 0; int vy = 0; if (m_desktop.getParent() instanceof JViewport) { vx = ((JViewport) m_desktop.getParent()).getViewPosition().x; vy = ((JViewport) m_desktop.getParent()).getViewPosition().y; } int w = m_desktop.getParent().getWidth(); int h = m_desktop.getParent().getHeight(); int x = getX(); int y = getY(); int ex = e.getX(); int ey = e.getY(); if ((ey + y > vy && ey + y < h+vy) && (ex + x > vx && ex + x < w+vx)) { setLocation(ex-m_XDifference + x, ey-m_YDifference + y); } else if (!(ey + y > vy && ey + y < h+vy) && (ex + x > vx && ex + x < w+vx)) { if (!(ey + y > vy) && ey + y < h+vy) setLocation(ex-m_XDifference + x, vy-m_YDifference); else if (ey + y > vy && !(ey + y < h+vy)) setLocation(ex-m_XDifference + x, (h+vy)-m_YDifference); } else if ((ey + y >vy && ey + y < h+vy) && !(ex + x > vx && ex + x < w+vx)) { if (!(ex + x > vx) && ex + x < w+vx) setLocation(vx-m_XDifference, ey-m_YDifference + y); else if (ex + x > vx && !(ex + x < w)) setLocation((w+vx)-m_XDifference, ey-m_YDifference + y); } else if (!(ey + y > vy) && ey + y < h+vy && !(ex + x > vx) && ex + x < w+vx) setLocation(vx-m_XDifference, vy-m_YDifference); else if (!(ey + y > vy) && ey + y < h+vy && ex + x > vx && !(ex + x < w+vx)) setLocation((w+vx)-m_XDifference, vy-m_YDifference); else if (ey + y > vy && !(ey + y < h+vy) && !(ex + x > vx) && ex + x < w+vx) setLocation(vx-m_XDifference, (h+vy)-m_YDifference); else if (ey + y > vy && !(ey + y < h+vy) && ex + x > vx && !(ex + x < w+vx)) setLocation((w+vx)-m_XDifference, (h+vy)-m_YDifference); } public void mouseEntered(MouseEvent e) { setCursor(Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR)); } public void mouseExited(MouseEvent e) { setCursor(Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR)); } }; addMouseListener(ma); addMouseMotionListener(ma); } public void paintComponent(Graphics g) { super.paintComponent(g); m_height = getHeight(); m_width = getWidth(); g.setColor(C_BACKGROUND); g.fillRect(0,0,m_width,m_height); Component[] components = m_desktop.getComponents(); m_widthratio = ((float) m_desktop.getWidth())/((float) m_width); m_heightratio = ((float) m_desktop.getHeight())/((float) m_height); for (int i=components.length-1; i>-1; i--) { if (components[i].isVisible()) { g.setColor(C_UNSELECTED); if (components[i] instanceof JInternalFrame) { if (((JInternalFrame) components[i]).isSelected()) g.setColor(C_SELECTED); } else if(components[i] instanceof WindowWatcher) g.setColor(C_WWATCHER); g.fillRect( (int)(((float)components[i].getX())/m_widthratio), (int)(((float)components[i].getY())/m_heightratio), (int)(((float)components[i].getWidth())/m_widthratio), (int)(((float)components[i].getHeight())/m_heightratio)); g.setColor(Color.black); g.drawRect( (int)(((float)components[i].getX())/m_widthratio), (int)(((float)components[i].getY())/m_heightratio), (int)(((float)components[i].getWidth())/m_widthratio), (int)(((float)components[i].getHeight())/m_heightratio)); } } g.drawLine(m_width/2,0,m_width/2,m_height); g.drawLine(0,m_height/2,m_width,m_height/2); } }
Understanding the Code
Class JavaXWin
JavaXWin
extends JFrame and provides the main container for this example. Several instance variables are needed:int m_count
, int m_tencount: used for cascadingJButton m_newFrame
: used to create new frames.JDesktopPane m_desktop
: our desktop pane.int m_wmX
: keeps track of the most recent x coordinate of the desktop scrollpane’s view position.int m_wmY
: keeps track of the most recent y coordinate of the desktop scrollpane’s view position.WindowManager m_wm
: our custom DesktopManager implementation that updates WindowWatcher whenever any of its methods are called.JViewport viewport
: The viewport of the scrollpane that will contain our desktop.In the
JavaXWin constructor we create a new JDesktopPane and place it inside a JScrollPane. Then we create a new WindowManager (see below) and pass it a reference to our desktop. We then tell our desktop that this WindowManager is a DesktopManager implementation and it should be used to manage our internal frames. This is done with JDesktopPane’s setDesktopManager() method. We then place our WindowManager’s WindowWatcher in our desktop’s PALETTE_LAYER. This will guarantee that it is always displayed over all internal frames:m_wm = new WindowManager(m_desktop);
m_desktop.setDesktopManager(m_wm);
m_desktop.add(m_wm.getWindowWatcher(),
JLayeredPane.PALETTE_LAYER);
A custom
JViewport is constructed with an overriden setViewPosition() method. This method is responsible for keeping our WindowWatcher in the same place as we scroll the desktop. Each time the view is changed we reposition the WindowWatcher to give the impression that it lies completely above our desktop and is unaffected by scrolling. Basically, this code just computes the difference between the current and most recent viewport position, and adds this difference to the coordinates of the WindowWatcher. We then use this JViewport as the viewport for the JScrollPane our desktop is contained in using JScrollPane’s setView() method.Next we construct a
ComponentAdapter and attach it to our viewport. We override its componentResized() method to move WindowWatcher to the topwhenever the viewport is resized. This is done so that WindowWatcher will never disappear from our view when the application is resized.The
newFrame()
method is almost identical to that of CascadeDemo. The only difference is that we place a JTextArea in each internal frame and load a text file into it (the original press release from Sun announcing a Linux port of JDK1.2!)
Class WindowManager
The
WindowManager class is a simple extension of DefaultDesktopManager which overrides all JInternalFrame related methods. Only one instance variable is necessary:WindowWatcher ww
: our custom pager component.Each of the methods overriden from
DefaultDesktopManager call their superclass counterparts by using super, and then call repaint on ww. So each time the user performs an action on an internal frame, WindowManager basically just tells our WidowWatcher to repaint itself.The
WindowManager constructor takes a reference to the desktop it manages, and in turn passes this reference to the WindowWatcher constructor. WindowWatcher uses this reference to find out all the information it needs to know about our desktop’s contents to paint itself correctly (see below).The
getWindowWatcher() method just returns a reference to the WindowWatcher object, ww, and is used when the desktop is scrolled as discussed above.Class WindowWatcher
This class is our version of an X windows pager. It uses the
XXResizeEdge components in our custom resize package to allow full resizability. Four class variables are necessary:Color C_UNSELECTED
: used to represent all components but selected JInternalFrames and WindowWatcher itself.Color C_SELECTED
: used to represent selected JInternalFrames.Color C_BACKGROUND
: used for the WindowWatcher background.Color C_WWATCHER
: used to represent the WindowWatcher itself.Instance variables:
float m_widthratio
: Keeps the ratio of desktop width to WindowWatcher width.float m_heightratio
: Keeps the ratio of desktop height to WindowWatcher height.int m_width
: The current WindowWatcher width.int m_height
: The current WindowWatcher height.int m_XDifference
: Used for dragging the WindowWatcher horizontally.int m_YDifference
: Used for dragging the WindowWatcher vertically.NorthResizeEdge m_northResizer
: north resize componentSouthResizeEdge m_southResizer
: south resize componentEastResizeEdge m_eastResizer
: east resize componentWestResizeEdge m_westResizer
: west resize componentJDesktopPane m_desktop
: Reference to the desktop the WindowWatcher is watching over.The constructor is passed a
JDesktopPane reference which is assigned to m_desktop. We use a BorderLayout for this component and add instances of our resize package’s XXResizeEdge classes to each outer region, allowing WindowWatcher to be fully resizable.
Note: See the
resize package source code for details about these components. They were introduced and discussed in chapter 15. We encourage you to add more accessors to these classes to allow such things as setting thickness and color.We then construct custom
MouseInputAdapter. This adapter overrides the mousePressed(), mouseDragged(), mouseEntered(), and mouseExited() events. The mousePressed() method stores the location of the mouse press in our m_XDifference and m_YDifference class variables. These are used in the mouseDragged() method to allow WindowWatcher to be continuously dragged from any point within its bounds.The
mouseDragged() method allows the user to drag WindowManager anywhere within the visible region of the desktop. In order to enforce this and still allow smooth movement we need handle many different cases depending on mouse position and, possibly, the current JViewport position that the desktop is contained within. Note that we do not assume that WindowWatcher and its associated desktop are contained within a JViewport. However, in such a case we have to handle WindowWatcher‘s movement differently.
Reference: The
mouseDragged code is a straight-forward adaptation of the code we used to control dragging our InnerFrames in chapter 15. See section 15.5.The
mouseEntered() method just changes the cursor to MOVE_CURSOR and mouseExited changes the cursor back to DEFAULT_CURSOR.Finally we add this adapter with both
addMouseListener() and addMouseMotionListener(). (Note that MouseInputAdapter implements both of the MouseListener and MouseMotionListener interfaces.)The
paintComponent() method starts by filling the background, getting the current dimensions, and retrieving an array of components contained in the desktop. The ratios of desktop size to WindowWatcher size are computed and then we enter a loop which is executed for each component in the array. This loop starts by setting the color to C_UNSELECTED. We then check if the component under consideration is a JInternalFrame. If it is we check if it is selected. If it is selected we set the current color to C_SELECTED. If it the component is not a JInt! ernalFrame we check if it is the WindowWatcher itself. If so we set the current color to C_WWATCHER.for (int i=components.length-1; i>-1; i--) { if (components[i].isVisible()) { g.setColor(C_UNSELECTED); if (components[i] instanceof JInternalFrame) { if (((JInternalFrame) components[i]).isSelected()) g.setColor(C_SELECTED); } else if(components[i] instanceof WindowWatcher) g.setColor(C_WWATCHER); g.fillRect((int) (((float) . . . } }
Once the color is selected we paint a filled, scaled rectangle representing that component. We scale this rectangle based on the ratios we computed earlier, making sure to use
floats to avoid otherwise large rounding errors. We then paint a black outline around this rectangle and move on to the next component in our array until it has been exhausted. Note that we cycle through this array from the highest index down to 0 so that the rectangles are painted in the same order that the components appear in the JDesktopPane (the appearance of layering is consistent).Running the code:
Figure 16.5 shows
JavaXWin in action and figure 16.6 is a snapshot of the WindowWatcher itself. Try moving frames around and resizing them. Note that WindowWatcher smoothly captures and displays each component as it changes position and size. Try moving WindowWatcher and note that you cannot move it outside the visible region of the desktop. Now try scrolling to a different position within the desktop and note that WindowWatcher follows us and remains in the same position within our view. Also note that WindowWatcher can be resized because we’ve taken advantage of the classes in our custom resize package. In the next example we will build on top of JavaXWin and WindowManager to construct a multi-user, networked desktop environment.WindowWatcher
does not fully implement the functionality of most pagers. Usually clicking on an area of the pager repositions the view of our desktop. This may be an interesting and useful feature to implement in WindowWatcher.16.5 A basic multi-user desktop environment using sockets
Collaborative environments are becoming more commonplace as the internet flourishes. They will no doubt continue to grow in popularity. Imagine a class taught using an interactive whiteboard or a set of whiteboards each contained in an internal frame.
In this section we show how to construct a basic multi-user
JDesktopPane using sockets. We support a server and only one client. Both the server and client-side users can move, resize, and close frames, as well as chat in a console window. (We only allow the client to create frames.) All JInternalFrame actions invoked by one user are sent to the other user’s desktop using a lightweight message-passing scheme. What we end up with is the beginnings of a true multi-user desktop environment.
Note: We’ve tested this environment between South Carolina and New York with satisfatory response times (using 28.8 modems dialed in to typical ISPs).
Before we present the code, it is helpful here to briefly summarize the network-centric classes that this example takes advantage of (see the API docs or the Java tutorial for more thorough coverage).
16.5.1 Socket
class java.net.Socket
A
Socket is a connection to a single remote machine. Each Socket has an InputStream and an OutputStream associated with it. Data can be sent to the remote machine by writing to the OutputStream and data is retrieved from the remote machine via the InputStream. Each Socket also has an InetAdress instance associated with ! it which encapsulates the IP address that it is connected to.16.5.2 SocketServer
class java.net.SocketServer
A
SocketServer is used to establish Socket-to-Socket connections. Usually a SocketServer calls its accept() method to wait for a client to connect. Once a client connects a ServerSocket can return a Socket that the host machine uses for communication with that client.16.5.3 InetAddress
class java.net.InetAddress
Encapsulates information about an IP address, such as the host name and the address.
This example works by sending messages back and forth between the client and server. Each message is received and processed identically on each end. Two types of messages can be sent:
Chat messages: messages of this type can be of any length and always begin with "cc".
Internal frame messages: messages of this type are always 29 characters long, are represeted only by numeric characters, and have a distinct six field structure.
Figure 16.7 illustrates our internal frame message structure.
Figure 16.7 Internal frame message structure
<<file figure16-7.gif>>
ID
represents the WindowManager method to invoke.TAG is a unique identifier returned by each internal frame’s overridden
toString() method.new X is the new x coordinate of the internal frame (if applicable).
new X is the new y coordinate of the internal frame (if applicable).
new width is the new width of the internal frame (if applicable).
new height is the new height of the internal frame (if applicable).
We will discuss how and when both types of messages are constructed and interpreted after we present the code. The server is implemented as class
JavaXServer, the client as JavaXClient. JavaXClient was largely built from JavaXServer. We have highlighted the changes below, and inserted comments to denote where code has been modifed or unchanged.The
WindowManager class from the last section has been completely rebuilt and defined in a separate class file. The WindowWatcher class remains unchanged, using the XXResizeEdge classes in our resize package to allow full resizability. Both JavaXServer and JavaXClient use instances of the new WindowManager to manage their desktop, send messages, and interpret calls invoked by their message receiving mechanism (the processMessage() method).Figure 16.8 JavaXClient with established connection
<<file figure16-8.gif>>
Figure 16.9 JavaXServer with established connection
<<file figure16-9.gif>>
The Code: JavaXServer.java
see \Chapter16\3
import java.beans.PropertyVetoException; import javax.swing.*; import java.awt.event.*; import java.io.*; import java.awt.*; import java.net.*; public class JavaXServer extends JFrame implements Runnable { protected int m_count; protected int m_tencount; protected int m_wmX, m_wmY; protected JDesktopPane m_desktop; protected WindowManager m_wm; protected JViewport viewport; protected JTextArea m_consoletext, m_consolechat; protected JTextField m_chatText; protected boolean m_connected; protected JLabel m_status; protected DataInputStream m_input; protected DataOutputStream m_output; protected Socket m_client; protected ServerSocket m_server; protected Thread m_listenThread; protected ConThread m_conthread; public JavaXServer() { setTitle("JavaX Server"); m_count = m_tencount = 0; m_desktop = new JDesktopPane(); m_status = new JLabel("No Client"); JScrollPane scroller = new JScrollPane(); m_wm = new WindowManager(m_desktop); m_desktop.setDesktopManager(m_wm); m_desktop.add(m_wm.getWindowWatcher(), JLayeredPane.PALETTE_LAYER); m_wm.getWindowWatcher().setBounds(555,5,100,100); viewport = new JViewport() { public void setViewPosition(Point p) { super.setViewPosition(p); m_wm.getWindowWatcher().setLocation( m_wm.getWindowWatcher().getX() + (getViewPosition().x-m_wmX), m_wm.getWindowWatcher().getY() + (getViewPosition().y-m_wmY)); m_wmX = getViewPosition().x; m_wmY = getViewPosition().y; } }; viewport.setView(m_desktop); scroller.setViewport(viewport); ComponentAdapter ca = new ComponentAdapter() { JViewport view = viewport; public void componentResized(ComponentEvent e) { m_wm.getWindowWatcher().setLocation( view.getViewPosition().x + view.getWidth()- m_wm.getWindowWatcher().getWidth()-15, view.getViewPosition().y + 5); } }; viewport.addComponentListener(ca); getContentPane().setLayout(new BorderLayout()); getContentPane().add("Center", scroller); getContentPane().add("South", m_status); setupConsole(); Dimension dim = getToolkit().getScreenSize(); setSize(800,600); setLocation(dim.width/2-getWidth()/2, dim.height/2-getHeight()/2); m_desktop.setPreferredSize(new Dimension(1600,1200)); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(l); setVisible(true); } public void setupConsole() { JInternalFrame console = new JInternalFrame( "JavaX Server Console", false, false, false, false) { int TAG = m_count; public String toString() { return "" + TAG; } }; m_count++; console.setBounds(20, 20, 500, 300); JPanel chatPanel = new JPanel(); JLabel chatLabel = new JLabel(" Chat"); chatPanel.setLayout(new BorderLayout()); m_consoletext = new JTextArea(); m_consoletext.setPreferredSize(new Dimension(500,50)); m_consoletext.setLineWrap(true); m_consoletext.setText("Server Started." + "\nWaiting for client..."); m_consoletext.setEditable(false); m_consolechat = new JTextArea(); m_consolechat.setLineWrap(true); m_consolechat.setEditable(false); m_chatText = new JTextField(); m_chatText.addActionListener(new ChatAdapter()); JButton chatSend = new JButton("Send"); chatSend.addActionListener(new ChatAdapter()); JPanel sendPanel = new JPanel(); sendPanel.setLayout(new BorderLayout()); sendPanel.add("Center", m_chatText); sendPanel.add("West", chatSend); JScrollPane cscroller1 = new JScrollPane(m_consoletext); JScrollPane cscroller2 = new JScrollPane(m_consolechat); chatPanel.add("North", chatLabel); chatPanel.add("Center", cscroller2); chatPanel.add("South", sendPanel); JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, cscroller1, chatPanel); console.getContentPane().add(splitter); m_desktop.add(console); m_wm.getWindowWatcher().repaint(); try { m_server = new ServerSocket(5000,500); } catch (IOException e) { m_consoletext.append("\n" + e); } m_conthread = new ConThread(); } public void run() { while (m_connected) { try { processMessage(m_input.readUTF()); } catch (IOException e) { m_consoletext.append("\n" + e); m_connected = false; } } } public void newFrame() { JInternalFrame jif = new JInternalFrame("Frame " + m_count, true, true, false, false) { int TAG = m_count; public String toString() { return "" + TAG; } }; jif.setBounds(20*(m_count%10) + m_tencount*80, 20*(m_count%10), 200, 200); m_desktop.add(jif); try { jif.setSelected(true); } catch (PropertyVetoException pve) { System.out.println("Could not select " + jif.getTitle()); } m_count++; if (m_count%10 == 0) { if (m_tencount < 3) m_tencount++; else m_tencount = 0; } } public void processMessage(String s) { if (s.startsWith("cc")) { m_consolechat.append("CLIENT: " + s.substring(2) + "\n"); m_consolechat.setCaretPosition( m_consolechat.getText().length()); } else { int id = (Integer.valueOf(s.substring(0,2))).intValue(); m_wm.setPropagate(false); if (id == 16) { newFrame(); } else { Component[] components = m_desktop.getComponentsInLayer(0); int index = 0; int tag = (Integer.valueOf(s.substring(2,5))).intValue(); int param1 = (Integer.valueOf(s.substring(5,11))).intValue(); int param2 = (Integer.valueOf(s.substring(11,17))).intValue(); int param3 = (Integer.valueOf(s.substring(17,23))).intValue(); int param4 = (Integer.valueOf(s.substring(23))).intValue(); boolean found = false; for (int i=components.length-1; i>-1;i--) { if (components[i] instanceof JInternalFrame) { if (Integer.valueOf( components[i].toString()).intValue() == tag) { try { ((JInternalFrame) components[i]).setSelected(true); ((JInternalFrame) components[i]).toFront(); index = i; found = true; break; } catch (PropertyVetoException pve) { System.out.println( "Could not select JInternalFrame with tag " + tag); } } } } if (found == false) return; switch (id) { case 1: m_wm.activateFrame((JInternalFrame) components[index]); break; case 2: m_wm.beginDraggingFrame((JComponent) components[index]); break; case 3: m_wm.beginResizingFrame( (JComponent) components[index], param1); break; case 4: m_wm.closeFrame((JInternalFrame) components[index]); break; // case 5: not implemented // case 6: not implemented case 7: m_wm.dragFrame( (JComponent)components[index], param1, param2); break; case 8: m_wm.endDraggingFrame((JComponent) components[index]); break; case 9: m_wm.endResizingFrame((JComponent) components[index]); break; // case 10: not implemented // case 11: not implemented // case 12: not implemented case 13: m_wm.openFrame((JInternalFrame) components[index]); break; case 14: m_wm.resizeFrame( (JComponent) components[index], param1, param2, param3, param4); break; case 15: m_wm.setBoundsForFrame( (JComponent) components[index], param1, param2, param3, param4); break; } } m_wm.setPropagate(true); } m_desktop.repaint(); } public static void main(String[] args) { new JavaXServer(); } class ChatAdapter implements ActionListener { public void actionPerformed(ActionEvent e) { m_wm.sendMessage("cc" + m_chatText.getText()); m_consolechat.append("SERVER: " + m_chatText.getText() + "\n"); m_chatText.setText(""); } } class ConThread extends Thread { ConThread() { start(); } public void run() { while(true) { try { m_client = m_server.accept(); m_connected = true; m_input = new DataInputStream(m_client.getInputStream()); m_output = new DataOutputStream( m_client.getOutputStream()); m_wm.setOutputStream(m_output); m_consoletext.append("\nStreams established..."); m_listenThread = new Thread(JavaXServer.this); m_listenThread.start(); m_status.setText("Client Connected from: " + m_client.getInetAddress()); m_consoletext.append("\nClient Connected from: " + m_client.getInetAddress()); } catch (Exception ex) { m_consoletext.append("\n" + ex); } } } } }
The Code: WindowManager.java
see \Chapter16\3
import javax.swing.*; import java.io.*; import java.awt.*; public class WindowManager extends DefaultDesktopManager { //ID 16 means new Frame protected static final int ACIVATE_ID = 1; protected static final int BEGINDRAG_ID = 2; protected static final int BEGINRESIZE_ID = 3; protected static final int CLOSE_ID = 4; protected static final int DEACTIVATE_ID = 5; protected static final int DEICONIFY_ID = 6; protected static final int DRAG_ID = 7; protected static final int ENDDRAG_ID = 8; protected static final int ENDRESIZE_ID = 9; protected static final int ICONIFY_ID = 10; protected static final int MAXIMIZE_ID = 11; protected static final int MINIMIZE_ID = 12; protected static final int OPEN_ID = 13; protected static final int RESIZE_ID = 14; protected static final int SETBOUNDS_ID = 15; protected WindowWatcher ww; protected DataOutputStream m_output; protected JDesktopPane m_desktop; protected boolean m_prop; public WindowManager(JDesktopPane desktop) { m_desktop = desktop; m_prop = true; ww = new WindowWatcher(desktop); } public WindowWatcher getWindowWatcher() { return ww; } public void setOutputStream(DataOutputStream output) { m_output = output; } public void sendMessage(String s) { try { if (m_output != null) m_output.writeUTF(s); } catch (IOException e) {} } public void setPropagate(boolean b) { m_prop = b; } public String getStringIndex(Component f) { String s = f.toString(); while (s.length() < 3) s = ("0").concat(s); return s; } public String getString(int number) { String s; if(number < 0) s = "" + (-number); else s = "" + number; while (s.length() < 6) s = ("0").concat(s); if (number < 0) s = "-" + s.substring(1,6); return s; } public void activateFrame(JInternalFrame f) { String index = getStringIndex(f); super.activateFrame(f); ww.repaint(); if (m_prop) sendMessage("01" + index + "000000000000000000000000"); } public void beginDraggingFrame(JComponent f) { String index = getStringIndex(f); super.beginDraggingFrame(f); ww.repaint(); if (m_prop) sendMessage("02" + index + "000000000000000000000000"); } public void beginResizingFrame(JComponent f, int direction) { String index = getStringIndex(f); String dir = getString(direction); super.beginResizingFrame(f,direction); ww.repaint(); if (m_prop) sendMessage("03" + index + dir + "000000000000000000"); } public void closeFrame(JInternalFrame f) { String index = getStringIndex(f); super.closeFrame(f); ww.repaint(); if (m_prop) sendMessage("04" + index + "000000000000000000000000"); } public void deactivateFrame(JInternalFrame f) { super.deactivateFrame(f); ww.repaint(); // ID 05 - not implemented } public void deiconifyFrame(JInternalFrame f) { super.deiconifyFrame(f); ww.repaint(); // ID 06 - not implemented } public void dragFrame(JComponent f, int newX, int newY) { String index = getStringIndex(f); String x = getString(newX); String y = getString(newY); f.setLocation(newX, newY); ww.repaint(); if (m_prop) sendMessage("07" + index + x + y +"000000000000"); } public void endDraggingFrame(JComponent f) { String index = getStringIndex(f); super.endDraggingFrame(f); ww.repaint(); if (m_prop) sendMessage("08" + index + "000000000000000000000000"); } public void endResizingFrame(JComponent f) { String index = getStringIndex(f); super.endResizingFrame(f); ww.repaint(); if (m_prop) sendMessage("09" + index + "000000000000000000000000"); } public void iconifyFrame(JInternalFrame f) { super.iconifyFrame(f); ww.repaint(); // ID 10 - not implemented } public void maximizeFrame(JInternalFrame f) { String index = getStringIndex(f); super.maximizeFrame(f); ww.repaint(); // ID 11 - not implemented } public void minimizeFrame(JInternalFrame f) { super.minimizeFrame(f); ww.repaint(); // ID 12 - not implemented } public void openFrame(JInternalFrame f) { String index = getStringIndex(f); super.openFrame(f); ww.repaint(); if (m_prop) sendMessage("13" + index + "000000000000000000000000"); } public void resizeFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) { String index = getStringIndex(f); String x = getString(newX); String y = getString(newY); String w = getString(newWidth); String h = getString(newHeight); f.setBounds(newX, newY, newWidth, newHeight); ww.repaint(); if (m_prop) sendMessage("14" + index + x + y + w + h); } public void setBoundsForFrame(JComponent f, int newX, int newY, int newWidth, int newHeight) { String index = getStringIndex(f); String x = getString(newX); String y = getString(newY); String w = getString(newWidth); String h = getString(newHeight); if (newWidth > m_desktop.getWidth()) newWidth = m_desktop.getWidth(); if (newHeight > m_desktop.getHeight()) newHeight = m_desktop.getHeight(); f.setBounds(newX, newY, newWidth, newHeight); ww.repaint(); if (m_prop) sendMessage("15" + index + x + y + w + h); } }
Code: JavaXClient.java
see \Chapter16\3
import java.beans.PropertyVetoException; import javax.swing.*; import java.awt.event.*; import java.io.*; import java.awt.*; import java.net.*; public class JavaXClient extends JFrame implements Runnable { protected int m_count; protected int m_tencount; protected int m_wmX, m_wmY; protected JButton m_newFrame; protected JDesktopPane m_desktop; protected WindowManager m_wm; protected JViewport viewport; protected JTextArea m_consoletext, m_consolechat; protected JTextField m_text, m_chatText; protected boolean m_connected; protected JLabel m_status; protected DataInputStream m_input; protected DataOutputStream m_output; protected Socket m_client; protected Thread m_listenThread; // ServerSocket and ConThread code removed. protected JButton m_connect; public JavaXClient() { setTitle("JavaX Client"); m_count = m_tencount = 0; m_desktop = new JDesktopPane(); m_status = new JLabel("Not Connected"); JScrollPane scroller = new JScrollPane(); m_wm = new WindowManager(m_desktop); m_desktop.setDesktopManager(m_wm); m_desktop.add(m_wm.getWindowWatcher(), JLayeredPane.PALETTE_LAYER); m_wm.getWindowWatcher().setBounds(555,5,100,100); viewport = new JViewport() { //...identical in JavaXServer }; viewport.setView(m_desktop); scroller.setViewport(viewport); ComponentAdapter ca = new ComponentAdapter() { //...identical in JavaXServer }; viewport.addComponentListener(ca); m_newFrame = new JButton("New Frame"); m_newFrame.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { m_wm.setPropagate(false); newFrame(); if (m_connected) m_wm.sendMessage("16000000000000000000000000000"); m_wm.setPropagate(true); } }); m_newFrame.setEnabled(false); JPanel topPanel = new JPanel(true); topPanel.add(m_newFrame); m_connect = new JButton("Connect"); m_connect.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (m_listenThread == null) { Thread connector = new Thread() { public void run() { try { m_consoletext.append( "\nTrying " + m_text.getText() + " ..."); m_client = new Socket( InetAddress.getByName(m_text.getText()),5000); m_input = new DataInputStream( m_client.getInputStream()); m_output = new DataOutputStream( m_client.getOutputStream()); m_connected = true; m_listenThread = new Thread(JavaXClient.this); m_listenThread.start(); m_wm.setOutputStream(m_output); m_consoletext.append("\nStreams established..."); m_status.setText("Connected to " + m_text.getText()); m_connect.setEnabled(false); m_newFrame.setEnabled(true); } catch (Exception ex) { m_consoletext.append("\n" + ex); m_newFrame.setEnabled(false); } } }; connector.start(); } } }); JPanel XPanel = new JPanel(); XPanel.setLayout(new BorderLayout()); JLabel hl = new JLabel("Connect to: ", SwingConstants.CENTER); m_text = new JTextField(15); XPanel.add("North", hl); XPanel.add("Center", m_text); XPanel.add("East", m_connect); JPanel upperPanel = new JPanel(); upperPanel.setLayout(new BorderLayout()); upperPanel.add("Center", XPanel); upperPanel.add("East",topPanel); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", upperPanel); getContentPane().add("Center", scroller); getContentPane().add("South", m_status); topPanel.add(m_newFrame); setupConsole(); Dimension dim = getToolkit().getScreenSize(); setSize(800,600); setLocation(dim.width/2-getWidth()/2, dim.height/2-getHeight()/2); m_desktop.setPreferredSize(new Dimension(1600,1200)); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(l); setVisible(true); } public void setupConsole() { JInternalFrame console = new JInternalFrame( "JavaX Client Console", false, false, false, false) { int TAG = m_count; public String toString() { return "" + TAG; } }; m_count++; console.setBounds(20, 20, 500, 300); JPanel chatPanel = new JPanel(); JLabel chatLabel = new JLabel(" Chat"); chatPanel.setLayout(new BorderLayout()); m_consoletext = new JTextArea(); m_consoletext.setPreferredSize(new Dimension(500,50)); m_consoletext.setLineWrap(true); m_consoletext.setText("Client Started..."); m_consoletext.setEditable(false); m_consolechat = new JTextArea(); m_consolechat.setLineWrap(true); m_consolechat.setEditable(false); m_chatText = new JTextField(); m_chatText.addActionListener(new ChatAdapter()); JButton chatSend = new JButton("Send"); chatSend.addActionListener(new ChatAdapter()); JPanel sendPanel = new JPanel(); sendPanel.setLayout(new BorderLayout()); sendPanel.add("Center", m_chatText); sendPanel.add("West", chatSend); JScrollPane cscroller1 = new JScrollPane(m_consoletext); JScrollPane cscroller2 = new JScrollPane(m_consolechat); chatPanel.add("North", chatLabel); chatPanel.add("Center", cscroller2); chatPanel.add("South", sendPanel); JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true, cscroller1, chatPanel); console.getContentPane().add(splitter); m_desktop.add(console); m_wm.getWindowWatcher().repaint(); } public void run() { while (m_connected) { try { processMessage(m_input.readUTF()); } catch (IOException e) { m_consoletext.append("\n" + e); m_connected = false; } } } public void newFrame() { JInternalFrame jif = new JInternalFrame("Frame " + m_count, true, true, false, false) { int TAG = m_count; public String toString() { return "" + TAG; } }; jif.setBounds(20*(m_count%10) + m_tencount*80, 20*(m_count%10), 200, 200); m_desktop.add(jif); try { jif.setSelected(true); } catch (PropertyVetoException pve) { System.out.println("Could not select " + jif.getTitle()); } m_count++; if (m_count%10 == 0) { if (m_tencount < 3) m_tencount++; else m_tencount = 0; } } public void processMessage(String s) { if (s.startsWith("cc")) { m_consolechat.append("SERVER: " + s.substring(2) + "\n"); m_consolechat.setCaretPosition( m_consolechat.getText().length()); } else { int id = (Integer.valueOf(s.substring(0,2))).intValue(); m_wm.setPropagate(false); if (id == 16) { newFrame(); } else { Component[] components = m_desktop.getComponentsInLayer(0); int index = 0; int tag = (Integer.valueOf(s.substring(2,5))).intValue(); int param1 = (Integer.valueOf(s.substring(5,11))).intValue(); int param2 = (Integer.valueOf(s.substring(11,17))).intValue(); int param3 = (Integer.valueOf(s.substring(17,23))).intValue(); int param4 = (Integer.valueOf(s.substring(23))).intValue(); boolean found = false; for (int i=components.length-1; i>-1;i--) { if (components[i] instanceof JInternalFrame) { if (Integer.valueOf( components[i].toString()).intValue() == tag) { try { ((JInternalFrame) components[i]).setSelected(true); ((JInternalFrame) components[i]).toFront(); index = i; found = true; break; } catch (PropertyVetoException pve) { System.out.println( "Could not select JInternalFrame with tag " + tag); } } } } if (found == false) return; switch (id) { case 1: m_wm.activateFrame((JInternalFrame) components[index]); break; case 2: m_wm.beginDraggingFrame((JComponent) components[index]); break; case 3: m_wm.beginResizingFrame( (JComponent) components[index], param1); break; case 4: m_wm.closeFrame((JInternalFrame) components[index]); break; // case 5: not implemented // case 6: not implemented case 7: m_wm.dragFrame( (JComponent)components[index], param1, param2); break; case 8: m_wm.endDraggingFrame((JComponent) components[index]); break; case 9: m_wm.endResizingFrame((JComponent) components[index]); break; // case 10: not implemented // case 11: not implemented // case 12: not implemented case 13: m_wm.openFrame((JInternalFrame) components[index]); break; case 14: m_wm.resizeFrame( (JComponent) components[index], param1, param2, param3, param4); break; case 15: m_wm.setBoundsForFrame( (JComponent) components[index], param1, param2, param3, param4); break; } } m_wm.setPropagate(true); } m_desktop.repaint(); } public static void main(String[] args) { new JavaXClient(); } class ChatAdapter implements ActionListener { public void actionPerformed(ActionEvent e) { m_wm.sendMessage("cc" + m_chatText.getText()); m_consolechat.append("CLIENT: " + m_chatText.getText() + "\n"); m_chatText.setText(""); } } }
Understanding the Code
Class JavaXServer
JavaXServer
implements the Runnable interface allowing us to define a separate thread of execution. Several instance variables are necessary:int m_count
, int m_tencount: used for cascadingint m_wmX
: keeps track of the most recent x coordinate of the desktop scrollpane’s view position.int m_wmY
: keeps track of the most recent y coordinate of the desktop scrollpane’s view position.JDesktopPane m_desktop
: our desktop pane.WindowManager m_wm
: our custom DesktopManager implementation.JViewport viewport
: The viewport of the scrollpane that will contain our desktop.JTextArea m_consoletext
: The console text area used to display server status information.JTextArea m_consolechat
: The console text area used for chatting between server and client.boolean m_connected
: Flag specifying whether a client is connected or not.JLabel m_status
: Status bar used to display the IP address of the connected client.DataInputStream m_input
: The DataInputStream of the client connection Socket.DataOutputStream m_output
: The DataOutputStream of the client connection Socket.Socket m_client
: The Socket created when a client connects.ServerSocket m_server
: Used to wait for an incoming client and establish the client Socket.Thread m_listenThread
: A handle used for creating and starting the JavaXServer thread.ConThread m_conthred
: An instance of our custom Thread extended inner class used to allow the ServerSocket to wait for client connections without hogging our application’s thread.The
JavaXServer constructor performs familiar GUI layout tasks, and is very similar to the JavaXWin constructor we studied in the last section. Before the frame is made visible our setupConsole() method is called. This method is responsible for contructing our chat console internal frame (it also acts as a message log for the server). We override this JInternalFrame’s toString() method to return a unique TAG which is the value of the m_count variable at the time of its creation. Since the console is the first frame c! reated it will have a TAG of 0. We then increment m_count so that the next frame, created in the newFrame() method (see below), will have a different TAG value. This is how we identify frames when sending internal frame messages.The console contains two text areas, a "Send" button and a text field. The send button and text field are used for chatting, the upper text area is used to display server status information, and the lower text field is used to display chat text. We attach an instance of our custom
ChatAdapater class (see below) to both the send button, chatSend, and the text field, m_chatText.The
setupConsole() method ends by actually starting the server. We create a new ServerSocket on port 5000 with queue length 500. A queue length of 500 represents the maximum amount of messages that can be buffered at any given time by the ServerSocket.
Note: This example uses a fixed port. In professional applications the server would most likely provide the operator with a field to enter the desired port number. Also, note that a maximum queue length of 500 is not really necessary here, as we are only expecting one client to connect through this
ServerSocket. However, it does not add any extra overhead, and if for some reason this port gets bombarded with extraneous messages this will give our client a better chance of getting in.The run() method defines the separate thread of execution that is created and called within the
ConThread class (see below). When a client is connected this method continuously reads data from the client Socket, m_client, and sends it to our custom processMessage() method (see below).Our
newFrame() method looks familiar, however, each JInternalFrame created gets assigned a unique TAG, which is the value of the m_count variable at the time of its creation, and its toString() method is overriden to return this tag. This is how we identify destinations of internal frame messages.The
processMessage() method takes a String parameter representing a message from the client. First we check if it is a chat message (remember that all chat messages start with "cc"). If so we simply append the this message (minus the "cc" header) to our chat text area and set the cursor position accordinlgy.If it is not a chat message then we process it as an internal frame message. First we get the method ID and check if it is 16. (See discussion of the
WindowManager class below for an explanation of what method each ID corresponds to):1. If the id is 16 all we need to do is create a new frame, so the
2. If the ID is not 16 we first call our
WindowManager’s custom setPropagate() message to stop processed messages sent to our WindowManager from being sent back to the client (this is effectively how we consume each message). Then we grab an array of all the components in layer 0 and check each JInternalFrame’s TAG until we find a match. (Note that all frames are contained at layer 0 in this example. In a more complete example we would search through all components in the desktop, regardless of layer.) We then extract the internal frame message parameters and if a TAG match is found we send a message to our WindowManager, m_wm, based on the id and extracted parameters (if applicable).Class JavaXServer.ChatAdapter
ChatAdapter
is used as an ActionListener attached to our console’s chat text field and send button (see the setupConsole() method). Whenever the user presses enter in the chat input text field or clicks the console’s "Send" button, this adapter sends the text field contents as a message to the client, appends it to the console chat text area, m_consolechat, and clears this text field.Class JavaXServer.ConThread
Inner class
ConThread extends Thread. Its constructor calls its start() method, and its start() method calls its run() method. We override the run() method to create an endless loop that starts by invoking accept() on our m_server ServerSocket. This method blocks execution until a client connects. When a client does con! nect our ServerSocket returns from the accept() method with a Socket, m_client, representing the connection with the client. We then set our m_connected flag to true so that when our main JavaXServer thread is started, it can receive and process messages from the client, as discussed above. We assign m_client‘s DataInputStream to m_input and its DataOutputStream to m_output. Then we pass m_output to our WindowManager’s setOutputStream() method (see discussion of WindowManager below). We print a message in the console to inform the server operator that a connection has been established and then start() the JavaXServer thread (see the run() method--discussed above). Fina! lly we display the IP address the client connected from in the status bar and append this information to our console.Class WindowManager
The
WindowManager class starts by defining 15 self-explanitory int id fields, each corresponding to one of the internal frame methods defined within this class. Four instance variables are used:WindowWatcher ww
: our custom pager component.DataOutputStrean
m_output: The client Socket’s output stream used to send messages.JDesktopPane m_desktop
: The desktop we are managing.boolean m_prop
: Used to block messages from being sent back to the sender when being processed.The
setOutputStream() method is called when a client connects. This is used to provide WindowManager with a reference to the client Socket‘s DataOutputStream for sending messages.The
sendMessage() method takes a String parameter representing a message to be sent to the client and attempts to write it to the cleint’s DataOutputStream:public void sendMessage(String s) { try { if (m_output != null) { m_output.writeUTF(s); } } catch (IOException e) {} }
The
setPropagate() method takes a boolean parameter which is used to block messages from being sent back to the client when being processed. This method is called from JavaXServer‘s processMessage() method.The
getStringIndex() method takes an int parameter, representing the TAG of an internal frame, and converts it to a String 3 characters long by concatenating 0s in front if necessary. This is used to build the TAG field in an internal frame message (which, as we know from our discussion in the beginning of this section, is always 3 characters long) which is returned.public String getStringIndex(Component f) { String s = f.toString(); while (s.length() < 3) s = ("0").concat(s); return s;
}
In the
getString() method we take an int parameter which can represent any of four possible parameters passed to one of the internal frame methods defined within this class. We then convert this value to a String. If this value was negative we remove the "-" sign. Then we concatenate a number of 0s to the front of the string forcing the length to be 6. We then check if the value passed in was negative one more time. If it was we replace the first character with a "-" sign:public String getString(int number) { String s; if (number < 0) s = "" + (-number); else s = "" + number; while (s.length() < 6) s = ("0").concat(s); if (number < 0) s = "-" + s.substring(1,6); return s; }
Note: This example assumes that no frame coordinate or dimension will ever be larger than 999999 or smaller than -99999. For all practical purposes, this is a completely safe assumption to make!
Whenever any of the internal frame methods are invoked we first get the
TAG of the frame associated with the method call. Then we call the superclass counterpart of that method, repaint our WindowWatcher, and check our message propagation flag, m_prop. If it is set to true we go ahead and construct our message, using the getString() method where applicable, and pass it to our sendMessage method to send to the client. If it is false, no me! ssage is sent.Class JavaXClient
JavaXClient
functions very similar to JavaXServer. It sends, receives, and interprets messages identically to JavaXServer. However, unlike JavaXServer, JavaXClient can create new frames and send new frame messages to the server (new frame messages have ID 16) These messages are constructed and sent within JavaXClient’s actionPerformed method:if (e.getSource() == m_newFrame) { m_wm.setPropagate(false); newFrame(); if (m_connected) m_wm.sendMessage("16000000000000000000000000000"); m_wm.setPropagate(true); }
JavaXClient
also has some additional GUI components added to the top of its frame. A text field for entering the server’s IP address, a "New Frame" button, and a connect button. The ActionListener for this button is wrapped in a thread to allow connection attempts while not blocking the main thread of execution. It attempts to connect to the address specified in the text field by creating a new Socket:m_client = new Socket(InetAddress.getByName(m_text.getText()),5000);
If this works it establishes input and output data streams and starts the
JavaXClient thread (see its run() method--identical to JavaXServer’s run() method) to listen for messages. We then append text to the console, update the status bar, and enable the "New Frame" button. (Note that the client can only create new frames after a connection has been established.)Running the Code:
Figure 16.8 and 16.9 show
JavaXClient and JavaXServer during a collaborative session. Ideally you test this example out with a friend in a remote, far-away place. If this is not possible try using two machines in your network. (If you are not networked you can run both client and the server on your machine by connecting to 127.0.0.1 which is always used as a pointer to your own machine.)Try chatting and resizing each other’s frames. Now is the time to think of other possible applications of such a multi-user desktop environment. Clearly we will begin to see more and more remote interaction and collaboration as the web and its surrounding technologies continue to grow.