package lu.tudor.santec.gecamed.formeditor.gui.component;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.event.MouseWheelListener;
import java.awt.font.TextAttribute;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.TransferHandler;
import javax.swing.border.LineBorder;

import lu.tudor.santec.gecamed.core.gui.GECAMedColors;
import lu.tudor.santec.gecamed.core.gui.IconFetcher;
import lu.tudor.santec.gecamed.formeditor.gui.FormEditorModule;
import lu.tudor.santec.gecamed.formeditor.gui.FormWidgets;
import lu.tudor.santec.gecamed.formeditor.gui.controller.EditableScrollingListener;
import lu.tudor.santec.gecamed.formeditor.gui.controller.FormController;
import lu.tudor.santec.gecamed.formeditor.gui.exception.NotProperInitializedException;
import lu.tudor.santec.gecamed.formeditor.gui.model.ComboBoxElement;
import lu.tudor.santec.gecamed.formeditor.gui.model.FormEditorModel;
import lu.tudor.santec.gecamed.formeditor.gui.model.FormModel;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jdom.Element;

import com.jgoodies.forms.layout.CellConstraints;


/**
 * @author Jens Ferring
 * 
 *         This is the Parent class for all Types of components, that can be
 *         added to the editor. It contains all important attributes and
 *         methods, but the main attribute, the component is only a
 *         JComponent and has to be specified in the child class. If the
 *         type is a dummy, it is automatically made to a dropTarget, if it
 *         is a "real"-component, it is automatically made draggable.
 */
public abstract class EditableComponent extends JPanel
{
	/* ======================================== */
	// 		CONSTANTS
	/* ======================================== */
	
	private static final long				serialVersionUID			= 1L;
	
	/** the logger Object for this class */
	protected static Logger					logger						= Logger.getLogger(EditableComponent.class.getName());
	
	public static final MouseWheelListener	scrollingListener			= new EditableScrollingListener();
	
	// default listener method position
	public static final int					SUPER_LISTENER_METHOD_SIZE	= 13;
	public static final int					MOUSE_CLICKED				= 0;
	public static final int					MOUSE_ENTERED				= 1;
	public static final int					MOUSE_EXITED				= 2;
	public static final int					MOUSE_PRESSED				= 3;
	public static final int					MOUSE_RELEASED				= 4;
	public static final int					MOUSE_DRAGGED				= 5;
	public static final int					MOUSE_MOVED					= 6;
	public static final int					MOUSE_WHEEL_MOVED			= 7;
	public static final int					FOCUS_GAINED				= 8;
	public static final int					FOCUS_LOST					= 9;
	public static final int					KEY_PRESSED					= 10;
	public static final int					KEY_RELEASED				= 11;
	public static final int					KEY_TYPED					= 12;
	// public static final int PROPERTY_CHANGE = 13;
	
	// values for type
	public static final int					BORDER_COMPONENT			= 0;
	public static final int					DUMMY_COMPONENT				= 1;
	public static final int					REAL_COMPONENT				= 2;
	public static final int					FINAL_COMPONENT				= 3;
	
	// the colors for hover and select effects
	public static final Color				HOVER_COLOR					= GECAMedColors.c_GECAMedDarkerBackground;
	public static final Color				HOVER_COLOR_BORDER			= new Color(230, 230, 230, 255);
	public static final Color				SELECTED_COLOR				= Color.orange;
	public static final Color				SELECTED_HOVER_COLOR		= Color.orange.darker();
	protected static final Color			DEFAULT_BACKGROUND			= GECAMedColors.c_GECAMedBackground;
	
	protected static final String			IMG_DATA_PROPERTY_NAME		= "img_data";
	protected static final String			IMG_WIDTH_PROPERTY_NAME		= "img_width";
	protected static final String			IMG_HEIGHT_PROPERTY_NAME	= "img_height";
	
	public static final String				MISSING_VALUE				= "#NULL";
	public static final String				CREATION_DATE_REF			= "#creationDate";
	
	protected static final String			XML_ELEMENT_TEXT			= "text";
	protected static final String			XML_ELEMENT_DATA			= "data";
	
	public static final int					MAX_SCALING					= 3500;
	
	protected static final ImageIcon 		EMPTY_ICON 					= new ImageIcon(IconFetcher.getIcon(
			FormEditorModule.class, "empty.png").getImage().getScaledInstance(10, 10, Image.SCALE_DEFAULT));
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	protected String[]						listenerMethodNames;
	
	protected FormEditorModel				model;
	/*
	 * The component, that is added in center of the EditableComponent (JPanel)
	 * and represents the intrinsic part of the component.
	 */
	protected JComponent					component;
	protected CellConstraints				constraints;
	protected String						name;
	// a unique identifier, can be used in the formula
	private String							key							= "";
	// a formula, which calculates a value or calls an event
	protected String[]						scripts;
	
	protected byte							type;
	
	/**
	 * only for dummy-components, to save the width and the height for a new
	 * Component
	 */
	private SpinnerNumberModel				columnSpinnerModel;
	private SpinnerNumberModel				rowSpinnerModel;
	
