/*
* Copyright (C) 2010 Markus Echterhoff <tam@edu.uni-klu.ac.at>
*
* This file is part of EvoPaint.
*
* EvoPaint 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.
*
* This program 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 EvoPaint. If not, see <http://www.gnu.org/licenses/>.
*/
package evopaint.gui.rulesetmanager;
import com.thoughtworks.xstream.XStreamException;
import evopaint.Configuration;
import evopaint.gui.rulesetmanager.util.NamedObjectListCellRenderer;
import evopaint.pixel.rulebased.Rule;
import evopaint.pixel.rulebased.RuleSet;
import evopaint.pixel.rulebased.RuleSetCollection;
import evopaint.util.CollectionNode;
import evopaint.util.ExceptionHandler;
import evopaint.util.RuleSetNode;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
/**
*
* @author Markus Echterhoff <tam@edu.uni-klu.ac.at>
*/
public class JRuleSetBrowser extends JPanel implements TreeSelectionListener {
private Configuration configuration;
private JRuleSetTree tree;
private DefaultTreeModel treeModel;
private DefaultMutableTreeNode root;
private Component newDialogOwner;
private JButton browserBtnDelete;
private JButton browserBtnCopy;
private JButton browserBtnImport;
private JButton browserBtnExport;
private JRuleList jRuleListReference;
public JRuleSetBrowser(Configuration configuration, JRuleSetTree tree, JRuleList jRuleListReference) {
this.configuration = configuration;
this.tree = tree;
this.treeModel = (DefaultTreeModel)tree.getModel();
this.root = (DefaultMutableTreeNode)treeModel.getRoot();
this.jRuleListReference = jRuleListReference;
tree.addTreeSelectionListener(this);
setLayout(new BorderLayout());
setBackground(Color.WHITE);
setBorder(new LineBorder(Color.GRAY));
// don't set the preferred size! set the divider location instead
// or else the scrollpane will scroll even if the tree is empty
//setPreferredSize(new Dimension(250, 250));
JScrollPane scrollPaneForTree = new JScrollPane(tree,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPaneForTree.setBorder(null);
scrollPaneForTree.setViewportBorder(null);
// create buttons for control panel of browser
JPanel controlPanel = new JPanel();
controlPanel.setBackground(new Color(0xF2F2F5));
JButton browserBtnNew = new JButton(new ImageIcon(getClass().getResource("icons/button-add.png")));
browserBtnNew.setToolTipText("Opens a dialog to create a new collection or rule set");
newDialogOwner = browserBtnNew;
browserBtnNew.addActionListener(new BrowserBtnNewListener());
controlPanel.add(browserBtnNew);
browserBtnCopy = new JButton(new ImageIcon(getClass().getResource("icons/button-copy.png")));
browserBtnCopy.setToolTipText("Copies the selected rule set or collection");
browserBtnCopy.addActionListener(new BtnCopyListener());
browserBtnCopy.setEnabled(false);
controlPanel.add(browserBtnCopy);
browserBtnDelete = new JButton(new ImageIcon(getClass().getResource("icons/button-delete.png")));
browserBtnDelete.setToolTipText("Deletes the selected collection or rule set");
browserBtnDelete.addActionListener(new BtnDeleteListener());
browserBtnDelete.setEnabled(false);
controlPanel.add(browserBtnDelete);
browserBtnImport = new JButton(new ImageIcon(getClass().getResource("icons/button-import.png")));
browserBtnImport.setToolTipText("Opens an import dialog to import a rule set");
browserBtnImport.addActionListener(new BtnImportListener());
controlPanel.add(browserBtnImport);
browserBtnExport = new JButton(new ImageIcon(getClass().getResource("icons/button-export.png")));
browserBtnExport.setToolTipText("Exports the selected rule set to the clipboard. Paste anywhere with Ctrl-V");
browserBtnExport.addActionListener(new BtnExportListener());
browserBtnExport.setEnabled(false);
controlPanel.add(browserBtnExport);
add(scrollPaneForTree, BorderLayout.CENTER);
add(controlPanel, BorderLayout.SOUTH);
}
public void valueChanged(TreeSelectionEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)e.getPath().getLastPathComponent();
Object userObject = node.getUserObject();
if (userObject == null) {
browserBtnDelete.setEnabled(false);
browserBtnCopy.setEnabled(false);
browserBtnExport.setEnabled(false);
return;
}
browserBtnDelete.setEnabled(true);
browserBtnCopy.setEnabled(true);
if (userObject instanceof RuleSetCollection) {
browserBtnExport.setEnabled(false);
return;
}
if (userObject instanceof RuleSet) {
browserBtnExport.setEnabled(true);
return;
}
}
private class BrowserBtnNewListener implements ActionListener {
private JDialog dialog;
private JRadioButton collectionRadio;
private JRadioButton ruleSetRadio;
private JComboBox comboBoxCollections;
private JTextField nameField;
public void actionPerformed(ActionEvent e) {
dialog = new JDialog((JFrame)SwingUtilities.getWindowAncestor(newDialogOwner), "Edit Action", true);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.CENTER;
constraints.insets = new Insets(10, 10, 10, 10);
JPanel radioPanel = new JPanel();
radioPanel.setBorder(new TitledBorder("Type"));
ruleSetRadio = new JRadioButton("Rule Set");
if (root.getChildCount() == 0) {
ruleSetRadio.setEnabled(false);
}
radioPanel.add(ruleSetRadio);
collectionRadio = new JRadioButton("Rule Set Collection");
radioPanel.add(collectionRadio);
ButtonGroup group = new ButtonGroup();
group.add(collectionRadio);
group.add(ruleSetRadio);
dialog.add(radioPanel, constraints);
// setup combobox for collectin selection for rule set
DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
Enumeration children = root.children();
while (children != null && children.hasMoreElements()) {
comboBoxModel.addElement((CollectionNode)children.nextElement());
}
comboBoxCollections = new JComboBox(comboBoxModel);
comboBoxCollections.setRenderer(new NamedObjectListCellRenderer());
final JPanel comboBoxWrapper = new JPanel(new GridLayout());
comboBoxWrapper.setBorder(new TitledBorder("Add to Collection"));
comboBoxWrapper.add(comboBoxCollections);
comboBoxWrapper.setVisible(false);
constraints.gridy = 1;
dialog.add(comboBoxWrapper, constraints);
// intelligent preset
DefaultMutableTreeNode selectedNode =
(DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
if (selectedNode == null || selectedNode.getUserObject() == null) {
collectionRadio.setSelected(true);
comboBoxWrapper.setVisible(false);
} else if (selectedNode instanceof CollectionNode) {
comboBoxCollections.setSelectedItem(selectedNode);
ruleSetRadio.setSelected(true);
comboBoxWrapper.setVisible(true);
} else if (selectedNode instanceof RuleSetNode) {
CollectionNode collectionNode = (CollectionNode)selectedNode.getParent();
comboBoxCollections.setSelectedItem(collectionNode);
ruleSetRadio.setSelected(true);
comboBoxWrapper.setVisible(true);
} else {
assert(false);
}
collectionRadio.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (comboBoxWrapper.isVisible()) {
comboBoxWrapper.setVisible(false);
dialog.pack();
}
}
});
ruleSetRadio.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (false == comboBoxWrapper.isVisible()) {
comboBoxWrapper.setVisible(true);
dialog.pack();
}
}
});
// name field
JPanel nameFieldWrapper = new JPanel(new GridLayout());
nameFieldWrapper.setBorder(new TitledBorder("Name"));
nameField = new JTextField();
nameFieldWrapper.add(nameField);
constraints.gridy = 2;
dialog.add(nameFieldWrapper, constraints);
// and the control panel
JPanel controlPanel = new JPanel();
final JButton btnOK = new JButton("OK");
btnOK.addActionListener(new newDialogOKListener());
controlPanel.add(btnOK);
final JButton btnCancel = new JButton("Cancel");
btnCancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dialog.dispose();
}
});
controlPanel.add(btnCancel);
constraints.gridy = 3;
dialog.add(controlPanel, constraints);
dialog.pack();
dialog.setLocationRelativeTo(newDialogOwner);
nameField.requestFocusInWindow();
dialog.getRootPane().setDefaultButton(btnOK);
dialog.setVisible(true);
}
private class newDialogOKListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
dialog.dispose();
// creating a collection
if (collectionRadio.isSelected()) {
RuleSetCollection collection = new RuleSetCollection(
nameField.getText(),
"I am a sad collection, because I have no description but you can brighten up my day by clicking 'Edit' to describe me.");
// check if name is free
String desiredName = nameField.getText();
if (root.getChildCount() > 0) {
if (false == tree.isUniqueSiblingName(root, collection, desiredName)) {
return;
}
}
// add new collection to tree model
CollectionNode collectionNode = new CollectionNode(collection);
treeModel.insertNodeInto(collectionNode,
root, root.getChildCount());
tree.updateVisibleInsert(collectionNode);
} else { // creating a rule set
RuleSet ruleSet = new RuleSet(nameField.getText(),
"These rules are made for ruling, that's just what they'll do. One of these days these rule are gonna rule all over you.",
new ArrayList<Rule>());
// check if name is free
String desiredName = nameField.getText();
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)comboBoxCollections.getSelectedItem();
if (false == tree.isUniqueSiblingName(parentNode, ruleSet, desiredName)) {
return;
}
RuleSetNode ruleSetNode = new RuleSetNode(ruleSet);
// the following line is a bugfix for the bug where
// deletion of nodes that where inserted when their
// parent was not expanded causes the syntUI to eat null
// pointers. see below (around line 340) "WARNING"...
tree.expandPath(new TreePath(parentNode.getPath()));
treeModel.insertNodeInto(ruleSetNode, parentNode, parentNode.getChildCount());
tree.updateVisibleInsert(ruleSetNode);
}
}
}
}
private class BtnCopyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (jRuleListReference.isDirty()) {
jRuleListReference.clean(); // so that if you create a rule
// and immediatly copy it, the copy
// will not be empty. I hate polishing.
}
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
if (selectedNode == null) {
return;
}
// collection copy
if (selectedNode instanceof CollectionNode) {
RuleSetCollection collection =
(RuleSetCollection)selectedNode.getUserObject();
RuleSetCollection newCollection = new RuleSetCollection(collection);
// make sure the name of the copy is unique
boolean found = true;
String originalName = collection.getName().replaceAll(" *\\(\\d+\\)", "");
for (int i = 1; found == true; i++) {
newCollection.setName(originalName + " (" + i + ")");
found = false;
Enumeration siblingCollectionNodes = root.children();
while (siblingCollectionNodes.hasMoreElements()) {
CollectionNode node = (CollectionNode)siblingCollectionNodes.nextElement();
RuleSetCollection rsc = (RuleSetCollection)node.getUserObject();
if (rsc.getName().equals(newCollection.getName())) {
found = true;
break;
}
}
}
// create new collection node
CollectionNode newCollectionNode = new CollectionNode(newCollection);
// copy them rule sets to collection node too
Enumeration ruleSetNodes = selectedNode.children();
if (ruleSetNodes != null) {
while (ruleSetNodes.hasMoreElements()) {
RuleSetNode node = (RuleSetNode)ruleSetNodes.nextElement();
RuleSet ruleSet = (RuleSet)node.getUserObject();
RuleSet newRuleSet = new RuleSet(ruleSet);
RuleSetNode newRuleSetNode = new RuleSetNode(newRuleSet);
newCollectionNode.insert(newRuleSetNode, newCollectionNode.getChildCount());
}
}
// add collection with copied rule sets to tree and fire event
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
model.insertNodeInto(newCollectionNode, root, root.getChildCount());
// collapse the old collection
tree.collapsePath(new TreePath(selectedNode.getPath()));
tree.updateVisibleInsert(newCollectionNode);
} else { // rule set copy
CollectionNode collectionNode = (CollectionNode)selectedNode.getParent();
RuleSetCollection collection = (RuleSetCollection)
collectionNode.getUserObject();
RuleSet ruleSet = (RuleSet)selectedNode.getUserObject();
RuleSet newRuleSet = null;
try {
ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(outByteStream);
out.writeObject(ruleSet);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(outByteStream.toByteArray()));
newRuleSet = (RuleSet) in.readObject();
} catch (ClassNotFoundException ex) {
ExceptionHandler.handle(ex, true);
} catch (IOException ex) {
ExceptionHandler.handle(ex, true);
}
// make sure the name of the copy is unique
String originalName = ruleSet.getName().replaceAll(" *\\(\\d+\\)", "");
boolean found = true;
for (int i = 1; found == true; i++) {
newRuleSet.setName(originalName + " (" + i + ")");
found = false;
Enumeration siblingRuleSetNodes = collectionNode.children();
while (siblingRuleSetNodes.hasMoreElements()) {
RuleSetNode node = (RuleSetNode)
siblingRuleSetNodes.nextElement();
RuleSet rs = (RuleSet)node.getUserObject();
if (rs.getName().equals(newRuleSet.getName())) {
found = true;
break;
}
}
}
// add new rule set to tree
RuleSetNode newNode = new RuleSetNode(newRuleSet);
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
model.insertNodeInto(newNode, collectionNode, collectionNode.getChildCount());
tree.updateVisibleInsert(newNode);
}
}
}
private class BtnDeleteListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)
tree.getLastSelectedPathComponent();
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)
selectedNode.getParent();
assert(parentNode != null);
final int childIndex = parentNode.getIndex(selectedNode);
if (selectedNode == null) {
return;
}
// WARNING
// bugged. reproduce: create a rule set without expanding
// its collection node first, then delete it.
//
// as of JDK 1.6.0_15 this causes a NullPointerException
// when using any synth laf
// in javax.swing.plaf.synth.SynthTreeUI.paint(SynthTreeUI.java:297)
treeModel.removeNodeFromParent(selectedNode);
// this works, but will cause a structure change event to be posted
// instead of removed
//DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)selectedNode.getParent();
//parentNode.remove(selectedNode);
//treeModel.reload(parentNode);
tree.updateVisibleRemove(parentNode, childIndex);
}
}
private class BtnImportListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
new JImportRuleSetDialog(configuration, (JFrame)SwingUtilities.getWindowAncestor(browserBtnImport), tree);
}
}
private class BtnExportListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
RuleSetNode ruleSetNode = (RuleSetNode)
tree.getLastSelectedPathComponent();
RuleSet ruleSet = (RuleSet)ruleSetNode.getUserObject();
try {
String xml = configuration.xStream.toXML(ruleSet);
StringSelection contents = new StringSelection(xml);
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
cb.setContents(contents, null);
} catch (XStreamException ex) {
ExceptionHandler.handle(ex, false);
}
}
}
}