1
/***************************************************************************
2
* Copyright (C) 2005 - 2007 by *
3
* Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm> *
4
* Erik Jaelevik, Last.fm Ltd <erik@last.fm> *
6
* This program is free software; you can redistribute it and/or modify *
7
* it under the terms of the GNU General Public License as published by *
8
* the Free Software Foundation; either version 2 of the License, or *
9
* (at your option) any later version. *
11
* This program is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU General Public License for more details. *
16
* You should have received a copy of the GNU General Public License *
17
* along with this program; if not, write to the *
18
* Free Software Foundation, Inc., *
19
* 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307, USA. *
20
***************************************************************************/
23
#include "draglabel.h"
24
#include <QApplication>
25
#include "browserthread.h"
35
// Width of the chords at the side ( each of them is chordw / 2 wide )
36
static const int chordw = 6;
37
static const int chordMargin = int(0.5f * chordw);
38
static const int cornerRadius = 4;
39
static const float s_lineSpacing = 1.0f;
40
static const int afterHeaderSpace = 4;
42
DragLabel::DragLabel( QWidget *parent ) :
46
m_lastHfwSize( -1, -1 ),
48
m_answerRect( QRectF( 0, 0, 0, 0 ) ),
49
m_hoverPoint( QPoint( -1, -1 ) ),
52
m_selectable( false ),
55
m_uniformLineHeight( -1 )
58
setMouseTracking( true );
60
setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
61
setCursor( QCursor( Qt::ArrowCursor ) );
63
// Repaint only newly visible items on resize
64
setAttribute( Qt::WA_StaticContents );
68
DragLabel::append( const QString& text )
71
QString t = m_commas ? text + "," : text + " ";
73
calcFontProperties( d );
81
m_items.erase( m_items.begin() + m_itemsStartAt, m_items.end() );
89
DragLabel::clearText()
91
for ( int i = m_itemsStartAt; i < m_items.count(); ++i )
93
m_items[i].m_text = "";
94
m_items[i].m_tooltip = "";
95
m_items[i].m_url = "";
101
DragLabel::clearSelections()
103
foreach( DragItem d, m_items )
105
d.m_selected = false;
108
// No size change so only calling update
113
DragLabel::setBackground( const QPixmap& pixmap )
115
m_background = pixmap;
119
DragLabel::setHeader( const QString& header, const QFont& font )
121
if ( m_itemsStartAt == 0 )
126
//QFont defaultFont = font();
127
//defaultFont.setBold( true );
130
calcFontProperties( d, true );
132
m_items.insert( 0, d );
138
DragItem& d = m_items[0];
141
calcFontProperties( d, true );
149
DragLabel::setText( const QString& text )
151
if ( m_items.isEmpty() )
157
QString t = m_commas ? text + "," : text + " ";
158
m_items[0].m_text = t;
159
calcFontProperties( m_items[0] );
165
DragLabel::text() const
167
if ( m_items.count() <= m_itemsStartAt )
173
QString s = m_items.at( m_itemsStartAt ).m_text;
180
DragLabel::setURL( QString url )
182
if ( m_items.isEmpty() )
190
m_items[0].m_url = url;
195
DragLabel::setFont( const QFont& font )
197
if ( m_items.isEmpty() )
205
m_items[0].m_font = font;
208
calcFontProperties( m_items[0] );
212
DragLabel::setItems( const QStringList& list )
215
foreach ( QString s, list )
226
for ( int i = m_itemsStartAt; i < m_items.count(); ++i )
228
QString s = m_items[i].m_text;
236
DragLabel::setItemText( int index, const QString& text )
238
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
240
QString t = m_commas ? text + "," : text + " ";
241
m_items[m_itemsStartAt + index].m_text = t;
245
DragLabel::setItemTooltip( int index, const QString& text )
247
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
249
m_items[m_itemsStartAt + index].m_tooltip = text;
253
DragLabel::setItemFont( int index, QFont font )
255
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
257
m_items[m_itemsStartAt + index].m_font = font;
258
calcFontProperties( m_items[m_itemsStartAt + index] );
262
DragLabel::setItemColor( int index, QColor col )
264
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
266
m_items[m_itemsStartAt + index].m_colour = col;
270
DragLabel::setItemURL( int index, QString url )
272
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
274
m_items[m_itemsStartAt + index].m_url = url;
278
DragLabel::setItemData( int index, QHash<QString, QString> data )
280
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
282
m_items[m_itemsStartAt + index].m_dragData = data;
285
QHash<QString, QString>
286
DragLabel::itemData( int index )
288
Q_ASSERT( index < ( m_items.count() - m_itemsStartAt ) );
290
return m_items[m_itemsStartAt + index].m_dragData;
294
DragLabel::setItemType( int type )
300
DragLabel::itemType()
306
DragLabel::setItemsSelectable( bool selectable )
308
m_selectable = selectable;
310
m_hoverPoint = QPoint( -1, -1 );
314
DragLabel::calcFontProperties( DragItem& d, bool isHeader )
316
// This defaults to application's default font if not set
317
QFontMetrics fm( d.m_font );
319
QRect rect = fm.boundingRect( d.m_text );
321
// Augment to font height as this particular string might be less tall
322
if ( fm.height() > rect.height() )
324
rect.setHeight( fm.height() );
327
// boundingRect sometimes returns negative values so make sure it's at 0, 0
330
// Move rect to its correct pos and add padding
333
rect.adjust( 0, 0, chordMargin + afterHeaderSpace, 0 );
337
rect.adjust( 0, 0, chordMargin * 2, 0 );
341
d.m_ascent = fm.ascent();
345
DragLabel::updateDragLabel()
348
m_lastHfwSize.setHeight( -1 );
349
m_lastHfwSize.setWidth( -1 );
350
m_sizeHint.setHeight( -1 );
351
m_sizeHint.setWidth( -1 );
353
// Tell the layout manager to recalculate itself
361
DragLabel::layoutItems( QList<QRect>& layoutOut, int width ) const
366
int firstIdxOfLine = 0;
370
int margLeft, margTop, margRight, margBottom;
371
getContentsMargins( &margLeft, &margTop, &margRight, &margBottom );
377
width = width - margLeft - margRight;
381
for ( int i = 0; i < m_items.count(); i++ )
383
// Find out how much space the pill needs
384
QRect textRect = m_items[i].m_extent;
386
// Use the uniform height if we have one, otherwise use the height of the widget font
387
int itemHeight = m_uniformLineHeight > 0 ? m_uniformLineHeight : textRect.height();
388
if ( itemHeight > lineHeight )
390
lineHeight = itemHeight;
393
if ( textRect.width() > widest )
395
widest = textRect.width();
398
// Move rect to its correct pos
399
textRect.moveTo( x, y );
401
bool tooBigForCurLine = textRect.width() > ( width - x );
402
bool firstThingToBeDrawn = i == 0;
404
// Do we need a newline?
405
if ( tooBigForCurLine && !firstThingToBeDrawn && wordWrap() )
407
baseAlign( layoutOut, firstIdxOfLine, i - 1, lineHeight );
410
justify( layoutOut, firstIdxOfLine, i - 1, width );
413
y += lineHeight + int(s_lineSpacing);
415
textRect.moveTo( x, y );
417
// Reset line height to height of this item
418
lineHeight = itemHeight;
423
layoutOut << textRect;
425
x += textRect.width();
428
if ( ( m_items.count() - firstIdxOfLine ) > 0 )
430
baseAlign( layoutOut, firstIdxOfLine, m_items.count() - 1, lineHeight );
436
if ( width == INT_MAX )
443
actualWidth = ( widest > width ) ? widest : width;
446
QSize ret( actualWidth, y );
452
DragLabel::baseAlign( QList<QRect>& layoutOut, int startIdx, int endIdx, int lineHeight ) const
454
// Find tallest item in line
457
for ( int i = startIdx; i <= endIdx; ++i )
459
int height = m_items[i].m_extent.height();
460
if ( height > maxHeight )
467
int maxAscent = m_items[tallest].m_ascent;
468
int distToBottom = lineHeight - m_items[tallest].m_extent.bottom() - 1;
470
Q_ASSERT( maxAscent != -1 );
472
// Move all items down to align their baseline with the tallest item
473
for ( int j = startIdx; j <= endIdx; ++j )
475
int pushDist = maxAscent - m_items[j].m_ascent;
476
Q_ASSERT( pushDist >= 0 );
478
pushDist += distToBottom;
479
layoutOut[j].translate( 0, pushDist );
484
DragLabel::justify( QList<QRect>& layoutOut, int startIdx, int endIdx, int width ) const
486
int combinedWidth = 0;
487
for ( int i = startIdx; i <= endIdx; ++i )
489
combinedWidth += m_items[i].m_extent.width();
492
int space = width - combinedWidth;
493
int nGaps = qMax( endIdx - startIdx, 1 );
494
int gapSpace = space / nGaps;
497
for ( int j = startIdx + 1; j <= endIdx; ++j, ++cnt )
499
layoutOut[j].translate( gapSpace * cnt, 0 );
504
DragLabel::heightForWidth( int w ) const
506
// This function is called by the layout manager instead of sizeHint when we
507
// have specified that word wrap should be used.
511
if ( m_lastWidth == -1 || w != m_lastWidth )
515
m_lastHfwSize = layoutItems( m_hfwLayout, w );
518
return m_lastHfwSize.height();
522
DragLabel::sizeHint() const
524
// Work out width if all items are put on one line and return that.
525
// Only used as a default when we don't have word wrapping enabled.
526
// This will also be used if specifying Fixed as the size policy.
528
if ( !m_sizeHint.isValid() )
530
m_lineLayout.clear();
531
m_sizeHint = layoutItems( m_lineLayout, INT_MAX );
538
DragLabel::minimumSizeHint() const
540
// A layout will never size us smaller than what we return here
541
if ( !m_sizeHint.isValid() )
543
m_lineLayout.clear();
544
m_sizeHint = layoutItems( m_lineLayout, INT_MAX );
547
return QSize( 0, m_sizeHint.height() );
551
DragLabel::event( QEvent* event )
553
if ( event->type() == QEvent::ToolTip )
555
QHelpEvent* helpEvent = static_cast<QHelpEvent *>( event );
556
QPoint hoverPos = helpEvent->pos();
558
int index = itemAt( hoverPos );
562
//QToolTip::showText( helpEvent->globalPos(), m_items[index].m_tooltip, this, m_hfwLayout.at( index ) );
563
QToolTip::showText( helpEvent->globalPos(), m_items[index].m_tooltip );
567
QToolTip::hideText();
571
return QLabel::event( event );
575
DragLabel::paintEvent( QPaintEvent * event )
577
//qDebug() << "paintevent rect: " << event->rect();
579
QPainter painter( this );
581
if ( !m_background.isNull() )
583
painter.drawTiledPixmap( event->rect(), m_background );
586
m_answerRect = QRectF( 0, 0, 0, 0 );
589
if ( !anythingToDraw() )
594
// The space we've been given by the layout system
595
QRect space = contentsRect();
598
int w = space.width();
599
if ( m_lastWidth == -1 || w != m_lastWidth )
603
m_lastHfwSize = layoutItems( m_hfwLayout, w );
606
for ( int i = 0; i < m_items.count(); ++i )
608
DragItem& item = m_items[i];
610
painter.setFont( item.m_font );
611
if ( item.m_colour.isValid() )
613
painter.setPen( item.m_colour );
614
painter.setBrush( item.m_colour );
618
painter.setPen( palette().text().color() );
619
painter.setBrush( palette().text().color() );
622
QString text = item.m_text;
624
// Remove the trailing comma/space from the last item
625
if ( i == ( m_items.count() - 1 ) )
630
QRect itemRect = m_hfwLayout[i];
632
// Crop itemRect to the width of our boundaries
633
bool cropped = false;
634
if ( space.width() < itemRect.width() )
636
itemRect.setWidth( space.width() );
640
// According to Alberto Garcia, this fixes the draglabel painting issue on Linux.
641
QRect textRect( itemRect.topLeft() + QPoint( chordMargin, 0 ),
642
itemRect.bottomRight() ); // - QPoint( chordMargin, 0 ) );
646
QFontMetrics fm = painter.fontMetrics();
647
text = fm.elidedText( text, Qt::ElideRight, textRect.width() );
650
bool isHeader = i == 0 && m_itemsStartAt == 1;
652
// Draw pill if we're hovered or selected
653
if ( ( itemRect.contains( m_hoverPoint ) || item.m_selected ) && !isHeader )
655
if ( itemRect.contains( m_hoverPoint ) )
658
m_answerRect = itemRect;
662
// Looks shit on Win with aa on
663
painter.setRenderHint( QPainter::Antialiasing, false );
665
// Looks shit on Mac with aa off
666
painter.setRenderHint( QPainter::Antialiasing, true );
669
// On hover, we remove the comma or trailing space
670
if ( i != ( m_items.count() - 1 ) && !cropped )
675
QColor highlight( 0xb4, 0xc2, 0xd4 );
676
QColor background( 0xd6, 0xdf, 0xec );
678
// Hannah doesn't like the anti-aliasing on the Mac
680
painter.setPen( background );
682
painter.setPen( highlight );
684
painter.setBrush( background );
686
// Get new rect for text without comma
687
QRect pillRect = painter.fontMetrics().boundingRect( itemRect, Qt::AlignLeft, text );
688
pillRect.adjust( 0, 0, chordMargin * 2, 0 );
690
// Paint the rectangle
691
QRect r( pillRect.topLeft() + QPoint( 1, 1 ),
692
pillRect.bottomRight() + QPoint( -1, -1 ) );
694
painter.drawRoundRect( r, roundnessForLength( r.width() ),
695
roundnessForLength( r.height() ) );
697
// We want white text on hovering
698
//painter.setPen( Qt::white );
699
//painter.setBrush( Qt::white );
700
painter.setPen( item.m_colour );
704
painter.setRenderHint( QPainter::Antialiasing, true );
705
painter.setRenderHint( QPainter::TextAntialiasing, true );
706
painter.drawText( textRect, text );
711
if ( sizePolicy().verticalPolicy() == QSizePolicy::MinimumExpanding )
713
setMinimumHeight( m_lastHfwSize.height() );
716
// set the empty bottom space as our cached answerRect
717
if ( !m_answerRect.width() )
718
m_answerRect = QRectF( 0, m_lastHfwSize.height(), 1, 1 );
722
DragLabel::anythingToDraw()
724
if ( m_items.count() == 0 || ( m_items.count() == 1 && m_itemsStartAt == 1 ) )
729
bool haveText = false;
730
foreach( DragItem item, m_items )
732
if ( item.m_text != "" && item.m_text != " " )
743
DragLabel::roundnessForLength( int len )
745
if ( len == 0 ) return 0;
747
int round = (int)( ( (float)cornerRadius / len ) * 100 );
748
round = qMin( 99, round );
749
round = qMax( 1, round );
751
//qDebug() << len << " -> " << round;
757
DragLabel::itemAt( const QPoint& pos )
759
for ( int i = 0; i < m_hfwLayout.size(); ++i )
761
const QRect& itemRect = m_hfwLayout.at( i );
762
if ( itemRect.contains( pos ) )
771
DragLabel::leaveEvent( QEvent* /*event*/ )
773
m_answerRect = QRectF( 0, 0, 0, 0 );
774
m_hoverPoint = QPoint( -1, -1 );
777
emit urlHovered( "" );
781
DragLabel::mousePressEvent( QMouseEvent *event )
783
if ( !m_selectable && m_hoverIndex >= 0 )
784
QLabel::mousePressEvent( event );
786
if ( event->button() == Qt::LeftButton )
788
m_dragStartPosition = event->pos();
789
//setCursor( QCursor( Qt::ClosedHandCursor ) );
796
DragLabel::mouseReleaseEvent( QMouseEvent *event )
798
if ( m_hoverIndex >= 0 )
802
m_items[m_hoverIndex].m_selected = !m_items[m_hoverIndex].m_selected;
807
if ( ( event->pos() - m_dragStartPosition ).manhattanLength() > QApplication::startDragDistance() )
810
if ( !m_items[m_hoverIndex].m_url.isEmpty() )
812
new BrowserThread( m_items[m_hoverIndex].m_url ); // self-deleting
816
//setCursor( QCursor( Qt::ArrowCursor ) );
818
emit clicked( m_hoverIndex );
824
DragLabel::mouseMoveEvent( QMouseEvent *event )
826
QLabel::mouseMoveEvent( event );
831
if ( !m_answerRect.contains( event->pos() ) )
833
// We've just moved off/on a pill
834
m_hoverPoint = event->pos();
838
// Stuff from here on is stuff we need to update for each nudge of the mouse
839
if ( m_hoverIndex < 0 )
841
setCursor( QCursor( Qt::ArrowCursor ) );
843
emit urlHovered( "" );
847
//if ( !( event->buttons() & Qt::LeftButton ) )
849
// setCursor( QCursor( Qt::OpenHandCursor ) );
852
setCursor( QCursor( Qt::PointingHandCursor ) );
854
// Emit hovered over url
855
QString url = m_items[m_hoverIndex].m_url;
856
if ( !url.isEmpty() )
858
emit urlHovered( url );
862
// Early out if left button isn't pressed or we're not over a pill
863
if ( !( event->buttons() & Qt::LeftButton ) || m_hoverIndex < 0 )
866
// Early out if drag not long enough yet
867
if ( ( event->pos() - m_dragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
870
QString anchor = m_items[m_hoverIndex].m_text;
872
anchor = anchor.trimmed();
873
if ( anchor.endsWith( "," ) )
878
if ( !anchor.isEmpty() )
880
QDrag* drag = new QDrag( this );
881
// qDebug() << "New drag with type" << itemType();
883
QMimeData *mimeData = new QMimeData();
884
mimeData->setText( anchor );
885
mimeData->setData( "item/type", QByteArray::number( itemType() ) );
887
QHash<QString, QString> data = m_items[m_hoverIndex].m_dragData;
890
for ( int i = 0; i < data.count(); i++ )
892
//qDebug() << "Setting data" << data.keys().at( i ) << data.values().at( i );
893
mimeData->setData( QString( "item/%1" ).arg( data.keys().at( i ) ), data.values().at( i ).toUtf8() );
901
mimeData->setData( "item/artist", anchor.toUtf8() );
905
mimeData->setData( "item/tag", anchor.toUtf8() );
909
mimeData->setData( "item/user", anchor.toUtf8() );
913
mimeData->setData( "item/station", anchor.toUtf8() );
918
// From the docs: Warning: Unless a widget has the Qt::WA_PaintOutsidePaintEvent
919
// attribute set. A QPainter can only be used on a widget inside a paintEvent()
920
// or a function called by a paintEvent(). On Mac OS X, you can only paint on
921
// a widget in a paintEvent() regardless of this attribute's setting.
923
QPixmap pixmap( painter.fontMetrics().width( anchor ) + 16, painter.fontMetrics().height() + 4 );
924
QRect rect( 0, 0, pixmap.width() - 1, pixmap.height() - 1 );
926
painter.begin( &pixmap );
927
painter.setBackgroundMode( Qt::OpaqueMode );
929
painter.setBrush( Qt::white );
930
painter.setPen( Qt::black );
931
painter.drawRect( rect );
933
painter.setPen( Qt::black );
934
painter.drawText( rect, Qt::AlignCenter, anchor );
937
drag->setMimeData( mimeData );
938
drag->setPixmap( pixmap );
940
Qt::DropAction dropAction = drag->start( Qt::CopyAction );
942
Q_UNUSED( dropAction )
948
DragLabel::setItemSelected( const QString& text, bool selected, bool emitSignal )
951
for( int i = m_itemsStartAt; i < m_items.count(); ++i )
953
if ( m_items[i].m_text == text )
959
setItemSelected( index - m_itemsStartAt, selected, emitSignal );
964
DragLabel::setItemSelected( int index, bool selected, bool emitSignal )
966
if ( index >= 0 && index < m_items.count() )
968
m_items[m_itemsStartAt + index].m_selected = selected;
972
emit clicked( index );