1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright 1998-2002 by Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 package com.sun.dhcpmgr.ui;
29 
30 import javax.swing.*;
31 import javax.swing.border.*;
32 
33 import java.awt.event.*;
34 import java.awt.*;
35 import java.util.*;
36 import java.text.NumberFormat;
37 
38 // Interface for notification of button events
39 interface WizButtonListener {
buttonPressed(int buttonId)40     public void buttonPressed(int buttonId);
41     public static final int BACK = 0;
42     public static final int FORWARD = 1;
43     public static final int CANCEL = 2;
44     public static final int HELP = 3;
45     public static final int FINISH = 4;
46 }
47 
48 /**
49  * This is a widget for presenting a multi-step task, such as an installation
50  * sequence or other task with many questions or multiple possible paths.  This
51  * component displays as a dialog, with a contents outline on the left and a
52  * panel for display of each step on the right, with a set of control buttons
53  * at the bottom.  Typical usage is to subclass this class and implement a set
54  * of WizardStep classes which provide the steps to be executed.
55  *
56  * @see WizardStep
57  */
58 public class Wizard extends JDialog {
59 
60     // Panel to manage display and processing of buttons at base of window
61     class WizButtonPanel extends JPanel {
62 
63 	// Adaptor class to catch all button presses and pass along to listeners
64 	class ButtonAdaptor implements ActionListener {
actionPerformed(ActionEvent e)65 	    public void actionPerformed(ActionEvent e) {
66 		int buttonId = -1;
67 		Object source = e.getSource();
68 		if (source == backButton || source == backButton2) {
69 		    buttonId = WizButtonListener.BACK;
70 		} else if (source == forwardButton) {
71 		    buttonId = WizButtonListener.FORWARD;
72 		} else if (source == cancelButton || source == cancelButton2) {
73 		    buttonId = WizButtonListener.CANCEL;
74 		} else if (source == helpButton || source == helpButton2) {
75 		    buttonId = WizButtonListener.HELP;
76 		} else if (source == finishButton) {
77 		    buttonId = WizButtonListener.FINISH;
78 		}
79 		Enumeration en = listeners.elements();
80 		while (en.hasMoreElements()) {
81 		    WizButtonListener l = (WizButtonListener)en.nextElement();
82 		    l.buttonPressed(buttonId);
83 		}
84 	    }
85 	}
86 
87 	PreviousButton backButton, backButton2;
88 	NextButton forwardButton;
89 	JButton cancelButton, helpButton, finishButton;
90 	JButton cancelButton2, helpButton2;
91 	Vector listeners;
92 	ButtonAdaptor adaptor;
93 	JPanel innerPanel;
94 
WizButtonPanel()95 	public WizButtonPanel() {
96 	    super();
97 
98 	    setBorder(new EtchedBorder());
99 	    // Right justify the buttons
100 	    setLayout(new FlowLayout(FlowLayout.RIGHT));
101 	    innerPanel = new JPanel(new CardLayout());
102 
103 	    // Create event handler
104 	    adaptor = new ButtonAdaptor();
105 	    listeners = new Vector();
106 
107 	    /*
108 	     * Construct back buttons; need 2 for the separate cards used
109 	     * in the button panel
110 	     */
111 	    backButton = new PreviousButton();
112 	    backButton2 = new PreviousButton();
113 	    backButton.addActionListener(adaptor);
114 	    backButton2.addActionListener(adaptor);
115 	    backButton.setAlignmentY(Component.CENTER_ALIGNMENT);
116 	    backButton2.setAlignmentY(Component.CENTER_ALIGNMENT);
117 
118 	    // Construct forward button
119 	    forwardButton = new NextButton();
120 	    forwardButton.addActionListener(adaptor);
121 	    forwardButton.setAlignmentY(Component.CENTER_ALIGNMENT);
122 
123 	    Mnemonic mnFinish =
124 		new Mnemonic(ResourceStrings.getString("finish_button"));
125 	    finishButton = new JButton(mnFinish.getString());
126 	    finishButton.setToolTipText(mnFinish.getString());
127             finishButton.setMnemonic(mnFinish.getMnemonic());
128 	    finishButton.addActionListener(adaptor);
129 	    finishButton.setAlignmentY(Component.CENTER_ALIGNMENT);
130 
131 	    Mnemonic mnCancel =
132 		new Mnemonic(ResourceStrings.getString("cancel_button"));
133             cancelButton = new JButton(mnCancel.getString());
134             cancelButton.setToolTipText(mnCancel.getString());
135             cancelButton.setMnemonic(mnCancel.getMnemonic());
136 
137 	    cancelButton.addActionListener(adaptor);
138 	    cancelButton.setAlignmentY(Component.CENTER_ALIGNMENT);
139 
140 	    cancelButton2 = new JButton(mnCancel.getString());
141 	    cancelButton2.setToolTipText(mnCancel.getString());
142             cancelButton2.setMnemonic(mnCancel.getMnemonic());
143 
144 	    cancelButton2.addActionListener(adaptor);
145 	    cancelButton2.setAlignmentY(Component.CENTER_ALIGNMENT);
146 
147             Mnemonic mnHelp =
148 		new Mnemonic(ResourceStrings.getString("help_button"));
149             helpButton = new JButton(mnHelp.getString());
150             helpButton.setToolTipText(mnHelp.getString());
151             helpButton.setMnemonic(mnHelp.getMnemonic());
152 
153 	    helpButton.addActionListener(adaptor);
154 	    helpButton.setAlignmentY(Component.CENTER_ALIGNMENT);
155 
156 	    helpButton2 = new JButton(mnHelp.getString());
157             helpButton2.setToolTipText(mnHelp.getString());
158             helpButton2.setMnemonic(mnHelp.getMnemonic());
159 
160 	    helpButton2.addActionListener(adaptor);
161 	    helpButton2.setAlignmentY(Component.CENTER_ALIGNMENT);
162 
163 	    /*
164 	     * Now create cards; we created two copies of buttons that
165 	     * needed to be on both cards
166 	     */
167 	    Box box = Box.createHorizontalBox();
168 	    box.add(Box.createHorizontalGlue());
169 	    box.add(backButton);
170 	    box.add(Box.createHorizontalStrut(5));
171 	    box.add(forwardButton);
172 	    box.add(Box.createHorizontalStrut(5));
173 	    box.add(cancelButton);
174 	    box.add(Box.createHorizontalStrut(5));
175 	    box.add(helpButton);
176 	    innerPanel.add(box, "normal");
177 
178 	    // Finish panel replaces the forward button with the finish button
179 	    box = Box.createHorizontalBox();
180 	    box.add(Box.createHorizontalGlue());
181 	    box.add(backButton2);
182 	    box.add(Box.createHorizontalStrut(5));
183 	    box.add(finishButton);
184 	    box.add(Box.createHorizontalStrut(5));
185 	    box.add(cancelButton2);
186 	    box.add(Box.createHorizontalStrut(5));
187 	    box.add(helpButton2);
188 	    innerPanel.add(box, "finish");
189 
190 	    add(innerPanel);
191 	}
192 
193 	// Show the first step
showFirst()194 	public void showFirst() {
195 	    backButton.setEnabled(false); // Can't go backwards
196 	    setForwardEnabled(false);
197 	    getRootPane().setDefaultButton(forwardButton);
198 	    forwardButton.requestFocus(true);
199 	    ((CardLayout)innerPanel.getLayout()).show(innerPanel, "normal");
200 	}
201 
202 	// Show the last step
showLast()203 	public void showLast() {
204 	    backButton.setEnabled(true);
205 	    setFinishEnabled(false);
206 	    getRootPane().setDefaultButton(finishButton);
207 	    finishButton.requestFocus(true);
208 	    ((CardLayout)innerPanel.getLayout()).show(innerPanel, "finish");
209 	}
210 
211 	// Show any other step
showMiddle()212 	public void showMiddle() {
213 	    backButton.setEnabled(true);
214 	    setForwardEnabled(false);
215 	    getRootPane().setDefaultButton(forwardButton);
216 	    cancelButton.requestFocus(true);
217 	    ((CardLayout)innerPanel.getLayout()).show(innerPanel, "normal");
218 	}
219 
220 	// Allow steps to control when user may advance to next step
setForwardEnabled(boolean state)221 	public void setForwardEnabled(boolean state) {
222 	    forwardButton.setEnabled(state);
223 	}
224 
225 	// Allow the final step to control when a user may complete the task
setFinishEnabled(boolean state)226 	public void setFinishEnabled(boolean state) {
227 	    finishButton.setEnabled(state);
228 	}
229 
addWizButtonListener(WizButtonListener l)230 	public void addWizButtonListener(WizButtonListener l) {
231 	    listeners.addElement(l);
232 	}
233 
removeWizButtonListener(WizButtonListener l)234 	public void removeWizButtonListener(WizButtonListener l) {
235 	    listeners.removeElement(l);
236 	}
237     }
238 
239     /*
240      * Panel to display the list of steps; we use a very custom JList
241      * for this as it does reasonable rendering with minimal effort on our part.
242      */
243     class WizContentsPanel extends JPanel {
244 
245 	// Data model for holding the description text displayed
246 	class ContentsModel extends AbstractListModel {
247 	    Vector data;
248 
ContentsModel()249 	    public ContentsModel() {
250 		data = new Vector();
251 	    }
252 
getElementAt(int index)253 	    public Object getElementAt(int index) {
254 		return data.elementAt(index);
255 	    }
256 
getSize()257 	    public int getSize() {
258 		return data.size();
259 	    }
260 
addItem(String description)261 	    public void addItem(String description) {
262 		data.addElement(description);
263 		fireIntervalAdded(this, data.size()-1, data.size()-1);
264 	    }
265 	}
266 
267 	// Class to render the cells in the list
268 	class ContentsRenderer implements ListCellRenderer {
269 	    NumberFormat nf;
270 
ContentsRenderer()271 	    public ContentsRenderer() {
272 		nf = NumberFormat.getInstance();
273 	    }
274 
getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)275 	    public Component getListCellRendererComponent(JList list,
276 		    Object value, int index, boolean isSelected,
277 		    boolean cellHasFocus) {
278 
279 		// Format label properly for i18n
280 		JTextArea text = new JTextArea((String)value, 2, 15);
281 		text.setWrapStyleWord(true);
282 		text.setLineWrap(true);
283 		text.setOpaque(false);
284 		text.setAlignmentY(Component.TOP_ALIGNMENT);
285 		JLabel l = new JLabel(nf.format(index + 1));
286 		l.setForeground(Color.black);
287 		l.setAlignmentY(Component.TOP_ALIGNMENT);
288 
289 		JPanel stepBox = new JPanel();
290 		stepBox.setLayout(new BoxLayout(stepBox, BoxLayout.X_AXIS));
291 		stepBox.add(l);
292 		stepBox.add(Box.createHorizontalStrut(5));
293 		stepBox.add(text);
294 		stepBox.setBackground(list.getSelectionBackground());
295 		// Selected component is opaque, others transparent
296 		stepBox.setOpaque(isSelected);
297 		return stepBox;
298 	    }
299 	}
300 
301 	/*
302 	 * This class is defined strictly so that we can prevent
303 	 * focus from ever reaching the steps list as it is a display-only
304 	 * use of JList, not intended for any sort of input by the user.
305 	 */
306 	class MyList extends JList {
MyList(ListModel m)307 	    public MyList(ListModel m) {
308 	        super(m);
309 		// Don't allow this list to be focused
310 		setFocusable(false);
311 	    }
312 	    // Ignore mouse clicks so highlighted step can't be changed
processMouseEvent(MouseEvent e)313 	    protected void processMouseEvent(MouseEvent e) {
314 		return;
315 	    }
316 	    // Ignore mouse drags, which can also change highlighting
processMouseMotionEvent(MouseEvent e)317 	    protected void processMouseMotionEvent(MouseEvent e) {
318 		return;
319 	    }
320 	}
321 
322 	MyList contentsList;
323 	ContentsModel model;
324 
WizContentsPanel()325 	public WizContentsPanel() {
326 	    setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 10));
327 	    setBackground(Color.white);
328 	    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
329 
330 	    Mnemonic mnSteps =
331 		new Mnemonic(ResourceStrings.getString("steps_label"));
332 	    JLabel l = new JLabel(mnSteps.getString());
333 	    l.setLabelFor(this);
334 	    l.setToolTipText(mnSteps.getString());
335 	    l.setDisplayedMnemonic(mnSteps.getMnemonic());
336 
337 	    l.setForeground(Color.black);
338 	    l.setAlignmentX(Component.LEFT_ALIGNMENT);
339 	    add(l);
340 
341 	    model = new ContentsModel();
342 	    contentsList = new MyList(model);
343 	    contentsList.setCellRenderer(new ContentsRenderer());
344 	    /*
345 	     * Wrap the list with scroll bars, vertical as necessary but
346 	     * never a horizontal one.
347 	     */
348 	    JScrollPane scrollPane = new JScrollPane(contentsList,
349 		ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
350 		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
351 	    scrollPane.setBorder(BorderFactory.createEmptyBorder(15, 10, 0, 0));
352 	    scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
353 	    scrollPane.setBackground(Color.white);
354 	    add(scrollPane);
355 	    add(Box.createVerticalGlue());
356 	}
357 
addStep(WizardStep step)358 	public void addStep(WizardStep step) {
359 	    // Index the steps using the description string
360 	    model.addItem(step.getDescription());
361 	}
362 
showStep(WizardStep step)363 	public void showStep(WizardStep step) {
364 	    contentsList.setSelectedValue(step.getDescription(), true);
365 	}
366     }
367 
368     /*
369      * This class provides a simple card layout to display each step as needed.
370      */
371     class WizStepPanel extends JPanel {
372 	CardLayout layout;
373 
WizStepPanel()374 	public WizStepPanel() {
375 	    setLayout(layout = new CardLayout());
376 	    // Make sure we have some space around the edges.
377 	    setBorder(BorderFactory.createEmptyBorder(15, 10, 10, 10));
378 	}
379 
addStep(WizardStep step)380 	public void addStep(WizardStep step) {
381 	    add(step.getComponent(), step.getDescription());
382 	}
383 
showStep(WizardStep step)384 	public void showStep(WizardStep step) {
385 	    layout.show(this, step.getDescription());
386 	}
387     }
388 
389     WizButtonPanel buttonPanel;
390     WizContentsPanel contentsPanel;
391     WizStepPanel stepPanel;
392     Vector steps;
393     WizardStep activeStep = null;
394     int stepNumber = 0;
395     Vector listeners;
396 
397     /**
398      * Constructs a wizard with the specified owning frame and title.
399      *
400      * @param	owner	the owning frame
401      * @param	title	the wizard's title string
402      */
Wizard(Frame owner, String title)403     public Wizard(Frame owner, String title) {
404 	super(owner, title);
405 
406 	setLocationRelativeTo(owner);
407 
408 	getContentPane().setLayout(new BorderLayout());
409 	steps = new Vector();
410 	listeners = new Vector();
411 
412 	// Put the buttons at the bottom.
413 	buttonPanel = new WizButtonPanel();
414 	getContentPane().add(buttonPanel, BorderLayout.SOUTH);
415 
416 	/*
417 	 * The main display is designed to use a 7:11 ratio for the horizontal
418 	 * area consumed by the contents panel and the display panel,
419 	 * respectively.  There's nothing particularly magical about the
420 	 * ratio, that's just the way the designer drew it.
421 	 */
422 	JPanel mainPanel = new JPanel(new ProportionalLayout());
423 
424 	contentsPanel = new WizContentsPanel();
425 	mainPanel.add(contentsPanel, "7");
426 
427 	stepPanel = new WizStepPanel();
428 	mainPanel.add(stepPanel, "11");
429 
430 	// Consume all space not needed for buttons
431 	getContentPane().add(mainPanel, BorderLayout.CENTER);
432 
433 	/*
434 	 * We manage the button interactions, but the steps get limited veto
435 	 * power
436 	 */
437 	buttonPanel.addWizButtonListener(new WizButtonListener() {
438 	    public void buttonPressed(int buttonId) {
439 		switch (buttonId) {
440 		case BACK:
441 		    showPreviousStep();
442 		    break;
443 		case FORWARD:
444 		    showNextStep();
445 		    break;
446 		case FINISH:
447 		    doFinish();
448 		    break;
449 		case CANCEL:
450 		    doCancel();
451 		    break;
452 		case HELP:
453 		    doHelp();
454 		    break;
455 		default:
456 		}
457 	    }
458 	});
459     }
460 
461     /**
462      * Override of setVisible to control size when displayed.  Perhaps this
463      * should be relaxed, but subclasses can always do whatever they want.
464      * @param state make visible or not?
465      */
setVisible(boolean state)466     public void setVisible(boolean state) {
467 	if (state) {
468 	    setSize(525, 425);
469 	}
470 	super.setVisible(state);
471     }
472 
473     /**
474      * Adds a step to the wizard.  Steps <bold>must</bold> be added in the
475      * sequence they will be displayed when traversing forward.
476      * @param step a <code>WizardStep</code>
477      */
addStep(WizardStep step)478     public void addStep(WizardStep step) {
479 	steps.addElement(step);
480 	contentsPanel.addStep(step);
481 	stepPanel.addStep(step);
482     }
483 
showStep(WizardStep step, int direction)484     private boolean showStep(WizardStep step, int direction) {
485 	// Deactivate currently active step
486 	if (activeStep != null) {
487 	    if (!activeStep.setInactive(direction)) {
488 		// Step vetoed its deactivation.  We honor its wishes.
489 		return false;
490 	    }
491 	}
492 	/*
493 	 * Activate new step by updating contents, display area, and possibly
494 	 * buttons
495 	 */
496 	activeStep = step;
497 	contentsPanel.showStep(step);
498 	stepPanel.showStep(step);
499 	if (step == steps.firstElement()) {
500 	    buttonPanel.showFirst();
501 	} else if (step == steps.lastElement()) {
502 	    buttonPanel.showLast();
503 	} else {
504 	    buttonPanel.showMiddle();
505 	}
506 	activeStep.setActive(direction);
507 	return true;
508     }
509 
510     // Show some arbitrary step indexed by number.
showStep(int index, int direction)511     private boolean showStep(int index, int direction)
512 	    throws ArrayIndexOutOfBoundsException {
513 	WizardStep ws = (WizardStep)steps.elementAt(index);
514 	return showStep(ws, direction);
515     }
516 
517     /**
518      * Show the very first step.
519      */
showFirstStep()520     public void showFirstStep() {
521 	stepNumber = 0;
522 	showStep(stepNumber, WizardStep.FORWARD);
523     }
524 
525     /**
526      * Show the next step.
527      */
showNextStep()528     public void showNextStep() {
529 	++stepNumber;
530 	try {
531 	    // Handle step vetoing deactivation
532 	    if (!showStep(stepNumber, WizardStep.FORWARD)) {
533 		--stepNumber;
534 	    }
535 	} catch (ArrayIndexOutOfBoundsException e) {
536 	    --stepNumber;
537 	}
538     }
539 
540     /**
541      * Show the previous step.
542      */
showPreviousStep()543     public void showPreviousStep() {
544 	--stepNumber;
545 	try {
546 	    if (!showStep(stepNumber, WizardStep.BACKWARD)) {
547 		++stepNumber;
548 	    }
549 	} catch (ArrayIndexOutOfBoundsException e) {
550 	    ++stepNumber;
551 	}
552     }
553 
554     /**
555      * Show the last step.
556      */
showLastStep()557     public void showLastStep() {
558 	int saveStep = stepNumber;
559 	stepNumber = steps.size()-1;
560 	try {
561 	    if (!showStep(stepNumber, WizardStep.FORWARD)) {
562 		stepNumber = saveStep;
563 	    }
564 	} catch (ArrayIndexOutOfBoundsException e) {
565 	    stepNumber = saveStep;
566 	}
567     }
568 
569     /**
570      * Control state of the forward button.
571      * @param state <code>true</code> to enable the button
572      */
setForwardEnabled(boolean state)573     public void setForwardEnabled(boolean state) {
574 	buttonPanel.setForwardEnabled(state);
575     }
576 
577     /**
578      * Control state of the finish button.
579      * @param state <code>true</code> to enable the button
580      */
setFinishEnabled(boolean state)581     public void setFinishEnabled(boolean state) {
582 	buttonPanel.setFinishEnabled(state);
583     }
584 
585     /**
586      * Handle user's press of the Cancel button.  Subclasses can override for
587      * special cleanup needs.
588      */
doCancel()589     public void doCancel() {
590 	fireActionPerformed("cancelled");
591 	dispose();
592     }
593 
594     /**
595      * Handle user's press of the Finish button.  Subclasses should override
596      * to perform whatever processing needed to complete the wizard's task.
597      */
doFinish()598     public void doFinish() {
599 	fireActionPerformed("finished");
600 	dispose();
601     }
602 
603     /**
604      * Handle user's press of the Help button.  Does nothing by default,
605      * subclasses can override to provide help as desired.
606      */
doHelp()607     public void doHelp() {
608     }
609 
610     /**
611      * Utility function to create a multi-line text display such as for the
612      * explanatory text that most wizard steps use.
613      * @param text the text to display
614      * @param rows the number of rows to use for displaying the text
615      * @param columns the number of columns to wrap text at.  45 is generally a
616      *			good number for standard wizards with standard fonts
617      * @return a <code>JComponent</code> displaying the supplied text
618      */
createTextArea( String text, int rows, int columns)619     public static JComponent createTextArea(
620 	    String text, int rows, int columns) {
621 
622 	// We extend JTextArea in order to make this behave more like a label
623 	class MyTextArea extends JTextArea {
624 	    public MyTextArea(String text, int rows, int columns) {
625 		/*
626 		 * Create a text area with word-wrapping, no editing,
627 		 * and no background.
628 		 */
629 		super(text, rows, columns);
630 		setLineWrap(true);
631 		setWrapStyleWord(true);
632 		setEditable(false);
633 		setOpaque(false);
634 		setFocusable(false);
635 	    }
636 	}
637 
638 	MyTextArea area = new MyTextArea(text, rows, columns);
639 
640 	// Put it in a scrollpane to get sizing to happen
641 	JScrollPane scrollPane = new JScrollPane(area,
642 	    ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER,
643 	    ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
644 
645 	// Put empty borders on the subcomponents so that all we get is text
646 	Border b = BorderFactory.createEmptyBorder();
647 	area.setBorder(b);
648 	scrollPane.setBorder(b);
649 	return scrollPane;
650     }
651 
652     /**
653      * Add a listener for action events.  Wizard fires an
654      * <code>ActionEvent</code> when user either cancels or finishes the wizard.
655      */
addActionListener(ActionListener l)656     public void addActionListener(ActionListener l) {
657 	listeners.addElement(l);
658     }
659 
660     /**
661      * Remove an action listener.
662      */
removeActionListener(ActionListener l)663     public void removeActionListener(ActionListener l) {
664 	listeners.removeElement(l);
665     }
666 
667     /**
668      * Fire an action event.
669      */
fireActionPerformed(String command)670     protected void fireActionPerformed(String command) {
671 	ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
672 	    command);
673 	for (Enumeration en = listeners.elements(); en.hasMoreElements(); ) {
674 	    ActionListener l = (ActionListener)en.nextElement();
675 	    l.actionPerformed(e);
676 	}
677     }
678 }
679