/*
* This file is part of Alida, a Java library for
* Advanced Library for Integrated Development of Data Analysis Applications.
*
* Copyright (C) 2010 - @YEAR@
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*
* Fore more information on Alida, visit
*
* http://www.informatik.uni-halle.de/alida/
*
*/
package de.unihalle.informatik.Alida.dataio.provider.swing;
import de.unihalle.informatik.Alida.annotations.ALDDataIOProvider;
import de.unihalle.informatik.Alida.dataio.ALDDataIOManagerSwing;
import de.unihalle.informatik.Alida.dataio.provider.ALDDataIOSwingInitialGUIValueDefaultHandler;
import de.unihalle.informatik.Alida.dataio.provider.helpers.ALDCollectionDataIOHelper;
import de.unihalle.informatik.Alida.dataio.provider.swing.components.ALDSwingComponent;
import de.unihalle.informatik.Alida.dataio.provider.swing.events.ALDSwingValueChangeEvent;
import de.unihalle.informatik.Alida.dataio.provider.swing.events.ALDSwingValueChangeListener;
import de.unihalle.informatik.Alida.dataio.provider.swing.events.ALDSwingValueChangeReporter;
import de.unihalle.informatik.Alida.exceptions.*;
import de.unihalle.informatik.Alida.exceptions.ALDDataIOProviderException.ALDDataIOProviderExceptionType;
import de.unihalle.informatik.Alida.operator.ALDParameterDescriptor;
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
/**
* Class for generic loading/saving of collections from/to GUI in Alida.
*
* @author moeller
*/
@ALDDataIOProvider
public class ALDCollectionDataIOSwing
extends ALDDataIOSwingInitialGUIValueDefaultHandler {
/**
* Interface method to announce class for which IO is provided for.
*
* @return Collection of classes provided.
*/
@Override
public Collection<Class<?>> providedClasses() {
LinkedList<Class<?>> classes = new LinkedList<Class<?>>();
classes.add(Collection.class);
return classes;
}
/**
* Generic reading of collections.
*/
/* (non-Javadoc)
* @see de.unihalle.informatik.Alida.helpers.ALDDataIOSwing#createGUIElement(java.lang.Class, java.lang.Object)
*/
@Override
public ALDSwingComponent createGUIElement(
Field field, Class<?> cl, Object obj, ALDParameterDescriptor descr) {
return new CollectionConfigButton(field, cl, obj, descr);
}
@Override
public void setValue(
Field field, Class<?> cl, ALDSwingComponent guiElement, Object value)
throws ALDDataIOProviderException {
if (!(guiElement instanceof CollectionConfigButton))
throw new ALDDataIOProviderException(
ALDDataIOProviderExceptionType.INVALID_GUI_ELEMENT,
"CollectionDataIO: setValue() received invalid GUI element!");
((CollectionConfigButton)guiElement).setValue(field, cl, value);
}
@Override
public Object readData(
Field field, Class<?> cl, ALDSwingComponent guiElement)
throws ALDDataIOProviderException {
if (!(guiElement instanceof CollectionConfigButton))
throw new ALDDataIOProviderException(
ALDDataIOProviderExceptionType.INVALID_GUI_ELEMENT,
"CollectionDataIO: readData received invalid GUI element!");
return ((CollectionConfigButton)guiElement).readData(field, cl);
}
@Override
public JComponent writeData(Object obj, ALDParameterDescriptor descr)
throws ALDDataIOProviderException {
if (!(obj instanceof Collection))
throw new ALDDataIOProviderException(
ALDDataIOProviderExceptionType.OBJECT_TYPE_ERROR,
"CollectionDataIO: object to write has wrong type!");
// return a button to show a window with the elements
return new CollectionShowButton(obj, descr);
}
/**
* GUI element for configuring collections.
* <p>
* This button has a collection configuration window attached to it
* where specific data is stored and accessable.
*
* @author moeller
*/
private class CollectionConfigButton extends ALDSwingComponent
implements ALDSwingValueChangeListener {
/**
* Button to open configuration window.
*/
private JButton confButton;
/**
* Collection configuration window.
*/
private CollectionConfigWindow confWin;
/**
* Constructor.
*
* @param field Field of collection.
* @param cl Class of collection.
* @param obj Default object.
* @param descr Optional descriptor with additional information.
*/
public CollectionConfigButton(Field field, Class<?> cl, Object obj,
ALDParameterDescriptor descr) {
this.confWin = new CollectionConfigWindow(field, cl, obj, descr);
this.confWin.addValueChangeEventListener(this);
this.confButton = new JButton("Configure Collection...");
this.confButton.setActionCommand("configButtonPressed");
this.confButton.addActionListener(this.confWin);
}
@Override
public JButton getJComponent() {
return this.confButton;
}
/**
* Gets the data from the configuration window.
*
* @param field Field of collection.
* @param cl Class of collection.
* @return Current data.
*/
public Object readData(Field field, Class<?> cl) {
return this.confWin.readData(field, cl);
}
/**
* Sets new values in configuration window.
*
* @param field Field of collection.
* @param cl Class of collection.
* @param value New value.
*/
public void setValue(@SuppressWarnings("unused") Field field,
@SuppressWarnings("unused") Class<?> cl, Object value) {
this.confWin.setValue(value);
}
@Override
public void handleValueChangeEvent(ALDSwingValueChangeEvent event) {
this.fireALDSwingValueChangeEvent(event);
}
@Override
public void disableComponent() {
this.confWin.disableComponent();
}
@Override
public void enableComponent() {
this.confWin.enableComponent();
this.confButton.setEnabled(true);
}
@Override
public void dispose() {
// close the associated configuration window
this.confWin.dispose();
}
}
/**
* GUI element for displaying collections.
* <p>
* This button opens a window displaying a collection.
*
* @author moeller
*/
private class CollectionShowButton extends JButton
implements ActionListener {
/**
* Data to be displayed.
*/
private Object data;
/**
* Descriptor associated with parameter object belongs to.
*/
private ALDParameterDescriptor descriptor;
/**
* Constructor.
* @param obj Object to be visualized on button press.
* @param descr Optional descriptor with additional information.
*/
public CollectionShowButton(Object obj, ALDParameterDescriptor descr) {
super("Show collection data...");
this.setActionCommand("showButtonPressed");
this.addActionListener(this);
this.data = obj;
this.descriptor = descr;
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("showButtonPressed")) {
Collection<?> c = (Collection<?>)this.data;
JFrame win = new JFrame();
// win.setSize(375, 25 * c.size());
JPanel winPanel = new JPanel();
GridLayout grl = new GridLayout(c.size(),1);
winPanel.setLayout(grl);
int objectID = -1;
for (Object o: c) {
// objects being null are skipped
if (o == null)
continue;
// update object ID counter
++objectID;
try {
// get a component for a single element of the collection...
ALDParameterDescriptor p = new ALDParameterDescriptor(
this.descriptor.getName(), this.descriptor.getClass(),
this.descriptor.getExplanation(),
"Show entry " + objectID + "...",
this.descriptor.isRequired(), this.descriptor.getField(),
this.descriptor.getDataIOOrder(),
this.descriptor.getHandlingMode(),
this.descriptor.getCallback(),
this.descriptor.parameterModificationMode(),
this.descriptor.isInfo());
JComponent comp =
ALDDataIOManagerSwing.getInstance().writeData(o, p);
if (comp == null) {
String type = o.getClass().getSimpleName();
Object[] options = { "OK" };
JOptionPane.showOptionDialog(null,
"Null component received for type " + type + "!", "Warning",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
continue;
}
winPanel.add(comp);
} catch (ALDDataIOException e1) {
String pureMsg = "Unknown error!";
if (e1 instanceof ALDDataIOProviderException) {
switch (((ALDDataIOProviderException) e1).getType())
{
case SYNTAX_ERROR:
case OBJECT_TYPE_ERROR:
case OBJECT_INSTANTIATION_ERROR:
case FILE_IO_ERROR:
case INVALID_GUI_ELEMENT:
case SET_VALUE_FAILED:
case UNSPECIFIED_ERROR:
pureMsg = e1.getCommentString();
break;
}
}
else if (e1 instanceof ALDDataIOManagerException) {
switch (((ALDDataIOManagerException) e1).getType())
{
case NO_PROVIDER_FOUND:
case UNSPECIFIED_ERROR:
pureMsg = e1.getCommentString();
break;
}
}
Object[] options = { "OK" };
JOptionPane.showOptionDialog(null,
"Displaying collection failed! Reason:\n" + pureMsg, "Warning",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
}
}
win.add(winPanel);
if (this.descriptor != null)
win.setTitle("Contents of collection <"
+ this.descriptor.getLabel() + ">");
win.setVisible(true);
win.validate();
win.pack();
}
}
}
/**
* Collection configuration window.
*
* @author moeller
*/
private class CollectionConfigWindow extends ALDSwingValueChangeReporter
implements ActionListener, ALDSwingValueChangeListener {
/**
* Fixed minimal width of window.
*/
private static final int frameWidthMin = 500;
/**
* Fixed minimal height of the configuration window.
*/
private static final int frameHeightMin = 300;
/**
* Main frame.
*/
private JFrame window;
/**
* Main panel of main frame.
*/
private JPanel mainPanel = null;
/**
* Scroller element for collection elements.
*/
private JScrollPane scroller = null;
/**
* Button to add an element.
*/
private JButton addButton;
/**
* Button to delete an element;
*/
private JButton delButton;
/**
* Button to move an element upwards.
*/
private JButton upButton;
/**
* Button to move an element downwards.
*/
private JButton downButton;
/**
* Button to close the configuration window.
*/
private JButton closeButton;
/**
* Flag to remember if window is enabled or disabled.
*/
private boolean isEnabled;
/**
* Stores the index of last element added (may vary due to element swaps).
*/
private int lastAdded = 0;
/**
* Collection element field.
*/
private Field elemField;
/**
* Collection element type.
*/
private Type elemFieldType;
/**
* Collection element class.
*/
private Class<?> elemClass;
/**
* Default collection object.
*/
private Object defObject;
/**
* Parameter descriptor of associated parameter.
*/
private ALDParameterDescriptor paramDescriptor;
/**
* List of current GUI components in window.
*/
private LinkedList<ALDSwingComponent> elemComps =
new LinkedList<ALDSwingComponent>();
/**
* Number of elements.
*/
private int elemCounter = 0;
/**
* Default constructor.
* @param field Field to specify input data objects.
* @param cl Class of collection elements.
* @param obj Initial value of collection.
* @param descr Optional descriptor for additional information.
*/
public CollectionConfigWindow(Field field,
@SuppressWarnings("unused") Class<?> cl, Object obj,
ALDParameterDescriptor descr) {
this.defObject= obj;
this.elemField = field;
this.elemFieldType = ALDCollectionDataIOHelper.lookupType(field);
this.elemClass = (Class<?>)this.elemFieldType;
this.paramDescriptor = descr;
// initialize the window
this.window = new JFrame();
this.window.setResizable(true);
String title = "unknown";
if (descr != null) title = descr.getLabel();
String type = this.elemClass.getSimpleName();
this.window.setTitle("Collection <" +title+ ">, "
+ "element type: <" + type + ">");
this.window.setSize(frameWidthMin, frameHeightMin);
this.window.setPreferredSize(
new Dimension(frameWidthMin, frameHeightMin));
this.window.pack();
this.elemComps.clear();
this.elemCounter = 0;
try {
if (this.defObject instanceof Collection<?>) {
for (Object c: (Collection<?>)this.defObject) {
ALDSwingComponent elemComp =
ALDDataIOManagerSwing.getInstance().createGUIElement(
// this.elemField,this.elemClass, c, null);
this.elemField, c.getClass(), c, null);
// register window for value changes
elemComp.addValueChangeEventListener(this);
this.elemComps.add(elemComp);
this.elemCounter++;
}
}
} catch (ALDDataIOException e) {
Object[] options = { "OK" };
JOptionPane.showOptionDialog(null,
"Initializing collection view failed! Element invalid because...\n"+
e.getCommentString(),
"Warning",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
}
this.isEnabled = true;
}
/**
* Extracts current collection data.
*
* @param field Field of collection elements.
* @param cl Class of collection elements.
* @return Current collection.
*/
@SuppressWarnings("unchecked")
public Collection<?> readData(Field field, Class<?> cl) {
if (this.elemComps.size() == 0)
return null;
Collection<Object> res = null;
try {
res = (Collection<Object>)cl.newInstance();
// save current values
for (ALDSwingComponent c: this.elemComps) {
res.add(ALDDataIOManagerSwing.getInstance().readData(
field, this.elemClass, c));
}
} catch (Exception e) {
System.err.println("ALDCollectionDataIOSwing::readData - error!!!");
return null;
}
return res;
}
/**
* Updates current collection data.
*
* @param value New value.
*/
@SuppressWarnings("unchecked")
public void setValue(Object value) {
Collection<Object> coll = (Collection<Object>)value;
// if value is null, just ignore the call to this function
if (coll == null) {
return;
}
// add new values
try {
// adjust size of list with components
int elements = this.elemCounter;
if (this.elemComps.size() < coll.size()) {
for (int i=0; i<coll.size() - elements; ++i) {
ALDSwingComponent comp =
ALDDataIOManagerSwing.getInstance().createGUIElement(
this.elemField, this.elemClass, null, null);
comp.addValueChangeEventListener(this);
this.elemComps.add(comp);
this.elemCounter++;
this.lastAdded= this.elemCounter-1;
}
}
else if (this.elemComps.size() > coll.size()) {
for (int i=0; i< elements - coll.size(); ++i) {
// remove last element, dispose all its resources
ALDSwingComponent obsoleteComp = this.elemComps.removeLast();
obsoleteComp.dispose();
this.elemCounter--;
}
}
// update component values
int i=0;
for (Object obj: coll) {
// skip objects in collection which are null
if (obj == null) {
continue;
}
if ( obj.getClass().equals(this.elemClass)
|| this.elemClass.isAssignableFrom(obj.getClass()))
ALDDataIOManagerSwing.getInstance().setValue(
this.elemField, this.elemClass, this.elemComps.get(i), obj);
else {
ALDSwingComponent obsoleteComp = this.elemComps.get(i);
obsoleteComp.dispose();
ALDSwingComponent comp =
ALDDataIOManagerSwing.getInstance().createGUIElement(
this.elemField, this.elemClass, null, null);
comp.addValueChangeEventListener(this);
this.elemComps.set(i, comp);
ALDDataIOManagerSwing.getInstance().setValue(
this.elemField, obj.getClass(), this.elemComps.get(i), obj);
}
++i;
}
this.updateWindow();
} catch (Exception e) {
System.err.println("ALDCollectionDataIOSwing::setValue() - error!!!");
e.printStackTrace();
}
}
/**
* Deactivates the configuration window to prohibit value changes.
*/
public void disableComponent() {
this.isEnabled = false;
for (ALDSwingComponent comp: this.elemComps)
comp.disableComponent();
if (this.downButton != null)
this.downButton.setEnabled(false);
if (this.addButton != null)
this.addButton.setEnabled(false);
if (this.upButton != null)
this.upButton.setEnabled(false);
if (this.delButton != null)
this.delButton.setEnabled(false);
}
/**
* Reactivates the configuration window to allow for value changes.
*/
public void enableComponent() {
this.isEnabled = true;
for (ALDSwingComponent comp: this.elemComps)
comp.enableComponent();
if (this.downButton != null)
this.downButton.setEnabled(true);
if (this.addButton != null)
this.addButton.setEnabled(true);
if (this.upButton != null)
this.upButton.setEnabled(true);
if (this.delButton != null)
this.delButton.setEnabled(true);
}
/**
* Disposes this window and all sub-components.
*/
public void dispose() {
for (ALDSwingComponent comp : this.elemComps)
comp.dispose();
this.window.dispose();
}
/**
* Updates the collection in the window.
* <p>
* This function is called each time an element is added or removed or
* if two elements have been swapped.
*/
private void updateWindow() {
int wWidth = this.window.getWidth() < frameWidthMin ?
frameWidthMin : this.window.getWidth();
int wHeight = this.window.getHeight() < frameHeightMin ?
frameHeightMin : this.window.getHeight();
// on the first call, init the main panel
if (this.mainPanel == null) {
this.mainPanel = new JPanel();
BorderLayout bl = new BorderLayout();
this.mainPanel.setLayout(bl);
// add buttons
GridLayout gl = new GridLayout(1,5);
JPanel tmpPanel = new JPanel();
tmpPanel.setLayout(gl);
this.addButton = new JButton(" + ");
this.addButton.setActionCommand("addElement");
this.addButton.addActionListener(this);
this.delButton = new JButton(" - ");
this.delButton.setActionCommand("delElement");
this.delButton.addActionListener(this);
this.upButton = new JButton("Up");
this.upButton.setActionCommand("upElement");
this.upButton.addActionListener(this);
this.downButton = new JButton("Down");
this.downButton.setActionCommand("downElement");
this.downButton.addActionListener(this);
this.closeButton = new JButton("Close");
this.closeButton.setActionCommand("close");
this.closeButton.addActionListener(this);
tmpPanel.add(this.addButton);
tmpPanel.add(this.delButton);
tmpPanel.add(this.upButton);
tmpPanel.add(this.downButton);
tmpPanel.add(this.closeButton);
this.mainPanel.add(tmpPanel,BorderLayout.NORTH);
this.window.add(this.mainPanel);
}
else {
this.mainPanel.remove(this.scroller);
}
// editable list of elements
JPanel elementPanel = new JPanel();
GridLayout glep = new GridLayout(this.elemCounter, 1, 5, 1);
elementPanel.setLayout(glep);
for (ALDSwingComponent c: this.elemComps) {
elementPanel.add(c.getJComponent());
}
this.scroller = new JScrollPane(elementPanel);
this.mainPanel.add(this.scroller, BorderLayout.CENTER);
// scale window to a proper size
this.window.validate();
int pWidth =
this.window.getWidth() < wWidth ? wWidth : this.window.getWidth();
int pHeight =
this.window.getHeight() < wHeight ? wHeight : this.window.getHeight();
this.window.setPreferredSize(new Dimension(pWidth, pHeight));
this.window.pack();
this.window.repaint();
}
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("configButtonPressed")) {
this.updateWindow();
this.window.setVisible(true);
}
else if (cmd.equals("addElement")) {
// if window is disabled, ignore clicks
if (!this.isEnabled)
return;
try {
ALDSwingComponent comp =
ALDDataIOManagerSwing.getInstance().createGUIElement(
this.elemField, this.elemClass, null, null);
comp.addValueChangeEventListener(this);
this.elemComps.add(comp);
this.elemCounter++;
this.lastAdded= this.elemCounter-1;
this.updateWindow();
} catch (ALDDataIOException e1) {
Object[] options = { "OK" };
JOptionPane.showOptionDialog(null,
"Adding element failed! Current collection invalid!\n" +
"Reason:\n" + e1.getCommentString(),
"Warning",JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE,
null, options, options[0]);
}
}
else if (cmd.equals("delElement")) {
// if window is disabled, ignore clicks
if (!this.isEnabled)
return;
if (!(this.elemCounter==0)) {
// remove last element, dispose all its resources
ALDSwingComponent obsoleteComp = this.elemComps.removeLast();
obsoleteComp.dispose();
--this.elemCounter;
}
this.updateWindow();
}
// move the recently added element one position up
else if (cmd.equals("downElement")) {
// if window is disabled, ignore clicks
if (!this.isEnabled)
return;
// check if element has meanwhile been deleted,
// if so, just ignore the request
if (this.lastAdded >= this.elemComps.size() - 1)
return;
ALDSwingComponent ccomp = this.elemComps.get(this.lastAdded);
ALDSwingComponent cnext = this.elemComps.get(this.lastAdded + 1);
this.elemComps.set(this.lastAdded, cnext);
this.elemComps.set(this.lastAdded+1, ccomp);
++this.lastAdded;
this.updateWindow();
}
else if (cmd.equals("upElement")) {
// if window is disabled, ignore clicks
if (!this.isEnabled)
return;
if (this.lastAdded == 0)
return;
// check if element has meanwhile been deleted,
// if so, just ignore the request
if (this.lastAdded >= this.elemComps.size())
return;
ALDSwingComponent ccomp = this.elemComps.get(this.lastAdded);
ALDSwingComponent cnext = this.elemComps.get(this.lastAdded - 1);
this.elemComps.set(this.lastAdded, cnext);
this.elemComps.set(this.lastAdded-1, ccomp);
--this.lastAdded;
this.updateWindow();
}
else if (cmd.equals("close")) {
this.window.setVisible(false);
}
}
@Override
public void handleValueChangeEvent(ALDSwingValueChangeEvent event) {
this.fireALDSwingValueChangeEvent(event);
}
}
}