/*-*-c++-*-
 * $Id: cgraphmanager.cpp,v 1.1 2002/01/28 15:38:32 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (let@users.sourceforge.net)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#include "cgraphmanager.h"
#include "cgraphobject.h"
#include "crashtypes.h"
#include "cgraphbutton.h"
#include <assert.h>
#ifdef WIN32
#include <malloc.h>
#else
#include <alloca.h>
#endif
#include <math.h>
#include <stdlib.h>

#include <qapplication.h>
#include <qlistview.h>
#include <qframe.h>
#include <qheader.h>
#include <qpushbutton.h>
#include <qpainter.h>

#include <iostream.h>

#define PEN_WIDTH 2

// milliseconds between two scroll units during automatic scrolling
#define SCROLL_DELAY 40

#define SCROLL_INTERVALL_HOR 4
#define SCROLL_INTERVALL_VER 4

int sortFunc(const void* x, const void* y);
int sortFunc(const void* x, const void* y)
{
	if ((*((CGraphObject**)x))->y() < (*((CGraphObject**)y))->y())
		return -1;
	if ((*((CGraphObject**)x))->y() > (*((CGraphObject**)y))->y())
		return 1;
	return 0;
}

CGraphManager::CGraphManager(QWidget* parent, const char* name)
	: QScrollView(parent, name, WDestructiveClose)
	, oSelectedObject(0)
	, oLastAddedObject(0)
{
	oChildList.setAutoDelete(true);
	oMatrix.setAutoDelete(true);
	oMainWidget = new QWidget(viewport());
	oMainWidget->setMouseTracking(true);
	oMainWidget->installEventFilter(this);
	oMainWidget->show();
	mainWidget()->setBackgroundColor(Qt::white);
	
	QScrollView::addChild(oMainWidget);
	oMainWidget->resize(8000, 8000);
	setMaximumSize(QApplication::desktop()->size());
	
	oGridX = 150;
	oGridY = 30;	
	oCurrentHighlight.obj = 0;
	
	connect(&oPostTimer, SIGNAL(timeout()), SLOT(slotPostTimeout()));
	connect(&oScrollTimer, SIGNAL(timeout()), SLOT(slotScrollTimeout()));
	
/*
	CGraphObject *a, *b, *c, *d, *e, *f, *g, *h, *i, *j, *k;
	QPushButton* button;
	CGraphButton* gb;
	a = addWidget(new QPushButton("Button 1", oMainWidget));
	button = new QPushButton("Button 2", oMainWidget); button->resize(100, 50);
	b = addWidget(button);
	button = new QPushButton("Button 3", oMainWidget); button->resize(100, 40);
	c = addWidget(button);
	button = new QPushButton("Button 4", oMainWidget); button->resize(100, 70);
	d = addWidget(button);
	e = addWidget(new QPushButton("Button 5", oMainWidget));
	button = new QPushButton("Button 6", oMainWidget); button->resize(100, 90);
	f = addWidget(button);
	g = addWidget(new QPushButton("Button 7", oMainWidget));
	h = addWidget(new QPushButton("Button 8", oMainWidget));
	i = addWidget(new QPushButton("Button 9", oMainWidget));
	j = addWidget(new QPushButton("Button A", oMainWidget));
	j->setCaption("struct *(task_struct*)");
	gb = new CGraphButton(oMainWidget);
	k = addWidget(gb);
	
	attach(a, b, QString("type a"));
	attach(a, c, QString("type b"));
	attach(b, d, QString("type c"));
	attach(b, e, QString("*(struct tast_struct*)"));
	attach(c, f, QString("a_struct*"));
	attach(c, g, QString("ab_ptr*"));
	attach(c, h, QString("size_t"));
	attach(c, i, QString("struct task_struct*"));
	attach(h, j, QString("int*"));
	
	QListView* lv = new QListView;
	lv->setFrameStyle(QFrame::NoFrame);
	lv->header()->hide();
	lv->resize(200, 200);
	lv->addColumn(QString::null);
	QListViewItem* lvi;
	lvi = new QListViewItem(lv, "Item 1");
	g->setWidget(lv);
	
	layout();
	
	attach(a, k, QString("double"));
	layoutObject(k);
*/
}

