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