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 ************************************************************************/
23 #include "../QcWidgetFactory.h"
24 #include "../style/routines.hpp"
27 #include <QMouseEvent>
28 #include <QApplication>
32 QC_DECLARE_QWIDGET_FACTORY(QcGraph
);
34 void QcGraphModel::append( QcGraphElement
* e
) {
35 if( _elems
.count() ) {
36 QcGraphElement
*prev
= _elems
.last();
41 Q_EMIT( appended(e
) );
44 void QcGraphModel::removeAt( int i
) {
45 QcGraphElement
*e
= _elems
[i
];
46 int ci
= _conns
.count();
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
;
61 QtCollider::Style::Client(this),
62 _thumbSize( QSize( 18, 18 ) ),
67 _selectionForm( ElasticSelection
),
72 QPalette
plt( palette() );
74 setFocusPolicy( Qt::StrongFocus
);
75 setSizePolicy( QSizePolicy::Expanding
, QSizePolicy::Expanding
);
77 connect( &_model
, SIGNAL(removed(QcGraphElement
*)), this, SLOT(onElementRemoved(QcGraphElement
*)) );
80 VariantList
QcGraph::value() const
84 QList
<QcGraphElement
*> elems
= _model
.elements();
85 Q_FOREACH( QcGraphElement
* e
, elems
) {
86 QPointF val
= e
->value
;
87 x
.data
.append( val
.x() );
88 y
.data
.append( val
.y() );
92 values
.data
.append( QVariant::fromValue
<VariantList
>(x
) );
93 values
.data
.append( QVariant::fromValue
<VariantList
>(y
) );
98 float QcGraph::currentX() const
100 if( _curIndex
< 0 ) return 0.f
;
101 QcGraphElement
*e
= _model
.elementAt(_curIndex
);
105 float QcGraph::currentY() const
107 if( _curIndex
< 0 ) return 0.f
;
108 QcGraphElement
*e
= _model
.elementAt(_curIndex
);
112 void QcGraph::setValue( const VariantList
&list
)
114 if( list
.data
.count() != 2 ) return;
115 VariantList xList
= list
.data
[0].value
<VariantList
>();
116 VariantList yList
= list
.data
[1].value
<VariantList
>();
118 int newc
= qMin( xList
.data
.count(), yList
.data
.count() );
121 int c
= _model
.elementCount();
125 _model
.removeAt( c
);
129 for( i
= 0; i
< newc
; ++i
)
131 QPointF
val( xList
.data
[i
].value
<float>(),
132 yList
.data
[i
].value
<float>() );
134 QcGraphElement
*e
= _model
.elementAt(i
);
138 QcGraphElement
*e
= new QcGraphElement();
144 if( newc
) ensureOrder();
151 void QcGraph::setStrings( const VariantList
&list
)
153 int strc
= list
.data
.count();
154 int c
= _model
.elementCount();
156 for( i
= 0; i
< c
&& i
< strc
; ++i
) {
157 QcGraphElement
*e
= _model
.elementAt(i
);
158 e
->text
= list
.data
[i
].toString();
163 void QcGraph::setCurves( const VariantList
& curves
)
165 for( int i
= 0; i
< curves
.data
.size() && i
< _model
.elementCount(); ++i
) {
166 QVariant data
= curves
.data
[i
];
167 QcGraphElement::CurveType type
;
169 if( data
.type() == QVariant::Int
) {
170 type
= (QcGraphElement::CurveType
) data
.toInt();
174 type
= QcGraphElement::Curvature
;
175 curvature
= data
.value
<double>();
177 _model
.elementAt(i
)->setCurveType( type
, curvature
);
182 void QcGraph::setCurves( double curvature
)
184 Q_FOREACH( QcGraphElement
* e
, _model
.elements() )
185 e
->setCurveType( QcGraphElement::Curvature
, curvature
);
189 void QcGraph::setCurves( int typeId
)
191 QcGraphElement::CurveType type
= (QcGraphElement::CurveType
)typeId
;
192 Q_FOREACH( QcGraphElement
* e
, _model
.elements() )
193 e
->setCurveType( type
);
197 void QcGraph::setStringAt( int i
, const QString
& str
)
199 int c
= _model
.elementCount();
200 if( i
>= 0 && i
< c
) {
201 QcGraphElement
*e
= _model
.elementAt(i
);
207 void QcGraph::connectElements( int src
, VariantList targets
)
209 int c
= _model
.elementCount();
210 if( src
< 0 || src
>= c
) return;
212 Q_FOREACH( QVariant var
, targets
.data
) {
213 int trg
= var
.toInt();
214 if( trg
< 0 || trg
>= c
) continue;
215 _model
.connect( src
, trg
);
221 void QcGraph::setIndex( int i
) {
222 if( i
>= -1 && i
< _model
.elementCount() ) {
228 void QcGraph::select( int i
, bool exclusive
) {
229 if( i
>= 0 && i
< _model
.elementCount() ) {
230 if( exclusive
) setAllDeselected();
231 setIndexSelected( i
, true );
236 void QcGraph::deselect( int i
) {
237 if( i
>= 0 && i
< _model
.elementCount() ) {
238 setIndexSelected( i
, false );
243 void QcGraph::deselectAll() {
247 void QcGraph::setCurrentX( float f
)
249 if( _curIndex
< 0 ) return;
250 QcGraphElement
*e
= _model
.elementAt(_curIndex
);
251 QPointF val
= e
->value
;
253 if( _xOrder
!= NoOrder
) orderRestrictValue(e
,val
,true);
254 else restrictValue(val
);
259 void QcGraph::setCurrentY( float f
)
261 if( _curIndex
< 0 ) return;
262 QcGraphElement
*e
= _model
.elementAt(_curIndex
);
263 QPointF val
= e
->value
;
265 if( _xOrder
!= NoOrder
) orderRestrictValue(e
,val
,true);
266 else restrictValue(val
);
271 void QcGraph::setFillColor( const QColor
& color
)
273 int c
= _model
.elementCount();
274 for( int i
=0; i
<c
; ++i
) {
275 QcGraphElement
*e
= _model
.elementAt(i
);
276 e
->fillColor
= color
;
281 void QcGraph::setFillColorAt( int i
, const QColor
& color
)
283 int c
= _model
.elementCount();
284 if( i
>= 0 && i
< c
) {
285 QcGraphElement
*e
= _model
.elementAt(i
);
286 e
->fillColor
= color
;
291 void QcGraph::setEditableAt( int i
, bool b
)
293 int c
= _model
.elementCount();
294 if( i
>= 0 && i
< c
) {
295 QcGraphElement
*e
= _model
.elementAt(i
);
300 void QcGraph::setStep( double step
)
302 _step
= qMax( 0.0, step
);
304 if( _model
.elementCount() ) {
310 void QcGraph::setHorizontalOrder( int i
) {
312 if( _xOrder
!= NoOrder
) {
318 void QcGraph::onElementRemoved( QcGraphElement
*e
)
320 _selection
.elems
.removeAll( SelectedElement(e
) );
323 void QcGraph::setAllDeselected()
325 int c
= _model
.elementCount();
326 for( int i
= 0; i
< c
; ++i
) {
327 QcGraphElement
*e
= _model
.elementAt(i
);
330 _selection
.elems
.clear();
333 void QcGraph::setIndexSelected( int index
, bool select
)
335 Q_ASSERT( index
>= 0 && index
< _model
.elementCount() );
337 QcGraphElement
*e
= _model
.elementAt( index
);
338 if( e
->selected
== select
) return;
342 int c
= _model
.elementCount();
346 if( _model
.elementAt(i
)->selected
) ++si
;
349 _selection
.elems
.insert( si
, SelectedElement(e
) );
353 _selection
.elems
.removeAll( SelectedElement(e
) );
359 inline static void qc_graph_round( double &val
, double &step
, bool &grid
)
365 double ratio
= ( val
+ (step
*0.5) > 1.0 ) ? floor(1.0/step
) : round(val
/step
);
368 else if ( val
> 1.0 ) {
373 inline void QcGraph::restrictValue( QPointF
& val
)
377 bool grid
= _step
> 0.0;
378 qc_graph_round(x
,_step
,grid
);
379 qc_graph_round(y
,_step
,grid
);
384 void QcGraph::orderRestrictValue( QcGraphElement
*e
, QPointF
& val
, bool selected
)
388 double x0
= e
->value
.x();
395 // new x is smaller, check if not too small;
396 QcGraphElement
*prev
= e
->prev();
397 if( prev
&& (selected
|| !prev
->selected
) && x
< prev
->value
.x() )
398 val
.setX( prev
->value
.x() );
401 // new x is larger, check if not too large;
402 QcGraphElement
*next
= e
->next();
403 if( next
&& (selected
|| !next
->selected
) && x
> next
->value
.x() )
404 val
.setX( next
->value
.x() );
408 inline void QcGraph::setValue( QcGraphElement
* e
, const QPointF
& pt
)
411 restrictValue( val
);
415 void QcGraph::ensureOrder()
417 int c
= _model
.elementCount();
419 for( int i
= 0; i
< c
; ++i
) {
420 QcGraphElement
*e
= _model
.elementAt(i
);
421 QPointF val
= e
->value
;
422 if( _xOrder
!= NoOrder
&& val
.x() < x_min
) val
.setX(x_min
);
424 x_min
= e
->value
.x();
428 void QcGraph::moveFree( QcGraphElement
*e
, const QPointF
& val
)
430 if( !e
->editable
) return;
434 void QcGraph::moveOrderRestricted( QcGraphElement
*e
, const QPointF
& val
)
436 if( !e
->editable
) return;
438 orderRestrictValue( e
, v
, true );
442 void QcGraph::moveSelected( const QPointF
& dif
, SelectionForm form
, bool cached
)
444 int c
= _selection
.count();
448 case ElasticSelection
: {
454 for( int i
= 0; i
< c
; ++i
) {
455 SelectedElement
& se
= _selection
.elems
[i
];
456 moveFree( se
.elem
, (cached
? se
.moveOrigin
: se
.elem
->value
) + dif
);
464 for( int i
= 0; i
< c
; ++i
) {
465 SelectedElement
& se
= _selection
.elems
[i
];
466 moveOrderRestricted( se
.elem
, (cached
? se
.moveOrigin
: se
.elem
->value
) + dif
);
470 for( int i
= _selection
.count() - 1; i
>= 0; --i
) {
471 SelectedElement
& se
= _selection
.elems
[i
];
472 moveOrderRestricted( se
.elem
, (cached
? se
.moveOrigin
: se
.elem
->value
) + dif
);
482 case RigidSelection
: {
484 // reduce dif until acceptable by all nodes
486 for( int i
= 0; i
< c
; ++i
) {
487 SelectedElement
& se
= _selection
.elems
[i
];
488 // if any node in selection is not editable, abort, since
489 // we want to keep the selection form!
490 if( !se
.elem
->editable
) return;
491 QPointF val0
= (cached
? se
.moveOrigin
: se
.elem
->value
);
492 QPointF val
= val0
+ d
;
493 if( _xOrder
== NoOrder
) {
494 restrictValue( val
);
497 orderRestrictValue( se
.elem
, val
, false );
502 // if no dif left, do not bother moving
503 if( d
.isNull() ) return;
505 // move all with the new dif
506 for( int i
= 0; i
< c
; ++i
) {
507 SelectedElement
& se
= _selection
.elems
[i
];
508 if( !se
.elem
->editable
) continue;
509 se
.elem
->value
= (cached
? se
.moveOrigin
: se
.elem
->value
) + d
;
519 void QcGraph::addCurve( QPainterPath
&path
, QcGraphElement
*e1
, QcGraphElement
*e2
)
521 QcGraphElement::CurveType type
= e1
->curveType
;
523 const QPointF
&pt1
= e1
->value
;
524 const QPointF
&pt2
= e2
->value
;
526 // coefficients for control points of cubic curve
527 // approximating first quarter of sinusoid
528 // technically: y = sin(pi*x/2) over x = [0,1]
529 static const float ax
= 1.0/3.0;
530 static const float ay
= 0.52359877f
; // pi/6
531 static const float bx
= 2.0/3.0;
532 static const float by
= 1.0;
535 case QcGraphElement::Step
:
537 path
.lineTo( pt1
.x(), pt2
.y() );
540 case QcGraphElement::Linear
:
544 case QcGraphElement::Sine
: {
545 // half of difference between end points
546 float dx
= (pt2
.x() - pt1
.x()) * 0.5f
;
547 float dy
= (pt2
.y() - pt1
.y()) * 0.5f
;
550 QPointF mid
= pt1
+ QPointF( dx
, dy
);
553 path
.cubicTo( pt1
+ QPointF( dx
*(1-bx
), dy
*(1-by
) ), pt1
+ QPointF( dx
*(1-ax
), dy
*(1-ay
) ), mid
);
554 path
.cubicTo( mid
+ QPointF( dx
*ax
, dy
*ay
), mid
+ QPointF( dx
*bx
, dy
*by
), pt2
);
558 case QcGraphElement::Welch
: {
559 // difference between points
560 float dx
= (pt2
.x() - pt1
.x());
561 float dy
= (pt2
.y() - pt1
.y());
565 path
.cubicTo( pt1
+ QPointF( dx
*ax
, dy
*ay
), pt1
+ QPointF( dx
*bx
, dy
*by
), pt2
);
567 path
.cubicTo( pt1
+ QPointF( dx
*(1-bx
), dy
*(1-by
) ), pt1
+ QPointF( dx
*(1-ax
), dy
*(1-ay
) ), pt2
);
571 case QcGraphElement::Exponential
: {
573 // FIXME: find a Bezier curve approximation
577 float dx
= (pt2
.x() - pt1
.x());
578 float dy
= (pt2
.y() - pt1
.y());
580 // prevent NaN, optimize
581 if( pt1
.y() <= 0.f
|| pt2
.y() <= 0.f
) {
582 path
.lineTo( dy
< 0 ? QPointF(pt1
.x(),pt2
.y()) : QPointF(pt2
.x(), pt1
.y()) );
586 const float n
= 100.f
;
587 const float yratio
= pt2
.y() / pt1
.y();
588 for( float ph
=1/n
; ph
<=(1-1/n
); ph
+=1/n
) {
589 qreal y
= pt1
.y() * pow( yratio
, ph
);
590 path
.lineTo( pt1
.x() + (dx
* ph
), y
);
597 case QcGraphElement::Curvature
:
599 // FIXME: find a Bezier curve approximation
604 double curve
= qBound( -100.0, e1
->curvature
, 100.0 );
606 if( std::abs( curve
) < 0.0001 ) {
610 float dx
= (pt2
.x() - pt1
.x());
611 float dy
= (pt2
.y() - pt1
.y());
612 double denom
= 1.0 - exp( curve
);
613 const float n
= 100.f
;
614 for( float ph
=1/n
; ph
<=(1-1/n
); ph
+=1/n
) {
615 double numer
= 1.0 - exp( ph
* curve
);
616 qreal y
= pt1
.y() + dy
* (numer
/ denom
);
617 path
.lineTo( pt1
.x() + (dx
* ph
), y
);
625 QRectF
QcGraph::labelRect( QcGraphElement
*e
, const QPointF
&pt
, const QRect
&bounds
, const QFontMetrics
&fm
)
627 QRectF
textRect( fm
.boundingRect(e
->text
) );
628 qreal hnd_w_2
= _thumbSize
.width() * 0.5;
629 qreal hnd_h_2
= _thumbSize
.height() * 0.5;
630 textRect
.moveBottomLeft( pt
+ QPointF(hnd_w_2
, -hnd_h_2
) );
631 if( textRect
.y() < bounds
.y() )
632 textRect
.moveTop( pt
.y() + hnd_h_2
);
633 if( textRect
.right() > bounds
.right() )
634 textRect
.moveRight( pt
.x() - hnd_w_2
);
638 void QcGraph::paintEvent( QPaintEvent
* )
640 using namespace QtCollider::Style
;
643 QPalette
plt( palette() );
645 p
.setRenderHint( QPainter::Antialiasing
, true );
646 RoundRect
frame(rect(), 2);
647 drawSunken( &p
, plt
, frame
, plt
.color(QPalette::Base
), hasFocus() ? focusColor() : QColor() );
648 p
.setRenderHint( QPainter::Antialiasing
, false );
650 QRect
contentsRect( marginsRect( sunkenContentsRect( rect() ), _thumbSize
) );
652 QColor strokeColor
= _strokeColor
.isValid() ? _strokeColor
: plt
.color(QPalette::Text
);
654 if(_gridColor
.isValid())
655 gridColor
= _gridColor
;
657 gridColor
= plt
.color(QPalette::Text
);
658 gridColor
.setAlpha(40);
664 p
.setBrush( Qt::NoBrush
);
665 p
.drawRect( contentsRect
);
668 float dx
= _gridMetrics
.x();
669 float dy
= _gridMetrics
.y();
670 float cl
= contentsRect
.left();
671 float cr
= contentsRect
.right();
672 float ct
= contentsRect
.top();
673 float cb
= contentsRect
.bottom();
675 if( contentsRect
.width() > 0.f
&& dx
> 0.f
&& dx
< 1.f
) {
676 dx
*= contentsRect
.width();
679 while( (x
= i
* dx
+ cl
) < cr
) {
680 p
.drawLine( x
, ct
, x
, cb
);
685 if( contentsRect
.height() > 0.f
&& dy
> 0.f
&& dy
< 1.f
) {
686 dy
*= contentsRect
.height();
689 while( (y
= cb
- i
* dy
) > ct
) {
690 p
.drawLine( cl
, y
, cr
, y
);
696 QList
<QcGraphElement
*> elems
= _model
.elements();
698 int c
= elems
.count();
701 p
.setPen( strokeColor
);
707 QList
<QcGraphModel::Connection
> conns
= _model
.connections();
709 if( conns
.count() ) {
711 Q_FOREACH( QcGraphModel::Connection c
, conns
) {
712 addCurve( lines
, c
.a
, c
.b
);
718 QcGraphElement
*e1
= elems
[0];
720 for( i
= 1; i
< c
; ++i
) {
721 QcGraphElement
*e2
= elems
[i
];
722 addCurve( lines
, e1
, e2
);
729 p
.setRenderHint( QPainter::Antialiasing
, true );
730 p
.setBrush( Qt::NoBrush
);
731 p
.translate( contentsRect
.x(), contentsRect
.y() + contentsRect
.height() );
732 p
.scale( contentsRect
.width(), -contentsRect
.height() );
737 // draw rects and strings
740 p
.setRenderHint( QPainter::Antialiasing
, true );
742 QFontMetrics
fm( font() );
743 QColor thumbColor
= plt
.color(QPalette::Button
).lighter(105); thumbColor
.setAlpha(70);
744 QColor defFillColor
= plt
.color(QPalette::Text
);
745 QRectF rect
; rect
.setSize( _thumbSize
);
749 for( i
= 0; i
< c
; ++i
) {
751 QcGraphElement
*e
= elems
[i
];
753 pt
= Style::pos( e
->value
, contentsRect
);
754 rect
.moveCenter( pt
.toPoint() );
758 drawRaised( &p
, plt
, thumb
, thumbColor
);
761 QRectF
r( thumb
._rect
);
762 qreal wdif
= r
.width() * 0.3;
763 qreal hdif
= r
.height() * 0.3;
764 p
.setPen( Qt::NoPen
);
765 p
.setBrush( e
->fillColor
.isValid() ? e
->fillColor
: defFillColor
);
766 p
.drawEllipse( r
.adjusted( wdif
, hdif
, -wdif
, -hdif
) );
768 // selection indicator
770 p
.setBrush(Qt::NoBrush
);
771 p
.setPen( plt
.color(QPalette::Highlight
) );
772 p
.drawEllipse( thumb
._rect
.adjusted(1,1,-1,-1) );
776 p
.setPen( strokeColor
);
777 QString text
= e
->text
;
778 if( !text
.isEmpty() ) {
779 QRectF
lblRect( labelRect( e
, pt
, contentsRect
, fm
) );
780 p
.drawText( lblRect
, 0, text
);
788 void QcGraph::mousePressEvent( QMouseEvent
*ev
)
790 using namespace QtCollider::Style
;
792 QList
<QcGraphElement
*> elems
= _model
.elements();
793 int c
= elems
.count();
796 QPointF mpos
= ev
->pos();
797 QRectF
valueRect( marginsRect( sunkenContentsRect( rect() ), _thumbSize
) );
800 r
.setSize( _thumbSize
);
803 for( i
= 0; i
< c
; ++i
) {
804 QcGraphElement
*e
= elems
[i
];
805 QPointF
pt( Style::pos( e
->value
, valueRect
) );
807 if( r
.contains( mpos
) ) {
810 if( ev
->modifiers() & Qt::ShiftModifier
) {
811 setIndexSelected( i
, !e
->selected
);
816 setIndexSelected( i
, true );
820 _selection
.cached
= false;
822 // if the element that was hit ended up selected
823 // prepare for moving
824 _selection
.shallMove
= true;
825 _selection
.moveOrigin
= Style::value(mpos
, valueRect
);
828 _selection
.shallMove
= false;
836 _selection
.shallMove
= false;
838 if( !(ev
->modifiers() & Qt::ShiftModifier
) ) {
846 void QcGraph::mouseMoveEvent( QMouseEvent
*ev
)
848 using namespace QtCollider::Style
;
850 if( !ev
->buttons() ) return;
852 if( !_editable
|| !_selection
.shallMove
|| !_selection
.size() ) return;
854 if( !_selection
.cached
) {
855 int c
= _selection
.count();
856 for( int i
= 0; i
< c
; ++i
) {
857 SelectedElement
&se
= _selection
.elems
[i
];
858 se
.moveOrigin
= se
.elem
->value
;
860 _selection
.cached
= true;
863 QRectF
valueRect( marginsRect( sunkenContentsRect( rect() ), _thumbSize
) );
864 QPointF
dValue( Style::value( ev
->pos(), valueRect
) );
865 dValue
= dValue
- _selection
.moveOrigin
;
867 moveSelected( dValue
, _selectionForm
, true );
870 doAction( ev
->modifiers() );
873 void QcGraph::keyPressEvent( QKeyEvent
*event
)
875 if( _curIndex
< 0 ) return;
877 if( event
->modifiers() & Qt::AltModifier
) {
880 if( !_editable
|| !_selection
.size() ) return;
884 switch( event
->key() ) {
886 dValue
.setY( _step
);
889 dValue
.setY( - _step
);
892 dValue
.setX( _step
);
895 dValue
.setX( - _step
);
901 moveSelected( dValue
, _selectionForm
, false );
904 doAction( event
->modifiers() );
910 switch( event
->key() ) {
912 setIndex( _curIndex
+1 );
913 if( !(event
->modifiers() & Qt::ShiftModifier
) ) setAllDeselected();
914 setIndexSelected( _curIndex
, true );
917 // always keep an index current:
918 if( _curIndex
> 0 ) setIndex( _curIndex
-1 );
919 if( !(event
->modifiers() & Qt::ShiftModifier
) ) setAllDeselected();
920 setIndexSelected( _curIndex
, true );
928 void QcGraph::doAction( Qt::KeyboardModifiers mods
)
930 Qt::KeyboardModifier ctrlMod
=
938 Q_EMIT( metaAction() );