CGraphManager::~CGraphManager()
{
}

void
CGraphManager::viewportResizeEvent(QResizeEvent*)
{
	postUpdate();
}

void
CGraphManager::redraw()
{
	QPixmap pm(viewport()->width(), viewport()->height());
	pm.fill(mainWidget()->backgroundColor());
	QPainter p(&pm);
	QPen pen(QColor(100, 100, 255));
	pen.setWidth(PEN_WIDTH);
	p.setPen(pen);
	p.translate(-contentsX(), -contentsY());
	
	int col = oMatrix.count();
	for (int n = 0; n < col; n++) {
		int row = oMatrix[n]->count();
		for (int i = 0; i < row; i++) {
			connectNodes(&p, oMatrix[n]->find(i));
		}
	}
	
	p.flush();
	bitBlt(mainWidget(), contentsX(), contentsY(), &pm, 0, 0, pm.width(), pm.height(), CopyROP, true);
}

CGraphObject*
CGraphManager::addWidget(QWidget* child, const QString title)
{
	QPoint pos(10, 10);
	CGraphObject* obj = new CGraphObject(this);
	obj->setWidget(child);
	obj->setCaption(title);
	connect(obj, SIGNAL(sigClicked(CGraphObject*)), SLOT(slotClicked(CGraphObject*)));
	connect(obj, SIGNAL(sigMoved(CGraphObject*)), SLOT(slotChildMoved(CGraphObject*)));
	connect(obj, SIGNAL(sigClose(CGraphObject*)), SLOT(slotCloseChild(CGraphObject*)));
	connect(obj, SIGNAL(sigDisc(CGraphObject*)), SLOT(slotDiscChild(CGraphObject*)));
	
	// Position for the new child. Use layout() if there are dependencies between objects.
	CGraphObject* it = oChildList.last();
	if (it != (CGraphObject*) 0) {
		int h = it->y() + it->height();
		if (h > pos.y()) {
			pos.setY(h + 10);
			pos.setX(10);
		}
	}
	
	obj->move(pos);
	oChildList.append(obj);
	
	oLastAddedObject = obj;
	
	// focus
	slotClicked(obj);
	
	return obj;
}

void
CGraphManager::slotClicked(CGraphObject* obj)
{
	if (oSelectedObject != 0) {
		oSelectedObject->setPaintFocus(false);
	}
	obj->setPaintFocus(true);

	oSelectedObject = obj;
	oSelectedObject->setFocus();
	
	// prepare moving
	oOldChildX = obj->x();
	oOldChildY = obj->y();
}

void
CGraphManager::layout()
{
	CGraphObject* walker = oChildList.first();
	QList<CGraphObject> rootList;
	
	if (walker == 0) { // nothing to do
		return;
	}
	
	// make a list with all root nodes
	while (walker != 0) {
		if (walker->parent() == 0) {
			rootList.append(walker);
		}
		
		walker = oChildList.next();
	}
	
	// layout line by line
	layoutMatrix(rootList);
/*
	printMatrix();
	printChildList();
*/

	// lines
	walker = oChildList.first();
	while (walker != 0) {
		calculateEdges(walker);
		walker = oChildList.next();
	}	

	if (oLastAddedObject != 0) {
		adjustNewObject(oLastAddedObject);
		oLastAddedObject = 0;
	}
	else if (oSelectedObject != 0) {
		adjustNewObject(oSelectedObject);
	}
	
	postUpdate();
}

