public class Connector extends EData implements PulseInteractor, EEventListener {

	// static key for observation messages
	public static final String CABLE_TRANSMITTION = "cable_transmittion"; //$NON-NLS-1$

	// static keys for pulse direction
	// private static final EnumSet<Tag> DIRECTION = EnumSet.of(Tag.IN, Tag.OUT,
// Tag.BOTH);

	// pulse direction of this connector
	private Tag _direction;

	// id of the connector, that we are connected with by a cable.
	// if no cable, then this is -1.
	private int _partner;

	// flag indicating whether this connector has a cable.
	// note: This is another thing than having a partner. If a cable ist just
	// created by users click, the connector is plugged, but the cable ends
	// at the mouse-dragging-point. So no partner in this case.
	private boolean _plugged = false;

	// timeslot where we got the last pulse.
	// note: if choose -1 for this, the connector will be painted in highlight
	// mode at startup. I don't know why.
	private long _lastPulse = -11;

	// value of the pulse. If this is a program connector, it is 0 or 1.
	// in case of a digit connector, it is according to the pulse transmission.
	private long _pulseValue = 0;

	// ============================ lifecycle
	// ===================================

	public Connector() {
		// empty constructor
	}

	public void setAttributes(Attributes attrs) {
		super.setAttributes(attrs);
		_direction = XMLUtil.parseEnum(attrs, Tag.IO, Tag.class);
		_partner = XMLUtil.parseInt(attrs, Tag.PARTNER);
		// _plugged = _partner >= 0;
		// note: the connector is plugged, when both partners are set to
		// the cable
	}

	// ================================ methods
	// =================================

	public String getAttributes() {
		return super.getAttributes() + XMLUtil.wrapAttribute(Tag.IO, _direction.toString())
				+ XMLUtil.wrapAttribute(Tag.PARTNER, Integer.toString(_partner));
	}

	public int getPartner() {
		return _partner;
	}

	public void setPartnerID(int id) {
		_partner = id;
	}

	public boolean isPlugged() {
		return _plugged;
	}

	public void setPlugged(boolean b) {
		if (_plugged != b) {
			_plugged = b;
			setChanged();
			notifyObservers(REPAINT);
		}
	}

	public Tag getDirection() {
		return _direction;
	}

	private boolean isSender() {
		return (_direction == Tag.OUT || _direction == Tag.BOTH);
	}

	private boolean isReceiver() {
		return (_direction == Tag.IN || _direction == Tag.BOTH);
	}

	private boolean isDigit() {
		return _type == EType.DIGIT_CONNECTOR || _type == EType.DIGIT_CONNECTOR_CROSS || _type == EType.INTER_CONNECTOR;
	}

	private boolean isProgram() {
		return _type == EType.PROGRAM_CONNECTOR;
	}

	public void setLastPulse(long time) {
		if (_lastPulse < time) {
			// adjust pulse time and value
			_lastPulse = time;

			// if we are hightlighting, call for repaint
			if ((boolean) Status.HIGHLIGHT_PULSE.getValue()) {
				setChanged();
				notifyObservers(EData.PAINT_IMMEDIATELY);
			}
			// set alarm clock for changing our highlightning in the following
			// eevent time slot.
			// note: maybe the highlightning-flag is not set now.
			// But if we are in stepping mode it might switched on before our
			// call-back occures. So we have to set alarm in any case.
			Configuration config = (Configuration) Status.CONFIGURATION.getValue();
			config.getCyclingLights().setAlarmClock(time, this);
		}
	}

	public long getLastPulse() {
		return _lastPulse;
	}

	// ========================= pulseInteractor methods
	// ========================

	/**
	 * @param time
	 * @param source
	 * @see eniac.data.PulseInteractor#canReceiveDigit(long,
	 *      eniac.data.PulseInteractor)
	 */
	public boolean canReceiveDigit(long time, PulseInteractor source) {
		return isReceiver() && isDigit() && ((PulseInteractor) getParent()).canReceiveDigit(time, this);
	}

	private long checkDigit(long time, long value) {

		// check, if this is the first pulse within this time slot
		if (_lastPulse < time) {

			// adjust pulse time and value
			setLastPulse(time);
			_pulseValue = value;

		}
		else {

			// there has been a pulse before. Adjust values
			long oldPulseValue = _pulseValue;
			_pulseValue |= value;
			value = _pulseValue ^ oldPulseValue;
		}
		return value;
	}

	/**
	 * @param time
	 * @param value
	 * @param source
	 * @see eniac.data.PulseInteractor#receive(long, int,
	 *      eniac.data.PulseInteractor)
	 */
	public void receiveDigit(long time, long value, PulseInteractor source) {

		// if value contains no pulse, there is nothing to do
		if (value == 0) {
			return;
		}

		// check value
		value = checkDigit(time, value);

		// transmitt pulse to parent
		// note: we already asked parent if it can receive
		if (value > 0) {
			((PulseInteractor) _parent).receiveDigit(time, value, this);
		}
	}

	/**
	 * @param time
	 * @param value
	 * @param source
	 * @see eniac.data.PulseInteractor#sendDigit(long, long, PulseInteractor)
	 */
	public void sendDigit(long time, long value, PulseInteractor source) {

		// if (getType() == EType.INTER_CONNECTOR) {
		//    System.out.println("jkjkj"); //$NON-NLS-1$
		// }

		// check that we potentially can send a digit pulse and haven't done
		// in current timeslice
		if (isDigit() && isSender() && value > 0) {

			// check value
			value = checkDigit(time, value);

			// if value contains no pulse, we don't need to send
			if (value == 0) {
				return;
			}

			// check, if we are plugged and have a valid partner
			if (_plugged && _partner >= 0) {

				// send pulse to partner if he can receive.
				Connector c = (Connector) getConfiguration().getIDManager().get(_partner);
				if (c.canReceiveDigit(time, this)) {
					c.receiveDigit(time, value, this);
				}
				// notify cable for highlightning
				setChanged();
				notifyObservers(CABLE_TRANSMITTION);
			}
		}
	}

	/**
	 * @param time
	 * @param source
	 * @see eniac.data.PulseInteractor#canReceiveProgram(long,
	 *      eniac.data.PulseInteractor)
	 */
	public boolean canReceiveProgram(long time, PulseInteractor source) {
		return isReceiver() && isProgram() && _lastPulse < time
				&& ((PulseInteractor) getParent()).canReceiveProgram(time, this);
	}

