diff --git a/src/ActionClasses.cpp b/src/ActionClasses.cpp index 0e03b8a984..2f977416ce 100644 --- a/src/ActionClasses.cpp +++ b/src/ActionClasses.cpp @@ -1,502 +1,502 @@ /**************************************************************************************** * Copyright (c) 2004 Max Howell * * Copyright (c) 2008 Mark Kretschmann * * Copyright (c) 2009 Artur Szymiec * * * * 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, see . * ****************************************************************************************/ #define DEBUG_PREFIX "ActionClasses" #include "ActionClasses.h" #include "App.h" #include "EngineController.h" #include "KNotificationBackend.h" #include "MainWindow.h" #include "aboutdialog/OcsData.h" #include "amarokconfig.h" #include #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "playlist/PlaylistActions.h" #include "playlist/PlaylistModelStack.h" #include "widgets/Osd.h" #include #include #include #include #include #include extern OcsData ocsData; namespace Amarok { bool favorNone() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::Off; } bool favorScores() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherScores; } bool favorRatings() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::HigherRatings; } bool favorLastPlay() { return AmarokConfig::favorTracks() == AmarokConfig::EnumFavorTracks::LessRecentlyPlayed; } bool entireAlbums() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum || AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; } bool repeatEnabled() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatTrack || AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatAlbum || AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RepeatPlaylist; } bool randomEnabled() { return AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomTrack || AmarokConfig::trackProgression() == AmarokConfig::EnumTrackProgression::RandomAlbum; } } using namespace Amarok; KHelpMenu *Menu::s_helpMenu = 0; static void safePlug( KActionCollection *ac, const char *name, QWidget *w ) { if( ac ) { QAction *a = (QAction*) ac->action( name ); if( a && w ) w->addAction( a ); } } ////////////////////////////////////////////////////////////////////////////////////////// // MenuAction && Menu // KActionMenu doesn't work very well, so we derived our own ////////////////////////////////////////////////////////////////////////////////////////// MenuAction::MenuAction( KActionCollection *ac, QObject *parent ) : QAction( parent ) { setText(i18n( "Amarok Menu" )); ac->addAction("amarok_menu", this); setShortcutConfigurable ( false ); //FIXME disabled as it doesn't work, should use QCursor::pos() } void MenuAction::setShortcutConfigurable(bool b) { setProperty("isShortcutConfigurable", b); } Menu* Menu::s_instance = 0; Menu::Menu( QWidget* parent ) : QMenu( parent ) { s_instance = this; KActionCollection *ac = Amarok::actionCollection(); safePlug( ac, "repeat", this ); safePlug( ac, "random_mode", this ); addSeparator(); safePlug( ac, "playlist_playmedia", this ); addSeparator(); safePlug( ac, "cover_manager", this ); safePlug( ac, "queue_manager", this ); safePlug( ac, "script_manager", this ); addSeparator(); safePlug( ac, "update_collection", this ); safePlug( ac, "rescan_collection", this ); #ifndef Q_WS_MAC addSeparator(); safePlug( ac, KStandardAction::name(KStandardAction::ShowMenubar), this ); #endif addSeparator(); safePlug( ac, KStandardAction::name(KStandardAction::ConfigureToolbars), this ); safePlug( ac, KStandardAction::name(KStandardAction::KeyBindings), this ); // safePlug( ac, "options_configure_globals", this ); //we created this one safePlug( ac, KStandardAction::name(KStandardAction::Preferences), this ); addSeparator(); addMenu( helpMenu( this ) ); addSeparator(); safePlug( ac, KStandardAction::name(KStandardAction::Quit), this ); } Menu* Menu::instance() { return s_instance ? s_instance : new Menu( The::mainWindow() ); } QMenu* Menu::helpMenu( QWidget *parent ) //STATIC { if ( s_helpMenu == 0 ) s_helpMenu = new KHelpMenu( parent, KAboutData::applicationData(), Amarok::actionCollection() ); QMenu* menu = s_helpMenu->menu(); // "What's This" isn't currently defined for anything in Amarok, so let's remove it s_helpMenu->action( KHelpMenu::menuWhatsThis )->setVisible( false ); // Hide the default "About App" dialog, as we replace it with a custom one s_helpMenu->action( KHelpMenu::menuAboutApp )->setVisible( false ); return menu; } ////////////////////////////////////////////////////////////////////////////////////////// // PlayPauseAction ////////////////////////////////////////////////////////////////////////////////////////// PlayPauseAction::PlayPauseAction( KActionCollection *ac, QObject *parent ) : KToggleAction( parent ) { ac->addAction( "play_pause", this ); setText( i18n( "Play/Pause" ) ); setShortcut( Qt::Key_Space ); EngineController *engine = The::engineController(); if( engine->isPaused() ) paused(); else if( engine->isPlaying() ) playing(); else stopped(); connect( this, &PlayPauseAction::triggered, engine, &EngineController::playPause ); connect( engine, &EngineController::stopped, this, &PlayPauseAction::stopped ); connect( engine, &EngineController::paused, this, &PlayPauseAction::paused ); connect( engine, &EngineController::trackPlaying, this, &PlayPauseAction::playing ); } void PlayPauseAction::stopped() { setChecked( false ); setIcon( QIcon::fromTheme("media-playback-start-amarok") ); } void PlayPauseAction::paused() { setChecked( true ); setIcon( QIcon::fromTheme("media-playback-start-amarok") ); } void PlayPauseAction::playing() { setChecked( false ); setIcon( QIcon::fromTheme("media-playback-pause-amarok") ); } ////////////////////////////////////////////////////////////////////////////////////////// // ToggleAction ////////////////////////////////////////////////////////////////////////////////////////// ToggleAction::ToggleAction( const QString &text, void ( *f ) ( bool ), KActionCollection* const ac, const char *name, QObject *parent ) : KToggleAction( parent ) , m_function( f ) { setText(text); ac->addAction(name, this); } void ToggleAction::setChecked( bool b ) { const bool announce = b != isChecked(); m_function( b ); KToggleAction::setChecked( b ); - AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing + AmarokConfig::self()->save(); //So we don't lose the setting when crashing if( announce ) emit toggled( b ); //KToggleAction doesn't do this for us. How gay! } void ToggleAction::setEnabled( bool b ) { const bool announce = b != isEnabled(); KToggleAction::setEnabled( b ); - AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing + AmarokConfig::self()->save(); //So we don't lose the setting when crashing if( announce ) emit QAction::triggered( b ); } ////////////////////////////////////////////////////////////////////////////////////////// // SelectAction ////////////////////////////////////////////////////////////////////////////////////////// SelectAction::SelectAction( const QString &text, void ( *f ) ( int ), KActionCollection* const ac, const char *name, QObject *parent ) : KSelectAction( parent ) , m_function( f ) { PERF_LOG( "In SelectAction" ); setText(text); ac->addAction(name, this); } void SelectAction::setCurrentItem( int n ) { const bool announce = n != currentItem(); debug() << "setCurrentItem: " << n; m_function( n ); KSelectAction::setCurrentItem( n ); - AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing + AmarokConfig::self()->save(); //So we don't lose the setting when crashing if( announce ) emit triggered( n ); } void SelectAction::actionTriggered( QAction *a ) { m_function( currentItem() ); - AmarokConfig::self()->writeConfig(); + AmarokConfig::self()->save(); KSelectAction::actionTriggered( a ); } void SelectAction::setEnabled( bool b ) { const bool announce = b != isEnabled(); KSelectAction::setEnabled( b ); - AmarokConfig::self()->writeConfig(); //So we don't lose the setting when crashing + AmarokConfig::self()->save(); //So we don't lose the setting when crashing if( announce ) emit QAction::triggered( b ); } void SelectAction::setIcons( QStringList icons ) { m_icons = icons; foreach( QAction *a, selectableActionGroup()->actions() ) { a->setIcon( QIcon::fromTheme(icons.takeFirst()) ); } } QStringList SelectAction::icons() const { return m_icons; } QString SelectAction::currentIcon() const { if( m_icons.count() ) return m_icons.at( currentItem() ); return QString(); } QString SelectAction::currentText() const { return KSelectAction::currentText() + "

