1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-17 19:37:09 +02:00

Merge branch 'master' into dynamic

Conflicts:
	src/libtomahawk/result.h
This commit is contained in:
Leo Franchi
2011-01-13 17:44:22 -05:00
34 changed files with 1263 additions and 265 deletions

View File

@@ -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();

View File

@@ -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

View File

@@ -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 );

View File

@@ -168,7 +168,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>8</number>
<number>4</number>
</property>
<property name="topMargin">
<number>6</number>
@@ -177,14 +177,35 @@
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0,0">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="trackLabelLayout">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ElidedLabel" name="artistTrackLabel">
<widget class="QueryLabel" name="artistTrackLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
@@ -194,7 +215,19 @@
</widget>
</item>
<item>
<widget class="ElidedLabel" name="albumLabel">
<widget class="QueryLabel" name="albumLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>16</height>
</size>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
@@ -212,8 +245,8 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
<width>4</width>
<height>8</height>
</size>
</property>
</spacer>
@@ -246,13 +279,16 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>4</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="timeLabel">
<property name="text">
@@ -459,9 +495,9 @@
<header>imagebutton.h</header>
</customwidget>
<customwidget>
<class>ElidedLabel</class>
<class>QueryLabel</class>
<extends>QLabel</extends>
<header location="global">elidedlabel.h</header>
<header>querylabel.h</header>
</customwidget>
</customwidgets>
<resources/>

View File

@@ -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} ..

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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();

View File

@@ -6,7 +6,7 @@
#include <QSqlError>
#include <QTime>
#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 )
{}

View File

@@ -1,11 +1,18 @@
#include <QDebug>
#include "bufferiodevice.h"
#include <QDebug>
#include <QCoreApplication>
#include <QThread>
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 );
}

View File

@@ -4,35 +4,58 @@
#include <QIODevice>
#include <QMutexLocker>
#include <QDebug>
#include <QFile>
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<QByteArray> m_buffer;
mutable QMutex m_mut; //const methods need to lock
unsigned int m_size, m_received;
unsigned int m_pos;
};
#endif // BUFFERIODEVICE_H

View File

@@ -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();
}
}

View File

@@ -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 ) );
}

View File

@@ -55,6 +55,8 @@ private slots:
void sendSome();
void showStats( qint64 tx, qint64 rx );
void onBlockRequest( int pos );
private:
QSharedPointer<QIODevice> m_iodev;
ControlConnection* m_cc;
@@ -62,6 +64,8 @@ private:
Type m_type;
QSharedPointer<QIODevice> m_readdev;
int m_curBlock;
int m_badded, m_bsent;
bool m_allok; // got last msg ok, transfer complete?

View File

@@ -0,0 +1,89 @@
#include "portfwdthread.h"
#include <QApplication>
#include <QDebug>
#include <QStringList>
#include <QTime>
#include <QTimer>
#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 );
}
}

View File

@@ -0,0 +1,32 @@
#ifndef PORTFWDTHREAD_H
#define PORTFWDTHREAD_H
#include <QThread>
#include <QMutex>
#include <QHostAddress>
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

View File

@@ -4,7 +4,6 @@
#include <QMutexLocker>
#include <QNetworkInterface>
#include <QFile>
#include <QTime>
#include <QThread>
#include <QNetworkRequest>
#include <QNetworkReply>
@@ -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<QSharedPointer<QIODevice>(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<QIODevice>
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 );

View File

@@ -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<QSharedPointer<QIODevice>(Tomahawk::result_ptr)> > m_iofactories;
PortFwdThread* m_portfwd;
static Servent* s_instance;
};

View File

@@ -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();
}

View File

@@ -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()
{

View File

@@ -23,6 +23,8 @@ public:
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();

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 );
}

View File

@@ -7,27 +7,15 @@
<x>0</x>
<y>0</y>
<width>359</width>
<height>58</height>
<height>56</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>58</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>58</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
@@ -92,7 +80,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>4</width>
<width>2</width>
<height>20</height>
</size>
</property>
@@ -101,10 +89,10 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="topMargin">
<number>4</number>
<number>2</number>
</property>
<property name="bottomMargin">
<number>4</number>
<number>2</number>
</property>
<item>
<widget class="ElidedLabel" name="nameLabel">
@@ -127,7 +115,7 @@
</widget>
</item>
<item>
<widget class="ElidedLabel" name="activityLabel">
<widget class="QueryLabel" name="activityLabel">
<property name="text">
<string>TextLabel</string>
</property>
@@ -196,6 +184,11 @@
<extends>QPushButton</extends>
<header>imagebutton.h</header>
</customwidget>
<customwidget>
<class>QueryLabel</class>
<extends>QLabel</extends>
<header>querylabel.h</header>
</customwidget>
<customwidget>
<class>ElidedLabel</class>
<extends>QLabel</extends>

View File

@@ -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<QNetworkProxy::ProxyType>(TomahawkSettings::instance()->proxyType()), TomahawkSettings::instance()->proxyHost(), TomahawkSettings::instance()->proxyPort(), TomahawkSettings::instance()->proxyUsername(), TomahawkSettings::instance()->proxyPassword() );
qDebug() << "Proxy type = " << QString::number( static_cast<int>(m_proxy->type()) );
qDebug() << "Proxy host = " << m_proxy->hostName();
qDebug() << "Proxy type =" << QString::number( static_cast<int>(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<QIODevice> >("QSharedPointer<QIODevice>");
qRegisterMetaType< QFileInfo >("QFileInfo");
qRegisterMetaType< QHostAddress >("QHostAddress");
qRegisterMetaType< QMap<QString, unsigned int> >("QMap<QString, unsigned int>");
qRegisterMetaType< QMap< QString, plentry_ptr > >("QMap< QString, plentry_ptr >");
qRegisterMetaType< QHash< QString, QMap<quint32, quint16> > >("QHash< QString, QMap<quint32, quint16> >");
@@ -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 );
}
}

View File

@@ -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();
}

View File

@@ -1,6 +1,5 @@
#include "elidedlabel.h"
#include <QTime>
#include <QEvent>
#include <QPainter>
#include <QFontMetrics>

View File

@@ -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 );

530
src/utils/querylabel.cpp Normal file
View File

@@ -0,0 +1,530 @@
#include "querylabel.h"
#include <QApplication>
#include <QEvent>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QPainter>
#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<Tomahawk::result_ptr> 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;
}

94
src/utils/querylabel.h Normal file
View File

@@ -0,0 +1,94 @@
#ifndef QUERYLABEL_H
#define QUERYLABEL_H
#include <QFrame>
#include <QTime>
#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

View File

@@ -4,6 +4,8 @@
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QPainter>
#include <QPixmap>
#ifdef WIN32
#include <windows.h>
@@ -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

View File

@@ -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