From 971cc742bb1c10a9e24f46175b1ba17492b65ef9 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 00:45:16 -0400 Subject: [PATCH 01/72] Add ability to query all sources with a getInfo call. Watch out for finished() :-) --- src/libtomahawk/infosystem/infosystem.cpp | 8 +-- src/libtomahawk/infosystem/infosystem.h | 4 +- .../infosystem/infosystemworker.cpp | 63 +++++++++++-------- src/libtomahawk/infosystem/infosystemworker.h | 14 ++--- 4 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/libtomahawk/infosystem/infosystem.cpp b/src/libtomahawk/infosystem/infosystem.cpp index 973be3844..787e5fdee 100644 --- a/src/libtomahawk/infosystem/infosystem.cpp +++ b/src/libtomahawk/infosystem/infosystem.cpp @@ -120,15 +120,15 @@ InfoSystem::newNam() const void -InfoSystem::getInfo( const InfoRequestData &requestData, uint timeoutMillis ) +InfoSystem::getInfo( const InfoRequestData &requestData, uint timeoutMillis, bool allSources ) { qDebug() << Q_FUNC_INFO; - QMetaObject::invokeMethod( m_worker.data(), "getInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ), Q_ARG( uint, timeoutMillis ) ); + QMetaObject::invokeMethod( m_worker.data(), "getInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ), Q_ARG( uint, timeoutMillis ), Q_ARG( bool, allSources ) ); } void -InfoSystem::getInfo( const QString &caller, const InfoTypeMap &inputMap, const QVariantMap &customData, const InfoTimeoutMap &timeoutMap ) +InfoSystem::getInfo( const QString &caller, const InfoTypeMap &inputMap, const QVariantMap &customData, const InfoTimeoutMap &timeoutMap, bool allSources ) { InfoRequestData requestData; requestData.caller = caller; @@ -137,7 +137,7 @@ InfoSystem::getInfo( const QString &caller, const InfoTypeMap &inputMap, const Q { requestData.type = type; requestData.input = inputMap[ type ]; - QMetaObject::invokeMethod( m_worker.data(), "getInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ), Q_ARG( uint, ( timeoutMap.contains( type ) ? timeoutMap[ type ] : 0 ) ) ); + QMetaObject::invokeMethod( m_worker.data(), "getInfo", Qt::QueuedConnection, Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ), Q_ARG( uint, ( timeoutMap.contains( type ) ? timeoutMap[ type ] : 0 ) ), Q_ARG( bool, allSources ) ); } } diff --git a/src/libtomahawk/infosystem/infosystem.h b/src/libtomahawk/infosystem/infosystem.h index c918970cb..87366ec4a 100644 --- a/src/libtomahawk/infosystem/infosystem.h +++ b/src/libtomahawk/infosystem/infosystem.h @@ -201,9 +201,9 @@ public: InfoSystem( QObject *parent ); ~InfoSystem(); - void getInfo( const InfoRequestData &requestData, uint timeoutMillis = 0 ); + void getInfo( const InfoRequestData &requestData, uint timeoutMillis = 0, bool allSources = false ); //WARNING: if changing timeoutMillis above, also change in below function in .cpp file - void getInfo( const QString &caller, const InfoTypeMap &inputMap, const QVariantMap &customData, const InfoTimeoutMap &timeoutMap = InfoTimeoutMap() ); + void getInfo( const QString &caller, const InfoTypeMap &inputMap, const QVariantMap &customData, const InfoTimeoutMap &timeoutMap = InfoTimeoutMap(), bool allSources = false ); void pushInfo( const QString &caller, const InfoType type, const QVariant &input ); void pushInfo( const QString &caller, const InfoTypeMap &input ); diff --git a/src/libtomahawk/infosystem/infosystemworker.cpp b/src/libtomahawk/infosystem/infosystemworker.cpp index 7b11ded1f..b868503cf 100644 --- a/src/libtomahawk/infosystem/infosystemworker.cpp +++ b/src/libtomahawk/infosystem/infosystemworker.cpp @@ -153,12 +153,12 @@ InfoSystemWorker::registerInfoTypes( const InfoPluginPtr &plugin, const QSet< In } -QLinkedList< InfoPluginPtr > +QList< InfoPluginPtr > InfoSystemWorker::determineOrderedMatches( const InfoType type ) const { //Dummy function for now that returns the various items in the QSet; at some point this will //probably need to support ordering based on the data source - QLinkedList< InfoPluginPtr > providers; + QList< InfoPluginPtr > providers; Q_FOREACH( InfoPluginPtr ptr, m_infoGetMap[type] ) providers << ptr; return providers; @@ -166,11 +166,11 @@ InfoSystemWorker::determineOrderedMatches( const InfoType type ) const void -InfoSystemWorker::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData, uint timeoutMillis ) +InfoSystemWorker::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData, uint timeoutMillis, bool allSources ) { // qDebug() << Q_FUNC_INFO; - QLinkedList< InfoPluginPtr > providers = determineOrderedMatches( requestData.type ); + QList< InfoPluginPtr > providers = determineOrderedMatches( requestData.type ); if ( providers.isEmpty() ) { emit info( requestData, QVariant() ); @@ -178,33 +178,42 @@ InfoSystemWorker::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData, ui return; } - InfoPluginPtr ptr = providers.first(); - if ( !ptr ) + if ( !allSources ) + providers = QList< InfoPluginPtr >( providers.mid( 0, 1 ) ); + + bool foundOne = false; + foreach ( InfoPluginPtr ptr, providers ) + { + if ( !ptr ) + continue; + + foundOne = true; + uint requestId = ++m_nextRequest; + m_requestSatisfiedMap[ requestId ] = false; + if ( timeoutMillis != 0 ) + { + qint64 currMs = QDateTime::currentMSecsSinceEpoch(); + m_timeRequestMapper.insert( currMs + timeoutMillis, requestId ); + } + // qDebug() << "Assigning request with requestId" << requestId << "and type" << requestData.type; + m_dataTracker[ requestData.caller ][ requestData.type ] = m_dataTracker[ requestData.caller ][ requestData.type ] + 1; + // qDebug() << "Current count in dataTracker for target" << requestData.caller << "and type" << requestData.type << "is" << m_dataTracker[ requestData.caller ][ requestData.type ]; + + InfoRequestData* data = new InfoRequestData; + data->caller = requestData.caller; + data->type = requestData.type; + data->input = requestData.input; + data->customData = requestData.customData; + m_savedRequestMap[ requestId ] = data; + + QMetaObject::invokeMethod( ptr.data(), "getInfo", Qt::QueuedConnection, Q_ARG( uint, requestId ), Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ) ); + } + + if ( !foundOne ) { emit info( requestData, QVariant() ); checkFinished( requestData.caller ); - return; } - - uint requestId = ++m_nextRequest; - m_requestSatisfiedMap[ requestId ] = false; - if ( timeoutMillis != 0 ) - { - qint64 currMs = QDateTime::currentMSecsSinceEpoch(); - m_timeRequestMapper.insert( currMs + timeoutMillis, requestId ); - } -// qDebug() << "Assigning request with requestId" << requestId << "and type" << requestData.type; - m_dataTracker[ requestData.caller ][ requestData.type ] = m_dataTracker[ requestData.caller ][ requestData.type ] + 1; -// qDebug() << "Current count in dataTracker for target" << requestData.caller << "and type" << requestData.type << "is" << m_dataTracker[ requestData.caller ][ requestData.type ]; - - InfoRequestData* data = new InfoRequestData; - data->caller = requestData.caller; - data->type = requestData.type; - data->input = requestData.input; - data->customData = requestData.customData; - m_savedRequestMap[ requestId ] = data; - - QMetaObject::invokeMethod( ptr.data(), "getInfo", Qt::QueuedConnection, Q_ARG( uint, requestId ), Q_ARG( Tomahawk::InfoSystem::InfoRequestData, requestData ) ); } diff --git a/src/libtomahawk/infosystem/infosystemworker.h b/src/libtomahawk/infosystem/infosystemworker.h index dd0de2b54..419c74b2e 100644 --- a/src/libtomahawk/infosystem/infosystemworker.h +++ b/src/libtomahawk/infosystem/infosystemworker.h @@ -21,13 +21,13 @@ #include "infosystem/infosystem.h" -#include +#include #include #include #include #include #include -#include +#include #include #include @@ -57,7 +57,7 @@ signals: public slots: void init( QWeakPointer< Tomahawk::InfoSystem::InfoSystemCache > cache ); - void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData, uint timeoutMillis ); + void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData, uint timeoutMillis, bool allSources ); void pushInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input ); void infoSlot( uint requestId, Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ); @@ -76,13 +76,13 @@ private: QHash< uint, bool > m_requestSatisfiedMap; QHash< uint, InfoRequestData* > m_savedRequestMap; - QLinkedList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; + QList< InfoPluginPtr > determineOrderedMatches( const InfoType type ) const; // For now, statically instantiate plugins; this is just somewhere to keep them - QLinkedList< InfoPluginPtr > m_plugins; + QList< InfoPluginPtr > m_plugins; - QMap< InfoType, QLinkedList< InfoPluginPtr > > m_infoGetMap; - QMap< InfoType, QLinkedList< InfoPluginPtr > > m_infoPushMap; + QMap< InfoType, QList< InfoPluginPtr > > m_infoGetMap; + QMap< InfoType, QList< InfoPluginPtr > > m_infoPushMap; QWeakPointer< QNetworkAccessManager> m_nam; From dccc4e6225730ff68dc8560cae65a59a8dc7c41e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 02:29:05 -0400 Subject: [PATCH 02/72] Add definitions for bringToFront. Can't test on X11 as TH crashes with database errors before I get the chance. --- src/libtomahawk/CMakeLists.txt | 5 +- src/libtomahawk/utils/tomahawkutils.cpp | 51 +- thirdparty/libqnetwm/fixx11h.h | 243 ++++++++ thirdparty/libqnetwm/netwm.cpp | 730 ++++++++++++++++++++++++ thirdparty/libqnetwm/netwm.h | 186 ++++++ 5 files changed, 1198 insertions(+), 17 deletions(-) create mode 100644 thirdparty/libqnetwm/fixx11h.h create mode 100644 thirdparty/libqnetwm/netwm.cpp create mode 100644 thirdparty/libqnetwm/netwm.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 5b477f96d..a838ccf4e 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -410,7 +410,7 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/. ${LIBPORTFWD_INCLUDE_DIR} ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb - ${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src + ${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/libqnetwm ) IF( UNIX AND NOT APPLE ) @@ -428,7 +428,8 @@ IF( UNIX AND NOT APPLE ) infosystem/infoplugins/unix/fdonotifyplugin.h ) IF( BUILD_GUI AND X11_FOUND ) - SET( LINK_LIBRARIES ${LINK_LIBRARIES} ${X11_LIBRARIES} ) + SET( libSources ${libSources} ${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/libqnetwm/netwm.cpp ) + SET( LINK_LIBRARIES ${LINK_LIBRARIES} ${X11_LIBRARIES} Xcomposite ) ENDIF() ENDIF( UNIX AND NOT APPLE ) diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index eb5e0a40d..4c1c342a1 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -45,13 +45,16 @@ #include #ifdef Q_WS_X11 - extern "C" { - #include - } + #include + #include + #endif + + #ifdef Q_WS_WIN + #include + #include #endif #endif - #include #include "utils/logger.h" #include "config.h" @@ -531,23 +534,41 @@ setNam( QNetworkAccessManager* nam ) i++; QWidget *widget = widgetList.at( i ); - WId winId = widget->winId(); - Display *display = XOpenDisplay( NULL ); - if ( !display ) - { - qDebug() << Q_FUNC_INFO << "Could not find display to raise"; - return; - } + widget->show(); + widget->activateWindow(); + widget->raise(); + + WId wid = widget->winId(); - XRaiseWindow( display, winId ); - XSetInputFocus( display, winId, RevertToNone, CurrentTime ); - //widget->activateWindow(); - //widget->raise(); + NETWM::init(); + + XEvent e; + + e.xclient.type = ClientMessage; + e.xclient.message_type = NETWM::NET_ACTIVE_WINDOW; + e.xclient.display = QX11Info::display(); + e.xclient.window = wid; + e.xclient.format = 32; + e.xclient.data.l[0] = 2; + e.xclient.data.l[1] = QX11Info::appTime(); + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = 0l; + e.xclient.data.l[4] = 0l; + + XSendEvent( QX11Info::display(), RootWindow( QX11Info::display(), DefaultScreen( QX11Info::display() ) ), False, SubstructureRedirectMask | SubstructureNotifyMask, &e ); } #elif defined(Q_WS_WIN) void bringToFront() { + HWND hwndActiveWin = GetForegroundWindow(); + int idActive = GetWindowThreadProcessId(hwndActiveWin, NULL); + if ( AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) ) + { + SetForegroundWindow( win ); + SetFocus( win ); + AttachThreadInput(GetCurrentThreadId(), idActive, FALSE); + } } #else #ifndef Q_OS_MAC diff --git a/thirdparty/libqnetwm/fixx11h.h b/thirdparty/libqnetwm/fixx11h.h new file mode 100644 index 000000000..460a4c796 --- /dev/null +++ b/thirdparty/libqnetwm/fixx11h.h @@ -0,0 +1,243 @@ +//#ifdef don't do this, this file is supposed to be included +//#define multiple times + +/* Usage: + + If you get compile errors caused by X11 includes (the line + where first error appears contains word like None, Unsorted, + Below, etc.), put #include in the .cpp file + (not .h file!) between the place where X11 headers are + included and the place where the file with compile + error is included (or the place where the compile error + in the .cpp file occurs). + + This file remaps X11 #defines to const variables or + inline functions. The side effect may be that these + symbols may now refer to different variables + (e.g. if X11 #defined NoButton, after this file + is included NoButton would no longer be X11's + NoButton, but Qt::NoButton instead). At this time, + there's no conflict known that could cause problems. + + The original X11 symbols are still accessible + (e.g. for None) as X::None, XNone, and also still + None, unless name lookup finds different None + first (in the current class, etc.) + + Use 'Unsorted', 'Bool' and 'index' as templates. + +*/ + +namespace X +{ + +// template ---> +// Affects: Should be without side effects. +#ifdef Unsorted +#ifndef FIXX11H_Unsorted +#define FIXX11H_Unsorted +const int XUnsorted = Unsorted; +#undef Unsorted +const int Unsorted = XUnsorted; +#endif +#undef Unsorted +#endif +// template <--- + +// Affects: Should be without side effects. +#ifdef None +#ifndef FIXX11H_None +#define FIXX11H_None +const XID XNone = None; +#undef None +const XID None = XNone; +#endif +#undef None +#endif + +// template ---> +// Affects: Should be without side effects. +#ifdef Bool +#ifndef FIXX11H_Bool +#define FIXX11H_Bool +typedef Bool XBool; +#undef Bool +typedef XBool Bool; +#endif +#undef Bool +#endif + +#ifdef FontChange +#ifndef FIXX11H_FontChange +#define FIXX11H_FontChange +const int XFontChange = FontChange; +#undef FontChange +const int FontChange = XFontChange; +#endif +#undef FontChange +#endif +// template <--- + +// Affects: Should be without side effects. +#ifdef KeyPress +#ifndef FIXX11H_KeyPress +#define FIXX11H_KeyPress +const int XKeyPress = KeyPress; +#undef KeyPress +const int KeyPress = XKeyPress; +#endif +#undef KeyPress +#endif + +// Affects: Should be without side effects. +#ifdef KeyRelease +#ifndef FIXX11H_KeyRelease +#define FIXX11H_KeyRelease +const int XKeyRelease = KeyRelease; +#undef KeyRelease +const int KeyRelease = XKeyRelease; +#endif +#undef KeyRelease +#endif + +// Affects: Should be without side effects. +#ifdef Above +#ifndef FIXX11H_Above +#define FIXX11H_Above +const int XAbove = Above; +#undef Above +const int Above = XAbove; +#endif +#undef Above +#endif + +// Affects: Should be without side effects. +#ifdef Below +#ifndef FIXX11H_Below +#define FIXX11H_Below +const int XBelow = Below; +#undef Below +const int Below = XBelow; +#endif +#undef Below +#endif + +// Affects: Should be without side effects. +#ifdef FocusIn +#ifndef FIXX11H_FocusIn +#define FIXX11H_FocusIn +const int XFocusIn = FocusIn; +#undef FocusIn +const int FocusIn = XFocusIn; +#endif +#undef FocusIn +#endif + +// Affects: Should be without side effects. +#ifdef FocusOut +#ifndef FIXX11H_FocusOut +#define FIXX11H_FocusOut +const int XFocusOut = FocusOut; +#undef FocusOut +const int FocusOut = XFocusOut; +#endif +#undef FocusOut +#endif + +// Affects: Should be without side effects. +#ifdef Always +#ifndef FIXX11H_Always +#define FIXX11H_Always +const int XAlways = Always; +#undef Always +const int Always = XAlways; +#endif +#undef Always +#endif + +// Affects: Should be without side effects. +#ifdef Success +#ifndef FIXX11H_Success +#define FIXX11H_Success +const int XSuccess = Success; +#undef Success +const int Success = XSuccess; +#endif +#undef Success +#endif + +// Affects: Should be without side effects. +#ifdef GrayScale +#ifndef FIXX11H_GrayScale +#define FIXX11H_GrayScale +const int XGrayScale = GrayScale; +#undef GrayScale +const int GrayScale = XGrayScale; +#endif +#undef GrayScale +#endif + +// Affects: Should be without side effects. +#ifdef Status +#ifndef FIXX11H_Status +#define FIXX11H_Status +typedef Status XStatus; +#undef Status +typedef XStatus Status; +#endif +#undef Status +#endif + +// Affects: Should be without side effects. +#ifdef CursorShape +#ifndef FIXX11H_CursorShape +#define FIXX11H_CursorShape +const int XCursorShape = CursorShape; +#undef CursorShape +const int CursorShape = CursorShape; +#endif +#undef CursorShape +#endif + +// template ---> +// Affects: Should be without side effects. +#ifdef index +#ifndef FIXX11H_index +#define FIXX11H_index +inline +char* Xindex( const char* s, int c ) + { + return index( s, c ); + } +#undef index +inline +char* index( const char* s, int c ) + { + return Xindex( s, c ); + } +#endif +#undef index +#endif +// template <--- + +#ifdef rindex +// Affects: Should be without side effects. +#ifndef FIXX11H_rindex +#define FIXX11H_rindex +inline +char* Xrindex( const char* s, int c ) + { + return rindex( s, c ); + } +#undef rindex +inline +char* rindex( const char* s, int c ) + { + return Xrindex( s, c ); + } +#endif +#undef rindex +#endif +} + +using namespace X; diff --git a/thirdparty/libqnetwm/netwm.cpp b/thirdparty/libqnetwm/netwm.cpp new file mode 100644 index 000000000..ed0a51157 --- /dev/null +++ b/thirdparty/libqnetwm/netwm.cpp @@ -0,0 +1,730 @@ +/*************************************************************************** + * Copyright (C) 2010 by Dmitry 'Krasu' Baryshev * + * ksquirrel.iv@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "netwm.h" + +#define DBG(...) //fprintf(stderr, ##__VA_ARGS__) + +Atom NETWM::UTF8_STRING = 0; +Atom NETWM::XROOTPMAP_ID = 0; + +Atom NETWM::WM_STATE = 0; +Atom NETWM::WM_CLASS = 0; +Atom NETWM::WM_NAME = 0; +Atom NETWM::WM_DELETE_WINDOW = 0; +Atom NETWM::WM_PROTOCOLS = 0; +Atom NETWM::WM_CHANGE_STATE = 0; +Atom NETWM::WM_WINDOW_ROLE = 0; + +Atom NETWM::NET_WORKAREA = 0; +Atom NETWM::NET_CLIENT_LIST = 0; +Atom NETWM::NET_CLIENT_LIST_STACKING = 0; +Atom NETWM::NET_NUMBER_OF_DESKTOPS = 0; +Atom NETWM::NET_CURRENT_DESKTOP = 0; +Atom NETWM::NET_DESKTOP_NAMES = 0; +Atom NETWM::NET_ACTIVE_WINDOW = 0; +Atom NETWM::NET_CLOSE_WINDOW = 0; +Atom NETWM::NET_SUPPORTED = 0; +Atom NETWM::NET_WM_DESKTOP = 0; +Atom NETWM::NET_SHOWING_DESKTOP = 0; + +Atom NETWM::NET_WM_STATE = 0; +Atom NETWM::NET_WM_STATE_MODAL = 0; +Atom NETWM::NET_WM_STATE_STICKY = 0; +Atom NETWM::NET_WM_STATE_MAXIMIZED_VERT = 0; +Atom NETWM::NET_WM_STATE_MAXIMIZED_HORZ = 0; +Atom NETWM::NET_WM_STATE_SHADED = 0; +Atom NETWM::NET_WM_STATE_SKIP_TASKBAR = 0; +Atom NETWM::NET_WM_STATE_SKIP_PAGER = 0; +Atom NETWM::NET_WM_STATE_HIDDEN = 0; +Atom NETWM::NET_WM_STATE_FULLSCREEN = 0; +Atom NETWM::NET_WM_STATE_ABOVE = 0; +Atom NETWM::NET_WM_STATE_BELOW = 0; +Atom NETWM::NET_WM_STATE_STAYS_ON_TOP = 0; +Atom NETWM::NET_WM_STATE_STAYS_ON_BOTTOM = 0; +Atom NETWM::NET_WM_STATE_DEMANDS_ATTENTION = 0; + +Atom NETWM::NET_WM_WINDOW_TYPE = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_DESKTOP = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_DOCK = 0; +Atom NETWM::MODERRO_WINDOW_TYPE_DOCK = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_TOOLBAR = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_MENU = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_UTILITY = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_SPLASH = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_DIALOG = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_DROPDOWN_MENU = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_POPUP_MENU = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_TOOLTIP = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_NOTIFICATION = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_COMBO = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_DND = 0; +Atom NETWM::NET_WM_WINDOW_TYPE_NORMAL = 0; +Atom NETWM::NET_WM_WINDOW_OPACITY = 0; +Atom NETWM::NET_WM_NAME = 0; +Atom NETWM::NET_WM_VISIBLE_NAME = 0; +Atom NETWM::NET_WM_STRUT = 0; +Atom NETWM::NET_WM_STRUT_PARTIAL = 0; +Atom NETWM::NET_WM_ICON = 0; +Atom NETWM::NET_WM_PID = 0; + +NETWM::net_wm_state::net_wm_state() + : modal(0), sticky(0), maximized_vert(0), + maximized_horz(0), shaded(0), skip_taskbar(0), + skip_pager(0), hidden(0), fullscreen(0), + above(0), below(0), stays_on_top(0), stays_on_bottom(0), + demands_attention(0), valid(false) +{} + +NETWM::net_wm_window_type::net_wm_window_type() + : desktop(0), dock(0), toolbar(0), + menu(0), utility(0), splash(0), dialog(0), + dropdown(0), popup(0), tooltip(0), notification(0), + combo(0), dnd(0), normal(0), valid(false) +{} + +/**********************************************************/ + +void NETWM::init() +{ + Display *dpy = QX11Info::display(); + + UTF8_STRING = XInternAtom(dpy, "UTF8_STRING", False); + XROOTPMAP_ID = XInternAtom(dpy, "_XROOTPMAP_ID", False); + WM_STATE = XInternAtom(dpy, "WM_STATE", False); + WM_CLASS = XInternAtom(dpy, "WM_CLASS", False); + WM_NAME = XInternAtom(dpy, "WM_NAME", False); + WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + WM_CHANGE_STATE = XInternAtom(dpy, "WM_CHANGE_STATE", False); + WM_WINDOW_ROLE = XInternAtom(dpy, "WM_WINDOW_ROLE", False); + + WM_PROTOCOLS = XInternAtom(dpy, "WM_PROTOCOLS", False); + NET_WORKAREA = XInternAtom(dpy, "_NET_WORKAREA", False); + NET_CLIENT_LIST = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + NET_CLIENT_LIST_STACKING = XInternAtom(dpy, "_NET_CLIENT_LIST_STACKING", False); + NET_NUMBER_OF_DESKTOPS = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False); + NET_CURRENT_DESKTOP = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False); + NET_DESKTOP_NAMES = XInternAtom(dpy, "_NET_DESKTOP_NAMES", False); + NET_ACTIVE_WINDOW = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + NET_CLOSE_WINDOW = XInternAtom(dpy, "_NET_CLOSE_WINDOW", False); + NET_SUPPORTED = XInternAtom(dpy, "_NET_SUPPORTED", False); + NET_WM_DESKTOP = XInternAtom(dpy, "_NET_WM_DESKTOP", False); + NET_SHOWING_DESKTOP = XInternAtom(dpy, "_NET_SHOWING_DESKTOP", False); + + NET_WM_STATE = XInternAtom(dpy, "_NET_WM_STATE", False); + NET_WM_STATE_MODAL = XInternAtom(dpy, "_NET_WM_STATE_MODAL", False); + NET_WM_STATE_STICKY = XInternAtom(dpy, "_NET_WM_STATE_STICKY", False); + NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False); + NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False); + NET_WM_STATE_SHADED = XInternAtom(dpy, "_NET_WM_STATE_SHADED", False); + NET_WM_STATE_SKIP_TASKBAR = XInternAtom(dpy, "_NET_WM_STATE_SKIP_TASKBAR", False); + NET_WM_STATE_SKIP_PAGER = XInternAtom(dpy, "_NET_WM_STATE_SKIP_PAGER", False); + NET_WM_STATE_HIDDEN = XInternAtom(dpy, "_NET_WM_STATE_HIDDEN", False); + NET_WM_STATE_FULLSCREEN = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + NET_WM_STATE_ABOVE = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False); + NET_WM_STATE_BELOW = XInternAtom(dpy, "_NET_WM_STATE_BELOW", False); + NET_WM_STATE_STAYS_ON_TOP = XInternAtom(dpy, "_NET_WM_STATE_STAYS_ON_TOP", False); + NET_WM_STATE_STAYS_ON_BOTTOM = XInternAtom(dpy, "_NET_WM_STATE_STAYS_ON_BOTTOM", False); + NET_WM_STATE_DEMANDS_ATTENTION = XInternAtom(dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", False); + + NET_WM_WINDOW_TYPE = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + NET_WM_WINDOW_TYPE_DESKTOP = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DESKTOP", False); + NET_WM_WINDOW_TYPE_DOCK = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); + MODERRO_WINDOW_TYPE_DOCK = XInternAtom(dpy, "_MODERRO_WINDOW_TYPE_DOCK", False); + NET_WM_WINDOW_TYPE_TOOLBAR = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_TOOLBAR", False); + NET_WM_WINDOW_TYPE_MENU = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_MENU", False); + NET_WM_WINDOW_TYPE_UTILITY = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_UTILITY", False); + NET_WM_WINDOW_TYPE_SPLASH = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_SPLASH", False); + NET_WM_WINDOW_TYPE_DIALOG = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + NET_WM_WINDOW_TYPE_DROPDOWN_MENU = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", False); + NET_WM_WINDOW_TYPE_POPUP_MENU = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_POPUP_MENU", False); + NET_WM_WINDOW_TYPE_TOOLTIP = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_TOOLTIP", False); + NET_WM_WINDOW_TYPE_NOTIFICATION = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False); + NET_WM_WINDOW_TYPE_COMBO = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_COMBO", False); + NET_WM_WINDOW_TYPE_DND = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DND", False); + NET_WM_WINDOW_TYPE_NORMAL = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NORMAL", False); + + NET_WM_WINDOW_OPACITY = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); + NET_WM_NAME = XInternAtom(dpy, "_NET_WM_NAME", False); + NET_WM_VISIBLE_NAME = XInternAtom(dpy, "_NET_WM_VISIBLE_NAME", False); + NET_WM_STRUT = XInternAtom(dpy, "_NET_WM_STRUT", False); + NET_WM_STRUT_PARTIAL = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False); + NET_WM_ICON = XInternAtom(dpy, "_NET_WM_ICON", False); + NET_WM_PID = XInternAtom(dpy, "_NET_WM_PID", False); +} + +int NETWM::setProperty(Window window, Atom atom, long offset, uchar *data, int nelem) +{ + NETWM::checkInit(); + + return XChangeProperty(QX11Info::display(), window, atom, offset, 32, PropModeReplace, data, nelem); +} + +int NETWM::setPropertySkipTaskbar(Window window) +{ + NETWM::checkInit(); + + Atom state[3]; + + state[0] = NETWM::NET_WM_STATE_SKIP_PAGER; + state[1] = NETWM::NET_WM_STATE_SKIP_TASKBAR; + state[2] = NETWM::NET_WM_STATE_STICKY; + + return NETWM::setProperty(window, NETWM::NET_WM_STATE, XA_ATOM, (uchar *)&state, 3); +} + +int NETWM::setPropertyOnTop(Window window) +{ + NETWM::checkInit(); + + Atom state[2]; + + state[0] = NETWM::NET_WM_STATE_ABOVE; + state[1] = NETWM::NET_WM_STATE_STAYS_ON_TOP; + + return NETWM::setProperty(window, NETWM::NET_WM_STATE, XA_ATOM, (uchar *)&state, 2); +} + +void* NETWM::property(Window win, Atom prop, Atom type, int *nitems, bool *ok) +{ + NETWM::checkInit(); + + Atom type_ret; + int format_ret; + unsigned long items_ret; + unsigned long after_ret; + unsigned char *prop_data = 0; + + if(XGetWindowProperty(QX11Info::display(), + win, + prop, + 0, + 0x7fffffff, + False, + type, + &type_ret, + &format_ret, + &items_ret, + &after_ret, + &prop_data) != Success) + { + if(ok) + *ok = false; + + return 0; + } + + if(nitems) + *nitems = items_ret; + + if(ok) + *ok = true; + + return prop_data; +} + +bool NETWM::climsg(Window win, long type, long l0, long l1, long l2, long l3, long l4) +{ + NETWM::checkInit(); + + XClientMessageEvent xev; + + xev.type = ClientMessage; + xev.window = win; + xev.message_type = type; + xev.format = 32; + xev.data.l[0] = l0; + xev.data.l[1] = l1; + xev.data.l[2] = l2; + xev.data.l[3] = l3; + xev.data.l[4] = l4; + + return (XSendEvent(QX11Info::display(), QX11Info::appRootWindow(), False, + (SubstructureNotifyMask | SubstructureRedirectMask), + (XEvent *)&xev) == Success); +} + +bool NETWM::climsgwm(Window win, Atom type, Atom arg) +{ + NETWM::checkInit(); + + XClientMessageEvent xev; + + xev.type = ClientMessage; + xev.window = win; + xev.message_type = type; + xev.format = 32; + xev.data.l[0] = arg; + xev.data.l[1] = CurrentTime; + + return (XSendEvent(QX11Info::display(), win, False, 0L, (XEvent *)&xev) == Success); +} + +uint NETWM::netwmDesktopsNumber() +{ + NETWM::checkInit(); + + uint desknum; + quint32 *data; + + data = (quint32 *)NETWM::property(QX11Info::appRootWindow(), NETWM::NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 0); + + if(!data) + return 0; + + desknum = *data; + XFree(data); + + return desknum; +} + +uint NETWM::netwmCurrentDesktop() +{ + NETWM::checkInit(); + + uint desk; + quint32 *data; + + data = (quint32 *)NETWM::property(QX11Info::appRootWindow(), NETWM::NET_CURRENT_DESKTOP, XA_CARDINAL, 0); + + if(!data) + return 0; + + desk = *data; + XFree(data); + + return desk; +} + +qint64 NETWM::netwmPid(Window win) +{ + NETWM::checkInit(); + + qint64 pid = -1; + ulong *data; + + data = (ulong *)NETWM::property(win, NETWM::NET_WM_PID, XA_CARDINAL, 0); + + if(data) + { + pid = *data; + XFree(data); + } + + return pid; +} + +bool NETWM::netwmActivateWindow(Window win) +{ + NETWM::checkInit(); + + return NETWM::climsg(win, NETWM::NET_ACTIVE_WINDOW, 2, CurrentTime); +} + +QList NETWM::netwmWindowList() +{ + NETWM::checkInit(); + + QList list; + int num; + + Window *win = reinterpret_cast(NETWM::property(QX11Info::appRootWindow(), NETWM::NET_CLIENT_LIST, XA_WINDOW, &num)); + + if(!win) + { + qDebug("NETWM: Cannot get window list"); + return list; + } + + for(int i = 0;i < num;i++) + list.append(win[i]); + + XFree(win); + + return list; +} + +int NETWM::netwmDesktop(Window win) +{ + NETWM::checkInit(); + + int desk = 0; + ulong *data; + + data = (ulong *)NETWM::property(win, NETWM::NET_WM_DESKTOP, XA_CARDINAL, 0); + + if(data) + { + desk = *data; + XFree(data); + } + + return desk; +} + +NETWM::net_wm_state NETWM::netwmState(Window win) +{ + NETWM::checkInit(); + + net_wm_state nws; + Atom *state; + int num3; + + if(!(state = (Atom *)NETWM::property(win, NETWM::NET_WM_STATE, XA_ATOM, &num3))) + return nws; + + while(--num3 >= 0) + { + if(state[num3] == NETWM::NET_WM_STATE_MODAL) + { + DBG("NET_WM_STATE_MODAL\n"); + nws.modal = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_STICKY) + { + DBG("NET_WM_STATE_STICKY\n"); + nws.sticky = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_MAXIMIZED_VERT) + { + DBG("NET_WM_STATE_MAXIMIZED_VERT\n"); + nws.maximized_vert = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_MAXIMIZED_HORZ) + { + DBG("NET_WM_STATE_MAXIMIZED_HORZ\n"); + nws.maximized_horz = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_SHADED) + { + DBG("NET_WM_STATE_SHADED\n"); + nws.shaded = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_SKIP_TASKBAR) + { + DBG("NET_WM_STATE_SKIP_TASKBAR\n"); + nws.skip_taskbar = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_SKIP_PAGER) + { + DBG("NET_WM_STATE_SKIP_PAGER\n"); + nws.skip_pager = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_HIDDEN) + { + DBG("NET_WM_STATE_HIDDEN\n"); + nws.hidden = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_FULLSCREEN) + { + DBG("NET_WM_STATE_FULLSCREEN\n"); + nws.fullscreen = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_ABOVE) + { + DBG("NET_WM_STATE_ABOVE\n"); + nws.above = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_BELOW) + { + DBG("NET_WM_STATE_BELOW\n"); + nws.below = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_STAYS_ON_TOP) + { + DBG("NET_WM_STATE_STAYS_ON_TOP\n"); + nws.stays_on_top = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_STAYS_ON_BOTTOM) + { + DBG("NET_WM_STATE_STAYS_ON_BOTTOM\n"); + nws.stays_on_bottom = 1; + } + else if(state[num3] == NETWM::NET_WM_STATE_DEMANDS_ATTENTION) + { + DBG("NET_WM_STATE_DEMANDS_ATTENTION\n"); + nws.demands_attention = 1; + } + } + + nws.valid = true; + + XFree(state); + + return nws; +} + +NETWM::net_wm_window_type NETWM::netwmWindowType(Window win) +{ + NETWM::checkInit(); + + net_wm_window_type nwwt; + Atom *state; + int num3; + bool ok; + + + if(!(state = (Atom *)NETWM::property(win, NETWM::NET_WM_WINDOW_TYPE, XA_ATOM, &num3, &ok))) + { + if(ok) + { + nwwt.valid = true; + nwwt.normal = 1; + } + + return nwwt; + } + + nwwt.valid = true; + + while(--num3 >= 0) + { + if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_DESKTOP) + { + DBG("NET_WM_WINDOW_TYPE_DESKTOP\n"); + nwwt.desktop = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_DOCK) + { + DBG("NET_WM_WINDOW_TYPE_DOCK\n"); + nwwt.dock = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_TOOLBAR) + { + DBG("NET_WM_WINDOW_TYPE_TOOLBAR\n"); + nwwt.toolbar = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_MENU) + { + DBG("NET_WM_WINDOW_TYPE_MENU\n"); + nwwt.menu = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_UTILITY) + { + DBG("NET_WM_WINDOW_TYPE_UTILITY\n"); + nwwt.utility = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_SPLASH) + { + DBG("NET_WM_WINDOW_TYPE_SPLASH\n"); + nwwt.splash = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_DIALOG) + { + DBG("NET_WM_WINDOW_TYPE_DIALOG\n"); + nwwt.dialog = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_DROPDOWN_MENU) + { + DBG("NET_WM_WINDOW_TYPE_DROPDOWN_MENU\n"); + nwwt.dropdown = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_POPUP_MENU) + { + DBG("NET_WM_WINDOW_TYPE_POPUP_MENU\n"); + nwwt.popup = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_TOOLTIP) + { + DBG("NET_WM_WINDOW_TYPE_TOOLTIP\n"); + nwwt.tooltip = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_NOTIFICATION) + { + DBG("NET_WM_WINDOW_TYPE_NOTIFICATION\n"); + nwwt.notification = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_COMBO) + { + DBG("NET_WM_WINDOW_TYPE_COMBO\n"); + nwwt.combo = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_DND) + { + DBG("NET_WM_WINDOW_TYPE_DND\n"); + nwwt.dnd = 1; + } + else if(state[num3] == NETWM::NET_WM_WINDOW_TYPE_NORMAL) + { + DBG("NET_WM_WINDOW_TYPE_NORMAL\n"); + nwwt.normal = 1; + } + } + + XFree(state); + + return nwwt; +} + +QString NETWM::icccmString(Window win, Atom atom) +{ + NETWM::checkInit(); + + QString s; + char *data; + + if(!(data = (char *)NETWM::property(win, atom, XA_STRING))) + return s; + + s = QString::fromUtf8(data); + + XFree(data); + + return s; +} + +QString NETWM::icccmUtf8String(Window win, Atom atom) +{ + NETWM::checkInit(); + + Atom type; + int format; + ulong nitems; + ulong bytes_after; + int result; + uchar *tmp = 0; + QString val; + + type = None; + + result = XGetWindowProperty(QX11Info::display(), win, atom, 0, LONG_MAX, False, + NETWM::UTF8_STRING, &type, &format, &nitems, + &bytes_after, &tmp); + + if(result != Success || type == None || !tmp) + return val; + + if(type == NETWM::UTF8_STRING && format == 8 && nitems != 0) + val = QString::fromUtf8(reinterpret_cast(tmp)); + + XFree(tmp); + + return val; +} + +QString NETWM::icccmWindowRole(Window win) +{ + NETWM::checkInit(); + + return NETWM::icccmString(win, NETWM::WM_WINDOW_ROLE); +} + +QStringList NETWM::icccmClass(Window win) +{ + NETWM::checkInit(); + + QStringList l; + char *data; + + if(!(data = (char *)NETWM::property(win, NETWM::WM_CLASS, XA_STRING))) + return l; + + l.append(QString::fromUtf8(data)); + l.append(QString::fromUtf8(data+strlen(data)+1)); + + XFree(data); + + return l; +} + +QString NETWM::icccmName(Window win) +{ + NETWM::checkInit(); + + return NETWM::icccmString(win, NETWM::WM_NAME); +} + +QStringList NETWM::icccmCommand(Window win) +{ + NETWM::checkInit(); + + QStringList list; + char **argv; + int argc; + + if(!XGetCommand(QX11Info::display(), win, &argv, &argc)) + return list; + + for(int i = 0;i < argc;i++) + list.append(argv[i]); + + XFreeStringList(argv); + + return list; +} + +#define MO_NETWM_OPAQUE 0xffffffff + +void NETWM::transset(Window window, double d) +{ + NETWM::checkInit(); + + Display *dpy = QX11Info::display(); + + uint opacity = (uint)(d * MO_NETWM_OPAQUE); + + if(opacity == MO_NETWM_OPAQUE) + XDeleteProperty(dpy, window, NETWM::NET_WM_WINDOW_OPACITY); + else + XChangeProperty(dpy, window, NETWM::NET_WM_WINDOW_OPACITY, + XA_CARDINAL, 32, PropModeReplace, (uchar *)&opacity, 1L); + + XSync(dpy, False); +} + +bool NETWM::isComposite() +{ + int event_base, error_base; + + Display *dpy = QX11Info::display(); + + // extension is not supported + if(!XCompositeQueryExtension(dpy, &event_base, &error_base)) + { + qDebug("NETWM: Composite extension is not supported"); + return false; + } + + // NETWM-compliant composite manager MUST set selection owner + // of _NET_WM_CM_Sn + Window owner = XGetSelectionOwner(dpy, XInternAtom(dpy, "_NET_WM_CM_S0", False)); + + return (owner != None); +} + +void NETWM::checkInit() +{ + if(!NETWM::WM_STATE) + NETWM::init(); +} diff --git a/thirdparty/libqnetwm/netwm.h b/thirdparty/libqnetwm/netwm.h new file mode 100644 index 000000000..8fec8d772 --- /dev/null +++ b/thirdparty/libqnetwm/netwm.h @@ -0,0 +1,186 @@ +/*************************************************************************** + * Copyright (C) 2010 by Dmitry 'Krasu' Baryshev * + * ksquirrel.iv@gmail.com * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef NETWM_H +#define NETWM_H + +#include +#include + +#include "fixx11h.h" + +#include +#include +#include + +class NETWM +{ + +public: + struct net_wm_state + { + net_wm_state(); + + unsigned int modal : 1; + unsigned int sticky : 1; + unsigned int maximized_vert : 1; + unsigned int maximized_horz : 1; + unsigned int shaded : 1; + unsigned int skip_taskbar : 1; + unsigned int skip_pager : 1; + unsigned int hidden : 1; + unsigned int fullscreen : 1; + unsigned int above : 1; + unsigned int below : 1; + unsigned int stays_on_top : 1; + unsigned int stays_on_bottom : 1; + unsigned int demands_attention : 1; + bool valid; + }; + + struct net_wm_window_type + { + net_wm_window_type(); + + unsigned int desktop : 1; + unsigned int dock : 1; + unsigned int toolbar : 1; + unsigned int menu : 1; + unsigned int utility : 1; + unsigned int splash : 1; + unsigned int dialog : 1; + unsigned int dropdown : 1; + unsigned int popup : 1; + unsigned int tooltip : 1; + unsigned int notification : 1; + unsigned int combo : 1; + unsigned int dnd : 1; + unsigned int normal : 1; + bool valid; + }; + + /*************************************************************************/ + + static void init(); + + static void transset(Window, double); + + static bool isComposite(); + + static int setProperty(Window, Atom, long, uchar *, int); + + static int setPropertySkipTaskbar(Window); + static int setPropertyOnTop(Window); + + static void* property(Window win, Atom prop, Atom type, int *nitems = 0, bool *ok = 0); + + static bool climsg(Window win, long type, long l0, long l1 = 0, long l2 = 0, long l3 = 0, long l4 = 0); + static bool climsgwm(Window win, Atom type, Atom arg); + + // NETWM helper functions + static qint64 netwmPid(Window win); + static QList netwmWindowList(); + static uint netwmDesktopsNumber(); + static uint netwmCurrentDesktop(); + static int netwmDesktop(Window win); + static net_wm_state netwmState(Window win); + static net_wm_window_type netwmWindowType(Window win); + static bool netwmActivateWindow(Window win); + + // ICCCM helper functions + static QString icccmString(Window win, Atom atom); + static QString icccmUtf8String(Window win, Atom atom); + static QString icccmWindowRole(Window win); + static QStringList icccmClass(Window win); + static QString icccmName(Window win); + static QStringList icccmCommand(Window win); + + /*************************************************************************/ + /********************************* Atoms *********************************/ + /*************************************************************************/ + + static Atom UTF8_STRING; + static Atom XROOTPMAP_ID; + + static Atom WM_STATE; + static Atom WM_CLASS; + static Atom WM_NAME; + static Atom WM_DELETE_WINDOW; + static Atom WM_PROTOCOLS; + static Atom WM_CHANGE_STATE; + static Atom WM_WINDOW_ROLE; + + static Atom NET_WORKAREA; + static Atom NET_CLIENT_LIST; + static Atom NET_CLIENT_LIST_STACKING; + static Atom NET_NUMBER_OF_DESKTOPS; + static Atom NET_CURRENT_DESKTOP; + static Atom NET_DESKTOP_NAMES; + static Atom NET_ACTIVE_WINDOW; + static Atom NET_CLOSE_WINDOW; + static Atom NET_SUPPORTED; + static Atom NET_WM_DESKTOP; + static Atom NET_SHOWING_DESKTOP; + + static Atom NET_WM_STATE; + static Atom NET_WM_STATE_MODAL; + static Atom NET_WM_STATE_STICKY; + static Atom NET_WM_STATE_MAXIMIZED_VERT; + static Atom NET_WM_STATE_MAXIMIZED_HORZ; + static Atom NET_WM_STATE_SHADED; + static Atom NET_WM_STATE_SKIP_TASKBAR; + static Atom NET_WM_STATE_SKIP_PAGER; + static Atom NET_WM_STATE_HIDDEN; + static Atom NET_WM_STATE_FULLSCREEN; + static Atom NET_WM_STATE_ABOVE; + static Atom NET_WM_STATE_BELOW; + static Atom NET_WM_STATE_STAYS_ON_TOP; + static Atom NET_WM_STATE_STAYS_ON_BOTTOM; + static Atom NET_WM_STATE_DEMANDS_ATTENTION; + + static Atom NET_WM_WINDOW_TYPE; + static Atom NET_WM_WINDOW_TYPE_DESKTOP; + static Atom NET_WM_WINDOW_TYPE_DOCK; + static Atom MODERRO_WINDOW_TYPE_DOCK; + static Atom NET_WM_WINDOW_TYPE_TOOLBAR; + static Atom NET_WM_WINDOW_TYPE_MENU; + static Atom NET_WM_WINDOW_TYPE_UTILITY; + static Atom NET_WM_WINDOW_TYPE_SPLASH; + static Atom NET_WM_WINDOW_TYPE_DIALOG; + static Atom NET_WM_WINDOW_TYPE_DROPDOWN_MENU; + static Atom NET_WM_WINDOW_TYPE_POPUP_MENU; + static Atom NET_WM_WINDOW_TYPE_TOOLTIP; + static Atom NET_WM_WINDOW_TYPE_NOTIFICATION; + static Atom NET_WM_WINDOW_TYPE_COMBO; + static Atom NET_WM_WINDOW_TYPE_DND; + static Atom NET_WM_WINDOW_TYPE_NORMAL; + static Atom NET_WM_WINDOW_OPACITY; + static Atom NET_WM_NAME; + static Atom NET_WM_VISIBLE_NAME; + static Atom NET_WM_STRUT; + static Atom NET_WM_STRUT_PARTIAL; + static Atom NET_WM_ICON; + static Atom NET_WM_PID; + +private: + static void checkInit(); +}; + +#endif From 4b5caaceef28a6dd91994a9dbadfac46dfeac9f5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 02:37:50 -0400 Subject: [PATCH 03/72] Fix compile on mingw32 --- src/libtomahawk/utils/tomahawkutils.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 4c1c342a1..be1013c44 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -561,12 +561,25 @@ setNam( QNetworkAccessManager* nam ) void bringToFront() { + qDebug() << Q_FUNC_INFO; + QWidgetList widgetList = qApp->topLevelWidgets(); + int i = 0; + while( !widgetList.at( i )->isWindow() ) + i++; + QWidget *widget = widgetList.at( i ); + + widget->show(); + widget->activateWindow(); + widget->raise(); + + WId wid = widget->winId(); + HWND hwndActiveWin = GetForegroundWindow(); int idActive = GetWindowThreadProcessId(hwndActiveWin, NULL); if ( AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) ) { - SetForegroundWindow( win ); - SetFocus( win ); + SetForegroundWindow( wid ); + SetFocus( wid ); AttachThreadInput(GetCurrentThreadId(), idActive, FALSE); } } From 87c86b6349fb18dd6554163657133763e9d42b59 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 02:47:17 -0400 Subject: [PATCH 04/72] Hells. Yes. Make TH window come to front when launched with a URL. (X11, not sure if Win works yet.) --- src/libtomahawk/utils/tomahawkutils.cpp | 16 ++++++++++++++-- src/tomahawkapp.cpp | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index be1013c44..6b698821e 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -530,8 +530,14 @@ setNam( QNetworkAccessManager* nam ) qDebug() << Q_FUNC_INFO; QWidgetList widgetList = qApp->topLevelWidgets(); int i = 0; - while( !widgetList.at( i )->isWindow() ) + while( i < widgetList.count() && widgetList.at( i )->objectName() != "TH_Main_Window" ) i++; + if ( i == widgetList.count() ) + { + qDebug() << Q_FUNC_INFO << " could not find main TH window"; + return; + } + QWidget *widget = widgetList.at( i ); widget->show(); @@ -564,8 +570,14 @@ setNam( QNetworkAccessManager* nam ) qDebug() << Q_FUNC_INFO; QWidgetList widgetList = qApp->topLevelWidgets(); int i = 0; - while( !widgetList.at( i )->isWindow() ) + while( i < widgetList.count() && widgetList.at( i )->objectName() != "TH_Main_Window" ) i++; + if ( i == widgetList.count() ) + { + qDebug() << Q_FUNC_INFO << " could not find main TH window"; + return; + } + QWidget *widget = widgetList.at( i ); widget->show(); diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index fad12a210..7624d4f94 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -222,6 +222,7 @@ TomahawkApp::init() tDebug() << "Init MainWindow."; m_mainwindow = new TomahawkWindow(); m_mainwindow->setWindowTitle( "Tomahawk" ); + m_mainwindow->setObjectName( "TH_Main_Window" ); m_mainwindow->show(); } #endif From 6b865eb4f29517dd22da9622e1926601497429f3 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 17:09:13 -0400 Subject: [PATCH 05/72] Add account skeleton --- src/libtomahawk/accounts/account.h | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/libtomahawk/accounts/account.h diff --git a/src/libtomahawk/accounts/account.h b/src/libtomahawk/accounts/account.h new file mode 100644 index 000000000..f1219d80c --- /dev/null +++ b/src/libtomahawk/accounts/account.h @@ -0,0 +1,74 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef ACCOUNT_H +#define ACCOUNT_H + +#include +#include +#include + +#include "typedefs.h" +#include "dllmacro.h" +#include "infosystem/infosystem.h" +#include "sip/SipPlugin.h" + +namespace Tomahawk +{ + +class DLLEXPORT Account +{ + + typedef QMap< QString, bool > ACLMap; + +public: + enum AccountTypes { InfoType, SipType }; + + explicit Account(); + virtual ~Account(); + + QString accountServiceName(); // e.g. "Twitter", "Last.fm" + void setAccountServiceName( const QString &serviceName ); + + QString accountFriendlyName(); // e.g. screen name on the service, JID, etc. + void setAccountFriendlyName( const QString &friendlyName ); + + bool autoConnect(); + void setAutoConnect( bool autoConnect ); + + QStringMap credentials(); + void setCredentials( const QStringMap &credentialMap ); + + QVariantMap configuration(); + void setConfiguration( const QVariantMap &configuration ); + QWidget* configurationWidget(); + + ACLMap acl(); + void setAcl( const ACLMap &acl ); + QWidget* aclWidget(); + + QSet< AccountTypes > types(); + void setTypes( const QSet< AccountTypes > types ); + + Tomahawk::InfoSystem::InfoPlugin* infoPlugin(); + SipPlugin* sipPlugin(); +}; + +}; + +#endif // ACCOUNT_H From 42508b7917d886449832450efb7a6c20665e4d58 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Aug 2011 20:44:44 +0200 Subject: [PATCH 06/72] initial work on a menu for drag and drop --- src/CMakeLists.txt | 1 + src/sourcetree/items/playlistitems.cpp | 6 + src/sourcetree/items/playlistitems.h | 2 + src/sourcetree/items/sourcetreeitem.cpp | 1 + src/sourcetree/items/sourcetreeitem.h | 16 ++ src/sourcetree/sourcedelegate.cpp | 314 ++++++++++++++++++++++++ src/sourcetree/sourcedelegate.h | 30 +++ src/sourcetree/sourcetreeview.cpp | 248 +++---------------- src/sourcetree/sourcetreeview.h | 2 + 9 files changed, 404 insertions(+), 216 deletions(-) create mode 100644 src/sourcetree/sourcedelegate.cpp create mode 100644 src/sourcetree/sourcedelegate.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4553c2b4..f6a413d21 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} sourcetree/sourcesmodel.cpp sourcetree/sourcesproxymodel.cpp sourcetree/sourcetreeview.cpp + sourcetree/sourcedelegate.cpp sourcetree/items/sourcetreeitem.cpp sourcetree/items/collectionitem.cpp sourcetree/items/playlistitems.cpp diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 02cc4e453..1e60efa9b 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -138,6 +138,12 @@ PlaylistItem::willAcceptDrag( const QMimeData* data ) const return !m_playlist.isNull() && m_playlist->author()->isLocal(); } +PlaylistItem::DropTypes +PlaylistItem::supportedDropTypes() const +{ + return DropTypeAllItems | DropTypeLocalItems | DropTypeTop10; +} + bool PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index 8febb2d78..62ddefc2d 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -34,6 +34,7 @@ public: virtual Qt::ItemFlags flags() const; virtual void activate(); virtual bool willAcceptDrag( const QMimeData* data ) const; + virtual DropTypes supportedDropTypes() const; virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action ); virtual QIcon icon() const; virtual bool setData(const QVariant& v, bool role); @@ -54,6 +55,7 @@ private: bool m_loaded; Tomahawk::playlist_ptr m_playlist; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::DropTypes) // can be a station or an auto playlist class DynamicPlaylistItem : public PlaylistItem diff --git a/src/sourcetree/items/sourcetreeitem.cpp b/src/sourcetree/items/sourcetreeitem.cpp index 5f82833c3..51e5ed14b 100644 --- a/src/sourcetree/items/sourcetreeitem.cpp +++ b/src/sourcetree/items/sourcetreeitem.cpp @@ -28,6 +28,7 @@ SourceTreeItem::SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, Sou , m_type( thisType ) , m_parent( parent ) , m_model( model ) + //, m_dropHovering( false ) { connect( this, SIGNAL( beginChildRowsAdded( int,int ) ), m_model, SLOT( onItemRowsAddedBegin( int,int ) ) ); connect( this, SIGNAL( beginChildRowsRemoved( int,int ) ), m_model, SLOT( onItemRowsRemovedBegin( int,int ) ) ); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index f8f57dc3f..4234c3e10 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -32,6 +32,16 @@ class SourceTreeItem : public QObject { Q_OBJECT public: + enum DropType + { + DropTypesNone = 0x00, + DropTypeAllItems = 0x01, + DropTypeLocalItems = 0x02, + DropTypeTop10 = 0x04, + DropTypesAllTypes = 0xff + }; + Q_DECLARE_FLAGS( DropTypes, DropType ) + SourceTreeItem() : m_type( SourcesModel::Invalid ), m_parent( 0 ), m_model( 0 ) {} SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int index = -1 ); // if index is -1, append at end of parent's child list virtual ~SourceTreeItem(); @@ -56,6 +66,10 @@ public: virtual bool setData( const QVariant&, bool ) { return false; } virtual int peerSortValue() const { return 0; } // How to sort relative to peers in the tree. virtual int IDValue() const { return 0; } +// virtual bool dropHovering() const { return m_dropHovering; } +// virtual void setDropHovering( bool dropHovering ) { m_dropHovering = dropHovering; emit updated(); } + virtual DropTypes supportedDropTypes() const { return DropTypesNone; } + virtual void setDropType( DropType type ) { m_dropType = type; } /// don't call me unless you are a sourcetreeitem. i prefer this to making everyone a friend void beginRowsAdded( int from, int to ) { emit beginChildRowsAdded( from, to ); } @@ -83,6 +97,8 @@ private: SourceTreeItem* m_parent; QList< SourceTreeItem* > m_children; SourcesModel* m_model; + + DropType m_dropType; }; Q_DECLARE_METATYPE( SourceTreeItem* ); diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp new file mode 100644 index 000000000..5b8bb25d2 --- /dev/null +++ b/src/sourcetree/sourcedelegate.cpp @@ -0,0 +1,314 @@ +#include "sourcedelegate.h" + +#include "items/sourcetreeitem.h" +#include "items/collectionitem.h" +#include "items/playlistitems.h" + +#include "utils/tomahawkutils.h" + +#include +#include + +#define TREEVIEW_INDENT_ADD -7 + +QSize +SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + SourceTreeItem *item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); + + if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::Collection ) + return QSize( option.rect.width(), 44 ); + else if ( index == m_dropHoverIndex ) + { + QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); + return originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); + } + else + return QStyledItemDelegate::sizeHint( option, index ); +} + + +void +SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + QStyleOptionViewItem o = option; + +#ifdef Q_WS_MAC + QFont savedFont = painter->font(); + QFont smaller = savedFont; + smaller.setPointSize( smaller.pointSize() - 2 ); + painter->setFont( smaller ); + o.font = smaller; +#endif + + if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) + { + o.state = QStyle::State_Enabled; + + if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) + { + o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) ); + } + } + + SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); + SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); + Q_ASSERT( item ); + + QStyleOptionViewItemV4 o3 = option; + if ( type != SourcesModel::Collection && type != SourcesModel::Category ) + o3.rect.setX( 0 ); + + QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter ); + + if ( type == SourcesModel::Collection ) + { + painter->save(); + + QFont normal = painter->font(); + QFont bold = painter->font(); + bold.setBold( true ); + + CollectionItem* colItem = qobject_cast< CollectionItem* >( item ); + Q_ASSERT( colItem ); + bool status = !( !colItem || colItem->source().isNull() || !colItem->source()->isOnline() ); + + QString tracks; + QString name = index.data().toString(); + int figWidth = 0; + + if ( status && colItem && !colItem->source().isNull() ) + { + tracks = QString::number( colItem->source()->trackCount() ); + figWidth = painter->fontMetrics().width( tracks ); + name = colItem->source()->friendlyName(); + } + + QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 ); + + QPixmap avatar = colItem->icon().pixmap( iconRect.size() ); + painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); + + if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) + { + painter->setPen( o.palette.color( QPalette::HighlightedText ) ); + } + + QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 24, 0 ); + if ( status || colItem->source().isNull() ) + painter->setFont( bold ); + QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); + painter->drawText( textRect, text ); + + QString desc = status ? colItem->source()->textStatus() : tr( "Offline" ); + if ( colItem->source().isNull() ) + desc = tr( "All available tracks" ); + if ( status && desc.isEmpty() && !colItem->source()->currentTrack().isNull() ) + desc = colItem->source()->currentTrack()->artist() + " - " + colItem->source()->currentTrack()->track(); + if ( desc.isEmpty() ) + desc = tr( "Online" ); + + textRect = option.rect.adjusted( iconRect.width() + 8, painter->fontMetrics().height() + 6, -figWidth - 24, -4 ); + painter->setFont( normal ); + text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); + QTextOption to( Qt::AlignBottom ); + painter->drawText( textRect, text, to ); + + if ( status ) + { + painter->setRenderHint( QPainter::Antialiasing ); + + QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 8, 0, -13, -o.rect.height() + 16 ); + int hd = ( option.rect.height() - figRect.height() ) / 2; + figRect.adjust( 0, hd, 0, hd ); +#ifdef Q_OS_WIN + figRect.adjust( -3, 0, 3, 0 ); +#endif + painter->setFont( bold ); + + QColor figColor( 167, 183, 211 ); + painter->setPen( figColor ); + painter->setBrush( figColor ); + + TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect ); + } + + painter->restore(); + } + else if ( type == SourcesModel::StaticPlaylist ) + { + painter->save(); + + QFont normal = painter->font(); + QFont bold = painter->font(); + bold.setBold( true ); + + PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); + Q_ASSERT( plItem ); + +// QString tracks; + QString name = index.data().toString(); +// int figWidth = 0; + + if ( plItem && !plItem->playlist().isNull() ) + { +// tracks = QString::number( plItem->source()->trackCount() ); +// figWidth = painter->fontMetrics().width( tracks ); + name = plItem->playlist()->title(); + } + + int height = option.rect.height(); + if ( index == m_dropHoverIndex ) + height /= ( dropTypeCount( item ) + 1 ); + + QRect iconRect = option.rect.adjusted( 4, 1, -option.rect.width() + height - 2 + 4, -option.rect.height() + height -1 ); + + QPixmap avatar = index.data( Qt::DecorationRole ).value< QIcon >().pixmap( iconRect.width(), iconRect.height() ); + painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); + + if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) + { + painter->setPen( o.palette.color( QPalette::HighlightedText ) ); + } + + QRect textRect = option.rect.adjusted( iconRect.width() + 8, 2, /*-figWidth - 24*/ 0, 0 ); + QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); + painter->drawText( textRect, text ); + + if ( index == m_dropHoverIndex ) + { + QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); + qDebug() << "cursorpos is" << cursorPos; + + int hoveredDropTypeIndex = ( cursorPos.y() - o.rect.y() ) / height; + int verticalOffset = height * hoveredDropTypeIndex; + QRect selectionRect = o.rect.adjusted( 0, verticalOffset, 0, -o.rect.height() + height + verticalOffset ); + painter->drawRoundedRect( selectionRect, 5, 5 ); + + int count = 1; + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeAllItems ) ) + { + text = "All items"; + textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); + painter->drawText( textRect, text ); + if ( count == hoveredDropTypeIndex ) + m_hoveredDropType = SourceTreeItem::DropTypeAllItems; + count++; + } + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeLocalItems ) ) + { + text = "Local items"; + textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); + painter->drawText( textRect, text ); + if ( count == hoveredDropTypeIndex ) + m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; + count++; + } + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeTop10 ) ) + { + text = "Top 10"; + textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); + painter->drawText( textRect, text ); + if ( count == hoveredDropTypeIndex ) + m_hoveredDropType = SourceTreeItem::DropTypeTop10; + count++; + } + + qDebug() << "***************+ verticaloffset:" << hoveredDropTypeIndex << count; + } + +// QString desc = status ? colItem->source()->textStatus() : tr( "Offline" ); +// if ( colItem->source().isNull() ) +// desc = tr( "All available tracks" ); +// if ( status && desc.isEmpty() && !colItem->source()->currentTrack().isNull() ) +// desc = colItem->source()->currentTrack()->artist() + " - " + colItem->source()->currentTrack()->track(); +// if ( desc.isEmpty() ) +// desc = tr( "Online" ); + +// textRect = option.rect.adjusted( iconRect.width() + 8, painter->fontMetrics().height() + 6, -figWidth - 24, -4 ); +// painter->setFont( normal ); +// text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); +// QTextOption to( Qt::AlignBottom ); +// painter->drawText( textRect, text, to ); + +// if ( status ) +// { +// painter->setRenderHint( QPainter::Antialiasing ); + +// QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 8, 0, -13, -o.rect.height() + 16 ); +// int hd = ( option.rect.height() - figRect.height() ) / 2; +// figRect.adjust( 0, hd, 0, hd ); +//#ifdef Q_OS_WIN +// figRect.adjust( -3, 0, 3, 0 ); +//#endif +// painter->setFont( bold ); + +// QColor figColor( 167, 183, 211 ); +// painter->setPen( figColor ); +// painter->setBrush( figColor ); + +// TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect ); +// } + + painter->restore(); + + } + else + { + QStyledItemDelegate::paint( painter, o, index ); + /*QStyleOptionViewItemV4 opt = o; + initStyleOption( &opt, index ); + + // shrink the indentations. count how indented this item is and remove it + int indentMult = 0; + QModelIndex counter = index; + while ( counter.parent().isValid() ) + { + indentMult++; + counter = counter.parent(); + } + int realX = opt.rect.x() + indentMult * TREEVIEW_INDENT_ADD; + + opt.rect.setX( realX ); + const QWidget *widget = opt.widget; + QStyle *style = widget ? widget->style() : QApplication::style(); + style->drawControl( QStyle::CE_ItemViewItem, &opt, painter, widget ); */ + } + +#ifdef Q_WS_MAC + painter->setFont( savedFont ); +#endif +} + +void +SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const +{ + if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist ) + editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) ); + else + QStyledItemDelegate::updateEditorGeometry( editor, option, index ); + + editor->setGeometry( editor->geometry().adjusted( 2*TREEVIEW_INDENT_ADD, 0, 0, 0 ) ); +} + +int +SourceDelegate::dropTypeCount( SourceTreeItem* item ) const +{ + int menuCount = 0; + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeAllItems ) ) + menuCount++; + + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeLocalItems ) ) + menuCount++; + + if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeTop10 ) ) + menuCount++; + + return menuCount; +} + +SourceTreeItem::DropType +SourceDelegate::hoveredDropType() const +{ + return m_hoveredDropType; +} diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h new file mode 100644 index 000000000..a05f31b6f --- /dev/null +++ b/src/sourcetree/sourcedelegate.h @@ -0,0 +1,30 @@ +#ifndef SOURCEDELEGATE_H +#define SOURCEDELEGATE_H + +#include "sourcetreeview.h" +#include "items/sourcetreeitem.h" + +#include + +class SourceDelegate : public QStyledItemDelegate +{ +public: + SourceDelegate( QAbstractItemView* parent = 0 ) : QStyledItemDelegate( parent ), m_parent( parent ) {} + + void setDropHoverIndex( const QModelIndex &index ) { m_dropHoverIndex = index; } + + SourceTreeItem::DropType hoveredDropType() const; + +protected: + virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const; + virtual int dropTypeCount( SourceTreeItem* item ) const; + +private: + QAbstractItemView* m_parent; + QModelIndex m_dropHoverIndex; + mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() +}; + +#endif // SOURCEDELEGATE_H diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 4297caffe..eaa68af99 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -32,6 +32,7 @@ #include "viewmanager.h" #include "sourcesproxymodel.h" #include "sourcelist.h" +#include "sourcedelegate.h" #include "sourcetree/items/playlistitems.h" #include "sourcetree/items/collectionitem.h" #include "audio/audioengine.h" @@ -46,33 +47,6 @@ using namespace Tomahawk; -#define TREEVIEW_INDENT_ADD -7 - - -class SourceDelegate : public QStyledItemDelegate -{ -public: - SourceDelegate( QAbstractItemView* parent = 0 ) : QStyledItemDelegate( parent ), m_parent( parent ) {} - -protected: - virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; - virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; - virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const - { - if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist ) - editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) ); - else - QStyledItemDelegate::updateEditorGeometry( editor, option, index ); - - editor->setGeometry( editor->geometry().adjusted( 2*TREEVIEW_INDENT_ADD, 0, 0, 0 ) ); - } - virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ); - -private: - QAbstractItemView* m_parent; - mutable int m_iconHeight; -}; - SourceTreeView::SourceTreeView( QWidget* parent ) : QTreeView( parent ) @@ -105,7 +79,8 @@ SourceTreeView::SourceTreeView( QWidget* parent ) // so investigate // setAnimated( true ); - setItemDelegate( new SourceDelegate( this ) ); + m_delegate = new SourceDelegate( this ); + setItemDelegate( m_delegate ); setContextMenuPolicy( Qt::CustomContextMenu ); connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) ); @@ -464,6 +439,13 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); +// SourceTreeItem* oldItem = m_dropIndex.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); +// if ( oldItem ) +// { +// oldItem->setDropHovering( false ); +// } + m_delegate->setDropHoverIndex( QModelIndex() ); + dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex(); } @@ -479,6 +461,13 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); +// SourceTreeItem* oldItem = m_dropIndex.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); +// if ( oldItem ) +// { +// oldItem->setDropHovering( false ); +// } + m_delegate->setDropHoverIndex( QModelIndex() ); + dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex( index ); if ( index.isValid() ) @@ -486,9 +475,14 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) const QRect rect = visualRect( index ); m_dropRect = rect; - const SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index ); + SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index ); if( item->willAcceptDrag( event->mimeData() ) ) + { accept = true; +// item->setDropHovering( true ); + m_delegate->setDropHoverIndex( index ); + dataChanged(index, index); + } } else { @@ -511,6 +505,15 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) void SourceTreeView::dropEvent( QDropEvent* event ) { + const QPoint pos = event->pos(); + const QModelIndex index = indexAt( pos ); + PlaylistItem* item = itemFromIndex< PlaylistItem >( index ); + Q_ASSERT( item ); + + item->setDropType( m_delegate->hoveredDropType() ); + qDebug() << "dropType is " << m_delegate->hoveredDropType(); + m_delegate->setDropHoverIndex( QModelIndex() ); + dataChanged( index, index ); QTreeView::dropEvent( event ); m_dragging = false; m_dropIndex = QPersistentModelIndex(); @@ -576,190 +579,3 @@ SourceTreeView::itemFromIndex( const QModelIndex& index ) const return item; } - -QSize -SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const -{ - if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::Collection ) - return QSize( option.rect.width(), 44 ); - else - return QStyledItemDelegate::sizeHint( option, index ); -} - - -void -SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const -{ - QStyleOptionViewItem o = option; - -#ifdef Q_WS_MAC - QFont savedFont = painter->font(); - QFont smaller = savedFont; - smaller.setPointSize( smaller.pointSize() - 2 ); - painter->setFont( smaller ); - o.font = smaller; -#endif - - if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled ) - { - o.state = QStyle::State_Enabled; - - if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) - { - o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) ); - } - } - - SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); - SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); - Q_ASSERT( item ); - - QStyleOptionViewItemV4 o3 = option; - if ( type != SourcesModel::Collection && type != SourcesModel::Category ) - o3.rect.setX( 0 ); - - QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter ); - - if ( type == SourcesModel::Collection ) - { - painter->save(); - - QFont normal = painter->font(); - QFont bold = painter->font(); - bold.setBold( true ); - - CollectionItem* colItem = qobject_cast< CollectionItem* >( item ); - Q_ASSERT( colItem ); - bool status = !( !colItem || colItem->source().isNull() || !colItem->source()->isOnline() ); - - QString tracks; - QString name = index.data().toString(); - int figWidth = 0; - - if ( status && colItem && !colItem->source().isNull() ) - { - tracks = QString::number( colItem->source()->trackCount() ); - figWidth = painter->fontMetrics().width( tracks ); - name = colItem->source()->friendlyName(); - } - - QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 ); - - QPixmap avatar = colItem->icon().pixmap( iconRect.size() ); - painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); - - if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) - { - painter->setPen( o.palette.color( QPalette::HighlightedText ) ); - } - - QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 24, 0 ); - if ( status || colItem->source().isNull() ) - painter->setFont( bold ); - QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); - painter->drawText( textRect, text ); - - QString desc = status ? colItem->source()->textStatus() : tr( "Offline" ); - if ( colItem->source().isNull() ) - desc = tr( "All available tracks" ); - if ( status && desc.isEmpty() && !colItem->source()->currentTrack().isNull() ) - desc = colItem->source()->currentTrack()->artist() + " - " + colItem->source()->currentTrack()->track(); - if ( desc.isEmpty() ) - desc = tr( "Online" ); - - textRect = option.rect.adjusted( iconRect.width() + 8, painter->fontMetrics().height() + 6, -figWidth - 24, -4 ); - painter->setFont( normal ); - text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); - QTextOption to( Qt::AlignBottom ); - painter->drawText( textRect, text, to ); - - if ( status ) - { - painter->setRenderHint( QPainter::Antialiasing ); - - QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 8, 0, -13, -o.rect.height() + 16 ); - int hd = ( option.rect.height() - figRect.height() ) / 2; - figRect.adjust( 0, hd, 0, hd ); -#ifdef Q_OS_WIN - figRect.adjust( -3, 0, 3, 0 ); -#endif - painter->setFont( bold ); - - QColor figColor( 167, 183, 211 ); - painter->setPen( figColor ); - painter->setBrush( figColor ); - - TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect ); - } - - painter->restore(); - } - else - { - QStyledItemDelegate::paint( painter, o, index ); - - if ( type == SourcesModel::TemporaryPage ) - { - TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( item ); - Q_ASSERT( gpi ); - - if ( gpi && o3.state & QStyle::State_MouseOver ) - { - // draw close icon - int padding = 3; - m_iconHeight = ( o3.rect.height() - 2*padding ); - QPixmap p( RESPATH "images/list-remove.png" ); - p = p.scaledToHeight( m_iconHeight, Qt::SmoothTransformation ); - - QRect r ( o3.rect.right() - padding - m_iconHeight, padding + o3.rect.y(), m_iconHeight, m_iconHeight ); - painter->drawPixmap( r, p ); - } - } - /*QStyleOptionViewItemV4 opt = o; - - // shrink the indentations. count how indented this item is and remove it - int indentMult = 0; - QModelIndex counter = index; - while ( counter.parent().isValid() ) - { - indentMult++; - counter = counter.parent(); - } - int realX = opt.rect.x() + indentMult * TREEVIEW_INDENT_ADD; - - opt.rect.setX( realX ); - const QWidget *widget = opt.widget; - QStyle *style = widget ? widget->style() : QApplication::style(); - style->drawControl( QStyle::CE_ItemViewItem, &opt, painter, widget ); */ - } - -#ifdef Q_WS_MAC - painter->setFont( savedFont ); -#endif -} - -bool -SourceDelegate::editorEvent ( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ) -{ - - if ( event->type() == QEvent::MouseButtonRelease ) - { - SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); - if ( type == SourcesModel::TemporaryPage ) - { - TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() ); - Q_ASSERT( gpi ); - QMouseEvent* ev = static_cast< QMouseEvent* >( event ); - - QStyleOptionViewItemV4 o = option; - initStyleOption( &o, index ); - int padding = 3; - QRect r ( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight ); - - if ( r.contains( ev->pos() ) ) - gpi->removeFromList(); - } - } - - return QStyledItemDelegate::editorEvent ( event, model, option, index ); -} diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 2eca36fa2..8bba33076 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -29,6 +29,7 @@ class CollectionModel; class PlaylistModel; class SourcesModel; class SourcesProxyModel; +class SourceDelegate; class SourceTreeView : public QTreeView { @@ -81,6 +82,7 @@ private: SourcesModel* m_model; SourcesProxyModel* m_proxyModel; QModelIndex m_contextMenuIndex; + SourceDelegate* m_delegate; QMenu m_playlistMenu; QMenu m_roPlaylistMenu; From a14db5e7f2297baf9df088df07ca55ac61bcc67b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Aug 2011 21:30:55 +0200 Subject: [PATCH 07/72] readded the editorEvent which somehow got lost in the previous merge and cleaned up a bit --- src/sourcetree/items/sourcetreeitem.cpp | 1 - src/sourcetree/items/sourcetreeitem.h | 2 -- src/sourcetree/sourcedelegate.cpp | 28 +++++++++++++++++++++++++ src/sourcetree/sourcedelegate.h | 2 ++ src/sourcetree/sourcetreeview.cpp | 11 ---------- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/sourcetree/items/sourcetreeitem.cpp b/src/sourcetree/items/sourcetreeitem.cpp index 51e5ed14b..5f82833c3 100644 --- a/src/sourcetree/items/sourcetreeitem.cpp +++ b/src/sourcetree/items/sourcetreeitem.cpp @@ -28,7 +28,6 @@ SourceTreeItem::SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, Sou , m_type( thisType ) , m_parent( parent ) , m_model( model ) - //, m_dropHovering( false ) { connect( this, SIGNAL( beginChildRowsAdded( int,int ) ), m_model, SLOT( onItemRowsAddedBegin( int,int ) ) ); connect( this, SIGNAL( beginChildRowsRemoved( int,int ) ), m_model, SLOT( onItemRowsRemovedBegin( int,int ) ) ); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index 4234c3e10..4c5ea93f8 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -66,8 +66,6 @@ public: virtual bool setData( const QVariant&, bool ) { return false; } virtual int peerSortValue() const { return 0; } // How to sort relative to peers in the tree. virtual int IDValue() const { return 0; } -// virtual bool dropHovering() const { return m_dropHovering; } -// virtual void setDropHovering( bool dropHovering ) { m_dropHovering = dropHovering; emit updated(); } virtual DropTypes supportedDropTypes() const { return DropTypesNone; } virtual void setDropType( DropType type ) { m_dropType = type; } diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 5b8bb25d2..41381db90 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -5,9 +5,11 @@ #include "items/playlistitems.h" #include "utils/tomahawkutils.h" +#include "items/temporarypageitem.h" #include #include +#include #define TREEVIEW_INDENT_ADD -7 @@ -291,6 +293,32 @@ SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewIte editor->setGeometry( editor->geometry().adjusted( 2*TREEVIEW_INDENT_ADD, 0, 0, 0 ) ); } +bool +SourceDelegate::editorEvent ( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ) +{ + + if ( event->type() == QEvent::MouseButtonRelease ) + { + SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); + if ( type == SourcesModel::TemporaryPage ) + { + TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() ); + Q_ASSERT( gpi ); + QMouseEvent* ev = static_cast< QMouseEvent* >( event ); + + QStyleOptionViewItemV4 o = option; + initStyleOption( &o, index ); + int padding = 3; + QRect r ( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight ); + + if ( r.contains( ev->pos() ) ) + gpi->removeFromList(); + } + } + + return QStyledItemDelegate::editorEvent ( event, model, option, index ); +} + int SourceDelegate::dropTypeCount( SourceTreeItem* item ) const { diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index a05f31b6f..f0e7ad18a 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -20,9 +20,11 @@ protected: virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; virtual void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const; virtual int dropTypeCount( SourceTreeItem* item ) const; + virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ); private: QAbstractItemView* m_parent; + mutable int m_iconHeight; QModelIndex m_dropHoverIndex; mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() }; diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index eaa68af99..feaf44efb 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -439,11 +439,6 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); -// SourceTreeItem* oldItem = m_dropIndex.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); -// if ( oldItem ) -// { -// oldItem->setDropHovering( false ); -// } m_delegate->setDropHoverIndex( QModelIndex() ); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex(); @@ -461,11 +456,6 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); -// SourceTreeItem* oldItem = m_dropIndex.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); -// if ( oldItem ) -// { -// oldItem->setDropHovering( false ); -// } m_delegate->setDropHoverIndex( QModelIndex() ); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex( index ); @@ -479,7 +469,6 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) if( item->willAcceptDrag( event->mimeData() ) ) { accept = true; -// item->setDropHovering( true ); m_delegate->setDropHoverIndex( index ); dataChanged(index, index); } From eea5fa2826e99d6f3550ca3fe172f53fd90a7e13 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Aug 2011 21:48:15 +0200 Subject: [PATCH 08/72] cleanup --- src/sourcetree/sourcedelegate.cpp | 47 ++----------------------------- 1 file changed, 3 insertions(+), 44 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 41381db90..b128b5f38 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -148,14 +148,10 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); Q_ASSERT( plItem ); -// QString tracks; QString name = index.data().toString(); -// int figWidth = 0; if ( plItem && !plItem->playlist().isNull() ) { -// tracks = QString::number( plItem->source()->trackCount() ); -// figWidth = painter->fontMetrics().width( tracks ); name = plItem->playlist()->title(); } @@ -180,8 +176,6 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co if ( index == m_dropHoverIndex ) { QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); - qDebug() << "cursorpos is" << cursorPos; - int hoveredDropTypeIndex = ( cursorPos.y() - o.rect.y() ) / height; int verticalOffset = height * hoveredDropTypeIndex; QRect selectionRect = o.rect.adjusted( 0, verticalOffset, 0, -o.rect.height() + height + verticalOffset ); @@ -190,7 +184,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co int count = 1; if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeAllItems ) ) { - text = "All items"; + text = tr( "All items" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) @@ -199,7 +193,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeLocalItems ) ) { - text = "Local items"; + text = tr( "Local items" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) @@ -208,50 +202,15 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeTop10 ) ) { - text = "Top 10"; + text = tr( "Top 10" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) m_hoveredDropType = SourceTreeItem::DropTypeTop10; count++; } - - qDebug() << "***************+ verticaloffset:" << hoveredDropTypeIndex << count; } -// QString desc = status ? colItem->source()->textStatus() : tr( "Offline" ); -// if ( colItem->source().isNull() ) -// desc = tr( "All available tracks" ); -// if ( status && desc.isEmpty() && !colItem->source()->currentTrack().isNull() ) -// desc = colItem->source()->currentTrack()->artist() + " - " + colItem->source()->currentTrack()->track(); -// if ( desc.isEmpty() ) -// desc = tr( "Online" ); - -// textRect = option.rect.adjusted( iconRect.width() + 8, painter->fontMetrics().height() + 6, -figWidth - 24, -4 ); -// painter->setFont( normal ); -// text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); -// QTextOption to( Qt::AlignBottom ); -// painter->drawText( textRect, text, to ); - -// if ( status ) -// { -// painter->setRenderHint( QPainter::Antialiasing ); - -// QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 8, 0, -13, -o.rect.height() + 16 ); -// int hd = ( option.rect.height() - figRect.height() ) / 2; -// figRect.adjust( 0, hd, 0, hd ); -//#ifdef Q_OS_WIN -// figRect.adjust( -3, 0, 3, 0 ); -//#endif -// painter->setFont( bold ); - -// QColor figColor( 167, 183, 211 ); -// painter->setPen( figColor ); -// painter->setBrush( figColor ); - -// TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect ); -// } - painter->restore(); } From 6aa75316232ef9317361ef701c8dcca041f499c7 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 20 Aug 2011 22:31:37 +0200 Subject: [PATCH 09/72] dropping "All items" and "Only local items" works now --- src/libtomahawk/dropjob.cpp | 22 +++++++++++++++++++++- src/libtomahawk/dropjob.h | 4 +++- src/sourcetree/items/playlistitems.cpp | 6 +++++- src/sourcetree/items/sourcetreeitem.h | 1 + src/sourcetree/sourcetreeview.cpp | 4 ++-- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 3de1ea050..8fff1080d 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -96,9 +96,10 @@ DropJob::acceptsMimeData( const QMimeData* data, bool tracksOnly ) void -DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates ) +DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates, bool onlyLocal ) { m_allowDuplicates = allowDuplicates; + m_onlyLocal = onlyLocal; parseMimeData( data ); @@ -107,6 +108,9 @@ DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates ) if ( !allowDuplicates ) removeDuplicates(); + if ( onlyLocal ) + removeRemoteSources(); + emit tracks( m_resultList ); deleteLater(); } @@ -339,6 +343,9 @@ DropJob::onTracksAdded( const QList& tracksList ) if ( !m_allowDuplicates ) removeDuplicates(); + if ( m_onlyLocal ) + removeRemoteSources(); + emit tracks( m_resultList ); deleteLater(); } @@ -361,3 +368,16 @@ DropJob::removeDuplicates() } m_resultList = list; } + +void +DropJob::removeRemoteSources() +{ + QList< Tomahawk::query_ptr > list; + foreach ( const Tomahawk::query_ptr& item, m_resultList ) + { + if ( !item->results().isEmpty() && item->results().first()->collection()->source() ) + if ( item->results().first()->collection()->source()->isLocal() ) + list.append( item ); + } + m_resultList = list; +} diff --git a/src/libtomahawk/dropjob.h b/src/libtomahawk/dropjob.h index 186417f9b..aab952cb0 100644 --- a/src/libtomahawk/dropjob.h +++ b/src/libtomahawk/dropjob.h @@ -43,7 +43,7 @@ public: */ static bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); static QStringList mimeTypes(); - void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false ); + void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false ); signals: /// QMimeData parsing results @@ -66,9 +66,11 @@ private: QList< Tomahawk::query_ptr > tracksFromMixedData( const QMimeData* d ); void removeDuplicates(); + void removeRemoteSources(); int m_queryCount; bool m_allowDuplicates; + bool m_onlyLocal; QList< Tomahawk::query_ptr > m_resultList; }; diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 1e60efa9b..b3a55ef92 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -158,7 +158,11 @@ PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) DropJob *dj = new DropJob(); connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); - dj->tracksFromMimeData( data ); + + if ( dropType() == DropTypeLocalItems ) + dj->tracksFromMimeData( data, false, true ); + else + dj->tracksFromMimeData( data, false, false ); // TODO cant' know if it works or not yet... return true; diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index 4c5ea93f8..2c174f220 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -68,6 +68,7 @@ public: virtual int IDValue() const { return 0; } virtual DropTypes supportedDropTypes() const { return DropTypesNone; } virtual void setDropType( DropType type ) { m_dropType = type; } + virtual DropType dropType() const { return m_dropType; } /// don't call me unless you are a sourcetreeitem. i prefer this to making everyone a friend void beginRowsAdded( int from, int to ) { emit beginChildRowsAdded( from, to ); } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index feaf44efb..fd71166c8 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -501,11 +501,11 @@ SourceTreeView::dropEvent( QDropEvent* event ) item->setDropType( m_delegate->hoveredDropType() ); qDebug() << "dropType is " << m_delegate->hoveredDropType(); - m_delegate->setDropHoverIndex( QModelIndex() ); - dataChanged( index, index ); QTreeView::dropEvent( event ); m_dragging = false; m_dropIndex = QPersistentModelIndex(); + m_delegate->setDropHoverIndex( QModelIndex() ); + dataChanged( index, index ); } From 34c8c79ef5490f0b8bd4bed4b835ab5392566f81 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 21 Aug 2011 00:19:01 +0200 Subject: [PATCH 10/72] fixed order of filtering. initial work on the Top10 drop action (does actually Top50 for now and tracks can not be resolved yet) --- src/libtomahawk/dropjob.cpp | 86 +++++++++++++++++++++----- src/libtomahawk/dropjob.h | 7 ++- src/sourcetree/items/playlistitems.cpp | 3 + 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 8fff1080d..6f1a79d2e 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -27,6 +27,7 @@ #include "utils/shortenedlinkparser.h" #include "utils/logger.h" #include "globalactionmanager.h" +#include "infosystem/infosystem.h" using namespace Tomahawk; @@ -96,21 +97,22 @@ DropJob::acceptsMimeData( const QMimeData* data, bool tracksOnly ) void -DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates, bool onlyLocal ) +DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates, bool onlyLocal, bool top10 ) { m_allowDuplicates = allowDuplicates; m_onlyLocal = onlyLocal; + m_top10 = top10; parseMimeData( data ); if ( m_queryCount == 0 ) { - if ( !allowDuplicates ) - removeDuplicates(); - if ( onlyLocal ) removeRemoteSources(); + if ( !allowDuplicates ) + removeDuplicates(); + emit tracks( m_resultList ); deleteLater(); } @@ -228,15 +230,38 @@ DropJob::tracksFromArtistMetaData( const QMimeData *data ) QString artist; stream >> artist; - artist_ptr artistPtr = Artist::get( artist ); - if ( artistPtr->tracks().isEmpty() ) + if ( !m_top10 ) { - connect( artistPtr.data(), SIGNAL( tracksAdded( QList ) ), - SLOT( onTracksAdded( QList ) ) ); - m_queryCount++; + artist_ptr artistPtr = Artist::get( artist ); + if ( artistPtr->tracks().isEmpty() ) + { + connect( artistPtr.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + m_queryCount++; + } + else + queries << artistPtr->tracks(); } else - queries << artistPtr->tracks(); + { + connect( Tomahawk::InfoSystem::InfoSystem::instance(), + SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), + SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); + + Tomahawk::InfoSystem::InfoCriteriaHash artistInfo; + artistInfo["artist"] = artist; + + Tomahawk::InfoSystem::InfoRequestData requestData; + requestData.caller = "changeme"; + requestData.customData = QVariantMap(); + + requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoCriteriaHash >( artistInfo ); + + requestData.type = Tomahawk::InfoSystem::InfoArtistSongs; + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); + + m_queryCount++; + } } return queries; } @@ -340,12 +365,12 @@ DropJob::onTracksAdded( const QList& tracksList ) if ( --m_queryCount == 0 ) { - if ( !m_allowDuplicates ) - removeDuplicates(); - if ( m_onlyLocal ) removeRemoteSources(); + if ( !m_allowDuplicates ) + removeDuplicates(); + emit tracks( m_resultList ); deleteLater(); } @@ -375,9 +400,38 @@ DropJob::removeRemoteSources() QList< Tomahawk::query_ptr > list; foreach ( const Tomahawk::query_ptr& item, m_resultList ) { - if ( !item->results().isEmpty() && item->results().first()->collection()->source() ) - if ( item->results().first()->collection()->source()->isLocal() ) - list.append( item ); + bool hasLocalSource = false; + foreach ( const Tomahawk::result_ptr& result, item->results() ) + { + if ( result->collection()->source() && result->collection()->source()->isLocal() ) + hasLocalSource = true; + } + if ( hasLocalSource ) + list.append( item ); } m_resultList = list; } + +void +DropJob::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ) +{ + if ( requestData.caller == "changeme" ) + { + Tomahawk::InfoSystem::InfoCriteriaHash artistInfo; + + artistInfo = requestData.input.value< Tomahawk::InfoSystem::InfoCriteriaHash >(); + + QString artist = artistInfo["artist"]; + + qDebug() << "Got requestData response for artist" << artist << output; + + QList< query_ptr > results; + foreach ( const QVariant& title, output.toMap().value( "tracks" ).toList() ) + { + qDebug() << "got title" << title; + results << Query::get( artist, title.toString(), QString() ); + } + + onTracksAdded( results ); + } +} diff --git a/src/libtomahawk/dropjob.h b/src/libtomahawk/dropjob.h index aab952cb0..ca542ee7b 100644 --- a/src/libtomahawk/dropjob.h +++ b/src/libtomahawk/dropjob.h @@ -22,6 +22,8 @@ #include "query.h" +#include "infosystem/infosystem.h" + #include #include #include @@ -43,7 +45,7 @@ public: */ static bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); static QStringList mimeTypes(); - void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false ); + void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false, bool top10 = false ); signals: /// QMimeData parsing results @@ -54,6 +56,8 @@ private slots: void onTracksAdded( const QList& ); + void infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ); + private: /// handle parsing mime data void parseMimeData( const QMimeData* data ); @@ -71,6 +75,7 @@ private: int m_queryCount; bool m_allowDuplicates; bool m_onlyLocal; + bool m_top10; QList< Tomahawk::query_ptr > m_resultList; }; diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index b3a55ef92..5899405f9 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -161,6 +161,8 @@ PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) if ( dropType() == DropTypeLocalItems ) dj->tracksFromMimeData( data, false, true ); + else if ( dropType() == DropTypeTop10 ) + dj->tracksFromMimeData( data, false, false, true ); else dj->tracksFromMimeData( data, false, false ); @@ -171,6 +173,7 @@ PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) void PlaylistItem::parsedDroppedTracks( const QList< query_ptr >& tracks) { + qDebug() << "adding" << tracks.count() << "tracks"; if ( tracks.count() && !m_playlist.isNull() && m_playlist->author()->isLocal() ) { qDebug() << "on playlist:" << m_playlist->title() << m_playlist->guid() << m_playlist->currentrevision(); From bb72c3efb2bc0bb1d972a886e5c7f11faf78199c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 21 Aug 2011 00:54:24 +0200 Subject: [PATCH 11/72] correctly check for isNull --- src/libtomahawk/dropjob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 6f1a79d2e..312c9d438 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -403,7 +403,7 @@ DropJob::removeRemoteSources() bool hasLocalSource = false; foreach ( const Tomahawk::result_ptr& result, item->results() ) { - if ( result->collection()->source() && result->collection()->source()->isLocal() ) + if ( !result->collection()->source().isNull() && result->collection()->source()->isLocal() ) hasLocalSource = true; } if ( hasLocalSource ) From 13db62b71cb9c85e72efa83ab56bb729b6fc57a4 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 21 Aug 2011 01:10:25 +0200 Subject: [PATCH 12/72] * Fixed always resolving top songs in DropJob. --- src/libtomahawk/dropjob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 312c9d438..313940ab7 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -429,7 +429,7 @@ DropJob::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVar foreach ( const QVariant& title, output.toMap().value( "tracks" ).toList() ) { qDebug() << "got title" << title; - results << Query::get( artist, title.toString(), QString() ); + results << Query::get( artist, title.toString(), QString(), uuid() ); } onTracksAdded( results ); From a227ae4bcb717b44a9154904361ebdbc3bb94a17 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 21 Aug 2011 04:09:19 +0200 Subject: [PATCH 13/72] more work on the drag and drop menu --- src/libtomahawk/dropjob.cpp | 95 ++++++++++++++++++++------ src/libtomahawk/dropjob.h | 8 +++ src/libtomahawk/playlist/treemodel.cpp | 90 ++++++++++++++++++++++++ src/sourcetree/items/categoryitems.cpp | 6 ++ src/sourcetree/items/categoryitems.h | 1 + src/sourcetree/items/playlistitems.cpp | 33 ++++++++- src/sourcetree/items/playlistitems.h | 2 +- src/sourcetree/items/sourcetreeitem.h | 14 ++-- src/sourcetree/sourcedelegate.cpp | 74 ++++++++++++++------ src/sourcetree/sourcedelegate.h | 3 +- src/sourcetree/sourcetreeview.cpp | 8 +-- 11 files changed, 279 insertions(+), 55 deletions(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 313940ab7..95af9f038 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -95,6 +95,18 @@ DropJob::acceptsMimeData( const QMimeData* data, bool tracksOnly ) return false; } +void +DropJob::setGetWholeArtists( bool getWholeArtists ) +{ + m_getWholeArtists = getWholeArtists; +} + +void +DropJob::setGetWholeAlbums( bool getWholeAlbums ) +{ + m_getWholeAlbums = getWholeAlbums; +} + void DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates, bool onlyLocal, bool top10 ) @@ -158,7 +170,19 @@ DropJob::tracksFromQueryList( const QMimeData* data ) if ( query && !query->isNull() ) { tDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); - queries << *query; + + if ( m_getWholeArtists ) + { + queries << getArtist( query->data()->artist() ); + } + else if ( m_getWholeAlbums ) + { + queries << getAlbum( query->data()->artist(), query->data()->album() ); + } + else + { + queries << *query; + } } } @@ -182,8 +206,20 @@ DropJob::tracksFromResultList( const QMimeData* data ) { tDebug() << "Dropped result item:" << result->data()->artist()->name() << "-" << result->data()->track(); query_ptr q = result->data()->toQuery(); - q->addResults( QList< result_ptr >() << *result ); - queries << q; + + if ( m_getWholeArtists ) + { + queries << getArtist( q->artist() ); + } + else if ( m_getWholeAlbums ) + { + queries << getAlbum( q->artist(), q->album() ); + } + else + { + q->addResults( QList< result_ptr >() << *result ); + queries << q; + } } } @@ -204,16 +240,10 @@ DropJob::tracksFromAlbumMetaData( const QMimeData *data ) QString album; stream >> album; - artist_ptr artistPtr = Artist::get( artist ); - album_ptr albumPtr = Album::get( artistPtr, album ); - if ( albumPtr->tracks().isEmpty() ) - { - connect( albumPtr.data(), SIGNAL( tracksAdded( QList ) ), - SLOT( onTracksAdded( QList ) ) ); - m_queryCount++; - } + if ( m_getWholeArtists ) + queries << getArtist( artist ); else - queries << albumPtr->tracks(); + queries << getAlbum( artist, album ); } return queries; } @@ -232,15 +262,7 @@ DropJob::tracksFromArtistMetaData( const QMimeData *data ) if ( !m_top10 ) { - artist_ptr artistPtr = Artist::get( artist ); - if ( artistPtr->tracks().isEmpty() ) - { - connect( artistPtr.data(), SIGNAL( tracksAdded( QList ) ), - SLOT( onTracksAdded( QList ) ) ); - m_queryCount++; - } - else - queries << artistPtr->tracks(); + queries << getArtist( artist ); } else { @@ -435,3 +457,34 @@ DropJob::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVar onTracksAdded( results ); } } + +QList< query_ptr > +DropJob::getArtist( const QString &artist ) +{ + artist_ptr artistPtr = Artist::get( artist ); + if ( artistPtr->tracks().isEmpty() ) + { + connect( artistPtr.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + m_queryCount++; + return QList< query_ptr >(); + } + else + return artistPtr->tracks(); +} + +QList< query_ptr > +DropJob::getAlbum(const QString &artist, const QString &album) +{ + artist_ptr artistPtr = Artist::get( artist ); + album_ptr albumPtr = Album::get( artistPtr, album ); + if ( albumPtr->tracks().isEmpty() ) + { + connect( albumPtr.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + m_queryCount++; + return QList< query_ptr >(); + } + else + return albumPtr->tracks(); +} diff --git a/src/libtomahawk/dropjob.h b/src/libtomahawk/dropjob.h index ca542ee7b..eecc4e9e1 100644 --- a/src/libtomahawk/dropjob.h +++ b/src/libtomahawk/dropjob.h @@ -45,6 +45,9 @@ public: */ static bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); static QStringList mimeTypes(); + + void setGetWholeArtists( bool getWholeArtists ); + void setGetWholeAlbums( bool getWholeAlbums ); void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false, bool top10 = false ); signals: @@ -69,12 +72,17 @@ private: QList< Tomahawk::query_ptr > tracksFromAlbumMetaData( const QMimeData* d ); QList< Tomahawk::query_ptr > tracksFromMixedData( const QMimeData* d ); + QList< Tomahawk::query_ptr > getArtist( const QString& artist ); + QList< Tomahawk::query_ptr > getAlbum( const QString& artist, const QString& album ); + void removeDuplicates(); void removeRemoteSources(); int m_queryCount; bool m_allowDuplicates; bool m_onlyLocal; + bool m_getWholeArtists; + bool m_getWholeAlbums; bool m_top10; QList< Tomahawk::query_ptr > m_resultList; diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index eb1d1c63e..6e0e2552b 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -318,6 +318,96 @@ TreeModel::mimeData( const QModelIndexList &indexes ) const QByteArray resultData; QDataStream resultStream( &resultData, QIODevice::WriteOnly ); + // lets try with artist only + bool fail = false; + foreach ( const QModelIndex& i, indexes) + { + if ( i.column() > 0 || indexes.contains( i.parent() ) ) + continue; + + TreeModelItem* item = itemFromIndex( i ); + if ( !item ) + continue; + + if ( !item->artist().isNull() ) + { + const artist_ptr& artist = item->artist(); + resultStream << artist->name(); + } + else + { + fail = true; + break; + } + } + if ( !fail ) + { + QMimeData* mimeData = new QMimeData(); + mimeData->setData( "application/tomahawk.metadata.artist", resultData ); + return mimeData; + } + + // lets try with album only + fail = false; + foreach ( const QModelIndex& i, indexes) + { + if ( i.column() > 0 || indexes.contains( i.parent() ) ) + continue; + + TreeModelItem* item = itemFromIndex( i ); + if ( !item ) + continue; + + if ( !item->album().isNull() ) + { + const album_ptr& album = item->album(); + resultStream << album->artist()->name(); + resultStream << album->name(); + } + else + { + fail = true; + break; + } + } + if ( !fail ) + { + QMimeData* mimeData = new QMimeData(); + mimeData->setData( "application/tomahawk.metadata.album", resultData ); + return mimeData; + } + + + // lets try with tracks only + fail = false; + foreach ( const QModelIndex& i, indexes) + { + if ( i.column() > 0 || indexes.contains( i.parent() ) ) + continue; + + TreeModelItem* item = itemFromIndex( i ); + if ( !item ) + continue; + + if ( !item->result().isNull() ) + { + const result_ptr& result = item->result(); + resultStream << qlonglong( &result ); + } + else + { + fail = true; + break; + } + } + if ( !fail ) + { + QMimeData* mimeData = new QMimeData(); + mimeData->setData( "application/tomahawk.result.list", resultData ); + return mimeData; + } + + // Ok... we have to use mixed foreach ( const QModelIndex& i, indexes ) { if ( i.column() > 0 || indexes.contains( i.parent() ) ) diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index 86bc03134..dc05d2cff 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -139,6 +139,12 @@ CategoryAddItem::willAcceptDrag( const QMimeData* data ) const return false; } +SourceTreeItem::DropTypes +CategoryAddItem::supportedDropTypes( const QMimeData* data ) const +{ + return DropTypesNone; +} + bool CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction ) diff --git a/src/sourcetree/items/categoryitems.h b/src/sourcetree/items/categoryitems.h index a7de58b0f..18cb21d77 100644 --- a/src/sourcetree/items/categoryitems.h +++ b/src/sourcetree/items/categoryitems.h @@ -33,6 +33,7 @@ public: virtual int peerSortValue() const; virtual bool willAcceptDrag(const QMimeData* data) const; + virtual DropTypes supportedDropTypes(const QMimeData* data) const; virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action); private slots: diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 5899405f9..58570168c 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -139,9 +139,25 @@ PlaylistItem::willAcceptDrag( const QMimeData* data ) const } PlaylistItem::DropTypes -PlaylistItem::supportedDropTypes() const +PlaylistItem::supportedDropTypes( const QMimeData* data ) const { - return DropTypeAllItems | DropTypeLocalItems | DropTypeTop10; + if ( data->hasFormat( "application/tomahawk.query.list" ) ) + return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.result.list" ) ) + return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + return DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + return DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.mixed" ) ) + { + return DropTypesNone; + } + else if ( data->hasFormat( "text/plain" ) ) + { + return DropTypesNone; + } + return DropTypesNone; } @@ -159,10 +175,21 @@ PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) DropJob *dj = new DropJob(); connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + if ( dropType() == DropTypeAllFromArtist ) + dj->setGetWholeArtists( true ); + if ( dropType() == DropTypeThisAlbum ) + dj->setGetWholeAlbums( true ); + if ( dropType() == DropTypeLocalItems ) + { + dj->setGetWholeArtists( true ); dj->tracksFromMimeData( data, false, true ); - else if ( dropType() == DropTypeTop10 ) + } + else if ( dropType() == DropTypeTop50 ) + { + dj->setGetWholeArtists( true ); dj->tracksFromMimeData( data, false, false, true ); + } else dj->tracksFromMimeData( data, false, false ); diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index 62ddefc2d..cc02e7bfb 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -34,7 +34,7 @@ public: virtual Qt::ItemFlags flags() const; virtual void activate(); virtual bool willAcceptDrag( const QMimeData* data ) const; - virtual DropTypes supportedDropTypes() const; + virtual DropTypes supportedDropTypes( const QMimeData* data ) const; virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action ); virtual QIcon icon() const; virtual bool setData(const QVariant& v, bool role); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index 2c174f220..a3bdaec1a 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -34,11 +34,13 @@ class SourceTreeItem : public QObject public: enum DropType { - DropTypesNone = 0x00, - DropTypeAllItems = 0x01, - DropTypeLocalItems = 0x02, - DropTypeTop10 = 0x04, - DropTypesAllTypes = 0xff + DropTypesNone = 0x00, + DropTypeThisTrack = 0x01, + DropTypeThisAlbum = 0x02, + DropTypeAllFromArtist = 0x04, + DropTypeLocalItems = 0x08, + DropTypeTop50 = 0x10, + DropTypesAllTypes = 0xff }; Q_DECLARE_FLAGS( DropTypes, DropType ) @@ -66,7 +68,7 @@ public: virtual bool setData( const QVariant&, bool ) { return false; } virtual int peerSortValue() const { return 0; } // How to sort relative to peers in the tree. virtual int IDValue() const { return 0; } - virtual DropTypes supportedDropTypes() const { return DropTypesNone; } + virtual DropTypes supportedDropTypes( const QMimeData* mimeData ) const { Q_UNUSED( mimeData ); return DropTypesNone; } virtual void setDropType( DropType type ) { m_dropType = type; } virtual DropType dropType() const { return m_dropType; } diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index b128b5f38..17cbef2d3 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -3,6 +3,7 @@ #include "items/sourcetreeitem.h" #include "items/collectionitem.h" #include "items/playlistitems.h" +#include "items/categoryitems.h" #include "utils/tomahawkutils.h" #include "items/temporarypageitem.h" @@ -23,6 +24,7 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& else if ( index == m_dropHoverIndex ) { QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); + qDebug() << "droptypecount is" << dropTypeCount( item ); return originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); } else @@ -137,22 +139,31 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->restore(); } - else if ( type == SourcesModel::StaticPlaylist ) + else if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) { painter->save(); - QFont normal = painter->font(); QFont bold = painter->font(); bold.setBold( true ); - PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); - Q_ASSERT( plItem ); - QString name = index.data().toString(); - - if ( plItem && !plItem->playlist().isNull() ) + if ( type == SourcesModel::StaticPlaylist ) { - name = plItem->playlist()->title(); + PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); + Q_ASSERT( plItem ); + + + if ( plItem && !plItem->playlist().isNull() ) + { + name = plItem->playlist()->title(); + } + } + else if ( type == SourcesModel::CategoryAdd ) + { + CategoryAddItem* cItem = qobject_cast< CategoryAddItem* >( item ); + Q_ASSERT( cItem ); + + name = cItem->text(); } int height = option.rect.height(); @@ -182,31 +193,50 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->drawRoundedRect( selectionRect, 5, 5 ); int count = 1; - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeAllItems ) ) + SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); + if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) { - text = tr( "All items" ); + text = tr( "This track" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeAllItems; + m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; count++; } - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeLocalItems ) ) + if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) { - text = tr( "Local items" ); + text = tr( "This album" ); + textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); + painter->drawText( textRect, text ); + if ( count == hoveredDropTypeIndex ) + m_hoveredDropType = SourceTreeItem::DropTypeThisAlbum; + count++; + } + if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) + { + text = tr( "All from artist" ); + textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); + painter->drawText( textRect, text ); + if ( count == hoveredDropTypeIndex ) + m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; + count++; + } + if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) + { + text = tr( "All local from Artist" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; count++; } - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeTop10 ) ) + if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) { - text = tr( "Top 10" ); + text = tr( "Top 50" ); textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); painter->drawText( textRect, text ); if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeTop10; + m_hoveredDropType = SourceTreeItem::DropTypeTop50; count++; } } @@ -282,13 +312,19 @@ int SourceDelegate::dropTypeCount( SourceTreeItem* item ) const { int menuCount = 0; - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeAllItems ) ) + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisTrack ) ) menuCount++; - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeLocalItems ) ) + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisAlbum ) ) menuCount++; - if ( item->supportedDropTypes().testFlag( SourceTreeItem::DropTypeTop10 ) ) + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) + menuCount++; + + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeLocalItems ) ) + menuCount++; + + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeTop50 ) ) menuCount++; return menuCount; diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index f0e7ad18a..124b8c3c7 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -11,7 +11,7 @@ class SourceDelegate : public QStyledItemDelegate public: SourceDelegate( QAbstractItemView* parent = 0 ) : QStyledItemDelegate( parent ), m_parent( parent ) {} - void setDropHoverIndex( const QModelIndex &index ) { m_dropHoverIndex = index; } + void setDropHoverIndex( const QModelIndex &index, const QMimeData *mimeData ) { m_dropHoverIndex = index; m_dropMimeData = const_cast< QMimeData* >( mimeData ); } SourceTreeItem::DropType hoveredDropType() const; @@ -26,6 +26,7 @@ private: QAbstractItemView* m_parent; mutable int m_iconHeight; QModelIndex m_dropHoverIndex; + QMimeData *m_dropMimeData; mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() }; diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index fd71166c8..1354d1e46 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -439,7 +439,7 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); - m_delegate->setDropHoverIndex( QModelIndex() ); + m_delegate->setDropHoverIndex( QModelIndex(), 0 ); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex(); } @@ -456,7 +456,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); - m_delegate->setDropHoverIndex( QModelIndex() ); + m_delegate->setDropHoverIndex( QModelIndex(), event->mimeData() ); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex( index ); @@ -469,7 +469,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) if( item->willAcceptDrag( event->mimeData() ) ) { accept = true; - m_delegate->setDropHoverIndex( index ); + m_delegate->setDropHoverIndex( index, event->mimeData() ); dataChanged(index, index); } } @@ -504,7 +504,7 @@ SourceTreeView::dropEvent( QDropEvent* event ) QTreeView::dropEvent( event ); m_dragging = false; m_dropIndex = QPersistentModelIndex(); - m_delegate->setDropHoverIndex( QModelIndex() ); + m_delegate->setDropHoverIndex( QModelIndex(), 0 ); dataChanged( index, index ); } From 1d6c4070034d2411379f6b95aea92ee3177cd1e6 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 21 Aug 2011 16:29:36 +0200 Subject: [PATCH 14/72] TWK-411: Dragging an album/artist to "New Station" creates a station based on that artist instead of all the tracks --- src/sourcetree/items/categoryitems.cpp | 85 ++++++++++++++++++++++++++ src/sourcetree/sourcetreeview.cpp | 13 ++-- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index dc05d2cff..70136e4c7 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -149,6 +149,91 @@ CategoryAddItem::supportedDropTypes( const QMimeData* data ) const bool CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction ) { + // As DropJob always converts dropped items to query_ptrs for all tracks we need to extract album/artist metadata ourselves for stations + if ( m_categoryType == SourcesModel::StationsCategory && + ( data->hasFormat( "application/tomahawk.metadata.artist" ) || data->hasFormat( "application/tomahawk.metadata.album" ) ) ) + { + QByteArray mimeData; + if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + mimeData = data->data( "application/tomahawk.metadata.artist" ); + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + mimeData = data->data( "application/tomahawk.metadata.album" ); + + QDataStream stream( &mimeData, QIODevice::ReadOnly ); + + dynplaylist_ptr newpl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), QString(), "", SourceList::instance()->getLocal()->friendlyName(), OnDemand, false ); + newpl->setMode( OnDemand ); + + QString firstArtist; + // now we want to add each artist as a filter... + QList< dyncontrol_ptr > contrls; + while ( !stream.atEnd() ) + { + QString artist; + stream >> artist; + if ( firstArtist.isEmpty() ) + firstArtist = artist; + + QString album; + if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + stream >> album; // throw away album title... we only create artists filters for now + + dyncontrol_ptr c = newpl->generator()->createControl( "Artist" ); + c->setInput( QString( "%1" ).arg( artist ) ); + contrls << c; + } + + QString name = firstArtist.isEmpty() ? tr( "New Station" ) : tr( "%1 Station" ).arg( firstArtist ); + newpl->rename( name ); + newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), contrls ); + + ViewManager::instance()->show( newpl ); + return true; + } + + // This could be needed once echonest supports filtering by album. + // If they never will, or if they do and this code still is not used, throw it away! + // If you enable this, make sure to remove the checks for album above. + + /* if ( m_categoryType == SourcesModel::StationsCategory && data->hasFormat( "application/tomahawk.metadata.album" ) ) + { + QByteArray mimeData = data->data( "application/tomahawk.metadata.album" ); + QDataStream stream( &mimeData, QIODevice::ReadOnly ); + + dynplaylist_ptr newpl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), QString(), "", SourceList::instance()->getLocal()->friendlyName(), OnDemand, false ); + newpl->setMode( OnDemand ); + + QString firstAlbum; + // now we want to add each artist as a filter... + QList< dyncontrol_ptr > contrls; + while ( !stream.atEnd() ) + { + QString artist; + stream >> artist; + QString album; + stream >> album; + + if ( firstAlbum.isEmpty() ) + { + firstAlbum = album; + } + + dyncontrol_ptr c = newpl->generator()->createControl( "Album" ); + c->setInput( QString( "%1" ).arg( artist ) ); + contrls << c; + } + + QString name = firstAlbum.isEmpty() ? tr( "New Station" ) : tr( "%1 Station" ).arg( firstAlbum ); + newpl->rename( name ); + newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), contrls ); + + ViewManager::instance()->show( newpl ); + // Give a shot to try to rename it. The playlist has to be created first. ugly. + QTimer::singleShot( 300, APP->mainWindow()->sourceTreeView(), SLOT( renamePlaylist() ) ); + return true; + + } */ + // Create a new playlist seeded with these items DropJob *dj = new DropJob(); connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 1354d1e46..bd40aa415 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -496,11 +496,16 @@ SourceTreeView::dropEvent( QDropEvent* event ) { const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); - PlaylistItem* item = itemFromIndex< PlaylistItem >( index ); - Q_ASSERT( item ); - item->setDropType( m_delegate->hoveredDropType() ); - qDebug() << "dropType is " << m_delegate->hoveredDropType(); + if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::PlaylistsCategory ) + { + PlaylistItem* item = itemFromIndex< PlaylistItem >( index ); + Q_ASSERT( item ); + + item->setDropType( m_delegate->hoveredDropType() ); + qDebug() << "dropType is " << m_delegate->hoveredDropType(); + } + QTreeView::dropEvent( event ); m_dragging = false; m_dropIndex = QPersistentModelIndex(); From 4fe15984ce7199b5352629a4fab6148040ff7a74 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 21 Aug 2011 17:30:11 +0200 Subject: [PATCH 15/72] TWK-434: Added drag indicators for artist and album --- src/libtomahawk/playlist/albumview.cpp | 2 +- src/libtomahawk/playlist/artistview.cpp | 10 +++++++++- src/libtomahawk/playlist/trackview.cpp | 2 +- src/libtomahawk/playlist/treemodel.cpp | 3 +++ src/libtomahawk/utils/querylabel.cpp | 2 +- src/libtomahawk/utils/tomahawkutils.cpp | 19 +++++++++++++++++-- src/libtomahawk/utils/tomahawkutils.h | 9 ++++++++- 7 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 16eb7e0a8..101760fff 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -227,7 +227,7 @@ AlbumView::startDrag( Qt::DropActions supportedActions ) QDrag* drag = new QDrag( this ); drag->setMimeData( data ); - const QPixmap p = TomahawkUtils::createDragPixmap( indexes.count() ); + const QPixmap p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeAlbum, indexes.count() ); drag->setPixmap( p ); drag->setHotSpot( QPoint( -20, -20 ) ); diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index 199e8b1a4..07d616aac 100644 --- a/src/libtomahawk/playlist/artistview.cpp +++ b/src/libtomahawk/playlist/artistview.cpp @@ -215,7 +215,15 @@ ArtistView::startDrag( Qt::DropActions supportedActions ) QDrag* drag = new QDrag( this ); drag->setMimeData( data ); - const QPixmap p = TomahawkUtils::createDragPixmap( indexes.count() ); + + QPixmap p; + if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeArtist, indexes.count() ); + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeAlbum, indexes.count() ); + else + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeTrack, indexes.count() ); + drag->setPixmap( p ); drag->setHotSpot( QPoint( -20, -20 ) ); diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 99c1d4cbc..f1cb6be0d 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -378,7 +378,7 @@ TrackView::startDrag( Qt::DropActions supportedActions ) QDrag* drag = new QDrag( this ); drag->setMimeData( data ); - const QPixmap p = TomahawkUtils::createDragPixmap( indexes.count() ); + const QPixmap p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeTrack, indexes.count() ); drag->setPixmap( p ); drag->setHotSpot( QPoint( -20, -20 ) ); diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 6e0e2552b..dc4e0caec 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -349,6 +349,7 @@ TreeModel::mimeData( const QModelIndexList &indexes ) const // lets try with album only fail = false; + resultData.clear(); foreach ( const QModelIndex& i, indexes) { if ( i.column() > 0 || indexes.contains( i.parent() ) ) @@ -380,6 +381,7 @@ TreeModel::mimeData( const QModelIndexList &indexes ) const // lets try with tracks only fail = false; + resultData.clear(); foreach ( const QModelIndex& i, indexes) { if ( i.column() > 0 || indexes.contains( i.parent() ) ) @@ -408,6 +410,7 @@ TreeModel::mimeData( const QModelIndexList &indexes ) const } // Ok... we have to use mixed + resultData.clear(); foreach ( const QModelIndex& i, indexes ) { if ( i.column() > 0 || indexes.contains( i.parent() ) ) diff --git a/src/libtomahawk/utils/querylabel.cpp b/src/libtomahawk/utils/querylabel.cpp index 33a671a2a..2ded49c42 100644 --- a/src/libtomahawk/utils/querylabel.cpp +++ b/src/libtomahawk/utils/querylabel.cpp @@ -568,7 +568,7 @@ QueryLabel::startDrag() mimeData->setData( "application/tomahawk.query.list", queryData ); QDrag *drag = new QDrag( this ); drag->setMimeData( mimeData ); - drag->setPixmap( TomahawkUtils::createDragPixmap() ); + drag->setPixmap( TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeTrack ) ); // QPoint hotSpot = event->pos() - child->pos(); // drag->setHotSpot( hotSpot ); diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 6b698821e..6e991c98e 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -307,7 +307,7 @@ alphaBlend( const QColor& colorFrom, const QColor& colorTo, float opacity ) QPixmap -createDragPixmap( int itemCount ) +createDragPixmap( MediaType type, 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. @@ -344,11 +344,26 @@ createDragPixmap( int itemCount ) QPainter painter( &dragPixmap ); painter.setRenderHint( QPainter::Antialiasing ); + + QPixmap pixmap; + switch ( type ) + { + case MediaTypeArtist: + pixmap = QPixmap( ":/data/images/artist-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); + break; + case MediaTypeAlbum: + pixmap = QPixmap( ":/data/images/album-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); + break; + case MediaTypeTrack: + pixmap = QPixmap( QString( ":/data/images/track-icon-%2x%2.png" ).arg( size ) ); + break; + } + int x = 0; int y = 0; for( int i = 0; i < itemCount; ++i ) { - const QPixmap pixmap = QPixmap( QString( ":/data/images/track-icon-%2x%2.png" ).arg( size ) ); + painter.drawPixmap( x, y, pixmap ); x += size + 1; diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h index 1fe759bc5..3b3d5ea39 100644 --- a/src/libtomahawk/utils/tomahawkutils.h +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -40,6 +40,13 @@ class QNetworkAccessManager; namespace TomahawkUtils { + enum MediaType + { + MediaTypeArtist, + MediaTypeAlbum, + MediaTypeTrack + }; + class DLLEXPORT NetworkProxyFactory : public QNetworkProxyFactory { public: @@ -74,7 +81,7 @@ namespace TomahawkUtils DLLEXPORT QString extensionToMimetype( const QString& extension ); DLLEXPORT QColor alphaBlend( const QColor& colorFrom, const QColor& colorTo, float opacity ); - DLLEXPORT QPixmap createDragPixmap( int itemCount = 1 ); + DLLEXPORT QPixmap createDragPixmap( MediaType type, int itemCount = 1 ); DLLEXPORT void drawBackgroundAndNumbers( QPainter* p, const QString& text, const QRect& rect ); From b70810da9cde2ee6c95840d4533084ff02226938 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Sun, 21 Aug 2011 18:07:01 -0400 Subject: [PATCH 16/72] Icons for artist drop areas for playlist drawer --- data/images/drop-all-songs.png | Bin 0 -> 903825 bytes data/images/drop-local-songs.png | Bin 0 -> 1054090 bytes data/images/drop-top-songs.png | Bin 0 -> 1054090 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/images/drop-all-songs.png create mode 100644 data/images/drop-local-songs.png create mode 100644 data/images/drop-top-songs.png diff --git a/data/images/drop-all-songs.png b/data/images/drop-all-songs.png new file mode 100644 index 0000000000000000000000000000000000000000..f5ef7944fcd4f2b031a9f041ad48d1803818827d GIT binary patch literal 903825 zcmeI537lTj-N)~JX0k|RAwtBKP_#uN_NtN)O=FZGmU+9`OBJP5F-&5r>eW^^q}HNs z5)!H%-nLqT)>2Eh7F8ueBtm50GjrePN!)nm$+O($oO`zKe5&)@d(ZhV-~XBO%e~LJ z_g+8hm=Qg?_3!3+UXPJq7=FCx`G3y;*WR^re$V_vpAYiC{BIvW;&AVQm$rW|ztQ>h zh7rT_zw|7){veWCH&p4Yuk;XmKI_4a=Gon0;&`Nbo<{HJT*z6TGO`@8)M z_IJMI$Wtyk;==RKKkI@^ydy3?>$FSGI&;8f=U#HofDt3VIBLL!7YsOUz!y(Dbs|eIvl?};200O!aNGot* zUZEWXFC@b6ngnt+ThIUk5P(2(0tI0T#VNU!O?%3)xv3ZSSV4d@IxEUZ5)jaiKo&w2 z`rzzNyYlN55NC9H)q`jtU^;=|x!vEmG+YaiPbL(jtn$j*UsieHM-Tx-PY_7VK)`VV z1+h^nI4XTRUV8o0=Q5`LH6lU?=u7~C(YZRrww?fIbPNOtKp+VL)n{ z6oLsLbb^6m4gv-eK;R54T1FvX{oyC3TD2B{@9Q!eIk&Zgc3mDgyO`QDFhHYQz}KA5J*MmWu>b*`2%vI0RfAZxAb`+mQ3is5 zKxzUATxx_Eu$2HpXKQ82%M}6$T=`1#1nBZyoZO4hIazj+wtxTvXF(B3AsYe+U7qtv zkKOs6(6s>B)DABYKq<&O8WBL?5P{3Sh`NktW5o`k%YM+}Q62$ZCIopIsX2sxM%Sw-v;VN2Xe_pw}v&$uyx&?O#^bW}`0_X$BUp49G*o6J&YtkwYI zSt5UgP9j`POCi97AO-72qX^ta6q$T5aXc4dHfo$8beW80oCqLb;)Ea|?MzGo5pn^7h}c`qL*a2(kBZ7 zHcbjy@QUNZm+oL&Q!V+fvJn<5D6TZXptL@Twh_HV*yvP&fIgE%g#ZoSjo4*Z zJjQW$tTXY1&}D+zdM8W_@=A=r$zxk;nQ>f7#FA}<&XPhZN`r|(9(V*!9@|pOjN?)w zmTe<+mK8LkbeI_Afkxouu`RXCI4&h(={7=VX<;R$<c7%q#2ogo3;L@$hs-|`7CImly- zz{z7a4`)&M<#&KMMh<8Lna4#r9GaP*#YYAdpqeFovtZ;ZQsR2wgm0y0>O>kXs=HPA=bqb;huK#?E;_=J)NvD2Snr3ewYN@T?CXZ(s|iKQ3>5V{z!6gFjo zkOv=ulgBo+%=isS@LLH1giZUlR-7TO(ux+ z3@cec;3|<3v!Agmh9g!g5m@n!=XXr8768$!1UCEqOcrvPBX)B6GJfUq#d-QZir6It7oi(s7Xea=CXC$_@aU=!0fbIoEtn+au|?qIu`O6;?8@bf^{fdrw6r#% zcH*fsQ;268T14eS5^{bscI9xzs$v2Nowx!pNr+=vY6*cWB_eK{aVw4`mNX@R(4{Yo zgo$FSW3If8wE!ihLF`I^2;7t~h6P54DF_H8faoQM$wZNyU9Fx7TrEP$k27wQv!kb> z1Q5Dpg=4Zv#;sZ}#H|{g^v4*p=^0|dPyz^D{K7F|#AjBm2Le}(PWoev*YpgrU^oGU zE?xndG~)59)*6AUMkn1d#%Vf!7WcmA9|z5Ldo2K>7kzj$kwj-zttDbtjZV5_jL~%b z=xr$hgf3cPnLwg(s@4jDt41f?F~(;)e)P7S076&0z)T{w8C7e6z*VD@?igb;9Y1N8seK#j=G2{2<_Orczz~!DCJdj|J1mj1NEayD9+$t`yJe+ipHg z>{<|8Om)NxcjF^uIAa-(w0%9kJD^?-Ufq=E5*C9(bk`h4Rl9H?54}q)BhocY>N&uk~ ziqjfn2%MZ!B5-oJVif|_2{g2{HlcRa8M$^8m6KC(R89_8tU@3X0fa6RChHp^aN(B|r6YY0F<3jzq87G=mhh;ZUY z@T#K^IaP1PQ3yalYXS(J*5#2NTsV;)O%e4S%VL9eKocLW5zc6RVN=>>DaECy7(Q_w%GA&OaocwJT zyfDY|1la2Yftjz(^9G$buCZLQA3SmC_&&Y6mpjBh!wBRIi1=h_4P^zeQhWC6O8}wMw>COOW`j7`kb_QR!j4${x}Mb%S+j0kwZj}U zgaATkNTD|5pTkAAboh(!@^kpqf5`zE)#1P>6 zr5KL7T3NlrUwf-w3s4XwS)wGQ=03s$T5)dhN2wX+X z9FR(&&hOOT&h<>Gj3paK=p+Ni{Q5JyG=hh~F^H5C_;4}z9VjnCiU9b&E(JWg#v%}k zUDWUL!uGM8&=>*`fIuk$&ge>suq~TFwKKHv7$PUT3fPZDVA+b*vD!P(c$@QDmZ)9} zkO(uPmk1l31P}-Xu3)P8vjEeWfPh#61CRPyBf=+^X_nRyx-4nqs~iE&X|<3uZg1pL8cB0X~{m_BB_K)0BJKq>;;9eHCTV=@&&Wt};rQ-&VL z5P$##Adm$Cgf0up_<(>A0`Fb+)}M@A3m_zW-Hg0DKff@8AL9rhdd8KEfFS?@RRk6- z{=WJ=ejk%}yDit5v_%cTD;LYXNFe;y47HBry1-Uveqau*xHPh82u(ApimO1m1Xi zf%+hbATj}jE;1?_LI45~*g#+*D>0243J9H1L5^ituYGXx;uDuG2y zxU<}p+9PzPREjtu00FHC9Q2htxSpw1MJfk_&{bx`5ePs40#ymT{Q7%Uk8@BD0tlTR zbs!1|K)^r(dz^3!XLSbD4WTojNQ5Yaz`)Np|CqG^LQvXiWQ(4grB3YK`HNHAz<^u= zyscF(U#vqQQv$E$R|tNva79_42M^h)?0%PbZr`t0>h!n!{7+5}rZrDYZ3qL>2q1LQ z&|(+@*%P?&o~JJ#H+kx;vYy+6)xpfM50pK|&g2AwAh&K^yH{V&#j@lO<@Tg9a`|E% z0+|x1C~}1;a(E zY66mlZkuJ#@4;FC)kVfX1j-Tk;x~SCTDkq~OGzLXczGt*{UoS?a> zA#~=}j>sVZ0nG>;{FOV7)+{8#$rC{67zPl4fPn=5H1GG{)O;;K&hKF$SnESZ^sFy2 z2|}PFfqqB)h(}v>L?U#{-}9v(i z5J2eksRfZhATt8<-e+-&OxT_F8GDjU``C_00HKS=i`Ec;00e3i@cp{ljcAb>frggW zCe$u7+-5r({^Y0ssQFp|vjHoNe%0!A3cb7G+8^9J+Ko*0&l1tozeYp|0pSD+FJD`@ zxcuu7ggbNApx^1$H|9H=y-tZEbUIaoSRjxgfp^|plA$*@U43fQO?-K$LFo}X4T?Yr z5Kuv2&mJ`i^!KA* ze)Px7b&Si-nCiMMSh(EI)HKZsq0_W5gav^t35>gc+8bH=bKTcgLvLtYxoWNJ7&6Nr zq00>QHT{2?_cg#9IeZw;I?s%{%Z|=DVaR|)`R4-Wg+$MdQq zDsg{0Mk7!ktr@K#00HX>@G9-hOZ>Uh?rQYfgVn*zu@7YK+#!dY(K%FXQiVV*0{-B! zC)R4ialr&~ewTC>x4-ub`MV-n3lNcFLrZHD$`=t9%^=_gfq=_C3JU$_r(fqW+!DDX zbQ0lW8Uk4pxbp17u1o9lw~x$tGOZ2z$0IP}++Uyl=G&}PiAODZYlJR3D_TN8dji|{ z>zz8rFJ1n?Evb!QAU1)WkN#O>>`ruuL;#_Sgo#EFaErj4d5hgjSd(nez2+evUn8Lw z2%Q9|n6#U~alha7VwcteY(L^g&NLJZ>2Z^%&U)p)^A&iu+a;oBcYVnY0*MK%S=W}h zFCCK;Sg@GKsU)XYz9&Lgo(p>+;5>mY`?hdar(hHmZoHenfCZT%(Ia#sX<`ckmJwL9 zW}RgjnUT&mhy5=%I|?inLMIR=rXXM&fi8QGZLlpTBl4-MtAFURS6(wBRs(`GMd+@5 zxN%RH)&dwXbO_No0-fr5I+wF136HvHVzVZp7$1fyqGvp45`ciI1ZK}&WNPTf$Nl2V zbBxbHhXfEh9jZVK5YU*w)K})dwqWrJjU%)-wEY@yYxMVOKHA<4v&jsh%Z4*vAYd$k z!X>o#7c4V2e)9uwPx*9c^9iKQ0HI4ukp2*GfWW7YyV=&;4$eDtiEqwYfLvYYXQt0~ zV8qEw5z&*!7RwN@ioi>JcciSI2Y>ZWTmR9K2~&hF6U;b)fcXUWJ>}L>=4WAU2L4`G zonUVCY9mMJ)bhtW1k54e`H=`wb1q=sjyVJX784lFApil-oASc!Rc^m?##j3Ohy0^dPGKBICxFmJXGKd0K%j`g zHb?yM_@YgxKF+xR>-WDn{;cuJlFs@0Tj2TWK;RQcK68D9wE&1-2Wo6U00NN-w0WH) zx3#e0KA+xBX;%K;oZ&?+074g)6zw1YfsO?7XBhsVW~UdayztvI)t%wx??qU*uDxST z*eXi^p(~4poe+RPBm((GD*t}-{YXu1Y_#tl+bd1${O|waa-{(n;vAtfq)>zj0bL1v z^0;3(y+~sDb5|%ks~b0Y>Z}#3*62Dkw22F$)20*z0s->~{NgJoJ>Ow1K>l)CyTe!0 zx^?aB{d4j;%7ky$9Nwl|>#qpWt3`<85P(2U0(C>iG`2gec6)rHpXUW_OKO6W&|r__ zt$pv3Fj@$mFq9aAfZGKAK6SR!0A3jX^@mjfShQrND%VE%Md*wu6G1{ie*%Y|{wt@S z3|&~H@_Os}N-kk;@ww}rW?$>C1EH%$h~p4|Kx6{`;4w~5xV!Ao$GiFRkxE|hw`AF> z$aQk9Awp+f#ea74QTudQ3t(OfMC=NI*WdcUl~l{-dDiItl{}bv#j3Ss<6)=gA$p7g z2tYt6fnAUJMdQ2k7Yns%lCk>woHysui}RN=gG`lY+arGD^qFKp+u; z4;HRS)X$bq{kQJvz5S<)C)tvZ>3JY@rdN>oA)q0F!6*H)arGLdF9A?arT^AFyjlNu zVWtaJ-kIm_nRK;+BXrg1a0~(vNKByXzAZ;5?rcHFTrTJB)PD=lYY$dumCg<$ZcKCe z^4T5U-p2a10Ek{zqY*z4aE(CD?=kbWxvpisNxs3%u@5|b_vx9P<-NaP*(NbKzoVh0 zwF$Lzei#4%0`dq9I`NljPC4WSma*l}?7d@V9v5>D!X~T{x=oO<0|F2bNx&a`)v+S& znQAMTK4!cZ_|or$`(P1|P$?ZF2wf==wjlrk!365M2{ve~xnM@~gZVG<0+|VX=Bsyb z7F_}up(_ExCIm7gaL}WNJZAk`fK6%s`RUh}Zo1daJxiXwe1g~ROMNlKYqQ*av0%AT zh3J)|!afK{6^@EDHVZ2 zz-$6t4!BmybF*}%7EEiNoEJM3DT4wbbQwU#1q2|FClCa=$N%vLD2fOyefsi=MVrDt z?s3A+PQNO?6eL1dN`!3)KtLLS!_T;vv$`U>n|JH%tzVhx3tp^}e?9!N71FMZ7G+?@ zN9TlY!swoFIk*N)%d^Ro4R^}%mW5dF01*Jm#%>&wNR&WWCj zMJ8tm=tQ7j{&D8_B)Dnm}@X*J=;_1LaJ9xAH zbdi+BDuy7C5+*_?1uI4&0D)QrwjO#z&bS5HOU${0}+9D_6QLdTo~9o$YJQ%f(^KK7`JeN|FZz3?wjM#Lu`5p+en) z>0>A5pPiZNk!o+fyRd?Fw{|0RZq=J?A)pt5{H+HszC1^-m^6xJ^@~@vrU_?0FR@#X z51~slis%ag2&5*kU*m0!^B1H#aY-G6t_C`HuJ_((y&&=Tb<0++)it$D<3i{%#f>w; z1iJfQ{I%${0D?orTwDTg&s!X~i!<#9?y#kI&Iv;XBuV)2v+m&)KY4J7UXsy69|%A| zD1rU{=XNIKDnx&J#nIPQIn1G21R7den^3!2lsFCn7YK-cC}RRtMRgVO{=u#kn z&@m1m0D<)c4%ufXwIl++%QMqwtBpZ(@Be=2Jf;5_(Hu|3@Zt&G-v+Mg##(@iFgXAL zn+VK#^dhB`>cL;VlP~Y845-}(^ewy$u&qiH4wWGgPxQ(dU>5`+U=M-*TlZ3)40k|R zFTujUefPfVEjU_A0HG@-!ZrjPCopy5*Oi{t@hBC?2cU%X5jqJ_F$n>a3GDsJZM_ek z{Ej-Y%RfrRWPY+^@7**1kj%EW$Z!68dTRa!)sFu#pd23nqz{2|1p4&Ke>30SmfbrD z29G%Vhze@~{Jol4nOUw#AMIQI!d3TIIn1FF0*GD-5H=wIfei$LnJtr*v8c;+Qsx`S z))7GHtSc!AK|maV*IUn5b2&2za>Gu&^E7dp*k!5k3Uhv<6F}&qv!W#goFlNy4t+e& z_p`g4SwwisOKs02g zyYEBx*-`fpwTTa*)20;J2eQL!uYS71T7c|>!J{z*Uj5xSC#VQ^%JXlkaEnjN2q1cv z6_j)!AcMf}`6qSEpK@vDm(7Nq{;N}Dy|+{tN$nM^(D`wS)jdXGtMR0|HqQ_~404CqH)U zS2Meex&N1Vduvv;73JsucHVB4u4@4hJyBK04g?$_P!ASB?t%AOL}?1PY5H<~(|F{tPczTeYE}gMlDBwwc+6m$ybMX+ukE6KWTY z6|Eouff54!x9;gJd*;eXCA+0;PWi{1uSqeCQ6~r>bWW6+Bq1P&K=*E)z1ROB{~2PV zE}GaZCj)d*y}j&R{u@>hWfzOCo)9`+D??lmFp$9R1N-J5 ztrD!35&CwY|1r-BD+9J)eko=!^O}iX5VV#4S6Dp6kLT3`0Rj+!fJg$t%&`wVk$+P2 z&@=CD6lvEBe1FxNbzaxbOk3r+1~bOqS8hN1JdZQFq@!TjZr2XdV=X|^4Cn`eOb8sd z|3L5Y+rB1zvC7+X7iAJZPV6Fp=-E|IvVnj$1cn|k&}%t&=t0^9L?F%*K1i}1VRE^^xDkZE&pT{ejuQN z079pNIzA!bAc5`r_3{?~{ky4GWvEB0XYQf>+@4j(6&U5b*w_5BLRf2BM-J9 z0D+7M1T$J5_S*fjPv*}k1l+hnprNI;3AJ;j+T;lV4GGMD@{)-fhDAu05kTlHD=6te zz$5~Fx9DzCxWsBG0YySLe)@C2V=aK8V}`IHAdSGY_nwh>rHZtmFl-e8MWSaFLz002 z1X2(@=r9bcQQMIZ!W9RY;Sx{{I*1k5Cm^L;Y|Cu;2pAavT7hyWqr27yEN z$zRIk2LciZAaoL-ViE!nNJyYPSemdW-5^kr076$04F@6<*zsFlewn}kZ{+Y{qa$~s zAq1)ucz*m@t<_s_6aw)HAbRnc(E|bykVK$!r#eZNG3zn`gwEyalRE@7BQR+9Z8eLC zaBLue(AiK$a)1B?AOL}i1Q5E4XgB}?2tWV=N(mggdzZfnUJHQeDXl2ZApijgKtMJD zgibbL>_Y$o@dyNMOX78)H3X^?KMI>!e^vw*|FrHp&-8v+o300gWefY4c0 zPLhED1Rwx`QUVBFDG|0I009Ukc$1>W)%Yc{4l_=KB}_3G-nZQQ{JCkP;P zPL!D>A>bYXe{l0Lp6_+@0^i@5JtDUeg-ah0I+vy3arVgU_=Tpkqzhf`GFG z{JpOF;`-!sb(=d2fyAp4FkSSjCdWYtK)`MS?=M{Lc?g`{MHY}9H`xh*hzSTlAX@^3 zrIpW2o9%5q^ad_lW{ciPZ=BH?SvG=(fZGH%Kk(Y)y`100Z3<)`l>ldSQAyDb0uWG6 zpgq5=(jPpgF$0mCc=0(uWG#U5ii-~b(G$-Uix7Z-1Of{eukh;j&I=wt5Qs?tp^FKO z<`95D_5@y=^}e^|q1TVj-ZLI;Ab`->P(^ZpfaL^sKlY}=-Qk@qPm}Z{5kTl9fyFEY zARvao`tRq&m=b2?jdvElCd?Ryq!U2sq$9>bbOO)+W_dr>0z~KKR?ET@QFQznbc|M2Eqz})2U7!aL$ z&U<9;g8cg30p7^r!?|5p;}imR6WIFfoAWY|Y%RbpN8fm&-E_!KB>_ZF zC4GED00N#rsQDz%tK)^5j0I@~5ISjSF$@6+L?RI6_@XWnC>kXtfY2o+M?VNaAT@zo zCOkLM~z=k)l+L8@<=u7~i)44jt1_4V6tXtP^MP>)= zxt$ex*q#JJXM2Uo4+0tz=(O)ORy;&9mtzXmaCmJJ5>?s+2|FMF0ht8WtXU`1JhsIX zn6US>+pJj&far-IAXtO|1ga3|vj6w2IJ2vgB8N01fY51H5W;}~1k51t;`p=1n-M8d zB6N%a2tXh+0?Svd$;=myAP^Ej=t2_wh5!Vl6WIK~Yk4u9bjmm=C4kVC5@8zxS`awz z+T;Ia&02s>h^}9pV$qG`n|CwrRYaTA#FZV0o-36mPY5U`@Zpk`iX#x~ezyUA#Tvw# zGy({nG_)9o00e9$@X~~{S(IW>aS=L$ibbdpFqy!%NBqc|TMX*!m^w`wi@Xrgq-qf> z1WE|_gRVTv3+g-0x;ei`Ni*e}3l=R`9s>b5OrW8qwF$NRXaMx7yZb$R*8(^^$}LXa z->bRflEz%8UY-H(I$geEtyNX|)Gk|Fm5=Sobo{5G?b(ta1PmmwaPbQ7d$&&csXus3 zzt6oIw5P@m0&h?fqU+o=w>K!L|xjGiNYf}4H zANt1qnuJ0a5eXo45kb)m0$CFna^kpCHk`ScFLFDLxN$=+SwocIZ9hWvOIlI%Q6a?Zjfpf2U=<;!sr_QpG z!IEXGtx0RgZF^gji%W@cM(0ws$yG}NZ_WA8vsL7@1fDpkU~OH@OPGmW(NQP?gia_< zj6oneffXy)c+X6m?F~5UXVyGUq+Is9HvIgUa{Eo&xAd7SCz%#A@opf%rAtPFjRZdZ zxp9{pwHBcJLD%NR!0&EjF*@W_XMT=v{qbAld96W*sv5Py*_~14B3N4p_=B29IU#bE z6s*_gT`bAO%`^}?H|tK;3JDB5_0H2g-``AO2)K?(pjQvd6%(0O%@I1S%0V!;6Y%%E z>ewk$XRp#N&vBDpdPTRGbcyHPzkHi__vliLgmEBr3Bl0~0*MIJ*LO|SPj)BHFv}Be zZ(Fl%U+vx+Q9f4oRwN)$=+63A&+izu7N8;&4nROQf&7`>iCz#0{QDCd@oyiV@uXOb z=384kee7iOvmgU)|L7iMfbe;yw~ggimaUCxFn2 zXNpA#bRaP8m3KWa#~sKWpct?bOdrGdbOzKZN{9$u6iT##Ktcj*)~)k~oN}wxH;N}j zqjk6Ee{&`xr*(zm1&7eZ<3(!-#3ImTNK41JhR5oiapSsrDL1muxc}=ZMPtAV*Bqp1 zEr1^c`|Y`X#)ZJ0R00hxtxYJORKyr}nZQ5)GcSJ)Gye*4$A32K(#a?S!L+dxj0%`w zYZ5@{Y9gWm1mY7|v}C2X*XVIv!;FtkX^*KB&Q5tcsx*9@S0I4URRF_o2&5sf#pk|% zVw(P{`WD_RB-;0Y?Y0zcQj)#4b$frRza-n3RY?G$Q%N7+RuXvmzw`5X>3wfOCGg`W zzx1C<$CB=^ug_02ZEfq4_RpkWdk_TETX_3wCa|-2g3x8pTzij0gU;O)tp(7YO`HH1 zEL!fp_rbC_U7Tq9)}OzvwV=#=;9S0=vv?#TdKMR#)UqcqYtBON@U!pXd%En=C-)e% zt=eVEg5g4@FHd?(e zV8N5$;kJTG#gept>Sxb7N%dL)L{Cz2G3yqA=V!i?f6F*;V69g5ep?kZPlHvN`NyI> zd+!at=})A87snpKv@Fs&R^nvb_O$Bq`s)FYge^X&dL|AeyD3# zvmRAu*bve(T+ZmUEDJ%IL}0t2*B@`AeA|M$UZxHA56Jay4X#-@9X0a9UObnX z8MYw+fp`Sk)-8|MAzkY}TjbxF*B-3S|3jfmI!onx`|IXCcJciPTsb!G?L+9?t2r5q zBv9vd_N1;^6dA03wt|^sA1K_Waln_w+<^bY9jA`3tMk3j=Fj5zfj~$Ap$kdy8v-#2 z_(f-PbW0{Rd@==7-tkw8ER zfho6tP5N`Xlq7~zSp1j?fuD_DCR00*jZe%F>;5U%fW(BHafLtRP^l$Q9@#bXJT(l7K)m0z8={ z88^pz87*>!S|N0fjabr!KuF-3d$?>Fvfy_`0>+D6Au}C?Zp#^Gey`eEfQl9200gom z(EGDLuwzk*9|Q*t-XTkL_%e@x6@pj5&V!%L8+=3zfz$;4`-clAq&9*9TM1YraAAQE zx{w6FA>a^!t^4$FC|gpEK)^b2E6y3AE2hC`2slEZ@Xj}WAdn|uwZMfHM(9Ek{Dy!d z1QtB`9arz8Tl>jJXR#Jw({b*E*oA2$dLapZL%;z7kLRC#xn-|z4rEG-6$m&ba2pvk zw6r#%b{lc<0Rr|DICS5g>`#&mO9{BCa77dlx*`~Sgn-oq^7k0zays5_WHoh_$vH1@ zA$)``B*AY8SWY0AHkNw~EU)oL=^=0>bw=n)fUqf$z5>l<~usmidH>d1g<)tvPTiR zvRK#&f${{pcQxp7BSG7$eLlTi`9#=j5&^`mv|7&{weKzo*8(7Vr7+osKotV@_2zuV z9n5HXuu3W%(w_igC#Xi|{M8Cd7&8z^OQ3D))!Hvkd2#$%+Q*o-4i@(>2pl3Ou)v(r z351C$2xvi|ZQXJ%Uusc_xIrLpafwB2&lz1rP&9);NMQX_NAfEh!)@JuFMr|c-|H3= z@l+%bp1mP*6%nOBz!_b7hFEZsKrYwjd4YC+Kf&}d(k@x{n0?%F3D*L+I6~s&jL5~I zm9i~ocPRnV-Aw|)^yd3@%lzicM~>1hCgRzIKzKH{$pLoABcR>CPUWSFWe5l+(B9te zY4RlHr|&s^{AUL5C^$jP2_(Q7oIsdyO>ssSml*9K@DYKvQ^!oy0ml3(mySPhFGNm< zT9m{Pp1mP*C1A4NI1I{8Vwu*e}ACG4NXM zIHRjYNbT{zJvZVXtOZb8gUH^ePdev;$c;rd%&!K0CV%M?!4nBJwXN`MF0~;Hh$X<; zomie&bBw^C-M7sz%Jf%xe$X|`)aN~Z$%JjT>XD^Oe3d7_nOu1;dhKm!X>H;ZPp_H~ zje!J$8O;yo)h?Tx?t-<83-=eGbjk+`r*O(k5Iq2dE;=h(LZBjn4X^z6*HvsF;y}>0 zB$#pSBO**;BQXI4E-^ZF9T7Tp?D1?Ffw_OV`2L|ob`*adGk+F0?Y^&%&p*NWVat%2 zl#WFLci1_x=lu8k&(F6vb@~u-24c7u7YP!`D zvD2+065~PiB*Mis1R$Ut0R&FFlE@DTp_9)T4-kNWdIAWX`Wh&U0HITc9>)-XfMf!Q zn`FSIm`CVLsT6TS00L17AaGGhInvH*p*#AFPwgMB1#qOEqzM5CY#@NxF%rrWuv+xW zX3S0qK)^Nv2%K#d6`T)3Cm1N^AOL|(2q12mV79{vLT5)E$pQkd5J2EuskFd65jue| zF$Dn#NF$(?z}?k-=O-J3?0j5QoJPK=j11#1aG`P@4b(SDO(n1QW;!wr7pGVEUK|)+9qB z5KvE`AaIDB`Z_on0cUhhmYt*_kO={VEfdT*sYL*xt3`<85P(2j0tj4OVzie^pz@{5 z3DduSJ8J@IWUID~+81UQ?suB3J)RQc@At~?WG z!&zNobab?vKzR0s$k|K0EJMhIZ~ zl(Aqif#UIMa2kXTf#?Jfy6CK;vQ3Rrgy5w{>9m1x961ev0B4FsqWTj+^z^F{;-_CtR7Mnz2bD448v;fXK zUpT>Wwx@56WJMO%UY33ALcl2k2%Y}r4ktYXPybq_jWDcrT08WI00fE%Aaq4AH2)Y* zf(V}Gg-Q@!Sla|$=>h==)FOb;)gn|l{@C&}Z_{%vKsZ4neo^YEcbl-5dPhlg5HOqo zqGxUa!$}gsGpmMS-OP%bXdz%Q0ff%r;(Zi)@x+Pf>08g@I_VoFkwL&h0tlU5MGYrY z1W%v3hBeVAA|io+V+0U7I}098t_Yr*`iA_~c*mqiU2~F3ZHSE!wDI| z6H~g7rxWqk8d66|#%plO7O&fD!@-of7QYI1MLl zR|K!A%M-mttOW?UX;Vl7fdB-e5kT~!ak9Nt@g(kw07k88F`KA;Xa@lZXhZ;^)2JAl zgc45dt_xnsL6fo&1_U6GlmJ4Pl$`zj!inA`!3%S?zq(`y0fz`6bPg3eVXEQeZ?oWq zIVMCzHwZvLX95`r-L1ply;a0o0G%;97h5O{Hi=)DlXJx+VFqV z;Dzx!Q7n>#00cY_p<@&zB@haRDS{Wqn>1hgK>z}V5+py(ia#qrop-r4N#e*CuMSqtE7`4c9N=p_V4x2gm}!O2?uLcdjm z;~)ee;1&Ud&aHZ9ooy&QvIQ^nlXZUhg8&2!Ab`*rP$UaN424Lv;DvTAKtu`+W96oIH r#+|}GL>Y_-oG{!Nf`EGjhHdq^e!F~a@neO|M;>|1@COb*^_u?&{R;fK literal 0 HcmV?d00001 diff --git a/data/images/drop-local-songs.png b/data/images/drop-local-songs.png new file mode 100644 index 0000000000000000000000000000000000000000..587cbffe52e2fbb46f13588ef2c71acd35d929be GIT binary patch literal 1054090 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�Og@F^<}9Y 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 Nh8#M0#lQ*Y{y$H_f6@Q| literal 0 HcmV?d00001 diff --git a/data/images/drop-top-songs.png b/data/images/drop-top-songs.png new file mode 100644 index 0000000000000000000000000000000000000000..e12f91052474c8a8b0853af4d5c51a6b5c631f4a GIT binary patch literal 1054090 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 literal 0 HcmV?d00001 From e7cd5bddc46c79e39e940cf46ab6c22e6b30a16e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 01:35:15 +0200 Subject: [PATCH 17/72] more work on the drag and drop menu --- src/CMakeLists.txt | 3 + src/sourcetree/animationhelper.cpp | 116 ++++++++ src/sourcetree/animationhelper.h | 58 ++++ src/sourcetree/sourcedelegate.cpp | 417 +++++++++++++++++++++++------ src/sourcetree/sourcedelegate.h | 13 +- src/sourcetree/sourcetreeview.cpp | 27 +- src/sourcetree/sourcetreeview.h | 4 + 7 files changed, 544 insertions(+), 94 deletions(-) create mode 100644 src/sourcetree/animationhelper.cpp create mode 100644 src/sourcetree/animationhelper.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6a413d21..d6c8d0f81 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} sourcetree/sourcesproxymodel.cpp sourcetree/sourcetreeview.cpp sourcetree/sourcedelegate.cpp + sourcetree/animationhelper.cpp sourcetree/items/sourcetreeitem.cpp sourcetree/items/collectionitem.cpp sourcetree/items/playlistitems.cpp @@ -103,6 +104,8 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui} sourcetree/sourcesmodel.h sourcetree/sourcesproxymodel.h sourcetree/sourcetreeview.h + sourcetree/sourcedelegate.h + sourcetree/animationhelper.h sourcetree/items/sourcetreeitem.h sourcetree/items/collectionitem.h sourcetree/items/playlistitems.h diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp new file mode 100644 index 000000000..3b15b3a44 --- /dev/null +++ b/src/sourcetree/animationhelper.cpp @@ -0,0 +1,116 @@ +#include "animationhelper.h" + +#include "QDebug" + +AnimationHelper::AnimationHelper( const QModelIndex& index, QObject *parent ) + :QObject( parent ) + , m_index( index ) + , m_fullyExpanded( false ) + , m_expandAnimation( 0 ) + , m_forceClosing( false ) +{ + m_expandTimer.setSingleShot( true ); + m_expandTimer.setInterval( 1000 ); + connect( &m_expandTimer, SIGNAL(timeout()), SLOT(expandTimeout())); + + m_collapseTimer.setSingleShot( true ); + m_collapseTimer.setInterval( 1000 ); + connect( &m_collapseTimer, SIGNAL(timeout()), SLOT(collapseTimeout())); +} + +bool AnimationHelper::initialized() const +{ + return m_expandAnimation != 0; +} + +void AnimationHelper::initialize( const QSize& startValue, const QSize& endValue, int duration ) +{ + m_size = startValue; + m_targetSize = endValue; + m_startSize = startValue; + + m_expandAnimation = new QPropertyAnimation( this, "size", this ); + m_expandAnimation->setStartValue( startValue ); + m_expandAnimation->setEndValue( endValue ); + m_expandAnimation->setDuration( duration ); + m_expandAnimation->setEasingCurve( QEasingCurve::OutBounce ); + qDebug() << "starting animation" << startValue << endValue << duration; + connect( m_expandAnimation, SIGNAL( finished() ), SLOT(expandAnimationFinished())); + + m_collapseAnimation= new QPropertyAnimation( this, "size", this ); + m_collapseAnimation->setStartValue( endValue ); + m_collapseAnimation->setEndValue( startValue ); + m_collapseAnimation->setDuration( duration ); + m_collapseAnimation->setEasingCurve( QEasingCurve::OutBounce ); + connect( m_collapseAnimation, SIGNAL( finished() ), SLOT(collapseAnimationFinished())); + +} + +void AnimationHelper::setSize( const QSize& size ) +{ + m_size = size; + emit sizeChanged(); + qDebug() << "animaton setting size to" << size; +} + +void AnimationHelper::expand() +{ + m_collapseTimer.stop(); + m_expandTimer.start(); +} + +void AnimationHelper::collapse( bool immediately ) +{ + if ( immediately ) + { + m_fullyExpanded = false; + m_forceClosing = true; + m_collapseAnimation->start(); + } + else + { + //m_fullyExpanded = false; + m_expandTimer.stop(); + m_collapseTimer.start(); + } +} + +bool AnimationHelper::partlyExpanded() +{ + if ( m_forceClosing ) + return false; + + return m_fullyExpanded + || ( m_expandAnimation->state() == QPropertyAnimation::Running && m_expandAnimation->currentTime() > 250 ) + || ( m_collapseAnimation->state() == QPropertyAnimation::Running && m_collapseAnimation->currentTime() < 100 ); +} + +bool AnimationHelper::fullyExpanded() +{ + return m_fullyExpanded; +} + +void AnimationHelper::expandTimeout() +{ + m_expandAnimation->start(); +// m_fullyExpanded = true; +} + +void AnimationHelper::collapseTimeout() +{ +// m_size = m_startSize; + m_fullyExpanded = false; +// emit finished( m_index ); + m_collapseAnimation->start(); +} + +void AnimationHelper::expandAnimationFinished() +{ + m_fullyExpanded = true; +} + +void AnimationHelper::collapseAnimationFinished() +{ + m_fullyExpanded = false; + emit finished( m_index ); +} diff --git a/src/sourcetree/animationhelper.h b/src/sourcetree/animationhelper.h new file mode 100644 index 000000000..cc46013df --- /dev/null +++ b/src/sourcetree/animationhelper.h @@ -0,0 +1,58 @@ +#ifndef ANIMATIONHELPER_H +#define ANIMATIONHELPER_H + +#include +#include +#include +#include +#include + +class AnimationHelper: public QObject +{ + Q_OBJECT + Q_PROPERTY( QSize size READ size WRITE setSize NOTIFY sizeChanged ) + +public: + AnimationHelper( const QModelIndex& index, QObject *parent = 0 ); + + QSize originalSize() const { return m_startSize; } + QSize size() const { return m_size; } + + bool initialized() const; + void initialize( const QSize& startValue, const QSize& endValue, int duration ); + + void setSize( const QSize& size ); + + void expand(); + void collapse( bool immediately = false ); + + bool partlyExpanded(); + bool fullyExpanded(); + +signals: + void sizeChanged(); + void finished( const QModelIndex& index); + +private slots: + void expandTimeout(); + void collapseTimeout(); + void expandAnimationFinished(); + void collapseAnimationFinished(); + +private: + QModelIndex m_index; + QSize m_size; + QSize m_targetSize; + QSize m_startSize; + + QTimer m_expandTimer; + QTimer m_collapseTimer; + + bool m_fullyExpanded; + bool m_forceClosing; + + QPropertyAnimation *m_expandAnimation; + QPropertyAnimation *m_collapseAnimation; +}; + +#endif // ANIMATIONHELPER_H diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 17cbef2d3..a059a8504 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -7,6 +7,7 @@ #include "utils/tomahawkutils.h" #include "items/temporarypageitem.h" +#include "animationhelper.h" #include #include @@ -14,6 +15,12 @@ #define TREEVIEW_INDENT_ADD -7 +SourceDelegate::SourceDelegate( QAbstractItemView* parent ) + : QStyledItemDelegate( parent ) + , m_parent( parent ) +{ +} + QSize SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { @@ -21,11 +28,19 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::Collection ) return QSize( option.rect.width(), 44 ); - else if ( index == m_dropHoverIndex ) + else if ( m_expandedMap.contains( index ) ) { - QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); - qDebug() << "droptypecount is" << dropTypeCount( item ); - return originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); + if ( !m_expandedMap.value( index )->initialized() ) + { + qDebug() << "droptypecount is " << dropTypeCount( item ); + QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); +// QSize targetSize = originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); // useful for vertical menu + QSize targetSize = originalSize + QSize( 0, 56 ); + m_expandedMap.value( index )->initialize( originalSize, targetSize, 500 ); + m_expandedMap.value( index )->expand(); + } + QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) ); + return m_expandedMap.value( index )->size(); } else return QStyledItemDelegate::sizeHint( option, index ); @@ -141,105 +156,298 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } else if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) { + if ( !( m_expandedMap.contains( index) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) ) + { + QStyledItemDelegate::paint( painter, option, index ); + return; + } + + // Let Qt paint the original item. We add our stuff after it + QStyleOptionViewItem o = option; + o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() + 2 ); + QStyledItemDelegate::paint( painter, o, index ); + painter->save(); - QFont bold = painter->font(); - bold.setBold( true ); - QString name = index.data().toString(); - if ( type == SourcesModel::StaticPlaylist ) + // Get whole rect for the menu + QRect itemsRect = option.rect.adjusted( -option.rect.x(), m_expandedMap.value( index )->originalSize().height(), 0, 0 ); + + // draw the background + + QLinearGradient linearGradient( 0, 0, 0, itemsRect.height() ); + linearGradient.setColorAt( 0.0, QColor( 0xdb, 0x1b, 0x06 ) ); + linearGradient.setColorAt( 1.0, QColor( 0xf4, 0x17, 0x05 ) ); + painter->setBrush( linearGradient ); + painter->drawRect( itemsRect ); + + int totalCount = dropTypeCount( item ); + int itemWidth = itemsRect.width() / totalCount; + int iconSpacing = ( itemWidth - 32 ) / 2; + + // adjust to one single entry + itemsRect.adjust( 0, 0, -itemsRect.width() + itemWidth, 0 ); + + int count = 0; + + QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); + + QPen pen(Qt::white); + painter->setPen(pen); + + QFont font = painter->font(); + font.setPixelSize( 10 ); + painter->setFont( font ); + QFont fontBold = painter->font(); + fontBold.setBold( true ); + + QString text; + QRect textRect; + QPixmap icon = QPixmap( ":/data/images/new-additions.png" ).scaledToHeight( 32, Qt::SmoothTransformation ); + + QMap< int, SourceTreeItem::DropType > dropTypeMap; + dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); + dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum ); + dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist ); + dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems ); + dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 ); + + QMap< int, QString > dropTypeTextMap; + dropTypeTextMap.insert( 0, "Track" ); + dropTypeTextMap.insert( 1, "Album" ); + dropTypeTextMap.insert( 2, "Artist" ); + dropTypeTextMap.insert( 3, "Local" ); + dropTypeTextMap.insert( 4, "Top 10" ); + + SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); + + for ( int i = 0; i < 5; ++i ) { - PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); - Q_ASSERT( plItem ); + if ( !dropTypes.testFlag( dropTypeMap.value( i ) ) ) + continue; - if ( plItem && !plItem->playlist().isNull() ) + text = dropTypeTextMap.value( i ); + + if ( count > 0 ) + itemsRect.adjust( itemWidth, 0, itemWidth, 0 ); + + if ( itemsRect.contains( cursorPos ) ) { - name = plItem->playlist()->title(); + painter->setFont( fontBold ); + m_hoveredDropType = dropTypeMap.value( i ); } - } - else if ( type == SourcesModel::CategoryAdd ) - { - CategoryAddItem* cItem = qobject_cast< CategoryAddItem* >( item ); - Q_ASSERT( cItem ); + else + painter->setFont( font ); - name = cItem->text(); + textRect = itemsRect.adjusted( 0, 4, 0, 0 ); + painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); + + int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; + textRect.adjust( textSpacing, 32 + 4, 0, 0 ); + painter->drawText( textRect, text ); + count++; } - int height = option.rect.height(); - if ( index == m_dropHoverIndex ) - height /= ( dropTypeCount( item ) + 1 ); - QRect iconRect = option.rect.adjusted( 4, 1, -option.rect.width() + height - 2 + 4, -option.rect.height() + height -1 ); +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) +// { +// text = tr( "Track" ); - QPixmap avatar = index.data( Qt::DecorationRole ).value< QIcon >().pixmap( iconRect.width(), iconRect.height() ); - painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); +// itemsRect.adjust( itemWidth * count, 0, itemWidth * count, 0 ); +// if ( itemRect.contains( cursorPos ) ) +// { +// painter->setFont( fontBold ); +// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; +// } +// else +// painter->setFont( font ); - if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) - { - painter->setPen( o.palette.color( QPalette::HighlightedText ) ); - } +// textRect = itemsRect.adjusted( 0, 4, 0, 0 ); +// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - QRect textRect = option.rect.adjusted( iconRect.width() + 8, 2, /*-figWidth - 24*/ 0, 0 ); - QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); - painter->drawText( textRect, text ); +// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; +// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); +// painter->drawText( textRect, text ); +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) +// { +// text = tr( "Album" ); +// itemsRect.adjust( itemWidth * count, 0, itemWidth * count, 0 ); +// if ( itemRect.contains( cursorPos ) ) +// { +// painter->setFont( fontBold ); +// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; +// } +// else +// painter->setFont( font ); - if ( index == m_dropHoverIndex ) - { - QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); - int hoveredDropTypeIndex = ( cursorPos.y() - o.rect.y() ) / height; - int verticalOffset = height * hoveredDropTypeIndex; - QRect selectionRect = o.rect.adjusted( 0, verticalOffset, 0, -o.rect.height() + height + verticalOffset ); - painter->drawRoundedRect( selectionRect, 5, 5 ); +// textRect = itemsRect.adjusted( 0, 4, 0, 0 ); +// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - int count = 1; - SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); - if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) - { - text = tr( "This track" ); - textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); - painter->drawText( textRect, text ); - if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; - count++; - } - if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) - { - text = tr( "This album" ); - textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); - painter->drawText( textRect, text ); - if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeThisAlbum; - count++; - } - if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) - { - text = tr( "All from artist" ); - textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); - painter->drawText( textRect, text ); - if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; - count++; - } - if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) - { - text = tr( "All local from Artist" ); - textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); - painter->drawText( textRect, text ); - if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; - count++; - } - if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) - { - text = tr( "Top 50" ); - textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); - painter->drawText( textRect, text ); - if ( count == hoveredDropTypeIndex ) - m_hoveredDropType = SourceTreeItem::DropTypeTop50; - count++; - } - } +// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; +// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); +// painter->drawText( textRect, text ); +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) +// { +// text = tr( "Artist" ); +// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); +// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); + +// if ( itemRect.contains( cursorPos ) ) +// { +// painter->setFont( fontBold ); +// m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; +// } +// else +// painter->setFont( font ); + +// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; +// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); +// painter->drawText( textRect, text ); +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) +// { +// text = tr( "Local" ); +// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); +// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); + +// if ( itemRect.contains( cursorPos ) ) +// { +// painter->setFont( fontBold ); +// m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; +// } +// else +// painter->setFont( font ); + +// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; +// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); +// painter->drawText( textRect, text ); +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) +// { +// text = tr( "Top 10" ); +// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); +// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); + +// if ( itemRect.contains( cursorPos ) ) +// { +// painter->setFont( fontBold ); +// m_hoveredDropType = SourceTreeItem::DropTypeTop50; +// } +// else +// painter->setFont( font ); + +// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; +// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); +// painter->drawText( textRect, text ); +// count++; +// } + + + +// QFont bold = painter->font(); +// bold.setBold( true ); + +// QString name = index.data().toString(); +// if ( type == SourcesModel::StaticPlaylist ) +// { +// PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); +// Q_ASSERT( plItem ); + + +// if ( plItem && !plItem->playlist().isNull() ) +// { +// name = plItem->playlist()->title(); +// } +// } +// else if ( type == SourcesModel::CategoryAdd ) +// { +// CategoryAddItem* cItem = qobject_cast< CategoryAddItem* >( item ); +// Q_ASSERT( cItem ); + +// name = cItem->text(); +// } + +// int height = option.rect.height(); +// if ( m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() ) +// height /= ( dropTypeCount( item ) + 1 ); + +// QRect iconRect = option.rect.adjusted( 4, 1, -option.rect.width() + option.decorationSize.width() - 2 + 4, -option.rect.height() + option.decorationSize.height() -1 ); + +// QPixmap avatar = index.data( Qt::DecorationRole ).value< QIcon >().pixmap( iconRect.width(), iconRect.height() ); +// painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); + +// if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) +// { +// painter->setPen( o.palette.color( QPalette::HighlightedText ) ); +// } + +// QRect textRect = option.rect.adjusted( iconRect.width() + 8, 2, /*-figWidth - 24*/ 0, 0 ); +// QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); +// painter->drawText( textRect, text ); + +// if ( m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() ) +// { +// QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); +// int hoveredDropTypeIndex = ( cursorPos.y() - o.rect.y() ) / height; +// int verticalOffset = height * hoveredDropTypeIndex; +// QRect selectionRect = o.rect.adjusted( 0, verticalOffset, 0, -o.rect.height() + height + verticalOffset ); +// painter->drawRoundedRect( selectionRect, 5, 5 ); + +// int count = 1; +// SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) +// { +// text = tr( "This track" ); +// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); +// painter->drawText( textRect, text ); +// if ( count == hoveredDropTypeIndex ) +// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) +// { +// text = tr( "This album" ); +// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); +// painter->drawText( textRect, text ); +// if ( count == hoveredDropTypeIndex ) +// m_hoveredDropType = SourceTreeItem::DropTypeThisAlbum; +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) +// { +// text = tr( "All from artist" ); +// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); +// painter->drawText( textRect, text ); +// if ( count == hoveredDropTypeIndex ) +// m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) +// { +// text = tr( "All local from Artist" ); +// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); +// painter->drawText( textRect, text ); +// if ( count == hoveredDropTypeIndex ) +// m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; +// count++; +// } +// if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) +// { +// text = tr( "Top 50" ); +// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); +// painter->drawText( textRect, text ); +// if ( count == hoveredDropTypeIndex ) +// m_hoveredDropType = SourceTreeItem::DropTypeTop50; +// count++; +// } +// } painter->restore(); @@ -335,3 +543,40 @@ SourceDelegate::hoveredDropType() const { return m_hoveredDropType; } + +void +SourceDelegate::hovered(const QModelIndex &index, const QMimeData *mimeData) +{ + if ( !index.isValid() ) + { + return; + } + if ( !m_expandedMap.contains( index ) ) + { + foreach ( AnimationHelper *helper, m_expandedMap ) + { + helper->collapse(); + } + + m_newDropHoverIndex = index; + m_dropMimeData = const_cast< QMimeData* >( mimeData ); + m_expandedMap.insert( m_newDropHoverIndex, new AnimationHelper( m_newDropHoverIndex ) ); + connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) ); + + } +} + +void +SourceDelegate::dragLeaveEvent() +{ + foreach ( AnimationHelper *helper, m_expandedMap ) + { + helper->collapse( true ); + } +} + +void +SourceDelegate::animationFinished( const QModelIndex& index ) +{ + delete m_expandedMap.take( index ); +} diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index 124b8c3c7..e764b9340 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -5,13 +5,18 @@ #include "items/sourcetreeitem.h" #include +#include + +class AnimationHelper; class SourceDelegate : public QStyledItemDelegate { + Q_OBJECT public: - SourceDelegate( QAbstractItemView* parent = 0 ) : QStyledItemDelegate( parent ), m_parent( parent ) {} + SourceDelegate( QAbstractItemView* parent = 0 ); - void setDropHoverIndex( const QModelIndex &index, const QMimeData *mimeData ) { m_dropHoverIndex = index; m_dropMimeData = const_cast< QMimeData* >( mimeData ); } + void hovered( const QModelIndex &index, const QMimeData *mimeData ); + void dragLeaveEvent(); SourceTreeItem::DropType hoveredDropType() const; @@ -22,12 +27,16 @@ protected: virtual int dropTypeCount( SourceTreeItem* item ) const; virtual bool editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index ); +private slots: + void animationFinished( const QModelIndex& ); private: QAbstractItemView* m_parent; mutable int m_iconHeight; QModelIndex m_dropHoverIndex; + QModelIndex m_newDropHoverIndex; QMimeData *m_dropMimeData; mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() + QMap< QModelIndex, AnimationHelper* > m_expandedMap; }; #endif // SOURCEDELEGATE_H diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index bd40aa415..c7bc2c7c7 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -439,7 +439,7 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); - m_delegate->setDropHoverIndex( QModelIndex(), 0 ); + m_delegate->dragLeaveEvent(); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex(); } @@ -456,7 +456,6 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); - m_delegate->setDropHoverIndex( QModelIndex(), event->mimeData() ); dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex( index ); @@ -469,7 +468,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) if( item->willAcceptDrag( event->mimeData() ) ) { accept = true; - m_delegate->setDropHoverIndex( index, event->mimeData() ); + m_delegate->hovered( index, event->mimeData() ); dataChanged(index, index); } } @@ -497,7 +496,7 @@ SourceTreeView::dropEvent( QDropEvent* event ) const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); - if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::PlaylistsCategory ) + if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist ) { PlaylistItem* item = itemFromIndex< PlaylistItem >( index ); Q_ASSERT( item ); @@ -506,10 +505,21 @@ SourceTreeView::dropEvent( QDropEvent* event ) qDebug() << "dropType is " << m_delegate->hoveredDropType(); } - QTreeView::dropEvent( event ); + // Need to fake the dropevent because the treeview would reject it if it is outside the item (on the tree) + if ( pos.x() < 100 ) + { + QDropEvent* newEvent = new QDropEvent( pos + QPoint( 100, 0 ), event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type() ); + QTreeView::dropEvent( newEvent ); + delete newEvent; + } + else + { + QTreeView::dropEvent( event ); + } + m_dragging = false; m_dropIndex = QPersistentModelIndex(); - m_delegate->setDropHoverIndex( QModelIndex(), 0 ); + m_delegate->dragLeaveEvent(); dataChanged( index, index ); } @@ -573,3 +583,8 @@ SourceTreeView::itemFromIndex( const QModelIndex& index ) const return item; } +void +SourceTreeView::update( const QModelIndex &index ) +{ + dataChanged( index, index ); +} diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 8bba33076..6a60e07cf 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -24,6 +24,7 @@ #include "source.h" #include "sourcetree/sourcesmodel.h" +#include "sourcetree/sourcedelegate.h" class CollectionModel; class PlaylistModel; @@ -42,6 +43,9 @@ public slots: void showOfflineSources( bool offlineSourcesShown ); void renamePlaylist(); + + void update( const QModelIndex &index ); + signals: void onOnline( const QModelIndex& index ); void onOffline( const QModelIndex& index ); From 71a5888cba36aeacc0b72be1206345794d6f167d Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 01:44:42 +0200 Subject: [PATCH 18/72] make use of new icons --- resources.qrc | 3 + src/sourcetree/sourcedelegate.cpp | 112 +++--------------------------- 2 files changed, 12 insertions(+), 103 deletions(-) diff --git a/resources.qrc b/resources.qrc index 2b2e4e459..9ed648556 100644 --- a/resources.qrc +++ b/resources.qrc @@ -109,5 +109,8 @@ data/sql/dbmigrate-25_to_26.sql data/js/tomahawk.js data/images/avatar_frame.png + data/images/drop-all-songs.png + data/images/drop-local-songs.png + data/images/drop-top-songs.png diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index a059a8504..70f0429ba 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -203,7 +203,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co QString text; QRect textRect; - QPixmap icon = QPixmap( ":/data/images/new-additions.png" ).scaledToHeight( 32, Qt::SmoothTransformation ); + QPixmap icon; QMap< int, SourceTreeItem::DropType > dropTypeMap; dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); @@ -219,6 +219,13 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co dropTypeTextMap.insert( 3, "Local" ); dropTypeTextMap.insert( 4, "Top 10" ); + QMap< int, QString > dropTypeImageMap; + dropTypeImageMap.insert( 0, ":/data/images/new-additions.png" ); + dropTypeImageMap.insert( 1, ":/data/images/new-additions.png" ); + dropTypeImageMap.insert( 2, ":/data/images/drop-all-songs.png" ); + dropTypeImageMap.insert( 3, ":/data/images/drop-local-songs.png" ); + dropTypeImageMap.insert( 4, ":/data/images/drop-top-songs.png" ); + SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); for ( int i = 0; i < 5; ++i ) @@ -241,7 +248,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->setFont( font ); textRect = itemsRect.adjusted( 0, 4, 0, 0 ); - painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); + painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), QPixmap( dropTypeImageMap.value( i ) ).scaledToWidth( 32, Qt::SmoothTransformation ) ); int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; textRect.adjust( textSpacing, 32 + 4, 0, 0 ); @@ -250,107 +257,6 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) -// { -// text = tr( "Track" ); - -// itemsRect.adjust( itemWidth * count, 0, itemWidth * count, 0 ); -// if ( itemRect.contains( cursorPos ) ) -// { -// painter->setFont( fontBold ); -// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; -// } -// else -// painter->setFont( font ); - -// textRect = itemsRect.adjusted( 0, 4, 0, 0 ); -// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - -// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; -// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); -// painter->drawText( textRect, text ); -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) -// { -// text = tr( "Album" ); -// itemsRect.adjust( itemWidth * count, 0, itemWidth * count, 0 ); -// if ( itemRect.contains( cursorPos ) ) -// { -// painter->setFont( fontBold ); -// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; -// } -// else -// painter->setFont( font ); - -// textRect = itemsRect.adjusted( 0, 4, 0, 0 ); -// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - -// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; -// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); -// painter->drawText( textRect, text ); -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) -// { -// text = tr( "Artist" ); -// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); -// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - -// if ( itemRect.contains( cursorPos ) ) -// { -// painter->setFont( fontBold ); -// m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; -// } -// else -// painter->setFont( font ); - -// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; -// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); -// painter->drawText( textRect, text ); -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) -// { -// text = tr( "Local" ); -// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); -// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - -// if ( itemRect.contains( cursorPos ) ) -// { -// painter->setFont( fontBold ); -// m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; -// } -// else -// painter->setFont( font ); - -// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; -// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); -// painter->drawText( textRect, text ); -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) -// { -// text = tr( "Top 10" ); -// textRect = itemsRect.adjusted( itemWidth * count, 4, itemWidth * count, 0 ); -// painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), icon ); - -// if ( itemRect.contains( cursorPos ) ) -// { -// painter->setFont( fontBold ); -// m_hoveredDropType = SourceTreeItem::DropTypeTop50; -// } -// else -// painter->setFont( font ); - -// int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; -// textRect.adjust( textSpacing, 32 + 4, 0, 0 ); -// painter->drawText( textRect, text ); -// count++; -// } - - - // QFont bold = painter->font(); // bold.setBold( true ); From fbefe4185f71e1abc22674034f4676fe372589ea Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 21:02:46 +0200 Subject: [PATCH 19/72] fixes in dragndrop menu --- src/sourcetree/animationhelper.cpp | 12 +++-- src/sourcetree/sourcedelegate.cpp | 77 ++++++++++++++++-------------- src/sourcetree/sourcedelegate.h | 5 ++ src/sourcetree/sourcetreeview.cpp | 4 ++ 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp index 3b15b3a44..3e8c79a4c 100644 --- a/src/sourcetree/animationhelper.cpp +++ b/src/sourcetree/animationhelper.cpp @@ -61,17 +61,19 @@ void AnimationHelper::expand() void AnimationHelper::collapse( bool immediately ) { + m_fullyExpanded = false; + m_expandTimer.stop(); + if ( immediately ) { - m_fullyExpanded = false; m_forceClosing = true; - m_collapseAnimation->start(); + if ( m_size != m_startSize ) + m_collapseAnimation->start(); } else { - //m_fullyExpanded = false; - m_expandTimer.stop(); - m_collapseTimer.start(); + if ( m_size != m_startSize ) + m_collapseTimer.start(); } } diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 70f0429ba..59d998653 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -19,6 +19,23 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) : QStyledItemDelegate( parent ) , m_parent( parent ) { + m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); + m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum ); + m_dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist ); + m_dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems ); + m_dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 ); + + m_dropTypeTextMap.insert( 0, "Track" ); + m_dropTypeTextMap.insert( 1, "Album" ); + m_dropTypeTextMap.insert( 2, "Artist" ); + m_dropTypeTextMap.insert( 3, "Local" ); + m_dropTypeTextMap.insert( 4, "Top 10" ); + + m_dropTypeImageMap.insert( 0, QPixmap( ":/data/images/new-additions.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 1, QPixmap( ":/data/images/new-additions.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 2, QPixmap( ":/data/images/drop-all-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 3, QPixmap( ":/data/images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 4, QPixmap( ":/data/images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); } QSize @@ -169,18 +186,24 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->save(); - // Get whole rect for the menu QRect itemsRect = option.rect.adjusted( -option.rect.x(), m_expandedMap.value( index )->originalSize().height(), 0, 0 ); + QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); + bool cursorInRect = false; + if ( itemsRect.contains( cursorPos ) ) + cursorInRect = true; + // draw the background - QLinearGradient linearGradient( 0, 0, 0, itemsRect.height() ); + QLinearGradient linearGradient( itemsRect.topLeft(), itemsRect.bottomLeft() ); linearGradient.setColorAt( 0.0, QColor( 0xdb, 0x1b, 0x06 ) ); - linearGradient.setColorAt( 1.0, QColor( 0xf4, 0x17, 0x05 ) ); +// linearGradient.setColorAt( 1.0, QColor( 0xf4, 0x17, 0x05 ) ); + linearGradient.setColorAt( 1.0, Qt::black ); painter->setBrush( linearGradient ); painter->drawRect( itemsRect ); + // calculate sizes for the icons int totalCount = dropTypeCount( item ); int itemWidth = itemsRect.width() / totalCount; int iconSpacing = ( itemWidth - 32 ) / 2; @@ -190,8 +213,6 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co int count = 0; - QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); - QPen pen(Qt::white); painter->setPen(pen); @@ -201,58 +222,34 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co QFont fontBold = painter->font(); fontBold.setBold( true ); - QString text; QRect textRect; - QPixmap icon; - - QMap< int, SourceTreeItem::DropType > dropTypeMap; - dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); - dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum ); - dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist ); - dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems ); - dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 ); - - QMap< int, QString > dropTypeTextMap; - dropTypeTextMap.insert( 0, "Track" ); - dropTypeTextMap.insert( 1, "Album" ); - dropTypeTextMap.insert( 2, "Artist" ); - dropTypeTextMap.insert( 3, "Local" ); - dropTypeTextMap.insert( 4, "Top 10" ); - - QMap< int, QString > dropTypeImageMap; - dropTypeImageMap.insert( 0, ":/data/images/new-additions.png" ); - dropTypeImageMap.insert( 1, ":/data/images/new-additions.png" ); - dropTypeImageMap.insert( 2, ":/data/images/drop-all-songs.png" ); - dropTypeImageMap.insert( 3, ":/data/images/drop-local-songs.png" ); - dropTypeImageMap.insert( 4, ":/data/images/drop-top-songs.png" ); SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); for ( int i = 0; i < 5; ++i ) { - if ( !dropTypes.testFlag( dropTypeMap.value( i ) ) ) + if ( !dropTypes.testFlag( m_dropTypeMap.value( i ) ) ) continue; - text = dropTypeTextMap.value( i ); - if ( count > 0 ) itemsRect.adjust( itemWidth, 0, itemWidth, 0 ); - if ( itemsRect.contains( cursorPos ) ) + if ( itemsRect.contains( cursorPos ) | !cursorInRect ) { painter->setFont( fontBold ); - m_hoveredDropType = dropTypeMap.value( i ); + m_hoveredDropType = m_dropTypeMap.value( i ); + cursorInRect = true; } else painter->setFont( font ); textRect = itemsRect.adjusted( 0, 4, 0, 0 ); - painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), QPixmap( dropTypeImageMap.value( i ) ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), m_dropTypeImageMap.value( i ) ); - int textSpacing = ( itemWidth - painter->fontMetrics().width( text ) ) / 2; - textRect.adjust( textSpacing, 32 + 4, 0, 0 ); - painter->drawText( textRect, text ); + int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2; + textRect.adjust( textSpacing, 32 + 6, 0, 0 ); + painter->drawText( textRect, m_dropTypeTextMap.value( i ) ); count++; } @@ -455,6 +452,10 @@ SourceDelegate::hovered(const QModelIndex &index, const QMimeData *mimeData) { if ( !index.isValid() ) { + foreach ( AnimationHelper *helper, m_expandedMap ) + { + helper->collapse(); + } return; } if ( !m_expandedMap.contains( index ) ) @@ -470,6 +471,8 @@ SourceDelegate::hovered(const QModelIndex &index, const QMimeData *mimeData) connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) ); } + else + qDebug() << "expandedMap already contains index" << index; } void diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index e764b9340..2c2313cb9 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -37,6 +37,11 @@ private: QMimeData *m_dropMimeData; mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() QMap< QModelIndex, AnimationHelper* > m_expandedMap; + + QMap< int, SourceTreeItem::DropType > m_dropTypeMap; + QMap< int, QString > m_dropTypeTextMap; + QMap< int, QPixmap > m_dropTypeImageMap; + }; #endif // SOURCEDELEGATE_H diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index c7bc2c7c7..bff8ac3b4 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -471,6 +471,8 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) m_delegate->hovered( index, event->mimeData() ); dataChanged(index, index); } + else + m_delegate->hovered( QModelIndex(), 0 ); } else { @@ -586,5 +588,7 @@ SourceTreeView::itemFromIndex( const QModelIndex& index ) const void SourceTreeView::update( const QModelIndex &index ) { +// updateGeometries(); +// QTreeView::update( index ); dataChanged( index, index ); } From 559433162f26b3237ccbede24e4ff2fdfa21fb9b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 21:08:23 +0200 Subject: [PATCH 20/72] make top 10 really top 10 instead of top 50 --- src/libtomahawk/dropjob.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 95af9f038..76c5eecd0 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -448,10 +448,15 @@ DropJob::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVar qDebug() << "Got requestData response for artist" << artist << output; QList< query_ptr > results; + + int i = 0; foreach ( const QVariant& title, output.toMap().value( "tracks" ).toList() ) { qDebug() << "got title" << title; results << Query::get( artist, title.toString(), QString(), uuid() ); + + if ( ++i == 10 ) // Only getting top ten for now. Would make sense to make it configurable + break; } onTracksAdded( results ); From 74fa9411599c1426680d215fefd7ef7398ed0a57 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 21:28:52 +0200 Subject: [PATCH 21/72] fix bug where non expanded items were tracked expanded ones --- src/sourcetree/animationhelper.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp index 3e8c79a4c..42b496e64 100644 --- a/src/sourcetree/animationhelper.cpp +++ b/src/sourcetree/animationhelper.cpp @@ -61,20 +61,21 @@ void AnimationHelper::expand() void AnimationHelper::collapse( bool immediately ) { - m_fullyExpanded = false; - m_expandTimer.stop(); + if ( m_expandTimer.isActive() ) + { + m_expandTimer.stop(); + emit finished( m_index ); + return; + } if ( immediately ) { m_forceClosing = true; - if ( m_size != m_startSize ) - m_collapseAnimation->start(); + m_fullyExpanded = false; + m_collapseAnimation->start(); } else - { - if ( m_size != m_startSize ) - m_collapseTimer.start(); - } + m_collapseTimer.start(); } bool AnimationHelper::partlyExpanded() @@ -95,14 +96,11 @@ bool AnimationHelper::fullyExpanded() void AnimationHelper::expandTimeout() { m_expandAnimation->start(); -// m_fullyExpanded = true; } void AnimationHelper::collapseTimeout() { -// m_size = m_startSize; - m_fullyExpanded = false; -// emit finished( m_index ); + m_fullyExpanded = false; m_collapseAnimation->start(); } @@ -113,6 +111,5 @@ void AnimationHelper::expandAnimationFinished() void AnimationHelper::collapseAnimationFinished() { - m_fullyExpanded = false; emit finished( m_index ); } From f1dfd8421303a2e34fe30589d8f0dce42fc63ed3 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 22:52:31 +0200 Subject: [PATCH 22/72] make the dropmenu work with categoryitems --- src/sourcetree/items/categoryitems.cpp | 34 ++++++++++++++++++++++++-- src/sourcetree/sourcedelegate.cpp | 5 ++-- src/sourcetree/sourcetreeview.cpp | 5 ++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index 70136e4c7..00cd45f57 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -142,7 +142,21 @@ CategoryAddItem::willAcceptDrag( const QMimeData* data ) const SourceTreeItem::DropTypes CategoryAddItem::supportedDropTypes( const QMimeData* data ) const { - return DropTypesNone; + SourceTreeItem::DropTypes types = DropTypesNone; + + if ( m_categoryType == SourcesModel::PlaylistsCategory ) + { + if ( data->hasFormat( "application/tomahawk.query.list" ) ) + return types | DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.result.list" ) ) + return types | DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + return types | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + return types | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + } + + return types; } @@ -237,7 +251,23 @@ CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction ) // Create a new playlist seeded with these items DropJob *dj = new DropJob(); connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); - dj->tracksFromMimeData( data ); + if ( dropType() == DropTypeAllFromArtist ) + dj->setGetWholeArtists( true ); + if ( dropType() == DropTypeThisAlbum ) + dj->setGetWholeAlbums( true ); + + if ( dropType() == DropTypeLocalItems ) + { + dj->setGetWholeArtists( true ); + dj->tracksFromMimeData( data, false, true ); + } + else if ( dropType() == DropTypeTop50 ) + { + dj->setGetWholeArtists( true ); + dj->tracksFromMimeData( data, false, false, true ); + } + else + dj->tracksFromMimeData( data, false, false ); return true; } diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 59d998653..c02764cc7 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -49,10 +49,11 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& { if ( !m_expandedMap.value( index )->initialized() ) { - qDebug() << "droptypecount is " << dropTypeCount( item ); + int dropTypes = dropTypeCount( item ); + qDebug() << "droptypecount is " << dropTypes; QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); // QSize targetSize = originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); // useful for vertical menu - QSize targetSize = originalSize + QSize( 0, 56 ); + QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 56 ); m_expandedMap.value( index )->initialize( originalSize, targetSize, 500 ); m_expandedMap.value( index )->expand(); } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index bff8ac3b4..d08e9152e 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -498,9 +498,10 @@ SourceTreeView::dropEvent( QDropEvent* event ) const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); - if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist ) + if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist + || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::CategoryAdd ) { - PlaylistItem* item = itemFromIndex< PlaylistItem >( index ); + SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index ); Q_ASSERT( item ); item->setDropType( m_delegate->hoveredDropType() ); From 2c5040c638e0f1f876d8f4919caa1a1be18ee5e9 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 22 Aug 2011 23:38:42 +0200 Subject: [PATCH 23/72] improved animation --- src/sourcetree/animationhelper.cpp | 7 ++++--- src/sourcetree/sourcedelegate.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp index 42b496e64..c49f58e74 100644 --- a/src/sourcetree/animationhelper.cpp +++ b/src/sourcetree/animationhelper.cpp @@ -83,9 +83,10 @@ bool AnimationHelper::partlyExpanded() if ( m_forceClosing ) return false; - return m_fullyExpanded - || ( m_expandAnimation->state() == QPropertyAnimation::Running && m_expandAnimation->currentTime() > 250 ) - || ( m_collapseAnimation->state() == QPropertyAnimation::Running && m_collapseAnimation->currentTime() < 100 ); + return m_size != m_startSize; +// return m_fullyExpanded +// || ( m_expandAnimation->state() == QPropertyAnimation::Running && m_expandAnimation->currentTime() > 250 ) +// || ( m_collapseAnimation->state() == QPropertyAnimation::Running && m_collapseAnimation->currentTime() < 100 ); } bool AnimationHelper::fullyExpanded() diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index c02764cc7..a5c40182f 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -246,7 +246,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->setFont( font ); textRect = itemsRect.adjusted( 0, 4, 0, 0 ); - painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), m_dropTypeImageMap.value( i ) ); + painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 0, 32, qMax( 4, textRect.height() ) ) ); int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2; textRect.adjust( textSpacing, 32 + 6, 0, 0 ); From b1ed8d241909d6fa5c4c649788740ab4313be272 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Mon, 22 Aug 2011 21:20:25 -0400 Subject: [PATCH 24/72] Add drop zone icons for song and album. --- data/images/drop-album.png | Bin 0 -> 1054090 bytes data/images/drop-song.png | Bin 0 -> 1054090 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/images/drop-album.png create mode 100644 data/images/drop-song.png diff --git a/data/images/drop-album.png b/data/images/drop-album.png new file mode 100644 index 0000000000000000000000000000000000000000..bd4ff88e517a0e41e91cbc0b8cfc0e685df7dc17 GIT binary patch literal 1054090 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 literal 0 HcmV?d00001 diff --git a/data/images/drop-song.png b/data/images/drop-song.png new file mode 100644 index 0000000000000000000000000000000000000000..582ded3272261835141e69e9d58a018944e01c29 GIT binary patch literal 1054090 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 Date: Tue, 23 Aug 2011 07:59:23 +0200 Subject: [PATCH 25/72] make use of new drop icons --- resources.qrc | 2 ++ src/sourcetree/sourcedelegate.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources.qrc b/resources.qrc index 9ed648556..7a0bc3272 100644 --- a/resources.qrc +++ b/resources.qrc @@ -112,5 +112,7 @@ data/images/drop-all-songs.png data/images/drop-local-songs.png data/images/drop-top-songs.png + data/images/drop-song.png + data/images/drop-album.png diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index a5c40182f..229aed497 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -31,8 +31,8 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) m_dropTypeTextMap.insert( 3, "Local" ); m_dropTypeTextMap.insert( 4, "Top 10" ); - m_dropTypeImageMap.insert( 0, QPixmap( ":/data/images/new-additions.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); - m_dropTypeImageMap.insert( 1, QPixmap( ":/data/images/new-additions.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 0, QPixmap( ":/data/images/drop-song.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 1, QPixmap( ":/data/images/drop-album.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 2, QPixmap( ":/data/images/drop-all-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 3, QPixmap( ":/data/images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 4, QPixmap( ":/data/images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); From a7d1275e52cf14aad675746b37eb3e507da5fcc8 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 23 Aug 2011 08:29:25 +0200 Subject: [PATCH 26/72] replace BounceAnimaton with ExpoAnimation --- src/sourcetree/animationhelper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp index c49f58e74..b40f30c74 100644 --- a/src/sourcetree/animationhelper.cpp +++ b/src/sourcetree/animationhelper.cpp @@ -33,7 +33,7 @@ void AnimationHelper::initialize( const QSize& startValue, const QSize& endValue m_expandAnimation->setStartValue( startValue ); m_expandAnimation->setEndValue( endValue ); m_expandAnimation->setDuration( duration ); - m_expandAnimation->setEasingCurve( QEasingCurve::OutBounce ); + m_expandAnimation->setEasingCurve( QEasingCurve::OutExpo ); qDebug() << "starting animation" << startValue << endValue << duration; connect( m_expandAnimation, SIGNAL( finished() ), SLOT(expandAnimationFinished())); @@ -41,7 +41,7 @@ void AnimationHelper::initialize( const QSize& startValue, const QSize& endValue m_collapseAnimation->setStartValue( endValue ); m_collapseAnimation->setEndValue( startValue ); m_collapseAnimation->setDuration( duration ); - m_collapseAnimation->setEasingCurve( QEasingCurve::OutBounce ); + m_collapseAnimation->setEasingCurve( QEasingCurve::InExpo ); connect( m_collapseAnimation, SIGNAL( finished() ), SLOT(collapseAnimationFinished())); } From f0a05acf01a9e866bfdbc1071c5f1561c3e445f5 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 23 Aug 2011 08:36:31 +0200 Subject: [PATCH 27/72] fix off-by-one painting bug in dropmenu --- src/sourcetree/sourcedelegate.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 229aed497..5de5ef138 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -174,7 +174,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } else if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) { - if ( !( m_expandedMap.contains( index) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) ) + if ( !m_expandedMap.contains( index) ) { QStyledItemDelegate::paint( painter, option, index ); return; @@ -182,9 +182,12 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co // Let Qt paint the original item. We add our stuff after it QStyleOptionViewItem o = option; - o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() + 2 ); + o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); QStyledItemDelegate::paint( painter, o, index ); + if ( !( m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) ) + return; + painter->save(); // Get whole rect for the menu From 2e3e0afa7cdda551ba945133db409d59e060ccdf Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Tue, 23 Aug 2011 15:04:05 -0400 Subject: [PATCH 28/72] Replace all songs drop zone icon with something that is more square. --- data/images/drop-all-songs.png | Bin 903825 -> 1054090 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/images/drop-all-songs.png b/data/images/drop-all-songs.png index f5ef7944fcd4f2b031a9f041ad48d1803818827d..5e743fa5b8c8c700f80fde5b21c6228cddf09a0a 100644 GIT binary patch literal 1054090 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 L5qIo##2Nn&eW^^q}HNs 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 From 9cc919eb0871045927dad739a69321448bcd0b3f Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 23 Aug 2011 21:09:09 +0200 Subject: [PATCH 29/72] improved the animation --- src/sourcetree/sourcedelegate.cpp | 116 ++++-------------------------- 1 file changed, 12 insertions(+), 104 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 5de5ef138..a5f501dc3 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -33,7 +33,7 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) m_dropTypeImageMap.insert( 0, QPixmap( ":/data/images/drop-song.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 1, QPixmap( ":/data/images/drop-album.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); - m_dropTypeImageMap.insert( 2, QPixmap( ":/data/images/drop-all-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + m_dropTypeImageMap.insert( 2, QPixmap( ":/data/images/drop-all-songs.png" ).scaledToHeight( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 3, QPixmap( ":/data/images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 4, QPixmap( ":/data/images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); } @@ -52,7 +52,6 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& int dropTypes = dropTypeCount( item ); qDebug() << "droptypecount is " << dropTypes; QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); -// QSize targetSize = originalSize + QSize( 0, originalSize.height() * dropTypeCount( item ) ); // useful for vertical menu QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 56 ); m_expandedMap.value( index )->initialize( originalSize, targetSize, 500 ); m_expandedMap.value( index )->expand(); @@ -227,6 +226,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co fontBold.setBold( true ); QRect textRect; + QRect imageRect; SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); @@ -248,114 +248,22 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co else painter->setFont( font ); - textRect = itemsRect.adjusted( 0, 4, 0, 0 ); - painter->drawPixmap( textRect.x() + iconSpacing, textRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 0, 32, qMax( 4, textRect.height() ) ) ); - int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2; - textRect.adjust( textSpacing, 32 + 6, 0, 0 ); + textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 4, 0, 0 ); painter->drawText( textRect, m_dropTypeTextMap.value( i ) ); + + int maxHeight = itemsRect.height() - textRect.height() - 4; + int verticalOffset = qMax( 0, maxHeight - 32 ); + if ( itemsRect.bottom() - textRect.height() - 2 > itemsRect.top() ) + { + imageRect = itemsRect.adjusted( iconSpacing, verticalOffset, -iconSpacing, -textRect.height() - 2 ); + painter->drawPixmap( imageRect.x(), imageRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 32 - imageRect.height(), 32, imageRect.height() ) ); + } + count++; } -// QFont bold = painter->font(); -// bold.setBold( true ); - -// QString name = index.data().toString(); -// if ( type == SourcesModel::StaticPlaylist ) -// { -// PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); -// Q_ASSERT( plItem ); - - -// if ( plItem && !plItem->playlist().isNull() ) -// { -// name = plItem->playlist()->title(); -// } -// } -// else if ( type == SourcesModel::CategoryAdd ) -// { -// CategoryAddItem* cItem = qobject_cast< CategoryAddItem* >( item ); -// Q_ASSERT( cItem ); - -// name = cItem->text(); -// } - -// int height = option.rect.height(); -// if ( m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() ) -// height /= ( dropTypeCount( item ) + 1 ); - -// QRect iconRect = option.rect.adjusted( 4, 1, -option.rect.width() + option.decorationSize.width() - 2 + 4, -option.rect.height() + option.decorationSize.height() -1 ); - -// QPixmap avatar = index.data( Qt::DecorationRole ).value< QIcon >().pixmap( iconRect.width(), iconRect.height() ); -// painter->drawPixmap( iconRect, avatar.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ) ); - -// if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected ) -// { -// painter->setPen( o.palette.color( QPalette::HighlightedText ) ); -// } - -// QRect textRect = option.rect.adjusted( iconRect.width() + 8, 2, /*-figWidth - 24*/ 0, 0 ); -// QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() ); -// painter->drawText( textRect, text ); - -// if ( m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() ) -// { -// QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() ); -// int hoveredDropTypeIndex = ( cursorPos.y() - o.rect.y() ) / height; -// int verticalOffset = height * hoveredDropTypeIndex; -// QRect selectionRect = o.rect.adjusted( 0, verticalOffset, 0, -o.rect.height() + height + verticalOffset ); -// painter->drawRoundedRect( selectionRect, 5, 5 ); - -// int count = 1; -// SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisTrack ) ) -// { -// text = tr( "This track" ); -// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); -// painter->drawText( textRect, text ); -// if ( count == hoveredDropTypeIndex ) -// m_hoveredDropType = SourceTreeItem::DropTypeThisTrack; -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeThisAlbum ) ) -// { -// text = tr( "This album" ); -// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); -// painter->drawText( textRect, text ); -// if ( count == hoveredDropTypeIndex ) -// m_hoveredDropType = SourceTreeItem::DropTypeThisAlbum; -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeAllFromArtist ) ) -// { -// text = tr( "All from artist" ); -// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); -// painter->drawText( textRect, text ); -// if ( count == hoveredDropTypeIndex ) -// m_hoveredDropType = SourceTreeItem::DropTypeAllFromArtist; -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeLocalItems ) ) -// { -// text = tr( "All local from Artist" ); -// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); -// painter->drawText( textRect, text ); -// if ( count == hoveredDropTypeIndex ) -// m_hoveredDropType = SourceTreeItem::DropTypeLocalItems; -// count++; -// } -// if ( dropTypes.testFlag( SourceTreeItem::DropTypeTop50 ) ) -// { -// text = tr( "Top 50" ); -// textRect = option.rect.adjusted( iconRect.width() + 8, 2 + ( count * height ), 0, 0 ); -// painter->drawText( textRect, text ); -// if ( count == hoveredDropTypeIndex ) -// m_hoveredDropType = SourceTreeItem::DropTypeTop50; -// count++; -// } -// } - painter->restore(); } From 39af693de1f95ff6b34a5d798438778bdba645bc Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Aug 2011 00:06:09 +0200 Subject: [PATCH 30/72] remove styleoption selected before painting the text to avoid two selection boxes --- src/sourcetree/sourcedelegate.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index a5f501dc3..0a2fbdfac 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -173,14 +173,16 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } else if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) { + o.state &= QStyle::State_Selected; + o.showDecorationSelected = false; if ( !m_expandedMap.contains( index) ) { - QStyledItemDelegate::paint( painter, option, index ); + QStyledItemDelegate::paint( painter, o, index ); return; } // Let Qt paint the original item. We add our stuff after it - QStyleOptionViewItem o = option; +// QStyleOptionViewItem o = option; o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); QStyledItemDelegate::paint( painter, o, index ); From 52037d88470caff53dd4a3a51d21010ce1735662 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Aug 2011 00:29:59 +0200 Subject: [PATCH 31/72] cache the mimedata so we can paint the items after the dropLeaveEvent and made animations a bit faster --- src/sourcetree/animationhelper.cpp | 9 ++------- src/sourcetree/animationhelper.h | 1 - src/sourcetree/sourcedelegate.cpp | 16 ++++++++++++++-- src/sourcetree/sourcedelegate.h | 1 + 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp index b40f30c74..8efa25bf8 100644 --- a/src/sourcetree/animationhelper.cpp +++ b/src/sourcetree/animationhelper.cpp @@ -7,14 +7,13 @@ AnimationHelper::AnimationHelper( const QModelIndex& index, QObject *parent ) , m_index( index ) , m_fullyExpanded( false ) , m_expandAnimation( 0 ) - , m_forceClosing( false ) { m_expandTimer.setSingleShot( true ); - m_expandTimer.setInterval( 1000 ); + m_expandTimer.setInterval( 600 ); connect( &m_expandTimer, SIGNAL(timeout()), SLOT(expandTimeout())); m_collapseTimer.setSingleShot( true ); - m_collapseTimer.setInterval( 1000 ); + m_collapseTimer.setInterval( 600 ); connect( &m_collapseTimer, SIGNAL(timeout()), SLOT(collapseTimeout())); } @@ -70,7 +69,6 @@ void AnimationHelper::collapse( bool immediately ) if ( immediately ) { - m_forceClosing = true; m_fullyExpanded = false; m_collapseAnimation->start(); } @@ -80,9 +78,6 @@ void AnimationHelper::collapse( bool immediately ) bool AnimationHelper::partlyExpanded() { - if ( m_forceClosing ) - return false; - return m_size != m_startSize; // return m_fullyExpanded // || ( m_expandAnimation->state() == QPropertyAnimation::Running && m_expandAnimation->currentTime() > 250 ) diff --git a/src/sourcetree/animationhelper.h b/src/sourcetree/animationhelper.h index cc46013df..1e4998132 100644 --- a/src/sourcetree/animationhelper.h +++ b/src/sourcetree/animationhelper.h @@ -49,7 +49,6 @@ private: QTimer m_collapseTimer; bool m_fullyExpanded; - bool m_forceClosing; QPropertyAnimation *m_expandAnimation; QPropertyAnimation *m_collapseAnimation; diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 0a2fbdfac..55b41dfc6 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -36,6 +36,13 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) m_dropTypeImageMap.insert( 2, QPixmap( ":/data/images/drop-all-songs.png" ).scaledToHeight( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 3, QPixmap( ":/data/images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); m_dropTypeImageMap.insert( 4, QPixmap( ":/data/images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) ); + + m_dropMimeData = new QMimeData(); +} + +SourceDelegate::~SourceDelegate() +{ + delete m_dropMimeData; } QSize @@ -53,7 +60,7 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& qDebug() << "droptypecount is " << dropTypes; QSize originalSize = QStyledItemDelegate::sizeHint( option, index ); QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 56 ); - m_expandedMap.value( index )->initialize( originalSize, targetSize, 500 ); + m_expandedMap.value( index )->initialize( originalSize, targetSize, 300 ); m_expandedMap.value( index )->expand(); } QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) ); @@ -380,7 +387,12 @@ SourceDelegate::hovered(const QModelIndex &index, const QMimeData *mimeData) } m_newDropHoverIndex = index; - m_dropMimeData = const_cast< QMimeData* >( mimeData ); + m_dropMimeData->clear(); + foreach ( const QString &mimeDataFormat, mimeData->formats() ) + { + m_dropMimeData->setData( mimeDataFormat, mimeData->data( mimeDataFormat ) ); + } + m_expandedMap.insert( m_newDropHoverIndex, new AnimationHelper( m_newDropHoverIndex ) ); connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) ); diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index 2c2313cb9..442c19a37 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -14,6 +14,7 @@ class SourceDelegate : public QStyledItemDelegate Q_OBJECT public: SourceDelegate( QAbstractItemView* parent = 0 ); + ~SourceDelegate(); void hovered( const QModelIndex &index, const QMimeData *mimeData ); void dragLeaveEvent(); From 9f6dd0a10404bc7f0267d67b81cf6f5a0b902b25 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Aug 2011 08:36:32 +0200 Subject: [PATCH 32/72] fix font shrinking issue on OSX with the dragndrop menu --- src/sourcetree/sourcedelegate.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 55b41dfc6..7c500dd7b 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -178,24 +178,15 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->restore(); } - else if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) + else if ( (type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) + && m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) { + // Let Qt paint the original item. We add our stuff after it o.state &= QStyle::State_Selected; o.showDecorationSelected = false; - if ( !m_expandedMap.contains( index) ) - { - QStyledItemDelegate::paint( painter, o, index ); - return; - } - - // Let Qt paint the original item. We add our stuff after it -// QStyleOptionViewItem o = option; o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); QStyledItemDelegate::paint( painter, o, index ); - if ( !( m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) ) - return; - painter->save(); // Get whole rect for the menu From 01c29273398a16c931ac24d323fc485d0f2b2a55 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Aug 2011 08:42:44 +0200 Subject: [PATCH 33/72] paint the hovered item w/o QStyle::State_Selected but w/ QStyle::State_Active --- src/sourcetree/sourcedelegate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 7c500dd7b..99a90d146 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -182,7 +182,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co && m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) { // Let Qt paint the original item. We add our stuff after it - o.state &= QStyle::State_Selected; + o.state &= ~QStyle::State_Selected; o.showDecorationSelected = false; o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); QStyledItemDelegate::paint( painter, o, index ); From 851f9124baaa83e7e76dd8ad392d5b633a790844 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 24 Aug 2011 08:46:59 +0200 Subject: [PATCH 34/72] increase font size in dropmenu. People say it was too small for distinguishing normal and bold. --- src/sourcetree/sourcedelegate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 99a90d146..f486437ea 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -220,7 +220,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->setPen(pen); QFont font = painter->font(); - font.setPixelSize( 10 ); + font.setPixelSize( 12 ); painter->setFont( font ); QFont fontBold = painter->font(); fontBold.setBold( true ); @@ -249,10 +249,10 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co painter->setFont( font ); int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2; - textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 4, 0, 0 ); + textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 2, 0, 0 ); painter->drawText( textRect, m_dropTypeTextMap.value( i ) ); - int maxHeight = itemsRect.height() - textRect.height() - 4; + int maxHeight = itemsRect.height() - textRect.height() - 2; int verticalOffset = qMax( 0, maxHeight - 32 ); if ( itemsRect.bottom() - textRect.height() - 2 > itemsRect.top() ) { From eaca24227717fadc2a3c5c42f1451a6d21d53f7c Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 25 Aug 2011 00:05:12 +0200 Subject: [PATCH 35/72] changed the colors to silver --- src/sourcetree/sourcedelegate.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index f486437ea..482f84fd1 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -200,9 +200,15 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co // draw the background QLinearGradient linearGradient( itemsRect.topLeft(), itemsRect.bottomLeft() ); - linearGradient.setColorAt( 0.0, QColor( 0xdb, 0x1b, 0x06 ) ); -// linearGradient.setColorAt( 1.0, QColor( 0xf4, 0x17, 0x05 ) ); - linearGradient.setColorAt( 1.0, Qt::black ); + linearGradient.setColorAt( 0.0, Qt::white ); +// linearGradient.setColorAt( 0.8, QColor( 0xd6, 0xd6, 0xd6 ) ); // light grey +// linearGradient.setColorAt( 1.0, QColor( 0xf4, 0x17, 0x05 ) ); // dark red +// linearGradient.setColorAt( 1.0, QColor( 0xb1, 0xb1, 0xb1 ) ); // not so light but still not dark grey + linearGradient.setColorAt( 0.9, QColor( 0x88, 0x88, 0x88 ) ); + linearGradient.setColorAt( 1.0, QColor( 0x99, 0x99, 0x99 ) ); // dark grey + + QPen pen = painter->pen(); + painter->setPen( QPen( Qt::NoPen ) ); painter->setBrush( linearGradient ); painter->drawRect( itemsRect ); @@ -216,8 +222,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co int count = 0; - QPen pen(Qt::white); - painter->setPen(pen); + pen.setColor( Qt::white ); + painter->setPen( pen ); QFont font = painter->font(); font.setPixelSize( 12 ); From 16e77cb144bf3e2ee24e64e3c49b4f63885c4aed Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Aug 2011 17:24:12 +0200 Subject: [PATCH 36/72] fix dropping of top 10 for albums and tracks --- src/libtomahawk/dropjob.cpp | 77 +++++++++++++++++++++++-------------- src/libtomahawk/dropjob.h | 2 + 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 76c5eecd0..b8308e196 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -207,18 +207,25 @@ DropJob::tracksFromResultList( const QMimeData* data ) tDebug() << "Dropped result item:" << result->data()->artist()->name() << "-" << result->data()->track(); query_ptr q = result->data()->toQuery(); - if ( m_getWholeArtists ) + if ( m_top10 ) { - queries << getArtist( q->artist() ); - } - else if ( m_getWholeAlbums ) - { - queries << getAlbum( q->artist(), q->album() ); + getTopTen( q->artist() ); } else { - q->addResults( QList< result_ptr >() << *result ); - queries << q; + if ( m_getWholeArtists ) + { + queries << getArtist( q->artist() ); + } + else if ( m_getWholeAlbums ) + { + queries << getAlbum( q->artist(), q->album() ); + } + else + { + q->addResults( QList< result_ptr >() << *result ); + queries << q; + } } } } @@ -240,10 +247,17 @@ DropJob::tracksFromAlbumMetaData( const QMimeData *data ) QString album; stream >> album; - if ( m_getWholeArtists ) - queries << getArtist( artist ); + if ( m_top10 ) + { + getTopTen( artist ); + } else - queries << getAlbum( artist, album ); + { + if ( m_getWholeArtists ) + queries << getArtist( artist ); + else + queries << getAlbum( artist, album ); + } } return queries; } @@ -266,23 +280,7 @@ DropJob::tracksFromArtistMetaData( const QMimeData *data ) } else { - connect( Tomahawk::InfoSystem::InfoSystem::instance(), - SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), - SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); - - Tomahawk::InfoSystem::InfoCriteriaHash artistInfo; - artistInfo["artist"] = artist; - - Tomahawk::InfoSystem::InfoRequestData requestData; - requestData.caller = "changeme"; - requestData.customData = QVariantMap(); - - requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoCriteriaHash >( artistInfo ); - - requestData.type = Tomahawk::InfoSystem::InfoArtistSongs; - Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); - - m_queryCount++; + getTopTen( artist ); } } return queries; @@ -493,3 +491,26 @@ DropJob::getAlbum(const QString &artist, const QString &album) else return albumPtr->tracks(); } + +void +DropJob::getTopTen( const QString &artist ) +{ + connect( Tomahawk::InfoSystem::InfoSystem::instance(), + SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), + SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); + + Tomahawk::InfoSystem::InfoCriteriaHash artistInfo; + artistInfo["artist"] = artist; + + Tomahawk::InfoSystem::InfoRequestData requestData; + requestData.caller = "changeme"; + requestData.customData = QVariantMap(); + + requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoCriteriaHash >( artistInfo ); + + requestData.type = Tomahawk::InfoSystem::InfoArtistSongs; + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); + + m_queryCount++; + +} diff --git a/src/libtomahawk/dropjob.h b/src/libtomahawk/dropjob.h index eecc4e9e1..3e84d6510 100644 --- a/src/libtomahawk/dropjob.h +++ b/src/libtomahawk/dropjob.h @@ -75,6 +75,8 @@ private: QList< Tomahawk::query_ptr > getArtist( const QString& artist ); QList< Tomahawk::query_ptr > getAlbum( const QString& artist, const QString& album ); + void getTopTen( const QString& artist ); + void removeDuplicates(); void removeRemoteSources(); From 6a12e9fe0498ae11d350405f76a34a2bd3270f7c Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 14:02:22 -0400 Subject: [PATCH 37/72] Changelogify++ --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 8edf7301a..9d543226a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,8 @@ Version 0.3.0: Version 0.2.3: * Fixed issue where artist bio could be referring to a different artist. + * Opening a "tomahawk" URL (or other URL with Tomahawk) brings the Tomahawk + window to the foreground. Version 0.2.2: * Fixed crash when pressing previous and next when playing a song from the Queue. From f39a982e000d9122f1dadd089e4c07e01625934b Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Aug 2011 20:31:37 +0200 Subject: [PATCH 38/72] fix dropping of query_ptrs to Top10 --- src/libtomahawk/dropjob.cpp | 40 +++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index b8308e196..1f92f24a6 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -171,7 +171,11 @@ DropJob::tracksFromQueryList( const QMimeData* data ) { tDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); - if ( m_getWholeArtists ) + if ( m_top10 ) + { + getTopTen( query->data()->artist() ); + } + else if ( m_getWholeArtists ) { queries << getArtist( query->data()->artist() ); } @@ -211,21 +215,18 @@ DropJob::tracksFromResultList( const QMimeData* data ) { getTopTen( q->artist() ); } + else if ( m_getWholeArtists ) + { + queries << getArtist( q->artist() ); + } + else if ( m_getWholeAlbums ) + { + queries << getAlbum( q->artist(), q->album() ); + } else { - if ( m_getWholeArtists ) - { - queries << getArtist( q->artist() ); - } - else if ( m_getWholeAlbums ) - { - queries << getAlbum( q->artist(), q->album() ); - } - else - { - q->addResults( QList< result_ptr >() << *result ); - queries << q; - } + q->addResults( QList< result_ptr >() << *result ); + queries << q; } } } @@ -248,16 +249,11 @@ DropJob::tracksFromAlbumMetaData( const QMimeData *data ) stream >> album; if ( m_top10 ) - { getTopTen( artist ); - } + else if ( m_getWholeArtists ) + queries << getArtist( artist ); else - { - if ( m_getWholeArtists ) - queries << getArtist( artist ); - else - queries << getAlbum( artist, album ); - } + queries << getAlbum( artist, album ); } return queries; } From 49373bdfd03e0de6aedeff1edb830c03a2737dcf Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Aug 2011 20:36:27 +0200 Subject: [PATCH 39/72] dont crash when dropping an item with no album to album --- src/libtomahawk/dropjob.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 1f92f24a6..467821e29 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -477,6 +477,10 @@ DropJob::getAlbum(const QString &artist, const QString &album) { artist_ptr artistPtr = Artist::get( artist ); album_ptr albumPtr = Album::get( artistPtr, album ); + + if ( albumPtr.isNull() ) + return QList< query_ptr >(); + if ( albumPtr->tracks().isEmpty() ) { connect( albumPtr.data(), SIGNAL( tracksAdded( QList ) ), From 50bce627a8e95beb03b55d427870b4fd32c4f357 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 27 Aug 2011 15:27:28 -0400 Subject: [PATCH 40/72] Show recently created playlists and stations instead of recently looked at. Old model is still there. --- src/libtomahawk/CMakeLists.txt | 8 +- .../databasecommand_loadallautoplaylists.cpp | 26 +- .../databasecommand_loadallautoplaylists.h | 13 + .../databasecommand_loadallplaylists.cpp | 28 +- .../databasecommand_loadallplaylists.h | 27 +- ...databasecommand_loadallsortedplaylists.cpp | 108 ++++++++ .../databasecommand_loadallsortedplaylists.h | 65 +++++ .../databasecommand_loadallstations.cpp | 27 +- .../databasecommand_loadallstations.h | 13 + src/libtomahawk/playlist.h | 2 + .../playlist/dynamic/DynamicPlaylist.h | 24 +- .../dynamic/DynamicPlaylistRevision.h | 51 ++++ .../dynamic/echonest/EchonestGenerator.cpp | 2 - .../dynamic/widgets/DynamicWidget.cpp | 17 ++ .../playlist/dynamic/widgets/DynamicWidget.h | 15 +- src/libtomahawk/viewmanager.cpp | 13 + src/libtomahawk/viewmanager.h | 4 + .../widgets/RecentPlaylistsModel.cpp | 243 ++++++++++++++++++ .../widgets/RecentPlaylistsModel.h | 59 +++++ ...l.cpp => RecentlyPlayedPlaylistsModel.cpp} | 22 +- ...model.h => RecentlyPlayedPlaylistsModel.h} | 10 +- src/libtomahawk/widgets/welcomewidget.cpp | 28 +- src/sourcetree/items/playlistitems.cpp | 12 +- src/sourcetree/items/playlistitems.h | 4 +- src/sourcetree/sourcesmodel.cpp | 59 ++++- src/sourcetree/sourcesmodel.h | 7 +- src/sourcetree/sourcesproxymodel.cpp | 2 + 27 files changed, 797 insertions(+), 92 deletions(-) create mode 100644 src/libtomahawk/database/databasecommand_loadallsortedplaylists.cpp create mode 100644 src/libtomahawk/database/databasecommand_loadallsortedplaylists.h create mode 100644 src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h create mode 100644 src/libtomahawk/widgets/RecentPlaylistsModel.cpp create mode 100644 src/libtomahawk/widgets/RecentPlaylistsModel.h rename src/libtomahawk/widgets/{welcomeplaylistmodel.cpp => RecentlyPlayedPlaylistsModel.cpp} (90%) rename src/libtomahawk/widgets/{welcomeplaylistmodel.h => RecentlyPlayedPlaylistsModel.h} (88%) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index a838ccf4e..e5ec1cf04 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -67,6 +67,7 @@ set( libSources database/databasecommand_playbackhistory.cpp database/databasecommand_setplaylistrevision.cpp database/databasecommand_loadallplaylists.cpp + database/databasecommand_loadallsortedplaylists.cpp database/databasecommand_loadallsources.cpp database/databasecommand_createplaylist.cpp database/databasecommand_deleteplaylist.cpp @@ -180,7 +181,8 @@ set( libSources widgets/SeekSlider.cpp widgets/playlisttypeselectordlg.cpp widgets/welcomewidget.cpp - widgets/welcomeplaylistmodel.cpp + widgets/RecentlyPlayedPlaylistsModel.cpp + widgets/RecentPlaylistsModel.cpp widgets/overlaywidget.cpp widgets/HeaderLabel.cpp widgets/SocialPlaylistWidget.cpp @@ -248,6 +250,7 @@ set( libHeaders database/databasecommand_playbackhistory.h database/databasecommand_setplaylistrevision.h database/databasecommand_loadallplaylists.h + database/databasecommand_loadallsortedplaylists.h database/databasecommand_loadallsources.h database/databasecommand_createplaylist.h database/databasecommand_deleteplaylist.h @@ -360,7 +363,8 @@ set( libHeaders widgets/SeekSlider.h widgets/playlisttypeselectordlg.h widgets/welcomewidget.h - widgets/welcomeplaylistmodel.h + widgets/RecentlyPlayedPlaylistsModel.h + widgets/RecentPlaylistsModel.h widgets/overlaywidget.h widgets/HeaderLabel.h widgets/SocialPlaylistWidget.h diff --git a/src/libtomahawk/database/databasecommand_loadallautoplaylists.cpp b/src/libtomahawk/database/databasecommand_loadallautoplaylists.cpp index 4abbb4d94..3eda9d501 100644 --- a/src/libtomahawk/database/databasecommand_loadallautoplaylists.cpp +++ b/src/libtomahawk/database/databasecommand_loadallautoplaylists.cpp @@ -31,11 +31,31 @@ void DatabaseCommand_LoadAllAutoPlaylists::exec( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); + QString orderToken, sourceToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case DatabaseCommand_LoadAllPlaylists::ModificationTime: + orderToken = "playlist.createdOn"; + } + + if ( !source().isNull() ) + sourceToken = QString( "AND source %1 " ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + query.exec( QString( "SELECT playlist.guid as guid, title, info, creator, createdOn, lastmodified, shared, currentrevision, dynamic_playlist.pltype, dynamic_playlist.plmode " - "FROM playlist, dynamic_playlist WHERE source %1 AND dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid AND dynamic_playlist.plmode = %2 AND dynamic_playlist.autoload = 'true'" ) - .arg( source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ) ) - .arg( Static ) ); + "FROM playlist, dynamic_playlist WHERE dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid AND dynamic_playlist.plmode = %1 AND dynamic_playlist.autoload = 'true' " + "%2" + "%3 %4 %5" + ) + .arg( Static ) + .arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_limitAmount > 0 ? QString( "LIMIT 0, %1" ).arg( m_limitAmount ) : QString() ) ); QList plists; while ( query.next() ) diff --git a/src/libtomahawk/database/databasecommand_loadallautoplaylists.h b/src/libtomahawk/database/databasecommand_loadallautoplaylists.h index 0bf9cd5be..7d270bf97 100644 --- a/src/libtomahawk/database/databasecommand_loadallautoplaylists.h +++ b/src/libtomahawk/database/databasecommand_loadallautoplaylists.h @@ -24,6 +24,7 @@ #include "databasecommand.h" #include "typedefs.h" +#include "databasecommand_loadallplaylists.h" class DatabaseCommand_LoadAllAutoPlaylists : public DatabaseCommand { @@ -32,15 +33,27 @@ class DatabaseCommand_LoadAllAutoPlaylists : public DatabaseCommand public: explicit DatabaseCommand_LoadAllAutoPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) : DatabaseCommand( s, parent ) + , m_limitAmount( 0 ) + , m_sortOrder( DatabaseCommand_LoadAllPlaylists::None ) + , m_sortDescending( false ) {} virtual void exec( DatabaseImpl* ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "loadallautoplaylists"; } + void setLimit( unsigned int limit ) { m_limitAmount = limit; } + void setSortOrder( DatabaseCommand_LoadAllPlaylists::SortOrder order ) { m_sortOrder = order; } + void setSortDescending( bool descending ) { m_sortDescending = descending; } + signals: void autoPlaylistLoaded( const Tomahawk::source_ptr& source, const QVariantList& data ); void done(); + +private: + unsigned int m_limitAmount; + DatabaseCommand_LoadAllPlaylists::SortOrder m_sortOrder; + bool m_sortDescending; }; #endif diff --git a/src/libtomahawk/database/databasecommand_loadallplaylists.cpp b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp index 9ed6ddea3..b338f3444 100644 --- a/src/libtomahawk/database/databasecommand_loadallplaylists.cpp +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,12 +32,31 @@ void DatabaseCommand_LoadAllPlaylists::exec( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); + QString orderToken, sourceToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case ModificationTime: + orderToken = "playlist.createdOn"; + } + + if ( !source().isNull() ) + sourceToken = QString( "AND source %1 " ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + query.exec( QString( "SELECT guid, title, info, creator, lastmodified, shared, currentrevision, createdOn " - "FROM playlist WHERE source %1 AND dynplaylist = 'false'" ) - .arg( source()->isLocal() ? "IS NULL" : - QString( "= %1" ).arg( source()->id() ) - ) ); + "FROM playlist " + "WHERE dynplaylist = 'false' " + "%1 " + "%2 %3 %4" + ) + .arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_limitAmount > 0 ? QString( "LIMIT 0, %1" ).arg( m_limitAmount ) : QString() ) ); QList plists; while ( query.next() ) diff --git a/src/libtomahawk/database/databasecommand_loadallplaylists.h b/src/libtomahawk/database/databasecommand_loadallplaylists.h index 4a2c6f66d..831a54044 100644 --- a/src/libtomahawk/database/databasecommand_loadallplaylists.h +++ b/src/libtomahawk/database/databasecommand_loadallplaylists.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === - * + * * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,19 +30,41 @@ class DLLEXPORT DatabaseCommand_LoadAllPlaylists : public DatabaseCommand { -Q_OBJECT + Q_OBJECT public: + enum SortOrder { + None = 0, + ModificationTime = 1 + }; + enum SortAscDesc { + NoOrder = 0, + Ascending = 1, + Descending = 2 + }; + explicit DatabaseCommand_LoadAllPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) : DatabaseCommand( s, parent ) + , m_limitAmount( 0 ) + , m_sortOrder( None ) + , m_sortDescending( false ) {} virtual void exec( DatabaseImpl* ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "loadallplaylists"; } + void setLimit( unsigned int limit ) { m_limitAmount = limit; } + void setSortOrder( SortOrder order ) { m_sortOrder = order; } + void setSortDescending( bool descending ) { m_sortDescending = descending; } + signals: void done( const QList& playlists ); + +private: + unsigned int m_limitAmount; + SortOrder m_sortOrder; + bool m_sortDescending; }; #endif // DATABASECOMMAND_LOADALLPLAYLIST_H diff --git a/src/libtomahawk/database/databasecommand_loadallsortedplaylists.cpp b/src/libtomahawk/database/databasecommand_loadallsortedplaylists.cpp new file mode 100644 index 000000000..3b0ae5e77 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadallsortedplaylists.cpp @@ -0,0 +1,108 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "databasecommand_loadallsortedplaylists.h" +#include "databaseimpl.h" + +#include "playlist.h" +#include + +using namespace Tomahawk; + +void +DatabaseCommand_LoadAllSortedPlaylists::exec( DatabaseImpl* dbi ) +{ + TomahawkSqlQuery query = dbi->newquery(); + QString orderToken, sourceToken, ascDescToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case DatabaseCommand_LoadAllPlaylists::ModificationTime: + orderToken = "playlist.createdOn"; + } + + switch ( m_sortAscDesc ) + { + case DatabaseCommand_LoadAllPlaylists::NoOrder: + break; + case DatabaseCommand_LoadAllPlaylists::Ascending: + ascDescToken = "ASC"; + break; + case DatabaseCommand_LoadAllPlaylists::Descending: + ascDescToken = "DESC"; + break; + } + + if ( !source().isNull() ) + sourceToken = QString( "AND source %1 " ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + + + query.exec( QString( "SELECT playlist.guid as guid, title, info, creator, lastmodified, shared, currentrevision, createdOn, dynplaylist, source, dynamic_playlist.pltype, dynamic_playlist.plmode " + "FROM playlist " + "LEFT JOIN dynamic_playlist ON playlist.guid = dynamic_playlist.guid " + "%1 " + "%2 %3 %4" + ) + .arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( ascDescToken ) + .arg( m_limitAmount > 0 ? QString( "LIMIT 0, %1" ).arg( m_limitAmount ) : QString() ) ); + + QList plists; + while ( query.next() ) + { + plists << QPair< int, QString >( query.value(9).toInt(), query.value(0).toString() ); +// playlist_ptr p; +// bool dynamic = query.value(8).toBool(); +// source_ptr s = SourceList::instance()->get( query.value(9).toInt() ); +// +// if ( dynamic ) +// { +// p = dynplaylist_ptr( new DynamicPlaylist( s, +// query.value(6).toString(), //current rev +// query.value(1).toString(), //title +// query.value(2).toString(), //info +// query.value(3).toString(), //creator +// query.value(7).toInt(), //createdOn +// query.value(10).toString(), //type +// (GeneratorMode)query.value(11).toInt(), // mode +// query.value(5).toBool(), //shared +// query.value(4).toInt(), //lastmod +// query.value(0).toString() //GUID +// ) ); +// } else +// { +// p = playlist_ptr( new Playlist( s, //src +// query.value(6).toString(), //current rev +// query.value(1).toString(), //title +// query.value(2).toString(), //info +// query.value(3).toString(), //creator +// query.value(7).toInt(), //createdOn +// query.value(5).toBool(), //shared +// query.value(4).toInt(), //lastmod +// query.value(0).toString() //GUID +// ) ); +// } +// plists.append( p ); + } + + emit done( plists ); +} diff --git a/src/libtomahawk/database/databasecommand_loadallsortedplaylists.h b/src/libtomahawk/database/databasecommand_loadallsortedplaylists.h new file mode 100644 index 000000000..ed82e487e --- /dev/null +++ b/src/libtomahawk/database/databasecommand_loadallsortedplaylists.h @@ -0,0 +1,65 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef DATABASECOMMAND_LOADALLSORTEDPLAYLISTS_H +#define DATABASECOMMAND_LOADALLSORTEDPLAYLISTS_H + +#include "libtomahawk/database/databasecommand.h" +#include "databasecommand_loadallplaylists.h" + +/** + * Loads *all* playlists, automatic playlists, and stations. Another dbcmd because otherwise loading them all + * is fragmented across 3 dbcmds with a different interface. + * + * You probably want to limit / sort the output. + */ +class DatabaseCommand_LoadAllSortedPlaylists : public DatabaseCommand +{ + Q_OBJECT + +public: + // don't macros rock... not + typedef QPair SourcePlaylistPair; + explicit DatabaseCommand_LoadAllSortedPlaylists( const Tomahawk::source_ptr& s, QObject* parent = 0 ) + : DatabaseCommand( s, parent ) + , m_limitAmount( 0 ) + , m_sortOrder( DatabaseCommand_LoadAllPlaylists::None ) + , m_sortAscDesc( DatabaseCommand_LoadAllPlaylists::NoOrder ) + { + qRegisterMetaType >("QList"); + } + + virtual void exec( DatabaseImpl* ); + virtual bool doesMutates() const { return false; } + virtual QString commandname() const { return "loadallsortedplaylists"; } + + void setLimit( unsigned int limit ) { m_limitAmount = limit; } + void setSortOrder( DatabaseCommand_LoadAllPlaylists::SortOrder order ) { m_sortOrder = order; } + void setSortAscDesc( DatabaseCommand_LoadAllPlaylists::SortAscDesc asc ) { m_sortAscDesc = asc; } + +signals: + void done( const QList& playlistGuids ); // QPair< sourceid, playlistguid> + +private: + unsigned int m_limitAmount; + DatabaseCommand_LoadAllPlaylists::SortOrder m_sortOrder; + DatabaseCommand_LoadAllPlaylists::SortAscDesc m_sortAscDesc; +}; + +Q_DECLARE_METATYPE(QList) +#endif // DATABASECOMMAND_LOADALLSORTEDPLAYLISTS_H diff --git a/src/libtomahawk/database/databasecommand_loadallstations.cpp b/src/libtomahawk/database/databasecommand_loadallstations.cpp index 90edeeb16..2d627dc66 100644 --- a/src/libtomahawk/database/databasecommand_loadallstations.cpp +++ b/src/libtomahawk/database/databasecommand_loadallstations.cpp @@ -31,11 +31,32 @@ void DatabaseCommand_LoadAllStations::exec( DatabaseImpl* dbi ) { TomahawkSqlQuery query = dbi->newquery(); + QString orderToken, sourceToken; + + switch ( m_sortOrder ) + { + case 0: + break; + + case DatabaseCommand_LoadAllPlaylists::ModificationTime: + orderToken = "playlist.createdOn"; + } + + if ( !source().isNull() ) + sourceToken = QString( "AND source %1 " ).arg( source()->isLocal() ? "IS NULL" : QString( "= %1" ).arg( source()->id() ) ); + query.exec( QString( "SELECT playlist.guid as guid, title, info, creator, createdOn, lastmodified, shared, currentrevision, dynamic_playlist.pltype, dynamic_playlist.plmode " - "FROM playlist, dynamic_playlist WHERE source %1 AND dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid AND dynamic_playlist.plmode = %2 AND dynamic_playlist.autoload = 'true'" ) - .arg( source()->isLocal() ? "IS NULL" : QString( "=%1" ).arg( source()->id() ) ) - .arg( OnDemand ) ); + "FROM playlist, dynamic_playlist WHERE " + "dynplaylist = 'true' AND playlist.guid = dynamic_playlist.guid AND dynamic_playlist.plmode = %1 AND dynamic_playlist.autoload = 'true' " + "%2" + "%3 %4 %5" + ) + .arg( OnDemand ) + .arg( sourceToken ) + .arg( m_sortOrder > 0 ? QString( "ORDER BY %1" ).arg( orderToken ) : QString() ) + .arg( m_sortDescending ? "DESC" : QString() ) + .arg( m_limitAmount > 0 ? QString( "LIMIT 0, %1" ).arg( m_limitAmount ) : QString() ) ); QList plists; while ( query.next() ) diff --git a/src/libtomahawk/database/databasecommand_loadallstations.h b/src/libtomahawk/database/databasecommand_loadallstations.h index 1e838d9c7..fe16ac7cc 100644 --- a/src/libtomahawk/database/databasecommand_loadallstations.h +++ b/src/libtomahawk/database/databasecommand_loadallstations.h @@ -24,6 +24,7 @@ #include "databasecommand.h" #include "typedefs.h" +#include "databasecommand_loadallplaylists.h" class DatabaseCommand_LoadAllStations : public DatabaseCommand { @@ -32,15 +33,27 @@ class DatabaseCommand_LoadAllStations : public DatabaseCommand public: explicit DatabaseCommand_LoadAllStations( const Tomahawk::source_ptr& s, QObject* parent = 0 ) : DatabaseCommand( s, parent ) + , m_limitAmount( 0 ) + , m_sortOrder( DatabaseCommand_LoadAllPlaylists::None ) + , m_sortDescending( false ) {} virtual void exec( DatabaseImpl* ); virtual bool doesMutates() const { return false; } virtual QString commandname() const { return "loadallstations"; } + void setLimit( unsigned int limit ) { m_limitAmount = limit; } + void setSortOrder( DatabaseCommand_LoadAllPlaylists::SortOrder order ) { m_sortOrder = order; } + void setSortDescending( bool descending ) { m_sortDescending = descending; } + signals: void stationLoaded( const Tomahawk::source_ptr& source, const QVariantList& data ); void done(); + +private: + unsigned int m_limitAmount; + DatabaseCommand_LoadAllPlaylists::SortOrder m_sortOrder; + bool m_sortDescending; }; #endif \ No newline at end of file diff --git a/src/libtomahawk/playlist.h b/src/libtomahawk/playlist.h index 7df3a942c..e97da070b 100644 --- a/src/libtomahawk/playlist.h +++ b/src/libtomahawk/playlist.h @@ -33,6 +33,7 @@ #include class DatabaseCommand_LoadAllPlaylists; +class DatabaseCommand_LoadAllSortedPlaylists; class DatabaseCommand_SetPlaylistRevision; class DatabaseCommand_CreatePlaylist; @@ -124,6 +125,7 @@ Q_PROPERTY( unsigned int createdon READ createdOn WRITE setCreatedOn ) Q_PROPERTY( bool shared READ shared WRITE setShared ) friend class ::DatabaseCommand_LoadAllPlaylists; +friend class ::DatabaseCommand_LoadAllSortedPlaylists; friend class ::DatabaseCommand_SetPlaylistRevision; friend class ::DatabaseCommand_CreatePlaylist; friend class DynamicPlaylist; diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h index 35ed57ed9..655bef6f8 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylist.h @@ -26,12 +26,14 @@ #include "playlist.h" #include "typedefs.h" #include "playlist/dynamic/DynamicControl.h" +#include "playlist/dynamic/DynamicPlaylistRevision.h" #include "dllmacro.h" class DatabaseCommand_LoadAllDynamicPlaylists; class DatabaseCommand_SetDynamicPlaylistRevision; class DatabaseCommand_CreateDynamicPlaylist; +class DatabaseCommand_LoadAllSortedPlaylists; class DatabaseCollection; namespace Tomahawk { @@ -43,27 +45,6 @@ class DatabaseCommand_LoadDynamicPlaylist; * It uses normal PlaylistEntries but also has a mode, a generator, and a list of controls */ -struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision -{ -public: - - QList< dyncontrol_ptr > controls; - Tomahawk::GeneratorMode mode; - QString type; - - DynamicPlaylistRevision( const PlaylistRevision& other ) - { - revisionguid = other.revisionguid; - oldrevisionguid = other.oldrevisionguid; - newlist = other.newlist; - added = other.added; - removed = other.removed; - applied = other.applied; - } - - DynamicPlaylistRevision() {} -}; - struct DynQueueItem : RevisionQueueItem { QString type; @@ -86,6 +67,7 @@ class DLLEXPORT DynamicPlaylist : public Playlist friend class ::DatabaseCommand_SetDynamicPlaylistRevision; friend class ::DatabaseCommand_CreateDynamicPlaylist; friend class Tomahawk::DatabaseCommand_LoadDynamicPlaylist; + friend class ::DatabaseCommand_LoadAllSortedPlaylists; friend class ::DatabaseCollection; /// :-( public: diff --git a/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h new file mode 100644 index 000000000..da89dc29e --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/DynamicPlaylistRevision.h @@ -0,0 +1,51 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef DYNAMIC_PLAYLIST_REVISION_H +#define DYNAMIC_PLAYLIST_REVISION_H + +#include "playlist.h" +#include "dllmacro.h" + +namespace Tomahawk +{ + +struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision +{ +public: + + QList< dyncontrol_ptr > controls; + Tomahawk::GeneratorMode mode; + QString type; + + DynamicPlaylistRevision( const PlaylistRevision& other ) + { + revisionguid = other.revisionguid; + oldrevisionguid = other.oldrevisionguid; + newlist = other.newlist; + added = other.added; + removed = other.removed; + applied = other.applied; + } + + DynamicPlaylistRevision() {} +}; + +} + +#endif diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp index 2b72fce03..4fa8eab12 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp @@ -524,12 +524,10 @@ EchonestGenerator::sentenceSummary() suffix = ", "; sentence += prefix + allcontrols.value( i ).dynamicCast< EchonestControl >()->summary() + suffix; } - qDebug() << "Got artists and contents:" << sentence; if( !sorting.isNull() ) { sentence += "and " + sorting.dynamicCast< EchonestControl >()->summary() + "."; } - qDebug() << "Got full summary:" << sentence; return sentence; } diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 8da3b345a..5e1b4e0fa 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -105,6 +105,12 @@ DynamicWidget::~DynamicWidget() { } +dynplaylist_ptr +DynamicWidget::playlist() +{ + return m_playlist; +} + void DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) @@ -427,6 +433,17 @@ DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& /* pal */, QRect& p.drawRoundedRect( r, 10, 10 ); } +QString +DynamicWidget::description() const +{ + return m_model->description(); +} + +QString +DynamicWidget::title() const +{ + return m_model->title(); +} QPixmap DynamicWidget::pixmap() const diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 26f61baae..26b0a43a6 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -23,10 +23,7 @@ #include "typedefs.h" #include "viewpage.h" - -#include "dynamic/DynamicPlaylist.h" -#include "dynamic/DynamicControl.h" -#include "dynamic/DynamicModel.h" +#include "playlist/dynamic/DynamicPlaylistRevision.h" class LoadingSpinner; class QShowEvent; @@ -45,10 +42,10 @@ class ReadOrWriteWidget; namespace Tomahawk { +class DynamicModel; + class DynamicSetupWidget; - class DynamicView; - class CollapsibleControls; @@ -63,7 +60,7 @@ public: virtual ~DynamicWidget(); void loadDynamicPlaylist( const dynplaylist_ptr& playlist ); - dynplaylist_ptr playlist() { return m_playlist; } + dynplaylist_ptr playlist(); virtual PlaylistInterface* playlistInterface() const; @@ -75,8 +72,8 @@ public: virtual QWidget* widget() { return this; } - virtual QString title() const { return m_model->title(); } - virtual QString description() const { return m_model->description(); } + virtual QString title() const; + virtual QString description() const; virtual QPixmap pixmap() const; virtual bool jumpToCurrentTrack(); diff --git a/src/libtomahawk/viewmanager.cpp b/src/libtomahawk/viewmanager.cpp index 033ad86ec..77a48dcd1 100644 --- a/src/libtomahawk/viewmanager.cpp +++ b/src/libtomahawk/viewmanager.cpp @@ -168,6 +168,19 @@ ViewManager::createPageForPlaylist( const playlist_ptr& pl ) return view; } +playlist_ptr +ViewManager::playlistForPage( ViewPage* page ) const +{ + playlist_ptr p; + if ( dynamic_cast< PlaylistView* >( page ) && dynamic_cast< PlaylistView* >( page )->playlistModel() && + !dynamic_cast< PlaylistView* >( page )->playlistModel()->playlist().isNull() ) + p = dynamic_cast< PlaylistView* >( page )->playlistModel()->playlist(); + else if ( dynamic_cast< DynamicWidget* >( page ) ) + p = dynamic_cast< DynamicWidget* >( page )->playlist(); + + return p; +} + Tomahawk::ViewPage* ViewManager::show( const Tomahawk::playlist_ptr& playlist ) diff --git a/src/libtomahawk/viewmanager.h b/src/libtomahawk/viewmanager.h index 8fd2320d4..9fcbc5a26 100644 --- a/src/libtomahawk/viewmanager.h +++ b/src/libtomahawk/viewmanager.h @@ -89,6 +89,10 @@ public: Tomahawk::ViewPage* pageForDynPlaylist( const Tomahawk::dynplaylist_ptr& pl ) const; Tomahawk::ViewPage* pageForCollection( const Tomahawk::collection_ptr& pl ) const; + /// Get a playlist (or dynamic playlist ) from a ViewPage* if the page is PlaylistView or DynamicWidget. + /// Lives here but used by SourcesModel + Tomahawk::playlist_ptr playlistForPage( Tomahawk::ViewPage* ) const; + // only use this is you need to create a playlist and show it directly and want it to be // linked to the sidebar. call it right after creating the playlist PlaylistView* createPageForPlaylist( const Tomahawk::playlist_ptr& pl ); diff --git a/src/libtomahawk/widgets/RecentPlaylistsModel.cpp b/src/libtomahawk/widgets/RecentPlaylistsModel.cpp new file mode 100644 index 000000000..919f7bce3 --- /dev/null +++ b/src/libtomahawk/widgets/RecentPlaylistsModel.cpp @@ -0,0 +1,243 @@ +/* + Copyright (C) 2011 Leo Franchi + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include "RecentPlaylistsModel.h" + +#include "tomahawksettings.h" +#include "audio/audioengine.h" +#include "sourcelist.h" +#include "utils/logger.h" +#include "dynamic/DynamicPlaylist.h" +#include "database/database.h" +#include "database/databasecommand_loadallsortedplaylists.h" +#include "RecentlyPlayedPlaylistsModel.h" +#include + +using namespace Tomahawk; + + +RecentPlaylistsModel::RecentPlaylistsModel( unsigned int maxPlaylists, QObject* parent ) + : QAbstractListModel( parent ) + , m_maxPlaylists( maxPlaylists ) +{ + + if ( Servent::instance()->isReady() ) + onReady(); + else + connect( Servent::instance(), SIGNAL( ready() ), this, SLOT( onReady() ) ); + + // Load recent playlists initially + refresh(); +} + +void +RecentPlaylistsModel::refresh() +{ + DatabaseCommand_LoadAllSortedPlaylists* cmd = new DatabaseCommand_LoadAllSortedPlaylists( source_ptr() ); + cmd->setLimit( 15 ); + cmd->setSortOrder( DatabaseCommand_LoadAllPlaylists::ModificationTime ); + cmd->setSortAscDesc( DatabaseCommand_LoadAllPlaylists::Descending ); + connect( cmd, SIGNAL( done( QList ) ), this, SLOT( playlistsLoaded( QList ) ) ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); +} + +void +RecentPlaylistsModel::onReady() +{ + foreach( const source_ptr& s, SourceList::instance()->sources() ) + onSourceAdded( s ); + + connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), this, SLOT( onSourceAdded( Tomahawk::source_ptr ) ), Qt::QueuedConnection ); + refresh(); +} + + +void +RecentPlaylistsModel::playlistsLoaded ( const QList& playlistGuids ) +{ + beginResetModel(); + m_playlists.clear(); + + DatabaseCommand_LoadAllSortedPlaylists::SourcePlaylistPair plPair; + foreach ( plPair, playlistGuids ) + { + source_ptr s = SourceList::instance()->get( plPair.first ); + if ( s.isNull() ) + continue; + + if ( plPair.first == 0 ) + s = SourceList::instance()->getLocal(); + + playlist_ptr pl = s->collection()->playlist( plPair.second ); + if ( pl.isNull() ) + pl = s->collection()->autoPlaylist( plPair.second ); + if ( pl.isNull() ) + pl = s->collection()->station( plPair.second ); + + if ( pl.isNull() ) + { + qDebug() << "Found a playlist that is NOT LOADED FOR ANY SOURCE:" << plPair.first << plPair.second; + continue; + } + connect( pl.data(), SIGNAL( changed() ), this, SLOT( updatePlaylist() ) ); + m_playlists << pl; + } + + endResetModel(); +} + + +QVariant +RecentPlaylistsModel::data( const QModelIndex& index, int role ) const +{ + if( !index.isValid() || !hasIndex( index.row(), index.column(), index.parent() ) ) + return QVariant(); + + playlist_ptr pl = m_playlists[index.row()]; + switch( role ) + { + case Qt::DisplayRole: + return pl->title(); + case RecentlyPlayedPlaylistsModel::PlaylistRole: + return QVariant::fromValue< Tomahawk::playlist_ptr >( pl ); + case RecentlyPlayedPlaylistsModel::ArtistRole: + { + if( m_artists.value( pl ).isEmpty() ) + { + QStringList artists; + + foreach( const Tomahawk::plentry_ptr& entry, pl->entries() ) + { + if ( !artists.contains( entry->query()->artist() ) ) + artists << entry->query()->artist(); + } + + m_artists[pl] = artists.join( ", " ); + } + + return m_artists[pl]; + } + case RecentlyPlayedPlaylistsModel::PlaylistTypeRole: + { + if ( !pl.dynamicCast< Tomahawk::DynamicPlaylist >().isNull() ) + { + dynplaylist_ptr dynp = pl.dynamicCast< Tomahawk::DynamicPlaylist >(); + if ( dynp->mode() == Static ) + return RecentlyPlayedPlaylistsModel::AutoPlaylist; + else if ( dynp->mode() == OnDemand ) + return RecentlyPlayedPlaylistsModel::Station; + } else + { + return RecentlyPlayedPlaylistsModel::StaticPlaylist; + } + } + case RecentlyPlayedPlaylistsModel::DynamicPlaylistRole: + { + dynplaylist_ptr dynp = pl.dynamicCast< Tomahawk::DynamicPlaylist >(); + return QVariant::fromValue< Tomahawk::dynplaylist_ptr >( dynp ); + } + case RecentlyPlayedPlaylistsModel::TrackCountRole: + { + if ( !pl.dynamicCast< Tomahawk::DynamicPlaylist >().isNull() && pl.dynamicCast< Tomahawk::DynamicPlaylist >()->mode() == OnDemand ) + return QString( QChar( 0x221E ) ); + else + return pl->entries().count(); + } + default: + return QVariant(); + } +} + +void +RecentPlaylistsModel::updatePlaylist() +{ + Playlist* p = qobject_cast< Playlist* >( sender() ); + Q_ASSERT( p ); + + for ( int i = 0; i < m_playlists.size(); i++ ) + { + if ( m_playlists[ i ]->guid() == p->guid() ) + { + QModelIndex idx = index( i, 0, QModelIndex() ); + emit dataChanged( idx, idx ); + } + } +} + +void +RecentPlaylistsModel::onSourceAdded( const Tomahawk::source_ptr& source ) +{ + connect( source.data(), SIGNAL( online() ), this, SLOT( sourceOnline() ) ); + connect( source->collection().data(), SIGNAL( playlistsAdded( QList ) ), SLOT( refresh() ), Qt::QueuedConnection ); + connect( source->collection().data(), SIGNAL( autoPlaylistsAdded(QList)), SLOT( refresh() ), Qt::QueuedConnection ); + connect( source->collection().data(), SIGNAL( stationsAdded(QList)), SLOT( refresh() ), Qt::QueuedConnection ); + connect( source->collection().data(), SIGNAL( playlistsDeleted( QList ) ), SLOT( onPlaylistsRemoved( QList ) ) ); + connect( source->collection().data(), SIGNAL( autoPlaylistsDeleted(QList) ), SLOT( onDynPlaylistsRemoved( QList ) ) ); + connect( source->collection().data(), SIGNAL( stationsDeleted(QList) ), SLOT( onDynPlaylistsRemoved( QList ) ) ); +} + +void +RecentPlaylistsModel::sourceOnline() +{ + Source* s = qobject_cast< Source* >( sender() ); + Q_ASSERT( s ); + + for ( int i = 0; i < m_playlists.size(); i++ ) + { + if ( m_playlists[ i ]->author().data() == s ) + { + QModelIndex idx = index( i, 0, QModelIndex() ); + emit dataChanged( idx, idx ); + } + } +} + +void +RecentPlaylistsModel::onDynPlaylistsRemoved( QList< dynplaylist_ptr > playlists ) +{ + QList< playlist_ptr > pls; + foreach( const dynplaylist_ptr& p, playlists ) + pls << p; + onPlaylistsRemoved( pls ); +} + + +void +RecentPlaylistsModel::onPlaylistsRemoved( QList< playlist_ptr > playlists ) +{ + foreach( const playlist_ptr& pl, playlists ) { + if( m_playlists.contains( pl ) ) { + m_artists.remove( pl ); + + int idx = m_playlists.indexOf( pl ); + beginRemoveRows( QModelIndex(), idx, idx ); + m_playlists.removeAt( idx ); + endRemoveRows(); + } + } + + emit emptinessChanged( m_playlists.isEmpty() ); +} + + +int +RecentPlaylistsModel::rowCount( const QModelIndex& ) const +{ + return m_playlists.count(); +} diff --git a/src/libtomahawk/widgets/RecentPlaylistsModel.h b/src/libtomahawk/widgets/RecentPlaylistsModel.h new file mode 100644 index 000000000..7aea0e896 --- /dev/null +++ b/src/libtomahawk/widgets/RecentPlaylistsModel.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 Leo Franchi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef RECENTLPLAYLISTSMODEL_H +#define RECENTLPLAYLISTSMODEL_H + +#include + +#include "playlist.h" +#include "database/databasecommand_loadallsortedplaylists.h" + +class RecentPlaylistsModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit RecentPlaylistsModel( unsigned int maxPlaylists, QObject* parent = 0 ); + + virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const; + virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const; + +public slots: + void refresh(); + void onReady(); + +signals: + void emptinessChanged( bool isEmpty ); + +private slots: + void playlistsLoaded( const QList& playlistGuids ); + + void onPlaylistsRemoved( QList< Tomahawk::playlist_ptr > playlists ); + void onDynPlaylistsRemoved( QList< Tomahawk::dynplaylist_ptr > playlists ); + void updatePlaylist(); + + void sourceOnline(); + void onSourceAdded( const Tomahawk::source_ptr& source ); +private: + QList< Tomahawk::playlist_ptr > m_playlists; + mutable QHash< Tomahawk::playlist_ptr, QString > m_artists; + unsigned int m_maxPlaylists; +}; + +#endif // RECENTLPLAYLISTSMODEL_H diff --git a/src/libtomahawk/widgets/welcomeplaylistmodel.cpp b/src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.cpp similarity index 90% rename from src/libtomahawk/widgets/welcomeplaylistmodel.cpp rename to src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.cpp index 618354562..f1f5147e3 100644 --- a/src/libtomahawk/widgets/welcomeplaylistmodel.cpp +++ b/src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.cpp @@ -17,7 +17,7 @@ */ -#include "welcomeplaylistmodel.h" +#include "RecentlyPlayedPlaylistsModel.h" #include "tomahawksettings.h" #include "audio/audioengine.h" @@ -28,7 +28,7 @@ using namespace Tomahawk; -WelcomePlaylistModel::WelcomePlaylistModel( QObject* parent ) +RecentlyPlayedPlaylistsModel::RecentlyPlayedPlaylistsModel( QObject* parent ) : QAbstractListModel( parent ) , m_maxPlaylists( 0 ) , m_waitingForSome( true ) @@ -44,7 +44,7 @@ WelcomePlaylistModel::WelcomePlaylistModel( QObject* parent ) void -WelcomePlaylistModel::loadFromSettings() +RecentlyPlayedPlaylistsModel::loadFromSettings() { // qDebug() << Q_FUNC_INFO; if( !m_waitingForSome ) @@ -87,7 +87,7 @@ WelcomePlaylistModel::loadFromSettings() QVariant -WelcomePlaylistModel::data( const QModelIndex& index, int role ) const +RecentlyPlayedPlaylistsModel::data( const QModelIndex& index, int role ) const { if( !index.isValid() || !hasIndex( index.row(), index.column(), index.parent() ) ) return QVariant(); @@ -148,7 +148,7 @@ WelcomePlaylistModel::data( const QModelIndex& index, int role ) const } void -WelcomePlaylistModel::playlistRevisionLoaded() +RecentlyPlayedPlaylistsModel::playlistRevisionLoaded() { Playlist* p = qobject_cast< Playlist* >( sender() ); Q_ASSERT( p ); @@ -165,7 +165,7 @@ WelcomePlaylistModel::playlistRevisionLoaded() void -WelcomePlaylistModel::onSourceAdded( const Tomahawk::source_ptr& source ) +RecentlyPlayedPlaylistsModel::onSourceAdded( const Tomahawk::source_ptr& source ) { connect( source.data(), SIGNAL( online() ), this, SLOT( sourceOnline() ) ); connect( source->collection().data(), SIGNAL( playlistsAdded( QList ) ), SLOT( loadFromSettings() ) ); @@ -173,7 +173,7 @@ WelcomePlaylistModel::onSourceAdded( const Tomahawk::source_ptr& source ) } void -WelcomePlaylistModel::sourceOnline() +RecentlyPlayedPlaylistsModel::sourceOnline() { Source* s = qobject_cast< Source* >( sender() ); Q_ASSERT( s ); @@ -190,7 +190,7 @@ WelcomePlaylistModel::sourceOnline() void -WelcomePlaylistModel::onPlaylistsRemoved( QList< playlist_ptr > playlists ) +RecentlyPlayedPlaylistsModel::onPlaylistsRemoved( QList< playlist_ptr > playlists ) { foreach( const playlist_ptr& pl, playlists ) { if( m_recplaylists.contains( pl ) ) { @@ -209,14 +209,14 @@ WelcomePlaylistModel::onPlaylistsRemoved( QList< playlist_ptr > playlists ) int -WelcomePlaylistModel::rowCount( const QModelIndex& ) const +RecentlyPlayedPlaylistsModel::rowCount( const QModelIndex& ) const { return m_recplaylists.count(); } void -WelcomePlaylistModel::plAdded( const playlist_ptr& pl ) +RecentlyPlayedPlaylistsModel::plAdded( const playlist_ptr& pl ) { onPlaylistsRemoved( QList< playlist_ptr >() << pl ); @@ -229,7 +229,7 @@ WelcomePlaylistModel::plAdded( const playlist_ptr& pl ) void -WelcomePlaylistModel::playlistChanged( Tomahawk::PlaylistInterface* pli ) +RecentlyPlayedPlaylistsModel::playlistChanged( Tomahawk::PlaylistInterface* pli ) { // ARG if( Playlist* pl = dynamic_cast< Playlist* >( pli ) ) { diff --git a/src/libtomahawk/widgets/welcomeplaylistmodel.h b/src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.h similarity index 88% rename from src/libtomahawk/widgets/welcomeplaylistmodel.h rename to src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.h index c30d83ea0..da271aea2 100644 --- a/src/libtomahawk/widgets/welcomeplaylistmodel.h +++ b/src/libtomahawk/widgets/RecentlyPlayedPlaylistsModel.h @@ -17,15 +17,15 @@ */ -#ifndef WELCOMEPLAYLISTMODEL_H -#define WELCOMEPLAYLISTMODEL_H +#ifndef RECENTLYPLAYEDPLAYLISTSMODEL_H +#define RECENTLYPLAYEDPLAYLISTSMODEL_H #include #include "playlist.h" -class WelcomePlaylistModel : public QAbstractListModel +class RecentlyPlayedPlaylistsModel : public QAbstractListModel { Q_OBJECT public: @@ -34,7 +34,7 @@ public: enum PlaylistTypes { StaticPlaylist, AutoPlaylist, Station }; - explicit WelcomePlaylistModel( QObject* parent = 0 ); + explicit RecentlyPlayedPlaylistsModel( QObject* parent = 0 ); void setMaxPlaylists( unsigned int max ) { m_maxPlaylists = max; } @@ -64,4 +64,4 @@ private: void sourceOnline(); }; -#endif // WELCOMEPLAYLISTMODEL_H +#endif // RECENTLYPLAYEDPLAYLISTSMODEL_H diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index 5e090f090..6223d14dc 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -24,7 +24,7 @@ #include "viewmanager.h" #include "sourcelist.h" #include "tomahawksettings.h" -#include "welcomeplaylistmodel.h" +#include "RecentPlaylistsModel.h" #include "audio/audioengine.h" #include "playlist/albummodel.h" @@ -33,6 +33,7 @@ #include "utils/tomahawkutils.h" #include "utils/logger.h" #include +#include "RecentlyPlayedPlaylistsModel.h" #define HISTORY_TRACK_ITEMS 25 #define HISTORY_PLAYLIST_ITEMS 10 @@ -52,8 +53,7 @@ WelcomeWidget::WelcomeWidget( QWidget* parent ) ui->splitter_2->setStretchFactor( 0, 2 ); ui->splitter_2->setStretchFactor( 0, 1 ); - WelcomePlaylistModel* model = new WelcomePlaylistModel( this ); - model->setMaxPlaylists( HISTORY_PLAYLIST_ITEMS ); + RecentPlaylistsModel* model = new RecentPlaylistsModel( HISTORY_PLAYLIST_ITEMS, this ); ui->playlistWidget->setFrameShape( QFrame::NoFrame ); ui->playlistWidget->setAttribute( Qt::WA_MacShowFocusRect, 0 ); @@ -180,7 +180,7 @@ WelcomeWidget::onPlaylistActivated( const QModelIndex& item ) { qDebug() << Q_FUNC_INFO; - Tomahawk::playlist_ptr pl = item.data( WelcomePlaylistModel::PlaylistRole ).value< Tomahawk::playlist_ptr >(); + Tomahawk::playlist_ptr pl = item.data( RecentlyPlayedPlaylistsModel::PlaylistRole ).value< Tomahawk::playlist_ptr >(); if( Tomahawk::dynplaylist_ptr dynplaylist = pl.dynamicCast< Tomahawk::DynamicPlaylist >() ) ViewManager::instance()->show( dynplaylist ); else @@ -240,12 +240,12 @@ PlaylistDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, boldFont.setBold( true ); QPixmap icon; - WelcomePlaylistModel::PlaylistTypes type = (WelcomePlaylistModel::PlaylistTypes)index.data( WelcomePlaylistModel::PlaylistTypeRole ).toInt(); - if( type == WelcomePlaylistModel::StaticPlaylist ) + RecentlyPlayedPlaylistsModel::PlaylistTypes type = (RecentlyPlayedPlaylistsModel::PlaylistTypes)index.data( RecentlyPlayedPlaylistsModel::PlaylistTypeRole ).toInt(); + if( type == RecentlyPlayedPlaylistsModel::StaticPlaylist ) icon = m_playlistIcon; - else if( type == WelcomePlaylistModel::AutoPlaylist ) + else if( type == RecentlyPlayedPlaylistsModel::AutoPlaylist ) icon = m_autoIcon; - else if( type == WelcomePlaylistModel::Station ) + else if( type == RecentlyPlayedPlaylistsModel::Station ) icon = m_stationIcon; QRect pixmapRect = option.rect.adjusted( 10, 13, -option.rect.width() + 48, -13 ); @@ -254,12 +254,12 @@ PlaylistDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, painter->drawPixmap( pixmapRect, icon ); QString descText; - if ( type == WelcomePlaylistModel::Station ) + if ( type == RecentlyPlayedPlaylistsModel::Station ) { - descText = index.data( WelcomePlaylistModel::DynamicPlaylistRole ).value< Tomahawk::dynplaylist_ptr >()->generator()->sentenceSummary(); + descText = index.data( RecentlyPlayedPlaylistsModel::DynamicPlaylistRole ).value< Tomahawk::dynplaylist_ptr >()->generator()->sentenceSummary(); } else { - descText = index.data( WelcomePlaylistModel::ArtistRole ).toString(); + descText = index.data( RecentlyPlayedPlaylistsModel::ArtistRole ).toString(); } QColor c = painter->pen().color(); painter->setPen( QColor( Qt::gray ).darker() ); @@ -278,10 +278,10 @@ PlaylistDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, painter->setPen( c ); painter->setFont( font ); - if ( type != WelcomePlaylistModel::Station ) + if ( type != RecentlyPlayedPlaylistsModel::Station ) { painter->save(); - QString tracks = index.data( WelcomePlaylistModel::TrackCountRole ).toString(); + QString tracks = index.data( RecentlyPlayedPlaylistsModel::TrackCountRole ).toString(); int width = painter->fontMetrics().width( tracks ); // int bottomEdge = pixmapRect // right edge 10px past right edge of pixmapRect @@ -300,7 +300,7 @@ PlaylistDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, painter->restore(); } - QPixmap avatar = index.data( WelcomePlaylistModel::PlaylistRole ).value< Tomahawk::playlist_ptr >()->author()->avatar( Source::FancyStyle ); + QPixmap avatar = index.data( RecentlyPlayedPlaylistsModel::PlaylistRole ).value< Tomahawk::playlist_ptr >()->author()->avatar( Source::FancyStyle ); if ( avatar.isNull() ) avatar = m_defaultAvatar; QRect r( option.rect.width() - avatar.width() - 10, option.rect.top() + option.rect.height()/2 - avatar.height()/2, avatar.width(), avatar.height() ); diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 58570168c..c59f4258f 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -231,7 +231,7 @@ PlaylistItem::setData( const QVariant& v, bool role ) return false; } -bool +SourceTreeItem* PlaylistItem::activateCurrent() { if( ViewManager::instance()->pageForPlaylist( m_playlist ) == ViewManager::instance()->currentPage() ) @@ -239,10 +239,10 @@ PlaylistItem::activateCurrent() model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() ); emit selectRequest( this ); - return true; + return this; } - return false; + return 0; } @@ -386,7 +386,7 @@ DynamicPlaylistItem::icon() const } } -bool +SourceTreeItem* DynamicPlaylistItem::activateCurrent() { if( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) == ViewManager::instance()->currentPage() ) @@ -394,9 +394,9 @@ DynamicPlaylistItem::activateCurrent() model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() ); emit selectRequest( this ); - return true; + return this; } - return false; + return 0; } diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index cc02e7bfb..8ab795bc3 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -41,7 +41,7 @@ public: virtual int peerSortValue() const; virtual int IDValue() const; - virtual bool activateCurrent(); + virtual SourceTreeItem* activateCurrent(); protected: void setLoaded( bool loaded ); @@ -73,7 +73,7 @@ public: virtual int IDValue() const; virtual QIcon icon() const; - virtual bool activateCurrent(); + virtual SourceTreeItem* activateCurrent(); private slots: void onDynamicPlaylistLoaded( Tomahawk::DynamicPlaylistRevision revision ); diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index d3f0af496..023a603ff 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -35,6 +35,8 @@ #include "globalactionmanager.h" #include "dropjob.h" #include "items/playlistitems.h" +#include "playlist/playlistview.h" +#include "playlist/dynamic/widgets/DynamicWidget.h" using namespace Tomahawk; @@ -274,6 +276,19 @@ SourcesModel::removeItem( const Tomahawk::source_ptr& source ) void SourcesModel::viewPageActivated( Tomahawk::ViewPage* page ) { + if ( !m_sourcesWithViewPage.isEmpty() ) + { + // Hide again any offline sources we exposed, since we're showing a different page now. they'll be re-shown if the user selects a playlist that is from an offline user + QList< source_ptr > temp = m_sourcesWithViewPage; + m_sourcesWithViewPage.clear(); + foreach ( const source_ptr& s, temp ) + { + QModelIndex idx = indexFromItem( m_sourcesWithViewPageItems.value( s ) ); + emit dataChanged( idx, idx ); + } + m_sourcesWithViewPageItems.clear(); + } + if ( m_sourceTreeLinks.contains( page ) ) { Q_ASSERT( m_sourceTreeLinks[ page ] ); @@ -287,32 +302,62 @@ SourcesModel::viewPageActivated( Tomahawk::ViewPage* page ) } else { + playlist_ptr p = ViewManager::instance()->playlistForPage( page ); // HACK // try to find it if it is a playlist. not pretty at all.... but this happens when ViewManager loads a playlist or dynplaylist NOT from the sidebar but from somewhere else // we don't know which sourcetreeitem is related to it, so we have to find it. we also don't know if this page is a playlist or dynplaylist or not, but we can't check as we can't // include DynamicWidget.h here (so can't dynamic_cast). // this could also be fixed by keeping a master list of playlists/sourcetreeitems... but that's even uglier i think. this is only called the first time a certain viewpage is clicked from external // sources. - activatePlaylistPage( page, m_rootItem ); + SourceTreeItem* item = activatePlaylistPage( page, m_rootItem ); m_viewPageDelayedCacheItem = page; + + if ( !p.isNull() ) + { + source_ptr s= p->author(); + if ( !s.isNull() && !s->isOnline() && item ) + { + m_sourcesWithViewPage << s; + + // show the collection now... yeah. + if ( !item->parent() || !item->parent()->parent() ) + { + tLog() << "Found playlist item with no category parent or collection parent!" << item->text(); + return; + } + + SourceTreeItem* collectionOfPlaylist = item->parent()->parent(); + if ( !m_rootItem->children().contains( collectionOfPlaylist ) ) // verification to make sure we're not stranded + { + tLog() << "Got what we assumed to be a parent col of a playlist not as a child of our root node...:" << collectionOfPlaylist; + return; + } + + QModelIndex idx = indexFromItem( collectionOfPlaylist ); + m_sourcesWithViewPageItems[ s ] = collectionOfPlaylist; + tDebug() << "Emitting dataChanged for offline source:" << idx << idx.isValid() << collectionOfPlaylist << collectionOfPlaylist->text(); + emit dataChanged( idx, idx ); + + } + } } } -bool +SourceTreeItem* SourcesModel::activatePlaylistPage( ViewPage* p, SourceTreeItem* i ) { if( !i ) - return false; + return 0; if( qobject_cast< PlaylistItem* >( i ) && qobject_cast< PlaylistItem* >( i )->activateCurrent() ) - return true; + return i; - bool ret = false; + SourceTreeItem* ret = 0; for( int k = 0; k < i->children().size(); k++ ) { - if( activatePlaylistPage( p, i->children().at( k ) ) ) - ret = true; + if( SourceTreeItem* retItem = activatePlaylistPage( p, i->children().at( k ) ) ) + ret = retItem; } return ret; diff --git a/src/sourcetree/sourcesmodel.h b/src/sourcetree/sourcesmodel.h index 7bf1c2ef0..d3f8d2f4b 100644 --- a/src/sourcetree/sourcesmodel.h +++ b/src/sourcetree/sourcesmodel.h @@ -93,6 +93,8 @@ public: QModelIndex indexFromItem( SourceTreeItem* item ) const; + QList< Tomahawk::source_ptr > sourcesWithViewPage() const { return m_sourcesWithViewPage; } + public slots: void loadSources(); @@ -120,10 +122,13 @@ private slots: private: SourceTreeItem* itemFromIndex( const QModelIndex& idx ) const; int rowForItem( SourceTreeItem* item ) const; - bool activatePlaylistPage( Tomahawk::ViewPage* p, SourceTreeItem* i ); + SourceTreeItem* activatePlaylistPage( Tomahawk::ViewPage* p, SourceTreeItem* i ); SourceTreeItem* m_rootItem; + QList< Tomahawk::source_ptr > m_sourcesWithViewPage; + QHash< Tomahawk::source_ptr, SourceTreeItem* > m_sourcesWithViewPageItems; + QHash< Tomahawk::ViewPage*, SourceTreeItem* > m_sourceTreeLinks; Tomahawk::ViewPage* m_viewPageDelayedCacheItem; }; diff --git a/src/sourcetree/sourcesproxymodel.cpp b/src/sourcetree/sourcesproxymodel.cpp index 3188cda77..b5d484c5e 100644 --- a/src/sourcetree/sourcesproxymodel.cpp +++ b/src/sourcetree/sourcesproxymodel.cpp @@ -63,6 +63,8 @@ SourcesProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourcePar { if ( sti->source().isNull() || sti->source()->isOnline() ) return true; + else if ( m_model->sourcesWithViewPage().contains( sti->source() ) ) + return true; else return false; } From 33a63a45e8b8191843293b9725023c07d650b873 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 27 Aug 2011 21:46:13 +0200 Subject: [PATCH 41/72] * Fixed mutex deadlock in SourceList. --- src/libtomahawk/sourcelist.cpp | 16 +++++++++------- src/libtomahawk/widgets/RecentPlaylistsModel.cpp | 6 +----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index ab222eeae..0ed9bf10e 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -83,15 +83,17 @@ SourceList::loadSources() void SourceList::setSources( const QList& sources ) { - QMutexLocker lock( &m_mut ); - - m_isReady = true; - foreach( const source_ptr& src, sources ) { - add( src ); - } + QMutexLocker lock( &m_mut ); - tLog() << Q_FUNC_INFO << "- Total sources now:" << m_sources.size(); + m_isReady = true; + foreach( const source_ptr& src, sources ) + { + add( src ); + } + + tLog() << Q_FUNC_INFO << "- Total sources now:" << m_sources.size(); + } emit ready(); } diff --git a/src/libtomahawk/widgets/RecentPlaylistsModel.cpp b/src/libtomahawk/widgets/RecentPlaylistsModel.cpp index 919f7bce3..2a0ea9bec 100644 --- a/src/libtomahawk/widgets/RecentPlaylistsModel.cpp +++ b/src/libtomahawk/widgets/RecentPlaylistsModel.cpp @@ -36,11 +36,7 @@ RecentPlaylistsModel::RecentPlaylistsModel( unsigned int maxPlaylists, QObject* : QAbstractListModel( parent ) , m_maxPlaylists( maxPlaylists ) { - - if ( Servent::instance()->isReady() ) - onReady(); - else - connect( Servent::instance(), SIGNAL( ready() ), this, SLOT( onReady() ) ); + connect( SourceList::instance(), SIGNAL( ready() ), SLOT( onReady() ) ); // Load recent playlists initially refresh(); From dc9f2503220e53833d84f4b05ae6a0ffbca89a40 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 16:07:27 -0400 Subject: [PATCH 42/72] Fix include path --- src/libtomahawk/CMakeLists.txt | 4 +++- src/libtomahawk/utils/tomahawkutils.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index e5ec1cf04..bf7f2fd53 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -414,7 +414,9 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/. ${LIBPORTFWD_INCLUDE_DIR} ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb - ${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/libqnetwm + + # For thirdparty/ + ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) IF( UNIX AND NOT APPLE ) diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 6e991c98e..923f1d2ac 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -46,7 +46,7 @@ #ifdef Q_WS_X11 #include - #include + #include "thirdparty/libqnetwm/netwm.h" #endif #ifdef Q_WS_WIN From 644135c88ac43bdf07acd990ca2a380f06ffb8da Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 16:21:19 -0400 Subject: [PATCH 43/72] Move libqnetwm down one level and hopefully fix building for leo --- src/libtomahawk/CMakeLists.txt | 5 ++--- src/libtomahawk/utils/tomahawkutils.cpp | 2 +- thirdparty/libqnetwm/{ => libqnetwm}/fixx11h.h | 0 thirdparty/libqnetwm/{ => libqnetwm}/netwm.cpp | 0 thirdparty/libqnetwm/{ => libqnetwm}/netwm.h | 0 5 files changed, 3 insertions(+), 4 deletions(-) rename thirdparty/libqnetwm/{ => libqnetwm}/fixx11h.h (100%) rename thirdparty/libqnetwm/{ => libqnetwm}/netwm.cpp (100%) rename thirdparty/libqnetwm/{ => libqnetwm}/netwm.h (100%) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index bf7f2fd53..b63f650b7 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -415,8 +415,6 @@ include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/. ${LIBPORTFWD_INCLUDE_DIR} ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/qxtweb - # For thirdparty/ - ${CMAKE_CURRENT_SOURCE_DIR}/../.. ) IF( UNIX AND NOT APPLE ) @@ -434,7 +432,8 @@ IF( UNIX AND NOT APPLE ) infosystem/infoplugins/unix/fdonotifyplugin.h ) IF( BUILD_GUI AND X11_FOUND ) - SET( libSources ${libSources} ${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/libqnetwm/netwm.cpp ) + INCLUDE_DIRECTORIES( ${THIRDPARTY_DIR}/libqnetwm ) + SET( libSources ${libSources} ${THIRDPARTY_DIR}/libqnetwm/libqnetwm/netwm.cpp ) SET( LINK_LIBRARIES ${LINK_LIBRARIES} ${X11_LIBRARIES} Xcomposite ) ENDIF() ENDIF( UNIX AND NOT APPLE ) diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 923f1d2ac..14df50094 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -46,7 +46,7 @@ #ifdef Q_WS_X11 #include - #include "thirdparty/libqnetwm/netwm.h" + #include #endif #ifdef Q_WS_WIN diff --git a/thirdparty/libqnetwm/fixx11h.h b/thirdparty/libqnetwm/libqnetwm/fixx11h.h similarity index 100% rename from thirdparty/libqnetwm/fixx11h.h rename to thirdparty/libqnetwm/libqnetwm/fixx11h.h diff --git a/thirdparty/libqnetwm/netwm.cpp b/thirdparty/libqnetwm/libqnetwm/netwm.cpp similarity index 100% rename from thirdparty/libqnetwm/netwm.cpp rename to thirdparty/libqnetwm/libqnetwm/netwm.cpp diff --git a/thirdparty/libqnetwm/netwm.h b/thirdparty/libqnetwm/libqnetwm/netwm.h similarity index 100% rename from thirdparty/libqnetwm/netwm.h rename to thirdparty/libqnetwm/libqnetwm/netwm.h From 678eeb1232f14e2e0725ae2fd648c5d07105e984 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 18:00:24 -0400 Subject: [PATCH 44/72] Fix twitter saved offer key issue, maybe --- src/sip/twitter/twitter.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index b01f8a434..c1ce66a07 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -72,16 +72,14 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) if ( !Database::instance() || Database::instance()->dbid() != twitterSavedDbid() ) { - if ( !twitterSavedDbid().isEmpty() ) //remove eventually (post 0.2), here for migration purposes - { - setTwitterCachedDirectMessagesSinceId( 0 ); - setTwitterCachedFriendsSinceId( 0 ); - setTwitterCachedMentionsSinceId( 0 ); - setTwitterCachedPeers( QHash< QString, QVariant >() ); - } - setTwitterSavedDbid( Database::instance()->dbid() ); + setTwitterCachedDirectMessagesSinceId( 0 ); + setTwitterCachedFriendsSinceId( 0 ); + setTwitterCachedMentionsSinceId( 0 ); + setTwitterCachedPeers( QHash< QString, QVariant >() ); } + setTwitterSavedDbid( Database::instance()->dbid() ); + m_checkTimer.setInterval( 150000 ); m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); From d8c09ff5021bf1be9fbf4dc257e3b6de1437914e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 20:28:59 -0400 Subject: [PATCH 45/72] Hopefully really fix the twitter issue --- src/sip/twitter/twitter.cpp | 67 +++++++++++++++++++++++-------------- src/sip/twitter/twitter.h | 13 +++---- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index c1ce66a07..97d6be07f 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -70,12 +70,12 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) { qDebug() << Q_FUNC_INFO; - if ( !Database::instance() || Database::instance()->dbid() != twitterSavedDbid() ) + if ( Database::instance()->dbid() != twitterSavedDbid() ) { setTwitterCachedDirectMessagesSinceId( 0 ); setTwitterCachedFriendsSinceId( 0 ); setTwitterCachedMentionsSinceId( 0 ); - setTwitterCachedPeers( QHash< QString, QVariant >() ); + setTwitterCachedPeers( QVariantHash() ); } setTwitterSavedDbid( Database::instance()->dbid() ); @@ -90,9 +90,9 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) m_configWidget = QWeakPointer< TwitterConfigWidget >( new TwitterConfigWidget( this, 0 ) ); connect( m_configWidget.data(), SIGNAL( twitterAuthed( bool ) ), SLOT( configDialogAuthedSignalSlot( bool ) ) ); - } + void TwitterPlugin::configDialogAuthedSignalSlot( bool authed ) { @@ -169,10 +169,16 @@ TwitterPlugin::connectPlugin( bool startup ) qStableSort( peerlist.begin(), peerlist.end() ); foreach( QString screenName, peerlist ) { - QHash< QString, QVariant > cachedPeer = m_cachedPeers[screenName].toHash(); + QVariantHash cachedPeer = m_cachedPeers[screenName].toHash(); + if ( cachedPeer.contains( "onod" ) && cachedPeer["onod"] != Database::instance()->dbid() ) + { + m_cachedPeers.remove( screenName ); + syncConfig(); + } foreach( QString prop, cachedPeer.keys() ) qDebug() << "TwitterPlugin : " << screenName << ", key " << prop << ", value " << ( cachedPeer[prop].canConvert< QString >() ? cachedPeer[prop].toString() : QString::number( cachedPeer[prop].toInt() ) ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&cachedPeer ) ); + + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, cachedPeer ) ); } if ( twitterOAuthToken().isEmpty() || twitterOAuthTokenSecret().isEmpty() ) @@ -233,6 +239,7 @@ TwitterPlugin::disconnectPlugin() if( !m_twitterAuth.isNull() ) delete m_twitterAuth.data(); + syncConfig(); m_cachedPeers.empty(); m_state = Disconnected; emit stateChanged( m_state ); @@ -347,12 +354,13 @@ TwitterPlugin::connectTimerFired() foreach( QString screenName, peerlist ) { qDebug() << Q_FUNC_INFO << " checking peer " << screenName; - QHash< QString, QVariant > peerData = m_cachedPeers[screenName].toHash(); + QVariantHash peerData = m_cachedPeers[screenName].toHash(); if ( Servent::instance()->connectedToSession( peerData["node"].toString() ) ) { peerData["lastseen"] = QDateTime::currentMSecsSinceEpoch(); m_cachedPeers[screenName] = peerData; + syncConfig(); qDebug() << Q_FUNC_INFO << " already connected"; continue; } @@ -361,6 +369,7 @@ TwitterPlugin::connectTimerFired() { qDebug() << Q_FUNC_INFO << " aging peer " << screenName << " out of cache"; m_cachedPeers.remove( screenName ); + syncConfig(); m_cachedAvatars.remove( screenName ); continue; } @@ -371,7 +380,7 @@ TwitterPlugin::connectTimerFired() continue; } - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, peerData ) ); } } @@ -405,21 +414,23 @@ TwitterPlugin::parseGotTomahawk( const QRegExp ®ex, const QString &screenName else qDebug() << "TwitterPlugin parsed node " << node << " out of the tweet"; - if ( screenName == myScreenName && node == Database::instance()->dbid() ) + if ( node == Database::instance()->dbid() ) { - qDebug() << "My screen name and my dbid found; ignoring"; + qDebug() << "My dbid found; ignoring"; return; } - QHash< QString, QVariant > peerData; + QVariantHash peerData; if( m_cachedPeers.contains( screenName ) ) { peerData = m_cachedPeers[screenName].toHash(); //force a re-send of info but no need to re-register peerData["resend"] = QVariant::fromValue< bool >( true ); + if ( peerData["node"].toString() != node ) + peerData["rekey"] = QVariant::fromValue< bool >( true ); } peerData["node"] = QVariant::fromValue< QString >( node ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, peerData ) ); } void @@ -578,9 +589,9 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) qDebug() << "TwitterPlugin found a peerstart message from " << status.senderScreenName() << " with host " << host << " and port " << port << " and pkey " << pkey << " and node " << splitNode[0] << " destined for node " << splitNode[1]; - QHash< QString, QVariant > peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? + QVariantHash peerData = ( m_cachedPeers.contains( status.senderScreenName() ) ) ? m_cachedPeers[status.senderScreenName()].toHash() : - QHash< QString, QVariant >(); + QVariantHash(); peerData["host"] = QVariant::fromValue< QString >( host ); peerData["port"] = QVariant::fromValue< int >( port ); @@ -588,7 +599,7 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) peerData["node"] = QVariant::fromValue< QString >( splitNode[0] ); peerData["dirty"] = QVariant::fromValue< bool >( true ); - QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&peerData ) ); + QMetaObject::invokeMethod( this, "registerOffer", Q_ARG( QString, status.senderScreenName() ), Q_ARG( QVariantHash, peerData ) ); if ( Database::instance()->dbid().startsWith( splitNode[1] ) ) { @@ -603,7 +614,7 @@ TwitterPlugin::directMessages( const QList< QTweetDMStatus > &messages ) } void -TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) +TwitterPlugin::registerOffer( const QString &screenName, const QVariantHash &peerData ) { qDebug() << Q_FUNC_INFO; @@ -616,7 +627,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( !m_cachedAvatars.contains( screenName ) ) QMetaObject::invokeMethod( this, "fetchAvatar", Q_ARG( QString, screenName ) ); - QHash< QString, QVariant > _peerData( peerData ); + QVariantHash _peerData( peerData ); if ( _peerData.contains( "dirty" ) ) { @@ -644,8 +655,11 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q needToSend = true; } - if ( !m_keyCache.contains( _peerData["okey"].toString() ) ) + if ( _peerData.contains( "rekey" ) || !m_keyCache.contains( _peerData["okey"].toString() ) ) + { + _peerData.remove( "rekey" ); needToAddToCache = true; + } if ( !_peerData.contains( "ohst" ) || !_peerData.contains( "oprt" ) || _peerData["ohst"].toString() != Servent::instance()->externalAddress() || @@ -666,7 +680,7 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q _peerData["oprt"] = QVariant::fromValue< int >( Servent::instance()->externalPort() ); peersChanged = true; if( !Servent::instance()->externalAddress().isEmpty() && !Servent::instance()->externalPort() == 0 ) - QMetaObject::invokeMethod( this, "sendOffer", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + QMetaObject::invokeMethod( this, "sendOffer", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, _peerData ) ); else qDebug() << "TwitterPlugin did not send offer because external address is " << Servent::instance()->externalAddress() << " and external port is " << Servent::instance()->externalPort(); } @@ -674,17 +688,17 @@ TwitterPlugin::registerOffer( const QString &screenName, const QHash< QString, Q if ( peersChanged ) { _peerData["lastseen"] = QString::number( QDateTime::currentMSecsSinceEpoch() ); - m_cachedPeers[screenName] = QVariant::fromValue< QHash< QString, QVariant > >( _peerData ); - setTwitterCachedPeers( m_cachedPeers ); + m_cachedPeers[screenName] = QVariant::fromValue< QVariantHash >( _peerData ); + syncConfig(); } if ( m_state == Connected && _peerData.contains( "host" ) && _peerData.contains( "port" ) && _peerData.contains( "pkey" ) ) - QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), QGenericArgument( "QHash< QString, QVariant >", (const void*)&_peerData ) ); + QMetaObject::invokeMethod( this, "makeConnection", Q_ARG( QString, screenName ), Q_ARG( QVariantHash, _peerData ) ); } void -TwitterPlugin::sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerData ) +TwitterPlugin::sendOffer( const QString &screenName, const QVariantHash &peerData ) { qDebug() << Q_FUNC_INFO; QString offerString = QString( "TOMAHAWKPEER:Host=%1:Port=%2:Node=%3*%4:PKey=%5" ).arg( peerData["ohst"].toString() ) @@ -698,7 +712,7 @@ TwitterPlugin::sendOffer( const QString &screenName, const QHash< QString, QVari } void -TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerData ) +TwitterPlugin::makeConnection( const QString &screenName, const QVariantHash &peerData ) { qDebug() << Q_FUNC_INFO; if ( !peerData.contains( "host" ) || !peerData.contains( "port" ) || !peerData.contains( "pkey" ) || !peerData.contains( "node" ) || @@ -1020,7 +1034,7 @@ TwitterPlugin::setTwitterCachedDirectMessagesSinceId( qint64 cachedId ) TomahawkSettings::instance()->setValue( pluginId() + "/cacheddirectmessagessinceid", cachedId ); } -QHash +QVariantHash TwitterPlugin::twitterCachedPeers() const { TomahawkSettings* s = TomahawkSettings::instance(); @@ -1045,13 +1059,14 @@ TwitterPlugin::twitterCachedPeers() const } s->endGroup(); - return s->value( pluginId() + "/cachedpeers", QHash() ).toHash(); + return s->value( pluginId() + "/cachedpeers", QVariantHash() ).toHash(); } void -TwitterPlugin::setTwitterCachedPeers( const QHash &cachedPeers ) +TwitterPlugin::setTwitterCachedPeers( const QVariantHash &cachedPeers ) { TomahawkSettings::instance()->setValue( pluginId() + "/cachedpeers", cachedPeers ); + TomahawkSettings::instance()->sync(); } Q_EXPORT_PLUGIN2( sipfactory, TwitterFactory ) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index cdeaa8306..7ba36421e 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -108,14 +108,15 @@ private slots: void directMessagePosted( const QTweetDMStatus &message ); void directMessagePostError( QTweetNetBase::ErrorCode errorCode, const QString &message ); void directMessageDestroyed( const QTweetDMStatus &message ); - void registerOffer( const QString &screenName, const QHash< QString, QVariant > &peerdata ); - void sendOffer( const QString &screenName, const QHash< QString, QVariant > &peerdata ); - void makeConnection( const QString &screenName, const QHash< QString, QVariant > &peerdata ); + void registerOffer( const QString &screenName, const QVariantHash &peerdata ); + void sendOffer( const QString &screenName, const QVariantHash &peerdata ); + void makeConnection( const QString &screenName, const QVariantHash &peerdata ); void fetchAvatar( const QString &screenName ); void avatarUserDataSlot( const QTweetUser &user ); void profilePicReply(); private: + inline void syncConfig() { setTwitterCachedPeers( m_cachedPeers ); } bool refreshTwitterAuth(); void parseGotTomahawk( const QRegExp ®ex, const QString &screenName, const QString &text ); // handle per-plugin config @@ -133,8 +134,8 @@ private: void setTwitterCachedMentionsSinceId( qint64 sinceid ); qint64 twitterCachedDirectMessagesSinceId() const; void setTwitterCachedDirectMessagesSinceId( qint64 sinceid ); - QHash twitterCachedPeers() const; - void setTwitterCachedPeers( const QHash &cachedPeers ); + QVariantHash twitterCachedPeers() const; + void setTwitterCachedPeers( const QVariantHash &cachedPeers ); QWeakPointer< TomahawkOAuthTwitter > m_twitterAuth; QWeakPointer< QTweetFriendsTimeline > m_friendsTimeline; @@ -149,7 +150,7 @@ private: qint64 m_cachedFriendsSinceId; qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; - QHash< QString, QVariant > m_cachedPeers; + QVariantHash m_cachedPeers; QHash< QString, QPixmap > m_cachedAvatars; QSet m_keyCache; bool m_finishedFriends; From cca1ac153262551210b86fce2f2e321427eb2e77 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 22:04:24 -0400 Subject: [PATCH 46/72] Some further cleanup and make dms poll a bit more often while keeping the timelines longer --- src/sip/twitter/twitter.cpp | 32 +++++++++++++++++--------------- src/sip/twitter/twitter.h | 3 +-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index 97d6be07f..a28bf19bd 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -59,13 +59,12 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) , m_isAuthed( false ) , m_checkTimer( this ) , m_connectTimer( this ) + , m_dmPollTimer( this ) , m_cachedFriendsSinceId( 0 ) , m_cachedMentionsSinceId( 0 ) , m_cachedDirectMessagesSinceId( 0 ) , m_cachedPeers() , m_keyCache() - , m_finishedFriends( false ) - , m_finishedMentions( false ) , m_state( Disconnected ) { qDebug() << Q_FUNC_INFO; @@ -84,6 +83,10 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); + m_dmPollTimer.setInterval( 60000 ); + m_dmPollTimer.setSingleShot( false ); + connect( &m_dmPollTimer, SIGNAL( timeout() ), SLOT( pollDirectMessages() ) ); + m_connectTimer.setInterval( 150000 ); m_connectTimer.setSingleShot( false ); connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); @@ -226,6 +229,7 @@ TwitterPlugin::disconnectPlugin() qDebug() << Q_FUNC_INFO; m_checkTimer.stop(); m_connectTimer.stop(); + m_dmPollTimer.stop(); if( !m_friendsTimeline.isNull() ) delete m_friendsTimeline.data(); if( !m_mentions.isNull() ) @@ -255,6 +259,7 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) m_state = Disconnected; m_connectTimer.stop(); m_checkTimer.stop(); + m_dmPollTimer.stop(); emit stateChanged( m_state ); } else @@ -279,6 +284,7 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) emit stateChanged( m_state ); m_connectTimer.start(); m_checkTimer.start(); + m_dmPollTimer.start(); QMetaObject::invokeMethod( this, "checkTimerFired", Qt::AutoConnection ); QTimer::singleShot( 20000, this, SLOT( connectTimerFired() ) ); } @@ -297,6 +303,7 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) m_state = Disconnected; m_connectTimer.stop(); m_checkTimer.stop(); + m_dmPollTimer.stop(); emit stateChanged( m_state ); } } @@ -438,7 +445,6 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) { qDebug() << Q_FUNC_INFO; QRegExp regex( s_gotTomahawkRegex, Qt::CaseSensitive, QRegExp::RegExp2 ); - QString myScreenName = twitterScreenName(); QHash< QString, QTweetStatus > latestHash; foreach ( QTweetStatus status, statuses ) @@ -465,9 +471,6 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) } setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); - - m_finishedFriends = true; - QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); } void @@ -501,20 +504,11 @@ TwitterPlugin::mentionsStatuses( const QList< QTweetStatus > &statuses ) } setTwitterCachedMentionsSinceId( m_cachedMentionsSinceId ); - - m_finishedMentions = true; - QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); } void TwitterPlugin::pollDirectMessages() { - if ( !m_finishedMentions || !m_finishedFriends ) - return; - - m_finishedFriends = false; - m_finishedMentions = false; - if ( !isValid() ) return; @@ -721,6 +715,14 @@ TwitterPlugin::makeConnection( const QString &screenName, const QVariantHash &pe qDebug() << "TwitterPlugin could not find host and/or port and/or pkey and/or node for peer " << screenName; return; } + + if ( peerData["host"].toString() == Servent::instance()->externalAddress() && + peerData["port"].toInt() == Servent::instance()->externalPort() ) + { + qDebug() << "TwitterPlugin asked to make connection to our own host and port, ignoring " << screenName; + return; + } + QString friendlyName = QString( '@' + screenName ); if ( !Servent::instance()->connectedToSession( peerData["node"].toString() ) ) Servent::instance()->connectToPeer( peerData["host"].toString(), diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 7ba36421e..adeebc34d 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -147,14 +147,13 @@ private: bool m_isAuthed; QTimer m_checkTimer; QTimer m_connectTimer; + QTimer m_dmPollTimer; qint64 m_cachedFriendsSinceId; qint64 m_cachedMentionsSinceId; qint64 m_cachedDirectMessagesSinceId; QVariantHash m_cachedPeers; QHash< QString, QPixmap > m_cachedAvatars; QSet m_keyCache; - bool m_finishedFriends; - bool m_finishedMentions; ConnectionState m_state; QWeakPointer m_configWidget; From 9e4d320cffb8b66de7cc021262d684359598a5c9 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 22:04:58 -0400 Subject: [PATCH 47/72] Make public/mentions timelines check less often to offset bumping up the dm timer --- src/sip/twitter/twitter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index a28bf19bd..0e0d1cd10 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -79,7 +79,7 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) setTwitterSavedDbid( Database::instance()->dbid() ); - m_checkTimer.setInterval( 150000 ); + m_checkTimer.setInterval( 180000 ); m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); @@ -87,7 +87,7 @@ TwitterPlugin::TwitterPlugin( const QString& pluginId ) m_dmPollTimer.setSingleShot( false ); connect( &m_dmPollTimer, SIGNAL( timeout() ), SLOT( pollDirectMessages() ) ); - m_connectTimer.setInterval( 150000 ); + m_connectTimer.setInterval( 180000 ); m_connectTimer.setSingleShot( false ); connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); From 71339862b649bef698971d7c6861b4e64c414bc5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sat, 27 Aug 2011 22:09:45 -0400 Subject: [PATCH 48/72] Changelogify Twitter issue --- ChangeLog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog b/ChangeLog index 9d543226a..31779ad20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,8 @@ Version 0.3.0: * Added MPRIS 2.1 support. Version 0.2.3: + * Fixed an issue where the Twitter plugin could get out of sync if the + database was cleared, leading to eventual crashes when connecting to peers. * Fixed issue where artist bio could be referring to a different artist. * Opening a "tomahawk" URL (or other URL with Tomahawk) brings the Tomahawk window to the foreground. From 89aff8bcbb2056b14f4eba5d44a468c3cac3feba Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 28 Aug 2011 04:50:20 +0200 Subject: [PATCH 49/72] * Cleaned up database syncing a bit. --- .../databasecommand_collectionstats.cpp | 6 +-- src/libtomahawk/network/dbsyncconnection.cpp | 46 ++++++++++--------- src/libtomahawk/network/dbsyncconnection.h | 13 ++++-- src/libtomahawk/source.cpp | 11 +---- src/libtomahawk/source.h | 15 ++---- 5 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_collectionstats.cpp b/src/libtomahawk/database/databasecommand_collectionstats.cpp index dec833e8f..4138bc58c 100644 --- a/src/libtomahawk/database/databasecommand_collectionstats.cpp +++ b/src/libtomahawk/database/databasecommand_collectionstats.cpp @@ -57,11 +57,7 @@ DatabaseCommand_CollectionStats::exec( DatabaseImpl* dbi ) { m.insert( "numfiles", query.value( 0 ).toInt() ); m.insert( "lastmodified", query.value( 1 ).toInt() ); - - if ( !source()->isLocal() && !source()->lastOpGuid().isEmpty() ) - m.insert( "lastop", source()->lastOpGuid() ); - else - m.insert( "lastop", query.value( 2 ).toString() ); + m.insert( "lastop", query.value( 2 ).toString() ); } emit done( m ); diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 6c193e257..d69518412 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -134,27 +134,23 @@ DBSyncConnection::check() } m_uscache.clear(); - m_themcache.clear(); m_us.clear(); changeState( CHECKING ); // load last-modified etc data for our collection and theirs from our DB: - DatabaseCommand_CollectionStats* cmd_us = - new DatabaseCommand_CollectionStats( SourceList::instance()->getLocal() ); - - DatabaseCommand_CollectionStats* cmd_them = - new DatabaseCommand_CollectionStats( m_source ); - - connect( cmd_us, SIGNAL( done( QVariantMap ) ), - SLOT( gotUs( QVariantMap ) ) ); - - connect( cmd_them, SIGNAL( done( QVariantMap ) ), - SLOT( gotThemCache( QVariantMap ) ) ); - - + DatabaseCommand_CollectionStats* cmd_us = new DatabaseCommand_CollectionStats( SourceList::instance()->getLocal() ); + connect( cmd_us, SIGNAL( done( QVariantMap ) ), SLOT( gotUs( QVariantMap ) ) ); Database::instance()->enqueue( QSharedPointer(cmd_us) ); - Database::instance()->enqueue( QSharedPointer(cmd_them) ); + + if ( m_lastop.isEmpty() ) + { + DatabaseCommand_CollectionStats* cmd_them = new DatabaseCommand_CollectionStats( m_source ); + connect( cmd_them, SIGNAL( done( QVariantMap ) ), SLOT( gotThem( QVariantMap ) ) ); + Database::instance()->enqueue( QSharedPointer(cmd_them) ); + } + else + fetchOpsData( m_lastop ); // restarts idle countdown m_timer.start(); @@ -174,16 +170,24 @@ DBSyncConnection::gotUs( const QVariantMap& m ) /// Called once we've loaded our cached data about their collection void -DBSyncConnection::gotThemCache( const QVariantMap& m ) +DBSyncConnection::gotThem( const QVariantMap& m ) +{ + m_lastop = m.value( "lastop" ).toString(); + + fetchOpsData( m_lastop ); +} + + +void +DBSyncConnection::fetchOpsData( const QString& sinceguid ) { - m_themcache = m; changeState( FETCHING ); - tLog() << "Sending a FETCHOPS cmd since:" << m_themcache.value( "lastop" ).toString(); + tLog() << "Sending a FETCHOPS cmd since:" << sinceguid; QVariantMap msg; msg.insert( "method", "fetchops" ); - msg.insert( "lastop", m_themcache.value( "lastop" ).toString() ); + msg.insert( "lastop", sinceguid ); sendMsg( msg ); } @@ -235,8 +239,6 @@ DBSyncConnection::handleMsg( msg_ptr msg ) return; } -// qDebug() << "APPLYING CMD" << cmd->commandname() << cmd->guid(); - if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch { changeState( SAVING ); // just DB work left to complete @@ -244,7 +246,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) } if ( !cmd->singletonCmd() ) - m_source->setLastOpGuid( cmd->guid() ); + m_lastop = cmd->guid(); Database::instance()->enqueue( QSharedPointer( cmd ) ); return; diff --git a/src/libtomahawk/network/dbsyncconnection.h b/src/libtomahawk/network/dbsyncconnection.h index 4fbccdcc5..84080f8e4 100644 --- a/src/libtomahawk/network/dbsyncconnection.h +++ b/src/libtomahawk/network/dbsyncconnection.h @@ -64,9 +64,13 @@ public slots: private slots: void gotUs( const QVariantMap& m ); - void gotThemCache( const QVariantMap& m ); - void lastOpApplied(); + void gotThem( const QVariantMap& m ); + + void fetchOpsData( const QString& sinceguid ); void sendOpsData( QString sinceguid, QString lastguid, QList< dbop_ptr > ops ); + + void lastOpApplied(); + void check(); void idleTimeout(); @@ -76,11 +80,12 @@ private: void changeState( State newstate ); Tomahawk::source_ptr m_source; - QVariantMap m_us, m_uscache, m_themcache; - State m_state; + QVariantMap m_us, m_uscache; + QString m_lastop; QString m_lastSentOp; + State m_state; QTimer m_timer; }; diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 148e53510..d809389b4 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -74,7 +74,7 @@ Source::setControlConnection( ControlConnection* cc ) { m_cc = cc; if ( cc ) - connect( cc, SIGNAL( finished() ), SLOT( remove() ), Qt::QueuedConnection ); + connect( cc, SIGNAL( finished() ), SLOT( setOffline() ), Qt::QueuedConnection ); } @@ -97,15 +97,6 @@ Source::setStats( const QVariantMap& m ) } -void -Source::remove() -{ - qDebug() << Q_FUNC_INFO; - - setOffline(); -} - - QString Source::friendlyName() const { diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index 655ea44d8..36eeaee03 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -40,8 +40,9 @@ class DLLEXPORT Source : public QObject { Q_OBJECT -friend class ::DatabaseCommand_LogPlayback; friend class ::DBSyncConnection; +friend class ::ControlConnection; +friend class ::DatabaseCommand_LogPlayback; friend class ::DatabaseCommand_SocialAction; public: @@ -53,8 +54,6 @@ public: bool isLocal() const { return m_isLocal; } bool isOnline() const { return m_online; } - QString lastOpGuid() const { return m_lastOpGuid; } - QString userName() const { return m_username; } QString friendlyName() const; void setFriendlyName( const QString& fname ); @@ -73,9 +72,6 @@ public: void scanningProgress( unsigned int files ); void scanningFinished( unsigned int files ); - void setOffline(); - void setOnline(); - unsigned int trackCount() const; Tomahawk::query_ptr currentTrack() const { return m_currentTrack; } @@ -105,10 +101,10 @@ public slots: void setStats( const QVariantMap& m ); private slots: - void setLastOpGuid( const QString& guid ) { m_lastOpGuid = guid; } - void dbLoaded( unsigned int id, const QString& fname ); - void remove(); + + void setOffline(); + void setOnline(); void onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::State oldstate, const QString& info ); void onPlaybackStarted( const Tomahawk::query_ptr& query ); @@ -124,7 +120,6 @@ private: int m_id; QList< QSharedPointer > m_collections; QVariantMap m_stats; - QString m_lastOpGuid; bool m_scrubFriendlyName; Tomahawk::query_ptr m_currentTrack; From c5876c0f9e77c3a58f9fc20e7dc0bb754104bdad Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 28 Aug 2011 05:44:45 +0200 Subject: [PATCH 50/72] * Absolutely prevent dupe temp commands from being executed twice. --- src/libtomahawk/network/dbsyncconnection.cpp | 18 +++++++++++++++--- src/libtomahawk/network/dbsyncconnection.h | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index d69518412..dc925c1a9 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -238,6 +238,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) lastOpApplied(); return; } + QSharedPointer cmdsp = QSharedPointer(cmd); if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch { @@ -245,10 +246,21 @@ DBSyncConnection::handleMsg( msg_ptr msg ) connect( cmd, SIGNAL( finished() ), SLOT( lastOpApplied() ) ); } - if ( !cmd->singletonCmd() ) - m_lastop = cmd->guid(); + if ( m_recentTempOps.contains( cmd->guid() ) ) + { + qDebug() << "Ignoring dupe temporary command:" << cmd->guid(); + return; + } - Database::instance()->enqueue( QSharedPointer( cmd ) ); + if ( !cmd->singletonCmd() ) + { + m_lastop = cmd->guid(); + m_recentTempOps.clear(); + } + else + m_recentTempOps << cmd->guid(); + + Database::instance()->enqueue( cmdsp ); return; } diff --git a/src/libtomahawk/network/dbsyncconnection.h b/src/libtomahawk/network/dbsyncconnection.h index 84080f8e4..0f4e24c8a 100644 --- a/src/libtomahawk/network/dbsyncconnection.h +++ b/src/libtomahawk/network/dbsyncconnection.h @@ -84,6 +84,7 @@ private: QString m_lastop; QString m_lastSentOp; + QStringList m_recentTempOps; State m_state; QTimer m_timer; From dffb3deebe476a2c1dce9f03d418dfb94d599e78 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 28 Aug 2011 06:08:45 +0200 Subject: [PATCH 51/72] * Updated ChangeLog. --- ChangeLog | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 31779ad20..72035b18a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,19 +3,23 @@ Version 0.3.0: Version 0.2.3: * Fixed an issue where the Twitter plugin could get out of sync if the - database was cleared, leading to eventual crashes when connecting to peers. + database was cleared, leading to eventual crashes when re-connecting. + * Fixed duplicate albums showing up on Dashboard. + * Automatically sort search results by score. + * Fixed stations being stuck not fetching more songs. * Fixed issue where artist bio could be referring to a different artist. * Opening a "tomahawk" URL (or other URL with Tomahawk) brings the Tomahawk window to the foreground. Version 0.2.2: - * Fixed crash when pressing previous and next when playing a song from the Queue. - * Fixed issue where wrench for newly added resolvers would not show up immediately. + * Fixed crash pressing previous and next when playing a song from the Queue. + * Fixed issue where wrench for newly added resolvers would not show up. * Fixed sidebar statistics not updating after collection scan finished. * Fixed omitting a few tracks in the Collection tree-view. * Fixed sidebar & track sorting issues. * Seek- & volume sliders now directly jump to the position you clicked on. - * Added ability to drag artists and albums within Tomahawk (to playlists, queue, etc.). + * Added ability to drag artists and albums within Tomahawk. + * (OS X) Fixed Ogg Vorbis support. Version 0.2.1: * Fixed crashing trying to play an unavailable track. From ea6d8920583a1c6140f7095681302eae07c89c26 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 02:16:27 -0400 Subject: [PATCH 52/72] Fix regression in audioengine logic when encountering the end of the playlist --- src/libtomahawk/audio/audioengine.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index d14731106..95cacfc27 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -552,7 +552,7 @@ AudioEngine::playlistNextTrackReady() return; m_waitingOnNewTrack = false; - next(); + loadNextTrack(); } @@ -604,7 +604,14 @@ AudioEngine::onStateChanged( Phonon::State newState, Phonon::State oldState ) { m_expectStop = false; tDebug( LOGEXTRA ) << "Finding next track."; - next(); + if ( canGoNext() ) + loadNextTrack(); + else + { + if ( !m_playlist.isNull() && m_playlist.data()->retryMode() == Tomahawk::PlaylistInterface::Retry ) + m_waitingOnNewTrack = true; + stop(); + } } } } From 7166ba161f2ff8c8d9d90bd519ef8264d0aa192a Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 02:23:23 -0400 Subject: [PATCH 53/72] Update changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 72035b18a..a5fcb8a89 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ Version 0.3.0: * Added MPRIS 2.1 support. Version 0.2.3: + * When Listening Along, the last song a peer plays is no longer duplicated. * Fixed an issue where the Twitter plugin could get out of sync if the database was cleared, leading to eventual crashes when re-connecting. * Fixed duplicate albums showing up on Dashboard. From d2af8a8a393c86492d32530c8b336fcf94f212c2 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 03:47:11 -0400 Subject: [PATCH 54/72] Fix null pointer crash in treeproxymodel --- src/libtomahawk/playlist/treeproxymodel.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/treeproxymodel.cpp b/src/libtomahawk/playlist/treeproxymodel.cpp index 4dbd8e0c3..a8ffe3e7f 100644 --- a/src/libtomahawk/playlist/treeproxymodel.cpp +++ b/src/libtomahawk/playlist/treeproxymodel.cpp @@ -230,7 +230,7 @@ Tomahawk::result_ptr TreeProxyModel::currentItem() const { TreeModelItem* item = itemFromIndex( mapToSource( currentIndex() ) ); - if ( item && item->result()->isOnline() ) + if ( item && !item->result().isNull() && item->result()->isOnline() ) return item->result(); return Tomahawk::result_ptr(); } @@ -239,6 +239,9 @@ TreeProxyModel::currentItem() const QString TreeProxyModel::textForItem( TreeModelItem* item ) const { + if ( !item ) + return QString(); + if ( !item->artist().isNull() ) { return item->artist()->name(); From e5f6f40289ce496fa47c36c533f346904cd0ca58 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 04:16:18 -0400 Subject: [PATCH 55/72] Initial work on the sleeker slider --- src/audiocontrols.cpp | 40 ++++++++++++++++++++++---- src/audiocontrols.h | 4 +++ src/libtomahawk/widgets/SeekSlider.cpp | 24 ++++++++++++++++ src/libtomahawk/widgets/SeekSlider.h | 14 +++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index ca161106c..d9f9c2d9f 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -90,11 +90,16 @@ AudioControls::AudioControls( QWidget* parent ) ui->ownerLabel->setForegroundRole( QPalette::Dark ); ui->metaDataArea->setStyleSheet( "QWidget#metaDataArea {\nborder-width: 4px;\nborder-image: url(" RESPATH "images/now-playing-panel.png) 4 4 4 4 stretch stretch; }" ); - + ui->seekSlider->setEnabled( true ); ui->volumeSlider->setRange( 0, 100 ); ui->volumeSlider->setValue( AudioEngine::instance()->volume() ); + m_sliderTimeLine.setCurveShape( QTimeLine::LinearCurve ); + ui->seekSlider->setTimeLine( &m_sliderTimeLine ); + + connect( &m_sliderTimeLine, SIGNAL( frameChanged( int ) ), ui->seekSlider, SLOT( setValue( int ) ) ); + connect( ui->seekSlider, SIGNAL( valueChanged( int ) ), AudioEngine::instance(), SLOT( seek( int ) ) ); connect( ui->volumeSlider, SIGNAL( valueChanged( int ) ), AudioEngine::instance(), SLOT( setVolume( int ) ) ); connect( ui->prevButton, SIGNAL( clicked() ), AudioEngine::instance(), SLOT( previous() ) ); @@ -179,7 +184,7 @@ AudioControls::onVolumeChanged( int volume ) void AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) { - tDebug( LOGEXTRA ) << Q_FUNC_INFO; + tDebug() << Q_FUNC_INFO; onPlaybackLoading( result ); @@ -242,7 +247,7 @@ AudioControls::infoSystemFinished( QString target ) void AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) { - tDebug( LOGEXTRA ) << Q_FUNC_INFO; + tDebug() << Q_FUNC_INFO; m_currentTrack = result; @@ -254,8 +259,16 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) ui->timeLabel->setText( TomahawkUtils::timeToString( 0 ) ); ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result->duration() ) ); - ui->seekSlider->setRange( 0, m_currentTrack->duration() * 1000 ); + ui->seekSlider->setRange( 0, result->duration() * 1000 ); ui->seekSlider->setValue( 0 ); + + tLog() << Q_FUNC_INFO << " setting sliderTimeLine duration to " << (result->duration() * 1000); + tLog() << Q_FUNC_INFO << " setting sliderTimeLine frames to " << (result->duration() * 1000); + + m_sliderTimeLine.setDuration( result->duration() * 1000 ); + m_sliderTimeLine.setFrameRange( 0, result->duration() * 1000 ); + m_sliderTimeLine.setCurrentTime( 0 ); + ui->seekSlider->setVisible( true ); ui->stackedLayout->setCurrentWidget( ui->pauseButton ); @@ -293,20 +306,26 @@ AudioControls::socialActionsLoaded() void AudioControls::onPlaybackPaused() { + tDebug() << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->playPauseButton ); + m_sliderTimeLine.setPaused( true ); } void AudioControls::onPlaybackResumed() { + tDebug() << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->pauseButton ); ui->loveButton->setVisible( true ); + ui->seekSlider->setNeedsUpdate( true ); + m_sliderTimeLine.resume(); } void AudioControls::onPlaybackStopped() { + tDebug() << Q_FUNC_INFO; m_currentTrack.clear(); ui->artistTrackLabel->setText( "" ); @@ -316,6 +335,8 @@ AudioControls::onPlaybackStopped() ui->timeLeftLabel->setText( "" ); ui->coverImage->setPixmap( QPixmap() ); ui->seekSlider->setVisible( false ); + m_sliderTimeLine.stop(); + m_sliderTimeLine.setCurrentTime( 0 ); ui->stackedLayout->setCurrentWidget( ui->playPauseButton ); ui->loveButton->setEnabled( false ); @@ -326,6 +347,7 @@ AudioControls::onPlaybackStopped() void AudioControls::onPlaybackTimer( qint64 msElapsed ) { + tDebug() << Q_FUNC_INFO; if ( m_currentTrack.isNull() ) return; @@ -334,7 +356,15 @@ AudioControls::onPlaybackTimer( qint64 msElapsed ) const int seconds = msElapsed / 1000; ui->timeLabel->setText( TomahawkUtils::timeToString( seconds ) ); ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) ); - ui->seekSlider->setValue( msElapsed ); + tLog() << Q_FUNC_INFO << " setting sliderTimeLine elapsed time to " << msElapsed; + + if ( ui->seekSlider->needsUpdate() ) + { + m_sliderTimeLine.setCurrentTime( msElapsed ); + ui->seekSlider->setNeedsUpdate( false ); + } + else if ( m_sliderTimeLine.state() == QTimeLine::NotRunning ) + m_sliderTimeLine.start(); ui->seekSlider->blockSignals( false ); } diff --git a/src/audiocontrols.h b/src/audiocontrols.h index c4e8cfe27..476e97e79 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -20,6 +20,7 @@ #define AUDIOCONTROLS_H #include +#include #include "result.h" #include "playlistinterface.h" @@ -79,6 +80,7 @@ private slots: void droppedTracks( QList ); void socialActionsLoaded(); + private: Ui::AudioControls *ui; @@ -87,6 +89,8 @@ private: Tomahawk::result_ptr m_currentTrack; Tomahawk::PlaylistInterface::RepeatMode m_repeatMode; bool m_shuffled; + + QTimeLine m_sliderTimeLine; }; #endif // AUDIOCONTROLS_H diff --git a/src/libtomahawk/widgets/SeekSlider.cpp b/src/libtomahawk/widgets/SeekSlider.cpp index 007c18bef..abed4b9e2 100644 --- a/src/libtomahawk/widgets/SeekSlider.cpp +++ b/src/libtomahawk/widgets/SeekSlider.cpp @@ -19,6 +19,7 @@ #include "SeekSlider.h" #include +#include #include "utils/tomahawkutils.h" #include "utils/logger.h" @@ -26,6 +27,8 @@ SeekSlider::SeekSlider( QWidget* parent ) : QSlider( parent ) + , m_timeLine( 0 ) + , m_needsUpdate( false ) { setFixedHeight( 20 ); setStyleSheet( "QSlider::groove::horizontal {" @@ -64,3 +67,24 @@ SeekSlider::mousePressEvent( QMouseEvent* event ) else QSlider::mousePressEvent( event ); } + + +void +SeekSlider::setValue( int value ) +{ + int newVal = value; + if ( value > maximum() ) + newVal = maximum(); + if ( value < minimum() ) + newVal = minimum(); + + if ( !m_timeLine || sender() != m_timeLine ) + { + QSlider::setValue( newVal ); + return; + } + + blockSignals( true ); + QSlider::setValue( newVal ); + blockSignals( false ); +} diff --git a/src/libtomahawk/widgets/SeekSlider.h b/src/libtomahawk/widgets/SeekSlider.h index 42392b202..9ea33d5f1 100644 --- a/src/libtomahawk/widgets/SeekSlider.h +++ b/src/libtomahawk/widgets/SeekSlider.h @@ -23,6 +23,8 @@ #include "dllmacro.h" +class QTimeLine; + class DLLEXPORT SeekSlider : public QSlider { Q_OBJECT @@ -31,8 +33,20 @@ public: SeekSlider( QWidget* parent = 0 ); ~SeekSlider(); + void setTimeLine( QTimeLine* timeline ) { m_timeLine = timeline; } + + void setNeedsUpdate( bool needsUpdate ) { m_needsUpdate = needsUpdate; } + bool needsUpdate() { return m_needsUpdate; } + +public slots: + void setValue( int value ); + protected: void mousePressEvent( QMouseEvent* event ); + +private: + QTimeLine* m_timeLine; + bool m_needsUpdate; }; #endif // SEEKSLIDER_H From 4b4cecc027aa3dc226aa91f14d77360fd7a1afe1 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 14:38:33 -0400 Subject: [PATCH 56/72] Finish making the seekslider the sleekslider --- src/audiocontrols.cpp | 48 +++++++++++++++----------- src/libtomahawk/widgets/SeekSlider.cpp | 1 - src/libtomahawk/widgets/SeekSlider.h | 4 --- 3 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index d9f9c2d9f..1061cca14 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -186,8 +186,30 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) { tDebug() << Q_FUNC_INFO; - onPlaybackLoading( result ); + if ( result.isNull() ) + return; + + if ( m_currentTrack.isNull() || ( !m_currentTrack.isNull() && m_currentTrack.data()->id() != result.data()->id() ) ) + onPlaybackLoading( result ); + qint64 duration = AudioEngine::instance()->currentTrackTotalTime(); + + if ( duration == -1 ) + duration = result.data()->duration() * 1000; + + ui->seekSlider->setRange( 0, duration ); + ui->seekSlider->setValue( 0 ); + + tLog() << Q_FUNC_INFO << " setting sliderTimeLine duration to " << (duration); + tLog() << Q_FUNC_INFO << " setting sliderTimeLine frames to " << (duration); + + m_sliderTimeLine.stop(); + m_sliderTimeLine.setDuration( duration ); + m_sliderTimeLine.setFrameRange( 0, duration ); + m_sliderTimeLine.setCurrentTime( 0 ); + + ui->seekSlider->setVisible( true ); + Tomahawk::InfoSystem::InfoCriteriaHash trackInfo; trackInfo["artist"] = result->artist()->name(); trackInfo["album"] = result->album()->name(); @@ -257,19 +279,7 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) ui->coverImage->setPixmap( m_defaultCover ); ui->timeLabel->setText( TomahawkUtils::timeToString( 0 ) ); - ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result->duration() ) ); - - ui->seekSlider->setRange( 0, result->duration() * 1000 ); - ui->seekSlider->setValue( 0 ); - - tLog() << Q_FUNC_INFO << " setting sliderTimeLine duration to " << (result->duration() * 1000); - tLog() << Q_FUNC_INFO << " setting sliderTimeLine frames to " << (result->duration() * 1000); - - m_sliderTimeLine.setDuration( result->duration() * 1000 ); - m_sliderTimeLine.setFrameRange( 0, result->duration() * 1000 ); - m_sliderTimeLine.setCurrentTime( 0 ); - - ui->seekSlider->setVisible( true ); + ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result.data()->duration() ) ); ui->stackedLayout->setCurrentWidget( ui->pauseButton ); @@ -317,7 +327,6 @@ AudioControls::onPlaybackResumed() tDebug() << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->pauseButton ); ui->loveButton->setVisible( true ); - ui->seekSlider->setNeedsUpdate( true ); m_sliderTimeLine.resume(); } @@ -358,13 +367,10 @@ AudioControls::onPlaybackTimer( qint64 msElapsed ) ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) ); tLog() << Q_FUNC_INFO << " setting sliderTimeLine elapsed time to " << msElapsed; - if ( ui->seekSlider->needsUpdate() ) - { + if ( m_sliderTimeLine.currentTime() > msElapsed ) m_sliderTimeLine.setCurrentTime( msElapsed ); - ui->seekSlider->setNeedsUpdate( false ); - } - else if ( m_sliderTimeLine.state() == QTimeLine::NotRunning ) - m_sliderTimeLine.start(); + else if ( m_sliderTimeLine.duration() > msElapsed && m_sliderTimeLine.state() == QTimeLine::NotRunning ) + m_sliderTimeLine.resume(); ui->seekSlider->blockSignals( false ); } diff --git a/src/libtomahawk/widgets/SeekSlider.cpp b/src/libtomahawk/widgets/SeekSlider.cpp index abed4b9e2..87c4699a1 100644 --- a/src/libtomahawk/widgets/SeekSlider.cpp +++ b/src/libtomahawk/widgets/SeekSlider.cpp @@ -28,7 +28,6 @@ SeekSlider::SeekSlider( QWidget* parent ) : QSlider( parent ) , m_timeLine( 0 ) - , m_needsUpdate( false ) { setFixedHeight( 20 ); setStyleSheet( "QSlider::groove::horizontal {" diff --git a/src/libtomahawk/widgets/SeekSlider.h b/src/libtomahawk/widgets/SeekSlider.h index 9ea33d5f1..034c214ce 100644 --- a/src/libtomahawk/widgets/SeekSlider.h +++ b/src/libtomahawk/widgets/SeekSlider.h @@ -35,9 +35,6 @@ public: void setTimeLine( QTimeLine* timeline ) { m_timeLine = timeline; } - void setNeedsUpdate( bool needsUpdate ) { m_needsUpdate = needsUpdate; } - bool needsUpdate() { return m_needsUpdate; } - public slots: void setValue( int value ); @@ -46,7 +43,6 @@ protected: private: QTimeLine* m_timeLine; - bool m_needsUpdate; }; #endif // SEEKSLIDER_H From a9a9d11f974d0969b0f873fdbfb4d16ce82ba30c Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 14:41:02 -0400 Subject: [PATCH 57/72] Remove debugging --- src/audiocontrols.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 1061cca14..b324f4fda 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -184,7 +184,7 @@ AudioControls::onVolumeChanged( int volume ) void AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) { - tDebug() << Q_FUNC_INFO; + tDebug( LOGEXTRA ) << Q_FUNC_INFO; if ( result.isNull() ) return; @@ -200,9 +200,6 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) ui->seekSlider->setRange( 0, duration ); ui->seekSlider->setValue( 0 ); - tLog() << Q_FUNC_INFO << " setting sliderTimeLine duration to " << (duration); - tLog() << Q_FUNC_INFO << " setting sliderTimeLine frames to " << (duration); - m_sliderTimeLine.stop(); m_sliderTimeLine.setDuration( duration ); m_sliderTimeLine.setFrameRange( 0, duration ); @@ -269,7 +266,7 @@ AudioControls::infoSystemFinished( QString target ) void AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) { - tDebug() << Q_FUNC_INFO; + tDebug( LOGEXTRA ) << Q_FUNC_INFO; m_currentTrack = result; @@ -316,7 +313,7 @@ AudioControls::socialActionsLoaded() void AudioControls::onPlaybackPaused() { - tDebug() << Q_FUNC_INFO; + tDebug( LOGEXTRA ) << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->playPauseButton ); m_sliderTimeLine.setPaused( true ); } @@ -324,7 +321,7 @@ AudioControls::onPlaybackPaused() void AudioControls::onPlaybackResumed() { - tDebug() << Q_FUNC_INFO; + tDebug( LOGEXTRA ) << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->pauseButton ); ui->loveButton->setVisible( true ); m_sliderTimeLine.resume(); @@ -334,7 +331,7 @@ AudioControls::onPlaybackResumed() void AudioControls::onPlaybackStopped() { - tDebug() << Q_FUNC_INFO; + tDebug( LOGEXTRA ) << Q_FUNC_INFO; m_currentTrack.clear(); ui->artistTrackLabel->setText( "" ); @@ -356,7 +353,6 @@ AudioControls::onPlaybackStopped() void AudioControls::onPlaybackTimer( qint64 msElapsed ) { - tDebug() << Q_FUNC_INFO; if ( m_currentTrack.isNull() ) return; @@ -365,7 +361,6 @@ AudioControls::onPlaybackTimer( qint64 msElapsed ) const int seconds = msElapsed / 1000; ui->timeLabel->setText( TomahawkUtils::timeToString( seconds ) ); ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) ); - tLog() << Q_FUNC_INFO << " setting sliderTimeLine elapsed time to " << msElapsed; if ( m_sliderTimeLine.currentTime() > msElapsed ) m_sliderTimeLine.setCurrentTime( msElapsed ); From 57e72e53f9220f49ae3c369cd51e8d91ed7bd278 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 16:13:27 -0400 Subject: [PATCH 58/72] Fix seeking for local files; also, disable dragging the seek slider if phonon reports that the object cannot be seeked, to not have ugly jumping around. --- src/audiocontrols.cpp | 30 +++++++++++++++++++++++++-- src/audiocontrols.h | 2 ++ src/libtomahawk/audio/audioengine.cpp | 5 ++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index b324f4fda..6213ca6ed 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -126,6 +126,7 @@ AudioControls::AudioControls( QWidget* parent ) connect( AudioEngine::instance(), SIGNAL( paused() ), SLOT( onPlaybackPaused() ) ); connect( AudioEngine::instance(), SIGNAL( resumed() ), SLOT( onPlaybackResumed() ) ); connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ) ); + connect( AudioEngine::instance(), SIGNAL( seeked( qint64 ) ), SLOT( onPlaybackSeeked( qint64 ) ) ); connect( AudioEngine::instance(), SIGNAL( timerMilliSeconds( qint64 ) ), SLOT( onPlaybackTimer( qint64 ) ) ); connect( AudioEngine::instance(), SIGNAL( volumeChanged( int ) ), SLOT( onVolumeChanged( int ) ) ); @@ -204,6 +205,7 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) m_sliderTimeLine.setDuration( duration ); m_sliderTimeLine.setFrameRange( 0, duration ); m_sliderTimeLine.setCurrentTime( 0 ); + m_seekMsecs = -1; ui->seekSlider->setVisible( true ); @@ -328,6 +330,16 @@ AudioControls::onPlaybackResumed() } +void +AudioControls::onPlaybackSeeked( qint64 msec ) +{ + //tDebug( LOGEXTRA ) << Q_FUNC_INFO << " setting current timer to " << msec; + m_sliderTimeLine.setPaused( true ); + m_sliderTimeLine.setCurrentTime( msec ); + m_seekMsecs = msec; +} + + void AudioControls::onPlaybackStopped() { @@ -353,6 +365,7 @@ AudioControls::onPlaybackStopped() void AudioControls::onPlaybackTimer( qint64 msElapsed ) { + //tDebug( LOGEXTRA ) << Q_FUNC_INFO << " msElapsed = " << msElapsed << " and timer current time = " << m_sliderTimeLine.currentTime() << " and m_seekMsecs = " << m_seekMsecs; if ( m_currentTrack.isNull() ) return; @@ -362,10 +375,23 @@ AudioControls::onPlaybackTimer( qint64 msElapsed ) ui->timeLabel->setText( TomahawkUtils::timeToString( seconds ) ); ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) ); - if ( m_sliderTimeLine.currentTime() > msElapsed ) + if ( m_sliderTimeLine.currentTime() > msElapsed || ( m_seekMsecs != -1 && m_sliderTimeLine.currentTime() < msElapsed ) ) + { + m_sliderTimeLine.setPaused( true ); m_sliderTimeLine.setCurrentTime( msElapsed ); - else if ( m_sliderTimeLine.duration() > msElapsed && m_sliderTimeLine.state() == QTimeLine::NotRunning ) + m_seekMsecs = -1; m_sliderTimeLine.resume(); + } + else if ( m_sliderTimeLine.duration() > msElapsed && m_sliderTimeLine.state() == QTimeLine::NotRunning ) + { + ui->seekSlider->setEnabled( AudioEngine::instance()->canSeek() ); + m_sliderTimeLine.resume(); + } + else if ( m_sliderTimeLine.state() == QTimeLine::Paused && AudioEngine::instance()->state() != AudioEngine::Paused ) + { + ui->seekSlider->setEnabled( AudioEngine::instance()->canSeek() ); + m_sliderTimeLine.resume(); + } ui->seekSlider->blockSignals( false ); } diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 476e97e79..7fc47eb92 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -61,6 +61,7 @@ private slots: void onPlaybackLoading( const Tomahawk::result_ptr& result ); void onPlaybackPaused(); void onPlaybackResumed(); + void onPlaybackSeeked( qint64 msec ); void onPlaybackStopped(); void onPlaybackTimer( qint64 msElapsed ); @@ -91,6 +92,7 @@ private: bool m_shuffled; QTimeLine m_sliderTimeLine; + qint64 m_seekMsecs; }; #endif // AUDIOCONTROLS_H diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 95cacfc27..5a5166d11 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -244,7 +244,10 @@ AudioEngine::canGoPrevious() bool AudioEngine::canSeek() { - return !m_playlist.isNull() && ( m_playlist.data()->seekRestrictions() != PlaylistInterface::NoSeek ); + bool phononCanSeek = true; + if ( m_mediaObject && m_mediaObject->isValid() ) + phononCanSeek = m_mediaObject->isSeekable(); + return !m_playlist.isNull() && ( m_playlist.data()->seekRestrictions() != PlaylistInterface::NoSeek ) && phononCanSeek; } void From 271e6c480dbd20a45ecf496f35a4fb9a21f88739 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 16:23:25 -0400 Subject: [PATCH 59/72] Re-disable phonon seek checking, for now --- src/libtomahawk/audio/audioengine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 5a5166d11..f90ec3e3f 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -245,8 +245,10 @@ bool AudioEngine::canSeek() { bool phononCanSeek = true; + /* TODO: When phonon properly reports this, re-enable it if ( m_mediaObject && m_mediaObject->isValid() ) phononCanSeek = m_mediaObject->isSeekable(); + */ return !m_playlist.isNull() && ( m_playlist.data()->seekRestrictions() != PlaylistInterface::NoSeek ) && phononCanSeek; } From 4fc7c6415bbc31b619948f87978de045ab23c47b Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Sun, 28 Aug 2011 16:47:14 -0400 Subject: [PATCH 60/72] Fix seeker getting "stuck" if it goes ahead on some tracks --- src/audiocontrols.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 6213ca6ed..604c27833 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -333,7 +333,7 @@ AudioControls::onPlaybackResumed() void AudioControls::onPlaybackSeeked( qint64 msec ) { - //tDebug( LOGEXTRA ) << Q_FUNC_INFO << " setting current timer to " << msec; + tDebug( LOGEXTRA ) << Q_FUNC_INFO << " setting current timer to " << msec; m_sliderTimeLine.setPaused( true ); m_sliderTimeLine.setCurrentTime( msec ); m_seekMsecs = msec; @@ -375,12 +375,13 @@ AudioControls::onPlaybackTimer( qint64 msElapsed ) ui->timeLabel->setText( TomahawkUtils::timeToString( seconds ) ); ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( m_currentTrack->duration() - seconds ) ); - if ( m_sliderTimeLine.currentTime() > msElapsed || ( m_seekMsecs != -1 && m_sliderTimeLine.currentTime() < msElapsed ) ) + if ( m_sliderTimeLine.currentTime() > msElapsed || m_seekMsecs != -1 ) { m_sliderTimeLine.setPaused( true ); m_sliderTimeLine.setCurrentTime( msElapsed ); m_seekMsecs = -1; - m_sliderTimeLine.resume(); + if ( AudioEngine::instance()->state() != AudioEngine::Paused ) + m_sliderTimeLine.resume(); } else if ( m_sliderTimeLine.duration() > msElapsed && m_sliderTimeLine.state() == QTimeLine::NotRunning ) { From cf25f3c072f11455e21323bc41feb4f6729a5512 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 29 Aug 2011 16:01:53 +0200 Subject: [PATCH 61/72] * Some debug and safety meassures. --- .../database/databasecommand_loadops.cpp | 15 +++++++++++++++ src/libtomahawk/database/databaseworker.cpp | 1 + src/sip/jabber/avatarmanager.cpp | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp index 730060170..1c84539b4 100644 --- a/src/libtomahawk/database/databasecommand_loadops.cpp +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -26,6 +26,21 @@ DatabaseCommand_loadOps::exec( DatabaseImpl* dbi ) { QList< dbop_ptr > ops; + { + TomahawkSqlQuery query = dbi->newquery(); + query.prepare( QString( "SELECT id FROM oplog WHERE guid = ?" ) ); + query.addBindValue( m_since ); + query.exec(); + + if ( !query.next() ) + { + tLog() << "Unknown oplog guid, requested, not replying:" << m_since; + Q_ASSERT( false ); + emit done( m_since, m_since, ops ); + return; + } + } + TomahawkSqlQuery query = dbi->newquery(); query.prepare( QString( "SELECT guid, command, json, compressed, singleton " diff --git a/src/libtomahawk/database/databaseworker.cpp b/src/libtomahawk/database/databaseworker.cpp index 9498e81be..93791cdab 100644 --- a/src/libtomahawk/database/databaseworker.cpp +++ b/src/libtomahawk/database/databaseworker.cpp @@ -110,6 +110,7 @@ DatabaseWorker::doWork() try { { + tDebug() << "Executing cmd:" << cmd->guid(); cmd->_exec( m_dbimpl ); // runs actual SQL stuff if ( cmd->loggable() ) diff --git a/src/sip/jabber/avatarmanager.cpp b/src/sip/jabber/avatarmanager.cpp index 0a925cc86..a781a2842 100644 --- a/src/sip/jabber/avatarmanager.cpp +++ b/src/sip/jabber/avatarmanager.cpp @@ -82,8 +82,8 @@ void AvatarManager::onNewPresence(const Jreen::Presence& presence) // qDebug() << presence.from().full() << "vcard: photo already cached no request necessary " << update->photoHash(); m_JidsAvatarHashes.insert( update->photoHash(), presence.from().bare() ); - Q_ASSERT(!this->avatar(presence.from().bare()).isNull()); - emit newAvatar(presence.from().bare()); + if ( !this->avatar( presence.from().bare() ).isNull() ) + emit newAvatar(presence.from().bare()); } } else From e44a78ba8dc9a41abf95895b525618b65b5b1dd4 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 29 Aug 2011 16:07:57 +0200 Subject: [PATCH 62/72] * Be safer. --- src/libtomahawk/database/databasecommand_loadops.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp index 1c84539b4..fbdc0b7bd 100644 --- a/src/libtomahawk/database/databasecommand_loadops.cpp +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -26,6 +26,7 @@ DatabaseCommand_loadOps::exec( DatabaseImpl* dbi ) { QList< dbop_ptr > ops; + if ( !m_since.isEmpty() ) { TomahawkSqlQuery query = dbi->newquery(); query.prepare( QString( "SELECT id FROM oplog WHERE guid = ?" ) ); From fa8746fc70974254b32fc7073b1da2f22678f0cb Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 10:17:14 -0400 Subject: [PATCH 63/72] Sync changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index a5fcb8a89..8f7841957 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ Version 0.3.0: * Added MPRIS 2.1 support. Version 0.2.3: + * Fixed potential crash in sidebar during syncing of sources. * When Listening Along, the last song a peer plays is no longer duplicated. * Fixed an issue where the Twitter plugin could get out of sync if the database was cleared, leading to eventual crashes when re-connecting. From 3b832576afe239c06f193caa9394df3ea67eb726 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 10:18:46 -0400 Subject: [PATCH 64/72] Add new feature to 0.3 changelog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 8f7841957..e48726e74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ Version 0.3.0: + * Show recently added playlists in dashoard rather than recently opened playlists. * Added MPRIS 2.1 support. Version 0.2.3: From 6cca932bf4d7393eb5737ecb33d4ee7b8f6d5b35 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 29 Aug 2011 16:20:33 +0200 Subject: [PATCH 65/72] * Stick to 80-char max-length for ChangeLog. --- ChangeLog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e48726e74..d7a20de51 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ Version 0.3.0: - * Show recently added playlists in dashoard rather than recently opened playlists. + * Show recently added playlists in dashoard rather than recently opened + playlists. * Added MPRIS 2.1 support. Version 0.2.3: From 5c5042769ba4a8e8f5086401c5064a11d5bca418 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 28 Aug 2011 10:40:24 -0400 Subject: [PATCH 66/72] Update overlay text --- src/libtomahawk/widgets/welcomewidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index 6223d14dc..c5562ab08 100644 --- a/src/libtomahawk/widgets/welcomewidget.cpp +++ b/src/libtomahawk/widgets/welcomewidget.cpp @@ -138,7 +138,7 @@ WelcomeWidget::updatePlaylists() int num = ui->playlistWidget->model()->rowCount( QModelIndex() ); if ( num == 0 ) { - ui->playlistWidget->overlay()->setText( tr( "You have not played any playlists yet." ) ); + ui->playlistWidget->overlay()->setText( tr( "No recently created playlists in your network." ) ); ui->playlistWidget->overlay()->show(); } else From 8ed476ccf68ee6b291b9d145d5e0a6a957bd63c5 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 00:10:30 -0400 Subject: [PATCH 67/72] Add QCA2 as an optional dep to tomahawk as well for HMAC signature generation for resolvers Also add a helper JS function --- CMakeLists.txt | 3 ++- data/js/tomahawk.js | 5 +++++ src/CMakeLists.txt | 2 ++ src/resolvers/qtscriptresolver.cpp | 27 +++++++++++++++++++++++++++ src/resolvers/qtscriptresolver.h | 6 ++++++ 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f9e89dc..9626afd2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,8 @@ check_taglib_filename( COMPLEX_TAGLIB_FILENAME ) macro_optional_find_package(Boost) macro_log_feature(Boost_FOUND "Boost" "Provides free peer-reviewed portable C++ source libraries" "http://www.boost.org" TRUE "" "") #FIXME: give useful explaination - +macro_optional_find_package(QCA2) +macro_log_feature(QCA2_FOUND "QCA2" "Provides encryption and signing functions required for Grooveshark resolver" "http://delta.affinix.com/qca/" TRUE "" "") # required #While we distribute our own liblastfm2, don't need to look for it diff --git a/data/js/tomahawk.js b/data/js/tomahawk.js index d6a2490b0..c95562a5c 100644 --- a/data/js/tomahawk.js +++ b/data/js/tomahawk.js @@ -91,6 +91,11 @@ var TomahawkResolver = { var configJson = JSON.stringify( config ); window.localStorage[ this.scriptPath() ] = configJson; + + this.newConfigSaved(); + }, + newConfigSaved: function() + { }, resolve: function( qid, artist, album, title ) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d6c8d0f81..94b939cda 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -163,6 +163,7 @@ INCLUDE_DIRECTORIES( ${QJSON_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR}/.. + ${QCA2_INCLUDE_DIR} ) SET( OS_SPECIFIC_LINK_LIBRARIES "" ) @@ -245,6 +246,7 @@ TARGET_LINK_LIBRARIES( tomahawk ${QXTWEB_LIBRARIES} ${QJSON_LIBRARIES} ${TAGLIB_LIBRARIES} + ${QCA2_LIBRARIES} ) diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 00c555470..9cc49ae01 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -25,6 +25,7 @@ #include "utils/tomahawkutils.h" #include +#include #include "utils/logger.h" @@ -119,6 +120,32 @@ QtScriptResolverHelper::setResolverConfig( const QVariantMap& config ) m_resolverConfig = config; } +QString +QtScriptResolverHelper::hmac( const QByteArray& key, const QByteArray &input ) +{ + if ( !QCA::isSupported( "hmac(md5)" ) ) + { + tLog() << "HMAC(md5) not supported with qca-ossl plugin, or qca-ossl plugin is not installed! Unable to generate signature!"; + return QByteArray(); + } + + QCA::MessageAuthenticationCode md5hmac1( "hmac(md5)", QCA::SecureArray() ); + QCA::SymmetricKey keyObject( key ); + md5hmac1.setup( keyObject ); + + md5hmac1.update( QCA::SecureArray( input ) ); + QCA::SecureArray resultArray = md5hmac1.final(); + + QString result = QCA::arrayToHex( resultArray.toByteArray() ); + return result.toUtf8(); +} + +QString +QtScriptResolverHelper::md5( const QByteArray& input ) +{ + QByteArray const digest = QCryptographicHash::hash( input, QCryptographicHash::Md5 ); + return QString::fromLatin1( digest.toHex() ); +} void ScriptEngine::javaScriptConsoleMessage( const QString& message, int lineNumber, const QString& sourceID ) diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 62981c978..83c4942f5 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -30,6 +30,7 @@ #include #include #include +#include class QtScriptResolver; @@ -41,6 +42,10 @@ public: QtScriptResolverHelper( const QString& scriptPath, QtScriptResolver* parent ); void setResolverConfig( const QVariantMap& config ); + + // Return a HMAC (md5) signature of the input text with the desired key + Q_INVOKABLE QString hmac( const QByteArray& key, const QByteArray& input ); + Q_INVOKABLE QString md5( const QByteArray& input ); public slots: QByteArray readRaw( const QString& fileName ); QString readBase64( const QString& fileName ); @@ -58,6 +63,7 @@ private: QString m_scriptPath; QVariantMap m_resolverConfig; QtScriptResolver* m_resolver; + QCA::Initializer m_qcaInit; }; class ScriptEngine : public QWebPage From bde3d93f6a5706e2d2d5d391d24717c28001f381 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 10:50:59 -0400 Subject: [PATCH 68/72] Changelog++ --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index d7a20de51..1cc25ac96 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ Version 0.3.0: * Added MPRIS 2.1 support. Version 0.2.3: + * Fixed opening Rdio links. * Fixed potential crash in sidebar during syncing of sources. * When Listening Along, the last song a peer plays is no longer duplicated. * Fixed an issue where the Twitter plugin could get out of sync if the From d5a2e4b325bd51f567a48512029842f3e360f3ea Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Mon, 29 Aug 2011 11:02:37 -0400 Subject: [PATCH 69/72] Remove dependency on XComposite on X11. It wasn't actually a dependency on the library, just the development headers (used to check for th existence of compositing at runtime, but not necessary for us). --- src/libtomahawk/CMakeLists.txt | 2 +- thirdparty/libqnetwm/libqnetwm/netwm.cpp | 5 +++++ thirdparty/libqnetwm/libqnetwm/netwm.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index b63f650b7..eeabc3471 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -434,7 +434,7 @@ IF( UNIX AND NOT APPLE ) IF( BUILD_GUI AND X11_FOUND ) INCLUDE_DIRECTORIES( ${THIRDPARTY_DIR}/libqnetwm ) SET( libSources ${libSources} ${THIRDPARTY_DIR}/libqnetwm/libqnetwm/netwm.cpp ) - SET( LINK_LIBRARIES ${LINK_LIBRARIES} ${X11_LIBRARIES} Xcomposite ) + SET( LINK_LIBRARIES ${LINK_LIBRARIES} ${X11_LIBRARIES} ) ENDIF() ENDIF( UNIX AND NOT APPLE ) diff --git a/thirdparty/libqnetwm/libqnetwm/netwm.cpp b/thirdparty/libqnetwm/libqnetwm/netwm.cpp index ed0a51157..6f92b4923 100644 --- a/thirdparty/libqnetwm/libqnetwm/netwm.cpp +++ b/thirdparty/libqnetwm/libqnetwm/netwm.cpp @@ -30,7 +30,10 @@ #include #include + +#if 0 #include +#endif #include "netwm.h" @@ -703,6 +706,7 @@ void NETWM::transset(Window window, double d) XSync(dpy, False); } +#if 0 bool NETWM::isComposite() { int event_base, error_base; @@ -722,6 +726,7 @@ bool NETWM::isComposite() return (owner != None); } +#endif void NETWM::checkInit() { diff --git a/thirdparty/libqnetwm/libqnetwm/netwm.h b/thirdparty/libqnetwm/libqnetwm/netwm.h index 8fec8d772..8f02db4a1 100644 --- a/thirdparty/libqnetwm/libqnetwm/netwm.h +++ b/thirdparty/libqnetwm/libqnetwm/netwm.h @@ -82,7 +82,9 @@ public: static void transset(Window, double); +#if 0 static bool isComposite(); +#endif static int setProperty(Window, Atom, long, uchar *, int); From 28e7413bddf81f9db0d38bd4265be60183b9c0e1 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 11:08:57 -0400 Subject: [PATCH 70/72] Make QCA2 an optional dep, it's only required for HMAC(md5) for resolvers --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 11 ++++++++--- src/config.h.in | 1 + src/resolvers/qtscriptresolver.cpp | 5 +++++ src/resolvers/qtscriptresolver.h | 6 ++++++ 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9626afd2f..d796aef90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ macro_optional_find_package(Boost) macro_log_feature(Boost_FOUND "Boost" "Provides free peer-reviewed portable C++ source libraries" "http://www.boost.org" TRUE "" "") #FIXME: give useful explaination macro_optional_find_package(QCA2) -macro_log_feature(QCA2_FOUND "QCA2" "Provides encryption and signing functions required for Grooveshark resolver" "http://delta.affinix.com/qca/" TRUE "" "") +macro_log_feature(QCA2_FOUND "QCA2" "Provides encryption and signing functions required for Grooveshark resolver" "http://delta.affinix.com/qca/" FALSE "" "") # required #While we distribute our own liblastfm2, don't need to look for it diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94b939cda..b027c1543 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -163,7 +163,6 @@ INCLUDE_DIRECTORIES( ${QJSON_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR} ${LIBECHONEST_INCLUDE_DIR}/.. - ${QCA2_INCLUDE_DIR} ) SET( OS_SPECIFIC_LINK_LIBRARIES "" ) @@ -195,6 +194,10 @@ IF(GLOOX_FOUND) ENDIF(GLOOX_FOUND) ADD_SUBDIRECTORY( sip ) +IF(QCA2_FOUND) + INCLUDE_DIRECTORIES( ${QCA2_INCLUDE_DIR} ) +ENDIF(QCA2_FOUND) + kde4_add_app_icon( tomahawkSources "${CMAKE_SOURCE_DIR}/data/icons/tomahawk-icon-*.png" ) qt4_add_resources( RC_SRCS "../resources.qrc" ) qt4_wrap_cpp( tomahawkMoc ${tomahawkHeaders} ) @@ -235,6 +238,10 @@ IF(GLOOX_FOUND) SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${GLOOX_LIBRARIES} ) ENDIF(GLOOX_FOUND) +IF(QCA2_FOUND) + SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${QCA2_LIBRARIES} ) +ENDIF(QCA2_FOUND) + TARGET_LINK_LIBRARIES( tomahawk ${LINK_LIBRARIES} ${TOMAHAWK_LIBRARIES} @@ -246,10 +253,8 @@ TARGET_LINK_LIBRARIES( tomahawk ${QXTWEB_LIBRARIES} ${QJSON_LIBRARIES} ${TAGLIB_LIBRARIES} - ${QCA2_LIBRARIES} ) - IF( APPLE ) IF(HAVE_SPARKLE) MESSAGE("Sparkle Found, installing framekwork in bundle") diff --git a/src/config.h.in b/src/config.h.in index 7ac3931a2..c065cf2d6 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -17,5 +17,6 @@ #cmakedefine LIBLASTFM_FOUND #cmakedefine GLOOX_FOUND +#cmakedefine QCA2_FOUND #endif // CONFIG_H_IN diff --git a/src/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 9cc49ae01..9f4577105 100644 --- a/src/resolvers/qtscriptresolver.cpp +++ b/src/resolvers/qtscriptresolver.cpp @@ -123,6 +123,7 @@ QtScriptResolverHelper::setResolverConfig( const QVariantMap& config ) QString QtScriptResolverHelper::hmac( const QByteArray& key, const QByteArray &input ) { +#ifdef QCA2_FOUND if ( !QCA::isSupported( "hmac(md5)" ) ) { tLog() << "HMAC(md5) not supported with qca-ossl plugin, or qca-ossl plugin is not installed! Unable to generate signature!"; @@ -138,6 +139,10 @@ QtScriptResolverHelper::hmac( const QByteArray& key, const QByteArray &input ) QString result = QCA::arrayToHex( resultArray.toByteArray() ); return result.toUtf8(); +#else + tLog() << "Tomahawk compiled without QCA support, cannot generate HMAC signature"; + return QString(); +#endif } QString diff --git a/src/resolvers/qtscriptresolver.h b/src/resolvers/qtscriptresolver.h index 83c4942f5..4549169c3 100644 --- a/src/resolvers/qtscriptresolver.h +++ b/src/resolvers/qtscriptresolver.h @@ -23,6 +23,7 @@ #include "query.h" #include "result.h" #include "utils/tomahawkutils.h" +#include "config.h" #include #include @@ -30,7 +31,10 @@ #include #include #include + +#ifdef QCA2_FOUND #include +#endif class QtScriptResolver; @@ -63,7 +67,9 @@ private: QString m_scriptPath; QVariantMap m_resolverConfig; QtScriptResolver* m_resolver; +#ifdef QCA2_FOUND QCA::Initializer m_qcaInit; +#endif }; class ScriptEngine : public QWebPage From 89104fc27d08e75ac42a5db7e06f424337578956 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 29 Aug 2011 17:11:52 +0200 Subject: [PATCH 71/72] * Added more debug output for playlist errors. --- src/libtomahawk/playlist.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libtomahawk/playlist.cpp b/src/libtomahawk/playlist.cpp index 1b5f037aa..ac7a6f29b 100644 --- a/src/libtomahawk/playlist.cpp +++ b/src/libtomahawk/playlist.cpp @@ -398,12 +398,6 @@ Playlist::setNewRevision( const QString& rev, QList entries; foreach( const QString& id, neworderedguids ) { -/* qDebug() << "id:" << id; - qDebug() << "newordered:" << neworderedguids.count() << neworderedguids; - qDebug() << "entriesmap:" << entriesmap.count() << entriesmap; - qDebug() << "addedmap:" << addedmap.count() << addedmap; - qDebug() << "m_entries" << m_entries; */ - if( entriesmap.contains( id ) ) { entries.append( entriesmap.value( id ) ); @@ -416,6 +410,13 @@ Playlist::setNewRevision( const QString& rev, } else { + /* qDebug() << "id:" << id; + * qDebug() << "newordered:" << neworderedguids.count() << neworderedguids; + * qDebug() << "entriesmap:" << entriesmap.count() << entriesmap; + * qDebug() << "addedmap:" << addedmap.count() << addedmap; + * qDebug() << "m_entries" << m_entries; */ + + tLog() << "Playlist error for playlist with guid" << guid() << "from source" << author()->friendlyName(); Q_ASSERT( false ); // XXX } } From 65709e92ff51d31bddd3274ea0e89238a542fb4f Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Mon, 29 Aug 2011 11:27:35 -0400 Subject: [PATCH 72/72] Add file --- CMakeModules/FindQCA2.cmake | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 CMakeModules/FindQCA2.cmake diff --git a/CMakeModules/FindQCA2.cmake b/CMakeModules/FindQCA2.cmake new file mode 100644 index 000000000..79518ce24 --- /dev/null +++ b/CMakeModules/FindQCA2.cmake @@ -0,0 +1,48 @@ +# - Try to find QCA2 (Qt Cryptography Architecture 2) +# Once done this will define +# +# QCA2_FOUND - system has QCA2 +# QCA2_INCLUDE_DIR - the QCA2 include directory +# QCA2_LIBRARIES - the libraries needed to use QCA2 +# QCA2_DEFINITIONS - Compiler switches required for using QCA2 +# +# use pkg-config to get the directories and then use these values +# in the FIND_PATH() and FIND_LIBRARY() calls + +# Copyright (c) 2006, Michael Larouche, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(FindLibraryWithDebug) + +if (QCA2_INCLUDE_DIR AND QCA2_LIBRARIES) + + # in cache already + set(QCA2_FOUND TRUE) + +else (QCA2_INCLUDE_DIR AND QCA2_LIBRARIES) + + + if (NOT WIN32) + find_package(PkgConfig) + pkg_check_modules(PC_QCA2 qca2) + set(QCA2_DEFINITIONS ${PC_QCA2_CFLAGS_OTHER}) + endif (NOT WIN32) + + find_library_with_debug(QCA2_LIBRARIES + WIN32_DEBUG_POSTFIX d + NAMES qca + HINTS ${PC_QCA2_LIBDIR} ${PC_QCA2_LIBRARY_DIRS} + ) + + find_path(QCA2_INCLUDE_DIR qca.h + HINTS ${PC_QCA2_INCLUDEDIR} ${PC_QCA2_INCLUDE_DIRS} + PATH_SUFFIXES QtCrypto) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(QCA2 DEFAULT_MSG QCA2_LIBRARIES QCA2_INCLUDE_DIR) + + mark_as_advanced(QCA2_INCLUDE_DIR QCA2_LIBRARIES) + +endif (QCA2_INCLUDE_DIR AND QCA2_LIBRARIES)