mirror of
https://github.com/arduino/Arduino.git
synced 2024-12-01 12:24:14 +01:00
Support selectable, user-defined themes contained in zip files
This commit is contained in:
parent
78ef37ef08
commit
794ef806f1
@ -38,6 +38,7 @@ import processing.app.Editor;
|
|||||||
import processing.app.I18n;
|
import processing.app.I18n;
|
||||||
import processing.app.PreferencesData;
|
import processing.app.PreferencesData;
|
||||||
import processing.app.Theme;
|
import processing.app.Theme;
|
||||||
|
import processing.app.Theme.ZippedTheme;
|
||||||
import processing.app.helpers.FileUtils;
|
import processing.app.helpers.FileUtils;
|
||||||
import processing.app.legacy.PApplet;
|
import processing.app.legacy.PApplet;
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ import java.awt.*;
|
|||||||
import java.awt.event.ItemEvent;
|
import java.awt.event.ItemEvent;
|
||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import static processing.app.I18n.tr;
|
import static processing.app.I18n.tr;
|
||||||
@ -159,6 +161,9 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
autoProxyUsername = new javax.swing.JTextField();
|
autoProxyUsername = new javax.swing.JTextField();
|
||||||
autoProxyPassword = new javax.swing.JPasswordField();
|
autoProxyPassword = new javax.swing.JPasswordField();
|
||||||
autoProxyPasswordLabel = new javax.swing.JLabel();
|
autoProxyPasswordLabel = new javax.swing.JLabel();
|
||||||
|
comboThemeLabel = new javax.swing.JLabel();
|
||||||
|
comboTheme = new JComboBox();
|
||||||
|
requiresRestartLabel2 = new javax.swing.JLabel();
|
||||||
javax.swing.JPanel jPanel3 = new javax.swing.JPanel();
|
javax.swing.JPanel jPanel3 = new javax.swing.JPanel();
|
||||||
javax.swing.JButton okButton = new javax.swing.JButton();
|
javax.swing.JButton okButton = new javax.swing.JButton();
|
||||||
javax.swing.JButton cancelButton = new javax.swing.JButton();
|
javax.swing.JButton cancelButton = new javax.swing.JButton();
|
||||||
@ -303,6 +308,12 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
|
|
||||||
jLabel3.setText("%");
|
jLabel3.setText("%");
|
||||||
|
|
||||||
|
comboThemeLabel.setText(tr("Theme: "));
|
||||||
|
|
||||||
|
comboTheme.getAccessibleContext().setAccessibleName("Theme (requires restart of Arduino)");
|
||||||
|
|
||||||
|
requiresRestartLabel2.setText(tr(" (requires restart of Arduino)"));
|
||||||
|
|
||||||
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
|
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
|
||||||
jPanel1.setLayout(jPanel1Layout);
|
jPanel1.setLayout(jPanel1Layout);
|
||||||
jPanel1Layout.setHorizontalGroup(
|
jPanel1Layout.setHorizontalGroup(
|
||||||
@ -341,9 +352,14 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addComponent(comboLanguageLabel)
|
.addComponent(comboLanguageLabel)
|
||||||
.addComponent(fontSizeLabel))
|
.addComponent(fontSizeLabel)
|
||||||
|
.addComponent(comboThemeLabel))
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
|
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||||
|
.addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addComponent(requiresRestartLabel2))
|
||||||
.addComponent(fontSizeField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
.addComponent(fontSizeField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||||
.addComponent(comboLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
.addComponent(comboLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
@ -363,7 +379,7 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
.addContainerGap())
|
.addContainerGap())
|
||||||
);
|
);
|
||||||
|
|
||||||
jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {comboLanguageLabel, comboWarningsLabel, fontSizeLabel, jLabel1, showVerboseLabel});
|
jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {comboLanguageLabel, comboWarningsLabel, fontSizeLabel, jLabel1, showVerboseLabel, comboThemeLabel});
|
||||||
|
|
||||||
jPanel1Layout.setVerticalGroup(
|
jPanel1Layout.setVerticalGroup(
|
||||||
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
@ -391,6 +407,11 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
.addComponent(autoScaleCheckBox)
|
.addComponent(autoScaleCheckBox)
|
||||||
.addComponent(jLabel3))
|
.addComponent(jLabel3))
|
||||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
|
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||||
|
.addComponent(comboThemeLabel)
|
||||||
|
.addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
|
.addComponent(requiresRestartLabel2))
|
||||||
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||||
.addComponent(showVerboseLabel)
|
.addComponent(showVerboseLabel)
|
||||||
.addComponent(verboseCompilationBox)
|
.addComponent(verboseCompilationBox)
|
||||||
@ -742,6 +763,9 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
private javax.swing.JCheckBox verboseCompilationBox;
|
private javax.swing.JCheckBox verboseCompilationBox;
|
||||||
private javax.swing.JCheckBox verboseUploadBox;
|
private javax.swing.JCheckBox verboseUploadBox;
|
||||||
private javax.swing.JCheckBox verifyUploadBox;
|
private javax.swing.JCheckBox verifyUploadBox;
|
||||||
|
private javax.swing.JComboBox comboTheme;
|
||||||
|
private javax.swing.JLabel comboThemeLabel;
|
||||||
|
private javax.swing.JLabel requiresRestartLabel2;
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
private java.util.List<String> validateData() {
|
private java.util.List<String> validateData() {
|
||||||
@ -770,6 +794,12 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
Language newLanguage = (Language) comboLanguage.getSelectedItem();
|
Language newLanguage = (Language) comboLanguage.getSelectedItem();
|
||||||
PreferencesData.set("editor.languages.current", newLanguage.getIsoCode());
|
PreferencesData.set("editor.languages.current", newLanguage.getIsoCode());
|
||||||
|
|
||||||
|
if (comboTheme.getSelectedIndex() == 0) {
|
||||||
|
PreferencesData.set("theme.file", "");
|
||||||
|
} else {
|
||||||
|
PreferencesData.set("theme.file", ((ZippedTheme) comboTheme.getSelectedItem()).getKey());
|
||||||
|
}
|
||||||
|
|
||||||
String newSizeText = fontSizeField.getText();
|
String newSizeText = fontSizeField.getText();
|
||||||
try {
|
try {
|
||||||
int newSize = Integer.parseInt(newSizeText.trim());
|
int newSize = Integer.parseInt(newSizeText.trim());
|
||||||
@ -835,6 +865,16 @@ public class Preferences extends javax.swing.JDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String selectedTheme = PreferencesData.get("theme.file", "");
|
||||||
|
Collection<ZippedTheme> availablethemes = Theme.getAvailablethemes();
|
||||||
|
comboTheme.addItem(tr("Default theme"));
|
||||||
|
for (ZippedTheme theme : availablethemes) {
|
||||||
|
comboTheme.addItem(theme);
|
||||||
|
if (theme.getKey().equals(selectedTheme)) {
|
||||||
|
comboTheme.setSelectedItem(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Font editorFont = PreferencesData.getFont("editor.font");
|
Font editorFont = PreferencesData.getFont("editor.font");
|
||||||
fontSizeField.setText(String.valueOf(editorFont.getSize()));
|
fontSizeField.setText(String.valueOf(editorFont.getSize()));
|
||||||
|
|
||||||
|
@ -38,10 +38,23 @@ import java.awt.Toolkit;
|
|||||||
import java.awt.font.TextAttribute;
|
import java.awt.font.TextAttribute;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
import javax.swing.text.StyleContext;
|
import javax.swing.text.StyleContext;
|
||||||
|
|
||||||
@ -50,7 +63,7 @@ import org.apache.batik.transcoder.TranscoderException;
|
|||||||
import org.apache.batik.transcoder.TranscoderInput;
|
import org.apache.batik.transcoder.TranscoderInput;
|
||||||
import org.apache.batik.transcoder.TranscoderOutput;
|
import org.apache.batik.transcoder.TranscoderOutput;
|
||||||
import org.apache.batik.transcoder.image.PNGTranscoder;
|
import org.apache.batik.transcoder.image.PNGTranscoder;
|
||||||
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
import processing.app.helpers.OSUtils;
|
import processing.app.helpers.OSUtils;
|
||||||
import processing.app.helpers.PreferencesHelper;
|
import processing.app.helpers.PreferencesHelper;
|
||||||
import processing.app.helpers.PreferencesMap;
|
import processing.app.helpers.PreferencesMap;
|
||||||
@ -63,6 +76,233 @@ import processing.app.helpers.PreferencesMap;
|
|||||||
public class Theme {
|
public class Theme {
|
||||||
|
|
||||||
static final String THEME_DIR = "theme/";
|
static final String THEME_DIR = "theme/";
|
||||||
|
static final String THEME_FILE_NAME = "theme.txt";
|
||||||
|
|
||||||
|
static final String NAMESPACE_APP = "app:";
|
||||||
|
static final String NAMESPACE_USER = "user:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A theme resource, this is returned instead of {@link File} so that we can
|
||||||
|
* support zip-packaged resources as well as files in the file system
|
||||||
|
*/
|
||||||
|
public static class Resource {
|
||||||
|
|
||||||
|
// Priority levels used to determine whether one resource should override
|
||||||
|
// another
|
||||||
|
static public final int PRIORITY_DEFAULT = 0;
|
||||||
|
static public final int PRIORITY_USER_ZIP = 1;
|
||||||
|
static public final int PRIORITY_USER_FILE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority of this resource.
|
||||||
|
*/
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resource name (original name of requested resource, relative path only).
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File if this resource represents a file, can be null.
|
||||||
|
*/
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip theme if the resource is contained within a zipped theme
|
||||||
|
*/
|
||||||
|
private final ZippedTheme theme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip entry if this resource represents a zip entry, can be null.
|
||||||
|
*/
|
||||||
|
private final ZipEntry zipEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL of this resource regardless of type, theoretically shouldn't ever be
|
||||||
|
* null though it might be if a particular resource path can't be
|
||||||
|
* successfully transformed into a URL (eg. {@link Theme#getUrl} traps a
|
||||||
|
* <tt>MalformedURLException</tt>).
|
||||||
|
*/
|
||||||
|
private final URL url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this resource supercedes a resource with a lower priority, this field
|
||||||
|
* stores a reference to the superceded resource. This allows consumers to
|
||||||
|
* traverse the resource hierarchy if required.
|
||||||
|
*/
|
||||||
|
private Resource parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ctor for file resources
|
||||||
|
*/
|
||||||
|
Resource(int priority, String name, URL url, File file) {
|
||||||
|
this(priority, name, url, file, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ctor for zip resources
|
||||||
|
*/
|
||||||
|
Resource(int priority, String name, URL url, ZippedTheme theme, ZipEntry entry) {
|
||||||
|
this(priority, name, url, null, theme, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resource(int priority, String name, URL url, File file, ZippedTheme theme, ZipEntry zipEntry) {
|
||||||
|
this.priority = priority;
|
||||||
|
this.name = name;
|
||||||
|
this.file = file;
|
||||||
|
this.theme = theme;
|
||||||
|
this.zipEntry = zipEntry;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource getParent() {
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL getUrl() {
|
||||||
|
return this.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return this.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserDefined() {
|
||||||
|
return this.priority > PRIORITY_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exists() {
|
||||||
|
return this.zipEntry != null || this.file == null || this.file.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
if (this.file != null) {
|
||||||
|
return new FileInputStream(this.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.zipEntry != null) {
|
||||||
|
return this.theme.getZip().getInputStream(this.zipEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.url != null) {
|
||||||
|
return this.url.openStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource withParent(Resource parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Struct which keeps information about a discovered .zip theme file
|
||||||
|
*/
|
||||||
|
public static class ZippedTheme {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration key, this key consists of a "namespace" which determines
|
||||||
|
* the root folder the theme was found in without actually storing the path
|
||||||
|
* itself, followed by the file name.
|
||||||
|
*/
|
||||||
|
private final String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File containing the theme
|
||||||
|
*/
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip file handle for retrieving entries
|
||||||
|
*/
|
||||||
|
private final ZipFile zip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name, defaulted to filename but can be read from metadata
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version number, plain text string read from metadata
|
||||||
|
*/
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
private ZippedTheme(String namespace, File file, ZipFile zip, String name, String version) {
|
||||||
|
this.key = namespace + file.getName();
|
||||||
|
this.file = file;
|
||||||
|
this.zip = zip;
|
||||||
|
this.name = name;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return this.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZipFile getZip() {
|
||||||
|
return this.zip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s %s (%s)", this.getName(), this.getVersion(), this.file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to parse the supplied zip file as a theme file. This is largely
|
||||||
|
* determined by the file being readable and containing a theme.txt entry.
|
||||||
|
* Returns null if the file is unreadable or doesn't contain theme.txt
|
||||||
|
*/
|
||||||
|
static ZippedTheme load(String namespace, File file) {
|
||||||
|
ZipFile zip = null;
|
||||||
|
try {
|
||||||
|
zip = new ZipFile(file);
|
||||||
|
ZipEntry themeTxtEntry = zip.getEntry(THEME_FILE_NAME);
|
||||||
|
if (themeTxtEntry != null) {
|
||||||
|
String name = file.getName().substring(0, file.getName().length() - 4);
|
||||||
|
String version = "";
|
||||||
|
|
||||||
|
ZipEntry themePropsEntry = zip.getEntry("theme.properties");
|
||||||
|
if (themePropsEntry != null) {
|
||||||
|
Properties themeProperties = new Properties();
|
||||||
|
themeProperties.load(zip.getInputStream(themePropsEntry));
|
||||||
|
|
||||||
|
name = themeProperties.getProperty("name", name);
|
||||||
|
version = themeProperties.getProperty("version", version);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ZippedTheme(namespace, file, zip, name, version);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
IOUtils.closeQuietly(zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy of the defaults in case the user mangles a preference.
|
* Copy of the defaults in case the user mangles a preference.
|
||||||
@ -73,10 +313,21 @@ public class Theme {
|
|||||||
*/
|
*/
|
||||||
static PreferencesMap table = new PreferencesMap();
|
static PreferencesMap table = new PreferencesMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available zipped themes
|
||||||
|
*/
|
||||||
|
static private final Map<String, ZippedTheme> availableThemes = new TreeMap<String, ZippedTheme>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip file containing user-defined theme elements
|
||||||
|
*/
|
||||||
|
static private ZippedTheme zipTheme;
|
||||||
|
|
||||||
static protected void init() {
|
static protected void init() {
|
||||||
|
zipTheme = openZipTheme();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
table.load(new File(BaseNoGui.getContentFile("lib"), THEME_DIR + "theme.txt"));
|
loadFromResource(table, THEME_DIR + THEME_FILE_NAME);
|
||||||
table.load(getThemeFile(THEME_DIR + "theme.txt"));
|
|
||||||
} catch (Exception te) {
|
} catch (Exception te) {
|
||||||
Base.showError(null, tr("Could not read color theme settings.\n"
|
Base.showError(null, tr("Could not read color theme settings.\n"
|
||||||
+ "You'll need to reinstall Arduino."),
|
+ "You'll need to reinstall Arduino."),
|
||||||
@ -90,6 +341,44 @@ public class Theme {
|
|||||||
defaults = new PreferencesMap(table);
|
defaults = new PreferencesMap(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private ZippedTheme openZipTheme() {
|
||||||
|
refreshAvailableThemes();
|
||||||
|
String selectedTheme = PreferencesData.get("theme.file", "");
|
||||||
|
synchronized(availableThemes) {
|
||||||
|
return availableThemes.get(selectedTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static private void refreshAvailableThemes() {
|
||||||
|
Map<String, ZippedTheme> discoveredThemes = new TreeMap<String, ZippedTheme>();
|
||||||
|
|
||||||
|
refreshAvailableThemes(discoveredThemes, NAMESPACE_APP, new File(BaseNoGui.getContentFile("lib"), THEME_DIR));
|
||||||
|
refreshAvailableThemes(discoveredThemes, NAMESPACE_USER, new File(BaseNoGui.getSketchbookFolder(), THEME_DIR));
|
||||||
|
|
||||||
|
synchronized(availableThemes) {
|
||||||
|
availableThemes.clear();
|
||||||
|
availableThemes.putAll(discoveredThemes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static private void refreshAvailableThemes(Map<String, ZippedTheme> discoveredThemes, String namespace, File folder) {
|
||||||
|
if (!folder.isDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File zipFile : folder.listFiles((dir, name) -> name.endsWith(".zip"))) {
|
||||||
|
ZippedTheme theme = ZippedTheme.load(namespace, zipFile);
|
||||||
|
if (theme != null) {
|
||||||
|
discoveredThemes.put(theme.getKey(), theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<ZippedTheme> getAvailablethemes() {
|
||||||
|
refreshAvailableThemes();
|
||||||
|
return Collections.<ZippedTheme>unmodifiableCollection(availableThemes.values());
|
||||||
|
}
|
||||||
|
|
||||||
static public String get(String attribute) {
|
static public String get(String attribute) {
|
||||||
return table.get(attribute);
|
return table.get(attribute);
|
||||||
}
|
}
|
||||||
@ -254,32 +543,31 @@ public class Theme {
|
|||||||
Image image = null;
|
Image image = null;
|
||||||
|
|
||||||
// Use vector image when available
|
// Use vector image when available
|
||||||
File vectorFile = getThemeFile(filename + ".svg");
|
Resource vectorFile = getThemeResource(filename + ".svg");
|
||||||
if (vectorFile.exists()) {
|
if (vectorFile.exists()) {
|
||||||
try {
|
try {
|
||||||
image = imageFromSVG(vectorFile.toURI().toURL(), width, height);
|
image = imageFromSVG(vectorFile.getUrl(), width, height);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Failed to load " + vectorFile.getAbsolutePath()
|
System.err.println("Failed to load " + vectorFile + ": " + e.getMessage());
|
||||||
+ ": " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File bitmapFile = getThemeFile(filename + ".png");
|
Resource bitmapFile = getThemeResource(filename + ".png");
|
||||||
|
|
||||||
// Otherwise fall-back to PNG bitmaps, allowing user-defined bitmaps to
|
// Otherwise fall-back to PNG bitmaps, allowing user-defined bitmaps to
|
||||||
// override built-in svgs
|
// override built-in svgs
|
||||||
if (image == null || (!isUserThemeFile(vectorFile) && isUserThemeFile(bitmapFile))) {
|
if (image == null || bitmapFile.getPriority() > vectorFile.getPriority()) {
|
||||||
File bitmap2xFile = getThemeFile(filename + "@2x.png");
|
Resource bitmap2xFile = getThemeResource(filename + "@2x.png");
|
||||||
|
|
||||||
File imageFile;
|
Resource imageFile;
|
||||||
if (((getScale() > 125 && bitmap2xFile.exists()) || !bitmapFile.exists())
|
if (((getScale() > 125 && bitmap2xFile.exists()) || !bitmapFile.exists())
|
||||||
&& isUserThemeFile(bitmapFile) == isUserThemeFile(bitmap2xFile)) {
|
&& (bitmapFile.isUserDefined() && bitmap2xFile.isUserDefined())) {
|
||||||
imageFile = bitmap2xFile;
|
imageFile = bitmap2xFile;
|
||||||
} else {
|
} else {
|
||||||
imageFile = bitmapFile;
|
imageFile = bitmapFile;
|
||||||
}
|
}
|
||||||
Toolkit tk = Toolkit.getDefaultToolkit();
|
Toolkit tk = Toolkit.getDefaultToolkit();
|
||||||
image = tk.getImage(imageFile.getAbsolutePath());
|
image = tk.getImage(imageFile.getUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaTracker tracker = new MediaTracker(who);
|
MediaTracker tracker = new MediaTracker(who);
|
||||||
@ -334,19 +622,48 @@ public class Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the specified file is a user-defined theme file
|
* Loads the supplied {@link PreferencesMap} from the specified resource,
|
||||||
|
* recursively loading parent resources such that entries are loaded in order
|
||||||
|
* of priority (lowest first).
|
||||||
|
*
|
||||||
|
* @param map preference map to populate
|
||||||
|
* @param name name of resource to load
|
||||||
*/
|
*/
|
||||||
static public boolean isUserThemeFile(File file) {
|
static public PreferencesMap loadFromResource(PreferencesMap map, String name) throws IOException {
|
||||||
return file.exists() && file.getAbsolutePath().startsWith(BaseNoGui.getSketchbookFolder().getAbsolutePath());
|
return loadFromResource(map, getThemeResource(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static private PreferencesMap loadFromResource(PreferencesMap map, Resource resource) throws IOException {
|
||||||
|
if (resource != null) {
|
||||||
|
loadFromResource(map, resource.getParent());
|
||||||
|
map.load(resource.getInputStream());
|
||||||
|
}
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param name
|
* @param name
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
static public File getThemeFile(String name) {
|
static public Resource getThemeResource(String name) {
|
||||||
File sketchBookThemeFolder = new File(BaseNoGui.getSketchbookFolder(), THEME_DIR);
|
File defaultfile = getDefaultFile(name);
|
||||||
|
Resource resource = new Resource(Resource.PRIORITY_DEFAULT, name, getUrl(defaultfile), defaultfile);
|
||||||
|
|
||||||
|
ZipEntry themeZipEntry = getThemeZipEntry(name);
|
||||||
|
if (themeZipEntry != null) {
|
||||||
|
resource = new Resource(Resource.PRIORITY_USER_ZIP, name, getUrl(themeZipEntry), zipTheme, themeZipEntry).withParent(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
File themeFile = getThemeFile(name);
|
||||||
|
if (themeFile != null) {
|
||||||
|
resource = new Resource(Resource.PRIORITY_USER_FILE, name, getUrl(themeFile), themeFile).withParent(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private File getThemeFile(String name) {
|
||||||
|
File sketchBookThemeFolder = new File(BaseNoGui.getSketchbookFolder(), THEME_DIR);
|
||||||
File themeFile = new File(sketchBookThemeFolder, name);
|
File themeFile = new File(sketchBookThemeFolder, name);
|
||||||
if (themeFile.exists()) {
|
if (themeFile.exists()) {
|
||||||
return themeFile;
|
return themeFile;
|
||||||
@ -359,6 +676,47 @@ public class Theme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private ZipEntry getThemeZipEntry(String name) {
|
||||||
|
if (zipTheme == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith(THEME_DIR)) {
|
||||||
|
name = name.substring(THEME_DIR.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
return zipTheme.getZip().getEntry(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static private File getDefaultFile(String name) {
|
||||||
return new File(BaseNoGui.getContentFile("lib"), name);
|
return new File(BaseNoGui.getContentFile("lib"), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static URL getUrl(File file) {
|
||||||
|
try {
|
||||||
|
return file.toURI().toURL();
|
||||||
|
} catch (MalformedURLException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL getUrl(ZipEntry entry) {
|
||||||
|
try {
|
||||||
|
// Adjust file name for URL format on Windows
|
||||||
|
String zipFile = zipTheme.getZip().getName().replace('\\', '/');
|
||||||
|
if (!zipFile.startsWith("/")) {
|
||||||
|
zipFile = "/" + zipFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a URL which points to the internal resource
|
||||||
|
URI uri = new URI("jar", "file:" + zipFile + "!/" + entry.getName(), null);
|
||||||
|
return uri.toURL();
|
||||||
|
|
||||||
|
} catch (MalformedURLException | URISyntaxException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ public class PasswordAuthorizationDialog extends JDialog {
|
|||||||
|
|
||||||
typePasswordLabel.setText(dialogText);
|
typePasswordLabel.setText(dialogText);
|
||||||
|
|
||||||
icon.setIcon(new ImageIcon(Theme.getThemeFile("theme/lock.png").getAbsolutePath()));
|
icon.setIcon(new ImageIcon(Theme.getThemeResource("theme/lock.png").getUrl()));
|
||||||
|
|
||||||
passwordLabel.setText(tr("Password:"));
|
passwordLabel.setText(tr("Password:"));
|
||||||
|
|
||||||
|
@ -32,6 +32,16 @@ package processing.app.syntax;
|
|||||||
|
|
||||||
import java.awt.event.InputEvent;
|
import java.awt.event.InputEvent;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
import org.fife.ui.rsyntaxtextarea.*;
|
import org.fife.ui.rsyntaxtextarea.*;
|
||||||
@ -91,9 +101,9 @@ public class SketchTextArea extends RSyntaxTextArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setTheme(String name) throws IOException {
|
private void setTheme(String name) throws IOException {
|
||||||
FileInputStream defaultXmlInputStream = null;
|
InputStream defaultXmlInputStream = null;
|
||||||
try {
|
try {
|
||||||
defaultXmlInputStream = new FileInputStream(processing.app.Theme.getThemeFile("theme/syntax/" + name + ".xml"));
|
defaultXmlInputStream = processing.app.Theme.getThemeResource("theme/syntax/" + name + ".xml").getInputStream();
|
||||||
Theme theme = Theme.load(defaultXmlInputStream);
|
Theme theme = Theme.load(defaultXmlInputStream);
|
||||||
theme.apply(this);
|
theme.apply(this);
|
||||||
} finally {
|
} finally {
|
||||||
|
Loading…
Reference in New Issue
Block a user