/*
* Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Substance Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tools.jitterbug;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractListModel;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceSkin;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
public class JColorSchemeList extends JComponent {
private SubstanceSkin.ColorSchemes schemes;
private JList schemeList;
private JPanel cardPanel;
private ColorSchemeListModel schemeListModel;
static final String LIST = "List";
static final String INSTRUCTIONAL = "Instructional";
private File schemesFile;
private boolean isModified;
private JButton moveUpButton;
private JButton moveDownButton;
private JButton renameButton;
private JButton deleteButton;
class ColorSchemeListModel extends AbstractListModel {
@Override
public int getSize() {
return (schemes == null) ? 0 : schemes.size();
}
@Override
public Object getElementAt(int index) {
if (schemes == null)
return null;
return schemes.get(index);
}
void fireContentsChanged() {
this.fireContentsChanged(schemeList, 0, getSize());
}
}
class ColorSchemeListRenderer extends SubstanceDefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
SubstanceColorScheme scheme = (SubstanceColorScheme) value;
return super.getListCellRendererComponent(list, scheme
.getDisplayName(), index, isSelected, cellHasFocus);
}
}
public JColorSchemeList() {
this.schemes = new SubstanceSkin.ColorSchemes();
this.schemeList = new JList();
this.schemeListModel = new ColorSchemeListModel();
this.schemeList.setModel(schemeListModel);
this.schemeList.setCellRenderer(new ColorSchemeListRenderer());
this.schemeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.schemeList.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
Object selected = schemeList.getSelectedValue();
firePropertyChange("selectedColorScheme", null,
selected);
}
});
JPanel bottomButtonsPanel = new JPanel(new FlowLayout(
FlowLayout.TRAILING));
JButton addColorScheme = new JButton();
addColorScheme.setToolTipText("Adds a new color scheme");
addColorScheme.setIcon(new ImageIcon(JColorSchemeList.class
.getClassLoader().getResource("tools/jitterbug/add.png")));
addColorScheme.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
String newName = getNewColorSchemeName(null);
if (newName == null)
return;
Color[] colors = new Color[] { Color.white,
Color.white, Color.white, Color.white,
Color.white, Color.white, Color.black };
SubstanceColorScheme newScheme = SubstanceColorSchemeUtilities
.getLightColorScheme(newName, colors);
schemes.add(newScheme);
schemeListModel.fireContentsChanged();
schemeList.setSelectedValue(newScheme, true);
setModified(true);
}
});
}
});
bottomButtonsPanel.add(addColorScheme);
bottomButtonsPanel.add(Box.createHorizontalStrut(10));
moveUpButton = new JButton();
moveUpButton
.setToolTipText("Moves the currently selected color scheme up");
moveUpButton.setEnabled(false);
moveUpButton.setIcon(new ImageIcon(JColorSchemeList.class
.getClassLoader().getResource("tools/jitterbug/arrow_up.png")));
moveUpButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
.getSelectedValue();
schemes.switchWithPrevious(selected.getDisplayName());
schemeListModel.fireContentsChanged();
schemeList.setSelectedValue(selected, true);
setModified(true);
}
});
}
});
bottomButtonsPanel.add(moveUpButton);
moveDownButton = new JButton();
moveDownButton
.setToolTipText("Moves the currently selected color scheme down");
moveDownButton.setEnabled(false);
moveDownButton
.setIcon(new ImageIcon(JColorSchemeList.class.getClassLoader()
.getResource("tools/jitterbug/arrow_down.png")));
moveDownButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
.getSelectedValue();
schemes.switchWithNext(selected.getDisplayName());
schemeListModel.fireContentsChanged();
schemeList.setSelectedValue(selected, true);
setModified(true);
}
});
}
});
bottomButtonsPanel.add(moveDownButton);
bottomButtonsPanel.add(Box.createHorizontalStrut(10));
renameButton = new JButton();
renameButton
.setToolTipText("Rename the currently selected color scheme");
renameButton.setEnabled(false);
renameButton.setIcon(new ImageIcon(JColorSchemeList.class
.getClassLoader().getResource(
"tools/jitterbug/chart_line_edit.png")));
renameButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
.getSelectedValue();
String newName = getNewColorSchemeName(selected
.getDisplayName());
if (newName == null)
return;
if (newName.equals(selected.getDisplayName()))
return;
boolean isLight = !selected.isDark();
Color ultraDark = selected.getUltraDarkColor();
Color dark = selected.getDarkColor();
Color mid = selected.getMidColor();
Color light = selected.getLightColor();
Color extraLight = selected.getExtraLightColor();
Color ultraLight = selected.getUltraLightColor();
Color foreground = selected.getForegroundColor();
Color[] colors = new Color[] { ultraLight, extraLight,
light, mid, dark, ultraDark, foreground };
SubstanceColorScheme renamedScheme = isLight ? SubstanceColorSchemeUtilities
.getLightColorScheme(newName, colors)
: SubstanceColorSchemeUtilities
.getDarkColorScheme(newName, colors);
schemes.replace(selected.getDisplayName(),
renamedScheme);
schemeListModel.fireContentsChanged();
schemeList.getSelectionModel().clearSelection();
schemeList.setSelectedValue(renamedScheme, true);
setModified(true);
}
});
}
});
bottomButtonsPanel.add(renameButton);
deleteButton = new JButton();
deleteButton
.setToolTipText("Removes the currently selected color scheme");
deleteButton.setEnabled(false);
deleteButton.setIcon(new ImageIcon(JColorSchemeList.class
.getClassLoader().getResource("tools/jitterbug/delete.png")));
deleteButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
SubstanceColorScheme selected = (SubstanceColorScheme) schemeList
.getSelectedValue();
schemes.delete(selected.getDisplayName());
schemeListModel.fireContentsChanged();
schemeList.getSelectionModel().clearSelection();
setModified(true);
}
});
}
});
bottomButtonsPanel.add(deleteButton);
this.schemeList.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
int selectedIndex = schemeList.getSelectedIndex();
boolean hasSelection = (selectedIndex >= 0);
renameButton.setEnabled(hasSelection);
deleteButton.setEnabled(hasSelection);
moveUpButton.setEnabled(hasSelection
&& (selectedIndex > 0));
moveDownButton.setEnabled(hasSelection
&& (selectedIndex < (schemes.size() - 1)));
}
});
JPanel mainControlPanel = new JPanel(new BorderLayout());
mainControlPanel.add(new JScrollPane(this.schemeList),
BorderLayout.CENTER);
mainControlPanel.add(bottomButtonsPanel, BorderLayout.SOUTH);
// Set up a card panel. It has two cards:
// * instructional text
// * list of color schemes with button controls
this.cardPanel = new JPanel(new CardLayout());
JTextArea textArea = new JTextArea(
"List of color schemes. Use one of the following:\n * Drag a .colorschemes file here\n * Click the \"New\" button below");
textArea.setBorder(new EmptyBorder(10, 8, 0, 8));
textArea.setEditable(false);
textArea.setOpaque(false);
textArea.setRows(9);
// textArea.setColumns(20);
this.cardPanel.add(textArea, INSTRUCTIONAL);
this.cardPanel.add(mainControlPanel, LIST);
this.setLayout(new BorderLayout());
this.add(this.cardPanel, BorderLayout.CENTER);
// constrain the preferred size so that the layout doesn't
// change once the control panel is shown
this.setPreferredSize(textArea.getPreferredSize());
}
public void setColorSchemeList(File file) {
if (!this.checkModifiedStateAndSaveIfNecessary()) {
return;
}
try {
this.schemesFile = file;
if (file != null) {
this.schemes = SubstanceSkin.getColorSchemes(file.toURI()
.toURL());
} else {
this.schemes = new SubstanceSkin.ColorSchemes();
}
// switch to the list view
CardLayout cl = (CardLayout) (cardPanel.getLayout());
cl.show(cardPanel, LIST);
// let the view know that the model has changed
schemeListModel.fireContentsChanged();
// update the buttons state
if (file == null) {
moveUpButton.setEnabled(false);
moveDownButton.setEnabled(false);
deleteButton.setEnabled(false);
renameButton.setEnabled(false);
}
this.setModified(false);
} catch (Exception exc) {
exc.printStackTrace();
}
}
public boolean checkModifiedStateAndSaveIfNecessary() {
if (!this.isModified) {
return true;
}
// does the user want to save modifications?
String fileName = (this.schemesFile == null) ? "Unsaved"
: this.schemesFile.getName();
int userSelection = JOptionPane.showConfirmDialog(SwingUtilities
.getWindowAncestor(this),
"Do you want to save the changes to '" + fileName + "'?",
"Modified contents", JOptionPane.YES_NO_CANCEL_OPTION);
if (userSelection == JOptionPane.CANCEL_OPTION) {
return false;
}
if (userSelection == JOptionPane.YES_OPTION) {
if (this.schemesFile != null) {
// ask to save
this.save();
} else {
this.saveAs();
}
}
return true;
}
private String getFileNameForSaving() {
JFileChooser jfc = new JFileChooser();
FileNameExtensionFilter filter = new FileNameExtensionFilter(
"Color scheme files", "colorschemes");
jfc.setFileFilter(filter);
int returnVal = jfc.showSaveDialog(SwingUtilities
.getWindowAncestor(this));
if (returnVal == JFileChooser.APPROVE_OPTION) {
String res = jfc.getSelectedFile().getAbsolutePath();
if (!res.endsWith(".colorschemes"))
res += ".colorschemes";
return res;
}
return null;
}
private String getNewColorSchemeName(String nameToStartWith) {
String result = JOptionPane.showInputDialog(SwingUtilities
.getWindowAncestor(this), "Type the color scheme name",
nameToStartWith);
if (result == null)
return null;
if (result.equals(nameToStartWith))
return result;
result = result.trim();
// check characters
Pattern pattern = Pattern.compile("[a-zA-Z ]+");
Matcher matcher = pattern.matcher(result);
if (!matcher.matches()) {
JOptionPane.showMessageDialog(SwingUtilities
.getWindowAncestor(this), "Only use letters and spaces",
"Name invalid", JOptionPane.ERROR_MESSAGE);
return null;
}
// check for uniqueness
SubstanceColorScheme existing = this.schemes.get(result);
if (existing != null) {
JOptionPane.showMessageDialog(SwingUtilities
.getWindowAncestor(this), "Name already exists",
"Name clash", JOptionPane.ERROR_MESSAGE);
return null;
}
return result;
}
public void save() {
this.saveAs(this.schemesFile);
}
public void saveAs() {
// ask for a filename for the save
String fileName = this.getFileNameForSaving();
if (fileName != null) {
File file = new File(fileName);
this.saveAs(file);
}
}
private void saveAs(File file) {
PrintStream printStream = null;
try {
printStream = new PrintStream(new FileOutputStream(file));
for (int i = 0; i < this.schemes.size(); i++) {
SubstanceColorScheme colorScheme = this.schemes.get(i);
String encodedColorScheme = colorScheme.toString();
printStream.println(encodedColorScheme);
printStream.println();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
if (printStream != null) {
printStream.close();
}
this.schemesFile = file;
this.setModified(false);
}
public void setModified(boolean isModified) {
if (this.isModified == isModified)
return;
boolean old = this.isModified;
this.isModified = isModified;
this.firePropertyChange("modified", old, isModified);
}
public File getCurrentFile() {
return this.schemesFile;
}
public void updateColorScheme(SubstanceColorScheme colorScheme) {
SubstanceColorScheme existing = this.schemes.get(colorScheme
.getDisplayName());
if (existing == null)
throw new IllegalArgumentException("Color scheme "
+ colorScheme.getDisplayName() + " not found");
this.schemes.replace(colorScheme.getDisplayName(), colorScheme);
}
public boolean isModified() {
return isModified;
}
}