2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.form.editors;
44
import java.awt.Component;
48
import java.io.IOException;
49
import java.net.MalformedURLException;
50
import java.net.URISyntaxException;
52
import java.util.logging.Level;
53
import java.util.logging.Logger;
54
import javax.imageio.ImageIO;
57
import org.openide.explorer.propertysheet.editors.XMLPropertyEditor;
58
import org.openide.filesystems.FileObject;
59
import org.openide.ErrorManager;
60
import org.openide.filesystems.FileUtil;
61
import org.netbeans.api.java.classpath.ClassPath;
62
import org.netbeans.modules.form.FormModel;
63
import org.netbeans.modules.form.FormAwareEditor;
64
import org.netbeans.modules.form.FormDesignValue;
65
import org.netbeans.modules.form.FormDesignValueAdapter;
66
import org.netbeans.modules.form.FormEditor;
67
import org.netbeans.modules.form.FormProperty;
70
* PropertyEditor for Icons. Depends on existing DataObject for images.
71
* Images must be represented by some DataObject which returns itself
72
* as cookie, and has image file as a primary file. File extensions
73
* for images is specified in isImage method.
75
* @author Jan Jancura, Jan Stola, Tomas Pavek
77
public class IconEditor extends PropertyEditorSupport
78
implements XMLPropertyEditor, FormAwareEditor
80
/** Type constant for icons from URL. */
81
public static final int TYPE_URL = 1;
82
/** Type constant for icons from file. */
83
public static final int TYPE_FILE = 2;
84
/** Type constant for icons from classpath. */
85
public static final int TYPE_CLASSPATH = 3;
88
* Names of subfolders (relative to the form file) where we try to look for
89
* images by default. If none exists, the folder of the form file is used.
91
private static final String[] DEFAULT_DIRS = { "resources", "resource", "images" }; // NOI18N
94
* Resource name of the current package (uses / as separator but does not
95
* contain the initial /). Short file names are resolved against this
96
* package, and also the package content is offered in getTags().
98
private String currentPackage;
99
private String[] currentFiles;
101
//private FormModel formModel;
102
private FileObject sourceFile;
103
private boolean externalIconsAllowed = true;
105
public void setValue(Object value) {
106
if (sameValue(value, getValue()))
110
if (value instanceof NbImageIcon) {
111
nbIcon = (NbImageIcon) value;
112
if (nbIcon.getType() == TYPE_CLASSPATH) {
113
setCurrentPackage(getResourcePackage(nbIcon.getName()));
118
if (value == null && currentPackage == null)
119
setCurrentPackage(getDefaultResourcePackage());
121
super.setValue(value);
123
currentFiles = null; // hack - reset sometimes to read new folder content
126
private static boolean sameValue(Object val1, Object val2) {
127
if (val1 == null && val2 == null)
129
if (val1 instanceof NbImageIcon && val2 instanceof NbImageIcon)
130
return sameIcon((NbImageIcon)val1, (NbImageIcon)val2);
134
private static boolean sameIcon(NbImageIcon nbIcon1, NbImageIcon nbIcon2) {
135
return nbIcon1.getType() == nbIcon2.getType()
136
&& nbIcon1.getName().equals(nbIcon2.getName());
139
public String getAsText() {
140
Object val = getValue();
141
if (val instanceof NbImageIcon) {
142
NbImageIcon nbIcon = (NbImageIcon) val;
143
if (nbIcon.getType() == TYPE_CLASSPATH) {
144
String resName = nbIcon.getName();
145
if (currentPackage != null && resName.startsWith(currentPackage))
146
return resName.substring(currentPackage.length() + 1);
150
else return nbIcon.getName();
155
public void setAsText(String string) throws IllegalArgumentException {
156
setValue(createIconFromText(string));
159
public String getJavaInitializationString() {
160
if (getValue() instanceof NbImageIcon) {
161
NbImageIcon ii = (NbImageIcon)getValue();
163
case TYPE_URL: return
164
"new javax.swing.JLabel() {\n" + // NOI18N
165
" public javax.swing.Icon getIcon() {\n" + // NOI18N
166
" try {\n" + // NOI18N
167
" return new javax.swing.ImageIcon(\n" + // NOI18N
168
" new java.net.URL(\"" + convert(ii.name) + "\")\n" + // NOI18N
170
" } catch (java.net.MalformedURLException e) {\n" + // NOI18N
172
" return null;\n" + // NOI18N
174
"}.getIcon()"; // NOI18N
175
case TYPE_FILE: return
176
"*/\n\\1NOI18N*/\n\\0" // NOI18N
177
+ "new javax.swing.ImageIcon(\"" + convert(ii.name) + "\")"; // NOI18N
178
case TYPE_CLASSPATH: return
179
"*/\n\\1NOI18N*/\n\\0" // NOI18N
180
+ "new javax.swing.ImageIcon(getClass().getResource(\"/" + ii.name + "\"))"; // NOI18N
181
// */\n\\1 is a special code mark for line comment
182
// */\n\\0 is a special code mark to indicate that a real code follows
185
return "null"; // NOI18N
189
* Duplicates backslashes in the input string.
190
* @param s string to duplicate backslashes in
191
* @return string with duplicated backslashes
193
private static String convert(String s) {
194
StringTokenizer st = new StringTokenizer(s, "\\"); // NOI18N
195
StringBuilder sb = new StringBuilder();
196
if (st.hasMoreElements()) {
197
sb.append(st.nextElement());
198
while (st.hasMoreElements())
199
sb.append("\\\\").append(st.nextElement()); // NOI18N
201
return sb.toString();
204
public String[] getTags() {
205
if (currentFiles == null)
206
currentFiles = getAvailableFileNames();
210
public boolean supportsCustomEditor() {
214
public Component getCustomEditor() {
215
CustomIconEditor customEditor = new CustomIconEditor(this);
216
customEditor.setValue((NbImageIcon)getValue());
217
currentFiles = null; // hack - reset sometimes to read new folder content
221
// FormAwareEditor implementation
222
public void setContext(FormModel model, FormProperty prop) {
223
if (model != null) { // might be null when loaded as constraints property of JTabbedPane's tab
224
this.sourceFile = FormEditor.getFormDataObject(model).getPrimaryFile();
227
prop.setValue("canEditAsText", true); // NOI18N
228
if (currentPackage == null && sourceFile != null) {
229
setCurrentPackage(getDefaultResourcePackage());
233
// FormAwareEditor implementation
234
public void updateFormVersionLevel() {
239
FileObject getSourceFile() {
243
public void setSourceFile(FileObject sourceFile) {
244
this.sourceFile = sourceFile;
247
public void setExternalIconsAllowed(boolean externalIconsAllowed) {
248
this.externalIconsAllowed = externalIconsAllowed;
250
public boolean isExternalIconsAllowed() {
251
return this.externalIconsAllowed;
255
* Returns the "current folder" which is used to resolve short file names,
256
* its content is offered via getTags(), and it is also selected in the
258
* @return the current folder used to pick image files from preferentially
260
public FileObject getCurrentFolder() {
261
if (currentPackage != null) {
262
FileObject sourceFile = getSourceFile();
263
FileObject folder = ClassPath.getClassPath(sourceFile, ClassPath.SOURCE)
264
.findResource(currentPackage);
266
folder = ClassPath.getClassPath(sourceFile, ClassPath.EXECUTE)
267
.findResource(currentPackage);
274
* Sets the "current folder" which is used to resolve short file names, its
275
* content is offered via getTags(), and it is also selected in the custom
276
* editor. The folder is kept as a resource name of the corresponding
277
* package. It should be on the classpath of the source file's project.
278
* It also gets set when a classpath-based icon is set (if null, it is
279
* initially set to getDefaultResourceFolder).
280
* @param folder the current preferred folder for images
282
public void setCurrentFolder(FileObject folder) {
283
if (folder != null) {
284
FileObject sourceFile = getSourceFile();
285
FileObject root = ClassPath.getClassPath(sourceFile, ClassPath.SOURCE)
286
.findOwnerRoot(folder);
288
root = ClassPath.getClassPath(sourceFile, ClassPath.EXECUTE)
289
.findOwnerRoot(folder);
291
setCurrentPackage(FileUtil.getRelativePath(root, folder));
293
else setCurrentPackage(null);
296
private void setCurrentPackage(String pkg) {
297
currentPackage = pkg;
302
* @param resName resource name of an image file
303
* @return String representing the package of the resource
305
private String getResourcePackage(String resName) {
306
int i = resName.lastIndexOf('/');
307
if (i < 0) // default package? don't even try
310
return resName.substring(0, i);
313
FileObject getDefaultResourceFolder() {
314
FileObject sourceFile = getSourceFile();
315
for (String dir : DEFAULT_DIRS) {
316
FileObject folder = sourceFile.getParent().getFileObject(dir);
320
return sourceFile.getParent();
323
private String getDefaultResourcePackage() {
324
FileObject folder = getDefaultResourceFolder();
325
ClassPath cp = ClassPath.getClassPath(folder, ClassPath.SOURCE);
326
FileObject root = cp.findOwnerRoot(folder);
327
return FileUtil.getRelativePath(root, folder);
331
* @return names of files (without path) available in current folder
333
private String[] getAvailableFileNames() {
334
FileObject folder = getCurrentFolder();
335
if (folder != null) {
336
List<String> list = new LinkedList<String>();
337
for (FileObject fo : folder.getChildren()) {
339
list.add(fo.getNameExt());
341
String[] fileNames = new String[list.size()];
342
list.toArray(fileNames);
343
Arrays.sort(fileNames);
349
static boolean isImageFile(FileObject fo) {
350
return fo.isFolder() ? false : isImageFileName(fo.getNameExt());
353
static boolean isImageFileName(String name) {
354
name = name.toLowerCase();
355
return name.endsWith(".gif") || name.endsWith(".jpg") || name.endsWith(".png") // NOI18N
356
|| name.endsWith(".jpeg") || name.endsWith(".jpe"); // NOI18N
359
private NbImageIcon createIconFromText(String txt) {
360
if (txt == null || "".equals(txt.trim())) // NOI18N
363
if (!txt.contains("/") && !txt.contains("\\") && !txt.contains(":")) { // NOI18N
364
// just a file name within current folder
365
String pkg = currentPackage != null ? currentPackage : getDefaultResourcePackage();
366
txt = pkg + "/" + txt; // NOI18N
369
NbImageIcon nbIcon = iconFromResourceName(txt);
373
nbIcon = iconFromURL(txt, true);
377
return iconFromFileName(txt);
380
private NbImageIcon iconFromResourceName(String resName) {
381
FileObject srcFile = getSourceFile();
382
ClassPath cp = ClassPath.getClassPath(srcFile, ClassPath.SOURCE);
383
FileObject fo = cp.findResource(resName);
385
cp = ClassPath.getClassPath(srcFile, ClassPath.EXECUTE);
386
fo = cp.findResource(resName);
390
Icon icon = new ImageIcon(ImageIO.read(fo.getURL()));
391
return new NbImageIcon(TYPE_CLASSPATH, resName, icon);
392
} catch (IOException ex) { // should not happen
393
Logger.getLogger(IconEditor.class.getName()).log(Level.WARNING, null, ex);
399
private NbImageIcon iconFromURL(String urlString, boolean forceURL) {
401
URL url = new URL(urlString);
402
try { // is it a local file?
403
File f = new File(url.toURI());
404
if (f.exists() && !forceURL) { // prefer definition as file
405
String fileName = f.getAbsolutePath();
407
Icon icon = new ImageIcon(ImageIO.read(new File(fileName)));
408
return new NbImageIcon(TYPE_FILE, fileName, icon);
409
} catch (IOException ex) { // should not happen
410
Logger.getLogger(IconEditor.class.getName()).log(Level.WARNING, null, ex);
414
catch (URISyntaxException ex) {}
416
if (url != null) { // treat as url
418
Icon icon = new ImageIcon(ImageIO.read(url));
419
return new NbImageIcon(TYPE_URL, urlString, icon);
420
} catch (IOException ex) { // should not happen
421
Logger.getLogger(IconEditor.class.getName()).log(Level.WARNING, null, ex);
425
catch (MalformedURLException ex) {}
430
private NbImageIcon iconFromFileName(String fileName) {
431
File file = new File(fileName);
434
Icon icon = new ImageIcon(ImageIO.read(file));
435
return new NbImageIcon(TYPE_FILE, fileName, icon);
436
} catch (IOException ex) {
437
Logger.getLogger(IconEditor.class.getName()).log(Level.INFO, null, ex);
444
* A wrapper class for an icon value. It is public to be accessible by the
445
* resource-aware IconEditor in editors2 package.
447
public static class NbImageIcon extends FormDesignValueAdapter {
448
/** Source type of the icon (TYPE_CLASSPATH, TYPE_FILE, TYPE_URL). */
450
/** Name of the icon (can be resource name, file name, url string - according to type). */
452
/** The icon itself. */
455
public NbImageIcon(int type, String name, Icon icon) {
457
if (name.startsWith("/")) // NOI18N
458
name = name.substring(1);
463
public int getType() {
467
public String getName() {
471
public Icon getIcon() {
475
// FormDesignValue implementation
476
public Object getDesignValue() {
480
// FormDesignValue implementation
481
public String getDescription() {
485
// FormDesignValue implementation
486
public FormDesignValue copy(FormProperty formProperty) {
487
return new IconEditor.NbImageIcon(type, name, icon);
494
/** Root of the XML representation of the icon. */
495
public static final String XML_IMAGE = "Image"; // NOI18N
496
/** Attribute holding icon type. */
497
public static final String ATTR_TYPE = "iconType"; // NOI18N
498
/** Attribute holding icon name. */
499
public static final String ATTR_NAME = "name"; // NOI18N
501
public void readFromXML(org.w3c.dom.Node element) throws java.io.IOException {
502
if (!XML_IMAGE.equals(element.getNodeName())) {
503
throw new java.io.IOException();
505
org.w3c.dom.NamedNodeMap attributes = element.getAttributes();
507
int type = Integer.parseInt(attributes.getNamedItem(ATTR_TYPE).getNodeValue());
508
String name = attributes.getNamedItem(ATTR_NAME).getNodeValue();
514
setValue(iconFromURL(name, false));
517
setValue(iconFromFileName(name));
520
if (name.startsWith("/")) // NOI18N
521
name = name.substring(1);
522
setValue(iconFromResourceName(name));
525
} catch (NullPointerException e) {
526
java.io.IOException ioe = new java.io.IOException();
527
ErrorManager.getDefault().annotate(ioe, e);
532
public org.w3c.dom.Node storeToXML(org.w3c.dom.Document doc) {
533
org.w3c.dom.Element el = doc.createElement(XML_IMAGE);
534
Object value = getValue();
535
if (value instanceof NbImageIcon) {
536
NbImageIcon ii = (NbImageIcon) value;
537
String name = ii.getName();
538
if (ii.getType() == TYPE_CLASSPATH && !name.startsWith("/")) // NOI18N
539
name = "/" + name; // NOI18N
540
el.setAttribute(ATTR_TYPE, Integer.toString(ii.type));
541
el.setAttribute(ATTR_NAME, name);
543
el.setAttribute(ATTR_TYPE, "0"); // NOI18N
544
el.setAttribute(ATTR_NAME, "null"); // NOI18N