public static String computeHash(String text) {
	if(text==null||text.isEmpty())
		return null;
	try {
		byte[] digits = MessageDigest.getInstance("SHA").digest(text.getBytes());
		StringBuilder hash = new StringBuilder();
		for(byte digit:digits)
			hash.append(Integer.toHexString(digit&0xff));
		return hash.toString();
	} catch (NoSuchAlgorithmException e) { return Integer.toString(text.hashCode()); }
} public static String getTranslation(String key) { return getTranslation(key, TranslationType.TEXT); } public static Point addPoints(Point... points) {
	Point result = new Point();
	for(Point point:points)
		if(point!=null)
			result.translate(point.x, point.y);
	return result;
} public static Point invertPoint(Point point) {
	return new Point(-point.x, -point.y);
} public static class LegacyObjectInputStream extends AlternateClassLoaderObjectInputStream {
	public static final String LEGACY_PACKAGE_PREFIX = "de.ksquared.", PACKAGE_PREFIX = "lc.kra.";
	public LegacyObjectInputStream(InputStream in) throws IOException {
		super(in, getSimpleClassLoader()); }
	@Override protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
		ObjectStreamClass descriptor = super.readClassDescriptor(); String name = descriptor.getName();
		if(name.contains(LEGACY_PACKAGE_PREFIX)) descriptor = ObjectStreamClass.lookup(Class.forName(replaceLegacyPackage(name), false, alternateClassLoader));
		return descriptor;
	}
	public static String replaceLegacyPackage(String name) {
		return name.replaceFirst("^(\\[L)?"+Pattern.quote(LEGACY_PACKAGE_PREFIX), "$1"+Matcher.quoteReplacement(PACKAGE_PREFIX));
	}
} public static class RememberFileChooser extends JFileChooser {
	private static final long serialVersionUID = 1l;
	public RememberFileChooser() { super(getLastDirectory()); }
	private static File getLastDirectory() {
		String directory = getConfiguration("directory");
		if(directory!=null) {
			File file = new File(directory);
			if(file.exists()&&file.isDirectory())
				return file;
			else return null;
		} else return null;
	}
	@Override public void approveSelection() {
		super.approveSelection();
		File selected = getSelectedFile();
		if(!selected.isDirectory())
			setConfiguration("directory", selected.getParentFile().toString());
	}
} public static enum TranslationType { TEXT, ALTERNATIVE, TITLE, EXTERNAL, TOOLTIP, DESCRIPTION; } public abstract class Component implements Paintable, Locatable, Moveable, Cloneable, Serializable {
	private static final long serialVersionUID = 1l;

	public static final ComponentAttributes componentAttributes = null;

	private Point location;
	protected PropertyChangeSupport change;

	public Component() { this.location = new Point(); }
	public Component(Point location) { this.setLocation(location); }

	public abstract Dimension getSize();
	@Override public void moveTo(Point location) { this.setLocation(location); }
	@Override public void moveRelative(Point location) {
		if(this.location.x+location.x<0||this.location.y+location.y<0)
			throw new LocationOutOfBoundsException();
		Point oldLocation = new Point(this.location);
		this.location.translate(location.x, location.y);
		firePropertyChange("location", oldLocation, this.location);
	}
	@Override public final Point getLocation() { return this.location; }
	@Override public final void setLocation(Point location) throws LocationOutOfBoundsException {
		if(location.x<0||location.y<0)
			throw new LocationOutOfBoundsException();
		firePropertyChange("location", new Point(this.location), this.location = location);
	}

	protected void addPropertyChangeListener(PropertyChangeListener listener) {
		if(listener==null) return;
		else if(change==null) change = new PropertyChangeSupport(this);
		change.addPropertyChangeListener(listener);
	}
	protected void addPropertyChangeListener(String name, PropertyChangeListener listener) {
		if(listener==null) return;
		else if(change==null) change = new PropertyChangeSupport(this);
		change.addPropertyChangeListener(name, listener);
	}
	protected PropertyChangeListener[] getPropertyChangeListeners(String name) {
		if(change==null) return new PropertyChangeListener[0];
		return change.getPropertyChangeListeners(name);
	}
	protected void removePropertyChangeListener(PropertyChangeListener listener) {
		if(listener==null||change==null) return;
		change.removePropertyChangeListener(listener);
	}
	protected void removePropertyChangeListener(String name, PropertyChangeListener listener) {
		if(listener==null||change==null) return;
		change.removePropertyChangeListener(name, listener);
	}

	private void firePropertyChange(String name, Object oldValue, Object newValue) {
		if(change==null||(oldValue!=null&&newValue!=null&&oldValue.equals(newValue)))
			return;
		change.firePropertyChange(name, oldValue, newValue);
	}

	public abstract void calculate();

	public final ComponentAttributes getAttributes() { return Component.getAttributes(this.getClass()); }
	public final static ComponentAttributes getAttributes(Class<? extends Component> cls) {
		return getField(cls, "componentAttributes", new ComponentAttributes(cls.getSimpleName(), "group.general"));
	}

	@Override public Component clone() throws CloneNotSupportedException {
		Component clone = null;
		if((clone=(Component)Utilities.copy(this)) instanceof Sociable)
			for(Contact contact:((Sociable)clone).getContacts())
				contact.removeWires();
		return clone;
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		PropertyChangeSupport change = this.change;
		if(Utilities.isCopying())
			this.change = null;
		out.defaultWriteObject();
		this.change = change;
	}

	public static Set<Component> clone(Collection<Component> components) throws CloneNotSupportedException {
		HashMap<Component, Component> clonedComponents = new HashMap<Component, Component>();
		for(Component component:components) {
			Component clone = component.clone();
			clonedComponents.put(component, clone);
		}
		HashMap<Component, List<Wire>> clones = new HashMap<Component, List<Wire>>(); List<Wire> wires;
		for(Component component:components) {
			Component clone = clonedComponents.get(component);
			clones.put(clone, wires=new Vector<Wire>());
			if(component instanceof Sociable) {
				Iterator<Contact> contacts = Arrays.asList(((Sociable)component).getContacts()).iterator(),
						clonedContacts = Arrays.asList(((Sociable)clone).getContacts()).iterator();
				for(;contacts.hasNext()&&clonedContacts.hasNext();) {
					try {
						Contact contact = contacts.next(), clonedContact = clonedContacts.next();
						if(contact instanceof OutputContact)
							for(Wire wire:contact.getWires()) {
								Component target = wire.getTarget().getComponent(),
										cloneTarget = clonedComponents.get(target);
								if(cloneTarget!=null) {
									Iterator<Contact> targetContacts = Arrays.asList(((Sociable) target).getContacts()).iterator(),
											cloneTargetContacts = Arrays.asList(((Sociable) cloneTarget).getContacts()).iterator();
									for(;targetContacts.hasNext()&&cloneTargetContacts.hasNext();) {
										Contact targetContact = targetContacts.next(),
												cloneTargetContact = cloneTargetContacts.next();
										if(targetContact instanceof InputContact&&targetContact.equals(wire.getTarget())) {
											wires.add(new Wire(clonedContact, cloneTargetContact));
											break;
										}
									}
								}
							}
					}	catch (WireNotConnectable e) {
						//well, wire could not be connected... will never happen?
					}
				}
			}
		}
		return new HashSet<Component>(clonedComponents.values());
	}

	@Target(ElementType.TYPE)
	@Retention(RetentionPolicy.RUNTIME)
	public static @interface HiddenComponent{}

	public static class ComponentAttributes {
		public final String key, name, group, description, author;
		public final int version;
		public ComponentAttributes(String name, String group) { this(name, group, 0); }
		public ComponentAttributes(String name, String group, int version) { this(name, group, null, null, version); }
		public ComponentAttributes(String name, String group, String description, String author, int version) { this(name.replaceAll("\\W", new String()), name, group, description, author, version); }
		public ComponentAttributes(String key, String name, String group) { this(key, name, group, 0); }
		public ComponentAttributes(String key, String name, String group, int version) { this(key, name, group, null, null, version); }
		public ComponentAttributes(String key, String name, String group, String description, String author, int version) {
			this.key = key;
			this.name = name;
			this.group = group;
			this.description = description;
			this.author = author;
			this.version = version;
		}
		@Override public int hashCode() { return (key+group).hashCode(); }
		@Override public boolean equals(Object object) {
			if(super.equals(object))
				return true;
			if(!(object instanceof ComponentAttributes))
				return false;
			ComponentAttributes attributes = (ComponentAttributes) object;
			return key.equals(attributes.key)&&group.equals(attributes.group);
		}
	}

	public static class ComponentFlavor extends DataFlavor {
		private static ComponentFlavor flavour;
		public ComponentFlavor() { super(Component.class, "Component");	}
		public static ComponentFlavor getFlavor() {
			if(flavour==null)
				flavour = new ComponentFlavor();
			return flavour;
		}
	}
} public static class ComponentFlavor extends DataFlavor {
	private static ComponentFlavor flavour;
	public ComponentFlavor() { super(Component.class, "Component");	}
	public static ComponentFlavor getFlavor() {
		if(flavour==null)
			flavour = new ComponentFlavor();
		return flavour;
	}
} public interface Configurable {
	public Option[] getOptions();
	public Map<Option, Object> getConfiguration();
	public void setConfiguration(Map<Option, Object> configuration) throws PropertyVetoException;

	public static class Option {
		public enum OptionType {
			NUMBER(Number.class),
			BOOLEAN(Boolean.class),
			TEXT(String.class),
			LIST((new Object[0]).getClass()),
			FILE(FileFilter.class);
			private Class<?> cls;
			private OptionType(Class<?> cls) { this.cls = cls; }
			public boolean checkType(Object object) {
				return this.cls.isAssignableFrom(object.getClass());
			}
		}
		private final String key, label;
		private final OptionType type;
		private final Object value;
		public Option(String label, OptionType type) { this(label, type, null); }
		public Option(String label, OptionType type, Object dfault) { this(label.toLowerCase().replaceAll("\\W", new String()), label, type, dfault);	}
		public Option(String key, String label, OptionType type) { this(key, label, type, null); }
		public Option(String key, String label, OptionType type, Object value) {
			this.key = key; this.label = label; this.type = type;
			if(type.equals(OptionType.LIST)&&value==null)
				throw new NullPointerException("You need to provide a default value for option "+key+", because it is of type 'list'.");
			if(value!=null&&!type.checkType(value))
				throw new ClassCastException("Default value of option "+key+" has to be of type "+type.toString().toLowerCase()+" but is actually of type "+value.getClass().getSimpleName());
			this.value = value;
		}
		public String getKey() { return key; }
		public String getLabel() { return label; }
		public OptionType getType() { return type; }
		public Object getDefault() { return value; }
		public boolean hasDefault() { return value!=null; }
		@Override public int hashCode() { return this.key.hashCode(); }
		@Override public boolean equals(Object object) {
			if(!(object instanceof Option))
				return false;
			return this.key.equals(((Option)object).key);
		}
	}
} public static class Option {
	public enum OptionType {
		NUMBER(Number.class),
		BOOLEAN(Boolean.class),
		TEXT(String.class),
		LIST((new Object[0]).getClass()),
		FILE(FileFilter.class);
		private Class<?> cls;
		private OptionType(Class<?> cls) { this.cls = cls; }
		public boolean checkType(Object object) {
			return this.cls.isAssignableFrom(object.getClass());
		}
	}
	private final String key, label;
	private final OptionType type;
	private final Object value;
	public Option(String label, OptionType type) { this(label, type, null); }
	public Option(String label, OptionType type, Object dfault) { this(label.toLowerCase().replaceAll("\\W", new String()), label, type, dfault);	}
	public Option(String key, String label, OptionType type) { this(key, label, type, null); }
	public Option(String key, String label, OptionType type, Object value) {
		this.key = key; this.label = label; this.type = type;
		if(type.equals(OptionType.LIST)&&value==null)
			throw new NullPointerException("You need to provide a default value for option "+key+", because it is of type 'list'.");
		if(value!=null&&!type.checkType(value))
			throw new ClassCastException("Default value of option "+key+" has to be of type "+type.toString().toLowerCase()+" but is actually of type "+value.getClass().getSimpleName());
		this.value = value;
	}
	public String getKey() { return key; }
	public String getLabel() { return label; }
	public OptionType getType() { return type; }
	public Object getDefault() { return value; }
	public boolean hasDefault() { return value!=null; }
	@Override public int hashCode() { return this.key.hashCode(); }
	@Override public boolean equals(Object object) {
		if(!(object instanceof Option))
			return false;
		return this.key.equals(((Option)object).key);
	}
} public enum OptionType {
	NUMBER(Number.class),
	BOOLEAN(Boolean.class),
	TEXT(String.class),
	LIST((new Object[0]).getClass()),
	FILE(FileFilter.class);
	private Class<?> cls;
	private OptionType(Class<?> cls) { this.cls = cls; }
	public boolean checkType(Object object) {
		return this.cls.isAssignableFrom(object.getClass());
	}
} public interface Interactable {
	public void mouseClick(MouseEvent event);
	public void mouseDoubleClick(MouseEvent event);
	public void mouseDown(MouseEvent event);
	public void mouseUp(MouseEvent event);
} public interface Sociable {
	public Contact[] getContacts();
} @HiddenComponent public class Wire extends Component implements Sociable, Charged {
	private static final long serialVersionUID = 2l;

	private float voltage;

	private Contact source, target;
	private Contact[] contacts;

	private Point preferredLocation;

	private LocationChangeListener listener;
	private transient GeneralPath line;
	private transient Area selection;

	public Wire(Contact source, Contact target) throws WireNotConnectable {
		listener = new LocationChangeListener();
		setSource(source);
		setTarget(target);
		contacts = new Contact[] {};
	}

	public Contact getSource() { return this.source; }
	public Contact getTarget() { return this.target; }
	public void setSource(Contact source) throws WireNotConnectable {
		if(source==null)
			throw new WiringException("You can not remove one side of a wire.");
		if(this.source!=null)
			removeWire(this.source);
		addWire(this.source = source);
	}
	public void setTarget(Contact target) throws WireNotConnectable {
		if(target==null)
			throw new WiringException("You can not remove one side of a wire.");
		if(this.target!=null)
			removeWire(this.target);
		addWire(this.target = target);
	}
	public void removeWire() {
		source.getComponent().removePropertyChangeListener("location", listener);
		source.removeWire(this);
		target.getComponent().removePropertyChangeListener("location", listener);
		target.removeWire(this);
	}

	private void addWire(Contact contact) throws WireNotConnectable {
		contact.addWire(this);
		contact.getComponent().addPropertyChangeListener("location", listener);
		revalidate();
	}
	private void removeWire(Contact contact) {
		contact.getComponent().removePropertyChangeListener("location", listener);
		contact.removeWire(this);
	}

	@Override public void paint(Graphics defaultGraphics) { paint(defaultGraphics, (Point[])null); }
	public void paint(Graphics defaultGraphics, Point... offset) {
		Graphics2D graphics = ((Graphics2D)defaultGraphics);
		graphics.setColor(isCharged()?Color.RED:Color.BLUE);
		if(offset!=null)
			revalidate(offset);
		else if(line==null)
			revalidate();
		graphics.draw(line);
	}
	@Override public Contact[] getContacts() { return contacts; }
	@Override public Dimension getSize() { return null; }
	@Override public void calculate() { //note this calculation method will be called prior to all others
		if(source!=null) //should never happen
			this.voltage = source.getVoltage();
		else this.voltage = Float.NaN;
	}

	@Override public float getVoltage() { return voltage; }
	@Override public boolean isCharged() throws ForbiddenVoltageLevel {
		float voltage = Math.abs(getVoltage());
		//everything between below 1 is 0
		return Math.floor(voltage)!=0;
	}

	public boolean containsLocation(Point location) { return selection.contains(location); }
	public void setPreferredLocation(Point location) {
		preferredLocation = location;
		revalidate();
	}

	@Override public Wire clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException("You can not clone a single wire. You have to create a new wire to connect some components.");
	}

	public void revalidate() { revalidate((Point[])null); }
	public void revalidate(Point... offset) {
		if(source==null||source.getComponent()==null||target==null||target.getComponent()==null)
			return;
		Point from = addPoints(source.getComponent().getLocation(), source.getLocation()),
				to = addPoints(target.getComponent().getLocation(), target.getLocation());
		if(offset!=null) {
			if(offset.length>=1) from = addPoints(from, offset[0]);
			if(offset.length>=2)   to = addPoints(to  , offset[1]);
		}
		if(line==null)
			line = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
		else line.reset();
		line.moveTo(from.x, from.y);
		if(from.x<to.x) {
			int m_x;
			if(preferredLocation!=null)
				m_x = Math.max(from.x, Math.min(to.x, preferredLocation.x));
			else if(source.getComponent() instanceof Junction)
				m_x = from.x;
			else m_x = (from.x+to.x)/2;
			line.lineTo(m_x, from.y);
			line.lineTo(m_x, to.y);
			line.lineTo(to.x, to.y);
		} else {
			int m_y;
			if(preferredLocation!=null)
				if(Utilities.getConfiguration("limit")!=null)
					if(from.y<to.y)
						m_y = Math.max(from.y, Math.min(to.y, preferredLocation.y));
					else m_y = Math.max(to.y, Math.min(from.y, preferredLocation.y));
				else m_y = preferredLocation.y;
			else m_y = (from.y+to.y)/2;
			if(!(source.getComponent() instanceof Junction)) {
				line.lineTo(from.x+20, from.y);
				line.lineTo(from.x+20, m_y);
			} else line.lineTo(from.x, m_y);
			line.lineTo(to.x-20, m_y);
			line.lineTo(to.x-20, to.y);
			line.lineTo(to.x, to.y);
		}

		selection = new Area();
		PathIterator iterator = line.getPathIterator(null);
		float[] current = null;
		while(!iterator.isDone()) {
			float[] last = current; current = new float[2];
			iterator.currentSegment(current); iterator.next();
			if(last==null)
				continue;
			if(last[0]!=current[0]) //horrizontal (else vertical) line
				selection.add(new Guitilities.Rectangle(new Point2D.Float(last[0], last[1]-3), new Point2D.Float(current[0], current[1]+4)).toArea());
			else selection.add(new Guitilities.Rectangle(new Point2D.Float(last[0]-3, last[1]), new Point2D.Float(current[0]+4, current[1])).toArea());
			selection.add(new Area(new Rectangle2D.Double(current[0]-3, current[1]-3, 7, 7))); //corner point
		}
	}

	public Area getSelectionArea() {
		if(line==null)
			revalidate();
		return this.selection;
	}

	protected class LocationChangeListener implements PropertyChangeListener, Serializable {
		private static final long serialVersionUID = 1l;
		@Override public void propertyChange(PropertyChangeEvent event) {
			preferredLocation = null;
			revalidate();
		}
	}
} public class Voltmeter extends Component implements Sociable, Configurable, Comparable<Voltmeter> {
	private static final long serialVersionUID = 2l;

	private static final String KEY;
	static { KEY = "component.display."+Voltmeter.class.getSimpleName().toLowerCase(); }
	public static final ComponentAttributes componentAttributes = new ComponentAttributes(KEY, getTranslation(KEY), "group.display", getTranslation(KEY, TranslationType.DESCRIPTION), "Kristian Kraljic (kris@kra.lc)", 1);

	private static int count;

	private Dimension size;

	private int number;
	private String name;
	private float voltage;

	private InputContact input;
	private Contact[] contacts;

	public Voltmeter(Point location) { this(); this.setLocation(location); }
	public Voltmeter() {
		size = new Dimension(25, 20);

		number = Voltmeter.count++;
		name = getOptions()[0].getDefault().toString();

		input = new InputContact(this, new Point(0, size.height/2));
		contacts = new Contact[]{input};
	}

	@Override public void paint(Graphics graphics) {
		graphics.setFont(graphics.getFont().deriveFont(8f));
		FontMetrics metrics = graphics.getFontMetrics();
		graphics.setColor(Color.BLACK);
		graphics.drawOval(5, 0, size.height, size.height);
		graphics.drawString(Utilities.cropString(name, 3), 5+(size.width-5)/2-metrics.stringWidth(name)/2,
				(size.height/2+metrics.getAscent()/2)-1);
		ContactUtilities.paintSolderingJoints(graphics, contacts);
	}

	@Override public Dimension getSize() { return size; }
	@Override public Contact[] getContacts() { return contacts; }
	@Override public void calculate() { } //nothing has to be calculated

	@Override public Option[] getOptions() { return new Option[]{new Option("name", Utilities.getTranslation("component.voltmeter.name"), OptionType.TEXT, Utilities.getTranslation("component.voltmeter.name.default", number))}; }
	@Override public void setConfiguration(Map<Option, Object> configuration) throws PropertyVetoException {
		String name = configuration.get(getOptions()[0]).toString();
		if(name.isEmpty()) throw new PropertyVetoException(Utilities.getTranslation("component.voltmeter.name.error"), null);
		this.name = name;
	}
	@Override public Map<Option, Object> getConfiguration() {
		Map<Option, Object> configuration = new HashMap<Option, Object>();
		configuration.put(getOptions()[0], name);
		return configuration;
	}

	@Override public int compareTo(Voltmeter voltmeter) {
		int compare = this.getName().compareTo(voltmeter.getName());
		if(compare!=0)
			return compare;
		return new Integer(hashCode()).compareTo(voltmeter.hashCode());
	}

	public float getVoltage() { return this.voltage; }
	public float gaugeVoltage() { return (this.voltage=this.input.getVoltage()); }
	public String getName() { return this.name; }
} public class ExternalSimulation extends Component implements Sociable, Configurable {
	private static final long serialVersionUID = 1l;

	private static final String KEY;
	static { KEY = "component.external."+ExternalSimulation.class.getSimpleName().toLowerCase(); }
	public static final ComponentAttributes componentAttributes = new ComponentAttributes(KEY, getTranslation(KEY), "group.external", getTranslation(KEY, TranslationType.DESCRIPTION), "Kristian Kraljic (kris@kra.lc)", 1);

	private Dimension size;

	private File file;
	private transient Simulation simulation;

	private transient Map<Contact, Connector> connectors;
	private ContactList<InputContact> inputs;
	private ContactList<OutputContact> outputs;
	private Contact[] contacts;

	private transient int repaintDelay = 0;

	public ExternalSimulation() {
		size = new Dimension(150, 100);
		inputs = new ContactList<InputContact>(this, InputContact.class);
		outputs = new ContactList<OutputContact>(this, OutputContact.class);
		contacts = new Contact[0];
	}

	@Override public void paint(Graphics graphics) {
		FontMetrics metrics = graphics.getFontMetrics();
		graphics.setColor(Color.BLACK);
		graphics.drawRect(5, 0, size.width-10, size.height);
		String name = Utilities.getTranslation("component.simulation.file.unknown");
		if(file!=null) {
			name = file.getName();
			if(name.endsWith(".jdsim"))
				name = name.substring(0, name.length()-".jdsim".length());
			if(simulation==null)
				graphics.setColor(Color.RED);
		} else graphics.setColor(Color.GRAY);
		graphics.drawString(Utilities.cropString(name, 20), 5+(size.width-5)/2-metrics.stringWidth(name)/2,
				(size.height/2+metrics.getAscent()/2)-1);
		graphics.setFont(graphics.getFont().deriveFont(8f));
		metrics = graphics.getFontMetrics();
		int height = metrics.getAscent()/3;
		for(InputContact input:inputs) {
			Connector connector = connectors.get(input);
			if(connector!=null)
				graphics.drawString(connector.getName(), 10, input.getLocation().y+height);
		}
		for(OutputContact output:outputs) {
			Connector connector = connectors.get(output);
			if(connector!=null) {
				name = connector.getName();
				graphics.drawString(name, size.width-metrics.stringWidth(name)-10, output.getLocation().y+height);
			}
		}
		ContactUtilities.paintSolderingJoints(graphics, contacts);
	}

	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		if(file==null) return;
		if(!file.exists()) file = new File(Utilities.getFile(Utilities.getLocalPath()).getPath()+File.separatorChar+file.getName());
		if(!file.exists()&&Application.currentDirectory!=null)
			this.file = new File(Application.currentDirectory.getPath()+File.separatorChar+file.getName());
		loadSimulation(this.file);
	}

	@Override public Dimension getSize() { return size; }
	@Override public Contact[] getContacts() { return contacts; }
	@Override public void calculate() {
		if(simulation==null)
			return;
		for(InputContact input:inputs) {
			Connector connector = connectors.get(input);
			if(connector instanceof InboundConnector)
				((InboundConnector)connector).setCharged(input.isCharged());
		}
		simulation.doSimulationStep();
		for(OutputContact output:outputs) {
			Connector connector = connectors.get(output);
			if(connector instanceof OutboundConnector)
				output.setCharged(((OutboundConnector)connector).isCharged());
		}
		if(++repaintDelay>=10) {
			repaintDelay = 0;
			simulation.repaint();
		}
	}

	@Override public Option[] getOptions() { return new Option[]{new Option("file", Utilities.getTranslation("component.simulation.file"), OptionType.FILE, new FileNameExtensionFilter(getTranslation("persist.filter.description"), Application.FILE_EXTENSION))}; }
	@Override public void setConfiguration(Map<Option, Object> configuration) throws PropertyVetoException {
		loadSimulation(file = (File)configuration.get(getOptions()[0]));
	}
	@Override public Map<Option, Object> getConfiguration() {
		Map<Option, Object> configuration = new HashMap<Option, Object>();
		configuration.put(getOptions()[0], file);
		return configuration;
	}

	public Simulation getSimulation() { return this.simulation; }
	public File getFile() { return this.file; }

	private void loadSimulation(File file) {
		if(file==null||!file.exists()||!file.isFile()||!file.canRead())
			return;
		try {
			int inputs=0, outputs=0;
			//important use of LINKEDHashMap, because sort ordering is required because keySet method is used for getContacts
			connectors = new LinkedHashMap<Contact, Connector>();
			simulation = Simulation.readSimulation(new BufferedInputStream(new FileInputStream(file)));
			for(Component component:simulation.getAllComponents())
				if(component instanceof InboundConnector) {
					inputs++;
					if(this.inputs.getContactsCount()<inputs)
						this.inputs.setContacts(inputs);
					connectors.put(this.inputs.getContact(inputs-1), (Connector)component);
				} else if(component instanceof OutboundConnector) {
					outputs++;
					if(this.outputs.getContactsCount()<outputs)
						this.outputs.setContacts(outputs);
					connectors.put(this.outputs.getContact(outputs-1), (Connector)component);
				}
			this.inputs.setContacts(inputs);
			this.inputs.setContactLocations(new Point(0, 0));
			this.outputs.setContacts(outputs);
			this.outputs.setContactLocations(new Point(size.width, 0));
			contacts = connectors.keySet().toArray(contacts);
		} catch (PasswordRequiredException e) {
			//TODO
		}	catch(Exception e) {
			e.printStackTrace();
		}
	}
} @HiddenComponent public class Junction extends Component implements Sociable {
	private static final long serialVersionUID = 2l;

	private static final String KEY;
	static { KEY = "component.general."+Junction.class.getSimpleName().toLowerCase(); }
	public static final ComponentAttributes componentAttributes = new ComponentAttributes(KEY, getTranslation(KEY), "group.general", getTranslation(KEY, TranslationType.DESCRIPTION), "Kristian Kraljic (kris@kra.lc)", 1);

	private Dimension size;

	public final InputContact input;
	public final OutputContact output;
	private Contact[] contacts;

	public Junction(Point location) { this(); setLocation(location); }
	public Junction() {
		size = new Dimension(20, 20);
		input = new InputContact(this, new Point(size.width/2, size.height/2));
		output = new OutputContact(this, new Point(size.width/2, size.height/2)) {
			private static final long serialVersionUID = 1l;
			@Override public float getVoltage() { return input.getVoltage(); }
		};
		contacts = new Contact[]{input, output};
	}

	@Override public void paint(Graphics graphics) {
		graphics.setColor(Color.BLACK);
		graphics.fillOval(6, 6, size.width-12, size.height-12);
	}

	@Override public Dimension getSize() { return size; }
	@Override public Contact[] getContacts() { return contacts; }
	@Override public void calculate() {} //no calculation requried, special beaviour of outbound contact
} public abstract class Contact implements Cloneable, Paintable, Locatable, Charged, Serializable {
	private static final long serialVersionUID = 1l;

	protected static final int CONTACT_SIZE = 4;

	protected List<Wire> wires;

	public Contact() { wires = new ArrayList<Wire>(); }
	public Contact(Wire wire) throws WireNotConnectable {
		this();
		this.setWire(wire);
	}

	public void setWire(Wire wire) throws WireNotConnectable { setWires(wire); }
	public void setWires(Wire... wires) throws WireNotConnectable { this.setWires(Arrays.asList(wires)); }
	public void setWires(List<Wire> wires) throws WireNotConnectable {
		this.wires.clear();
		for(Wire wire:wires)
			addWire(wire);
	}
	public void addWire(Wire wire) throws WireNotConnectable {
		if(!this.wires.contains(wire))
			this.wires.add(wire);
	}
	public Wire getWire() { return !isWired()?null:this.wires.get(0); }
	public List<Wire> getWires() { return this.wires; }
	public boolean isWired() { return countWires()!=0; }
	public int countWires() { return wires.size(); }
	public boolean removeWire(Wire wire) { return this.wires.remove(wire); }
	public void removeWires() { this.wires.clear(); }

	public abstract Component getComponent();

	@Override public abstract float getVoltage();
	@Override public boolean isCharged() throws ForbiddenVoltageLevel {
		float voltage = Math.abs(getVoltage());
		//everything between below 1 is 0
		return Math.floor(voltage)!=0;
	}

	@Override public void paint(Graphics g) {
		try { validateLocation(); }
		catch(LocationOutOfBoundsException e) {
			return; //this location does not exist for this component
		}
		Point location = getLocation();
		g.setColor(Color.BLACK);
		g.fillRect(location.x-CONTACT_SIZE/2, location.y-CONTACT_SIZE/2,
				CONTACT_SIZE, CONTACT_SIZE);
	}

	protected abstract void validateLocation() throws LocationOutOfBoundsException;

	@Override public Contact clone() throws CloneNotSupportedException {
		Contact clone = (Contact)super.clone();
		clone.wires = new ArrayList<Wire>(); //do not clone wires!
		return clone;
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		List<Wire> wires = this.wires;
		if(Utilities.isCopying()) //do not clone wires!
			this.wires = new ArrayList<Wire>();
		out.defaultWriteObject();
		this.wires = wires;
	}
} public final class DragContact extends Contact {
	private static final long serialVersionUID = 1l;

	public final Component component;
	private EventListener[] listener;

	public DragContact(final Simulation simulation, final Class<? extends Contact> cls) {
		this.component = new Component() {
			private static final long serialVersionUID = 1l;
			@Override public void paint(Graphics g) { }
			@Override public Dimension getSize() { return new Dimension(1, 1); }
			@Override public void calculate() {}
			@Override public Component clone() throws CloneNotSupportedException {
				throw new CloneNotSupportedException("You shall not try to clone the component of a drag contact.");
			}
		};
		listener = new EventListener[2];
		simulation.addMouseListener((MouseListener) (listener[0]=new MouseAdapter() {
			@Override public void mouseReleased(MouseEvent event) {
				simulation.removeComponent(component);
				simulation.removeMouseListener((MouseListener)listener[0]);
				simulation.removeMouseMotionListener((MouseMotionListener)listener[1]);
				Contact contact = simulation.searchContactAt(simulation.applyZoom(event.getPoint()), cls);
				Wire wire = getWire();
				if(contact!=null)
					try {
						if(wire.getSource().equals(DragContact.this)) wire.setSource(contact);
						else if(wire.getTarget().equals(DragContact.this)) wire.setTarget(contact);
					} catch(WireNotConnectable e) { wire.removeWire(); }
				else wire.removeWire();
				simulation.repaint();
			}
		}));
		simulation.addMouseMotionListener((MouseMotionListener) (listener[1]=new MouseMotionAdapter() {
			private Point center = new Point(-1, -1);
			@Override public void mouseDragged(MouseEvent event) {
				try {
					Point location = simulation.applyZoom(event.getPoint());
					Contact contact = simulation.searchContactAt(location, cls);
					if(contact!=null)
						component.setLocation(Guitilities.addPoints(contact.getComponent().getLocation(), contact.getLocation(), center));
					else component.setLocation(location);
				}	catch(LocationOutOfBoundsException e) { } //ignore this, if it is dropped outside, nothing happens
			}
		}));
	}
	@Override public float getVoltage() { return 0; }
	@Override public Point getLocation() { return new Point(1, 1); }
	@Override public void setLocation(Point location) {}
	@Override protected void validateLocation() {}
	@Override public Component getComponent() { return component; }
	@Override public void paint(Graphics g) { }
} public class InputContact extends ComponentContact implements Cloneable {
	private static final long serialVersionUID = 1l;

	public InputContact(Component component) { super(component); }
	public InputContact(Component component, Point location) { super(component, location); }

	@Override public void addWire(Wire wire) throws WireNotConnectable {
		if(countWires()>=1)
			throw new WireNotConnectable("Is is only allowed to connect one wire to an input contact.");
		else if(wire.getTarget()!=this)
			throw new WireNotConnectable("This contact needs to be the target contact to be connected with an wire.");
		else if(wire.getSource()!=null&&wire.getSource().getClass().equals(InputContact.class))
			throw new WireNotConnectable("This contact can not be connected to another input contact.");
		else super.addWire(wire);
	}

	@Override public float getVoltage() {
		if(!isWired())
			return Float.NaN;
		/*Contact contact = getWire().getSource();
		if(contact.equals(this)) {
			System.err.println("Wrong wiring!");
			return Float.NaN;
		}*/
		return getWire().getVoltage();
	}
	@Override
	public boolean isCharged() throws ForbiddenVoltageLevel {
		float voltage = Math.abs(getVoltage());
		//forbidden voltage level between 0.4 and 2.4
		if((voltage<0.4&&voltage>2.4)||voltage>5)
			throw new ForbiddenVoltageLevel(voltage, new float[]{0f, 0.4f, 2.4f, 5f});
		return super.isCharged(); //check for NaN
	}

	@Override public void paint(Graphics g) {
		try { validateLocation(); }
		catch(LocationOutOfBoundsException e) {
			return; //this location does not exist for this component
		} finally { super.paint(g); }
		Point location = getLocation();
		g.setColor(Color.GRAY);
		g.fillRect(location.x-CONTACT_SIZE/2+1, location.y-CONTACT_SIZE/2+1,
				CONTACT_SIZE-2, CONTACT_SIZE-2);
	}
} public class OutputContact extends ComponentContact implements Cloneable, Chargeable {
	private static final long serialVersionUID = 1l;

	private float voltage;

	public OutputContact(Component component) { super(component); }
	public OutputContact(Component component, Point location) { super(component, location); }

	@Override public void addWire(Wire wire) throws WireNotConnectable {
		if(wire.getSource()!=this)
			throw new WireNotConnectable("This contact needs to be the source contact to be connected with an wire.");
		else if(wire.getTarget()!=null&&wire.getTarget().getClass().equals(OutputContact.class))
			throw new WireNotConnectable("This contact can not be connected to another output contact.");
		else super.addWire(wire);
	}

	@Override
	public void setCharged() { this.setCharged(true); }
	@Override
	public void setCharged(boolean charged) { this.setVoltage(charged?5:0);	}
	@Override
	public void setVoltage(float voltage) { this.voltage = voltage; }
	@Override
	public float getVoltage() { return this.voltage; }
	@Override public boolean isCharged() {
		float voltage = Math.abs(getVoltage());
		//forbidden voltage level between 0.8 and 2
		if((voltage<0.8&&voltage>2)||voltage>5)
			throw new ForbiddenVoltageLevel(voltage, new float[]{0f, 0.8f, 2f, 5f});
		return super.isCharged(); //check for NaN
	}

	@Override public void paint(Graphics g) {
		try { validateLocation(); }
		catch(LocationOutOfBoundsException e) {
			return; //this location does not exist for this component
		} finally { super.paint(g); }
		Point location = getLocation();
		if(this.isCharged())
			g.setColor(Color.RED);
		else g.setColor(Color.BLUE);
		g.fillRect(location.x-CONTACT_SIZE/2+1, location.y-CONTACT_SIZE/2+1,
				CONTACT_SIZE-2, CONTACT_SIZE-2);
	}
} public class LocationOutOfBoundsException extends RuntimeException {
	private static final long serialVersionUID = 3291698016184856236L;
	public LocationOutOfBoundsException() { super("The location you have tried to set is out of bounds."); }
	public LocationOutOfBoundsException(Point location) { super("The location "+location.x+"/"+location.y+" is out of bounds."); }
	public LocationOutOfBoundsException(Point location, Dimension bounds) {
		super("The location "+location.x+"/"+location.y+" is out of the bounds "+bounds.width+"/"+bounds.height);
	}
} public class PasswordRequiredException extends GeneralSecurityException {
	private static final long serialVersionUID = 1l;
} public class WireNotConnectable extends Exception {
	private static final long serialVersionUID = -3969620371862087405L;
	public WireNotConnectable() { super(); }
	public WireNotConnectable(String message) { super(message); }
} public class WiringException extends RuntimeException {
	private static final long serialVersionUID = 1l;
	public WiringException() { super(); }
	public WiringException(String message) { super(message); }
} public class Application extends JFrame {
	private static final long serialVersionUID = -4693271310855486553L;

	public static final String FILE_EXTENSION = "jdsim";
	public static File pluginDirectory, currentDirectory;

	private static final String VERSION = "2.1.0", COPYRIGHT = "2010-2022", LINES_OF_CODE = "9.509", WORDS_OF_CODE = "36.133", PAGES_OF_CODE = "245";

	private static final String[]
		TOOLBAR_FRAME_FOCUS = new String[]{"save", "print", "print_level", "simulate", "left", "right", "up", "down", "grid", "secure", "zoom_default", "zoom", "zoom_in", "zoom_out"},
		TOOLBAR_COMPONENT_FOCUS = new String[]{"front", "back"},
		MENUBAR_FRAME_FOCUS = new String[]{"file.close", "file.save", "file.save_as", "file.print", "file.print_level", "edit.paste", "edit.select_all"},
		MENUBAR_COMPONENT_FOCUS = new String[]{"edit.cut", "edit.copy", "edit.delete", "edit.front", "edit.back"};

	private static final Set<Application> applications = new HashSet<Application>(1);

	private Map<String, HashMap<ComponentAttributes, Class<? extends Component>>> componentGroups;
	private List<ComponentButton> componentButtons;
	private List<CollapsiblePanel> collapsiblePanels;

	private JToolBar toolbar;
	private SimulationDesktop desktop;
	private SimulationOscilloscope oscilloscope;
	private Clipboard clipboard;

	public Application() {
		clipboard = new Clipboard();
		componentGroups = new HashMap<String, HashMap<ComponentAttributes, Class<? extends Component>>>();
		componentButtons = new Vector<ComponentButton>(); collapsiblePanels = new Vector<CollapsiblePanel>();
		loadBuildinComponents(); loadPluginComponents();
		this.configureGlassPane();
		this.setJMenuBar(createMenuBar());
		this.add(toolbar=createToolBar(), BorderLayout.NORTH);
		this.setSize(new Dimension(1000, 600));
		this.setLocationByPlatform(true);
		if(!hasConfiguration(CONFIGURATION_WINDOW_MAXIMIZED)||!Boolean.parseBoolean(getConfiguration(CONFIGURATION_WINDOW_MAXIMIZED))) {
			if(hasConfiguration(CONFIGURATION_WINDOW_SIZE)) {
				String[] size = getConfiguration(CONFIGURATION_WINDOW_SIZE).split(", ", 3);
				if(size.length==2&&Utilities.isNumeric(size[0])&&Utilities.isNumeric(size[1]))
					this.setSize(new Dimension(Integer.parseInt(size[0]), Integer.parseInt(size[1])));
			}
			if(hasConfiguration(CONFIGURATION_WINDOW_LOCATION)) {
				String[] location = getConfiguration(CONFIGURATION_WINDOW_LOCATION).split(", ", 3);
				if(location.length==2&&Utilities.isNumeric(location[0])&&Utilities.isNumeric(location[1]))
					this.setLocation(Integer.parseInt(location[0]), Integer.parseInt(location[1]));
			}
		} else this.setExtendedState(MAXIMIZED_BOTH);
		this.add(createSplitPane(JSplitPane.VERTICAL_SPLIT, Guitilities.getActualSize(this).height-200, true, 0.95d, createSplitPane(JSplitPane.HORIZONTAL_SPLIT, 200, true, 0.1d, createSidePanel(), new JScrollPane(desktop=createDesktopPanel())), new JScrollPane(oscilloscope=createOscilloscope())), BorderLayout.CENTER);
		this.setToolBarButtonHeight(40);
		this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		this.setIconImage(Guitilities.createImageIcon("icon.png").getImage());
		this.setTitle("JDigitalSimulator "+getTranslation("translation.title"));
		this.addWindowListener(new WindowAdapter() {
			@Override public void windowClosing(WindowEvent event) { exitApplication(); }
		});
		this.setVisible(true);
		applications.add(this);
	}

	private void configureGlassPane() {
		JPanel panel = (JPanel) getGlassPane();
		panel.setVisible(true);
		panel.setLayout(null);
	}

	private JSplitPane createSplitPane(int orientation, int location, boolean continuous, double resizeWeight, java.awt.Component component_a, java.awt.Component component_b) {
		JSplitPane split = new JSplitPane(orientation, continuous, component_a, component_b);
		split.setResizeWeight(resizeWeight);
		split.setDividerLocation(location);
		split.setOneTouchExpandable(true);
		return split;
	}

	private void loadBuildinComponents() {
		loadComponents("lc.kra.jds.components");
		if(componentGroups.isEmpty())
			JOptionPane.showMessageDialog(Application.this, getTranslation("components.load.error"), getTranslation("components.load.error", TranslationType.TITLE), JOptionPane.WARNING_MESSAGE);
	}
	private void loadPluginComponents() {
		if(pluginDirectory==null||!pluginDirectory.exists())
			pluginDirectory = Utilities.getFile(Utilities.getLocalPath());
		while(pluginDirectory!=null&&pluginDirectory.exists()&&!pluginDirectory.isDirectory())
			pluginDirectory = pluginDirectory.getParentFile();
		if(pluginDirectory==null||!pluginDirectory.exists()) return;
		File[] files = pluginDirectory.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) { return name.equalsIgnoreCase("plugins"); }
		});
		if(files!=null&&files.length>=1)
			loadComponents(files[0], null);
	}

	@SuppressWarnings("unused")
	private void loadComponents() { loadComponents(null); }
	private void loadComponents(String checkPackage) { loadComponents(Utilities.getLocalPath(), checkPackage); }
	private void loadComponents(URL ressource, String checkPackage) {
		if(ressource.getProtocol().equals("jar"))
			try { loadComponents(new JarFile(Utilities.getFile(ressource)), checkPackage); }
		catch(Throwable t) { }
		else loadComponents(Utilities.getFile(ressource), checkPackage);
	}
	private void loadComponents(File likelyFile, String checkPackage) { loadComponents(likelyFile, checkPackage, null); }
	private void loadComponents(File likelyFile, String checkPackage, String currentPackage) {
		if(likelyFile.isDirectory())
			for(File file:likelyFile.listFiles())
				if(file.isDirectory())
					loadComponents(file, checkPackage, currentPackage!=null&&!currentPackage.isEmpty()?currentPackage+'.'+file.getName():file.getName());
				else loadComponents(file, checkPackage, currentPackage);
		else try { loadComponents(new JarFile(likelyFile), checkPackage); }
		catch(Throwable t) {
			try { loadComponent(likelyFile.getName(), checkPackage, currentPackage); }
			catch(Throwable t_a) {
				try { loadComponent(Utilities.getSimpleClassLoader().loadClass(likelyFile)); }
				catch(Throwable t_b) {	} //that was our last trick... nothing to do here anymore
			}
		}
	}
	private void loadComponents(final JarFile file, String checkPackage) {
		Enumeration<JarEntry> enumeration = file.entries();
		while(enumeration.hasMoreElements()) {
			final JarEntry entry = enumeration.nextElement();
			try { loadComponent(entry.getName(), checkPackage); }
			catch (Throwable t) {
				try { loadComponent(Utilities.getSimpleClassLoader().loadClass(file, entry)); }
				catch(Throwable t_a) { } //that was our last trick... nothing to do here anymore
			}
		}
	}

	private boolean loadComponent(String name, String checkCurrentPackage) throws ClassNotFoundException { return loadComponent(name, checkCurrentPackage, checkCurrentPackage); }
	private boolean loadComponent(String name, String checkPackage, String currentPackage) throws ClassNotFoundException {
		if(name.indexOf('/')<0) {
			if(currentPackage!=null) name = currentPackage.isEmpty()?name:currentPackage+'.'+name;
			else if(checkPackage  !=null) name = checkPackage.isEmpty()  ?name:checkPackage+'.'+name;
		} else name = name.replace('/', '.');
		if((checkPackage==null||name.startsWith(checkPackage))&&name.endsWith(".class"))
			return loadComponent(Class.forName(name.substring(0, name.length()-6)));
		return false;
	}
	@SuppressWarnings("unchecked")
	private boolean loadComponent(Class<?> cls) {
		if(cls.isAnnotationPresent(HiddenComponent.class)
				|| Modifier.isAbstract(cls.getModifiers())
				||!Component.class.isAssignableFrom(cls)
				|| cls.isLocalClass() || cls.isMemberClass() || cls.isAnonymousClass())
			return false;
		for(Class<?> superCls=cls;;superCls=superCls.getSuperclass())
			if(superCls.equals(Component.class)) break;
			else if(superCls.equals(Object.class)) return false;
		Class<? extends Component> componentCls = null;
		try { componentCls = (Class<? extends Component>) cls; }
		catch(ClassCastException e) { return false; }
		ComponentAttributes attributes = Component.getAttributes(componentCls);
		HashMap<ComponentAttributes, Class<? extends Component>> component_group;
		if(!componentGroups.containsKey(attributes.group)) {
			component_group = new HashMap<ComponentAttributes, Class<? extends Component>>();
			componentGroups.put(attributes.group, component_group);
		} else component_group = componentGroups.get(attributes.group);
		component_group.put(attributes, componentCls);
		return true;
	}

	private JPanel createSidePanel() {
		JPanel side = new JPanel(new BorderLayout());
		side.add(createTitlePanel(), BorderLayout.NORTH);
		side.add(Guitilities.createScrollPane(createCatalogPanel(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER, 14, 0), BorderLayout.CENTER);
		return side;
	}

	private JPanel createTitlePanel() {
		JPanel title = new JPanel(new BorderLayout());
		title.add(Guitilities.createGradientTitle(getTranslation("components.title")), BorderLayout.NORTH);

		JPanel interact = new JPanel();
		interact.setBackground(UIManager.getColor(Guitilities.SUB_PANEL_BACKGROUND));
		interact.setLayout(new BoxLayout(interact, BoxLayout.X_AXIS));
		interact.setBorder(new CompoundBorder(Guitilities.CHISEL_BORDER, new EmptyBorder(12, 8, 12, 8)));
		final JCheckBox checkImages = new JCheckBox(getTranslation("group.images"), true);
		checkImages.setBackground(UIManager.getColor(Guitilities.SUB_PANEL_BACKGROUND));
		checkImages.addActionListener(new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				for(ComponentButton button:componentButtons)
					button.setIconVisible(checkImages.isSelected());
				for(CollapsiblePanel panel:collapsiblePanels)
					panel.setExpanded(false);
			}
		});
		interact.add(checkImages);
		title.add(interact, BorderLayout.CENTER);
		return title;
	}

	private JPanel createCatalogPanel() {
		JPanel selector = new JPanel();

		Icon expandedIcon = new ArrowIcon(ArrowIcon.SOUTH, UIManager.getColor(Guitilities.TITLE_FOREGROUND));
		Icon collapsedIcon = new ArrowIcon(ArrowIcon.EAST, UIManager.getColor(Guitilities.TITLE_FOREGROUND));

		GridBagLayout gridbag = new GridBagLayout();
		selector.setLayout(gridbag);
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.gridx = constraints.gridy = 0; constraints.weightx = 1;
		constraints.fill = GridBagConstraints.HORIZONTAL;

		GridBagConstraints categoryConstraints = new GridBagConstraints();
		categoryConstraints.gridx = categoryConstraints.gridy = 0; categoryConstraints.weightx = 1;
		categoryConstraints.fill = GridBagConstraints.HORIZONTAL;

		CollapsiblePanel collapsible;
		for(Entry<String, HashMap<ComponentAttributes, Class<? extends Component>>> group_entry:componentGroups.entrySet()) {
			JPanel category = new JPanel();
			GridBagLayout categoryGridbag = new GridBagLayout();
			category.setLayout(categoryGridbag);

			collapsible = new CollapsiblePanel(category, getTranslation(group_entry.getKey()), getTranslation("group.expand"));
			collapsible.setBorder(Guitilities.CATEGORY_BORDER);
			collapsible.setFont(UIManager.getFont("CheckBox.font").deriveFont(Font.BOLD));
			collapsible.setForeground(UIManager.getColor(Guitilities.TITLE_FOREGROUND));
			collapsible.setExpandedIcon(expandedIcon);
			collapsible.setCollapsedIcon(collapsedIcon);
			collapsible.setExpanded(false);
			collapsiblePanels.add(collapsible);

			gridbag.addLayoutComponent(collapsible, constraints);
			selector.add(collapsible);
			constraints.gridy++;

			for(Entry<ComponentAttributes, Class<? extends Component>> entry:group_entry.getValue().entrySet())
				try {
					ComponentButton button = new ComponentButton(entry.getKey(), entry.getValue());
					categoryGridbag.addLayoutComponent(button, categoryConstraints);
					categoryConstraints.gridy++;
					category.add(button);
					componentButtons.add(button);
				} catch (Exception e) { e.printStackTrace(); }
		}

		JPanel trailer = new JPanel();
		constraints.weighty = 1.0;
		gridbag.addLayoutComponent(trailer, constraints);
		selector.add(trailer);

		return selector;
	}

	private SimulationDesktop createDesktopPanel() {
		final SimulationDesktop desktop = new SimulationDesktop();
		desktop.setAutoscrolls(true);
		desktop.addContainerListener(new ContainerListener() {
			@Override public void componentRemoved(ContainerEvent event) { revalidate(); }
			@Override public void componentAdded(ContainerEvent event) { revalidate(); }
		});
		return desktop;
	}

	private JMenuBar createMenuBar() {
		JMenuBar menubar = new JMenuBar();
		JMenu file = new JMenu(getTranslation("menubar.file"));
		file.add(createMenuItem("file.new", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { newWorksheet(); }
		}));
		file.add(createMenuItem("file.open", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { openWorksheet(); }
		}));
		file.add(createMenuItem("file.close", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				try { desktop.getSelectedFrame().setClosed(true); }
				catch (PropertyVetoException e) { }
			}
		}));
		file.add(createMenuItem("file.save", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { saveWorksheet(); }
		}));
		file.add(createMenuItem("file.save_as", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { saveWorksheetAs(); }
		}));
		file.add(new JSeparator());
		file.add(createMenuItem("file.print", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { printWorksheet(); }
		}));
		file.add(createMenuItem("file.print_level", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { printWorksheetLevel(); }
		}));
		file.add(new JSeparator());
		file.add(createMenuItem("file.exit", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { exitApplication(); }
		}));
		menubar.add(file);

		JMenu edit = new JMenu(getTranslation("menubar.edit"));
		edit.add(createMenuItem("edit.undo", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				getActiveSimulation().undoChange();
				revalidate();
			}
		}));
		edit.add(createMenuItem("edit.redo", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				getActiveSimulation().redoChange();
				revalidate();
			}
		}));
		edit.add(new JSeparator());
		edit.add(createMenuItem("edit.cut", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				try { getActiveSimulation().cutIntoClipboard(); }
				catch (CloneNotSupportedException e) { }
				revalidate();
			}
		}));
		edit.add(createMenuItem("edit.copy", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				try { getActiveSimulation().copyToClipboard(); }
				catch (CloneNotSupportedException e) { }
				revalidate();
			}
		}));
		edit.add(createMenuItem("edit.paste", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				try { getActiveSimulation().pasteFromClipboard(); }
				catch (CloneNotSupportedException e) { }
				revalidate();
			}
		}));
		edit.add(new JSeparator());
		edit.add(createMenuItem("edit.delete", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.removeComponents(simulation.getSelectedComponents());
				revalidate();
			}
		}));
		edit.add(createMenuItem("edit.select_all", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setSelectedComponents(simulation.getAllComponents());
				revalidate();
			}
		}));
		edit.add(new JSeparator());
		edit.add(createMenuItem("edit.front", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setComponentLayers(simulation.getSelectedComponents(), Layer.TOPMOST);
				revalidate();
			}
		}));
		edit.add(createMenuItem("edit.back", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setComponentLayers(simulation.getSelectedComponents(), Layer.BOTTOMMOST);
				revalidate();
			}
		}));
		menubar.add(edit);

		JMenu properties = new JMenu(getTranslation("menubar.properties"));
		properties.add(createMenuItem("properties.worksheet", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				SimulationFrame frame = desktop.getSelectedFrame();
				Simulation simulation = frame.getSimulation();
				final Simulation.SimulationProperies properties = simulation.properties;
				String fileName = getTranslation("properties.worksheet", TranslationType.TITLE, frame.getFileName());
				final JDialog dialog = Guitilities.createDialog(Application.this, fileName);

				JPanel centerPane = new JPanel();
				centerPane.setBorder(Guitilities.LARGE_EMPTY_BORDER);
				centerPane.setLayout(new BoxLayout(centerPane, BoxLayout.Y_AXIS));
				dialog.add(centerPane, BorderLayout.CENTER);

				centerPane.add(Guitilities.createSeparator(getTranslation("properties.author")));
				JPanel authorPane = new JPanel(new GridBagLayout());
				final JTextField authorName = Guitilities.addGridPairLine(authorPane, 0, new JLabel(getTranslation("properties.author.name")), new JTextField(properties.author.name));
				final JTextField authorMail = Guitilities.addGridPairLine(authorPane, 1, new JLabel(getTranslation("properties.author.mail")), new JTextField(properties.author.mail));
				centerPane.add(authorPane);

				centerPane.add(Guitilities.createSeparator(getTranslation("properties.circuit")));
				JPanel circuitPane = new JPanel(new GridBagLayout());
				final JTextField         circuitName = Guitilities.addGridPairLine(circuitPane, 0, new JLabel(getTranslation("properties.circuit.name")), new JTextField(properties.circuit.name));
				Guitilities.pair_left_constraint.anchor = GridBagConstraints.FIRST_LINE_END;
				final JScrollPane circuitDescription = Guitilities.addGridPairLine(circuitPane, 1, new JLabel(getTranslation("properties.circuit.description")), new JScrollPane(new JTextArea(properties.circuit.description, 6, 0)));
				final JTextField      circuitVersion = Guitilities.addGridPairLine(circuitPane, 2, new JLabel(getTranslation("properties.circuit.version")), new JTextField(properties.circuit.version));
				centerPane.add(circuitPane);

				JPanel bottomPane = Guitilities.createGradientFooter();
				bottomPane.add(Guitilities.createButton(getTranslation("properties.secure"), new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) { getToolBarButton("secure").getActionListeners()[0].actionPerformed(event); }
				}));
				bottomPane.add(new JSeparator(JSeparator.VERTICAL));
				final ActionListener cancelListener = new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) { dialog.dispose(); }
				}, applyListener = new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) {
						properties.author.name = authorName.getText();
						properties.author.mail = authorMail.getText();
						properties.circuit.name = circuitName.getText();
						properties.circuit.description = ((JTextArea)circuitDescription.getViewport().getComponent(0)).getText();
						properties.circuit.version = circuitVersion.getText();
					}
				};
				bottomPane.add(Guitilities.createButton(getTranslation("properties.okay"), new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) {
						applyListener.actionPerformed(event);
						cancelListener.actionPerformed(event);
					}
				}));
				bottomPane.add(Guitilities.createButton(getTranslation("properties.cancel"), cancelListener));
				bottomPane.add(Guitilities.createButton(getTranslation("properties.apply"), applyListener));
				dialog.add(bottomPane, BorderLayout.SOUTH);

				dialog.pack(); dialog.setSize(new Dimension(500, dialog.getHeight()));
				dialog.setVisible(true);
			}
		}));
		properties.add(new JSeparator());
		properties.add(createMenuItem("properties.general", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				final JDialog dialog = Guitilities.createDialog(Application.this, getTranslation("properties.general", TranslationType.TITLE));

				JPanel centerPane = new JPanel();
				centerPane.setBorder(Guitilities.LARGE_EMPTY_BORDER);
				centerPane.setLayout(new BoxLayout(centerPane, BoxLayout.Y_AXIS));
				dialog.add(centerPane, BorderLayout.CENTER);

				centerPane.add(Guitilities.createSeparator(getTranslation("properties.localization")));
				JPanel localizationPane = new JPanel(new GridBagLayout());
				final JComboBox<String> localizationLanguage = Guitilities.addGridPairLine(localizationPane, 0, new JLabel(getTranslation("properties.localization.language")), new JComboBox<String>());
				for(Locale locale:Utilities.SUPPORTED_LOCALES)
					localizationLanguage.addItem(locale.getDisplayLanguage());
				localizationLanguage.setSelectedItem(Utilities.getCurrentLocale().getDisplayLanguage());
				centerPane.add(localizationPane);

				centerPane.add(Box.createVerticalStrut(20));

				centerPane.add(Guitilities.createSeparator(getTranslation("properties.lookandfeel")));
				JPanel lookAndFeelPane = new JPanel(new GridBagLayout());
				final JComboBox<String> lookAndFeel = Guitilities.addGridPairLine(lookAndFeelPane, 0, new JLabel(getTranslation("properties.lookandfeel.name")), new JComboBox<String>());
				for(LookAndFeelInfo info:UIManager.getInstalledLookAndFeels())
					lookAndFeel.addItem(info.getName());
				lookAndFeel.setSelectedItem(UIManager.getLookAndFeel().getName());
				centerPane.add(lookAndFeelPane);

				JPanel bottomPane = Guitilities.createGradientFooter();
				final ActionListener cancelListener = new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) { dialog.dispose(); }
				}, applyListener = new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) {
						String displayName = localizationLanguage.getSelectedItem().toString();
						for(Locale locale:Utilities.SUPPORTED_LOCALES)
							if(displayName.equals(locale.getDisplayName()) && !locale.equals(Utilities.getCurrentLocale())) {
								Utilities.setCurrentLocale(locale);
								Utilities.setConfiguration(Utilities.CONFIGURATION_LOCALIZATION_LANGUAGE, locale.getLanguage());
								JOptionPane.showMessageDialog(dialog, getTranslation("properties.localization.info"));
							}
						String lookAndFeelName = lookAndFeel.getSelectedItem().toString();
						for(LookAndFeelInfo info:UIManager.getInstalledLookAndFeels())
							if(lookAndFeelName.equals(info.getName()) && !info.getName().equals(UIManager.getLookAndFeel().getName())) {
								Guitilities.setLookAndFeel(info.getClassName(), info.getName());
								Utilities.setConfiguration(Utilities.CONFIGURATION_LOOK_AND_FEEL_CLASS, info.getClassName());
								Utilities.setConfiguration(Utilities.CONFIGURATION_LOOK_AND_FEEL_NAME, info.getName());
								SwingUtilities.updateComponentTreeUI(dialog);
								SwingUtilities.updateComponentTreeUI(Application.this);
							}
					}
				};
				bottomPane.add(Guitilities.createButton(getTranslation("properties.okay"), new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) {
						applyListener.actionPerformed(event);
						cancelListener.actionPerformed(event);
					}
				}));
				bottomPane.add(Guitilities.createButton(getTranslation("properties.cancel"), cancelListener));
				bottomPane.add(Guitilities.createButton(getTranslation("properties.apply"), applyListener));
				dialog.add(bottomPane, BorderLayout.SOUTH);

				dialog.pack(); dialog.setSize(new Dimension(500, dialog.getHeight()));
				dialog.setVisible(true);
			}
		}));
		menubar.add(properties);

		JMenu display = new JMenu(getTranslation("menubar.display"));
		display.add(createMenuItem("display.zoom_default", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				getActiveSimulation().setZoom(1.0f);
			}
		}));
		display.add(createMenuItem("display.zoom_in", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setZoom(simulation.getZoom()*1.25);
			}
		}));
		display.add(createMenuItem("display.zoom_out", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setZoom(simulation.getZoom()/1.25);
			}
		}));
		display.add(new JSeparator());
		display.add(createMenuItem("display.fullscreen", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				GraphicsDevice device = getGraphicsConfiguration().getDevice();
				if(!device.isFullScreenSupported())
					JOptionPane.showMessageDialog(Application.this, getTranslation("fullscreen.not_supported"));
				JWindow window = (JWindow)device.getFullScreenWindow();
				if(window!=null) {
					device.setFullScreenWindow(null);
					Application.this.setContentPane(window.getContentPane());
					Application.this.toolbar.setFloatable(true);
					SwingUtilities.updateComponentTreeUI(Application.this);
				} else {
					window = new JWindow();
					window.setContentPane(Application.this.getContentPane());
					Application.this.toolbar.setFloatable(false);
					device.setFullScreenWindow(window);
				}
			}
		}));
		menubar.add(display);

		JMenu window = new JMenu(getTranslation("menubar.window"));
		window.add(createMenuItem("window.cascade", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { desktop.cascadeAllFrames(); }
		}));
		window.add(createMenuItem("window.tile", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) { desktop.tileAllFrames(); }
		}));
		menubar.add(window);

		JMenu help = new JMenu(getTranslation("menubar.help"));
		help.add(createMenuItem("help.about", new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				final JDialog dialog = Guitilities.createDialog(Application.this, getTranslation("about", TranslationType.TITLE));

				JPanel centerPane = new JPanel();
				centerPane.setLayout(new BoxLayout(centerPane, BoxLayout.Y_AXIS));
				final JLabel labelTitle, labelCopyright, labelUrl, labelDescription;
				centerPane.add(labelTitle=new JLabel("JDigitalSimulator ["+VERSION+"]"));
				centerPane.add(labelCopyright=new JLabel("Copyright "+COPYRIGHT+", Kristian Kraljic"));
				centerPane.add(labelUrl=new JLabel("http://kra.lc/projects/jdigitalsimulator/"));
				centerPane.add(labelDescription=Guitilities.createLabel("This software is a remake of Andreas Herz &quot;DigitalSimulator Version 5.57&quot; available at FreeGroup.de. The goal was to adapt the appearance and behaviour of the old application as best as possible, but build it, based on the newest object oriented standards and techniques. The whole application was effectively developed in one week by one person. "+LINES_OF_CODE+" lines of pure hand coded Java (without comments, no external libraries were used), "+WORDS_OF_CODE+" words, "+PAGES_OF_CODE+" pages of plain A4 paper."));
				labelTitle.setAlignmentX(JLabel.LEFT_ALIGNMENT);
				labelTitle.setFont(labelTitle.getFont().deriveFont(Font.BOLD));
				labelTitle.setBorder(Guitilities.MEDIUM_EMPTY_BORDER);
				labelCopyright.setBorder(new EmptyBorder(10, 20, 20, 0));
				labelUrl.addMouseListener(new MouseAdapter() {
					@Override public void mouseClicked(MouseEvent event) {
						try { Desktop.getDesktop().browse(new URI(labelUrl.getText())); }
						catch(Exception e) {}
					}
				});
				labelUrl.setBorder(new CompoundBorder(new EmptyBorder(0, 0, 20, 0), Guitilities.TITLE_BORDER));
				Guitilities.setFittingLabelSize(labelDescription, 400);
				centerPane.add(Box.createVerticalStrut(10));
				centerPane.setBorder(Guitilities.HUGE_EMPTY_BORDER);
				dialog.add(centerPane, BorderLayout.CENTER);

				JPanel bottomPane = Guitilities.createGradientFooter();;
				bottomPane.add(Guitilities.createButton(getTranslation("about.donate"), new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) { labelUrl.getMouseListeners()[0].mouseClicked(null); }
				}));
				bottomPane.add(Guitilities.createButton(getTranslation("about.close"), new ActionListener() {
					@Override public void actionPerformed(ActionEvent event) { dialog.dispose(); }
				}));
				dialog.add(bottomPane, BorderLayout.SOUTH);

				dialog.pack(); dialog.setSize(new Dimension(500, dialog.getHeight()));
				dialog.setVisible(true);
			}
		}));
		menubar.add(help);

		return menubar;
	}

	private JMenuItem createMenuItem(String key, ActionListener listener) {
		return Guitilities.createMenuItem(key, Guitilities.createImageIcon("images/menubar/"+key.replace('.', '-')+".png"), listener);
	}
	private JToolBar createToolBar() {
		JToolBar toolbar = new JToolBar(getTranslation("toolbar"));
		toolbar.add(createToolBarButton("new", getMenuBarItem("file.new").getActionListeners()[0]));
		toolbar.add(createToolBarButton("open", getMenuBarItem("file.open").getActionListeners()[0]));
		toolbar.add(createToolBarButton("save", getMenuBarItem("file.save").getActionListeners()[0]));
		toolbar.add(createToolBarButton("exit", getMenuBarItem("file.exit").getActionListeners()[0]));
		toolbar.addSeparator();
		toolbar.add(createToolBarButton("print", getMenuBarItem("file.print").getActionListeners()[0]));
		toolbar.add(createToolBarButton("print_level", getMenuBarItem("file.print_level").getActionListeners()[0]));
		toolbar.addSeparator();
		JToggleButton simulateButton = new JToggleButton(getTranslation("toolbar.simulate"));
		simulateButton.setFont(simulateButton.getFont().deriveFont(Font.BOLD, 14f));
		simulateButton.setToolTipText(getTranslation("toolbar.simulate", TranslationType.TOOLTIP));
		simulateButton.setActionCommand("simulate");
		simulateButton.setPreferredSize(new Dimension(simulateButton.getPreferredSize().width, 40));
		simulateButton.addActionListener(new ActionListener() {
			@Override public void actionPerformed(ActionEvent e) {
				Simulation simulation = getActiveSimulation();
				if(((JToggleButton)e.getSource()).isSelected())
					simulation.startSimulating();
				else simulation.stopSimulating();
			}
		});
		toolbar.add(simulateButton);
		toolbar.addSeparator();
		class TraverseViewportActionListener implements ActionListener {
			private Point traverse;
			public TraverseViewportActionListener(Point traverse) { this.traverse = traverse; }
			@Override public void actionPerformed(ActionEvent event) {
				JViewport viewport = (JViewport) getActiveSimulation().getParent();
				Point position = Guitilities.addPoints(viewport.getViewPosition(), traverse);
				if(position.x<0) position.x = 0;
				if(position.y<0) position.y = 0;
				viewport.setViewPosition(position);
			}
		}
		toolbar.add(createToolBarButton("left", new TraverseViewportActionListener(new Point(-50, 0))));
		toolbar.add(createToolBarButton("right", new TraverseViewportActionListener(new Point(50, 0))));
		toolbar.add(createToolBarButton("up", new TraverseViewportActionListener(new Point(0, -50))));
		toolbar.add(createToolBarButton("down", new TraverseViewportActionListener(new Point(0, 50))));
		toolbar.addSeparator();
		toolbar.add(createToolBarButton("front", getMenuBarItem("edit.front").getActionListeners()[0]));
		toolbar.add(createToolBarButton("back", getMenuBarItem("edit.back").getActionListeners()[0]));
		toolbar.add(createToolBarButton("grid", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				Simulation simulation = getActiveSimulation();
				simulation.setGridVisible(!simulation.isGridVisible());
			}
		}));
		toolbar.add(createToolBarButton("secure", new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				Simulation.SimulationProperies properties = getActiveSimulation().properties;
				boolean hadPassword;
				if(hadPassword=properties.hasPassword())
					if(!properties.removePassword(Guitilities.showPasswordDialog(Application.this, getTranslation("password.enter.old"), getTranslation("password.enter", TranslationType.TITLE)))) {
						JOptionPane.showMessageDialog(Application.this, getTranslation("password.wrong"), null, JOptionPane.WARNING_MESSAGE);
						return;
					}
				properties.setPassword(Guitilities.showPasswordDialog(Application.this, getTranslation("password.enter.new"), getTranslation("password.enter", TranslationType.TITLE)));
				if(properties.hasPassword())
					JOptionPane.showMessageDialog(Application.this, getTranslation("password.changed"));
				else if(hadPassword&&!properties.hasPassword())
					JOptionPane.showMessageDialog(Application.this, getTranslation("password.removed"));
			}
		}));
		toolbar.addSeparator();
		toolbar.add(createToolBarButton("zoom_default", getMenuBarItem("display.zoom_default").getActionListeners()[0]));
		toolbar.add(createToolBarButton("zoom_in", getMenuBarItem("display.zoom_in").getActionListeners()[0]));
		toolbar.add(createToolBarButton("zoom_out", getMenuBarItem("display.zoom_out").getActionListeners()[0]));
		toolbar.addSeparator();
		toolbar.add(createToolBarButton("fullscreen", getMenuBarItem("display.fullscreen").getActionListeners()[0]));
		toolbar.addSeparator();
		toolbar.add(createToolBarButton("properties", getMenuBarItem("properties.worksheet").getActionListeners()[0]));
		return toolbar;
	}

	private JFileChooser getFileChooser() {
		JFileChooser chooser = new RememberFileChooser() {
			private static final long serialVersionUID = 1l;
			@Override
			public void approveSelection() {
				File file = getSelectedFile();
				if(!file.getName().toLowerCase().endsWith('.'+FILE_EXTENSION))
					setSelectedFile(file=new File(file.getAbsolutePath()+'.'+FILE_EXTENSION));
				if(getDialogType()==SAVE_DIALOG&&file!=null&&file.exists())
					if(JOptionPane.showOptionDialog(Application.this, getTranslation("persist.overwrite", file.getName()), getTranslation("persist.overwrite", TranslationType.TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null)==JOptionPane.NO_OPTION)
						return;
				currentDirectory = file.getParentFile();
				if(currentDirectory!=null&&!currentDirectory.isDirectory())
					currentDirectory = null;
				super.approveSelection();
			}
		};
		chooser.setFileFilter(new FileFilter() {
			@Override public String getDescription() { return getTranslation("persist.filter.description"); }
			@Override public boolean accept(File file) { return file.isDirectory()||file.getName().toLowerCase().endsWith('.'+FILE_EXTENSION); }
		});
		return chooser;
	}

	private JButton createToolBarButton(String key, ActionListener listener) {
		JButton button = new JButton(getTranslation("toolbar."+key));
		button.setActionCommand(key);
		button.setToolTipText(getTranslation("toolbar."+key, TranslationType.TOOLTIP));
		URL resource = getResource("images/toolbar/"+key+".gif");
		if(resource!=null) {
			button.setText(new String());
			button.setIcon(new ImageIcon(getResource("images/toolbar/"+key+".gif"), getTranslation("toolbar."+key, TranslationType.ALTERNATIVE)));
		}
		button.addActionListener(listener);
		return button;
	}
	private void setToolBarButtonHeight(int height) {
		for(java.awt.Component component:toolbar.getComponents())
			component.setSize(new Dimension(component.getSize().width, height));
		toolbar.setPreferredSize(new Dimension(toolbar.getPreferredSize().width, height));
		toolbar.revalidate();
	}

	private SimulationOscilloscope createOscilloscope() {
		SimulationOscilloscope oscilloscope = new SimulationOscilloscope();
		oscilloscope.setPreferredSize(new Dimension(2000, 0));
		return oscilloscope;
	}

	public Simulation getActiveSimulation() {
		SimulationFrame selected = desktop.getSelectedFrame();
		if(selected!=null)
			return selected.getSimulation();
		else return null;
	}

	public void newWorksheet() { addWorksheet(new SimulationFrame()); }
	public void openWorksheet(File file) throws IOException, GeneralSecurityException { addWorksheet(new SimulationFrame(file));	}
	public void openWorksheet() {
		JFileChooser chooser = getFileChooser();
		if(chooser.showOpenDialog(Application.this)!=JFileChooser.APPROVE_OPTION)
			return;
		try {
			openWorksheet(chooser.getSelectedFile());
		} catch(StreamCorruptedException e) { //wrong file or wrong password
			JOptionPane.showMessageDialog(Application.this, getTranslation("password.wrong_file"), getTranslation("password.wrong", TranslationType.TITLE), JOptionPane.WARNING_MESSAGE);
		} catch(PasswordRequiredException e) {
			JOptionPane.showMessageDialog(Application.this, getTranslation("password.error.missing"), getTranslation("password.error", TranslationType.TITLE), JOptionPane.WARNING_MESSAGE);
		} catch(GeneralSecurityException e) {
			JOptionPane.showMessageDialog(Application.this, getTranslation("password.error.security"), getTranslation("password.error", TranslationType.TITLE), JOptionPane.WARNING_MESSAGE);
		}	catch(IOException e) {
			JOptionPane.showMessageDialog(Application.this, getTranslation("persist.error.load"), getTranslation("persist.error.load", TranslationType.TITLE), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		}
	}
	public void addWorksheet(final SimulationFrame frame) {
		final int gap=25, number=frame.getNumber()-1, count=5, bound=gap+gap*(number%count), width=Math.max(100, (int)((getWidth()-2*gap)*0.75)), height=(int)((width/4)*1.8);
		frame.setBounds(bound+(number/count*gap*3), bound, width, height);
		frame.setVisible(true);
		frame.addInternalFrameListener(new InternalFrameAdapter() {
			@Override public void internalFrameOpened(InternalFrameEvent event) { revalidate(); }
			@Override public void internalFrameActivated(InternalFrameEvent event) { revalidate(); }
			@Override public void internalFrameDeactivated(InternalFrameEvent event) { revalidate(); }
			@Override public void internalFrameClosed(InternalFrameEvent event) { revalidate(); }
		});
		desktop.add(frame);
		Guitilities.selectFrame(frame);
	}

	public void saveWorksheet() { saveWorksheet(desktop.getSelectedFrame()); }
	public void saveWorksheet(SimulationFrame frame) {
		if(frame==null)
			return;
		File file = frame.getFile();
		if(file!=null)
			saveWorksheet(desktop.getSelectedFrame(), file);
		else saveWorksheetAs(frame);
	}
	public void saveWorksheetAs() { saveWorksheetAs(desktop.getSelectedFrame()); }
	public void saveWorksheetAs(SimulationFrame frame) {
		if(frame==null)
			return;
		JFileChooser chooser = getFileChooser();
		chooser.setSelectedFile(new File(frame.getFileName()));
		if(chooser.showSaveDialog(Application.this)!=JFileChooser.APPROVE_OPTION)
			return;
		saveWorksheet(desktop.getSelectedFrame(), chooser.getSelectedFile());
	}
	private void saveWorksheet(SimulationFrame frame, File file) {
		Simulation simulation = frame.getSimulation();
		try {
			simulation.writeSimulation(new BufferedOutputStream(new FileOutputStream(file)));
			frame.setChanged(false);
			frame.setFile(file);
		}	catch(IOException e) {
			JOptionPane.showMessageDialog(Application.this, getTranslation("persist.error.save"), getTranslation("persist.error.save", TranslationType.TITLE), JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		}
	}

	public void printWorksheet() {
		PrinterJob print = PrinterJob.getPrinterJob();
		print.setPrintable(desktop.getSelectedFrame());
		if(print.printDialog(new HashPrintRequestAttributeSet()))
			try { print.print(); }
		catch(PrinterException e) {}
	}
	public void printWorksheetLevel() {
		PrinterJob print = PrinterJob.getPrinterJob();
		PrintRequestAttributeSet set = new HashPrintRequestAttributeSet();
		set.add(OrientationRequested.LANDSCAPE);
		print.setPrintable(oscilloscope);
		if(print.printDialog(set))
			try { print.print(); }
		catch(PrinterException e) {}
	}

	public void exitApplication() {
		for(SimulationFrame frame:desktop.getAllFrames())
			try { frame.setClosed(true); }
		catch(PropertyVetoException e) { return; }
		this.dispose();
		setConfiguration(CONFIGURATION_WINDOW_SIZE, new StringBuilder().append(getSize().width).append(',').append(getSize().height).toString());
		setConfiguration(CONFIGURATION_WINDOW_LOCATION, new StringBuilder().append(getLocation().x).append(',').append(getLocation().y).toString());
		setConfiguration(CONFIGURATION_WINDOW_MAXIMIZED, Boolean.toString((getExtendedState()&JFrame.MAXIMIZED_BOTH)==JFrame.MAXIMIZED_BOTH));
		System.exit(0);
	}

	@Override
	public void revalidate() {
		SimulationFrame frame = desktop.getSelectedFrame();
		boolean selected = frame!=null;
		setToolBarEnabled(selected, TOOLBAR_FRAME_FOCUS);
		setMenuBarEnabled(selected, MENUBAR_FRAME_FOCUS);
		if(selected) {
			Simulation simulation = frame.getSimulation();
			getToolBarButton("simulate").setSelected(simulation.isSimulating());
			boolean selected_components = simulation.getSelectedComponents().size()!=0;
			setToolBarEnabled(selected_components, TOOLBAR_COMPONENT_FOCUS);
			setMenuBarEnabled(selected_components, MENUBAR_COMPONENT_FOCUS);
		} else getToolBarButton("simulate").setSelected(false);
	}

	private void setMenuBarEnabled(boolean enabled, String... items) { setMenuBarEnabled(getJMenuBar(), enabled, items); }
	private void setMenuBarEnabled(JComponent menu, boolean enabled, String... items) {
		java.awt.Component[] menubar_components = menu instanceof JMenu?((JMenu)menu).getMenuComponents():menu.getComponents();
		List<String> list = null;
		if(items!=null&&items.length>0)
			list = Arrays.asList(items);
		for(java.awt.Component menu_component:menubar_components)
			if(menu_component instanceof JMenuItem) {
				if(list==null||list.contains(((JMenuItem) menu_component).getActionCommand()))
					menu_component.setEnabled(enabled);
				else if(menu_component instanceof JMenu)
					setMenuBarEnabled((JMenu)menu_component, enabled, items);
			}
	}
	private void setToolBarEnabled(boolean enabled, String... buttons) {
		java.awt.Component[] toolbar_components = toolbar.getComponents();
		if(buttons!=null&&buttons.length>0) {
			List<String> list = Arrays.asList(buttons);
			for(java.awt.Component component:toolbar_components)
				if(component instanceof AbstractButton
						&& list.contains(((AbstractButton)component).getActionCommand()))
					component.setEnabled(enabled);
		} else for(java.awt.Component toolbar_component:toolbar_components)
			toolbar_component.setEnabled(enabled);
	}

	private JMenuItem getMenuBarItem(String item) { return getMenuBarItem(getJMenuBar(), item); }
	private JMenuItem getMenuBarItem(JComponent menu, String item) {
		java.awt.Component[] menubar_components = menu instanceof JMenu?((JMenu)menu).getMenuComponents():menu.getComponents();
		for(java.awt.Component menu_component:menubar_components)
			if(menu_component instanceof JMenuItem) {
				JMenuItem menuItem = (JMenuItem) menu_component;
				if(menuItem.getActionCommand().equals(item))
					return menuItem;
				else if(menu_component instanceof JMenu) {
					JMenuItem subMenuItem = getMenuBarItem((JMenu)menu_component, item);
					if(subMenuItem!=null)
						return subMenuItem;
				}
			}
		return null;
	}

	private AbstractButton getToolBarButton(String button) {
		java.awt.Component[] toolbar_components = toolbar.getComponents();
		for(java.awt.Component component:toolbar_components)
			if(component instanceof AbstractButton
					&& ((AbstractButton)component).getActionCommand().equals(button))
				return (AbstractButton) component;
		return null;
	}

	protected class ComponentButton extends JButton {
		private static final long serialVersionUID = 1l;
		protected ComponentAttributes attributes;
		private Component component;
		private ImageIcon icon;

		public ComponentButton(ComponentAttributes attributes, final Class<? extends Component> cls) throws InstantiationException, IllegalAccessException {
			this.component = createComponent(cls);
			setText(attributes.name);
			setToolTipText(attributes.description);
			setIcon(icon=createComponentImage(component, attributes.description));
			setIconTextGap(10);
			setHorizontalTextPosition(JButton.TRAILING);
			setHorizontalAlignment(JButton.LEADING);
			setOpaque(false);
			setFocusPainted(false);
			setContentAreaFilled(false);
			setTransferHandler(new TransferHandler("digital-simulator-component") {
				private static final long serialVersionUID = 1l;
				@Override protected Transferable createTransferable(JComponent component) {
					return new Transferable() {
						@Override public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor instanceof ComponentFlavor;	}
						@Override public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[]{ComponentFlavor.getFlavor()}; }
						@Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
							if(!isDataFlavorSupported(flavor))
								throw new UnsupportedFlavorException(flavor);
							return cls;
						}
					};
				}
				@Override public int getSourceActions(JComponent component) { return TransferHandler.COPY; }
			});

			addMouseListener(new MouseAdapter() {
				@Override public void mouseReleased(MouseEvent event) {
					JInternalFrame frame = desktop.getSelectedFrame();
					if(frame==null)
						return;
					try {
						Component component = cls.newInstance();
						component.setLocation(new Point(20, 20));
						((SimulationFrame)frame).getSimulation().addComponent(component);
					} catch (Exception e) {}
				}
			});
			addMouseMotionListener(new MouseMotionAdapter() {
				@Override public void mouseDragged(MouseEvent event) {
					JComponent component = (JComponent) event.getSource();
					TransferHandler handler = component.getTransferHandler();
					handler.exportAsDrag(component, event, TransferHandler.COPY);
					((JButton)event.getSource()).transferFocusUpCycle(); //revalidate 'button-down'
				}
			});
		}

		public void setIconVisible(boolean visible) {
			if(!visible)
				this.setIcon(null);
			else this.setIcon(icon);
			repaint();
		}

		private Component createComponent(Class<? extends Component> cls) throws InstantiationException, IllegalAccessException { return cls.newInstance(); }
		private ImageIcon createComponentImage(Component component, String tooltip) {
			Dimension size = component.getSize();
			Image image = Guitilities.createTranslucentImage(size.width+1, size.height+1);
			Graphics2D graphics = (Graphics2D) image.getGraphics();
			graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			component.paint(graphics);
			return new ImageIcon(image, tooltip);
		}
	}

	public static Simulation[] getSimulations() {
		Set<Simulation> simulations = new HashSet<Simulation>();
		for(Application application:applications)
			for(SimulationFrame frame:application.desktop.getAllFrames())
				simulations.add(frame.getSimulation());
		return simulations.toArray(new Simulation[0]);
	}
	public static Simulation getSimulation(Component component) {
		for(Simulation simulation:getSimulations())
			if(simulation.hasComponent(component))
				return simulation;
		return null;
	}

	public static class SimulationDesktop extends JDesktopPane {
		private static final long serialVersionUID = 1l;
		private static int simulationNumber = 0;
		public static int getSimulationNumber() { return simulationNumber; }
		@Override protected void addImpl(java.awt.Component component, Object obj, int layer) {
			super.addImpl(component, obj, layer);
			if(component instanceof SimulationFrame)
				simulationNumber++;
		}
		@Override public SimulationFrame selectFrame(boolean flag) { return (SimulationFrame) super.selectFrame(flag); }
		@Override public SimulationFrame getSelectedFrame() { return (SimulationFrame) super.getSelectedFrame();	}
		@Override public SimulationFrame[] getAllFrames() { return cast(super.getAllFrames());	}
		@Override public SimulationFrame[] getAllFramesInLayer(int layer) { return cast(super.getAllFramesInLayer(layer)); }
		private SimulationFrame[] cast(JInternalFrame[] frames) {
			Vector<SimulationFrame> list = new Vector<SimulationFrame>(frames.length);
			for(JInternalFrame frame:frames)
				list.add((SimulationFrame)frame);
			return list.toArray(new SimulationFrame[0]);
		}

		public void tileAllFrames() { tileFrames(this.getAllFrames()); }
		public void tileAllFramesInLayer(int layer) { tileFrames(this.getAllFramesInLayer(layer)); }
		private void tileFrames(JInternalFrame[] frames) {
			if(frames.length<=0) return;
			Dimension size = getSize();
			int columns = (int) Math.sqrt(frames.length), rows = (int) (Math.ceil(((double) frames.length) / columns));
			int lastRow = frames.length - columns * (rows - 1);
			int width, height;

			if (lastRow == 0) {
				rows--;
				height = size.height / rows;
			} else {
				height = size.height / rows;
				if (lastRow < columns) {
					rows--;
					width = size.width / lastRow;
					for (int i = 0; i < lastRow; i++) {
						frames[columns * rows + i].setBounds(i * width, rows * height, width, height);
					}
				}
			}

			width = size.width / columns;
			for (int j = 0; j < rows; j++) {
				for (int i = 0; i < columns; i++) {
					frames[i + j * columns].setBounds(i * width, j * height, width, height);
				}
			}
		}

		public void cascadeAllFrames() {	cascadeFrames(this.getAllFrames(), 25); }
		public void cascadeAllFramesInLayer(int layer) { cascadeFrames(this.getAllFramesInLayer(layer), 25);	}
		private void cascadeFrames(JInternalFrame[] frames, int separation) {
			if(frames.length<=0) return;
			java.awt.Rectangle bounds = getBounds();
			int margin = frames.length*separation+separation;
			for(int frame=0;frame<frames.length;frame++)
				frames[frame].setBounds(separation+bounds.x+frame*separation,
						separation+bounds.y+frame*separation, bounds.width-margin,
						bounds.height-margin);
		}
	}
	public class SimulationFrame extends JInternalFrame implements Printable {
		private static final long serialVersionUID = 1l;

		private Simulation simulation;
		private int number;
		private File file;
		private boolean changed;

		public SimulationFrame() { this(new Simulation()); }
		public SimulationFrame(File file) throws IOException, GeneralSecurityException {
			this(new BufferedInputStream(new FileInputStream(file)));
			this.setFile(file);
		}
		public SimulationFrame(InputStream in) throws IOException, GeneralSecurityException {
			try {
				try { simulation = Simulation.readSimulation(in); }
				catch(PasswordRequiredException e) {
					simulation = Simulation.readSimulation(in, Guitilities.showPasswordDialog(Application.this, getTranslation("password.enter"), getTranslation("password.enter", TranslationType.TITLE)));
				}
			} catch(ClassNotFoundException e) { throw new IOException(e); }
			initialize();
		}
		public SimulationFrame(Simulation simulation) {
			this.simulation = simulation;
			initialize();
		}
		public SimulationFrame(Simulation simulation, File file) {
			this(simulation);
			this.setFile(file);
		}

		private void initialize() {
			number = SimulationDesktop.getSimulationNumber()+1;
			this.setFile(null);
			this.setClosable(true);
			this.setResizable(true);
			this.setMaximizable(true);
			this.setIconifiable(true);
			this.addVetoableChangeListener(new VetoableChangeListener() {
				@Override public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
					if(event.getPropertyName().equals("closed")&&event.getNewValue().equals(true))
						if(hasChanged())
							switch(JOptionPane.showConfirmDialog(Application.this, getTranslation("persist.changes", getFileName()), getTranslation("persist.changes", TranslationType.TITLE), JOptionPane.YES_NO_CANCEL_OPTION)) {
							case JOptionPane.YES_OPTION:
								saveWorksheet(SimulationFrame.this);
								if(!hasChanged())
									return; //file was saved successfully
							case JOptionPane.CANCEL_OPTION: throw new PropertyVetoException(new String(), event);
							}
				}
			});
			this.add(Guitilities.createExtendedScrollPane(simulation, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
					JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
			simulation.setClipboard(clipboard);
			simulation.addFocusListener(new FocusListener() {
				@Override public void focusLost(FocusEvent event) { focusGained(event); }
				@Override public void focusGained(FocusEvent event) {
					boolean selected_components = simulation.getSelectedComponents().size()!=0;
					setToolBarEnabled(selected_components, TOOLBAR_COMPONENT_FOCUS);
					setMenuBarEnabled(selected_components, MENUBAR_COMPONENT_FOCUS);
					setMenuBarEnabled(simulation.canUndoChange(), "edit.undo");
					setMenuBarEnabled(simulation.canRedoChange(), "edit.redo");
					getToolBarButton("simulate").setSelected(simulation.isSimulating());
				}
			});
			simulation.addChangeListener(new Simulation.ChangeListener() {
				@Override public void changed(ChangeEvent event) { setChanged(true); }
			});
			simulation.addKeyListener(new KeyAdapter() {
				@Override public void keyPressed(KeyEvent event) {
					if(event.isControlDown())
						switch(event.getKeyCode()) {
						case KeyEvent.VK_O:
							if(event.isAltDown()&&event.isShiftDown()) {
								JFileChooser chooser = new RememberFileChooser();
								chooser.setFileFilter(new FileFilter() {
									@Override public String getDescription() { return "Class (*.class), Jar-File (*.jar)"; }
									@Override public boolean accept(File file) {
										return file.isDirectory()||file.getName().endsWith(".class")
												||file.getName().endsWith(".jar");
									}
								});
								if(chooser.showOpenDialog(Application.this)==JFileChooser.APPROVE_OPTION)
									loadComponents(chooser.getSelectedFile(), null);
							} else openWorksheet();
							break;
						case KeyEvent.VK_S:
							saveWorksheet();
							break;
						}
				}
			});
			simulation.setOscilloscope(oscilloscope);
		}

		public boolean hasChanged() { return this.changed; }
		public void setChanged(boolean changed) {
			if(changed!=this.changed) {
				this.changed = changed;
				updateTitle();
			}
		}

		public File getFile() { return this.file; }
		public void setFile(File file) { this.file = file; updateTitle(); }
		public String getFileName() {
			if(file==null)
				return super.getTitle().replaceAll("[ \\/:*?\"<>|]", new String()).trim();
			else return file.getName();
		}

		private void updateTitle() {
			StringBuilder title = new StringBuilder();
			if(file==null)
				title.append(getTranslation("worksheet.title")).append(number);
			else title.append(file.getName());
			if(hasChanged())
				title.append(" *");
			this.setTitle(title.toString());
		}

		public Simulation getSimulation() { return simulation; }
		public int getNumber() { return number; }

		@Override public int print(Graphics default_graphics, PageFormat pageFormat, int pageNumber) throws PrinterException {
			if(simulation.print(default_graphics, pageFormat, pageNumber)!=NO_SUCH_PAGE) {
				Graphics2D graphics = (Graphics2D) default_graphics.create((int)pageFormat.getImageableX(), (int)pageFormat.getImageableY(), (int)pageFormat.getImageableWidth(), (int)pageFormat.getImageableHeight());
				graphics.setColor(Color.BLACK); graphics.setFont(default_graphics.getFont().deriveFont(Font.BOLD, 14));
				String title = getFileName();	FontMetrics metrics = default_graphics.getFontMetrics();
				graphics.drawString(title, (int)(pageFormat.getImageableWidth()/2-metrics.stringWidth(title)/2),
						(metrics.getAscent()));
				return PAGE_EXISTS;
			} else return NO_SUCH_PAGE;
		}
	}

	public static void main(String[] args) {
		Guitilities.initializeUIManager();
		String lookAndFeelClass = Utilities.getConfiguration(Utilities.CONFIGURATION_LOOK_AND_FEEL_CLASS),
				lookAndFeelName = Utilities.getConfiguration(Utilities.CONFIGURATION_LOOK_AND_FEEL_NAME);
		if(!Guitilities.setLookAndFeel(lookAndFeelClass, lookAndFeelName))
			if(!Guitilities.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel", "Nimbus"))
				if(!Guitilities.setLookAndFeel(UIManager.getSystemLookAndFeelClassName(), "System"))
					Guitilities.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName(), "CrossPlatform");
		String localizationLanguage = Utilities.getConfiguration(Utilities.CONFIGURATION_LOCALIZATION_LANGUAGE);
		if(localizationLanguage!=null)
			for(Locale locale:Utilities.SUPPORTED_LOCALES)
				if(locale.getLanguage().equals(localizationLanguage))
					Utilities.setCurrentLocale(locale);
		Application application = new Application();
		if(args.length!=0)
			try { application.openWorksheet(new File(args[0])); }
		catch(Exception e) { application.newWorksheet(); }
		else application.newWorksheet();
	}
} public class SimulationFrame extends JInternalFrame implements Printable {
	private static final long serialVersionUID = 1l;

	private Simulation simulation;
	private int number;
	private File file;
	private boolean changed;

	public SimulationFrame() { this(new Simulation()); }
	public SimulationFrame(File file) throws IOException, GeneralSecurityException {
		this(new BufferedInputStream(new FileInputStream(file)));
		this.setFile(file);
	}
	public SimulationFrame(InputStream in) throws IOException, GeneralSecurityException {
		try {
			try { simulation = Simulation.readSimulation(in); }
			catch(PasswordRequiredException e) {
				simulation = Simulation.readSimulation(in, Guitilities.showPasswordDialog(Application.this, getTranslation("password.enter"), getTranslation("password.enter", TranslationType.TITLE)));
			}
		} catch(ClassNotFoundException e) { throw new IOException(e); }
		initialize();
	}
	public SimulationFrame(Simulation simulation) {
		this.simulation = simulation;
		initialize();
	}
	public SimulationFrame(Simulation simulation, File file) {
		this(simulation);
		this.setFile(file);
	}

	private void initialize() {
		number = SimulationDesktop.getSimulationNumber()+1;
		this.setFile(null);
		this.setClosable(true);
		this.setResizable(true);
		this.setMaximizable(true);
		this.setIconifiable(true);
		this.addVetoableChangeListener(new VetoableChangeListener() {
			@Override public void vetoableChange(PropertyChangeEvent event) throws PropertyVetoException {
				if(event.getPropertyName().equals("closed")&&event.getNewValue().equals(true))
					if(hasChanged())
						switch(JOptionPane.showConfirmDialog(Application.this, getTranslation("persist.changes", getFileName()), getTranslation("persist.changes", TranslationType.TITLE), JOptionPane.YES_NO_CANCEL_OPTION)) {
						case JOptionPane.YES_OPTION:
							saveWorksheet(SimulationFrame.this);
							if(!hasChanged())
								return; //file was saved successfully
						case JOptionPane.CANCEL_OPTION: throw new PropertyVetoException(new String(), event);
						}
			}
		});
		this.add(Guitilities.createExtendedScrollPane(simulation, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
		simulation.setClipboard(clipboard);
		simulation.addFocusListener(new FocusListener() {
			@Override public void focusLost(FocusEvent event) { focusGained(event); }
			@Override public void focusGained(FocusEvent event) {
				boolean selected_components = simulation.getSelectedComponents().size()!=0;
				setToolBarEnabled(selected_components, TOOLBAR_COMPONENT_FOCUS);
				setMenuBarEnabled(selected_components, MENUBAR_COMPONENT_FOCUS);
				setMenuBarEnabled(simulation.canUndoChange(), "edit.undo");
				setMenuBarEnabled(simulation.canRedoChange(), "edit.redo");
				getToolBarButton("simulate").setSelected(simulation.isSimulating());
			}
		});
		simulation.addChangeListener(new Simulation.ChangeListener() {
			@Override public void changed(ChangeEvent event) { setChanged(true); }
		});
		simulation.addKeyListener(new KeyAdapter() {
			@Override public void keyPressed(KeyEvent event) {
				if(event.isControlDown())
					switch(event.getKeyCode()) {
					case KeyEvent.VK_O:
						if(event.isAltDown()&&event.isShiftDown()) {
							JFileChooser chooser = new RememberFileChooser();
							chooser.setFileFilter(new FileFilter() {
								@Override public String getDescription() { return "Class (*.class), Jar-File (*.jar)"; }
								@Override public boolean accept(File file) {
									return file.isDirectory()||file.getName().endsWith(".class")
											||file.getName().endsWith(".jar");
								}
							});
							if(chooser.showOpenDialog(Application.this)==JFileChooser.APPROVE_OPTION)
								loadComponents(chooser.getSelectedFile(), null);
						} else openWorksheet();
						break;
					case KeyEvent.VK_S:
						saveWorksheet();
						break;
					}
			}
		});
		simulation.setOscilloscope(oscilloscope);
	}

	public boolean hasChanged() { return this.changed; }
	public void setChanged(boolean changed) {
		if(changed!=this.changed) {
			this.changed = changed;
			updateTitle();
		}
	}

	public File getFile() { return this.file; }
	public void setFile(File file) { this.file = file; updateTitle(); }
	public String getFileName() {
		if(file==null)
			return super.getTitle().replaceAll("[ \\/:*?\"<>|]", new String()).trim();
		else return file.getName();
	}

	private void updateTitle() {
		StringBuilder title = new StringBuilder();
		if(file==null)
			title.append(getTranslation("worksheet.title")).append(number);
		else title.append(file.getName());
		if(hasChanged())
			title.append(" *");
		this.setTitle(title.toString());
	}

	public Simulation getSimulation() { return simulation; }
	public int getNumber() { return number; }

	@Override public int print(Graphics default_graphics, PageFormat pageFormat, int pageNumber) throws PrinterException {
		if(simulation.print(default_graphics, pageFormat, pageNumber)!=NO_SUCH_PAGE) {
			Graphics2D graphics = (Graphics2D) default_graphics.create((int)pageFormat.getImageableX(), (int)pageFormat.getImageableY(), (int)pageFormat.getImageableWidth(), (int)pageFormat.getImageableHeight());
			graphics.setColor(Color.BLACK); graphics.setFont(default_graphics.getFont().deriveFont(Font.BOLD, 14));
			String title = getFileName();	FontMetrics metrics = default_graphics.getFontMetrics();
			graphics.drawString(title, (int)(pageFormat.getImageableWidth()/2-metrics.stringWidth(title)/2),
					(metrics.getAscent()));
			return PAGE_EXISTS;
		} else return NO_SUCH_PAGE;
	}
} public class Guitilities {
	public static final Border MENU_EMPTY_BORDER = new EmptyBorder(3, 5, 3, 25);
	public static final Border SMALL_EMPTY_BORDER = new EmptyBorder(6, 6, 6, 6);
	public static final Border MEDIUM_EMPTY_BORDER = new EmptyBorder(10, 10, 10, 10);
	public static final Border LARGE_EMPTY_BORDER = new EmptyBorder(18, 18, 18, 18);
	public static final Border HUGE_EMPTY_BORDER = new EmptyBorder(30, 30, 30, 30);

	public static final Border CHISEL_BORDER = new ChiselBorder();
	public static final Border TITLE_BORDER = new CompoundBorder(CHISEL_BORDER, new EmptyBorder(6, 8, 6, 0));
	public static final Border FOOTER_BORDER = new CompoundBorder(CHISEL_BORDER, new EmptyBorder(0, 8, 6, 0));
	public static final Border CATEGORY_BORDER = new CompoundBorder(CHISEL_BORDER, new EmptyBorder(0, 0, 10, 0));

	public static final String TITLE_FOREGROUND = "titleForegroundColor";
	public static final String TITLE_GRADIENT_COLOR_A = "titleGradientColor1";
	public static final String TITLE_GRADIENT_COLOR_B = "titleGradientColor2";
	public static final String TITLE_FONT = "titleFont";

	public static final String FOOTER_GRADIENT_COLOR_A = "footerGradientColor1";
	public static final String FOOTER_GRADIENT_COLOR_B = "footerGradientColor2";

	public static final String SUB_PANEL_BACKGROUND = "subPanelBackgroundColor";

	public static final String FILECHOOSER_READONLY = "FileChooser.readOnly";

	public static GridBagConstraints pair_left_constraint       = new GridBagConstraints(0, 0, 1, 1, 0.1d, 0d, GridBagConstraints.LINE_END        , GridBagConstraints.NONE, new Insets(0, 10, 0, 10), 0, 0);
	public static GridBagConstraints pair_right_constraint      = new GridBagConstraints(1, 0, 1, 1, 0.9d, 0d, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 0, 10), 0, 0);
	public static GridBagConstraints pair_additional_constraint = new GridBagConstraints(2, 0, 1, 1, 0.3d, 0d, GridBagConstraints.LINE_START      , GridBagConstraints.NONE, new Insets(0, 10, 0, 10), 0, 0);
	private static final GridBagConstraints PAIR_LEFT_DEFAULT_CONSTRAINT = (GridBagConstraints)pair_left_constraint.clone(),
			PAIR_RIGHT_DEFAULT_CONSTRAINT = (GridBagConstraints)pair_right_constraint.clone(),
			PAIR_ADDITIONAL_DEFAULT_CONSTRAINT = (GridBagConstraints)pair_additional_constraint.clone();

	public static final Color TRANSPARENT = new Color(0, 0, 0, 0);

	private static boolean usingNimbusLookAndFeel = false;

	public enum Size { REGULAR, MINI, SMALL, LARGE;
		@Override
		public String toString() { return super.toString().toLowerCase(); };
	}
	public enum Direction { HORRIZONTAL, VERTICAL }

	public static void setComponentSize(JComponent component, Size size) {
		component.putClientProperty("JComponent.sizeVariant", size.toString());
	}

	public static void initializeUIManager() {
		Color color = UIManager.getColor(Guitilities.usingNimbusLookAndFeel()? "nimbusBase" : "activeCaption");
		if(color==null)
			color = UIManager.getColor("control");
		float hsb[] = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
		UIManager.put(TITLE_GRADIENT_COLOR_A, Color.getHSBColor(hsb[0]-.013f, .15f, .85f));
		UIManager.put(TITLE_GRADIENT_COLOR_B, Color.getHSBColor(hsb[0]-.005f, .24f, .80f));
		UIManager.put(TITLE_FOREGROUND, Color.getHSBColor(hsb[0], .54f, .40f));

		color = UIManager.getColor(Guitilities.usingNimbusLookAndFeel()? "nimbusSelectionBackground" : "inactiveCaption");
		if(color==null)
			color = UIManager.getColor("control");
		UIManager.put(FOOTER_GRADIENT_COLOR_A, Color.getHSBColor(hsb[0]-.013f, .10f, .90f));
		UIManager.put(FOOTER_GRADIENT_COLOR_B, Color.getHSBColor(hsb[0]-.005f, .14f, .85f));

		UIManager.put(SUB_PANEL_BACKGROUND, Guitilities.deriveColorHSB(UIManager.getColor("Panel.background"), 0, 0, -.15f));

		Font font = UIManager.getFont("Label.font");
		UIManager.put(TITLE_FONT, font.deriveFont(Font.BOLD, font.getSize()+4f));

		UIManager.put(FILECHOOSER_READONLY, Boolean.TRUE);
	}

	public static boolean usingNimbusLookAndFeel() { return usingNimbusLookAndFeel; }
	public static boolean setLookAndFeel(String classname, String name) {
		if((classname==null||classname.isEmpty())&&(name==null||name.isEmpty()))
			return false;
		try {
			UIManager.setLookAndFeel(classname);
			usingNimbusLookAndFeel = UIManager.getLookAndFeel().getName().equals("Nimbus");
			return true;
		}	catch(Exception e_a) {
			try {
				for(LookAndFeelInfo look_and_feel:UIManager.getInstalledLookAndFeels())
					if(name.equals(look_and_feel.getName())) {
						UIManager.setLookAndFeel(look_and_feel.getClassName());
						usingNimbusLookAndFeel = UIManager.getLookAndFeel().getName().equals("Nimbus");
						return true;
					}
			} catch(Exception e_b) {}
		}
		return false;
	}

	public static Point addPoints(Point... points) {
		Point result = new Point();
		for(Point point:points)
			if(point!=null)
				result.translate(point.x, point.y);
		return result;
	}
	public static Point invertPoint(Point point) {
		return new Point(-point.x, -point.y);
	}
	public static class RelativeMouseEvent extends MouseEvent {
		private static final long serialVersionUID = 1l;
		public RelativeMouseEvent(MouseEvent event, Point point, Point relative) {
			super(event.getComponent(), event.getID(), event.getWhen(), event.getModifiers(),
					point.x-relative.x, point.y-relative.y, point.x, point.y,
					event.getClickCount(), event.isPopupTrigger(), event.getButton());
		}
	}

	public static Image makeColorTransparent(Image image, final Color color) {
		ImageFilter filter = new RGBImageFilter() {
			public int markerRGB = color.getRGB()|0xFF000000;
			@Override
			public final int filterRGB(int x, int y, int rgb) {
				if((rgb|0xFF000000)==markerRGB)
					return 0x00FFFFFF & rgb;
				else return rgb;
			}
		};
		return Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), filter));
	}

	private static GraphicsConfiguration getDefaultGraphicsConfiguration() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); }
	public static Dimension getStringBounds(Font font, String text) { return font.getStringBounds(text, new FontRenderContext(getDefaultGraphicsConfiguration().getDefaultTransform(), true, false)).getBounds().getSize(); }
	public static LineMetrics getLineMetrics(Font font, String text) { return font.getLineMetrics(text, new FontRenderContext(getDefaultGraphicsConfiguration().getDefaultTransform(), true, false)); }
	public static BufferedImage createCompatibleImage(int width, int height) { return getDefaultGraphicsConfiguration().createCompatibleImage(width, height); }
	public static BufferedImage createTranslucentImage(int width, int height) { return getDefaultGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); }
	public static BufferedImage createWhiteImage(int width, int height) {
		BufferedImage image = createCompatibleImage(width, height);
		Graphics graphics = image.getGraphics();
		graphics.setColor(Color.WHITE);
		graphics.fillRect(0, 0, width, height);
		return image;
	}
	public static BufferedImage createGradientImage(int width, int height, Color gradient_a, Color gradient_b) {
		BufferedImage image = createCompatibleImage(width, height);
		GradientPaint gradient = new GradientPaint(0, 0, gradient_a, 0, height, gradient_b, false);
		Graphics2D graphics = (Graphics2D) image.getGraphics();
		graphics.setPaint(gradient);
		graphics.fillRect(0, 0, width, height);
		graphics.dispose();
		return image;
	}

	public static GradientPanel createGradientTitle(String title) {
		GradientPanel panel = new GradientPanel(UIManager.getColor(TITLE_GRADIENT_COLOR_A), UIManager.getColor(TITLE_GRADIENT_COLOR_B));
		panel.setLayout(new BorderLayout());
		panel.setBorder(TITLE_BORDER);
		panel.setPreferredSize(new Dimension(0, 40));
		JLabel label = new JLabel(title);
		label.setOpaque(false);
		label.setFont(UIManager.getFont(TITLE_FONT));
		label.setForeground(UIManager.getColor(TITLE_FOREGROUND));
		label.setHorizontalAlignment(JLabel.LEADING);
		panel.add(label, BorderLayout.CENTER);
		return panel;
	}
	public static GradientPanel createGradientFooter() {
		GradientPanel panel = new GradientPanel(UIManager.getColor(FOOTER_GRADIENT_COLOR_A), UIManager.getColor(FOOTER_GRADIENT_COLOR_B));
		panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
		panel.setBorder(FOOTER_BORDER);
		panel.setPreferredSize(new Dimension(0, 40));
		return panel;
	}

	public static JLabel createLabel(String text) {
		JLabel label = new JLabel();
		label.setFocusable(false);
		label.setText(new StringBuilder("<html>").append(text.replaceAll("\n", "<br>")).append("</html>").toString());
		return label;
	}
	public static void setFittingLabelSize(JLabel label, int width) {
		int wordWidth, lines=1, size=0;
		if(width<=0||label.getText().isEmpty())
			return;
		StringTokenizer words = new StringTokenizer(label.getText());
		FontMetrics metrics = label.getFontMetrics(label.getFont());
		while(words.hasMoreTokens())
			if((size+=(wordWidth=metrics.stringWidth(words.nextToken())))>width) {
				size = wordWidth;
				lines++;
			}
		label.setPreferredSize(new Dimension(width, lines*metrics.getHeight()));
		label.revalidate();
	}

	public static JMenuItem createMenuItem(String key, ActionListener listener) { return createMenuItem(key, (ImageIcon)null, listener); }
	public static JMenuItem createMenuItem(String key, String text, ActionListener listener) { return createMenuItem(key, text, (ImageIcon)null, listener); }
	public static JMenuItem createMenuItem(String key, String text, String tooltip, ActionListener listener) { return createMenuItem(key, text, tooltip, (ImageIcon)null, listener); }
	public static JMenuItem createMenuItem(String key, Icon icon, ActionListener listener) { return createMenuItem(key, getTranslation("menu."+key), getTranslation("menu."+key, TranslationType.TOOLTIP), icon, listener); }
	public static JMenuItem createMenuItem(String key, String text, Icon icon, ActionListener listener) { return createMenuItem(key, text, null, icon, listener); }
	public static JMenuItem createMenuItem(String key, String text, String tooltip, Icon icon, ActionListener listener) {
		JMenuItem item = new JMenuItem(text);
		item.setToolTipText(tooltip);
		item.setActionCommand(key);
		item.setBorder(MENU_EMPTY_BORDER);
		if(icon!=null)
			item.setIcon(icon);
		item.addActionListener(listener);
		return item;
	}

	public static Color deriveColorAlpha(Color color, int alpha) { return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha); }
	public static Color deriveColorHSB(Color color, float dH, float dS, float dB) {
		float hsb[] = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
		hsb[0] += dH; hsb[1] += dS; hsb[2] += dB;
		return Color.getHSBColor(hsb[0] < 0 ? 0 : (hsb[0] > 1 ? 1 : hsb[0]),
				hsb[1] < 0 ? 0 : (hsb[1] > 1 ? 1 : hsb[1]),
						hsb[2] < 0 ? 0 : (hsb[2] > 1 ? 1 : hsb[2]));
	}

	public static Point getAbsoluteLocation(Component component) {
		if(component==null||component instanceof Frame)
			return new Point();
		return addPoints(component.getLocation(), getAbsoluteLocation(component.getParent()));
	}
	public static Dimension getActualSize(Frame frame) {
		try {
			int extendedState = frame.getExtendedState();
			java.awt.Rectangle bounds = frame.getMaximizedBounds(), systemBounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
			return new Dimension((extendedState&Frame.MAXIMIZED_HORIZ)==Frame.MAXIMIZED_HORIZ?(bounds!=null&&bounds.width !=Integer.MAX_VALUE?bounds.width :systemBounds.width ):frame.getWidth(),
					(extendedState&Frame.MAXIMIZED_VERT) ==Frame.MAXIMIZED_VERT ?(bounds!=null&&bounds.height!=Integer.MAX_VALUE?bounds.height:systemBounds.height):frame.getHeight());
		} catch(HeadlessException e) { return frame.getSize(); }
	}

	public static JScrollPane createScrollPane(Component component) { return createScrollPane(component, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); }
	public static JScrollPane createScrollPane(Component component, int vsbPolicy, int hsbPolicy) { return new JScrollPane(component, vsbPolicy, hsbPolicy); }
	public static JScrollPane createScrollPane(Component component, int vsbPolicy, int hsbPolicy, int vsbUnitIncrement, int hsbUnitIncrement) {
		JScrollPane scroll = createScrollPane(component, vsbPolicy, hsbPolicy);
		scroll.getVerticalScrollBar().setUnitIncrement(vsbUnitIncrement);
		scroll.getHorizontalScrollBar().setUnitIncrement(hsbUnitIncrement);
		return scroll;
	}

	public static JScrollPane createExtendedScrollPane(Component component) { return createExtendedScrollPane(component, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); }
	public static JScrollPane createExtendedScrollPane(Component component, int vsbPolicy, int hsbPolicy) {
		JScrollPane scroll = new JScrollPane(vsbPolicy, hsbPolicy);
		final JViewport viewport = new JViewport() {
			private static final long serialVersionUID = 1l;
			@Override public Dimension getViewSize() {
				Component component = getView();
				if(component==null)
					return new Dimension(0, 0);
				if(isViewSizeSet)
					return component.getSize();
				else if(component instanceof Scrollable)
					return ((Scrollable) component).getPreferredScrollableViewportSize();
				else return component.getPreferredSize();
			}
		};
		viewport.addChangeListener(new ChangeListener() {
			private Dimension size;
			@Override public void stateChanged(ChangeEvent event) {
				Dimension size = viewport.getViewSize();
				if(this.size!=null&&!this.size.equals(size)) {
					viewport.setViewPosition(new Point());
					this.size = size;
				} else this.size = size;
			}
		});
		scroll.setViewport(viewport);
		scroll.setViewportView(component);
		return scroll;
	}

	public static String showPasswordDialog(Component component, String text) { return showPasswordDialog(component, text, null); }
	public static String showPasswordDialog(Component component, String text, String title) {
		JPasswordField field = new JPasswordField(); field.setEchoChar('\u25CF');
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
		panel.add(createLabel(text));
		panel.add(field);
		if(JOptionPane.showConfirmDialog(component, panel, title, JOptionPane.OK_CANCEL_OPTION)==JOptionPane.OK_OPTION)
			return new String(field.getPassword());
		else return null;
	}

	public static ImageIcon createImageIcon(String name) { return createImageIcon(name, null); }
	public static ImageIcon createImageIcon(String name, ImageIcon fallback) {
		URL resource = Utilities.getResource(name);
		if(resource!=null)
			return new ImageIcon(resource);
		else return fallback!=null?fallback:null;
	}


	public static class BoundaryPoints {
		public Point minimum, maximum;
		public Dimension dimension;
		public BoundaryPoints(Point point, Dimension dimension) {
			minimum = new Point(point); maximum = new Point(point);
			maximum.translate(dimension.width, dimension.height);
			this.dimension = dimension;
		}
		public void checkBoundary(Point point, Dimension dimension) {
			minimum.x = Math.min(minimum.x, point.x);
			minimum.y = Math.min(minimum.y, point.y);
			maximum.x = Math.max(maximum.x, point.x+dimension.width);
			maximum.y = Math.max(maximum.y, point.y+dimension.height);
			this.dimension.setSize(maximum.x-minimum.x, maximum.y-minimum.y);
		}
	}

	public static class Rectangle {
		public Point point_ul, point_lr;
		public Dimension size;
		public Rectangle(Point2D point_a, Point2D point_b) { this(new Point((int)point_a.getX(), (int)point_a.getY()),
				new Point((int)point_b.getX(), (int)point_b.getY())); }
		public Rectangle(Point point, Dimension dimension) { this(point, new Point(point.x+dimension.width, point.y+dimension.height)); }
		public Rectangle(Point point_a, Point point_b) {
			this.point_ul = new Point(point_a);
			this.point_lr = new Point(point_b);
			if(point_a.x>point_b.x) {
				int x = point_b.x;
				this.point_lr.x = point_a.x;
				this.point_ul.x = x;
			}
			if(point_a.y>point_b.y) {
				int y = point_b.y;
				this.point_lr.y = point_a.y;
				this.point_ul.y = y;
			}
			this.size = new Dimension(this.point_lr.x-this.point_ul.x,
					this.point_lr.y-this.point_ul.y);
		}
		public boolean contains(Point point) { return contains(point, new Dimension()); }
		public boolean contains(Point point, Dimension dimension) {
			return point_ul.x<=point.x&& point.x+dimension.width <=point_lr.x
					&& point_ul.y<=point.y&& point.y+dimension.height<=point_lr.y;
		}
		public long getArea() { return size.width*size.height; }

		public java.awt.Rectangle toRectangle() { return new java.awt.Rectangle(point_ul, size);	}
		public Area toArea() { return new Area(toRectangle()); }
	}

	private static class ChiselBorder implements Border {
		private Insets insets = new Insets(1, 0, 1, 0);
		@Override
		public Insets getBorderInsets(java.awt.Component component) { return insets; }
		@Override
		public boolean isBorderOpaque() { return true; }
		@Override
		public void paintBorder(java.awt.Component component, Graphics g, int x, int y, int width, int height) {
			Color color = component.getBackground();
			g.setColor(Guitilities.deriveColorHSB(color, 0, 0, .2f));
			g.drawLine(x, y, x + width, y);
			g.setColor(Guitilities.deriveColorHSB(color, 0, 0, -.2f));
			g.drawLine(x, y + height - 1, x + width, y + height - 1);
		}
	}

	public static TitledBorder createTitledBorder(String title) {
		return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title);
	}

	public static JDialog createDialog(Frame owner, String title) { return createDialog(owner, title, title); }
	public static JDialog createDialog(Frame owner, String title, String gradientTitle) {
		JDialog dialog = new JDialog(owner, title, true);
		dialog.setLayout(new BorderLayout());
		dialog.add(Guitilities.createGradientTitle(title), BorderLayout.NORTH);
		dialog.setLocationRelativeTo(owner);
		dialog.setLocationByPlatform(true);
		dialog.setResizable(false);
		return dialog;
	}

	public static JPanel createSeparator(String text) {
		JLabel title = new JLabel(text);
		title.setVerticalAlignment(0);
		title.setHorizontalAlignment(JLabel.LEFT);
		title.setFont(UIManager.getFont(TITLE_FONT).deriveFont(12f));
		title.setForeground(UIManager.getColor(TITLE_FOREGROUND));
		return createSeparator(title);
	}
	public static JPanel createSeparator(JLabel label) {
		JPanel panel = new JPanel(new TitledSeperatorLayout(true));
		panel.setOpaque(false);
		panel.add(label);
		panel.add(new JSeparator());
		return panel;
	}

	public static <Type extends JComponent> Type addGridPairLine(JPanel panel, int gridy, JComponent left, Type right) { return addGridPairLine(panel, gridy, left, right, null); }
	public static <Type extends JComponent> Type addGridPairLine(JPanel panel, int gridy, JComponent left, Type right, JComponent additional) {
		pair_left_constraint.gridy = pair_right_constraint.gridy = pair_additional_constraint.gridy = gridy;
		if(additional!=null)
			pair_right_constraint.weightx = 0.6d;

		panel.add(left, pair_left_constraint);
		panel.add(right, pair_right_constraint);
		if(additional!=null)
			panel.add(additional, pair_additional_constraint);

		if(!pair_left_constraint.equals(PAIR_LEFT_DEFAULT_CONSTRAINT))
			pair_left_constraint = (GridBagConstraints)PAIR_LEFT_DEFAULT_CONSTRAINT.clone();
		if(!pair_right_constraint.equals(PAIR_RIGHT_DEFAULT_CONSTRAINT))
			pair_right_constraint = (GridBagConstraints)PAIR_RIGHT_DEFAULT_CONSTRAINT.clone();
		if(!pair_additional_constraint.equals(PAIR_ADDITIONAL_DEFAULT_CONSTRAINT))
			pair_additional_constraint = (GridBagConstraints)PAIR_ADDITIONAL_DEFAULT_CONSTRAINT.clone();

		return right;
	}

	public static JButton createButton(String text, ActionListener listener) {
		JButton button = new JButton(text);
		button.addActionListener(listener);
		return button;
	}

	public static int centerText(Direction direction, Graphics graphics, Dimension size, String text) { return centerText(direction, graphics, new java.awt.Rectangle(size), text); }
	public static int centerText(Direction direction, Graphics graphics, java.awt.Rectangle bounds, String text) {
		FontMetrics metrics = graphics.getFontMetrics();
		switch(direction) {
		case HORRIZONTAL: return bounds.x+bounds.width/2-metrics.stringWidth(text)/2;
		case VERTICAL: return bounds.y+(int)((bounds.height-metrics.getStringBounds(text, graphics).getHeight())/2+metrics.getAscent());
		default: return 0; }
	}

	public static void selectFrame(final JInternalFrame frame) {
		SwingUtilities.invokeLater(new Runnable(){
			@Override public void run() {
				frame.requestFocusInWindow();
				try { frame.setSelected(true); }	catch (PropertyVetoException e) {}
				frame.toFront();
			}
		});
	}
} public enum Direction { HORRIZONTAL, VERTICAL } public static class Rectangle {
	public Point point_ul, point_lr;
	public Dimension size;
	public Rectangle(Point2D point_a, Point2D point_b) { this(new Point((int)point_a.getX(), (int)point_a.getY()),
			new Point((int)point_b.getX(), (int)point_b.getY())); }
	public Rectangle(Point point, Dimension dimension) { this(point, new Point(point.x+dimension.width, point.y+dimension.height)); }
	public Rectangle(Point point_a, Point point_b) {
		this.point_ul = new Point(point_a);
		this.point_lr = new Point(point_b);
		if(point_a.x>point_b.x) {
			int x = point_b.x;
			this.point_lr.x = point_a.x;
			this.point_ul.x = x;
		}
		if(point_a.y>point_b.y) {
			int y = point_b.y;
			this.point_lr.y = point_a.y;
			this.point_ul.y = y;
		}
		this.size = new Dimension(this.point_lr.x-this.point_ul.x,
				this.point_lr.y-this.point_ul.y);
	}
	public boolean contains(Point point) { return contains(point, new Dimension()); }
	public boolean contains(Point point, Dimension dimension) {
		return point_ul.x<=point.x&& point.x+dimension.width <=point_lr.x
				&& point_ul.y<=point.y&& point.y+dimension.height<=point_lr.y;
	}
	public long getArea() { return size.width*size.height; }

	public java.awt.Rectangle toRectangle() { return new java.awt.Rectangle(point_ul, size);	}
	public Area toArea() { return new Area(toRectangle()); }
} public static class RelativeMouseEvent extends MouseEvent {
	private static final long serialVersionUID = 1l;
	public RelativeMouseEvent(MouseEvent event, Point point, Point relative) {
		super(event.getComponent(), event.getID(), event.getWhen(), event.getModifiers(),
				point.x-relative.x, point.y-relative.y, point.x, point.y,
				event.getClickCount(), event.isPopupTrigger(), event.getButton());
	}
}
import static lc.kra.jds.Utilities.computeHash;
import static lc.kra.jds.Utilities.getTranslation;
import static lc.kra.jds.gui.Guitilities.addPoints;
import static lc.kra.jds.gui.Guitilities.invertPoint;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.NumberFormatter;
import lc.kra.jds.Utilities.LegacyObjectInputStream;
import lc.kra.jds.Utilities.RememberFileChooser;
import lc.kra.jds.Utilities.TranslationType;
import lc.kra.jds.components.Component;
import lc.kra.jds.components.Component.ComponentFlavor;
import lc.kra.jds.components.Configurable;
import lc.kra.jds.components.Configurable.Option;
import lc.kra.jds.components.Configurable.Option.OptionType;
import lc.kra.jds.components.Interactable;
import lc.kra.jds.components.Sociable;
import lc.kra.jds.components.Wire;
import lc.kra.jds.components.buildin.display.Voltmeter;
import lc.kra.jds.components.buildin.external.ExternalSimulation;
import lc.kra.jds.components.buildin.general.Junction;
import lc.kra.jds.contacts.Contact;
import lc.kra.jds.contacts.DragContact;
import lc.kra.jds.contacts.InputContact;
import lc.kra.jds.contacts.OutputContact;
import lc.kra.jds.exceptions.LocationOutOfBoundsException;
import lc.kra.jds.exceptions.PasswordRequiredException;
import lc.kra.jds.exceptions.WireNotConnectable;
import lc.kra.jds.exceptions.WiringException;
import lc.kra.jds.gui.Application;
import lc.kra.jds.gui.Application.SimulationFrame;
import lc.kra.jds.gui.Guitilities;
import lc.kra.jds.gui.Guitilities.Direction;
import lc.kra.jds.gui.Guitilities.Rectangle;
import lc.kra.jds.gui.Guitilities.RelativeMouseEvent;
/*
 * JDigitalSimulator
 * Copyright (C) 2017 Kristian Kraljic
 *
 * 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/>.
 */
package lc.kra.jds;





public class Simulation extends JComponent implements Scrollable, Printable {
	private static final long serialVersionUID = 1l;

	public enum Layer { TOPMOST, BOTTOMMOST; }

	private static final Color SELECTION = new Color(10, 200, 10, 75);
	private static final Dimension SIZE = new Dimension(2000, 2000);
	private static final int GRID_STEPS = 20;
	private static final int STACK_SIZE = 200;

	public SimulationProperies properties;
	private SimulationData data;

	private Timer timer;
	private SimulationOscilloscope oscilloscope;

	private AffineTransform transform;

	private long paint;
	private Set<Component> selected;
	private Point select, current, last;
	private Clipboard clipboard;

	private Wire selectWire;
	private Contact selectContact;
	private Component selectComponent;

	private DragContact dragContact;