	/**
	 * @param time
	 * @param source
	 * @see eniac.data.PulseInteractor#receiveProgram(long,
	 *      eniac.data.PulseInteractor)
	 */
	public void receiveProgram(long time, PulseInteractor source) {

		// adjust pulse time
		setLastPulse(time);

		// transmitt pulse to parent.
		// note: it has already been checked, that parent can receive.
		// so we don't ask again.
		((PulseInteractor) _parent).receiveProgram(time, this);
	}

	/**
	 * @param time
	 * @param source
	 * @see eniac.data.PulseInteractor#sendProgram(long,
	 *      eniac.data.PulseInteractor)
	 */
	public void sendProgram(long time, PulseInteractor source) {

		// check if pulse can go and haven't already gone this time
		if (isProgram() && isSender() && _lastPulse < time) {

			// adjust pulse time
			setLastPulse(time);

			// if we are plugged and have a valid partner, transmitt pulse.
			if (_plugged && _partner >= 0) {

				// TODO: synchronize this. maybe cable is removed.
				Connector c = (Connector) getConfiguration().getIDManager().get(_partner);
				if (c.canReceiveProgram(time, this)) {
					c.receiveProgram(time, this);
				}
				// notify cable to paint
				setChanged();
				notifyObservers(CABLE_TRANSMITTION);
			}
		}
	}

	// ============================= event listening
	// ============================