" + i18n("Click to change"); } void RandomAction::setCurrentItem( int n ) { // Porting //if( QAction *a = parentCollection()->action( "favor_tracks" ) ) // a->setEnabled( n ); SelectAction::setCurrentItem( n ); } ////////////////////////////////////////////////////////////////////////////////////////// // ReplayGainModeAction ////////////////////////////////////////////////////////////////////////////////////////// ReplayGainModeAction::ReplayGainModeAction( KActionCollection *ac, QObject *parent ) : SelectAction( i18n( "&Replay Gain Mode" ), &AmarokConfig::setReplayGainMode, ac, "replay_gain_mode", parent ) { setItems( QStringList() << i18nc( "Replay Gain state, as in, disabled", "&Off" ) << i18nc( "Item, as in, music", "&Track" ) << i18n( "&Album" ) ); EngineController *engine = EngineController::instance(); Q_ASSERT( engine ); if( engine->supportsGainAdjustments() ) setCurrentItem( AmarokConfig::replayGainMode() ); else { // Note: it would be nice to set a tooltip that would explain why this is disabled // to users, but tooltips aren't shown in meny anyway :-( actions().at( 1 )->setEnabled( false ); actions().at( 2 )->setEnabled( false ); } } ////////////////////////////////////////////////////////////////////////////////////////// // BurnMenuAction ////////////////////////////////////////////////////////////////////////////////////////// BurnMenuAction::BurnMenuAction( KActionCollection *ac, QObject *parent ) : QAction( parent ) { setText(i18n( "Burn" )); ac->addAction("burn_menu", this); } QWidget* BurnMenuAction::createWidget( QWidget *w ) { KToolBar *bar = dynamic_cast(w); - if( bar && KAuthorized::authorizeKAction( objectName() ) ) + if( bar && KAuthorized::authorizeAction( objectName() ) ) { //const int id = QAction::getToolButtonID(); //addContainer( bar, id ); w->addAction( this ); //connect( bar, &KToolBar::destroyed, this, &BurnMenuAction::slotDestroyed ); //bar->insertButton( QString::null, id, true, i18n( "Burn" ), index ); //KToolBarButton* button = bar->getButton( id ); //button->setPopup( Amarok::BurnMenu::instance() ); //button->setObjectName( "toolbutton_burn_menu" ); //button->setIcon( "k3b" ); //return associatedWidgets().count() - 1; return 0; } //else return -1; else return 0; } BurnMenu* BurnMenu::s_instance = 0; BurnMenu::BurnMenu( QWidget* parent ) : QMenu( parent ) { s_instance = this; addAction( i18n("Current Playlist"), this, &BurnMenu::slotBurnCurrentPlaylist ); addAction( i18n("Selected Tracks"), this, &BurnMenu::slotBurnSelectedTracks ); //TODO add "album" and "all tracks by artist" } QMenu* BurnMenu::instance() { return s_instance ? s_instance : new BurnMenu( The::mainWindow() ); } void BurnMenu::slotBurnCurrentPlaylist() //SLOT { //K3bExporter::instance()->exportCurrentPlaylist(); } void BurnMenu::slotBurnSelectedTracks() //SLOT { //K3bExporter::instance()->exportSelectedTracks(); } ////////////////////////////////////////////////////////////////////////////////////////// // StopAction ////////////////////////////////////////////////////////////////////////////////////////// StopAction::StopAction( KActionCollection *ac, QObject *parent ) : QAction( parent ) { ac->addAction( "stop", this ); setText( i18n( "Stop" ) ); setIcon( QIcon::fromTheme("media-playback-stop-amarok") ); KGlobalAccel::setGlobalShortcut(this, QKeySequence() ); connect( this, &StopAction::triggered, this, &StopAction::stop ); EngineController *engine = The::engineController(); if( engine->isStopped() ) stopped(); else playing(); connect( engine, &EngineController::stopped, this, &StopAction::stopped ); connect( engine, &EngineController::trackPlaying, this, &StopAction::playing ); } void StopAction::stopped() { setEnabled( false ); } void StopAction::playing() { setEnabled( true ); } void StopAction::stop() { The::engineController()->stop(); } ////////////////////////////////////////////////////////////////////////////////////////// // StopPlayingAfterCurrentTrackAction ////////////////////////////////////////////////////////////////////////////////////////// StopPlayingAfterCurrentTrackAction::StopPlayingAfterCurrentTrackAction( KActionCollection *ac, QObject *parent ) : QAction( parent ) { ac->addAction( "stop_after_current", this ); setText( i18n( "Stop after current Track" ) ); setIcon( QIcon::fromTheme("media-playback-stop-amarok") ); KGlobalAccel::setGlobalShortcut(this, QKeySequence( Qt::META + Qt::SHIFT + Qt::Key_V ) ); connect( this, &StopPlayingAfterCurrentTrackAction::triggered, this, &StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack ); } void StopPlayingAfterCurrentTrackAction::stopPlayingAfterCurrentTrack() { QString text; quint64 activeTrack = Playlist::ModelStack::instance()->bottom()->activeId(); if( activeTrack ) { if( The::playlistActions()->willStopAfterTrack( activeTrack ) ) { The::playlistActions()->stopAfterPlayingTrack( 0 ); text = i18n( "Stop after current track: Off" ); } else { The::playlistActions()->stopAfterPlayingTrack( activeTrack ); text = i18n( "Stop after current track: On" ); } } else text = i18n( "No track playing" ); Amarok::OSD::instance()->OSDWidget::show( text ); if( Amarok::KNotificationBackend::instance()->isEnabled() ) Amarok::KNotificationBackend::instance()->show( i18n( "Amarok" ), text ); } diff --git a/src/KNotificationBackend.cpp b/src/KNotificationBackend.cpp index b4f3ae1e21..3b1b29fcb5 100644 --- a/src/KNotificationBackend.cpp +++ b/src/KNotificationBackend.cpp @@ -1,138 +1,138 @@ /**************************************************************************************** * Copyright (c) 2009-2011 Kevin Funk * * * * 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, see . * ****************************************************************************************/ #include "KNotificationBackend.h" #include "EngineController.h" #include "SvgHandler.h" #include "core/meta/Meta.h" #include "core/support/Debug.h" #include #include #include #include using namespace Amarok; KNotificationBackend * KNotificationBackend::s_instance = 0; KNotificationBackend * KNotificationBackend::instance() { if( !s_instance ) s_instance = new KNotificationBackend(); return s_instance; } void KNotificationBackend::destroy() { delete s_instance; s_instance = 0; } KNotificationBackend::KNotificationBackend() : m_enabled( false ) { EngineController *engine = The::engineController(); connect( engine, &EngineController::trackPlaying, this, &KNotificationBackend::showCurrentTrack ); connect( engine, &EngineController::trackMetadataChanged, this, &KNotificationBackend::showCurrentTrack ); connect( engine, &EngineController::albumMetadataChanged, this, &KNotificationBackend::showCurrentTrack ); if( engine->isPlaying() ) showCurrentTrack(); } KNotificationBackend::~KNotificationBackend() { if( m_notify ) m_notify->close(); } void KNotificationBackend::setEnabled( bool enable ) { m_enabled = enable; } bool KNotificationBackend::isEnabled() const { return m_enabled; } bool KNotificationBackend::isFullscreenWindowActive() const { // Get information of the active window. - KWindowInfo activeWindowInfo = KWindowSystem::windowInfo( KWindowSystem::activeWindow(), NET::WMState ); + KWindowInfo activeWindowInfo( KWindowSystem::activeWindow(), NET::WMState ); // Check if it is running in fullscreen mode. return activeWindowInfo.hasState( NET::FullScreen ); } void KNotificationBackend::show( const QString &title, const QString &body, const QPixmap &pixmap ) { QPixmap icon; if( pixmap.isNull() ) { KIconLoader loader; icon = loader.loadIcon( QString("amarok"), KIconLoader::Desktop ); } else icon = pixmap; KNotification *notify = new KNotification( "message" ); notify->setTitle( title ); notify->setText( body ); notify->setPixmap( icon ); notify->sendEvent(); } void KNotificationBackend::showCurrentTrack( bool force ) { if( !m_enabled && !force ) return; EngineController *engine = The::engineController(); Meta::TrackPtr track = engine->currentTrack(); if( !track ) { warning() << __PRETTY_FUNCTION__ << "null track!"; return; } const QString title = i18n( "Now playing" ); const QString text = engine->prettyNowPlaying(); Meta::AlbumPtr album = track->album(); const QPixmap pixmap = album ? The::svgHandler()->imageWithBorder( album, 80 ) : QPixmap(); KNotification *notify = m_notify.data(); if( !notify ) notify = new KNotification( "trackChange" ); notify->setTitle( title ); notify->setText( text ); notify->setPixmap( pixmap ); if( m_notify ) // existing notification already shown notify->update(); notify->sendEvent(); // (re)start timeout in both cases m_notify = notify; } diff --git a/src/SvgTinter.cpp b/src/SvgTinter.cpp index 8ad2c0bc43..1858ff4abd 100644 --- a/src/SvgTinter.cpp +++ b/src/SvgTinter.cpp @@ -1,140 +1,140 @@ /**************************************************************************************** * Copyright (c) 2007 Nikolaj Hald Nielsen * * Copyright (c) 2007 Mark Kretschmann * * * * 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, see . * ****************************************************************************************/ #include "SvgTinter.h" #include "App.h" #include "core/support/Debug.h" #include #include -#include +#include SvgTinter * SvgTinter::s_instance = 0; SvgTinter::SvgTinter() : m_firstRun( true ) { init(); m_firstRun = false; } SvgTinter::~SvgTinter() {} QByteArray SvgTinter::tint( const QString &filename) { QFile file( filename ); if ( !file.open( QIODevice::ReadOnly ) ) { error() << "Unable to open file: " << filename; return QByteArray(); } QByteArray svg_source( file.readAll() ); // Copied from KSvgrenderer.cpp as we don't load it directly. if (!svg_source.startsWith("open(QIODevice::ReadOnly)) { delete flt; return QByteArray(); } svg_source = flt->readAll(); delete flt; } // QString svg_string( svg_source ); QHashIterator tintIter( m_tintMap ); while( tintIter.hasNext() ) { tintIter.next(); svg_source.replace( tintIter.key(), tintIter.value().toLocal8Bit() ); } return svg_source; } void SvgTinter::init() { if ( m_lastPalette != pApp->palette() || m_firstRun ) { m_tintMap.insert( "#666765", pApp->palette().window().color().name() ); //insert a color for bright ( highlight color ) m_tintMap.insert( "#66ffff", pApp->palette().highlight().color().name() ); //a slightly lighter than window color: m_tintMap.insert( "#e8e8e8", blendColors( pApp->palette().window().color(), "#ffffff", 90 ).name() ); //a slightly darker than window color: m_tintMap.insert( "#565755", blendColors( pApp->palette().window().color(), "#000000", 90 ).name() ); //list background: #ifdef Q_WS_MAC m_tintMap.insert( "#f0f0f0", blendColors( pApp->palette().window().color(), "#000000", 90 ).name() ); m_tintMap.insert( "#ffffff", blendColors( pApp->palette().window().color(), "#000000", 98 ).name() ); #else m_tintMap.insert( "#f0f0f0", pApp->palette().base().color().name() ); #endif //alternate list background: m_tintMap.insert( "#e0e0e0", pApp->palette().alternateBase().color().name() ); //highlight/window mix: m_tintMap.insert( "#123456", blendColors( pApp->palette().window().color(), pApp->palette().highlight().color().name(), 80 ).name() ); //text color, useful for adding contrast m_tintMap.insert( "#010101", pApp->palette().text().color().name() ); m_lastPalette = pApp->palette(); } } QColor SvgTinter::blendColors( const QColor& color1, const QColor& color2, int percent ) { const float factor1 = ( float ) percent / 100; const float factor2 = ( 100 - ( float ) percent ) / 100; const int r = static_cast( color1.red() * factor1 + color2.red() * factor2 ); const int g = static_cast( color1.green() * factor1 + color2.green() * factor2 ); const int b = static_cast( color1.blue() * factor1 + color2.blue() * factor2 ); QColor result; result.setRgb( r, g, b ); return result; } namespace The { SvgTinter* svgTinter() { if ( SvgTinter::s_instance == 0 ) SvgTinter::s_instance = new SvgTinter(); return SvgTinter::s_instance; } } diff --git a/src/aboutdialog/AnimatedBarWidget.cpp b/src/aboutdialog/AnimatedBarWidget.cpp index 77fd83c58c..21d1d07b18 100644 --- a/src/aboutdialog/AnimatedBarWidget.cpp +++ b/src/aboutdialog/AnimatedBarWidget.cpp @@ -1,201 +1,201 @@ /**************************************************************************************** * Copyright (c) 2009 Téo Mrnjavac * * Copyright (c) 2012 Lachlan Dufton * * * * 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, see . * ****************************************************************************************/ #include "AnimatedBarWidget.h" #include #include #include #include AnimatedBarWidget::AnimatedBarWidget( const QIcon &icon, const QString &text, const QString &animatedIconName, QWidget *parent ) : QAbstractButton( parent ) { setIconSize( QSize( 22, 22 ) ); setIcon( icon ); setText( text ); m_animating = false; m_animatedWidget = new AnimatedWidget( animatedIconName, this ); m_animatedWidget->setFixedSize( 22, 22 ); m_animatedWidget->hide(); m_animatedWidget->setAutoFillBackground( false ); setFocusPolicy( Qt::NoFocus ); m_hoverHint = false; // Need to change the size policy to allow for wrapping QSizePolicy poli( QSizePolicy::Preferred, QSizePolicy::Preferred ); poli.setHeightForWidth( true ); setSizePolicy( poli ); } AnimatedBarWidget::~AnimatedBarWidget() {} void AnimatedBarWidget::animate() { m_animating = true; m_animatedWidget->show(); m_animatedWidget->start(); update(); } void AnimatedBarWidget::stop() { m_animating = false; m_animatedWidget->stop(); m_animatedWidget->hide(); update(); } void AnimatedBarWidget::fold() { hide(); } //protected: void AnimatedBarWidget::setHoverHintEnabled( bool enable ) { m_hoverHint = enable; update(); } bool AnimatedBarWidget::isHoverHintEnabled() const { return m_hoverHint; } void AnimatedBarWidget::enterEvent( QEvent* event ) { QWidget::enterEvent( event ); setHoverHintEnabled( true ); update(); } void AnimatedBarWidget::leaveEvent( QEvent* event ) { QWidget::leaveEvent( event ); setHoverHintEnabled( false ); update(); } void AnimatedBarWidget::paintEvent( QPaintEvent* event ) { Q_UNUSED(event); QPainter painter(this); const int buttonHeight = height(); int buttonWidth = width(); drawHoverBackground(&painter); int left, top, right, bottom; getContentsMargins ( &left, &top, &right, &bottom ); const int padding = 2; const int iconWidth = iconSize().width(); const int iconHeight = iconSize().height(); const int iconTop = ( (buttonHeight - top - bottom) - iconHeight ) / 2; if( !m_animating ) { const QRect iconRect( left + padding, iconTop, iconWidth, iconHeight ); painter.drawPixmap( iconRect, icon().pixmap( iconSize() ) ); } else m_animatedWidget->move( left + padding, iconTop ); const QRect textRect( left + (padding * 3) + iconWidth, top, buttonWidth - (left + padding * 3 + iconWidth) - padding, buttonHeight); QFontMetrics fm( font() ); painter.drawText( textRect, Qt::AlignVCenter | Qt::TextWordWrap, text() ); } void AnimatedBarWidget::drawHoverBackground(QPainter* painter) { const bool isHovered = isHoverHintEnabled(); if( isHovered ) { - QStyleOptionViewItemV4 option; + QStyleOptionViewItem option; option.initFrom(this); option.state = QStyle::State_Enabled | QStyle::State_Selected; - option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + option.viewItemPosition = QStyleOptionViewItem::OnlyOne; style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter, this ); } else { - QStyleOptionViewItemV4 option; + QStyleOptionViewItem option; option.initFrom(this); option.state = QStyle::State_Enabled | QStyle::State_MouseOver; - option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + option.viewItemPosition = QStyleOptionViewItem::OnlyOne; style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter, this ); } } QColor AnimatedBarWidget::foregroundColor() const { const bool isHighlighted = isHoverHintEnabled(); QColor foregroundColor = palette().color( foregroundRole() ); if( !isHighlighted ) foregroundColor.setAlpha( 60 ); return foregroundColor; } QSize AnimatedBarWidget::sizeHint() const { QSize size = QAbstractButton::sizeHint(); size.setHeight( iconSize().height() + 8 ); return size; } /** * Find the height required to fit the widget with wrapped text * and the icon, plus padding */ int AnimatedBarWidget::heightForWidth(int w) const { const int hPadding = 2; const int vPadding = 4; // Padding to the left and right of both the icon and text const QRect bound( 0, 0, (w - hPadding * 4 - iconSize().width()), 0 ); QFontMetrics fm( font() ); int fontHeight = fm.boundingRect( bound, Qt::TextWordWrap, text() ).height(); if( fontHeight < iconSize().height() ) return iconSize().height() + 2 * vPadding; return fontHeight + 2 * vPadding; } diff --git a/src/aboutdialog/FramedLabel.cpp b/src/aboutdialog/FramedLabel.cpp index 1aa3a740f4..b35672431d 100644 --- a/src/aboutdialog/FramedLabel.cpp +++ b/src/aboutdialog/FramedLabel.cpp @@ -1,50 +1,50 @@ /**************************************************************************************** * Copyright (c) 2009 Téo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #include "FramedLabel.h" #include #include #include FramedLabel::FramedLabel( QWidget *parent, Qt::WindowFlags f ) : QLabel( parent, f ) {} FramedLabel::FramedLabel( const QString &text, QWidget *parent, Qt::WindowFlags f ) : QLabel( text, parent, f ) {} FramedLabel::~FramedLabel() {} void FramedLabel::paintEvent( QPaintEvent *event ) { Q_UNUSED( event ) if( frameShape() == QFrame::StyledPanel ) { QPainter painter( this ); - QStyleOptionViewItemV4 option; + QStyleOptionViewItem option; option.initFrom( this ); option.state = QStyle::State_Enabled | QStyle::State_MouseOver; - option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + option.viewItemPosition = QStyleOptionViewItem::OnlyOne; style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, &painter, this ); } QRect cr = contentsRect(); QPaintEvent *e = new QPaintEvent( cr ); QLabel::paintEvent( e ); } diff --git a/src/aboutdialog/OcsPersonItem.cpp b/src/aboutdialog/OcsPersonItem.cpp index 67e067cf46..4817fa1db0 100644 --- a/src/aboutdialog/OcsPersonItem.cpp +++ b/src/aboutdialog/OcsPersonItem.cpp @@ -1,352 +1,352 @@ /**************************************************************************************** * Copyright (c) 2009 Téo Mrnjavac * * * * 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, see . * ****************************************************************************************/ #include "OcsPersonItem.h" #include "core/support/Debug.h" #include "libattica-ocsclient/provider.h" #include "libattica-ocsclient/providerinitjob.h" #include "libattica-ocsclient/personjob.h" #include #include #include #include #include #include #include OcsPersonItem::OcsPersonItem( const KAboutPerson &person, const QString ocsUsername, PersonStatus status, QWidget *parent ) : QWidget( parent ) , m_status( status ) , m_state( Offline ) { m_person = &person; m_ocsUsername = ocsUsername; setupUi( this ); init(); m_avatar->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); //TODO: Add favorite artists! } void OcsPersonItem::init() { m_textLabel->setTextInteractionFlags( Qt::TextBrowserInteraction ); m_textLabel->setOpenExternalLinks( true ); m_textLabel->setContentsMargins( 5, 0, 0, 2 ); m_verticalLayout->setSpacing( 0 ); m_vertLine->hide(); m_initialSpacer->changeSize( 0, 40, QSizePolicy::Fixed, QSizePolicy::Fixed ); layout()->invalidate(); m_aboutText.append( "" + m_person->name() + "" ); if( !m_person->task().isEmpty() ) m_aboutText.append( "
" + m_person->task() ); m_iconsBar = new KToolBar( this, false, false ); m_snBar = new KToolBar( this, false, false ); m_iconsBar->setIconSize( QSize( 22, 22 ) ); m_iconsBar->setContentsMargins( 0, 0, 0, 0 ); m_iconsBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); if( m_status == Author ) { QHBoxLayout *iconsLayout = new QHBoxLayout( this ); iconsLayout->setMargin( 0 ); iconsLayout->setSpacing( 0 ); m_verticalLayout->insertLayout( m_verticalLayout->count() - 1, iconsLayout ); iconsLayout->addWidget( m_iconsBar ); iconsLayout->addWidget( m_snBar ); iconsLayout->addStretch( 0 ); m_snBar->setIconSize( QSize( 16, 16 ) ); m_snBar->setContentsMargins( 0, 0, 0, 0 ); m_snBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); } else { layout()->addWidget( m_iconsBar ); m_snBar->hide(); } if( !m_person->emailAddress().isEmpty() ) { QAction *email = new QAction( QIcon::fromTheme( "internet-mail" ), i18n("Email contributor"), this ); email->setToolTip( m_person->emailAddress() ); email->setData( QString( "mailto:" + m_person->emailAddress() ) ); m_iconsBar->addAction( email ); } if( !m_person->webAddress().isEmpty() ) { QAction *homepage = new QAction( QIcon::fromTheme( "applications-internet" ), i18n("Visit contributor's homepage"), this ); homepage->setToolTip( m_person->webAddress() ); homepage->setData( m_person->webAddress() ); m_iconsBar->addAction( homepage ); } connect( m_iconsBar, &KToolBar::actionTriggered, this, &OcsPersonItem::launchUrl ); connect( m_snBar, &KToolBar::actionTriggered, this, &OcsPersonItem::launchUrl ); m_textLabel->setText( m_aboutText ); } OcsPersonItem::~OcsPersonItem() {} QString OcsPersonItem::name() { return m_person->name(); } void OcsPersonItem::launchUrl( QAction *action ) //SLOT { QUrl url = QUrl( action->data().toString() ); - KRun::runUrl( url, "text/html", 0, false ); + KRun::runUrl( url, "text/html", nullptr, KRun::RunExecutables, QString() ); } void OcsPersonItem::switchToOcs( const AmarokAttica::Provider &provider ) { if( m_state == Online ) return; m_avatar->setFixedWidth( 56 ); m_vertLine->show(); m_initialSpacer->changeSize( 5, 40, QSizePolicy::Fixed, QSizePolicy::Fixed ); layout()->invalidate(); if( !m_ocsUsername.isEmpty() ) { AmarokAttica::PersonJob *personJob; if( m_ocsUsername == QString( "%%category%%" ) ) //TODO: handle grouping return; personJob = provider.requestPerson( m_ocsUsername ); connect( personJob, &AmarokAttica::PersonJob::result, this, &OcsPersonItem::onJobFinished ); emit ocsFetchStarted(); m_state = Online; } } void OcsPersonItem::onJobFinished( KJob *job ) { AmarokAttica::PersonJob *personJob = qobject_cast< AmarokAttica::PersonJob * >( job ); if( personJob->error() == 0 ) { fillOcsData( personJob->person() ); } emit ocsFetchResult( personJob->error() ); } void OcsPersonItem::fillOcsData( const AmarokAttica::Person &ocsPerson ) { if( !( ocsPerson.avatar().isNull() ) ) { m_avatar->setFixedSize( 56, 56 ); m_avatar->setFrameShape( QFrame::StyledPanel ); //this is a FramedLabel, otherwise oxygen wouldn't paint the frame m_avatar->setPixmap( ocsPerson.avatar() ); m_avatar->setAlignment( Qt::AlignCenter ); } if( !ocsPerson.country().isEmpty() ) { m_aboutText.append( "
" ); if( !ocsPerson.city().isEmpty() ) m_aboutText.append( i18nc( "A person's location: City, Country", "%1, %2", ocsPerson.city(), ocsPerson.country() ) ); else m_aboutText.append( ocsPerson.country() ); } if( m_status == Author ) { if( !ocsPerson.extendedAttribute( "ircchannels" ).isEmpty() ) { QString channelsString = ocsPerson.extendedAttribute( "ircchannels" ); //We extract the channel names from the string provided by OCS: QRegExp channelrx = QRegExp( "#+[\\w\\.\\-\\/!()+]+([\\w\\-\\/!()+]?)", Qt::CaseInsensitive ); QStringList channels; int pos = 0; while( ( pos = channelrx.indexIn( channelsString, pos ) ) != -1 ) { channels << channelrx.cap( 0 ); pos += channelrx.matchedLength(); } m_aboutText.append( "
" + i18n("IRC channels: ") ); QString link; foreach( const QString &channel, channels ) { const QString channelName = QString( channel ).remove( '#' ); link = QString( "irc://irc.freenode.org/%1" ).arg( channelName ); m_aboutText.append( QString( "%2" ).arg( link, channel ) + " " ); } } if( !ocsPerson.extendedAttribute( "favouritemusic" ).isEmpty() ) { QStringList artists = ocsPerson.extendedAttribute( "favouritemusic" ).split( ", " ); //TODO: make them clickable m_aboutText.append( "
" + i18n( "Favorite music: " ) + artists.join( ", " ) ); } } QAction *visitProfile = new QAction( QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/opendesktop-22.png" ) ) ), i18n( "Visit %1's openDesktop.org profile", ocsPerson.firstName() ), this ); visitProfile->setToolTip( i18n( "Visit %1's profile on openDesktop.org", ocsPerson.firstName() ) ); visitProfile->setData( ocsPerson.extendedAttribute( "profilepage" ) ); m_iconsBar->addAction( visitProfile ); if( m_status == Author ) { QList< QPair< QString, QString > > ocsHomepages; ocsHomepages.append( QPair< QString, QString >( ocsPerson.extendedAttribute( "homepagetype" ), ocsPerson.homepage() ) ); debug() << "USER HOMEPAGE DATA STARTS HERE"; debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second; for( int i = 2; i <= 10; i++ ) //OCS supports 10 total homepages as of 2/oct/2009 { QString type = ocsPerson.extendedAttribute( QString( "homepagetype%1" ).arg( i ) ); ocsHomepages.append( QPair< QString, QString >( ( type == " " ) ? "" : type, ocsPerson.extendedAttribute( QString( "homepage%1" ).arg( i ) ) ) ); debug() << ocsHomepages.last().first << " :: " << ocsHomepages.last().second; } bool fillHomepageFromOcs = m_person->webAddress().isEmpty(); //We check if the person already has a homepage in KAboutPerson. for( QList< QPair< QString, QString > >::const_iterator entry = ocsHomepages.constBegin(); entry != ocsHomepages.constEnd(); ++entry ) { QString type = (*entry).first; QString url = (*entry).second; QIcon icon; QString text; if( type == "Blog" ) { icon = QIcon::fromTheme( "kblogger" ); text = i18n( "Visit contributor's blog" ); } else if( type == "delicious" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-delicious.png" ) ) ); text = i18n( "Visit contributor's del.icio.us profile" ); } else if( type == "Digg" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-digg.png" ) ) ); text = i18n( "Visit contributor's Digg profile" ); } else if( type == "Facebook" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-facebook.png" ) ) ); text = i18n( "Visit contributor's Facebook profile" ); } else if( type == "Homepage" || type == "other" || ( type.isEmpty() && !url.isEmpty() ) ) { if( fillHomepageFromOcs ) { QAction *homepage = new QAction( QIcon::fromTheme( "applications-internet" ), i18n("Visit contributor's homepage"), this ); homepage->setToolTip( url ); homepage->setData( url ); m_iconsBar->addAction( homepage ); fillHomepageFromOcs = false; continue; } if( type == "other" && url.contains( "last.fm/" ) ) //HACK: assign a last.fm icon if the URL contains last.fm { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-lastfm.png" ) ) ); text = i18n( "Visit contributor's Last.fm profile" ); } else continue; } else if( type == "LinkedIn" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-linkedin.png" ) ) ); text = i18n( "Visit contributor's LinkedIn profile" ); } else if( type == "MySpace" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-myspace.png" ) ) ); text = i18n( "Visit contributor's MySpace homepage" ); } else if( type == "Reddit" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-reddit.png" ) ) ); text = i18n( "Visit contributor's Reddit profile" ); } else if( type == "YouTube" ) { icon = QIcon( "dragonplayer" ); //FIXME: icon text = i18n( "Visit contributor's YouTube profile" ); } else if( type == "Twitter" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-twitter.png" ) ) ); text = i18n( "Visit contributor's Twitter feed" ); } else if( type == "Wikipedia" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-wikipedia.png" ) ) ); text = i18n( "Visit contributor's Wikipedia profile" ); } else if( type == "Xing" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-xing.png" ) ) ); text = i18n( "Visit contributor's Xing profile" ); } else if( type == "identi.ca" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-identica.png" ) ) ); text = i18n( "Visit contributor's identi.ca feed" ); } else if( type == "libre.fm" ) { icon = QIcon( "juk" ); //FIXME: icon text = i18n( "Visit contributor's libre.fm profile" ); } else if( type == "StackOverflow" ) { icon = QIcon( QPixmap( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/emblem-stackoverflow.png" ) ) ); text = i18n( "Visit contributor's StackOverflow profile" ); } else break; QAction *action = new QAction( icon, text, this ); action->setToolTip( url ); action->setData( url ); m_snBar->addAction( action ); } debug() << "END USER HOMEPAGE DATA"; } m_textLabel->setText( m_aboutText ); } diff --git a/src/browsers/playlistbrowser/QtGroupingProxy.cpp b/src/browsers/playlistbrowser/QtGroupingProxy.cpp index c5803e917b..9d6439235a 100644 --- a/src/browsers/playlistbrowser/QtGroupingProxy.cpp +++ b/src/browsers/playlistbrowser/QtGroupingProxy.cpp @@ -1,898 +1,896 @@ /**************************************************************************************** * Copyright (c) 2007-2011 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #include "QtGroupingProxy.h" #include #include #include #include /*! \class QtGroupingProxy \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level. The source model can be flat or tree organized, but only the original top level rows are used for determining the grouping. \ingroup model-view */ QtGroupingProxy::QtGroupingProxy( QObject *parent ) : QAbstractProxyModel( parent ) { } QtGroupingProxy::QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootIndex, int groupedColumn, QObject *parent ) : QAbstractProxyModel( parent ) , m_rootIndex( rootIndex ) , m_groupedColumn( 0 ) { setSourceModel( model ); if( groupedColumn != -1 ) setGroupedColumn( groupedColumn ); } QtGroupingProxy::~QtGroupingProxy() { } void QtGroupingProxy::setSourceModel( QAbstractItemModel *sourceModel ) { QAbstractProxyModel::setSourceModel( sourceModel ); // signal proxies connect( sourceModel, &QAbstractItemModel::dataChanged, this, &QtGroupingProxy::modelDataChanged ); connect( sourceModel, &QAbstractItemModel::rowsInserted, this, &QtGroupingProxy::modelRowsInserted ); connect( sourceModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &QtGroupingProxy::modelRowsAboutToBeInserted ); connect( sourceModel, &QAbstractItemModel::rowsRemoved, this, &QtGroupingProxy::modelRowsRemoved ); connect( sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &QtGroupingProxy::modelRowsAboutToBeRemoved ); connect( sourceModel, &QAbstractItemModel::layoutChanged, this, &QtGroupingProxy::buildTree ); connect( sourceModel, &QAbstractItemModel::dataChanged, this, &QtGroupingProxy::modelDataChanged ); //set invalid index from source as root index m_rootIndex = sourceModel->index( -1, -1 ); } void QtGroupingProxy::setRootIndex( const QModelIndex &rootIndex ) { if( m_rootIndex == rootIndex ) return; m_rootIndex = rootIndex; //TODO: invalidate tree so buildTree() can be called later. } void QtGroupingProxy::setGroupedColumn( int groupedColumn ) { m_groupedColumn = groupedColumn; //TODO: invalidate tree so buildTree() can be called later. buildTree(); } /** Maps to what groups the source row belongs by returning the data of those groups. * * @returns a list of data for the rows the argument belongs to. In common cases this list will * contain only one entry. An empty list means that the source item will be placed in the root of * this proxyModel. There is no support for hiding source items. * * Group data can be pre-loaded in the return value so it's added to the cache maintained by this * class. This is required if you want to have data that is not present in the source model. */ QList QtGroupingProxy::belongsTo( const QModelIndex &idx ) { //qDebug() << __FILE__ << __FUNCTION__; QList rowDataList; //get all the data for this index from the model ItemData itemData = sourceModel()->itemData( idx ); QMapIterator i( itemData ); while( i.hasNext() ) { i.next(); int role = i.key(); QVariant variant = i.value(); // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant; if( variant.type() == QVariant::List ) { //a list of variants get's expanded to multiple rows QVariantList list = variant.toList(); for( int i = 0; i < list.length(); i++ ) { //take an existing row data or create a new one RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i ) : RowData(); //we only gather data for the first column ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData(); indexData.insert( role, list.value( i ) ); rowData.insert( 0, indexData ); //for the grouped column the data should not be gathered from the children //this will allow filtering on the content of this column with a //QSortFilterProxyModel rowData.insert( m_groupedColumn, indexData ); rowDataList.insert( i, rowData ); } } else if( !variant.isNull() ) { //it's just a normal item. Copy all the data and break this loop. RowData rowData; rowData.insert( 0, itemData ); rowDataList << rowData; break; } } return rowDataList; } /* m_groupHash layout * key : index of the group in m_groupMaps * value : a QList of the original rows in sourceModel() for the children of this group * * key = -1 contains a QList of the non-grouped indexes * * TODO: sub-groups */ void QtGroupingProxy::buildTree() { if( !sourceModel() ) return; beginResetModel(); m_groupHash.clear(); //don't clear the data maps since most of it will probably be needed again. m_parentCreateList.clear(); int max = sourceModel()->rowCount( m_rootIndex ); //qDebug() << QString("building tree with %1 leafs.").arg( max ); //WARNING: these have to be added in order because the addToGroups function is optimized for //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best. for( int row = 0; row < max; row++ ) { QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootIndex ); addSourceRow( idx ); } // dumpGroups(); endResetModel(); } QList QtGroupingProxy::addSourceRow( const QModelIndex &idx ) { QList updatedGroups; QList groupData = belongsTo( idx ); //an empty list here means it's supposed to go in root. if( groupData.isEmpty() ) { updatedGroups << -1; if( !m_groupHash.keys().contains( -1 ) ) m_groupHash.insert( -1, QList() ); //add an empty placeholder } //an item can be in multiple groups foreach( RowData data, groupData ) { int updatedGroup = -1; if( !data.isEmpty() ) { // qDebug() << QString("index %1 belongs to group %2").arg( row ) // .arg( data[0][Qt::DisplayRole].toString() ); foreach( const RowData &cachedData, m_groupMaps ) { //when this matches the index belongs to an existing group if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] ) { data = cachedData; break; } } updatedGroup = m_groupMaps.indexOf( data ); //-1 means not found if( updatedGroup == -1 ) { QModelIndex newGroupIdx = addEmptyGroup( data ); updatedGroup = newGroupIdx.row(); } if( !m_groupHash.keys().contains( updatedGroup ) ) m_groupHash.insert( updatedGroup, QList() ); //add an empty placeholder } if( !updatedGroups.contains( updatedGroup ) ) updatedGroups << updatedGroup; } //update m_groupHash to the new source-model layout (one row added) QMutableHashIterator > i( m_groupHash ); while( i.hasNext() ) { i.next(); QList &groupList = i.value(); int insertedProxyRow = groupList.count(); for( ; insertedProxyRow > 0 ; insertedProxyRow-- ) { int &rowValue = groupList[insertedProxyRow-1]; if( idx.row() <= rowValue ) { //increment the rows that come after the new row since they moved one place up. rowValue++; } else { break; } } if( updatedGroups.contains( i.key() ) ) { //the row needs to be added to this group beginInsertRows( index( i.key() ), insertedProxyRow, insertedProxyRow ); groupList.insert( insertedProxyRow, idx.row() ); endInsertRows(); } } return updatedGroups; } /** Each ModelIndex has in it's internalId a position in the parentCreateList. * struct ParentCreate are the instructions to recreate the parent index. * It contains the proxy row number of the parent and the postion in this list of the grandfather. * This function creates the ParentCreate structs and saves them in a list. */ int QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const { if( !parent.isValid() ) return -1; struct ParentCreate pc; for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) { pc = m_parentCreateList[i]; if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() ) return i; } //there is no parentCreate yet for this index, so let's create one. pc.parentCreateIndex = parent.internalId(); pc.row = parent.row(); m_parentCreateList << pc; //dumpParentCreateList(); // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() ); // for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) // { // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex << // " | " << m_parentCreateList[i].row; // } return m_parentCreateList.size() - 1; } QModelIndex QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const { // qDebug() << "index requested for: (" << row << "," << column << "), " << parent; if( !hasIndex(row, column, parent) ) return QModelIndex(); if( parent.column() > 0 ) return QModelIndex(); /* We save the instructions to make the parent of the index in a struct. * The place of the struct in the list is stored in the internalId */ int parentCreateIndex = indexOfParentCreate( parent ); return createIndex( row, column, parentCreateIndex ); } QModelIndex QtGroupingProxy::parent( const QModelIndex &index ) const { //qDebug() << "parent: " << index; if( !index.isValid() ) return QModelIndex(); int parentCreateIndex = index.internalId(); //qDebug() << "parentCreateIndex: " << parentCreateIndex; if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) return QModelIndex(); struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")"; //only items at column 0 have children return createIndex( pc.row, 0, pc.parentCreateIndex ); } int QtGroupingProxy::rowCount( const QModelIndex &index ) const { //qDebug() << "rowCount: " << index; if( !index.isValid() ) { //the number of top level groups + the number of non-grouped playlists int rows = m_groupMaps.count() + m_groupHash.value( -1 ).count(); //qDebug() << rows << " in root group"; return rows; } //TODO:group in group support. if( isGroup( index ) ) { qint64 groupIndex = index.row(); int rows = m_groupHash.value( groupIndex ).count(); //qDebug() << rows << " in group " << m_groupMaps[groupIndex]; return rows; } QModelIndex originalIndex = mapToSource( index ); int rowCount = sourceModel()->rowCount( originalIndex ); //qDebug() << "original item: rowCount == " << rowCount; return rowCount; } int QtGroupingProxy::columnCount( const QModelIndex &index ) const { if( !index.isValid() ) return sourceModel()->columnCount( m_rootIndex ); if( index.column() != 0 ) return 0; return sourceModel()->columnCount( mapToSource( index ) ); } QVariant QtGroupingProxy::data( const QModelIndex &index, int role ) const { if( !index.isValid() ) return sourceModel()->data( m_rootIndex, role ); //rootNode could have useful data //qDebug() << __FUNCTION__ << index << " role: " << role; int row = index.row(); int column = index.column(); if( isGroup( index ) ) { //qDebug() << __FUNCTION__ << "is a group"; //use cached or precalculated data if( m_groupMaps[row][column].contains( role ) ) { //qDebug() << "Using cached data"; return m_groupMaps[row][column].value( role ); } //for column 0 we gather data from the grouped column instead if( column == 0 ) column = m_groupedColumn; //map all data from children to columns of group to allow grouping one level up QVariantList variantsOfChildren; int childCount = m_groupHash.value( row ).count(); if( childCount == 0 ) return QVariant(); //qDebug() << __FUNCTION__ << "childCount: " << childCount; //Need a parentIndex with column == 0 because only those have children. QModelIndex parentIndex = this->index( row, 0, index.parent() ); for( int childRow = 0; childRow < childCount; childRow++ ) { QModelIndex childIndex = this->index( childRow, column, parentIndex ); QVariant data = mapToSource( childIndex ).data( role ); //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type()); if( data.isValid() && !variantsOfChildren.contains( data ) ) variantsOfChildren << data; } //qDebug() << "gathered this data from children: " << variantsOfChildren; //saving in cache ItemData roleMap = m_groupMaps[row].value( column ); foreach( const QVariant &variant, variantsOfChildren ) { if( roleMap[ role ] != variant ) roleMap.insert( role, variantsOfChildren ); } //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role]; //only one unique variant? No need to return a list if( variantsOfChildren.count() == 1 ) return variantsOfChildren.first(); if( variantsOfChildren.count() == 0 ) return QVariant(); return variantsOfChildren; } return mapToSource( index ).data( role ); } bool QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role ) { if( !idx.isValid() ) return false; //no need to set data to exactly the same value if( idx.data( role ) == value ) return false; if( isGroup( idx ) ) { ItemData columnData = m_groupMaps[idx.row()][idx.column()]; columnData.insert( role, value ); //QItemDelegate will always use Qt::EditRole if( role == Qt::EditRole ) columnData.insert( Qt::DisplayRole, value ); //and make sure it's stored in the map m_groupMaps[idx.row()].insert( idx.column(), columnData ); int columnToChange = idx.column() ? idx.column() : m_groupedColumn; foreach( int originalRow, m_groupHash.value( idx.row() ) ) { QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange, m_rootIndex ); if( childIdx.isValid() ) sourceModel()->setData( childIdx, value, role ); } //TODO: we might need to reload the data from the children at this point emit dataChanged( idx, idx ); return true; } return sourceModel()->setData( mapToSource( idx ), value, role ); } bool QtGroupingProxy::isGroup( const QModelIndex &index ) const { int parentCreateIndex = index.internalId(); if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() ) return true; return false; } QModelIndex QtGroupingProxy::mapToSource( const QModelIndex &index ) const { //qDebug() << "mapToSource: " << index; if( !index.isValid() ) return m_rootIndex; if( isGroup( index ) ) { //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString(); return m_rootIndex; } QModelIndex proxyParent = index.parent(); //qDebug() << "parent: " << proxyParent; QModelIndex originalParent = mapToSource( proxyParent ); //qDebug() << "originalParent: " << originalParent; int originalRow = index.row(); if( originalParent == m_rootIndex ) { int indexInGroup = index.row(); if( !proxyParent.isValid() ) indexInGroup -= m_groupMaps.count(); //qDebug() << "indexInGroup" << indexInGroup; QList childRows = m_groupHash.value( proxyParent.row() ); if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) return QModelIndex(); originalRow = childRows.at( indexInGroup ); //qDebug() << "originalRow: " << originalRow; } return sourceModel()->index( originalRow, index.column(), originalParent ); } QModelIndexList QtGroupingProxy::mapToSource( const QModelIndexList& list ) const { QModelIndexList originalList; foreach( const QModelIndex &index, list ) { QModelIndex originalIndex = mapToSource( index ); if( originalIndex.isValid() ) originalList << originalIndex; } return originalList; } QModelIndex QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const { if( !idx.isValid() ) return QModelIndex(); QModelIndex proxyParent; QModelIndex sourceParent = idx.parent(); //qDebug() << "sourceParent: " << sourceParent; int proxyRow = idx.row(); int sourceRow = idx.row(); if( sourceParent.isValid() && ( sourceParent != m_rootIndex ) ) { //idx is a child of one of the items in the source model proxyParent = mapFromSource( sourceParent ); } else { //idx is an item in the top level of the source model (child of the rootnode) int groupRow = -1; QHashIterator > iterator( m_groupHash ); while( iterator.hasNext() ) { iterator.next(); if( iterator.value().contains( sourceRow ) ) { groupRow = iterator.key(); break; } } if( groupRow != -1 ) //it's in a group, let's find the correct row. { proxyParent = this->index( groupRow, 0, QModelIndex() ); proxyRow = m_groupHash.value( groupRow ).indexOf( sourceRow ); } else { proxyParent = QModelIndex(); // if the proxy item is not in a group it will be below the groups. int groupLength = m_groupMaps.count(); //qDebug() << "groupNames length: " << groupLength; int i = m_groupHash.value( -1 ).indexOf( sourceRow ); //qDebug() << "index in hash: " << i; proxyRow = groupLength + i; } } //qDebug() << "proxyParent: " << proxyParent; //qDebug() << "proxyRow: " << proxyRow; return this->index( proxyRow, 0, proxyParent ); } Qt::ItemFlags QtGroupingProxy::flags( const QModelIndex &idx ) const { if( !idx.isValid() ) { Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootIndex ); if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) ) return Qt::ItemFlags( Qt::ItemIsDropEnabled ); return 0; } //only if the grouped column has the editable flag set allow the //actions leading to setData on the source (edit & drop) // qDebug() << idx; if( isGroup( idx ) ) { // dumpGroups(); Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); bool groupIsEditable = true; //it's possible to have empty groups if( m_groupHash.value( idx.row() ).count() == 0 ) { //check the flags of this column with the root node QModelIndex originalRootNode = sourceModel()->index( m_rootIndex.row(), m_groupedColumn, m_rootIndex.parent() ); groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable ); } else { foreach( int originalRow, m_groupHash.value( idx.row() ) ) { QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn, m_rootIndex ); // qDebug() << "originalIdx: " << originalIdx; groupIsEditable = groupIsEditable ? originalIdx.flags().testFlag( Qt::ItemIsEditable ) : false; if( !groupIsEditable ) //all children need to have an editable grouped column break; } } if( groupIsEditable ) return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled ); return defaultFlags; } QModelIndex originalIdx = mapToSource( idx ); Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx ); //check the source model to see if the grouped column is editable; QModelIndex groupedColumnIndex = sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() ); bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable ); if( groupIsEditable ) return originalItemFlags | Qt::ItemIsDragEnabled; return originalItemFlags; } QModelIndex QtGroupingProxy::buddy( const QModelIndex &index ) const { /* We need to override this method in case of groups. Otherwise, at least editing * of groups is prevented, following sequence occurs: * * #0 QtGroupingProxy::mapToSource (this=0x15ad8a0, index=...) at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/QtGroupingProxy.cpp:492 * #1 0x00007ffff609d7b6 in QAbstractProxyModel::buddy (this=0x15ad8a0, index=...) at itemviews/qabstractproxymodel.cpp:306 * #2 0x00007ffff609ed25 in QSortFilterProxyModel::buddy (this=0x15ae730, index=...) at itemviews/qsortfilterproxymodel.cpp:2015 * #3 0x00007ffff6012a2c in QAbstractItemView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0) at itemviews/qabstractitemview.cpp:2569 * #4 0x00007ffff6f9aa9f in Amarok::PrettyTreeView::edit (this=0x15aec30, index=..., trigger=QAbstractItemView::AllEditTriggers, event=0x0) * at /home/strohel/projekty/amarok/src/widgets/PrettyTreeView.cpp:58 * #5 0x00007ffff6007f1e in QAbstractItemView::edit (this=0x15aec30, index=...) at itemviews/qabstractitemview.cpp:1138 * #6 0x00007ffff6dc86e4 in PlaylistBrowserNS::PlaylistBrowserCategory::createNewFolder (this=0x159bf90) * at /home/strohel/projekty/amarok/src/browsers/playlistbrowser/PlaylistBrowserCategory.cpp:298 * * but we return invalid index in mapToSource() for group index. */ if( index.isValid() && isGroup( index ) ) return index; return QAbstractProxyModel::buddy( index ); } QVariant QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const { return sourceModel()->headerData( section, orientation, role ); } bool QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const { if( !parent.isValid() ) return false; if( isGroup( parent ) ) return false; return sourceModel()->canFetchMore( mapToSource( parent ) ); } void QtGroupingProxy::fetchMore ( const QModelIndex & parent ) { if( !parent.isValid() ) return; if( isGroup( parent ) ) return; return sourceModel()->fetchMore( mapToSource( parent ) ); } QModelIndex QtGroupingProxy::addEmptyGroup( const RowData &data ) { int newRow = m_groupMaps.count(); beginInsertRows( QModelIndex(), newRow, newRow ); m_groupMaps << data; endInsertRows(); return index( newRow, 0, QModelIndex() ); } bool QtGroupingProxy::removeGroup( const QModelIndex &idx ) { beginRemoveRows( idx.parent(), idx.row(), idx.row() ); m_groupHash.remove( idx.row() ); m_groupMaps.removeAt( idx.row() ); m_parentCreateList.removeAt( idx.internalId() ); endRemoveRows(); //TODO: only true if all data could be unset. return true; } bool QtGroupingProxy::hasChildren( const QModelIndex &parent ) const { if( !parent.isValid() ) return true; if( isGroup( parent ) ) return !m_groupHash.value( parent.row() ).isEmpty(); return sourceModel()->hasChildren( mapToSource( parent ) ); } void QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end ) { if( parent != m_rootIndex ) { //an item will be added to an original index, remap and pass it on QModelIndex proxyParent = mapFromSource( parent ); beginInsertRows( proxyParent, start, end ); } } void QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end ) { if( parent == m_rootIndex ) { //top level of the model changed, these new rows need to be put in groups for( int modelRow = start; modelRow <= end ; modelRow++ ) { addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootIndex ) ); } } else { //beginInsertRows had to be called in modelRowsAboutToBeInserted() endInsertRows(); } } void QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ) { if( parent == m_rootIndex ) { QHash >::const_iterator i; //HACK, we are going to call beginRemoveRows() multiple times without // endRemoveRows() if a source index is in multiple groups. // This can be a problem for some views/proxies, but Q*Views can handle it. // TODO: investigate a queue for applying proxy model changes in the correct order for( i = m_groupHash.constBegin(); i != m_groupHash.constEnd(); ++i ) { int groupIndex = i.key(); const QList &groupList = i.value(); QModelIndex proxyParent = index( groupIndex, 0 ); foreach( int originalRow, groupList ) { if( originalRow >= start && originalRow <= end ) { int proxyRow = groupList.indexOf( originalRow ); if( groupIndex == -1 ) //adjust for non-grouped (root level) original items proxyRow += m_groupMaps.count(); //TODO: optimize for continues original rows in the same group beginRemoveRows( proxyParent, proxyRow, proxyRow ); } } } } else { //child item(s) of an original item will be removed, remap and pass it on // qDebug() << parent; QModelIndex proxyParent = mapFromSource( parent ); // qDebug() << proxyParent; beginRemoveRows( proxyParent, start, end ); } } void QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end ) { if( parent == m_rootIndex ) { //TODO: can be optimised by iterating over m_groupHash and checking start <= r < end //rather than increasing i we change the stored sourceRows in-place and reuse argument start //X-times (where X = end - start). for( int i = start; i <= end; i++ ) { //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows() // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved() //NOTE: easier to do reverse with java style iterator QMutableHashIterator > it( m_groupHash ); it.toBack(); while( it.hasPrevious() ) { it.previous(); - int groupIndex = it.key(); //has to be a modifiable reference for remove and replace operations QList &groupList = it.value(); int rowIndex = groupList.indexOf( start ); if( rowIndex != -1 ) { - QModelIndex proxyParent = index( groupIndex, 0 ); groupList.removeAt( rowIndex ); } //Now decrement all source rows that are after the removed row for( int j = 0; j < groupList.count(); j++ ) { int sourceRow = groupList.at( j ); if( sourceRow > start ) groupList.replace( j, sourceRow-1 ); } if( rowIndex != -1) endRemoveRows(); //end remove operation only after group was updated. } } return; } //beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); endRemoveRows(); } void QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight ) { //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly QModelIndex proxyTopLeft = mapFromSource( topLeft ); if( !proxyTopLeft.isValid() ) return; if( topLeft == bottomRight ) { emit dataChanged( proxyTopLeft, proxyTopLeft ); } else { QModelIndex proxyBottomRight = mapFromSource( bottomRight ); emit dataChanged( proxyTopLeft, proxyBottomRight ); } } bool QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const { foreach( const QModelIndex &index, list ) { if( isGroup( index ) ) return true; } return false; } void QtGroupingProxy::dumpGroups() const { qDebug() << "m_groupHash: "; for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ ) { qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex ); } qDebug() << "m_groupMaps: "; for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ); qDebug() << m_groupHash.value( -1 ); } diff --git a/src/browsers/playlistbrowser/QtGroupingProxy.h b/src/browsers/playlistbrowser/QtGroupingProxy.h index bccb782fb1..04504f6fb8 100644 --- a/src/browsers/playlistbrowser/QtGroupingProxy.h +++ b/src/browsers/playlistbrowser/QtGroupingProxy.h @@ -1,132 +1,132 @@ /**************************************************************************************** * Copyright (c) 2007-2010 Bart Cerneels * * * * 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, see . * ****************************************************************************************/ #ifndef GROUPINGPROXY_H #define GROUPINGPROXY_H #include #include #include #include #include typedef QMap ItemData; typedef QMap RowData; class QtGroupingProxy : public QAbstractProxyModel { Q_OBJECT public: explicit QtGroupingProxy( QObject *parent = 0 ); QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootIndex = QModelIndex(), int groupedColumn = -1, QObject *parent = 0 ); ~QtGroupingProxy(); /* QtGroupingProxy methods */ void setRootIndex( const QModelIndex &rootIndex ); void setGroupedColumn( int groupedColumn ); virtual QModelIndex addEmptyGroup( const RowData &data ); virtual bool removeGroup( const QModelIndex &idx ); /* QAbstractProxyModel methods */ //re-implemented to connect to source signals virtual void setSourceModel( QAbstractItemModel *sourceModel ); virtual QModelIndex index( int row, int column = 0, const QModelIndex& parent = QModelIndex() ) const; virtual Qt::ItemFlags flags( const QModelIndex &idx ) const; virtual QModelIndex buddy( const QModelIndex &index ) const; virtual QModelIndex parent( const QModelIndex &idx ) const; virtual int rowCount( const QModelIndex &idx = QModelIndex() ) const; virtual int columnCount( const QModelIndex &idx ) const; virtual QModelIndex mapToSource( const QModelIndex &idx ) const; virtual QModelIndexList mapToSource( const QModelIndexList &list ) const; virtual QModelIndex mapFromSource( const QModelIndex &idx ) const; virtual QVariant data( const QModelIndex &idx, int role ) const; virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ); virtual QVariant headerData ( int section, Qt::Orientation orientation, int role ) const; virtual bool canFetchMore( const QModelIndex &parent ) const; virtual void fetchMore( const QModelIndex &parent ); virtual bool hasChildren( const QModelIndex &parent = QModelIndex() ) const; Q_SIGNALS: void renameIndex( const QModelIndex &idx ); protected Q_SLOTS: virtual void buildTree(); private Q_SLOTS: void modelDataChanged( const QModelIndex &, const QModelIndex & ); void modelRowsInserted( const QModelIndex &, int, int ); void modelRowsAboutToBeInserted( const QModelIndex &, int ,int ); void modelRowsRemoved( const QModelIndex &, int, int ); void modelRowsAboutToBeRemoved( const QModelIndex &, int ,int ); protected: /** Maps an item to a group. * The return value is a list because an item can put in multiple groups. * Inside the list is a 2 dimensional map. * Mapped to column-number is another map of role-number to QVariant. * This data prepolulates the group-data cache. The rest is gathered on demand * from the children of the group. */ virtual QList belongsTo( const QModelIndex &idx ); /** * calls belongsTo(), checks cached data and adds the index to existing or new groups. * @returns the groups this index was added to where -1 means it was added to the root. */ QList addSourceRow( const QModelIndex &idx ); bool isGroup( const QModelIndex &index ) const; bool isAGroupSelected( const QModelIndexList &list ) const; /** Maintains the group -> sourcemodel row mapping * The reason a QList is use instead of a QMultiHash is that the values have to be * reordered when rows are inserted or removed. * TODO:use some auto-incrementing container class (steveire's?) for the list */ QHash > m_groupHash; /** The data cache of the groups. * This can be pre-loaded with data in belongsTo() */ QList m_groupMaps; /** "instuctions" how to create an item in the tree. * This is used by parent( QModelIndex ) */ struct ParentCreate { - int parentCreateIndex; + quintptr parentCreateIndex; int row; }; mutable QList m_parentCreateList; /** @returns index of the "instructions" to recreate the parent. Will create new if it doesn't exist yet. */ int indexOfParentCreate( const QModelIndex &parent ) const; QModelIndexList m_selectedGroups; QModelIndex m_rootIndex; int m_groupedColumn; /* debug function */ void dumpGroups() const; }; #endif //GROUPINGPROXY_H diff --git a/src/scripting/scriptconsole/CompletionModel.cpp b/src/scripting/scriptconsole/CompletionModel.cpp index 82d3c471eb..2ddb6f45ad 100644 --- a/src/scripting/scriptconsole/CompletionModel.cpp +++ b/src/scripting/scriptconsole/CompletionModel.cpp @@ -1,120 +1,120 @@ /**************************************************************************************** * Copyright (c) 2013 Anmol Ahuja * * * * 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, see . * ****************************************************************************************/ #include "CompletionModel.h" #include "core/support/Debug.h" #include #include #include #include using namespace ScriptConsoleNS; AmarokScriptCodeCompletionModel::AmarokScriptCodeCompletionModel( QObject *parent ) : CodeCompletionModel( parent ) { const QUrl url( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/scriptconsole/" ) ); QFile file( url.path() + "AutoComplete.txt" ); if( file.open( QFile::ReadOnly ) ) { QTextStream in( &file ); while ( !in.atEnd() ) m_autoCompleteStrings << in.readLine(); } else debug() << "No autocomplete file found for the script console"; } void AmarokScriptCodeCompletionModel::completionInvoked( KTextEditor::View *view, const KTextEditor::Range &range, KTextEditor::CodeCompletionModel::InvocationType invocationType ) { Q_UNUSED( invocationType ) beginResetModel(); m_completionList.clear(); const QString ¤tText = view->document()->text( range ); foreach( const QString &completionItem, m_autoCompleteStrings ) { int index = completionItem.indexOf( currentText, Qt::CaseInsensitive ) + currentText.length(); if( index != -1 && !QStringRef( &completionItem, index, completionItem.size()-index ).contains( '.' ) && completionItem != currentText ) m_completionList << completionItem; } setRowCount( m_completionList.count() ); endResetModel(); } QVariant AmarokScriptCodeCompletionModel::data( const QModelIndex &index, int role ) const { if( !index.isValid() || role != Qt::DisplayRole || index.row() < 0 || index.row() >= rowCount() || index.column() != KTextEditor::CodeCompletionModel::Name ) return QVariant(); return m_completionList[ index.row() ]; } KTextEditor::Range AmarokScriptCodeCompletionModel::completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) { const QString& line = view->document()->line(position.line()); KTextEditor::Range range(position, position); // include everything non-space before for( int i = position.column() - 1; i >= 0; --i ) { if( line.at( i ).isSpace() ) break; else range.start().setColumn( i ); } // include everything non-space after for( int i = position.column() + 1; i < line.length(); ++i ) { if( line.at( i ).isSpace() ) break; else range.end().setColumn( i ); } return range; } bool AmarokScriptCodeCompletionModel::shouldAbortCompletion( KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion ) { if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) return true; //Always abort when the completion-range has been left for( int i = 0; i < currentCompletion.length(); ++i ) { if( currentCompletion.at( i ).isSpace() ) return true; } // else it's valid return false; } void -AmarokScriptCodeCompletionModel::executeCompletionItem( KTextEditor::Document *document, const KTextEditor::Range &range, int row ) const +AmarokScriptCodeCompletionModel::executeCompletionItem( KTextEditor::View *view, const KTextEditor::Range &range, const QModelIndex &index ) const { - document->replaceText( range, m_completionList.at( row ) ); + view->document()->replaceText( range, m_completionList.at( index.row() ) ); } AmarokScriptCodeCompletionModel::~AmarokScriptCodeCompletionModel() { DEBUG_BLOCK m_completionList.clear(); } diff --git a/src/scripting/scriptconsole/CompletionModel.h b/src/scripting/scriptconsole/CompletionModel.h index 6e8a63c83f..4ef44b26fb 100644 --- a/src/scripting/scriptconsole/CompletionModel.h +++ b/src/scripting/scriptconsole/CompletionModel.h @@ -1,49 +1,49 @@ /**************************************************************************************** * Copyright (c) 2013 Anmol Ahuja * * * * 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, see . * ****************************************************************************************/ #ifndef SCRIPTCONSOLE_COMPLETIONMODEL_H #define SCRIPTCONSOLE_COMPLETIONMODEL_H #include #include namespace KTextEditor { class View; class Document; } namespace ScriptConsoleNS { class AmarokScriptCodeCompletionModel : public KTextEditor::CodeCompletionModelControllerInterface , public KTextEditor::CodeCompletionModel { public: AmarokScriptCodeCompletionModel( QObject *parent ); virtual ~AmarokScriptCodeCompletionModel(); private: QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const; void completionInvoked( KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType ); - void executeCompletionItem( KTextEditor::Document *document, const KTextEditor::Range &range, int row ) const; + void executeCompletionItem( KTextEditor::View *view, const KTextEditor::Range &range, const QModelIndex &index ) const; KTextEditor::Range completionRange( KTextEditor::View *view, const KTextEditor::Cursor &position ); bool shouldAbortCompletion( KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion ); QStringList m_completionList; QStringList m_autoCompleteStrings; }; } #endif // SCRIPTCONSOLE_COMPLETIONMODEL_H diff --git a/src/services/magnatune/MagnatuneXmlParser.cpp b/src/services/magnatune/MagnatuneXmlParser.cpp index 2b3a956b10..1373dfb4a5 100644 --- a/src/services/magnatune/MagnatuneXmlParser.cpp +++ b/src/services/magnatune/MagnatuneXmlParser.cpp @@ -1,433 +1,432 @@ /**************************************************************************************** * Copyright (c) 2006,2007 Nikolaj Hald Nielsen * * * * 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, see . * ****************************************************************************************/ #include "MagnatuneXmlParser.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include "core/support/Components.h" #include "core/interfaces/Logger.h" -#include +#include #include #include #include using namespace Meta; MagnatuneXmlParser::MagnatuneXmlParser( const QString &filename ) : QObject() , ThreadWeaver::Job() { m_sFileName = filename; connect( this, &MagnatuneXmlParser::done, this, &MagnatuneXmlParser::completeJob ); } MagnatuneXmlParser::~MagnatuneXmlParser() { QFile(m_sFileName).remove(); qDeleteAll(m_currentAlbumTracksList); } void MagnatuneXmlParser::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) { Q_UNUSED(self); Q_UNUSED(thread); readConfigFile( m_sFileName ); } void MagnatuneXmlParser::defaultBegin(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { Q_EMIT started(self); ThreadWeaver::Job::defaultBegin(self, thread); } void MagnatuneXmlParser::defaultEnd(const ThreadWeaver::JobPointer& self, ThreadWeaver::Thread *thread) { ThreadWeaver::Job::defaultEnd(self, thread); if (!self->success()) { Q_EMIT failed(self); } Q_EMIT done(self); } void MagnatuneXmlParser::completeJob( ) { Amarok::Components::logger()->longMessage( i18ncp( "First part of: Magnatune.com database update complete. Database contains 3 tracks on 4 albums from 5 artists.", "Magnatune.com database update complete. Database contains 1 track on ", "Magnatune.com database update complete. Database contains %1 tracks on ", m_nNumberOfTracks) + i18ncp( "Middle part of: Magnatune.com database update complete. Database contains 3 tracks on 4 albums from 5 artists.", "1 album from ", "%1 albums from ", m_nNumberOfAlbums) + i18ncp( "Last part of: Magnatune.com database update complete. Database contains 3 tracks on 4 albums from 5 artists.", "1 artist.", "%1 artists.", m_nNumberOfArtists ) , Amarok::Logger::Information ); emit doneParsing(); deleteLater(); } void MagnatuneXmlParser::readConfigFile( const QString &filename ) { DEBUG_BLOCK m_nNumberOfTracks = 0; m_nNumberOfAlbums = 0; m_nNumberOfArtists = 0; QDomDocument doc( "config" ); if ( !QFile::exists( filename ) ) { debug() << "Magnatune xml file does not exist"; return; } - QIODevice *file = KFilterDev::deviceForFile( filename, "application/x-bzip2", true ); - if ( !file || !file->open( QIODevice::ReadOnly ) ) { + QFile file( filename ); + auto device = new KCompressionDevice( &file, true, KCompressionDevice::BZip2 ); + if ( !device || !device->open( QIODevice::ReadOnly ) ) { debug() << "MagnatuneXmlParser::readConfigFile error reading file"; return ; } - if ( !doc.setContent( file ) ) + if ( !doc.setContent( device ) ) { debug() << "MagnatuneXmlParser::readConfigFile error parsing file"; - file->close(); + device->close(); return ; } - file->close(); - delete file; - + device->close(); + delete device; m_dbHandler->destroyDatabase(); m_dbHandler->createDatabase(); //run through all the elements QDomElement docElem = doc.documentElement(); - m_dbHandler->begin(); //start transaction (MAJOR speedup!!) parseElement( docElem ); m_dbHandler->commit(); //complete transaction return ; } void MagnatuneXmlParser::parseElement( const QDomElement &e ) { QString sElementName = e.tagName(); sElementName == "Album" ? parseAlbum( e ) : parseChildren( e ); } void MagnatuneXmlParser::parseChildren( const QDomElement &e ) { QDomNode n = e.firstChild(); while ( !n.isNull() ) { if ( n.isElement() ) parseElement( n.toElement() ); n = n.nextSibling(); } } void MagnatuneXmlParser::parseAlbum( const QDomElement &e ) { //DEBUG_BLOCK QString sElementName; QString name; QString albumCode; QStringList magnatuneGenres; int launchYear = 0; QString coverUrl; QString description; QString artistName; QString artistDescription; QUrl artistPhotoUrl; QString mp3Genre; QUrl artistPageUrl; QDomNode n = e.firstChild(); QDomElement childElement; while ( !n.isNull() ) { if ( n.isElement() ) { childElement = n.toElement(); QString sElementName = childElement.tagName(); if ( sElementName == "albumname" ) //printf(("|--+" + childElement.text() + "\n").toLatin1()); //m_currentAlbumItem = new MagnatuneListViewAlbumItem( m_currentArtistItem); name = childElement.text(); else if ( sElementName == "albumsku" ) albumCode = childElement.text(); else if ( sElementName == "magnatunegenres" ) magnatuneGenres = childElement.text().split(',', QString::SkipEmptyParts); else if ( sElementName == "launchdate" ) { QString dateString = childElement.text(); QDate date = QDate::fromString( dateString, Qt::ISODate ); launchYear = date.year(); } else if ( sElementName == "cover_small" ) coverUrl = childElement.text(); else if ( sElementName == "artist" ) artistName = childElement.text(); else if ( sElementName == "artistdesc" ) artistDescription = childElement.text(); else if ( sElementName == "artistphoto" ) artistPhotoUrl = QUrl( childElement.text() ); else if ( sElementName == "mp3genre" ) mp3Genre = childElement.text(); else if ( sElementName == "home" ) artistPageUrl = QUrl( childElement.text() ); else if ( sElementName == "Track" ) parseTrack( childElement ); else if ( sElementName == "album_notes" ) description = childElement.text(); } n = n.nextSibling(); } m_pCurrentAlbum.reset(new MagnatuneAlbum( name )); m_pCurrentAlbum->setAlbumCode( albumCode); m_pCurrentAlbum->setLaunchYear( launchYear ); m_pCurrentAlbum->setCoverUrl( coverUrl ); m_pCurrentAlbum->setDescription( description ); // now we should have gathered all info about current album (and artist)... //Time to add stuff to the database //check if artist already exists, if not, create him/her/them/it int artistId; if ( artistNameIdMap.contains( artistName ) ) { artistId = artistNameIdMap.value( artistName ); } else { //does not exist, lets create it... m_pCurrentArtist.reset(new MagnatuneArtist( artistName )); m_pCurrentArtist->setDescription( artistDescription ); m_pCurrentArtist->setPhotoUrl( artistPhotoUrl ); m_pCurrentArtist->setMagnatuneUrl( artistPageUrl ); //this is tricky in postgresql, returns id as 0 (we are within a transaction, might be the cause...) artistId = m_dbHandler->insertArtist( m_pCurrentArtist.data() ); m_nNumberOfArtists++; if ( artistId == 0 ) { artistId = m_dbHandler->getArtistIdByExactName( m_pCurrentArtist->name() ); } m_pCurrentArtist->setId( artistId ); artistNameIdMap.insert( m_pCurrentArtist->name() , artistId ); } m_pCurrentAlbum->setArtistId( artistId ); int albumId = m_dbHandler->insertAlbum( m_pCurrentAlbum.data() ); if ( albumId == 0 ) // again, postgres can play tricks on us... { albumId = m_dbHandler->getAlbumIdByAlbumCode( m_pCurrentAlbum->albumCode() ); } m_pCurrentAlbum->setId( albumId ); m_nNumberOfAlbums++; QList::iterator it; for ( it = m_currentAlbumTracksList.begin(); it != m_currentAlbumTracksList.end(); ++it ) { ( *it )->setAlbumId( m_pCurrentAlbum->id() ); ( *it )->setArtistId( artistId ); int trackId = m_dbHandler->insertTrack( ( *it ) ); m_dbHandler->insertMoods( trackId, ( *it )->moods() ); m_nNumberOfTracks++; } // handle genres foreach( const QString &genreName, magnatuneGenres ) { //debug() << "inserting genre with album_id = " << albumId << " and name = " << genreName; ServiceGenre currentGenre( genreName ); currentGenre.setAlbumId( albumId ); m_dbHandler->insertGenre( ¤tGenre ); } magnatuneGenres.clear(); qDeleteAll(m_currentAlbumTracksList); m_currentAlbumTracksList.clear(); } void MagnatuneXmlParser::parseTrack( const QDomElement &e ) { //DEBUG_BLOCK m_currentTrackMoodList.clear(); QString trackName; QString trackNumber; QString streamingUrl; QString sElementName; QDomElement childElement; MagnatuneTrack * pCurrentTrack = new MagnatuneTrack( QString() ); QDomNode n = e.firstChild(); while ( !n.isNull() ) { if ( n.isElement() ) { childElement = n.toElement(); QString sElementName = childElement.tagName(); if ( sElementName == "trackname" ) { pCurrentTrack->setTitle( childElement.text() ); } else if ( sElementName == "url" ) { pCurrentTrack->setUidUrl( childElement.text() ); } else if ( sElementName == "oggurl" ) { pCurrentTrack->setOggUrl( childElement.text() ); } else if ( sElementName == "mp3lofi" ) { pCurrentTrack->setLofiUrl( childElement.text() ); } else if ( sElementName == "tracknum" ) { pCurrentTrack->setTrackNumber( childElement.text().toInt() ); } else if ( sElementName == "seconds" ) { pCurrentTrack->setLength( childElement.text().toInt() ); } else if ( sElementName == "moods" ) { parseMoods( childElement ); } } n = n.nextSibling(); } pCurrentTrack->setMoods( m_currentTrackMoodList ); m_currentAlbumTracksList.append( pCurrentTrack ); } void MagnatuneXmlParser::parseMoods( const QDomElement &e ) { //DEBUG_BLOCK QDomNode n = e.firstChild(); QDomElement childElement; while ( !n.isNull() ) { if ( n.isElement() ) { childElement = n.toElement(); QString sElementName = childElement.tagName(); if ( sElementName == "mood" ) { m_currentTrackMoodList.append( childElement.text() ); } else { //error, should not be here.... } } n = n.nextSibling(); } } void MagnatuneXmlParser::setDbHandler(MagnatuneDatabaseHandler * dbHandler) { m_dbHandler = dbHandler; } diff --git a/src/statsyncing/models/CommonModel.cpp b/src/statsyncing/models/CommonModel.cpp index 8291e35115..8126ce859b 100644 --- a/src/statsyncing/models/CommonModel.cpp +++ b/src/statsyncing/models/CommonModel.cpp @@ -1,215 +1,215 @@ /**************************************************************************************** * Copyright (c) 2012 Matěj Laitl * * * * 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, see . * ****************************************************************************************/ #include "CommonModel.h" #include "MetaValues.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Debug.h" #include "statsyncing/Options.h" #include #include #include #include using namespace StatSyncing; const QSize CommonModel::s_ratingSize( 5*16, 16 ); CommonModel::CommonModel( const QList &columns, const Options &options ) : m_columns( columns ) , m_options( options ) { Q_ASSERT( m_columns.value( 0 ) == Meta::valTitle ); } QVariant CommonModel::headerData( int section, Qt::Orientation orientation, int role ) const { if( orientation != Qt::Horizontal || section < 0 || section >= m_columns.count() ) return QVariant(); qint64 field = m_columns.at( section ); switch( role ) { case Qt::DisplayRole: return Meta::i18nForField( field ); case Qt::SizeHintRole: return sizeHintData( field ); case ResizeModeRole: switch( field ) { case Meta::valTitle: return QHeaderView::Stretch; case Meta::valRating: case Meta::valFirstPlayed: case Meta::valLastPlayed: case Meta::valPlaycount: return QHeaderView::ResizeToContents; default: return QHeaderView::Interactive; } case FieldRole: return field; } return QVariant(); } QVariant CommonModel::sizeHintData( qint64 field ) const { switch( field ) { case Meta::valRating: { static QSize size; if( size.isValid() ) // optimization return size; - QStyleOptionViewItemV4 opt; - opt.features = QStyleOptionViewItemV2::HasDisplay - | QStyleOptionViewItemV2::HasCheckIndicator - | QStyleOptionViewItemV2::HasDecoration; + QStyleOptionViewItem opt; + opt.features = QStyleOptionViewItem::HasDisplay + | QStyleOptionViewItem::HasCheckIndicator + | QStyleOptionViewItem::HasDecoration; opt.state = QStyle::State_Enabled; opt.decorationSize = s_ratingSize; const QWidget *widget = opt.widget; QStyle *style = widget ? widget->style() : QApplication::style(); size = style->sizeFromContents( QStyle::CT_ItemViewItem, &opt, QSize(), widget ); return size; } case Meta::valFirstPlayed: case Meta::valLastPlayed: { static QSize size; if( size.isValid() ) // optimization return size; - QStyleOptionViewItemV4 opt; - opt.features = QStyleOptionViewItemV2::HasDisplay; + QStyleOptionViewItem opt; + opt.features = QStyleOptionViewItem::HasDisplay; opt.state = QStyle::State_Enabled; opt.text = "88.88.8888 88:88"; QStyle *style = QApplication::style(); size = style->sizeFromContents( QStyle::CT_ItemViewItem, &opt, QSize(), 0 ); return size; } case Meta::valPlaycount: { static QSize size; if( size.isValid() ) // optimization return size; - QStyleOptionViewItemV4 opt; - opt.features = QStyleOptionViewItemV2::HasDisplay; + QStyleOptionViewItem opt; + opt.features = QStyleOptionViewItem::HasDisplay; opt.state = QStyle::State_Enabled; opt.text = "888 (88)"; opt.font.setBold( true ); QStyle *style = QApplication::style(); size = style->sizeFromContents( QStyle::CT_ItemViewItem, &opt, QSize(), 0 ); return size; } } return QVariant(); } QVariant CommonModel::textAlignmentData( qint64 field ) const { switch( field ) { case Meta::valRating: case Meta::valFirstPlayed: case Meta::valLastPlayed: case Meta::valPlaycount: return Qt::AlignRight; } return QVariant(); } QVariant CommonModel::trackData( const TrackPtr &track, qint64 field, int role ) const { switch( role ) { case Qt::DisplayRole: switch( field ) { case Meta::valTitle: return trackTitleData( track ); case Meta::valRating: return track->rating(); case Meta::valFirstPlayed: return track->firstPlayed(); case Meta::valLastPlayed: return track->lastPlayed(); case Meta::valPlaycount: { int recent = track->recentPlayCount(); return recent ? QVariant( i18nc( "%1 is play count and %2 is recent play count", "%1 (%2)", track->playCount(), recent ) ) : QVariant( track->playCount() ); } case Meta::valLabel: return QStringList( ( track->labels() - m_options.excludedLabels() ).toList() ).join( i18nc( "comma between list words", ", " ) ); default: return QString( "Unknown field!" ); } break; case Qt::ToolTipRole: switch( field ) { case Meta::valTitle: return trackToolTipData( track ); case Meta::valPlaycount: return i18np( "Played %2 times of which one play is recent and unique " "to this source", "Played %2 times of which %1 plays are recent " "and unique to this source", track->recentPlayCount(), track->playCount() ); case Meta::valLabel: { QSet labels = track->labels() - m_options.excludedLabels(); QSet excludedLabels = track->labels() & m_options.excludedLabels(); QStringList texts; if( !labels.isEmpty() ) texts << i18n( "Labels: %1", QStringList( labels.toList() ).join( i18nc( "comma between list words", ", " ) ) ); if( !excludedLabels.isEmpty() ) texts << i18n( "Ignored labels: %1", QStringList( excludedLabels.toList() ).join( i18nc( "comma between list words", ", " ) ) ); return texts.isEmpty() ? QVariant() : texts.join( "\n" ); } } break; case Qt::TextAlignmentRole: return textAlignmentData( field ); case Qt::SizeHintRole: return sizeHintData( field ); case FieldRole: return field; } return QVariant(); } QVariant CommonModel::trackTitleData( const TrackPtr &track ) const { return i18n( "%1 - %2 - %3", track->artist(), track->album(), track->name() ); } QVariant CommonModel::trackToolTipData( const TrackPtr &track ) const { return trackTitleData( track ); // TODO nicer toolTip, display more fields } diff --git a/src/statsyncing/models/MatchedTracksModel.cpp b/src/statsyncing/models/MatchedTracksModel.cpp index e23fd37939..703b0c626c 100644 --- a/src/statsyncing/models/MatchedTracksModel.cpp +++ b/src/statsyncing/models/MatchedTracksModel.cpp @@ -1,409 +1,409 @@ /**************************************************************************************** * Copyright (c) 2012 Matěj Laitl * * * * 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, see . * ****************************************************************************************/ #include "MatchedTracksModel.h" #include "MetaValues.h" #include "core/meta/support/MetaConstants.h" #include "core/support/Debug.h" #include "statsyncing/TrackTuple.h" #include #include using namespace StatSyncing; -static const int tupleIndexIndernalId = -1; +static const quintptr tupleIndexIndernalId = 0; MatchedTracksModel::MatchedTracksModel( const QList &matchedTuples, const QList &columns, const Options &options, QObject *parent ) : QAbstractItemModel( parent ) , CommonModel( columns, options ) , m_matchedTuples( matchedTuples ) { m_titleColumn = m_columns.indexOf( Meta::valTitle ); } QModelIndex MatchedTracksModel::index( int row, int column, const QModelIndex &parent ) const { if( !parent.isValid() && column >= 0 && column < m_columns.count() ) return createIndex( row, column, tupleIndexIndernalId ); if( parent.internalId() == tupleIndexIndernalId && parent.row() >= 0 && parent.row() < m_matchedTuples.count() && parent.column() == m_titleColumn && row >= 0 && row < m_matchedTuples.at( parent.row() ).count() && column >=0 && column < m_columns.count() ) { return createIndex( row, column, parent.row() ); } return QModelIndex(); } QModelIndex MatchedTracksModel::parent( const QModelIndex &child ) const { if( !child.isValid() || child.internalId() == tupleIndexIndernalId ) return QModelIndex(); return createIndex( child.internalId(), m_titleColumn, tupleIndexIndernalId ); } bool MatchedTracksModel::hasChildren( const QModelIndex &parent ) const { if( !parent.isValid() ) return !m_matchedTuples.isEmpty(); if( parent.internalId() == tupleIndexIndernalId && parent.row() >= 0 && parent.row() < m_matchedTuples.count() && parent.column() == m_titleColumn ) { return true; // we expect only nonempty tuples } return false; // leaf node } int MatchedTracksModel::rowCount( const QModelIndex &parent ) const { if( !parent.isValid() ) return m_matchedTuples.count(); if( parent.internalId() == tupleIndexIndernalId && parent.column() == m_titleColumn ) return m_matchedTuples.value( parent.row() ).count(); // handles invalid row numbers gracefully return 0; // parent is leaf node } int MatchedTracksModel::columnCount( const QModelIndex &parent ) const { if( !parent.isValid() || ( parent.internalId() == tupleIndexIndernalId && parent.column() == m_titleColumn ) ) { return m_columns.count(); } return 0; // parent is leaf node } QVariant MatchedTracksModel::headerData( int section, Qt::Orientation orientation, int role ) const { return CommonModel::headerData( section, orientation, role ); } QVariant MatchedTracksModel::data( const QModelIndex &index, int role ) const { if( !index.isValid() || index.column() < 0 || index.column() >= m_columns.count() ) return QVariant(); qint64 field = m_columns.at( index.column() ); if( index.internalId() == tupleIndexIndernalId ) { TrackTuple tuple = m_matchedTuples.value( index.row() ); if( tuple.isEmpty() ) return QVariant(); return tupleData( tuple, field, role ); } - else if( index.internalId() < m_matchedTuples.count() ) + else if( index.internalId() < (quintptr)m_matchedTuples.count() ) { TrackTuple tuple = m_matchedTuples.value( index.internalId() ); ProviderPtr provider = tuple.provider( index.row() ); if( !provider ) return QVariant(); return trackData( provider, tuple, field, role ); } return QVariant(); } bool MatchedTracksModel::setData( const QModelIndex &idx, const QVariant &value, int role ) { if( !idx.isValid() || - idx.internalId() >= m_matchedTuples.count() || + idx.internalId() >= (quintptr)m_matchedTuples.count() || role != Qt::CheckStateRole ) { return false; } qint64 field = m_columns.value( idx.column() ); TrackTuple &tuple = m_matchedTuples[ idx.internalId() ]; // we need reference ProviderPtr provider = tuple.provider( idx.row() ); if( !provider ) return false; switch( field ) { case Meta::valRating: switch( Qt::CheckState( value.toInt() ) ) { case Qt::Checked: tuple.setRatingProvider( provider ); break; case Qt::Unchecked: tuple.setRatingProvider( ProviderPtr() ); break; default: return false; } break; case Meta::valLabel: { ProviderPtrSet labelProviders = tuple.labelProviders(); switch( Qt::CheckState( value.toInt() ) ) { case Qt::Checked: labelProviders.insert( provider ); tuple.setLabelProviders( labelProviders ); break; case Qt::Unchecked: labelProviders.remove( provider ); tuple.setLabelProviders( labelProviders ); break; default: return false; } break; } default: return false; } // parent changes: QModelIndex parent = idx.parent(); QModelIndex parentRating = index( parent.row(), idx.column(), parent.parent() ); emit dataChanged( parentRating, parentRating ); // children change: QModelIndex topLeft = index( 0, idx.column(), parent ); QModelIndex bottomRight = index( tuple.count() - 1, idx.column(), parent ); emit dataChanged( topLeft, bottomRight ); return true; } Qt::ItemFlags MatchedTracksModel::flags( const QModelIndex &index ) const { // many false positives here, but no-one is hurt return QAbstractItemModel::flags( index ) | Qt::ItemIsUserCheckable; } const QList & MatchedTracksModel::matchedTuples() { return m_matchedTuples; } bool MatchedTracksModel::hasUpdate() const { foreach( const TrackTuple &tuple, m_matchedTuples ) { if( tuple.hasUpdate( m_options ) ) return true; } return false; } bool MatchedTracksModel::hasConflict( int i ) const { if( i >= 0 ) return m_matchedTuples.value( i ).hasConflict( m_options ); foreach( const TrackTuple &tuple, m_matchedTuples ) { if( tuple.hasConflict( m_options ) ) return true; } return false; } void MatchedTracksModel::takeRatingsFrom( const ProviderPtr &provider ) { for( int i = 0; i < m_matchedTuples.count(); i++ ) { TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference if( !tuple.fieldHasConflict( Meta::valRating, m_options ) ) continue; if( tuple.ratingProvider() == provider ) continue; // short-cut tuple.setRatingProvider( provider ); // does nothing if non-null provider isn't in tuple // parent changes: int ratingColumn = m_columns.indexOf( Meta::valRating ); QModelIndex parentRating = index( i, ratingColumn ); emit dataChanged( parentRating, parentRating ); // children change: QModelIndex parent = index( i, 0 ); QModelIndex topLeft = index( 0, ratingColumn, parent ); QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent ); emit dataChanged( topLeft, bottomRight ); } } void MatchedTracksModel::includeLabelsFrom( const ProviderPtr &provider ) { if( !provider ) return; // has no sense for( int i = 0; i < m_matchedTuples.count(); i++ ) { TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) ) continue; ProviderPtrSet providers = tuple.labelProviders(); providers.insert( provider ); if( providers == tuple.labelProviders() ) continue; // short-cut tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple // parent changes: int ratingColumn = m_columns.indexOf( Meta::valRating ); QModelIndex parentRating = index( i, ratingColumn ); emit dataChanged( parentRating, parentRating ); // children change: QModelIndex parent = index( i, 0 ); QModelIndex topLeft = index( 0, ratingColumn, parent ); QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent ); emit dataChanged( topLeft, bottomRight ); } } void MatchedTracksModel::excludeLabelsFrom( const ProviderPtr &provider ) { for( int i = 0; i < m_matchedTuples.count(); i++ ) { TrackTuple &tuple = m_matchedTuples[ i ]; // we need reference if( !tuple.fieldHasConflict( Meta::valLabel, m_options ) ) continue; ProviderPtrSet providers = tuple.labelProviders(); if( provider ) // normal more, remove one provider providers.remove( provider ); else // reset mode, clear providers providers.clear(); if( providers == tuple.labelProviders() ) continue; // short-cut tuple.setLabelProviders( providers ); // does nothing if provider isn't in tuple // parent changes: int ratingColumn = m_columns.indexOf( Meta::valRating ); QModelIndex parentRating = index( i, ratingColumn ); emit dataChanged( parentRating, parentRating ); // children change: QModelIndex parent = index( i, 0 ); QModelIndex topLeft = index( 0, ratingColumn, parent ); QModelIndex bottomRight = index( tuple.count() - 1, ratingColumn, parent ); emit dataChanged( topLeft, bottomRight ); } } QVariant MatchedTracksModel::tupleData( const TrackTuple &tuple, qint64 field, int role ) const { ProviderPtr firstProvider = tuple.provider( 0 ); TrackPtr first = tuple.track( firstProvider ); switch( role ) { case Qt::DisplayRole: switch( field ) { case Meta::valTitle: return trackTitleData( first ); case Meta::valRating: return tuple.syncedRating( m_options ); case Meta::valFirstPlayed: return tuple.syncedFirstPlayed( m_options ); case Meta::valLastPlayed: return tuple.syncedLastPlayed( m_options ); case Meta::valPlaycount: return tuple.syncedPlaycount( m_options ); case Meta::valLabel: if( tuple.fieldHasConflict( field, m_options, /* includeResolved */ false ) ) return -1; // display same icon as for rating conflict return QStringList( tuple.syncedLabels( m_options ).toList() ).join( i18nc( "comma between list words", ", " ) ); default: return QString( "Unknown field!" ); } break; case Qt::ToolTipRole: switch( field ) { case Meta::valTitle: return trackToolTipData( first ); // TODO way to specify which additional meta-data to display case Meta::valLabel: return QStringList( tuple.syncedLabels( m_options ).toList() ).join( i18nc( "comma between list words", ", " ) ); } break; case Qt::BackgroundRole: if( tuple.fieldUpdated( field, m_options ) ) return KColorScheme( QPalette::Active ).background( KColorScheme::PositiveBackground ); break; case Qt::TextAlignmentRole: return textAlignmentData( field ); case Qt::SizeHintRole: return sizeHintData( field ); case CommonModel::FieldRole: return field; case TupleFlagsRole: int flags = tuple.hasUpdate( m_options ) ? HasUpdate : 0; flags |= tuple.hasConflict( m_options ) ? HasConflict : 0; return flags; } return QVariant(); } QVariant MatchedTracksModel::trackData( ProviderPtr provider, const TrackTuple &tuple, qint64 field, int role ) const { TrackPtr track = tuple.track( provider ); if( role == Qt::DisplayRole && field == Meta::valTitle ) return provider->prettyName(); else if( role == Qt::DecorationRole && field == Meta::valTitle ) return provider->icon(); // no special background if the field in whole tuple is not updated else if( role == Qt::BackgroundRole && tuple.fieldUpdated( field, m_options ) ) { KColorScheme::BackgroundRole backgroundRole = tuple.fieldUpdated( field, m_options, provider ) ? KColorScheme::NegativeBackground : KColorScheme::PositiveBackground; return KColorScheme( QPalette::Active ).background( backgroundRole ); } else if( role == Qt::CheckStateRole && tuple.fieldHasConflict( field, m_options ) ) { switch( field ) { case Meta::valRating: return ( tuple.ratingProvider() == provider ) ? Qt::Checked : Qt::Unchecked; case Meta::valLabel: return ( tuple.labelProviders().contains( provider ) ) ? Qt::Checked : Qt::Unchecked; default: warning() << __PRETTY_FUNCTION__ << "this should be never reached"; } } return trackData( track, field, role ); } diff --git a/src/statsyncing/ui/TrackDelegate.cpp b/src/statsyncing/ui/TrackDelegate.cpp index 3372722f98..ef392af1e9 100644 --- a/src/statsyncing/ui/TrackDelegate.cpp +++ b/src/statsyncing/ui/TrackDelegate.cpp @@ -1,95 +1,95 @@ /**************************************************************************************** * Copyright (c) 2012 Matěj Laitl * * * * 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, see . * ****************************************************************************************/ #include "TrackDelegate.h" #include "MetaValues.h" #include "core/support/Debug.h" #include "statsyncing/models/CommonModel.h" #include #include #include #include #include #include using namespace StatSyncing; TrackDelegate::TrackDelegate( QObject *parent ) : QStyledItemDelegate( parent ) { } void TrackDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const { qint64 field = index.data( CommonModel::FieldRole ).value(); QVariant data = index.data(); // display the icon even for label conflicts: if( ( field == Meta::valRating || field == Meta::valLabel ) && data.type() == QVariant::Int ) { // following is largely inspired by QStyledItemDelegate::paint() - QStyleOptionViewItemV4 opt = option; + QStyleOptionViewItem opt = option; initStyleOption( &opt, index ); QPixmap starsPixmap( CommonModel::s_ratingSize ); starsPixmap.fill( Qt::transparent ); { KRatingPainter ratingPainter; int rating = data.toInt(); int hoverRating = -1; if( rating < 0 ) // unresolved conflict { rating = 0; ratingPainter.setIcon( QIcon::fromTheme( "status_unknown" ) ); ratingPainter.setEnabled( false ); ratingPainter.setMaxRating( 2 ); } QPainter starsPainter( &starsPixmap ); ratingPainter.paint( &starsPainter, QRect( QPoint( 0, 0 ), CommonModel::s_ratingSize ), rating, hoverRating ); } opt.text.clear(); - opt.features |= QStyleOptionViewItemV2::HasDecoration; + opt.features |= QStyleOptionViewItem::HasDecoration; opt.decorationSize = CommonModel::s_ratingSize; opt.decorationAlignment = Qt::AlignRight | Qt::AlignVCenter; opt.decorationPosition = QStyleOptionViewItem::Right; opt.icon = QIcon( starsPixmap ); const QWidget *widget = opt.widget; QStyle *style = widget ? widget->style() : QApplication::style(); style->drawControl( QStyle::CE_ItemViewItem, &opt, painter, widget ); } else QStyledItemDelegate::paint( painter, option, index ); } QString TrackDelegate::displayText( const QVariant &value, const QLocale &locale ) const { if( value.type() == QVariant::DateTime ) { QDateTime date = value.toDateTime(); return date.isValid() ? QLocale().toString( date, QLocale::ShortFormat ) : QString(); } return QStyledItemDelegate::displayText( value, locale ); } diff --git a/src/widgets/BreadcrumbItemButton.cpp b/src/widgets/BreadcrumbItemButton.cpp index 5cb8580ddb..fe4ece2b77 100644 --- a/src/widgets/BreadcrumbItemButton.cpp +++ b/src/widgets/BreadcrumbItemButton.cpp @@ -1,329 +1,329 @@ /**************************************************************************************** * Copyright (c) 2006 Peter Penz * * Copyright (c) 2006 Aaron Seigo * * Copyright (c) 2009 Seb Ruiz * * * * 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, see . * ****************************************************************************************/ #include "BreadcrumbItemButton.h" #include "amarokurls/AmarokUrlAction.h" #include "amarokurls/AmarokUrlHandler.h" #include "core/support/Amarok.h" #include #include #include #include #include #include #include #include #include #include BreadcrumbItemButton::BreadcrumbItemButton( QWidget *parent ) : Amarok::ElidingButton( parent ) , m_displayHint( 0 ) { init(); } BreadcrumbItemButton::BreadcrumbItemButton( const QString &text, QWidget *parent ) : Amarok::ElidingButton( text, parent ) , m_displayHint( 0 ) { init(); } BreadcrumbItemButton::BreadcrumbItemButton( const QIcon &icon, const QString &text, QWidget *parent ) : Amarok::ElidingButton( icon, text, parent ) , m_displayHint( 0 ) { init(); } void BreadcrumbItemButton::init() { setFocusPolicy( Qt::NoFocus ); setDisplayHintEnabled( HoverHint, false ); } BreadcrumbItemButton::~BreadcrumbItemButton() { } void BreadcrumbItemButton::setActive( const bool active ) { setDisplayHintEnabled( ActiveHint, active ); QFont f = font(); f.setBold( active ); setFont( f ); } void BreadcrumbItemButton::setDisplayHintEnabled( DisplayHint hint, bool enable ) { if( enable ) m_displayHint = m_displayHint | hint; else m_displayHint = m_displayHint & ~hint; update(); } bool BreadcrumbItemButton::isDisplayHintEnabled( DisplayHint hint ) const { return (m_displayHint & hint) > 0; } void BreadcrumbItemButton::enterEvent( QEvent* event ) { QPushButton::enterEvent( event ); setDisplayHintEnabled( HoverHint, true ); update(); } void BreadcrumbItemButton::leaveEvent( QEvent* event ) { QPushButton::leaveEvent( event ); setDisplayHintEnabled( HoverHint, false ); update(); } void BreadcrumbItemButton::paintEvent( QPaintEvent* event ) { Q_UNUSED(event); QPainter painter(this); const int buttonHeight = height(); int buttonWidth = width(); int preferredWidth = sizeHint().width(); if (preferredWidth < minimumWidth()) { preferredWidth = minimumWidth(); } if (buttonWidth > preferredWidth) { buttonWidth = preferredWidth; } drawHoverBackground(&painter); int left, top, right, bottom; getContentsMargins ( &left, &top, &right, &bottom ); const int padding = 2; int xoffset; if( !icon().isNull() ) { const int iconWidth = iconSize().width(); const int iconHeight = iconSize().height(); const int iconTop = ( (buttonHeight - top - bottom) - iconHeight ) / 2; const QRect iconRect( left + padding, iconTop, iconWidth, iconHeight ); painter.drawPixmap( iconRect, icon().pixmap( iconSize() ) ); xoffset = left + (padding * 2) + iconWidth; } else xoffset = left + (padding * 2); const QRect textRect( xoffset, top, buttonWidth, buttonHeight); painter.drawText(textRect, Qt::AlignVCenter, text()); } void BreadcrumbItemButton::drawHoverBackground(QPainter* painter) { const bool isHovered = isDisplayHintEnabled( HoverHint ); if( isHovered ) { // QColor backgroundColor = palette().color(QPalette::Highlight); // TODO: the backgroundColor should be applied to the style - QStyleOptionViewItemV4 option; + QStyleOptionViewItem option; option.initFrom(this); option.state = QStyle::State_Enabled | QStyle::State_MouseOver; - option.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; + option.viewItemPosition = QStyleOptionViewItem::OnlyOne; style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter, this ); } } QColor BreadcrumbItemButton::foregroundColor() const { const bool isHighlighted = isDisplayHintEnabled( HoverHint ); const bool isActive = isDisplayHintEnabled( ActiveHint ); QColor foregroundColor = palette().color( foregroundRole() ); if( !isActive && !isHighlighted ) foregroundColor.setAlpha( 180 ); return foregroundColor; } QSize BreadcrumbItemButton::sizeHint() const { QSize size = Amarok::ElidingButton::sizeHint(); int width = 8; if( !icon().isNull() ) { width += iconSize().width(); } if( !text().isEmpty() ) { QFontMetrics fm( font() ); width += fm.width( text() ); } size.setWidth( width ); return size; } BreadcrumbItemMenuButton::BreadcrumbItemMenuButton( QWidget* parent ) : BreadcrumbItemButton( parent ) { setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); } void BreadcrumbItemMenuButton::paintEvent( QPaintEvent* event ) { Q_UNUSED(event); QPainter painter(this); drawHoverBackground(&painter); const QColor fgColor = foregroundColor(); QStyleOption option; option.initFrom(this); option.rect = QRect(0, 0, width(), height()); option.palette = palette(); option.palette.setColor(QPalette::Text, fgColor); option.palette.setColor(QPalette::WindowText, fgColor); option.palette.setColor(QPalette::ButtonText, fgColor); if (layoutDirection() == Qt::LeftToRight) { style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this); } else { style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this); } } BreadcrumbUrlMenuButton::BreadcrumbUrlMenuButton( const QString &urlsCommand, QWidget *parent ) : BreadcrumbItemButton( QIcon::fromTheme( "bookmark-new-list" ), QString(), parent ) , m_urlsCommand( urlsCommand ) , m_copyToClipboardAction( 0 ) { setToolTip( i18n( "List and run bookmarks, or create new ones" ) ); connect( this, &QAbstractButton::clicked, this, &BreadcrumbUrlMenuButton::showMenu ); } BreadcrumbUrlMenuButton::~BreadcrumbUrlMenuButton() { } void BreadcrumbUrlMenuButton::generateMenu( const QPoint &pos ) { DEBUG_BLOCK BookmarkList list = The::amarokUrlHandler()->urlsByCommand( m_urlsCommand ); QMenu * menu = new QMenu(); menu->setTitle( i18n("Amarok Bookmarks" ) ); if( m_urlsCommand == "navigate" ) menu->addAction( Amarok::actionCollection()->action( "bookmark_browser" ) ); else if( m_urlsCommand == "playlist" ) { menu->addAction( Amarok::actionCollection()->action( "bookmark_playlistview" ) ); debug()<<"Adding bookmark playlist action"; } else if( m_urlsCommand == "context" ) { menu->addAction( Amarok::actionCollection()->action( "bookmark_contextview" ) ); debug()<<"Adding bookmark context view action"; } else warning()<<"Bad URL command."; if( !m_copyToClipboardAction ) { m_copyToClipboardAction = new QAction( QIcon::fromTheme( "klipper" ), i18n( "Copy Current View Bookmark to Clipboard" ), this ); connect( m_copyToClipboardAction, &QAction::triggered, this, &BreadcrumbUrlMenuButton::copyCurrentToClipboard ); } menu->addAction( m_copyToClipboardAction ); menu->addAction( Amarok::actionCollection()->action( "bookmark_manager" ) ); menu->addSeparator(); foreach( AmarokUrlPtr url, list ) { menu->addAction( new AmarokUrlAction( url, menu ) ); } debug() << "showing menu at " << pos; menu->exec( pos ); delete menu; } void BreadcrumbUrlMenuButton::showMenu() { QPoint pos( 0, height() ); generateMenu( mapToGlobal( pos ) ); } void BreadcrumbUrlMenuButton::copyCurrentToClipboard() { QString urlString; if( m_urlsCommand == "navigate" ) { AmarokUrl url = The::amarokUrlHandler()->createBrowserViewBookmark(); urlString = url.url(); } else if( m_urlsCommand == "playlist" ) { AmarokUrl url = The::amarokUrlHandler()->createPlaylistViewBookmark(); urlString = url.url(); } else if( m_urlsCommand == "context" ) { AmarokUrl url = The::amarokUrlHandler()->createContextViewBookmark(); urlString = url.url(); } QApplication::clipboard()->setText( urlString ); } diff --git a/tests/dynamic/TestDynamicModel.cpp b/tests/dynamic/TestDynamicModel.cpp index 3dc1049fee..485b6ae089 100644 --- a/tests/dynamic/TestDynamicModel.cpp +++ b/tests/dynamic/TestDynamicModel.cpp @@ -1,358 +1,357 @@ /**************************************************************************************** * Copyright (c) 2011 Ralf Engels * * * * 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, see . * ****************************************************************************************/ #include "TestDynamicModel.h" #include "amarokconfig.h" #include "dynamic/Bias.h" #include "dynamic/BiasedPlaylist.h" #include "dynamic/DynamicModel.h" #include "core/support/Amarok.h" #include "core/support/Debug.h" #include #include #include #include #include Q_DECLARE_METATYPE(QModelIndex); QTemporaryDir *s_tmpDir = 0; // Memory leak here now, but if it's deleted, we have a segfault // We return a special saveLocation. QString Amarok::saveLocation( const QString &directory ) { return s_tmpDir->path() + directory; } QTEST_GUILESS_MAIN( TestDynamicModel ) TestDynamicModel::TestDynamicModel() { qRegisterMetaType(); } void TestDynamicModel::init() { AmarokConfig::instance("amarokrc"); s_tmpDir = new QTemporaryDir(); QVERIFY( s_tmpDir->isValid() ); } void TestDynamicModel::cleanup() { } void TestDynamicModel::testData() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); // now we should have the four default playlists QModelIndex playlistIndex = model->index( 0, 0 ); QCOMPARE( model->data( playlistIndex ).toString(), QString("Random") ); QCOMPARE( model->data( playlistIndex, Qt::EditRole ).toString(), QString("Random") ); QVERIFY( model->data( playlistIndex, Dynamic::DynamicModel::PlaylistRole ).isValid() ); QVERIFY( !model->data( playlistIndex, Dynamic::DynamicModel::BiasRole ).isValid() ); QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); QVERIFY( !model->data( biasIndex ).toString().isEmpty() ); QVERIFY( !model->data( biasIndex, Dynamic::DynamicModel::PlaylistRole ).isValid() ); QVERIFY( model->data( biasIndex, Dynamic::DynamicModel::BiasRole ).isValid() ); } void TestDynamicModel::testPlaylistIndex() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); // now we should have the four default playlists QCOMPARE( model->rowCount(), 4 ); QCOMPARE( model->columnCount(), 1 ); // -- random playlist with one bias QModelIndex playlistIndex = model->index( 0, 0 ); QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); QCOMPARE( model->rowCount( playlistIndex ), 1 ); QCOMPARE( model->rowCount( biasIndex ), 0 ); QCOMPARE( playlistIndex.parent(), QModelIndex() ); QCOMPARE( biasIndex.parent(), playlistIndex ); // -- albumplay playlist with bias structure playlistIndex = model->index( 2, 0 ); biasIndex = model->index( 0, 0, playlistIndex ); QModelIndex subBiasIndex = model->index( 1, 0, biasIndex ); QCOMPARE( model->rowCount( playlistIndex ), 1 ); QCOMPARE( model->rowCount( biasIndex ), 2 ); QCOMPARE( model->rowCount( subBiasIndex ), 0 ); QCOMPARE( playlistIndex.parent(), QModelIndex() ); QCOMPARE( biasIndex.parent(), playlistIndex ); QCOMPARE( subBiasIndex.parent(), biasIndex ); // and now the non-model index functions: model->setActivePlaylist( 2 ); Dynamic::DynamicPlaylist* playlist = model->activePlaylist(); playlistIndex = model->index( model->activePlaylistIndex(), 0 ); QCOMPARE( model->index( playlist ), playlistIndex ); Dynamic::BiasPtr bias = qobject_cast(playlist)->bias(); biasIndex = model->index( 0, 0, playlistIndex ); QCOMPARE( model->index( bias ), biasIndex ); Dynamic::BiasPtr subBias = qobject_cast(bias.data())->biases().at(0); subBiasIndex = model->index( 0, 0, biasIndex ); QCOMPARE( model->index( subBias ), subBiasIndex ); } void TestDynamicModel::testSlots() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); QSignalSpy spy1( model, &Dynamic::DynamicModel::rowsAboutToBeRemoved ); QSignalSpy spy2( model, &Dynamic::DynamicModel::rowsRemoved ); QSignalSpy spy3( model, &Dynamic::DynamicModel::rowsAboutToBeInserted ); QSignalSpy spy4( model, &Dynamic::DynamicModel::rowsInserted ); // -- removeAt with playlist QModelIndex playlistIndex = model->index( 1, 0 ); QString oldName = model->data( playlistIndex ).toString(); model->removeAt( playlistIndex ); QCOMPARE( spy1.count(), 1 ); QCOMPARE( spy3.count(), 0 ); QList args1 = spy1.takeFirst(); QVERIFY( args1.value(0).canConvert() ); QCOMPARE( args1.value(0).value(), QModelIndex() ); QCOMPARE( args1.value(1).toInt(), 1 ); QCOMPARE( args1.value(2).toInt(), 1 ); QCOMPARE( spy2.count(), 1 ); spy2.takeFirst(); // name should be different playlistIndex = model->index( 1, 0 ); QVERIFY( model->data( playlistIndex ).toString() != oldName ); QCOMPARE( model->rowCount(), 3 ); // -- removeAt with bias playlistIndex = model->index( 1, 0 ); QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); QModelIndex subBiasIndex = model->index( 0, 0, biasIndex ); QCOMPARE( model->rowCount( biasIndex ), 2 ); model->removeAt( subBiasIndex ); QCOMPARE( spy1.count(), 1 ); QCOMPARE( spy3.count(), 0 ); args1 = spy1.takeFirst(); QCOMPARE( args1.count(), 3 ); QVERIFY( args1.value(0).canConvert() ); QCOMPARE( args1.value(0).value(), biasIndex ); QCOMPARE( args1.value(1).toInt(), 0 ); QCOMPARE( args1.value(2).toInt(), 0 ); QCOMPARE( spy2.count(), 1 ); spy2.takeFirst(); QCOMPARE( model->rowCount( biasIndex ), 1 ); QCOMPARE( model->rowCount(), 3 ); // only the bias was removed // -- cloneAt with level 2 bias playlistIndex = model->index( 1, 0 ); biasIndex = model->index( 0, 0, playlistIndex ); subBiasIndex = model->index( 0, 0, biasIndex ); QCOMPARE( model->rowCount( biasIndex ), 1 ); QModelIndex resultIndex = model->cloneAt(subBiasIndex); QCOMPARE( resultIndex.row(), 1 ); QCOMPARE( resultIndex.parent(), biasIndex ); QCOMPARE( spy3.count(), 1 ); args1 = spy3.takeFirst(); QVERIFY( args1.value(0).canConvert() ); QCOMPARE( args1.value(0).value(), biasIndex ); QCOMPARE( args1.value(1).toInt(), 1 ); QCOMPARE( args1.value(2).toInt(), 1 ); QCOMPARE( spy4.count(), 1 ); spy4.takeFirst(); QCOMPARE( model->rowCount( biasIndex ), 2 ); QCOMPARE( model->rowCount(), 3 ); // only the bias was cloned // -- newPlaylist QCOMPARE( spy1.count(), 0 ); QCOMPARE( spy3.count(), 0 ); QCOMPARE( model->rowCount(), 3 ); resultIndex = model->newPlaylist(); QCOMPARE( model->rowCount(), 4 ); QCOMPARE( resultIndex.row(), 3 ); QCOMPARE( resultIndex.parent(), QModelIndex() ); QCOMPARE( spy1.count(), 0 ); QCOMPARE( spy3.count(), 1 ); args1 = spy3.takeFirst(); QVERIFY( args1.value(0).canConvert() ); QCOMPARE( args1.value(0).value(), QModelIndex() ); QCOMPARE( args1.value(1).toInt(), 3 ); QCOMPARE( args1.value(2).toInt(), 3 ); QCOMPARE( spy4.count(), 1 ); spy4.takeFirst(); // -- cloneAt with playlist playlistIndex = model->index( 1, 0 ); QCOMPARE( model->rowCount(), 4 ); resultIndex = model->cloneAt(playlistIndex); QCOMPARE( model->rowCount(), 5 ); QCOMPARE( resultIndex.row(), 4 ); QCOMPARE( resultIndex.parent(), QModelIndex() ); QCOMPARE( model->rowCount( resultIndex ), 1 ); QCOMPARE( spy3.count(), 1 ); args1 = spy3.takeFirst(); QVERIFY( args1.value(0).canConvert() ); QCOMPARE( args1.value(0).value(), QModelIndex() ); QCOMPARE( args1.value(1).toInt(), 4 ); QCOMPARE( args1.value(2).toInt(), 4 ); QCOMPARE( spy4.count(), 1 ); spy4.takeFirst(); } QModelIndex TestDynamicModel::serializeUnserialize( const QModelIndex& index ) { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); QByteArray bytes; QDataStream stream( &bytes, QIODevice::WriteOnly ); model->serializeIndex( &stream, index ); QDataStream stream2( &bytes, QIODevice::ReadOnly ); return model->unserializeIndex( &stream2 ); } void TestDynamicModel::testSerializeIndex() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); QModelIndex playlistIndex = model->index( 2, 0 ); QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); QModelIndex subBiasIndex = model->index( 0, 0, biasIndex ); QCOMPARE( QModelIndex(), serializeUnserialize( QModelIndex() ) ); QCOMPARE( biasIndex, serializeUnserialize( biasIndex ) ); QCOMPARE( subBiasIndex, serializeUnserialize( subBiasIndex ) ); } void TestDynamicModel::testDnD() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); // -- copy a playlist QModelIndex playlistIndex = model->index( 2, 0 ); QModelIndexList indexes; indexes << playlistIndex; int oldRowCount = model->rowCount(); QString oldName = model->data( playlistIndex ).toString(); QMimeData* data = model->mimeData( indexes ); QVERIFY( model->dropMimeData( data, Qt::CopyAction, 0, 0, QModelIndex() ) ); QCOMPARE( model->rowCount(), oldRowCount + 1 ); playlistIndex = model->index( 0, 0 ); QCOMPARE( oldName, model->data( playlistIndex ).toString() ); delete data; // -- move a playlist (to the end) playlistIndex = model->index( 0, 0 ); indexes.clear(); indexes << playlistIndex; oldRowCount = model->rowCount(); oldName = model->data( playlistIndex ).toString(); data = model->mimeData( indexes ); QVERIFY( model->dropMimeData( data, Qt::MoveAction, oldRowCount, 0, QModelIndex() ) ); QCOMPARE( model->rowCount(), oldRowCount ); playlistIndex = model->index( oldRowCount - 1, 0 ); QCOMPARE( oldName, model->data( playlistIndex ).toString() ); delete data; // -- copy a bias // TODO - QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); - QModelIndex subBiasIndex = model->index( 0, 0, biasIndex ); - +// QModelIndex biasIndex = model->index( 0, 0, playlistIndex ); +// QModelIndex subBiasIndex = model->index( 0, 0, biasIndex ); } void TestDynamicModel::testRemoveActive() { Dynamic::DynamicModel *model = Dynamic::DynamicModel::instance(); // load from the empty directory model->loadPlaylists(); QCOMPARE( model->rowCount(), 4 ); // -- try to remove the active playlist model->setActivePlaylist( 2 ); QCOMPARE( model->activePlaylistIndex(), 2 ); Dynamic::DynamicPlaylist* pl = model->activePlaylist(); model->removeAt( model->index( pl ) ); QCOMPARE( model->rowCount(), 3 ); QVERIFY( model->activePlaylist() != pl ); // -- now remove all playlists remaining three playlists model->removeAt( model->index( model->activePlaylist() ) ); model->removeAt( model->index( model->activePlaylist() ) ); model->removeAt( model->index( model->activePlaylist() ) ); QCOMPARE( model->rowCount(), 0 ); QCOMPARE( model->activePlaylist(), static_cast(0) ); }