void
CGraphManager::adjustNewObject(CGraphObject* p)
{
	int x = -1, y = -1;
	
	// horizontal
	if (p->x() + p->width() > contentsX() + visibleWidth()) {
		x = contentsX() + visibleWidth() - p->width() - 10;
	}
	if (p->x() < contentsX() || x < contentsX() && x != -1) {
		x = contentsX() + 10;
	}
	
	// vertical
	if (p->y() + p->height() > contentsY() + visibleHeight()) {
		y = contentsY() + visibleHeight() - p->height() - 10;
	}
	if (p->y() < contentsY() || y < contentsY() && y != -1) {
		y = contentsY() + 10;
	}
	
	// new coordinates ?
	if (x != -1 || y != -1) {
		if (x == -1) {
			x = p->x();
		}
		else if (y == -1) {
			y = p->y();
		}
		
		scrollBy(p->x() - x, p->y() - y);
	}
	
	// emulate mouse click on this object
	slotClicked(p);
}

void
CGraphManager::calculateEdges(CGraphObject* obj)
{
	if (obj == 0) {
		return;
	}
	
	int x1 = obj->x() + obj->width() / 2;
	int y1 = obj->y() + obj->height() / 2;
	int xp1=x1, yp1=y1, xp2, yp2;
	
	obj->destinations().resize(obj->childList()->count());
	
	CGraphObject* walker = obj->childList()->first();
	int count = 0;
	while (walker != 0) {
		int x2 = walker->x() + walker->width() / 2;
		int y2 = walker->y() + walker->height() / 2;
		int qdr = findQuadrant(x1, y1, x2, y2);
		
		if (qdr > 0) {
			int y = int(tanAlpha(x1, y1, x2, y2) * obj->width() / 2);
			if (y > obj->height() / 2) {
				int y = int(tanAlpha(y1, x1, y2, x2) * obj->height() / 2);
				if (qdr == 2 || qdr == 3) {
					xp1 = obj->x() + obj->width() / 2 + y;
				}
				else {
					xp1 = obj->x() + obj->width() / 2 - y;
				}
				if (qdr == 3 || qdr == 4) {
					yp1 = obj->y() + obj->height();
				}
				else {
					yp1 = obj->y();
				}
			}
			else {
				if (qdr == 1 || qdr == 4) {
					xp1 = obj->x();
				}
				else {
					xp1 = obj->x() + obj->width();
				}
				if (qdr == 1 || qdr == 2) {
					yp1 = obj->y() + obj->height() / 2 - y;
				}
				else {
					yp1 = obj->y() + obj->height() / 2 + y;
				}
			}
		}
		else {
			if (x1 == x2) {
				xp1 = obj->x() + obj->width() / 2;
				yp1 = (y1 > y2) ? obj->y() : obj->y() + obj->height();
			}
			else {
				xp1 = (x1 > x2) ? obj->x() : obj->x() + obj->width();
				yp1 = obj->y() + obj->height() / 2;
			}
		}
		
		xp2 = x2;
		yp2 = y2;
		// x2,y2
		qdr = findQuadrant(x2, y2, x1, y1);
		if (qdr > 0) {
			int y = int(tanAlpha(x1, y1, x2, y2) * walker->width() / 2);
			if (y >  walker->height() / 2) {
				y = int(tanAlpha(y1, x1, y2, x2) * walker->height() / 2);
				if (qdr == 2 || qdr == 3) {
					xp2 = walker->x() + walker->width() / 2 + y;
				}
				else {
					xp2 = walker->x() + walker->width() / 2 - y;
				}
				if (qdr == 3 || qdr == 4) {
					yp2 = walker->y() + walker->height();
				}
				else {
					yp2 = walker->y();
				}
			}
			else {
				if (qdr == 1 || qdr == 4) {
					xp2 = walker->x();
				}
				else {
					xp2 = walker->x() + walker->width();
				}
				if (qdr == 1 || qdr == 2) {
					yp2 = walker->y() + walker->height() / 2 - y;
				}
				else {
					yp2 = walker->y() + walker->height() / 2 + y;
				}
			}
		}
		else {
			if (x1 == x2) {
				xp2 = walker->x() + walker->width() / 2;
				yp2 = (y1 > y2) ? walker->y() + walker->height() : walker->y();
			}
			else {
				xp2 = (x1 > x2) ? walker->x() + walker->width() : walker->x();
				yp2 = walker->y() + walker->height() / 2;
			}
		}
		
		CLine& dest = obj->destinations().at(count++);
		dest.x1 = xp1;
		dest.y1 = yp1;
		dest.x2 = xp2;
		dest.y2 = yp2;
		dest.obj = walker;
		
		walker = obj->childList()->next();
	}
}

