package adecWatt.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JScrollBar;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import misc.DimensionDouble;
import adecWatt.control.AdecWattManager;
import adecWatt.model.Acc;
import adecWatt.model.AdecWatt;
import adecWatt.model.Comp;
import adecWatt.model.ImageDB;
import adecWatt.model.Item;
import adecWatt.model.PermanentDB;
import adecWatt.model.Prop;
import adecWatt.model.Unit;
import adecWatt.model.UnitNode;
import adecWatt.model.User;
import adecWatt.model.unit.Accessory;
import adecWatt.model.unit.Building;
import adecWatt.model.unit.Lightplot;
import adecWatt.model.unit.NonWorkspace;
import adecWatt.model.unit.Workspace;

@SuppressWarnings ("serial") public class JWorkspaceView<T extends Workspace> extends JLayeredPane {

    // ========================================
    static public final int closePixelsBounds = 10;
    static public final int closePixelsAccs = 2;
    static public final int closePixelsItems = 4;
    static final double gridStep = .1;

    static public final Color noColor = new Color (255, 255, 255, 0);
    static public final Color selectionColor = new Color (0, 255, 0, 64);
    static public final Color selectionBoundColor = new Color (0, 0, 255, 96);

    protected double scale = 1, minScale = 1, maxScale = 1, initScale = 1, scaleStep = 1;
    protected JAdecWatt jAdecWatt;
    protected AdecWatt adecWatt;
    protected User user;
    protected PermanentDB permanentDB;
    protected ImageDB iconDB;
    
    protected T workspace;
    protected DimensionDouble realSize;
    protected Rectangle2D.Double backgroundShape;
    protected Dimension currentSize;
    protected JLabel jSelectionPane;
    protected Graphics2D selectionGraphics;
    protected Item lastSelectedItem;
    protected Hashtable<String, JItem> selectedItems = new Hashtable<String, JItem> ();
    protected int lastSelectedAccId;
    protected JAcc selectedAcc;

    public JAdecWatt		getJAdecWatt ()			{ return jAdecWatt; }
    public AdecWatt		getAdecWatt ()			{ return adecWatt; }
    public PermanentDB		getPermanentDB ()		{ return permanentDB; }
    public ImageDB		getIconDB ()			{ return iconDB; }
    public T			getWorkspace ()			{ return workspace; }
    public double		getScale ()			{ return scale; }
    public double		getInitScale ()			{ return initScale; }
    public DimensionDouble	getRealSize ()			{ return realSize; }
    public boolean		notEditable ()			{ return !user.isEditor (workspace); }

    public boolean		isSelected (Item item) {
	return selectedItems.containsKey (item.getId ());
    }

    public boolean isSticky (Item item) {
	if (!workspace.getInheritedEmbedded ().contains (item) || item.isReserved ())
	    return true;
	return item.isSticky ();
    }

    private JScrollPaneWorkspace getJScrollPaneWorkspace () {
	for (Container parent = getParent () ; parent != null; parent = parent.getParent ())
	    if (parent instanceof JScrollPaneWorkspace)
		return (JScrollPaneWorkspace) parent;
	return null;
    }

    // ========================================
    static public JWorkspaceView<?> getInstance (JAdecWatt jAdecWatt, Workspace workspace) {
	switch (workspace.getTypeToken ()) {
	case Furniture:
	case Information:
	case Line:
	case Accessory:
	    throw new IllegalArgumentException ("Can't find workspace view for type "+workspace.getTypeToken ()+".");
	case Building:
	    return new JBuildingView (jAdecWatt, (Building) workspace);
	case Lightplot:
	    return new JLightplotView (jAdecWatt, (Lightplot) workspace);
	}
	throw new IllegalArgumentException ("Can't find constructor for type "+workspace.getTypeToken ()+".");
    }

    protected JWorkspaceView (JAdecWatt jAdecWatt, T workspace) {
	this.jAdecWatt = jAdecWatt;
	this.workspace = workspace;
	adecWatt = jAdecWatt.getAdecWatt ();
	user = adecWatt.getUser ();
	permanentDB = adecWatt.getPermanentDB ();
	iconDB = adecWatt.getIconDB ();
	// XXX comment mettre à jour realSize
	realSize = workspace.getRealSize ();
	backgroundShape = new Rectangle2D.Double (.0, .0, realSize.width, realSize.height);
	jSelectionPane = createJLabelLayer (1001);
	initScale ();
	addMouseListener (workspaceViewMouseAdapter);
	addMouseMotionListener (workspaceViewMouseAdapter);
	addMouseWheelListener (workspaceViewMouseAdapter);
	workspace.stateNotifier.addUpdateObserver (this, AdecWatt.BroadcastStory);
	adecWatt.addMsgObserver (this, Unit.BroadcastSetSelectionItems);
	workspace.stateNotifier.
	    addMsgObserver (this,
			    Unit.BroadcastNewItem, Unit.BroadcastRemoveItem, Unit.BroadcastUpdateItem,
			    Unit.BroadcastUpdatePermItem,
			    Unit.BroadcastChangeRotSize, Unit.BroadcastChangePos, Unit.BroadcastChangeRot);
    }

    // ========================================
    private JLabel createJLabelLayer (int layer) {
	JLabel result = new JLabel ();
	result.setLocation (0, 0);
	add (result);
	setLayer (result, layer);
	return result;
    }
    private Graphics2D createGraphicsLayer (JLabel layer) {
	BufferedImage image = new BufferedImage (currentSize.width, currentSize.height, BufferedImage.TYPE_INT_ARGB);
	layer.setIcon (new ImageIcon (image));
	layer.setSize (currentSize);
	return image.createGraphics ();
    }

    // ========================================
    public ArrayList<Item> findItems (Point pos) {
	return workspace.findItems (scaleViewToModel (pos), closePixelsItems/scale);
    }
    public Hashtable<Item, ArrayList<Acc>> findAccs (Point pos, ArrayList<Item> overlap) {
	Hashtable<Item, ArrayList<Acc>> result = new Hashtable<Item, ArrayList<Acc>> ();
	if (overlap ==null)
	    return result;
	Point2D.Double realPos = scaleViewToModel (new Point (pos.x, pos.y));
	for (Item item : overlap) {
	    ArrayList<Acc> accs = item.findAccs (realPos, closePixelsAccs/scale);
	    if (accs.size () > 0)
		result.put (item, accs);
	}
	return result;
    }

    // ========================================
    public void paint (Graphics g) {
	Graphics2D g2 = (Graphics2D) g;
	AffineTransform iat = g2.getTransform ();
	Shape clip = g2.getClip ();
	g2.scale (scale, scale);
	g2.setColor (Color.WHITE);
	g2.fill (backgroundShape);
	g2.setColor (Color.BLACK);
	g2.setStroke (new BasicStroke ((float) (1/scale)));
	g2.draw (backgroundShape);
	workspace.print (g2, scale, selectedItems.keySet (), true);
	g2.setTransform (iat);
	g2.setClip (clip);
	paintChildren (g);
    }

    // ========================================
    MouseAdapter workspaceViewMouseAdapter = new MouseAdapter () {
	    public void mouseClicked (MouseEvent e) { JWorkspaceView.this.mouseClicked (e); }
	    public void mousePressed (MouseEvent e) { JWorkspaceView.this.mousePressed (e); }
	    public void mouseReleased (MouseEvent e) { JWorkspaceView.this.mouseReleased (e); }
	    public void mouseDragged (MouseEvent e) { JWorkspaceView.this.mouseDragged (e); }
	    public void mouseWheelMoved (MouseWheelEvent e) {
		jAdecWatt.setCurrentWorkspace (workspace);
		try {
		    if (0 != (e.getModifiersEx () & (MouseWheelEvent.SHIFT_MASK|MouseWheelEvent.SHIFT_DOWN_MASK)))
			scrollView (true, e.getWheelRotation ());
		    else if (0 != (e.getModifiersEx () & (MouseWheelEvent.CTRL_MASK|MouseWheelEvent.CTRL_DOWN_MASK)))
			changeScale (e.getPoint (), e.getWheelRotation ());
		    else
			scrollView (false, e.getWheelRotation ());
		} catch (Exception e2) {
		}
	    }
	};
    // ========================================
    public void mouseClicked (MouseEvent e) {
	requestFocusInWindow ();
	jAdecWatt.setCurrentWorkspace (workspace);
	if (selectedAcc != null) {
	    adecWatt.broadcastDisplay (AdecWattManager.actionDisplayAcc, selectedAcc.getAcc ());
	    return;
	}
	ArrayList<Item> overlapItems = findItems (e.getPoint ());
	if (e.getClickCount () > 1) {
	    // double click
	    if (selectedItems.size () > 1)
		for (Item item : overlapItems)
		    if (selectedItems.containsKey (item.getId ()))
			// XXX info multiple
			return;
	    if (overlapItems.size () < 1)
		return;
	    setSelectedItems (overlapItems.get ((overlapItems.indexOf (lastSelectedItem)+1)%overlapItems.size ()));
	    List<Item> param = Arrays.asList (lastSelectedItem);
	    adecWatt.broadcastDisplay (AdecWattManager.actionDisplayItem, param);
	    return;
	}
    }
    public void mousePressed (MouseEvent e) {
	jAdecWatt.setCurrentWorkspace (workspace);
	if (SwingUtilities.isRightMouseButton (e)) {
	    if (performPopup (e))
		return;
	    performSelection (e);
	    performPopup (e);
	    return;
	}
	if (e.isControlDown ()) {
	    rotation (e);
	    return;
	}
	performSelection (e);
    }
    protected boolean performPopup (MouseEvent e) {
	if (selectedAcc != null) {
	    new JAccPopup (this, selectedAcc.getItem (), selectedAcc.getAcc (), e.getPoint ());
	    return true;
	}
	if (selectedItems.size () > 0) {
	    // XXX ??? collectif ?
	    new JItemPopup (this, selectedItems.values ().iterator ().next ().getItem (), e.getPoint ());
	    return true;
	}
	return false;
    }
    private ArrayList<Item> overlapItems;
    protected void performSelection (MouseEvent e) {
	Point mPos = e.getPoint ();
	setSelectedAcc ();
	overlapItems = findItems (mPos);
	if (overlapItems.size () < 1) {
	    startsZoneSelection (e);
	    return;
	}
	firstClick = lastClick = mPos;
	Hashtable<Item, ArrayList<Acc>> overlapAccs = findAccs (mPos, overlapItems);
	if (overlapAccs.size () > 0) {
	    lastSelectedAccId = (lastSelectedAccId+1)%nbOverlapAccs (overlapAccs);
	    setSelectedAcc (overlapAccs, lastSelectedAccId);
	    return;
	}
	lastSelectedAccId = -1;
	// XXX revoir si overlapAccs deja selectionnes
	if (e.isShiftDown ()) {
	    invertItemSelection (overlapItems.get (0));
	    if (selectedItems.size () < 1) {
		firstClick = lastClick = null;
		refHandle = null;
		handleOff = null;
	    }
	    // XXX changement du choix de la poignée de référence ?
	    return;
	}
	if (selectedItems.size () > 0)
	    for (Item item : overlapItems)
		if (selectedItems.containsKey (item.getId ())) {
		    selectionRefHandle (item, mPos);
		    return;
		}
	Item item = overlapItems.get ((overlapItems.indexOf (lastSelectedItem)+1) % overlapItems.size ());
	selectionRefHandle (item, mPos);
	setSelectedItems (item);
    }
    public void selectionRefHandle (Item item, Point mPos) {
	Point2D.Double firstModelClick = new Point2D.Double (mPos.x/scale, mPos.y/scale);
	refHandle = (Point2D.Double) firstModelClick.clone ();
	item.getCloseBound (firstModelClick, Double.MAX_VALUE, refHandle);
	handleOff = new DimensionDouble (refHandle.x-firstModelClick.x,  refHandle.y-firstModelClick.y);
    }
    public void mouseReleased (MouseEvent e) {
	if (selectedAcc != null) {
	    dropAcc (e);
	    return;
	}
	if (zoneSelectionStart != null) {
	    endsZoneSelection (e);
	    return;
	}
	if (e.isControlDown () && refHandle == null) {
	    rotation (e);
	    storyRotSelectedComp ();
	    return;
	}
	//dragComps (e);
	storyMoveSelectedItem (e);
    }
    public void mouseDragged (MouseEvent e) {
	if (selectedAcc != null) {
	    dragAcc (e);
	    return;
	}
	if (zoneSelectionStart != null) {
	    extendsZoneSelection (e);
	    return;
	}
	if (e.isControlDown () && refHandle == null) {
	    rotation (e);
	    return;
	}
	dragItems (e);
    }

    // ========================================
    Point zoneSelectionStart;
    Point zoneSelectionEnd;
    public void startsZoneSelection (MouseEvent e) {
	removeAllSelectedItems ();
	zoneSelectionEnd = zoneSelectionStart = e.getPoint ();
	updateSelection ();
    }
    public void extendsZoneSelection (MouseEvent e) {
	zoneSelectionEnd = e.getPoint ();
	updateSelectionLayer ();
    }
    public void endsZoneSelection (MouseEvent e) {
	removeAllSelectedItems ();
	zoneSelectionEnd = e.getPoint ();
	Point2D.Double realStart = scaleViewToModel (zoneSelectionStart);
	Rectangle2D.Double selectedZone = new Rectangle2D.Double (realStart.x, realStart.y, 0, 0);
	selectedZone.add (scaleViewToModel (zoneSelectionEnd));
	for (Item item : workspace.getAllItems ()) {
	    if (!isSticky (item) && item.inside (selectedZone))
		addSelectedItem (item);
	}
	zoneSelectionEnd = zoneSelectionStart = null;
	updateSelection ();
    }
    // ========================================
    Line2D.Double selectedBound;
    protected void setSelectedBound (Line2D.Double selectedBound) {
	this.selectedBound = selectedBound;
    }

    protected void removeAllSelectedItems () {
	for (JItem jItem : selectedItems.values ())
	    remove (jItem);
	selectedItems.clear ();
	selectedBound = null;
	selectedJItemHandlers.clear ();
    }
    protected boolean removeSelectedItem (Item item) {
	selectedBound = null;
	JItem jItem = selectedItems.remove (item.getId ());
	if (jItem == null)
	    return false;
	remove (jItem);
	return true;
    }
    protected void addSelectedItem (Item item) {
	String itemId = item.getId ();
	if (selectedItems.containsKey (itemId))
	    return;
	JItem jItem = JItem.getNewJItem (this, item);
	selectedItems.put (itemId, jItem);
	add (jItem);
	setLayer (jItem, levelModelToView (item.getLevel ()));
    }

    public void setSelectedItemsBySearch (String text) {
	removeAllSelectedItems ();
	if (text != null && !text.isEmpty ())
	    for (Item item : workspace.getAllItems ())
		try {
		    if (item.match (text))
			addSelectedItem (item);
		} catch (Exception e) {
		    // XXX un pb étrange !!!
		    // e.printStackTrace ();
		    // System.err.println ("coucou:"+e+" "+item+" "+item.getModel ()+" "+item.getPos ());
		}
	updateSelection ();
    }
    public void selectAllItems () {
	removeAllSelectedItems ();
	for (Item item : workspace.getAllItems ())
	    if (!isSticky (item))
		addSelectedItem (item);
	updateSelection ();
    }
    public void setSelectedItems () {
	removeAllSelectedItems ();
	updateSelection ();
    }
    public void setSelectedItems (List<Item> items) {
	removeAllSelectedItems ();
	if (items == null)
	    return;
	for (Item item : items) {
	    if (!isSticky (item))
		addSelectedItem (item);
	}
	updateSelection ();
    }
    public void setSelectedItems (Item item) {
	removeAllSelectedItems ();
	if (item != null)
	    addSelectedItem (item);
	lastSelectedItem = item;
	updateSelection ();
    }
    public void invertItemSelection (Item item) {
	JItem jItem = selectedItems.remove (item.getId ());
	if (jItem == null)
	    addSelectedItem (item);
	else
	    remove (jItem);
	updateSelection ();
    }
    public void updateSelection () {
	jAdecWatt.stateNotifier.broadcastUpdate (JAdecWatt.BroadcastSelection);
	updateSelectionLayer ();
    }
    public int getNbSelectedItems () {
	int result = 0;
	for (String itemId : selectedItems.keySet ())
	    if (workspace.containsItem (itemId))
		result++;
	return result;
    }
    public Vector<Item> getSelectedItems () {
	int nbSelected = selectedItems.size ();
	Vector<Item> items = new Vector<Item> (nbSelected);
	if (nbSelected < 1)
	    return items;
	for (String itemId : selectedItems.keySet ()) {
	    if (workspace.containsItem (itemId))
		items.add (selectedItems.get (itemId).getItem ());
	}
	return items;
    }

    protected int nbOverlapAccs (Hashtable<Item, ArrayList<Acc>> overlapAccs) {
	int result = 0;
	for (ArrayList<Acc> accs : overlapAccs.values ())
	    result += accs.size ();
	return result;
    }
    protected void setSelectedAcc () {
	if (selectedAcc == null)
	    return;
	remove (selectedAcc);
	selectedAcc = null;
	updateSelection ();
    }
    protected void setSelectedAcc (Hashtable<Item, ArrayList<Acc>> overlapAccs, int idx) {
	for (Item item : overlapAccs.keySet ()) {
	    ArrayList<Acc> accs = overlapAccs.get (item);
	    int size = accs.size ();
	    if (idx >= size) {
		idx -= size;
		continue;
	    }
	    selectedAcc = new JAcc (this, item, accs.get (idx));
	    add (selectedAcc);
	    setLayer (selectedAcc, levelModelToView (item.getLevel ()));
	}
	updateSelection ();
    }

    // ========================================
    protected Hashtable<JItem, JItemHandler> selectedJItemHandlers = new Hashtable<JItem, JItemHandler> ();
    protected ArrayList<JLabel> newJLabel = new ArrayList<JLabel> ();
    public void recordJLabel (JLabel jLabel) {
	newJLabel.add (jLabel);
    }
    public void updateSelectionLayer () {
	AffineTransform af = selectionGraphics.getTransform ();
	jSelectionPane.invalidate ();
	newJLabel.clear ();
	selectionGraphics.setBackground (noColor);
	selectionGraphics.clearRect (0, 0, currentSize.width, currentSize.height);
	selectionGraphics.setColor (selectionColor);
	if (zoneSelectionStart != null) {
	    int x = Math.min (zoneSelectionStart.x, zoneSelectionEnd.x);
	    int y = Math.min (zoneSelectionStart.y, zoneSelectionEnd.y);
	    int width = Math.abs (zoneSelectionEnd.x - zoneSelectionStart.x);
	    int height = Math.abs (zoneSelectionEnd.y - zoneSelectionStart.y);
	    selectionGraphics.fillRect (x, y, width, height);
	}
	if (selectedAcc != null) {
	    // XXX selectionGraphics à l'échelle ?
	    Item item = selectedAcc.getItem ();
	    Acc acc = selectedAcc.getAcc ();
	    String accId = acc.getId ();
	    DimensionDouble accSize = item.getAccSize (accId);
	    double halfWidth = accSize.width*scale/2, halfHeight = accSize.height*scale/2;
	    AffineTransform at = AffineTransform.getRotateInstance (Math.toRadians (item.getThetaDegree ()));
	    double [] bounds = new double [] {-halfWidth, -halfHeight, -halfWidth, halfHeight, halfWidth, halfHeight, halfWidth, -halfHeight};
	    at.transform (bounds, 0, bounds, 0, 4);
	    Point accPos = selectedAcc.getLocation ();
	    int [] x = new int[4];
	    int [] y = new int[4];
	    for (int i = 0; i < 4; i++) {
		x[i] = (int) bounds [2*i]+accPos.x+8;
		y[i] = (int) bounds [2*i+1]+accPos.y+8;
	    }
	    selectionGraphics.fillPolygon (x, y, 4);
	}
	// XXX selectionGraphics à l'échelle ?
	for (String itemId : selectedItems.keySet ()) {
	    if (!workspace.containsItem (itemId))
		continue;
	    try {
		JItem jItem = selectedItems.get (itemId);
		Item item = jItem.getItem ();
		double [] bounds = jItem.getCurrentBounds ();
		int [] x = new int[4];
		int [] y = new int[4];
		for (int i = 0, j = 1, k = 0; k < 4; i += 2, j += 2, k++) {
		    x [k] = (int) (bounds [i]*scale);
		    y [k] = (int) (bounds [j]*scale);
		}
		selectionGraphics.fillPolygon (x, y, 4);
		if (item.getPropNoMouse (Prop.PropSize))
		    continue;

		JItemHandler handler = selectedJItemHandlers.get (jItem);
		if (handler == null)
		    selectedJItemHandlers.put (jItem, handler = new JItemHandler (this, jSelectionPane, jItem, !(jItem instanceof JComp)));
		handler.setCurrentBounds (bounds);
	    } catch (Exception e) {
		e.printStackTrace ();
		// XXX
	    }
	}
	for (Component component : jSelectionPane.getComponents ())
	    if (!newJLabel.contains (component))
		jSelectionPane.remove (component);
	for (JLabel label : newJLabel)
	    if (!jSelectionPane.isAncestorOf (label))
		jSelectionPane.add (label);
	selectionGraphics.scale (scale, scale);
	if (selectedBound != null) {
	    selectionGraphics.setPaint (selectionBoundColor);
	    selectionGraphics.setStroke (new BasicStroke (2*closePixelsBounds/(float)scale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f));
	    if (selectedBound.x1 == selectedBound.x2 && selectedBound.y1 == selectedBound.y2) {
		double r = closePixelsItems/scale, dr = r+r;
		java.awt.Shape disc = new java.awt.geom.Ellipse2D.Double (selectedBound.x1-r, selectedBound.y1-r, dr, dr);
		selectionGraphics.draw (disc);
	    } else
		selectionGraphics.draw (selectedBound);
	}
	selectionGraphics.setTransform (af);
	jSelectionPane.repaint ();
	jSelectionPane.validate ();
    }

    // ========================================
    public void scrollView (boolean horizontal, int delta) {
	JScrollPaneWorkspace jScrollPaneWorkspace = getJScrollPaneWorkspace ();
	JScrollBar jScrollBar = horizontal ? jScrollPaneWorkspace.getHorizontalScrollBar () : jScrollPaneWorkspace.getVerticalScrollBar ();
	int min = jScrollBar.getMinimum ();
	int max = jScrollBar.getMaximum ();
	int step = (max-min)/10;
	jScrollBar.setValue (Math.min (max, Math.max (min, jScrollBar.getValue ()+delta*step)));
	jScrollPaneWorkspace.updateScale ();
    }
    public void changeScale (Point mousePos, int delta) {
	JViewport jViewport = getJScrollPaneWorkspace ().getViewport ();
	Point viewPosA = jViewport.getViewPosition ();
	Point2D.Double realPos = scaleViewToModel (mousePos);
	setScale (delta < 0 ? scale+scaleStep : scale-scaleStep);
	Point viewPosB = scaleModelToView (realPos);
	viewPosB.translate (-mousePos.x+viewPosA.x, -mousePos.y+viewPosA.y);
	if (viewPosB.x < 0)
	    viewPosB.x = 0;
	if (viewPosB.y < 0)
	    viewPosB.y = 0;
	jViewport.setViewPosition (viewPosB);
    }
    public void initScale () {
	initScale = Math.rint (Math.min (jAdecWatt.screenSize.width/realSize.getWidth (), jAdecWatt.screenSize.height/realSize.getHeight ()));
	minScale = initScale/10;
	maxScale = initScale*10;
	scaleStep = minScale/2;
	setScale (initScale);
    }
    public void setScale (double scale) {
	scale = Math.rint (Math.max (minScale, Math.min (maxScale, scale)));
	this.scale = scale;
	currentSize = scaleModelToView (realSize);
	setPreferredSize (currentSize);
	selectionGraphics = createGraphicsLayer (jSelectionPane);
	for (JItem jItem : selectedItems.values ())
	    jItem.setScale ();
	// XXX selectedAcc.setScale ();
	updateSelectionLayer ();
	repaint ();
	jAdecWatt.stateNotifier.broadcastUpdate (JAdecWatt.BroadcastChangeScale);
	JScrollPaneWorkspace jScrollPaneWorkspace = getJScrollPaneWorkspace ();
	if (jScrollPaneWorkspace == null)
	    return;
	jScrollPaneWorkspace.updateScale ();
    }

    // ========================================
    public int levelModelToView (double level) {
	return (int) (level*10);
    }
    public Point2D.Double scaleViewToModel (Point pos) {
	return new Point2D.Double (pos.x/scale, pos.y/scale);
    }
    public DimensionDouble scaleViewToModel (Dimension size) {
	return new DimensionDouble (size.width/scale, size.height/scale);
    }
    public Point scaleModelToView (Point2D pos) {
	return new Point ((int)(pos.getX ()*scale), (int)(pos.getY ()*scale));
    }
    public Dimension scaleModelToView (DimensionDouble size) {
	return new Dimension ((int)(size.getWidth ()*scale), (int)(size.getHeight ()*scale));
    }

    // ========================================
    public void rotation (MouseEvent e) {
	if (notEditable ())
	    return;
	for (String itemId : selectedItems.keySet ()) {
	    if (!workspace.containsItem (itemId))
		continue;
	    try {
		// XXX et segm ?
		JComp jComp = (JComp) selectedItems.get (itemId);
		if (jComp.comp.getPropNoMouse (Prop.PropRot))
		    continue;
		jComp.rotation (SwingUtilities.convertMouseEvent (this, e, jComp));
	    } catch (Exception e2) {
		// XXX c'est un segm
	    }
	}
	updateSelectionLayer ();
    }
    Point firstClick, lastClick;
    Point2D.Double refHandle;
    DimensionDouble handleOff;
    public void dragItems (MouseEvent e) {
	if (notEditable () || firstClick == null)
	    return;
	Point newPos = e.getPoint ();

	Point2D.Double newModelPos = new Point2D.Double (newPos.x/scale+handleOff.width, newPos.y/scale+handleOff.height);
	setSelectedBound (e.isControlDown () ? null : workspace.getMagnetPoint (newModelPos, onGrid (newModelPos), closePixelsBounds/scale));
	
	lastClick = newPos;
	DimensionDouble delta = new DimensionDouble (newModelPos.x-refHandle.x, newModelPos.y-refHandle.y);
	refHandle = newModelPos;
	for (String itemId : selectedItems.keySet ()) {
	    if (!workspace.containsItem (itemId))
		continue;
	    JItem jItem = selectedItems.get (itemId);
	    if (jItem.getItem ().getPropNoMouse (Prop.PropPos))
		continue;
	    jItem.dragItem (delta);
	}
	updateSelectionLayer ();
    }
    public void dragAcc (MouseEvent e) {
	if (notEditable () || lastClick == null)
	    return;
	Point newPos = e.getPoint ();
	Dimension delta = new Dimension (newPos.x-lastClick.x, newPos.y-lastClick.y);
	lastClick = newPos;
	selectedAcc.dragAcc (delta);
    }

    // ========================================
    public void dropAcc (MouseEvent e) {
	if (notEditable ())
	    return;
	try {
	    Point2D.Double realPos = scaleViewToModel (e.getPoint ());
	    ArrayList<Item> dstItems = workspace.findItems (realPos, closePixelsItems/scale);
	    PermanentDB permanentDB = adecWatt.getPermanentDB ();
	    if (selectedAcc.getAcc ().getDirectUnit ().isDescendingFrom (permanentDB.getPowerPlugId ())) {
		

		if (dstItems.size () < 1) {
		    eraseAcc (selectedAcc.getAcc ());
		    return;
		}
		// rechercher un "avec prise" le plus proche
		// XXX Il peut y avoir plusieurs items
		ArrayList<Acc> dstAccs = dstItems.get (0).findAccs (realPos, closePixelsAccs/scale);
		// XXX Il peut y avoir plusieurs accs
		// XXX prendre le premier acc compatible et libre
		Acc dstAcc = dstAccs.size () < 1 ? null : dstAccs.get (0);
		try {
		    // XXX et segm
		    storyConnect ((Comp) selectedAcc.getItem (), selectedAcc.getAcc (), (Comp) dstItems.get (0), dstAcc, permanentDB.getPowerSocketId ());
		} catch (Exception e2) {
		    // c'est un segm
		}
	    }
	    if (dstItems.size () < 1) {
		// si pas prise mais "avec prise" => prendre la première libre
		eraseAcc (selectedAcc.getAcc ());
		return;
	    }
	    // XXX sinon traiter la gélatine
	} catch (Exception e2) {
	    e2.printStackTrace ();
	} finally {
	    setSelectedAcc ();
	}
    }
    public void dropAcc (Accessory accessory, Point pos) {
	if (notEditable () || accessory == null)
	    return;
	ArrayList<Item> overlapItems = findItems (pos);
	if (overlapItems.size () < 1)
	    return;
	//|| isSticky (jItem.item))
	// XXX 
	workspace.storyAddAcc (overlapItems.get (0), accessory);
    }
    static public Point2D.Double onGrid (Point2D.Double pos) {
	return new Point2D.Double (gridStep*Math.rint (pos.x/gridStep), gridStep*Math.rint (pos.y/gridStep));
    }
    public void dropSegm (NonWorkspace model, Point mPos) {
	workspace.storyAddSegm (model, onGrid (scaleViewToModel (mPos)));
    }
    public void dropComp (NonWorkspace model, Point mPos) {
	workspace.storyAddComp (model, onGrid (scaleViewToModel (mPos)));
    }
    public void eraseAcc (Acc acc) {
	workspace.storyRemoveAcc (acc);
    }
    public void cloneItem (Item item, Point pos) {
	workspace.storyCloneItem (item, scaleViewToModel (pos));
    }
    public void hideShowItem (List<Item> items, boolean hidden) {
	workspace.storyHideShowItem (items, hidden);
    }
    public void eraseItem (List<Item> items) {
	workspace.storyRemoveItems (items);
    }
    public void storyRotSelectedComp () {
	// XXX et segm
	if (notEditable ())
	    return;
	int nbSelected = selectedItems.size ();
	if (nbSelected < 1)
	    return;
	Vector<Item> items = new Vector<Item> (nbSelected);
	Vector<Double> thetas = new Vector<Double> (nbSelected);
	for (String itemId : selectedItems.keySet ()) {
	    if (!workspace.containsItem (itemId))
		continue;
	    Item item = selectedItems.get (itemId).getItem ();
	    if (item.getPropNoMouse (Prop.PropRot))
		continue;
	    items.add (item);
	    thetas.add (selectedItems.get (itemId).getCurrentThetaDegree ());
	}
	workspace.storyRotItem (items, thetas);
    }
    public void storyMoveSelectedItem (MouseEvent e) {
	if (notEditable ())
	    return;
	try {
	    if (firstClick == null || firstClick.equals (e.getPoint ()))
		return;
	    int nbSelected = selectedItems.size ();
	    if (nbSelected < 1)
		return;
	    Vector<Item> items = new Vector<Item> (nbSelected);
	    Vector<Point.Double> poss = new Vector<Point.Double> (nbSelected);
	    for (String itemId : selectedItems.keySet ()) {
		if (!workspace.containsItem (itemId))
		    continue;
		Item item = selectedItems.get (itemId).getItem ();
		if (item.getPropNoMouse (Prop.PropPos))
		    continue;
		items.add (item);
		poss.add (selectedItems.get (itemId).getCurrentPos ());
	    }
	    workspace.storyMoveItem (items, poss);
	} catch (Exception e2) {
	    e2.printStackTrace ();
	} finally {
	    firstClick = lastClick = null;
	    refHandle = null;
	    handleOff = null;
	}
    }
    public void storyRotResizeItem (JItem jItem) {
	if (notEditable ())
	    return;
	workspace.storyRotResize (jItem.getItem (), jItem.getCurrentPos (),
				  jItem.getCurrentThetaDegree (), jItem.getCurrentSize ());
    }

    public void storyConnect (Comp srcComp, Acc srcAcc, Comp dstComp, Acc dstAcc, String dstModelId) {
	if (notEditable ())
	    return;
	if (dstAcc == null)
	    dstAcc = dstComp.findEmbeddedBaseOn (dstModelId);
	// XXX accessory powerSocket libre !
	if (dstAcc == null || !dstAcc.getDirectUnit ().isDescendingFrom (dstModelId))
	    return;
	// XXX recherche d'autres objets ?
	workspace.storyConnect (srcComp, srcAcc, dstComp.getId (), dstAcc.getId ());
    }

    // ========================================
    public void updateStory () {
	updateSelection ();
    }

    public void displaySetSelectionItems (Object... objects) {
	setSelectedItems ((List<Item>) objects[1]);
    }

    public void displayNewItem (Object... objects) {
	updateSelection ();
	repaint ();
    }

    public void displayRemoveItem (Object... objects) {
	updateSelection ();
	//setSelectedItems ();
	repaint ();
    }

    public void displayUpdateItem (Object... objects) {
	try {
	    Item item = (Item) objects[0];
	    selectedItems.get (item.getId ()).setItem (item);
	} catch (Exception e) {
	}
	repaint ();
    }
    public void displayUpdatePermItem (Object... objects) {
	NonWorkspace permItem = (NonWorkspace) objects[0];
	UnitNode<?> ancestor = (UnitNode<?>) permItem.getUnitNode ();
	boolean repaint = false;
	for (Item item : workspace.getAllItems ())
	    if (item.getModel ().getUnitNode ().isNodeAncestor (ancestor)) {
		repaint = true;
		break;
	    }
	if (! repaint)
	    return;
	repaint ();
    }
    public void displayChangePos (Object... objects) {
	for (Object obj : objects) {
	    JItem jItem = updateJItem (obj);
	    if (jItem == null)
		continue;
	    jItem.setCurrentPos ();
	}
    }

    public void displayChangeRotSize (Object... objects) {
	for (Object obj : objects) {
	    JItem jItem = updateJItem (obj);
	    if (jItem == null)
		continue;
	    jItem.setCurrentSize ();
	}
    }

    public void displayChangeRot (Object... objects) {
	for (Object obj : objects) {
	    // XXX et segm
	    JItem jItem = updateJItem (obj);
	    if (jItem == null)
		return;
	    try {
		jItem.setCurrentThetaDegree ();
	    } catch (Exception e) {
		// XXX c'est un segm
	    }
	}
    }
    private JItem updateJItem (Object obj) {
	Item item = (Item) obj;
	JItem jItem = selectedItems.get (item.getId ());
	if (jItem == null)
	    return null;
	jItem.setItem (item);
	return jItem;
    }

    // ========================================
}
