001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.javahelp;
043:
044: import java.awt.Image;
045: import java.awt.Toolkit;
046: import java.awt.event.ActionEvent;
047: import java.beans.*;
048: import java.io.ByteArrayInputStream;
049: import java.io.IOException;
050: import javax.swing.AbstractAction;
051: import javax.swing.Action;
052: import javax.swing.ImageIcon;
053: import javax.swing.event.ChangeEvent;
054: import javax.swing.event.ChangeListener;
055:
056: import org.xml.sax.*;
057:
058: import org.openide.cookies.InstanceCookie;
059: import org.openide.loaders.XMLDataObject;
060: import org.openide.nodes.*;
061: import org.openide.util.HelpCtx;
062: import org.openide.util.Lookup;
063: import org.openide.util.Utilities;
064:
065: import org.netbeans.api.javahelp.Help;
066:
067: /** XML processor for help context links.
068: * The associated instance makes it suitable for
069: * inclusion in a menu or toolbar.
070: * @author Jesse Glick
071: */
072: public final class HelpCtxProcessor implements XMLDataObject.Processor,
073: InstanceCookie.Of {
074:
075: private static Help findHelp() {
076: return (Help) Lookup.getDefault().lookup(Help.class);
077: }
078:
079: /** the XML file being parsed
080: */
081: private XMLDataObject xml;
082:
083: /** the cached action
084: */
085: private Action p;
086:
087: /** Bind to an XML file.
088: * @param xml the file to parse
089: */
090: public void attachTo(XMLDataObject xml) {
091: this .xml = xml;
092: Installer.log.fine("processing help context ref: "
093: + xml.getPrimaryFile());
094: }
095:
096: /** Get the class produced.
097: * @throws IOException doesn't
098: * @throws ClassNotFoundException doesn't
099: * @return the presenter class
100: */
101: public Class instanceClass() throws IOException,
102: ClassNotFoundException {
103: return ShortcutAction.class;
104: }
105:
106: /** Get the name of the class produced.
107: * @return the name of the presenter class
108: */
109: public String instanceName() {
110: return "org.netbeans.modules.javahelp.HelpCtxProcessor$ShortcutAction"; // NOI18N
111: }
112:
113: /** Test if this instance is of a suitable type.
114: * @param type some superclass
115: * @return true if it can be assigned to the desired superclass
116: */
117: public boolean instanceOf(Class type) {
118: return type == Action.class;
119: }
120:
121: /** Create the presenter.
122: * @throws IOException doesn't
123: * @throws ClassNotFoundException doesn't
124: * @return the new presenter
125: */
126: public Object instanceCreate() throws IOException,
127: ClassNotFoundException {
128: if (p != null)
129: return p;
130:
131: Installer.log.fine("creating help context presenter from "
132: + xml.getPrimaryFile());
133:
134: EntityResolver resolver = new EntityResolver() {
135: public InputSource resolveEntity(String pubid, String sysid) {
136: return new InputSource(new ByteArrayInputStream(
137: new byte[0]));
138: }
139: };
140:
141: HandlerBase handler = new HandlerBase() {
142: public void startElement(String name, AttributeList amap)
143: throws SAXException {
144: if ("helpctx".equals(name)) { // NOI18N
145: String id = amap.getValue("id"); // NOI18N
146: String showmaster = amap.getValue("showmaster"); // NOI18N
147: if (id != null && !"".equals(id)) { // NOI18N
148: p = new ShortcutAction(xml, id, Boolean
149: .valueOf(showmaster).booleanValue());
150: }
151: }
152: }
153: };
154:
155: Parser parser = xml.createParser();
156: parser.setEntityResolver(resolver);
157: parser.setDocumentHandler(handler);
158:
159: try {
160: parser.parse(new InputSource(xml.getPrimaryFile()
161: .getInputStream()));
162: } catch (SAXException saxe) {
163: throw (IOException) new IOException(saxe.toString())
164: .initCause(saxe);
165: }
166:
167: if (p == null) {
168: throw new IOException("No <helpctx> element in "
169: + xml.getPrimaryFile()); // NOI18N
170: }
171:
172: return p;
173: }
174:
175: /** The presenter to be shown in a menu, e.g.
176: */
177: private static final class ShortcutAction extends AbstractAction
178: implements HelpCtx.Provider, NodeListener, ChangeListener {
179:
180: /** associated XML file representing it
181: */
182: private final XMLDataObject obj;
183:
184: /** the cached help context
185: */
186: private String helpID;
187:
188: /** cached flag to show the master help set
189: */
190: private boolean showmaster;
191:
192: /** Create a new presenter.
193: * @param obj XML file describing it
194: */
195: public ShortcutAction(XMLDataObject obj, String helpID,
196: boolean showmaster) {
197: this .obj = obj;
198: this .helpID = helpID;
199: this .showmaster = showmaster;
200: putValue("noIconInMenu", Boolean.TRUE); // NOI18N
201: Installer.log.fine("new ShortcutAction: " + obj + " "
202: + helpID + " showmaster=" + showmaster);
203: updateText();
204: updateIcon();
205: updateEnabled();
206: if (obj.isValid()) {
207: Node n = obj.getNodeDelegate();
208: n.addNodeListener(org.openide.nodes.NodeOp
209: .weakNodeListener(this , n));
210: }
211: Help h = findHelp();
212: if (h != null)
213: h.addChangeListener(org.openide.util.WeakListeners
214: .change(this , h));
215: }
216:
217: /** Show the help.
218: * @param actionEvent ignored
219: */
220: public void actionPerformed(ActionEvent actionEvent) {
221: Help h = findHelp();
222: if (h != null) {
223: Installer.log.fine("ShortcutAction.actionPerformed: "
224: + helpID + " showmaster=" + showmaster);
225: h.showHelp(new HelpCtx(helpID), showmaster);
226: } else {
227: Toolkit.getDefaultToolkit().beep();
228: }
229: }
230:
231: /**
232: * Help for the shortcut itself is generic.
233: * @return a neutral help context - welcome page
234: */
235: public HelpCtx getHelpCtx() {
236: // #23565:
237: return new HelpCtx("ide.welcome"); // NOI18N
238: }
239:
240: /** Help sets may have changed.
241: * @param changeEvent ignore
242: */
243: public void stateChanged(ChangeEvent e) {
244: updateEnabled();
245: }
246:
247: /** Called when the node delegate changes somehow,
248: * @param ev event indicating whether the change
249: * was of display name, icon, or other
250: */
251: public void propertyChange(PropertyChangeEvent ev) {
252: String prop = ev.getPropertyName();
253: if (!obj.isValid())
254: return;
255: if (prop == null || prop.equals(Node.PROP_NAME)
256: || prop.equals(Node.PROP_DISPLAY_NAME)) {
257: updateText();
258: }
259: if (prop == null || prop.equals(Node.PROP_ICON)) {
260: updateIcon();
261: }
262: }
263:
264: /** Update the text of the button according to node's
265: * display name. Handle mnemonics sanely.
266: */
267: private void updateText() {
268: String text;
269: if (obj.isValid()) {
270: text = obj.getNodeDelegate().getDisplayName();
271: } else {
272: // #16364
273: text = "dead"; // NOI18N
274: }
275: putValue(Action.NAME, text);
276: }
277:
278: /** Update the icon of the button according to the
279: * node delegate.
280: */
281: private void updateIcon() {
282: if (obj.isValid()) {
283: Image icon = obj.getNodeDelegate().getIcon(
284: BeanInfo.ICON_COLOR_16x16);
285: if (icon != null) {
286: putValue(Action.SMALL_ICON, new ImageIcon(icon));
287: }
288: }
289: }
290:
291: private void updateEnabled() {
292: Help h = findHelp();
293: Boolean valid = h == null ? Boolean.FALSE : h.isValidID(
294: helpID, false);
295: if (valid != null) {
296: setEnabled(valid.booleanValue());
297: }
298: Installer.log.fine("enabled: xml=" + obj.getPrimaryFile()
299: + " id=" + helpID + " enabled=" + valid);
300: }
301:
302: public void nodeDestroyed(NodeEvent ev) {
303: setEnabled(false);
304: updateText();
305: }
306:
307: public void childrenAdded(NodeMemberEvent ev) {
308: }
309:
310: public void childrenRemoved(NodeMemberEvent ev) {
311: }
312:
313: public void childrenReordered(NodeReorderEvent ev) {
314: }
315:
316: }
317:
318: }
|