	protected boolean						selected;
	private boolean							opaque;
	protected Color							hoverColor					= HOVER_COLOR;
	private boolean							showInHistory				= false;
	private Boolean							storeInDB;
	private float							historyIndex;
	private JLabel							description					= new JLabel("");
	private String							descriptionPosition			= "none";
	
	// private String startText = null;
	private String							startStructure				= null;
	protected boolean						iconEnabled					= false;
	protected ImageIcon						icon;
	protected byte[]						imgData;
	protected boolean						scaleSmooth;
	private int								scaleWidth;
	private int								scaleHeight;
	
	protected DefaultPropertyPanel			propertyPanel;
	protected DefaultOptionPanel			optionPanel;
	
	
	
	/* ======================================== */
	// 		CONSTRUCTORS
	/* ======================================== */
	
	public EditableComponent()
	{
		scripts = new String[getListenerMethodSize() + SUPER_LISTENER_METHOD_SIZE];
		for (int i = 0; i < scripts.length; i++)
		{
			scripts[i] = "";
		}
		
		// listener names of a JComponent
		listenerMethodNames = new String[scripts.length];
		// listenerMethodNames[PROPERTY_CHANGE] = "propertyChange";
		listenerMethodNames[MOUSE_CLICKED] = "mouseClicked";
		listenerMethodNames[MOUSE_ENTERED] = "mouseEntered";
		listenerMethodNames[MOUSE_EXITED] = "mouseExited";
		listenerMethodNames[MOUSE_PRESSED] = "mousePressed";
		listenerMethodNames[MOUSE_RELEASED] = "mouseReleased";
		listenerMethodNames[MOUSE_DRAGGED] = "mouseDragged";
		listenerMethodNames[MOUSE_MOVED] = "mouseMoved";
		listenerMethodNames[MOUSE_WHEEL_MOVED] = "mouseWheelMoved";
		listenerMethodNames[FOCUS_GAINED] = "focusGained";
		listenerMethodNames[FOCUS_LOST] = "focusLost";
		listenerMethodNames[KEY_PRESSED] = "keyPressed";
		listenerMethodNames[KEY_RELEASED] = "keyReleased";
		listenerMethodNames[KEY_TYPED] = "keyTyped";
		addListenerMethodNames();
	}
	
	
	/**
	 * initializes the component and makes it draggable, if it's a
	 * "real"-component or a dropTarget, if it's a dummy
	 * 
	 * @param constraints
	 * @param model
	 */
	public void init(CellConstraints constraints, FormEditorModel model, byte formModelType)
	{
		this.model = model;
		this.constraints = constraints;
		this.type = formModelType;
		
		NotProperInitializedException.testEditableComponent(this);
		
		if (type == FormModel.TYPE_EDITOR)
			initType();
		
		this.setLayout(new BorderLayout());
		this.add(component, BorderLayout.CENTER);
		this.setOpaque(false);
		this.setBackground(DEFAULT_BACKGROUND);
		this.setOpaque(opaque);
		opaque = getComponentForProperty().isOpaque();
		
		changeFont(TextAttribute.FOREGROUND, Color.BLACK);
		
		getPropertyPanel().setBackgroundColor(copyBackground());
	}
	
	
	/**
	 * Only called if this components used in editor (type ==
	 * FormModel.TYPE_EDITOR)
	 */
	protected void initType()
	{
		// only non-dummy- and non-border-elements can be dragged this is a
		// "real"-component
		
		/*
		 * "name" calls the method getName(), when dragging the component around
		 * Could be another method, the TransferHandler just needs a get-method,
		 * that doesn't has a parameter and returns a String. Otherwise the
		 * Component won't be draggable
		 */
		TransferHandler handler = new TransferHandler("name");
		// this.setTransferHandler(handler);
		this.component.setTransferHandler(handler);
		// component.addMouseListener(new EditorPanelController(model));
		// this.setDropTarget(null);
		this.component.setDropTarget(null);
		
		for (int i = 1; !setKey(getComponentType() + "_" + i); i++)
			;
		
		model.getView().addModifieListener(this.component);
		
//		this.component.addPropertyChangeListener(new PropertyChangeListener()
//		{
//			public void propertyChange(PropertyChangeEvent evt)
//			{
//				if (component.getWidth() <= 0
//						|| component.getHeight() <= 0)
//				{
////					component.setMinimumSize(new Dimension(10, 10));
////					component.setSize(new Dimension(10, 10));
//					setImage(new ImageIcon(EMPTY_ICON.getImage().getScaledInstance(10, 10, Image.SCALE_DEFAULT)));
//				}
//			}
//		});
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	/**
	 * creates the hover effects - is called when the mouse enters / exits the
	 * component
	 */
	public void stopHover()
	{
		if (!selected)
		{
			this.resetComponentBackground();
			// component.setBackground(bgColor);
		}
		else
		{
			this.setComponentBackgroundTemporarily(SELECTED_COLOR);
			// component.setBackground(SELECTED_COLOR);
		}
	}
	
	
	public void startHover()
	{
		if (!selected)
		{
			this.setComponentBackgroundTemporarily(hoverColor);
			// component.setBackground(hoverColor);
		}
		else
		{
			this.setComponentBackgroundTemporarily(SELECTED_HOVER_COLOR);
			// component.setBackground(SELECTED_HOVER_COLOR);
		}
	}
	
	
	// MouseListener, MouseMotionListener, MouseWheelListener,
	// FocusListener, KeyListener,
	protected void addDefaultListener(JComponent component, FormController controller)
	{
		// add the listener, you can add to an JComponent
		// BUT only if there is code to execute
		
		// if (getScriptAt(PROPERTY_CHANGE).length() > 0)
		// {
		// component.addPropertyChangeListener(controller);
		// controller.setCodeAt(FormController.PROPERTY_CHANGE,
		// this.getScriptAt(PROPERTY_CHANGE));
		// }
		
		if (getScriptAt(MOUSE_CLICKED).length() > 0 || getScriptAt(MOUSE_PRESSED).length() > 0 || getScriptAt(MOUSE_RELEASED).length() > 0)
		{
			component.addMouseListener(controller);
			controller.setCodeAt(FormController.MOUSE_CLICKED, this.getScriptAt(MOUSE_CLICKED));
			controller.setCodeAt(FormController.MOUSE_PRESSED, this.getScriptAt(MOUSE_PRESSED));
			controller.setCodeAt(FormController.MOUSE_RELEASED, this.getScriptAt(MOUSE_RELEASED));
		}
		
		if (getScriptAt(MOUSE_ENTERED).length() > 0 || getScriptAt(MOUSE_EXITED).length() > 0 || getScriptAt(MOUSE_DRAGGED).length() > 0 || getScriptAt(MOUSE_MOVED).length() > 0)
		{
			component.addMouseMotionListener(controller);
			controller.setCodeAt(FormController.MOUSE_ENTERED, this.getScriptAt(MOUSE_ENTERED));
			controller.setCodeAt(FormController.MOUSE_EXITED, this.getScriptAt(MOUSE_EXITED));
			controller.setCodeAt(FormController.MOUSE_DRAGGED, this.getScriptAt(MOUSE_DRAGGED));
			controller.setCodeAt(FormController.MOUSE_MOVED, this.getScriptAt(MOUSE_MOVED));
		}
		
		if (getScriptAt(MOUSE_WHEEL_MOVED).length() > 0)
		{
			component.addMouseWheelListener(controller);
			controller.setCodeAt(FormController.MOUSE_WHEEL_MOVED, this.getScriptAt(MOUSE_WHEEL_MOVED));
		}
		
		if (getScriptAt(FOCUS_GAINED).length() > 0 || getScriptAt(FOCUS_LOST).length() > 0)
		{
			component.addFocusListener(controller);
			controller.setCodeAt(FormController.FOCUS_GAINED, this.getScriptAt(FOCUS_GAINED));
			controller.setCodeAt(FormController.FOCUS_LOST, this.getScriptAt(FOCUS_LOST));
		}
		
//		if (getFormulaAt(KEY_PRESSED).length() > 0 
//				|| getFormulaAt(KEY_RELEASED).length() > 0 
//				|| getFormulaAt(KEY_TYPED).length() > 0)
//		{
			component.addKeyListener(controller);
			controller.setCodeAt(FormController.KEY_PRESSED, this.getScriptAt(KEY_PRESSED));
			controller.setCodeAt(FormController.KEY_RELEASED, this.getScriptAt(KEY_RELEASED));
			controller.setCodeAt(FormController.KEY_TYPED, this.getScriptAt(KEY_TYPED));
//		}
		
		component.addMouseWheelListener(scrollingListener);
		component.setName(getKey());
	}
	
	
	protected void addDefaultProperties(Element data)
	{
		data.setAttribute("type", this.getComponentType());
		data.setAttribute("showInHistory", String.valueOf(this.showInHistory));
		data.setAttribute("historyIndex", String.valueOf(this.getHistoryIndex()));
		data.setAttribute("description", this.getDescription());
		data.setAttribute("fontFamily", String.valueOf(this.getFont(TextAttribute.FAMILY)));
		data.setAttribute("backgroundColor", getHexValueFromColor((Color) this.getFont(TextAttribute.BACKGROUND), null));
		data.setAttribute("foregroundColor", getHexValueFromColor((Color) this.getFont(TextAttribute.FOREGROUND), Color.BLACK));
		data.setAttribute("justification", String.valueOf(this.getFont(TextAttribute.JUSTIFICATION)));
		data.setAttribute("posture", String.valueOf(this.getFont(TextAttribute.POSTURE)));
		data.setAttribute("underline", String.valueOf(this.getFont(TextAttribute.UNDERLINE)));
		data.setAttribute("weight", String.valueOf(this.getFont(TextAttribute.WEIGHT)));
		data.setAttribute("strikethrough", String.valueOf(this.getFont(TextAttribute.STRIKETHROUGH)));
		data.setAttribute("fontSize", String.valueOf(this.getFont(TextAttribute.SIZE)));
	}
	
	
	public void copyLayout(JComponent newComponent, JPanel componentPanel)
	{
		// -- set the image as icon
		// no good practice, but it works ...
//		JComponent tmp = component;
//		component = newComponent;
//		this.setIcon(icon);
//		component = tmp;
		
		// -- paint the border
		if (getPropertyPanel().isPaintBorder())
		{
			componentPanel.setBorder(new LineBorder(propertyPanel.getBorderColor()));
		}
		
		// -- define the background
		newComponent.setBackground(getPropertyPanel().getBackgroundColor());
		newComponent.setOpaque(opaque);
		newComponent.setMinimumSize(new Dimension(10, 10));
		
		// -- copy the font
		newComponent.setFont(new Font(getComponentForProperty().getFont().getAttributes()));
		
		// -- set alignment
		// -> is done with copy constraints
	}
	
	
	/*
	 * Label returns 'Text' DateChooser returns 'Date' -> for the script
	 * '<<key>> = 1;' will become model.getComponent("key").setText("1");
	 */
	// public abstract String getMethodName ();
	
	// public static ArrayList<String> getAllPossibleListenerMethodNames() {
	// ArrayList<String> methodNames = new ArrayList<String>();
	// methodNames.add("mouseClicked");
	// methodNames.add("mouseEntered");
	// methodNames.add("mouseExited");
	// methodNames.add("mousePressed");
	// methodNames.add("mouseReleased");
	// methodNames.add("mouseDragged");
	// methodNames.add("mouseMoved");
	// methodNames.add("mouseWheelMoved");
	// methodNames.add("focusGained");
	// methodNames.add("focusLost");
	// methodNames.add("keyPressed");
	// methodNames.add("keyReleased");
	// methodNames.add("keyTyped");
	// methodNames.add("actionPerformed");
	// methodNames.add("caretUpdated");
	// return methodNames;
	// }
	
	/**
	 * This method can be overwritten, if an EditableComponent needs additional
	 * options.
	 * 
	 * @param model
	 * @return
	 */
	protected DefaultOptionPanel createOptionPanel()
	{
		return new DefaultOptionPanel(this);
	}
	
	
	protected JComponent createCaptionText()
	{
		return new JTextField();
	}
	
	
	protected DefaultPropertyPanel createPropertiesPanel()
	{
		return new DefaultPropertyPanel(this);
	}
	
	
	public String print()
	{
		return getHistoryText();
	}
	
	
	public void changeFont(TextAttribute key, Object value)
	{
		@SuppressWarnings("unchecked")
		Map<TextAttribute, Object> attributes = (Map<TextAttribute, Object>) getComponentForProperty().getFont().getAttributes();
		attributes.put(key, value);
		
		getComponentForProperty().setFont(new Font(attributes));
		getPropertyPanel().fontSettingsChanged(key);
		this.repaint();
	}
	
	
	/**
	 * Called when the form, this component belongs to is saved in the DB.
	 */
	public void saved()
	{
		// ...
	}
	
	
	@Override
	public String toString()
	{
		return new StringBuilder(name != null ? name : "null").append(";").append(key != null ? key : "null").toString();
	}
	
	
	/**
	 * This method checks, if a component can be added a position with the
	 * specified column width and row height.
	 * 
	 * @param ec
	 *            : the component to be added
	 * @param width
	 *            : the column width of the component
	 * @param height
	 *            : the row height of the component
	 * @return returns true if the component can be add
	 */
	public boolean canAddComponent(int width, int height)
	{
		FormEditorModule view = model.getView();
		if (this.getColumn() + width - 1 > view.getEditorColumnCount() || this.getRow() + height - 1 > view.getEditorRowCount())
		{
			/*
			 * if the component is too large and a part of it would be out of
			 * the panel it cannot be added.
			 */
			return false;
		}
		
		for (int i = this.getColumn(); i <= this.getColumn() + width - 1; i++)
		{
			for (int j = this.getRow(); j <= this.getRow() + height - 1; j++)
			{
				EditableComponent c = view.getComponent(i + ":" + j);
				if (c == null || !c.isDummy())
				{
					/*
					 * another component was found in range of the component to
					 * be added. The component cannot be add.
					 */
					return false;
				}
			}
		}
		
		/*
		 * no obstacle was found, the component can be added.
		 */
		return true;
	}
	
	
	public void dispose()
	{
		this.removeAll();
		this.component = null;
		this.model = null;
	}
	
	
	
	/* ======================================== */
	// 		GETTER & SETTER
	/* ======================================== */
	
	/**
	 * selects or deselects a component and marks it with a color
	 * 
	 * @param selected
	 */
	public void setSelected(boolean selected)
	{
		this.selected = selected;
		if (selected)
		{
			this.setComponentBackgroundTemporarily(SELECTED_COLOR);
			// component.setBackground(SELECTED_COLOR);
		}
		else
		{
			this.resetComponentBackground();
			// component.setBackground(bgColor);
		}
	}
	
	
	public boolean isSelected()
	{
		return selected;
	}
	
	
	public boolean isDummy()
	{
		return getType() == DUMMY_COMPONENT;
	}
	
	
	public boolean isRealComponent()
	{
		return getType() == REAL_COMPONENT;
	}
	
	
	public int getType()
	{
		return REAL_COMPONENT;
	}
	
	
	public FormEditorModel getFormEditorModel()
	{
		return model;
	}
	
	
	public void setConstraints(int col, int row, int width, int height)
	{
		setConstraints(new CellConstraints(col, row, width, height));
	}
	
	
	public void setConstraints(CellConstraints c)
	{
		constraints = c;
	}
	
	
	public CellConstraints getConstraints()
	{
		return constraints;
	}
	
	
	public int getColumn()
	{
		return constraints.gridX;
	}
	
	
	public void setColumn(int col)
	{
		constraints.gridX = col;
	}
	
	
	public int getRow()
	{
		return constraints.gridY;
	}
	
	
	public void setRow(int row)
	{
		constraints.gridY = row;
	}
	
	
	public int getColumnWidth()
	{
		return constraints.gridWidth;
	}
	
	
	public void setColumnWidth(int width)
	{
		constraints.gridWidth = width;
	}
	
	
	public int getRowHeight()
	{
		return constraints.gridHeight;
	}
	
	
	public void setRowHeight(int height)
	{
		constraints.gridHeight = height;
	}
	
	
	public JComponent getComponent()
	{
		return component;
	}
	
	
	public void setComponent(JComponent c)
	{
		try
		{
			if (component instanceof EditableComponent)
			{
				throw new Exception("component musn't be of type " + component.getClass());
			}
			else
			{
				this.component = c;
				// this.opaque = c.isOpaque();
			}
		}
		catch (Exception e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
		}
	}
	
	
	public JPanel getPanel()
	{
		Component c = component;
		while (!(c instanceof JPanel))
			c = c.getParent();
		return (JPanel) c;
	}
	
	
	// public void setComponentBackground(Color c) {
	// component.setBackground(c);
	// }
	
	public Color getComponentBackground()
	{
		return getPropertyPanel().getBackgroundColor();
	}
	
	
	public void setName(String name)
	{
		super.setName(name);
		if (component != null)
			component.setName(name);
	}
	
	
	/**
	 * calculates and returns the no. of the last column of this component
	 */
	public int getEndColumn()
	{
		return getColumn() + getColumnWidth() - 1;
	}
	
	
	/**
	 * calculates and returns the no. of the last row of this component
	 */
	public int getEndRow()
	{
		return getRow() + getRowHeight() - 1;
	}
	
	
	/**
	 * Sets the key for this component. With its key, a component can be called
	 * in the formula editor.
	 * 
	 * @param key
	 *            : The new value of the key
	 * @return Returns whether the key could have been set
	 */
	public boolean setKey(String key)
	{
		Map<String, EditableComponent> components = model.getEditableComponents();
		
		for (String[] fixKey : FormController.FIX_KEYS)
		{
			if (key.equals(fixKey[0]))
			{
				return false;
			}
		}
		
		for (String fixKey : FormController.FORBIDDEN_KEYS)
		{
			if (key.equals(fixKey))
			{
				return false;
			}
		}
		
		if (this.key.equals(key))
		{
			// the new key is equal to the old one -> it needn't be stored,
			// just return success
			return true;
		}
		else if (key.equals(""))
		{
			// the new key is empty, don't store it
			return false;
		}
		else if (components.get(key) == null)
		{
			// the key-name doesn't exist, yet. It can be stored
			components.put(key, components.remove(this.key));
			this.key = key;
			return true;
		}
		else
		{
			// the key-name already exists
			return false;
		}
	}
	
	
	/**
	 * set a valid key, without checking for emptiness or existence
	 * 
	 * @param key
	 */
	public void setKeyDo(String key)
	{
		key = key.replaceAll("[^\\d\\w_]", "");
		while (key.length() > 0 && String.valueOf(key.charAt(0)).matches("\\d"))
			key = key.substring(1);
		
		this.key = key;
	}
	
	
	public String getKey()
	{
		return key;
	}
	
	
	public Color copyBackground()
	{
		Color c = getComponentForProperty().getBackground();
		return new Color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
	}
	
	
	public void setFormulaAt(int index, String script)
	{
		this.scripts[index] = script;
	}
	
	
	public String getScriptAt(int index)
	{
		return scripts[index];
	}
	
	
	public void setColumnSpinnerModel(SpinnerNumberModel columnSpinnerModel)
	{
		this.columnSpinnerModel = columnSpinnerModel;
	}
	
	
	public SpinnerNumberModel getColumnSpinnerModel()
	{
		return columnSpinnerModel;
	}
	
	
	public void setRowSpinnerModel(SpinnerNumberModel rowSpinnerModel)
	{
		this.rowSpinnerModel = rowSpinnerModel;
	}
	
	
	public SpinnerNumberModel getRowSpinnerModel()
	{
		return rowSpinnerModel;
	}
	
	
	public boolean isShowInHistory()
	{
		return showInHistory;
	}
	
	
	public void setShowInHistory(boolean showInHistory)
	{
		this.showInHistory = showInHistory;
	}
	
	
	public void setStoreInDB(Boolean doStore)
	{
		this.storeInDB = doStore;
	}
	
	
	public boolean isStoredInDB()
	{
		if (storeInDB == null)
		{
			storeInDB = isComponentTypeStoredInDBByDefault();
			if (storeInDB == null)
			{
				storeInDB = false;
			}
		}
		return storeInDB;
	}
	
	
	/**
	 * Defines whether the check box to save the component is selected by
	 * default or not.
	 * 
	 * @return true:
	 *         <tr>
	 *         the checkbox is selected by default<br>
	 *         false:
	 *         <tr>
	 *         the checkbox is NOT selected by default<br>
	 *         null:
	 *         <tr>
	 *         the checkbox cannot be selected, because it is disabled
	 */
	public Boolean isComponentTypeStoredInDBByDefault()
	{
		return false;
	}
	
	
	public float getHistoryIndex()
	{
		return historyIndex;
	}
	
	
	public void setHistoryIndex(float index)
	{
		this.historyIndex = index;
	}
	
	
	public String getDescription()
	{
		return description.getText();
	}
	
	
	public void setDescription(String description)
	{
		this.description.setText(description);
	}
	
	
	public String getDescriptionPosition()
	{
		return descriptionPosition;
	}
	
	
	public void setDescriptionPosition(String descriptionPosition)
	{
		if (descriptionPosition != null && !this.descriptionPosition.equals(descriptionPosition))
		{
			this.descriptionPosition = descriptionPosition;
			this.remove(description);
			
			if (descriptionPosition != null && !descriptionPosition.equals(FormModel.NONE))
			{
				// show the label
				this.add(description, descriptionPosition);
			} // else: don't show the label
		}
	}
	
	
	/**
	 * returns the name of a listener method at the specified index
	 */
	public String getMethodNameOf(int index)
	{
		return listenerMethodNames[index];
	}
	
	
	public int getFormulaSize()
	{
		return SUPER_LISTENER_METHOD_SIZE + getListenerMethodSize();
	}
	
	
	public void setStartStructure(String structure)
	{
		this.startStructure = structure;
	}
	
	
	public String getStartStructure()
	{
		return startStructure;
	}
	
	
	public Element getDataAsXML()
	{
		Element data = new Element(getKey());
		Element text = new Element(XML_ELEMENT_TEXT);
		text.setText(getText());
		data.addContent(text);
		
		addDefaultProperties(data);
		return data;
	}
	
	
	private String getHexValueFromColor(Color color, Color defaultColor)
	{
		if (color == null)
			if (defaultColor == null)
				return "";
			else
				color = defaultColor;
		
		return "#" + Integer.toString(color.getRGB() & 0xffffff, 16);
	}
	
	
	/**
	 * 
	 * @param c
	 * @return
	 */
	public JPanel getAddablePanel(JComponent c)
	{
		JPanel panel = new JPanel(new BorderLayout());
		panel.setOpaque(false);
		panel.add(c, BorderLayout.CENTER);
		if (!this.getDescriptionPosition().equals(FormModel.NONE))
		{
			JLabel l = new JLabel(this.getDescription());
			l.setOpaque(false);
			panel.add(l, this.getDescriptionPosition());
		}
		return panel;
	}
	
	
	public DefaultOptionPanel getOptionPanel()
	{
		if (optionPanel == null)
			optionPanel = createOptionPanel();
		return optionPanel;
	}
	
	
	public DefaultPropertyPanel getPropertyPanel()
	{
		if (propertyPanel == null)
			propertyPanel = createPropertiesPanel();
		return propertyPanel;
	}
	
	
	/**
	 * Should be overridden, if the getText is not supposed to return the text
	 * shown in the history
	 * 
	 * @return the text that will be shown in the history
	 */
	public String getHistoryText()
	{
		return getText();
	}
	
	
	public void setFont(Font font)
	{
		if (this.component == null)
			super.setFont(font);
		else
		{
			getComponentForProperty().setFont(font);
			getPropertyPanel().fontSettingsChanged();
		}
	}
	
	
	public Object getFont(TextAttribute key)
	{
		Map<TextAttribute, ?> attributes = (Map<TextAttribute, ?>) getComponentForProperty().getFont().getAttributes();
		return attributes.get(key);
	}
	
	
	public JComponent getComponentForOptions()
	{
		return this.getComponent();
	}
	
	
	public JComponent getComponentForProperty()
	{
		return this.getComponent();
	}
	
	
	/**
	 * calls the method getComponenList(false) and returns its result
	 * 
	 * @return
	 */
	protected Vector<?> getComponentList()
	{
		return getComponentList(false);
	}
	
	
	/**
	 * Returns a vector, containing the names of all editable components of this
	 * template
	 * 
	 * @param asComboBoxElement
	 *            if true, the vector contains ComboBoxElements<br>
	 *            else, it contains Strings
	 * @return
	 */
	@SuppressWarnings("unchecked")
	protected Vector<?> getComponentList(boolean asComboBoxElement)
	{
		TreeSet<?> components;
		if (asComboBoxElement)
		{
			// components = new Vector<ComboBoxElement<String>>();
			components = new TreeSet<ComboBoxElement<String>>();
		}
		else
		{
			components = new TreeSet<String>();
		}
		
		for (EditableComponent ec : model.getEditableComponents().values())
		{
			if (ec == null || !ec.isStoredInDB())
			{
				continue;
			}
			
			String key = ec.getKey();
			if (asComboBoxElement)
			{
				((TreeSet<ComboBoxElement<String>>) components).add(new ComboBoxElement<String>(key, key));
			}
			else
			{
				((TreeSet<String>) components).add(key);
			}
		}
		
		if (asComboBoxElement)
		{
			return new Vector<ComboBoxElement<String>>((TreeSet<ComboBoxElement<String>>) components);
		}
		else
		{
			return new Vector<String>((TreeSet<String>) components);
		}
	}
	
	
	public String getComponentSimpleName()
	{
		return getComponent().getClass().getSimpleName();
	}
	
	
	
	/* ======================================== */
	// 		ABSTRACT METHODS
	/* ======================================== */
	
	/**
	 * <p>
	 * This method is called, if the XML-File and / or the database of the
	 * FormValue is read. It sets the value or the state of the object, stored
	 * in the XML, respectively in the component.
	 * <p>
	 * The state / value of the component stored in a String (by getText) can be
	 * reloaded with this method.
	 * 
	 * @param text
	 *            is the data stored in the XML-attribute "value" or in the
	 *            database (returned by getText)
	 */
	public abstract void setText(String text);
	
	
	/**
	 * <p>
	 * This method is called, if the object is stored in an XML-file or the
	 * database. It returns the value or the state of the object, so it can be
	 * written into an XML-file or the database.
	 * <p>
	 * The state or value of the component stored in the database or the
	 * xml-file can be returned with this method.
	 * 
	 * @return The text, that is to be stored in the XML-file (read by setText)
	 */
	public abstract String getText();
	
	
	/**
	 * This method is called to build the structure of a component, stored as a
	 * string in the template XML.
	 * 
	 * @param text
	 *            the structure as a string, returned by the getStructure
	 */
	public abstract void setStructure(String text);
	
	
	/**
	 * This method is called to return the structure of a component as a string,
	 * to be stored in the template XML.
	 * 
	 * @return the structure as a string, to be reloaded by getStructure.
	 */
	public abstract String getStructure();
	
	
	/**
	 * The translation of the component type.
	 * 
	 * @return
	 */
	public abstract String getTypeTranslation();
	
	
	/**
	 * Returns the type of the component as string (e.g. "Label" or
	 * "TextField").
	 */
	public abstract String getComponentType();
	
	
	/**
	 * This method creates a copy of the JComponent. A copy is needed, so the
	 * component can be placed at another panel (e.g. in the preview) and can
	 * stay in editor. A listener has to be added to the copied component, if
	 * the component is supposed to have one.
	 */
	public abstract JComponent copyComponent(FormModel model);
	
	
	/**
	 * Returns the number of listener methods, this component can call
	 * 
	 * @return
	 */
	protected abstract int getListenerMethodSize();
	
	
	/**
	 * adds the names of the listener-methods that additionally available for
	 * this EditableComponent
	 */
	protected abstract void addListenerMethodNames();
	
	
	/* ======================================== */
	// 		ICON HANDLING
	/* ======================================== */
	
	protected void setIcon(Icon icon)
	{
		if (this.iconEnabled)
			logger.info("Pictures are enabled for " + this.getClass().getName() + ", but the method setIcon is not overritten. That shouldn't happen");
	}
	
	
	protected Icon getIcon()
	{
		if (this.iconEnabled)
			logger.info("Pictures are enabled for " + this.getClass().getName() + ", but the method getIcon is not overritten. That shouldn't happen");
		return null;
	}
	
	
	public ImageIcon getImage()
	{
		return this.icon;
	}
	
	
	public void setImage(ImageIcon newIcon)
	{
		icon = newIcon;
		updateImage();
	}
	
	
	protected int getScaleWidth()
	{
		return scaleWidth;
	}
	
	
	protected void setScaleWidth(int scaleWidth)
	{
		this.scaleWidth = scaleWidth;
		updateImage();
	}
	
	
	protected int getScaleHeight()
	{
		return scaleHeight;
	}
	
	
	protected void setScaleHeight(int scaleHeight)
	{
		this.scaleHeight = scaleHeight;
		updateImage();
	}
	
	
	private void updateImage()
	{
		if (icon != null && scaleWidth > 0 && scaleHeight > 0)
		{
			Image scaledImage = icon.getImage().getScaledInstance(
					scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
			setIcon(new ImageIcon(scaledImage));
		}
		else
			setIcon(icon);
		
		component.repaint();
	}
	
	
	/**
	 * Reads the data of the image out of the properties into this
	 * EditableComponent
	 * 
	 * @param structure
	 *            The properties to read from.
	 */
	protected void readImage(Properties structure)
	{
		// create image
		try
		{
			imgData = FormWidgets.base64ToBytes(structure.getProperty(IMG_DATA_PROPERTY_NAME));
			this.setImage(new ImageIcon(imgData));
			// this.originalWidth = icon.getIconWidth();
			// this.originalHeight = icon.getIconHeight();
		}
		catch (Exception e)
		{
			logger.log(Level.WARN, "Couldn't load the picture for component " + this.getKey() + ".", e);
			return;
		}
		
		// get scaling
		try
		{
			setScaleWidth(Integer.parseInt(structure.getProperty(IMG_WIDTH_PROPERTY_NAME)));
			setScaleHeight(Integer.parseInt(structure.getProperty(IMG_HEIGHT_PROPERTY_NAME)));
			// scaleIcon();
		}
		catch (Exception e)
		{
			logger.warn("The picture of component " + this.getKey() + " couldn't be scaled.");
			
			setScaleWidth(getImage().getIconWidth());
			setScaleHeight(getImage().getIconHeight());
		}
	}
	
	
	/**
	 * Writes the data of the image of this EditableComponent into the
	 * properties.
	 * 
	 * @param structure
	 *            The properties to write into
	 */
	protected void writeImage(Properties structure)
	{
		structure.setProperty(IMG_DATA_PROPERTY_NAME, FormWidgets.bytesToBase64(imgData));
		structure.setProperty(IMG_WIDTH_PROPERTY_NAME, String.valueOf(getScaleWidth()));
		structure.setProperty(IMG_HEIGHT_PROPERTY_NAME, String.valueOf(getScaleHeight()));
	}
//	
//	
//	public void paintImage(Graphics g, int hAlign, int vAlign)
//	{
//		if (getImage() == null)
//			return;
//		
//		boolean center = hAlign == SwingConstants.CENTER;
//		Dimension size = component.getSize();
//		int width = size.width; // c.getWidth();
//		int height = size.height; // c.getHeight();
//		int x = 0;
//		int y = 0;
//		
//		
//		if (width < getScaleWidth())
//			setScaleWidth(width);
//		
//		if (height < getScaleHeight())
//			setScaleHeight(height);
//		
//		// define the x and y coordinate
//		if (center)
//		{
//			x = (width - getScaleWidth()) / 2;
//			y = (height - getScaleHeight()) / 2;
//		}
//		
//		// draw the image
//		g.drawImage(getImage().getImage(), x, y, getScaleWidth(), getScaleHeight(), component);
//	}
//	
//	
//	public void paintImage(Graphics g)
//	{
//		paintImage(g, this.icon, this.component, true, this.scaleWidth, this.scaleHeight);
//	}
//	
//	
//	public void scaleIcon()
//	{
//		if (imgData != null && scaleWidth > 0 && scaleHeight > 0)
//		{
//			scaledImg = new ImageIcon(imgData);
//			
//			scaledImg = new ImageIcon(scaledImg.getImage().getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH));
//			if (scaledImg.getIconWidth() > component.getWidth())
//			{
//				scaledImg.getImage().getGraphics().clearRect(component.getWidth(), 0, scaledImg.getIconWidth(), scaledImg.getIconHeight());
//			}
//			
//			this.setIcon(scaledImg);
//		}
//	}
//	
//	
//	public Dimension getImageSize()
//	{
//		/* ======================================== */
//		return new Dimension(scaleWidth, scaleHeight);
//		/* ======================================== */
//	}

	
	/* ======================================== */
	// 		BACKGROUND HANDLING
	/* ======================================== */
	
	/*
	 * CHANGE THE COMPONENT BACKGROUND
	 */
	private boolean	componentColorTemporarilyChanged	= false;
	
	
	// private boolean componentOpaqueTemporarilyChanged = false;
	
	public void setComponentOpaq(boolean opaque)
	{
		this.opaque = opaque;
		if (!componentColorTemporarilyChanged)
		{
			getComponentForProperty().setOpaque(opaque);
			getPropertyPanel().setPaintBackground(opaque);
		}
	}
	
	
	public void setComponentBackground(Color c)
	{
		getPropertyPanel().setBackgroundColor(c);
		if (!componentColorTemporarilyChanged)
		{
			getComponentForProperty().setBackground(c);
		}
	}
	
	
	protected void setComponentBackgroundTemporarily(Color c)
	{
		this.setComponentBackgroundTemporarily(getComponentForProperty(), c);
	}
	
	
	protected void setComponentBackgroundTemporarily(JComponent component, Color c)
	{
		if (!componentColorTemporarilyChanged)
		{
			componentColorTemporarilyChanged = true;
			getPropertyPanel().setBackgroundColor(component.getBackground());
			opaque = component.isOpaque();
		}
		
		if (this.getImage() == null)
		{
			component.setBackground(c);
			component.setOpaque(true);
		}
	}
	
	
	public void resetComponentBackground()
	{
		resetComponentBackground(getComponentForProperty());
	}
	
	
	protected void resetComponentBackground(JComponent component)
	{
		componentColorTemporarilyChanged = false;
		if (component != null)
		{
			component.setBackground(getPropertyPanel().getBackgroundColor());
			component.setOpaque(opaque);
		}
	}
}