void
CGraphManager::connectNodes(QPainter* p, CGraphObject* obj)
{
	int count = obj->destinations().count();
	for (int i = 0; i < count; i++) {
		const CLine& d = obj->destinations().at(i);
		drawArrow(p, d.x1, d.y1, d.x2, d.y2, d.obj->wireLabel());
	}
}

void
CGraphManager::postUpdate(int time)
{
	oPostTimer.start(time, true);
	
	// enable repainting even if there is an open dialog
	if (!oPostTimer.isActive()) {
		slotPostTimeout();
	}
}

void
CGraphManager::slotPostTimeout()
{
	redraw();
}

void
CGraphManager::drawArrow(QPainter* p, int x1, int y1, int x2, int y2, QString str)
{
	int dx = x2 - x1;
	int dy = y2 - y1;
	const double alpha = atan2(dy, dx);
	
	p->drawLine(x1, y1, x2, y2);
	
	dx = (int) (10 * cos(alpha + 0.3));
	dy = (int) (10 * sin(alpha + 0.3));
	p->drawLine(x2, y2, x2 - dx, y2 - dy);
	
	dx = (int) (10 * cos(alpha - 0.3));
	dy = (int) (10 * sin(alpha - 0.3));
	p->drawLine(x2, y2, x2 - dx, y2 - dy);
	
	// caption
	if (str.length() > 0) {
		dx = x2 - x1;
		dy = y2 - y1;
		int len = (int) (sqrt(dx*dx + dy*dy));
		len = len / 2;
		dx = (int) (len * cos(alpha));
		dy = (int) (len * sin(alpha));
		QFont f = p->font();
		f.setBold(true);
		p->setFont(f);
		p->save();
		p->setPen(black);
		QFontMetrics fm(p->font());
		len = fm.width(str);
		int h = fm.height();
		p->translate(x2 - dx, y2 - dy - 8);
		p->fillRect(-len/2 - 1, 2, len + 2, h - 2, white);
		p->drawText(-len/2, 0, len, h, Qt::AlignCenter, str);
		p->resetXForm();
		p->restore();
	}
}

void
CGraphManager::attach(CGraphObject* parent, CGraphObject* child, const QString& str)
{
	// remove child from a probably existing parent
	if (child->parent() != (CGraphObject*) 0) {
		child->parent()->removeChild(child);
	}
	
	parent->addChild(child);
	child->setWireLabel(str);
}

void
CGraphManager::detach(CGraphObject* child)
{
	if (child->parent() != 0) {
		child->parent()->removeChild(child);
	}
}