	/**
	 * @param e
	 * @see eniac.simulation.EEventListener#process(eniac.simulation.EEvent)
	 */
	public void process(EEvent e) {
		if (e.type == EEvent.ALARM && (boolean) Status.HIGHLIGHT_PULSE.getValue()) {

			// we are called for downlightning
			setChanged();
			notifyObservers(EData.PAINT_IMMEDIATELY);
		}
	}
} public class EData extends Observable implements Comparable<EData> {

	// static key indicating that that a repaint is recommended.
	public static final String REPAINT = "repaint"; //$NON-NLS-1$

	public static final String PAINT_IMMEDIATELY = "paint_immediately"; //$NON-NLS-1$

	public enum Tag {
		// tags
		ENIAC, PATH, NAME,

		// attributes
		ID, VALUE, NUMBER, POWER, PARTNER, LOCATION, IO, GRID, SIZE, INDEX, FLAG,

		// attribute values
		IN, OUT, BOTH, FALSE, TRUE,
	}

	// ============================= fields
	// =====================================

	// type of this dataObject as encoded by class DataTypes.
	// parsed from xml-tag
	protected EType _type;

	// unique id of this dataObject. All dataObjects have different ids
	// parsed from xml-attributes
	protected int _id;

	// name of this dataObject.
	// parsed from xml-attributes
	protected String _name;

	// numbers in parent's grid for layouting
	// parsed from xml-attributes
	protected int[] _gridNumbers;

	// index at parent.
	// this is not necessarily the same index as in the kindergarten.
	// two children may share the same index
	// (eg. two programconnectors in a tray)
	protected int _index;

	// parent dataObject. Configuration is root dataObject.
	protected ParentData _parent = null;

	// ============================= lifecycle
	// ==================================

	// default Constructor
	public EData() {
		// empty constructor
	}

	// constructor used by the parser's default handler
	public void setAttributes(Attributes attrs) {
		_id = XMLUtil.parseInt(attrs, Tag.ID);
		_index = XMLUtil.parseInt(attrs, Tag.INDEX);
		_name = XMLUtil.parseString(attrs, Tag.NAME);
		_gridNumbers = XMLUtil.parseIntArray(attrs, Tag.GRID);
	}

	/**
	 * initializes this dataObject. This default method just does nothing and
	 * should be overwritten by subclasses in case.
	 */
	public void init() {
		// register at idManager
		getConfiguration().getIDManager().put(this);
	}

	public void dispose() {
		// unregister at idManager
		getConfiguration().getIDManager().remove(_id);
	}

	// =========================== getter and setter
	// ============================

	public int getID() {
		return _id;
	}

	public void setID(int id) {
		_id = id;
	}

	public String getName() {
		return _name;
	}

	public void setName(String name) {
		_name = name;
	}

	public ParentData getParent() {
		return _parent;
	}

	public void setParent(ParentData pd) {
		_parent = pd;
	}

	public void setType(EType type) {
		_type = type;
	}

	public EType getType() {
		return _type;
	}

	public int[] getGridNumbers() {
		return _gridNumbers;
	}

	public void setIndex(int index) {
		_index = index;
	}

	public int getIndex() {
		return _index;
	}

	// =============================== methods
	// ==================================

	public Configuration getConfiguration() {
		return getParent().getConfiguration();
	}

	public boolean hasPower() {
		EData parent = getParent();
		if (parent instanceof Configuration) {
			Configuration config = (Configuration) parent;
			Unit unit = config.getUnit(_gridNumbers[0]);
			return unit.hasPower();
		}
		return parent.hasPower();
	}

	/**
	 * Returns the name of this dataObject. Useful to add this to a JList.
	 * 
	 * @return the string containing the name of this dataObject.
	 */
	public String toString() {
		return _name;
	}

	public int compareTo(EData data) {
		// compare by type, then by index
		int i = _type.compareTo(data.getType());
		if (i == 0) {
			i = _index - data.getIndex();
		}
		return i;
	}

	public List<Property> getProperties() {
		List<Property> l = new LinkedList<>();
		l.add(new ConstantProperty(Tag.NAME.toString(), _name));
		l.add(new ConstantProperty(Tag.ID.toString(), Integer.toString(_id)));
		return l;
	}

	public void setProperties(List<Property> l) {
		for (Property p : l) {
			if (p.getName().equals(Tag.NAME.name().toLowerCase())) {
				_name = ((ConditionedProperty) p).getValue();
			}
		}
	}

	public EPanel makePanel() {
		EPanel panel = _type.makeEPanel();
		panel.setData(this);
		return panel;
	}

	protected void assertInit(EData data) {
		IDManager idManager = getConfiguration().getIDManager();
		if (!idManager.containsID(data)) {
			data.init();
		}
	}

	// ============================= observer stuff
	// =============================

	public void addObserverToTree(Observer o) {
		addObserver(o);
	}

	// =========================== xml methods
	// ==================================

	/**
	 * Returns a String representing all Attributes and values for xml output.
	 * Over here the basic attributes <code>id</code> and <code>name</code> are
	 * added. If subclasses have more attributes to encode, then they should
	 * overwrite this method the following way: <br>
	 * 1. do a super-call to this method <br>
	 * 2. append additional attributes to the returned string <br>
	 * 3. return the string to caller. <br>
	 * 
	 * @return a <code>String</code> containing all attributes and values.
	 */
	protected String getAttributes() {
		return XMLUtil.wrapAttribute(Tag.ID, Integer.toString(_id)) + XMLUtil.wrapAttribute(Tag.NAME, _name)
				+ XMLUtil.wrapAttribute(Tag.GRID, StringConverter.toString(_gridNumbers))
				+ XMLUtil.wrapAttribute(Tag.INDEX, Integer.toString(_index));
	}

	/**
	 * Returns a list containing all Tag and child-Tag of this dataObject.
	 * 
	 * @return a <code>List</code> containing all tags.
	 */
	public void appendTags(List<String> l, int indent) {

		// if low indentation level, write comment line
		if (indent <= 2) {
			XMLUtil.appendCommentLine(l, indent, getName());
		}
		// add open-close-tag, a tag that closes in the same line.
		l.add(getOpenCloseTag(indent));
	}

	protected String getOpenTag(int indent) {
		return XMLUtil.TABS[indent] + XMLUtil.wrapOpenTag(_type.name().toLowerCase() + getAttributes());
	}

	protected String getCloseTag(int indent) {
		return XMLUtil.TABS[indent] + XMLUtil.wrapCloseTag(_type.name().toLowerCase());
	}

	protected String getOpenCloseTag(int indent) {
		return XMLUtil.TABS[indent] + XMLUtil.wrapOpenCloseTag(_type.name().toLowerCase() + getAttributes());
	}
} public class Configuration extends ParentData {

	private IDManager _idManager = new IDManager();

	// ============================ lifecycle
	// ===================================

	public Configuration() {
		// empty
	}

	public void dispose() {
		super.dispose();
		_idManager.dispose();
		_idManager = null;
	}

	// ============================== methods
	// ===================================

	public IDManager getIDManager() {
		return _idManager;
	}

	public Configuration getConfiguration() {
		return this;
	}

	public CyclingLights getCyclingLights() {
		return (CyclingLights) getGarten().getKind(EType.CYCLING_LIGHTS, 0);
	}
} public enum EType {

	/**
	 * A configuration of 3 units
	 */
	CONFIGURATION_3,

	/**
	 * A configuration of 4 units
	 */
	CONFIGURATION_4,

	/**
	 * A configuration of 8 units
	 */
	CONFIGURATION_8,

	/**
	 * A configuration of 12 units
	 */
	CONFIGURATION_12,

	/**
	 * A configuration of 16 units
	 */
	CONFIGURATION_16,

	/**
	 * A configuration of 20 units
	 */
	CONFIGURATION_20,

	/**
	 * the accumulator
	 */
	ACCUMULATOR_UNIT,

	/**
	 * the cycling unit
	 */
	CYCLING_UNIT,

	/**
	 * the initiating unit
	 */
	INITIATING_UNIT,

	/**
	 * the 1st constant transmitter unit
	 */
	CONSTANT_TRANSMITTER_1_UNIT,

	/**
	 * the 2nd constant transmitter unit
	 */
	CONSTANT_TRANSMITTER_2_UNIT,

	/**
	 * an upper trunk which is 2 units width
	 */
	UPPER_TRUNK_2,

	/**
	 * an upper trunk which is 4 units width
	 */
	UPPER_TRUNK_4,

	/**
	 * a lower trunk which is 3 units width
	 */
	LOWER_TRUNK_3,

	/**
	 * a lower trunk which is 4 units width
	 */
	LOWER_TRUNK_4,

	/**
	 * a tray (part of a trunk) which is 2 units width
	 */
	TRAY_2,

	/**
	 * a tray (part of a trunk) which is 3 units width
	 */
	TRAY_3,

	/**
	 * a tray (part of a trunk) which is 4 units width
	 */
	TRAY_4,

	/**
	 * a blinken lights of an accumulator
	 */
	BLINKEN_LIGHTS,

	/**
	 * the heaters switch of an accumulator (may display the accumulator icon)
	 */
	ACCU_HEATERS,

	/**
	 * a standard heaters switch
	 */
	HEATERS,

	/**
	 * a heaters switch that displays 0 - 1
	 */
	HEATERS_01,

	/**
	 * the step button of the cycling unit
	 */
	STEP_BUTTON,

	/**
	 * the go button of the initiating unit
	 */
	GO_BUTTON,

	/**
	 * the clear button of the initiating unit
	 */
	CLEAR_BUTTON,

	/**
	 * the clear button for the cycle counter
	 */
	CYCLE_COUNTER_CLEAR,

	/**
	 * the iteration switch of the cycling unit
	 */
	ITERATION_SWITCH,

	/**
	 * the significant figures switch of an accumulator
	 */
	SIGNIFICIANT_FIGURES_SWITCH,

	/**
	 * the operations switch of an accumulator
	 */
	OPERATION_SWITCH,

	/**
	 * the repeat switch of an accumulator
	 */
	REPEAT_SWITCH,

	/**
	 * a cipher of the cycle counter at the cycling unit
	 */
	CIPHER,

	/**
	 * a digit of the blinken lights
	 */
	BLINKEN_NUMBER_SWITCH,

	/**
	 * the sign of the blinken lights
	 */
	BLINKEN_SIGN_SWITCH,

	/**
	 * the cycling lights -- display the pulse
	 */
	CYCLING_LIGHTS,

	/**
	 * the go lights of the initiating unit
	 */
	GO_LIGHTS,

	/**
	 * a digit connector (10 digits + sign + carryover)
	 */
	DIGIT_CONNECTOR,

	/**
	 * a program connector (1 pulse)
	 */
	PROGRAM_CONNECTOR,

	/**
	 * an inter-connector (can connect 2 accumulator units)
	 */
	INTER_CONNECTOR,

	/**
	 * a digit connector in horizontal layout (e.g. at the edges of a tray)
	 */
	DIGIT_CONNECTOR_CROSS,

	/**
	 * a pair of program connectors (in + out)
	 */
	PROGRAM_CONNECTOR_PAIR,

	/**
	 * a cover, without functionality (accumulator, between heaters and
	 * significant figures switch)
	 */
	BLEND_8,

	/**
	 * a cover, without functionality (accumulator, above the repeat switches)
	 */
	BLEND_16,

	/**
	 * a cover, without functionality (constant transmitter panel)
	 */
	BLEND_A_10,

	/**
	 * the symbol for the cycling unit
	 */
	CYCLING_SYMBOL,

	/**
	 * the symbol for the initiating unit
	 */
	INITIATING_SYMBOL,

	/**
	 * the symbol for the constant transmitter unit 1
	 */
	CONSTANT_TRANSMITTER_1_SYMBOL,

	/**
	 * the symbol for the constant transmitter unit 2
	 */
	CONSTANT_TRANSMITTER_2_SYMBOL,

	/**
	 * the constant selector switch for A+B
	 */
	CONSTANT_SELECTOR_SWITCH_AB,

	/**
	 * the constant selector switch for C+D
	 */
	CONSTANT_SELECTOR_SWITCH_CD,

	/**
	 * the constant selector switch for E+F
	 */
	CONSTANT_SELECTOR_SWITCH_EF,

	/**
	 * the constant selector switch for G+H
	 */
	CONSTANT_SELECTOR_SWITCH_GH,

	/**
	 * the constant selector switch for J+K
	 */
	CONSTANT_SELECTOR_SWITCH_JK,

	/**
	 * a number switch at the constant transmitter unit 2
	 */
	CONSTANT_SWITCH,

	/**
	 * the image displayed at the initiating unit
	 */
	INITIATING_IMAGE,

	/**
	 * the frequency slider (cycling unit)
	 */
	FREQUENCY_SLIDER,

	/**
	 * the benchmark, displaying the number of addition cycles per second
	 */
	BENCHMARK,

	/**
	 * the addition cycle counter
	 */
	CYCLE_COUNTER,

	/**
	 * image to be used in the overview panel, as marker for the currently
	 * visible area
	 */
	XOR_IMAGE,

	/**
	 * toggle switch for the constant transmitter 2, row J, left
	 */
	CONSTANT_SIGN_TOGGLE_JL,

	/**
	 * toggle switch for the constant transmitter 2, row J, right
	 */
	CONSTANT_SIGN_TOGGLE_JR,

	/**
	 * toggle switch for the constant transmitter 2, row K, left
	 */
	CONSTANT_SIGN_TOGGLE_KL,

	/**
	 * toggle switch for the constant transmitter 2, row K, right
	 */
	CONSTANT_SIGN_TOGGLE_KR,

	/**
	 * the numbers display at the constant transmitter 2
	 */
	CONSTANT_2_LIGHTS,

	/**
	 * a decimal cipher of the constant transmitter 2 lights
	 */
	CONSTANT_BLINKEN_CIPHER,

	/**
	 * the +/- sign for a number at the constant transmitter 2 lights
	 */
	CONSTANT_BLINKEN_SIGN,

	/**
	 * a cipher displayed at constant transmitter 1, to be shown during a
	 * transmission
	 */
	CONSTANT_TRANSMITTION_CIPHER,

	/**
	 * a =/- sign displayed at constant transmitter 1, to be shown during a
	 * transmission
	 */
	CONSTANT_TRANSMITTION_SIGN,

	/**
	 * the lights at constant transmitter 1, will display number and sign during
	 * a transmission
	 */
	CONSTANT_TRANSMITTION_LIGHTS;

	public enum Tag {
		NAME, TYPE, MODEL, VIEW, CODE, CODES, VALUE;
	}

	private String _edataClass;

	private String _epanelClass;

	private EType.Tag _codeName;

	private String[] _codes;

	private Descriptor[] _descriptors;

	private Grid[] _gridCache;

	private EType() {
		_gridCache = new Grid[StringConverter.toInt(EProperties.getInstance().getProperty("GRID_CACHE_SIZE"))];
	}

	// ========================== getters and setters
	// ===========================

	public void setCodes(String[] codes) {
		_codes = codes;
	}

	public String[] getCodes() {
		return _codes;
	}

	public void setCodeName(EType.Tag codeName) {
		_codeName = codeName;
	}

	public EType.Tag getCodeName() {
		return _codeName;
	}

	public void setEDataClass(String edataClass) {
		_edataClass = edataClass;
	}

	public void setEPanelClass(String epanelClass) {
		_epanelClass = epanelClass;
	}

	public void setDescriptors(Descriptor[] descriptors) {
		// empty grid cache
		Arrays.fill(_gridCache, null);
		_descriptors = descriptors;
	}

	// ================================ methods
	// =================================

	public EData makeEData() throws InstantiationException, ClassNotFoundException, IllegalAccessException {

		EData edata = (EData) Class.forName(_edataClass).newInstance();
		edata.setType(this);
		return edata;
	}

	public EPanel makeEPanel() {
		try {
			return (EPanel) Class.forName(_epanelClass).newInstance();
		} catch (Exception e) {
			// System.out.println(_epanelClass);
			e.printStackTrace();
		}
		return null;
	}

	public Descriptor getDescriptor(int lod) {
		return _descriptors[lod];
	}

	public Grid getGrid(int width, int height, int lod) {
		int index = computeIndex(width, height);
		if (_gridCache[index] == null || _gridCache[index].width != width || _gridCache[index].height != height) {

			_gridCache[index] = getDescriptor(lod).makeGrid(width, height);
		}
		return _gridCache[index];
	}

	private int computeIndex(int width, int height) {
		return (width + height) % _gridCache.length;
	}
} public class ConnectorPanel extends EPanel {

	// last point that we have been painted on the buffered image
	private Point _bufferedPaintPoint = new Point();

	// ============================= lifecycle //===============================

	public ConnectorPanel() {
		// empty
	}

	public void init() {
		super.init();
		// check, if the connector has a partner.
		int partnerID = ((Connector) _data).getPartner();
		if (partnerID == _data.getID()) {
			// if we are partner of ourself, then we have a loadbox
		}
		else if (partnerID >= 0) {

			// if otherwise positive, register at cableManager
			CableManager cm = EFrame.getInstance().getConfigPanel().getCableManager();
			cm.addCop(this);
		}
	}

	// ============================== methods //================================

	protected void paintComponent(Graphics g, int x, int y, int width, int height, int lod) {

		// get descriptor. If no descriptor, just return.
		Descriptor d = getDescriptor(lod);
		if (d == null) {
			return;
		}
		// draw background
		drawBackground(g, x, y, width, height, lod, d);

		// get Image according to connector's state
		Connector con = (Connector) _data;
		boolean loadbox = con.getID() == con.getPartner();
		boolean plugged = con.isPlugged();
		Descriptor.Key key;
		if (loadbox) {
			key = Descriptor.Key.LOADBOX;
		}
		else if (plugged) {
			key = Descriptor.Key.PLUGGED;
		}
		else {
			key = Descriptor.Key.UNPLUGGED;
		}
		Image img = (Image) d.get(key);

		// init helper variables
		long lastPulse = con.getLastPulse();
		long simTime = (long) Status.SIMULATION_TIME.getValue();
		boolean highlightPulse = (Boolean) Status.HIGHLIGHT_PULSE.getValue();

		// if highlight and we have a current pulse, mark connector
		if (highlightPulse && lastPulse == simTime) {
			g.setXORMode(Color.RED);
		}
		// draw image
		g.drawImage(img, x, y, width, height, this);
		g.setPaintMode();

		// if we are painting on the bufferedImage, save our mid point
		// in the bufferedImage's coordinates.
		if (x != 0 || y != 0) {
			_bufferedPaintPoint.x = x + (width >> 1);
			_bufferedPaintPoint.y = y + (height >> 1);
		}
	}

	public Point getBufferedPaintPoint() {
		return _bufferedPaintPoint;
	}

	protected Controler getController() {
		return EFrame.getInstance().getConfigPanel().getCableManager();
	}
} public class ConfigPanel extends ParentPanel implements Scrollable, StatusListener {

	/*
	 * ============================== fields ===================================
	 */

	// cableManager tracking all cables
	private CableManager _cableManager = new CableManager();

	// current lod for detaied view
	private int _lod;

	// temporary Point for computation speed up
	private Point _p = new Point();

	/*
	 * =============================== lifecycle ===============================
	 */

	public void init() {

		// super call for initiating the guiGarten and adding children
		super.init();

		// init cables
		_cableManager.init();

		// add as propertychangelistener to status to receive simulation time
		// updates
		Status.HIGHLIGHT_PULSE.addListener(this);
		Status.ZOOMED_HEIGHT.addListener(this);
	}

	public void dispose() {
		super.dispose();
		Status.HIGHLIGHT_PULSE.removeListener(this);
		Status.ZOOMED_HEIGHT.removeListener(this);
		removeAll();
		_cableManager = null;
	}

	/*
	 * ============================== methods ==================================
	 */

	public int getLod() {
		return _lod;
	}

	public CableManager getCableManager() {
		return _cableManager;
	}

	/*
	 * =========================== gui methods =================================
	 */

	/**
	 * Computes the preferred size of this configPanel. The preferred width is
	 * always a multiple of the numberOfUnits. So every unit will have the same
	 * width.
	 */
	public Dimension getPreferredSize() {

		// get current configuration height
		int height = (int) Status.ZOOMED_HEIGHT.getValue();

		// set lod
		Skin skin = (Skin) Status.SKIN.getValue();
		_lod = skin.getLodByHeight(height);

		// get descriptor for this configuration
		EType type = _data.getType();
		int lod = skin.getLodByHeight(height);
		Descriptor descriptor = type.getDescriptor(lod);

		// if no descriptor, return a default dimension
		if (descriptor == null) {
			return new Dimension(0, 0);
		}

		// otherwise compute wanted width by the rule of three and return
		int width = height * descriptor.getWidth() / descriptor.getHeight();
		return new Dimension(width, height);
	}

	public void doLayout() {

		// compute bounds for all children
		Rectangle[] rectangles = getRectangles(_lod, getWidth(), getHeight());

		// set bounds for all of the children
		EPanel[] children = getChildren();
		for (int i = 0; i < children.length; ++i) {
			Rectangle r = rectangles[i];
			children[i].setBounds(r.x, r.y, r.width, r.height);
		}
	}

	public void paintAsIcon(Graphics g, int x, int y, int w, int h, int lod) {

		// paint background and children, then paint cables
		super.paintAsIcon(g, x, y, w, h, lod);
		_cableManager.paintOnBufferedImage(g, lod);
	}

	public void paintChildren(Graphics g) {

		// paint children by supercall, then paint cables
		super.paintChildren(g);
		_cableManager.paintOnConfigPanel(g, _lod);
	}

	/*
	 * ========================== scrollable methods ===========================
	 */

	public boolean getScrollableTracksViewportHeight() {
		return false;
	}

	public boolean getScrollableTracksViewportWidth() {
		return false;
	}

	public Dimension getPreferredScrollableViewportSize() {
		return getPreferredSize();
	}

	public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
		return 30;
	}

	public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
		return 30;
	}

	/*
	 * ============================ event listening ============================
	 */

	@Override
	public void statusChanged(Status status, Object newValue) {
		if (status == Status.HIGHLIGHT_PULSE) {
			// highlight mode changed by user action.
			// Because this won't happen too often don't be cheap:
			// just repaint.
			update(_data, EData.REPAINT);
		}
		else if (status == Status.ZOOMED_HEIGHT) {

			// zoom changed.
			// init with default values
			double scrollPercentageX = 0.5;
			double scrollPercentageY = 0.5;

			// get values
			Dimension preferredSize = getPreferredSize();
			JViewport viewPort = ((JViewport) getParent());
			Dimension viewPortSize = viewPort.getSize();
			Point currentPosition = viewPort.getViewPosition();

			// check, if preferred width > viewport width
			int widthDiff = preferredSize.width - viewPortSize.width;
			if (widthDiff > 0) {
				// compute relative scroll position x
				scrollPercentageX = currentPosition.x / (double) (getSize().width - viewPortSize.width);
			}

			// check, if preferred height > viewport height
			int heightDiff = preferredSize.height - viewPortSize.height;
			if (heightDiff > 0) {
				// compute relative scroll position y
				scrollPercentageY = currentPosition.y / (double) (getSize().height - viewPortSize.height);
			}

			// default _p to current position
			_p = getLocation();

			// check, if need to update x location
			if (widthDiff > 0) {
				// we cannot see the complete configuration in x direction
				_p.x = (int) (widthDiff * scrollPercentageX);
			}

			// check, if need to update x location
			if (heightDiff > 0) {
				// we cannot see the complete configuration in x direction
				_p.y = (int) (heightDiff * scrollPercentageY);
			}

			setSize(preferredSize);
			viewPort.setViewPosition(_p);
			revalidate();
		}
	}

	/*
	 * ========================== static methods ===============================
	 */

	public static float heightToPercentage() {
		// determine zoom and lod
		int basicHeight = StringConverter.toInt(EProperties.getInstance().getProperty("BASIC_CONFIGURATION_HEIGHT"));
		int zoomedHeight = (int) Status.ZOOMED_HEIGHT.getValue();
		return (float) zoomedHeight / (float) basicHeight;
	}
} public class EEvent {

	public static final short
	// event types
			GENERATE_NEW = 0,
			CPP = 1, PULSE_10P = 2, PULSE_9P = 3, PULSE_1P = 4, PULSE_2P = 5, PULSE_2AP = 6,
			PULSE_4P = 7,
			PULSE_1AP = 8, CCG_UP = 9, CCG_DOWN = 10, RP = 11, NOP = 12, ALARM = 13;

	/**
	 * Timestamp of this event
	 */
	public long time;

	/**
	 * Type of this event
	 */
	public short type;

	/**
	 * eevent listener for this event. If this is null, this event is dispatched
	 * by the eeventManager.dispatch(eevent) method.
	 */
	public EEventListener listener;

	public EEvent(long time, short type) {
		this.time = time;
		this.type = type;
	}

	public EEvent(long time, short type, EEventListener listener) {
		this.time = time;
		this.type = type;
		this.listener = listener;
	}

	public String toString() {
		return "[EEvent; time=" + time + ", type=" + type + "]"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
	}
} public interface EEventListener {

	public void process(EEvent e);

} public class Descriptor {

	/**
	 * Enumeration of all keys that are available in a descriptor
	 * 
	 * @author till
	 * 
	 *         TODO
	 */
	public enum Key {

		/**
		 * a background image
		 */
		BACK_IMAGE,

		/**
		 * an array of background images, as used by switches
		 */
		BACK_IMAGE_ARRAY,

		/**
		 * a foreground image
		 */
		FORE_IMAGE,

		/**
		 * an array of foreground images, as used by switchAndFlag
		 */
		FORE_IMAGE_ARRAY,

		/**
		 * The color, given as 6-digit rgb hex string
		 */
		COLOR,

		/**
		 * A rectangle defining the bounds of the epanel
		 */
		RECTANGLE,

		/**
		 * An array of rectangles, as for the light bulbs in blinkenlights
		 */
		RECTANGLE_ARRAY,

		/**
		 * An array of polygons
		 */
		AREAS,

		/**
		 * The controller class, used by switch
		 */
		ACTIONATOR,

		/**
		 * the color of the cable, as defined by the connectors
		 */
		CABLE_COLOR,

		/**
		 * the cable color in pulse highlighting mode
		 */
		CABLE_COLOR_HIGHLIGHT,

		/**
		 * the cable diameter in pixels
		 */
		CABLE_PIXELS,

		/**
		 * image for an unplugged connector
		 */
		UNPLUGGED,

		/**
		 * image for a plugged connector
		 */
		PLUGGED,

		/**
		 * image for a connector with a loadbox
		 */
		LOADBOX,

		/**
		 * the vertical lines of the grid
		 */
		GRID_X,

		/**
		 * the horizontal lines of the grid
		 */
		GRID_Y,

		/**
		 * the width of a slider (distance between min and max value)
		 */
		X,
	}

	public enum Fill {
		NONE, BOTH, HORIZONTAL, VERTICAL;
	}

	private int _width;

	private int _height;

	private Fill _fill = Fill.NONE;

	private EnumMap<Key, Object> _map;

	// ============================= lifecycle
	// ==================================

	public Descriptor() {
		_map = new EnumMap<>(Key.class);
	}

	// ============================= getters and setters
	// ========================

	public void setWidth(int width) {
		_width = width;
	}

	public int getWidth() {
		return _width;
	}

	public void setHeight(int height) {
		_height = height;
	}

	public int getHeight() {
		return _height;
	}

	public void setFill(Fill fill) {
		_fill = fill;
	}

	public Fill getFill() {
		return _fill;
	}

	// =============================== methods
	// ==================================

	public Object get(Descriptor.Key key) {
		return _map.get(key);
	}

	public void put(Descriptor.Key key, Object value) {
		_map.put(key, value);
	}

	public Grid makeGrid(int width, int height) {

		// get gridx. if gridx is null, return a simple grid.
		int[] _gridX = (int[]) get(Key.GRID_X);
		if (_gridX == null) {
			return new Grid(width, height);
		}

		// otherwise get gridy, too. Create ParentGrid
		int[] _gridY = (int[]) get(Key.GRID_Y);
		ParentGrid grid = new ParentGrid(width, height);

		// compute zoom
		grid.zoomX = (float) width / (float) _width;
		grid.zoomY = (float) height / (float) _height;

		// create arrays
		grid.xValues = new int[_gridX.length];
		grid.yValues = new int[_gridY.length];

		// copy grid numbers
		for (int i = 0; i < grid.xValues.length; ++i) {
			grid.xValues[i] = _gridX[i] * width / _width;
		}
		for (int i = 0; i < grid.yValues.length; ++i) {
			grid.yValues[i] = _gridY[i] * height / _height;
		}

		// return grid.
		return grid;
	}
} public enum Status {

	/**
	 * the current lifecycle state
	 */
	LIFECYCLE,

	/**
	 * the active configuration or null
	 */
	CONFIGURATION,

	/**
	 * flag indicating, whether the overview window is shown or not
	 */
	SHOW_OVERVIEW,

	/**
	 * the default height of the configuration panel
	 */
	BASIC_CONFIGURATION_HEIGHT,

	/**
	 * the height of the configuration panel
	 */
	ZOOMED_HEIGHT,

	/**
	 * flag indicating, whether the overview window is shown or not
	 */
	SHOW_LOG,

	/**
	 * the currently loaded skin (currently only buttercream) or null
	 */
	SKIN,

	/**
	 * the currently loaded language
	 */
	LANGUAGE,

	/**
	 * flag indicating, whether the pulses shall be highlighted or not
	 */
	HIGHLIGHT_PULSE,

	/**
	 * the current simulation time
	 */
	SIMULATION_TIME;

	/*
	 * ============================= fields ==================================
	 */

	// the value for this status key
	private Object _value;

	private LinkedList<StatusListener> _listeners = new LinkedList<>();

	/*
	 * ========================= singleton stuff =======================
	 */

	public static void initValues() {
		CONFIGURATION._value = null;
		SHOW_OVERVIEW._value = StringConverter.toBoolean(EProperties.getInstance().getProperty(SHOW_OVERVIEW));
		ZOOMED_HEIGHT._value = StringConverter.toInt(EProperties.getInstance().getProperty(BASIC_CONFIGURATION_HEIGHT));
		SHOW_LOG._value = StringConverter.toBoolean(EProperties.getInstance().getProperty(SHOW_LOG));
		SKIN._value = null;
		LANGUAGE._value = null;
		HIGHLIGHT_PULSE._value = StringConverter.toBoolean(EProperties.getInstance().getProperty(HIGHLIGHT_PULSE));
		SIMULATION_TIME._value = -1L;
	}

	// =========================== getter and setter
	// ============================

	public void setValue(Object newValue) {
		Object oldValue = _value;
		if (oldValue == null) {
			if (newValue != null) {
				_value = newValue;
				informListeners();
			}
		}
		else if (!oldValue.equals(newValue)) {
			_value = newValue;
			informListeners();
		}
	}

	public boolean toggle() {
		return (boolean) (_value = !((boolean) _value));
	}

	public Object getValue() {
		return _value;
	}

	/*
	 * ========================= listener stuff =======================
	 */

	public void addListener(StatusListener listener) {
		_listeners.add(listener);
	}

	public void removeListener(StatusListener listener) {
		_listeners.remove(listener);
	}

	private void informListeners() {
		for (StatusListener listener : _listeners) {
			listener.statusChanged(this, _value);
		}
	}
} public class EFrame extends JFrame implements StatusListener {

	/*
	 * ========================== private fields ===============================
	 */

	// scrollPane as south component of the contentPane (North component is
	// the actionBar).
	private JScrollPane _scrollPane;

	// configuration panel for display the current configuration of the eniac.
	// This component is child to the scrollPane.
	// If no current configuration, this is null.
	private ConfigPanel _configPanel = null;

	private MediaTracker _mediaTracker;

	/*
	 * ============================ singleton stuff ===========================
	 */

	private static EFrame instance;

	private EFrame() {

		// set bounds.
		Dimension mySize = StringConverter.toDimension(EProperties.getInstance().getProperty("EFRAME_SIZE"));
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		mySize.width = Math.min(mySize.width, screenSize.width);
		mySize.height = Math.min(mySize.height, screenSize.height);
		setSize(mySize);
		setLocation((screenSize.width - mySize.width) / 2, (screenSize.height - mySize.height) / 3);

		// create media tracker
		_mediaTracker = new MediaTracker(this);

		// add as singleton to starter
		Status.LIFECYCLE.addListener(this);
	}

	public static EFrame getInstance() {
		if (instance == null) {
			instance = new EFrame();
		}
		return instance;
	}

	/**
	 * Initializes this dvFrame. Listener registration is done, the frame is
	 * layouted and finally brought to the screen.
	 */
	public void toScreen() {

		// set window title
		setTitle(Dictionary.MAIN_FRAME_TITLE.getText());

		// window listener
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				Manager.getInstance().stop();
				Manager.getInstance().destroy();
				dispose();
			}
		});

		// init scrollPane
		_scrollPane = new JScrollPane();
		Color c = StringConverter.toColor(EProperties.getInstance().getProperty("BACKGROUND_COLOR"));
		_scrollPane.getViewport().setBackground(c);

		// add components
		// create and init menu handler
		Hashtable<String, EAction> actionDefaults = new Hashtable<>();
		MenuHandler handler = new MenuHandler(actionDefaults);
		handler.init();

		setJMenuBar(handler.getMenuBar());
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(handler.getToolBar(), BorderLayout.NORTH);
		getContentPane().add(_scrollPane, BorderLayout.CENTER);

		// update configurationPanel
		// TODO: find new way of initializing configuration
		updateConfigPanel();

		// add this as propertyChangeListener
		Status.CONFIGURATION.addListener(this);
		Status.SKIN.addListener(this);
		Status.LANGUAGE.addListener(this);

		// init LogWindow
		// LogWindow.getInstance();
		setVisible(true);
	}

	/*
	 * =============================== methods =================================
	 */

	// update config panel. This method is called, when the current
	// configuration changed.
	private void updateConfigPanel() {

		// dispose the old panel if any
		if (_configPanel != null) {
			_scrollPane.setViewportView(null);
			_configPanel.dispose();
		}

		// get current configuration and the viewDimension
		Configuration config = (Configuration) Status.CONFIGURATION.getValue();

		// determine which configPanel to set
		if (config == null) {

			// configuration is null. Set null.
			_configPanel = null;
		}
		else {
			// create new configurationPanel
			_configPanel = (ConfigPanel) config.makePanel();
			// add configPanel to scrollPane and init
			_configPanel.init();
			_scrollPane.setViewportView(_configPanel);
		}

		// tell OVWindow to adjust its panel to the new configPanel
		OVWindow.getInstance().configPanelChanged();
	}

	public int showFileChooser(JFileChooser chooser, String approveButtonText) {
		return chooser.showDialog(this, approveButtonText);
	}

	public ConfigPanel getConfigPanel() {
		return _configPanel;
	}

	/*
	 * ===================== PropertyChangeListener methods ==================
	 */

	/**
	 * This method is called when a status property is changed
	 * 
	 * @param evt
	 *            The propertyEvent indicating that the architecture changed
	 */
	public void statusChanged(Status status, Object newValue) {

		switch (status) {

			case CONFIGURATION :
				// configuration changed. Update the configPanel.
				updateConfigPanel();
				break;
			case SKIN :
				// skin changed. repaint
				repaint();
				break;

			case LANGUAGE :
				// language changed. repaint and adjust title.
				repaint();

				// set default locale to JComponent.
				// So optionPane buttons get the right language.
				JComponent.setDefaultLocale(new Locale((String) newValue));
				setTitle(Dictionary.MAIN_FRAME_TITLE.getText());
				break;

			case LIFECYCLE :
				if (newValue == Manager.LifeCycle.STOPPED) {
					setVisible(false);
				}
				else if (newValue == Manager.LifeCycle.DESTROYED) {
					dispose();
					instance = null;
				}
				break;

			default :
				break;
		}
	}

	/*
	 * ============================ changelistener ===========================
	 */

	public void addChangeListener(ChangeListener listener) {
		_scrollPane.getViewport().addChangeListener(listener);
	}

	public void removeChangeListener(ChangeListener listener) {
		_scrollPane.getViewport().removeChangeListener(listener);
	}

	/**
	 * @param path
	 * @return
	 */
	public Image getResourceAsImage(String name) {

		// get url. If path cannot be resolved, return null.
		URL url = Manager.class.getClassLoader().getResource(name);
		if (url == null) {
			return null;
		}

		Image img = Toolkit.getDefaultToolkit().createImage(url);
		_mediaTracker.addImage(img, 1);
		try {
			_mediaTracker.waitForAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		return img;
	}

} public class OVWindow extends JDialog {

	private OVPanel _ovPanel = null;

	// =========================== singleton stuff
	// ==============================

	private static OVWindow instance;

	private OVWindow() {
		super(EFrame.getInstance(), Dictionary.OVERVIEW_WINDOW_TITLE.getText(), false);
	}

	public static OVWindow getInstance() {
		if (instance == null) {
			instance = new OVWindow();
			instance.init();
		}
		return instance;
	}

	/**
	 * initializes this overviewWindow.
	 */
	private void init() {

		// init configPanel first
		configPanelChanged();

		// register as propertyChangeListener
		Status.SHOW_OVERVIEW.addListener(new StatusListener() {

			@Override
			public void statusChanged(Status status, Object newValue) {
				// show overview toggeled
				setVisible((Boolean) newValue);
			}
		});

		Status.LANGUAGE.addListener(new StatusListener() {

			@Override
			public void statusChanged(Status status, Object newValue) {
				// language changed. update window title
				setTitle(Dictionary.OVERVIEW_WINDOW_TITLE.getText());
			}
		});

		// add WindowListener
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				Status.SHOW_OVERVIEW.setValue(false);
			}
		});

		// to the screen
		pack();
		setLocation(StringConverter.toPoint(EProperties.getInstance().getProperty("OVERVIEW_WINDOW_LOCATION")));
		setVisible((boolean) Status.SHOW_OVERVIEW.getValue());
	}

	public void dispose() {
		super.dispose();
		instance = null;
	}

	// =============================== methods
	// ==================================

	/**
	 * initializes an overviewPane according to the actual view-dimension and
	 * the actual configurationPanel
	 */
	public void configPanelChanged() {

		// dispose old panel, if an overviewPanel
		if (_ovPanel != null) {
			_ovPanel.dispose();
		}

		// determine, which overviewPanel to set
		ConfigPanel configPanel = EFrame.getInstance().getConfigPanel();
		if (configPanel != null) {
			_ovPanel = new OVPanel();
			_ovPanel.init();
			setContentPane(_ovPanel);
		}
		// layout
		pack();
	}

	public OVPanel getOVPanel() {
		return _ovPanel;
	}
}
import eniac.window.EFrame;
import eniac.window.OVWindow;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Observable;
import java.util.Observer;
import eniac.data.model.Connector;
import eniac.data.model.EData;
import eniac.data.model.parent.Configuration;
import eniac.data.type.EType;
import eniac.data.view.ConnectorPanel;
import eniac.data.view.parent.ConfigPanel;
import eniac.simulation.EEvent;
import eniac.simulation.EEventListener;
import eniac.skin.Descriptor;
import eniac.util.Status;
/*******************************************************************************
 * Copyright (c) 2003-2005, 2013 Till Zoppke.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Till Zoppke - initial API and implementation
 ******************************************************************************/
