/***************************************************************************
 *   Copyright (C) 2004, 2005 Thomas Nagy                                  *
 *   tnagy2^8@yahoo.fr                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation (see COPYING)            *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/

#include <qpoint.h>
#include <qdragobject.h>
#include <qevent.h>
#include <qpen.h>
#include <qcursor.h>
#include <qfont.h>
#include <qpixmap.h>
#include <qwmatrix.h>
#include <qpainter.h>

#include <kurldrag.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kdebug.h>
#include <kfontdialog.h>
#include <kcolordialog.h>
#include <ktextedit.h>

#include "DCanvasTip.h"
#include "DCanvasItem.h"
#include "DCanvasPopup.h"
#include "DCanvasView.h"

#include "settings.h"
#include "aux.h"
#include "DDataItem.h"
#include "DDataControl.h"
#include "DCanvasPos.h"

class InlineEdit : public KTextEdit
{
public:
InlineEdit(DCanvasView* parent) : KTextEdit(parent) { m_canvasview=parent; }
~InlineEdit() {}
void keyPressEvent( QKeyEvent* e );
DCanvasView *m_canvasview;	
};

void InlineEdit::keyPressEvent( QKeyEvent* e )
{
	switch (e->key())
	{
		case Key_Return:
		case Key_Enter:
			m_canvasview->hideInlineEditor();
			break;
		case Key_Escape:
			m_canvasview->hideInlineEditor(false);
			break;
		default:
			KTextEdit::keyPressEvent(e);
			break;
	}
}

// z position for link threads
static const int s_linez = 50;

unsigned int DCanvasView::ExtensionLock::m_neesting = 0;

DCanvasView::DCanvasView(QWidget* parent, const char*) : QCanvasView(parent), DGuiView(parent)
{
	m_pressed       = false;
	m_wheelpressed  = false;

	m_menu          = NULL;
	m_startItem     = NULL;
	m_selectedlink  = NULL;
	m_selectedref   = NULL;

	m_lastitemtoselect = DItem::NOITEM;
	m_lastitem = DItem::NOITEM;

	m_tooltip = new DCanvasTip(this);
	m_currentAction = DCanvasView::act_point;
	setCursor(ArrowCursor);

	m_itemtosort = DItem::NOITEM;
	m_sortcount = 0;

	setCanvas(new QCanvas());
	canvas()->setAdvancePeriod(40);
	canvas()->resize(m_defaultsize, m_defaultsize);

	m_textedit = new InlineEdit(this);
	m_textedit->resize(200, 100);
	//m_textedit->setHScrollBarMode(QScrollView::AlwaysOff);
	//m_textedit->setHScrollBarMode(QScrollView::AlwaysOff);
	QScrollView::addChild(m_textedit, -40, -40);


	// rubber line for link action
	m_rubberLine = new QCanvasLine(canvas());
	m_rubberLine->setPoints(0,0, 0,0);
	m_rubberLine->setZ( s_linez );
	m_rubberLine->show();

	// rubber band for select action
	m_rubberRect = new QCanvasRectangle( canvas() );
	m_rubberRect->setSize(10, 10);
	m_rubberRect->move(-10,-10);
	m_rubberRect->show();
	m_rubberRect->setZ(20);

	setAcceptDrops( true );
	setDragAutoScroll( true );

	horizontalScrollBar()->setMaxValue(m_defaultsize);
	verticalScrollBar()->setMaxValue(m_defaultsize);
	horizontalScrollBar()->setValue(m_defaultsize/2);
	verticalScrollBar()->setValue(m_defaultsize/2);

	// focus on the center of the map newly created
	center(canvas()->width()/2, canvas()->height()/2);

	connect(m_textedit, SIGNAL(textChanged()), this, SLOT(updateFromTextEdit()) );
}

DCanvasView::~DCanvasView()
{
	unplug();

	delete m_tooltip;
	delete m_rubberRect;
	delete m_rubberLine;

	// Deleting all items from the canvas properly is tricky
	// We only delete the DCanvasItem because they will remove
	// their own canvas items 

	// an infinite loop here means that there is a bug.
	int count=0;
	while (true)
	{
		QCanvasItemList lst = canvas()->allItems();
		if (lst.count() == 0)
			break;

		for (unsigned int i=0; i<lst.count(); i++)
		{
			if (lst[i]->rtti() != DCanvasItem::Rtti_DCanvasItem)
				continue;
			delete (DCanvasItem*) lst[i];
			break;
		}
		if (count > 100000)
		{
			kdWarning() <<"BUG in DCanvasView destructor"<<endl;
			break;
		}
		count++;
	}

	canvas()->setAdvancePeriod(-1);
	delete canvas();
}

void DCanvasView::focusOnRoot()
{
	centerOnObject( dataTree()->rootID() );
	dataTree()->setItemSelected( dataTree()->rootID(), NULL );
}

DCanvasItem* DCanvasView::canvasItem( int id )
{
	if (id == DItem::NOITEM) return NULL;
	return (DCanvasItem*) Item( id );
}

void DCanvasView::contentsMousePressEvent(QMouseEvent* me)
{
	m_clickLock = std::auto_ptr<ExtensionLock>(new ExtensionLock); // no extension before task is finished

	setFocus();

	QPoint p = inverseWorldMatrix().map(me->pos());

	// remove the context menu
	if (m_menu != NULL)
	{
		m_menu->hide();
		disconnect(m_menu, SIGNAL(activated(int)), this, SLOT(popupMenuSel(int)));
		delete m_menu;
		m_menu = NULL;
	}

	if (me->button() == LeftButton)
	{
		m_pressed = true;
	}
	else if (me->button() == MidButton)
	{
		m_pressed = true;
		m_wheelpressed = true;
		m_wheelpressed_backup = m_currentAction;

		setActionType(act_link);
	}

	m_moveStart = p;

	int newitem = locateItem( me->pos() );

	// if the user has selected some items previously, let him move them directly
	if ( m_justSelectedSomething )
	{
		m_justSelectedSomething = false;

		if (me->button() != RightButton)
		{
			if ( m_selectedList.count() > 0 && m_selectedList.contains(newitem) )
			{
				// TODO : isn't this code redundant
				QValueList<int>::iterator end = m_selectedList.end();
				for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
				{
					canvasItem(*m_it)->setOffset( p );
				}

				m_justSelectedSomething = false;
				m_canSelect = false;
				return;
			}
		}
	}

	// set the rubberband for selecting items
	m_canSelect = false;
	if (newitem == DItem::NOITEM) m_canSelect = true;

	if (me->button() == LeftButton || me->button() == MidButton)
	{
		if (act_point == m_currentAction)
		{
			// Use this to debug the structure of the tree
			//dataTree()->debugItem(newitem);

			if ( newitem != DItem::NOITEM )
			{
				canvasItem(newitem)->setOffset( p );
				if (me->state() & ShiftButton)
				{
					if (m_selectedList.contains(newitem))
					{
						canvasItem(newitem)->setSelected(false);
						if ((me->state()&AltButton) && (me->state()&ControlButton)) selectAllChildren(newitem, false);
						m_selectedList.remove(newitem);
					}
					else
					{
						addItemToSelection( newitem );
						if ((me->state()&AltButton) && (me->state()&ControlButton)) selectAllChildren(newitem);
					}
				}
				else
				{
					if (m_selectedList.contains(newitem))
					{
						// add children if needed
						if ((me->state()&AltButton) && (me->state()&ControlButton)) selectAllChildren(newitem);
					}
					else
					{
						deselectAll();
						addItemToSelection( newitem );
						if ((me->state()&AltButton) && (me->state()&ControlButton)) selectAllChildren(newitem);
					}
				}
			}
			else
			{
				deselectAll();
			}

			QValueList<int>::iterator end = m_selectedList.end();
			for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
			{
				canvasItem(*m_it)->setOffset( p );
			}
		}
		else if (act_link == m_currentAction)
		{
			QPen pen;
			pen.setWidth( 2 );

			if (! (me->state() & ControlButton))
			{
				pen.setColor( dataTree()->m_col_link );
				pen.setStyle( Qt::SolidLine );
				m_rubberLine->setPen(pen);

				if (m_startItem)
					m_startItem->setSelected(false);

				m_startItem = canvasItem( newitem );

				if (m_startItem)
					m_startItem->setSelected(true);
			}
			else
			{
				pen.setColor( dataTree()->m_col_ref );
				pen.setStyle( Qt::DashLine );
				m_rubberLine->setPen(pen);

				if (m_startItem)
					m_startItem->setSelected(false);

				m_startItem = canvasItem( newitem );

				if (m_startItem)
					m_startItem->setSelected(true);
			}
		}
		else if (act_sort == m_currentAction)
		{
			int child = locateItemPos( me->pos() );
			deselectAll();

			if (child != DItem::NOITEM)
			{
				// set the position of a child
				// kdWarning()<<"sort child on mouse press "<<child<<endl;
				dataItem(m_itemtosort)->setChildIdx(child, m_sortcount);

				m_sortcount++;
				if (m_sortcount >= dataItem(m_itemtosort)->countChildren())
					m_sortcount = 0;

				dataTree()->notifyChildChange(m_itemtosort);
				dataTree()->notifyChildren(m_itemtosort);
			}
			else
			{
				// select another parent for sorting his children
				m_sortcount = 0;
				if (m_itemtosort != DItem::NOITEM)
				{
					//canvasItem(m_itemtosort)->setSelected(false);
					for (unsigned int i=0; i<dataItem(m_itemtosort)->countChildren(); i++)
					{
						canvasItem(dataItem(m_itemtosort)->childNum(i))->setDisplayIdx(false);
					}
				}

				m_itemtosort = newitem;

				if (m_itemtosort != DItem::NOITEM)
				{
					//canvasItem(m_itemtosort)->setSelected(true);
					addItemToSelection( m_itemtosort );
					dataTree()->setItemSelected( m_selectedList[0], this );

					for (unsigned int i=0; i<dataItem(m_itemtosort)->countChildren(); i++)
					{
						canvasItem(dataItem(m_itemtosort)->childNum(i))->setDisplayIdx(true);
					}
				}
				else
				{
					dataTree()->setItemSelected( DItem::NOITEM, this );
				}
			}
			canvas()->update();
		}
		else if (act_scroll == m_currentAction)
		{
			deselectAll();
			if ( newitem != DItem::NOITEM )
			{
				//canvasItem(newitem)->setSelected(true);
				addItemToSelection( newitem );
				dataTree()->setItemSelected( m_selectedList[0], this );
			}
			else
			{
				dataTree()->setItemSelected( DItem::NOITEM, this );
			}
		}
	}

	if (me->button() == RightButton)
	{
		if ( ! (me->state() & ShiftButton) && !m_selectedList.contains(newitem) )
			deselectAll();

		if ( newitem != DItem::NOITEM )
		{
			canvasItem(newitem)->setOffset( p );
			if (!m_selectedList.contains(newitem))
				addItemToSelection( newitem );
		}
		else
		{
			deselectAll();
		}

		if (oneItemIsSelected()) dataTree()->setItemSelected( m_selectedList[0], this );
		else dataTree()->setItemSelected( DItem::NOITEM, this );

		if (m_readWrite)
		{
			m_selectedlink = NULL;
			m_selectedref  = NULL;
			QCanvasItemList l = canvas()->collisions( p );
			QCanvasItemList::iterator it;
			QCanvasItemList::iterator end = l.end();
			for ( it = l.begin(); it != end; ++it  )
			{
				if ( (*it)->rtti() == DCanvasRef::Rtti_DCanvasRef )
				{
					m_selectedref = (DCanvasRef*) (*it);
					break;
				}
				else if ( (*it)->rtti() == DCanvasLink::Rtti_DCanvasLink )
				{
					m_selectedlink = (DCanvasLink*) (*it);
					break;
				}
			}

			m_menu = new DCanvasPopup(this);
			m_menu->popup(me->globalPos());
			connect(m_menu, SIGNAL(activated(int)), this, SLOT(popupMenuSel(int)));
		}
	}
	me->ignore();
}

void DCanvasView::addItemToSelection( int id )
{
	if (Settings::inlineEditor()) hideInlineEditor(false);

	if (id == DItem::NOITEM) return;
	if (!isRegistered(id)) return;
	if (m_selectedList.count() == 0) m_lastitemtoselect = id;
	canvasItem(id)->setSelected(true);
	if (m_selectedList.contains(id) == 0) m_selectedList.push_back(id);

	if (oneItemIsSelected())
	{
		if (! Settings::inlineEditor())
		{
			m_textedit->setText( dataItem(m_selectedList[0])->m_summary );
			m_textedit->setFocus();
		}
	}
}

void DCanvasView::updateFromTextEdit()
{
	if (! oneItemIsSelected()) return;
	if ( Settings::inlineEditor() ) return;
	dataItem(m_selectedList[0])->m_summary = m_textedit->text();
	dataTree()->notifyItemModified( m_selectedList[0] );
}

void DCanvasView::contentsMouseDoubleClickEvent(QMouseEvent* me)
{
	ExtensionLock lock(this);
	deselectAll();

	QPoint p = inverseWorldMatrix().map( me->pos() );

	int newitem = locateItem( me->pos() );
	if (m_readWrite)
	{
		if ( me->state() & ControlButton )
		{
			if (newitem != DItem::NOITEM)
			{
				dataTree()->newDelta();
				dataTree()->setOrphan( newitem );
			}
			else
			{
				QRect r(p.x()-5, p.y()-5, 10, 10);

				QCanvasItemList l = canvas()->collisions( r );
				QCanvasItemList::iterator it;
				QCanvasItemList::iterator end = l.end();
				for ( it = l.begin(); it != end; ++it  )
				{
					if ( (*it)->rtti() == DCanvasLink::Rtti_DCanvasLink )
					{
						DCanvasLink* link = (DCanvasLink*) (*it);
						dataTree()->newDelta();
						dataTree()->setOrphan( link->IdOrig() );
						break;
					}
					else if ( (*it)->rtti() == DCanvasRef::Rtti_DCanvasRef )
					{
						DCanvasRef* ref = (DCanvasRef*) (*it);
						dataTree()->requestRefChange( ref->IdOrig(), ref->IdDest(), false );
						break;
					}
				}
			}
		}
		else
		{
			dataTree()->newDelta();
			dataTree()->createItem( newitem );

			if (Settings::automode()) tidyClean();
		}
	}
}

void DCanvasView::contentsMouseMoveEvent(QMouseEvent* me)
{
	QPoint p = inverseWorldMatrix().map(me->pos());
	if (m_pressed && m_currentAction == act_point)
	{
		if (m_canSelect)
		{
			// draw the rubberband for selecting many items at once

			// move the rubberband
			m_rubberRect->move( m_moveStart.x(), m_moveStart.y() );
			m_rubberRect->setSize( p.x()-m_moveStart.x(), p.y()-m_moveStart.y());

			// set 'selected' all items inside the rubberband
			QCanvasItemList l = canvas()->collisions( m_rubberRect->rect() );
			QCanvasItemList::iterator it;

			// remove the previous selection
			deselectAll();

			// add the ids of the items to the selection
			QCanvasItemList::iterator end = l.end();
			for ( it = l.begin(); it != end; ++it  )
			{
				if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem) continue;

				DCanvasItem *citem = (DCanvasItem*) (*it);
				if (!citem->isSelected()) citem->setSelected( true );

				addItemToSelection( citem->Id() );
			}

			m_justSelectedSomething = true;
		}
		else if (m_readWrite && ! (me->state() & ShiftButton) )
		{
			// do not move items while doing a selection (annoying behaviour)

			m_clickLock = std::auto_ptr<ExtensionLock>(new ExtensionLock(this)); // make sure canvas size is updated after move
			// move the selected items
			QValueList<int>::iterator end = m_selectedList.end();
			for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
			{
				DCanvasItem *citem = canvasItem(*m_it);

				if (!citem) return;

				if (!citem->isSelected()) citem->setSelected(true);

				citem->moveOffset(p);

				//DDataItem *item = dataItem( *m_it );
				updateObjLinks( *m_it );
			}

			// the document has changed
			dataTree()->docChanged();
		}
	}

	// in link mode, draw the thread for linking items
	if (m_currentAction == act_link)
	{
		if (m_rubberLine) m_rubberLine->setPoints( m_moveStart.x(), m_moveStart.y(), p.x(), p.y());
	}

	// scrolling is done in mouseMoveEvent instead due to a loss of accuracy
	me->ignore();
}



void DCanvasView::contentsMouseReleaseEvent(QMouseEvent* me)
{
	m_clickLock = std::auto_ptr<ExtensionLock>(0); // free the lock
	m_pressed = false;

	m_rubberRect->move( -10, -10 );
	m_rubberRect->setSize( 10, 10 );

	// even with invalidate() there are redrawing errors .. :-/
	// m_rubberRect is a QCanvasRectangle though
	canvas()->setAllChanged();

	int newitem = locateItem( me->pos() );

	if (m_currentAction == act_link && m_readWrite)
	{
		m_rubberLine->setPoints(0, 0, 0, 0);

		if (m_startItem)
		{
			m_startItem->setSelected(false);
			int id = locateItem(me->pos());

			if (id != DItem::NOITEM)
			{
				if (m_startItem->Id() != id)
				{
					if (! (me->state() & ControlButton))
					{
						dataTree()->newDelta();
						dataTree()->linkItems( m_startItem->Id(), id );
					}
					else
					{
						dataTree()->newDelta();
						dataTree()->requestRefChange( m_startItem->Id(), id, true );
					}
				}
				else
				{
					deselectAll();
					addItemToSelection(id);
				}
				//updateItem(id);
			}
			updateItem(m_startItem->Id());
		}
		m_startItem = NULL;
	}

	if (m_currentAction == act_point)
	{
		if (m_lastitem != DItem::NOITEM && newitem != DItem::NOITEM && (me->state() & ControlButton && !(me->state() & AltButton)))
		{
			dataTree()->newDelta();
			dataTree()->linkItems( m_lastitem, newitem );
		}
		else if (!m_canSelect && !(me->state() & ShiftButton) && m_readWrite )
		{
			QPoint p = inverseWorldMatrix().map(me->pos());

			if (p.x()!=m_moveStart.x() || p.y()!=m_moveStart.y())
			{ // mouse has actually moved

				if (m_selectedList.count() > 0) dataTree()->newDelta();

				// Foreach item in selection, add a Delta
				QValueList<int>::iterator end = m_selectedList.end();
				for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
				{
					DCanvasItem *citem = canvasItem(*m_it);
					DDataItem *ditem = dataItem(*m_it);
					if (!citem) return;

					DDataItem::Coord dx = p.x() - m_moveStart.x();
					DDataItem::Coord dy = p.y() - m_moveStart.y();
					DDataItem::Coord x = ditem->x();
					DDataItem::Coord y = ditem->y();

					dataTree()->registerMapPositionChange(*m_it, x-dx, y-dy, x, y);
				}
			}
		}
		m_lastitem = newitem;
	}

	if (m_wheelpressed)
	{
		m_wheelpressed = false;
		setActionType(m_wheelpressed_backup);
	}


	if (oneItemIsSelected()) dataTree()->setItemSelected( m_selectedList[0], this );
	else dataTree()->setItemSelected( DItem::NOITEM, this );

	emit selectionChanged();
}

void DCanvasView::mousePressEvent(QMouseEvent* me)
{
	m_scrollStart = me->pos() + QPoint( contentsX(), contentsY() );
}

void DCanvasView::mouseMoveEvent(QMouseEvent* me)
{
	if (m_currentAction == act_scroll)
	{
		QPoint newpoint = m_scrollStart - me->pos();
		setContentsPos( newpoint.x(), newpoint.y() );
	}
}

void DCanvasView::contentsWheelEvent( QWheelEvent* e )
{
	if (! (e->state() & ControlButton))
	{
		e->ignore();
		return;
	}

	int incval = 1;
	if (e->delta() < 0)
	{
		incval = -1;
	}

	if (itemsAreSelected())
	{
		QValueList<int>::iterator end = m_selectedList.end();
		for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
		{
			DDataItem *item = dataItem(*m_it);
			item->incrementFontSize(incval);
			canvasItem(*m_it)->updatePos();
		}
	}
	else
	{
		int id = locateItem(e->pos());
		if (id != DItem::NOITEM)
		{
			DDataItem *item = dataItem(id);
			item->incrementFontSize(incval);
			canvasItem(id)->updatePos();
		}
	}
}

int DCanvasView::locateItem( const QPoint& p )
{
	DCanvasItem* item = NULL;
	DCanvasItem* obj  = NULL;

	QCanvasItemList l = canvas()->collisions( inverseWorldMatrix().map(p) );
	QCanvasItemList::iterator it;

	double z=-1;

	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem )
			continue;

		obj = (DCanvasItem*) (*it);

		if ( obj->z() > z )
		{
			z = obj->z();
			item = obj;
		}
	}

	if (!item) return DItem::NOITEM;

	return item->Id();
}

int DCanvasView::locateItemPos( const QPoint& p)
{
	DCanvasPos* pos = NULL;
	DCanvasPos* obj = NULL;
	QCanvasItemList l = canvas()->collisions( inverseWorldMatrix().map(p) );
	QCanvasItemList::iterator it;

	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != Rtti_DCanvasPos)
			continue;

		obj = (DCanvasPos*) (*it);
		break;
	}

	pos = obj;

	if (!pos)
		return DItem::NOITEM;

	return pos->Id();
}

void DCanvasView::moveSelObjectsUp()
{
	ExtensionLock lock(this);
	if (m_selectedList.count() > 0) dataTree()->newDelta();
	for (unsigned int i=0; i<m_selectedList.count(); i++)
	{
		DCanvasItem *item = canvasItem( m_selectedList[i] );
		item->move(item->x(), item->y() - 30);
		updateObjLinks( m_selectedList[i] );
	}
}

void DCanvasView::moveSelObjectsDown()
{
	ExtensionLock lock(this);
	if (m_selectedList.count() > 0) dataTree()->newDelta();
	for (unsigned int i=0; i<m_selectedList.count(); i++)
	{
		DCanvasItem *item = canvasItem( m_selectedList[i] );
		item->move(item->x(), item->y() + 30);
		updateObjLinks( m_selectedList[i] );
	}
}

void DCanvasView::moveSelObjectsLeft()
{
	ExtensionLock lock(this);
	if (m_selectedList.count() > 0) dataTree()->newDelta();
	for (unsigned int i=0; i<m_selectedList.count(); i++)
	{
		DCanvasItem *item = canvasItem( m_selectedList[i] );
		item->move(item->x() - 30, item->y());
		updateObjLinks( m_selectedList[i] );
	}
}

void DCanvasView::moveSelObjectsRight()
{
	ExtensionLock lock(this);
	if (m_selectedList.count() > 0) dataTree()->newDelta();
	for (unsigned int i=0; i<m_selectedList.count(); i++)
	{
		DCanvasItem *item = canvasItem( m_selectedList[i] );
		item->move(item->x() + 30, item->y());
		updateObjLinks( m_selectedList[i] );
	}
}

#define midX(a) ((a)->x()+(a)->width()/2)
#define distX(a,b) ( ABS(  (midX(a)) - (midX(b))  ) )
#define midY(a) ((a)->y()+(a)->height()/2)
#define distY(a,b) ( ABS(  (midY(a)) - (midY(b))  ) )
#define dist(a,b) ( distY(a, b)*distY(a, b) + distX(a, b)*distX(a, b) )

void DCanvasView::selectObjUp()
{
	if (! oneItemIsSelected()) return;

	DDataItem *item = dataItem( m_selectedList[0] );
	if (Settings::selHierarchic())
	{
		if (item->Parent() != DItem::NOITEM)
		{
			deselectAll();
			int newstuff = item->Parent();
			addItemToSelection( newstuff );
			makeObjectVisible( newstuff );
			dataTree()->setItemSelected( newstuff, this );
		}
	}
	else
	{
		DCanvasItem *citem = canvasItem( m_selectedList[0] );
		deselectAll();

		DCanvasItem* candidate = NULL;

		QCanvasItemList l = canvas()->allItems();
		QCanvasItemList::iterator it;
		QCanvasItemList::iterator end = l.end();
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem* obj = (DCanvasItem*) (*it);

			// Implements a 'cone' with a pi/2-opening of acceptable objects to the top
			if ( midY(obj) < midY(citem) && distY(obj,citem) > distX(obj,citem) )
			{
				if (candidate)
				{
					if (dist(candidate, citem) > dist(obj, citem))
						candidate = obj;
				}
				else
				{
					candidate = obj;
				}
			}
		}		

		if (candidate)
		{
			makeObjectVisible( candidate->Id() );
			addItemToSelection( candidate->Id() );
		}
		if (!oneItemIsSelected()) addItemToSelection( item->Id() );
	}
}

void DCanvasView::selectObjDown()
{
	if (!oneItemIsSelected()) return;

	DDataItem *item = dataItem( m_selectedList[0] );
	if (Settings::selHierarchic())
	{
		if (item->countChildren() > 0)
		{
			int newstuff = DItem::NOITEM;
			unsigned int lastchld = item->m_lastchild;
			if (lastchld < item->countChildren()) newstuff = item->childNum(lastchld);
			else newstuff = item->childNum(0);

			deselectAll();
			addItemToSelection( newstuff );
			makeObjectVisible( newstuff );
			dataTree()->setItemSelected( newstuff, this );
		}
	}
	else
	{
		DCanvasItem *citem = canvasItem( m_selectedList[0] );
		deselectAll();

		DCanvasItem* candidate = NULL;

		QCanvasItemList l = canvas()->allItems();
		QCanvasItemList::iterator it;
		QCanvasItemList::iterator end = l.end();
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem* obj = (DCanvasItem*) (*it);

			// Implements a 'cone' with a pi/2-opening of acceptable objects to the bottom
			if ( midY(obj) > midY(citem) && distY(obj,citem) > distX(obj,citem) )
			{
				if (candidate)
				{
					if (dist(candidate, citem) > dist(obj, citem))
						candidate = obj;
				}
				else
				{
					candidate = obj;
				}
			}
		}		

		if (candidate)
		{
			makeObjectVisible( candidate->Id() );
			addItemToSelection( candidate->Id() );
		}
		if (!oneItemIsSelected()) addItemToSelection( item->Id() );
	}
}

void DCanvasView::selectObjLeft()
{
	if (!oneItemIsSelected()) return;

	DDataItem *item = dataItem( dataItem( m_selectedList[0] )->Parent() );
	// make sure to have at least one object
	if ( !item )
	{
		item = dataItem( m_selectedList[0] );
	}

	if (Settings::selHierarchic())
	{
		if (item)
		{
			int idx = item->childIdx( m_selectedList[0] );
			int newidx = (idx-1+item->countChildren())%item->countChildren();
			item->m_lastchild = newidx;

			int newstuff = item->childNum(newidx);
			deselectAll();
			addItemToSelection( newstuff );
			makeObjectVisible( newstuff );
			dataTree()->setItemSelected( newstuff, this );
		}
	}
	else
	{
		DCanvasItem *citem = canvasItem( m_selectedList[0] );
		deselectAll();

		DCanvasItem* candidate = NULL;

		QCanvasItemList l = canvas()->allItems();
		QCanvasItemList::iterator it;
		QCanvasItemList::iterator end = l.end();
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem* obj = (DCanvasItem*) (*it);

			// Implements a 'cone' with a pi/2-opening of acceptable objects to the left
			if ( midX(obj) < midX(citem) && distX(obj,citem) > distY(obj,citem) )
			{
				if (candidate)
				{
					if (dist(candidate, citem) > dist(obj, citem))
						candidate = obj;
				}
				else
				{
					candidate = obj;
				}
			}
		}		

		if (candidate)
		{
			makeObjectVisible( candidate->Id() );
			addItemToSelection( candidate->Id() );
		}
		if (!oneItemIsSelected()) addItemToSelection( item->Id() );
	}
}

void DCanvasView::selectObjRight()
{
	if (!oneItemIsSelected()) return;

	DDataItem *item = dataItem( dataItem( m_selectedList[0] )->Parent() );
	// make sure to have at least one object
	if ( !item )
	{
		item = dataItem( m_selectedList[0] );
	}

	if (Settings::selHierarchic())
	{
		if (item)
		{
			int idx = item->childIdx( m_selectedList[0] );
			int newidx = (idx+1)%item->countChildren();
			item->m_lastchild = newidx;

			int newstuff = item->childNum(newidx);
			deselectAll();
			addItemToSelection( newstuff );
			makeObjectVisible( newstuff );
			dataTree()->setItemSelected( newstuff, this );
		}
	}
	else
	{
		DCanvasItem *citem = canvasItem( m_selectedList[0] );
		deselectAll();

		DCanvasItem* candidate = NULL;

		QCanvasItemList l = canvas()->allItems();
		QCanvasItemList::iterator it;
		QCanvasItemList::iterator end = l.end();
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem* obj = (DCanvasItem*) (*it);

			// Implements a 'cone' with a pi/2-opening of acceptable objects to the right
			if ( midX(obj) > midX(citem) && distX(obj,citem) > distY(obj,citem) )
			{
				if (candidate)
				{
					if (dist(candidate, citem) > dist(obj, citem))
						candidate = obj;
				}
				else
				{
					candidate = obj;
				}
			}
		}		

		if (candidate)
		{
			makeObjectVisible( candidate->Id() );
			addItemToSelection( candidate->Id() );
		}
		if (!oneItemIsSelected()) addItemToSelection( item->Id() );
	}
}

void DCanvasView::addChild()
{
	if (!oneItemIsSelected()) return;

	if (m_selectedList.count() > 0) dataTree()->newDelta();
	int id = dataTree()->createItem( m_selectedList[0] );

	if (Settings::automode()) tidyClean();
	makeObjectVisible( id );
}

void DCanvasView::addSibling()
{
	if (!oneItemIsSelected()) return;

	if (m_selectedList.count() > 0) dataTree()->newDelta();
	int id = dataTree()->createItem( dataItem( m_selectedList[0] )->Parent() );
	if (Settings::automode()) tidyClean();
	makeObjectVisible( id );
}

void DCanvasView::keyPressEvent( QKeyEvent* e )
{
	switch (e->key())
	{
		case Qt::Key_Delete:
			if (m_readWrite && !m_pressed) removeSelectedItems();
			break;

		case Qt::Key_Backspace:
			if (oneItemIsSelected() && !m_pressed)
			{
				DDataItem *item = dataItem( m_selectedList[0] );
				int newsize = item->m_summary.length() - 1;
				if (newsize < 0) newsize = 0;
				item->m_summary.truncate( newsize );
				dataTree()->notifyItemModified( m_selectedList[0] );
			}
			break;

		case Qt::Key_Control:
			if (!m_pressed)
			{
				m_lastitem = DItem::NOITEM;
			}
			e->ignore();
			break;

		case Qt::Key_Up:
			if ( !(e->state() & AltButton) && !(e->state() & ControlButton) && !(e->state() & MetaButton))
				scrollBy(0, -30);
			else e->ignore();
			break;
		case Qt::Key_Down:
			if ( !(e->state() & AltButton) && !(e->state() & ControlButton) && !(e->state() & MetaButton))
				scrollBy(0, 30);
			else e->ignore();
			break;
		case Qt::Key_Left:
			if ( !(e->state() & AltButton) && !(e->state() & ControlButton) && !(e->state() & MetaButton))
				scrollBy(-30, 0);
			else e->ignore();
			break;
		case Qt::Key_Right:
			if ( !(e->state() & AltButton) && !(e->state() & ControlButton) && !(e->state() & MetaButton))
				scrollBy(30, 0);
			else e->ignore();
			break;

		case Key_Tab:
		case Key_Prior:
			//case Key_Return:
			//case Key_Enter:
		case Key_Insert:
		case Key_F16:
		case Key_F18:
		case Key_F20:
		case Key_Direction_L:
		case Key_Direction_R:
			e->ignore();
			break;

		default:

			if (m_pressed)
			{
				e->ignore();
				break;
			}

			if (!itemsAreSelected())
			{
				addItemToSelection( m_lastitemtoselect );
			}

			if (oneItemIsSelected() && !( e->state() & ControlButton )
					&& !( e->state() & AltButton )
					&& !( e->state() & MetaButton )
					&& e->count() )
			{
				if (Settings::inlineEditor())
				{
					int doit=false;
					switch (e->key())
					{
						case Key_A:
						case Key_E:
						case Key_I:
						case Key_Enter:
						case Key_Return:
							doit=true;
							break;
						default:
							break;
					}

					if (doit)
					{
						DDataItem *item = dataItem( m_selectedList[0] );
						m_textedit->setFont( item->m_defaultFont );

						DCanvasItem *citem = canvasItem( m_selectedList[0] );
						QRect r = worldMatrix().map( citem->rect() );
						QPoint p = r.bottomLeft();
						moveChild( m_textedit, p.x(), p.y() );
						ensureVisible( p.x(), p.y(), m_textedit->width(), m_textedit->height());

						m_textedit->setText( item->m_summary );

						switch (e->key())
						{
							case Key_I:
								m_textedit->moveCursor(QTextEdit::MoveLineStart, false);
								break;
							case Key_A:
								m_textedit->moveCursor(QTextEdit::MoveLineEnd, false);
								break;
							default:
								m_textedit->selectAll();
								break;
						}

						m_textedit->show();
						m_textedit->setFocus();
					}
				}
				else
				{
					e->ignore();

					QPoint p(-500, -500);
					moveChild( m_textedit, p.x(), p.y() );
					m_textedit->show();

					/*DDataItem *item = dataItem( m_selectedList[0] );
					item->m_summary = m_textedit->text();
					dataTree()->notifyItemModified( m_selectedList[0] );
					*/
					/*
					QString txt = e->text();
					if (txt.length() > 0)
					{
						DDataItem *item = dataItem( m_selectedList[0] );
						item->m_summary.append( txt );
						dataTree()->notifyItemModified( m_selectedList[0] );
					}
					else e->ignore(); */
				}
			}
			else e->ignore();

			break;
	}
}