void
CGraphManager::layoutMatrix(QList<CGraphObject>& root)
{
	// create matrix
	oMatrix.clear();
	CGraphObject* walker = root.first();
	while (walker != 0) {
		layoutTraverse(walker, 0U);
		walker = root.next();
	}
	
	int highestCol = layoutHighestCol();
	int x = layoutMaxX(highestCol);
	int row, col, y;
	
	// layout highest column
	row = oMatrix[highestCol]->count();
	y = 0;
	for (int i = 0; i < row; i++) {
		CGraphObject* obj = oMatrix[highestCol]->find(i);
		obj->move(x + 10, y + 10);
		y += obj->height() + oGridY;
	}
	
	// layout columns left of highestCol
	int n;
	for (n = highestCol - 1; n >= 0; n--) {
		row = oMatrix[n]->count();
		x = layoutMaxX(n);
		y = 0;
		for (int i = 0; i < row; i++) {
			CGraphObject* obj = oMatrix[n]->find(i);
			int  my = layoutParentYPos(obj);
			if (my > y) {
				y = my;
			}
			obj->move(x + 10, y + 10);
			y += obj->height() + oGridY;
		}
	}
	
	// layout columns right of highestCol
	col = oMatrix.count() - 1;
	for (n = highestCol; n < col; n++) {
		row = oMatrix[n]->count();
		x = layoutMaxX(n + 1);
		y = 0;
		for (int i = 0; i < row; i++) {
			CGraphObject* obj = oMatrix[n]->find(i);
			int my = layoutChildYPos(obj);
			CGraphObject* walker = obj->childList()->first();
			if (my > y) {
				y = my;
			}
			while (walker != 0) {
				walker->move(x + 10, y + 10);
				y += walker->height() + oGridY;
				walker = obj->childList()->next();
			}
		}
	}
}

void
CGraphManager::layoutTraverse(CGraphObject* root, unsigned level)
{
	if (oMatrix.count() == level) { // create a new column
		oMatrix.insert(level, new QIntDict<CGraphObject>);
	}
	
	CGraphObject* walker = root->childList()->first();
	while (walker != 0) {
		layoutTraverse(walker, level + 1);
		walker = root->childList()->next();
	}
	
	oMatrix[level]->insert(oMatrix[level]->count(), root);
}

void
CGraphManager::print()
{
	unsigned row = oMatrix.count();
	unsigned col = 0;
	
	for (unsigned i = 0; i < row; i++) {
		if (oMatrix[i]->count() > col)
			col = oMatrix[i]->count();
	}
	
	for (unsigned n = 0; n < row; n++) {
		for (unsigned i = 0; i < col; i++) {
			CGraphObject* g = oMatrix[n]->find(i);
			if (g == 0)
				cout << "--------";
			else {
				cout << ((QPushButton*)g->widget())->text();
			}
			cout << " ";
		}
		cout << endl;
	}
}

int
CGraphManager::layoutSizeX(int col)
{
	assert(oMatrix[col] != 0);
	unsigned len = oMatrix[col]->count();
	int x = 0;
	for (unsigned i = 0; i < len; i++) {
		CGraphObject* obj = oMatrix[col]->find(i);
		if (obj->width() > x)
			x = obj->width();
	}
	
	return x;
}

int
CGraphManager::layoutSizeY(int col)
{
	assert(oMatrix[col] != 0);
	unsigned len = oMatrix[col]->count();
	int y = 0;
	for (unsigned i = 0; i < len; i++) {
		CGraphObject* obj = oMatrix[col]->find(i);
		y += obj->height() + oGridY;
	}
	
	return y - oGridY;
}

int
CGraphManager::layoutMaxX(int toCol)
{
	// exclude the given column
	bool calcAll = false;
	
	if (toCol < 0) {
		toCol = oMatrix.count();
	}
	if (toCol == (int) oMatrix.count()) {
		toCol--;
		calcAll = true;
	}
	
	int width = 0;
	if (toCol > 0) {
		for (int i = 0; i < toCol; i++) {
			width += layoutSizeX(i) + oGridX;
		}
	}
	
	if (calcAll && toCol >= 0) {
		width += layoutSizeX(toCol);
	}
	
	return width;
}

int
CGraphManager::layoutMaxY()
{
	int y = 0;
	unsigned row = oMatrix.count();
	for (unsigned n = 0; n < row; n++) {
		unsigned col = oMatrix[n]->count();
		int tmp = 0;
		for (unsigned i = 0; i < col; i++) {
			tmp += oMatrix[n]->find(i)->height() + oGridY;
		}
		tmp -= oGridY;
		if (tmp > y)
			y = tmp;
	}
	
	return y;
}

