/* * 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.CSSBorder; import com.codename1.ui.plaf.RoundBorder; import com.codename1.ui.plaf.RoundRectBorder; 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 com.codename1.util.StringUtil; 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 = 11; 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.opacity(b.getOpacity()); rb = rb.color(b.getRoundBorderColor()); rb = rb.rectangle(b.isRectangle()); rb = rb.shadowBlur(b.getShadowBlur()); rb = rb.shadowOpacity(b.getShadowOpacity()); rb = rb.shadowSpread((int)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("css".equals(b.getType())) { CSSBorder cb = new CSSBorder(this, b.getCSS()); theme.put(b.getKey(), cb); continue; } if("roundRect".equals(b.getType())) { RoundRectBorder rb = RoundRectBorder.create(); rb = rb.shadowBlur(b.getShadowBlur()); rb = rb.shadowOpacity(b.getShadowOpacity()); rb = rb.shadowSpread(b.getShadowSpread()); 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()); rb = rb.bezierCorners(b.isBezierCorners()); if (b.isTopOnlyMode()) { rb.topOnlyMode(true); } else if (b.isBottomOnlyMode()) { rb.bottomOnlyMode(true); } rb = rb.cornerRadius(b.getCornerRadius()); theme.put(b.getKey(), rb); continue; } if("line".equals(b.getType())) { if(b.getColor() == null) { if(b.isMillimeters()) { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().floatValue())); } else { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue())); } } else { if(b.isMillimeters()) { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().floatValue(), b.getColor().intValue())); } else { theme.put(b.getKey(), Border.createLineBorder(b.getThickness().intValue(), b.getColor().intValue())); } } continue; } if("underline".equals(b.getType())) { if(b.getColor() == null) { theme.put(b.getKey(), Border.createUndelineBorder(b.getThickness().intValue())); } else { theme.put(b.getKey(), Border.createUnderlineBorder(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; } if (border instanceof CSSBorder) { bw.write(" <border key=\"" + key + "\" type=\"css\" " + "css=\"" + encodeXML(((CSSBorder)border).toCSSString())+ "\"/>\n"); continue; } if(border instanceof RoundRectBorder) { RoundRectBorder rb = (RoundRectBorder)border; bw.write(" <border key=\"" + key + "\" type=\"roundRect\" " + "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()+ "\" " + "topOnlyMode=\"" + rb.isTopOnlyMode()+ "\" " + "bottomOnlyMode=\"" + rb.isBottomOnlyMode()+ "\" " + "cornerRadius=\"" + rb.getCornerRadius()+ "\" " + "bezierCorners=\"" + rb.isBezierCorners()+ "\" />\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\" millimeters=\"" + Accessor.isMillimeters(border) + "\" thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"line\" millimeters=\"" + Accessor.isMillimeters(border) + "\" thickness=\"" + Accessor.getThickness(border) + "\" color=\"" + Accessor.getColorA(border) + "\" />\n"); } continue; case BORDER_TYPE_UNDERLINE: // use theme colors? if(Accessor.isThemeColors(border)) { bw.write(" <border key=\"" + key + "\" type=\"underline\" millimeters=\"" + Accessor.isMillimeters(border) + "\" thickness=\"" + Accessor.getThickness(border) + "\" />\n"); } else { bw.write(" <border key=\"" + key + "\" type=\"underline\" millimeters=\"" + Accessor.isMillimeters(border) +"\" 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(