void DCanvasView::keyReleaseEvent(QKeyEvent * e)
{
	switch (e->key())
	{
		case Qt::Key_Shift:
			m_justSelectedSomething = true;
			break;

		case 0:
			break;

		default:
			e->ignore();
			break;
	}
}

void DCanvasView::deselectAll()
{
	if (Settings::inlineEditor()) hideInlineEditor(false);

	QValueList<int>::iterator end = m_selectedList.end();
	for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
	{
		canvasItem(*m_it)->setSelected(false);
	}
	m_selectedList.clear();
}

bool DCanvasView::oneItemIsSelected() const
{
	return (m_selectedList.count() == 1);
}

bool DCanvasView::itemsAreSelected() const
{
	return (m_selectedList.count() > 0);
}

void DCanvasView::popupMenuSel(int sel)
{
	switch (sel)
	{
		case DCanvasPopup::e_item:
			if (oneItemIsSelected()) dataTree()->createItem( m_selectedList[0] );
			else dataTree()->createItem();
			if (Settings::automode()) tidyClean();
			break;

		case DCanvasPopup::e_delete:
			removeSelectedItems();
			break;

		case DCanvasPopup::e_discon_parent:
			if (oneItemIsSelected())
			{
				dataTree()->newDelta();
				dataTree()->setOrphan( m_selectedList[0] );
			}
			break;

		case DCanvasPopup::e_discon_obj:
			{
				if (m_selectedList.count() > 0) dataTree()->newDelta();
				QValueList<int>::iterator end = m_selectedList.end();
				for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
					dataTree()->disconnectItem( *m_it );
			}
			break;

		case DCanvasPopup::e_discon_subtree:
			if (oneItemIsSelected())
			{
				dataTree()->newDelta();
				dataTree()->killFamily( m_selectedList[0] );
			}
			break;

		case DCanvasPopup::e_tidy_all:
			tidyCanvas();
			break;

		case DCanvasPopup::e_tidy_clean:
			tidyClean();
			break;

		case DCanvasPopup::e_tidy_unclutter:
			tidyUnclutter();
			break;

		case DCanvasPopup::e_font:
			{
				QFont font;

				if (oneItemIsSelected()) font = dataItem( m_selectedList[0] )->m_defaultFont;
				else font = dataTree()->m_canvasFont;

				int result = KFontDialog::getFont( font );
				if ( result == KFontDialog::Accepted )
				{
					QValueList<int>::iterator end = m_selectedList.end();
					for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
					{
						dataItem(*m_it)->m_defaultFont = font;
						canvasItem(*m_it)->updatePos();
					}
				}
			}
			break;

		case Settings::EnumColorMode::custom_:
			applyCustomColor();
			break;

		case Settings::EnumColorMode::default_:
		case Settings::EnumColorMode::theme1_:
		case Settings::EnumColorMode::theme2_:
		case Settings::EnumColorMode::theme3_:
		case Settings::EnumColorMode::theme4_:
		case Settings::EnumColorMode::theme5_:
		case Settings::EnumColorMode::theme6_:
			applyColorScheme(sel);
			break;

		case DCanvasPopup::e_discon_link:
			{
				if (m_selectedref)
				{
					int orig = m_selectedref->IdOrig();
					int dest = m_selectedref->IdDest();
					dataTree()->requestRefChange( orig, dest, false );
					m_selectedref = NULL;
				}
				else if (m_selectedlink)
				{
					dataTree()->newDelta();
					dataTree()->setOrphan( m_selectedlink->IdOrig() );
					m_selectedlink = NULL;
				}
				break;
			}
		case DCanvasPopup::e_select_subtree:
			if (!oneItemIsSelected()) break;
			if (m_selectedList.count()) selectAllChildren(m_selectedList[0]);
			break;

		default:
			break;
	}
}

