/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.sun.lwuit.util;
import com.sun.lwuit.Display;
import com.sun.lwuit.resources.editor.ResourceEditorView;
import com.sun.lwuit.resources.editor.editors.DataEditor;
import com.sun.lwuit.resources.editor.editors.FontEditor;
import com.sun.lwuit.resources.editor.editors.ImageIndexedEditor;
import com.sun.lwuit.resources.editor.editors.ImageMultiEditor;
import com.sun.lwuit.resources.editor.editors.ImageRGBEditor;
import com.sun.lwuit.resources.editor.editors.L10nEditor;
import com.sun.lwuit.resources.editor.editors.MultiImageSVGEditor;
import com.sun.lwuit.resources.editor.editors.ThemeEditor;
import com.sun.lwuit.resources.editor.editors.TimelineEditor;
import com.sun.lwuit.resources.editor.editors.UserInterfaceEditor;
import com.sun.lwuit.EditorFont;
import com.sun.lwuit.EncodedImage;
import com.sun.lwuit.Image;
import com.sun.lwuit.LWUITAccessor;
import com.sun.lwuit.IndexedImage;
import com.sun.lwuit.StaticAnimation;
import com.sun.lwuit.animations.AnimationAccessor;
import com.sun.lwuit.animations.AnimationObject;
import com.sun.lwuit.animations.Motion;
import com.sun.lwuit.animations.Timeline;
import com.sun.lwuit.impl.swing.SVG;
import com.sun.lwuit.plaf.Border;
import com.sun.lwuit.plaf.Accessor;
import com.sun.lwuit.plaf.Style;
import java.awt.Frame;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
* This class enhances the resources class by inheriting it and using package
* friendly accessor methods.
*
* @author Shai Almog
*/
public class EditableResources extends Resources implements TreeModel {
private static final short MINOR_VERSION = 3;
private static final short MAJOR_VERSION = 1;
private boolean modified;
private boolean loadingMode = false;
private boolean ignoreSVGMode;
private boolean ignorePNGMode;
private void writeImageAsPNG(Image image, int type, DataOutputStream output) throws IOException {
BufferedImage buffer = new BufferedImage(image.getWidth(), image.getHeight(), type);
buffer.setRGB(0, 0, image.getWidth(), image.getHeight(), image.getRGB(), 0, image.getWidth());
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ImageIO.write(buffer, "png", byteOut);
byte[] data = byteOut.toByteArray();
output.writeInt(data.length);
output.write(data);
}
/**
* @return the ignoreSVGMode
*/
public boolean isIgnoreSVGMode() {
return ignoreSVGMode;
}
/**
* @param ignoreSVGMode the ignoreSVGMode to set
*/
public void setIgnoreSVGMode(boolean ignoreSVGMode) {
this.ignoreSVGMode = ignoreSVGMode;
}
/**
* @return the ignorePNGMode
*/
public boolean isIgnorePNGMode() {
return ignorePNGMode;
}
/**
* @param ignorePNGMode the ignorePNGMode to set
*/
public void setIgnorePNGMode(boolean ignorePNGMode) {
this.ignorePNGMode = ignorePNGMode;
}
private abstract class UndoableEdit {
private boolean previouslyModified;
public final String doAction() {
previouslyModified = modified;
String selection = performAction();
modified = true;
if(onChange != null) {
onChange.run();
}
return selection;
}
public final String undoAction() {
String selection = performUndo();
modified = previouslyModified;
if(onChange != null) {
onChange.run();
}
return selection;
}
protected abstract String performAction();
protected abstract String performUndo();
}
private List<UndoableEdit> undoQueue = new ArrayList<UndoableEdit>();
private List<UndoableEdit> redoQueue = new ArrayList<UndoableEdit>();
private Runnable onChange;
/**
* Create an empty resource file
*/
public EditableResources() {
super();
}
EditableResources(InputStream input) throws IOException {
super(input);
}
public static void setResourcesClassLoader(Class cls) {
Resources.setClassLoader(cls);
}
public static void setCurrentPassword(String password) {
currentPassword = password;
if(currentPassword.length() == 0) {
currentPassword = null;
key = null;
} else {
setPassword(currentPassword);
try {
key = currentPassword.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
}
}
private static byte[] key;
private static String currentPassword;
void checkKey(String id) {
JPasswordField password = new JPasswordField();
if(currentPassword != null) {
password.setText(currentPassword);
}
int v = JOptionPane.showConfirmDialog(java.awt.Frame.getFrames()[0], password, "Enter Password", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
if(v == JOptionPane.OK_OPTION) {
currentPassword = password.getText();
setPassword(currentPassword);
try {
key = currentPassword.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
char l = (char)encode(id.charAt(0));
char w = (char)encode(id.charAt(1));
//keyOffset = 0;
if(l != 'l' || w != 'w') {
// incorrect password!
JOptionPane.showMessageDialog(java.awt.Frame.getFrames()[0],
"Incorrect Password!", "Error", JOptionPane.ERROR_MESSAGE);
throw new IllegalStateException("Incorrect password");
}
return;
}
super.checkKey(id);
}
private int encode(int val) {
val = key[keyOffset] ^ val;
keyOffset++;
if(keyOffset == key.length) {
keyOffset = 0;
}
return val;
}
public void setOnChange(Runnable run) {
onChange = run;
}
void setResource(final String id, final byte type, final Object value) {
/*if(!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
setResource(id, type, value);
}
});
} catch (Exception ex) {
ex.printStackTrace();
}
return;
}*/
boolean exists = false;
int index = -1;
if(value != null) {
exists = getResourceObject(id) != null;
} else {
index = getIndexOfChild(getParent(type), id);
}
Object superValue = value;
if(multiPending != null) {
if(superValue instanceof com.sun.lwuit.EncodedImage) {
superValue = multiPending;
}
multiPending = null;
}
super.setResource(id, type, superValue);
if(superValue != null) {
index = getIndexOfChild(getParent(type), id);
if(exists) {
fireTreeNodeChanged(id, index);
} else {
fireTreeNodeAdded(id, index);
}
} else {
fireTreeNodeRemoved(id, type, index);
}
}
public void clear() {
super.clear();
modified = false;
for(String name : getResourceNames()) {
setResource(name, getResourceType(name), null);
}
}
/*@Override
void startingEntry(String id, byte magic) {
if(filePosition != lastFileEntry) {
//System.out.println("Size = " + (filePosition - lastFileEntry));
}
lastFileEntry = filePosition;
//System.out.println("Starting entry: " + id);
}
private int filePosition = 0;
private int lastFileEntry = 0;*/
@Override
public void openFile(final InputStream input) throws IOException {
/*filePosition = 0;
lastFileEntry = 0;
// wrap the existing input stream so we can count the bytes loaded
InputStream input = new InputStream() {
@Override
public int available() throws IOException {
return inputOriginal.available();
}
@Override
public void close() throws IOException {
inputOriginal.close();
}
@Override
public synchronized void mark(int readlimit) {
inputOriginal.mark(readlimit);
}
@Override
public boolean markSupported() {
return inputOriginal.markSupported();
}
@Override
public int read(byte[] b) throws IOException {
int val = inputOriginal.read(b);
filePosition += val;
return val;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int val = inputOriginal.read(b, off, len);
filePosition += val;
return val;
}
@Override
public synchronized void reset() throws IOException {
inputOriginal.reset();
}
@Override
public long skip(long n) throws IOException {
long val = inputOriginal.skip(n);
filePosition += (int)val;
return val;
}
@Override
public int read() throws IOException {
return inputOriginal.read();
}
};*/
loadingMode = true;
com.sun.lwuit.Font.clearBitmapCache();
super.openFile(input);
/*if(filePosition != lastFileEntry) {
System.out.println("Size = " + (filePosition - lastFileEntry));
}*/
loadingMode = false;
modified = false;
undoQueue.clear();
redoQueue.clear();
// this is a legacy file! We need to warn the user about saving the file!
if(getMajorVersion() < 1 && getMinorVersion() < 1) {
JOptionPane.showMessageDialog(Frame.getFrames()[0], "You have opened a legacy resource file!\n" +
"Notice that saving this file using this version of Resource Editor\n" +
"will require a new version of LWUIT and disabling compatibility mode!\n" +
"For further details go to the LWUIT faq: https://lwuit.dev.java.net/faq.html",
"Legacy Resource", JOptionPane.WARNING_MESSAGE);
}
//Style.setStyleCompatibilityMode(false);
}
/**
* Undo the last operation
*/
public String undo() {
if(isUndoable()) {
UndoableEdit edit = undoQueue.remove(undoQueue.size() - 1);
redoQueue.add(edit);
return edit.undoAction();
}
return null;
}
public boolean isUndoable() {
return !undoQueue.isEmpty();
}
public boolean isRedoable() {
return !redoQueue.isEmpty();
}
public boolean containsResource(String res) {
return getResourceObject(res) != null;
}
public String redo() {
if(isRedoable()) {
UndoableEdit edit = redoQueue.remove(redoQueue.size() - 1);
undoQueue.add(edit);
return edit.doAction();
}
return null;
}
public boolean isModified() {
return modified;
}
public void setModified() {
this.modified = true;
}
public byte[] getDataByteArray(String id) {
Object o = getResourceObject(id);
return ((byte[])o);
}
public long getDataSize(String id) {
Object o = getResourceObject(id);
if ((o != null) && (o instanceof byte[]))
return ((byte[])o).length;
else
return -1;
}
/**
* Adapts loaded themes to the new resource selected/unselected modes
*/
Hashtable loadTheme(String id, boolean newerVersion) throws IOException {
Hashtable h = super.loadTheme(id, newerVersion);
Iterator keyIter = h.keySet().iterator();
while(keyIter.hasNext()) {
String key = (String)keyIter.next();
if(key.indexOf(".bgSelection") > -1 || key.indexOf(".fgSelection") > -1 ||
key.equals("bgSelection") || key.equals("fgSelection")) {
Object value = h.get(key);
h.remove(key);
int pointPos = key.indexOf('.');
if(pointPos > -1) {
key = key.substring(0, pointPos) + ".sel#" + key.substring(pointPos + 1).replace("Selection", "");
} else {
key = "sel#" + key.replace("Selection", "");
}
h.put(key, value);
keyIter = h.keySet().iterator();
}
if(key.indexOf("scaledImage") > -1) {
Object value = h.get(key);
h.remove(key);
int pointPos = key.indexOf('.');
if(pointPos > -1) {
key = key.substring(0, pointPos) + ".bgType";
} else {
key = "bgType";
}
if(((String)value).equals("true")) {
h.put(key, new Byte(Style.BACKGROUND_IMAGE_SCALED));
} else {
h.put(key, new Byte(Style.BACKGROUND_IMAGE_TILE_BOTH));
}
keyIter = h.keySet().iterator();
}
}
return h;
}
/**
* Allows us to store a modified resource file
*/
public void save(OutputStream out) throws IOException {
DataOutputStream output = new DataOutputStream(out);
String[] resourceNames = getResourceNames();
keyOffset = 0;
if(currentPassword != null) {
output.writeShort(resourceNames.length + 2);
output.writeByte(MAGIC_PASSWORD);
output.writeUTF("" + ((char)encode('l')) + ((char)encode('w')));
output.writeByte(encode(MAGIC_HEADER & 0xff));
} else {
output.writeShort(resourceNames.length + 1);
// write the header of the resource file
output.writeByte(MAGIC_HEADER);
}
output.writeUTF("");
// the size of the header
output.writeShort(6);
output.writeShort(MAJOR_VERSION);
output.writeShort(MINOR_VERSION);
// currently resource file meta-data isn't supported
output.writeShort(0);
for(int iter = 0 ; iter < resourceNames.length ; iter++) {
// write the magic number
byte magic = getResourceType(resourceNames[iter]);
switch(magic) {
case MAGIC_TIMELINE:
case MAGIC_ANIMATION_LEGACY:
case MAGIC_IMAGE_LEGACY:
case MAGIC_INDEXED_IMAGE_LEGACY:
magic = MAGIC_IMAGE;
break;
case MAGIC_THEME_LEGACY:
magic = MAGIC_THEME;
break;
case MAGIC_FONT_LEGACY:
magic = MAGIC_FONT;
break;
}
if(currentPassword != null) {
output.writeByte(encode(magic & 0xff));
char[] chars = resourceNames[iter].toCharArray();
for(int i = 0 ; i < chars.length ; i++) {
chars[i] = (char)encode(chars[i] & 0xffff);
}
output.writeUTF(new String(chars));
} else {
output.writeByte(magic);
output.writeUTF(resourceNames[iter]);
}
switch(magic) {
case MAGIC_IMAGE:
Object o = getResourceObject(resourceNames[iter]);
if(!(o instanceof MultiImage)) {
o = null;
}
saveImage(output, getImage(resourceNames[iter]), (MultiImage)o, BufferedImage.TYPE_INT_ARGB);
continue;
case MAGIC_THEME:
saveTheme(output, getTheme(resourceNames[iter]), magic == MAGIC_THEME_LEGACY);
continue;
case MAGIC_FONT:
saveFont(output, false, resourceNames[iter]);
continue;
case MAGIC_DATA: {
InputStream i = getData(resourceNames[iter]);
ByteArrayOutputStream outArray = new ByteArrayOutputStream();
int val = i.read();
while(val != -1) {
outArray.write(val);
val = i.read();
}
byte[] data = outArray.toByteArray();
output.writeInt(data.length);
output.write(data);
continue;
}
case MAGIC_UI: {
InputStream i = getUi(resourceNames[iter]);
ByteArrayOutputStream outArray = new ByteArrayOutputStream();
int val = i.read();
while(val != -1) {
outArray.write(val);
val = i.read();
}
byte[] data = outArray.toByteArray();
output.writeInt(data.length);
output.write(data);
continue;
}
case MAGIC_L10N:
// we are getting the theme which allows us to acces the l10n data
saveL10N(output, getTheme(resourceNames[iter]));
continue;
default:
throw new IOException("Corrupt theme file unrecognized magic number: " + Integer.toHexString(magic & 0xff));
}
}
modified = false;
undoQueue.clear();
redoQueue.clear();
}
private void savePacked(DataOutputStream output, com.sun.lwuit.IndexedImage image) throws IOException {
int[] tempPalette = LWUITAccessor.getPalette(image);
output.writeByte(tempPalette.length);
for(int iter = 0 ; iter < tempPalette.length ; iter++) {
output.writeInt(tempPalette[iter]);
}
output.writeShort(image.getWidth());
output.writeShort(image.getHeight());
byte[] data = LWUITAccessor.getImageData(image);
output.write(data, 0, data.length);
}
private void removeMultiConstants(Hashtable h) {
for(Object k : h.keySet()) {
String key = (String)k;
if(key.startsWith("@")) {
Object val = h.get(k);
if(val instanceof MultiImage) {
h.put(k, ((MultiImage)val).getBest());
removeMultiConstants(h);
return;
}
}
}
}
public Hashtable getTheme(String id) {
if(loadingMode) {
return new Hashtable();
}
Hashtable h = super.getTheme(id);
if(h != null) {
removeMultiConstants(h);
h.remove("name");
}
return h;
}
private void saveAnimation(DataOutputStream output, StaticAnimation animation) throws IOException {
// read the length of the palette;
int[] palette = LWUITAccessor.getPalette(animation);
output.writeByte(palette.length);
for(int iter = 0 ; iter < palette.length ; iter++) {
output.writeInt(palette[iter]);
}
output.writeShort(animation.getWidth());
output.writeShort(animation.getHeight());
output.writeByte(animation.getFrameCount());
output.writeInt(animation.getTotalAnimationTime());
output.writeBoolean(animation.isLoop());
output.write(LWUITAccessor.getImageData(animation));
// track previous rows, some rows are duplicated in memory but use the same pointer
List<byte[]> previousRows = new ArrayList<byte[]>();
// read the rest of the frames in the animation
for(int iter = 1 ; iter < animation.getFrameCount() ; iter++) {
output.writeInt(animation.getFrameTime(iter) - animation.getFrameTime(iter - 1));
// if this is a keyframe then just write the data else read the specific
// modified row offsets
boolean keyFrame = LWUITAccessor.isKeyframe(animation, iter);
output.writeBoolean(keyFrame);
if(keyFrame) {
output.write(LWUITAccessor.getKeyframe(animation, iter));
} else {
output.writeBoolean(LWUITAccessor.isDrawPrevious(animation, iter));
int[] modifiedRowOffsets = LWUITAccessor.getModifiedRowOffsets(animation, iter);
byte[][] modifiedRows = LWUITAccessor.getModifiedRows(animation, iter);
for(int counter = 0 ; counter < modifiedRowOffsets.length ; counter++) {
if(!contains(previousRows, modifiedRows[counter])) {
output.writeShort(modifiedRowOffsets[counter]);
output.write(modifiedRows[counter]);
previousRows.add(modifiedRows[counter]);
}
}
output.writeShort(-1);
}
}
}
private boolean contains(List<byte[]> b, byte[] arr) {
for(byte[] current : b) {
if(current == arr) {
return true;
}
}
return false;
}
private void saveTheme(DataOutputStream output, Hashtable theme, boolean newVersion) throws IOException {
theme.remove("name");
output.writeShort(theme.size());
for(Object currentKey : theme.keySet()) {
String key = (String)currentKey;
output.writeUTF(key);
if(key.startsWith("@")) {
if(key.endsWith("Image")) {
output.writeUTF(findId(theme.get(key)));
} else {
output.writeUTF((String)theme.get(key));
}
continue;
}
// if this is a simple numeric value
if(key.endsWith("Color")) {
output.writeInt(Integer.decode("0x" + theme.get(key)));
continue;
}
if(key.endsWith("align") || key.endsWith("textDecoration")) {
output.writeShort(((Number)theme.get(key)).shortValue());
continue;
}
// if this is a short numeric value
if(key.endsWith("transparency")) {
output.writeByte(Integer.parseInt((String)theme.get(key)));
continue;
}
// if this is a padding or margin then we will have the 4 values as bytes
if(key.endsWith("padding") || key.endsWith("margin")) {
String[] arr = ((String)theme.get(key)).split(",");
output.writeByte(Integer.parseInt(arr[0]));
output.writeByte(Integer.parseInt(arr[1]));
output.writeByte(Integer.parseInt(arr[2]));
output.writeByte(Integer.parseInt(arr[3]));
continue;
}
// padding and or margin type
if(key.endsWith("Unit")) {
for(byte b : (byte[])theme.get(key)) {
output.writeByte(b);
}
continue;
}
if(key.endsWith("border")) {
Border border = (Border)theme.get(key);
writeBorder(output, border, newVersion);
continue;
}
// if this is a font
if(key.endsWith("font")) {
com.sun.lwuit.Font f = (com.sun.lwuit.Font)theme.get(key);
// is this a new font?
boolean newFont = f instanceof EditorFont;
output.writeBoolean(newFont);
if(newFont) {
String fontId = findId(f);
output.writeUTF(fontId);
} else {
output.writeByte(f.getFace());
output.writeByte(f.getStyle());
output.writeByte(f.getSize());
}
continue;
}
// if this is a background image
if(key.endsWith("bgImage")) {
String imageId = findId(theme.get(key));
output.writeUTF(imageId);
continue;
}
if(key.endsWith("scaledImage")) {
output.writeBoolean(theme.get(key).equals("true"));
continue;
}
if(key.endsWith("derive")) {
output.writeUTF((String)theme.get(key));
continue;
}
// if this is a background gradient
if(key.endsWith("bgGradient")) {
Object[] gradient = (Object[])theme.get(key);
output.writeInt(((Integer)gradient[0]).intValue());
output.writeInt(((Integer)gradient[1]).intValue());
output.writeFloat(((Float)gradient[2]).floatValue());
output.writeFloat(((Float)gradient[3]).floatValue());
output.writeFloat(((Float)gradient[4]).floatValue());
continue;
}
if(key.endsWith(Style.BACKGROUND_TYPE) || key.endsWith(Style.BACKGROUND_ALIGNMENT)) {
output.writeByte(((Number)theme.get(key)).intValue());
continue;
}
// thow an exception no idea what this is
throw new IOException("Error while trying to read theme property: " + key);
}
}
private void writeBorder(DataOutputStream output, Border border, boolean newVersion) throws IOException {
int type = Accessor.getType(border);
switch(type) {
case BORDER_TYPE_EMPTY:
output.writeShort(0xff01);
return;
case BORDER_TYPE_LINE:
output.writeShort(0xff02);
// use theme colors?
if(Accessor.isThemeColors(border)) {
output.writeBoolean(true);
output.writeByte(Accessor.getThickness(border));
} else {
output.writeBoolean(false);
output.writeByte(Accessor.getThickness(border));
output.writeInt(Accessor.getColorA(border));
}
return;
case BORDER_TYPE_ROUNDED:
output.writeShort(0xff03);
// use theme colors?
if(Accessor.isThemeColors(border)) {
output.writeBoolean(true);
output.writeByte(Accessor.getArcWidth(border));
output.writeByte(Accessor.getArcHeight(border));
} else {
output.writeBoolean(false);
output.writeByte(Accessor.getArcWidth(border));
output.writeByte(Accessor.getArcHeight(border));
output.writeInt(Accessor.getColorA(border));
}
return;
case BORDER_TYPE_ETCHED_RAISED:
output.writeShort(0xff05);
writeEtchedBorder(output, border);
return;
case BORDER_TYPE_ETCHED_LOWERED:
output.writeShort(0xff04);
writeEtchedBorder(output, border);
return;
case BORDER_TYPE_BEVEL_LOWERED:
output.writeShort(0xff06);
writeBevelBorder(output, border);
return;
case BORDER_TYPE_BEVEL_RAISED:
output.writeShort(0xff07);
writeBevelBorder(output, border);
return;
case BORDER_TYPE_IMAGE:
output.writeShort(0xff08);
writeImageBorder(output, border);
return;
case BORDER_TYPE_IMAGE_HORIZONTAL:
output.writeShort(0xff09);
writeImageHVBorder(output, border);
return;
case BORDER_TYPE_IMAGE_VERTICAL:
output.writeShort(0xff10);
writeImageHVBorder(output, border);
return;
}
}
private void writeBevelBorder(DataOutputStream output, Border border) throws IOException {
// use theme colors?
if(Accessor.isThemeColors(border)) {
output.writeBoolean(true);
} else {
output.writeBoolean(false);
output.writeInt(Accessor.getColorA(border));
output.writeInt(Accessor.getColorB(border));
output.writeInt(Accessor.getColorC(border));
output.writeInt(Accessor.getColorD(border));
}
}
private void writeEtchedBorder(DataOutputStream output, Border border) throws IOException {
// use theme colors?
if(Accessor.isThemeColors(border)) {
output.writeBoolean(true);
} else {
output.writeBoolean(false);
output.writeInt(Accessor.getColorA(border));
output.writeInt(Accessor.getColorB(border));
}
}
private void writeImageHVBorder(DataOutputStream output, Border border) throws IOException {
Image[] images = Accessor.getImages(border);
output.writeUTF(findId(images[0]));
output.writeUTF(findId(images[1]));
output.writeUTF(findId(images[2]));
}
private void writeImageBorder(DataOutputStream output, Border border) throws IOException {
// Write the number of images can be 2, 3, 8 or 9
Image[] images = Accessor.getImages(border);
int resourceCount = 0;
for(int iter = 0 ; iter < images.length ; iter++) {
if(images[iter] != null && findId(images[iter]) != null) {
resourceCount++;
}
}
if(resourceCount != 2 && resourceCount != 3 && resourceCount != 8 && resourceCount != 9) {
System.out.println("Odd resource count for image border: " + resourceCount);
resourceCount = 2;
}
output.writeByte(resourceCount);
switch(resourceCount) {
case 2:
output.writeUTF(findId(images[0]));
output.writeUTF(findId(images[4]));
break;
case 3:
output.writeUTF(findId(images[0]));
output.writeUTF(findId(images[4]));
output.writeUTF(findId(images[8]));
break;
case 8:
for(int iter = 0 ; iter < 8 ; iter++) {
output.writeUTF(findId(images[iter]));
}
break;
case 9:
for(int iter = 0 ; iter < 9 ; iter++) {
output.writeUTF(findId(images[iter]));
}
break;
}
}
com.sun.lwuit.Font loadFont(DataInputStream input, String id, boolean packed) throws IOException {
if(getMinorVersion() == 0 && getMajorVersion() == 0) {
com.sun.lwuit.Font bitmapFont = super.loadFont(input, id, packed);
return new EditorFont(com.sun.lwuit.Font.createSystemFont(com.sun.lwuit.Font.FACE_SYSTEM, com.sun.lwuit.Font.STYLE_PLAIN, com.sun.lwuit.Font.SIZE_MEDIUM),
null, "Arial-plain-12", true, RenderingHints.VALUE_TEXT_ANTIALIAS_ON, bitmapFont.getCharset());
} else {
// read a system font fallback
int fallback = input.readByte() & 0xff;
com.sun.lwuit.Font systemFont = com.sun.lwuit.Font.createSystemFont(fallback & 0x60, fallback & 7, fallback & 0x18);
com.sun.lwuit.Font bitmapFont = null;
byte[] truetypeFont = null;
String lookupFont = null;
// do we have an emedded truetype font? Do we support embedded fonts?
boolean trueTypeIncluded = input.readBoolean();
if(trueTypeIncluded) {
truetypeFont = new byte[input.readInt()];
input.readFully(truetypeFont);
}
boolean lookupIncluded = input.readBoolean();
if(lookupIncluded) {
lookupFont = input.readUTF();
}
boolean bitmapIncluded = input.readBoolean();
if(bitmapIncluded) {
bitmapFont = loadBitmapFont(input, id, null);
return new EditorFont(systemFont, truetypeFont, lookupFont, true, renderingHint, bitmapFont.getCharset());
}
return new EditorFont(systemFont, truetypeFont, lookupFont, false, null, null);
}
}
private Object renderingHint;
void readRenderingHint(DataInputStream i) throws IOException {
renderingHint = EditorFont.RENDERING_HINTS[i.readByte()];
}
private void saveFont(DataOutputStream output, boolean packed, String id) throws IOException {
EditorFont f = (EditorFont)getFont(id);
// write the system font fallback
output.writeByte(f.getSystemFallback().getFace() | f.getSystemFallback().getSize() | f.getSystemFallback().getStyle());
// do we have an emedded truetype font? Do we support embedded fonts?
boolean trueTypeIncluded = f.getTruetypeFont() != null;
output.writeBoolean(trueTypeIncluded);
if(trueTypeIncluded) {
output.writeInt(f.getTruetypeFont().length);
output.write(f.getTruetypeFont());
}
boolean lookupIncluded = f.getLookupFont() != null;
output.writeBoolean(lookupIncluded);
if(lookupIncluded) {
output.writeUTF(f.getLookupFont());
}
boolean bitmapIncluded = f.isIncludesBitmap();
output.writeBoolean(bitmapIncluded);
if(bitmapIncluded) {
com.sun.lwuit.Font bitmapFont = f.getBitmapFont();
com.sun.lwuit.Image cache = LWUITAccessor.getImage(bitmapFont);
int[] imageArray = cache.getRGB();
for(int iter = 0 ; iter < imageArray.length ; iter++) {
imageArray[iter] = (imageArray[iter] >> 8) & 0xff0000;
}
if(packed) {
savePacked(output, IndexedImage.pack(imageArray, cache.getWidth(), cache.getHeight()));
} else {
saveImage(output, com.sun.lwuit.Image.createImage(imageArray, cache.getWidth(), cache.getHeight()), null, BufferedImage.TYPE_INT_RGB);
}
String charset = bitmapFont.getCharset();
int charCount = charset.length();
output.writeShort(charCount);
int[] cutOffsets = LWUITAccessor.getOffsets(bitmapFont);
int[] charWidth = LWUITAccessor.getWidths(bitmapFont);
for(int iter = 0 ; iter < charCount ; iter++) {
output.writeShort(cutOffsets[iter]);
}
for(int iter = 0 ; iter < charCount ; iter++) {
output.writeByte(charWidth[iter]);
}
output.writeUTF(charset);
output.writeByte(f.getRenderingHint());
}
}
public Object getResourceObject(String res) {
return super.getResourceObject(res);
}
public String findId(Object value) {
for(String key : getResourceNames()) {
Object o = getResourceObject(key);
// special case for multi image which can be all of the internal images...
if(o instanceof MultiImage) {
for(Object c : ((MultiImage)o).getInternalImages()) {
if(c == value) {
return key;
}
}
}
if(o == value) {
return key;
}
}
return null;
}
private int getImageType(com.sun.lwuit.Image image, MultiImage mi) {
if(mi != null) {
return 0xF6;
}
if(image instanceof StaticAnimation) {
return 0xf4;
}
if(image instanceof IndexedImage) {
return 0xf3;
}
if(image instanceof Timeline) {
return MAGIC_TIMELINE;
}
if(image.isSVG()) {
return 0xf7;
}
// png image
return 0xf1;
}
private void saveImage(DataOutputStream output, com.sun.lwuit.Image image, MultiImage mi, int type) throws IOException {
int rType = getImageType(image, mi);
if(ignoreSVGMode && (rType == 0xf5 || rType == 0xf7)) {
output.writeByte(0xf1);
} else {
output.writeByte(rType);
}
switch(rType) {
// PNG file
case 0xf1:
// JPEG File
case 0xf2:
if(image instanceof EncodedImage) {
byte[] data = ((EncodedImage)image).getImageData();
output.writeInt(data.length);
output.write(data);
} else {
writeImageAsPNG(image,type, output);
}
break;
// Indexed image
case 0xf3:
savePacked(output, (IndexedImage)image);
break;
// animation
case 0xf4:
saveAnimation(output, (StaticAnimation)image);
break;
// SVG
case 0xf5:
// multiimage with SVG
case 0xf7:
if(ignoreSVGMode) {
writeImageAsPNG(image, type, output);
break;
}
saveSVG(output, image, rType == 0xf7);
break;
case 0xF6:
writeMultiImage(output, mi);
break;
// Timeline
case MAGIC_TIMELINE:
writeTimeline(output, (Timeline)image);
break;
// Fail this is the wrong data type
default:
throw new IOException("Illegal type while creating image: " + Integer.toHexString(type));
}
}
private void writeMultiImage(DataOutputStream output, MultiImage mi) throws IOException {
output.writeInt(mi.getDpi().length);
for(int iter = 0 ; iter < mi.getDpi().length ; iter++) {
output.writeInt(mi.getDpi()[iter]);
output.writeInt(mi.getInternalImages()[iter].getImageData().length);
}
for(int iter = 0 ; iter < mi.getDpi().length ; iter++) {
output.write(mi.getInternalImages()[iter].getImageData());
}
}
private void writeTimeline(DataOutputStream output, Timeline t) throws IOException {
output.writeInt(t.getDuration());
output.writeInt(t.getSize().getWidth());
output.writeInt(t.getSize().getHeight());
AnimationObject[] animations = new AnimationObject[t.getAnimationCount()];
output.writeShort(animations.length);
for(int iter = 0 ; iter < animations.length ; iter++) {
animations[iter] = t.getAnimation(iter);
String name = AnimationAccessor.getImageName(animations[iter]);
if(name == null) {
name = findId(AnimationAccessor.getImage(animations[iter]));
}
output.writeUTF(name);
int startTime = animations[iter].getStartTime();
int animDuration = animations[iter].getEndTime() - startTime;
output.writeInt(startTime);
output.writeInt(animDuration);
Motion motionX = AnimationAccessor.getMotionX(animations[iter]);
Motion motionY = AnimationAccessor.getMotionY(animations[iter]);
output.writeInt(motionX.getSourceValue());
output.writeInt(motionY.getSourceValue());
int frameDelay = AnimationAccessor.getFrameDelay(animations[iter]);
output.writeInt(frameDelay);
if(frameDelay > -1) {
output.writeInt(AnimationAccessor.getFrameWidth(animations[iter]));
output.writeInt(AnimationAccessor.getFrameHeight(animations[iter]));
}
if(motionX.getSourceValue() != motionX.getDestinationValue()) {
output.writeBoolean(true);
output.writeInt(AnimationAccessor.getMotionType(motionX));
output.writeInt(motionX.getDestinationValue());
} else {
output.writeBoolean(false);
}
if(motionY.getSourceValue() != motionY.getDestinationValue()) {
output.writeBoolean(true);
output.writeInt(AnimationAccessor.getMotionType(motionY));
output.writeInt(motionY.getDestinationValue());
} else {
output.writeBoolean(false);
}
writeMotion(AnimationAccessor.getWidth(animations[iter]), output);
writeMotion(AnimationAccessor.getHeight(animations[iter]), output);
writeMotion(AnimationAccessor.getOpacity(animations[iter]), output);
writeMotion(AnimationAccessor.getOrientation(animations[iter]), output);
}
}
private void writeMotion(Motion m, DataOutputStream output) throws IOException {
if(m != null) {
output.writeBoolean(true);
output.writeInt(AnimationAccessor.getMotionType(m));
output.writeInt(m.getSourceValue());
output.writeInt(m.getDestinationValue());
} else {
output.writeBoolean(false);
}
}
private void saveSVG(DataOutputStream out, Image i, boolean isMultiImage) throws IOException {
SVG s = (SVG)i.getSVGDocument();
out.writeInt(s.getSvgData().length);
out.write(s.getSvgData());
if(s.getBaseURL() == null) {
out.writeUTF("");
} else {
out.writeUTF(s.getBaseURL());
}
// unknown???
out.writeBoolean(true);
if(ignorePNGMode) {
out.writeFloat(s.getRatioW());
out.writeFloat(s.getRatioH());
out.writeInt(0);
} else {
if(isMultiImage) {
writeMultiImage(out, svgToMulti(i));
} else {
out.writeFloat(s.getRatioW());
out.writeFloat(s.getRatioH());
writeImageAsPNG(i, BufferedImage.TYPE_INT_ARGB, out);
}
}
}
private com.sun.lwuit.EncodedImage toEncodedImage(Image image) throws IOException {
if(image instanceof EncodedImage) {
return (com.sun.lwuit.EncodedImage)image;
}
BufferedImage buffer = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
buffer.setRGB(0, 0, image.getWidth(), image.getHeight(), image.getRGB(), 0, image.getWidth());
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ImageIO.write(buffer, "png", byteOut);
byteOut.close();
return com.sun.lwuit.EncodedImage.create(byteOut.toByteArray());
}
private MultiImage svgToMulti(Image image) throws IOException {
SVG s = (SVG)image.getSVGDocument();
MultiImage mi = new MultiImage();
mi.dpi = s.getDpis();
if(mi.dpi == null || mi.dpi.length == 0) {
mi.dpi = new int[] {com.sun.lwuit.Display.DENSITY_MEDIUM};
mi.internalImages = new com.sun.lwuit.EncodedImage[] {toEncodedImage(image)};
return mi;
}
mi.internalImages = new com.sun.lwuit.EncodedImage[mi.dpi.length];
for(int iter = 0 ; iter < mi.dpi.length ; iter++) {
Image currentImage = image.scaled(s.getWidthForDPI()[iter], s.getHeightForDPI()[iter]);
mi.internalImages[iter] = toEncodedImage(currentImage);
}
return mi;
}
@Override
com.sun.lwuit.Image createSVG(boolean animated, byte[] data) throws IOException {
com.sun.lwuit.Image img = super.createSVG(animated, data);
SVG s = (SVG)img.getSVGDocument();
s.setDpis(dpisLoaded);
s.setWidthForDPI(widthForDPI);
s.setHeightForDPI(heightForDPI);
return img;
}
private int[] dpisLoaded;
private int[] widthForDPI;
private int[] heightForDPI;
private MultiImage multiPending;
@Override
Image readMultiImage(DataInputStream input, boolean skipAll) throws IOException {
com.sun.lwuit.EncodedImage resultImage = null;
int dpi = com.sun.lwuit.Display.getInstance().getDeviceDensity();
int dpiCount = input.readInt();
int bestFitOffset = 0;
int bestFitDPI = 0;
int[] lengths = new int[dpiCount];
dpisLoaded = new int[dpiCount];
widthForDPI = new int[dpiCount];
heightForDPI = new int[dpiCount];
for(int iter = 0 ; iter < dpiCount ; iter++) {
int currentDPI = input.readInt();
dpisLoaded[iter] = currentDPI;
lengths[iter] = input.readInt();
if(bestFitDPI != dpi && dpi >= currentDPI && currentDPI >= bestFitDPI) {
bestFitDPI = currentDPI;
bestFitOffset = iter;
}
}
multiPending = new MultiImage();
multiPending.setDpi(dpisLoaded);
multiPending.setInternalImages(new EncodedImage[dpisLoaded.length]);
for(int iter = 0 ; iter < lengths.length ; iter++) {
int size = lengths[iter];
if(!skipAll && bestFitOffset == iter) {
byte[] multiImageData = new byte[size];
input.readFully(multiImageData, 0, size);
resultImage = com.sun.lwuit.EncodedImage.create(multiImageData);
widthForDPI[iter] = resultImage.getWidth();
heightForDPI[iter] = resultImage.getHeight();
multiPending.getInternalImages()[iter] = resultImage;
} else {
byte[] multiImageData = new byte[size];
input.readFully(multiImageData, 0, size);
com.sun.lwuit.EncodedImage tmp = com.sun.lwuit.EncodedImage.create(multiImageData);
widthForDPI[iter] = tmp.getWidth();
heightForDPI[iter] = tmp.getHeight();
multiPending.getInternalImages()[iter] = tmp;
}
}
if(resultImage == null) {
return Image.createImage(5, 5);
}
return resultImage;
}
private float ratioW;
private float ratioH;
@Override
void loadSVGRatios(DataInputStream input) throws IOException {
ratioW = input.readFloat();
ratioH = input.readFloat();
}
@Override
Image createImage() throws IOException {
Image i = super.createImage();
if(i.isSVG()) {
SVG s = (SVG)i.getSVGDocument();
s.setRatioH(ratioH);
s.setRatioW(ratioW);
}
return i;
}
public com.sun.lwuit.Image getImage(String id) {
Object o = getResourceObject(id);
if(o instanceof MultiImage) {
MultiImage m = (MultiImage)o;
return m.getBest();
}
return (com.sun.lwuit.Image)o;
}
private void pushUndoable(UndoableEdit edit) {
undoQueue.add(edit);
edit.doAction();
redoQueue.clear();
}
public void setImage(final String name, final com.sun.lwuit.Image value) {
// we need to replace the image in all the themes...
final com.sun.lwuit.Image oldValue = getImage(name);
byte type;
if(value instanceof IndexedImage) {
type = MAGIC_INDEXED_IMAGE_LEGACY;
} else {
if(value instanceof StaticAnimation) {
type = MAGIC_ANIMATION_LEGACY;
} else {
if(value instanceof Timeline) {
type = MAGIC_TIMELINE;
} else {
type = MAGIC_IMAGE;
}
}
}
final byte finalType = type;
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
replaceThemeValue(oldValue, value);
setResource(name, finalType, value);
return name;
}
@Override
protected String performUndo() {
replaceThemeValue(value, oldValue);
setResource(name, finalType, oldValue);
return name;
}
});
}
public boolean isMultiImage(String name) {
return getResourceObject(name) instanceof MultiImage;
}
public void setMultiImage(final String name, final MultiImage value) {
// we need to replace the image in all the themes...
final Object oldValue = getResourceObject(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
replaceThemeValue(oldValue, value);
setResource(name, MAGIC_IMAGE, value);
return name;
}
@Override
protected String performUndo() {
replaceThemeValue(value, value);
setResource(name, MAGIC_IMAGE, oldValue);
return name;
}
});
}
public void setSVGDPIs(final String name, final int[] dpi, final int[] widths, final int[] heights) {
final SVG sv = (SVG)getImage(name).getSVGDocument();
final int[] currentDPIs = sv.getDpis();
final int[] currentWidths = sv.getWidthForDPI();
final int[] currentHeights = sv.getHeightForDPI();
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
sv.setDpis(dpi);
sv.setWidthForDPI(widths);
sv.setHeightForDPI(heights);
return name;
}
@Override
protected String performUndo() {
sv.setDpis(currentDPIs);
sv.setWidthForDPI(currentWidths);
sv.setHeightForDPI(currentHeights);
return name;
}
});
}
public void setAnimation(final String name, final com.sun.lwuit.Image value) {
final StaticAnimation oldValue = getAnimation(name);
// we need to replace the animation in all the themes...
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
replaceThemeValue(getAnimation(name), value);
setResource(name, MAGIC_ANIMATION_LEGACY, value);
return name;
}
@Override
protected String performUndo() {
replaceThemeValue(getAnimation(name), oldValue);
setResource(name, MAGIC_ANIMATION_LEGACY, oldValue);
return name;
}
});
}
public void setTheme(final String name, final Hashtable theme) {
final Hashtable oldTheme = getTheme(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
setResource(name, MAGIC_THEME_LEGACY, theme);
return name;
}
@Override
protected String performUndo() {
setResource(name, MAGIC_THEME_LEGACY, oldTheme);
return name;
}
});
}
public void setL10N(final String name, final Hashtable l10n) {
final Hashtable oldL10N = (Hashtable)getResourceObject(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
setResource(name, MAGIC_L10N, l10n);
return name;
}
@Override
protected String performUndo() {
setResource(name, MAGIC_L10N, oldL10N);
return name;
}
});
}
/**
* Place a locale property into the resource editor
*/
public void setLocaleProperty(final String localeName, final String locale, final String key, final Object value) {
final Object oldValue = getL10N(localeName, locale).get(key);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
if(value == null) {
getL10N(localeName, locale).remove(key);
return null;
} else {
getL10N(localeName, locale).put(key, value);
return localeName;
}
}
@Override
protected String performUndo() {
if(oldValue == null) {
getL10N(localeName, locale).remove(key);
} else {
getL10N(localeName, locale).put(key, oldValue);
}
return localeName;
}
});
}
/**
* Remove a locale
*/
public void removeLocale(final String localeName, final String locale) {
final Hashtable current = (Hashtable)((Hashtable)getResourceObject(localeName)).get(locale);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
((Hashtable)getResourceObject(localeName)).remove(locale);
return localeName;
}
@Override
protected String performUndo() {
((Hashtable)getResourceObject(localeName)).put(locale, current);
return localeName;
}
});
}
/**
* Adds a new locale baring the given name
*/
public void addLocale(final String localeName, final String locale) {
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
((Hashtable)getResourceObject(localeName)).put(locale, new Hashtable());
return localeName;
}
@Override
protected String performUndo() {
((Hashtable)getResourceObject(localeName)).remove(locale);
return localeName;
}
});
}
public Iterator getLocales(String localeName) {
return ((Hashtable)getResourceObject(localeName)).keySet().iterator();
}
/**
* Place a theme property into the resource editor
*/
public void setThemeProperty(final String themeName, final String key, final Object value) {
final Object oldValue = getTheme(themeName).get(key);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
if(value == null) {
getTheme(themeName).remove(key);
} else {
getTheme(themeName).put(key, value);
}
return themeName;
}
@Override
protected String performUndo() {
if(oldValue == null) {
getTheme(themeName).remove(key);
} else {
getTheme(themeName).put(key, oldValue);
}
return themeName;
}
});
}
/**
* Place a theme property into the resource editor
*/
public void setThemeProperties(final String themeName, final Hashtable newTheme) {
final Hashtable oldTheme = new Hashtable(getTheme(themeName));
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
getTheme(themeName).clear();
getTheme(themeName).putAll(newTheme);
return themeName;
}
@Override
protected String performUndo() {
getTheme(themeName).clear();
getTheme(themeName).putAll(oldTheme);
return themeName;
}
});
}
/**
* Place a theme property into the resource editor
*/
public void setThemeProperties(final String themeName, final String[] keys, final Object[] values) {
final Object[] oldValues = new Object[keys.length];
for(int iter = 0 ; iter < keys.length ; iter++) {
oldValues[iter] = getTheme(themeName).get(keys[iter]);
}
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
for(int iter = 0 ; iter < keys.length ; iter++) {
if(values[iter] == null) {
getTheme(themeName).remove(keys[iter]);
} else {
getTheme(themeName).put(keys[iter], values[iter]);
}
}
return themeName;
}
@Override
protected String performUndo() {
for(int iter = 0 ; iter < keys.length ; iter++) {
if(oldValues[iter] == null) {
getTheme(themeName).remove(keys[iter]);
} else {
getTheme(themeName).put(keys[iter], oldValues[iter]);
}
}
return themeName;
}
});
}
public void setData(final String name, final byte[] data) {
final byte[] oldData = (byte[])getResourceObject(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
setResource(name, MAGIC_DATA, data);
return name;
}
@Override
protected String performUndo() {
setResource(name, MAGIC_DATA, oldData);
return name;
}
});
}
public void setUi(final String name, final byte[] data) {
final byte[] oldData = (byte[])getResourceObject(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
setResource(name, MAGIC_UI, data);
return name;
}
@Override
protected String performUndo() {
setResource(name, MAGIC_UI, oldData);
return name;
}
});
}
/**
* Renames an entry in the resource tree
*/
public void addResourceObjectDuplicate(final String originalName, final String name, final Object value) {
pushUndoable(new UndoableEdit() {
private byte type;
@Override
protected String performAction() {
type = getResourceType(originalName);
setResource(name, type, value);
return name;
}
@Override
protected String performUndo() {
setResource(name, type, null);
return name;
}
});
}
/**
* Renames an entry in the resource tree
*/
public void renameEntry(final String oldName, final String newName) {
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
byte type = getResourceType(oldName);
Object value = getResourceObject(oldName);
setResource(oldName, type, null);
setResource(newName, type, value);
return newName;
}
@Override
protected String performUndo() {
byte type = getResourceType(newName);
Object value = getResourceObject(newName);
setResource(newName, type, null);
setResource(oldName, type, value);
return oldName;
}
});
}
private void saveL10N(DataOutputStream output, Hashtable l10n) throws IOException {
List keys = new ArrayList();
for(Object locale : l10n.keySet()) {
Hashtable current = (Hashtable)l10n.get(locale);
for(Object key : current.keySet()) {
if(!keys.contains(key)) {
keys.add(key);
}
}
}
output.writeShort(keys.size());
output.writeShort(l10n.size());
for(Object key : keys) {
output.writeUTF((String)key);
}
for(Object locale : l10n.keySet()) {
Hashtable currentLanguage = (Hashtable)l10n.get(locale);
output.writeUTF((String)locale);
for(Object key : keys) {
String k = (String)currentLanguage.get(key);
if(k != null) {
output.writeUTF(k);
} else {
output.writeUTF("");
}
}
}
}
public void setFont(final String name, final com.sun.lwuit.Font value) {
// we need to replace the font in all the themes...
final com.sun.lwuit.Font oldValue = getFont(name);
if(oldValue == null || !oldValue.equals(value)) {
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
replaceThemeValue(oldValue, value);
setResource(name, MAGIC_FONT, value);
return name;
}
@Override
protected String performUndo() {
replaceThemeValue(value, oldValue);
setResource(name, MAGIC_FONT, oldValue);
return name;
}
});
}
}
public void refreshThemeMultiImages() {
for(String themeName : getThemeResourceNames()) {
Hashtable theme = getTheme(themeName);
for(Object key : theme.keySet()) {
Object currentValue = theme.get(key);
if(currentValue instanceof com.sun.lwuit.Image) {
String id = findId(currentValue);
if(isMultiImage(id)) {
theme.put(key, ((MultiImage)getResourceObject(id)).getBest());
}
}
if(currentValue instanceof com.sun.lwuit.plaf.Border) {
com.sun.lwuit.Image[] images = Accessor.getImages((com.sun.lwuit.plaf.Border)currentValue);
if(images != null) {
for(int iter = 0 ; iter < images.length ; iter++) {
com.sun.lwuit.Image img = images[iter];
if(img != null) {
String id = findId(img);
if(isMultiImage(id)) {
images[iter] = ((MultiImage)getResourceObject(id)).getBest();
}
}
}
}
}
}
}
com.sun.lwuit.Form f = Display.getInstance().getCurrent();
if(f != null) {
f.revalidate();
}
}
/**
* Used when changing a font or an image to update the theme
*/
private void replaceThemeValue(Object oldValue, Object value) {
if(oldValue != null && value != null) {
if(oldValue instanceof MultiImage) {
MultiImage m = (MultiImage)oldValue;
Object newValue = value;
if(newValue instanceof MultiImage) {
newValue = ((MultiImage)newValue).getBest();
}
for(com.sun.lwuit.EncodedImage e : m.getInternalImages()) {
replaceThemeValue(e, newValue);
}
}
for(String themeName : getThemeResourceNames()) {
Hashtable theme = getTheme(themeName);
for(Object key : theme.keySet()) {
Object currentValue = theme.get(key);
if(currentValue == oldValue) {
theme.put(key, value);
}
}
}
// we need to check the existance of image borders to replace images there...
if(value instanceof Image) {
for(String themeName : getThemeResourceNames()) {
Hashtable theme = getTheme(themeName);
for(Object v : theme.values()) {
if(v instanceof Border) {
Border b = (Border)v;
if(Accessor.getType(b) == BORDER_TYPE_IMAGE ||
Accessor.getType(b) == BORDER_TYPE_IMAGE_VERTICAL ||
Accessor.getType(b) == BORDER_TYPE_IMAGE_HORIZONTAL) {
Image[] images = Accessor.getImages(b);
for(int i = 0 ; i < images.length ; i++) {
if(images[i] == oldValue) {
images[i] = (Image)value;
}
}
}
}
}
}
}
// check if a timeline is making use of said image and replace it
for(String image : getImageResourceNames()) {
com.sun.lwuit.Image current = getImage(image);
if(current instanceof com.sun.lwuit.animations.Timeline) {
com.sun.lwuit.animations.Timeline time = (com.sun.lwuit.animations.Timeline)current;
for(int iter = 0 ; iter < time.getAnimationCount() ; iter++) {
com.sun.lwuit.animations.AnimationObject o = time.getAnimation(iter);
if(AnimationAccessor.getImage(o) == oldValue) {
AnimationAccessor.setImage(o, (com.sun.lwuit.Image)value);
}
}
}
}
}
}
public void remove(final String name) {
final Object removedObject = getResourceObject(name);
final byte type = getResourceType(name);
pushUndoable(new UndoableEdit() {
@Override
protected String performAction() {
setResource(name, type, null);
return name;
}
@Override
protected String performUndo() {
setResource(name, type, removedObject);
return name;
}
});
}
public JComponent getResourceEditor(String name, ResourceEditorView view) {
byte magic = getResourceType(name);
switch(magic) {
case MAGIC_IMAGE:
case MAGIC_IMAGE_LEGACY:
Image i = getImage(name);
if(getResourceObject(name) instanceof MultiImage) {
ImageMultiEditor tl = new ImageMultiEditor(this, name);
tl.setImage((MultiImage)getResourceObject(name));
return tl;
}
if(i instanceof Timeline) {
TimelineEditor tl = new TimelineEditor(this, name);
tl.setImage((Timeline)i);
return tl;
}
if(i instanceof com.sun.lwuit.IndexedImage) {
ImageIndexedEditor img = new ImageIndexedEditor(this, name, view);
img.setImage(i);
return img;
}
if(i.isSVG()) {
MultiImageSVGEditor img = new MultiImageSVGEditor(this, name);
img.setImage(i);
return img;
}
ImageRGBEditor img = new ImageRGBEditor(this, name, view);
img.setImage(i);
return img;
case MAGIC_TIMELINE:
TimelineEditor tl = new TimelineEditor(this, name);
tl.setImage((Timeline)getImage(name));
return tl;
case MAGIC_INDEXED_IMAGE_LEGACY:
ImageIndexedEditor packed = new ImageIndexedEditor(this, name, view);
packed.setImage(getImage(name));
return packed;
case MAGIC_THEME:
case MAGIC_THEME_LEGACY:
ThemeEditor theme = new ThemeEditor(this, name, getTheme(name), view);
return theme;
case MAGIC_FONT:
case MAGIC_FONT_LEGACY:
case MAGIC_INDEXED_FONT_LEGACY:
FontEditor fonts = new FontEditor(this, getFont(name), name);
return fonts;
case MAGIC_ANIMATION_LEGACY:
ImageIndexedEditor ani = new ImageIndexedEditor(this, name, view);
//ani.setAnimation(true);
ani.setImage(getImage(name));
return ani;
case MAGIC_DATA:
DataEditor data = new DataEditor(this, name);
return data;
case MAGIC_UI:
UserInterfaceEditor uie = new UserInterfaceEditor(name, this, view.getProjectGeneratorSettings(), view);
return uie;
case MAGIC_L10N:
// we are cheating this isn't a theme but it should work since
// this is a hashtable that will include the nested locales
L10nEditor l10n = new L10nEditor(this, name);
return l10n;
default:
throw new IllegalArgumentException("Unrecognized magic number: " + Integer.toHexString(magic & 0xff));
}
}
public static EditableResources open(InputStream resource) throws IOException {
return new EditableResources(resource);
}
// ------------------------------------------------------------------------------------------------
// Tree Model implementation
// ------------------------------------------------------------------------------------------------
private static final Object root = new Object();
private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
private Node IMAGES = new Node("Images", "images.png") {
@Override
public String[] children() {
return getImageResourceNames();
}
};
private Node THEMES = new Node("Themes", "theme.png") {
@Override
public String[] children() {
return getThemeResourceNames();
}
};
private Node FONTS = new Node("Fonts", "font.png") {
@Override
public String[] children() {
return getFontResourceNames();
}
};
private Node L10N = new Node("Localization", "localization.png") {
@Override
public String[] children() {
return getL10NResourceNames();
}
};
private Node DATA = new Node("Data", "database.png") {
@Override
public String[] children() {
return getDataResourceNames();
}
};
private Node[] nodes = {
IMAGES, THEMES, FONTS, L10N, DATA
};
public Object getRoot() {
return root;
}
public Object getChild(Object parent, int index) {
if(parent == root) {
return nodes[index];
}
return ((Node)parent).children()[index];
}
public int getChildCount(Object parent) {
if(parent == root) {
return nodes.length;
}
if(parent instanceof Node) {
return ((Node)parent).children().length;
}
return 0;
}
public boolean isLeaf(Object node) {
return node instanceof String;
}
public void valueForPathChanged(TreePath path, Object newValue) {
Object oldValue = path.getLastPathComponent();
renameEntry((String)oldValue, (String)newValue);
TreeModelEvent ev = new TreeModelEvent(this, path.getParentPath().pathByAddingChild(newValue));
for(TreeModelListener l : listeners) {
l.treeNodesChanged(ev);
}
}
private Node getParent(byte type) {
Node parent = IMAGES;
switch(type) {
case MAGIC_THEME:
case MAGIC_THEME_LEGACY:
parent = THEMES;
break;
case MAGIC_FONT:
case MAGIC_FONT_LEGACY:
parent = FONTS;
break;
case MAGIC_DATA:
parent = DATA;
break;
case MAGIC_UI:
parent = DATA;
break;
case MAGIC_L10N:
parent = L10N;
break;
}
return parent;
}
private TreeModelEvent createEventForNode(String nodeName, byte type, int index) {
return new TreeModelEvent(this, new Object[]{root, getParent(type)}, new int[] {index}, new Object[] {nodeName});
}
public void fireTreeNodeAdded(final String nodeName, int index) {
if(nodeName == null) {
for(TreeModelListener l : listeners) {
l.treeNodesInserted(null);
}
return;
}
TreeModelEvent ev = createEventForNode(nodeName, getResourceType(nodeName), index);
for(TreeModelListener l : listeners) {
l.treeNodesInserted(ev);
}
}
public void fireTreeNodeChanged(final String nodeName, int index) {
TreeModelEvent ev = createEventForNode(nodeName, getResourceType(nodeName), index);
for(TreeModelListener l : listeners) {
l.treeNodesChanged(ev);
}
}
public void fireTreeNodeRemoved(final String nodeName, final byte type, int index) {
TreeModelEvent ev = createEventForNode(nodeName, type, index);
for(TreeModelListener l : listeners) {
l.treeNodesRemoved(ev);
}
}
public int getIndexOfChild(Object parent, Object child) {
if(parent == root) {
for(int i = 0 ; i < nodes.length ; i++) {
if(nodes[i] == child) {
return i;
}
}
}
String[] c = ((Node)parent).children();
for(int i = 0 ; i < c.length ; i++) {
if(c[i] == child) {
return i;
}
}
return -1;
}
public void addTreeModelListener(TreeModelListener l) {
if(!listeners.contains(l)) {
listeners.add(l);
}
}
public void removeTreeModelListener(TreeModelListener l) {
listeners.remove(l);
}
public static abstract class Node {
private String name;
private Icon icon;
Node(String name, String icon) {
this.name =name;
}
public String getName() {
return name;
}
public Icon getIcon() {
return icon;
}
public abstract String[] children();
}
public static class MultiImage {
private com.sun.lwuit.EncodedImage[] internalImages;
private int[] dpi;
/**
* @return the internalImages
*/
public com.sun.lwuit.EncodedImage[] getInternalImages() {
return internalImages;
}
/**
* @param internalImages the internalImages to set
*/
public void setInternalImages(com.sun.lwuit.EncodedImage[] internalImages) {
this.internalImages = internalImages;
}
/**
* @return the dpi
*/
public int[] getDpi() {
return dpi;
}
/**
* @param dpi the dpi to set
*/
public void setDpi(int[] dpi) {
this.dpi = dpi;
}
public com.sun.lwuit.EncodedImage getBest() {
if(internalImages.length == 0) {
return null;
}
int dpiVal = com.sun.lwuit.Display.getInstance().getDeviceDensity();
int bestFitOffset = 0;
int bestFitDPI = 0;
for(int iter = 0 ; iter < getDpi().length ; iter++) {
int currentDPI = getDpi()[iter];
if(bestFitDPI != dpiVal && dpiVal >= currentDPI && currentDPI >= bestFitDPI) {
bestFitDPI = currentDPI;
bestFitOffset = iter;
}
}
return getInternalImages()[bestFitOffset];
}
}
}