@Override
						public void ancestorMoved(AncestorEvent event) {
						}
					});
				} catch (Exception e) {
					log.debug("", e);
				}
			} else if (name.equals("Border")) {
				int zone = Integer.parseInt(elt.getAttribute("zone"));
				AutoHideButtonPanel borderPanel = borderPanes[zone];
				borderPanel.setVisible(true); // border may not be visible
				NodeList children = elt.getElementsByTagName("Dockable");
				for (int i = 0, len = children.getLength(); i < len; i++) {
					xmlBuildAutoHideNode(borderPanel,
							(Element) children.item(i));
				}
				borderPanel.revalidate();
			} else if (name.equals("Floating")) {
				int x = Integer.parseInt(elt.getAttribute("x"));
				int y = Integer.parseInt(elt.getAttribute("y"));
				int width = Integer.parseInt(elt.getAttribute("width"));
				int height = Integer.parseInt(elt.getAttribute("height"));
				Rectangle r = new Rectangle(x, y, width, height);
				GraphicsEnvironment ge = GraphicsEnvironment
						.getLocalGraphicsEnvironment();
				GraphicsDevice[] gs = ge.getScreenDevices();
				for (int j = 0; j < gs.length; j++) {
					GraphicsDevice gd = gs[j];
					GraphicsConfiguration[] gc = gd.getConfigurations();
					for (int i = 0; i < gc.length; i++) {

						if (gc[i].getBounds().contains(r)) {
							// log.error(r + " is on screen[" + j + "] "
							// + gc[i].getBounds());
						} else {
							// log.error(r + " is NOT on screen[" + j + "] "
							// + gc[i].getBounds());
						}
					}
				}

				NodeList children = elt.getElementsByTagName("Dockable");
				xmlBuildFloatingNode(children, new Rectangle(x, y, width,
						height)); // 2005/10/10

				/*
				 * for (int i = 0, len = children.getLength(); i < len; i++) {
				 * xmlBuildFloatingNode((Element)children.item(i), new
				 * Rectangle(x, y, width, height)); }
				 */
			} else if (name.equals("TabGroups")) {
				NodeList children = elt.getElementsByTagName("TabGroup");
				xmlBuildTabGroup(children); // 2005/10/10
			} else {
				throw new SAXNotRecognizedException(name);
			}
		}
	}

	private void xmlBuildAutoHideNode(AutoHideButtonPanel borderPanel,
			Element dockableElt) throws SAXException {

		Element hideElt = (Element) dockableElt.getElementsByTagName(
				"RelativePosition").item(0);
		float x = Float.parseFloat(hideElt.getAttribute("x"));
		float y = Float.parseFloat(hideElt.getAttribute("y"));
		float w = Float.parseFloat(hideElt.getAttribute("w"));
		float h = Float.parseFloat(hideElt.getAttribute("h"));
		RelativeDockablePosition position = new RelativeDockablePosition(x, y,
				w, h);

		AutoHideButton btn = new AutoHideButton();
		Dockable dockable = xmlGetDockable(dockableElt);
		autoHideButtons.put(dockable.getDockKey(), btn);

		context.setDockableState(dockable, new DockableState(this, dockable,
				DockableState.Location.HIDDEN, position));
		dockable.getDockKey().setLocation(DockableState.Location.HIDDEN);
		btn.init(dockable, borderPanel.getBorderZone());
		borderPanel.add(btn);

		if (dockable instanceof CompoundDockable) {
			// check for children and build them
			xmlBuildCompoundDockable((CompoundDockable) dockable, dockableElt,
					DockableState.Location.HIDDEN);
		}

	}

	/*
	 * private void xmlBuildFloatingNode(Element dockableElt, Rectangle bounds)
	 * {
	 */
	private void xmlBuildFloatingNode(NodeList dockables, Rectangle bounds)
			throws SAXException {

		// @todo not optimal.. we should refactor it oustide this method
		Window desktopWindow = SwingUtilities.getWindowAncestor(this);
		if (desktopWindow != null) {
			Point windowLocation = desktopWindow.getLocation();
			bounds.x += windowLocation.x; // position is relative
			bounds.y += windowLocation.y;
		}

		// JDialog dialog = null;
		FloatingDockableContainer fdc = null;
		TabbedDockableContainer tdc = null;
		if (dockables.getLength() > 1) { // it's a floating tab
			tdc = DockableContainerFactory.getFactory()
					.createTabbedDockableContainer();
			tdc.installDocking(this);
			((JTabbedPane) tdc).addChangeListener(focusHandler); // our best way
																	// to track
																	// selection
																	// (focus)
																	// changes
		}

		Dockable baseDockable = null; // used when there are tabs

		for (int i = 0; i < dockables.getLength(); i++) {
			Element dockableElt = (Element) dockables.item(i);
			Element hideElt = (Element) dockableElt.getElementsByTagName(
					"RelativePosition").item(0);
			float x = Float.parseFloat(hideElt.getAttribute("x"));
			float y = Float.parseFloat(hideElt.getAttribute("y"));
			float w = Float.parseFloat(hideElt.getAttribute("w"));
			float h = Float.parseFloat(hideElt.getAttribute("h"));
			RelativeDockablePosition position = new RelativeDockablePosition(
					dockingPanel, x, y, w, h);

			Dockable dockable = xmlGetDockable(dockableElt);
			if (i == 0) {
				baseDockable = dockable;
			}

			Element previousState = (Element) dockableElt.getElementsByTagName(
					"PreviousState").item(0);
			int istate = Integer.parseInt(previousState.getAttribute("state"));
			DockableState state = new DockableState(this, dockable,
					DockableState.getLocationFromInt(istate), position);
			storePreviousFloatingState(dockable, state);

			if (fdc == null) {
				// if (dialog == null}){
				// dialog = (JDialog) createFloatingDockableContainer(dockable);
				fdc = createFloatingDockableContainer(dockable);
			} else {
				// add as a tab
				if (tdc.getTabCount() == 0) {
					// first tab : replace the current DetachedDockView by the
					// tab container
					// not very efficient... @todo : sort this out
					DockableContainer base = DockingUtilities
							.findDockableContainer(baseDockable);
					DockingUtilities.replaceChild(
							((Component) base).getParent(), (Component) base,
							(Component) tdc);
					tdc.addDockable(baseDockable, 0);
				}
				tdc.addDockable(dockable, tdc.getTabCount());
			}

			context.setDockableState(dockable, new DockableState(this,
					dockable, DockableState.Location.FLOATING, position));
			dockable.getDockKey().setLocation(DockableState.Location.FLOATING);

			if (dockable instanceof CompoundDockable) {
				// check for children and build them
				xmlBuildCompoundDockable((CompoundDockable) dockable,
						dockableElt, DockableState.Location.FLOATING);
			}
		}

		DockingUtilities.setBounds(fdc, bounds);
		// dialog.setBounds(bounds);
		// dialog.validate();
		// dialog.setVisible(true);
		DockingUtilities.validate(fdc);
		DockingUtilities.setVisible(fdc, true);

	}

	private void xmlBuildDockingPanelNode(Node node) throws SAXException {
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			Component comp = xmlCreateComponent((Element) node,
					DockableState.Location.DOCKED);
			if (comp != null) {
				dockingPanel.add(comp, BorderLayout.CENTER);
			}
		}
	}

	private Component xmlCreateComponent(Element elt,
			DockableState.Location dockableLocation) throws SAXException {
		if (elt.getNodeName().equals("Split")) {
			SplitContainer split = xmlBuildSplitContainer(elt, dockableLocation);
			return split;
		} else if (elt.getNodeName().equals("Dockable")) {
			Dockable d = xmlGetDockable(elt);
			SingleDockableContainer sdc = DockableContainerFactory
					.getFactory()
					.createDockableContainer(
							d,
							DockableContainerFactory.ParentType.PARENT_SPLIT_CONTAINER);
			context.setDockableState(d, new DockableState(this, d,
					dockableLocation));
			d.getDockKey().setLocation(dockableLocation);
			sdc.installDocking(this);

			if (d instanceof CompoundDockable) {
				// check for children and build them
				xmlBuildCompoundDockable((CompoundDockable) d, elt,
						dockableLocation);
			}
			return (Component) sdc;
		} else if (elt.getNodeName().equals("TabbedDockable")) {

			TabbedDockableContainer tdc = xmlBuildTabbedDockableContainer(elt,
					dockableLocation);
			return (Component) tdc;
		} else if (elt.getNodeName().equals("MaximizedDockable")) {

			// this should be the last element from DockingPanel node
			Dockable d = xmlGetDockable(elt);
			maximize(d);
			return null;
		} else {
			throw new SAXNotRecognizedException(elt.getNodeName());
		}
	}

	private void xmlBuildCompoundDockable(CompoundDockable dockable,
			Element compoundElt, DockableState.Location dockableLocation)
			throws SAXException {
		/* a compound dockable can hold a sub dockable (or split/tabs) */
		CompoundDockingPanel compoundPanel = (CompoundDockingPanel) dockable
				.getComponent();
		compoundPanel.removeAll(); // cleanup the compound at every workspace
									// reloading 2007/01/08
		NodeList children = compoundElt.getChildNodes();
		for (int i = 0, len = children.getLength(); i < len; i++) {
			Node node = children.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				Element elt = (Element) node;
				if (elt.getNodeName().equals("Key")) {
					// ignore : it's the key of the compound dockable

				} else {
					Component comp = xmlCreateComponent(elt, dockableLocation);
					compoundPanel.add(comp, BorderLayout.CENTER);
					return; // no more than a single child
				}
			}
		}

	}

	private SplitContainer xmlBuildSplitContainer(Node node,
			DockableState.Location dockableLocation) throws SAXException {
		Element elt = (Element) node;
		int orientation = Integer.parseInt(elt.getAttribute("orientation"));
		String loc = elt.getAttribute("location");
		double location = 0.5;
		if (loc != null && !loc.equals("")) {
			location = Double.parseDouble(loc);
		}

		SplitContainer split = new SplitContainer(orientation);
		split.setDividerLocation(location);

		boolean left = true;
		for (int i = 0; i < node.getChildNodes().getLength(); i++) {
			Node child = node.getChildNodes().item(i);
			if (child.getNodeType() == Node.ELEMENT_NODE) {
				Component comp = xmlCreateComponent((Element) child,
						dockableLocation);
				if (left) {
					split.setLeftComponent(comp);
					left = false;
				} else {
					split.setRightComponent(comp);
				}
			}
		}
		return split;
	}

	private TabbedDockableContainer xmlBuildTabbedDockableContainer(
			Element elt, DockableState.Location dockableLocation)
			throws SAXException {

		TabbedDockableContainer tdc = DockableContainerFactory.getFactory()
				.createTabbedDockableContainer();
		tdc.installDocking(this);
		((JTabbedPane) tdc).addChangeListener(focusHandler); // our best way to
																// track
																// selection
																// (focus)
																// changes
		Dockable base = null;
		for (int i = 0; i < elt.getChildNodes().getLength(); i++) {
			Node child = elt.getChildNodes().item(i);
			if (child.getNodeType() == Node.ELEMENT_NODE) {
				Dockable d = xmlGetDockable((Element) child);
				if (base == null) {
					base = d;
				}
				context.setDockableState(d, new DockableState(this, d,
						dockableLocation));
				d.getDockKey().setLocation(dockableLocation);
				tdc.addDockable(d, tdc.getTabCount());

				// update the tab group
				if (base != d) {
					addToTabbedGroup(base, d);
				}

				if (d instanceof CompoundDockable) {
					// check for children and build them is needed
					xmlBuildCompoundDockable((CompoundDockable) d,
							(Element) child, dockableLocation);
				}
			}
		}
		return tdc;
	}

	private Dockable xmlGetDockable(Element dockableElt) {
		Element key = (Element) dockableElt.getElementsByTagName("Key").item(0);
		String name = key.getAttribute("dockName");
		Dockable dockable = context.getDockableByKey(name);
		if (dockable == null) {
			throw new NullPointerException("Dockable " + name
					+ " not registered");
		}

		return dockable;
	}

	/** rebuild the tabbedGroups structure (memory of tabbed dockables) */
	private void xmlBuildTabGroup(NodeList group) {
		// this pass is useful for hidden dockable that need to be reassociated
		// with their tab group.
		// (tabbed dockable don't need it as this information is implicit
		// (during xml tab creation)
		for (int i = 0; i < group.getLength(); i++) {
			Element groupElt = (Element) group.item(i);
			NodeList dockables = groupElt.getElementsByTagName("Dockable");
			Dockable base = null;
			for (int j = 0; j < dockables.getLength(); j++) {
				Element dockableElt = (Element) dockables.item(j);
				Dockable d = xmlGetDockable(dockableElt);
				if (j == 0) {
					base = d;
				} else {
					addToTabbedGroup(base, d);
				}
			}
		}
	}

	/**
	 * Adds a dockable in the docking desktop.
	 * <p>
	 * This method can be used mainly in two situations :
	 * <ul>
	 * <li>the desktop is empty (when not empty, you can use split() or
	 * createTab() to add a dockable relatively to another)
	 * <li>the dockable is currently closed, and it must be shown again
	 * <li>note that if the desktop is not empty, the dockable will be added in
	 * the bottom of the desktop (equivalent to addDockable(dockable
	 * 0,0.8,1,0.2)).
	 * </ul>
	 */
	public void addDockable(Dockable dockable) {
		/*
		 * addDockable isn't called internally by the API : just for user
		 * applications
		 */
		addDockable(dockable, new RelativeDockablePosition(0, 0.8, 0.5, 0.2));
	}

	/**
	 * Adds a dockable in the docking desktop, and tries to respect the relative
	 * positionning provided.
	 * <p>
	 * This method is used to reposition a closed dockable at its previous
	 * location on the desktop. As it relies on the
	 * Component.findComponentAt(Point) method , the desktop must already be
	 * visible.
	 * 
	 * <p>
	 * The preferred way to obtain a particular visual docking configuration is
	 * to use a combination of add(), split() and createTab() methods, as these
	 * methods do not rely on an interpretation (and approximation) of
	 * constraints.
	 * 
	 * <p>
	 * Precision of constraints : as the docking management is based on a mix of
	 * horizontal and vertical splitting zones, it is not always possible to
	 * respect the constraints given.
	 * <p>
	 * The current implementation will do the following :
	 * <ul>
	 * <li>find the splitter containing the given center (x + width/2, y +
	 * height/2) of the dockable
	 * <li>try to respect the x and y, constraints.
	 * <li>try to respect the width and height constraints.
	 * <li>sub-split the splitter (horizontally or vertically) zone and position
	 * the Dockable in the most appropriate zone (top, left, bottom, right).
	 * </ul>
	 * 
	 * @param dockable
	 *            the dockable to add (must not be already visible)
	 * @param position
	 *            relative position of the dockable
	 * 
	 * @throws IllegalArgumentException
	 *             if the dockable already belongs to the desktop containment
	 *             hierarchy.
	 */
	public void addDockable(Dockable dockable, RelativeDockablePosition position) {
		/*
		 * addDockable isn't called internally by the API : just for user
		 * applications
		 */
		if (this.isAncestorOf(dockable.getComponent())) {
			throw new IllegalArgumentException(
					"Dockable is already contained in the desktop");
		}

		registerDockable(dockable);

		if (DockingUtilities.findSingleDockableContainer(dockable) != null) {
			remove(dockable);
		}

		DockableState newState = new DockableState(this, dockable,
				DockableState.Location.DOCKED, position);

		DockableState currentState = getDockableState(dockable);
		DockableState.Location currentLocation = getLocation(currentState);

		DockableStateWillChangeEvent dswe = new DockableStateWillChangeEvent(
				currentState, newState);
		DockingActionEvent dae = new DockingActionAddDockableEvent(this,
				dockable, currentLocation, newState.getLocation(), dockingPanel);
		if (isDockingActionAccepted(dae, dswe)) {
			@SuppressWarnings("deprecation")
			SingleDockableContainer sdc = RelativeDockingUtilities
					.insertDockable(dockingPanel, dockable, position);
			sdc.installDocking(this);

			context.setDockableState(dockable, newState);
			dockable.getDockKey().setLocation(DockableState.Location.DOCKED);

			DockableStateChangeEvent dse = new DockableStateChangeEvent(
					currentState, newState);
			fireDockingAction(dae, dse);

			DockingUtilities.updateResizeWeights(dockingPanel);
		}
	}

	/**
	 * Replaces a dockable by another one.
	 * <p>
	 * Useful for example to reserve some space on a desktop by using a
	 * "placeholder' when other dockables aren't visible (Multiple Tabbed
	 * Document Interface with always a dockable visible even when no document
	 * is loaded).
	 * <p>
	 * Implementation note : this method assumes only the "base" dockable is
	 * visible (the replacer must be in the CLOSED state). It also assumes the
	 * base dockable isn't a compound dockable. These limitations will be
	 * removed in a later release.
	 * 
	 * <p>
	 * There is currently no DnD gesture associated to this action (although the
	 * "HotSwap" gesture would be a good candidate). So this method currently
	 * doesn't trigger any event (state change, action), yet it could change
	 * later.
	 * 
	 * @since 2.1
	 * @throws IllegalArgumentException
	 *             when dockables aren't in the appropriate state or hierarchy.
	 */
	public void replace(Dockable base, Dockable replacer) {
		DockableState baseState = context.getDockableState(base);
		DockableState replacerState = context.getDockableState(replacer);

		DockableState newBaseState = new DockableState(
				replacerState.getDesktop(), base, replacerState.getLocation());
		DockableState newReplacerState = new DockableState(
				baseState.getDesktop(), replacer, baseState.getLocation());

		TabbedDockableContainer tdcBase = DockingUtilities
				.findTabbedDockableContainer(base);

		if (!replacerState.isClosed()) {
			throw new IllegalArgumentException("replacer isn't closed");
		}
		if (base instanceof CompoundDockable) {
			throw new IllegalArgumentException(
					"base cannot be a compound dockable");
		}

		if (tdcBase != null) {
			int iBase = tdcBase.indexOfDockable(base);
			tdcBase.removeDockable(base);
			tdcBase.addDockable(replacer, iBase);
		} else {
			SingleDockableContainer sdcBase = DockingUtilities
					.findSingleDockableContainer(base);
			Container sdcBaseParent = ((Component) sdcBase).getParent();
			if (sdcBaseParent instanceof SplitContainer) {
				SplitContainer split = (SplitContainer) sdcBaseParent;
				boolean isLeft = split.getLeftComponent() == sdcBase;

				SingleDockableContainer sdc = DockableContainerFactory
						.getFactory()
						.createDockableContainer(
								replacer,
								DockableContainerFactory.ParentType.PARENT_SPLIT_CONTAINER);
				sdc.installDocking(this);
				if (isLeft) {
					split.setLeftComponent((Component) sdc);
				} else {
					split.setRightComponent((Component) sdc);
				}
				split.revalidate();
			} else if (sdcBaseParent instanceof DockingPanel) {
				// a single component
				DockingPanel dp = (DockingPanel) sdcBaseParent;
				dp.remove(0);
				SingleDockableContainer sdc = DockableContainerFactory
						.getFactory()
						.createDockableContainer(
								replacer,
								DockableContainerFactory.ParentType.PARENT_SPLIT_CONTAINER);
				sdc.installDocking(this);
				dp.add((Component) sdc, BorderLayout.CENTER);
				dp.revalidate();
			} else if (sdcBaseParent instanceof CompoundDockingPanel) {
				// a single component
				CompoundDockingPanel cdp = (CompoundDockingPanel) sdcBaseParent;
				cdp.remove(0);
				SingleDockableContainer sdc = DockableContainerFactory
						.getFactory()
						.createDockableContainer(
								replacer,
								DockableContainerFactory.ParentType.PARENT_SPLIT_CONTAINER);
				sdc.installDocking(this);
				cdp.add((Component) sdc, BorderLayout.CENTER);
				cdp.revalidate();
			} else {
				throw new RuntimeException("Wrong container hierarchy : "
						+ sdcBaseParent);
			}
		}

		// swap states
		context.setDockableState(base, newBaseState);
		context.setDockableState(replacer, newReplacerState);

	}

	/*
	 * small utility method to avoid bloating code everywhere (return a default
	 * state value if state object is null)
	 */
	private static DockableState.Location getLocation(DockableState state) {
		if (state == null) {
			return DockableState.Location.CLOSED;
		} else {
			return state.getLocation();
		}
	}

	/**
	 * Adds a dockable inside a compound dockable.
	 * <p>
	 * This is the method to start a nesting hierarchy inside a compound
	 * dockable (once the first dockable is added, you add subsequent dockables
	 * with standard split/createTab methods)
	 * <ul>
	 * <li>note that if the compound dockable is not empty, this method will
	 * raise an exception.
	 * </ul>
	 * 
	 * @since 2.1
	 */
	public void addDockable(CompoundDockable base, Dockable dockable) {

		registerDockable(dockable);

		DockableState currentState = getDockableState(dockable);
		DockableState.Location currentLocation = getLocation(currentState);

		DockableState newState = new DockableState(this, dockable,
				DockableState.Location.DOCKED);
		DockableStateWillChangeEvent dswe = new DockableStateWillChangeEvent(
				currentState, newState);

		CompoundDockingPanel panel = (CompoundDockingPanel) base.getComponent();

		DockingActionEvent dae = new DockingActionAddDockableEvent(this,
				dockable, currentLocation, newState.getLocation(), panel);

		if (isDockingActionAccepted(dae, dswe)) {
			DockableContainer dockableContainer = DockableContainerFactory
					.getFactory().createDockableContainer(dockable,
							DockableContainerFactory.ParentType.PARENT_DESKTOP);
			dockableContainer.installDocking(this);
			// TODO : check and raise exception if not empty
			panel.add((JComponent) dockableContainer, BorderLayout.CENTER);

			context.setDockableState(dockable, newState);
			dockable.getDockKey().setLocation(DockableState.Location.DOCKED);
			fireDockingAction(dae, new DockableStateChangeEvent(currentState,
					newState));
		}

	}

	/**
	 * Evaluates and returns the preferred size of the desktop, according to its
	 * contents and auto-hide borders.
	 */
	public Dimension getPreferredSize() {
		Insets i = getDockingPanelInsets();
		Dimension dim = dockingPanel.getPreferredSize();
		dim.width += i.left + i.right;
		dim.height += i.top + i.bottom;
		return dim;
	}

	/**
	 * Adds a new DockableStateChangeListener to this desktop.
	 * <p>
	 * DockableStateChange Events are triggered after the state change.
	 * 
	 * <p>
	 * As of version 2.0 of the framework, this method can also be replaced by
	 * adding a propertychangeListener on the DockKey object (and react to its
	 * DockableState property).
	 * 
	 * */
	public void addDockableStateChangeListener(
			DockableStateChangeListener listener) {
		context.addDockableStateChangeListener(listener);
	}

	/**
	 * Removes a DockableStateChangeListener from this desktop.
	 * */
	public void removeDockableStateChangeListener(
			DockableStateChangeListener listener) {
		context.removeDockableStateChangeListener(listener);
	}

	/**
	 * Adds a new DockableStateWillChangeListener to this desktop.
	 * <p>
	 * DockableStateWillChange Events are triggered <b>before</b> the state
	 * change, and are vetoable.
	 * */

	public void addDockableStateWillChangeListener(
			DockableStateWillChangeListener listener) {
		context.addDockableStateWillChangeListener(listener);
	}

	/**
	 * Removes a DockableStateWillChangeListener from this desktop.
	 * */
	public void removeDockableStateWillChangeListener(
			DockableStateWillChangeListener listener) {
		context.removeDockableStateWillChangeListener(listener);
	}

	/**
	 * Adds a new DockingActionListener to this desktop.
	 * <p>
	 * DockingAction Events are triggered before and after any docking action
	 * (split, tab, close, hide...) and allow for precise tracking (and vetoing)
	 * of these operations.
	 * 
	 * @see #removeDockingActionListener(DockingActionListener)
	 */
	public void addDockingActionListener(DockingActionListener listener) {
		context.addDockingActionListener(listener);
	}

	/**
	 * Removes a DockingActionListener from this desktop.
	 * 
	 * @see #addDockingActionListener(DockingActionListener)
	 */
	public void removeDockingActionListener(DockingActionListener listener) {
		context.removeDockingActionListener(listener);
	}

	/**
	 * Adds a new DockableSelectionListener to this desktop.
	 * <p>
	 * DockableSelection Events are triggered when a dockable takes the focus.
	 * 
	 */
	public void addDockableSelectionListener(DockableSelectionListener listener) {
		context.addDockableSelectionListener(listener);
	}

	/**
	 * Removes a DockableSelectionListener from this desktop.
	 * */
	public void removeDockableSelectionListener(
			DockableSelectionListener listener) {
		context.removeDockableSelectionListener(listener);
	}

	/**
	 * Registers and add a dockable on an auto-hide border.
	 * <P>
	 * this method should be called at startup time (when the dockable isn't yet
	 * displayed). to toggle a dockable from docked to auto-hide, please use
	 * setAutoHide(Dockable, boolean) instead.
	 * 
	 * <P>
	 * However, this method will not fail if the dockable is already displayed :
	 * in that case, it will delegate autohide to setAutoHide(Dockable,
	 * boolean), thus dropping the dockedPosition argument.
	 * 
	 * @param dockable
	 *            the dockable to add (to select the border where the dockable
	 *            will be positionned, use the DockKey.setAutoHideBorder mehod
	 * 
	 * @param dockedPosition
	 *            relative positionning (may be null) indicating where to dock
	 *            the dockable when leaving its auto-hide border.
	 * */
	public void addHiddenDockable(Dockable dockable,
			RelativeDockablePosition dockedPosition) {
		/* not used from inside the API : only for user applications */

		DockableState currentState = getDockableState(dockable); // should be
																	// null
		if (currentState == null || currentState.isClosed()) { // 2007/03/19
																// reformulate
																// tests
			// ok, that's the normal use case
		} else if (currentState.isHidden()) {
			// already hidden... avoid !
			return; // should I throw an exception ?
		} else if (currentState.isDocked()) {
			// if (currentState != null && !currentState.isHidden()){
			// delegate autohide to the appropriate method
			setAutoHide(dockable, true);
			return;
		}
		registerDockable(dockable);
		currentState = getDockableState(dockable); // now should be CLOSED

		DockableState.Location currentLocation = getLocation(currentState);

		DockableState newState = new DockableState(this, dockable,
				DockableState.Location.HIDDEN, dockedPosition);
		DockableStateWillChangeEvent dswe = new DockableStateWillChangeEvent(
				currentState, newState);
		DockingActionEvent dae = new DockingActionSimpleStateChangeEvent(this,
				dockable, currentLocation, DockableState.Location.HIDDEN);
		DockKey k = dockable.getDockKey();

		if (currentLocation != DockableState.Location.HIDDEN
				&& isDockingActionAccepted(dae, dswe)) {
			AutoHideButton btn = autoHideButtons.get(k);
			if (btn == null) {
				btn = new AutoHideButton();
				autoHideButtons.put(k, btn);

				int zone;
				if (k.getAutoHideBorder() == null) {
					zone = AutoHidePolicy.getPolicy().getDefaultHideBorder()
							.value();
				} else {
					zone = k.getAutoHideBorder().value();
				}
				btn.init(dockable, zone);

				borderPanes[zone].setVisible(true); // border may not be visible
				borderPanes[zone].add(btn);
				borderPanes[zone].revalidate();
			} else { // btn already existing, show it again
				int zone = btn.getZone();
				borderPanes[zone].setVisible(true); // may not be visible
				btn.setVisible(true);
				borderPanes[zone].revalidate();
			}
			context.setDockableState(dockable, newState);
			dockable.getDockKey().setLocation(DockableState.Location.HIDDEN);
			fireDockingAction(dae, new DockableStateChangeEvent(currentState,
					newState));
			revalidate();
		}

	}

	/**
	 * Changes the width of a dockable (the dockable must already be visible).
	 * 
	 * <p>
	 * Note that this method works by modifying the divider location of the
	 * nearest SplitContainer ancestor of the dockable (the first splitted
	 * horizontally).
	 * 
	 * <p>
	 * As this method must be called when the desktop is visible, a simple way
	 * to do this is to add a window listener to the parent frame of the
	 * desktop, and call this method from the windowOpened(WindowEvent)
	 * notification.
	 * 
	 * @param width
	 *            new width (if between 0 and 1, width is taken as a
	 *            proportional width, otherwise it is a pixel width.
	 * 
	 * */
	public void setDockableWidth(Dockable dockable, double width) {
		SplitContainer split = DockingUtilities.getSplitPane(dockable,
				JSplitPane.HORIZONTAL_SPLIT);
		if (split == null)
			return;
		if (((Container) split.getLeftComponent()).isAncestorOf(dockable
				.getComponent())) {
			split.setDividerLocation(width);
		} else {
			split.setDividerLocation(1.0 - width);
		}
	}

	/**
	 * Changes the height of a dockable (the dockable must already be visible).
	 * 
	 * <P>
	 * Note that this method works by modifying the divider location of the
	 * nearest SplitContainer ancestor of the dockable (the first splitted
	 * vertically).
	 * 
	 * <P>
	 * As this method must be called when the desktop is visible, a simple way
	 * to do this is to add a window listener to the parent frame of the
	 * desktop, and call this method from the windowOpened(WindowEvent)
	 * notification.
	 * 
	 * @param height
	 *            new height (if between 0 and 1, height is taken as a
	 *            proportional height, otherwise it is a pixel height.
	 * 
	 * */
	public void setDockableHeight(Dockable dockable, double height) {
		SplitContainer split = DockingUtilities.getSplitPane(dockable,
				JSplitPane.VERTICAL_SPLIT);
		if (split == null)
			return;
		if (((Container) split.getTopComponent()).isAncestorOf(dockable
				.getComponent())) {
			split.setDividerLocation(height);
		} else {
			split.setDividerLocation(1 - height);
		}
	}

	/**
	 * Updates the resizing behaviour of the desktop in order to resize this
	 * dockable (and keep others at fixed size).
	 * <P>
	 * This method should be called at least once (prior to showing the desktop)
	 * with an already docked dockable.
	 * <P>
	 * If the dockable doesn't belong to the desktop containment hierarchy, this
	 * method will do nothing.
	 * 
	 * @deprecated use setResizeWeight() in every dockKey for a better resizing
	 *             behaviour
	 * 
	 */
	public void setAutoResizableDockable(Dockable dockable) {
		/* this.autoResizeableDockable = dockable; */
		DockingUtilities.updateResizeWeights(dockingPanel);
	}

	/**
	 * Registers a dockable as belonging to a tab group.
	 * <p>
	 * It is used to have a memory of grouped (tabbed) dockables in order to
	 * keep the group together when dockable are restored from auto-hide mode.
	 * <p>
	 * This method is generally called by the tabbed container management, and
	 * not directly by the developper.
	 * 
	 * <p>
	 * However, there is a case where calling this method can be usefull : when,
	 * at startup, a desktop is built with multiple hidden dockables, and the
	 * developper wants them to be grouped in a tab container when they are
	 * restored to the desktop.
	 * 
	 * <p>
	 * note that the method is symetric when a group is empty : it such a case
	 * base and newTab args can be swapped.
	 * 
	 * @param base
	 *            an already tabbed dockable
	 * @param newTab
	 *            a dockable to add to the tab group
	 * 
	 * @since 1.1.2
	 */
	public void addToTabbedGroup(Dockable base, Dockable newTab) {// 2005/07/13
		/*
		 * this method is called when a dockable is added to a
		 * dockableTabbedContainer
		 */
		LinkedList<Dockable> group = tabbedGroups.get(base);
		if (group == null) {
			group = new LinkedList();
			group.add(base);
			tabbedGroups.put(base, group);
		}
		if (!group.contains(newTab)) {
			group.add(newTab);
			tabbedGroups.put(newTab, group);
		}
	}

	/**
	 * Unregisters a dockable from its current tab group (when it's removed from
	 * it).
	 * <p>
	 * For an application developper, there should be no need to call this
	 * method as it is managed internally by the framework, unless the
	 * developper wants to explicitely remove a component from a tab group when
	 * this component is in the auto-hide state.
	 * 
	 * @since 1.1.2
	 */
	public void removeFromTabbedGroup(Dockable dockable) { // 2005/07/13
		/* package protected */

		/*
		 * This method is invoked when a component is dragged outside of a tab
		 * group : meaning the user doesn't want anymore this dockable to be
		 * grouped in that tab.
		 */
		LinkedList<Dockable> group = tabbedGroups.get(dockable);
		if (group != null) {
			tabbedGroups.remove(dockable);
			group.remove(dockable);
			if (group.size() == 1) { // end of grouping as there are no more
										// dockables linked.
				Dockable d = (Dockable) group.removeFirst();
				tabbedGroups.remove(d);
			}
		}

	}

	/**
	 * Creates the autohide expand panel to be used in this desktop.
	 * <p>
	 * This method gives a chance to the developer to override expand panel
	 * creation and provide a custom subclass.
	 */
	protected AutoHideExpandPanel createAutoHideExpandPanel() {
		return new AutoHideExpandPanel();
	}

	/**
	 * used to track focus changes for DockableSelection events
	 */
	private class FocusHandler implements PropertyChangeListener,
			ChangeListener {

		/** this one is never null (except at first time) */
		Dockable lastFocusedDockable;

		/** this one can be null between focusLost() and focusGained() */
		Dockable currentDockable;

		// focusOwner
		public void propertyChange(PropertyChangeEvent e) {
			Component c = (Component) e.getNewValue();
			while (c != null && !(c instanceof SingleDockableContainer)) {
				c = c.getParent();
			}

			if (c instanceof SingleDockableContainer) {
				SingleDockableContainer sdc = (SingleDockableContainer) c;
				currentDockable = sdc.getDockable();
				if (sdc.getDockable() != lastFocusedDockable) {
					context.fireDockableSelectionChange(new DockableSelectionEvent(
							sdc.getDockable()));
				}
				lastFocusedDockable = sdc.getDockable();
			} else { // c == null
				currentDockable = null;
			}
		}

		// This method is called whenever the selected tab of all
		// TabbedDockableContainers changes
		public void stateChanged(ChangeEvent evt) {
			// our only problem is at startup : the focus may be out of the
			// tabbed pane
			// and we are firing a false selection event.
			// @todo correct this behaviour by checking if the focus is not
			// already
			// outside this component
			JTabbedPane pane = (JTabbedPane) evt.getSource();
			TabbedDockableContainer tdc = ((TabbedDockableContainer) pane);
			Dockable selDockable = tdc.getSelectedDockable();
			if (selDockable == null) { // this may happen when maximization
										// occurs
				// ignore
			} else {
				currentDockable = selDockable;
				if (selDockable != lastFocusedDockable) {
					lastFocusedDockable = selDockable;
					context.fireDockableSelectionChange(new DockableSelectionEvent(
							selDockable));
				}
			}
		}

		Dockable getCurrentDockable() {
			return currentDockable;
		}
	}

	/** Returns the name of this desktop (used by workspace management) */
	public String getDesktopName() {
		return desktopName;
	}

	/** Updates the name of this desktop (used by workspace management) */
	public void setDesktopName(String desktopName) {
		this.desktopName = desktopName;
	}

	/**
	 * Returns the docking context used by this desktop (might be shared with
	 * other desktop).
	 * 
	 * @since 2.1
	 */
	public DockingContext getContext() {
		return context;
	}

	/**
	 * Updates the docking context used by this desktop.
	 * <p>
	 * Warning : this method should only be used by the framework itself :
	 * changing a context "live" can have unpredicted and undesired side effect.
	 * 
	 * @since 2.1
	 */
	public void setContext(DockingContext context) {
		this.context = context;
	}

	/**
	 * returns the currently maximized dockable (or null if no dockable is in
	 * that state)
	 */
	public Dockable getMaximizedDockable() {
		if (maximizedComponent == null) {
			return null;
		} else {
			SingleDockableContainer maxContainer = (SingleDockableContainer) maximizedComponent;
			return maxContainer.getDockable();
		}
	}

	/**
	 * combines a docking action event and vetoable state change event to accept
	 * or reject a docking action.
	 */
	private boolean isDockingActionAccepted(DockingActionEvent dae,
			DockableStateWillChangeEvent dse) {
		boolean accepted = context.fireAcceptDockingAction(dae);
		if (accepted) {
			if (dse != null
					&& dae.getInitialLocation() != dae.getNextLocation()) {
				return context.fireDockableStateWillChange(dse);
			} else {
				return true;
			}
		} else {
			return false;
		}