void DCanvasView::selectAllChildren( int item, bool select)
{
	for (unsigned int i=0; i<dataItem(item)->countChildren();i++)
	{
		int childNum = dataItem(item)->childNum(i);
		canvasItem(childNum)->setSelected(select);
		if (select)
			m_selectedList.push_back(childNum);
		else
			m_selectedList.remove(childNum);
		selectAllChildren(childNum, select);
	}
}

void DCanvasView::applyCustomColor()
{
	QColor myColor;
	int result = KColorDialog::getColor( myColor );

	if ( result == KColorDialog::Accepted )
	{
		if (m_selectedList.count() > 0) dataTree()->newDelta();

		QValueList<int>::iterator end = m_selectedList.end();
		for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
		{
			dataItem(*m_it)->setCustomColors( 
					myColor, 
					QColor( Qt::black ), 
					QColor( Qt::black ) );
			canvasItem(*m_it)->updatePos();
		}
	}
}

void DCanvasView::applyColorScheme(int sel)
{
	if (m_selectedList.count() > 0) dataTree()->newDelta();

	QValueList<int>::iterator end = m_selectedList.end();
	for ( m_it = m_selectedList.begin(); m_it != end; ++m_it )
	{
		dataItem(*m_it)->setColorScheme( sel );
		canvasItem(*m_it)->updatePos();
	}
}


