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