File indexing completed on 2024-05-05 04:51:40

0001 /*
0002     SPDX-FileCopyrightText: 2010-2011 Michal Malek <michalm@jabster.pl>
0003     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "k3baudioeditorwidget.h"
0009 
0010 #include <QList>
0011 #include <QCursor>
0012 #include <QHelpEvent>
0013 #include <QMouseEvent>
0014 #include <QPainter>
0015 #include <QPixmap>
0016 #include <QPolygon>
0017 #include <QApplication>
0018 #include <QScreen>
0019 #include <QFrame>
0020 #include <QToolTip>
0021 
0022 
0023 
0024 class K3b::AudioEditorWidget::Range
0025 {
0026 public:
0027     Range( int i,
0028            const K3b::Msf& s,
0029            const K3b::Msf& e,
0030            bool sf,
0031            bool ef,
0032            const QString& t,
0033            const QBrush& b )
0034         : id(i),
0035           start(s),
0036           end(e),
0037           startFixed(sf),
0038           endFixed(ef),
0039           brush(b),
0040           toolTip(t) {
0041     }
0042 
0043     int id;
0044     K3b::Msf start;
0045     K3b::Msf end;
0046     bool startFixed;
0047     bool endFixed;
0048     QBrush brush;
0049     QString toolTip;
0050 
0051     bool operator<( const K3b::AudioEditorWidget::Range& r ) const {
0052         return start < r.start;
0053     }
0054     bool operator>( const K3b::AudioEditorWidget::Range& r ) const {
0055         return start > r.start;
0056     }
0057     bool operator==( const K3b::AudioEditorWidget::Range& r ) const {
0058         return id == r.id;
0059     }
0060 
0061     typedef QList<Range> List;
0062 };
0063 
0064 
0065 class K3b::AudioEditorWidget::Marker
0066 {
0067 public:
0068     Marker( int i,
0069             const K3b::Msf& msf,
0070             bool f,
0071             const QColor& c,
0072             const QString& t )
0073         : id(i),
0074           pos(msf),
0075           fixed(f),
0076           color(c),
0077           toolTip(t) {
0078     }
0079 
0080     int id;
0081     K3b::Msf pos;
0082     bool fixed;
0083     QColor color;
0084     QString toolTip;
0085 
0086     operator K3b::Msf& () { return pos; }
0087 
0088     bool operator==( const Marker& r ) const {
0089         return id == r.id;
0090     }
0091 
0092     typedef QList<Marker> List;
0093 };
0094 
0095 
0096 struct K3b::AudioEditorWidget::SortByStart
0097 {
0098     bool operator()( Range const* lhs, Range const* rhs )
0099     {
0100         return lhs->start < rhs->start;
0101     }
0102 };
0103 
0104 
0105 class K3b::AudioEditorWidget::Private
0106 {
0107 public:
0108     Private()
0109         : allowOverlappingRanges(true),
0110           rangeSelectionEnabled(false),
0111           selectedRangeId(0),
0112           draggedRangeId(0),
0113           movedRangeId(0),
0114           maxMarkers(1),
0115           idCnt(1),
0116           mouseAt(true),
0117           draggingRangeEnd(false),
0118           draggedMarker(0),
0119           margin(5) {
0120     }
0121 
0122     QBrush selectedRangeBrush;
0123 
0124     bool allowOverlappingRanges;
0125     bool rangeSelectionEnabled;
0126 
0127     int selectedRangeId;
0128     int draggedRangeId;
0129     int movedRangeId;
0130     K3b::Msf lastMovePosition;
0131 
0132     Range::List ranges;
0133     Marker::List markers;
0134 
0135     int maxMarkers;
0136     K3b::Msf length;
0137     int idCnt;
0138     bool mouseAt;
0139 
0140     bool draggingRangeEnd;
0141     Marker* draggedMarker;
0142 
0143     /**
0144      * Margin around the timethingy
0145      */
0146     int margin;
0147 };
0148 
0149 
0150 K3b::AudioEditorWidget::AudioEditorWidget( QWidget* parent )
0151     : QFrame( parent )
0152 {
0153     d = new Private;
0154     d->selectedRangeBrush = palette().highlight();
0155 
0156     setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum );
0157     setFrameStyle( StyledPanel|Sunken );
0158     setMouseTracking(true);
0159     setCursor( Qt::PointingHandCursor );
0160 }
0161 
0162 
0163 K3b::AudioEditorWidget::~AudioEditorWidget()
0164 {
0165     delete d;
0166 }
0167 
0168 
0169 QSize K3b::AudioEditorWidget::minimumSizeHint() const
0170 {
0171     // some fixed height minimum and enough space for a tickmark every minute
0172     // But never exceed 2/3 of the screen width, otherwise it just looks ugly
0173     // FIXME: this is still bad for long sources and there might be 60 minutes sources!
0174 
0175     int wantedWidth = 2*d->margin + 2*frameWidth() + (d->length.totalFrames()/75/60 + 1) * fontMetrics().horizontalAdvance( "000" );
0176     int maxWidth = wantedWidth;
0177     auto screen = QApplication::primaryScreen();
0178     if (screen) {
0179         maxWidth = screen->availableSize().width()*2/3;
0180     }
0181     return QSize( qMin( maxWidth, wantedWidth ),
0182                   2*d->margin + 12 + 6 /*12 for the tickmarks and 6 for the markers */ + fontMetrics().height() + 2*frameWidth() );
0183 }
0184 
0185 
0186 QSize K3b::AudioEditorWidget::sizeHint() const
0187 {
0188     return minimumSizeHint();
0189 }
0190 
0191 
0192 void K3b::AudioEditorWidget::setLength( const K3b::Msf& length )
0193 {
0194     d->length = length;
0195     // TODO: remove markers beyond length
0196     // TODO: shorten ranges if necessary
0197     update();
0198 }
0199 
0200 
0201 const K3b::Msf K3b::AudioEditorWidget::length() const
0202 {
0203     return d->length;
0204 }
0205 
0206 
0207 void K3b::AudioEditorWidget::setSelectedRangeBrush( const QBrush& b )
0208 {
0209     d->selectedRangeBrush = b;
0210 }
0211 
0212 
0213 const QBrush& K3b::AudioEditorWidget::selectedRangeBrush() const
0214 {
0215     return d->selectedRangeBrush;
0216 }
0217 
0218 
0219 void K3b::AudioEditorWidget::setAllowOverlappingRanges( bool b )
0220 {
0221     d->allowOverlappingRanges = b;
0222 }
0223 
0224 
0225 bool K3b::AudioEditorWidget::allowOverlappingRanges() const
0226 {
0227     return d->allowOverlappingRanges;
0228 }
0229 
0230 
0231 void K3b::AudioEditorWidget::enableRangeSelection( bool b )
0232 {
0233     d->rangeSelectionEnabled = b;
0234     update();
0235 }
0236 
0237 
0238 bool K3b::AudioEditorWidget::rangeSelectedEnabled() const
0239 {
0240     return d->selectedRangeId != 0;
0241 }
0242 
0243 
0244 void K3b::AudioEditorWidget::setSelectedRange( int id )
0245 {
0246     d->selectedRangeId = id;
0247     if( rangeSelectedEnabled() ) {
0248         update();
0249         emit selectedRangeChanged( d->selectedRangeId );
0250     }
0251 }
0252 
0253 
0254 int K3b::AudioEditorWidget::selectedRange() const
0255 {
0256     return d->selectedRangeId;
0257 }
0258 
0259 
0260 int K3b::AudioEditorWidget::addRange( const K3b::Msf& start, const K3b::Msf& end,
0261                                     bool startFixed, bool endFixed,
0262                                     const QString& toolTip,
0263                                     const QBrush& brush )
0264 {
0265     if( start > end || end > d->length-1 )
0266         return -1;
0267 
0268     Range r( d->idCnt++, start, end, startFixed, endFixed, toolTip,
0269              brush.style() != Qt::NoBrush ? brush : palette().window() );
0270     d->ranges.append( r );
0271 
0272     // only update the changed range
0273     QRect rect = contentsRect();
0274     rect.setLeft( msfToPos( start ) );
0275     rect.setRight( msfToPos( end ) );
0276     update( rect );
0277 
0278     return r.id;
0279 }
0280 
0281 
0282 int K3b::AudioEditorWidget::findRange( int pos ) const
0283 {
0284     Range* r = findRange( QPoint( pos, 0 ) );
0285     if( r )
0286         return r->id;
0287     else
0288         return 0;
0289 }
0290 
0291 
0292 int K3b::AudioEditorWidget::findRangeEdge( int pos, bool* end ) const
0293 {
0294     if( Range* r = findRangeEdge( QPoint( pos, 0 ), end ) )
0295         return r->id;
0296     else
0297         return 0;
0298 }
0299 
0300 
0301 bool K3b::AudioEditorWidget::modifyRange( int identifier, const K3b::Msf& start, const K3b::Msf& end )
0302 {
0303     if( Range* range = getRange( identifier ) ) {
0304         if( start > end )
0305             return false;
0306 
0307         if( end > d->length )
0308             return false;
0309 
0310         range->start = start;
0311         range->end = end;
0312 
0313         if( !d->allowOverlappingRanges )
0314             fixupOverlappingRanges( range->id );
0315 
0316         repaint();
0317 
0318         return true;
0319     }
0320     else
0321         return false;
0322 }
0323 
0324 
0325 bool K3b::AudioEditorWidget::removeRange( int identifier )
0326 {
0327     if( Range* range = getRange( identifier ) ) {
0328         emit rangeRemoved( identifier );
0329 
0330         // repaint only the part of the range
0331         QRect rect = contentsRect();
0332         rect.setLeft( msfToPos( range->start ) );
0333         rect.setRight( msfToPos( range->end ) );
0334 
0335         if( d->selectedRangeId == range->id )
0336             setSelectedRange( 0 );
0337 
0338         d->ranges.removeAll( *range );
0339 
0340         update( rect );
0341 
0342         return true;
0343     }
0344     else
0345         return false;
0346 }
0347 
0348 
0349 K3b::Msf K3b::AudioEditorWidget::rangeStart( int identifier ) const
0350 {
0351     if( Range* range = getRange( identifier ) )
0352         return range->start;
0353     else
0354         return 0;
0355 }
0356 
0357 
0358 K3b::Msf K3b::AudioEditorWidget::rangeEnd( int identifier ) const
0359 {
0360     if( Range* range = getRange( identifier ) )
0361         return range->end;
0362     else
0363         return 0;
0364 }
0365 
0366 
0367 QList<int> K3b::AudioEditorWidget::allRanges() const
0368 {
0369     // First collect all ranges to one random-access container...
0370     QList<Range const*> ranges;
0371     Q_FOREACH( Range const& range, d->ranges ) {
0372         ranges.push_back( &range );
0373     }
0374 
0375     // ...then sort it...
0376     std::sort(ranges.begin(), ranges.end(), SortByStart());
0377 
0378     // ...finally collect its identifiers and return the collection.
0379     QList<int> identifiers;
0380     Q_FOREACH( Range const* range, ranges ) {
0381         identifiers.push_back( range->id );
0382     }
0383     return identifiers;
0384 }
0385 
0386 
0387 void K3b::AudioEditorWidget::setMaxNumberOfMarkers( int i )
0388 {
0389     d->maxMarkers = i;
0390 
0391     // remove last markers
0392     while( d->markers.count() > qMax( 1, d->maxMarkers ) ) {
0393         removeMarker( d->markers.last().id );
0394     }
0395 }
0396 
0397 
0398 int K3b::AudioEditorWidget::addMarker( const K3b::Msf& pos, bool fixed, const QString& toolTip, const QColor& color )
0399 {
0400     if( pos < d->length ) {
0401         Marker m( d->idCnt++, pos, fixed, color.isValid() ? color : palette().windowText().color(), toolTip );
0402         d->markers.append( m );
0403         return m.id;
0404     }
0405     else
0406         return -1;
0407 }
0408 
0409 
0410 bool K3b::AudioEditorWidget::removeMarker( int identifier )
0411 {
0412     if( Marker* m = getMarker( identifier ) ) {
0413         emit markerRemoved( identifier );
0414 
0415         // TODO: in case a marker is bigger than one pixel this needs to be changed
0416         QRect rect = contentsRect();
0417         rect.setLeft( msfToPos( m->pos ) );
0418         rect.setRight( msfToPos( m->pos ) );
0419 
0420         d->markers.removeAll( *m );
0421 
0422         update( rect );
0423 
0424         return true;
0425     }
0426     else
0427         return false;
0428 }
0429 
0430 
0431 bool K3b::AudioEditorWidget::moveMarker( int identifier, const K3b::Msf& pos )
0432 {
0433     if( pos < d->length )
0434         if( Marker* m = getMarker( identifier ) ) {
0435             QRect rect = contentsRect();
0436             rect.setLeft( qMin( msfToPos( pos ), msfToPos( m->pos ) ) );
0437             rect.setRight( qMax( msfToPos( pos ), msfToPos( m->pos ) ) );
0438 
0439             m->pos = pos;
0440 
0441             // TODO: in case a marker is bigger than one pixel this needs to be changed
0442             update( rect );
0443 
0444             return true;
0445         }
0446 
0447     return false;
0448 }
0449 
0450 
0451 void K3b::AudioEditorWidget::enableMouseAtSignal( bool b )
0452 {
0453     d->mouseAt = b;
0454 }
0455 
0456 
0457 void K3b::AudioEditorWidget::paintEvent( QPaintEvent* e )
0458 {
0459     Q_UNUSED( e );
0460 
0461     QPainter p( this );
0462 
0463     QRect drawRect( contentsRect() );
0464     drawRect.setLeft( drawRect.left() + d->margin );
0465     drawRect.setRight( drawRect.right() - d->margin );
0466 
0467     // from minimumSizeHint()
0468 //   int neededHeight = fontMetrics().height() + 12 + 6;
0469 
0470 //   drawRect.setTop( drawRect.top() + (drawRect.height() - neededHeight)/2 );
0471 //   drawRect.setHeight( neededHeight );
0472 
0473     drawRect.setTop( drawRect.top() + d->margin );
0474     drawRect.setBottom( drawRect.bottom() - d->margin );
0475 
0476     drawAll( &p, drawRect );
0477 }
0478 
0479 
0480 void K3b::AudioEditorWidget::drawAll( QPainter* p, const QRect& drawRect )
0481 {
0482     // we simply draw the ranges one after the other.
0483     for( Range::List::const_iterator it = d->ranges.constBegin(); it != d->ranges.constEnd(); ++it )
0484         drawRange( p, drawRect, *it );
0485 
0486     // Hack to make sure the currently selected range is always on top
0487     if( Range* selectedRange = getRange( d->selectedRangeId ) )
0488         drawRange( p, drawRect, *selectedRange );
0489 
0490     for( Marker::List::const_iterator it = d->markers.constBegin(); it != d->markers.constEnd(); ++it )
0491         drawMarker( p, drawRect, *it );
0492 
0493 
0494     // left vline
0495     p->drawLine( drawRect.left(), drawRect.top(),
0496                  drawRect.left(), drawRect.bottom() );
0497 
0498     // timeline
0499     p->drawLine( drawRect.left(), drawRect.bottom(),
0500                  drawRect.right(), drawRect.bottom() );
0501 
0502     // right vline
0503     p->drawLine( drawRect.right(), drawRect.top(),
0504                  drawRect.right(), drawRect.bottom() );
0505 
0506     // draw minute markers every minute
0507     int minute = 1;
0508     int minuteStep = 1;
0509     int markerVPos = drawRect.bottom();
0510     int maxMarkerWidth = fontMetrics().horizontalAdvance( QString::number(d->length.minutes()) );
0511     int minNeededSpace = maxMarkerWidth + 1;
0512     int x = 0;
0513     while( minute*60*75 < d->length ) {
0514         int newX = msfToPos( minute*60*75 );
0515 
0516         // only draw the mark if we have enough space
0517         if( newX - x >= minNeededSpace ) {
0518             p->drawLine( newX, markerVPos, newX, markerVPos-5 );
0519             QRect txtRect( newX-(maxMarkerWidth/2),
0520                            markerVPos - 6 - fontMetrics().height(),
0521                            maxMarkerWidth,
0522                            fontMetrics().height() );
0523             p->drawText( txtRect, Qt::AlignCenter, QString::number(minute) );
0524 
0525             // FIXME: draw second markers if we have enough space
0526 
0527             x = newX;
0528         }
0529         else {
0530             minute -= minuteStep;
0531             if( minuteStep == 1 )
0532                 minuteStep = 5;
0533             else
0534                 minuteStep *= 2;
0535         }
0536 
0537         minute += minuteStep;
0538     }
0539 }
0540 
0541 
0542 void K3b::AudioEditorWidget::drawRange( QPainter* p, const QRect& drawRect, const K3b::AudioEditorWidget::Range& r )
0543 {
0544     p->save();
0545 
0546     int start = msfToPos( r.start );
0547     int end = msfToPos( r.end );
0548 
0549     if( rangeSelectedEnabled() && r.id == d->selectedRangeId )
0550         p->setBrush( selectedRangeBrush() );
0551     else
0552         p->setBrush( r.brush );
0553 
0554     p->drawRect( start, drawRect.top()+6 , end-start+1-1, drawRect.height()-6-1 );
0555 
0556     p->restore();
0557 }
0558 
0559 
0560 void K3b::AudioEditorWidget::drawMarker( QPainter* p, const QRect& drawRect, const K3b::AudioEditorWidget::Marker& m )
0561 {
0562     p->save();
0563 
0564     p->setPen( m.color );
0565     p->setBrush( m.color );
0566 
0567     int x = msfToPos( m.pos );
0568     p->drawLine( x, drawRect.bottom(), x, drawRect.top() );
0569 
0570     QPolygon points( 3 );
0571     points.setPoint( 0, x, drawRect.top() + 6 );
0572     points.setPoint( 1, x-3, drawRect.top() );
0573     points.setPoint( 2, x+3, drawRect.top() );
0574     p->drawPolygon( points );
0575 
0576     p->restore();
0577 }
0578 
0579 
0580 void K3b::AudioEditorWidget::fixupOverlappingRanges( int rangeId )
0581 {
0582     Range* r = getRange( rangeId );
0583     Range::List::iterator range = d->ranges.begin();
0584 
0585     while( r != 0 && range != d->ranges.end() ) {
0586         if( range->id != rangeId ) {
0587 
0588             // remove the range if it is covered completely
0589             if( range->start >= r->start &&
0590                 range->end <= r->end ) {
0591                 if( d->selectedRangeId == range->id )
0592                     setSelectedRange( 0 );
0593 
0594                 range = d->ranges.erase( range );
0595                 emit rangeRemoved( rangeId );
0596                 // "r" may be invalid at this point, let's find it once again
0597                 r = getRange( rangeId );
0598             }
0599             else {
0600                 // split the range if it contains r completely
0601                 if( r->start >= range->start &&
0602                     r->end <= range->end ) {
0603                     // create a new range that spans the part after r
0604                     addRange( r->end+1, range->end,
0605                               range->startFixed, range->endFixed,
0606                               range->toolTip,
0607                               range->brush );
0608 
0609                     // modify the old range to only span the part before r
0610                     range->end = r->start-1;
0611                     emit rangeChanged( range->id, range->start, range->end );
0612                 }
0613                 else if( range->start >= r->start && range->start <= r->end ) {
0614                     range->start = r->end+1;
0615                     emit rangeChanged( range->id, range->start, range->end );
0616                 }
0617                 else if( range->end >= r->start && range->end <= r->end ) {
0618                     range->end = r->start-1;
0619                     emit rangeChanged( range->id, range->start, range->end );
0620                 }
0621                 ++range;
0622             }
0623         }
0624         else {
0625             ++range;
0626         }
0627     }
0628 }
0629 
0630 
0631 void K3b::AudioEditorWidget::mousePressEvent( QMouseEvent* e )
0632 {
0633     d->draggedRangeId = 0;
0634     d->draggedMarker = 0;
0635 
0636     bool end;
0637     if( Range* r = findRangeEdge( e->pos(), &end ) ) {
0638         d->draggedRangeId = r->id;
0639         d->draggingRangeEnd = end;
0640         setSelectedRange( r->id );
0641     }
0642     else if( Range* r = findRange( e->pos() ) ) {
0643         d->movedRangeId = r->id;
0644         d->lastMovePosition = posToMsf( e->pos().x() );
0645         setSelectedRange( r->id );
0646         d->draggedMarker = findMarker( e->pos() );
0647     }
0648 
0649     QFrame::mousePressEvent(e);
0650 }
0651 
0652 
0653 void K3b::AudioEditorWidget::mouseReleaseEvent( QMouseEvent* e )
0654 {
0655     if( !d->allowOverlappingRanges ) {
0656         //
0657         // modify and even delete ranges that we touched
0658         //
0659         if( d->draggedRangeId != 0 ) {
0660             fixupOverlappingRanges( d->draggedRangeId );
0661             repaint();
0662         }
0663         else if( d->movedRangeId != 0 ) {
0664             fixupOverlappingRanges( d->movedRangeId );
0665             repaint();
0666         }
0667     }
0668 
0669     d->draggedRangeId = 0;
0670     d->draggedMarker = 0;
0671     d->movedRangeId = 0;
0672 
0673     QFrame::mouseReleaseEvent(e);
0674 }
0675 
0676 
0677 void K3b::AudioEditorWidget::mouseDoubleClickEvent( QMouseEvent* e )
0678 {
0679     QFrame::mouseDoubleClickEvent(e);
0680 }
0681 
0682 
0683 void K3b::AudioEditorWidget::mouseMoveEvent( QMouseEvent* e )
0684 {
0685     if( d->mouseAt )
0686         emit mouseAt( posToMsf( e->pos().x() ) );
0687 
0688     if( e->buttons() & Qt::LeftButton ) {
0689         if( Range* draggedRange = getRange( d->draggedRangeId ) ) {
0690             // determine the position the range's end was dragged to and its other end
0691             K3b::Msf msfPos = qMax( K3b::Msf(), qMin( posToMsf( e->pos().x() ), d->length-1 ) );
0692             K3b::Msf otherEnd = ( d->draggingRangeEnd ? draggedRange->start : draggedRange->end );
0693 
0694             // move it to the new pos
0695             if( d->draggingRangeEnd )
0696                 draggedRange->end = msfPos;
0697             else
0698                 draggedRange->start = msfPos;
0699 
0700             // if we pass the other end switch them
0701             if( draggedRange->start > draggedRange->end ) {
0702                 K3b::Msf buf = draggedRange->start;
0703                 draggedRange->start = draggedRange->end;
0704                 draggedRange->end = buf;
0705                 d->draggingRangeEnd = !d->draggingRangeEnd;
0706             }
0707 
0708             emit rangeChanged( draggedRange->id, draggedRange->start, draggedRange->end );
0709 
0710             repaint();
0711         }
0712         else if( d->draggedMarker ) {
0713             d->draggedMarker->pos = posToMsf( e->pos().x() );
0714             emit markerMoved( d->draggedMarker->id, d->draggedMarker->pos );
0715 
0716             repaint();
0717         }
0718         else if( Range* movedRange = getRange( d->movedRangeId ) ) {
0719             int diff = posToMsf( e->pos().x() ).lba() - d->lastMovePosition.lba();
0720             if( movedRange->end + diff >= d->length )
0721                 diff = d->length.lba() - movedRange->end.lba() - 1;
0722             else if( movedRange->start - diff < 0 )
0723                 diff = -1 * movedRange->start.lba();
0724             movedRange->start += diff;
0725             movedRange->end += diff;
0726 
0727 //       if( !d->allowOverlappingRanges )
0728 //  fixupOverlappingRanges( d->movedRangeId );
0729 
0730             d->lastMovePosition = posToMsf( e->pos().x() );
0731 
0732             emit rangeChanged( movedRange->id, movedRange->start, movedRange->end );
0733 
0734             repaint();
0735         }
0736     }
0737     else if( findRangeEdge( e->pos() ) || findMarker( e->pos() ) )
0738         setCursor( Qt::SizeHorCursor );
0739     else
0740         setCursor( Qt::PointingHandCursor );
0741 
0742     QFrame::mouseMoveEvent(e);
0743 }
0744 
0745 bool K3b::AudioEditorWidget::event( QEvent* e )
0746 {
0747     if( e->type() == QEvent::ToolTip ) {
0748         QHelpEvent* helpEvent = dynamic_cast<QHelpEvent*>( e );
0749         const QPoint pos = mapFromGlobal( helpEvent->globalPos() );
0750 
0751         if( Marker* m = findMarker( pos ) ) {
0752             QToolTip::showText( helpEvent->globalPos(),
0753                                 m->toolTip.isEmpty() ? m->pos.toString() : QString("%1 (%2)").arg(m->toolTip).arg(m->pos.toString()),
0754                                 this );
0755         }
0756         else if( Range* range = findRange( pos ) ) {
0757             QToolTip::showText( helpEvent->globalPos(),
0758                                 range->toolTip.isEmpty()
0759                                 ? QString("%1 - %2").arg(range->start.toString()).arg(range->end.toString())
0760                                 : QString("%1 (%2 - %3)").arg(range->toolTip).arg(range->start.toString()).arg(range->end.toString()),
0761                                 this );
0762 
0763         }
0764         else {
0765             QToolTip::hideText();
0766         }
0767 
0768         e->accept();
0769         return true;
0770     }
0771     else {
0772         return QWidget::event( e );
0773     }
0774 }
0775 
0776 
0777 K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::getRange( int i ) const
0778 {
0779     for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it )
0780         if( (*it).id == i )
0781             return &( *it );
0782 
0783     return 0;
0784 }
0785 
0786 
0787 K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::findRange( const QPoint& p ) const
0788 {
0789     // TODO: binary search; maybe store start and end positions in sorted lists for quick searching
0790     // this might be a stupid approach but we do not have many ranges anyway
0791     for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it ) {
0792         Range& range = *it;
0793         int start = msfToPos( range.start );
0794         int end = msfToPos( range.end );
0795 
0796         if( p.x() >= start && p.x() <= end ) {
0797             return &range;
0798         }
0799     }
0800     return 0;
0801 }
0802 
0803 
0804 K3b::AudioEditorWidget::Range* K3b::AudioEditorWidget::findRangeEdge( const QPoint& p, bool* isEnd ) const
0805 {
0806     // TODO: binary search
0807     // this might be a stupid approach but we do not have many ranges anyway
0808     for( Range::List::iterator it = d->ranges.begin(); it != d->ranges.end(); ++it ) {
0809         Range& range = *it;
0810         int start = msfToPos( range.start );
0811         int end = msfToPos( range.end );
0812 
0813         //
0814         // In case two ranges meet at one point moving the mouse cursor deeper into one
0815         // range allows for grabbing that end
0816         //
0817 
0818         if( p.x() - 3 <= start && p.x() >= start && !range.startFixed ) {
0819             if( isEnd )
0820                 *isEnd = false;
0821             return &range;
0822         }
0823         else if( p.x() <= end && p.x() + 3 >= end && !range.endFixed ) {
0824             if( isEnd )
0825                 *isEnd = true;
0826             return &range;
0827         }
0828     }
0829     return 0;
0830 }
0831 
0832 
0833 K3b::AudioEditorWidget::Marker* K3b::AudioEditorWidget::getMarker( int i ) const
0834 {
0835     for( Marker::List::iterator it = d->markers.begin(); it != d->markers.end(); ++it )
0836         if( (*it).id == i )
0837             return &( *it );
0838 
0839     return 0;
0840 }
0841 
0842 
0843 K3b::AudioEditorWidget::Marker* K3b::AudioEditorWidget::findMarker( const QPoint& p ) const
0844 {
0845     // TODO: binary search
0846     for( Marker::List::iterator it = d->markers.begin(); it != d->markers.end(); ++it ) {
0847         Marker& marker = *it;
0848         int start = msfToPos( marker.pos );
0849 
0850         if( p.x() - 1 <= start && p.x() + 1 >= start && !marker.fixed )
0851             return &marker;
0852     }
0853 
0854     return 0;
0855 }
0856 
0857 
0858 // p is in widget coordinates
0859 K3b::Msf K3b::AudioEditorWidget::posToMsf( int p ) const
0860 {
0861     int w = contentsRect().width() - 2*d->margin;
0862     int x = qMin( p-frameWidth()-d->margin, w );
0863     return ( (int)((double)(d->length.lba()-1) / (double)w * (double)x) );
0864 }
0865 
0866 
0867 // returns widget coordinates
0868 int K3b::AudioEditorWidget::msfToPos( const K3b::Msf& msf ) const
0869 {
0870     int w = contentsRect().width() - 2*d->margin;
0871     int pos = (int)((double)w / (double)(d->length.lba()-1) * (double)msf.lba());
0872     return frameWidth() + d->margin + qMin( pos, w-1 );
0873 }
0874 
0875 #include "moc_k3baudioeditorwidget.cpp"