/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.theme; import com.mucommander.text.Translator; import java.awt.*; import java.util.WeakHashMap; /** * @author Nicolas Rinaudo */ public class Theme extends ThemeData { // - Theme types --------------------------------------------------------------------- // ----------------------------------------------------------------------------------- /** Describes the user defined theme. */ public static final int USER_THEME = 0; /** Describes predefined muCommander themes. */ public static final int PREDEFINED_THEME = 1; /** Describes custom muCommander themes. */ public static final int CUSTOM_THEME = 2; // - Theme listeners ----------------------------------------------------------------- // ----------------------------------------------------------------------------------- private static WeakHashMap<ThemeListener, ?> listeners = new WeakHashMap<ThemeListener, Object>(); // - Instance variables -------------------------------------------------------------- // ----------------------------------------------------------------------------------- /** Theme name. */ private String name; /** Theme type. */ private int type; // While this field might look useless, it's actually critical for proper event notification: // ThemeData uses a weak hashmap to store its listeners, meaning that each listener must be 'linked' // somewhere or be garbage collected. Simply put, if we do not store the instance here, we might // as well not bother registering it. /** Default values listener. */ private DefaultValuesListener defaultValuesListener; // - Initialisation ------------------------------------------------------------------ // ----------------------------------------------------------------------------------- /** * Creates a new empty user theme. */ Theme(ThemeListener listener) { super(); init(listener, USER_THEME, null); } Theme(ThemeListener listener, int type, String name) { super(); init(listener, type, name); } Theme(ThemeListener listener, ThemeData template) { super(template); init(listener, USER_THEME, null); } Theme(ThemeListener listener, ThemeData template, int type, String name) { super(template); init(listener, type, name); } private void init(ThemeListener listener, int type, String name) { // This might seem like a roundabout way of doing things, but it's actually necessary. // If we didn't explicitly call a defaultValuesListener method, proGuard would 'optimise' // the instance out with catastrophic results (the listener would become a weak reference, // be removed by the garbage collector, and all our carefully crafted event system would // crumble). // While Theme.addDefaultValuesListener(defaultValuesListener = new DefaultValuesListener(this)); // might seem like a more compact way of doing things, it wouldn't actually work. defaultValuesListener = new DefaultValuesListener(); defaultValuesListener.setTheme(this); ThemeData.addDefaultValuesListener(defaultValuesListener); addThemeListener(listener); setType(type); if(name != null) setName(name); } // - Data retrieval ------------------------------------------------------------------ // ----------------------------------------------------------------------------------- /** * Checks whether this theme is modifiable. * <p> * A theme is modifiable if and only if it's the user theme. This is a utility method * which produces exactly the same result as <code>getType() == USER_THEME</code>. * </p> * @return <code>true</code> if the theme is modifiable, <code>false</code> otherwise. */ public boolean canModify() {return type == USER_THEME;} /** * Returns the theme's type. * @return the theme's type. */ public int getType() {return type;} /** * Returns the theme's name. * @return the theme's name. */ public String getName() {return name;} // - Data modification --------------------------------------------------------------- // ----------------------------------------------------------------------------------- /** * Sets one of the theme's fonts. * <p> * Note that this method will only work if the theme is the user one. Any other * theme type will throw an exception. * </p> * @see ThemeManager#setCurrentFont(int,Font) * @param id identifier of the font to set. * @param font value for the specified font. * @throws IllegalStateException thrown if the theme is not the user one. */ @Override public boolean setFont(int id, Font font) { // Makes sure we're not trying to modify a non-user theme. if(type != USER_THEME) throw new IllegalStateException("Trying to modify a non user theme."); if(super.setFont(id, font)) { // We're using getFont here to make sure that no event is propagated with a null value. triggerFontEvent(new FontChangedEvent(this, id, getFont(id))); return true; } return false; } /** * Sets one of the theme's colors. * <p> * Note that this method will only work if the theme is the user one. Any other * theme type will throw an exception. * </p> * @see ThemeManager#setCurrentColor(int,Color) * @param id identifier of the color to set. * @param color value for the specified color. * @throws IllegalStateException thrown if the theme is not the user one. */ @Override public boolean setColor(int id, Color color) { // Makes sure we're not trying to modify a non-user theme. if(type != USER_THEME) throw new IllegalStateException("Trying to modify a non user theme."); if(super.setColor(id, color)) { // We're using getColor here to make sure that no event is propagated with a null value. triggerColorEvent(new ColorChangedEvent(this, id, getColor(id))); return true; } return false; } /** * Sets this theme's type. * <p> * If <code>type</code> is set to {@link #USER_THEME}, this method will also set the * theme's name to the proper value taken from the dictionary. * </p> * @param type theme's type. */ void setType(int type) { checkType(type); this.type = type; if(type == USER_THEME) setName(Translator.get("theme.custom_theme")); } /** * Sets this theme's name. * @param name theme's name. */ void setName(String name) {this.name = name;} // - Misc. --------------------------------------------------------------------------- // ----------------------------------------------------------------------------------- static void checkType(int type) { if(type != USER_THEME && type != PREDEFINED_THEME && type != CUSTOM_THEME) throw new IllegalArgumentException("Illegal theme type: " + type); } /** * Returns the theme's name. * @return the theme's name. */ public String toString() {return getName();} private static void addThemeListener(ThemeListener listener) {listeners.put(listener, null);} private static void removeThemeListener(ThemeListener listener) {listeners.remove(listener);} private static void triggerFontEvent(FontChangedEvent event) { for(ThemeListener listener : listeners.keySet()) listener.fontChanged(event); } private static void triggerColorEvent(ColorChangedEvent event) { for(ThemeListener listener : listeners.keySet()) listener.colorChanged(event); } private class DefaultValuesListener implements ThemeListener { private Theme theme; public DefaultValuesListener() {} public void setTheme(Theme theme) {this.theme = theme;} public void colorChanged(ColorChangedEvent event) { if(!theme.isColorSet(event.getColorId())) Theme.triggerColorEvent(new ColorChangedEvent(theme, event.getColorId(), getColor(event.getColorId()))); } public void fontChanged(FontChangedEvent event) { if(!theme.isFontSet(event.getFontId())) Theme.triggerFontEvent(new FontChangedEvent(theme, event.getFontId(), getFont(event.getFontId()))); } } }