0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.javahelp;
0043:
0044: import java.awt.AWTEvent;
0045: import java.awt.BorderLayout;
0046: import java.awt.Dialog;
0047: import java.awt.Dimension;
0048: import java.awt.Frame;
0049: import java.awt.Point;
0050: import java.awt.Rectangle;
0051: import java.awt.Toolkit;
0052: import java.awt.Window;
0053: import java.awt.event.AWTEventListener;
0054: import java.awt.event.WindowEvent;
0055: import java.lang.ref.Reference;
0056: import java.lang.ref.SoftReference;
0057: import java.lang.reflect.InvocationTargetException;
0058: import java.lang.reflect.Method;
0059: import java.net.MalformedURLException;
0060: import java.net.URL;
0061: import java.util.ArrayList;
0062: import java.util.Collection;
0063: import java.util.HashMap;
0064: import java.util.Iterator;
0065: import java.util.List;
0066: import java.util.Map;
0067: import java.util.Stack;
0068: import java.util.logging.Level;
0069: import java.util.logging.LogRecord;
0070: import javax.help.HelpSet;
0071: import javax.help.HelpSetException;
0072: import javax.help.JHelp;
0073: import javax.swing.BorderFactory;
0074: import javax.swing.BoundedRangeModel;
0075: import javax.swing.DefaultBoundedRangeModel;
0076: import javax.swing.JComponent;
0077: import javax.swing.JDialog;
0078: import javax.swing.JFrame;
0079: import javax.swing.SwingUtilities;
0080: import org.netbeans.api.progress.ProgressHandle;
0081: import org.netbeans.api.progress.ProgressHandleFactory;
0082: import org.openide.util.HelpCtx;
0083: import org.openide.util.NbBundle;
0084: import org.openide.util.RequestProcessor;
0085: import org.openide.util.Task;
0086: import org.openide.util.TaskListener;
0087: import org.openide.util.Utilities;
0088: import org.openide.windows.WindowManager;
0089:
0090: // [PENDING] should event dispatch thread be used thruout?
0091:
0092: /** Help implementation using the JavaHelp 1.x system.
0093: * @author Jesse Glick, Richard Gregor
0094: */
0095: public final class JavaHelp extends AbstractHelp implements
0096: AWTEventListener {
0097:
0098: /** Make a JavaHelp implementation of the Help.Impl interface.
0099: *Or, use {@link #getDefaultJavaHelp}.
0100: */
0101: public JavaHelp() {
0102: Installer.log.fine("JavaHelp created");
0103: if (!isModalExcludedSupported()) {
0104: Toolkit.getDefaultToolkit().addAWTEventListener(this ,
0105: AWTEvent.WINDOW_EVENT_MASK);
0106: }
0107: }
0108:
0109: void deactivate() {
0110: if (!isModalExcludedSupported()) {
0111: Toolkit.getDefaultToolkit().removeAWTEventListener(this );
0112: }
0113: }
0114:
0115: // [PENDING] hold help sets weakly? softly? try to conserve memory...
0116: /** The master help set.
0117: */
0118: private HelpSet master = null;
0119: /** map from help sets to (soft refs to) components showing them */
0120: private Map<HelpSet, Reference<JHelp>> availableJHelps = new HashMap<HelpSet, Reference<JHelp>>();
0121: /** viewer (may be invisible) showing help normally; null until first used; if invisible, is empty */
0122: private JFrame frameViewer = null;
0123: /** viewer showing help parented to current modal dialog; initially null */
0124: private JDialog dialogViewer = null;
0125: /** whether user explicitly closed dialog viewer.
0126: * true - frame viewer was initially open, then reparented to dialog viewer,
0127: * then user closes main dialog and we ought to reparent to frame viewer
0128: * false - frame viewer not initially open anyway, or it was but the user
0129: * explicitly closed it as a dialog viewer, we should leave it closed
0130: */
0131: private boolean reparentToFrameLater = false;
0132: /** the modal dialog(s) currently in effect */
0133: private Stack<Dialog> currentModalDialogs = new Stack<Dialog>();
0134: /** modal dialogs stack has been used successfully */
0135: private boolean currentModalDialogsReady = false;
0136: /** last-displayed JHelp */
0137: private JHelp lastJH = null;
0138:
0139: /** progress of merging help sets; max is # of sets to merge */
0140: private static final BoundedRangeModel mergeModel = new DefaultBoundedRangeModel(
0141: 0, 0, 0, 0);
0142:
0143: private ProgressHandle progressHandle = null;
0144:
0145: private static final boolean isJdk15;
0146:
0147: static {
0148: String javaVersion = System.getProperty("java.version");
0149: isJdk15 = javaVersion.startsWith("1.5");
0150: }
0151:
0152: /** Get the master help set that others will be merged into.
0153: * @return the master help set
0154: */
0155: private synchronized HelpSet getMaster() {
0156: if (master == null) {
0157: ClassLoader loader = JavaHelp.class.getClassLoader();
0158: try {
0159: master = new HelpSet(
0160: loader,
0161: new URL(
0162: "nbresloc:/org/netbeans/modules/javahelp/resources/masterHelpSet.xml")); // NOI18N
0163: Collection<? extends HelpSet> sets = getHelpSets();
0164: List<HelpSet> toMerge = new ArrayList<HelpSet>(Math
0165: .min(1, sets.size()));
0166: for (HelpSet hs : sets) {
0167: if (shouldMerge(hs)) {
0168: toMerge.add(hs);
0169: }
0170: }
0171: mergeModel.setValue(0);
0172: mergeModel.setMaximum(toMerge.size());
0173: for (HelpSet hs : toMerge) {
0174: master.add(hs);
0175: mergeModel.setValue(mergeModel.getValue() + 1);
0176: }
0177: } catch (HelpSetException hse) {
0178: Installer.log.log(Level.WARNING, null, hse);
0179: master = new HelpSet();
0180: } catch (MalformedURLException mfue) {
0181: mfue.printStackTrace();
0182: throw new IllegalStateException();
0183: }
0184: }
0185: return master;
0186: }
0187:
0188: /** Called when set of helpsets changes.
0189: * Here, clear the master helpset, since it may
0190: * need to have different contents (or a different
0191: * order of contents) when next viewed.
0192: */
0193: protected void helpSetsChanged() {
0194: synchronized (this ) {
0195: // XXX might be better to incrementally add/remove helpsets?
0196: // Unfortunately the JavaHelp API does not provide a way to
0197: // insert them except in last position, which prevents smart
0198: // navigator ordering.
0199: master = null;
0200: }
0201: mergeModel.setValue(0);
0202: mergeModel.setMaximum(0);
0203: super .helpSetsChanged();
0204: }
0205:
0206: private Dialog currentModalDialog() {
0207: if (currentModalDialogs.empty()) {
0208: Window w = HelpAction.WindowActivatedDetector
0209: .getCurrentActivatedWindow();
0210: if (!currentModalDialogsReady && (w instanceof Dialog)
0211: && !(w instanceof ProgressDialog)
0212: && w != dialogViewer && ((Dialog) w).isModal()) {
0213: // #21286. A modal dialog was opened before JavaHelp was even created.
0214: Installer.log.fine("Early-opened modal dialog: "
0215: + w.getName() + " [" + ((Dialog) w).getTitle()
0216: + "]");
0217: return (Dialog) w;
0218: } else {
0219: return null;
0220: }
0221: } else {
0222: return currentModalDialogs.peek();
0223: }
0224: }
0225:
0226: private void ensureFrameViewer() {
0227: Installer.log.fine("ensureFrameViewer");
0228: if (frameViewer == null) {
0229: Installer.log.fine("\tcreating new");
0230: frameViewer = new JFrame();
0231: frameViewer
0232: .setIconImage(Utilities
0233: .loadImage("org/netbeans/modules/javahelp/resources/help.gif")); // NOI18N
0234: frameViewer.getAccessibleContext()
0235: .setAccessibleDescription(
0236: NbBundle.getMessage(JavaHelp.class,
0237: "ACSD_JavaHelp_viewer"));
0238:
0239: if (isModalExcludedSupported()) {
0240: setModalExcluded(frameViewer);
0241: frameViewer.getRootPane().putClientProperty(
0242: "netbeans.helpframe", Boolean.TRUE); // NOI18N
0243: }
0244: }
0245: }
0246:
0247: private void ensureDialogViewer() {
0248: Installer.log.fine("ensureDialogViewer");
0249: Dialog parent = currentModalDialog();
0250: if (dialogViewer != null && dialogViewer.getOwner() != parent) {
0251: Installer.log.fine("\tdisposing old");
0252: dialogViewer.setVisible(false);
0253: dialogViewer.dispose();
0254: dialogViewer = null;
0255: }
0256: if (dialogViewer == null) {
0257: Installer.log.fine("\tcreating new");
0258: dialogViewer = new JDialog(parent);
0259: dialogViewer.getAccessibleContext()
0260: .setAccessibleDescription(
0261: NbBundle.getMessage(JavaHelp.class,
0262: "ACSD_JavaHelp_viewer"));
0263: }
0264: }
0265:
0266: private void displayHelpInFrame(JHelp jh) {
0267: Installer.log.fine("displayHelpInFrame");
0268: if (jh == null)
0269: jh = lastJH;
0270: if (jh == null)
0271: throw new IllegalStateException();
0272: boolean newFrameViewer = (frameViewer == null);
0273: ensureFrameViewer();
0274: if (dialogViewer != null) {
0275: Installer.log.fine("\tdisposing old dialog");
0276: dialogViewer.setVisible(false);
0277: dialogViewer.getContentPane().removeAll();
0278: dialogViewer.dispose();
0279: dialogViewer = null;
0280: }
0281: if (frameViewer.getContentPane().getComponentCount() > 0
0282: && frameViewer.getContentPane().getComponent(0) != jh) {
0283: Installer.log.fine("\treplacing content");
0284: frameViewer.getContentPane().removeAll();
0285: }
0286: if (frameViewer.getContentPane().getComponentCount() == 0) {
0287: Installer.log.fine("\tadding content");
0288: frameViewer.getContentPane().add(jh, BorderLayout.CENTER);
0289: frameViewer.setTitle(jh.getModel().getHelpSet().getTitle());
0290: frameViewer.pack();
0291: }
0292: if (newFrameViewer) {
0293: // #22445: only do this stuff once when frame is made.
0294: // After that we need to remember the size and position.
0295: Rectangle bounds = Utilities.getUsableScreenBounds();
0296: Dimension frameSize = frameViewer.getSize();
0297: // #108255: Increase size of Help window by 30%
0298: frameSize.width = (int) (1.3 * frameSize.width);
0299: frameSize.height = (int) (1.3 * frameSize.height);
0300:
0301: frameViewer.setSize(frameSize);
0302:
0303: // #11018: have mercy on little screens
0304: if (frameSize.width > bounds.width) {
0305: frameSize.width = bounds.width;
0306: }
0307: if (frameSize.height > bounds.height) {
0308: frameSize.height = bounds.height;
0309: }
0310: if ((frameSize.width > bounds.width)
0311: || (frameSize.height > bounds.height)) {
0312: frameViewer.setSize(frameSize);
0313: }
0314: //Put frame to top right
0315: frameViewer.setLocation(new Point(bounds.x + bounds.width
0316: - frameViewer.getSize().width, bounds.y));
0317: }
0318:
0319: frameViewer.setState(Frame.NORMAL);
0320: if (frameViewer.isVisible()) {
0321: frameViewer.repaint();
0322: frameViewer.toFront(); // #20048
0323: Installer.log.fine("\talready visible, just repainting");
0324: } else {
0325: frameViewer.setVisible(true);
0326: }
0327: //#29417: This call of requestFocus causes lost focus when Help window
0328: //is reopened => removed.
0329: //frameViewer.requestFocus();
0330: lastJH = jh;
0331: }
0332:
0333: private void displayHelpInDialog(JHelp jh) {
0334: Installer.log.fine("displayHelpInDialog");
0335: if (jh == null)
0336: jh = lastJH;
0337: if (jh == null)
0338: throw new IllegalStateException();
0339: ensureDialogViewer();
0340: Rectangle bounds = null;
0341: if (frameViewer != null) {
0342: Installer.log.fine("\thiding old frame viewer");
0343: if (frameViewer.isVisible()) {
0344: bounds = frameViewer.getBounds();
0345: frameViewer.setVisible(false);
0346: }
0347: frameViewer.getContentPane().removeAll();
0348: }
0349: if (dialogViewer.getContentPane().getComponentCount() > 0
0350: && dialogViewer.getContentPane().getComponent(0) != jh) {
0351: Installer.log.fine("\tchanging content");
0352: dialogViewer.getContentPane().removeAll();
0353: }
0354: if (dialogViewer.getContentPane().getComponentCount() == 0) {
0355: Installer.log.fine("\tadding content");
0356: dialogViewer.getContentPane().add(jh, BorderLayout.CENTER);
0357: dialogViewer
0358: .setTitle(jh.getModel().getHelpSet().getTitle());
0359: dialogViewer.pack();
0360: }
0361: if (bounds != null) {
0362: Installer.log.fine("\tcopying bounds from frame viewer: "
0363: + bounds);
0364: dialogViewer.setBounds(bounds);
0365: }
0366: rearrange(currentModalDialog());
0367: if (dialogViewer.isVisible()) {
0368: Installer.log.fine("\talready visible, just repainting");
0369: dialogViewer.repaint();
0370: } else {
0371: dialogViewer.setVisible(true);
0372: }
0373: lastJH = jh;
0374: }
0375:
0376: /*
0377: private void closeFrameViewer() {
0378: if (frameViewer == null || !frameViewer.isVisible()) throw new IllegalStateException();
0379: Installer.log.fine("Closing frame viewer");
0380: frameViewer.setVisible(false);
0381: frameViewer.getContentPane().removeAll();
0382: }
0383: private void closeDialogViewer() {
0384: if (dialogViewer == null || !dialogViewer.isVisible()) throw new IllegalStateException();
0385: Installer.log.fine("Closing dialog viewer");
0386: dialogViewer.setVisible(false);
0387: dialogViewer.getContentPane().removeAll();
0388: dialogViewer.dispose();
0389: dialogViewer = null;
0390: }
0391: */
0392:
0393: /** Show some help.
0394: *This is the basic call which should be used externally
0395: *and is the result of {@link TopManager#showHelp}.
0396: *Handles null contexts, missing or null help IDs, and null URLs.
0397: *If there is any problem, shows the master set
0398: *instead, or it may also create a new help window.
0399: *Works correctly if invoked while a modal dialog is open--creates a new modal
0400: *dialog with the help. Else creates a frame to view the help in.
0401: * @param ctx the help context to display
0402: * @param showmaster whether to show the master help set or not
0403: */
0404: public void showHelp(HelpCtx ctx, final boolean showmaster) {
0405: final HelpCtx ctx2 = (ctx != null) ? ctx : HelpCtx.DEFAULT_HELP;
0406: if (!SwingUtilities.isEventDispatchThread()) {
0407: Installer.log.fine("showHelp later...");
0408: SwingUtilities.invokeLater(new Runnable() {
0409: public void run() {
0410: showHelp(ctx2, showmaster);
0411: }
0412: });
0413: return;
0414: }
0415: LogRecord r = new LogRecord(Level.FINE, "LOG_SHOWING_HELP"); // NOI18N
0416: r.setParameters(new Object[] { ctx2.getHelpID() });
0417: r.setResourceBundleName("org.netbeans.modules.javahelp.Bundle"); // NOI18N
0418: r.setResourceBundle(NbBundle.getBundle(JavaHelp.class)); // NOI18N
0419: r.setLoggerName(Installer.UI.getName());
0420: Installer.log.log(r);
0421: Installer.UI.log(r);
0422: final HelpSet[] hs_ = new HelpSet[1];
0423: Runnable run = new Runnable() {
0424: public void run() {
0425: String id = ctx2.getHelpID();
0426: if (showmaster || ctx2.equals(HelpCtx.DEFAULT_HELP)
0427: || id == null) {
0428: Installer.log.fine("getting master...");
0429: hs_[0] = getMaster();
0430: Installer.log.fine("getting master...done");
0431: }
0432: if (hs_[0] == null
0433: ||
0434: /* #22670: if ID in hidden helpset, use that HS, even if showmaster */
0435: (id != null && !hs_[0].getCombinedMap()
0436: .isValidID(id, hs_[0]))) {
0437: Installer.log.fine("finding help set for " + id
0438: + "...");
0439: hs_[0] = findHelpSetForID(id);
0440: Installer.log.fine("finding help set for " + id
0441: + "...done");
0442: }
0443: }
0444: };
0445: if (master == null) {
0446: // Computation required. Show the progress dialog and do the computation
0447: // in a separate thread. When finished, the progress dialog will hide
0448: // itself and control will return to event thread.
0449: Installer.log.fine("showing progress dialog...");
0450: progressHandle = ProgressHandleFactory.createHandle("");
0451: createProgressDialog(run, currentModalDialog()).setVisible(
0452: true);
0453: progressHandle.finish();
0454: Installer.log.fine("dialog done.");
0455: } else {
0456: // Nothing much to do, run it synchronously in event thread.
0457: run.run();
0458: }
0459: HelpSet hs = hs_[0];
0460: if (hs == null) {
0461: // Interrupted dialog?
0462: return;
0463: }
0464: JHelp jh = createJHelp(hs);
0465: if (jh == null) {
0466: return;
0467: }
0468:
0469: if (isModalExcludedSupported()) {
0470: displayHelpInFrame(jh);
0471: } else {
0472: if (currentModalDialog() == null) {
0473: Installer.log.fine("showing as non-dialog");
0474: displayHelpInFrame(jh);
0475: } else {
0476: Installer.log.fine("showing as dialog");
0477: displayHelpInDialog(jh);
0478: }
0479: }
0480: displayInJHelp(jh, ctx2.getHelpID(), ctx2.getHelp());
0481: }
0482:
0483: /** Handle modal dialogs opening and closing. Note reparentToFrameLater state = rTFL.
0484: * Cases:
0485: * 1. No viewer open. Dialog opened. Push it on stack. rTFL = false.
0486: * 2. No viewer open, !rTFL. Top dialog closed. Pop it.
0487: * 3. No viewer open, rTFL. Only top dialog closed. Pop it. Create frame viewer.
0488: * 4. No viewer open, rTFL. Some top dialog closed. Pop it. Create dialog viewer.
0489: * 5. Frame viewer open. Dialog opened. Push it. Close frame viewer. Create dialog viewer. rTFL = true.
0490: * 6. Dialog viewer open. Dialog opened. Push it. Reparent dialog viewer.
0491: * 7. Dialog viewer open. Viewer closed. rTFL = false.
0492: * It cannot happen that the dialog viewer is still open when the top dialog has been
0493: * closed, as AWT will automatically close the dialog viewer first. However in this case
0494: * it sends only CLOSED for the dialog viewer. If the user closes it, CLOSING is sent at
0495: * that time, and CLOSED also later when the main dialog is closed.
0496: */
0497: public void eventDispatched(AWTEvent awtev) {
0498: WindowEvent ev = (WindowEvent) awtev;
0499: int type = ev.getID();
0500: Window w = ev.getWindow();
0501: if (type == WindowEvent.WINDOW_CLOSING && w == dialogViewer) {
0502: Installer.log
0503: .fine("7. Dialog viewer open. Viewer closed. rTFL = false.");
0504: reparentToFrameLater = false;
0505: }
0506: if (type != WindowEvent.WINDOW_CLOSED
0507: && type != WindowEvent.WINDOW_OPENED) {
0508: //Installer.log.fine("uninteresting window event: " + ev);
0509: return;
0510: }
0511: if (w instanceof Dialog) {
0512: Dialog d = (Dialog) w;
0513: String dlgClass = d.getClass().getName();
0514: if ((d.isModal() && !(d instanceof ProgressDialog))
0515: || d == dialogViewer) {
0516: //#40950: Print and Page Setup dialogs was not displayed from java help window.
0517: if ("sun.awt.windows.WPageDialog".equals(dlgClass)
0518: || // NOI18N
0519: "sun.awt.windows.WPrintDialog".equals(dlgClass)
0520: || // NOI18N
0521: "sun.print.ServiceDialog".equals(dlgClass)
0522: || "apple.awt.CPrinterJobDialog"
0523: .equals(dlgClass)
0524: || "apple.awt.CPrinterPageDialog"
0525: .equals(dlgClass)) { // NOI18N
0526: //It is the print or print settings dialog for javahelp, do nothing
0527: return;
0528: }
0529:
0530: //#47150: Race condition in toolkit if two dialogs are shown in a row
0531: if (d instanceof JDialog) {
0532: if ("true".equals(((JDialog) d).getRootPane()
0533: .getClientProperty(
0534: "javahelp.ignore.modality"))) { //NOI18N
0535: return;
0536: }
0537: }
0538:
0539: if (Installer.log.isLoggable(Level.FINE)) {
0540: Installer.log
0541: .fine("modal (or viewer) dialog event: "
0542: + ev + " [" + d.getTitle() + "]");
0543: }
0544: if (type == WindowEvent.WINDOW_CLOSED) {
0545: if (d == dialogViewer) {
0546: // ignore, expected
0547: } else if (d == currentModalDialog()) {
0548: if (!currentModalDialogs.isEmpty()) {
0549: currentModalDialogs.pop();
0550: currentModalDialogsReady = true;
0551: } else {
0552: Installer.log.log(Level.WARNING, null,
0553: new IllegalStateException(
0554: "Please see IZ #24993")); // NOI18N
0555: }
0556: showDialogStack();
0557: if ((frameViewer == null
0558: || !frameViewer.isVisible() ||
0559: /* 14393 */frameViewer.getState() == Frame.ICONIFIED)
0560: && (dialogViewer == null || !dialogViewer
0561: .isVisible())) {
0562: if (!reparentToFrameLater) {
0563: Installer.log
0564: .fine("2. No viewer open, !rTFL. Top dialog closed. Pop it.");
0565: } else if (currentModalDialog() == null) {
0566: Installer.log
0567: .fine("3. No viewer open, rTFL. Only top dialog closed. Pop it. Create frame viewer.");
0568: //#47150 - reusing the old frame viewer can cause
0569: //re-showing the frame viewer to re-show the dialog
0570: if (frameViewer != null) {
0571: frameViewer.dispose();
0572: frameViewer = null;
0573: }
0574: displayHelpInFrame(null);
0575: } else {
0576: Installer.log
0577: .fine("4. No viewer open, rTFL. Some top dialog closed. Pop it. Create dialog viewer.");
0578: displayHelpInDialog(null);
0579: }
0580: } else if (dialogViewer != null
0581: && dialogViewer.isVisible()) {
0582: Installer.log
0583: .warning("dialogViewer should not still be open"); // NOI18N
0584: } else {
0585: Installer.log
0586: .warning("frameViewer visible when a dialog was closing"); // NOI18N
0587: }
0588: } else {
0589: Installer.log
0590: .fine("some random modal dialog closed: "
0591: + d.getName()
0592: + " ["
0593: + d.getTitle() + "]");
0594: }
0595: } else {
0596: // WINDOW_OPENED
0597: if (d != dialogViewer) {
0598: currentModalDialogs.push(d);
0599: showDialogStack();
0600: if ((frameViewer == null
0601: || !frameViewer.isVisible() ||
0602: /* 14393 */frameViewer.getState() == Frame.ICONIFIED)
0603: && (dialogViewer == null || !dialogViewer
0604: .isVisible())) {
0605: Installer.log
0606: .fine("1. No viewer open. Dialog opened. Push it on stack. rTFL = false.");
0607: reparentToFrameLater = false;
0608: } else if (frameViewer != null
0609: && frameViewer.isVisible()) {
0610: Installer.log
0611: .fine("5. Frame viewer open. Dialog opened. Push it. Close frame viewer. Create dialog viewer. rTFL = true.");
0612: displayHelpInDialog(null);
0613: reparentToFrameLater = true;
0614: } else if (dialogViewer != null
0615: && dialogViewer.isVisible()) {
0616: Installer.log
0617: .fine("6. Dialog viewer open. Dialog opened. Push it. Reparent dialog viewer.");
0618: displayHelpInDialog(null);
0619: } else {
0620: Installer.log.warning("logic error"); // NOI18N
0621: }
0622: } else {
0623: // dialog viewer opened, fine
0624: }
0625: }
0626: } else {
0627: //Installer.log.fine("nonmodal dialog event: " + ev);
0628: }
0629: } else {
0630: //Installer.log.fine("frame event: " + ev);
0631: }
0632: }
0633:
0634: private void showDialogStack() {
0635: if (Installer.log.isLoggable(Level.FINE)) {
0636: StringBuffer buf = new StringBuffer(
0637: "new modal dialog stack: ["); // NOI18N
0638: boolean first = true;
0639: Iterator it = currentModalDialogs.iterator();
0640: while (it.hasNext()) {
0641: if (first) {
0642: first = false;
0643: } else {
0644: buf.append(", "); // NOI18N
0645: }
0646: buf.append(((Dialog) it.next()).getTitle());
0647: }
0648: buf.append("]"); // NOI18N
0649: Installer.log.fine(buf.toString());
0650: }
0651: }
0652:
0653: /** If needed, visually rearrange dialogViewer and dlg on screen.
0654: * If they overlap, try to make them not overlap.
0655: * @param dlg the visible modal dialog
0656: */
0657: private void rearrange(Dialog dlg) {
0658: Rectangle r1 = dlg.getBounds();
0659: Rectangle r2 = dialogViewer.getBounds();
0660: if (r1.intersects(r2)) {
0661: Installer.log
0662: .fine("modal dialog and dialog viewer overlap");
0663: Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
0664: int xExtra = s.width - r1.width - r2.width;
0665: int yExtra = s.height - r1.height - r2.height;
0666: if (xExtra >= yExtra) {
0667: //compare y axes of r1 and r2 to know how to relocate them - horizontal relocation
0668: int r1Yaxis = r1.x + (r1.width / 2);
0669: int r2Yaxis = r2.x + (r2.width / 2);
0670: if (r1Yaxis <= r2Yaxis) {
0671: Installer.log.fine(" send help to the right");
0672: if ((r1.x + r1.width + r2.width) <= s.width) {
0673: Installer.log
0674: .fine("there is enough place fo help");
0675: r2.x = r1.x + r1.width;
0676: } else {
0677: Installer.log.fine("there is not enough place");
0678: if ((r1.width + r2.width) < s.width) {
0679: Installer.log.fine("relocate both");
0680: r2.x = s.width - r2.width;
0681: r1.x = r2.x - r1.width;
0682: } else {
0683: Installer.log
0684: .fine("relocate both and resize help");
0685: r1.x = 0;
0686: r2.x = r1.width;
0687: r2.width = s.width - r1.width;
0688: }
0689: }
0690: } else {
0691: Installer.log.fine("send help to the left");
0692: if ((r1.x - r2.width) > 0) {
0693: Installer.log
0694: .fine("there is enough place for help");
0695: r2.x = r1.x - r2.width;
0696: } else {
0697: Installer.log.fine("there is not enough place");
0698: if ((r1.width + r2.width) < s.width) {
0699: Installer.log.fine("relocate both");
0700: r2.x = 0;
0701: r1.x = r2.width;
0702: } else {
0703: Installer.log
0704: .fine("relocate both and resize help");
0705: r1.x = s.width - r1.width;
0706: r2.x = 0;
0707: r2.width = r1.x;
0708: }
0709: }
0710: }
0711: } else {
0712: //compare x axes of r1 and r2 to know how to relocate them
0713: int r1Xaxis = r1.y + (r1.height / 2);
0714: int r2Xaxis = r2.y + (r2.height / 2);
0715: if (r1Xaxis <= r2Xaxis) {
0716: Installer.log.fine(" send help to the bottom");
0717: if ((r1.y + r1.height + r2.height) <= s.height) {
0718: Installer.log
0719: .fine("there is enough place fo help");
0720: r2.y = r1.y + r1.height;
0721: } else {
0722: Installer.log.fine("there is not enough place");
0723: if ((r1.height + r2.height) < s.height) {
0724: Installer.log.fine("relocate both");
0725: r2.y = s.height - r2.height;
0726: r1.y = r2.y - r1.height;
0727: } else {
0728: Installer.log
0729: .fine("relocate both and resize help");
0730: r1.y = 0;
0731: r2.y = r1.height;
0732: r2.height = s.height - r1.height;
0733: }
0734: }
0735: } else {
0736: Installer.log.fine("send help to the top");
0737: if ((r1.y - r2.height) > 0) {
0738: Installer.log
0739: .fine("there is enough place for help");
0740: r2.y = r1.y - r2.height;
0741: } else {
0742: Installer.log.fine("there is not enough place");
0743: if ((r1.height + r2.height) < s.height) {
0744: Installer.log.fine("relocate both");
0745: r2.y = 0;
0746: r1.y = r2.height;
0747: } else {
0748: Installer.log
0749: .fine("relocate both and resize help");
0750: r1.y = s.height - r1.height;
0751: r2.y = 0; //or with -1
0752: r2.height = r1.y;
0753: }
0754: }
0755: }
0756: }
0757: dlg.setBounds(r1);
0758: dialogViewer.setBounds(r2);
0759: }
0760: }
0761:
0762: /** Make a dialog showing progress of parsing & merging help sets.
0763: * Show it; when the runnable is done, it will hide itself.
0764: * @param run something to do while it is showing
0765: * @param parent dialog (may be null)
0766: * @return a new progress dialog
0767: */
0768: private JDialog createProgressDialog(Runnable run, Dialog parent) {
0769: return (parent == null) ? new ProgressDialog(run, WindowManager
0770: .getDefault().getMainWindow()) : new ProgressDialog(
0771: run, parent);
0772: }
0773:
0774: private final class ProgressDialog extends JDialog implements
0775: TaskListener, Runnable {
0776: private Runnable run;
0777:
0778: public ProgressDialog(Runnable run, Dialog parent) {
0779: super (parent, NbBundle.getMessage(JavaHelp.class,
0780: "TITLE_loading_help_sets"), true);
0781: init(run);
0782: }
0783:
0784: public ProgressDialog(Runnable run, Frame parent) {
0785: super (parent, NbBundle.getMessage(JavaHelp.class,
0786: "TITLE_loading_help_sets"), true);
0787: init(run);
0788: }
0789:
0790: private void init(Runnable run) {
0791: this .run = run;
0792: JComponent c = ProgressHandleFactory
0793: .createProgressComponent(progressHandle);
0794: c.setPreferredSize(new Dimension(
0795: 3 * c.getPreferredSize().width, 3 * c
0796: .getPreferredSize().height));
0797: c
0798: .setBorder(BorderFactory.createEmptyBorder(10, 10,
0799: 10, 10));
0800: getContentPane().add(c);
0801: progressHandle.start();
0802: getAccessibleContext().setAccessibleDescription(
0803: NbBundle.getMessage(JavaHelp.class,
0804: "ACSD_Loading_Dialog")); //NOI18N
0805: pack();
0806: Dimension screen = Toolkit.getDefaultToolkit()
0807: .getScreenSize();
0808: Dimension me = getSize();
0809: setLocation((screen.width - me.width) / 2,
0810: (screen.height - me.height) / 2);
0811: }
0812:
0813: public void setVisible(boolean show) {
0814: if (show && run != null) {
0815: Installer.log
0816: .fine("posting request from progress dialog...");
0817: getHelpLoader().post(run).addTaskListener(this );
0818: run = null;
0819: }
0820: super .setVisible(show);
0821: }
0822:
0823: public void taskFinished(Task task) {
0824: Installer.log
0825: .fine("posting request from progress dialog...request finished.");
0826: SwingUtilities.invokeLater(this );
0827: }
0828:
0829: public void run() {
0830: setVisible(false);
0831: dispose();
0832: }
0833: }
0834:
0835: private static RequestProcessor helpLoader = null;
0836:
0837: private static RequestProcessor getHelpLoader() {
0838: if (helpLoader == null) {
0839: helpLoader = new RequestProcessor(
0840: "org.netbeans.modules.javahelp.JavaHelp"); // NOI18N
0841: }
0842: return helpLoader;
0843: }
0844:
0845: /** Find the proper help set for an ID.
0846: * @param id the ID to look up (may be null)
0847: * @return the proper help set (master if not otherwise found)
0848: */
0849: private HelpSet findHelpSetForID(String id) {
0850: if (id != null) {
0851: Iterator it = getHelpSets().iterator();
0852: while (it.hasNext()) {
0853: HelpSet hs = (HelpSet) it.next();
0854: if (hs.getCombinedMap().isValidID(id, hs))
0855: return hs;
0856: }
0857: warnBadID(id);
0858: }
0859: return getMaster();
0860: }
0861:
0862: public Boolean isValidID(String id, boolean force) {
0863: if (force || helpSetsReady()) {
0864: Iterator it = getHelpSets().iterator();
0865: if (MASTER_ID.equals(id)) {
0866: if (it.hasNext()) {
0867: Installer.log.fine("master id, and >=1 help set");
0868: return Boolean.TRUE;
0869: } else {
0870: Installer.log.fine("master id, and 0 help sets");
0871: return Boolean.FALSE;
0872: }
0873: } else {
0874: // Regular ID.
0875: while (it.hasNext()) {
0876: HelpSet hs = (HelpSet) it.next();
0877: if (hs.getCombinedMap().isValidID(id, hs)) {
0878: Installer.log.fine("found normal valid id "
0879: + id + " in " + hs.getTitle());
0880: return Boolean.TRUE;
0881: }
0882: }
0883: Installer.log.fine("did not find id " + id);
0884: return Boolean.FALSE;
0885: }
0886: } else {
0887: Installer.log.fine("not checking " + id + " specifically");
0888: return null;
0889: }
0890: }
0891:
0892: /** Warn that an ID was not found in any help set.
0893: * @param id the help ID
0894: */
0895: private static void warnBadID(String id) {
0896: // PLEASE DO NOT COMMENT OUT...localized warning
0897: Installer.log.fine(NbBundle.getMessage(JavaHelp.class,
0898: "MSG_jh_id_not_found", id));
0899: }
0900:
0901: /** Display something in a JHelp.
0902: *Handles {@link #MASTER_ID}, as well as help IDs
0903: *that were not found in any help set, various exceptions, etc.
0904: * @param jh the help component
0905: * @param helpID a help ID string to display, may be <CODE>null</CODE>
0906: * @param url a URL to display, may be <CODE>null</CODE>; lower priority than the help ID
0907: */
0908: private synchronized void displayInJHelp(JHelp jh, String helpID,
0909: URL url) {
0910: if (jh == null)
0911: throw new NullPointerException();
0912: if (jh.getModel() == null)
0913: throw new IllegalArgumentException();
0914: Installer.log.fine("displayInJHelp: " + helpID + " " + url);
0915: try {
0916: if (helpID != null && !helpID.equals(MASTER_ID)) {
0917: HelpSet hs = jh.getModel().getHelpSet();
0918: if (hs.getCombinedMap().isValidID(helpID, hs)) {
0919: jh.setCurrentID(helpID);
0920: } else {
0921: warnBadID(helpID);
0922: }
0923: } else if (url != null) {
0924: jh.setCurrentURL(url);
0925: }
0926: } catch (RuntimeException e) {
0927: Installer.log.log(Level.WARNING, null, e);
0928: }
0929: }
0930:
0931: /** Create & return a JHelp with the supplied help set.
0932: * In the case of the master help, will show the home page for
0933: * the distinguished help set if there is exactly one such,
0934: * or in the case of exactly one home page, will show that.
0935: * Caches the result and the result may be a reused JHelp.
0936: * @return the new JHelp
0937: * @param hs the help set to show
0938: */
0939: private JHelp createJHelp(HelpSet hs) {
0940: if (hs == null)
0941: throw new NullPointerException();
0942: JHelp jh;
0943: synchronized (availableJHelps) {
0944: Reference<JHelp> r = availableJHelps.get(hs);
0945: if (r != null) {
0946: jh = r.get();
0947: if (jh != null) {
0948: return jh;
0949: }
0950: }
0951: }
0952: String title = null; // for debugging purposes
0953: try {
0954: title = hs.getTitle();
0955: jh = new JHelp(hs);
0956: } catch (RuntimeException e) {
0957: Installer.log.log(Level.WARNING,
0958: "While trying to display: " + title, e); // NOI18N
0959: return null;
0960: }
0961: synchronized (availableJHelps) {
0962: availableJHelps.put(hs, new SoftReference<JHelp>(jh));
0963: }
0964: try {
0965: javax.help.Map.ID home = hs.getHomeID();
0966: if (home != null) {
0967: jh.setCurrentID(home);
0968: }
0969: } catch (Exception e) {
0970: Installer.log.log(Level.WARNING, null, e);
0971: }
0972: return jh;
0973: }
0974:
0975: // XXX(ttran) see JDK bug 5092094 for details
0976:
0977: private static int modalExcludedSupported = -1;
0978:
0979: private static boolean isModalExcludedSupported() {
0980: if (modalExcludedSupported == -1) {
0981: modalExcludedSupported = 0;
0982: if (isJdk15) {
0983: try {
0984: Class<?> clazz = Class
0985: .forName("sun.awt.SunToolkit"); // NOI18N
0986: Method m = clazz
0987: .getMethod("isModalExcludedSupported"); // NOI18N
0988: Boolean b = (Boolean) m.invoke(null);
0989: modalExcludedSupported = b.booleanValue() ? 1 : 0;
0990: Installer.log.fine("isModalExcludedSupported = "
0991: + modalExcludedSupported); // NOI18N
0992: } catch (ThreadDeath ex) {
0993: throw ex;
0994: } catch (Throwable ex) {
0995: Installer.log
0996: .info("isModalExcludedSupported() failed "
0997: + ex); // NOI18N
0998: }
0999: } else {
1000: //Newer JDK version
1001: Class typeClass = null;
1002: try {
1003: typeClass = Class
1004: .forName("java.awt.Dialog$ModalExclusionType");
1005: } catch (ClassNotFoundException ex) {
1006: Installer.log
1007: .info("Cannot get class java.awt.Dialog$ModalExclusionType "
1008: + ex); // NOI18N
1009: return false;
1010: }
1011: Method isSupportedMethod = null;
1012: try {
1013: isSupportedMethod = Toolkit.class.getMethod(
1014: "isModalExclusionTypeSupported", typeClass);
1015: } catch (NoSuchMethodException ex) {
1016: Installer.log
1017: .info("Cannot get method isModalExcludedSupported "
1018: + ex); // NOI18N
1019: return false;
1020: }
1021: Method methodValues = null;
1022: try {
1023: methodValues = typeClass.getMethod("valueOf",
1024: String.class);
1025: } catch (NoSuchMethodException ex) {
1026: Installer.log.info("Cannot get method valueOf "
1027: + ex); // NOI18N
1028: return false;
1029: }
1030: Object modalityExclusionType = null;
1031: try {
1032: modalityExclusionType = methodValues.invoke(null,
1033: "APPLICATION_EXCLUDE");
1034: } catch (IllegalAccessException ex) {
1035: Installer.log.info("Cannot invoke method valueOf "
1036: + ex); // NOI18N
1037: return false;
1038: } catch (InvocationTargetException ex) {
1039: Installer.log.info("Cannot invoke method valueOf "
1040: + ex); // NOI18N
1041: return false;
1042: }
1043: try {
1044: Boolean b = (Boolean) isSupportedMethod.invoke(
1045: Toolkit.getDefaultToolkit(),
1046: modalityExclusionType);
1047: modalExcludedSupported = b.booleanValue() ? 1 : 0;
1048: Installer.log.fine("isModalExcludedSupported = "
1049: + modalExcludedSupported); // NOI18N
1050: } catch (IllegalAccessException ex) {
1051: Installer.log
1052: .info("Cannot invoke method isModalExcludedSupported "
1053: + ex); // NOI18N
1054: return false;
1055: } catch (InvocationTargetException ex) {
1056: Installer.log
1057: .info("Cannot invoke method isModalExcludedSupported "
1058: + ex); // NOI18N
1059: return false;
1060: }
1061: }
1062: }
1063: return modalExcludedSupported == 1;
1064: }
1065:
1066: private static void setModalExcluded(Window window) {
1067: if (modalExcludedSupported == 0) {
1068: return;
1069: }
1070:
1071: if (isJdk15) {
1072: try {
1073: Class<?> clazz = Class.forName("sun.awt.SunToolkit"); // NOI18N
1074: Method m = clazz.getMethod("setModalExcluded",
1075: Window.class); // NOI18N
1076: m.invoke(null, window);
1077: } catch (ThreadDeath ex) {
1078: throw ex;
1079: } catch (Throwable ex) {
1080: Installer.log.info("setModalExcluded(Window) failed "
1081: + ex); // NOI18N
1082: modalExcludedSupported = 0;
1083: }
1084: } else {
1085: Class typeClass = null;
1086: try {
1087: typeClass = Class
1088: .forName("java.awt.Dialog$ModalExclusionType");
1089: } catch (ClassNotFoundException ex) {
1090: Installer.log
1091: .info("Cannot get class java.awt.Dialog$ModalExclusionType "
1092: + ex); // NOI18N
1093: modalExcludedSupported = 0;
1094: return;
1095: }
1096: Method methodValues = null;
1097: try {
1098: methodValues = typeClass.getMethod("valueOf",
1099: String.class);
1100: } catch (NoSuchMethodException ex) {
1101: Installer.log.info("Cannot get method valueOf(String) "
1102: + ex); // NOI18N
1103: modalExcludedSupported = 0;
1104: return;
1105: }
1106: Object modalityExclusionType = null;
1107: try {
1108: modalityExclusionType = methodValues.invoke(null,
1109: new Object[] { "APPLICATION_EXCLUDE" });
1110: } catch (IllegalAccessException ex) {
1111: Installer.log
1112: .info("Cannot invoke method valueOf(String) "
1113: + ex); // NOI18N
1114: modalExcludedSupported = 0;
1115: return;
1116: } catch (InvocationTargetException ex) {
1117: Installer.log
1118: .info("Cannot invoke method valueOf(String) "
1119: + ex); // NOI18N
1120: modalExcludedSupported = 0;
1121: return;
1122: }
1123: Method setMethod = null;
1124: try {
1125: setMethod = Window.class.getMethod(
1126: "setModalExclusionType", typeClass); // NOI18N
1127: } catch (NoSuchMethodException ex) {
1128: Installer.log
1129: .info("Cannot get method setModalExclusionType "
1130: + ex); // NOI18N
1131: modalExcludedSupported = 0;
1132: return;
1133: }
1134: try {
1135: setMethod.invoke(window, modalityExclusionType);
1136: } catch (IllegalAccessException ex) {
1137: Installer.log
1138: .info("Cannot invoke method setModalExclusionType "
1139: + ex); // NOI18N
1140: modalExcludedSupported = 0;
1141: return;
1142: } catch (InvocationTargetException ex) {
1143: Installer.log
1144: .info("Cannot invoke method setModalExclusionType "
1145: + ex); // NOI18N
1146: modalExcludedSupported = 0;
1147: return;
1148: }
1149: }
1150: }
1151: }
|