void DCanvasView::extendIfNecessary(DDataItem::Coord x1, DDataItem::Coord y1, DDataItem::Coord x2, DDataItem::Coord y2)
{
	// avoid recursion (not thread safe!) and don't run during undo/redo
	if (ExtensionLock::abort() || dataTree()->isUndoRedoLocked()) return;
	ExtensionLock lock;

	// check whether we need to expand, and how much
	static const int limit(m_defaultsize/4);
	static const int extend(m_defaultsize/2);
	double dx(0), dy(0), offx(0), offy(0);
	if (x1<limit) dx = offx = extend+x1-limit;
	if (y1<limit) dy = offy = extend+y1-limit;
	if (x2>canvas()->width()-limit) dx += extend+x2-limit;
	if (y2>canvas()->height()-limit) dy += extend+y2-limit;

	// just for security: check for hard system limits
	if (((double)canvas()->width()+dx) > (double)std::numeric_limits<typeof(canvas()->width())>::max() || ((double)canvas()->height()+dy) > (double)std::numeric_limits<typeof(canvas()->height())>::max()) {
		static bool told(false); // should never happen, but say it once...
		if (!told) KMessageBox::error(this, i18n("The size of your image exceeds your platform's hardware limit!"));
		told = true;
		return;
	}

	// expand if necessary
	if (dx>0 || dy>0) {
		QPoint max(worldMatrix().map(QPoint(canvas()->width()+(int)dx, canvas()->height()+(int)dy)));
		horizontalScrollBar()->setMaxValue(max.x());
		verticalScrollBar()->setMaxValue(max.y());
		canvas()->resize(canvas()->width()+(int)dx, canvas()->height()+(int)dy);
	}

	// move children if necessary (recenter)
	if (offx>0 || offy>0) {
		QPoint pos(worldMatrix().map(QPoint(horizontalScrollBar()->value()+(int)offx, verticalScrollBar()->value()+(int)offy)));
		horizontalScrollBar()->setValue(pos.x());
		verticalScrollBar()->setValue(pos.y());
		QCanvasItemList l(canvas()->allItems());
		for (QCanvasItemList::iterator it(l.begin()); it != l.end(); ++it)
			if ((*it)->rtti() == DCanvasItem::Rtti_DCanvasItem) {
				dataTree()->registerMapPositionChange(((DCanvasItem*)*it)->Id(), (*it)->x(), (*it)->y(), (*it)->x()+offx, (*it)->y()+offy);
				((DCanvasItem*)*it)->move((*it)->x()+offx, (*it)->y()+offy);
			}
		for (QCanvasItemList::iterator it(l.begin()); it != l.end(); ++it)
			if ((*it)->rtti() == DCanvasLink::Rtti_DCanvasLink)
				((DCanvasLink*)*it)->updatePos();
		//dataTree()->updateDelta(offx, offy);
	}
}



