diff --git a/CMakeLists.txt b/CMakeLists.txt index 94f9e89dc..d796aef90 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/" FALSE "" "") # required #While we distribute our own liblastfm2, don't need to look for it 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) diff --git a/ChangeLog b/ChangeLog index 8edf7301a..1cc25ac96 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,17 +1,30 @@ Version 0.3.0: + * Show recently added playlists in dashoard rather than recently opened + playlists. * 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 + 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. diff --git a/data/images/drop-album.png b/data/images/drop-album.png new file mode 100644 index 000000000..bd4ff88e5 Binary files /dev/null and b/data/images/drop-album.png differ diff --git a/data/images/drop-all-songs.png b/data/images/drop-all-songs.png new file mode 100644 index 000000000..5e743fa5b Binary files /dev/null and b/data/images/drop-all-songs.png differ diff --git a/data/images/drop-local-songs.png b/data/images/drop-local-songs.png new file mode 100644 index 000000000..587cbffe5 Binary files /dev/null and b/data/images/drop-local-songs.png differ diff --git a/data/images/drop-song.png b/data/images/drop-song.png new file mode 100644 index 000000000..582ded327 Binary files /dev/null and b/data/images/drop-song.png differ diff --git a/data/images/drop-top-songs.png b/data/images/drop-top-songs.png new file mode 100644 index 000000000..e12f91052 Binary files /dev/null and b/data/images/drop-top-songs.png differ 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/resources.qrc b/resources.qrc index 30d6bd31d..5a6337c21 100644 --- a/resources.qrc +++ b/resources.qrc @@ -110,5 +110,10 @@ 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 + data/images/drop-song.png + data/images/drop-album.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4553c2b4..b027c1543 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} sourcetree/sourcesmodel.cpp sourcetree/sourcesproxymodel.cpp sourcetree/sourcetreeview.cpp + sourcetree/sourcedelegate.cpp + sourcetree/animationhelper.cpp sourcetree/items/sourcetreeitem.cpp sourcetree/items/collectionitem.cpp sourcetree/items/playlistitems.cpp @@ -102,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 @@ -190,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} ) @@ -230,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} @@ -243,7 +255,6 @@ TARGET_LINK_LIBRARIES( tomahawk ${TAGLIB_LIBRARIES} ) - IF( APPLE ) IF(HAVE_SPARKLE) MESSAGE("Sparkle Found, installing framekwork in bundle") diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index ca161106c..604c27833 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() ) ); @@ -121,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 ) ) ); @@ -181,8 +187,28 @@ AudioControls::onPlaybackStarted( const Tomahawk::result_ptr& result ) { tDebug( LOGEXTRA ) << 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 ); + + m_sliderTimeLine.stop(); + m_sliderTimeLine.setDuration( duration ); + m_sliderTimeLine.setFrameRange( 0, duration ); + m_sliderTimeLine.setCurrentTime( 0 ); + m_seekMsecs = -1; + + ui->seekSlider->setVisible( true ); + Tomahawk::InfoSystem::InfoCriteriaHash trackInfo; trackInfo["artist"] = result->artist()->name(); trackInfo["album"] = result->album()->name(); @@ -252,11 +278,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, m_currentTrack->duration() * 1000 ); - ui->seekSlider->setValue( 0 ); - ui->seekSlider->setVisible( true ); + ui->timeLeftLabel->setText( "-" + TomahawkUtils::timeToString( result.data()->duration() ) ); ui->stackedLayout->setCurrentWidget( ui->pauseButton ); @@ -293,20 +315,35 @@ AudioControls::socialActionsLoaded() void AudioControls::onPlaybackPaused() { + tDebug( LOGEXTRA ) << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->playPauseButton ); + m_sliderTimeLine.setPaused( true ); } void AudioControls::onPlaybackResumed() { + tDebug( LOGEXTRA ) << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->pauseButton ); ui->loveButton->setVisible( true ); + m_sliderTimeLine.resume(); +} + + +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() { + tDebug( LOGEXTRA ) << Q_FUNC_INFO; m_currentTrack.clear(); ui->artistTrackLabel->setText( "" ); @@ -316,6 +353,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 +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; @@ -334,7 +374,25 @@ 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 ); + + if ( m_sliderTimeLine.currentTime() > msElapsed || m_seekMsecs != -1 ) + { + m_sliderTimeLine.setPaused( true ); + m_sliderTimeLine.setCurrentTime( msElapsed ); + m_seekMsecs = -1; + if ( AudioEngine::instance()->state() != AudioEngine::Paused ) + 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 c4e8cfe27..7fc47eb92 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -20,6 +20,7 @@ #define AUDIOCONTROLS_H #include +#include #include "result.h" #include "playlistinterface.h" @@ -60,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 ); @@ -79,6 +81,7 @@ private slots: void droppedTracks( QList ); void socialActionsLoaded(); + private: Ui::AudioControls *ui; @@ -87,6 +90,9 @@ private: Tomahawk::result_ptr m_currentTrack; Tomahawk::PlaylistInterface::RepeatMode m_repeatMode; bool m_shuffled; + + QTimeLine m_sliderTimeLine; + qint64 m_seekMsecs; }; #endif // AUDIOCONTROLS_H 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/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 71f392dcf..25cbf469f 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 @@ -181,8 +182,9 @@ set( libSources widgets/SeekSlider.cpp widgets/playlisttypeselectordlg.cpp widgets/welcomewidget.cpp - widgets/welcomeplaylistmodel.cpp widgets/whatshotwidget.cpp + widgets/RecentlyPlayedPlaylistsModel.cpp + widgets/RecentPlaylistsModel.cpp widgets/overlaywidget.cpp widgets/HeaderLabel.cpp widgets/HeaderWidget.cpp @@ -252,6 +254,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 @@ -365,8 +368,13 @@ set( libHeaders widgets/SeekSlider.h widgets/playlisttypeselectordlg.h widgets/welcomewidget.h +<<<<<<< HEAD widgets/whatshotwidget.h widgets/welcomeplaylistmodel.h +======= + widgets/RecentlyPlayedPlaylistsModel.h + widgets/RecentPlaylistsModel.h +>>>>>>> master widgets/overlaywidget.h widgets/HeaderLabel.h widgets/HeaderWidget.h @@ -419,7 +427,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 + ) IF( UNIX AND NOT APPLE ) @@ -437,6 +445,8 @@ IF( UNIX AND NOT APPLE ) infosystem/infoplugins/unix/fdonotifyplugin.h ) 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} ) ENDIF() ENDIF( UNIX AND NOT APPLE ) 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 diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index d14731106..f90ec3e3f 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -244,7 +244,12 @@ AudioEngine::canGoPrevious() bool AudioEngine::canSeek() { - return !m_playlist.isNull() && ( m_playlist.data()->seekRestrictions() != PlaylistInterface::NoSeek ); + 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; } void @@ -552,7 +557,7 @@ AudioEngine::playlistNextTrackReady() return; m_waitingOnNewTrack = false; - next(); + loadNextTrack(); } @@ -604,7 +609,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(); + } } } } 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/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/database/databasecommand_loadops.cpp b/src/libtomahawk/database/databasecommand_loadops.cpp index 730060170..fbdc0b7bd 100644 --- a/src/libtomahawk/database/databasecommand_loadops.cpp +++ b/src/libtomahawk/database/databasecommand_loadops.cpp @@ -26,6 +26,22 @@ 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 = ?" ) ); + 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/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 3de1ea050..467821e29 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; @@ -94,16 +95,33 @@ DropJob::acceptsMimeData( const QMimeData* data, bool tracksOnly ) return false; } +void +DropJob::setGetWholeArtists( bool getWholeArtists ) +{ + m_getWholeArtists = getWholeArtists; +} void -DropJob::tracksFromMimeData( const QMimeData* data, bool allowDuplicates ) +DropJob::setGetWholeAlbums( bool getWholeAlbums ) +{ + m_getWholeAlbums = getWholeAlbums; +} + + +void +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 ( onlyLocal ) + removeRemoteSources(); + if ( !allowDuplicates ) removeDuplicates(); @@ -152,7 +170,23 @@ DropJob::tracksFromQueryList( const QMimeData* data ) if ( query && !query->isNull() ) { tDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); - queries << *query; + + if ( m_top10 ) + { + getTopTen( query->data()->artist() ); + } + else if ( m_getWholeArtists ) + { + queries << getArtist( query->data()->artist() ); + } + else if ( m_getWholeAlbums ) + { + queries << getAlbum( query->data()->artist(), query->data()->album() ); + } + else + { + queries << *query; + } } } @@ -176,8 +210,24 @@ 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_top10 ) + { + getTopTen( q->artist() ); + } + 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; + } } } @@ -198,16 +248,12 @@ 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_top10 ) + getTopTen( artist ); + else if ( m_getWholeArtists ) + queries << getArtist( artist ); else - queries << albumPtr->tracks(); + queries << getAlbum( artist, album ); } return queries; } @@ -224,15 +270,14 @@ 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++; + queries << getArtist( artist ); } else - queries << artistPtr->tracks(); + { + getTopTen( artist ); + } } return queries; } @@ -336,6 +381,9 @@ DropJob::onTracksAdded( const QList& tracksList ) if ( --m_queryCount == 0 ) { + if ( m_onlyLocal ) + removeRemoteSources(); + if ( !m_allowDuplicates ) removeDuplicates(); @@ -361,3 +409,108 @@ DropJob::removeDuplicates() } m_resultList = list; } + +void +DropJob::removeRemoteSources() +{ + QList< Tomahawk::query_ptr > list; + foreach ( const Tomahawk::query_ptr& item, m_resultList ) + { + bool hasLocalSource = false; + foreach ( const Tomahawk::result_ptr& result, item->results() ) + { + if ( !result->collection()->source().isNull() && 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; + + 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 ); + } +} + +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.isNull() ) + return QList< query_ptr >(); + + if ( albumPtr->tracks().isEmpty() ) + { + connect( albumPtr.data(), SIGNAL( tracksAdded( QList ) ), + SLOT( onTracksAdded( QList ) ) ); + m_queryCount++; + return QList< query_ptr >(); + } + 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 186417f9b..3e84d6510 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,10 @@ public: */ static bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); static QStringList mimeTypes(); - void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false ); + + void setGetWholeArtists( bool getWholeArtists ); + void setGetWholeAlbums( bool getWholeAlbums ); + void tracksFromMimeData( const QMimeData* data, bool allowDuplicates = false, bool onlyLocal = false, bool top10 = false ); signals: /// QMimeData parsing results @@ -54,6 +59,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 ); @@ -65,10 +72,20 @@ 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 getTopTen( const QString& artist ); + 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/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 7037da075..b65c62a59 100644 --- a/src/libtomahawk/infosystem/infosystem.h +++ b/src/libtomahawk/infosystem/infosystem.h @@ -210,9 +210,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; diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 6c193e257..dc925c1a9 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 ); } @@ -234,8 +238,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) lastOpApplied(); return; } - -// qDebug() << "APPLYING CMD" << cmd->commandname() << cmd->guid(); + QSharedPointer cmdsp = QSharedPointer(cmd); if ( !msg->is( Msg::FRAGMENT ) ) // last msg in this batch { @@ -243,10 +246,21 @@ DBSyncConnection::handleMsg( msg_ptr msg ) connect( cmd, SIGNAL( finished() ), SLOT( lastOpApplied() ) ); } - if ( !cmd->singletonCmd() ) - m_source->setLastOpGuid( 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 4fbccdcc5..0f4e24c8a 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,13 @@ 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; + QStringList m_recentTempOps; + State m_state; QTimer m_timer; }; 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 } } 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/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/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/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 eb1d1c63e..dc4e0caec 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -318,6 +318,99 @@ 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; + resultData.clear(); + 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; + resultData.clear(); + 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 + resultData.clear(); foreach ( const QModelIndex& i, indexes ) { if ( i.column() > 0 || indexes.contains( i.parent() ) ) 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(); 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; 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/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 eb5e0a40d..14df50094 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" @@ -304,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. @@ -341,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; @@ -527,27 +545,70 @@ 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++; - QWidget *widget = widgetList.at( i ); - - WId winId = widget->winId(); - Display *display = XOpenDisplay( NULL ); - if ( !display ) + if ( i == widgetList.count() ) { - qDebug() << Q_FUNC_INFO << "Could not find display to raise"; + qDebug() << Q_FUNC_INFO << " could not find main TH window"; return; } - XRaiseWindow( display, winId ); - XSetInputFocus( display, winId, RevertToNone, CurrentTime ); - //widget->activateWindow(); - //widget->raise(); + QWidget *widget = widgetList.at( i ); + + widget->show(); + widget->activateWindow(); + widget->raise(); + + WId wid = widget->winId(); + + 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() { + qDebug() << Q_FUNC_INFO; + QWidgetList widgetList = qApp->topLevelWidgets(); + int i = 0; + 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(); + widget->activateWindow(); + widget->raise(); + + WId wid = widget->winId(); + + HWND hwndActiveWin = GetForegroundWindow(); + int idActive = GetWindowThreadProcessId(hwndActiveWin, NULL); + if ( AttachThreadInput(GetCurrentThreadId(), idActive, TRUE) ) + { + SetForegroundWindow( wid ); + SetFocus( wid ); + AttachThreadInput(GetCurrentThreadId(), idActive, FALSE); + } } #else #ifndef Q_OS_MAC 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 ); diff --git a/src/libtomahawk/viewmanager.cpp b/src/libtomahawk/viewmanager.cpp index 06679d1ab..3a090e294 100644 --- a/src/libtomahawk/viewmanager.cpp +++ b/src/libtomahawk/viewmanager.cpp @@ -170,6 +170,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 b9bc1201f..bc3dd5c67 100644 --- a/src/libtomahawk/viewmanager.h +++ b/src/libtomahawk/viewmanager.h @@ -92,6 +92,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..2a0ea9bec --- /dev/null +++ b/src/libtomahawk/widgets/RecentPlaylistsModel.cpp @@ -0,0 +1,239 @@ +/* + 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 ) +{ + connect( SourceList::instance(), SIGNAL( ready() ), 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/SeekSlider.cpp b/src/libtomahawk/widgets/SeekSlider.cpp index 007c18bef..87c4699a1 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,7 @@ SeekSlider::SeekSlider( QWidget* parent ) : QSlider( parent ) + , m_timeLine( 0 ) { setFixedHeight( 20 ); setStyleSheet( "QSlider::groove::horizontal {" @@ -64,3 +66,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..034c214ce 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,16 @@ public: SeekSlider( QWidget* parent = 0 ); ~SeekSlider(); + void setTimeLine( QTimeLine* timeline ) { m_timeLine = timeline; } + +public slots: + void setValue( int value ); + protected: void mousePressEvent( QMouseEvent* event ); + +private: + QTimeLine* m_timeLine; }; #endif // SEEKSLIDER_H diff --git a/src/libtomahawk/widgets/welcomewidget.cpp b/src/libtomahawk/widgets/welcomewidget.cpp index 5e090f090..c5562ab08 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 ); @@ -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 @@ -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/resolvers/qtscriptresolver.cpp b/src/resolvers/qtscriptresolver.cpp index 00c555470..9f4577105 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,37 @@ QtScriptResolverHelper::setResolverConfig( const QVariantMap& config ) m_resolverConfig = 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!"; + 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(); +#else + tLog() << "Tomahawk compiled without QCA support, cannot generate HMAC signature"; + return QString(); +#endif +} + +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..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 @@ -31,6 +32,10 @@ #include #include +#ifdef QCA2_FOUND +#include +#endif + class QtScriptResolver; class QtScriptResolverHelper : public QObject @@ -41,6 +46,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 +67,9 @@ private: QString m_scriptPath; QVariantMap m_resolverConfig; QtScriptResolver* m_resolver; +#ifdef QCA2_FOUND + QCA::Initializer m_qcaInit; +#endif }; class ScriptEngine : public QWebPage 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 diff --git a/src/sip/twitter/twitter.cpp b/src/sip/twitter/twitter.cpp index b01f8a434..0e0d1cd10 100644 --- a/src/sip/twitter/twitter.cpp +++ b/src/sip/twitter/twitter.cpp @@ -59,42 +59,43 @@ 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; - if ( !Database::instance() || Database::instance()->dbid() != twitterSavedDbid() ) + if ( 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( QVariantHash() ); } - m_checkTimer.setInterval( 150000 ); + setTwitterSavedDbid( Database::instance()->dbid() ); + + m_checkTimer.setInterval( 180000 ); m_checkTimer.setSingleShot( false ); connect( &m_checkTimer, SIGNAL( timeout() ), SLOT( checkTimerFired() ) ); - m_connectTimer.setInterval( 150000 ); + m_dmPollTimer.setInterval( 60000 ); + m_dmPollTimer.setSingleShot( false ); + connect( &m_dmPollTimer, SIGNAL( timeout() ), SLOT( pollDirectMessages() ) ); + + m_connectTimer.setInterval( 180000 ); m_connectTimer.setSingleShot( false ); connect( &m_connectTimer, SIGNAL( timeout() ), SLOT( connectTimerFired() ) ); m_configWidget = QWeakPointer< TwitterConfigWidget >( new TwitterConfigWidget( this, 0 ) ); connect( m_configWidget.data(), SIGNAL( twitterAuthed( bool ) ), SLOT( configDialogAuthedSignalSlot( bool ) ) ); - } + void TwitterPlugin::configDialogAuthedSignalSlot( bool authed ) { @@ -171,10 +172,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() ) @@ -222,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() ) @@ -235,6 +243,7 @@ TwitterPlugin::disconnectPlugin() if( !m_twitterAuth.isNull() ) delete m_twitterAuth.data(); + syncConfig(); m_cachedPeers.empty(); m_state = Disconnected; emit stateChanged( m_state ); @@ -250,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 @@ -274,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() ) ); } @@ -292,6 +303,7 @@ TwitterPlugin::connectAuthVerifyReply( const QTweetUser &user ) m_state = Disconnected; m_connectTimer.stop(); m_checkTimer.stop(); + m_dmPollTimer.stop(); emit stateChanged( m_state ); } } @@ -349,12 +361,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; } @@ -363,6 +376,7 @@ TwitterPlugin::connectTimerFired() { qDebug() << Q_FUNC_INFO << " aging peer " << screenName << " out of cache"; m_cachedPeers.remove( screenName ); + syncConfig(); m_cachedAvatars.remove( screenName ); continue; } @@ -373,7 +387,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 ) ); } } @@ -407,21 +421,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 @@ -429,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 ) @@ -456,9 +471,6 @@ TwitterPlugin::friendsTimelineStatuses( const QList< QTweetStatus > &statuses ) } setTwitterCachedFriendsSinceId( m_cachedFriendsSinceId ); - - m_finishedFriends = true; - QMetaObject::invokeMethod( this, "pollDirectMessages", Qt::AutoConnection ); } void @@ -492,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; @@ -580,9 +583,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 ); @@ -590,7 +593,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] ) ) { @@ -605,7 +608,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; @@ -618,7 +621,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" ) ) { @@ -646,8 +649,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() || @@ -668,7 +674,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(); } @@ -676,17 +682,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() ) @@ -700,7 +706,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" ) || @@ -709,6 +715,14 @@ TwitterPlugin::makeConnection( const QString &screenName, const QHash< QString, 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(), @@ -1022,7 +1036,7 @@ TwitterPlugin::setTwitterCachedDirectMessagesSinceId( qint64 cachedId ) TomahawkSettings::instance()->setValue( pluginId() + "/cacheddirectmessagessinceid", cachedId ); } -QHash +QVariantHash TwitterPlugin::twitterCachedPeers() const { TomahawkSettings* s = TomahawkSettings::instance(); @@ -1047,13 +1061,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..adeebc34d 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; @@ -146,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; - QHash< QString, QVariant > m_cachedPeers; + QVariantHash m_cachedPeers; QHash< QString, QPixmap > m_cachedAvatars; QSet m_keyCache; - bool m_finishedFriends; - bool m_finishedMentions; ConnectionState m_state; QWeakPointer m_configWidget; diff --git a/src/sourcetree/animationhelper.cpp b/src/sourcetree/animationhelper.cpp new file mode 100644 index 000000000..8efa25bf8 --- /dev/null +++ b/src/sourcetree/animationhelper.cpp @@ -0,0 +1,111 @@ +#include "animationhelper.h" + +#include "QDebug" + +AnimationHelper::AnimationHelper( const QModelIndex& index, QObject *parent ) + :QObject( parent ) + , m_index( index ) + , m_fullyExpanded( false ) + , m_expandAnimation( 0 ) +{ + m_expandTimer.setSingleShot( true ); + m_expandTimer.setInterval( 600 ); + connect( &m_expandTimer, SIGNAL(timeout()), SLOT(expandTimeout())); + + m_collapseTimer.setSingleShot( true ); + m_collapseTimer.setInterval( 600 ); + 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::OutExpo ); + 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::InExpo ); + 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 ( m_expandTimer.isActive() ) + { + m_expandTimer.stop(); + emit finished( m_index ); + return; + } + + if ( immediately ) + { + m_fullyExpanded = false; + m_collapseAnimation->start(); + } + else + m_collapseTimer.start(); +} + +bool AnimationHelper::partlyExpanded() +{ + 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() +{ + return m_fullyExpanded; +} + +void AnimationHelper::expandTimeout() +{ + m_expandAnimation->start(); +} + +void AnimationHelper::collapseTimeout() +{ + m_fullyExpanded = false; + m_collapseAnimation->start(); +} + +void AnimationHelper::expandAnimationFinished() +{ + m_fullyExpanded = true; +} + +void AnimationHelper::collapseAnimationFinished() +{ + emit finished( m_index ); +} diff --git a/src/sourcetree/animationhelper.h b/src/sourcetree/animationhelper.h new file mode 100644 index 000000000..1e4998132 --- /dev/null +++ b/src/sourcetree/animationhelper.h @@ -0,0 +1,57 @@ +#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; + + QPropertyAnimation *m_expandAnimation; + QPropertyAnimation *m_collapseAnimation; +}; + +#endif // ANIMATIONHELPER_H diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index 86bc03134..00cd45f57 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -139,14 +139,135 @@ CategoryAddItem::willAcceptDrag( const QMimeData* data ) const return false; } +SourceTreeItem::DropTypes +CategoryAddItem::supportedDropTypes( const QMimeData* data ) const +{ + 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; +} + 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 > ) ) ); - 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/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 02cc4e453..c59f4258f 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -138,6 +138,28 @@ PlaylistItem::willAcceptDrag( const QMimeData* data ) const return !m_playlist.isNull() && m_playlist->author()->isLocal(); } +PlaylistItem::DropTypes +PlaylistItem::supportedDropTypes( const QMimeData* data ) const +{ + 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; +} + bool PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) @@ -152,7 +174,24 @@ 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() == 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 ); // TODO cant' know if it works or not yet... return true; @@ -161,6 +200,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(); @@ -191,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() ) @@ -199,10 +239,10 @@ PlaylistItem::activateCurrent() model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() ); emit selectRequest( this ); - return true; + return this; } - return false; + return 0; } @@ -346,7 +386,7 @@ DynamicPlaylistItem::icon() const } } -bool +SourceTreeItem* DynamicPlaylistItem::activateCurrent() { if( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) == ViewManager::instance()->currentPage() ) @@ -354,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 8febb2d78..8ab795bc3 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -34,13 +34,14 @@ public: virtual Qt::ItemFlags flags() const; virtual void activate(); virtual bool willAcceptDrag( const QMimeData* data ) 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); virtual int peerSortValue() const; virtual int IDValue() const; - virtual bool activateCurrent(); + virtual SourceTreeItem* activateCurrent(); protected: void setLoaded( bool loaded ); @@ -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 @@ -71,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/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index f8f57dc3f..a3bdaec1a 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -32,6 +32,18 @@ class SourceTreeItem : public QObject { Q_OBJECT public: + enum DropType + { + DropTypesNone = 0x00, + DropTypeThisTrack = 0x01, + DropTypeThisAlbum = 0x02, + DropTypeAllFromArtist = 0x04, + DropTypeLocalItems = 0x08, + DropTypeTop50 = 0x10, + 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 +68,9 @@ 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 QMimeData* mimeData ) const { Q_UNUSED( mimeData ); 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 ); } @@ -83,6 +98,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..482f84fd1 --- /dev/null +++ b/src/sourcetree/sourcedelegate.cpp @@ -0,0 +1,414 @@ +#include "sourcedelegate.h" + +#include "items/sourcetreeitem.h" +#include "items/collectionitem.h" +#include "items/playlistitems.h" +#include "items/categoryitems.h" + +#include "utils/tomahawkutils.h" +#include "items/temporarypageitem.h" +#include "animationhelper.h" + +#include +#include +#include + +#define TREEVIEW_INDENT_ADD -7 + +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/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" ).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 +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 ( m_expandedMap.contains( index ) ) + { + if ( !m_expandedMap.value( index )->initialized() ) + { + int dropTypes = dropTypeCount( item ); + 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, 300 ); + 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 ); +} + + +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 || 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; + o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); + QStyledItemDelegate::paint( painter, o, index ); + + 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( itemsRect.topLeft(), itemsRect.bottomLeft() ); + 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 ); + + // calculate sizes for the icons + 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; + + pen.setColor( Qt::white ); + painter->setPen( pen ); + + QFont font = painter->font(); + font.setPixelSize( 12 ); + painter->setFont( font ); + QFont fontBold = painter->font(); + fontBold.setBold( true ); + + QRect textRect; + QRect imageRect; + + SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData ); + + for ( int i = 0; i < 5; ++i ) + { + if ( !dropTypes.testFlag( m_dropTypeMap.value( i ) ) ) + continue; + + + if ( count > 0 ) + itemsRect.adjust( itemWidth, 0, itemWidth, 0 ); + + if ( itemsRect.contains( cursorPos ) | !cursorInRect ) + { + painter->setFont( fontBold ); + m_hoveredDropType = m_dropTypeMap.value( i ); + cursorInRect = true; + } + else + painter->setFont( font ); + + int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2; + 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() - 2; + 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++; + } + + + 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 ) ); +} + +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 +{ + int menuCount = 0; + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisTrack ) ) + menuCount++; + + if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisAlbum ) ) + menuCount++; + + 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; +} + +SourceTreeItem::DropType +SourceDelegate::hoveredDropType() const +{ + return m_hoveredDropType; +} + +void +SourceDelegate::hovered(const QModelIndex &index, const QMimeData *mimeData) +{ + if ( !index.isValid() ) + { + foreach ( AnimationHelper *helper, m_expandedMap ) + { + helper->collapse(); + } + return; + } + if ( !m_expandedMap.contains( index ) ) + { + foreach ( AnimationHelper *helper, m_expandedMap ) + { + helper->collapse(); + } + + m_newDropHoverIndex = index; + 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 ) ) ); + + } + else + qDebug() << "expandedMap already contains index" << index; +} + +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 new file mode 100644 index 000000000..442c19a37 --- /dev/null +++ b/src/sourcetree/sourcedelegate.h @@ -0,0 +1,48 @@ +#ifndef SOURCEDELEGATE_H +#define SOURCEDELEGATE_H + +#include "sourcetreeview.h" +#include "items/sourcetreeitem.h" + +#include +#include + +class AnimationHelper; + +class SourceDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + SourceDelegate( QAbstractItemView* parent = 0 ); + ~SourceDelegate(); + + void hovered( const QModelIndex &index, const QMimeData *mimeData ); + void dragLeaveEvent(); + + 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; + 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; + + QMap< int, SourceTreeItem::DropType > m_dropTypeMap; + QMap< int, QString > m_dropTypeTextMap; + QMap< int, QPixmap > m_dropTypeImageMap; + +}; + +#endif // SOURCEDELEGATE_H 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; } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 4297caffe..d08e9152e 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,8 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); + m_delegate->dragLeaveEvent(); + dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex(); } @@ -479,6 +456,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); + dataChanged(m_dropIndex, m_dropIndex); m_dropIndex = QPersistentModelIndex( index ); if ( index.isValid() ) @@ -486,9 +464,15 @@ 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; + m_delegate->hovered( index, event->mimeData() ); + dataChanged(index, index); + } + else + m_delegate->hovered( QModelIndex(), 0 ); } else { @@ -511,9 +495,35 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) void SourceTreeView::dropEvent( QDropEvent* event ) { - QTreeView::dropEvent( event ); + const QPoint pos = event->pos(); + const QModelIndex index = indexAt( pos ); + + if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist + || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::CategoryAdd ) + { + SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index ); + Q_ASSERT( item ); + + item->setDropType( m_delegate->hoveredDropType() ); + qDebug() << "dropType is " << m_delegate->hoveredDropType(); + } + + // 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->dragLeaveEvent(); + dataChanged( index, index ); } @@ -576,190 +586,10 @@ 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 +SourceTreeView::update( const QModelIndex &index ) { - 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 ); +// updateGeometries(); +// QTreeView::update( index ); + dataChanged( index, index ); } diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 2eca36fa2..6a60e07cf 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -24,11 +24,13 @@ #include "source.h" #include "sourcetree/sourcesmodel.h" +#include "sourcetree/sourcedelegate.h" class CollectionModel; class PlaylistModel; class SourcesModel; class SourcesProxyModel; +class SourceDelegate; class SourceTreeView : public QTreeView { @@ -41,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 ); @@ -81,6 +86,7 @@ private: SourcesModel* m_model; SourcesProxyModel* m_proxyModel; QModelIndex m_contextMenuIndex; + SourceDelegate* m_delegate; QMenu m_playlistMenu; QMenu m_roPlaylistMenu; diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index b3bad5658..59f5bf570 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 diff --git a/thirdparty/libqnetwm/libqnetwm/fixx11h.h b/thirdparty/libqnetwm/libqnetwm/fixx11h.h new file mode 100644 index 000000000..460a4c796 --- /dev/null +++ b/thirdparty/libqnetwm/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/libqnetwm/netwm.cpp b/thirdparty/libqnetwm/libqnetwm/netwm.cpp new file mode 100644 index 000000000..6f92b4923 --- /dev/null +++ b/thirdparty/libqnetwm/libqnetwm/netwm.cpp @@ -0,0 +1,735 @@ +/*************************************************************************** + * 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 + +#if 0 +#include +#endif + +#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); +} + +#if 0 +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); +} +#endif + +void NETWM::checkInit() +{ + if(!NETWM::WM_STATE) + NETWM::init(); +} diff --git a/thirdparty/libqnetwm/libqnetwm/netwm.h b/thirdparty/libqnetwm/libqnetwm/netwm.h new file mode 100644 index 000000000..8f02db4a1 --- /dev/null +++ b/thirdparty/libqnetwm/libqnetwm/netwm.h @@ -0,0 +1,188 @@ +/*************************************************************************** + * 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); + +#if 0 + static bool isComposite(); +#endif + + 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