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.apisupport.project.ui.customizer;
043:
044: import java.awt.Color;
045: import java.awt.Dialog;
046: import java.awt.EventQueue;
047: import java.awt.Rectangle;
048: import java.awt.event.ActionEvent;
049: import java.awt.event.ActionListener;
050: import java.net.URL;
051: import java.util.Locale;
052: import java.util.Set;
053: import javax.swing.AbstractAction;
054: import javax.swing.Action;
055: import javax.swing.ActionMap;
056: import javax.swing.InputMap;
057: import javax.swing.JComponent;
058: import javax.swing.JPanel;
059: import javax.swing.KeyStroke;
060: import javax.swing.event.DocumentEvent;
061: import javax.swing.event.ListSelectionEvent;
062: import javax.swing.event.ListSelectionListener;
063: import javax.swing.text.BadLocationException;
064: import javax.swing.text.Style;
065: import javax.swing.text.StyleConstants;
066: import javax.swing.text.StyledDocument;
067: import org.netbeans.modules.apisupport.project.Util;
068: import org.netbeans.modules.apisupport.project.ui.UIUtil;
069: import org.netbeans.modules.apisupport.project.universe.NbPlatform;
070: import org.openide.DialogDescriptor;
071: import org.openide.DialogDisplayer;
072: import org.openide.ErrorManager;
073: import org.openide.awt.HtmlBrowser;
074: import org.openide.util.HelpCtx;
075: import org.openide.util.Mutex;
076: import org.openide.util.NbBundle;
077: import org.openide.util.RequestProcessor;
078:
079: /**
080: * Represents panel for adding new dependency for a module. Shown after
081: * <em>Add</em> button on the <code>CustomizerLibraries</code> panel has been
082: * pushed.
083: *
084: * @author Martin Krauskopf, Jesse Glick
085: */
086: public final class AddModulePanel extends JPanel {
087:
088: private static final String FILTER_DESCRIPTION = getMessage("LBL_FilterDescription");
089: private static Rectangle lastSize;
090:
091: private CustomizerComponentFactory.DependencyListModel universeModules;
092: private RequestProcessor.Task filterTask;
093: private AddModuleFilter filterer;
094: private URL currectJavadoc;
095:
096: private final SingleModuleProperties props;
097:
098: public static ModuleDependency[] selectDependencies(
099: final SingleModuleProperties props) {
100: final AddModulePanel addPanel = new AddModulePanel(props);
101: final DialogDescriptor descriptor = new DialogDescriptor(
102: addPanel, getMessage("CTL_AddModuleDependencyTitle"));
103: descriptor.setHelpCtx(new HelpCtx(AddModulePanel.class));
104: descriptor.setClosingOptions(new Object[0]);
105: final Dialog d = DialogDisplayer.getDefault().createDialog(
106: descriptor);
107: descriptor.setButtonListener(new ActionListener() {
108: public void actionPerformed(ActionEvent e) {
109: if (DialogDescriptor.OK_OPTION.equals(e.getSource())
110: && addPanel.getSelectedDependencies().length == 0) {
111: return;
112: }
113: d.setVisible(false);
114: d.dispose();
115: }
116: });
117: if (lastSize != null) {
118: d.setBounds(lastSize);
119: }
120: d.setVisible(true);
121: lastSize = d.getBounds();
122: d.dispose();
123: if (descriptor.getValue().equals(DialogDescriptor.OK_OPTION)) {
124: return addPanel.getSelectedDependencies();
125: } else {
126: return new ModuleDependency[0]; // #114932
127: }
128: }
129:
130: public AddModulePanel(final SingleModuleProperties props) {
131: this .props = props;
132: initComponents();
133: initAccessibility();
134: filterValue.setText(FILTER_DESCRIPTION);
135: fillUpUniverseModules();
136: moduleList.setCellRenderer(CustomizerComponentFactory
137: .getDependencyCellRenderer(false));
138: moduleList
139: .addListSelectionListener(new ListSelectionListener() {
140: public void valueChanged(ListSelectionEvent e) {
141: showDescription();
142: currectJavadoc = null;
143: ModuleDependency[] deps = getSelectedDependencies();
144: if (deps.length == 1) {
145: NbPlatform platform = props
146: .getActivePlatform();
147: if (platform == null) { // NetBeans.org module
148: currectJavadoc = Util
149: .findJavadocForNetBeansOrgModules(deps[0]);
150: } else {
151: currectJavadoc = Util.findJavadoc(
152: deps[0], platform);
153: }
154: }
155: showJavadocButton
156: .setEnabled(currectJavadoc != null);
157: }
158: });
159: filterValue.getDocument().addDocumentListener(
160: new UIUtil.DocumentAdapter() {
161: public void insertUpdate(DocumentEvent e) {
162: if (!FILTER_DESCRIPTION.equals(filterValue
163: .getText())) {
164: search();
165: }
166: }
167: });
168: // Make basic navigation commands from the list work from the text field.
169: String[][] listNavCommands = {
170: { "selectPreviousRow", "selectPreviousRow" }, // NOI18N
171: { "selectNextRow", "selectNextRow" }, // NOI18N
172: { "selectFirstRow", "selectFirstRow" }, // NOI18N
173: { "selectLastRow", "selectLastRow" }, // NOI18N
174: { "scrollUp", "scrollUp" }, // NOI18N
175: { "scrollDown", "scrollDown" }, // NOI18N
176: };
177: String[][] areaNavCommands = {
178: { "selection-page-up", "page-up" },// NOI18N
179: { "selection-page-down", "page-down" },// NOI18N
180: { "selection-up", "caret-up" },// NOI18N
181: { "selection-down", "caret-down" },// NOI18N
182: };
183: exchangeCommands(listNavCommands, moduleList, filterValue);
184: exchangeCommands(areaNavCommands, descValue, filterValue);
185: }
186:
187: private static void exchangeCommands(String[][] commandsToExchange,
188: final JComponent target, final JComponent source) {
189: InputMap targetBindings = target.getInputMap();
190: KeyStroke[] targetBindingKeys = targetBindings.allKeys();
191: ActionMap targetActions = target.getActionMap();
192: InputMap sourceBindings = source.getInputMap();
193: ActionMap sourceActions = source.getActionMap();
194: for (int i = 0; i < commandsToExchange.length; i++) {
195: String commandFrom = commandsToExchange[i][0];
196: String commandTo = commandsToExchange[i][1];
197: final Action orig = targetActions.get(commandTo);
198: if (orig == null) {
199: continue;
200: }
201: sourceActions.put(commandTo, new AbstractAction() {
202: public void actionPerformed(ActionEvent e) {
203: orig.actionPerformed(new ActionEvent(target, e
204: .getID(), e.getActionCommand(),
205: e.getWhen(), e.getModifiers()));
206: }
207: });
208: for (int j = 0; j < targetBindingKeys.length; j++) {
209: if (targetBindings.get(targetBindingKeys[j]).equals(
210: commandFrom)) {
211: sourceBindings.put(targetBindingKeys[j], commandTo);
212: }
213: }
214: }
215: }
216:
217: private void fillUpUniverseModules() {
218: filterValue.setEnabled(false);
219: moduleList.setEnabled(false);
220: showNonAPIModules.setEnabled(false);
221: final String lastFilter = filterValue.getText();
222: filterValue.setText(CustomizerComponentFactory.WAIT_VALUE);
223: moduleList.setModel(CustomizerComponentFactory
224: .createListWaitModel());
225: final boolean nonApiDeps = showNonAPIModules.isSelected();
226: ModuleProperties.RP.post(new Runnable() {
227: public void run() {
228: final Set<ModuleDependency> universeDeps = props
229: .getUniverseDependencies(true, !nonApiDeps);
230: EventQueue.invokeLater(new Runnable() {
231: public void run() {
232: universeModules = CustomizerComponentFactory
233: .createSortedDependencyListModel(universeDeps);
234: filterer = null;
235: moduleList.setModel(universeModules);
236: moduleList.setEnabled(true);
237: filterValue.setEnabled(true);
238: showNonAPIModules.setEnabled(true);
239: filterValue.setText(lastFilter);
240: if (!FILTER_DESCRIPTION.equals(lastFilter)) {
241: search();
242: } else {
243: filterValue.selectAll();
244: }
245: filterValue.requestFocusInWindow();
246: }
247: });
248: }
249: });
250: }
251:
252: private void showDescription() {
253: StyledDocument doc = descValue.getStyledDocument();
254: try {
255: doc.remove(0, doc.getLength());
256: ModuleDependency[] deps = getSelectedDependencies();
257: if (deps.length != 1) {
258: return;
259: }
260: String longDesc = deps[0].getModuleEntry()
261: .getLongDescription();
262: if (longDesc != null) {
263: doc.insertString(0, longDesc, null);
264: }
265: String filterText = filterValue.getText().trim();
266: if (filterText.length() != 0
267: && !FILTER_DESCRIPTION.equals(filterText)) {
268: doc.insertString(doc.getLength(), "\n\n", null); // NOI18N
269: Style bold = doc.addStyle(null, null);
270: bold.addAttribute(StyleConstants.Bold, Boolean.TRUE);
271: doc.insertString(doc.getLength(),
272: getMessage("TEXT_matching_filter_contents"),
273: bold);
274: doc.insertString(doc.getLength(), "\n", null); // NOI18N
275: if (filterText.length() > 0) {
276: String filterTextLC = filterText
277: .toLowerCase(Locale.US);
278: Style match = doc.addStyle(null, null);
279: match.addAttribute(StyleConstants.Background,
280: new Color(246, 248, 139));
281: boolean isEven = false;
282: Style even = doc.addStyle(null, null);
283: even.addAttribute(StyleConstants.Background,
284: new Color(235, 235, 235));
285: if (filterer == null) {
286: return; // #101776
287: }
288: for (String hit : filterer.getMatchesFor(
289: filterText, deps[0])) {
290: int loc = doc.getLength();
291: doc.insertString(loc, hit, (isEven ? even
292: : null));
293: int start = hit.toLowerCase(Locale.US).indexOf(
294: filterTextLC);
295: while (start != -1) {
296: doc.setCharacterAttributes(loc + start,
297: filterTextLC.length(), match, true);
298: start = hit.toLowerCase(Locale.US).indexOf(
299: filterTextLC, start + 1);
300: }
301: doc.insertString(doc.getLength(), "\n",
302: (isEven ? even : null)); // NOI18N
303: isEven ^= true;
304: }
305: } else {
306: Style italics = doc.addStyle(null, null);
307: italics.addAttribute(StyleConstants.Italic,
308: Boolean.TRUE);
309: doc.insertString(doc.getLength(),
310: getMessage("TEXT_no_filter_specified"),
311: italics);
312: }
313: }
314: descValue.setCaretPosition(0);
315: } catch (BadLocationException e) {
316: Util.err.notify(ErrorManager.INFORMATIONAL, e);
317: }
318: }
319:
320: public ModuleDependency[] getSelectedDependencies() {
321: ModuleDependency[] deps;
322: if (CustomizerComponentFactory.isWaitModel(moduleList
323: .getModel())) {
324: deps = new ModuleDependency[0];
325: } else {
326: Object[] objects = moduleList.getSelectedValues();
327: deps = new ModuleDependency[objects.length];
328: System.arraycopy(objects, 0, deps, 0, objects.length);
329: }
330: return deps;
331: }
332:
333: private void search() {
334: if (filterTask != null) {
335: filterTask.cancel();
336: filterTask = null;
337: }
338: final String text = filterValue.getText();
339: if (text.length() == 0) {
340: moduleList.setModel(universeModules);
341: moduleList.setSelectedIndex(0);
342: moduleList.ensureIndexIsVisible(0);
343: } else {
344: final Runnable compute = new Runnable() {
345: public void run() {
346: final Set<ModuleDependency> matches = filterer
347: .getMatches(text);
348: filterTask = null;
349: Mutex.EVENT.readAccess(new Runnable() {
350: public void run() {
351: // XXX would be better to have more fine-grained control over the thread
352: if (!text.equals(filterValue.getText())) {
353: return; // no longer valid, don't apply
354: }
355: moduleList
356: .setModel(CustomizerComponentFactory
357: .createDependencyListModel(matches));
358: int index = matches.isEmpty() ? -1 : 0;
359: moduleList.setSelectedIndex(index);
360: moduleList.ensureIndexIsVisible(index);
361: }
362: });
363: }
364: };
365: if (filterer == null) {
366: // Slow to create it, so show Please wait...
367: moduleList.setModel(CustomizerComponentFactory
368: .createListWaitModel());
369: filterTask = RequestProcessor.getDefault().post(
370: new Runnable() {
371: public void run() {
372: if (filterer == null) {
373: filterer = new AddModuleFilter(
374: universeModules
375: .getDependencies(),
376: props.getCodeNameBase());
377: compute.run();
378: }
379: }
380: });
381: } else {
382: // Pretty fast once we have it, so do right now and avoid flickering.
383: compute.run();
384: }
385: }
386: }
387:
388: private void initAccessibility() {
389: this .getAccessibleContext().setAccessibleDescription(
390: getMessage("ACS_AddModuleDependency"));
391: filterValue.getAccessibleContext().setAccessibleDescription(
392: getMessage("ACS_LBL_Filter"));
393: moduleList.getAccessibleContext().setAccessibleDescription(
394: getMessage("ACS_CTL_ModuleList"));
395: moduleSP.getVerticalScrollBar().getAccessibleContext()
396: .setAccessibleName(
397: getMessage("ACS_CTL_ModuleListVerticalScroll"));
398: moduleSP
399: .getVerticalScrollBar()
400: .getAccessibleContext()
401: .setAccessibleDescription(
402: getMessage("ACSD_CTL_ModuleListVerticalScroll"));
403: moduleSP
404: .getHorizontalScrollBar()
405: .getAccessibleContext()
406: .setAccessibleName(
407: getMessage("ACS_CTL_ModuleListHorizontalScroll"));
408: moduleSP
409: .getHorizontalScrollBar()
410: .getAccessibleContext()
411: .setAccessibleDescription(
412: getMessage("ACSD_CTL_ModuleListHorizontalScroll"));
413: showNonAPIModules.getAccessibleContext()
414: .setAccessibleDescription(
415: getMessage("ACSD_ShowNonApiModules"));
416: }
417:
418: private static String getMessage(String key) {
419: return NbBundle.getMessage(AddModulePanel.class, key);
420: }
421:
422: /** This method is called from within the constructor to
423: * initialize the form.
424: * WARNING: Do NOT modify this code. The content of this method is
425: * always regenerated by the Form Editor.
426: */
427: // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
428: private void initComponents() {
429: java.awt.GridBagConstraints gridBagConstraints;
430:
431: moduleLabel = new javax.swing.JLabel();
432: moduleSP = new javax.swing.JScrollPane();
433: moduleList = new javax.swing.JList();
434: descLabel = new javax.swing.JLabel();
435: filter = new javax.swing.JLabel();
436: filterValue = new javax.swing.JTextField();
437: descValueSP = new javax.swing.JScrollPane();
438: descValue = new javax.swing.JTextPane();
439: showJavadocButton = new javax.swing.JButton();
440: showNonAPIModules = new javax.swing.JCheckBox();
441:
442: setLayout(new java.awt.GridBagLayout());
443:
444: setBorder(javax.swing.BorderFactory.createEmptyBorder(6, 6, 6,
445: 6));
446: setPreferredSize(new java.awt.Dimension(400, 400));
447: moduleLabel.setLabelFor(moduleList);
448: org.openide.awt.Mnemonics.setLocalizedText(moduleLabel,
449: org.openide.util.NbBundle.getMessage(
450: AddModulePanel.class, "LBL_Module"));
451: gridBagConstraints = new java.awt.GridBagConstraints();
452: gridBagConstraints.gridx = 0;
453: gridBagConstraints.gridy = 2;
454: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
455: gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
456: gridBagConstraints.insets = new java.awt.Insets(24, 0, 0, 0);
457: add(moduleLabel, gridBagConstraints);
458:
459: moduleSP.setViewportView(moduleList);
460:
461: gridBagConstraints = new java.awt.GridBagConstraints();
462: gridBagConstraints.gridx = 0;
463: gridBagConstraints.gridy = 3;
464: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
465: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
466: gridBagConstraints.weighty = 1.0;
467: add(moduleSP, gridBagConstraints);
468:
469: descLabel.setLabelFor(descValue);
470: org.openide.awt.Mnemonics.setLocalizedText(descLabel,
471: org.openide.util.NbBundle.getMessage(
472: AddModulePanel.class, "LBL_Description"));
473: gridBagConstraints = new java.awt.GridBagConstraints();
474: gridBagConstraints.gridx = 0;
475: gridBagConstraints.gridy = 4;
476: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
477: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
478: gridBagConstraints.insets = new java.awt.Insets(24, 0, 0, 0);
479: add(descLabel, gridBagConstraints);
480:
481: filter.setLabelFor(filterValue);
482: org.openide.awt.Mnemonics.setLocalizedText(filter,
483: org.openide.util.NbBundle.getMessage(
484: AddModulePanel.class, "LBL_Filter"));
485: gridBagConstraints = new java.awt.GridBagConstraints();
486: gridBagConstraints.gridx = 0;
487: gridBagConstraints.gridy = 0;
488: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
489: gridBagConstraints.insets = new java.awt.Insets(0, 0, 0, 12);
490: add(filter, gridBagConstraints);
491:
492: gridBagConstraints = new java.awt.GridBagConstraints();
493: gridBagConstraints.gridx = 1;
494: gridBagConstraints.gridy = 0;
495: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
496: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
497: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
498: gridBagConstraints.weightx = 1.0;
499: add(filterValue, gridBagConstraints);
500:
501: descValue.setEditable(false);
502: descValue.setPreferredSize(new java.awt.Dimension(6, 100));
503: descValueSP.setViewportView(descValue);
504:
505: gridBagConstraints = new java.awt.GridBagConstraints();
506: gridBagConstraints.gridx = 0;
507: gridBagConstraints.gridy = 5;
508: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
509: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
510: gridBagConstraints.weighty = 1.0;
511: add(descValueSP, gridBagConstraints);
512:
513: org.openide.awt.Mnemonics
514: .setLocalizedText(
515: showJavadocButton,
516: java.util.ResourceBundle
517: .getBundle(
518: "org/netbeans/modules/apisupport/project/ui/customizer/Bundle")
519: .getString("CTL_ShowJavadoc"));
520: showJavadocButton.setEnabled(false);
521: showJavadocButton
522: .addActionListener(new java.awt.event.ActionListener() {
523: public void actionPerformed(
524: java.awt.event.ActionEvent evt) {
525: showJavadoc(evt);
526: }
527: });
528:
529: gridBagConstraints = new java.awt.GridBagConstraints();
530: gridBagConstraints.gridx = 0;
531: gridBagConstraints.gridy = 6;
532: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
533: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
534: gridBagConstraints.insets = new java.awt.Insets(24, 0, 0, 0);
535: add(showJavadocButton, gridBagConstraints);
536:
537: org.openide.awt.Mnemonics.setLocalizedText(showNonAPIModules,
538: org.openide.util.NbBundle.getMessage(
539: AddModulePanel.class, "CTL_ShowNonAPIModules"));
540: showNonAPIModules.setBorder(javax.swing.BorderFactory
541: .createEmptyBorder(0, 0, 0, 0));
542: showNonAPIModules.setMargin(new java.awt.Insets(0, 0, 0, 0));
543: showNonAPIModules
544: .addActionListener(new java.awt.event.ActionListener() {
545: public void actionPerformed(
546: java.awt.event.ActionEvent evt) {
547: showNonAPIModulesActionPerformed(evt);
548: }
549: });
550:
551: gridBagConstraints = new java.awt.GridBagConstraints();
552: gridBagConstraints.gridx = 0;
553: gridBagConstraints.gridy = 1;
554: gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
555: gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
556: gridBagConstraints.insets = new java.awt.Insets(12, 0, 0, 0);
557: add(showNonAPIModules, gridBagConstraints);
558:
559: }// </editor-fold>//GEN-END:initComponents
560:
561: private void showNonAPIModulesActionPerformed(
562: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showNonAPIModulesActionPerformed
563: fillUpUniverseModules();
564: }//GEN-LAST:event_showNonAPIModulesActionPerformed
565:
566: private void showJavadoc(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_showJavadoc
567: HtmlBrowser.URLDisplayer.getDefault().showURL(currectJavadoc);
568: }//GEN-LAST:event_showJavadoc
569:
570: // Variables declaration - do not modify//GEN-BEGIN:variables
571: private javax.swing.JLabel descLabel;
572: private javax.swing.JTextPane descValue;
573: private javax.swing.JScrollPane descValueSP;
574: private javax.swing.JLabel filter;
575: javax.swing.JTextField filterValue;
576: private javax.swing.JLabel moduleLabel;
577: javax.swing.JList moduleList;
578: private javax.swing.JScrollPane moduleSP;
579: private javax.swing.JButton showJavadocButton;
580: private javax.swing.JCheckBox showNonAPIModules;
581: // End of variables declaration//GEN-END:variables
582:
583: }
|