void DCanvasView::extendIfNecessary() {
	// find the smallest and largest item position
	double minx(canvas()->width()), miny(canvas()->height()), maxx(0), maxy(0);
	QCanvasItemList l(canvas()->allItems());
	for (QCanvasItemList::iterator it(l.begin()); it != l.end(); ++it)
		if ((*it)->rtti() == DCanvasItem::Rtti_DCanvasItem) {
			DCanvasItem* item((DCanvasItem*)*it);
			if (item->x()<minx) minx=item->x();
			if (item->x()+item->width()>maxx) maxx=item->x()+item->width();
			if (item->y()<miny) miny=item->y();
			if (item->y()+item->height()>maxy) maxy=item->y()+item->height();
		}
	extendIfNecessary(minx, miny, maxx, maxy);
}



void DCanvasView::settingsChanged()
{
	QPoint p(-500, -500);
	moveChild( m_textedit, p.x(), p.y() );
	m_textedit->show();

	hideInlineEditor(false);

	DCanvasItem::updateRefColor();

	QCanvasItemList l = canvas()->allItems();
	QCanvasItemList::iterator it;
	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

		DCanvasItem* obj = (DCanvasItem*) (*it);
		obj->updatePos();
		//obj->updateLink();
	}

	QPen pen;
	pen.setStyle( Qt::DotLine );
	canvas()->setBackgroundColor( dataTree()->m_col_background );
	pen.setColor( QColor( Qt::black ) );
	m_rubberRect->setPen( pen );
}

