fix String:wrapExtend to fill out to the actual requested size
[supercollider.git] / QtCollider / widgets / QcGraph.cpp
blob1123b76bd453f0fc225e0563ac0733b3683442c1
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 _thumbSize( QSize( 18, 18 ) ),
63 _drawLines( true ),
64 _drawRects( true ),
65 _editable( true ),
66 _step( 0.f ),
67 _selectionForm( ElasticSelection ),
68 _xOrder( NoOrder ),
69 _gridOn( false ),
70 _curIndex( -1 )
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
82 VariantList x;
83 VariantList y;
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() );
91 VariantList values;
92 values.data.append( QVariant::fromValue<VariantList>(x) );
93 values.data.append( QVariant::fromValue<VariantList>(y) );
95 return values;
98 float QcGraph::currentX() const
100 if( _curIndex < 0 ) return 0.f;
101 QcGraphElement *e = _model.elementAt(_curIndex);
102 return e->value.x();
105 float QcGraph::currentY() const
107 if( _curIndex < 0 ) return 0.f;
108 QcGraphElement *e = _model.elementAt(_curIndex);
109 return e->value.y();
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() );
119 if( !newc ) return;
121 int c = _model.elementCount();
122 while( c > newc )
124 --c;
125 _model.removeAt( c );
128 int i;
129 for( i = 0; i < newc; ++i )
131 QPointF val( xList.data[i].value<float>(),
132 yList.data[i].value<float>() );
133 if( i < c ) {
134 QcGraphElement *e = _model.elementAt(i);
135 setValue( e, val );
137 else {
138 QcGraphElement *e = new QcGraphElement();
139 setValue( e, val );
140 _model.append( e );
144 if( newc ) ensureOrder();
146 _curIndex = -1;
148 update();
151 void QcGraph::setStrings( const VariantList &list )
153 int strc = list.data.count();
154 int c = _model.elementCount();
155 int i;
156 for( i = 0; i < c && i < strc; ++i ) {
157 QcGraphElement *e = _model.elementAt(i);
158 e->text = list.data[i].toString();
160 update();
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;
168 double curvature;
169 if( data.type() == QVariant::Int ) {
170 type = (QcGraphElement::CurveType) data.toInt();
171 curvature = 0.0;
173 else {
174 type = QcGraphElement::Curvature;
175 curvature = data.value<double>();
177 _model.elementAt(i)->setCurveType( type, curvature );
179 update();
182 void QcGraph::setCurves( double curvature )
184 Q_FOREACH( QcGraphElement* e, _model.elements() )
185 e->setCurveType( QcGraphElement::Curvature, curvature );
186 update();
189 void QcGraph::setCurves( int typeId )
191 QcGraphElement::CurveType type = (QcGraphElement::CurveType)typeId;
192 Q_FOREACH( QcGraphElement* e, _model.elements() )
193 e->setCurveType( type );
194 update();
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);
202 e->text = str;
203 update();
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 );
218 update();
221 void QcGraph::setIndex( int i ) {
222 if( i >= -1 && i < _model.elementCount() ) {
223 _curIndex = i;
224 update();
228 void QcGraph::select( int i, bool exclusive ) {
229 if( i >= 0 && i < _model.elementCount() ) {
230 if( exclusive ) setAllDeselected();
231 setIndexSelected( i, true );
232 update();
236 void QcGraph::deselect( int i ) {
237 if( i >= 0 && i < _model.elementCount() ) {
238 setIndexSelected( i, false );
239 update();
243 void QcGraph::deselectAll() {
244 setAllDeselected();
247 void QcGraph::setCurrentX( float f )
249 if( _curIndex < 0 ) return;
250 QcGraphElement *e = _model.elementAt(_curIndex);
251 QPointF val = e->value;
252 val.setX( f );
253 if( _xOrder != NoOrder ) orderRestrictValue(e,val,true);
254 else restrictValue(val);
255 e->value = val;
256 update();
259 void QcGraph::setCurrentY( float f )
261 if( _curIndex < 0 ) return;
262 QcGraphElement *e = _model.elementAt(_curIndex);
263 QPointF val = e->value;
264 val.setY( f );
265 if( _xOrder != NoOrder ) orderRestrictValue(e,val,true);
266 else restrictValue(val);
267 e->value = val;
268 update();
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;
278 update();
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;
287 update();
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);
296 e->editable = b;
300 void QcGraph::setStep( double step )
302 _step = qMax( 0.0, step );
304 if( _model.elementCount() ) {
305 ensureOrder();
306 update();
310 void QcGraph::setHorizontalOrder( int i ) {
311 _xOrder = (Order) i;
312 if( _xOrder != NoOrder ) {
313 ensureOrder();
314 update();
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);
328 e->selected = false;
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;
340 if( select ) {
341 e->selected = true;
342 int c = _model.elementCount();
343 int si = 0;
344 int i = 0;
345 while( i < index ) {
346 if( _model.elementAt(i)->selected ) ++si;
347 ++i;
349 _selection.elems.insert( si, SelectedElement(e) );
351 else {
352 e->selected = false;
353 _selection.elems.removeAll( SelectedElement(e) );
356 update();
359 inline static void qc_graph_round( double &val, double &step, bool &grid )
361 if( val < 0.0 ) {
362 val = 0.0;
364 else if ( grid ) {
365 double ratio = ( val + (step*0.5) > 1.0 ) ? floor(1.0/step) : round(val/step);
366 val = ratio * step;
368 else if ( val > 1.0 ) {
369 val = 1.0;
373 inline void QcGraph::restrictValue( QPointF & val )
375 double x = val.x();
376 double y = val.y();
377 bool grid = _step > 0.0;
378 qc_graph_round(x,_step,grid);
379 qc_graph_round(y,_step,grid);
380 val.setX(x);
381 val.setY(y);
384 void QcGraph::orderRestrictValue( QcGraphElement *e, QPointF & val, bool selected )
386 restrictValue(val);
388 double x0 = e->value.x();
389 double x = val.x();
391 if( x == x0 ) {
392 return;
394 else if( x < x0 ) {
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() );
400 else {
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 )
410 QPointF val(pt);
411 restrictValue( val );
412 e->value = val;
415 void QcGraph::ensureOrder()
417 int c = _model.elementCount();
418 double x_min = 0.0;
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);
423 setValue( e, val );
424 x_min = e->value.x();
428 void QcGraph::moveFree( QcGraphElement *e, const QPointF & val )
430 if( !e->editable ) return;
431 setValue( e, val );
434 void QcGraph::moveOrderRestricted( QcGraphElement *e, const QPointF & val )
436 if( !e->editable ) return;
437 QPointF v(val);
438 orderRestrictValue( e, v, true );
439 e->value = v;
442 void QcGraph::moveSelected( const QPointF & dif, SelectionForm form, bool cached )
444 int c = _selection.count();
446 switch( form ) {
448 case ElasticSelection: {
450 switch( _xOrder ) {
452 case NoOrder:
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 );
459 break;
461 case RigidOrder:
463 if( dif.x() <= 0 ) {
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 );
469 else {
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 );
476 break;
479 break;
482 case RigidSelection: {
484 // reduce dif until acceptable by all nodes
485 QPointF d(dif);
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 );
496 else {
497 orderRestrictValue( se.elem, val, false );
499 d = val - val0;
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;
512 break;
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;
534 switch( type ) {
535 case QcGraphElement::Step:
536 path.moveTo( pt1 );
537 path.lineTo( pt1.x(), pt2.y() );
538 path.lineTo( pt2 );
539 break;
540 case QcGraphElement::Linear:
541 path.moveTo( pt1 );
542 path.lineTo( pt2 );
543 break;
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;
549 // middle point
550 QPointF mid = pt1 + QPointF( dx, dy );
552 path.moveTo( pt1 );
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 );
556 break;
558 case QcGraphElement::Welch: {
559 // difference between points
560 float dx = (pt2.x() - pt1.x());
561 float dy = (pt2.y() - pt1.y());
563 path.moveTo( pt1 );
564 if( dy > 0 )
565 path.cubicTo( pt1 + QPointF( dx*ax, dy*ay ), pt1 + QPointF( dx*bx, dy*by ), pt2 );
566 else
567 path.cubicTo( pt1 + QPointF( dx*(1-bx), dy*(1-by) ), pt1 + QPointF( dx*(1-ax), dy*(1-ay) ), pt2 );
569 break;
571 case QcGraphElement::Exponential: {
573 // FIXME: find a Bezier curve approximation
575 path.moveTo( pt1 );
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()) );
583 path.lineTo( pt2 );
585 else {
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 );
592 path.lineTo( pt2 );
595 break;
597 case QcGraphElement::Curvature:
599 // FIXME: find a Bezier curve approximation
601 path.moveTo( pt1 );
603 // prevent NaN
604 double curve = qBound( -100.0, e1->curvature, 100.0 );
606 if( std::abs( curve ) < 0.0001 ) {
607 path.lineTo( pt2 );
609 else {
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 );
619 path.lineTo( pt2 );
621 break;
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 );
635 return textRect;
638 void QcGraph::paintEvent( QPaintEvent * )
640 using namespace QtCollider::Style;
642 QPainter p( this );
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);
653 QColor gridColor;
654 if(_gridColor.isValid())
655 gridColor = _gridColor;
656 else {
657 gridColor = plt.color(QPalette::Text);
658 gridColor.setAlpha(40);
661 //draw grid;
663 p.setPen(gridColor);
664 p.setBrush( Qt::NoBrush );
665 p.drawRect( contentsRect );
667 if( _gridOn ) {
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();
677 int i = 1;
678 float x;
679 while( (x = i * dx + cl) < cr ) {
680 p.drawLine( x, ct, x, cb );
681 ++i;
685 if( contentsRect.height() > 0.f && dy > 0.f && dy < 1.f ) {
686 dy *= contentsRect.height();
687 int i = 1;
688 float y;
689 while( (y = cb - i * dy) > ct ) {
690 p.drawLine( cl, y, cr, y );
691 ++i;
696 QList<QcGraphElement*> elems = _model.elements();
698 int c = elems.count();
699 if( !c ) return;
701 p.setPen( strokeColor );
703 // draw lines;
704 if( _drawLines ) {
706 QPainterPath lines;
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 );
716 else {
718 QcGraphElement *e1 = elems[0];
719 int i;
720 for( i = 1; i < c; ++i ) {
721 QcGraphElement *e2 = elems[i];
722 addCurve( lines, e1, e2 );
723 e1 = e2;
728 p.save();
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() );
733 p.drawPath( lines );
734 p.restore();
737 // draw rects and strings
738 if( _drawRects ) {
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 );
746 QPointF pt;
747 int i;
749 for( i = 0; i < c; ++i ) {
751 QcGraphElement *e = elems[i];
753 pt = Style::pos( e->value, contentsRect );
754 rect.moveCenter( pt.toPoint() );
756 // base
757 Ellipse thumb(rect);
758 drawRaised( &p, plt, thumb, thumbColor );
760 // marker
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
769 if( e->selected ) {
770 p.setBrush(Qt::NoBrush);
771 p.setPen( plt.color(QPalette::Highlight) );
772 p.drawEllipse( thumb._rect.adjusted(1,1,-1,-1) );
775 // label
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();
794 if( !c ) return;
796 QPointF mpos = ev->pos();
797 QRectF valueRect( marginsRect( sunkenContentsRect( rect() ), _thumbSize ) );
799 QRectF r;
800 r.setSize( _thumbSize );
802 int i;
803 for( i = 0; i < c; ++i ) {
804 QcGraphElement *e = elems[i];
805 QPointF pt( Style::pos( e->value, valueRect ) );
806 r.moveCenter( pt );
807 if( r.contains( mpos ) ) {
808 _curIndex = i;
810 if( ev->modifiers() & Qt::ShiftModifier ) {
811 setIndexSelected( i, !e->selected );
813 else {
814 if( !e->selected ) {
815 setAllDeselected();
816 setIndexSelected( i, true );
820 _selection.cached = false;
821 if( e->selected ) {
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);
827 else {
828 _selection.shallMove = false;
831 update();
832 return;
836 _selection.shallMove = false;
838 if( !(ev->modifiers() & Qt::ShiftModifier) ) {
839 _curIndex = 0;
840 setAllDeselected();
843 update();
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 );
869 update();
870 doAction( ev->modifiers() );
873 void QcGraph::keyPressEvent( QKeyEvent *event )
875 if( _curIndex < 0 ) return;
877 if( event->modifiers() & Qt::AltModifier ) {
878 // editing mode
880 if( !_editable || !_selection.size() ) return;
882 QPointF dValue;;
884 switch( event->key() ) {
885 case Qt::Key_Up:
886 dValue.setY( _step );
887 break;
888 case Qt::Key_Down:
889 dValue.setY( - _step );
890 break;
891 case Qt::Key_Right:
892 dValue.setX( _step );
893 break;
894 case Qt::Key_Left:
895 dValue.setX( - _step );
896 break;
897 default:
898 return;
901 moveSelected( dValue, _selectionForm, false );
903 update();
904 doAction( event->modifiers() );
907 else {
908 // selection mode
910 switch( event->key() ) {
911 case Qt::Key_Right:
912 setIndex( _curIndex+1 );
913 if( !(event->modifiers() & Qt::ShiftModifier) ) setAllDeselected();
914 setIndexSelected( _curIndex, true );
915 break;
916 case Qt::Key_Left:
917 // always keep an index current:
918 if( _curIndex > 0 ) setIndex( _curIndex-1 );
919 if( !(event->modifiers() & Qt::ShiftModifier) ) setAllDeselected();
920 setIndexSelected( _curIndex, true );
921 break;
922 default: break;
928 void QcGraph::doAction( Qt::KeyboardModifiers mods )
930 Qt::KeyboardModifier ctrlMod =
931 #ifdef Q_OS_MAC
932 Qt::MetaModifier;
933 #else
934 Qt::ControlModifier;
935 #endif
937 if( mods & ctrlMod )
938 Q_EMIT( metaAction() );
939 else
940 Q_EMIT( action() );