/* * 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.codename1.ui.util; import com.codename1.ui.Display; import com.codename1.designer.ResourceEditorView; import com.codename1.designer.DataEditor; import com.codename1.designer.FontEditor; import com.codename1.designer.ImageMultiEditor; import com.codename1.designer.ImageRGBEditor; import com.codename1.designer.L10nEditor; import com.codename1.designer.MultiImageSVGEditor; import com.codename1.designer.ThemeEditor; import com.codename1.designer.TimelineEditor; import com.codename1.designer.UserInterfaceEditor; import com.codename1.ui.EditorFont; import com.codename1.ui.EditorTTFFont; import com.codename1.ui.EncodedImage; import com.codename1.ui.Image; import com.codename1.ui.CodenameOneAccessor; import com.codename1.ui.animations.AnimationAccessor; import com.codename1.ui.animations.AnimationObject; import com.codename1.ui.animations.Motion; import com.codename1.ui.animations.Timeline; import com.codename1.impl.javase.SVG; import com.codename1.ui.plaf.Border; import com.codename1.ui.plaf.Accessor; import com.codename1.ui.plaf.Style; import com.codename1.designer.ResourceEditorApp; import com.codename1.impl.javase.JavaSEPortWithSVGSupport; import com.codename1.ui.Form; import com.codename1.ui.plaf.RoundBorder; import com.codename1.ui.util.xml.Data; import com.codename1.ui.util.xml.Entry; import com.codename1.ui.util.xml.L10n; import com.codename1.ui.util.xml.Lang; import com.codename1.ui.util.xml.LegacyFont; import com.codename1.ui.util.xml.ResourceFileXML; import com.codename1.ui.util.xml.Theme; import com.codename1.ui.util.xml.Ui; import com.codename1.ui.util.xml.Val; import com.codename1.ui.util.xml.comps.ComponentEntry; import java.awt.Frame; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.imageio.ImageIO; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JFrame; 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; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; /** * 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 = 7; private static final short MAJOR_VERSION = 1; private boolean modified; private boolean loadingMode = false; private boolean xmlUI; private boolean ignoreSVGMode; private boolean ignorePNGMode; private EditableResources overrideResource; private File overrideFile; private EditableResources parentResource; private static boolean xmlEnabled; private HashSet themeLoadingErrors; public static void setXMLEnabled(boolean b) { xmlEnabled = b; } public void setOverrideMode(EditableResources overrideResource, File overrideFile) { this.overrideResource = overrideResource; this.overrideFile = overrideFile; if(overrideResource != null) { overrideResource.parentResource = this; overrideResource.onChange = onChange; } } /** * Copies the value from the base to the override resource as a starting point * @param name the name of the resource */ public void overrideResource(String name) { overrideResource.setResource(name, getResourceType(name), getResourceObject(name)); } public boolean isOverrideMode() { return overrideResource != null; } public boolean isOverridenResource(String id) { if(overrideResource == null) { return true; } return overrideResource.getResourceObject(id) != null; } 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); output.writeInt(buffer.getWidth()); output.writeInt(buffer.getHeight()); output.writeBoolean(EncodedImage.create(data).isOpaque()); } /** * @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; updateModified(); if(onChange != null) { onChange.run(); } return selection; } public final String undoAction() { String selection = performUndo(); modified = previouslyModified; updateModified(); 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(); openFile(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; if(overrideResource != null) { overrideResource.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; }*/ if(overrideResource != null) { overrideResource.setResource(id, type, value); 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.codename1.ui.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() { if(overrideResource != null) { overrideResource.clear(); return; } super.clear(); modified = false; for(String name : getResourceNames()) { setResource(name, getResourceType(name), null); } updateModified(); } private byte[] readFile(File parent, String name, boolean normalize) throws IOException { if(normalize) { name = normalizeFileName(name); } File f = new File(parent, name); byte[] data = new byte[(int)f.length()]; DataInputStream fi = new DataInputStream(new FileInputStream(f)); fi.readFully(data); fi.close(); return data; } private byte[] readFileNoNormal(File f) throws IOException { byte[] data = new byte[(int)f.length()]; DataInputStream fi = new DataInputStream(new FileInputStream(f)); fi.readFully(data); fi.close(); return data; } /** * Converts a file name to a normalized name so it can be saved on a windows filesystem */ private String normalizeFileName(String fName) { return fName.replace("*", "_").replace("?", "_").replace(":", "_"); } public void openFileWithXMLSupport(File f) throws IOException { if(xmlEnabled && f.getParentFile().getName().equals("src")) { File res = new File(f.getParentFile().getParentFile(), "res"); if(res.exists()) { File xml = new File(res, f.getName().substring(0, f.getName().length() - 3) + "xml"); if(xml.exists()) { loadingMode = true; com.codename1.ui.Font.clearBitmapCache(); clear(); try { File resDir = new File(res, f.getName().substring(0, f.getName().length() - 4)); // open the XML file... JAXBContext ctx = JAXBContext.newInstance(ResourceFileXML.class); ResourceFileXML xmlData = (ResourceFileXML)ctx.createUnmarshaller().unmarshal(xml); boolean normalize = xmlData.getMajorVersion() > 1 || xmlData.getMinorVersion() > 5; majorVersion = (short)xmlData.getMajorVersion(); minorVersion = (short)xmlData.getMinorVersion(); xmlUI = xmlData.isUseXmlUI(); if(xmlData.getData() != null) { for(Data d : xmlData.getData()) { setResource(d.getName(), MAGIC_DATA, readFile(resDir, d.getName(), normalize)); } } if(xmlData.getLegacyFont() != null) { for(LegacyFont d : xmlData.getLegacyFont()) { String name = d.getName(); if(normalize) { name = normalizeFileName(name); } DataInputStream fi = new DataInputStream(new FileInputStream(new File(resDir, name))); setResource(d.getName(), MAGIC_FONT, loadFont(fi, d.getName(), false)); fi.close(); } } if(xmlData.getImage() != null) { for(com.codename1.ui.util.xml.Image d : xmlData.getImage()) { if(d.getType() == null) { // standara JPG or PNG String name = d.getName(); if(normalize) { name = normalizeFileName(name); } FileInputStream fi = new FileInputStream(new File(resDir, name)); EncodedImage e = EncodedImage.create(fi); fi.close(); setResource(d.getName(), MAGIC_IMAGE, e); continue; } if("svg".equals(d.getType())) { setResource(d.getName(), MAGIC_IMAGE, Image.createSVG(d.getType(), false, readFile(resDir, d.getName(), normalize))); continue; } if("timeline".equals(d.getType())) { String name = d.getName(); if(normalize) { name = normalizeFileName(name); } DataInputStream fi = new DataInputStream(new FileInputStream(new File(resDir, name))); setResource(d.getName(), MAGIC_IMAGE, readTimeline(fi)); fi.close(); continue; } if("multi".equals(d.getType())) { String name = d.getName(); if(normalize) { name = normalizeFileName(name); } File multiImageDir = new File(resDir, name); File hd4k = new File(multiImageDir, "4k.png"); File hd2 = new File(multiImageDir, "2hd.png"); File hd560 = new File(multiImageDir, "560.png"); File hd = new File(multiImageDir, "hd.png"); File veryhigh = new File(multiImageDir, "veryhigh.png"); File high = new File(multiImageDir, "high.png"); File medium = new File(multiImageDir, "medium.png"); File low = new File(multiImageDir, "low.png"); File veryLow = new File(multiImageDir, "verylow.png"); Map<Integer, EncodedImage> images = new HashMap<Integer, EncodedImage>(); if(hd4k.exists()) { images.put(new Integer(Display.DENSITY_4K), EncodedImage.create(readFileNoNormal(hd4k))); } if(hd2.exists()) { images.put(new Integer(Display.DENSITY_2HD), EncodedImage.create(readFileNoNormal(hd2))); } if(hd560.exists()) { images.put(new Integer(Display.DENSITY_560), EncodedImage.create(readFileNoNormal(hd560))); } if(hd.exists()) { images.put(new Integer(Display.DENSITY_HD), EncodedImage.create(readFileNoNormal(hd))); } if(veryhigh.exists()) { images.put(new Integer(Display.DENSITY_VERY_HIGH), EncodedImage.create(readFileNoNormal(veryhigh))); } if(high.exists()) { images.put(new Integer(Display.DENSITY_HIGH), EncodedImage.create(readFileNoNormal(high))); } if(medium.exists()) { images.put(new Integer(Display.DENSITY_MEDIUM), EncodedImage.create(readFileNoNormal(medium))); } if(low.exists()) { images.put(new Integer(Display.DENSITY_LOW), EncodedImage.create(readFileNoNormal(low))); } if(veryLow.exists()) { images.put(new Integer(Display.DENSITY_VERY_LOW), EncodedImage.create(readFileNoNormal(veryLow))); } int[] dpis = new int[images.size()]; EncodedImage[] imageArray = new EncodedImage[images.size()]; int count = 0; for(Map.Entry<Integer, EncodedImage> m : images.entrySet()) { dpis[count] = m.getKey().intValue(); imageArray[count] = m.getValue(); count++; } MultiImage result = new MultiImage(); result.setDpi(dpis); result.setInternalImages(imageArray); setResource(d.getName(), MAGIC_IMAGE, result); continue; } } } if(xmlData.getL10n() != null) { for(L10n d : xmlData.getL10n()) { Hashtable<String, Hashtable<String, String>> l10n = new Hashtable<String, Hashtable<String, String>>(); for(Lang l : d.getLang()) { Hashtable<String, String> language = new Hashtable<String, String>(); if(l != null && l.getEntry() != null) { for(Entry e : l.getEntry()) { language.put(e.getKey(), e.getValue()); } } l10n.put(l.getName(), language); } setResource(d.getName(), MAGIC_L10N, l10n); } } if(xmlData.getTheme() != null) { for(Theme d : xmlData.getTheme()) { Hashtable<String, Object> theme = new Hashtable<String, Object>(); theme.put("uninitialized", Boolean.TRUE); if(d.getVal() != null) { for(Val v : d.getVal()) { String key = v.getKey(); if(key.endsWith("align") || key.endsWith("textDecoration")) { theme.put(key, Integer.valueOf(v.getValue())); continue; } if(key.endsWith(Style.BACKGROUND_TYPE) || key.endsWith(Style.BACKGROUND_ALIGNMENT)) { theme.put(key, Byte.valueOf(v.getValue())); continue; } // padding and or margin type if(key.endsWith("Unit")) { String[] s = v.getValue().split(","); theme.put(key, new byte[] {Byte.parseByte(s[0]), Byte.parseByte(s[1]), Byte.parseByte(s[2]), Byte.parseByte(s[3])}); continue; } theme.put(key, v.getValue()); } } if(d.getBorder() != null) { for(com.codename1.ui.util.xml.Border b : d.getBorder()) { if("empty".equals(b.getType())) { theme.put(b.getKey(), Border.createEmpty()); continue; } if("round".equals(b.getType())) { RoundBorder rb = RoundBorder.create(); rb = rb.color(b.getRoundBorderColor()); rb = rb.rectangle(b.isRectangle()); rb = rb.shadowBlur(b.getShadowBlur()); rb = rb.shadowOpacity(b.getShadowOpacity()); rb = rb.shadowSpread(b.getShadowSpread(), b.isShadowMM()); rb = rb.shadowX(b.getShadowX()); rb = rb.shadowY(b.getShadowY()); rb = rb.stroke(b.getStrokeThickness(), b.isStrokeMM()); rb = rb.strokeColor(b.getStrokeColor()); rb = rb.strokeOpacity(b.getStrokeOpacity()); theme.put(b.getKey(), rb); continue; } if("line".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue())); } else { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue(), b.getColor().intValue())); } continue; } if("rounded".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createRoundBorder(b.getArcW().intValue(), b.getArcH().intValue())); } else { theme.put(b.getKey(), Border.createRoundBorder(b.getArcW().intValue(), b.getArcH().intValue(), b.getColor().intValue())); } continue; } if("etchedRaised".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createEtchedRaised()); } else { theme.put(b.getKey(), Border.createEtchedRaised(b.getColor().intValue(), b.getColorB().intValue())); } continue; } if("etchedLowered".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createEtchedLowered()); } else { theme.put(b.getKey(), Border.createEtchedLowered(b.getColor().intValue(), b.getColorB().intValue())); } continue; } if("bevelLowered".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createBevelLowered()); } else { theme.put(b.getKey(), Border.createBevelLowered(b.getColor().intValue(), b.getColorB().intValue(), b.getColorC().intValue(), b.getColorD().intValue())); } continue; } if("bevelRaised".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createBevelRaised()); } else { theme.put(b.getKey(), Border.createBevelRaised(b.getColor().intValue(), b.getColorB().intValue(), b.getColorC().intValue(), b.getColorD().intValue())); } continue; } if("image".equals(b.getType())) { int imageCount = 2; if(b.getI9() != null) { imageCount = 9; } else { if(b.getI8() != null) { imageCount = 8; } else { if(b.getI3() != null) { imageCount = 3; } } } String[] borderInstance; switch(imageCount) { case 2: borderInstance = new String[] {b.getI1(), b.getI2()}; break; case 3: borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3()}; break; case 8: borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3(), b.getI4(), b.getI5(), b.getI6(), b.getI7(), b.getI8()}; break; default: borderInstance = new String[] {b.getI1(), b.getI2(), b.getI3(), b.getI4(), b.getI5(), b.getI6(), b.getI7(), b.getI8(), b.getI9()}; break; } theme.put(b.getKey(), borderInstance); continue; } if("imageH".equals(b.getType())) { theme.put(b.getKey(), new String[] {"h", b.getI1(), b.getI2(), b.getI3()} ); continue; } if("imageV".equals(b.getType())) { theme.put(b.getKey(), new String[] {"v", b.getI1(), b.getI2(), b.getI3()} ); continue; } } } if(d.getFont() != null) { for(com.codename1.ui.util.xml.Font b : d.getFont()) { if("ttf".equals(b.getType())) { com.codename1.ui.Font system = com.codename1.ui.Font.createSystemFont(b.getFace().intValue(), b.getStyle().intValue(), b.getSize().intValue()); EditorTTFFont t; if(b.getName().startsWith("native:")) { t = new EditorTTFFont(b.getName(), b.getSizeSettings().intValue(), b.getActualSize().floatValue(), system); } else { t = new EditorTTFFont(new File(f.getParentFile(), b.getName()), b.getSizeSettings().intValue(), b.getActualSize().floatValue(), system); } theme.put(b.getKey(), t); continue; } if("system".equals(b.getType())) { com.codename1.ui.Font system = com.codename1.ui.Font.createSystemFont(b.getFace().intValue(), b.getStyle().intValue(), b.getSize().intValue()); theme.put(b.getKey(), system); continue; } // bitmap fonts aren't supported right now } } if(d.getGradient() != null) { for(com.codename1.ui.util.xml.Gradient b : d.getGradient()) { theme.put(b.getKey(), new Object[] { b.getColor1(), b.getColor2(), b.getPosX(), b.getPosY(), b.getRadius() }); } } setResource(d.getName(), MAGIC_THEME, theme); } } // we load the UI last since it might depend on images or other elements in the future if(xmlData.getUi() != null) { if(xmlData.isUseXmlUI()) { ArrayList<ComponentEntry> guiElements = new ArrayList<ComponentEntry>(); // place renderers first final ArrayList<String> renderers = new ArrayList<String>(); for(Ui d : xmlData.getUi()) { JAXBContext componentContext = JAXBContext.newInstance(ComponentEntry.class); File uiFile = new File(resDir, normalizeFileName(d.getName()) + ".ui"); ComponentEntry uiXMLData = (ComponentEntry)componentContext.createUnmarshaller().unmarshal(uiFile); guiElements.add(uiXMLData); uiXMLData.findRendererers(renderers); } Collections.sort(guiElements, new Comparator<ComponentEntry>() { private final ArrayList<String> entries1 = new ArrayList<String>(); private final ArrayList<String> entries2 = new ArrayList<String>(); @Override public int compare(ComponentEntry o1, ComponentEntry o2) { if(renderers.contains(o1.getName())) { return -1; } if(renderers.contains(o2.getName())) { return 1; } entries1.clear(); entries2.clear(); o1.findEmbeddedDependencies(entries1); o2.findEmbeddedDependencies(entries2); if(entries1.size() == 0) { if(entries2.size() == 0) { return 0; } return -1; } else { if(entries2.size() == 0) { return 1; } } for(String e : entries1) { if(e.equals(o2.getName())) { return 1; } } for(String e : entries2) { if(e.equals(o1.getName())) { return -1; } } return 0; } }); for(ComponentEntry uiXMLData : guiElements) { UIBuilderOverride uib = new UIBuilderOverride(); com.codename1.ui.Container cnt = uib.createInstance(uiXMLData, this); // encountered an error loading the component fallback to loading with the binary types if(cnt == null) { for(Ui ui : xmlData.getUi()) { setResource(uiXMLData.getName(), MAGIC_UI, readFile(resDir, ui.getName(), normalize)); } break; } else { byte[] data = UserInterfaceEditor.persistContainer(cnt, this); setResource(uiXMLData.getName(), MAGIC_UI, data); } } } else { for(Ui d : xmlData.getUi()) { setResource(d.getName(), MAGIC_UI, readFile(resDir, d.getName(), normalize)); } } } loadingMode = false; modified = false; updateModified(); // can occure when a resource file is opened via the constructor if(undoQueue != null) { undoQueue.clear(); redoQueue.clear(); } return; } catch(JAXBException err) { err.printStackTrace(); } } } } openFile(new FileInputStream(f)); } private void writeToFile(byte[] data, File f) throws IOException { FileOutputStream o = new FileOutputStream(f); o.write(data); o.close(); } /** * Converts a String to XML body string * @param s the string to convert * @return XMLized string with entity escapes */ public static String xmlize(String s) { s = s.replaceAll("&", "&"); s = s.replaceAll("<", "<"); s = s.replaceAll(">", ">"); s = s.replaceAll("\"", """); int charCount = s.length(); for(int iter = 0 ; iter < charCount ; iter++) { char c = s.charAt(iter); if(c > 127) { // we need to localize the string... StringBuilder b = new StringBuilder(); for(int counter = 0 ; counter < charCount ; counter++) { c = s.charAt(counter); if(c > 127) { b.append("&#x"); b.append(Integer.toHexString(c)); b.append(";"); } else { b.append(c); } } return b.toString(); } } return s; } private void saveXMLFile(File xml, File resourcesDir) throws IOException { // disable override for the duration of the save so stuff from the override doesn't // get into the main resource file File overrideFileBackup = overrideFile; EditableResources overrideResourceBackup = overrideResource; overrideResource = null; overrideFile = null; try { BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(xml), "UTF-8")); String[] resourceNames = getResourceNames(); Arrays.sort(resourceNames, String.CASE_INSENSITIVE_ORDER); bw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"); bw.write("<resource majorVersion=\"" + MAJOR_VERSION + "\" minorVersion=\"" + MINOR_VERSION + "\" useXmlUI=\"" + xmlUI + "\">\n"); for(int iter = 0 ; iter < resourceNames.length ; iter++) { String xResourceName = xmlize(resourceNames[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; } switch(magic) { case MAGIC_IMAGE: Object o = getResourceObject(resourceNames[iter]); if(!(o instanceof MultiImage)) { o = null; } bw.write(" <image name=\"" + xResourceName + "\" "); com.codename1.ui.Image image = getImage(resourceNames[iter]); MultiImage mi = (MultiImage)o; int rType = getImageType(image, mi); switch(rType) { // PNG file case 0xf1: // JPEG File case 0xf2: if(image instanceof EncodedImage) { byte[] data = ((EncodedImage)image).getImageData(); writeToFile(data, new File(resourcesDir, normalizeFileName(resourceNames[iter]))); } else { FileOutputStream fo = new FileOutputStream(new File(resourcesDir, normalizeFileName(resourceNames[iter]))); 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()); ImageIO.write(buffer, "png", fo); fo.close(); } break; // SVG case 0xf5: // multiimage with SVG case 0xf7: SVG s = (SVG)image.getSVGDocument(); writeToFile(s.getSvgData(), new File(resourcesDir, normalizeFileName(resourceNames[iter]))); if(s.getBaseURL() != null && s.getBaseURL().length() > 0) { bw.write("baseUrl=\"" + s.getBaseURL() + "\" "); } bw.write("type=\"svg\" "); break; case 0xF6: File multiImageDir = new File(resourcesDir, normalizeFileName(resourceNames[iter])); multiImageDir.mkdirs(); for(int imageIter = 0 ; imageIter < mi.getDpi().length ; imageIter++) { File f = null; switch(mi.getDpi()[imageIter]) { case Display.DENSITY_4K: f = new File(multiImageDir, "4k.png"); break; case Display.DENSITY_2HD: f = new File(multiImageDir, "2hd.png"); break; case Display.DENSITY_560: f = new File(multiImageDir, "560.png"); break; case Display.DENSITY_HD: f = new File(multiImageDir, "hd.png"); break; case Display.DENSITY_VERY_HIGH: f = new File(multiImageDir, "veryhigh.png"); break; case Display.DENSITY_HIGH: f = new File(multiImageDir, "high.png"); break; case Display.DENSITY_MEDIUM: f = new File(multiImageDir, "medium.png"); break; case Display.DENSITY_LOW: f = new File(multiImageDir, "low.png"); break; case Display.DENSITY_VERY_LOW: f = new File(multiImageDir, "verylow.png"); break; } writeToFile(mi.getInternalImages()[imageIter].getImageData(), f); } bw.write("type=\"multi\" "); break; // Timeline case MAGIC_TIMELINE: File timeline = new File(resourcesDir, normalizeFileName(resourceNames[iter])); DataOutputStream timelineOut = new DataOutputStream(new FileOutputStream(timeline)); writeTimeline(timelineOut, (Timeline)image); timelineOut.close(); bw.write("type=\"timeline\" "); break; // Fail this is the wrong data type default: throw new IOException("Illegal type while creating image: " + Integer.toHexString(rType)); } bw.write(" />\n"); continue; case MAGIC_THEME: Hashtable<String, Object> theme = getTheme(resourceNames[iter]); theme.remove("name"); bw.write(" <theme name=\"" + xResourceName + "\">\n"); ArrayList<String> setOfKeys = new ArrayList<String>(theme.keySet()); Collections.sort(setOfKeys); for(String key : setOfKeys) { if(key.startsWith("@")) { if(key.endsWith("Image")) { bw.write(" <val key=\"" + key + "\" value=\"" + findId(theme.get(key), true) + "\" />\n"); } else { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); } continue; } // if this is a simple numeric value if(key.endsWith("Color")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } if(key.endsWith("align") || key.endsWith("textDecoration")) { bw.write(" <val key=\"" + key + "\" value=\"" + ((Number)theme.get(key)).shortValue() + "\" />\n"); continue; } // if this is a short numeric value if(key.endsWith("transparency")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } if(key.endsWith("opacity")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } // if this is a padding or margin then we will have the 4 values as bytes if(key.endsWith("padding") || key.endsWith("margin")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } // padding and or margin type if(key.endsWith("Unit")) { byte[] b = (byte[])theme.get(key); bw.write(" <val key=\"" + key + "\" value=\"" + b[0] + "," + b[1] + "," + b[2] + "," + b[3] + "\" />\n"); continue; } if(key.endsWith("border")) { Border border = (Border)theme.get(key); if(border instanceof RoundBorder) { RoundBorder rb = (RoundBorder)border; bw.write(" <border key=\"" + key + "\" type=\"round\" " + "roundBorderColor=\"" + rb.getColor()+ "\" " + "opacity=\"" + rb.getOpacity() + "\" " + "strokeColor=\"" + rb.getStrokeColor()+ "\" " + "strokeOpacity=\"" + rb.getStrokeOpacity()+ "\" " + "strokeThickness=\"" + rb.getStrokeThickness()+ "\" " + "strokeMM=\"" + rb.isStrokeMM()+ "\" " + "shadowSpread=\"" + rb.getShadowSpread()+ "\" " + "shadowOpacity=\"" + rb.getShadowOpacity()+ "\" " + "shadowX=\"" + rb.getShadowX()+ "\" " + "shadowY=\"" + rb.getShadowY()+ "\" " + "shadowBlur=\"" + rb.getShadowBlur()+ "\" " + "shadowMM=\"" + rb.isShadowMM()+ "\" " + "rectangle=\"" + rb.isRectangle()+ "\" />\n"); continue; } int type = Accessor.getType(border); switch(type) { case BORDER_TYPE_EMPTY: bw.write(" <border key=\"" + key + "\" type=\"empty\" />\n"); continue; case BORDER_TYPE_LINE: // use theme colors? if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"line\" " + "thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"line\" " + "thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" />\n"); } continue; case BORDER_TYPE_ROUNDED: if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"rounded\" " + "thickness=\"" + Accessor.getThickness(border) + "\" arcW=\"" + Accessor.getArcWidth(border) + "\" arcH=\"" + Accessor.getArcHeight(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"rounded\" " + "thickness=\"" + Accessor.getThickness(border) + "\" arcW=\"" + Accessor.getArcWidth(border) + "\" arcH=\"" + Accessor.getArcHeight(border) + "\" color=\"" + Accessor.getColorA(border) + "\" />\n"); } continue; case BORDER_TYPE_ETCHED_RAISED: // use theme colors? if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"etchedRaised\" " + "thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"etchedRaised\" " + "thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" colorB=\"" + Accessor.getColorB(border) + "\" />\n"); } continue; case BORDER_TYPE_ETCHED_LOWERED: // use theme colors? if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"etchedLowered\" " + "thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"etchedLowered\" " + "thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" colorB=\"" + Accessor.getColorB(border) + "\" />\n"); } continue; case BORDER_TYPE_BEVEL_LOWERED: // use theme colors? if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"bevelLowered\" " + "thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"bevelLowered\" " + "thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" colorB=\"" + Accessor.getColorB(border) + "\" colorC=\"" + Accessor.getColorC(border) + "\" colorD=\"" + Accessor.getColorD(border) + "\" />\n"); } continue; case BORDER_TYPE_BEVEL_RAISED: if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"bevelRaised\" " + "thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"bevelRaised\" " + "thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" colorB=\"" + Accessor.getColorB(border) + "\" colorC=\"" + Accessor.getColorC(border) + "\" colorD=\"" + Accessor.getColorD(border) + "\" />\n"); } continue; //case BORDER_TYPE_IMAGE_SCALED: case BORDER_TYPE_IMAGE: { Image[] images = Accessor.getImages(border); int resourceCount = 0; for(int counter = 0 ; counter < images.length ; counter++) { if(images[counter] != null && findId(images[counter], true) != null) { resourceCount++; } } if(resourceCount != 2 && resourceCount != 3 && resourceCount != 8 && resourceCount != 9) { System.out.println("Odd resource count for image border: " + resourceCount); resourceCount = 2; } switch(resourceCount) { case 2: bw.write(" <border key=\"" + key + "\" type=\"image\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[4], true) + "\" />\n"); break; case 3: bw.write(" <border key=\"" + key + "\" type=\"image\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[4], true) + "\" " + "i3=\"" + findId(images[8], true) + "\" />\n"); break; case 8: bw.write(" <border key=\"" + key + "\" type=\"image\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[1], true) + "\" " + "i3=\"" + findId(images[2], true) + "\" " + "i4=\"" + findId(images[3], true) + "\" " + "i5=\"" + findId(images[4], true) + "\" " + "i6=\"" + findId(images[5], true) + "\" " + "i7=\"" + findId(images[6], true) + "\" " + "i8=\"" + findId(images[7], true) + "\" />\n"); break; case 9: bw.write(" <border key=\"" + key + "\" type=\"image\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[1], true) + "\" " + "i3=\"" + findId(images[2], true) + "\" " + "i4=\"" + findId(images[3], true) + "\" " + "i5=\"" + findId(images[4], true) + "\" " + "i6=\"" + findId(images[5], true) + "\" " + "i7=\"" + findId(images[6], true) + "\" " + "i8=\"" + findId(images[7], true) + "\" " + "i9=\"" + findId(images[8], true) + "\" />\n"); break; } continue; } case BORDER_TYPE_IMAGE_HORIZONTAL: { Image[] images = Accessor.getImages(border); bw.write(" <border key=\"" + key + "\" type=\"imageH\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[1], true) + "\" " + "i3=\"" + findId(images[2], true) + "\" />\n"); continue; } case BORDER_TYPE_IMAGE_VERTICAL: { Image[] images = Accessor.getImages(border); bw.write(" <border key=\"" + key + "\" type=\"imageV\" " + "i1=\"" + findId(images[0], true) + "\" " + "i2=\"" + findId(images[1], true) + "\" " + "i3=\"" + findId(images[2], true) + "\" />\n"); continue; } } continue; } // if this is a font if(key.endsWith("font")) { com.codename1.ui.Font f = (com.codename1.ui.Font)theme.get(key); // is this a new font? boolean newFont = f instanceof EditorFont; if(newFont) { bw.write(" <font key=\"" + key + "\" type=\"named\" " + "name=\"" + findId(f) + "\" />\n"); } else { if(f instanceof EditorTTFFont && (((EditorTTFFont)f).getFontFile() != null || ((EditorTTFFont)f).getNativeFontName() != null)) { EditorTTFFont ed = (EditorTTFFont)f; String fname; String ffName; if(((EditorTTFFont)f).getNativeFontName() != null) { fname = ((EditorTTFFont)f).getNativeFontName(); ffName = fname; } else { fname = ed.getFontFile().getName(); ffName = ((java.awt.Font)ed.getNativeFont()).getPSName(); } bw.write(" <font key=\"" + key + "\" type=\"ttf\" " + "face=\"" + f.getFace() + "\" " + "style=\"" + f.getStyle() + "\" " + "size=\"" + f.getSize() + "\" " + "name=\"" + fname + "\" " + "family=\"" + ffName+ "\" " + "sizeSettings=\"" + ed.getSizeSetting() + "\" " + "actualSize=\"" + ed.getActualSize() + "\" />\n"); } else { bw.write(" <font key=\"" + key + "\" type=\"system\" " + "face=\"" + f.getFace() + "\" " + "style=\"" + f.getStyle() + "\" " + "size=\"" + f.getSize() + "\" />\n"); } } continue; } // if this is a background image if(key.endsWith("bgImage")) { bw.write(" <val key=\"" + key + "\" value=\"" + findId(theme.get(key), true) + "\" />\n"); continue; } if(key.endsWith("scaledImage")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } if(key.endsWith("derive")) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } // if this is a background gradient if(key.endsWith("bgGradient")) { Object[] gradient = (Object[])theme.get(key); bw.write(" <gradient key=\"" + key + "\" color1=\"" + gradient[0] + "\"" + " color2=\"" + gradient[1] + "\"" + " posX=\"" + gradient[2] + "\"" + " posY=\"" + gradient[3] + "\"" + " radius=\"" + gradient[4] + "\" />\n"); continue; } if(key.endsWith(Style.BACKGROUND_TYPE) || key.endsWith(Style.BACKGROUND_ALIGNMENT)) { bw.write(" <val key=\"" + key + "\" value=\"" + theme.get(key) + "\" />\n"); continue; } // thow an exception no idea what this is throw new IOException("Error while trying to read theme property: " + key); } bw.write(" </theme>\n"); continue; case MAGIC_FONT: File legacyFont = new File(resourcesDir, normalizeFileName(resourceNames[iter])); DataOutputStream legacyFontOut = new DataOutputStream(new FileOutputStream(legacyFont)); saveFont(legacyFontOut, false, resourceNames[iter]); legacyFontOut.close(); bw.write(" <legacyFont name=\"" + xResourceName + "\" />\n"); continue; case MAGIC_DATA: { File dataFile = new File(resourcesDir, normalizeFileName(resourceNames[iter])); DataOutputStream dataFileOut = new DataOutputStream(new FileOutputStream(dataFile)); 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(); dataFileOut.write(data); dataFileOut.close(); bw.write(" <data name=\"" + xResourceName + "\" />\n"); continue; } case MAGIC_UI: { File uiXML = new File(resourcesDir, resourceNames[iter] + ".ui"); UIBuilderOverride u = new UIBuilderOverride(); com.codename1.ui.Container cnt = u.createContainer(this, resourceNames[iter]); FileOutputStream fos = new FileOutputStream(uiXML); writeUIXml(cnt, fos); fos.close(); File ui = new File(resourcesDir, resourceNames[iter]); DataOutputStream uiOut = new DataOutputStream(new FileOutputStream(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(); uiOut.write(data); uiOut.close(); bw.write(" <ui name=\"" + xResourceName + "\" />\n"); continue; } case MAGIC_L10N: // we are getting the theme which allows us to acces the l10n data bw.write(" <l10n name=\"" + xResourceName + "\">\n"); Hashtable<String, Object> l10n = getTheme(resourceNames[iter]); for(String locale : l10n.keySet()) { bw.write(" <lang name=\"" + locale + "\">\n"); Hashtable<String, String> current = (Hashtable<String, String>)l10n.get(locale); for(String key : current.keySet()) { String val = current.get(key); bw.write(" <entry key=\"" + xmlize(key) + "\" value=\"" + xmlize(val) + "\" />\n"); } bw.write(" </lang>\n"); } bw.write(" </l10n>\n"); continue; default: throw new IOException("Corrupt theme file unrecognized magic number: " + Integer.toHexString(magic & 0xff)); } } bw.write("</resource>\n"); bw.close(); } finally { overrideFile = overrideFileBackup; overrideResource = overrideResourceBackup; } } private void writeUIXml(com.codename1.ui.Container cnt, FileOutputStream dest) throws IOException { Writer w = new OutputStreamWriter(dest, "UTF-8"); w.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n"); StringBuilder bld = new StringBuilder(); UserInterfaceEditor.persistToXML(cnt, cnt, bld, this, ""); w.write(bld.toString()); w.flush(); } public void saveXML(File resFile) throws IOException { if(xmlEnabled && resFile.getParentFile().getName().equals("src")) { if(new File(resFile.getParentFile().getParentFile(), "codenameone_settings.properties").exists()) { File res = new File(resFile.getParentFile().getParentFile(), "res"); res.mkdir(); String filename = resFile.getName(); filename = filename.substring(0, filename.length() - 4); File xml = new File(res, filename + ".xml"); File resources = new File(res, filename); resources.mkdir(); if(overrideFile != null) { String name = overrideFile.getName(); name = name.substring(0, name.length() - 4); File overrideDir = new File(resources, name); overrideDir.mkdirs(); overrideResource.saveXMLFile(new File(resources, name + ".xml"), overrideDir); } saveXMLFile(xml, resources); } } } @Override public void openFile(final InputStream input) throws IOException { loadingMode = true; com.codename1.ui.Font.clearBitmapCache(); super.openFile(input); loadingMode = false; modified = false; updateModified(); // can occure when a resource file is opened via the constructor if(undoQueue != null) { undoQueue.clear(); redoQueue.clear(); } ThemeEditor.resetThemeLoaded(); } /** * Undo the last operation */ public String undo() { if(overrideResource != null) { return overrideResource.undo(); } if(isUndoable()) { UndoableEdit edit = undoQueue.remove(undoQueue.size() - 1); redoQueue.add(edit); return edit.undoAction(); } return null; } public boolean isUndoable() { if(overrideResource != null) { return overrideResource.isUndoable(); } return !undoQueue.isEmpty(); } public boolean isRedoable() { if(overrideResource != null) { return overrideResource.isRedoable(); } return !redoQueue.isEmpty(); } public boolean containsResource(String res) { if(overrideResource != null) { return overrideResource.containsResource(res); } return getResourceObject(res) != null; } public String redo() { if(overrideResource != null) { return overrideResource.redo(); } if(isRedoable()) { UndoableEdit edit = redoQueue.remove(redoQueue.size() - 1); undoQueue.add(edit); return edit.doAction(); } return null; } public boolean isModified() { if(overrideResource != null) { return overrideResource.isModified() || modified; } return modified; } private void updateModified() { if(overrideResource != null) { overrideResource.updateModified(); return; } if(ResourceEditorApp.IS_MAC) { for(java.awt.Window w : java.awt.Frame.getWindows()) { if(w instanceof JFrame) { if(modified) { ((JFrame)w).getRootPane().putClientProperty("Window.documentModified", Boolean.TRUE); } else { ((JFrame)w).getRootPane().putClientProperty("Window.documentModified", Boolean.FALSE); } } } } } public void setModified() { if(overrideResource != null) { overrideResource.setModified(); return; } this.modified = true; updateModified(); } public byte[] getDataByteArray(String id) { if(overrideResource != null) { byte[] d = overrideResource.getDataByteArray(id); if(d != null) { return d; } } Object o = getResourceObject(id); return ((byte[])o); } public long getDataSize(String id) { if(overrideResource != null) { long i = overrideResource.getDataSize(id); if(i > -1) { return i; } } 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 { if(overrideResource != null) { Hashtable h = overrideResource.loadTheme(id, newerVersion); if(h != null) { return h; } } 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 { if(overrideFile != null) { overrideResource.save(new FileOutputStream(overrideFile)); } // disable override for the duration of the save so stuff from the override doesn't // get into the main resource file File overrideFileBackup = overrideFile; EditableResources overrideResourceBackup = overrideResource; overrideResource = null; overrideFile = null; try { 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; updateModified(); undoQueue.clear(); redoQueue.clear(); } finally { overrideFile = overrideFileBackup; overrideResource = overrideResourceBackup; } } 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; } } } } /** * Returns the data resource from the file * * @param id name of the data resource * @return newly created input stream that allows reading the data of the resource */ public InputStream getData(String id) { if(overrideResource != null) { InputStream h = overrideResource.getData(id); if(h != null) { return h; } } return super.getData(id); } /** * Returns the ui resource from the file * * @param id name of the ui resource * @return newly created input stream that allows reading the ui of the resource */ InputStream getUi(String id) { if(overrideResource != null) { InputStream h = overrideResource.getUi(id); if(h != null) { return h; } } return super.getUi(id); } public Hashtable<String, Object> getTheme(String id) { try { if (overrideResource != null) { Hashtable h = overrideResource.getTheme(id); if (h != null) { return h; } } if (loadingMode) { return new Hashtable(); } Hashtable h = super.getTheme(id); if (h != null) { removeMultiConstants(h); h.remove("name"); } return h; } catch (Exception e) { if (themeLoadingErrors == null) { themeLoadingErrors = new HashSet(); } if (!themeLoadingErrors.contains(e.getMessage())) { JOptionPane.showMessageDialog(java.awt.Frame.getFrames()[0], e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); themeLoadingErrors.add(e.getMessage()); } } return new Hashtable<String, Object>(); } private String[] mergeArrays(String[] a, String[] b) { List<String> l = new ArrayList<String>(Arrays.asList(a)); for(String s : b) { if(!l.contains(s)) { l.add(s); } } String[] res = new String[l.size()]; l.toArray(res); return res; } public String[] getResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getResourceNames(), super.getResourceNames()); } return super.getResourceNames(); } public String[] getL10NResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getL10NResourceNames(), super.getL10NResourceNames()); } return super.getL10NResourceNames(); } public String[] getFontResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getFontResourceNames(), super.getFontResourceNames()); } return super.getFontResourceNames(); } public String[] getThemeResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getThemeResourceNames(), super.getThemeResourceNames()); } return super.getThemeResourceNames(); } public String[] getImageResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getImageResourceNames(), super.getImageResourceNames()); } return super.getImageResourceNames(); } public String[] getUIResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getUIResourceNames(), super.getUIResourceNames()); } return super.getUIResourceNames(); } public String[] getDataResourceNames() { if(overrideResource != null) { return mergeArrays(overrideResource.getDataResourceNames(), super.getDataResourceNames()); } return super.getDataResourceNames(); } com.codename1.ui.Font createTrueTypeFont(com.codename1.ui.Font f, String fontName, String fileName, float fontSize, int sizeSetting) { // workaround for NPE in case of people doing stupid things like moving the res file. if(ResourceEditorView.getLoadedFile() == null && !fileName.startsWith("native:")) { return f; } if(fileName.startsWith("native:")) { return new EditorTTFFont(fileName, sizeSetting, fontSize, f); } File fontFile = new File(ResourceEditorView.getLoadedFile().getParentFile(), fileName); if(fontFile.exists()) { return new EditorTTFFont(fontFile, sizeSetting, fontSize, f); } return f; } 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), true)); } 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 (key.endsWith("opacity")) { output.writeInt(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.codename1.ui.Font f = (com.codename1.ui.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()); if(f instanceof EditorTTFFont && (((EditorTTFFont)f).getFontFile() != null || ((EditorTTFFont)f).getNativeFontName() != null)) { output.writeBoolean(true); EditorTTFFont ed = (EditorTTFFont)f; if(ed.getNativeFontName() != null) { output.writeUTF(ed.getNativeFontName()); output.writeUTF(ed.getNativeFontName()); } else { output.writeUTF(ed.getFontFile().getName()); output.writeUTF(((java.awt.Font)ed.getNativeFont()).getPSName()); } output.writeInt(ed.getSizeSetting()); output.writeFloat(ed.getActualSize()); } else { output.writeBoolean(false); } } continue; } // if this is a background image if(key.endsWith("bgImage")) { String imageId = findId(theme.get(key), true); if(imageId == null) { imageId = ""; } 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 { if(border instanceof RoundBorder) { output.writeShort(0xff12); RoundBorder rb = (RoundBorder)border; output.writeBoolean(rb.isRectangle()); output.writeInt(rb.getColor()); output.writeInt(rb.getOpacity()); output.writeFloat(rb.getStrokeThickness()); output.writeBoolean(rb.isStrokeMM()); output.writeInt(rb.getStrokeColor()); output.writeInt(rb.getStrokeOpacity()); output.writeFloat(rb.getShadowBlur()); output.writeInt(rb.getShadowOpacity()); output.writeInt(rb.getShadowSpread()); output.writeBoolean(rb.isShadowMM()); output.writeFloat(rb.getShadowX()); output.writeFloat(rb.getShadowY()); return; } 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; case BORDER_TYPE_IMAGE_SCALED: output.writeShort(0xff11); writeImageBorder(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], true)); output.writeUTF(findId(images[1], true)); output.writeUTF(findId(images[2], true)); } 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], true) != 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], true)); output.writeUTF(findId(images[4], true)); break; case 3: output.writeUTF(findId(images[0], true)); output.writeUTF(findId(images[4], true)); output.writeUTF(findId(images[8], true)); break; case 8: for(int iter = 0 ; iter < 8 ; iter++) { output.writeUTF(findId(images[iter], true)); } break; case 9: for(int iter = 0 ; iter < 9 ; iter++) { output.writeUTF(findId(images[iter], true)); } break; } } com.codename1.ui.Font loadFont(DataInputStream input, String id, boolean packed) throws IOException { if(getMinorVersion() == 0 && getMajorVersion() == 0) { com.codename1.ui.Font bitmapFont = super.loadFont(input, id, packed); return new EditorFont(com.codename1.ui.Font.createSystemFont(com.codename1.ui.Font.FACE_SYSTEM, com.codename1.ui.Font.STYLE_PLAIN, com.codename1.ui.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.codename1.ui.Font systemFont = com.codename1.ui.Font.createSystemFont(fallback & 0x60, fallback & 7, fallback & 0x18); com.codename1.ui.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.codename1.ui.Font bitmapFont = f.getBitmapFont(); com.codename1.ui.Image cache = CodenameOneAccessor.getImage(bitmapFont); int[] imageArray = cache.getRGB(); for(int iter = 0 ; iter < imageArray.length ; iter++) { imageArray[iter] = (imageArray[iter] >> 8) & 0xff0000; } saveImage(output, com.codename1.ui.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 = CodenameOneAccessor.getOffsets(bitmapFont); int[] charWidth = CodenameOneAccessor.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) { if(overrideResource != null) { Object o = overrideResource.getResourceObject(res); if(o != null) { return o; } } return super.getResourceObject(res); } public String findId(Object value) { return findId(value, false); } public String findId(Object value, boolean checkParent) { if(overrideResource != null) { String o = overrideResource.findId(value, false); if(o != null) { return o; } } 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; } } if(checkParent && parentResource != null) { return parentResource.findId(value, false); } return null; } private int getImageType(com.codename1.ui.Image image, MultiImage mi) { if(mi != null) { return 0xF6; } if(image instanceof Timeline) { return MAGIC_TIMELINE; } if(image.isSVG()) { return 0xf7; } // png image return 0xf1; } private void saveImage(DataOutputStream output, com.codename1.ui.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); output.writeInt(image.getWidth()); output.writeInt(image.getHeight()); output.writeBoolean(image.isOpaque()); } else { writeImageAsPNG(image,type, output); } 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.codename1.ui.EncodedImage toEncodedImage(Image image) throws IOException { if(image instanceof EncodedImage) { return (com.codename1.ui.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.codename1.ui.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.codename1.ui.Display.DENSITY_MEDIUM}; mi.internalImages = new com.codename1.ui.EncodedImage[] {toEncodedImage(image)}; return mi; } mi.internalImages = new com.codename1.ui.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.codename1.ui.Image createSVG(boolean animated, byte[] data) throws IOException { com.codename1.ui.Image img = super.createSVG(animated, data); SVG s = (SVG)img.getSVGDocument(); if(s != null) { 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.codename1.ui.EncodedImage resultImage = null; int dpi = com.codename1.ui.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.codename1.ui.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.codename1.ui.EncodedImage tmp = com.codename1.ui.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; } @Override Image createImage(DataInputStream input) throws IOException { Image i = super.createImage(input); if(i.isSVG()) { SVG s = (SVG)i.getSVGDocument(); s.setRatioH(ratioH); s.setRatioW(ratioW); } return i; } private com.codename1.ui.Image getImageNoRecursion(String id) { Object o = getResourceObject(id); if(o instanceof MultiImage) { MultiImage m = (MultiImage)o; return m.getBest(); } return (com.codename1.ui.Image)o; } public com.codename1.ui.Image getImage(String id) { if(overrideResource != null) { com.codename1.ui.Image o = overrideResource.getImage(id); if(o != null) { return o; } } Object o = getImageNoRecursion(id); if(o == null && parentResource != null) { return parentResource.getImageNoRecursion(id); } return (com.codename1.ui.Image)o; } private void pushUndoable(UndoableEdit edit) { undoQueue.add(edit); edit.doAction(); redoQueue.clear(); } public void setImage(final String name, final com.codename1.ui.Image value) { if(overrideResource != null) { overrideResource.setImage(name, value); return; } // we need to replace the image in all the themes... final com.codename1.ui.Image oldValue = getImage(name); byte type; 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 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, (String)value); return localeName; } } @Override protected String performUndo() { if(oldValue == null) { getL10N(localeName, locale).remove(key); } else { getL10N(localeName, locale).put(key, (String)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]; Hashtable th = getTheme(themeName); if(th == null) { return; } for(int iter = 0 ; iter < keys.length ; iter++) { oldValues[iter] = th.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.codename1.ui.Font value) { // we need to replace the font in all the themes... final com.codename1.ui.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() { EditableResources ed = (EditableResources)JavaSEPortWithSVGSupport.getNativeTheme(); if(ed != null && ed != this) { ed.refreshThemeMultiImages(); } for(String themeName : getThemeResourceNames()) { Hashtable theme = getTheme(themeName); for(Object key : theme.keySet()) { Object currentValue = theme.get(key); if(currentValue instanceof EditorTTFFont) { ((EditorTTFFont)currentValue).refresh(); } if(currentValue instanceof com.codename1.ui.Image) { String id = findId(currentValue); if(isMultiImage(id)) { theme.put(key, ((MultiImage)getResourceObject(id)).getBest()); } } if(currentValue instanceof com.codename1.ui.plaf.Border) { com.codename1.ui.Image[] images = Accessor.getImages((com.codename1.ui.plaf.Border)currentValue); if(images != null) { for(int iter = 0 ; iter < images.length ; iter++) { com.codename1.ui.Image img = images[iter]; if(img != null) { String id = findId(img); if(id == null) { JOptionPane.showMessageDialog(java.awt.Frame.getFrames()[0], "Missing image from border: " + key, "Error", JOptionPane.ERROR_MESSAGE); continue; } if(isMultiImage(id)) { images[iter] = ((MultiImage)getResourceObject(id)).getBest(); } } } } } } } com.codename1.ui.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.codename1.ui.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.codename1.ui.Image current = getImage(image); if(current instanceof com.codename1.ui.animations.Timeline) { com.codename1.ui.animations.Timeline time = (com.codename1.ui.animations.Timeline)current; for(int iter = 0 ; iter < time.getAnimationCount() ; iter++) { com.codename1.ui.animations.AnimationObject o = time.getAnimation(iter); if(AnimationAccessor.getImage(o) == oldValue) { AnimationAccessor.setImage(o, (com.codename1.ui.Image)value); } } } } } } public void remove(final String name) { if(overrideResource != null) { overrideResource.remove(name); return; } 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 String getResourceTypeAsString(String name) { if(name == null) { return ""; } byte t = getResourceType(name); switch(t) { case MAGIC_SVG: return "SVG"; case MAGIC_TIMELINE: return "Timeline"; case MAGIC_THEME: return "Theme"; case MAGIC_FONT: return "Font"; case MAGIC_IMAGE: return "Image"; case MAGIC_L10N: return "L10n"; case MAGIC_DATA: return "Data"; case MAGIC_UI: return "GUI"; } return "Unknown: " + Integer.toHexString(t & 0xff); } byte getResourceType(String name) { if(overrideResource != null && isOverridenResource(name)) { return overrideResource.getResourceType(name); } return super.getResourceType(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, view); tl.setImage((MultiImage)getResourceObject(name)); return tl; } if(i instanceof Timeline) { TimelineEditor tl = new TimelineEditor(this, name, view); tl.setImage((Timeline)i); return tl; } 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, view); tl.setImage((Timeline)getImage(name)); return tl; 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_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.codename1.ui.EncodedImage[] internalImages; private int[] dpi; /** * @return the internalImages */ public com.codename1.ui.EncodedImage[] getInternalImages() { return internalImages; } /** * @param internalImages the internalImages to set */ public void setInternalImages(com.codename1.ui.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.codename1.ui.EncodedImage getBest() { if(internalImages.length == 0) { return null; } int dpiVal = com.codename1.ui.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]; } } }