/*
 * Created on 16.03.2004
 */
package eniac.data;



/**
 * @author zoppke
 */
public class Cable extends Observable implements Observer, EEventListener {

	// connectorPanels
	private ConnectorPanel _cop1;

	private ConnectorPanel _cop2;

	private Point _dragPoint = null;

	private boolean _pulseTransmittion = false;

	// ============================= lifecycle //===============================

	public Cable(ConnectorPanel cop) {
		_cop1 = cop;
		_cop1.getData().addObserver(this);
		((Connector) _cop1.getData()).setPlugged(true);
	}

	// =============================== methods //===============================

	public boolean isComplete() {
		return _cop1 != null && _cop2 != null;
	}

	public boolean isDragging() {
		return _dragPoint != null;
	}

	public void setDragPoint(Point p) {

		// adjust dragpoint
		_dragPoint = p;

		// notify observing configPanel
		setChanged();
		notifyObservers(EData.REPAINT);
		// TODO: find finer way of repainting.
		// Like this the whole configpanel will be updated.
	}

	public void paintOnBufferedImage(Graphics g, float zoom, int lod) {

		// check, if we can paint this cable
		if (!isComplete() && !isDragging()) {
			return;
		}
		// get points and paint
		Point p1 = getBufferedPaintPoint(_cop1);
		Point p2 = getBufferedPaintPoint(_cop2);
		paint(g, zoom, lod, p1, p2);
	}

