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-2007 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.visualweb.webui.jsf.defaulttheme;
043:
044: import org.netbeans.modules.visualweb.project.jsf.api.JsfProjectConstants;
045: import org.netbeans.modules.visualweb.project.jsf.api.JsfProjectUtils;
046: import org.netbeans.modules.visualweb.project.jsf.services.RefreshService;
047: import java.awt.Image;
048: import java.awt.event.ActionEvent;
049: import java.beans.PropertyChangeEvent;
050: import java.beans.PropertyChangeListener;
051: import java.io.File;
052: import java.io.IOException;
053: import java.net.URL;
054: import java.util.ArrayList;
055: import java.util.Collection;
056: import java.util.Collections;
057: import java.util.HashMap;
058: import java.util.Map;
059: import java.util.Iterator;
060: import java.util.jar.Manifest;
061: import java.util.jar.Attributes;
062: import javax.swing.AbstractAction;
063: import javax.swing.Action;
064: import org.netbeans.api.project.libraries.Library;
065: import org.netbeans.api.project.libraries.LibraryManager;
066: import org.openide.DialogDisplayer;
067: import org.openide.ErrorManager;
068: import org.openide.NotifyDescriptor;
069: import org.openide.DialogDescriptor;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileUtil;
072: import org.openide.nodes.AbstractNode;
073: import org.openide.nodes.Children;
074: import org.openide.nodes.Node;
075: import org.openide.util.NbBundle;
076: import org.openide.util.RequestProcessor;
077: import org.openide.util.HelpCtx;
078: import org.netbeans.api.project.Project;
079: import org.openide.util.Utilities;
080:
081: /**
082: * ThemesFolderNode displays the available themes and badges the current theme node
083: *
084: * @author Po-Ting Wu, Mark Dey, Winston Prakash
085: */
086: final class ThemesFolderNode extends AbstractNode {
087: static final String WoodStock_ThemeVersion = "4.2"; // NOI18N
088: static final RequestProcessor rp = new RequestProcessor();
089: private final String displayName;
090: private final Action[] themesNodeActions;
091: private final Project project;
092:
093: /**
094: * Creates new LibrariesNode named displayName displaying classPathProperty classpath
095: * and optionaly Java platform.
096: * @param project {@link Project} used for reading and updating project's metadata
097: */
098: ThemesFolderNode(Project project) {
099: super (new ThemesChildren(project));
100: this .displayName = NbBundle.getMessage(ThemesFolderNode.class,
101: "CTL_ThemesNode");
102: this .themesNodeActions = new Action[] {
103: /* TODO: Themes folder actions */
104: };
105: this .project = project;
106: }
107:
108: @Override
109: public String getDisplayName() {
110: return this .displayName;
111: }
112:
113: @Override
114: public String getName() {
115: return this .getDisplayName();
116: }
117:
118: @Override
119: public Action[] getActions(boolean context) {
120: return this .themesNodeActions;
121: }
122:
123: @Override
124: public Image getIcon(int type) {
125: return Utilities
126: .loadImage("org/netbeans/modules/visualweb/webui/jsf/defaulttheme/resources/JSF-themesFolder.png"); // NOI18N;
127: }
128:
129: @Override
130: public Image getOpenedIcon(int type) {
131: // TODO: need graphic for opened folder icon
132: return Utilities
133: .loadImage("org/netbeans/modules/visualweb/webui/jsf/defaulttheme/resources/JSF-themesFolder.png"); // NOI18N;
134: }
135:
136: @Override
137: public boolean canCopy() {
138: return false;
139: }
140:
141: //Static Action Factory Methods
142: public static Action createSetAsCurrentThemeAction(Library theme,
143: Project project) {
144: return new SetAsCurrentThemeAction(theme, project);
145: }
146:
147: //Static inner classes
148: private static class ThemesChildren extends Children.Keys implements
149: PropertyChangeListener {
150:
151: private final Project project;
152: private final LibraryManager projectLibraryManager;
153: private final LibraryManager globalLibraryManager;
154:
155: ThemesChildren(Project project) {
156: this .project = project;
157: projectLibraryManager = JsfProjectUtils
158: .getProjectLibraryManager(project);
159: globalLibraryManager = LibraryManager.getDefault();
160: }
161:
162: public void propertyChange(PropertyChangeEvent evt) {
163: this .setKeys(getKeys());
164: }
165:
166: @Override
167: protected void addNotify() {
168: projectLibraryManager.addPropertyChangeListener(this );
169: globalLibraryManager.addPropertyChangeListener(this );
170: this .setKeys(getKeys());
171: }
172:
173: @Override
174: protected void removeNotify() {
175: projectLibraryManager.removePropertyChangeListener(this );
176: globalLibraryManager.removePropertyChangeListener(this );
177: this .setKeys(Collections.EMPTY_SET);
178: }
179:
180: protected Node[] createNodes(Object obj) {
181: String version = getThemeLibraryVersion((Library) obj);
182:
183: if (WoodStock_ThemeVersion.equals(version)) {
184: Node n = new ThemeNode((Library) obj, project, version);
185: return new Node[] { n };
186: } else {
187: return new Node[] {};
188: }
189: }
190:
191: private Collection getKeys() {
192: java.util.Map themesList = new HashMap();
193: Library[] projectLibraries = projectLibraryManager
194: .getLibraries();
195: for (int i = 0; i < projectLibraries.length; i++) {
196: Library lib = projectLibraries[i];
197: if ("theme".equals(lib.getType())
198: && !themesList.containsKey(lib.getName())) {
199: themesList.put(lib.getName(), lib);
200: }
201: }
202:
203: Library[] globalLibraries = globalLibraryManager
204: .getLibraries();
205: for (int i = 0; i < globalLibraries.length; i++) {
206: Library lib = globalLibraries[i];
207: if ("theme".equals(lib.getType())
208: && !themesList.containsKey(lib.getName())) {
209: themesList.put(lib.getName(), lib);
210: }
211: }
212:
213: return themesList.values();
214: }
215: }
216:
217: private static class ThemeNode extends AbstractNode implements
218: PropertyChangeListener {
219:
220: Project project;
221: Library theme;
222: Action setCurrentThemeAction = null;
223: String version;
224:
225: public ThemeNode(Library theme, Project project, String version) {
226: super (Children.LEAF);
227: this .setDisplayName(theme.getDisplayName());
228: this .project = project;
229: this .theme = theme;
230: this .version = version;
231:
232: updateToolTip();
233: updateIcon();
234:
235: JsfProjectUtils.addProjectPropertyListener(project, this );
236:
237: // #6311905
238: theme
239: .addPropertyChangeListener(org.openide.util.WeakListeners
240: .propertyChange(this , theme));
241: }
242:
243: @Override
244: public void destroy() throws IOException {
245: JsfProjectUtils
246: .removeProjectPropertyListener(project, this );
247: super .destroy();
248: }
249:
250: public void propertyChange(PropertyChangeEvent evt) {
251: if (evt.getPropertyName().equals(
252: JsfProjectConstants.PROP_CURRENT_THEME)) {
253: this .fireIconChange();
254: } else if (Library.PROP_CONTENT.equals(evt
255: .getPropertyName())) {
256: version = getThemeLibraryVersion(theme);
257: updateToolTip();
258: updateIcon();
259: }
260: }
261:
262: @Override
263: public Action[] getActions(boolean context) {
264: return new Action[] { getAction() };
265: }
266:
267: public Action getAction() {
268: if (setCurrentThemeAction == null) {
269: setCurrentThemeAction = ThemesFolderNode
270: .createSetAsCurrentThemeAction(theme, project);
271: }
272: return setCurrentThemeAction;
273: }
274:
275: @Override
276: public Image getIcon(int type) {
277: Image baseImage = Utilities
278: .loadImage("org/netbeans/modules/visualweb/webui/jsf/defaulttheme/resources/JSF-theme.png"); // NOI18N
279: String currentTheme = JsfProjectUtils.getProjectProperty(
280: project, JsfProjectConstants.PROP_CURRENT_THEME);
281: if (currentTheme != null
282: && currentTheme.equals(theme.getName())) {
283: Image currentThemeBadge = Utilities
284: .loadImage("org/netbeans/modules/visualweb/webui/jsf/defaulttheme/resources/JSF-currentThemeBadge.png"); // NOI18N
285: baseImage = Utilities
286: .mergeImages(baseImage, currentThemeBadge,
287: baseImage.getWidth(null), baseImage
288: .getHeight(null)
289: - currentThemeBadge
290: .getHeight(null) + 1);
291: getAction().setEnabled(false);
292: } else {
293: if (version != null) {
294: getAction().setEnabled(true);
295: } else {
296: Image errorBadge = Utilities
297: .loadImage("org/netbeans/modules/visualweb/webui/jsf/defaulttheme/resources/JSF-error-badge.gif"); // NOI18N
298: baseImage = Utilities.mergeImages(baseImage,
299: errorBadge, baseImage.getWidth(null),
300: baseImage.getHeight(null)
301: - errorBadge.getHeight(null) + 1);
302:
303: getAction().setEnabled(false);
304: }
305: }
306:
307: return baseImage;
308: }
309:
310: private void updateToolTip() {
311: String toolTip;
312: if (version != null) {
313: toolTip = NbBundle.getMessage(ThemesFolderNode.class,
314: "LBL_ThemeLibraryDescription");
315: } else {
316: toolTip = NbBundle.getMessage(ThemesFolderNode.class,
317: "LBL_ThemeLibraryDescription_Invalid");
318: }
319:
320: setShortDescription(toolTip);
321: }
322:
323: private void updateIcon() {
324: // XXX If the getter (getIcon) is not overriden(incorrect), then here the setter would be called with the appropriate icon.
325: fireIconChange();
326: }
327: }
328:
329: private static class SetAsCurrentThemeAction extends AbstractAction {
330:
331: private final Project project;
332: private final Library theme;
333:
334: public SetAsCurrentThemeAction(Library theme, Project project) {
335: super (NbBundle.getMessage(ThemesFolderNode.class,
336: "LBL_SetAsCurrentTheme_Action"));
337: this .project = project;
338: this .theme = theme;
339: }
340:
341: public void actionPerformed(ActionEvent e) {
342: // TODO: set the project property to point at the current theme using
343: // the library name as the identifier. Add a new library reference to the project
344: // for the new theme.
345: // Is there any other config file that needs to be updated with the current theme?
346: // How do we handle multiple themes per project? In that case, we need lib refs for
347: // all "available" themes (i.e. those that are bundled with the project) but then
348: // we definitely need some way to communicate the current theme to the designtime
349: // and runtime components.
350: // Remove the old theme's library reference
351: String oldTheme = JsfProjectUtils.getProjectProperty(
352: project, JsfProjectConstants.PROP_CURRENT_THEME);
353: if (oldTheme == null) {
354: // TODO: Should scan all librefs for any theme libraries and remove them
355: oldTheme = "theme-default"; // NOI18N
356: }
357: Library oldThemeLibrary = JsfProjectUtils
358: .getProjectLibraryManager(project).getLibrary(
359: oldTheme);
360: if (oldThemeLibrary != null) {
361: try {
362: JsfProjectUtils.removeLibraryReferences(project,
363: new Library[] { oldThemeLibrary });
364: JsfProjectUtils.removeLocalizedTheme(project,
365: oldTheme);
366: } catch (Exception ex) {
367: ErrorManager.getDefault().notify(ex);
368: }
369: }
370: JsfProjectUtils.putProjectProperty(project,
371: JsfProjectConstants.PROP_CURRENT_THEME, theme
372: .getName());
373: try {
374: JsfProjectUtils.addLibraryReferences(project,
375: new Library[] { theme });
376: JsfProjectUtils.addLocalizedTheme(project, theme
377: .getName());
378: String msg = NbBundle
379: .getMessage(
380: ThemesFolderNode.class,
381: Utilities.isWindows() ? "MSG_ThemeChangeRestartIDE"
382: : "MSG_ThemeChangeRebuild");
383: DialogDescriptor nderr = new DialogDescriptor(
384: msg,
385: NbBundle.getMessage(NotifyDescriptor.class,
386: "NTF_InformationTitle"),
387: true,
388: new Object[] { NotifyDescriptor.OK_OPTION },
389: NotifyDescriptor.OK_OPTION,
390: DialogDescriptor.DEFAULT_ALIGN,
391: new HelpCtx(
392: "projrave_ui_elements_dialogs_theme_switch_info_db"),
393: null);
394: DialogDisplayer.getDefault().notify(nderr);
395: } catch (Exception ex) {
396: ErrorManager.getDefault().notify(ex);
397: }
398:
399: // Refresh pages in the project
400: RefreshService service = RefreshService.getDefault();
401: if (service != null) {
402: service.refresh(project);
403: }
404: }
405: }
406:
407: private static String getThemeLibraryVersion(Library library) {
408: // XXX It is a question whether to look for "classpath" or "runtime" or one of those.
409: for (URL url : library.getContent("classpath")) {
410: try {
411: if (!url.getProtocol().equals("jar")) {
412: continue; // ignore folders
413: }
414: url = FileUtil.getArchiveFile(url);
415:
416: FileObject fo = org.openide.filesystems.URLMapper
417: .findFileObject(url);
418: if (fo != null) {
419: File file = FileUtil.toFile(fo);
420: if (file != null) {
421: return getThemeJarFileVersion(new java.util.jar.JarFile(
422: file));
423: }
424: } else {
425: ErrorManager.getDefault().log(
426: "Cannot find file object for "
427: + url.toString());
428: }
429: } catch (IOException ioe) {
430: ErrorManager.getDefault().notify(
431: ErrorManager.INFORMATIONAL, ioe);
432: }
433: }
434: return null;
435: }
436:
437: private static String getThemeJarFileVersion(
438: java.util.jar.JarFile jarFile) {
439: if (jarFile == null) {
440: return null;
441: }
442:
443: try {
444: Manifest mf = jarFile.getManifest();
445: if (mf != null) {
446: Map<String, Attributes> entries = mf.getEntries();
447: Iterator<Attributes> iter = entries.values().iterator();
448: while (iter.hasNext()) {
449: Attributes attrs = iter.next();
450: String version = attrs
451: .getValue("X-SJWUIC-Theme-Version"); // NOI18N
452: if (WoodStock_ThemeVersion.equals(version)) {
453: return version;
454: }
455: }
456: }
457: } catch (IOException ioe) {
458: ErrorManager.getDefault().notify(
459: ErrorManager.INFORMATIONAL, ioe);
460: }
461:
462: return null;
463: }
464: }
|