/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 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.dialog.pref.general;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.FileFactory;
import com.mucommander.commons.runtime.OsFamily;
import com.mucommander.commons.runtime.OsVersion;
import com.mucommander.conf.MuConfigurations;
import com.mucommander.conf.MuPreference;
import com.mucommander.conf.MuPreferences;
import com.mucommander.extension.ClassFinder;
import com.mucommander.extension.ExtensionManager;
import com.mucommander.extension.LookAndFeelFilter;
import com.mucommander.job.FileCollisionChecker;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.InformationDialog;
import com.mucommander.ui.dialog.QuestionDialog;
import com.mucommander.ui.dialog.file.FileCollisionDialog;
import com.mucommander.ui.dialog.pref.PreferencesDialog;
import com.mucommander.ui.dialog.pref.PreferencesPanel;
import com.mucommander.ui.dialog.pref.component.PrefCheckBox;
import com.mucommander.ui.dialog.pref.component.PrefComboBox;
import com.mucommander.ui.dialog.pref.theme.ThemeEditorDialog;
import com.mucommander.ui.icon.FileIcons;
import com.mucommander.ui.icon.IconManager;
import com.mucommander.ui.icon.SpinningDial;
import com.mucommander.ui.layout.ProportionalGridPanel;
import com.mucommander.ui.layout.YBoxPanel;
import com.mucommander.ui.main.WindowManager;
import com.mucommander.ui.theme.Theme;
import com.mucommander.ui.theme.ThemeManager;
/**
* 'Appearance' preferences panel.
* @author Maxence Bernard, Nicolas Rinaudo
*/
class AppearancePanel extends PreferencesPanel implements ActionListener, Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(AppearancePanel.class);
// - Look and feel fields ------------------------------------------------------------
// -----------------------------------------------------------------------------------
/** Combo box containing the list of available look&feels. */
private PrefComboBox lookAndFeelComboBox;
/** All available look&feels. */
private UIManager.LookAndFeelInfo lookAndFeels[];
/** 'Use brushed metal look' checkbox */
private PrefCheckBox brushedMetalCheckBox;
/** Triggers look and feel importing. */
private JButton importLookAndFeelButton;
/** Triggers look and feel deletion. */
private JButton deleteLookAndFeelButton;
/** Used to notify the user that the system is working. */
private SpinningDial dial;
/** File from which to import looks and feels. */
private AbstractFile lookAndFeelLibrary;
// - Icon size fields ----------------------------------------------------------------
// -----------------------------------------------------------------------------------
/** Displays the list of available sizes for toolbar icons. */
private PrefComboBox toolbarIconsSizeComboBox;
/** Displays the list of available sizes for command bar icons. */
private PrefComboBox commandBarIconsSizeComboBox;
/** Displays the list of available sizes for file icons. */
private PrefComboBox fileIconsSizeComboBox;
/** All icon sizes label. */
private final static String ICON_SIZES[] = {"100%", "125%", "150%", "175%", "200%", "300%"};
/** All icon sizes scale factors. */
private final static float ICON_SCALE_FACTORS[] = {1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 3.0f};
// - Icons ---------------------------------------------------------------------------
// -----------------------------------------------------------------------------------
/** Icon used to identify 'locked' themes. */
private ImageIcon lockIcon;
/** Transparent icon used to align non-locked themes with the others. */
private ImageIcon transparentIcon;
// - Theme fields --------------------------------------------------------------------
// -----------------------------------------------------------------------------------
/** Lists all available themes. */
private PrefComboBox themeComboBox;
/** Triggers the theme editor. */
private JButton editThemeButton;
/** Triggers the theme duplication dialog. */
private JButton duplicateThemeButton;
/** Triggers the theme import dialog. */
private JButton importThemeButton;
/** Triggers the theme export dialog. */
private JButton exportThemeButton;
/** Triggers the theme rename dialog. */
private JButton renameThemeButton;
/** Triggers the theme delete dialog. */
private JButton deleteThemeButton;
/** Used to display the currently selected theme's type. */
private JLabel typeLabel;
/** Whether or not to ignore theme comobox related events. */
private boolean ignoreComboChanges;
/** Last folder that was selected in import or export operations. */
private AbstractFile lastSelectedFolder;
// - Misc. fields --------------------------------------------------------------------
// -----------------------------------------------------------------------------------
/** System icon combobox. */
private PrefComboBox useSystemFileIconsComboBox;
/** Identifier of 'yes' actions in question dialogs. */
private final static int YES_ACTION = 0;
/** Identifier of 'no' actions in question dialogs. */
private final static int NO_ACTION = 1;
/** Identifier of 'cancel' actions in question dialogs. */
private final static int CANCEL_ACTION = 2;
/** All known custom look and feels. */
private java.util.List<String> customLookAndFeels;
// - Initialisation ---------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Creates a new appearance panel with the specified parent.
* @param parent dialog in which this panel is placed.
*/
public AppearancePanel(PreferencesDialog parent) {
super(parent, Translator.get("prefs_dialog.appearance_tab"));
initUI();
// Initialises the known custom look and feels
initializeCustomLookAndFeels();
}
// - UI initialisation ------------------------------------------------------
// --------------------------------------------------------------------------
private void initUI() {
YBoxPanel mainPanel;
mainPanel = new YBoxPanel();
// Look and feel.
mainPanel.add(createLookAndFeelPanel());
mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));
// Themes.
mainPanel.add(createThemesPanel());
mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));
// System icons.
mainPanel.add(createSystemIconsPanel());
mainPanel.add(Box.createVerticalGlue());
// Icon size.
mainPanel.add(createIconSizePanel());
mainPanel.add(Box.createRigidArea(new Dimension(0, 10)));
setLayout(new BorderLayout());
add(mainPanel, BorderLayout.NORTH);
lookAndFeelComboBox.addDialogListener(parent);
themeComboBox.addDialogListener(parent);
useSystemFileIconsComboBox.addDialogListener(parent);
toolbarIconsSizeComboBox.addDialogListener(parent);
commandBarIconsSizeComboBox.addDialogListener(parent);
fileIconsSizeComboBox.addDialogListener(parent);
if(brushedMetalCheckBox!=null)
brushedMetalCheckBox.addDialogListener(parent);
}
/**
* Populates the look&feel combo box with all available look&feels.
*/
private void populateLookAndFeels() {
int currentIndex;
String currentName;
lookAndFeelComboBox.removeAllItems();
initializeAvailableLookAndFeels();
// Populates the combo box.
currentIndex = -1;
currentName = UIManager.getLookAndFeel().getClass().getName();
for(int i = 0; i < lookAndFeels.length; i++) {
// Looks for the currently selected look&feel.
if(lookAndFeels[i].getClassName().equals(currentName))
currentIndex = i;
lookAndFeelComboBox.addItem(lookAndFeels[i].getName());
}
// Sets the initial selection.
if(currentIndex == -1)
currentIndex = 0;
lookAndFeelComboBox.setSelectedIndex(currentIndex);
}
/**
* Creates the look and feel panel.
* @return the look and feel panel.
*/
private JPanel createLookAndFeelPanel() {
JPanel lnfPanel;
// Creates the panel.
lnfPanel = new YBoxPanel();
lnfPanel.setAlignmentX(LEFT_ALIGNMENT);
lnfPanel.setBorder(BorderFactory.createTitledBorder(Translator.get("prefs_dialog.look_and_feel")));
// Creates the look and feel combo box.
lookAndFeelComboBox = new PrefComboBox() {
public boolean hasChanged() {
int selectedIndex = getSelectedIndex();
if(selectedIndex<0)
return false;
return !lookAndFeels[selectedIndex].getClassName().equals(MuConfigurations.getPreferences().getVariable(MuPreference.LOOK_AND_FEEL));
}
};
lookAndFeelComboBox.setRenderer(new BasicComboBoxRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label;
label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if(index < 0)
return label;
// All look and feels that are not modifiable must be flagged with a lock icon.
if(isLookAndFeelModifiable(lookAndFeels[index]))
label.setIcon(transparentIcon);
else
label.setIcon(lockIcon);
return label;
}
});
// Populates the look and feel combo box.
populateLookAndFeels();
// Initialises buttons and event listening.
importLookAndFeelButton = new JButton(Translator.get("prefs_dialog.import") + "...");
deleteLookAndFeelButton = new JButton(Translator.get("delete"));
importLookAndFeelButton.addActionListener(this);
deleteLookAndFeelButton.addActionListener(this);
resetLookAndFeelButtons();
lookAndFeelComboBox.addActionListener(this);
// Adds the look and feel list and the action buttons to the panel.
JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
flowPanel.add(lookAndFeelComboBox);
flowPanel.add(importLookAndFeelButton);
flowPanel.add(deleteLookAndFeelButton);
flowPanel.add(new JLabel(dial = new SpinningDial()));
lnfPanel.add(flowPanel);
// For Mac OS X only, creates the 'brushed metal' checkbox.
// At the time of writing, the 'brushed metal' look causes the JVM to crash randomly under Leopard (10.5)
// so we disable brushed metal on that OS version but leave it for earlier versions where it works fine.
// See http://www.mucommander.com/forums/viewtopic.php?f=4&t=746 for more info about this issue.
if(OsFamily.MAC_OS_X.isCurrent() && OsVersion.MAC_OS_X_10_4.isCurrentOrLower()) {
// 'Use brushed metal look' option
brushedMetalCheckBox = new PrefCheckBox(Translator.get("prefs_dialog.use_brushed_metal")) {
public boolean hasChanged() {
return !String.valueOf(isSelected()).equals(MuConfigurations.getPreferences().getVariable(MuPreference.USE_BRUSHED_METAL));
}
};
brushedMetalCheckBox.setSelected(MuConfigurations.getPreferences().getVariable(MuPreference.USE_BRUSHED_METAL,
MuPreferences.DEFAULT_USE_BRUSHED_METAL));
flowPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
flowPanel.add(brushedMetalCheckBox);
lnfPanel.add(flowPanel);
}
return lnfPanel;
}
/**
* Creates the icon size panel.
* @return the icon size panel.
*/
private JPanel createIconSizePanel() {
ProportionalGridPanel gridPanel = new ProportionalGridPanel(2);
gridPanel.add(new JLabel(Translator.get("prefs_dialog.toolbar_icons")));
gridPanel.add(toolbarIconsSizeComboBox = createIconSizeCombo(MuPreference.TOOLBAR_ICON_SCALE, MuPreferences.DEFAULT_TOOLBAR_ICON_SCALE));
gridPanel.add(new JLabel(Translator.get("prefs_dialog.command_bar_icons")));
gridPanel.add(commandBarIconsSizeComboBox = createIconSizeCombo(MuPreference.COMMAND_BAR_ICON_SCALE, MuPreferences.DEFAULT_COMMAND_BAR_ICON_SCALE));
gridPanel.add(new JLabel(Translator.get("prefs_dialog.file_icons")));
gridPanel.add(fileIconsSizeComboBox = createIconSizeCombo(MuPreference.TABLE_ICON_SCALE, MuPreferences.DEFAULT_TABLE_ICON_SCALE));
JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get("prefs_dialog.icons_size")));
flowPanel.add(gridPanel);
return flowPanel;
}
/**
* Creates the themes panel.
* @return the themes panel.
*/
private JPanel createThemesPanel() {
JPanel gridPanel = new ProportionalGridPanel(4);
// Creates the various panel's buttons.
editThemeButton = new JButton(Translator.get("edit") + "...");
importThemeButton = new JButton(Translator.get("prefs_dialog.import") + "...");
exportThemeButton = new JButton(Translator.get("prefs_dialog.export") + "...");
renameThemeButton = new JButton(Translator.get("rename"));
deleteThemeButton = new JButton(Translator.get("delete"));
duplicateThemeButton = new JButton(Translator.get("duplicate"));
editThemeButton.addActionListener(this);
importThemeButton.addActionListener(this);
exportThemeButton.addActionListener(this);
renameThemeButton.addActionListener(this);
deleteThemeButton.addActionListener(this);
duplicateThemeButton.addActionListener(this);
// Creates the panel's 'type label'.
typeLabel = new JLabel("");
// Creates the theme combo box.
themeComboBox = new PrefComboBox() {
public boolean hasChanged() {
return !ThemeManager.isCurrentTheme((Theme)getSelectedItem());
}
};
themeComboBox.addActionListener(this);
// Sets the combobox's renderer.
lockIcon = IconManager.getIcon(IconManager.PREFERENCES_ICON_SET, "lock.png");
transparentIcon = new ImageIcon(new BufferedImage(lockIcon.getIconWidth(), lockIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB));
themeComboBox.setRenderer(new BasicComboBoxRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label;
Theme theme;
label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
theme = (Theme)value;
if(ThemeManager.isCurrentTheme(theme))
label.setText(theme.getName() + " (" + Translator.get("theme.current") + ")");
else
label.setText(theme.getName());
if(theme.getType() != Theme.CUSTOM_THEME)
label.setIcon(lockIcon);
else
label.setIcon(transparentIcon);
return label;
}
});
// Initialises the content of the combo box.
populateThemes(ThemeManager.getCurrentTheme());
gridPanel.add(themeComboBox);
gridPanel.add(editThemeButton);
gridPanel.add(importThemeButton);
gridPanel.add(exportThemeButton);
gridPanel.add(typeLabel);
gridPanel.add(renameThemeButton);
gridPanel.add(deleteThemeButton);
gridPanel.add(duplicateThemeButton);
JPanel flowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
flowPanel.setBorder(BorderFactory.createTitledBorder(Translator.get("prefs_dialog.themes")));
flowPanel.add(gridPanel);
return flowPanel;
}
private void populateThemes(Theme currentTheme) {
Iterator<Theme> themes;
ignoreComboChanges = true;
themeComboBox.removeAllItems();
themes = ThemeManager.availableThemes();
while(themes.hasNext())
themeComboBox.addItem(themes.next());
ignoreComboChanges = false;
themeComboBox.setSelectedItem(currentTheme);
}
/**
* Creates the system icons panel.
* @return the system icons panel.
*/
private JPanel createSystemIconsPanel() {
/* 'Use system file icons' combo box */
this.useSystemFileIconsComboBox = new PrefComboBox() {
public boolean hasChanged() {
String systemIconsPolicy;
switch(useSystemFileIconsComboBox.getSelectedIndex()) {
case 0:
systemIconsPolicy = FileIcons.USE_SYSTEM_ICONS_NEVER;
break;
case 1:
systemIconsPolicy = FileIcons.USE_SYSTEM_ICONS_APPLICATIONS;
break;
default:
systemIconsPolicy = FileIcons.USE_SYSTEM_ICONS_ALWAYS;
}
return !systemIconsPolicy.equals(MuConfigurations.getPreferences().getVariable(MuPreference.USE_SYSTEM_FILE_ICONS, systemIconsPolicy));
}
};
useSystemFileIconsComboBox.addItem(Translator.get("prefs_dialog.use_system_file_icons.never"));
useSystemFileIconsComboBox.addItem(Translator.get("prefs_dialog.use_system_file_icons.applications"));
useSystemFileIconsComboBox.addItem(Translator.get("prefs_dialog.use_system_file_icons.always"));
String systemIconsPolicy = FileIcons.getSystemIconsPolicy();
useSystemFileIconsComboBox.setSelectedIndex(FileIcons.USE_SYSTEM_ICONS_ALWAYS.equals(systemIconsPolicy)?2:FileIcons.USE_SYSTEM_ICONS_APPLICATIONS.equals(systemIconsPolicy)?1:0);
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel.setBorder(BorderFactory.createTitledBorder(Translator.get("prefs_dialog.use_system_file_icons")));
panel.add(useSystemFileIconsComboBox);
return panel;
}
/**
* Creates a combo box that allows to choose a size for a certain type of icon. The returned combo box is filled
* with allowed choices, and the current configuration value is selected.
*
* @param confVar the name of the configuration variable that contains the icon scale factor
* @param defaultValue the default value for the icon scale factor if the configuration variable has no value
* @return a combo box that allows to choose a size for a certain type of icon
*/
private PrefComboBox createIconSizeCombo(final MuPreference preference, float defaultValue) {
PrefComboBox iconSizeCombo = new PrefComboBox() {
public boolean hasChanged() {
return !String.valueOf(ICON_SCALE_FACTORS[getSelectedIndex()]).equals(
MuConfigurations.getPreferences().getVariable(preference));
}
};
for (String iconSize : ICON_SIZES)
iconSizeCombo.addItem(iconSize);
float scaleFactor = MuConfigurations.getPreferences().getVariable(preference, defaultValue);
int index = 0;
for(int i=0; i<ICON_SCALE_FACTORS.length; i++) {
if(scaleFactor==ICON_SCALE_FACTORS[i]) {
index = i;
break;
}
}
iconSizeCombo.setSelectedIndex(index);
return iconSizeCombo;
}
///////////////////////
// PrefPanel methods //
///////////////////////
@Override
protected void commit() {
// Look and Feel
if(MuConfigurations.getPreferences().setVariable(MuPreference.LOOK_AND_FEEL, lookAndFeels[lookAndFeelComboBox.getSelectedIndex()].getClassName())) {
resetLookAndFeelButtons();
SwingUtilities.updateComponentTreeUI(parent);
}
if(brushedMetalCheckBox!=null)
MuConfigurations.getPreferences().setVariable(MuPreference.USE_BRUSHED_METAL, brushedMetalCheckBox.isSelected());
// Set ToolBar's icon size
float scaleFactor = ICON_SCALE_FACTORS[toolbarIconsSizeComboBox.getSelectedIndex()];
MuConfigurations.getPreferences().setVariable(MuPreference.TOOLBAR_ICON_SCALE, scaleFactor);
// Set CommandBar's icon size
scaleFactor = ICON_SCALE_FACTORS[commandBarIconsSizeComboBox.getSelectedIndex()];
MuConfigurations.getPreferences().setVariable(MuPreference.COMMAND_BAR_ICON_SCALE , scaleFactor);
// Set file icon size
scaleFactor = ICON_SCALE_FACTORS[fileIconsSizeComboBox.getSelectedIndex()];
// Set scale factor in FileIcons first so that it has the new value when ConfigurationListener instances call it
FileIcons.setScaleFactor(scaleFactor);
MuConfigurations.getPreferences().setVariable(MuPreference.TABLE_ICON_SCALE , scaleFactor);
// Sets the current theme.
if(!ThemeManager.isCurrentTheme((Theme)themeComboBox.getSelectedItem())) {
ThemeManager.setCurrentTheme((Theme)themeComboBox.getSelectedItem());
resetThemeButtons((Theme)themeComboBox.getSelectedItem());
themeComboBox.repaint();
}
// Set system icons policy
int comboIndex = useSystemFileIconsComboBox.getSelectedIndex();
String systemIconsPolicy = comboIndex==0?FileIcons.USE_SYSTEM_ICONS_NEVER:comboIndex==1?FileIcons.USE_SYSTEM_ICONS_APPLICATIONS:FileIcons.USE_SYSTEM_ICONS_ALWAYS;
FileIcons.setSystemIconsPolicy(systemIconsPolicy);
MuConfigurations.getPreferences().setVariable(MuPreference.USE_SYSTEM_FILE_ICONS, systemIconsPolicy);
}
// - Look and feel actions --------------------------------------------------
// --------------------------------------------------------------------------
/**
* Initialises the list of custom look&feels.
*/
private void initializeCustomLookAndFeels() {
customLookAndFeels = MuConfigurations.getPreferences().getListVariable(MuPreference.CUSTOM_LOOK_AND_FEELS, MuPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);
}
/**
* Initialises the list of available look&feels.
*/
private void initializeAvailableLookAndFeels() {
// Loads all available look and feels.
lookAndFeels = UIManager.getInstalledLookAndFeels();
// Sorts them.
Arrays.sort(lookAndFeels, new Comparator<UIManager.LookAndFeelInfo>() {
public int compare(UIManager.LookAndFeelInfo a, UIManager.LookAndFeelInfo b) {return a.getName().compareTo(b.getName());}
public boolean equals(Object a) {return false;}
});
}
/**
* Returns <code>true</code> if the specified class name is that of a custom look and feel.
* @return <code>true</code> if the specified class name is that of a custom look and feel, <code>false</code> otherwise.
*/
private boolean isCustomLookAndFeel(String className) {
return customLookAndFeels != null && customLookAndFeels.contains(className);
}
/**
* Returns <code>true</code> if the specified look and feel is modifiable.
* <p>
* To be modifiable, a look and feel must meet all of the following conditions:
* <ul>
* <li>It must be a custom look and feel.</li>
* <li>It cannot be the application's current look and feel.</li>
* </ul>
* </p>
* @return <code>true</code> if the specified look and feel is modifiable, <code>false</code> otherwise.
*/
private boolean isLookAndFeelModifiable(UIManager.LookAndFeelInfo laf) {
if(isCustomLookAndFeel(laf.getClassName()))
return !laf.getClassName().equals(UIManager.getLookAndFeel().getClass().getName());
return false;
}
/**
* Resets the enabled status of the various look and feel buttons depending on the current selection.
*/
private void resetLookAndFeelButtons() {
// If the dial is animated, we're currently loading look&feels and should ignore this call.
if(dial == null || !dial.isAnimated()) {
int selectedIndex = lookAndFeelComboBox.getSelectedIndex();
if(selectedIndex!=-1)
deleteLookAndFeelButton.setEnabled(isLookAndFeelModifiable(lookAndFeels[selectedIndex]));
}
}
/**
* Uninstalls the specified look and feel.
* @param selection look and feel to uninstall.
*/
private void uninstallLookAndFeel(UIManager.LookAndFeelInfo selection) {
UIManager.LookAndFeelInfo[] buffer; // New array of installed look and feels.
int bufferIndex; // Current index in buffer.
// Copies the content of lookAndFeels into buffer, skipping over the look and feel to uninstall.
buffer = new UIManager.LookAndFeelInfo[lookAndFeels.length - 1];
bufferIndex = 0;
for (UIManager.LookAndFeelInfo lookAndFeel : lookAndFeels) {
if (!selection.getClassName().equals(lookAndFeel.getClassName())) {
buffer[bufferIndex] = lookAndFeel;
bufferIndex++;
}
}
// Resets the list of installed look and feels.
UIManager.setInstalledLookAndFeels(lookAndFeels = buffer);
}
/**
* Deletes the specified look and feel from the list of custom look and feels.
* @param selection currently selection look and feel.
*/
private void deleteCustomLookAndFeel(UIManager.LookAndFeelInfo selection) {
if(customLookAndFeels != null)
if(customLookAndFeels.remove(selection.getClassName()))
MuConfigurations.getPreferences().setVariable(MuPreference.CUSTOM_LOOK_AND_FEELS, customLookAndFeels, MuPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);
}
/**
* Deletes the currently selected look and feel.
* <p>
* After receiving user confirmation, this method will:
* <ul>
* <li>Remove the look and feel from the combobox.</li>
* <li>Remove the look and feel from <code>UIManager</code>'s list of installed look and feels.</li>
* <li>Remove the look and feel from the list of custom look and feels.</li>
* </ul>
* </p>
*/
private void deleteSelectedLookAndFeel() {
UIManager.LookAndFeelInfo selection; // Currently selected look and feel.
selection = lookAndFeels[lookAndFeelComboBox.getSelectedIndex()];
// Asks the user whether he's sure he wants to delete the selected look and feel.
if(new QuestionDialog(parent, null, Translator.get("prefs_dialog.delete_look_and_feel", selection.getName()), parent,
new String[] {Translator.get("yes"), Translator.get("no")},
new int[] {YES_ACTION, NO_ACTION},
0).getActionValue() != YES_ACTION)
return;
// Removes the selected look and feel from the combo box.
lookAndFeelComboBox.removeItem(selection.getName());
// Removes the selected look and feel from the list of installed look and feels.
uninstallLookAndFeel(selection);
// Removes the selected look and feel from the list of custom look and feels.
deleteCustomLookAndFeel(selection);
}
/**
* Updates the different look&feel related UI widgets depending on whether they are busy or not.
* @param loading whether look&feels are loading.
*/
private void setLookAndFeelsLoading(boolean loading) {
// Starts / stops the loading animation.
dial.setAnimated(loading);
// Disables / enables the import button and the combo box.
importLookAndFeelButton.setEnabled(!loading);
deleteLookAndFeelButton.setEnabled(!loading);
lookAndFeelComboBox.setEnabled(!loading);
// A special case must be made for the delete button
// as it might not need to be re-enabled.
if(loading)
deleteLookAndFeelButton.setEnabled(false);
else
resetLookAndFeelButtons();
}
/**
* Tries to import the specified library in the extensions folder.
* <p>
* If there is already a file with the same name in the extensions folder,
* this method will ask the user for confirmation before overwriting it.
* </p>
* @param library library to import in the extensions folder.
* @return <code>true</code> if the library was imported, <code>false</code> if the user cancelled the operation.
* @throws IOException if an I/O error occurred while importing the library
*/
private boolean importLookAndFeelLibrary(AbstractFile library) throws IOException {
// Tries to import the file, but if a version of it is already present in the extensions folder,
// asks the user for confirmation.
AbstractFile destFile = ExtensionManager.getExtensionsFile(library.getName());
int collision = FileCollisionChecker.checkForCollision(library, destFile);
if(collision!=FileCollisionChecker.NO_COLLOSION) {
// Do not offer the multiple files mode options such as 'skip' and 'apply to all'
int action = new FileCollisionDialog(parent, parent, collision, library, destFile, false, false).getActionValue();
// User chose to overwrite the file
if(action==FileCollisionDialog.OVERWRITE_ACTION) {
// Simply continue and file will be overwritten
}
else if(action==FileCollisionDialog.OVERWRITE_IF_OLDER_ACTION) {
// Overwrite if the source is more recent than the destination
if(library.getDate()<=destFile.getDate())
return false;
// Simply continue and file will be overwritten
}
// User chose to cancel or closed the dialog
else {
return false;
}
}
return ExtensionManager.importLibrary(library, true);
}
public void run() {
java.util.List<Class<?>> newLookAndFeels;
setLookAndFeelsLoading(true);
try {
// Identifies all the look&feels contained by the new library and adds them to the list of custom
// If no look&feel was found, notifies the user.
if((newLookAndFeels = new ClassFinder().find(lookAndFeelLibrary, new LookAndFeelFilter())).isEmpty())
InformationDialog.showWarningDialog(this, Translator.get("prefs_dialog.no_look_and_feel"));
else if(importLookAndFeelLibrary(lookAndFeelLibrary)) {
String currentName;
if(customLookAndFeels == null)
customLookAndFeels = new Vector<String>();
// Adds all new instances to the list of custom look&feels.
for(int i = 0; i < newLookAndFeels.size(); i++) {
currentName = newLookAndFeels.get(i).getName();
if(!customLookAndFeels.contains(currentName)) {
customLookAndFeels.add(currentName);
try {WindowManager.installLookAndFeel(currentName);}
catch(Throwable e) {}
}
}
if(customLookAndFeels.isEmpty())
customLookAndFeels = null;
else
MuConfigurations.getPreferences().setVariable(MuPreference.CUSTOM_LOOK_AND_FEELS, customLookAndFeels, MuPreferences.CUSTOM_LOOK_AND_FEELS_SEPARATOR);
populateLookAndFeels();
}
}
catch(Exception e) {
LOGGER.debug("Exception caught", e);
InformationDialog.showErrorDialog(this);
}
setLookAndFeelsLoading(false);
}
private void importLookAndFeel() {
JFileChooser chooser; // Used to select the theme to import.
AbstractFile file; // Path to the theme to import.
// Initialises the file chooser.
chooser = createFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.addChoosableFileFilter(new ExtensionFileFilter("jar", Translator.get("prefs_dialog.jar_file")));
chooser.setDialogTitle(Translator.get("prefs_dialog.import_look_and_feel"));
chooser.setDialogType(JFileChooser.OPEN_DIALOG);
if(chooser.showDialog(parent, Translator.get("prefs_dialog.import")) == JFileChooser.APPROVE_OPTION) {
file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());
lastSelectedFolder = file.getParent();
// Makes sure the file actually exists - JFileChooser apparently doesn't enforce that properly in all look&feels.
if(!file.exists()) {
InformationDialog.showErrorDialog(this, Translator.get("this_file_does_not_exist", file.getName()));
return;
}
// Imports the JAR in a separate thread.
lookAndFeelLibrary = file;
new Thread(this).start();
}
}
// - Theme actions ----------------------------------------------------------
// --------------------------------------------------------------------------
private void setTypeLabel(Theme theme) {
String label;
if(theme.getType() == Theme.USER_THEME)
label = Translator.get("theme.custom");
else if(theme.getType() == Theme.PREDEFINED_THEME)
label = Translator.get("theme.built_in");
else
label = Translator.get("theme.add_on");
typeLabel.setText(Translator.get("prefs_dialog.theme_type", label));
}
private void resetThemeButtons(Theme theme) {
if(ignoreComboChanges)
return;
setTypeLabel(theme);
if(theme.getType() != Theme.CUSTOM_THEME) {
renameThemeButton.setEnabled(false);
deleteThemeButton.setEnabled(false);
}
else {
renameThemeButton.setEnabled(true);
if(ThemeManager.isCurrentTheme(theme))
deleteThemeButton.setEnabled(false);
else
deleteThemeButton.setEnabled(true);
}
}
/**
* Renames the specified theme.
* @param theme theme to rename.
*/
private void renameTheme(Theme theme) {
ThemeNameDialog dialog;
if((dialog = new ThemeNameDialog(parent, theme.getName())).wasValidated()) {
// If the rename operation was a success, makes sure the theme is located at its proper position.
try {
ThemeManager.renameCustomTheme(theme, dialog.getText());
themeComboBox.removeItem(theme);
insertTheme(theme);
}
catch(Exception e) {
// Otherwise, notifies the user.
InformationDialog.showErrorDialog(this, Translator.get("prefs_dialog.rename_failed", theme.getName()));
}
}
}
/**
* Deletes the specified theme.
* @param theme theme to delete.
*/
private void deleteTheme(Theme theme) {
// Asks the user whether he's sure he wants to delete the selected theme.
if(new QuestionDialog(parent, null, Translator.get("prefs_dialog.delete_theme", theme.getName()), parent,
new String[] {Translator.get("yes"), Translator.get("no")},
new int[] {YES_ACTION, NO_ACTION},
0).getActionValue() != YES_ACTION)
return;
// Deletes the selected theme and removes it from the list.
try {
ThemeManager.deleteCustomTheme(theme.getName());
themeComboBox.removeItem(theme);
}
catch(Exception e) {
InformationDialog.showErrorDialog(this);
}
}
/**
* Starts the theme editor on the specified theme.
* @param theme to edit.
*/
private void editTheme(Theme theme) {
// If the edited theme was modified, we must re-populate the list.
if(new ThemeEditorDialog(parent, theme).editTheme())
populateThemes(ThemeManager.getCurrentTheme());
}
/**
* Creates a file chooser initialised on the last selected folder.
*/
private JFileChooser createFileChooser() {
if(lastSelectedFolder == null)
return new JFileChooser();
return new JFileChooser((java.io.File)lastSelectedFolder.getUnderlyingFileObject());
}
private void insertTheme(Theme theme) {
int count;
int i;
count = themeComboBox.getItemCount();
for(i = 0; i < count; i++) {
if(((Theme)themeComboBox.getItemAt(i)).getName().compareTo(theme.getName()) >= 0) {
themeComboBox.insertItemAt(theme, i);
break;
}
}
if(i == count)
themeComboBox.addItem(theme);
themeComboBox.setSelectedItem(theme);
}
/**
* Imports a new theme in muCommander.
*/
private void importTheme() {
JFileChooser chooser; // Used to select the theme to import.
AbstractFile file; // Path to the theme to import.
// Initialises the file chooser.
chooser = createFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.addChoosableFileFilter(new ExtensionFileFilter("xml", Translator.get("prefs_dialog.xml_file")));
chooser.setDialogTitle(Translator.get("prefs_dialog.import_theme"));
chooser.setDialogType(JFileChooser.OPEN_DIALOG);
if(chooser.showDialog(parent, Translator.get("prefs_dialog.import")) == JFileChooser.APPROVE_OPTION) {
// Makes sure the file actually exists - JFileChooser apparently doesn't enforce that properly in all look&feels.
file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());
lastSelectedFolder = file.getParent();
if(!file.exists()) {
InformationDialog.showErrorDialog(this, Translator.get("this_file_does_not_exist", file.getName()));
return;
}
// Imports the theme and makes sure it appears in the combobox.
try {insertTheme(ThemeManager.importTheme((java.io.File)file.getUnderlyingFileObject()));}
// Notifies the user that something went wrong.
catch(Exception ex) {
InformationDialog.showErrorDialog(this, Translator.get("prefs_dialog.error_in_import", file.getName()));
}
}
}
/**
* Exports the specified theme.
* @param theme theme to export.
*/
private void exportTheme(Theme theme) {
JFileChooser chooser;
AbstractFile file;
chooser = createFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
chooser.addChoosableFileFilter(new ExtensionFileFilter("xml", Translator.get("prefs_dialog.xml_file")));
chooser.setDialogTitle(Translator.get("prefs_dialog.export_theme", theme.getName()));
if(chooser.showDialog(parent, Translator.get("prefs_dialog.export")) == JFileChooser.APPROVE_OPTION) {
file = FileFactory.getFile(chooser.getSelectedFile().getAbsolutePath());
lastSelectedFolder = file.getParent();
// Makes sure the file's extension is .xml.
try {
if(!"xml".equalsIgnoreCase(file.getExtension())) // Note: getExtension() may return null if no extension
file = lastSelectedFolder.getDirectChild(file.getName()+".xml");
int collision = FileCollisionChecker.checkForCollision(null, file);
if(collision!=FileCollisionChecker.NO_COLLOSION) {
// Do not offer the multiple files mode options such as 'skip' and 'apply to all'
int action = new FileCollisionDialog(parent, parent, collision, null, file, false, false).getActionValue();
// User chose to overwrite the file
if(action==FileCollisionDialog.OVERWRITE_ACTION) {
// Simply continue and file will be overwritten
}
// User chose to cancel or closed the dialog
else {
return;
}
}
// Exports the theme.
ThemeManager.exportTheme(theme, (java.io.File)file.getUnderlyingFileObject());
// If it was exported to the custom themes folder, reload the theme combobox to reflect the
// changes.
if(lastSelectedFolder.equals(ThemeManager.getCustomThemesFolder()))
populateThemes(theme);
}
// Notifies users of errors.
catch(Exception exception) {
InformationDialog.showErrorDialog(this, Translator.get("write_error"), Translator.get("cannot_write_file", file.getName()));
}
}
}
/**
* Duplicates the specified theme.
*/
private void duplicateTheme(Theme theme) {
try {insertTheme(ThemeManager.duplicateTheme(theme));}
catch(Exception e) {
InformationDialog.showErrorDialog(this);
}
}
// - Listener code ----------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Called when a button is pressed.
*/
public void actionPerformed(ActionEvent e) {
Theme theme;
theme = (Theme)themeComboBox.getSelectedItem();
// Theme combobox selection changed.
if(e.getSource() == themeComboBox)
resetThemeButtons(theme);
// Look and feel combobox selection changed.
else if(e.getSource() == lookAndFeelComboBox)
resetLookAndFeelButtons();
// Delete look and feel button has been pressed.
else if(e.getSource() == deleteLookAndFeelButton)
deleteSelectedLookAndFeel();
// Import look and feel button has been pressed.
else if(e.getSource() == importLookAndFeelButton)
importLookAndFeel();
// Rename button was pressed.
else if(e.getSource() == renameThemeButton)
renameTheme(theme);
// Delete button was pressed.
else if(e.getSource() == deleteThemeButton)
deleteTheme(theme);
// Edit button was pressed.
else if(e.getSource() == editThemeButton)
editTheme(theme);
// Import button was pressed.
else if(e.getSource() == importThemeButton)
importTheme();
// Export button was pressed.
else if(e.getSource() == exportThemeButton)
exportTheme(theme);
// Export button was pressed.
else if(e.getSource() == duplicateThemeButton)
duplicateTheme(theme);
}
// - File filter ------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Filter used to only display XML files in the JFileChooser.
* @author Nicolas Rinaudo
*/
private static class ExtensionFileFilter extends javax.swing.filechooser.FileFilter {
/** Extension to match. */
private String extension;
/** Filter's description. */
private String description;
/**
* Creates a new extension file filter that will match files with the specified extension.
* @param extension extension to match.
*/
public ExtensionFileFilter(String extension, String description) {
this.extension = extension;
this.description = description;
}
/**
* Returns <code>true</code> if the specified file should be displayed in the chooser.
*/
@Override
public boolean accept(java.io.File file) {
String ext;
// Directories are always displayed.
if(file.isDirectory())
return true;
// If the file has an extension, and it matches .xml, return true.
// Otherwise, return false.
if((ext = AbstractFile.getExtension(file.getName())) != null)
return extension.equalsIgnoreCase(ext);
return false;
}
@Override
public String getDescription() {return description;}
}
}