	private Point getBufferedPaintPoint(ConnectorPanel cop) {
		if (cop == null) {
			Dimension ovSize = OVWindow.getInstance().getOVPanel().getSize();
			ConfigPanel cp = EFrame.getInstance().getConfigPanel();
			int x = _dragPoint.x * ovSize.width / cp.getWidth();
			int y = _dragPoint.y * ovSize.height / cp.getHeight();
			return new Point(x, y);
		}
		return cop.getBufferedPaintPoint();
	}

	public void paintOnConfigPanel(Graphics g, float zoom, int lod) {

		// check, if we can paint this cable
		if (!isComplete() && !isDragging()) {
			return;
		}
		// get points and paint
		Point p1 = getConfigPaintPoint(_cop1);
		Point p2 = getConfigPaintPoint(_cop2);
		paint(g, zoom, lod, p1, p2);
	}

	private Point getConfigPaintPoint(ConnectorPanel cop) {
		if (cop == null) {
			return _dragPoint;
		}
		Point p = cop.getLocationOnConfigPanel();
		p.x += cop.getWidth() >> 1;
		p.y += cop.getHeight() >> 1;
		return p;
	}

	private void paintImmediately() {

		// paint on configpanel
		ConfigPanel cp = EFrame.getInstance().getConfigPanel();
		float zoom = ConfigPanel.heightToPercentage();
		paintOnConfigPanel(cp.getGraphics(), zoom, cp.getLod());

		// notify observers to cause a repaint of configpanel and ovPanel.
		setChanged();
		notifyObservers(EData.REPAINT);
	}

	private void paint(Graphics g, float zoom, int lod, Point p1, Point p2) {

		// get descriptor
		EType type = _cop1.getData().getType();
		Descriptor descriptor = type.getDescriptor(lod);

		// if descriptor is null, then return.
		if (descriptor == null) {
			return;
		}

		// set color for drawing
		if (_pulseTransmittion && (Boolean) Status.HIGHLIGHT_PULSE.getValue()) {