int
CGraphManager::layoutParentYPos(CGraphObject* obj)
{
	int count = obj->childList()->count();
	int pos = -1;
	
	if (count > 0) {
		pos = obj->childList()->first()->y() - 10;
	}
	if (count > 1) {
		pos = (pos + obj->childList()->last()->y() + obj->childList()->last()->height() - 10) / 2;
		pos -= obj->height() / 2;
	}
	
	return pos;
}

int
CGraphManager::layoutHighestCol()
{
	int nr = 0, max = 0;
	int col = oMatrix.count();
	for (int n = 0; n < col; n++) {
		int row = oMatrix[n]->count();
		int h = 0;
		for (int i = 0; i < row; i++) {
			h += oMatrix[n]->find(i)->height();
		}
		if (h >= max) {
			max = h;
			nr = n;
		}
	}
	
	return nr;
}

int
CGraphManager::layoutChildYPos(CGraphObject* obj)
{
	CGraphObject* walker = obj->childList()->first();
	int h = 0;
	while (walker != 0) {
		h += walker->height() + oGridY;
		walker = obj->childList()->next();
	}
	int count = obj->childList()->count();
	if (count > 0) {
		h -= oGridY;
	}
	if (count == 0) {
		return -1;
	}
	if (count > 1) {
		h = obj->y() - 10 + obj->height()/2 - h/2;
	}
	else {
		h = obj->y() - 10;
	}
	
	return (h >= 0) ? h : 0;
}

void
CGraphManager::layoutObject(CGraphObject* obj)
{
	// i can't layout a single object without a valid matrix
	if (oMatrix.count() == 0) {
		layout();	
		return;
	}
	
	// X,y-position of the new object
	int x = 0;
	int y = 0;
	
	// find parent
	if (obj->parent() != 0) {
		x = obj->parent()->x() + obj->parent()->width() + oGridX;
	}
	
	// find any object that occupies the horizontal range of the newobject
	
	CGraphObject* walker = oChildList.first();
	CGraphObject** vec = (CGraphObject**) alloca(oChildList.count() * sizeof(CGraphObject*));
	int len = 0;
	while (walker != 0) {
		if (walker != obj) {
			if ((walker->x() + walker->width()) >= x && walker->x() <= (x + obj->width())) {
				vec[len++] = walker;
			}
		}
		walker = oChildList.next();
	}
	
	qsort(vec, len, sizeof(CGraphObject*), sortFunc);
	
	int h = obj->height() + oGridY;
	for (int i = 0; i < len; i++) {
		CGraphObject* tmp = vec[i];
		if ((tmp->y() - y - 10) < h) {
			y = tmp->y() + tmp->height();
		}
		else
			break;
	}
	if (y > 0) {
		y += oGridY;
	}
	
	// find parent and insert new object in matrix
	if (obj->parent() != 0) {
		int col = oMatrix.count();
		for (int n = 0; n < col; n++) {
			int row = oMatrix[n]->count();
			for (int i = 0; i < row; i++) {
				if (oMatrix[n]->find(i) == obj->parent()) {
					if (n+1 == col) {
						oMatrix.insert(n+1, new QIntDict<CGraphObject>);
					}
					oMatrix[n+1]->insert(oMatrix[n+1]->count(), obj);
					n = col;
					break;
				}
			}
		}
	}
	else {
		if (oMatrix[0] == 0) {
			oMatrix.insert(0, new QIntDict<CGraphObject>);
		}
		oMatrix[0]->insert(oMatrix[0]->count(), obj);
	}
	
	obj->move(x, y + 10);
	
	if (oLastAddedObject != 0) {
		adjustNewObject(oLastAddedObject);
		oLastAddedObject = 0;
	}
	oCurrentHighlight.obj = 0;
	
	calculateEdges(obj);
	calculateEdges(obj->parent());
	
	postUpdate();
}

