#include "graph.h" #include #include #include #include #include #include #include #include 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 NodeItemList; typedef QValueList 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 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 ); }