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 ⦥ 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 ⦥ 0822 } 0823 else if( p.x() <= end && p.x() + 3 >= end && !range.endFixed ) { 0824 if( isEnd ) 0825 *isEnd = true; 0826 return ⦥ 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 ▮ 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"