void DCanvasView::removeSelectedItems()
{
	disconnect(dataTree(), SIGNAL(itemSelected(int, DGuiView*)),
			this, SLOT( updateSelectedItem(int, DGuiView*)) );

	if (m_selectedList.count() > 0) dataTree()->newDelta();
	while ( m_selectedList.count() > 0 )
	{
		dataTree()->removeItem( m_selectedList.front() );
	}

	connect(dataTree(), SIGNAL(itemSelected(int, DGuiView*)),
			this, SLOT( updateSelectedItem(int, DGuiView*)) );
}

// Do not use directly, it is called by the data
void DCanvasView::createItem( int idx )
{
	m_lastitemtoselect = idx;

	DCanvasItem* item(0);
	{
		ExtensionLock lock; // don't extend during creation
		item = new DCanvasItem(this, idx);
	}

	if (!item) return;

	registerItem(item);
	if (dataItem(idx)->x() == 0 && dataItem(idx)->y() == 0)
	{
		// the change is added to the delta create automatically
		dataTree()->registerMapPositionChange(idx, m_moveStart.x(), m_moveStart.y(), m_moveStart.x(), m_moveStart.y());
		item->move( (double) m_moveStart.x(), (double) m_moveStart.y());
	}

	item->show();

	// WARNING: the following will not work, as the objects are not linked for the moment :)
	//if (Settings::automode()) tidyClean();
}

void DCanvasView::setActionType(ActionType action)
{
	deselectAll();
	dataTree()->setItemSelected( DItem::NOITEM, this );

	m_currentAction = action;
	m_justSelectedSomething = false;

	switch (m_currentAction)
	{
		case act_point:
			setCursor(ArrowCursor);
			break;
		case act_link:
			setCursor(CrossCursor);
			break;
		case act_sort:
			setCursor(PointingHandCursor);
			break;
		case act_scroll:
			setCursor(SizeAllCursor);
			break;
		default:
			break;
	}

	m_rubberRect->move( -10, -10 );
	m_rubberRect->setSize( 10, 10);

	m_rubberLine->setPoints(0,0, 0,0);

	if (m_currentAction == act_link)
	{
		m_rubberLine->setPen( QColor(Qt::black) );
	}

	if (m_currentAction != act_sort)
	{
		if (m_itemtosort != DItem::NOITEM)
		{
			canvasItem(m_itemtosort)->setSelected(false);
			for (unsigned int i=0; i<dataItem(m_itemtosort)->countChildren(); i++)
			{
				canvasItem(dataItem(m_itemtosort)->childNum(i))->setDisplayIdx(false);
			}
		}
	}
	emit actionType(action);
}

void DCanvasView::updateItem(int id)
{
	ExtensionLock lock;
	DCanvasItem* item = canvasItem(id);
	if (!item)
	{
		//kdWarning()<<"DCanvasView::updateItem unknown item (probably updating a document) "<<id<<endl;
		return;
	}

	//kdWarning()<<"DCanvasView::updateItem "<<id<<" "<<item<<endl;

	item->updatePos();
	//item->updateLink();
}

void DCanvasView::plug()
{
	if (!dataTree()) return;
	connect(dataTree(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()) );
	connect(dataTree(), SIGNAL(itemChanged(int)), this, SLOT(updateItem(int)) );
	connect(dataTree(), SIGNAL(itemCreated(int)), this, SLOT(createItem(int)) );
	connect(dataTree(), SIGNAL(itemRemoved(int)), this, SLOT(removeItem(int)) );
	connect(dataTree(), SIGNAL(refChanged(int, int, bool)), this, SLOT(changeRef(int, int, bool)) );
	connect(dataTree(), SIGNAL(itemSelected(int, DGuiView*)), this, SLOT(updateSelectedItem(int, DGuiView*)) );

	// load the document settings
	settingsChanged();
}

void DCanvasView::unplug()
{
	if (!dataTree()) return;
	disconnect(dataTree(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged()) );
	disconnect(dataTree(), SIGNAL(itemChanged(int)), this, SLOT(updateItem(int)) );
	disconnect(dataTree(), SIGNAL(itemCreated(int)), this, SLOT(createItem(int)) );
	disconnect(dataTree(), SIGNAL(itemRemoved(int)), this, SLOT(removeItem(int)) );
	disconnect(dataTree(), SIGNAL(refChanged(int, int, bool)), this, SLOT(changeRef(int, int, bool)) );
	disconnect(dataTree(), SIGNAL(itemSelected(int, DGuiView*)), this, SLOT( updateSelectedItem(int, DGuiView*)) );
}

