001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
011: * Microsystems, Inc. All Rights Reserved.
012:
013: If you wish your version of this file to be governed by only the CDDL
014: or only the GPL Version 2, indicate your decision by adding
015: "[Contributor] elects to include this software in this distribution
016: under the [CDDL or GPL Version 2] license." If you do not indicate a
017: single choice of license, a recipient has the option to distribute
018: your version of this file under either the CDDL, the GPL Version 2 or
019: to extend the choice of license to its licensees as provided above.
020: However, if you add GPL Version 2 code and therefore, elected the GPL
021: Version 2 license, then the option applies only if the new code is
022: made subject to such option by the copyright holder.
023: */
024:
025: package org.netbeans.modules.project.ui.actions;
026:
027: import java.awt.Component;
028: import java.awt.Dimension;
029: import java.awt.GridBagConstraints;
030: import java.awt.GridBagLayout;
031: import java.awt.Insets;
032: import java.awt.event.ActionEvent;
033: import java.awt.event.ActionListener;
034: import java.beans.PropertyChangeEvent;
035: import java.beans.PropertyChangeListener;
036: import java.io.IOException;
037: import java.util.Collection;
038: import java.util.logging.Level;
039: import java.util.logging.Logger;
040: import javax.swing.AbstractAction;
041: import javax.swing.Action;
042: import javax.swing.BorderFactory;
043: import javax.swing.ComboBoxModel;
044: import javax.swing.DefaultComboBoxModel;
045: import javax.swing.JComboBox;
046: import javax.swing.JComponent;
047: import javax.swing.JLabel;
048: import javax.swing.JList;
049: import javax.swing.JMenu;
050: import javax.swing.JMenuItem;
051: import javax.swing.JPanel;
052: import javax.swing.JRadioButtonMenuItem;
053: import javax.swing.ListCellRenderer;
054: import javax.swing.UIManager;
055: import javax.swing.border.Border;
056: import javax.swing.plaf.UIResource;
057: import org.netbeans.api.project.Project;
058: import org.netbeans.api.project.ProjectManager;
059: import org.netbeans.modules.project.ui.OpenProjectList;
060: import org.netbeans.spi.project.ProjectConfiguration;
061: import org.netbeans.spi.project.ProjectConfigurationProvider;
062: import org.openide.awt.DynamicMenuContent;
063: import org.openide.awt.Mnemonics;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileSystem;
066: import org.openide.filesystems.FileUtil;
067: import org.openide.filesystems.MultiFileSystem;
068: import org.openide.util.ContextAwareAction;
069: import org.openide.util.HelpCtx;
070: import org.openide.util.Lookup;
071: import org.openide.util.Mutex;
072: import org.openide.util.MutexException;
073: import org.openide.util.NbBundle;
074: import org.openide.util.actions.CallableSystemAction;
075: import org.openide.util.actions.Presenter;
076:
077: /**
078: * Action permitting selection of a configuration for the main project.
079: * @author Greg Crawley, Adam Sotona, Jesse Glick
080: */
081: public class ActiveConfigAction extends CallableSystemAction implements
082: ContextAwareAction {
083:
084: private static final Logger LOGGER = Logger
085: .getLogger(ActiveConfigAction.class.getName());
086:
087: private static final DefaultComboBoxModel EMPTY_MODEL = new DefaultComboBoxModel();
088: private static final Object CUSTOMIZE_ENTRY = new Object();
089:
090: private final PropertyChangeListener lst;
091: private final JComboBox configListCombo;
092: private boolean listeningToCombo = true;
093:
094: private Project currentProject;
095: private ProjectConfigurationProvider pcp;
096:
097: public ActiveConfigAction() {
098: super ();
099: putValue("noIconInMenu", Boolean.TRUE); // NOI18N
100: configListCombo = new JComboBox();
101: configListCombo.setRenderer(new ConfigCellRenderer());
102: configListCombo.setToolTipText(org.openide.awt.Actions
103: .cutAmpersand(getName()));
104: configurationsListChanged(null);
105: configListCombo.addActionListener(new ActionListener() {
106: public void actionPerformed(ActionEvent e) {
107: if (!listeningToCombo) {
108: return;
109: }
110: Object o = configListCombo.getSelectedItem();
111: if (o == CUSTOMIZE_ENTRY) {
112: activeConfigurationChanged(pcp != null ? getActiveConfiguration(pcp)
113: : null);
114: pcp.customize();
115: } else if (o != null) {
116: activeConfigurationSelected((ProjectConfiguration) o);
117: }
118: }
119: });
120: lst = new PropertyChangeListener() {
121: public void propertyChange(PropertyChangeEvent evt) {
122: if (ProjectConfigurationProvider.PROP_CONFIGURATIONS
123: .equals(evt.getPropertyName())) {
124: configurationsListChanged(getConfigurations(pcp));
125: } else if (ProjectConfigurationProvider.PROP_CONFIGURATION_ACTIVE
126: .equals(evt.getPropertyName())) {
127: activeConfigurationChanged(getActiveConfiguration(pcp));
128: }
129: }
130: };
131: activeProjectChanged(OpenProjectList.getDefault()
132: .getMainProject());
133: OpenProjectList.getDefault().addPropertyChangeListener(
134: new PropertyChangeListener() {
135: public void propertyChange(PropertyChangeEvent evt) {
136: if (OpenProjectList.PROPERTY_MAIN_PROJECT
137: .equals(evt.getPropertyName())) {
138: activeProjectChanged(OpenProjectList
139: .getDefault().getMainProject());
140: }
141: }
142: });
143: }
144:
145: private synchronized void configurationsListChanged(
146: Collection<? extends ProjectConfiguration> configs) {
147: LOGGER.log(Level.FINER, "configurationsListChanged: {0}",
148: configs);
149: if (configs == null) {
150: configListCombo.setModel(EMPTY_MODEL);
151: DynLayer.INSTANCE.setEnabled(false);
152: configListCombo.setEnabled(false); // possibly redundant, but just in case
153: } else {
154: DefaultComboBoxModel model = new DefaultComboBoxModel(
155: configs.toArray());
156: if (pcp.hasCustomizer()) {
157: model.addElement(CUSTOMIZE_ENTRY);
158: }
159: configListCombo.setModel(model);
160: DynLayer.INSTANCE.setEnabled(true);
161: configListCombo.setEnabled(true);
162: }
163: if (pcp != null) {
164: activeConfigurationChanged(getActiveConfiguration(pcp));
165: }
166: }
167:
168: private synchronized void activeConfigurationChanged(
169: ProjectConfiguration config) {
170: LOGGER.log(Level.FINER, "activeConfigurationChanged: {0}",
171: config);
172: listeningToCombo = false;
173: try {
174: configListCombo.setSelectedIndex(-1);
175: if (config != null) {
176: ComboBoxModel m = configListCombo.getModel();
177: for (int i = 0; i < m.getSize(); i++) {
178: if (config.equals(m.getElementAt(i))) {
179: configListCombo.setSelectedIndex(i);
180: break;
181: }
182: }
183: }
184: } finally {
185: listeningToCombo = true;
186: }
187: }
188:
189: private synchronized void activeConfigurationSelected(
190: ProjectConfiguration cfg) {
191: LOGGER
192: .log(Level.FINER, "activeConfigurationSelected: {0}",
193: cfg);
194: if (pcp != null && cfg != null
195: && !cfg.equals(getActiveConfiguration(pcp))) {
196: try {
197: setActiveConfiguration(pcp, cfg);
198: } catch (IOException x) {
199: LOGGER.log(Level.WARNING, null, x);
200: }
201: }
202: }
203:
204: public HelpCtx getHelpCtx() {
205: return new HelpCtx(ActiveConfigAction.class);
206: }
207:
208: public String getName() {
209: return NbBundle.getMessage(ActiveConfigAction.class,
210: "ActiveConfigAction.label");
211: }
212:
213: public void performAction() {
214: assert false;
215: }
216:
217: public Component getToolbarPresenter() {
218: // Do not return combo box directly; looks bad.
219: JPanel toolbarPanel = new JPanel(new GridBagLayout());
220: toolbarPanel.setOpaque(false); // don't interrupt JToolBar background
221: toolbarPanel.setMaximumSize(new Dimension(150, 80));
222: toolbarPanel.setMinimumSize(new Dimension(150, 0));
223: toolbarPanel.setPreferredSize(new Dimension(150, 23));
224: // XXX top inset of 2 looks better w/ small toolbar, but 1 seems to look better for large toolbar (the default):
225: toolbarPanel.add(configListCombo, new GridBagConstraints(0, 0,
226: 1, 1, 1.0, 1.0, GridBagConstraints.CENTER,
227: GridBagConstraints.HORIZONTAL, new Insets(1, 6, 1, 5),
228: 0, 0));
229: return toolbarPanel;
230: }
231:
232: /**
233: * Dynamically inserts or removes the action from the toolbar.
234: */
235: public static final class DynLayer extends MultiFileSystem {
236:
237: static DynLayer INSTANCE;
238:
239: private final FileSystem fragment;
240:
241: /**
242: * Default constructor for lookup.
243: */
244: public DynLayer() {
245: INSTANCE = this ;
246: fragment = FileUtil.createMemoryFileSystem();
247: try {
248: FileObject f = FileUtil
249: .createData(
250: fragment.getRoot(),
251: "Toolbars/Build/org-netbeans-modules-project-ui-actions-ActiveConfigAction.shadow"); // NOI18N
252: f
253: .setAttribute(
254: "originalFile",
255: "Actions/Project/org-netbeans-modules-project-ui-actions-ActiveConfigAction.instance"); // NOI18N
256: f.setAttribute("position", 80); // NOI18N
257: } catch (IOException e) {
258: throw new AssertionError(e);
259: }
260: }
261:
262: void setEnabled(boolean enabled) {
263: if (enabled) {
264: setDelegates(fragment);
265: } else {
266: setDelegates();
267: }
268: }
269:
270: }
271:
272: class ConfigMenu extends JMenu implements DynamicMenuContent,
273: ActionListener {
274:
275: private final Lookup context;
276:
277: public ConfigMenu(Lookup context) {
278: this .context = context;
279: if (context != null) {
280: Mnemonics.setLocalizedText(this , NbBundle.getMessage(
281: ActiveConfigAction.class,
282: "ActiveConfigAction.context.label"));
283: } else {
284: Mnemonics.setLocalizedText(this ,
285: ActiveConfigAction.this .getName());
286: }
287: }
288:
289: private ProjectConfigurationProvider<?> findPCP() {
290: if (context != null) {
291: Collection<? extends Project> projects = context
292: .lookupAll(Project.class);
293: if (projects.size() == 1) {
294: return projects.iterator().next().getLookup()
295: .lookup(ProjectConfigurationProvider.class);
296: } else {
297: // No selection, or multiselection.
298: return null;
299: }
300: } else {
301: return ActiveConfigAction.this .pcp; // global menu item; take from main project
302: }
303: }
304:
305: public JComponent[] getMenuPresenters() {
306: removeAll();
307: final ProjectConfigurationProvider<?> pcp = findPCP();
308: if (pcp != null) {
309: boolean something = false;
310: ProjectConfiguration activeConfig = getActiveConfiguration(pcp);
311: for (final ProjectConfiguration config : getConfigurations(pcp)) {
312: JRadioButtonMenuItem jmi = new JRadioButtonMenuItem(
313: config.getDisplayName(), config
314: .equals(activeConfig));
315: jmi.addActionListener(new ActionListener() {
316: public void actionPerformed(ActionEvent e) {
317: activeConfigurationSelected(config);
318: }
319: });
320: add(jmi);
321: something = true;
322: }
323: if (pcp.hasCustomizer()) {
324: if (something) {
325: addSeparator();
326: }
327: something = true;
328: JMenuItem customize = new JMenuItem();
329: Mnemonics.setLocalizedText(customize, NbBundle
330: .getMessage(ActiveConfigAction.class,
331: "ActiveConfigAction.customize"));
332: customize.addActionListener(this );
333: add(customize);
334: }
335: setEnabled(something);
336: } else {
337: // No configurations supported for this project.
338: setEnabled(false);
339: // to hide entirely just use: return new JComponent[0];
340: }
341: return new JComponent[] { this };
342: }
343:
344: public JComponent[] synchMenuPresenters(JComponent[] items) {
345: // Always rebuild submenu.
346: // For performance, could try to reuse it if context == null and nothing has changed.
347: return getMenuPresenters();
348: }
349:
350: public void actionPerformed(ActionEvent e) {
351: ProjectConfigurationProvider<?> pcp = findPCP();
352: if (pcp != null) {
353: pcp.customize();
354: }
355: }
356:
357: }
358:
359: public JMenuItem getMenuPresenter() {
360: return new ConfigMenu(null);
361: }
362:
363: private static class ConfigCellRenderer extends JLabel implements
364: ListCellRenderer, UIResource {
365:
366: private Border defaultBorder = getBorder();
367:
368: public ConfigCellRenderer() {
369: setOpaque(true);
370: }
371:
372: public Component getListCellRendererComponent(JList list,
373: Object value, int index, boolean isSelected,
374: boolean cellHasFocus) {
375: // #89393: GTK needs name to render cell renderer "natively"
376: setName("ComboBox.listRenderer"); // NOI18N
377:
378: String label = null;
379: if (value instanceof ProjectConfiguration) {
380: label = ((ProjectConfiguration) value).getDisplayName();
381: setBorder(defaultBorder);
382: } else if (value == CUSTOMIZE_ENTRY) {
383: label = org.openide.awt.Actions.cutAmpersand(NbBundle
384: .getMessage(ActiveConfigAction.class,
385: "ActiveConfigAction.customize"));
386: setBorder(BorderFactory.createCompoundBorder(
387: BorderFactory.createMatteBorder(1, 0, 0, 0,
388: UIManager.getColor("controlDkShadow")),
389: defaultBorder));
390: } else {
391: assert value == null;
392: label = null;
393: setBorder(defaultBorder);
394: }
395:
396: setText(label);
397: setIcon(null);
398:
399: if (isSelected) {
400: setBackground(list.getSelectionBackground());
401: setForeground(list.getSelectionForeground());
402: } else {
403: setBackground(list.getBackground());
404: setForeground(list.getForeground());
405: }
406:
407: return this ;
408: }
409:
410: // #89393: GTK needs name to render cell renderer "natively"
411: public String getName() {
412: String name = super .getName();
413: return name == null ? "ComboBox.renderer" : name; // NOI18N
414: }
415:
416: }
417:
418: private synchronized void activeProjectChanged(Project p) {
419: LOGGER.log(Level.FINER, "activeProjectChanged: {0} -> {1}",
420: new Object[] { currentProject, p });
421: if (currentProject != p) {
422: if (pcp != null) {
423: pcp.removePropertyChangeListener(lst);
424: }
425: currentProject = p;
426: if (currentProject != null) {
427: pcp = currentProject.getLookup().lookup(
428: ProjectConfigurationProvider.class);
429: if (pcp != null) {
430: pcp.addPropertyChangeListener(lst);
431: }
432: } else {
433: pcp = null;
434: }
435: configurationsListChanged(pcp == null ? null
436: : getConfigurations(pcp));
437:
438: }
439: }
440:
441: public Action createContextAwareInstance(final Lookup actionContext) {
442: class A extends AbstractAction implements Presenter.Popup {
443: public void actionPerformed(ActionEvent e) {
444: assert false;
445: }
446:
447: public JMenuItem getPopupPresenter() {
448: return new ConfigMenu(actionContext);
449: }
450: }
451: return new A();
452: }
453:
454: private static Collection<? extends ProjectConfiguration> getConfigurations(
455: final ProjectConfigurationProvider<?> pcp) {
456: return ProjectManager
457: .mutex()
458: .readAccess(
459: new Mutex.Action<Collection<? extends ProjectConfiguration>>() {
460: public Collection<? extends ProjectConfiguration> run() {
461: return pcp.getConfigurations();
462: }
463: });
464: }
465:
466: private static ProjectConfiguration getActiveConfiguration(
467: final ProjectConfigurationProvider<?> pcp) {
468: return ProjectManager.mutex().readAccess(
469: new Mutex.Action<ProjectConfiguration>() {
470: public ProjectConfiguration run() {
471: return pcp.getActiveConfiguration();
472: }
473: });
474: }
475:
476: @SuppressWarnings("unchecked")
477: // XXX would not be necessary in case PCP had a method to get run-time type information: Class<C> configurationType();
478: private static void setActiveConfiguration(
479: ProjectConfigurationProvider<?> pcp,
480: final ProjectConfiguration pc) throws IOException {
481: final ProjectConfigurationProvider _pcp = pcp;
482: try {
483: ProjectManager.mutex().writeAccess(
484: new Mutex.ExceptionAction<Void>() {
485: public Void run() throws IOException {
486: _pcp.setActiveConfiguration(pc);
487: return null;
488: }
489: });
490: } catch (MutexException e) {
491: throw (IOException) e.getException();
492: }
493: }
494:
495: }
|