scide: implement selectionLength for openDocument
[supercollider.git] / QtCollider / widgets / QcGraph.cpp
blobb4d08ca35e4e3dc0bb6c5146bae8ec5b48bca511
1 /************************************************************************
3 * Copyright 2010-2012 Jakob Leben (jakob.leben@gmail.com)
5 * This file is part of SuperCollider Qt GUI.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ************************************************************************/
22 #include "QcGraph.h"
23 #include "../QcWidgetFactory.h"
24 #include "../style/routines.hpp"
26 #include <QPainter>
27 #include <QMouseEvent>
28 #include <QApplication>
30 #include <cmath>
32 QC_DECLARE_QWIDGET_FACTORY(QcGraph);
34 void QcGraphModel::append( QcGraphElement * e ) {
35 if( _elems.count() ) {
36 QcGraphElement *prev = _elems.last();
37 prev->_next = e;
38 e->_prev = prev;
40 _elems.append(e);
41 Q_EMIT( appended(e) );
44 void QcGraphModel::removeAt( int i ) {
45 QcGraphElement *e = _elems[i];
46 int ci = _conns.count();
47 while( ci-- ) {
48 Connection c = _conns[ci];
49 if( c.a == e || c.b == e ) _conns.removeAt(ci);
51 if( e->_prev ) e->_prev->_next = e->_next;
52 if( e->_next ) e->_next->_prev = e->_prev;
53 _elems.removeAt(i);
54 Q_EMIT( removed(e) );
55 delete e;
60 QcGraph::QcGraph() :
61 QtCollider::Style::Client(this),
62 _defaultThumbSize( QSize(18,18) ),
63 _style(DotElements),
64 _drawLines( true ),
65 _drawRects( true ),
66 _editable( true ),
67 _step( 0.01 ),
68 _selectionForm( ElasticSelection ),
69 _xOrder( NoOrder ),
70 _gridOn( false ),
71 _geometryDirty( false ),
72 _lastIndex(-1)
74 QPalette plt( palette() );
76 setFocusPolicy( Qt::StrongFocus );
77 setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
79 connect( &_model, SIGNAL(removed(QcGraphElement*)), this, SLOT(onElementRemoved(QcGraphElement*)) );
82 VariantList QcGraph::value() const
84 VariantList x;
85 VariantList y;
86 QList<QcGraphElement*> elems = _model.elements();
87 Q_FOREACH( QcGraphElement* e, elems ) {
88 QPointF val = e->value;
89 x.data.append( val.x() );
90 y.data.append( val.y() );
93 VariantList values;
94 values.data.append( QVariant::fromValue<VariantList>(x) );
95 values.data.append( QVariant::fromValue<VariantList>(y) );
97 return values;
100 QcGraphElement *QcGraph::currentElement() const
102 return _selection.count() ? _selection.elems.first().elem : 0;
105 int QcGraph::index() const
107 QcGraphElement *e = currentElement();
108 return e ? _model.elements().indexOf(e) : -1;
111 float QcGraph::currentX() const
113 QcGraphElement *e = currentElement();
114 return e ? e->value.x() : 0.f;
117 float QcGraph::currentY() const
119 QcGraphElement *e = currentElement();
120 return e ? e->value.y() : 0.f;
123 void QcGraph::setValue( const VariantList &list )
125 if( list.data.count() != 2 ) return;
126 VariantList xList = list.data[0].value<VariantList>();
127 VariantList yList = list.data[1].value<VariantList>();
129 int newc = qMin( xList.data.count(), yList.data.count() );
130 if( !newc ) return;
132 int c = _model.elementCount();
133 while( c > newc )
135 --c;
136 _model.removeAt( c );
139 int i;
140 for( i = 0; i < newc; ++i )
142 QPointF val( xList.data[i].value<float>(),
143 yList.data[i].value<float>() );
144 if( i < c ) {
145 QcGraphElement *e = _model.elementAt(i);
146 setValue( e, val );
148 else {
149 QcGraphElement *e = new QcGraphElement(_defaultThumbSize);
150 setValue( e, val );
151 _model.append( e );
155 if( newc ) ensureOrder();
157 _geometryDirty = true;
159 update();
162 void QcGraph::setStrings( const VariantList &list )
164 int strc = list.data.count();
165 int c = _model.elementCount();
166 int i;
167 for( i = 0; i < c && i < strc; ++i ) {
168 QcGraphElement *e = _model.elementAt(i);
169 e->text = list.data[i].toString();
171 update();
174 void QcGraph::setCurves( const VariantList & curves )
176 for( int i = 0; i < curves.data.size() && i < _model.elementCount(); ++i ) {
177 QVariant data = curves.data[i];
178 QcGraphElement::CurveType type;
179 double curvature;
180 if( data.type() == QVariant::Int ) {
181 type = (QcGraphElement::CurveType) data.toInt();
182 curvature = 0.0;
184 else {
185 type = QcGraphElement::Curvature;
186 curvature = data.value<double>();
188 _model.elementAt(i)->setCurveType( type, curvature );
190 update();
193 void QcGraph::setCurves( double curvature )
195 Q_FOREACH( QcGraphElement* e, _model.elements() )
196 e->setCurveType( QcGraphElement::Curvature, curvature );
197 update();
200 void QcGraph::setCurves( int typeId )
202 QcGraphElement::CurveType type = (QcGraphElement::CurveType)typeId;
203 Q_FOREACH( QcGraphElement* e, _model.elements() )
204 e->setCurveType( type );
205 update();
208 void QcGraph::setStringAt( int i, const QString & str )
210 int c = _model.elementCount();
211 if( i >= 0 && i < c ) {
212 QcGraphElement *e = _model.elementAt(i);
213 e->text = str;
214 update();
218 void QcGraph::connectElements( int src, VariantList targets )
220 int c = _model.elementCount();
221 if( src < 0 || src >= c ) return;
223 Q_FOREACH( QVariant var, targets.data ) {
224 int trg = var.toInt();
225 if( trg < 0 || trg >= c ) continue;
226 _model.connect( src, trg );
229 update();
232 void QcGraph::setIndex( int i ) {
233 select(i);
236 void QcGraph::select( int i, bool exclusive ) {
237 if( i >= 0 && i < _model.elementCount() ) {
238 if( exclusive ) setAllDeselected();
239 setIndexSelected( i, true );
240 update();
244 void QcGraph::deselect( int i ) {
245 if( i >= 0 && i < _model.elementCount() ) {
246 setIndexSelected( i, false );
247 update();
251 void QcGraph::deselectAll() {
252 setAllDeselected();
255 void QcGraph::setCurrentX( float f )
257 QcGraphElement *e = currentElement();
258 if(!e) return;
260 QPointF val = e->value;
261 val.setX( f );
262 if( _xOrder != NoOrder ) orderRestrictValue(e,val,true);
263 else restrictValue(val);
264 e->value = val;
266 update();
269 void QcGraph::setCurrentY( float f )
271 QcGraphElement *e = currentElement();
272 if(!e) return;
274 QPointF val = e->value;
275 val.setY( f );
276 if( _xOrder != NoOrder ) orderRestrictValue(e,val,true);
277 else restrictValue(val);
278 e->value = val;
280 update();
283 void QcGraph::setThumbSize( int s )
285 QSize size(s,s);
287 _defaultThumbSize = size;
289 int c = _model.elementCount();
290 for( int i=0; i<c; ++i ) {
291 QcGraphElement *e = _model.elementAt(i);
292 e->size = size;
295 _largestThumbSize = size;
296 _geometryDirty = false;
298 update();
301 void QcGraph::setThumbWidth( int w )
303 _defaultThumbSize.setWidth(w);
305 int c = _model.elementCount();
306 for( int i=0; i<c; ++i ) {
307 QcGraphElement *e = _model.elementAt(i);
308 e->size.setWidth(w);
311 // For backward compatibility, switch to style that supports
312 // different thumb width and height:
313 _style = RectElements;
314 _largestThumbSize.setWidth(w);
316 update();
319 void QcGraph::setThumbHeight( int h )
321 _defaultThumbSize.setHeight(h);
323 int c = _model.elementCount();
324 for( int i=0; i<c; ++i ) {
325 QcGraphElement *e = _model.elementAt(i);
326 e->size.setHeight(h);
329 // For backward compatibility, switch to style that supports
330 // different thumb width and height:
331 _style = RectElements;
332 _largestThumbSize.setHeight(h);
334 update();
337 void QcGraph::setThumbSizeAt( int i, int s )
339 if( i < 0 || i >= _model.elementCount() ) return;
340 _model.elementAt(i)->size = QSize(s,s);
341 _geometryDirty = true;
342 update();
345 void QcGraph::setThumbWidthAt( int i, int w )
347 if( i < 0 || i >= _model.elementCount() ) return;
348 _model.elementAt(i)->size.setWidth(w);
349 // For backward compatibility, switch to style that supports
350 // different thumb width and height:
351 _style = RectElements;
352 _geometryDirty = true;
353 update();
356 void QcGraph::setThumbHeightAt( int i, int h )
358 if( i < 0 || i >= _model.elementCount() ) return;
359 _model.elementAt(i)->size.setHeight(h);
360 // For backward compatibility, switch to style that supports
361 // different thumb width and height:
362 _style = RectElements;
363 _geometryDirty = true;
364 update();
367 void QcGraph::setFillColor( const QColor & color )
369 int c = _model.elementCount();
370 for( int i=0; i<c; ++i ) {
371 QcGraphElement *e = _model.elementAt(i);
372 e->fillColor = color;
374 update();
377 void QcGraph::setFillColorAt( int i, const QColor & color )
379 int c = _model.elementCount();
380 if( i >= 0 && i < c ) {
381 QcGraphElement *e = _model.elementAt(i);
382 e->fillColor = color;
383 update();
387 void QcGraph::setEditableAt( int i, bool b )
389 int c = _model.elementCount();
390 if( i >= 0 && i < c ) {
391 QcGraphElement *e = _model.elementAt(i);
392 e->editable = b;
396 void QcGraph::setStep( double step )
398 _step = qMax( 0.0, step );
400 if( _model.elementCount() ) {
401 ensureOrder();
402 update();
406 void QcGraph::setHorizontalOrder( int i ) {
407 _xOrder = (Order) i;
408 if( _xOrder != NoOrder ) {
409 ensureOrder();
410 update();
414 void QcGraph::onElementRemoved( QcGraphElement *e )
416 _selection.elems.removeAll( SelectedElement(e) );
419 void QcGraph::setAllDeselected()
421 int c = _model.elementCount();
422 for( int i = 0; i < c; ++i ) {
423 QcGraphElement *e = _model.elementAt(i);
424 e->selected = false;
426 _selection.elems.clear();
429 void QcGraph::setIndexSelected( int index, bool select )
431 Q_ASSERT( index >= 0 && index < _model.elementCount() );
433 QcGraphElement *e = _model.elementAt( index );
434 if( e->selected == select ) return;
436 if( select ) {
437 e->selected = true;
438 int c = _model.elementCount();
439 int si = 0;
440 int i = 0;
441 while( i < index ) {
442 if( _model.elementAt(i)->selected ) ++si;
443 ++i;
445 _selection.elems.insert( si, SelectedElement(e) );
446 _lastIndex = index;
448 else {
449 e->selected = false;
450 _selection.elems.removeAll( SelectedElement(e) );
453 update();
456 inline static void qc_graph_round( double &val, double &step, bool &grid )
458 if( val < 0.0 ) {
459 val = 0.0;
461 else if ( grid ) {
462 double ratio = ( val + (step*0.5) > 1.0 ) ? floor(1.0/step) : round(val/step);
463 val = ratio * step;
465 else if ( val > 1.0 ) {
466 val = 1.0;
470 inline void QcGraph::restrictValue( QPointF & val )
472 double x = val.x();
473 double y = val.y();
474 bool grid = _step > 0.0;
475 qc_graph_round(x,_step,grid);
476 qc_graph_round(y,_step,grid);
477 val.setX(x);
478 val.setY(y);
481 void QcGraph::orderRestrictValue( QcGraphElement *e, QPointF & val, bool selected )
483 restrictValue(val);
485 double x0 = e->value.x();
486 double x = val.x();
488 if( x == x0 ) {
489 return;
491 else if( x < x0 ) {
492 // new x is smaller, check if not too small;
493 QcGraphElement *prev = e->prev();
494 if( prev && (selected || !prev->selected) && x < prev->value.x() )
495 val.setX( prev->value.x() );
497 else {
498 // new x is larger, check if not too large;
499 QcGraphElement *next = e->next();
500 if( next && (selected || !next->selected) && x > next->value.x() )
501 val.setX( next->value.x() );
505 inline void QcGraph::setValue( QcGraphElement * e, const QPointF& pt )
507 QPointF val(pt);
508 restrictValue( val );
509 e->value = val;
512 void QcGraph::ensureOrder()
514 int c = _model.elementCount();
515 double x_min = 0.0;
516 for( int i = 0; i < c; ++i ) {
517 QcGraphElement *e = _model.elementAt(i);
518 QPointF val = e->value;
519 if( _xOrder != NoOrder && val.x() < x_min ) val.setX(x_min);
520 setValue( e, val );
521 x_min = e->value.x();
525 void QcGraph::moveFree( QcGraphElement *e, const QPointF & val )
527 if( !e->editable ) return;
528 setValue( e, val );
531 void QcGraph::moveOrderRestricted( QcGraphElement *e, const QPointF & val )
533 if( !e->editable ) return;
534 QPointF v(val);
535 orderRestrictValue( e, v, true );
536 e->value = v;
539 void QcGraph::moveSelected( const QPointF & dif, SelectionForm form, bool cached )
541 int c = _selection.count();
543 switch( form ) {
545 case ElasticSelection: {
547 switch( _xOrder ) {
549 case NoOrder:
551 for( int i = 0; i < c; ++i ) {
552 SelectedElement & se = _selection.elems[i];
553 moveFree( se.elem, (cached ? se.moveOrigin : se.elem->value) + dif );
556 break;
558 case RigidOrder:
560 if( dif.x() <= 0 ) {
561 for( int i = 0; i < c; ++i ) {
562 SelectedElement & se = _selection.elems[i];
563 moveOrderRestricted( se.elem, (cached ? se.moveOrigin : se.elem->value) + dif );
566 else {
567 for( int i = _selection.count() - 1; i >= 0; --i ) {
568 SelectedElement & se = _selection.elems[i];
569 moveOrderRestricted( se.elem, (cached ? se.moveOrigin : se.elem->value) + dif );
573 break;
576 break;
579 case RigidSelection: {
581 // reduce dif until acceptable by all nodes
582 QPointF d(dif);
583 for( int i = 0; i < c; ++i ) {
584 SelectedElement & se = _selection.elems[i];
585 // if any node in selection is not editable, abort, since
586 // we want to keep the selection form!
587 if( !se.elem->editable ) return;
588 QPointF val0 = (cached ? se.moveOrigin : se.elem->value);
589 QPointF val = val0 + d;
590 if( _xOrder == NoOrder ) {
591 restrictValue( val );
593 else {
594 orderRestrictValue( se.elem, val, false );
596 d = val - val0;
599 // if no dif left, do not bother moving
600 if( d.isNull() ) return;
602 // move all with the new dif
603 for( int i = 0; i < c; ++i ) {
604 SelectedElement & se = _selection.elems[i];
605 if( !se.elem->editable ) continue;
606 se.elem->value = (cached ? se.moveOrigin : se.elem->value) + d;
609 break;
616 void QcGraph::addCurve( QPainterPath &path, QcGraphElement *e1, QcGraphElement *e2 )
618 QcGraphElement::CurveType type = e1->curveType;
620 const QPointF &pt1 = e1->value;
621 const QPointF &pt2 = e2->value;
623 // coefficients for control points of cubic curve
624 // approximating first quarter of sinusoid
625 // technically: y = sin(pi*x/2) over x = [0,1]
626 static const float ax = 1.0/3.0;
627 static const float ay = 0.52359877f; // pi/6
628 static const float bx = 2.0/3.0;
629 static const float by = 1.0;
631 switch( type ) {
632 case QcGraphElement::Step:
633 path.moveTo( pt1 );
634 path.lineTo( pt1.x(), pt2.y() );
635 path.lineTo( pt2 );
636 break;
637 case QcGraphElement::Linear:
638 path.moveTo( pt1 );
639 path.lineTo( pt2 );
640 break;
641 case QcGraphElement::Sine: {
642 // half of difference between end points
643 float dx = (pt2.x() - pt1.x()) * 0.5f;
644 float dy = (pt2.y() - pt1.y()) * 0.5f;
646 // middle point
647 QPointF mid = pt1 + QPointF( dx, dy );
649 path.moveTo( pt1 );
650 path.cubicTo( pt1 + QPointF( dx*(1-bx), dy*(1-by) ), pt1 + QPointF( dx*(1-ax), dy*(1-ay) ), mid );
651 path.cubicTo( mid + QPointF( dx*ax, dy*ay ), mid + QPointF( dx*bx, dy*by ), pt2 );
653 break;
655 case QcGraphElement::Welch: {
656 // difference between points
657 float dx = (pt2.x() - pt1.x());
658 float dy = (pt2.y() - pt1.y());
660 path.moveTo( pt1 );
661 if( dy > 0 )
662 path.cubicTo( pt1 + QPointF( dx*ax, dy*ay ), pt1 + QPointF( dx*bx, dy*by ), pt2 );
663 else
664 path.cubicTo( pt1 + QPointF( dx*(1-bx), dy*(1-by) ), pt1 + QPointF( dx*(1-ax), dy*(1-ay) ), pt2 );
666 break;
668 case QcGraphElement::Exponential: {
670 // FIXME: find a Bezier curve approximation
672 path.moveTo( pt1 );
674 float dx = (pt2.x() - pt1.x());
675 float dy = (pt2.y() - pt1.y());
677 // prevent NaN, optimize
678 if( pt1.y() <= 0.f || pt2.y() <= 0.f ) {
679 path.lineTo( dy < 0 ? QPointF(pt1.x(),pt2.y()) : QPointF(pt2.x(), pt1.y()) );
680 path.lineTo( pt2 );
682 else {
683 const float n = 100.f;
684 const float yratio = pt2.y() / pt1.y();
685 for( float ph=1/n; ph<=(1-1/n); ph+=1/n ) {
686 qreal y = pt1.y() * pow( yratio, ph );
687 path.lineTo( pt1.x() + (dx * ph), y );
689 path.lineTo( pt2 );
692 break;
694 case QcGraphElement::Curvature:
696 // FIXME: find a Bezier curve approximation
698 path.moveTo( pt1 );
700 // prevent NaN
701 double curve = qBound( -100.0, e1->curvature, 100.0 );
703 if( std::abs( curve ) < 0.0001 ) {
704 path.lineTo( pt2 );
706 else {
707 float dx = (pt2.x() - pt1.x());
708 float dy = (pt2.y() - pt1.y());
709 double denom = 1.0 - exp( curve );
710 const float n = 100.f;
711 for( float ph=1/n; ph<=(1-1/n); ph+=1/n ) {
712 double numer = 1.0 - exp( ph * curve );
713 qreal y = pt1.y() + dy * (numer / denom);
714 path.lineTo( pt1.x() + (dx * ph), y );
716 path.lineTo( pt2 );
718 break;
722 QSize QcGraph::drawnElementSize( QcGraphElement *e )
724 if (_style == DotElements) {
725 int s = qMin(e->size.width(), e->size.height());
726 return QSize(s,s);
728 else
729 return e->size;
732 QRect QcGraph::valueRect()
734 using namespace QtCollider::Style;
736 if (_geometryDirty)
738 int w = 0;
739 int h = 0;
740 int c = _model.elementCount();
741 if (_style == RectElements)
743 for (int i = 0; i < c; ++i) {
744 QSize s = _model.elementAt(i)->size;
745 w = qMax(w, s.width());
746 h = qMax(h, s.height());
749 else
751 for (int i = 0; i < c; ++i) {
752 QSize s = _model.elementAt(i)->size;
753 w = qMax(w, qMin(s.width(), s.height()));
755 h = w;
757 _largestThumbSize = QSize(w,h);
758 _geometryDirty = false;
761 return marginsRect( sunkenContentsRect( rect() ), _largestThumbSize ).adjusted(1,1,-1,-1);
764 QRectF QcGraph::labelRect( QcGraphElement *e, const QPointF &pt, const QRect &bounds, const QFontMetrics &fm )
766 QRectF textRect( fm.boundingRect(e->text) );
767 QSize sz( drawnElementSize(e) );
768 qreal hnd_w_2 = sz.width() * 0.5;
769 qreal hnd_h_2 = sz.height() * 0.5;
770 textRect.moveBottomLeft( pt + QPointF(hnd_w_2, -hnd_h_2) );
771 if( textRect.y() < bounds.y() )
772 textRect.moveTop( pt.y() + hnd_h_2 );
773 if( textRect.right() > bounds.right() )
774 textRect.moveRight( pt.x() - hnd_w_2 );
775 return textRect;
778 void QcGraph::paintEvent( QPaintEvent * )
780 using namespace QtCollider::Style;
781 using QtCollider::Style::Ellipse;
782 using QtCollider::Style::RoundRect;
784 QPainter p( this );
785 const QPalette & plt = palette();
787 p.setRenderHint( QPainter::Antialiasing, true );
788 RoundRect frame(rect(), 2);
789 drawSunken( &p, plt, frame, background(), hasFocus() ? focusColor() : QColor() );
790 p.setRenderHint( QPainter::Antialiasing, false );
792 QRect contentsRect( valueRect() );
794 //draw grid;
796 p.setPen( gridColor() );
797 p.setBrush( Qt::NoBrush );
798 p.drawRect( contentsRect );
800 if( _gridOn ) {
801 float dx = _gridMetrics.x();
802 float dy = _gridMetrics.y();
803 float cl = contentsRect.left();
804 float cr = contentsRect.right();
805 float ct = contentsRect.top();
806 float cb = contentsRect.bottom();
808 if( contentsRect.width() > 0.f && dx > 0.f && dx < 1.f ) {
809 dx *= contentsRect.width();
810 int i = 1;
811 float x;
812 while( (x = i * dx + cl) < cr ) {
813 p.drawLine( x, ct, x, cb );
814 ++i;
818 if( contentsRect.height() > 0.f && dy > 0.f && dy < 1.f ) {
819 dy *= contentsRect.height();
820 int i = 1;
821 float y;
822 while( (y = cb - i * dy) > ct ) {
823 p.drawLine( cl, y, cr, y );
824 ++i;
829 QList<QcGraphElement*> elems = _model.elements();
831 int c = elems.count();
832 if( !c ) return;
834 const QColor & strokeClr = strokeColor();
836 p.setPen( strokeClr );
838 // draw lines;
839 if( _drawLines ) {
841 QPainterPath lines;
842 QList<QcGraphModel::Connection> conns = _model.connections();
844 if( conns.count() ) {
846 Q_FOREACH( QcGraphModel::Connection c, conns ) {
847 addCurve( lines, c.a, c.b );
851 else {
853 QcGraphElement *e1 = elems[0];
854 int i;
855 for( i = 1; i < c; ++i ) {
856 QcGraphElement *e2 = elems[i];
857 addCurve( lines, e1, e2 );
858 e1 = e2;
863 p.save();
864 p.setRenderHint( QPainter::Antialiasing, true );
865 p.setBrush( Qt::NoBrush );
866 p.translate( contentsRect.x(), contentsRect.y() + contentsRect.height() );
867 p.scale( contentsRect.width(), -contentsRect.height() );
868 p.drawPath( lines );
869 p.restore();
872 // draw rects and strings
873 if( _drawRects ) {
875 p.setRenderHint( QPainter::Antialiasing, true );
877 QFontMetrics fm( font() );
878 QColor rectColor = plt.color(QPalette::Button).lighter(105);
879 QColor circleColor = rectColor; circleColor.setAlpha(70);
880 QColor dotColor = plt.color(QPalette::Text);
881 const QColor & selectClr = selectionColor();
882 QRectF rect;
883 QPointF pt;
884 int i;
886 for( i = 0; i < c; ++i ) {
888 QcGraphElement *e = elems[i];
890 rect.setSize( drawnElementSize(e) );
892 pt = Style::pos( e->value, contentsRect );
893 rect.moveCenter( pt.toPoint() );
895 if( _style == DotElements )
896 drawDotElement(e, rect, contentsRect,
897 dotColor, circleColor, strokeClr, selectClr,
898 plt, fm, &p);
899 else
900 drawRectElement(e, rect, rectColor, strokeClr, selectClr, plt, &p );
907 void QcGraph::drawDotElement
908 ( QcGraphElement *e, const QRectF &rect, const QRect & bounds,
909 const QColor & dotColor, const QColor & circleColor,
910 const QColor & textColor, const QColor & selectColor,
911 const QPalette &plt, const QFontMetrics &fm, QPainter *p )
913 using namespace QtCollider::Style;
914 using QtCollider::Style::Ellipse;
916 // base
917 Ellipse thumb(rect);
918 drawRaised( p, plt, thumb, circleColor );
920 // marker
921 QRectF r( thumb._rect );
922 qreal wdif = r.width() * 0.3;
923 qreal hdif = r.height() * 0.3;
924 p->setPen( Qt::NoPen );
925 p->setBrush( e->fillColor.isValid() ? e->fillColor : dotColor );
926 p->drawEllipse( r.adjusted( wdif, hdif, -wdif, -hdif ) );
928 // selection indicator
929 if( e->selected ) {
930 p->setBrush(Qt::NoBrush);
931 p->setPen( selectColor );
932 p->drawEllipse( thumb._rect.adjusted(1,1,-1,-1) );
935 // label
936 p->setPen( textColor );
937 QString text = e->text;
938 if( !text.isEmpty() ) {
939 QRectF lblRect( labelRect( e, rect.center(), bounds, fm ) );
940 p->drawText( lblRect, 0, text );
944 void QcGraph::drawRectElement
946 QcGraphElement *e, const QRectF &rect,
947 const QColor & fillColor,
948 const QColor & textColor,
949 const QColor & selectColor,
950 const QPalette &plt, QPainter *p )
952 using namespace QtCollider::Style;
953 using QtCollider::Style::RoundRect;
955 // base
956 RoundRect base(rect, 5);
957 drawRaised( p, plt, base, e->fillColor.isValid() ? e->fillColor : fillColor );
959 // selection indicator
960 if( e->selected ) {
961 p->setBrush(Qt::NoBrush);
962 p->setPen( selectColor );
963 p->drawRoundedRect( base._rect, 5, 5 );
966 // label
967 p->setPen( textColor );
968 QString text = e->text;
969 if( !text.isEmpty() ) {
970 p->drawText( rect, Qt::AlignCenter, text );
974 void QcGraph::mousePressEvent( QMouseEvent *ev )
976 using namespace QtCollider::Style;
978 QList<QcGraphElement*> elems = _model.elements();
979 int c = elems.count();
980 if( !c ) return;
982 QPointF mpos = ev->pos();
983 QRectF valueRect( this->valueRect() );
985 QRectF r;
987 int i;
988 for( i = 0; i < c; ++i ) {
989 QcGraphElement *e = elems[i];
990 r.setSize( drawnElementSize(e) );
991 QPointF pt( Style::pos( e->value, valueRect ) );
992 r.moveCenter( pt );
993 if( r.contains( mpos ) ) {
995 if( ev->modifiers() & Qt::ShiftModifier ) {
996 setIndexSelected( i, !e->selected );
998 else {
999 if( !e->selected ) {
1000 setAllDeselected();
1001 setIndexSelected( i, true );
1005 _selection.cached = false;
1006 if( e->selected ) {
1007 // if the element that was hit ended up selected
1008 // prepare for moving
1009 _selection.shallMove = true;
1010 _selection.moveOrigin = Style::value(mpos, valueRect);
1012 else {
1013 _selection.shallMove = false;
1016 update();
1017 return;
1021 _selection.shallMove = false;
1023 if( !(ev->modifiers() & Qt::ShiftModifier) ) {
1024 setAllDeselected();
1027 update();
1030 void QcGraph::mouseMoveEvent( QMouseEvent *ev )
1032 using namespace QtCollider::Style;
1034 if( !ev->buttons() ) return;
1036 if( !_editable || !_selection.shallMove || !_selection.size() ) return;
1038 if( !_selection.cached ) {
1039 int c = _selection.count();
1040 for( int i = 0; i < c; ++i ) {
1041 SelectedElement &se = _selection.elems[i];
1042 se.moveOrigin = se.elem->value;
1044 _selection.cached = true;
1047 QRectF valueRect( this->valueRect() );
1048 QPointF dValue( Style::value( ev->pos(), valueRect ) );
1049 dValue = dValue - _selection.moveOrigin;
1051 moveSelected( dValue, _selectionForm, true );
1053 update();
1054 doAction( ev->modifiers() );
1057 void QcGraph::keyPressEvent( QKeyEvent *event )
1059 int mods = event->modifiers();
1060 if( mods & Qt::AltModifier || mods & Qt::ShiftModifier )
1062 // selection mode
1064 int c = _model.elementCount();
1065 if(!c) return;
1067 switch( event->key() ) {
1068 case Qt::Key_Right:
1070 // select the index after the last selected one (wrapping)
1071 // or extend selection to the right
1073 int i = -1;
1074 if(_selection.count()) {
1075 for (i = c - 1; i >= 0; --i) {
1076 if(_model.elementAt(i)->selected)
1077 break;
1081 if( event->modifiers() & Qt::ShiftModifier ) {
1082 i = qMin(i + 1, c - 1);
1083 setIndexSelected( i, true );
1085 else {
1086 ++i;
1087 if (i >= c) i = 0;
1088 setAllDeselected();
1089 setIndexSelected( i, true );
1092 break;
1094 case Qt::Key_Left:
1096 // select the index before the first selected one (wrapping)
1097 // or extend selection to the left
1098 int i = c;
1099 if(_selection.count()) {
1100 for(i = 0; i < c; ++i) {
1101 if(_model.elementAt(i)->selected)
1102 break;
1106 if( event->modifiers() & Qt::ShiftModifier ) {
1107 i = qMax(i - 1, 0);
1108 setIndexSelected( i, true );
1110 else {
1111 --i;
1112 if (i < 0) i = c - 1;
1113 setAllDeselected();
1114 setIndexSelected( i, true );
1117 break;
1119 default: break;
1122 else
1124 // editing mode
1126 if( !_editable || !_selection.size() ) return;
1128 QPointF dValue;
1130 switch( event->key() ) {
1131 case Qt::Key_Up:
1132 dValue.setY( _step );
1133 break;
1134 case Qt::Key_Down:
1135 dValue.setY( - _step );
1136 break;
1137 case Qt::Key_Right:
1138 dValue.setX( _step );
1139 break;
1140 case Qt::Key_Left:
1141 dValue.setX( - _step );
1142 break;
1143 default:
1144 return;
1147 moveSelected( dValue, _selectionForm, false );
1149 update();
1150 doAction( event->modifiers() );
1154 void QcGraph::doAction( Qt::KeyboardModifiers mods )
1156 Qt::KeyboardModifier ctrlMod =
1157 #ifdef Q_OS_MAC
1158 Qt::MetaModifier;
1159 #else
1160 Qt::ControlModifier;
1161 #endif
1163 if( mods & ctrlMod )
1164 Q_EMIT( metaAction() );
1165 else
1166 Q_EMIT( action() );