void DCanvasView::updateSelectedItem(int id, DGuiView* view)
{
	// somewhere else this view has requested a change
	if (view == this) return;

	if (id == DItem::NOITEM)
	{
		deselectAll();
	}
	else if (oneItemIsSelected())
	{
		if (m_selectedList[0] != id)
		{
			deselectAll();
			addItemToSelection(id);
		}
	}
	else
	{
		deselectAll();
		addItemToSelection(id);
	}

	makeObjectVisible( id );
	//centerOnObject( id );

	// another view requested the change .. notify those watching the map
	emit selectionChanged();
}

void DCanvasView::removeItem(int id)
{
	unregisterItem(id);
	m_selectedList.remove(id);
	if (m_itemtosort == id) m_itemtosort = DItem::NOITEM;
}

void DCanvasView::drawContents( QPainter* p, int cx, int cy, int cw, int ch)
{
	QCanvasView::drawContents(p, cx, cy, cw, ch);

	// TODO is that needed anymore ?
	// emit redrawDone();
}

void DCanvasView::tidyClean()
{
	ExtensionLock lock(this);
	dataTree()->docChanged();

	if (dataTree()->rootID() != DItem::NOITEM) dataTree()->newDelta();

	QCanvasItemList l = canvas()->allItems();
	QCanvasItemList::iterator it;
	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

		DCanvasItem* item = (DCanvasItem*) (*it);
		DDataItem* data = dataItem( item->Id() );

		if ( data->Parent() == DItem::NOITEM )
		{
			if (data->countChildren() <= 0) continue;

			int idxmiddle = 0;
			DDataItem::Coord cumulated = 0;
			DDataItem::Coord totalsize = item->sizeH();

			DDataItem::Coord bestratio = (data->countChildren()>0)?canvasItem( data->childNum(0) )->sizeH():0;
			bestratio = ABS(totalsize-2*bestratio);

			for (unsigned int i=0; i<data->countChildren(); i++)
			{
				cumulated += canvasItem( data->childNum(i) )->sizeH();

				DDataItem::Coord newratio = ABS(totalsize-2*cumulated);

				if (newratio > bestratio)
				{
					idxmiddle = i-1;
					if (idxmiddle < 0) idxmiddle = 0;
					cumulated -= canvasItem( data->childNum(i) )->sizeH();
					break;
				}

				bestratio = newratio;
			}

			DDataItem::Coord sizeleft = cumulated;
			DDataItem::Coord sizeright = totalsize - cumulated;

			DDataItem::Coord orig = 0;

			// process the branches on the left
			orig = data->y() + item->height()/2 + NESTING/2 - sizeleft/2;
			for (int i=0; i<=idxmiddle; i++)
			{
				DCanvasItem* tmpitem = canvasItem( data->childNum(i) );
				//tmpitem->backupPosition();
				tmpitem->setPosition((int) item->x() - BINSPACE - tmpitem->width(), orig, false);
				//dataTree()->registerMapPositionChange(*m_it, x-dx, y-dy, x, y);
				orig += tmpitem->sizeH();
			}

			// process the branches on the right
			// if  orig = data->y() + item->height()/2 + NESTING/2 - sizeright/2; else
			if (Settings::reorgCyclicMap()) orig = data->y() + item->height()/2 - NESTING/2 + sizeright/2;
			else orig = data->y() + item->height()/2 + NESTING/2 - sizeright/2;

			for (unsigned int i=idxmiddle+1; i<data->countChildren(); i++)
			{
				DCanvasItem* tmpitem = canvasItem( data->childNum(i) );
				//tmpitem->backupPosition();
				tmpitem->setPosition((int) item->x() + BINSPACE + item->width(), orig, true);
				//dataTree()->registerMapPositionChange(*m_it, x-dx, y-dy, x, y);
				if (Settings::reorgCyclicMap()) orig -= tmpitem->sizeH();
				else orig += tmpitem->sizeH();
			}
		}
	}
}

void DCanvasView::tidyCanvas()
{
	ExtensionLock lock(this);
	dataTree()->docChanged();
	int result = KMessageBox::warningContinueCancel(
			this,
			i18n("KDissert is about to reorganize the whole mindmap using the smart(tm) reorganization heuristic. It can take a lot of time and give bad results on huge mindmaps. Proceed?"),
			i18n("Reorganize Mindmap"),
			i18n("Reorganize"),
			"kdissert_smart_reorganization");

	if (result != KMessageBox::Continue) return;

	if (dataTree()->rootID() != DItem::NOITEM) dataTree()->newDelta();

	// clear the selection
	deselectAll();

	QCanvasItemList l = canvas()->allItems();
	QCanvasItemList::iterator it;
	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

		DCanvasItem* item = (DCanvasItem*) (*it);
		DDataItem* data = dataItem( item->Id() );

		if ( data->Parent() == DItem::NOITEM )
		{
			// TODO: the unconnected items are spent way too far
			/*if ( data->countChildren() == 0 )
			  {
			  oldy += 10;
			  item->move(10, oldy+10);
			  oldy += item->height();
			  }*/
			//else
			{
				item->expandSubtree(-2.*PI, 0);
			}
		}
		//item->updatePos();
	}
	tidyUnclutter(); 
}

void DCanvasView::tidyUnclutter()
{
	ExtensionLock lock(this);
	dataTree()->docChanged();
	//kdWarning()<<"unclutter"<<endl;

	if (m_selectedList.count() > 0) dataTree()->newDelta();

	QCanvasItemList l = canvas()->allItems();

	bool cluttered = true;
	int count = 0;

	while (cluttered && count < 5)
	{
		//kdWarning()<<"iteration: "<<count<<endl;

		// conditions for stopping
		cluttered = false;
		count++;

		// compute the changes
		QCanvasItemList::iterator it;
		QCanvasItemList::iterator end = l.end();
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem *item = (DCanvasItem*) *it;
			item->m_vecX = 0;
			item->m_vecY = 0;

			double miditem_x = item->x()+item->width()/2.;
			double miditem_y = item->y()+item->height()/2.;

			// find overlapping items
			QCanvasItemList::iterator tit;
			for ( tit = l.begin(); tit != end; ++tit  )
			{
				//kdWarning() <<"collision"<<endl;
				if ( (*tit)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

				// To pronounce with a strong German accent
				DCanvasItem *intruder = (DCanvasItem*) *tit;
				// If you read the line above and you found it funny, send me an email :)
				// At least i would know that people read the source code from times to times

				if (item == intruder) continue;

				double midintruder_x = intruder->x()+intruder->width()/2.;
				double midintruder_y = intruder->y()+intruder->height()/2.;

				double lenx = (item->width() + intruder->width())/2.;
				double leny = (item->height() + intruder->height())/2.;

				if ( ABS(miditem_x - midintruder_x) > 15+lenx || ABS(miditem_y - midintruder_y) > 15+leny )
					continue;

				cluttered = true;

				if (midintruder_x >= miditem_x)
				{
					item->m_vecX -= 1;
				}
				else
				{
					item->m_vecX += 1;
				}

				if (midintruder_y >= miditem_y)
				{
					item->m_vecY -= 1;
				}
				else
				{
					item->m_vecY += 1;
				}
			}
		}

		// apply the changes
		for ( it = l.begin(); it != end; ++it  )
		{
			if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

			DCanvasItem *item = (DCanvasItem*) *it;
			item->move(item->x() + item->m_vecX, item->y() + item->m_vecY);
		}
	}
}

QRect DCanvasView::canvasSize()
{
	double minx = -1, miny = -1, maxx = -1, maxy = -1;

	if ( dataTree()->rootID() == DItem::NOITEM )
	{
		return canvas()->rect();
	}

	DCanvasItem* root = canvasItem(dataTree()->rootID());
	minx = root->x();
	miny = root->y();
	maxx = root->x()+root->width();
	maxy = root->y()+root->height();

	// return the minimum window containing the mindmap
	QCanvasItemList l = canvas()->allItems();
	QCanvasItemList::iterator it;
	QCanvasItemList::iterator end = l.end();
	for ( it = l.begin(); it != end; ++it  )
	{
		if ( (*it)->rtti() != DCanvasItem::Rtti_DCanvasItem ) continue;

		DCanvasItem* obj = (DCanvasItem*) (*it);

		minx = MIN( minx, obj->x()) ;
		maxx = MAX( maxx, obj->x()+obj->width() );
		miny = MIN( miny, obj->y()) ;
		maxy = MAX( maxy, obj->y()+obj->height() );
	}

	double offset = 20;
	minx -= offset;
	maxx += offset;
	miny -= offset;
	maxy += offset;

	minx = MAX(minx, 0);
	miny = MAX(miny, 0);
	maxx = MIN(maxx, canvas()->width());
	maxy = MIN(maxy, canvas()->height());

	return QRect((int) minx, (int) miny, (int) (maxx-minx), (int) (maxy-miny) );
}

