001: package net.xoetrope.swing;
002:
003: import java.lang.reflect.Field;
004: import java.lang.reflect.Method;
005:
006: import java.awt.BorderLayout;
007: import java.awt.Color;
008: import java.awt.Component;
009: import java.awt.Container;
010: import java.awt.Dimension;
011: import java.awt.Frame;
012: import java.awt.Graphics;
013: import java.awt.Insets;
014: import java.awt.Point;
015: import java.awt.SystemColor;
016: import java.awt.Window;
017: import java.awt.event.ActionEvent;
018: import java.awt.event.ActionListener;
019: import java.awt.event.KeyEvent;
020: import java.awt.event.KeyListener;
021: import java.awt.event.MouseEvent;
022: import java.awt.event.MouseListener;
023: import java.awt.event.MouseMotionListener;
024: import java.awt.event.WindowEvent;
025: import java.awt.event.WindowListener;
026: import javax.swing.JButton;
027: import javax.swing.JDialog;
028: import javax.swing.JWindow;
029:
030: import net.xoetrope.xui.XContentPane;
031: import net.xoetrope.xui.XPage;
032: import net.xoetrope.xui.XProjectManager;
033: import net.xoetrope.xui.XResourceManager;
034:
035: /**
036: * <p>Provides support for Popups. This class extends XPage giving
037: * a blank panel on which you can create custom dialogs. The dialog can be shown
038: * as a modal dialog which will block execution of the client code till the
039: * dialog is dismissed.</p>
040: * <p>Copyright: Copyright (c) Xoetrope Ltd., 1998-2003<br>
041: * License: see license.txt
042: * @version 1.0
043: */
044: public class XDialog extends XPage implements XContentPane {
045: public static final boolean trueField = true;
046:
047: public static final int DEFAULT_PADDING = 0;
048:
049: public static final int NOTHING_CLICKED_YET = 0;
050: public static final int OK_CLICKED = 1;
051: public static final int CANCEL_CLICKED = 2;
052: public static final int CLOSE_CLICKED = 3;
053:
054: private boolean bIsModal = false;
055: private boolean bUseNativeHeaders = false;
056: protected boolean saveOnClose = true;
057: protected int returnValue = 0;
058: protected static int lastReturnValue;
059: public Object returnObject;
060: protected XPanel contentPanel;
061: protected int padding = 2;
062: protected Component focusComponent = null;
063: protected Container parent = null;
064: boolean QAvailable = true;
065: private String callback;
066: private Component callbackParent;
067: private String title = "";
068: private boolean closeBtnClicked = false;
069:
070: private Window dialogWindow;
071:
072: private static int minorVersion = 1;
073:
074: static final boolean bFireEmptyEvent = useEmptyEvent();
075:
076: final Frame clientFrame = XResourceManager.getAppFrame();
077: final Window appWindow = XResourceManager.getAppWindow();
078:
079: /**
080: * Creates a new dialog and adds a content panel to the page. A handler is also
081: * set so that the dialog will be dismissed when the escape key is pressed.
082: */
083: public XDialog() {
084: padding = DEFAULT_PADDING;
085: bIsModal = true;
086:
087: init();
088: }
089:
090: /**
091: * Creates a new dialog and adds a content panel to the page. A handler is also
092: * set so that the dialog will be dismissed when the escape key is pressed.
093: */
094: public XDialog(boolean modal, int pad) {
095: padding = pad;
096: bIsModal = modal;
097:
098: init();
099: }
100:
101: /**
102: * Overload the XPage XCreated event and set the caption of the dialog from
103: * the title attribute. Call super if overloaded.
104: */
105: public void pageCreated() {
106: String titleAttrib = (String) getAttribute("title", null);
107: if (titleAttrib != null)
108: setCaption(componentFactory.translate(titleAttrib));
109: }
110:
111: private void init() {
112: contentPanel = (XPanel) componentFactory.addComponent(
113: XPage.PANEL, 0, 0, 800, 600);
114: componentFactory.setParentComponent(contentPanel);
115: super .setVisible(false);
116: setLayout(null);
117: contentPanel.setLayout(null);
118: setBackground(Color.white);
119: lastReturnValue = NOTHING_CLICKED_YET;
120: }
121:
122: /**
123: * Get the return value of the most recently dismissed dialog
124: * @return a value indicating the status or the button that was used to dismiss the dialog
125: */
126: public static int getLastReturnValue() {
127: return lastReturnValue;
128: }
129:
130: public Container getContentPane() {
131: return contentPanel;
132: }
133:
134: /**
135: * Size the dialog to hold the largest components (i.e. children of the content panel)
136: */
137: public void pack() {
138: if (padding == 0) {
139: String padStr = (String) attribs.get("padding");
140: if ((padStr != null) && (padStr.length() > 0))
141: padding = new Integer(padStr).intValue();
142: }
143:
144: Point size = getMaxCoordinates(contentPanel);
145: Point pt = contentPanel.getLocation();
146: setSize(size.x + 2 * padding + 2, size.y + 2 * padding + 4);
147: }
148:
149: /**
150: * Set the dialog caption/title
151: * @param c
152: */
153: public void setCaption(String c) {
154: title = c;
155: }
156:
157: public Dimension getMinimumSize() {
158: return getSize();
159: }
160:
161: public Dimension getPreferredSize() {
162: return getSize();
163: }
164:
165: /**
166: * Set the dialog to use the native platform decorations (title bar and borders).
167: * @param bh true to use native decorations.
168: */
169: public void setUseNativeHeaders(boolean bh) {
170: bUseNativeHeaders = bh;
171: }
172:
173: /**
174: * Set the dialog to be modal or non-modal
175: * @param modal true for a modal dialog
176: */
177: public void setModal(boolean modal) {
178: bIsModal = modal;
179: }
180:
181: /**
182: * Overrides the setVisible method to close or show the dialog
183: * @param b false to hide the dialog or true to show it
184: */
185: /* public void setVisible( boolean b )
186: {
187: if ( !b )
188: closeDlg();
189: else
190: super.setVisible( true );
191: }*/
192:
193: /**
194: * Set the save on close option
195: * @param save true to save the data when the dialog is closed or dismissed, false to
196: * discard the data.
197: */
198: public void setSaveOnClose(boolean save) {
199: saveOnClose = save;
200: }
201:
202: /**
203: * Dismiss the dialog and discard teh data.
204: */
205: public void cancelDlg() {
206: saveOnClose = false;
207: closeDlg();
208: }
209:
210: /**
211: * Close the dialog and restore focus
212: */
213: public void closeDlg() {
214: dialogWindow.setVisible(false);
215:
216: if (callback != null) {
217: try {
218: Class params[] = new Class[1];
219: params[0] = this .getClass(); //Class.forName( "net.xoetrope.xui.XDialog" );
220: Method m = callbackParent.getClass().getMethod(
221: callback, params);
222: Object args[] = new Object[1];
223: args[0] = this ;
224: m.invoke(callbackParent, args);
225: } catch (Exception ex) {
226: ex.printStackTrace();
227: }
228: }
229:
230: if (focusComponent != null)
231: focusComponent.requestFocus();
232: eventHandler.suppressFocusEvents(false);
233:
234: clientFrame.setEnabled(true);
235: appWindow.setEnabled(true);
236: if (saveOnClose) {
237: saveBoundComponentValues();
238: XPage currentPage = XProjectManager.getPageManager()
239: .getCurrentPage(null);
240: if (currentPage != null)
241: currentPage.updateBoundComponentValues();
242: }
243: setStatus(XPage.LOADED);
244:
245: if (closeBtnClicked)
246: lastReturnValue = CLOSE_CLICKED;
247: else if (saveOnClose)
248: lastReturnValue = CANCEL_CLICKED;
249: else
250: lastReturnValue = OK_CLICKED;
251: }
252:
253: /**
254: * Shows the dialog. This method calls showDialog( this ) after setting the
255: * title, location and after setting the dialog to a size just large enough to
256: * display all its content
257: *
258: * @param owner The container to which the dialog is added.
259: * @param title The dialog title/caption
260: * @param location The location on screen to show the dialog
261: * @return the returnValue
262: */
263: public int showDialog(Container owner, String title, Point location) {
264: setModal(true);
265: setCaption(componentFactory.translate(title));
266: setLocation(location);
267:
268: pack();
269: return showDialog(owner);
270: }
271:
272: /**
273: * Shows the dialog. For modal dialog the showDialog method blocks till the
274: * dialog is dismissed or hidden. This method provides an alternative that
275: * does not block execution of the calling thread but instead calls back a
276: * method specified as an argument once the dialog has been dismissed.
277: *
278: * In some VMs such as the Microsoft VM it is not possible to gain access to
279: * the EventQueue so as to implement blocking unless the code is loaded from a
280: * signed CAB file. If this situtaion occurs an exception is thrown and a
281: * non-blocking strategy is used.
282: *
283: * When the dialog is shown it will attempt to gain focus and upon dismissal
284: * focus will be returned to the component that had focus prior to display of
285: * the dialog. A special case occurs when the dialog is displayed in response
286: * to a focus event handler. The focus event will be processed as normal,
287: * allowing transfer of focus but focus handler invocations related to the
288: * showing and hiding of the dialog will be suppressed.
289: *
290: * @param cbParent The parent/owner for purposes of a callback.
291: * @param cb The name of a callback method in the parent (or null) to be invoked when the dialog is dismissed.
292: * @return the returnValue
293: */
294: public void showDialog(Component cbParent, String cb) {
295: callbackParent = cbParent;
296: callback = cb;
297: if (cbParent.getParent() == null)
298: showDialog((Container) cbParent);
299: else
300: showDialog(cbParent.getParent());
301: }
302:
303: /**
304: * Shows the dialog. For modal dialog this method blocks till the dialog is
305: * dismissed or hidden. A subclass can set the returnValue member to indicate
306: * the status of the dialog upon dismissal.
307: *
308: * When the dialog is shown it will attempt to gain focus and upon dismissal
309: * focus will be returned to the component that had focus prior to display of
310: * the dialog. A special case occurs when the dialog is displayed in response
311: * to a focus event handler. The focus event will be processed as normal,
312: * allowing transfer of focus but focus handler invocations related to the
313: * showing and hiding of the dialog will be suppressed.
314: *
315: * @param owner The container to which the dialog is added.
316: * @return the returnValue
317: */
318: public int showDialog(Container owner) {
319: closeBtnClicked = false;
320: if (owner != null)
321: focusComponent = getFocusComponent(owner);
322: updateBoundComponentValues();
323: pageActivated();
324: showModalWindow(this );
325:
326: // Gets around bug in the container control which doesn't diable it's child components
327: requestFocus();
328: repaint(0);
329:
330: if (minorVersion < 4) {
331: synchronized (getTreeLock()) {
332: if ((parent != null) && (parent.getPeer() == null))
333: parent.addNotify();
334: if (getPeer() == null)
335: addNotify();
336: }
337: validate();
338:
339: if (dialogWindow.isVisible()) {
340: if (bIsModal) {
341:
342: try {
343: if (QAvailable) {
344: dt = new XDialogEventDispatchThread(
345: "AWT-Dispatch-Proxy", getToolkit()
346: .getSystemEventQueue());
347: dt.start();
348: } else
349: return -1;
350: } catch (Exception ex) {
351: System.err.println("error 1");
352: ex.printStackTrace();
353: QAvailable = false;
354: return -1;
355: }
356:
357: while (dialogWindow.isVisible()) {
358: try {
359: Thread.currentThread().yield();
360: Thread.currentThread().sleep(100);
361: } catch (Exception e) {
362: }
363: }
364:
365: if (dt != null)
366: try {
367: dt.stopDispatching(bFireEmptyEvent);
368: } catch (Exception ex1) {
369: }
370: } else
371: super .show();
372:
373: dt = null;
374: }
375: }
376:
377: return returnValue;
378: }
379:
380: /**
381: * Set the size of the dialog and centres it within the parent.
382: * @param width The new width
383: * @param height The new height
384: */
385: public void setSize(int width, int height) {
386: contentPanel.setBounds(padding, padding, width - (padding * 2),
387: height - (padding * 2));
388: super .setSize(width, height);
389: }
390:
391: /**
392: * Gets the component that owns the focus.
393: * @param cont the container to be checked for focus.
394: * @return the focus component or null if the container does not have a
395: * component that owns the input focus.
396: */
397: protected Component getFocusComponent(Container cont) {
398: int numChildren = cont.getComponentCount();
399: for (int i = 0; i < numChildren; i++) {
400: Component c = cont.getComponent(i);
401: if (c instanceof Container) {
402: Component cc = getFocusComponent((Container) c);
403: if (cc != null)
404: return cc;
405: } else if ((XApplet.appWindow != null)
406: && (c == XApplet.appWindow.getFocusOwner()))
407: return c;
408: }
409:
410: return null;
411: }
412:
413: /**
414: * Provides access to an object representing the state of the dialog when it
415: * was closed. It is the responsibility of the subclass to set this value
416: * when it closes.
417: * @return the return object
418: */
419: public Object getReturnObject() {
420: return returnObject;
421: }
422:
423: /**
424: * A utility method used to determine if the last event corrseponds to a mouse
425: * click. The notion of a click is extended by assuming the a mouse press and
426: * release within a single component constitutes a click even if not at the
427: * same coordinate. A MouseEvent.MOUSE_CLICKED is only triggered when the press
428: * and release are at the same location and this is often inadequate for end-user
429: * interaction.
430: * @return true if the mouse was clicked
431: */
432: public boolean wasMouseClicked() {
433: boolean res = closeBtnClicked || eventHandler.wasMouseClicked();
434: closeBtnClicked = false;
435: return res;
436: }
437:
438: public void showModalWindow(Component contents) {
439: class HidingWindow extends JWindow implements MouseListener,
440: MouseMotionListener {
441: Point startPoint;
442: JButton b = new JButton("X");
443:
444: public HidingWindow(Frame frame) {
445: super (frame);
446:
447: addMouseListener(this );
448: addMouseMotionListener(this );
449: dialogWindow = this ;
450: setBackground(SystemColor.control);
451: setLayout(new BorderLayout());
452:
453: b.addActionListener(new ActionListener() {
454: public void actionPerformed(ActionEvent e) {
455: closeBtnClicked = true;
456: closeDlg();
457: }
458: });
459:
460: b.addKeyListener(new KeyListener() {
461: public void keyPressed(KeyEvent e) {
462: if (e.getKeyCode() == e.VK_ESCAPE) {
463: closeBtnClicked = true;
464: closeDlg(); //setVisible( false );
465: }
466: }
467:
468: public void keyReleased(KeyEvent e) {
469: }
470:
471: public void keyTyped(KeyEvent e) {
472: }
473: });
474:
475: b.setBackground(Color.red);
476: b.setForeground(Color.white);
477: add(b, BorderLayout.EAST);
478:
479: pack();
480: }
481:
482: public Insets getInsets() {
483: return new Insets(4, 4, 4, 4);
484: }
485:
486: public void setVisible(boolean show) {
487: super .setVisible(show);
488: if (!show) {
489: clientFrame.setEnabled(true);
490: appWindow.setEnabled(true);
491: clientFrame.toFront();
492:
493: } else {
494: toFront();
495: b.requestFocus();
496: }
497: }
498:
499: public void paint(Graphics g) {
500: Dimension size = getSize();
501: Color c = g.getColor();
502: g.setColor(SystemColor.activeCaption);
503: g.fill3DRect(0, 0, size.width, size.height, true);
504: g.setColor(Color.white);
505: g.drawString(title, 6, 17);
506: g.setColor(c);
507: super .paint(g);
508: }
509:
510: // Start of mouse methods-----------------------------------------------------
511: public void mouseClicked(MouseEvent e) {
512: }
513:
514: public void mouseEntered(MouseEvent e) {
515: }
516:
517: public void mouseExited(MouseEvent e) {
518: }
519:
520: public void mouseMoved(MouseEvent e) {
521: }
522:
523: public void mousePressed(MouseEvent e) {
524: startPoint = e.getPoint();
525: }
526:
527: public void mouseReleased(MouseEvent e) {
528: Point endPoint = e.getPoint();
529: Point oldPos = getLocation();
530: setLocation(oldPos.x + endPoint.x - startPoint.x,
531: oldPos.y + endPoint.y - startPoint.y);
532: }
533:
534: public void mouseDragged(MouseEvent e) {
535: Point endPoint = e.getPoint();
536: Point oldPos = getLocation();
537: setLocation(oldPos.x + endPoint.x - startPoint.x,
538: oldPos.y + endPoint.y - startPoint.y);
539: }
540: // End of mouse methods-------------------------------------------------------
541: }
542: ;
543:
544: class HidingDialog extends JDialog implements MouseListener,
545: MouseMotionListener {
546: Point startPoint;
547: JButton b = new JButton("X");
548:
549: public HidingDialog(Frame frame) {
550: super (frame, bIsModal);
551:
552: addMouseListener(this );
553: addMouseMotionListener(this );
554: dialogWindow = this ;
555: if (!bUseNativeHeaders) {
556: setBackground(SystemColor.control);
557: this .getContentPane().setLayout(new BorderLayout()); // 'this' identifier is needed for Ant compilation
558:
559: callDecorationMethod(this , true);
560:
561: b.addActionListener(new ActionListener() {
562: public void actionPerformed(ActionEvent e) {
563: closeBtnClicked = true;
564: closeDlg();
565: }
566: });
567:
568: b.addKeyListener(new KeyListener() {
569: public void keyPressed(KeyEvent e) {
570: if (e.getKeyCode() == e.VK_ESCAPE) {
571: closeBtnClicked = true;
572: closeDlg(); //setVisible( false );
573: }
574: }
575:
576: public void keyReleased(KeyEvent e) {
577: }
578:
579: public void keyTyped(KeyEvent e) {
580: }
581: });
582:
583: b.setBackground(Color.red);
584: b.setForeground(Color.white);
585: this .getContentPane().add(b, BorderLayout.EAST); // 'this' identifier is needed for Ant compilation
586: } else
587: super .setTitle(title);
588:
589: addWindowListener(new WindowListener() {
590: public void windowClosing(WindowEvent e) {
591: closeBtnClicked = true;
592: closeDlg();
593: }
594:
595: public void windowActivated(WindowEvent e) {
596: }
597:
598: public void windowDeactivated(WindowEvent e) {
599: }
600:
601: public void windowClosed(WindowEvent e) {
602: }
603:
604: public void windowOpened(WindowEvent e) {
605: }
606:
607: public void windowDeiconified(WindowEvent e) {
608: }
609:
610: public void windowIconified(WindowEvent e) {
611: }
612: });
613:
614: pack();
615: }
616:
617: public Insets getInsets() {
618: if (bUseNativeHeaders)
619: return super .getInsets();
620: return new Insets(4, 4, 4, 4);
621: }
622:
623: public void paint(Graphics g) {
624: if (!bUseNativeHeaders) {
625: Dimension size = getSize();
626: Color c = g.getColor();
627: g.setColor(SystemColor.activeCaption);
628: g.fill3DRect(0, 0, size.width, size.height, true);
629: g.setColor(Color.white);
630: g.drawString(title, 6, 17);
631: g.setColor(c);
632: int buttonHeight = b.getHeight();
633: int bX = b.getX() + 4;
634: int bY = b.getY() + 4;
635: g.translate(bX, bY);
636: b.paint(g);
637: g.translate(-bX, -bY);
638: g.setClip(4, 4 + buttonHeight, size.width - 8,
639: size.height - 6 - buttonHeight);
640: super .paint(g);
641: } else
642: super .paint(g);
643: }
644:
645: // Start of mouse methods-----------------------------------------------------
646: public void mouseClicked(MouseEvent e) {
647: }
648:
649: public void mouseEntered(MouseEvent e) {
650: }
651:
652: public void mouseExited(MouseEvent e) {
653: }
654:
655: public void mouseMoved(MouseEvent e) {
656: }
657:
658: public void mousePressed(MouseEvent e) {
659: startPoint = e.getPoint();
660: }
661:
662: public void mouseReleased(MouseEvent e) {
663: Point endPoint = e.getPoint();
664: Point oldPos = getLocation();
665: if (startPoint != null)
666: setLocation(oldPos.x + endPoint.x - startPoint.x,
667: oldPos.y + endPoint.y - startPoint.y);
668: }
669:
670: public void mouseDragged(MouseEvent e) {
671: Point endPoint = e.getPoint();
672: Point oldPos = getLocation();
673: if (startPoint != null)
674: setLocation(oldPos.x + endPoint.x - startPoint.x,
675: oldPos.y + endPoint.y - startPoint.y);
676: }
677: // End of mouse methods-------------------------------------------------------
678: }
679: ;
680:
681: final Window w = ((!bUseNativeHeaders && (minorVersion < 4)) ? (Window) new HidingWindow(
682: clientFrame)
683: : new HidingDialog(clientFrame));
684:
685: if (w instanceof JDialog)
686: ((JDialog) w).getContentPane().add(contents,
687: BorderLayout.SOUTH);
688: else
689: w.add(contents, BorderLayout.SOUTH);
690: contents.setVisible(true);
691: setStatus(XPage.ACTIVATED);
692:
693: w.pack();
694: Dimension wSize = w.getSize();
695: w
696: .setLocation(
697: appWindow.getLocation().x
698: + (appWindow.getSize().width / 2 - wSize.width / 2),
699: appWindow.getLocation().y
700: + (appWindow.getSize().height / 2 - wSize.height / 2));
701:
702: if (bUseNativeHeaders) {
703: clientFrame.setEnabled(false);
704: appWindow.setEnabled(false);
705: }
706: w.setVisible(true);
707: w.repaint();
708: }
709:
710: /**
711: * The event dispatch mechanism inserts an empty event when stopping dispatching.
712: * This mechanism fails on some VMs. This method determines whether or not to
713: * use the mechanism. It does not work for the MS VM (1.1.4) for instance.
714: * @return true to use the empty event mechanism.
715: */
716: private static boolean useEmptyEvent() {
717: String prop = System.getProperty("java.version");
718: int pos = prop.indexOf('.');
719: pos++;
720: int pos2 = prop.indexOf('.', pos);
721: minorVersion = new Integer(prop.substring(pos, pos2))
722: .intValue();
723:
724: if (minorVersion < 2)
725: return true;
726:
727: return true;
728: }
729:
730: /**
731: * Call setUndecorated via reflection as it is only in JDK1.4+
732: * @param obj a reference to the dialog instance
733: * @param value the boolean argument
734: * @return true if the call succeeds
735: */
736: private boolean callDecorationMethod(Object obj, boolean value) {
737: try {
738: Class c = obj.getClass();
739: Field f = this .getClass().getField("trueField");
740: Class[] params = new Class[1];
741: params[0] = f.getType();
742: Method theMethod = c.getMethod("setUndecorated", params);
743: String methodString = theMethod.getName();
744: Object args[] = new Object[1];
745: args[0] = f.get(this );
746: theMethod.invoke(obj, args);
747: return true;
748: } catch (Exception ex) {
749: ex.printStackTrace();
750: }
751: return false;
752: }
753:
754: /**
755: * Get the maximum x and y coordinates of the children
756: * @param cont the container whose children will be examined
757: * @return the coordinate of the maximum x and y
758: */
759: private Point getMaxCoordinates(Container cont) {
760: Point pt = cont.getLocation();
761:
762: int maxX = pt.x;
763: int maxY = pt.y;
764: int numChildren = cont.getComponentCount();
765: for (int i = 0; i < numChildren; i++) {
766: Component comp = cont.getComponent(i);
767: Dimension size = comp.getSize();
768: Point p = comp.getLocation();
769: maxX = Math.max(pt.x + p.x + size.width, maxX);
770: maxY = Math.max(pt.y + p.y + size.height, maxY);
771: if (comp instanceof Container) {
772: Point childDim = getMaxCoordinates((Container) comp);
773: maxX = Math.max(childDim.x, maxX);
774: maxY = Math.max(childDim.y, maxY);
775: }
776: }
777:
778: return new Point(maxX, maxY);
779: }
780:
781: private XDialogEventDispatchThread dt = null;
782: }
|