You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
446 lines
9.9 KiB
446 lines
9.9 KiB
#include "graph.h"
|
|
#include <qcanvas.h>
|
|
#include <stdlib.h>
|
|
#include <qdatetime.h>
|
|
#include <qhbox.h>
|
|
#include <qpushbutton.h>
|
|
#include <qslider.h>
|
|
#include <qlabel.h>
|
|
#include <qlayout.h>
|
|
|
|
const int bounce_rtti = 1234;
|
|
|
|
// We use a global variable to save memory - all the brushes and pens in
|
|
// the mesh are shared.
|
|
static QBrush *tb = 0;
|
|
static QPen *tp = 0;
|
|
|
|
class EdgeItem;
|
|
class NodeItem;
|
|
class FigureEditor;
|
|
typedef QValueList<NodeItem*> NodeItemList;
|
|
typedef QValueList<EdgeItem*> EdgeItemList;
|
|
|
|
#define SPEED2ADVANCE(x) (301-x)
|
|
|
|
class GraphWidgetPrivate
|
|
{
|
|
public:
|
|
GraphWidgetPrivate() {
|
|
moving = 0;
|
|
speed = 275;
|
|
}
|
|
~GraphWidgetPrivate() {
|
|
delete canvas;
|
|
}
|
|
NodeItemList nodeItems;
|
|
FigureEditor* editor;
|
|
QCanvas* canvas;
|
|
QCanvasItem* moving;
|
|
int speed;
|
|
};
|
|
|
|
class EdgeItem: public QCanvasLine
|
|
{
|
|
public:
|
|
EdgeItem( NodeItem*, NodeItem*, QCanvas* );
|
|
void setFromPoint( int x, int y ) ;
|
|
void setToPoint( int x, int y );
|
|
void moveBy(double dx, double dy);
|
|
|
|
NodeItem* from;
|
|
NodeItem* to;
|
|
};
|
|
|
|
|
|
|
|
class NodeItem: public QCanvasEllipse
|
|
{
|
|
public:
|
|
NodeItem( GraphWidgetPrivate* g );
|
|
~NodeItem() {}
|
|
|
|
void addInEdge( EdgeItem *edge ) { inList.append( edge ); }
|
|
void addOutEdge( EdgeItem *edge ) { outList.append( edge ); }
|
|
|
|
void moveBy(double dx, double dy);
|
|
|
|
void calcForce();
|
|
void advance( int stage );
|
|
|
|
private:
|
|
GraphWidgetPrivate* graph;
|
|
EdgeItemList inList;
|
|
EdgeItemList outList;
|
|
};
|
|
|
|
|
|
|
|
void EdgeItem::moveBy(double, double)
|
|
{
|
|
//nothing
|
|
}
|
|
|
|
EdgeItem::EdgeItem( NodeItem *fromItem, NodeItem *toItem, QCanvas *canvas )
|
|
: QCanvasLine( canvas )
|
|
{
|
|
from = fromItem;
|
|
to = toItem;
|
|
setPen( *tp );
|
|
setBrush( *tb );
|
|
from->addOutEdge( this );
|
|
to->addInEdge( this );
|
|
setPoints( int(from->x()), int(from->y()), int(to->x()), int(to->y()) );
|
|
setZ( 127 );
|
|
}
|
|
|
|
void EdgeItem::setFromPoint( int x, int y )
|
|
{
|
|
setPoints( x,y, endPoint().x(), endPoint().y() );
|
|
}
|
|
|
|
void EdgeItem::setToPoint( int x, int y )
|
|
{
|
|
setPoints( startPoint().x(), startPoint().y(), x, y );
|
|
}
|
|
|
|
|
|
void NodeItem::moveBy(double dx, double dy)
|
|
{
|
|
double nx = x() + dx;
|
|
double ny = y() + dy;
|
|
if ( graph->moving != this ) {
|
|
nx = QMAX( width()/2, nx );
|
|
ny = QMAX( height()/2, ny );
|
|
nx = QMIN( canvas()->width() - width()/2, nx );
|
|
ny = QMIN( canvas()->height() - height()/2, ny );
|
|
}
|
|
QCanvasEllipse::moveBy( nx-x(), ny-y() );
|
|
EdgeItemList::Iterator it;
|
|
for ( it = inList.begin(); it != inList.end(); ++it )
|
|
(*it)->setToPoint( int(x()), int(y()) );
|
|
for ( it = outList.begin(); it != outList.end(); ++it )
|
|
(*it)->setFromPoint( int(x()), int(y()) );
|
|
}
|
|
|
|
NodeItem::NodeItem( GraphWidgetPrivate* g )
|
|
: QCanvasEllipse( 32, 32, g->canvas )
|
|
{
|
|
graph = g;
|
|
graph->nodeItems.append( this );
|
|
setPen( *tp );
|
|
setBrush( *tb );
|
|
setZ( 128 );
|
|
}
|
|
|
|
void NodeItem::advance( int stage ) {
|
|
switch ( stage ) {
|
|
case 0:
|
|
calcForce();
|
|
break;
|
|
case 1:
|
|
QCanvasItem::advance(stage);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void NodeItem::calcForce() {
|
|
if ( graph->moving == this ) {
|
|
setVelocity( 0, 0 );
|
|
return;
|
|
}
|
|
double xvel = 0;
|
|
double yvel = 0;
|
|
for ( NodeItemList::Iterator it = graph->nodeItems.begin(); it != graph->nodeItems.end(); ++it ) {
|
|
NodeItem* n = (*it);
|
|
if ( n == this )
|
|
continue;
|
|
double dx = x() - n->x();
|
|
double dy = y() - n->y();
|
|
double l = 2 * ( dx * dx + dy * dy );
|
|
if ( l > 0 ) {
|
|
xvel = xvel + dx*260 / l;
|
|
yvel = yvel + dy*260 / l;
|
|
}
|
|
}
|
|
double w = 1 + outList.count() + inList.count();
|
|
w *= 10;
|
|
EdgeItemList::Iterator it2;
|
|
EdgeItem * e;
|
|
NodeItem* n;
|
|
for ( it2 = outList.begin(); it2 != outList.end(); ++it2 ) {
|
|
e = (*it2);
|
|
n = e->to;
|
|
xvel = xvel - ( x() - n->x() ) / w;
|
|
yvel = yvel - ( y() - n->y() ) / w;
|
|
}
|
|
for ( it2 = inList.begin(); it2 != inList.end(); ++it2 ) {
|
|
e = (*it2);
|
|
n = e->from;
|
|
xvel = xvel - ( x() - n->x() ) / w;
|
|
yvel = yvel - ( y() - n->y() ) / w;
|
|
}
|
|
if ( QABS( xvel ) < .1 && QABS( yvel ) < .1 )
|
|
xvel = yvel = 0;
|
|
setVelocity( xvel, yvel );
|
|
}
|
|
|
|
|
|
class FigureEditor : public QCanvasView {
|
|
public:
|
|
FigureEditor( GraphWidgetPrivate *g, QWidget* parent=0, const char* name=0, WFlags f=0);
|
|
|
|
QSize sizeHint() const;
|
|
|
|
|
|
protected:
|
|
void contentsMousePressEvent(QMouseEvent*);
|
|
void contentsMouseReleaseEvent(QMouseEvent*);
|
|
void contentsMouseMoveEvent(QMouseEvent*);
|
|
|
|
|
|
void resizeEvent( QResizeEvent* );
|
|
void showEvent( QShowEvent* );
|
|
void hideEvent( QHideEvent* e);
|
|
|
|
private:
|
|
void initialize();
|
|
QPoint moving_start;
|
|
GraphWidgetPrivate* graph;
|
|
};
|
|
|
|
|
|
FigureEditor::FigureEditor(
|
|
GraphWidgetPrivate* g, QWidget* parent,
|
|
const char* name, WFlags f) :
|
|
QCanvasView(g->canvas, parent,name,f)
|
|
{
|
|
graph = g;
|
|
}
|
|
|
|
void FigureEditor::contentsMousePressEvent(QMouseEvent* e)
|
|
{
|
|
QCanvasItemList l=canvas()->collisions(e->pos());
|
|
for (QCanvasItemList::Iterator it=l.begin(); it!=l.end(); ++it) {
|
|
if ((*it)->rtti()==bounce_rtti )
|
|
continue;
|
|
graph->moving = *it;
|
|
moving_start = e->pos();
|
|
return;
|
|
}
|
|
graph->moving = 0;
|
|
}
|
|
|
|
void FigureEditor::contentsMouseReleaseEvent(QMouseEvent* )
|
|
{
|
|
if ( graph->moving )
|
|
graph->moving = 0;
|
|
}
|
|
|
|
void FigureEditor::contentsMouseMoveEvent(QMouseEvent* e)
|
|
{
|
|
if ( graph->moving ) {
|
|
graph->moving->moveBy(e->pos().x() - moving_start.x(),
|
|
e->pos().y() - moving_start.y());
|
|
moving_start = e->pos();
|
|
canvas()->update();
|
|
}
|
|
}
|
|
|
|
class BouncyText : public QCanvasText {
|
|
void initPos();
|
|
void initSpeed();
|
|
public:
|
|
int rtti() const;
|
|
BouncyText(const QString&, QFont, QCanvas*);
|
|
void advance(int);
|
|
};
|
|
|
|
BouncyText::BouncyText( const QString& text, QFont f, QCanvas* canvas) :
|
|
QCanvasText(text, f, canvas)
|
|
{
|
|
setAnimated(TRUE);
|
|
initPos();
|
|
}
|
|
|
|
|
|
int BouncyText::rtti() const
|
|
{
|
|
return bounce_rtti;
|
|
}
|
|
|
|
void BouncyText::initPos()
|
|
{
|
|
initSpeed();
|
|
int trial=1000;
|
|
do {
|
|
move(rand()%(canvas()->width()-boundingRect().width()),
|
|
rand()%(canvas()->height()-boundingRect().height()));
|
|
advance(0);
|
|
} while (trial-- && xVelocity()==0.0 && yVelocity()==0.0);
|
|
}
|
|
|
|
void BouncyText::initSpeed()
|
|
{
|
|
const double speed = 2.0;
|
|
double d = (double)(rand()%1024) / 1024.0;
|
|
double e = (double)(rand()%1024) / 1024.0;
|
|
|
|
if ( d < .5 )
|
|
d = -1 - d;
|
|
else
|
|
d = d + 1;
|
|
if ( e < .5 )
|
|
e = -1 - e;
|
|
else
|
|
e = e + 1;
|
|
|
|
setVelocity( d*speed, e * speed );
|
|
}
|
|
|
|
void BouncyText::advance( int stage )
|
|
{
|
|
switch ( stage ) {
|
|
case 0: {
|
|
double vx = xVelocity();
|
|
double vy = yVelocity();
|
|
|
|
if ( vx == 0.0 && vy == 0.0 ) {
|
|
// stopped last turn
|
|
initSpeed();
|
|
vx = xVelocity();
|
|
vy = yVelocity();
|
|
}
|
|
|
|
QRect r = boundingRect();
|
|
r.moveBy( int(vx), int(vy) );
|
|
|
|
if ( r.left() < 0 || r.right() > canvas()->width() )
|
|
vx = -vx;
|
|
if ( r.top() < 0 || r.bottom() > canvas()->height() )
|
|
vy = -vy;
|
|
|
|
r = boundingRect();
|
|
r.moveBy( int(vx), int(vy) );
|
|
if ( r.left() < 0 || r.right() > canvas()->width() )
|
|
vx = 0;
|
|
if ( r.top() < 0 || r.bottom() > canvas()->height() )
|
|
vy = 0;
|
|
|
|
setVelocity( vx, vy );
|
|
} break;
|
|
case 1:
|
|
QCanvasItem::advance( stage );
|
|
break;
|
|
}
|
|
}
|
|
|
|
GraphWidget::GraphWidget( QWidget *parent, const char *name)
|
|
: QWidget( parent, name )
|
|
{
|
|
d = new GraphWidgetPrivate;
|
|
d->canvas = 0;
|
|
QVBoxLayout* vb = new QVBoxLayout( this, 11, 6 );
|
|
d->editor = new FigureEditor( d, this );
|
|
vb->addWidget( d->editor );
|
|
QHBoxLayout* hb = new QHBoxLayout( vb );
|
|
hb->addWidget( new QLabel("Slow", this ) );
|
|
QSlider* slider = new QSlider( 0, 300, 25, d->speed, Horizontal, this );
|
|
connect( slider, SIGNAL( valueChanged(int) ), this, SLOT( setSpeed(int) ) );
|
|
hb->addWidget( slider );
|
|
hb->addWidget( new QLabel("Fast", this ) );
|
|
hb->addSpacing( 10 );
|
|
QPushButton* btn = new QPushButton( "Shuffle Nodes", this );
|
|
connect( btn, SIGNAL( clicked() ), this, SLOT( shuffle() ) );
|
|
hb->addWidget( btn );
|
|
}
|
|
|
|
|
|
GraphWidget::~GraphWidget()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void GraphWidget::setSpeed(int s)
|
|
{
|
|
d->speed = s;
|
|
if ( isVisible() && d->canvas )
|
|
d->canvas->setAdvancePeriod( SPEED2ADVANCE( s ) );
|
|
}
|
|
|
|
void GraphWidget::shuffle()
|
|
{
|
|
|
|
for ( NodeItemList::Iterator it = d->nodeItems.begin(); it != d->nodeItems.end(); ++it ) {
|
|
NodeItem* ni = (*it);
|
|
ni->move(rand()%(d->canvas->width()-ni->width()),rand()%(d->canvas->height()-ni->height()));
|
|
}
|
|
}
|
|
|
|
|
|
QSize FigureEditor::sizeHint() const
|
|
{
|
|
return QSize( 600, 400 );
|
|
}
|
|
|
|
void FigureEditor::resizeEvent( QResizeEvent* e )
|
|
{
|
|
if ( canvas() )
|
|
canvas()->resize( contentsRect().width(), contentsRect().height() );
|
|
QCanvasView::resizeEvent( e );
|
|
}
|
|
|
|
void FigureEditor::showEvent( QShowEvent* )
|
|
{
|
|
initialize();
|
|
canvas()->setAdvancePeriod( SPEED2ADVANCE(graph->speed) );
|
|
}
|
|
|
|
void FigureEditor::hideEvent( QHideEvent* )
|
|
{
|
|
initialize();
|
|
canvas()->setAdvancePeriod( -10 );
|
|
}
|
|
|
|
void FigureEditor::initialize()
|
|
{
|
|
if ( canvas() )
|
|
return;
|
|
resize( sizeHint() );
|
|
graph->canvas = new QCanvas( contentsRect().width(), contentsRect().height() );
|
|
if ( !tb ) tb = new QBrush( Qt::red );
|
|
if ( !tp ) tp = new QPen( Qt::black );
|
|
srand( QTime::currentTime().msec() );
|
|
int nodecount = 0;
|
|
|
|
int rows = 3;
|
|
int cols = 3;
|
|
|
|
QMemArray<NodeItem*> lastRow(cols);
|
|
for ( int r = 0; r < rows; r++ ) {
|
|
NodeItem *prev = 0;
|
|
for ( int c = 0; c < cols; c++ ) {
|
|
NodeItem *ni = new NodeItem( graph );
|
|
ni->setAnimated( TRUE );
|
|
nodecount++;
|
|
ni->move(rand()%(graph->canvas->width()-ni->width()),rand()%(graph->canvas->height()-ni->height()));
|
|
|
|
if ( r > 0 )
|
|
(new EdgeItem( lastRow[c], ni, graph->canvas ))->show();
|
|
if ( prev )
|
|
(new EdgeItem( prev, ni, graph->canvas ))->show();
|
|
prev = ni;
|
|
lastRow[c] = ni;
|
|
ni->show();
|
|
}
|
|
}
|
|
|
|
graph->canvas->advance();
|
|
|
|
QCanvasItem* i = new BouncyText( tr( "Drag the nodes around!" ), QFont("helvetica", 24), graph->canvas);
|
|
i->show();
|
|
setCanvas( graph->canvas );
|
|
setMinimumSize( 600, 400 );
|
|
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
|
|
}
|