void DCanvasView::changeRef(int orig, int dest, bool add)
{
	DCanvasItem *item = canvasItem(orig);
	if (!item)
	{
		kdWarning()<<"BUG DCanvasView::changeRef "<<orig<<endl;
		return;
	}

	if (add) item->addRef(dest);
	else item->rmRef(dest);
}

void DCanvasView::focusOnNextRoot()
{
	int prev_root = DItem::NOITEM;
	if (oneItemIsSelected())
	{
		// there is already a root selected
		DDataItem *item = dataItem( m_selectedList[0] );
		if ( item->Parent() == DItem::NOITEM )
			prev_root = m_selectedList[0];
	}
	else
	{
		// set the focus on the main root first
		if ( dataTree()->rootID() == DItem::NOITEM ) return;

		deselectAll();
		addItemToSelection( dataTree()->rootID() );
		centerOnObject( dataTree()->rootID() );
		dataTree()->setItemSelected( dataTree()->rootID(), this );
		return;
	}

	int rootfound = DItem::NOITEM;
	QMapIterator<int, DItem*> it;
	for (it = m_map.begin(); it != m_map.end(); ++it)
	{
		// cycle and find the previous root, if any
		int id = (*it)->Id();

		if (prev_root == DItem::NOITEM)
		{
			// we have found another root, great
			DDataItem *item = dataItem(id);
			if (item->Parent() == DItem::NOITEM)
			{
				rootfound = id;
				break;
			}

		}
		else if (prev_root == id)
		{
			// we have found the previous root, continue
			prev_root = DItem::NOITEM;
		}
	}

	// still no root ? then get the first one
	if (rootfound == DItem::NOITEM)
	{
		for (it = m_map.begin(); it != m_map.end(); ++it)
		{
			int id = (*it)->Id();
			if (dataItem(id)->Parent() == DItem::NOITEM)
			{
				rootfound = id;
				break;
			}
		}
	}

	if (rootfound != DItem::NOITEM)
	{
		deselectAll();
		addItemToSelection( rootfound );
		centerOnObject( rootfound );
		dataTree()->setItemSelected( rootfound, this );
	}
}

void DCanvasView::centerOnObject( int obj )
{
	DCanvasItem* citem = canvasItem(obj);
	if (!citem) return;

	QRect r = worldMatrix().map( citem->rect() );
	center( (int) (r.x()+r.width()/2), (int) (r.y()+r.height()/2));
}

void DCanvasView::makeObjectVisible( int obj )
{
	DCanvasItem* citem = canvasItem(obj);
	if (!citem) return;

	QRect r = worldMatrix().map( citem->rect() );
	ensureVisible( (int) (r.x()+r.width()/2), (int) (r.y()+r.height()/2), r.width()/2, r.height()/2 );
}

void DCanvasView::updateObjLinks( int obj )
{
	DDataItem *item = dataItem( obj );
	if (!item) return;

	for (unsigned int i=0; i<item->countChildren(); i++)
	{
		DCanvasItem *citem = canvasItem( item->childNum(i) );
		citem->updateLink();
	}
}

void DCanvasView::dragEnterEvent( QDragEnterEvent *event )
{
	event->accept( /* TODO QImageDrag::canDecode(event) ||*/ QTextDrag::canDecode(event) );
}

void DCanvasView::dropEvent( QDropEvent *event )
{
	QString txt;
	KURL::List urllist;
	QPoint p = inverseWorldMatrix().map( viewportToContents( event->pos() ) );
	m_moveStart = p;
	int id = locateItem( viewportToContents( event->pos() ) );
	if ( KURLDrag::decode(event, urllist) )
	{
		kdDebug()<<"urls dropped !! "<<urllist.count()<<endl;
		if (! urllist.count()) return;

		if (id == DItem::NOITEM)
		{
			dataTree()->newDelta();

			id = dataTree()->createItem();
			if (id == DItem::NOITEM) kdDebug()<<"BUG in DCanvasView::dropEvent"<<endl;

			DDataItem *item = dataItem( id );

			KURL url = urllist[0];
			QString txt = url.url();
			item->m_summary = txt.left(150);

			if (urllist.count() == 1)
			{
				if ( txt.endsWith(".jpg", false) || txt.endsWith(".jpeg", false) || txt.endsWith(".png", false) )
				{
					item->loadPix(txt);
				}
			}
			else
			{
				for (unsigned int i=0; i<urllist.count(); ++i)
				{
					KURL turl = urllist[i];

					URLObject obj;
					obj.m_url = turl.url();
					item->m_urllist.append( obj );
				}
			}

			canvasItem( id )->move( p.x(), p.y() );
			//canvasItem( id )->updatePos();

			if (Settings::automode()) tidyClean();
		}
		else
		{
			DDataItem* item = dataItem( id );

			if (urllist.count() == 1)
			{
				QString txt = urllist[0].url();
				if ( txt.endsWith(".jpg", false) || txt.endsWith(".jpeg", false) || txt.endsWith(".png", false) )
				{
					if (item->m_pix.isNull())
						item->loadPix(txt);
				}
			}
			else
			{
				for (unsigned int i=0; i<urllist.count(); ++i)
				{
					KURL turl = urllist[i];

					URLObject obj;
					obj.m_url = turl.url();
					item->m_urllist.append( obj );
				}
			}
		}
		canvasItem( id )->updatePos();
		dataItem( id )->setChanged();
		dataTree()->setItemSelected( DItem::NOITEM, NULL );
		dataTree()->setItemSelected( id, NULL );
		dataTree()->notifyChildChange( id );
	}
	else if ( QTextDrag::decode(event, txt) )
	{
		// We are merging richtext - this is not perfect for the moment,
		// however:
		// 1 qtextedit is able to make the text valid 
		// 2 we need something looking like richtext (even if buggy) for the tooltips

		if (id == DItem::NOITEM)
		{
			dataTree()->newDelta();

			id = dataTree()->createItem();
			if (id == DItem::NOITEM) kdDebug()<<"BUG in DCanvasView::dropEvent"<<endl;

			dataItem( id )->m_summary = txt.left(80);

			if (!txt.endsWith("</html>") || !txt.startsWith("<html>"))
			{
				dataItem( id )->m_text.append( "<html><body><p>" );
				dataItem( id )->m_text.append( txt );
				dataItem( id )->m_text.append( "</p></body></html>" );
			}
			else
			{
				dataItem( id )->m_text = txt;
			}

			canvasItem( id )->move( p.x(), p.y() );

			if (Settings::automode()) tidyClean();
		}
		else
		{
			QString wholetext = dataItem( id )->m_text;
			if (wholetext.endsWith("</html>"))
				wholetext.truncate( wholetext.length() - 7 );

			if (!wholetext.startsWith("<html>"))
				wholetext.prepend("<html>");

			if (txt.startsWith("<html>"))
				wholetext.append( txt.right( txt.length() - 6 ) );
			else
				wholetext.append( txt );

			if (!txt.endsWith("</html>"))
				wholetext.append( "</html>" );

			dataItem( id )->m_text = wholetext;
		}
		dataItem( id )->setChanged();
		dataTree()->setItemSelected( DItem::NOITEM, NULL );
		dataTree()->setItemSelected( id, NULL );
		dataTree()->notifyChildChange( id );
	}
}

void DCanvasView::hideInlineEditor(bool save)
{
	if (!m_textedit->isShown()) return;

	if (!oneItemIsSelected()) return;

	int id = m_selectedList[0];

	if (save)
	{
		dataItem(id)->m_summary = m_textedit->text();
		dataTree()->notifyChildChange(id);
	}

	m_textedit->hide();
	setFocus();
}

#include "DCanvasView.moc"