int
CGraphManager::findQuadrant(int x1, int y1, int x2, int y2) const
{
	int q = 0;
	int dx = x2 - x1;
	int dy = y2 - y1;
	
	if (dx != 0 && dy != 0) {
		if (dx < 0 && dy < 0)
			q = 1;
		else if (dx > 0 && dy < 0)
			q = 2;
		else if (dx > 0 && dy > 0)
			q = 3;
		else
			q = 4;
	}
	
	return q;
}

double
CGraphManager::tanAlpha(int x1, int y1, int x2, int y2) const
{
	double dx = x2 - x1;
	double dy = y2 - y1;
	
	return fabs(dy / dx);
}

void
CGraphManager::slotChildMoved(CGraphObject* obj)
{
	oCurrentHighlight.obj = 0;
	calculateEdges(obj);
	calculateEdges(obj->parent());
	postUpdate();
}

void
CGraphManager::removeWidget(CGraphObject* obj)
{
	QListIterator<CGraphObject> it = *obj->childList();
	
	while (*it != 0) {
		removeWidget(*it);
		++it;
	}
	
	if (oSelectedObject == obj) {
		oSelectedObject = 0;
	}
	
	emit sigChildClosed(obj->widget());
	oChildList.remove(obj);
	
	if (!oChildList.count()) { // last child removed
		close();
	}
}

void
CGraphManager::slotCloseChild(CGraphObject* obj)
{
	if (obj->parent() != 0) {
		adjustNewObject(obj->parent());
	}
	detach(obj);
	removeWidget(obj);
	layout();
}

void
CGraphManager::slotDiscChild(CGraphObject* obj)
{
	detach(obj);
	layout();
	adjustNewObject(obj);
}

void
CGraphManager::printMatrix()
{
	cout << endl;
	int col = oMatrix.count();
	for (int n = 0; n < col; n++) {
		int row = oMatrix[n]->count();
		for (int i = 0; i < row; i++) {
			cout << oMatrix[n]->find(i) << "  ";
		}
		cout << endl;
	}
}

void
CGraphManager::printChildList()
{
	cout << endl;
	CGraphObject* obj = oChildList.first();
	
	while (obj != 0) {
		CGraphObject* walker = obj->childList()->first();
		cout << obj << ": ";
		while (walker != 0) {
			cout << walker << "  ";
			walker = obj->childList()->next();
		}
		cout << endl;
		obj = oChildList.next();
	}
}

void
CGraphManager::closeEvent(QCloseEvent* e)
{
	emit sigClosed(this);
	e->accept();
}

bool
CGraphManager::eventFilter(QObject* obj, QEvent* e)
{
	// observe mouse movement in oMainWidget
	if (obj == oMainWidget) {
		if (e->type() == QEvent::MouseMove) {
			QMouseEvent* me = static_cast<QMouseEvent*>(e);
			if (me->state() == MidButton) {
				QPoint p = oOldContentPos - me->globalPos();
				oOldContentPos = me->globalPos();
				scrollBy(p.x(), p.y());
			}
			else {
				highlightEdge(me->pos());
			}
		}
		else if (e->type() == QEvent::MouseButtonPress) {
			QMouseEvent* me = static_cast<QMouseEvent*>(e);
			oOldContentPos = me->globalPos();
		
			if (oCurrentHighlight.obj != 0) {
				if (me->button() == LeftButton) {
					adjustNewObject(oCurrentHighlight.obj->parent());
				}
				else if (me->button() == RightButton) {
					adjustNewObject(oCurrentHighlight.obj);
				}
			}
		}
		else if (e->type() == QEvent::Leave) {
			clearHighlight();
		}
		else if (e->type() == QEvent::Paint) {
			postUpdate();
		}
	}
	
	return QScrollView::eventFilter(obj, e);
}

