diff --git a/include/tomahawk/tomahawkapp.h b/include/tomahawk/tomahawkapp.h index 7be7cf29e..e048778dc 100644 --- a/include/tomahawk/tomahawkapp.h +++ b/include/tomahawk/tomahawkapp.h @@ -77,13 +77,15 @@ public: signals: void settingsChanged(); +private slots: + void setupSIP(); + private: void initLocalCollection(); void loadPlugins(); void registerMetaTypes(); void startServent(); void setupDatabase(); - void setupSIP(); void setupPipeline(); void startHTTP(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f0b86ab7f..7bb348a4e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET( tomahawkSources ${tomahawkSources} SET( tomahawkSourcesGui ${tomahawkSourcesGui} xspfloader.cpp + utils/querylabel.cpp utils/elidedlabel.cpp utils/imagebutton.cpp utils/progresstreeview.cpp @@ -137,6 +138,7 @@ SET( tomahawkHeaders ${tomahawkHeaders} SET( tomahawkHeadersGui ${tomahawkHeadersGui} xspfloader.h + utils/querylabel.h utils/elidedlabel.h utils/animatedcounterlabel.h utils/imagebutton.h diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 640b44b46..deb883d09 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -23,17 +23,17 @@ AudioControls::AudioControls( QWidget* parent ) ui->setupUi( this ); ui->buttonAreaLayout->setSpacing( 2 ); - ui->trackLabelLayout->setSpacing( 3 ); QFont font( ui->artistTrackLabel->font() ); font.setPixelSize( 12 ); -/* ui->artistTrackLabel->setMinimumSize( ui->artistTrackLabel->minimumSizeHint() ); - ui->albumLabel->setMinimumSize( ui->albumLabel->minimumSizeHint() );*/ ui->artistTrackLabel->setFont( font ); ui->artistTrackLabel->setElideMode( Qt::ElideMiddle ); + ui->artistTrackLabel->setType( QueryLabel::ArtistAndTrack ); ui->albumLabel->setFont( font ); + ui->albumLabel->setType( QueryLabel::Album ); + ui->timeLabel->setFont( font ); ui->timeLeftLabel->setFont( font ); @@ -229,8 +229,8 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) m_currentTrack = result; - ui->artistTrackLabel->setText( QString( "%1 - %2" ).arg( result->artist()->name() ).arg( result->track() ) ); - ui->albumLabel->setText( result->album()->name() ); + ui->artistTrackLabel->setResult( result ); + ui->albumLabel->setResult( result ); ui->ownerLabel->setText( result->collection()->source()->friendlyName() ); ui->coverImage->setPixmap( m_defaultCover ); diff --git a/src/audiocontrols.ui b/src/audiocontrols.ui index 0c2a8976d..033f51bef 100644 --- a/src/audiocontrols.ui +++ b/src/audiocontrols.ui @@ -168,7 +168,7 @@ - 8 + 4 6 @@ -177,14 +177,35 @@ 2 - + + + 0 + + + 0 + 0 + + 0 + - + + + + 0 + 0 + + + + + 0 + 16 + + PointingHandCursor @@ -194,7 +215,19 @@ - + + + + 0 + 0 + + + + + 0 + 16 + + PointingHandCursor @@ -212,8 +245,8 @@ - 40 - 20 + 4 + 8 @@ -246,13 +279,16 @@ 20 - 40 + 4 + + 4 + @@ -459,9 +495,9 @@
imagebutton.h
- ElidedLabel + QueryLabel QLabel -
elidedlabel.h
+
querylabel.h
diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index ecac78114..0dd1a2bbb 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -29,6 +29,7 @@ set( libSources network/filetransferconnection.cpp network/dbsyncconnection.cpp network/remotecollection.cpp + network/portfwdthread.cpp database/fuzzyindex.cpp database/databaseworker.cpp @@ -146,6 +147,7 @@ set( libHeaders network/servent.h network/connection.h network/controlconnection.h + network/portfwdthread.h ) include_directories( . ${CMAKE_CURRENT_BINARY_DIR} .. diff --git a/src/libtomahawk/database/databasecommand_playbackhistory.cpp b/src/libtomahawk/database/databasecommand_playbackhistory.cpp index 849d5ce8b..d6727a4e8 100644 --- a/src/libtomahawk/database/databasecommand_playbackhistory.cpp +++ b/src/libtomahawk/database/databasecommand_playbackhistory.cpp @@ -21,7 +21,9 @@ DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi ) "SELECT track, playtime, secs_played " "FROM playback_log " "%1 " - "ORDER BY playtime DESC").arg( whereToken ); + "ORDER BY playtime DESC " + "%2" ).arg( whereToken ) + .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); query.prepare( sql ); query.exec(); @@ -34,10 +36,8 @@ DatabaseCommand_PlaybackHistory::exec( DatabaseImpl* dbi ) "SELECT track.name, artist.name " "FROM track, artist " "WHERE artist.id = track.artist " - "AND track.id = %1 " - "%2" - ).arg( query.value( 0 ).toUInt() ) - .arg( m_amount > 0 ? QString( "LIMIT 0, %1" ).arg( m_amount ) : QString() ); + "AND track.id = %1" + ).arg( query.value( 0 ).toUInt() ); query_track.prepare( sql ); query_track.exec(); diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 394e449c8..3434d43eb 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -37,14 +37,21 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) if ( !m.isEmpty() ) { if ( m.value( "srcid" ).toUInt() > 0 ) - coll = SourceList::instance()->get( m.value( "srcid" ).toUInt() )->collection(); + { + source_ptr s = SourceList::instance()->get( m.value( "srcid" ).toUInt() ); + if ( !s.isNull() ) + coll = s->collection(); + } else coll = SourceList::instance()->getLocal()->collection(); - res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); - emit results( qid, res ); + if ( !coll.isNull() ) + { + res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); + emit results( qid, res ); - return; + return; + } } } diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index 1f1e8ffe2..3c94603b6 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -476,6 +476,7 @@ DatabaseImpl::result( const QString& url ) { TomahawkSqlQuery query = newquery(); Tomahawk::source_ptr s; + QVariantMap m; QString fileUrl; if ( url.contains( "servent://" ) ) @@ -483,6 +484,9 @@ DatabaseImpl::result( const QString& url ) QStringList parts = url.mid( QString( "servent://" ).length() ).split( "\t" ); s = SourceList::instance()->get( parts.at( 0 ) ); fileUrl = parts.at( 1 ); + + if ( s.isNull() ) + return m; } else if ( url.contains( "file://" ) ) { @@ -517,7 +521,6 @@ DatabaseImpl::result( const QString& url ) query.bindValue( 0, fileUrl ); query.exec(); - QVariantMap m; if( query.next() ) { const QString url_str = query.value( 0 ).toString(); diff --git a/src/libtomahawk/database/tomahawksqlquery.h b/src/libtomahawk/database/tomahawksqlquery.h index 448369f4f..5b21a03aa 100644 --- a/src/libtomahawk/database/tomahawksqlquery.h +++ b/src/libtomahawk/database/tomahawksqlquery.h @@ -6,7 +6,7 @@ #include #include -#define TOMAHAWK_QUERY_THRESHOLD 20 +#define TOMAHAWK_QUERY_THRESHOLD 60 class TomahawkSqlQuery : public QSqlQuery { @@ -17,7 +17,7 @@ public: : QSqlQuery() {} - TomahawkSqlQuery( QSqlDatabase db ) + TomahawkSqlQuery( const QSqlDatabase& db ) : QSqlQuery( db ) {} diff --git a/src/libtomahawk/network/bufferiodevice.cpp b/src/libtomahawk/network/bufferiodevice.cpp index b4e533643..c3be04748 100644 --- a/src/libtomahawk/network/bufferiodevice.cpp +++ b/src/libtomahawk/network/bufferiodevice.cpp @@ -1,11 +1,18 @@ -#include #include "bufferiodevice.h" +#include +#include +#include -BufferIODevice::BufferIODevice( unsigned int size, QObject *parent ) : - QIODevice( parent ), - m_size( size ), - m_received( 0 ) +// Msgs are framed, this is the size each msg we send containing audio data: +#define BLOCKSIZE 4096 + + +BufferIODevice::BufferIODevice( unsigned int size, QObject *parent ) + : QIODevice( parent ) + , m_size( size ) + , m_received( 0 ) + , m_pos( 0 ) { } @@ -16,7 +23,7 @@ BufferIODevice::open( OpenMode mode ) QMutexLocker lock( &m_mut ); qDebug() << Q_FUNC_INFO; - QIODevice::open( QIODevice::ReadWrite ); // FIXME? + QIODevice::open( QIODevice::ReadOnly | QIODevice::Unbuffered ); // FIXME? return true; } @@ -31,6 +38,32 @@ BufferIODevice::close() } +bool +BufferIODevice::seek( qint64 pos ) +{ + qDebug() << Q_FUNC_INFO << pos << m_size; + + if ( pos >= m_size ) + return false; + + int block = blockForPos( pos ); + if ( isBlockEmpty( block ) ) + emit blockRequest( block ); + + m_pos = pos; + qDebug() << "Finished seeking"; + + return true; +} + + +void +BufferIODevice::seeked( int block ) +{ + qDebug() << Q_FUNC_INFO << block << m_size; +} + + void BufferIODevice::inputComplete( const QString& errmsg ) { @@ -41,62 +74,76 @@ BufferIODevice::inputComplete( const QString& errmsg ) void -BufferIODevice::addData( QByteArray ba ) +BufferIODevice::addData( int block, const QByteArray& ba ) { - writeData( ba.data(), ba.length() ); + { + QMutexLocker lock( &m_mut ); + + while ( m_buffer.count() <= block ) + m_buffer << QByteArray(); + + m_buffer.replace( block, ba ); + } + + // If this was the last block of the transfer, check if we need to fill up gaps + if ( block + 1 == maxBlocks() ) + { + if ( nextEmptyBlock() >= 0 ) + { + emit blockRequest( nextEmptyBlock() ); + } + } + + emit bytesWritten( ba.count() ); + emit readyRead(); } qint64 BufferIODevice::bytesAvailable() const { - QMutexLocker lock( &m_mut ); - return m_buffer.length(); + return m_size - m_pos; } qint64 -BufferIODevice::readData( char * data, qint64 maxSize ) +BufferIODevice::readData( char* data, qint64 maxSize ) { - //qDebug() << Q_FUNC_INFO << maxSize; - QMutexLocker lock( &m_mut ); +// qDebug() << Q_FUNC_INFO << m_pos << maxSize << 1; - qint64 size = maxSize; - if ( m_buffer.length() < maxSize ) - size = m_buffer.length(); + if ( atEnd() ) + return 0; - memcpy( data, m_buffer.data(), size ); - m_buffer.remove( 0, size ); + QByteArray ba; + ba.append( getData( m_pos, maxSize ) ); + m_pos += ba.count(); - //qDebug() << "readData ends, bufersize:" << m_buffer.length(); - return size; +// qDebug() << Q_FUNC_INFO << maxSize << ba.count() << 2; + memcpy( data, ba.data(), ba.count() ); + + return ba.count(); } -qint64 BufferIODevice::writeData( const char * data, qint64 maxSize ) +qint64 BufferIODevice::writeData( const char* data, qint64 maxSize ) { - { - QMutexLocker lock( &m_mut ); - m_buffer.append( data, maxSize ); - m_received += maxSize; - } - - emit bytesWritten( maxSize ); - emit readyRead(); - return maxSize; + // call addData instead + Q_ASSERT( false ); + return 0; } qint64 BufferIODevice::size() const { + qDebug() << Q_FUNC_INFO << m_size; return m_size; } bool BufferIODevice::atEnd() const { - QMutexLocker lock( &m_mut ); - return ( m_size == m_received && m_buffer.length() == 0 ); +// qDebug() << Q_FUNC_INFO << ( m_size <= m_pos ); + return ( m_size <= m_pos ); } @@ -104,5 +151,102 @@ void BufferIODevice::clear() { QMutexLocker lock( &m_mut ); + + m_pos = 0; m_buffer.clear(); } + + +unsigned int +BufferIODevice::blockSize() +{ + return BLOCKSIZE; +} + + +int +BufferIODevice::blockForPos( qint64 pos ) const +{ + // 0 / 4096 -> block 0 + // 4095 / 4096 -> block 0 + // 4096 / 4096 -> block 1 + + return pos / BLOCKSIZE; +} + + +int +BufferIODevice::offsetForPos( qint64 pos ) const +{ + // 0 % 4096 -> offset 0 + // 4095 % 4096 -> offset 4095 + // 4096 % 4096 -> offset 0 + + return pos % BLOCKSIZE; +} + + +int +BufferIODevice::nextEmptyBlock() const +{ + int i = 0; + foreach( const QByteArray& ba, m_buffer ) + { + if ( ba.isEmpty() ) + return i; + + i++; + } + + if ( i == maxBlocks() ) + return -1; + + return i; +} + + +int +BufferIODevice::maxBlocks() const +{ + int i = m_size / BLOCKSIZE; + + if ( ( m_size % BLOCKSIZE ) > 0 ) + i++; + + return i; +} + + +bool +BufferIODevice::isBlockEmpty( int block ) const +{ + if ( block >= m_buffer.count() ) + return true; + + return m_buffer.at( block ).isEmpty(); +} + + +QByteArray +BufferIODevice::getData( qint64 pos, qint64 size ) +{ +// qDebug() << Q_FUNC_INFO << pos << size << 1; + QByteArray ba; + int block = blockForPos( pos ); + int offset = offsetForPos( pos ); + + QMutexLocker lock( &m_mut ); + while( ba.count() < size ) + { + if ( block > maxBlocks() ) + break; + + if ( isBlockEmpty( block ) ) + break; + + ba.append( m_buffer.at( block++ ).mid( offset ) ); + } + +// qDebug() << Q_FUNC_INFO << pos << size << 2; + return ba.left( size ); +} diff --git a/src/libtomahawk/network/bufferiodevice.h b/src/libtomahawk/network/bufferiodevice.h index 69a661d76..0fca19b94 100644 --- a/src/libtomahawk/network/bufferiodevice.h +++ b/src/libtomahawk/network/bufferiodevice.h @@ -4,35 +4,58 @@ #include #include #include +#include class BufferIODevice : public QIODevice { Q_OBJECT + public: explicit BufferIODevice( unsigned int size = 0, QObject *parent = 0 ); virtual bool open( OpenMode mode ); virtual void close(); + virtual bool seek( qint64 pos ); + void seeked( int block ); + virtual qint64 bytesAvailable() const; virtual qint64 size() const; virtual bool atEnd() const; + virtual qint64 pos() const { qDebug() << Q_FUNC_INFO << m_pos; return m_pos; } - void addData( QByteArray ba ); + void addData( int block, const QByteArray& ba ); void clear(); - OpenMode openMode() const { qDebug() << "openMode"; return QIODevice::ReadWrite; } + OpenMode openMode() const { qDebug() << "openMode"; return QIODevice::ReadOnly | QIODevice::Unbuffered; } void inputComplete( const QString& errmsg = "" ); + virtual bool isSequential() const { return false; } + + static unsigned int blockSize(); + + int maxBlocks() const; + int nextEmptyBlock() const; + bool isBlockEmpty( int block ) const; + +signals: + void blockRequest( int block ); + protected: - virtual qint64 readData( char * data, qint64 maxSize ); - virtual qint64 writeData( const char * data, qint64 maxSize ); + virtual qint64 readData( char* data, qint64 maxSize ); + virtual qint64 writeData( const char* data, qint64 maxSize ); private: - QByteArray m_buffer; + int blockForPos( qint64 pos ) const; + int offsetForPos( qint64 pos ) const; + QByteArray getData( qint64 pos, qint64 size ); + + QList m_buffer; mutable QMutex m_mut; //const methods need to lock unsigned int m_size, m_received; + + unsigned int m_pos; }; #endif // BUFFERIODEVICE_H diff --git a/src/libtomahawk/network/connection.cpp b/src/libtomahawk/network/connection.cpp index 2dc64db52..42c78bd74 100644 --- a/src/libtomahawk/network/connection.cpp +++ b/src/libtomahawk/network/connection.cpp @@ -5,7 +5,7 @@ #include "network/servent.h" -#define PROTOVER "3" // must match remote peer, or we can't talk. +#define PROTOVER "4" // must match remote peer, or we can't talk. Connection::Connection( Servent* parent ) @@ -70,8 +70,8 @@ Connection::handleIncomingQueueEmpty() if( m_sock->bytesAvailable() == 0 && m_peer_disconnected ) { qDebug() << "No more data to read, peer disconnected. shutting down connection." - << "bytesavail" << m_sock->bytesAvailable() - << "bytesrx" << m_rx_bytes; + << "bytesavail" << m_sock->bytesAvailable() + << "bytesrx" << m_rx_bytes; shutdown(); } } diff --git a/src/libtomahawk/network/filetransferconnection.cpp b/src/libtomahawk/network/filetransferconnection.cpp index 0b60de435..5a372f45c 100644 --- a/src/libtomahawk/network/filetransferconnection.cpp +++ b/src/libtomahawk/network/filetransferconnection.cpp @@ -10,9 +10,6 @@ #include "database/database.h" #include "sourcelist.h" -// Msgs are framed, this is the size each msg we send containing audio data: -#define BLOCKSIZE 4096 - using namespace Tomahawk; @@ -21,6 +18,7 @@ FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* c , m_cc( cc ) , m_fid( fid ) , m_type( RECEIVING ) + , m_curBlock( 0 ) , m_badded( 0 ) , m_bsent( 0 ) , m_allok( false ) @@ -37,7 +35,8 @@ FileTransferConnection::FileTransferConnection( Servent* s, ControlConnection* c // if the audioengine closes the iodev (skip/stop/etc) then kill the connection // immediately to avoid unnecessary network transfer - connect( m_iodev.data(), SIGNAL( aboutToClose() ), this, SLOT( shutdown() ), Qt::QueuedConnection ); + connect( m_iodev.data(), SIGNAL( aboutToClose() ), SLOT( shutdown() ), Qt::QueuedConnection ); + connect( m_iodev.data(), SIGNAL( blockRequest( int ) ), SLOT( onBlockRequest( int ) ) ); // auto delete when connection closes: connect( this, SIGNAL( finished() ), SLOT( deleteLater() ), Qt::QueuedConnection ); @@ -176,21 +175,41 @@ FileTransferConnection::startSending( const Tomahawk::result_ptr& result ) void FileTransferConnection::handleMsg( msg_ptr msg ) { - Q_ASSERT( m_type == FileTransferConnection::RECEIVING ); Q_ASSERT( msg->is( Msg::RAW ) ); - m_badded += msg->payload().length(); - ((BufferIODevice*)m_iodev.data())->addData( msg->payload() ); + if ( msg->payload().startsWith( "block" ) ) + { + int block = QString( msg->payload() ).mid( 5 ).toInt(); + m_readdev->seek( block * BufferIODevice::blockSize() ); + + qDebug() << "Seeked to block:" << block; + + QByteArray sm; + sm.append( QString( "doneblock%1" ).arg( block ) ); + + sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); + QTimer::singleShot( 0, this, SLOT( sendSome() ) ); + } + else if ( msg->payload().startsWith( "doneblock" ) ) + { + int block = QString( msg->payload() ).mid( 9 ).toInt(); + ((BufferIODevice*)m_iodev.data())->seeked( block ); + + m_curBlock = block; + qDebug() << "Next block is now:" << block; + } + else if ( msg->payload().startsWith( "data" ) ) + { + m_badded += msg->payload().length() - 4; + ((BufferIODevice*)m_iodev.data())->addData( m_curBlock++, msg->payload().mid( 4 ) ); + } //qDebug() << Q_FUNC_INFO << "flags" << (int) msg->flags() // << "payload len" << msg->payload().length() // << "written to device so far: " << m_badded; - if( !msg->is( Msg::FRAGMENT ) ) + if ( ((BufferIODevice*)m_iodev.data())->nextEmptyBlock() < 0 ) { - qDebug() << "*** Got last msg in filetransfer. added" << m_badded - << "io size" << m_iodev->size(); - m_allok = true; // tell our iodev there is no more data to read, no args meaning a success: ((BufferIODevice*)m_iodev.data())->inputComplete(); @@ -199,26 +218,26 @@ FileTransferConnection::handleMsg( msg_ptr msg ) } -Connection* FileTransferConnection::clone() +Connection* +FileTransferConnection::clone() { Q_ASSERT( false ); return 0; } -void FileTransferConnection::sendSome() +void +FileTransferConnection::sendSome() { Q_ASSERT( m_type == FileTransferConnection::SENDING ); - QByteArray ba = m_readdev->read( BLOCKSIZE ); - m_bsent += ba.length(); - //qDebug() << "Sending" << ba.length() << "bytes of audiofile"; + QByteArray ba = "data"; + ba.append( m_readdev->read( BufferIODevice::blockSize() ) ); + m_bsent += ba.length() - 4; if( m_readdev->atEnd() ) { sendMsg( Msg::factory( ba, Msg::RAW ) ); - qDebug() << "Sent all. DONE." << m_bsent; - shutdown( true ); return; } else @@ -231,3 +250,18 @@ void FileTransferConnection::sendSome() // (this is where upload throttling could be implemented) QTimer::singleShot( 0, this, SLOT( sendSome() ) ); } + + +void +FileTransferConnection::onBlockRequest( int block ) +{ + qDebug() << Q_FUNC_INFO << block; + + if ( m_curBlock == block ) + return; + + QByteArray sm; + sm.append( QString( "block%1" ).arg( block ) ); + + sendMsg( Msg::factory( sm, Msg::RAW | Msg::FRAGMENT ) ); +} diff --git a/src/libtomahawk/network/filetransferconnection.h b/src/libtomahawk/network/filetransferconnection.h index 72182f9b8..4600ba137 100644 --- a/src/libtomahawk/network/filetransferconnection.h +++ b/src/libtomahawk/network/filetransferconnection.h @@ -55,6 +55,8 @@ private slots: void sendSome(); void showStats( qint64 tx, qint64 rx ); + void onBlockRequest( int pos ); + private: QSharedPointer m_iodev; ControlConnection* m_cc; @@ -62,6 +64,8 @@ private: Type m_type; QSharedPointer m_readdev; + int m_curBlock; + int m_badded, m_bsent; bool m_allok; // got last msg ok, transfer complete? diff --git a/src/libtomahawk/network/portfwdthread.cpp b/src/libtomahawk/network/portfwdthread.cpp new file mode 100644 index 000000000..4da5c8a35 --- /dev/null +++ b/src/libtomahawk/network/portfwdthread.cpp @@ -0,0 +1,89 @@ +#include "portfwdthread.h" + +#include +#include +#include +#include +#include + +#include "portfwd/portfwd.h" + + +PortFwdThread::PortFwdThread( unsigned int port ) + : QThread() + , m_externalPort( 0 ) + , m_port( port ) +{ + moveToThread( this ); + start(); +} + + +PortFwdThread::~PortFwdThread() +{ + qDebug() << Q_FUNC_INFO << "waiting for event loop to finish..."; + quit(); + wait( 2500 ); + + delete m_portfwd; +} + + +void +PortFwdThread::work() +{ + qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); + m_portfwd = new Portfwd(); + + // try and pick an available port: + if( m_portfwd->init( 2000 ) ) + { + int tryport = m_port; + + // last.fm office firewall policy hack + // (corp. firewall allows outgoing connections to this port, + // so listen on this if you want lastfmers to connect to you) + if( qApp->arguments().contains( "--porthack" ) ) + { + tryport = 3389; + m_portfwd->remove( tryport ); + } + + for( int r = 0; r < 5; ++r ) + { + qDebug() << "Trying to setup portfwd on" << tryport; + if( m_portfwd->add( tryport, m_port ) ) + { + QString pubip = QString( m_portfwd->external_ip().c_str() ); + m_externalAddress = QHostAddress( pubip ); + m_externalPort = tryport; + qDebug() << "External servent address detected as" << pubip << ":" << m_externalPort; + qDebug() << "Max upstream " << m_portfwd->max_upstream_bps() << "bps"; + qDebug() << "Max downstream" << m_portfwd->max_downstream_bps() << "bps"; + break; + } + tryport = qAbs( 10000 + 50000 * (float)qrand() / RAND_MAX ); + } + } + else + qDebug() << "No UPNP Gateway device found?"; + + if( !m_externalPort ) + qDebug() << "Could not setup fwd for port:" << m_port; + + emit externalAddressDetected( m_externalAddress, m_externalPort ); +} + + +void +PortFwdThread::run() +{ + QTimer::singleShot( 0, this, SLOT( work() ) ); + exec(); + + if ( m_externalPort ) + { + qDebug() << "Unregistering port fwd"; + m_portfwd->remove( m_externalPort ); + } +} diff --git a/src/libtomahawk/network/portfwdthread.h b/src/libtomahawk/network/portfwdthread.h new file mode 100644 index 000000000..d18c699e4 --- /dev/null +++ b/src/libtomahawk/network/portfwdthread.h @@ -0,0 +1,32 @@ +#ifndef PORTFWDTHREAD_H +#define PORTFWDTHREAD_H + +#include +#include +#include + +class Portfwd; + +class PortFwdThread : public QThread +{ +Q_OBJECT + +public: + explicit PortFwdThread( unsigned int port ); + ~PortFwdThread(); + +signals: + void externalAddressDetected( QHostAddress ha, unsigned int port ); + +private slots: + void work(); + +private: + void run(); + + Portfwd* m_portfwd; + QHostAddress m_externalAddress; + unsigned int m_externalPort, m_port; +}; + +#endif // PORTFWDTHREAD_H diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index 73cc749db..dbfbdc236 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -18,7 +17,7 @@ #include "filetransferconnection.h" #include "sourcelist.h" -#include "portfwd/portfwd.h" +#include "portfwdthread.h" using namespace Tomahawk; @@ -36,12 +35,10 @@ Servent::Servent( QObject* parent ) : QTcpServer( parent ) , m_port( 0 ) , m_externalPort( 0 ) - , pf( new Portfwd() ) + , m_portfwd( 0 ) { s_instance = this; - qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - { boost::function(result_ptr)> fac = boost::bind( &Servent::localFileIODeviceFactory, this, _1 ); @@ -64,11 +61,7 @@ Servent::Servent( QObject* parent ) Servent::~Servent() { - if( m_externalPort ) - { - qDebug() << "Unregistering port fwd"; - pf->remove( m_externalPort ); - } + delete m_portfwd; } @@ -88,52 +81,6 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) qDebug() << "Servent listening on port" << m_port << " servent thread:" << thread(); } - // TODO check if we have a public/internet IP on this machine directly - // FIXME the portfwd stuff is blocking, so we hang here for 2 secs atm - if( upnp ) - { - // try and pick an available port: - if( pf->init( 2000 ) ) - { - int tryport = m_port; - - // last.fm office firewall policy hack - // (corp. firewall allows outgoing connections to this port, - // so listen on this if you want lastfmers to connect to you) - if( qApp->arguments().contains( "--porthack" ) ) - { - tryport = 3389; - pf->remove( tryport ); - } - - for( int r=0; r<5; ++r ) - { - qDebug() << "Trying to setup portfwd on" << tryport; - if( pf->add( tryport, m_port ) ) - { - QString pubip = QString( pf->external_ip().c_str() ); - m_externalAddress = QHostAddress( pubip ); - m_externalPort = tryport; - qDebug() << "External servent address detected as" << pubip << ":" << m_externalPort; - qDebug() << "Max upstream " << pf->max_upstream_bps() << "bps"; - qDebug() << "Max downstream" << pf->max_downstream_bps() << "bps"; - break; - } - tryport = 10000 + 50000 * (float)qrand()/RAND_MAX; - } - if( !m_externalPort ) - { - qDebug() << "Could not setup fwd for port:" << m_port; - } - } - else qDebug() << "No UPNP Gateway device found?"; - } - - if( m_externalPort == 0 ) - { - qDebug() << "No external access, LAN and outbound connections only!"; - } - // --lanhack means to advertise your LAN IP over jabber as if it were externallyVisible if( qApp->arguments().contains( "--lanhack" ) ) { @@ -149,6 +96,17 @@ Servent::startListening( QHostAddress ha, bool upnp, int port ) break; } } + else if( upnp ) + { + // TODO check if we have a public/internet IP on this machine directly + m_portfwd = new PortFwdThread( m_port ); + connect( m_portfwd, SIGNAL( externalAddressDetected( QHostAddress, unsigned int ) ), + SLOT( setExternalAddress( QHostAddress, unsigned int ) ) ); + } + else + { + emit ready(); + } return true; } @@ -160,7 +118,7 @@ Servent::createConnectionKey( const QString& name ) Q_ASSERT( this->thread() == QThread::currentThread() ); QString key = uuid(); - ControlConnection * cc = new ControlConnection( this ); + ControlConnection* cc = new ControlConnection( this ); cc->setName( name.isEmpty() ? QString( "KEY(%1)" ).arg( key ) : name ); registerOffer( key, cc ); return key; @@ -168,10 +126,17 @@ Servent::createConnectionKey( const QString& name ) void -Servent::setExternalAddress( QHostAddress ha, int port ) +Servent::setExternalAddress( QHostAddress ha, unsigned int port ) { m_externalAddress = ha; m_externalPort = port; + + if( m_externalPort == 0 ) + { + qDebug() << "No external access, LAN and outbound connections only!"; + } + + emit ready(); } @@ -217,8 +182,10 @@ void Servent::incomingConnection( int sd ) { Q_ASSERT( this->thread() == QThread::currentThread() ); + QTcpSocketExtra* sock = new QTcpSocketExtra; qDebug() << Q_FUNC_INFO << "Accepting connection, sock" << sock; + sock->moveToThread( thread() ); sock->_disowned = false; sock->_outbound = false; @@ -381,7 +348,6 @@ Servent::socketConnected() qDebug() << "Servent::SocketConnected" << thread() << "socket:" << sock; Connection* conn = sock->_conn; - handoverSocket( conn, sock ); } @@ -421,7 +387,7 @@ Servent::socketError( QAbstractSocket::SocketError e ) Connection* conn = sock->_conn; qDebug() << "Servent::SocketError:" << e << conn->id() << conn->name(); - if(!sock->_disowned) + if( !sock->_disowned ) { // connection will delete if we already transferred ownership, otherwise: sock->deleteLater(); @@ -749,7 +715,7 @@ QSharedPointer Servent::localFileIODeviceFactory( const Tomahawk::result_ptr& result ) { // ignore "file://" at front of url - QFile * io = new QFile( result->url().mid( QString( "file://" ).length() ) ); + QFile* io = new QFile( result->url().mid( QString( "file://" ).length() ) ); if ( io ) io->open( QIODevice::ReadOnly ); diff --git a/src/libtomahawk/network/servent.h b/src/libtomahawk/network/servent.h index f0e2884bb..02d2843be 100644 --- a/src/libtomahawk/network/servent.h +++ b/src/libtomahawk/network/servent.h @@ -33,7 +33,7 @@ class ControlConnection; class FileTransferConnection; class ProxyConnection; class RemoteCollectionConnection; -class Portfwd; +class PortFwdThread; // this is used to hold a bit of state, so when a connected signal is emitted // from a socket, we can associate it with a Connection object etc. @@ -90,7 +90,6 @@ public: void connectToPeer( const QString& ha, int port, const QString &key, Connection* conn ); void reverseOfferRequest( ControlConnection* orig_conn, const QString& key, const QString& theirkey ); - void setExternalAddress( QHostAddress ha, int port ); bool visibleExternally() const { return m_externalPort > 0 && !m_externalAddress.isNull(); } QHostAddress externalAddress() const { return m_externalAddress; } int externalPort() const { return m_externalPort; } @@ -111,11 +110,14 @@ public: signals: void fileTransferStarted( FileTransferConnection* ); void fileTransferFinished( FileTransferConnection* ); + void ready(); protected: void incomingConnection( int sd ); public slots: + void setExternalAddress( QHostAddress ha, unsigned int port ); + void socketError( QAbstractSocket::SocketError ); void createParallelConnection( Connection* orig_conn, Connection* new_conn, const QString& key ); @@ -145,9 +147,9 @@ private: QList< FileTransferConnection* > m_ftsessions; QMutex m_ftsession_mut; - Portfwd* pf; QMap< QString,boost::function(Tomahawk::result_ptr)> > m_iofactories; + PortFwdThread* m_portfwd; static Servent* s_instance; }; diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index cbfbb4e99..420cd049b 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -5,6 +5,7 @@ using namespace Tomahawk; + Query::Query( const QVariant& v ) : m_v( v ) , m_solved( false ) @@ -40,7 +41,8 @@ Query::addResults( const QList< Tomahawk::result_ptr >& newresults ) } } emit resultsAdded( newresults ); - if( becameSolved ) emit solvedStateChanged( true ); + if( becameSolved ) + emit solvedStateChanged( true ); } @@ -97,7 +99,8 @@ Query::numResults() const } -QID Query::id() const +QID +Query::id() const { if ( m_qid.isEmpty() ) { @@ -112,7 +115,8 @@ QID Query::id() const } -bool Query::resultSorter( const result_ptr& left, const result_ptr& right ) +bool +Query::resultSorter( const result_ptr& left, const result_ptr& right ) { return left->score() > right->score(); } diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp index 791705a60..88146df4f 100644 --- a/src/libtomahawk/result.cpp +++ b/src/libtomahawk/result.cpp @@ -77,6 +77,14 @@ Result::toString() const } +Tomahawk::query_ptr +Result::toQuery() const +{ + Tomahawk::query_ptr query = Tomahawk::query_ptr( new Tomahawk::Query( toVariant() ) ); + return query; +} + + void Result::updateAttributes() { diff --git a/src/libtomahawk/result.h b/src/libtomahawk/result.h index de45036e4..ff764d2a2 100644 --- a/src/libtomahawk/result.h +++ b/src/libtomahawk/result.h @@ -21,8 +21,10 @@ public: Result(); explicit Result( const QVariant& v, const collection_ptr& collection ); virtual ~Result(); - + QVariant toVariant() const { return m_v; } + QString toString() const; + Tomahawk::query_ptr toQuery() const; float score() const; RID id() const; @@ -45,9 +47,6 @@ public: unsigned int dbid() const { return m_id; } - // for debug output: - QString toString() const; - signals: // emitted when the collection this result comes from is going offline: void becomingUnavailable(); diff --git a/src/playlist/playlistmodel.cpp b/src/playlist/playlistmodel.cpp index 2c21ac2d6..4c5727740 100644 --- a/src/playlist/playlistmodel.cpp +++ b/src/playlist/playlistmodel.cpp @@ -240,7 +240,9 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r if ( action == Qt::IgnoreAction || isReadOnly() ) return true; - if ( !data->hasFormat( "application/tomahawk.query.list" ) && !data->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( !data->hasFormat( "application/tomahawk.query.list" ) + && !data->hasFormat( "application/tomahawk.plentry.list" ) + && !data->hasFormat( "application/tomahawk.result.list" ) ) return false; int beginRow; diff --git a/src/playlist/trackview.cpp b/src/playlist/trackview.cpp index c6ebd3173..5e769039f 100644 --- a/src/playlist/trackview.cpp +++ b/src/playlist/trackview.cpp @@ -161,7 +161,9 @@ TrackView::dragEnterEvent( QDragEnterEvent* event ) qDebug() << Q_FUNC_INFO; QTreeView::dragEnterEvent( event ); - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) { m_dragging = true; m_dropRect = QRect(); @@ -183,7 +185,9 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) return; } - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) ) + if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) + || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) { setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); @@ -216,6 +220,7 @@ TrackView::dropEvent( QDropEvent* event ) qDebug() << "Ignoring accepted event!"; } else + { if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) ) { const QPoint pos = event->pos(); @@ -229,6 +234,7 @@ TrackView::dropEvent( QDropEvent* event ) model()->dropMimeData( event->mimeData(), event->proposedAction(), index.row(), 0, index.parent() ); } } + } m_dragging = false; } @@ -298,7 +304,7 @@ TrackView::startDrag( Qt::DropActions supportedActions ) QDrag* drag = new QDrag( this ); drag->setMimeData( data ); - const QPixmap p = createDragPixmap( indexes.count() ); + const QPixmap p = TomahawkUtils::createDragPixmap( indexes.count() ); drag->setPixmap( p ); drag->setHotSpot( QPoint( -20, -20 ) ); @@ -308,65 +314,3 @@ TrackView::startDrag( Qt::DropActions supportedActions ) m_proxyModel->removeIndexes( pindexes ); } } - - -// Inspired from dolphin's draganddrophelper.cpp -QPixmap -TrackView::createDragPixmap( int itemCount ) const -{ - // If more than one item is dragged, align the items inside a - // rectangular grid. The maximum grid size is limited to 5 x 5 items. - int xCount = 3; - int size = 32; - - if ( itemCount > 16 ) - { - xCount = 5; - size = 16; - } else if( itemCount > 9 ) - { - xCount = 4; - size = 22; - } - - if( itemCount < xCount ) - { - xCount = itemCount; - } - - int yCount = itemCount / xCount; - if( itemCount % xCount != 0 ) - { - ++yCount; - } - if( yCount > xCount ) - { - yCount = xCount; - } - // Draw the selected items into the grid cells - QPixmap dragPixmap( xCount * size + xCount - 1, yCount * size + yCount - 1 ); - dragPixmap.fill( Qt::transparent ); - - QPainter painter( &dragPixmap ); - painter.setRenderHint( QPainter::Antialiasing ); - int x = 0; - int y = 0; - for( int i = 0; i < itemCount; ++i ) - { - const QPixmap pixmap = QPixmap( QString( ":/data/icons/audio-x-generic-%2x%2.png" ).arg( size ) ); - painter.drawPixmap( x, y, pixmap ); - - x += size + 1; - if ( x >= dragPixmap.width() ) - { - x = 0; - y += size + 1; - } - if ( y >= dragPixmap.height() ) - { - break; - } - } - - return dragPixmap; -} diff --git a/src/playlist/trackview.h b/src/playlist/trackview.h index 1b89d28e7..018a97c2c 100644 --- a/src/playlist/trackview.h +++ b/src/playlist/trackview.h @@ -55,8 +55,6 @@ private slots: void onFilterChanged( const QString& filter ); private: - QPixmap createDragPixmap( int itemCount ) const; - TrackModel* m_model; TrackProxyModel* m_proxyModel; PlaylistItemDelegate* m_delegate; diff --git a/src/sourcetree/sourcetreeitemwidget.cpp b/src/sourcetree/sourcetreeitemwidget.cpp index 2102f5488..d175d6766 100644 --- a/src/sourcetree/sourcetreeitemwidget.cpp +++ b/src/sourcetree/sourcetreeitemwidget.cpp @@ -21,6 +21,15 @@ SourceTreeItemWidget::SourceTreeItemWidget( const source_ptr& source, QWidget* p ui->setupUi( this ); ui->verticalLayout->setSpacing( 3 ); + ui->activityLabel->setType( QueryLabel::ArtistAndTrack ); + + QFont font = ui->nameLabel->font(); +// font.setPointSize( font.pointSize() - 1 ); + ui->nameLabel->setFont( font ); + + font.setPointSize( font.pointSize() - 1 ); + ui->infoLabel->setFont( font ); + ui->activityLabel->setFont( font ); QString displayname; if ( source.isNull() ) @@ -58,6 +67,10 @@ SourceTreeItemWidget::SourceTreeItemWidget( const source_ptr& source, QWidget* p ui->infoLabel->setForegroundRole( QPalette::Dark ); ui->activityLabel->setForegroundRole( QPalette::Dark ); + ui->nameLabel->setContentsMargins( 4, 0, 0, 0 ); + ui->infoLabel->setContentsMargins( 4, 0, 0, 0 ); + ui->activityLabel->setContentsMargins( 4, 0, 0, 0 ); + connect( ui->onOffButton, SIGNAL( clicked() ), SIGNAL( clicked() ) ); connect( ui->infoButton, SIGNAL( clicked() ), SLOT( onInfoButtonClicked() ) ); @@ -132,7 +145,8 @@ void SourceTreeItemWidget::onPlaybackStarted( const Tomahawk::query_ptr& query ) { qDebug() << Q_FUNC_INFO << query->toString(); - ui->activityLabel->setText( tr( "Playing: %1 by %2" ).arg( query->track() ).arg( query->artist() ) ); +// ui->activityLabel->setText( tr( "Playing: %1 by %2" ).arg( query->track() ).arg( query->artist() ) ); + ui->activityLabel->setQuery( query ); } diff --git a/src/sourcetree/sourcetreeitemwidget.ui b/src/sourcetree/sourcetreeitemwidget.ui index 3933cba59..17457afa9 100644 --- a/src/sourcetree/sourcetreeitemwidget.ui +++ b/src/sourcetree/sourcetreeitemwidget.ui @@ -7,27 +7,15 @@ 0 0 359 - 58 + 56
- + 0 0 - - - 0 - 58 - - - - - 16777215 - 58 - - Form @@ -92,7 +80,7 @@ - 4 + 2 20 @@ -101,10 +89,10 @@ - 4 + 2 - 4 + 2 @@ -127,7 +115,7 @@ - + TextLabel @@ -196,6 +184,11 @@ QPushButton
imagebutton.h
+ + QueryLabel + QLabel +
querylabel.h
+
ElidedLabel QLabel diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 00b04f854..651de7dea 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -110,10 +110,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) { qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); - new Pipeline( this ); - new SourceList( this ); - m_servent = new Servent( this ); - #ifdef TOMAHAWK_HEADLESS m_headless = true; #else @@ -136,6 +132,13 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) new TomahawkSettings( this ); m_audioEngine = new AudioEngine; + + new Pipeline( this ); + new SourceList( this ); + + m_servent = new Servent( this ); + connect( m_servent, SIGNAL( ready() ), SLOT( setupSIP() ) ); + setupDatabase(); GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); @@ -164,8 +167,8 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) { qDebug() << "Setting proxy to saved values"; m_proxy = new QNetworkProxy( static_cast(TomahawkSettings::instance()->proxyType()), TomahawkSettings::instance()->proxyHost(), TomahawkSettings::instance()->proxyPort(), TomahawkSettings::instance()->proxyUsername(), TomahawkSettings::instance()->proxyPassword() ); - qDebug() << "Proxy type = " << QString::number( static_cast(m_proxy->type()) ); - qDebug() << "Proxy host = " << m_proxy->hostName(); + qDebug() << "Proxy type =" << QString::number( static_cast(m_proxy->type()) ); + qDebug() << "Proxy host =" << m_proxy->hostName(); QNetworkAccessManager* nam = TomahawkApp::instance()->nam(); nam->setProxy( *m_proxy ); } @@ -174,14 +177,9 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) QNetworkProxy::setApplicationProxy( *m_proxy ); + m_sipHandler = new SipHandler( this ); m_infoSystem = new Tomahawk::InfoSystem::InfoSystem( this ); - if( !arguments().contains("--nojabber") ) - { - setupSIP(); - m_xmppBot = new XMPPBot( this ); - } - #ifndef TOMAHAWK_HEADLESS if ( !m_headless ) { @@ -200,8 +198,6 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) if( arguments().contains( "--http" ) || TomahawkSettings::instance()->value( "network/http", true ).toBool() ) startHTTP(); - m_sipHandler->connect(); - #ifndef TOMAHAWK_HEADLESS if ( !TomahawkSettings::instance()->hasScannerPath() ) { @@ -262,6 +258,7 @@ TomahawkApp::registerMetaTypes() qRegisterMetaType< QTcpSocket* >("QTcpSocket*"); qRegisterMetaType< QSharedPointer >("QSharedPointer"); qRegisterMetaType< QFileInfo >("QFileInfo"); + qRegisterMetaType< QHostAddress >("QHostAddress"); qRegisterMetaType< QMap >("QMap"); qRegisterMetaType< QMap< QString, plentry_ptr > >("QMap< QString, plentry_ptr >"); qRegisterMetaType< QHash< QString, QMap > >("QHash< QString, QMap >"); @@ -366,9 +363,6 @@ TomahawkApp::startServent() qDebug() << "Failed to start listening with servent"; exit( 1 ); } - - //QString key = m_servent.createConnectionKey(); - //qDebug() << "Generated an offer key: " << key; } @@ -414,8 +408,12 @@ TomahawkApp::setupSIP() { qDebug() << Q_FUNC_INFO; - m_sipHandler = new SipHandler( this ); + if( !arguments().contains( "--nojabber" ) ) + { + m_xmppBot = new XMPPBot( this ); -// m_sipHandler->setProxy( m_proxy ); + m_sipHandler->connect(); + // m_sipHandler->setProxy( m_proxy ); + } } diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 0254b0b4b..bdcb20c8d 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -180,6 +180,9 @@ TomahawkWindow::setupSignals() connect( APP->sipHandler(), SIGNAL( connected() ), SLOT( onSipConnected() ) ); connect( APP->sipHandler(), SIGNAL( disconnected() ), SLOT( onSipDisconnected() ) ); connect( APP->sipHandler(), SIGNAL( authError() ), SLOT( onSipError() ) ); + + // set initial connection state + onSipDisconnected(); } diff --git a/src/utils/elidedlabel.cpp b/src/utils/elidedlabel.cpp index 8c581997c..a17f31dca 100644 --- a/src/utils/elidedlabel.cpp +++ b/src/utils/elidedlabel.cpp @@ -1,6 +1,5 @@ #include "elidedlabel.h" -#include #include #include #include diff --git a/src/utils/elidedlabel.h b/src/utils/elidedlabel.h index a3e4f5d4f..f05b766c5 100644 --- a/src/utils/elidedlabel.h +++ b/src/utils/elidedlabel.h @@ -30,10 +30,10 @@ public: void init( const QString& txt = QString() ); void updateLabel(); -public Q_SLOTS: +public slots: void setText( const QString& text ); -Q_SIGNALS: +signals: void clicked(); void textChanged( const QString& text ); diff --git a/src/utils/querylabel.cpp b/src/utils/querylabel.cpp new file mode 100644 index 000000000..f5223f59d --- /dev/null +++ b/src/utils/querylabel.cpp @@ -0,0 +1,530 @@ +#include "querylabel.h" + +#include +#include +#include +#include +#include + +#include "tomahawkutils.h" +#include "artist.h" +#include "album.h" +#include "query.h" + + +#define BOXMARGIN 2 +#define DASH " - " + +QueryLabel::QueryLabel( QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( Complete ) +{ + init(); +} + + +QueryLabel::QueryLabel( DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) +{ + init(); +} + + +QueryLabel::QueryLabel( const Tomahawk::result_ptr& result, DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) + , m_result( result ) +{ + init(); +} + + +QueryLabel::QueryLabel( const Tomahawk::query_ptr& query, DisplayType type, QWidget* parent, Qt::WindowFlags flags ) + : QFrame( parent, flags ) + , m_type( type ) + , m_query( query ) +{ + init(); +} + + +QueryLabel::~QueryLabel() +{ +} + + +void +QueryLabel::init() +{ + setContentsMargins( 0, 0, 0, 0 ); + setMouseTracking( true ); + + align = Qt::AlignLeft; + mode = Qt::ElideMiddle; +} + + +QString +QueryLabel::text() const +{ + QString text; + + if ( m_result.isNull() && m_query.isNull() ) + return m_text; + + if ( !m_result.isNull() ) + { + if ( m_type & Artist ) + { + text += m_result->artist()->name(); + } + if ( m_type & Album ) + { + smartAppend( text, m_result->album()->name() ); + } + if ( m_type & Track ) + { + smartAppend( text, m_result->track() ); + } + } + else + { + if ( m_type & Artist ) + { + text += m_query->artist(); + } + if ( m_type & Album ) + { + smartAppend( text, m_query->album() ); + } + if ( m_type & Track ) + { + smartAppend( text, m_query->track() ); + } + } + + return text; +} + + +QString +QueryLabel::artist() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->artist()->name(); + else + return m_query->artist(); +} + + +QString +QueryLabel::album() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->album()->name(); + else + return m_query->album(); +} + + +QString +QueryLabel::track() const +{ + if ( m_result.isNull() && m_query.isNull() ) + return QString(); + + if ( !m_result.isNull() ) + return m_result->track(); + else + return m_query->track(); +} + + +void +QueryLabel::setText( const QString& text ) +{ + setContentsMargins( m_textMargins ); + + m_result.clear(); + m_query.clear(); + m_text = text; + + updateLabel(); + + emit textChanged( m_text ); + emit resultChanged( m_result ); +} + + +void +QueryLabel::setResult( const Tomahawk::result_ptr& result ) +{ + if ( result.isNull() ) + return; + + if ( !m_text.isEmpty() && contentsMargins().left() != 0 ) // FIXME: hacky + m_textMargins = contentsMargins(); + + setContentsMargins( BOXMARGIN * 2, BOXMARGIN / 2, BOXMARGIN * 2, BOXMARGIN / 2); + + if ( m_result.isNull() || m_result.data() != result.data() ) + { + m_result = result; + + m_query = m_result->toQuery(); + QList rl; + rl << m_result; + m_query->addResults( rl ); + + updateLabel(); + + emit textChanged( text() ); + emit resultChanged( m_result ); + } +} + + +void +QueryLabel::setQuery( const Tomahawk::query_ptr& query ) +{ + if ( query.isNull() ) + return; + + setContentsMargins( BOXMARGIN * 2, BOXMARGIN / 2, BOXMARGIN * 2, BOXMARGIN / 2 ); + + if ( m_query.isNull() || m_query.data() != query.data() ) + { + m_query = query; + m_result.clear(); + updateLabel(); + + emit textChanged( text() ); + emit queryChanged( m_query ); + } +} + + +Qt::Alignment +QueryLabel::alignment() const +{ + return align; +} + + +void +QueryLabel::setAlignment( Qt::Alignment alignment ) +{ + if ( this->align != alignment ) + { + this->align = alignment; + update(); // no geometry change, repaint is sufficient + } +} + + +Qt::TextElideMode +QueryLabel::elideMode() const +{ + return mode; +} + + +void +QueryLabel::setElideMode( Qt::TextElideMode mode ) +{ + if ( this->mode != mode ) + { + this->mode = mode; + updateLabel(); + } +} + + +void +QueryLabel::updateLabel() +{ + m_hoverArea = QRect(); + + updateGeometry(); + update(); +} + + +QSize +QueryLabel::sizeHint() const +{ + const QFontMetrics& fm = fontMetrics(); + QSize size( fm.width( text() ) + contentsMargins().left() * 2, fm.height() + contentsMargins().top() * 2 ); + return size; +} + + +QSize +QueryLabel::minimumSizeHint() const +{ + switch ( mode ) + { + case Qt::ElideNone: + return sizeHint(); + + default: + { + const QFontMetrics& fm = fontMetrics(); + QSize size( fm.width( "..." ), fm.height() + contentsMargins().top() * 2 ); + return size; + } + } +} + + +void +QueryLabel::paintEvent( QPaintEvent* event ) +{ + QFrame::paintEvent( event ); + QPainter p( this ); + QRect r = contentsRect(); + QString s = text(); + const QString elidedText = fontMetrics().elidedText( s, mode, r.width() ); + + p.save(); + p.setRenderHint( QPainter::Antialiasing ); + + if ( elidedText == s && m_hoverArea.width() ) + { + p.setPen( palette().mid().color() ); + p.setBrush( palette().highlight() ); + p.drawRoundedRect( m_hoverArea, 4.0, 4.0 ); + } + + if ( elidedText != s || ( m_result.isNull() && m_query.isNull() ) ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + p.drawText( r, align, elidedText ); + } + else + { + const QFontMetrics& fm = fontMetrics(); + int dashX = fm.width( DASH ); + int artistX = m_type & Artist ? fm.width( artist() ) : 0; + int albumX = m_type & Album ? fm.width( album() ) : 0; + int trackX = m_type & Track ? fm.width( track() ) : 0; + + if ( m_type & Artist ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_hoverArea.width() && m_hoverArea.left() + contentsMargins().left() == r.left() ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, artist() ); + r.adjust( artistX, 0, 0, 0 ); + } + if ( m_type & Album ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_type & Artist ) + { + p.drawText( r, align, DASH ); + r.adjust( dashX, 0, 0, 0 ); + } + if ( m_hoverArea.width() && m_hoverArea.left() + contentsMargins().left() == r.left() ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, album() ); + r.adjust( albumX, 0, 0, 0 ); + } + if ( m_type & Track ) + { + p.setBrush( palette().window() ); + p.setPen( palette().color( foregroundRole() ) ); + + if ( m_type & Artist || m_type & Album ) + { + p.drawText( r, align, DASH ); + r.adjust( dashX, 0, 0, 0 ); + } + if ( m_hoverArea.width() && m_hoverArea.left() + contentsMargins().left() == r.left() ) + { + p.setPen( palette().highlightedText().color() ); + p.setBrush( palette().highlight() ); + } + + p.drawText( r, align, track() ); + r.adjust( trackX, 0, 0, 0 ); + } + } + + p.restore(); +} + + +void +QueryLabel::changeEvent( QEvent* event ) +{ + QFrame::changeEvent( event ); + switch ( event->type() ) + { + case QEvent::FontChange: + case QEvent::ApplicationFontChange: + updateLabel(); + break; + + default: + break; + } +} + + +void +QueryLabel::mousePressEvent( QMouseEvent* event ) +{ + QFrame::mousePressEvent( event ); + time.start(); +} + + +void +QueryLabel::mouseReleaseEvent( QMouseEvent* event ) +{ + QFrame::mouseReleaseEvent( event ); + + m_dragPos = QPoint(); + if ( time.elapsed() < qApp->doubleClickInterval() ) + emit clicked(); +} + + +void +QueryLabel::mouseMoveEvent( QMouseEvent* event ) +{ + QFrame::mouseMoveEvent( event ); + int x = event->x(); + + if ( event->buttons() & Qt::LeftButton && + ( m_dragPos - event->pos() ).manhattanLength() >= QApplication::startDragDistance() ) + { + startDrag(); + leaveEvent( 0 ); + return; + } + + if ( m_query.isNull() && m_result.isNull() ) + { + m_hoverArea = QRect(); + return; + } + + const QFontMetrics& fm = fontMetrics(); + int dashX = fm.width( DASH ); + int artistX = m_type & Artist ? fm.width( artist() ) : 0; + int albumX = m_type & Album ? fm.width( album() ) : 0; + int trackX = m_type & Track ? fm.width( track() ) : 0; + + if ( m_type & Track ) + { + trackX += contentsMargins().left(); + } + if ( m_type & Album ) + { + trackX += albumX + dashX; + albumX += contentsMargins().left(); + } + if ( m_type & Artist ) + { + albumX += artistX + dashX; + trackX += artistX + dashX; + artistX += contentsMargins().left(); + } + + QRect hoverArea; + if ( m_type & Artist && x < artistX ) + { + hoverArea.setLeft( 0 ); + hoverArea.setRight( artistX + contentsMargins().left() ); + } + else if ( m_type & Album && x < albumX && x > artistX ) + { + int spacing = ( m_type & Artist ) ? dashX : 0; + hoverArea.setLeft( artistX + spacing ); + hoverArea.setRight( albumX + spacing + contentsMargins().left() ); + } + else if ( m_type & Track && x < trackX && x > albumX ) + { + int spacing = ( m_type & Album ) ? dashX : 0; + hoverArea.setLeft( albumX + spacing ); + hoverArea.setRight( trackX + contentsMargins().left() ); + } + + if ( hoverArea.width() ) + { + hoverArea.setY( 1 ); + hoverArea.setHeight( height() - 2 ); + } + if ( hoverArea != m_hoverArea ) + { + m_hoverArea = hoverArea; + repaint(); + } +} + + +void +QueryLabel::leaveEvent( QEvent* event ) +{ + m_hoverArea = QRect(); + repaint(); +} + + +void +QueryLabel::startDrag() +{ + if ( m_query.isNull() ) + return; + + QByteArray queryData; + QDataStream queryStream( &queryData, QIODevice::WriteOnly ); + QMimeData* mimeData = new QMimeData(); + mimeData->setText( text() ); + + queryStream << qlonglong( &m_query ); + + mimeData->setData( "application/tomahawk.query.list", queryData ); + QDrag *drag = new QDrag( this ); + drag->setMimeData( mimeData ); + drag->setPixmap( TomahawkUtils::createDragPixmap() ); + +// QPoint hotSpot = event->pos() - child->pos(); +// drag->setHotSpot( hotSpot ); + + drag->exec( Qt::CopyAction ); +} + + +QString +QueryLabel::smartAppend( QString& text, const QString& appendage ) const +{ + QString s; + if ( !text.isEmpty() ) + s = DASH; + + text += s + appendage; + return text; +} diff --git a/src/utils/querylabel.h b/src/utils/querylabel.h new file mode 100644 index 000000000..a9974966c --- /dev/null +++ b/src/utils/querylabel.h @@ -0,0 +1,94 @@ +#ifndef QUERYLABEL_H +#define QUERYLABEL_H + +#include +#include + +#include "result.h" + +class QueryLabel : public QFrame +{ +Q_OBJECT + +public: + enum DisplayType + { + Artist = 1, + Album = 2, + Track = 4, + ArtistAndAlbum = 3, + ArtistAndTrack = 5, + AlbumAndTrack = 6, + Complete = 7 + }; + + explicit QueryLabel( QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( const Tomahawk::result_ptr& result, DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + explicit QueryLabel( const Tomahawk::query_ptr& query, DisplayType type = Complete, QWidget* parent = 0, Qt::WindowFlags flags = 0 ); + virtual ~QueryLabel(); + + QString text() const; + QString artist() const; + QString album() const; + QString track() const; + + Tomahawk::result_ptr result() const { return m_result; } + Tomahawk::query_ptr query() const { return m_query; } + + DisplayType type() const { return m_type; } + void setType( DisplayType type ) { m_type = type; } + + Qt::Alignment alignment() const; + void setAlignment( Qt::Alignment alignment ); + + Qt::TextElideMode elideMode() const; + void setElideMode( Qt::TextElideMode mode ); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void init(); + void updateLabel(); + +public slots: + void setText( const QString& text ); + void setResult( const Tomahawk::result_ptr& result ); + void setQuery( const Tomahawk::query_ptr& query ); + +signals: + void clicked(); + + void textChanged( const QString& text ); + void resultChanged( const Tomahawk::result_ptr& result ); + void queryChanged( const Tomahawk::query_ptr& query ); + +protected: + virtual void mousePressEvent( QMouseEvent* event ); + virtual void mouseReleaseEvent( QMouseEvent* event ); + virtual void mouseMoveEvent( QMouseEvent* event ); + virtual void leaveEvent( QEvent* event ); + + virtual void changeEvent( QEvent* event ); + virtual void paintEvent( QPaintEvent* event ); + + virtual void startDrag(); + +private: + QString smartAppend( QString& text, const QString& appendage ) const; + QTime time; + + DisplayType m_type; + QString m_text; + Tomahawk::result_ptr m_result; + Tomahawk::query_ptr m_query; + + Qt::Alignment align; + Qt::TextElideMode mode; + + QRect m_hoverArea; + QPoint m_dragPos; + QMargins m_textMargins; +}; + +#endif // QUERYLABEL_H diff --git a/src/utils/tomahawkutils.cpp b/src/utils/tomahawkutils.cpp index a181651f9..d9a5e6c50 100644 --- a/src/utils/tomahawkutils.cpp +++ b/src/utils/tomahawkutils.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #ifdef WIN32 #include @@ -210,4 +212,65 @@ filesizeToString( unsigned int size ) return QString::number( size ); } + +QPixmap +createDragPixmap( int itemCount ) +{ + // If more than one item is dragged, align the items inside a + // rectangular grid. The maximum grid size is limited to 5 x 5 items. + int xCount = 3; + int size = 32; + + if ( itemCount > 16 ) + { + xCount = 5; + size = 16; + } else if( itemCount > 9 ) + { + xCount = 4; + size = 22; + } + + if( itemCount < xCount ) + { + xCount = itemCount; + } + + int yCount = itemCount / xCount; + if( itemCount % xCount != 0 ) + { + ++yCount; + } + if( yCount > xCount ) + { + yCount = xCount; + } + // Draw the selected items into the grid cells + QPixmap dragPixmap( xCount * size + xCount - 1, yCount * size + yCount - 1 ); + dragPixmap.fill( Qt::transparent ); + + QPainter painter( &dragPixmap ); + painter.setRenderHint( QPainter::Antialiasing ); + int x = 0; + int y = 0; + for( int i = 0; i < itemCount; ++i ) + { + const QPixmap pixmap = QPixmap( QString( ":/data/icons/audio-x-generic-%2x%2.png" ).arg( size ) ); + painter.drawPixmap( x, y, pixmap ); + + x += size + 1; + if ( x >= dragPixmap.width() ) + { + x = 0; + y += size + 1; + } + if ( y >= dragPixmap.height() ) + { + break; + } + } + + return dragPixmap; +} + } // ns diff --git a/src/utils/tomahawkutils.h b/src/utils/tomahawkutils.h index d902250bc..737f56612 100644 --- a/src/utils/tomahawkutils.h +++ b/src/utils/tomahawkutils.h @@ -4,6 +4,7 @@ class QDir; class QDateTime; class QString; +class QPixmap; namespace TomahawkUtils { @@ -13,6 +14,8 @@ namespace TomahawkUtils QString timeToString( int seconds ); QString ageToString( const QDateTime& time ); QString filesizeToString( unsigned int size ); + + QPixmap createDragPixmap( int itemCount = 1 ); } #endif // TOMAHAWKUTILS_H