void
CGraphManager::highlightEdge(QPoint pos)
{
	const int px = pos.x();
	const int py = pos.y();
	
	if (px <= 0 || py <= 0) {
		return;
	}
	
	CGraphObject* walker = oChildList.first();
	
	while (walker != 0) {
		int count = walker->destinations().count();
		
		for (int i = 0; i < count; i++) {
			CLine& d = walker->destinations().at(i);
			
			double a, b, w1, wd, t;
			
			a = d.x2 - d.x1;
			b = d.y2 - d.y1;
			
			if (fabs(a) > fabs(b)) {
				if (px < d.x1 && px < d.x2 || px > d.x1 && px > d.x2) {
					continue;
				}
				w1 = atan2(a, b);
				a = px - d.x1;
				t = tan(w1);
				b = a / ((t != 0.0) ? t : 1.0) + d.y1;
				wd = fabs(py - b);
			}
			else {
				if (py < d.y1 && py < d.y2 || py > d.y1 && py > d.y2) {
					continue;
				}
				w1 = atan2(b, a);
				b = py - d.y1;
				t = tan(w1);
				a = b / ((t != 0.0) ? t : 1.0) + d.x1;
				wd = fabs(px - a);
			}
			
		    if (wd < 15.0) { // hit !
				if (oCurrentHighlight.obj != d.obj) {
					QPainter p(mainWidget());
					QPen pen(QColor(0, 0, 255));
					pen.setWidth(PEN_WIDTH);
					p.setPen(pen);
					if (oCurrentHighlight.obj != 0) {
						drawArrow(
							&p,
							oCurrentHighlight.x1,
							oCurrentHighlight.y1,
							oCurrentHighlight.x2,
							oCurrentHighlight.y2
						);
					}
				
					pen.setColor(QColor(255, 0, 0));
					p.setPen(pen);
					drawArrow(&p, d.x1, d.y1, d.x2, d.y2, d.obj->wireLabel());
					
					oCurrentHighlight.x1 = d.x1;
					oCurrentHighlight.y1 = d.y1;
					oCurrentHighlight.x2 = d.x2;
					oCurrentHighlight.y2 = d.y2;
					oCurrentHighlight.obj = d.obj;
				}
						
				return;
			}
		}
		
		walker = oChildList.next();
	}
	
	clearHighlight();
}

void
CGraphManager::clearHighlight()
{
	if (oCurrentHighlight.obj != 0) {
		QPainter p(mainWidget());
		QPen pen(QColor(0, 0, 255));
		pen.setWidth(PEN_WIDTH);
		p.setPen(pen);
		
		drawArrow(
			&p,
			oCurrentHighlight.x1,
			oCurrentHighlight.y1,
			oCurrentHighlight.x2,
			oCurrentHighlight.y2,
			oCurrentHighlight.obj->wireLabel()
		);
		
		oCurrentHighlight.obj = 0;
	}
}

void
CGraphManager::setCursor(const QCursor& c)
{
	CGraphObject* walker = oChildList.first();
	while (walker != 0) {
		walker->setCursor(c);
		walker = oChildList.next();
	}
	QScrollView::setCursor(c);
}

void
CGraphManager::unsetCursor()
{
	CGraphObject* walker = oChildList.first();
	while (walker != 0) {
		walker->unsetCursor();
		walker = oChildList.next();
	}
	QScrollView::unsetCursor();
}

void
CGraphManager::scrollStart(CGraphManager::Direction d)
{
	oScrollDirection = d;
	oScrollTimer.start(SCROLL_DELAY);
}

void
CGraphManager::scrollEnd()
{
	oScrollTimer.stop();
}

void
CGraphManager::slotScrollTimeout()
{
	switch (oScrollDirection) {
		case Left:
			scrollBy(-SCROLL_INTERVALL_HOR, 0);
			break;
		case Right:
			scrollBy(SCROLL_INTERVALL_HOR, 0);
			break;
		case Up:
			scrollBy(0, -SCROLL_INTERVALL_HOR);
			break;
		case Down:
			scrollBy(0, SCROLL_INTERVALL_HOR);
			break;
		default:
			scrollEnd();
	}
}
