Add smart playlists, ratings and Qobuz

Fixes #259
Fixes #264
This commit is contained in:
Jonas Kvinge
2020-09-17 17:50:17 +02:00
parent fdf96e8342
commit 89d6b7cec0
102 changed files with 10949 additions and 525 deletions

View File

@@ -0,0 +1,43 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QObject>
#include <QString>
#include "core/logging.h"
#include "playlistgenerator.h"
#include "playlistquerygenerator.h"
const int PlaylistGenerator::kDefaultLimit = 20;
const int PlaylistGenerator::kDefaultDynamicHistory = 5;
const int PlaylistGenerator::kDefaultDynamicFuture = 15;
PlaylistGenerator::PlaylistGenerator() : QObject(nullptr) {}
PlaylistGeneratorPtr PlaylistGenerator::Create(const Type type) {
Q_UNUSED(type)
return PlaylistGeneratorPtr(new PlaylistQueryGenerator);
}

View File

@@ -0,0 +1,100 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTGENERATOR_H
#define PLAYLISTGENERATOR_H
#include "config.h"
#include <memory>
#include <QObject>
#include <QByteArray>
#include <QString>
#include "playlist/playlistitem.h"
class CollectionBackend;
class PlaylistGenerator : public QObject, public std::enable_shared_from_this<PlaylistGenerator> {
Q_OBJECT
public:
explicit PlaylistGenerator();
static const int kDefaultLimit;
static const int kDefaultDynamicHistory;
static const int kDefaultDynamicFuture;
enum Type {
Type_None = 0,
Type_Query = 1
};
// Creates a new PlaylistGenerator of the given type
static std::shared_ptr<PlaylistGenerator> Create(const Type type = Type_Query);
// Should be called before Load on a new PlaylistGenerator
void set_collection(CollectionBackend *backend) { backend_ = backend; }
void set_name(const QString &name) { name_ = name; }
CollectionBackend *collection() const { return backend_; }
QString name() const { return name_; }
// Name of the subclass
virtual Type type() const = 0;
// Serializes the PlaylistGenerator's settings
// Called on UI-thread.
virtual void Load(const QByteArray &data) = 0;
// Called on UI-thread.
virtual QByteArray Save() const = 0;
// Creates and returns a playlist
// Called from non-UI thread.
virtual PlaylistItemList Generate() = 0;
// If the generator can be used as a dynamic playlist then GenerateMore should return the next tracks in the sequence.
// The subclass should remember the last GetDynamicHistory() + GetDynamicFuture() tracks,
// and ensure that the tracks returned from this method are not in that set.
virtual bool is_dynamic() const { return false; }
virtual void set_dynamic(const bool dynamic) { Q_UNUSED(dynamic); }
// Called from non-UI thread.
virtual PlaylistItemList GenerateMore(int count) {
Q_UNUSED(count);
return PlaylistItemList();
}
virtual int GetDynamicHistory() { return kDefaultDynamicHistory; }
virtual int GetDynamicFuture() { return kDefaultDynamicFuture; }
signals:
void Error(const QString& message);
protected:
CollectionBackend *backend_;
private:
QString name_;
};
#include "playlistgenerator_fwd.h"
#endif // PLAYLISTGENERATOR_H

View File

@@ -0,0 +1,32 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTGENERATOR_FWD_H
#define PLAYLISTGENERATOR_FWD_H
#include "config.h"
#include <memory>
class PlaylistGenerator;
typedef std::shared_ptr<PlaylistGenerator> PlaylistGeneratorPtr;
#endif // PLAYLISTGENERATOR_FWD_H

View File

@@ -0,0 +1,90 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QObject>
#include <QString>
#include <QtConcurrentRun>
#include "core/closure.h"
#include "core/taskmanager.h"
#include "playlist/playlist.h"
#include "playlistgenerator.h"
#include "playlistgeneratorinserter.h"
class CollectionBackend;
PlaylistGeneratorInserter::PlaylistGeneratorInserter(TaskManager *task_manager, CollectionBackend *collection, QObject *parent)
: QObject(parent),
task_manager_(task_manager),
collection_(collection),
task_id_(-1),
is_dynamic_(false)
{}
PlaylistItemList PlaylistGeneratorInserter::Generate(PlaylistGeneratorPtr generator, int dynamic_count) {
if (dynamic_count) {
return generator->GenerateMore(dynamic_count);
}
else {
return generator->Generate();
}
}
void PlaylistGeneratorInserter::Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, PlaylistGeneratorPtr generator, const int dynamic_count) {
task_id_ = task_manager_->StartTask(tr("Loading smart playlist"));
destination_ = destination;
row_ = row;
play_now_ = play_now;
enqueue_ = enqueue;
enqueue_next_ = enqueue_next;
is_dynamic_ = generator->is_dynamic();
connect(generator.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
QFuture<PlaylistItemList> future = QtConcurrent::run(PlaylistGeneratorInserter::Generate, generator, dynamic_count);
NewClosure(future, this, SLOT(Finished(QFuture<PlaylistItemList>)), future);
}
void PlaylistGeneratorInserter::Finished(QFuture<PlaylistItemList> future) {
PlaylistItemList items = future.result();
if (items.isEmpty()) {
if (is_dynamic_) {
destination_->TurnOffDynamicPlaylist();
}
}
else {
destination_->InsertItems(items, row_, play_now_, enqueue_);
}
task_manager_->SetTaskFinished(task_id_);
deleteLater();
}

View File

@@ -0,0 +1,71 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTGENERATORINSERTER_H
#define PLAYLISTGENERATORINSERTER_H
#include "config.h"
#include <QObject>
#include <QFuture>
#include <QString>
#include "playlist/playlist.h"
#include "playlist/playlistitem.h"
#include "playlistgenerator_fwd.h"
class TaskManager;
class CollectionBackend;
class Playlist;
class PlaylistGeneratorInserter : public QObject {
Q_OBJECT
public:
explicit PlaylistGeneratorInserter(TaskManager *task_manager, CollectionBackend *collection, QObject *parent);
void Load(Playlist *destination, const int row, const bool play_now, const bool enqueue, const bool enqueue_next, PlaylistGeneratorPtr generator, const int dynamic_count = 0);
private:
static PlaylistItemList Generate(PlaylistGeneratorPtr generator, const int dynamic_count);
signals:
void Error(const QString &message);
void PlayRequested(const QModelIndex &idx);
private slots:
void Finished(QFuture<PlaylistItemList> future);
private:
TaskManager *task_manager_;
CollectionBackend *collection_;
int task_id_;
Playlist *destination_;
int row_;
bool play_now_;
bool enqueue_;
bool enqueue_next_;
bool is_dynamic_;
};
#endif // PLAYLISTGENERATORINSERTER_H

View File

@@ -0,0 +1,41 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTGENERATORMIMEDATA_H
#define PLAYLISTGENERATORMIMEDATA_H
#include "config.h"
#include <QObject>
#include <QMimeData>
#include "core/mimedata.h"
#include "playlistgenerator_fwd.h"
class PlaylistGeneratorMimeData : public MimeData {
Q_OBJECT
public:
PlaylistGeneratorMimeData(PlaylistGeneratorPtr generator) : generator_(generator) {}
PlaylistGeneratorPtr generator_;
};
#endif // PLAYLISTGENERATORMIMEDATA_H

View File

@@ -0,0 +1,101 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QIODevice>
#include <QDataStream>
#include <QByteArray>
#include <QString>
#include "core/logging.h"
#include "playlistquerygenerator.h"
#include "collection/collectionbackend.h"
PlaylistQueryGenerator::PlaylistQueryGenerator() : dynamic_(false), current_pos_(0) {}
PlaylistQueryGenerator::PlaylistQueryGenerator(const QString &name, const SmartPlaylistSearch &search, const bool dynamic)
: search_(search), dynamic_(dynamic), current_pos_(0) {
set_name(name);
}
void PlaylistQueryGenerator::Load(const SmartPlaylistSearch &search) {
search_ = search;
dynamic_ = false;
current_pos_ = 0;
}
void PlaylistQueryGenerator::Load(const QByteArray &data) {
QDataStream s(data);
s >> search_;
s >> dynamic_;
}
QByteArray PlaylistQueryGenerator::Save() const {
QByteArray ret;
QDataStream s(&ret, QIODevice::WriteOnly);
s << search_;
s << dynamic_;
return ret;
}
PlaylistItemList PlaylistQueryGenerator::Generate() {
previous_ids_.clear();
current_pos_ = 0;
return GenerateMore(0);
}
PlaylistItemList PlaylistQueryGenerator::GenerateMore(const int count) {
SmartPlaylistSearch search_copy = search_;
search_copy.id_not_in_ = previous_ids_;
if (count) {
search_copy.limit_ = count;
}
if (search_copy.sort_type_ != SmartPlaylistSearch::Sort_Random) {
search_copy.first_item_ = current_pos_;
current_pos_ += search_copy.limit_;
}
SongList songs = backend_->FindSongs(search_copy);
PlaylistItemList items;
for (const Song &song : songs) {
items << PlaylistItemPtr(PlaylistItem::NewFromSong(song));
previous_ids_ << song.id();
if (previous_ids_.count() > GetDynamicFuture() + GetDynamicHistory())
previous_ids_.removeFirst();
}
return items;
}

View File

@@ -0,0 +1,61 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef PLAYLISTQUERYGENERATOR_H
#define PLAYLISTQUERYGENERATOR_H
#include "config.h"
#include <QList>
#include <QByteArray>
#include <QString>
#include "playlistgenerator.h"
#include "smartplaylistsearch.h"
class PlaylistQueryGenerator : public PlaylistGenerator {
public:
explicit PlaylistQueryGenerator();
explicit PlaylistQueryGenerator(const QString &name, const SmartPlaylistSearch &search, const bool dynamic = false);
Type type() const { return Type_Query; }
void Load(const SmartPlaylistSearch &search);
void Load(const QByteArray &data);
QByteArray Save() const;
PlaylistItemList Generate();
PlaylistItemList GenerateMore(const int count);
bool is_dynamic() const { return dynamic_; }
void set_dynamic(bool dynamic) { dynamic_ = dynamic; }
SmartPlaylistSearch search() const { return search_; }
int GetDynamicFuture() { return search_.limit_; }
private:
SmartPlaylistSearch search_;
bool dynamic_;
QList<int> previous_ids_;
int current_pos_;
};
#endif // PLAYLISTQUERYGENERATOR_H

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistQuerySearchPage</class>
<widget class="QWidget" name="SmartPlaylistQuerySearchPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>448</width>
<height>450</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">#terms_scroll_area, #terms_scroll_area_content {
background: transparent;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Search mode</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QComboBox" name="type">
<item>
<property name="text">
<string>Match every search term (AND)</string>
</property>
</item>
<item>
<property name="text">
<string>Match one or more search terms (OR)</string>
</property>
</item>
<item>
<property name="text">
<string>Include all songs</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="terms_group">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
<property name="title">
<string>Search terms</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QScrollArea" name="terms_scroll_area">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="terms_scroll_area_content">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>418</width>
<height>251</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistQuerySortPage</class>
<widget class="QWidget" name="SmartPlaylistQuerySortPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>723</width>
<height>335</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Sorting</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="QRadioButton" name="random">
<property name="text">
<string>Put songs in a random order</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="field">
<property name="text">
<string>Sort songs by</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="field_value"/>
</item>
<item>
<widget class="QComboBox" name="order">
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Limits</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QRadioButton" name="limit_none">
<property name="text">
<string>Show all the songs</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="limit_limit">
<property name="text">
<string>Only show the first</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="limit_value">
<property name="suffix">
<string> songs</string>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>15</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="SmartPlaylistSearchPreview" name="preview" native="true"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SmartPlaylistSearchPreview</class>
<extends>QWidget</extends>
<header>smartplaylists/smartplaylistsearchpreview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,327 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QWizardPage>
#include <QList>
#include <QString>
#include <QVBoxLayout>
#include <QScrollBar>
#include "core/logging.h"
#include "playlistquerygenerator.h"
#include "smartplaylistquerywizardplugin.h"
#include "smartplaylistsearchtermwidget.h"
#include "ui_smartplaylistquerysearchpage.h"
#include "ui_smartplaylistquerysortpage.h"
class SmartPlaylistQueryWizardPlugin::SearchPage : public QWizardPage {
friend class SmartPlaylistQueryWizardPlugin;
public:
SearchPage(QWidget *parent = 0)
: QWizardPage(parent), ui_(new Ui_SmartPlaylistQuerySearchPage) {
ui_->setupUi(this);
}
bool isComplete() const {
if (ui_->type->currentIndex() == 2) // All songs
return true;
for (SmartPlaylistSearchTermWidget *widget : terms_) {
if (!widget->Term().is_valid()) return false;
}
return true;
}
QVBoxLayout *layout_;
QList<SmartPlaylistSearchTermWidget*> terms_;
SmartPlaylistSearchTermWidget *new_term_;
SmartPlaylistSearchPreview *preview_;
std::unique_ptr<Ui_SmartPlaylistQuerySearchPage> ui_;
};
class SmartPlaylistQueryWizardPlugin::SortPage : public QWizardPage {
public:
SortPage(SmartPlaylistQueryWizardPlugin *plugin, QWidget *parent, int next_id)
: QWizardPage(parent), next_id_(next_id), plugin_(plugin) {}
void showEvent(QShowEvent*) { plugin_->UpdateSortPreview(); }
int nextId() const { return next_id_; }
int next_id_;
SmartPlaylistQueryWizardPlugin *plugin_;
};
SmartPlaylistQueryWizardPlugin::SmartPlaylistQueryWizardPlugin(Application *app, CollectionBackend *collection, QObject *parent)
: SmartPlaylistWizardPlugin(app, collection, parent),
search_page_(nullptr),
previous_scrollarea_max_(0) {}
SmartPlaylistQueryWizardPlugin::~SmartPlaylistQueryWizardPlugin() {}
QString SmartPlaylistQueryWizardPlugin::name() const { return tr("Collection search"); }
QString SmartPlaylistQueryWizardPlugin::description() const {
return tr("Find songs in your collection that match the criteria you specify.");
}
int SmartPlaylistQueryWizardPlugin::CreatePages(QWizard *wizard, int finish_page_id) {
// Create the UI
search_page_ = new SearchPage(wizard);
QWizardPage *sort_page = new SortPage(this, wizard, finish_page_id);
sort_ui_.reset(new Ui_SmartPlaylistQuerySortPage);
sort_ui_->setupUi(sort_page);
sort_ui_->limit_value->setValue(PlaylistGenerator::kDefaultLimit);
connect(search_page_->ui_->type, SIGNAL(currentIndexChanged(int)), SLOT(SearchTypeChanged()));
// Create the new search term widget
search_page_->new_term_ = new SmartPlaylistSearchTermWidget(collection_, search_page_);
search_page_->new_term_->SetActive(false);
connect(search_page_->new_term_, SIGNAL(Clicked()), SLOT(AddSearchTerm()));
// Add an empty initial term
search_page_->layout_ = static_cast<QVBoxLayout*>(search_page_->ui_->terms_scroll_area_content->layout());
search_page_->layout_->addWidget(search_page_->new_term_);
AddSearchTerm();
// Ensure that the terms are scrolled to the bottom when a new one is added
connect(search_page_->ui_->terms_scroll_area->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(MoveTermListToBottom(int, int)));
// Add the preview widget at the bottom of the search terms page
QVBoxLayout *terms_page_layout = static_cast<QVBoxLayout*>(search_page_->layout());
terms_page_layout->addStretch();
search_page_->preview_ = new SmartPlaylistSearchPreview(search_page_);
search_page_->preview_->set_application(app_);
search_page_->preview_->set_collection(collection_);
terms_page_layout->addWidget(search_page_->preview_);
// Add sort field texts
for (int i = 0; i < SmartPlaylistSearchTerm::FieldCount; ++i) {
const SmartPlaylistSearchTerm::Field field = SmartPlaylistSearchTerm::Field(i);
const QString field_name = SmartPlaylistSearchTerm::FieldName(field);
sort_ui_->field_value->addItem(field_name);
}
connect(sort_ui_->field_value, SIGNAL(currentIndexChanged(int)), SLOT(UpdateSortOrder()));
UpdateSortOrder();
// Set the sort and limit radio buttons back to their defaults - they would
// have been changed by setupUi
sort_ui_->random->setChecked(true);
sort_ui_->limit_none->setChecked(true);
// Set up the preview widget that's already at the bottom of the sort page
sort_ui_->preview->set_application(app_);
sort_ui_->preview->set_collection(collection_);
connect(sort_ui_->field, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
connect(sort_ui_->field_value, SIGNAL(currentIndexChanged(int)), SLOT(UpdateSortPreview()));
connect(sort_ui_->limit_limit, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
connect(sort_ui_->limit_none, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
connect(sort_ui_->limit_value, SIGNAL(valueChanged(int)), SLOT(UpdateSortPreview()));
connect(sort_ui_->order, SIGNAL(currentIndexChanged(int)), SLOT(UpdateSortPreview()));
connect(sort_ui_->random, SIGNAL(toggled(bool)), SLOT(UpdateSortPreview()));
// Configure the page text
search_page_->setTitle(tr("Search terms"));
search_page_->setSubTitle(tr("A song will be included in the playlist if it matches these conditions."));
sort_page->setTitle(tr("Search options"));
sort_page->setSubTitle(tr("Choose how the playlist is sorted and how many songs it will contain."));
// Add the pages
const int first_page = wizard->addPage(search_page_);
wizard->addPage(sort_page);
return first_page;
}
void SmartPlaylistQueryWizardPlugin::SetGenerator(PlaylistGeneratorPtr g) {
std::shared_ptr<PlaylistQueryGenerator> gen = std::dynamic_pointer_cast<PlaylistQueryGenerator>(g);
if (!gen) return;
SmartPlaylistSearch search = gen->search();
// Search type
search_page_->ui_->type->setCurrentIndex(search.search_type_);
// Search terms
qDeleteAll(search_page_->terms_);
search_page_->terms_.clear();
for (const SmartPlaylistSearchTerm& term : search.terms_) {
AddSearchTerm();
search_page_->terms_.last()->SetTerm(term);
}
// Sort order
if (search.sort_type_ == SmartPlaylistSearch::Sort_Random) {
sort_ui_->random->setChecked(true);
}
else {
sort_ui_->field->setChecked(true);
sort_ui_->order->setCurrentIndex(
search.sort_type_ == SmartPlaylistSearch::Sort_FieldAsc ? 0 : 1);
sort_ui_->field_value->setCurrentIndex(search.sort_field_);
}
// Limit
if (search.limit_ == -1) {
sort_ui_->limit_none->setChecked(true);
}
else {
sort_ui_->limit_limit->setChecked(true);
sort_ui_->limit_value->setValue(search.limit_);
}
}
PlaylistGeneratorPtr SmartPlaylistQueryWizardPlugin::CreateGenerator() const {
std::shared_ptr<PlaylistQueryGenerator> gen(new PlaylistQueryGenerator);
gen->Load(MakeSearch());
return std::static_pointer_cast<PlaylistGenerator>(gen);
}
void SmartPlaylistQueryWizardPlugin::UpdateSortOrder() {
const SmartPlaylistSearchTerm::Field field = SmartPlaylistSearchTerm::Field(sort_ui_->field_value->currentIndex());
const SmartPlaylistSearchTerm::Type type = SmartPlaylistSearchTerm::TypeOf(field);
const QString asc = SmartPlaylistSearchTerm::FieldSortOrderText(type, true);
const QString desc = SmartPlaylistSearchTerm::FieldSortOrderText(type, false);
const int old_current_index = sort_ui_->order->currentIndex();
sort_ui_->order->clear();
sort_ui_->order->addItem(asc);
sort_ui_->order->addItem(desc);
sort_ui_->order->setCurrentIndex(old_current_index);
}
void SmartPlaylistQueryWizardPlugin::AddSearchTerm() {
SmartPlaylistSearchTermWidget *widget = new SmartPlaylistSearchTermWidget(collection_, search_page_);
connect(widget, SIGNAL(RemoveClicked()), SLOT(RemoveSearchTerm()));
connect(widget, SIGNAL(Changed()), SLOT(UpdateTermPreview()));
search_page_->layout_->insertWidget(search_page_->terms_.count(), widget);
search_page_->terms_ << widget;
UpdateTermPreview();
}
void SmartPlaylistQueryWizardPlugin::RemoveSearchTerm() {
SmartPlaylistSearchTermWidget *widget = qobject_cast<SmartPlaylistSearchTermWidget*>(sender());
if (!widget) return;
const int index = search_page_->terms_.indexOf(widget);
if (index == -1) return;
search_page_->terms_.takeAt(index)->deleteLater();
UpdateTermPreview();
}
void SmartPlaylistQueryWizardPlugin::UpdateTermPreview() {
SmartPlaylistSearch search = MakeSearch();
emit search_page_->completeChanged();
// When removing last term, update anyway the search
if (!search.is_valid() && !search_page_->terms_.isEmpty()) return;
// Don't apply limits in the term page
search.limit_ = -1;
search_page_->preview_->Update(search);
}
void SmartPlaylistQueryWizardPlugin::UpdateSortPreview() {
SmartPlaylistSearch search = MakeSearch();
if (!search.is_valid()) return;
sort_ui_->preview->Update(search);
}
SmartPlaylistSearch SmartPlaylistQueryWizardPlugin::MakeSearch() const {
SmartPlaylistSearch ret;
// Search type
ret.search_type_ = SmartPlaylistSearch::SearchType(search_page_->ui_->type->currentIndex());
// Search terms
for (SmartPlaylistSearchTermWidget *widget : search_page_->terms_) {
SmartPlaylistSearchTerm term = widget->Term();
if (term.is_valid()) ret.terms_ << term;
}
// Sort order
if (sort_ui_->random->isChecked()) {
ret.sort_type_ = SmartPlaylistSearch::Sort_Random;
}
else {
const bool ascending = sort_ui_->order->currentIndex() == 0;
ret.sort_type_ = ascending ? SmartPlaylistSearch::Sort_FieldAsc : SmartPlaylistSearch::Sort_FieldDesc;
ret.sort_field_ = SmartPlaylistSearchTerm::Field(sort_ui_->field_value->currentIndex());
}
// Limit
if (sort_ui_->limit_none->isChecked())
ret.limit_ = -1;
else
ret.limit_ = sort_ui_->limit_value->value();
return ret;
}
void SmartPlaylistQueryWizardPlugin::SearchTypeChanged() {
const bool all = search_page_->ui_->type->currentIndex() == 2;
search_page_->ui_->terms_scroll_area_content->setEnabled(!all);
UpdateTermPreview();
}
void SmartPlaylistQueryWizardPlugin::MoveTermListToBottom(int min, int max) {
Q_UNUSED(min);
// Only scroll to the bottom if a new term is added
if (previous_scrollarea_max_ < max)
search_page_->ui_->terms_scroll_area->verticalScrollBar()->setValue(max);
previous_scrollarea_max_ = max;
}

View File

@@ -0,0 +1,80 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTQUERYWIZARDPLUGIN_H
#define SMARTPLAYLISTQUERYWIZARDPLUGIN_H
#include "config.h"
#include <memory>
#include <QObject>
#include <QString>
#include "smartplaylistwizardplugin.h"
#include "smartplaylistsearch.h"
class QWizard;
class CollectionBackend;
class SmartPlaylistSearch;
class Ui_SmartPlaylistQuerySortPage;
class SmartPlaylistQueryWizardPlugin : public SmartPlaylistWizardPlugin {
Q_OBJECT
public:
explicit SmartPlaylistQueryWizardPlugin(Application *app, CollectionBackend *collection, QObject *parent);
~SmartPlaylistQueryWizardPlugin();
QString type() const { return "Query"; }
QString name() const;
QString description() const;
bool is_dynamic() const { return true; }
int CreatePages(QWizard *wizard, const int finish_page_id);
void SetGenerator(PlaylistGeneratorPtr);
PlaylistGeneratorPtr CreateGenerator() const;
private slots:
void AddSearchTerm();
void RemoveSearchTerm();
void SearchTypeChanged();
void UpdateTermPreview();
void UpdateSortPreview();
void UpdateSortOrder();
void MoveTermListToBottom(const int min, const int max);
private:
class SearchPage;
class SortPage;
SmartPlaylistSearch MakeSearch() const;
SearchPage *search_page_;
std::unique_ptr<Ui_SmartPlaylistQuerySortPage> sort_ui_;
int previous_scrollarea_max_;
};
#endif // SMARTPLAYLISTQUERYWIZARDPLUGIN_H

View File

@@ -0,0 +1,148 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QString>
#include <QStringList>
#include <QDataStream>
#include "search.h"
#include "core/logging.h"
#include "core/song.h"
#include "smartplaylistsearch.h"
SmartPlaylistSearch::SmartPlaylistSearch() { Reset(); }
SmartPlaylistSearch::SmartPlaylistSearch(const SearchType type, const TermList terms, const SortType sort_type, const SmartPlaylistSearchTerm::Field sort_field, const int limit)
: search_type_(type),
terms_(terms),
sort_type_(sort_type),
sort_field_(sort_field),
limit_(limit),
first_item_(0) {}
void SmartPlaylistSearch::Reset() {
search_type_ = Type_And;
terms_.clear();
sort_type_ = Sort_Random;
sort_field_ = SmartPlaylistSearchTerm::Field_Title;
limit_ = -1;
first_item_ = 0;
}
QString SmartPlaylistSearch::ToSql(const QString &songs_table) const {
QString sql = "SELECT ROWID," + Song::kColumnSpec + " FROM " + songs_table;
// Add search terms
QStringList where_clauses;
QStringList term_where_clauses;
for (const SmartPlaylistSearchTerm &term : terms_) {
term_where_clauses << term.ToSql();
}
if (!terms_.isEmpty() && search_type_ != Type_All) {
QString boolean_op = search_type_ == Type_And ? " AND " : " OR ";
where_clauses << "(" + term_where_clauses.join(boolean_op) + ")";
}
// Restrict the IDs of songs if we're making a dynamic playlist
if (!id_not_in_.isEmpty()) {
QString numbers;
for (int id : id_not_in_) {
numbers += (numbers.isEmpty() ? "" : ",") + QString::number(id);
}
where_clauses << "(ROWID NOT IN (" + numbers + "))";
}
// We never want to include songs that have been deleted,
// but are still kept in the database in case the directory containing them has just been unmounted.
where_clauses << "unavailable = 0";
if (!where_clauses.isEmpty()) {
sql += " WHERE " + where_clauses.join(" AND ");
}
// Add sort by
if (sort_type_ == Sort_Random) {
sql += " ORDER BY random()";
}
else {
sql += " ORDER BY " + SmartPlaylistSearchTerm::FieldColumnName(sort_field_) + (sort_type_ == Sort_FieldAsc ? " ASC" : " DESC");
}
// Add limit
if (first_item_) {
sql += QString(" LIMIT %1 OFFSET %2").arg(limit_).arg(first_item_);
}
else if (limit_ != -1) {
sql += " LIMIT " + QString::number(limit_);
}
//qLog(Debug) << sql;
return sql;
}
bool SmartPlaylistSearch::is_valid() const {
if (search_type_ == Type_All) return true;
return !terms_.isEmpty();
}
bool SmartPlaylistSearch::operator==(const SmartPlaylistSearch &other) const {
return search_type_ == other.search_type_ &&
terms_ == other.terms_ &&
sort_type_ == other.sort_type_ &&
sort_field_ == other.sort_field_ &&
limit_ == other.limit_;
}
QDataStream &operator<<(QDataStream &s, const SmartPlaylistSearch &search) {
s << search.terms_;
s << quint8(search.sort_type_);
s << quint8(search.sort_field_);
s << qint32(search.limit_);
s << quint8(search.search_type_);
return s;
}
QDataStream &operator>>(QDataStream &s, SmartPlaylistSearch &search) {
quint8 sort_type, sort_field, search_type;
qint32 limit;
s >> search.terms_ >> sort_type >> sort_field >> limit >> search_type;
search.sort_type_ = SmartPlaylistSearch::SortType(sort_type);
search.sort_field_ = SmartPlaylistSearchTerm::Field(sort_field);
search.limit_ = limit;
search.search_type_ = SmartPlaylistSearch::SearchType(search_type);
return s;
}

View File

@@ -0,0 +1,69 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSEARCH_H
#define SMARTPLAYLISTSEARCH_H
#include "config.h"
#include <QList>
#include <QString>
#include <QDataStream>
#include "playlistgenerator.h"
#include "smartplaylistsearchterm.h"
class SmartPlaylistSearch {
public:
typedef QList<SmartPlaylistSearchTerm> TermList;
// These values are persisted, so add to the end of the enum only
enum SearchType { Type_And = 0, Type_Or, Type_All, };
// These values are persisted, so add to the end of the enum only
enum SortType { Sort_Random = 0, Sort_FieldAsc, Sort_FieldDesc, };
explicit SmartPlaylistSearch();
explicit SmartPlaylistSearch(const SearchType type, const TermList terms, const SortType sort_type, const SmartPlaylistSearchTerm::Field sort_field, const int limit = PlaylistGenerator::kDefaultLimit);
bool is_valid() const;
bool operator==(const SmartPlaylistSearch &other) const;
bool operator!=(const SmartPlaylistSearch &other) const { return !(*this == other); }
SearchType search_type_;
TermList terms_;
SortType sort_type_;
SmartPlaylistSearchTerm::Field sort_field_;
int limit_;
// Not persisted, used to alter the behaviour of the query
QList<int> id_not_in_;
int first_item_;
void Reset();
QString ToSql(const QString &songs_table) const;
};
QDataStream &operator<<(QDataStream &s, const SmartPlaylistSearch &search);
QDataStream &operator>>(QDataStream &s, SmartPlaylistSearch &search);
#endif // SMARTPLAYLISTSEARCH_H

View File

@@ -0,0 +1,151 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <memory>
#include <QWidget>
#include <QAbstractItemView>
#include <QString>
#include <QtConcurrentRun>
#include "smartplaylistsearchpreview.h"
#include "ui_smartplaylistsearchpreview.h"
#include "core/closure.h"
#include "playlist/playlist.h"
#include "playlistquerygenerator.h"
SmartPlaylistSearchPreview::SmartPlaylistSearchPreview(QWidget* parent)
: QWidget(parent),
ui_(new Ui_SmartPlaylistSearchPreview),
model_(nullptr) {
ui_->setupUi(this);
// Prevent editing songs and saving settings (like header columns and geometry)
ui_->tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui_->tree->SetReadOnlySettings(true);
QFont bold_font;
bold_font.setBold(true);
ui_->preview_label->setFont(bold_font);
ui_->busy_container->hide();
}
SmartPlaylistSearchPreview::~SmartPlaylistSearchPreview() {
delete ui_;
}
void SmartPlaylistSearchPreview::set_application(Application *app) {
ui_->tree->Init(app);
}
void SmartPlaylistSearchPreview::set_collection(CollectionBackend *backend) {
backend_ = backend;
model_ = new Playlist(nullptr, nullptr, backend_, -1, QString(), false, this);
ui_->tree->setModel(model_);
ui_->tree->SetPlaylist(model_);
ui_->tree->SetItemDelegates();
}
void SmartPlaylistSearchPreview::Update(const SmartPlaylistSearch &search) {
if (search == last_search_) {
// This search was the same as the last one we did
return;
}
if (generator_ || isHidden()) {
// It's busy generating something already, or the widget isn't visible
pending_search_ = search;
return;
}
RunSearch(search);
}
void SmartPlaylistSearchPreview::showEvent(QShowEvent *e) {
if (pending_search_.is_valid() && !generator_) {
// There was a search waiting while we were hidden, so run it now
RunSearch(pending_search_);
pending_search_ = SmartPlaylistSearch();
}
QWidget::showEvent(e);
}
namespace {
PlaylistItemList DoRunSearch(PlaylistGeneratorPtr gen) { return gen->Generate(); }
} // namespace
void SmartPlaylistSearchPreview::RunSearch(const SmartPlaylistSearch &search) {
generator_.reset(new PlaylistQueryGenerator);
generator_->set_collection(backend_);
std::dynamic_pointer_cast<PlaylistQueryGenerator>(generator_)->Load(search);
ui_->busy_container->show();
ui_->count_label->hide();
QFuture<PlaylistItemList> future = QtConcurrent::run(DoRunSearch, generator_);
NewClosure(future, this, SLOT(SearchFinished(QFuture<PlaylistItemList>)), future);
}
void SmartPlaylistSearchPreview::SearchFinished(QFuture<PlaylistItemList> future) {
last_search_ = std::dynamic_pointer_cast<PlaylistQueryGenerator>(generator_)->search();
generator_.reset();
if (pending_search_.is_valid() && pending_search_ != last_search_) {
// There was another search done while we were running
// throw away these results and do that one now instead
RunSearch(pending_search_);
pending_search_ = SmartPlaylistSearch();
return;
}
PlaylistItemList all_items = future.result();
PlaylistItemList displayed_items = all_items.mid(0, PlaylistGenerator::kDefaultLimit);
model_->Clear();
model_->InsertItems(displayed_items);
if (displayed_items.count() < all_items.count()) {
ui_->count_label->setText(tr("%1 songs found (showing %2)").arg(all_items.count()).arg(displayed_items.count()));
}
else {
ui_->count_label->setText(tr("%1 songs found").arg(all_items.count()));
}
ui_->busy_container->hide();
ui_->count_label->show();
}

View File

@@ -0,0 +1,74 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSEARCHPREVIEW_H
#define SMARTPLAYLISTSEARCHPREVIEW_H
#include "config.h"
#include <QWidget>
#include <QFuture>
#include <QList>
#include "smartplaylistsearch.h"
#include "playlistgenerator_fwd.h"
class QShowEvent;
class Application;
class CollectionBackend;
class Playlist;
class Ui_SmartPlaylistSearchPreview;
class SmartPlaylistSearchPreview : public QWidget {
Q_OBJECT
public:
explicit SmartPlaylistSearchPreview(QWidget *parent = nullptr);
~SmartPlaylistSearchPreview();
void set_application(Application *app);
void set_collection(CollectionBackend *backend);
void Update(const SmartPlaylistSearch &search);
protected:
void showEvent(QShowEvent*);
private:
void RunSearch(const SmartPlaylistSearch &search);
private slots:
void SearchFinished(QFuture<PlaylistItemList> future);
private:
Ui_SmartPlaylistSearchPreview *ui_;
QList<SmartPlaylistSearchTerm::Field> fields_;
CollectionBackend *backend_;
Playlist *model_;
SmartPlaylistSearch pending_search_;
SmartPlaylistSearch last_search_;
PlaylistGeneratorPtr generator_;
};
#endif // SMARTPLAYLISTSEARCHPREVIEW_H

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistSearchPreview</class>
<widget class="QWidget" name="SmartPlaylistSearchPreview">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>651</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="preview_label">
<property name="text">
<string>Preview</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="count_label"/>
</item>
<item>
<widget class="QWidget" name="busy_container" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="BusyIndicator" name="widget" native="true"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Loading...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="PlaylistView" name="tree">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QWidget</extends>
<header>widgets/busyindicator.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PlaylistView</class>
<extends>QTreeView</extends>
<header>playlist/playlistview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,466 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QObject>
#include <QDataStream>
#include <QVariant>
#include <QString>
#include <QUrl>
#include "smartplaylistsearchterm.h"
#include "playlist/playlist.h"
SmartPlaylistSearchTerm::SmartPlaylistSearchTerm() : field_(Field_Title), operator_(Op_Equals) {}
SmartPlaylistSearchTerm::SmartPlaylistSearchTerm(Field field, Operator op, const QVariant &value)
: field_(field), operator_(op), value_(value) {}
QString SmartPlaylistSearchTerm::ToSql() const {
QString col = FieldColumnName(field_);
QString date = DateName(date_, true);
QString value = value_.toString();
value.replace('\'', "''");
QString second_value;
bool special_date_query = (operator_ == SmartPlaylistSearchTerm::Op_NumericDate ||
operator_ == SmartPlaylistSearchTerm::Op_NumericDateNot ||
operator_ == SmartPlaylistSearchTerm::Op_RelativeDate);
// Floating point problems...
// Theoretically 0.0 == 0 stars, 0.1 == 0.5 star, 0.2 == 1 star etc.
// but in reality we need to consider anything from [0.05, 0.15) range to be 0.5 star etc.
// To make this simple, I transform the ranges to integeres and then operate on ints: [0.0, 0.05) -> 0, [0.05, 0.15) -> 1 etc.
if (TypeOf(field_) == Type_Date) {
if (!special_date_query) {
// We have the exact date
// The calendar widget specifies no time so ditch the possible time part
// from integers representing the dates.
col = "DATE(" + col + ", 'unixepoch', 'localtime')";
value = "DATE(" + value + ", 'unixepoch', 'localtime')";
}
else {
// We have a numeric date, consider also the time for more precision
col = "DATETIME(" + col + ", 'unixepoch', 'localtime')";
second_value = second_value_.toString();
second_value.replace('\'', "''");
if (date == "weeks") {
// Sqlite doesn't know weeks, transform them to days
date = "days";
value = QString::number(value_.toInt() * 7);
second_value = QString::number(second_value_.toInt() * 7);
}
}
}
else if (TypeOf(field_) == Type_Time) {
// Convert seconds to nanoseconds
value = "CAST (" + value + " *1000000000 AS INTEGER)";
}
// File paths need some extra processing since they are stored as encoded urls in the database.
if (field_ == Field_Filepath) {
if (operator_ == Op_StartsWith || operator_ == Op_Equals) {
value = QUrl::fromLocalFile(value).toEncoded();
}
else {
value = QUrl(value).toEncoded();
}
}
else if (TypeOf(field_) == Type_Rating) {
col = "CAST ((" + col + " + 0.05) * 10 AS INTEGER)";
value = "CAST ((" + value + " + 0.05) * 10 AS INTEGER)";
}
switch (operator_) {
case Op_Contains:
return col + " LIKE '%" + value + "%'";
case Op_NotContains:
return col + " NOT LIKE '%" + value + "%'";
case Op_StartsWith:
return col + " LIKE '" + value + "%'";
case Op_EndsWith:
return col + " LIKE '%" + value + "'";
case Op_Equals:
if (TypeOf(field_) == Type_Text)
return col + " LIKE '" + value + "'";
else if (TypeOf(field_) == Type_Date || TypeOf(field_) == Type_Time || TypeOf(field_) == Type_Rating)
return col + " = " + value;
else
return col + " = '" + value + "'";
case Op_GreaterThan:
if (TypeOf(field_) == Type_Date || TypeOf(field_) == Type_Time || TypeOf(field_) == Type_Rating)
return col + " > " + value;
else
return col + " > '" + value + "'";
case Op_LessThan:
if (TypeOf(field_) == Type_Date || TypeOf(field_) == Type_Time || TypeOf(field_) == Type_Rating)
return col + " < " + value;
else
return col + " < '" + value + "'";
case Op_NumericDate:
return col + " > " + "DATETIME('now', '-" + value + " " + date + "', 'localtime')";
case Op_NumericDateNot:
return col + " < " + "DATETIME('now', '-" + value + " " + date + "', 'localtime')";
case Op_RelativeDate:
// Consider the time range before the first date but after the second one
return "(" + col + " < " + "DATETIME('now', '-" + value + " " + date + "', 'localtime') AND " + col + " > " + "DATETIME('now', '-" + second_value + " " + date + "', 'localtime'))";
case Op_NotEquals:
if (TypeOf(field_) == Type_Text) {
return col + " <> '" + value + "'";
}
else {
return col + " <> " + value;
}
case Op_Empty:
return col + " = ''";
case Op_NotEmpty:
return col + " <> ''";
}
return QString();
}
bool SmartPlaylistSearchTerm::is_valid() const {
// We can accept also a zero value in these cases
if (operator_ == SmartPlaylistSearchTerm::Op_NumericDate) {
return value_.toInt() >= 0;
}
else if (operator_ == SmartPlaylistSearchTerm::Op_RelativeDate) {
return (value_.toInt() >= 0 && value_.toInt() < second_value_.toInt());
}
switch (TypeOf(field_)) {
case Type_Text:
if (operator_ == SmartPlaylistSearchTerm::Op_Empty || operator_ == SmartPlaylistSearchTerm::Op_NotEmpty) {
return true;
}
// Empty fields should be possible.
// All values for Type_Text should be valid.
return !value_.toString().isEmpty();
case Type_Date:
return value_.toInt() != 0;
case Type_Number:
return value_.toInt() >= 0;
case Type_Time:
return true;
case Type_Rating:
return value_.toFloat() >= 0.0;
case Type_Invalid:
return false;
}
return false;
}
bool SmartPlaylistSearchTerm::operator==(const SmartPlaylistSearchTerm &other) const {
return field_ == other.field_ && operator_ == other.operator_ &&
value_ == other.value_ && date_ == other.date_ &&
second_value_ == other.second_value_;
}
SmartPlaylistSearchTerm::Type SmartPlaylistSearchTerm::TypeOf(const Field field) {
switch (field) {
case Field_Length:
return Type_Time;
case Field_Track:
case Field_Disc:
case Field_Year:
case Field_OriginalYear:
case Field_Filesize:
case Field_PlayCount:
case Field_SkipCount:
case Field_Samplerate:
case Field_Bitdepth:
case Field_Bitrate:
return Type_Number;
case Field_LastPlayed:
case Field_DateCreated:
case Field_DateModified:
return Type_Date;
case Field_Rating:
return Type_Rating;
default:
return Type_Text;
}
}
OperatorList SmartPlaylistSearchTerm::OperatorsForType(const Type type) {
switch (type) {
case Type_Text:
return OperatorList() << Op_Contains << Op_NotContains << Op_Equals
<< Op_NotEquals << Op_Empty << Op_NotEmpty
<< Op_StartsWith << Op_EndsWith;
case Type_Date:
return OperatorList() << Op_Equals << Op_NotEquals << Op_GreaterThan
<< Op_LessThan << Op_NumericDate
<< Op_NumericDateNot << Op_RelativeDate;
default:
return OperatorList() << Op_Equals << Op_NotEquals << Op_GreaterThan
<< Op_LessThan;
}
}
QString SmartPlaylistSearchTerm::OperatorText(const Type type, const Operator op) {
if (type == Type_Date) {
switch (op) {
case Op_GreaterThan:
return QObject::tr("after");
case Op_LessThan:
return QObject::tr("before");
case Op_Equals:
return QObject::tr("on");
case Op_NotEquals:
return QObject::tr("not on");
case Op_NumericDate:
return QObject::tr("in the last");
case Op_NumericDateNot:
return QObject::tr("not in the last");
case Op_RelativeDate:
return QObject::tr("between");
default:
return QString();
}
}
switch (op) {
case Op_Contains:
return QObject::tr("contains");
case Op_NotContains:
return QObject::tr("does not contain");
case Op_StartsWith:
return QObject::tr("starts with");
case Op_EndsWith:
return QObject::tr("ends with");
case Op_GreaterThan:
return QObject::tr("greater than");
case Op_LessThan:
return QObject::tr("less than");
case Op_Equals:
return QObject::tr("equals");
case Op_NotEquals:
return QObject::tr("not equals");
case Op_Empty:
return QObject::tr("empty");
case Op_NotEmpty:
return QObject::tr("not empty");
default:
return QString();
}
return QString();
}
QString SmartPlaylistSearchTerm::FieldColumnName(const Field field) {
switch (field) {
case Field_AlbumArtist:
return "albumartist";
case Field_Artist:
return "artist";
case Field_Album:
return "album";
case Field_Title:
return "title";
case Field_Track:
return "track";
case Field_Disc:
return "disc";
case Field_Year:
return "year";
case Field_OriginalYear:
return "originalyear";
case Field_Genre:
return "genre";
case Field_Composer:
return "composer";
case Field_Performer:
return "performer";
case Field_Grouping:
return "grouping";
case Field_Comment:
return "comment";
case Field_Length:
return "length";
case Field_Filepath:
return "filename";
case Field_Filetype:
return "filetype";
case Field_Filesize:
return "filesize";
case Field_DateCreated:
return "ctime";
case Field_DateModified:
return "mtime";
case Field_PlayCount:
return "playcount";
case Field_SkipCount:
return "skipcount";
case Field_LastPlayed:
return "lastplayed";
case Field_Rating:
return "rating";
case Field_Samplerate:
return "samplerate";
case Field_Bitdepth:
return "bitdepth";
case Field_Bitrate:
return "bitrate";
case FieldCount:
Q_ASSERT(0);
}
return QString();
}
QString SmartPlaylistSearchTerm::FieldName(const Field field) {
switch (field) {
case Field_AlbumArtist:
return Playlist::column_name(Playlist::Column_AlbumArtist);
case Field_Artist:
return Playlist::column_name(Playlist::Column_Artist);
case Field_Album:
return Playlist::column_name(Playlist::Column_Album);
case Field_Title:
return Playlist::column_name(Playlist::Column_Title);
case Field_Track:
return Playlist::column_name(Playlist::Column_Track);
case Field_Disc:
return Playlist::column_name(Playlist::Column_Disc);
case Field_Year:
return Playlist::column_name(Playlist::Column_Year);
case Field_OriginalYear:
return Playlist::column_name(Playlist::Column_OriginalYear);
case Field_Genre:
return Playlist::column_name(Playlist::Column_Genre);
case Field_Composer:
return Playlist::column_name(Playlist::Column_Composer);
case Field_Performer:
return Playlist::column_name(Playlist::Column_Performer);
case Field_Grouping:
return Playlist::column_name(Playlist::Column_Grouping);
case Field_Comment:
return QObject::tr("Comment");
case Field_Length:
return Playlist::column_name(Playlist::Column_Length);
case Field_Filepath:
return Playlist::column_name(Playlist::Column_Filename);
case Field_Filetype:
return Playlist::column_name(Playlist::Column_Filetype);
case Field_Filesize:
return Playlist::column_name(Playlist::Column_Filesize);
case Field_DateCreated:
return Playlist::column_name(Playlist::Column_DateCreated);
case Field_DateModified:
return Playlist::column_name(Playlist::Column_DateModified);
case Field_PlayCount:
return Playlist::column_name(Playlist::Column_PlayCount);
case Field_SkipCount:
return Playlist::column_name(Playlist::Column_SkipCount);
case Field_LastPlayed:
return Playlist::column_name(Playlist::Column_LastPlayed);
case Field_Rating:
return Playlist::column_name(Playlist::Column_Rating);
case Field_Samplerate:
return Playlist::column_name(Playlist::Column_Samplerate);
case Field_Bitdepth:
return Playlist::column_name(Playlist::Column_Bitdepth);
case Field_Bitrate:
return Playlist::column_name(Playlist::Column_Bitrate);
case FieldCount:
Q_ASSERT(0);
}
return QString();
}
QString SmartPlaylistSearchTerm::FieldSortOrderText(const Type type, const bool ascending) {
switch (type) {
case Type_Text:
return ascending ? QObject::tr("A-Z") : QObject::tr("Z-A");
case Type_Date:
return ascending ? QObject::tr("oldest first") : QObject::tr("newest first");
case Type_Time:
return ascending ? QObject::tr("shortest first") : QObject::tr("longest first");
case Type_Number:
case Type_Rating:
return ascending ? QObject::tr("smallest first") : QObject::tr("biggest first");
case Type_Invalid:
return QString();
}
return QString();
}
QString SmartPlaylistSearchTerm::DateName(const DateType date, const bool forQuery) {
// If forQuery is true, untranslated keywords are returned
switch (date) {
case Date_Hour:
return (forQuery ? "hours" : QObject::tr("Hours"));
case Date_Day:
return (forQuery ? "days" : QObject::tr("Days"));
case Date_Week:
return (forQuery ? "weeks" : QObject::tr("Weeks"));
case Date_Month:
return (forQuery ? "months" : QObject::tr("Months"));
case Date_Year:
return (forQuery ? "years" : QObject::tr("Years"));
}
return QString();
}
QDataStream &operator<<(QDataStream &s, const SmartPlaylistSearchTerm &term) {
s << quint8(term.field_);
s << quint8(term.operator_);
s << term.value_;
s << term.second_value_;
s << quint8(term.date_);
return s;
}
QDataStream &operator>>(QDataStream &s, SmartPlaylistSearchTerm &term) {
quint8 field, op, date;
s >> field >> op >> term.value_ >> term.second_value_ >> date;
term.field_ = SmartPlaylistSearchTerm::Field(field);
term.operator_ = SmartPlaylistSearchTerm::Operator(op);
term.date_ = SmartPlaylistSearchTerm::DateType(date);
return s;
}

View File

@@ -0,0 +1,141 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSEARCHTERM_H
#define SMARTPLAYLISTSEARCHTERM_H
#include "config.h"
#include <QList>
#include <QDataStream>
#include <QVariant>
#include <QString>
class SmartPlaylistSearchTerm {
public:
// These values are persisted, so add to the end of the enum only
enum Field {
Field_AlbumArtist = 0,
Field_Artist,
Field_Album,
Field_Title,
Field_Track,
Field_Disc,
Field_Year,
Field_OriginalYear,
Field_Genre,
Field_Composer,
Field_Performer,
Field_Grouping,
Field_Comment,
Field_Length,
Field_Filepath,
Field_Filetype,
Field_Filesize,
Field_DateCreated,
Field_DateModified,
Field_PlayCount,
Field_SkipCount,
Field_LastPlayed,
Field_Rating,
Field_Samplerate,
Field_Bitdepth,
Field_Bitrate,
FieldCount
};
// These values are persisted, so add to the end of the enum only
enum Operator {
// For text
Op_Contains = 0,
Op_NotContains = 1,
Op_StartsWith = 2,
Op_EndsWith = 3,
// For numbers
Op_GreaterThan = 4,
Op_LessThan = 5,
// For everything
Op_Equals = 6,
Op_NotEquals = 9,
// For numeric dates (e.g. in the last X days)
Op_NumericDate = 7,
// For relative dates
Op_RelativeDate = 8,
// For numeric dates (e.g. not in the last X days)
Op_NumericDateNot = 10,
Op_Empty = 11,
Op_NotEmpty = 12,
// Next value = 13
};
enum Type {
Type_Text,
Type_Date,
Type_Time,
Type_Number,
Type_Rating,
Type_Invalid
};
// These values are persisted, so add to the end of the enum only
enum DateType {
Date_Hour = 0,
Date_Day,
Date_Week,
Date_Month, Date_Year
};
explicit SmartPlaylistSearchTerm();
explicit SmartPlaylistSearchTerm(const Field field, const Operator op, const QVariant &value);
Field field_;
Operator operator_;
QVariant value_;
DateType date_;
// For relative dates, we need a second parameter, might be useful somewhere else
QVariant second_value_;
QString ToSql() const;
bool is_valid() const;
bool operator==(const SmartPlaylistSearchTerm &other) const;
bool operator!=(const SmartPlaylistSearchTerm &other) const { return !(*this == other); }
static Type TypeOf(const Field field);
static QList<Operator> OperatorsForType(const Type type);
static QString OperatorText(const Type type, const Operator op);
static QString FieldName(const Field field);
static QString FieldColumnName(const Field field);
static QString FieldSortOrderText(const Type type, const bool ascending);
static QString DateName(const DateType date, const bool forQuery);
};
typedef QList<SmartPlaylistSearchTerm::Operator> OperatorList;
QDataStream &operator<<(QDataStream &s, const SmartPlaylistSearchTerm &term);
QDataStream &operator>>(QDataStream &s, SmartPlaylistSearchTerm &term);
#endif // SMARTPLAYLISTSEARCHTERM_H

View File

@@ -0,0 +1,511 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QWidget>
#include <QTimer>
#include <QIODevice>
#include <QFile>
#include <QMessageBox>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include <QPalette>
#include <QDateTime>
#include <QPropertyAnimation>
#include <QtDebug>
#include <QKeyEvent>
#include <QEnterEvent>
#include "core/utilities.h"
#include "core/iconloader.h"
#include "playlist/playlist.h"
#include "playlist/playlistdelegates.h"
#include "smartplaylistsearchterm.h"
#include "smartplaylistsearchtermwidget.h"
#include "ui_smartplaylistsearchtermwidget.h"
// Exported by QtGui
void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0);
class SmartPlaylistSearchTermWidget::Overlay : public QWidget {
public:
explicit Overlay(SmartPlaylistSearchTermWidget *parent);
void Grab();
void SetOpacity(const float opacity);
float opacity() const { return opacity_; }
static const int kSpacing;
static const int kIconSize;
protected:
void paintEvent(QPaintEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void keyReleaseEvent(QKeyEvent *e) override;
private:
SmartPlaylistSearchTermWidget *parent_;
float opacity_;
QString text_;
QPixmap pixmap_;
QPixmap icon_;
};
const int SmartPlaylistSearchTermWidget::Overlay::kSpacing = 6;
const int SmartPlaylistSearchTermWidget::Overlay::kIconSize = 22;
SmartPlaylistSearchTermWidget::SmartPlaylistSearchTermWidget(CollectionBackend* collection, QWidget* parent)
: QWidget(parent),
ui_(new Ui_SmartPlaylistSearchTermWidget),
collection_(collection),
overlay_(nullptr),
animation_(new QPropertyAnimation(this, "overlay_opacity", this)),
active_(true),
initialized_(false),
current_field_type_(SmartPlaylistSearchTerm::Type_Invalid) {
ui_->setupUi(this);
connect(ui_->field, SIGNAL(currentIndexChanged(int)), SLOT(FieldChanged(int)));
connect(ui_->op, SIGNAL(currentIndexChanged(int)), SLOT(OpChanged(int)));
connect(ui_->remove, SIGNAL(clicked()), SIGNAL(RemoveClicked()));
connect(ui_->value_date, SIGNAL(dateChanged(QDate)), SIGNAL(Changed()));
connect(ui_->value_number, SIGNAL(valueChanged(int)), SIGNAL(Changed()));
connect(ui_->value_text, SIGNAL(textChanged(QString)), SIGNAL(Changed()));
connect(ui_->value_time, SIGNAL(timeChanged(QTime)), SIGNAL(Changed()));
connect(ui_->value_date_numeric, SIGNAL(valueChanged(int)), SIGNAL(Changed()));
connect(ui_->value_date_numeric1, SIGNAL(valueChanged(int)), SLOT(RelativeValueChanged()));
connect(ui_->value_date_numeric2, SIGNAL(valueChanged(int)), SLOT(RelativeValueChanged()));
connect(ui_->date_type, SIGNAL(currentIndexChanged(int)), SIGNAL(Changed()));
connect(ui_->date_type_relative, SIGNAL(currentIndexChanged(int)), SIGNAL(Changed()));
connect(ui_->value_rating, SIGNAL(RatingChanged(float)), SIGNAL(Changed()));
ui_->value_date->setDate(QDate::currentDate());
// Populate the combo boxes
for (int i = 0; i < SmartPlaylistSearchTerm::FieldCount; ++i) {
ui_->field->addItem(SmartPlaylistSearchTerm::FieldName(SmartPlaylistSearchTerm::Field(i)));
ui_->field->setItemData(i, i);
}
ui_->field->model()->sort(0);
// Populate the date type combo box
for (int i = 0; i < 5; ++i) {
ui_->date_type->addItem(SmartPlaylistSearchTerm::DateName(SmartPlaylistSearchTerm::DateType(i), false));
ui_->date_type->setItemData(i, i);
ui_->date_type_relative->addItem(SmartPlaylistSearchTerm::DateName(SmartPlaylistSearchTerm::DateType(i), false));
ui_->date_type_relative->setItemData(i, i);
}
// Icons on the buttons
ui_->remove->setIcon(IconLoader::Load("list-remove"));
// Set stylesheet
QFile stylesheet_file(":/style/smartplaylistsearchterm.css");
stylesheet_file.open(QIODevice::ReadOnly);
QString stylesheet = QString::fromLatin1(stylesheet_file.readAll());
const QColor base(222, 97, 97, 128);
stylesheet.replace("%light2", Utilities::ColorToRgba(base.lighter(140)));
stylesheet.replace("%light", Utilities::ColorToRgba(base.lighter(120)));
stylesheet.replace("%dark", Utilities::ColorToRgba(base.darker(120)));
stylesheet.replace("%base", Utilities::ColorToRgba(base));
setStyleSheet(stylesheet);
}
SmartPlaylistSearchTermWidget::~SmartPlaylistSearchTermWidget() { delete ui_; }
void SmartPlaylistSearchTermWidget::FieldChanged(int index) {
SmartPlaylistSearchTerm::Field field = SmartPlaylistSearchTerm::Field(ui_->field->itemData(index).toInt());
SmartPlaylistSearchTerm::Type type = SmartPlaylistSearchTerm::TypeOf(field);
// Populate the operator combo box
if (type != current_field_type_) {
ui_->op->clear();
for (SmartPlaylistSearchTerm::Operator op : SmartPlaylistSearchTerm::OperatorsForType(type)) {
const int i = ui_->op->count();
ui_->op->addItem(SmartPlaylistSearchTerm::OperatorText(type, op));
ui_->op->setItemData(i, op);
}
current_field_type_ = type;
}
// Show the correct value editor
QWidget* page = nullptr;
SmartPlaylistSearchTerm::Operator op = static_cast<SmartPlaylistSearchTerm::Operator>(
ui_->op->itemData(ui_->op->currentIndex()).toInt()
);
switch (type) {
case SmartPlaylistSearchTerm::Type_Time:
page = ui_->page_time;
break;
case SmartPlaylistSearchTerm::Type_Number:
page = ui_->page_number;
break;
case SmartPlaylistSearchTerm::Type_Date:
page = ui_->page_date;
break;
case SmartPlaylistSearchTerm::Type_Text:
if (op == SmartPlaylistSearchTerm::Op_Empty || op == SmartPlaylistSearchTerm::Op_NotEmpty) {
page = ui_->page_empty;
}
else {
page = ui_->page_text;
}
break;
case SmartPlaylistSearchTerm::Type_Rating:
page = ui_->page_rating;
break;
case SmartPlaylistSearchTerm::Type_Invalid:
page = nullptr;
break;
}
ui_->value_stack->setCurrentWidget(page);
// Maybe set a tag completer
switch (field) {
case SmartPlaylistSearchTerm::Field_Artist:
new TagCompleter(collection_, Playlist::Column_Artist, ui_->value_text);
break;
case SmartPlaylistSearchTerm::Field_Album:
new TagCompleter(collection_, Playlist::Column_Album, ui_->value_text);
break;
default:
ui_->value_text->setCompleter(nullptr);
}
emit Changed();
}
void SmartPlaylistSearchTermWidget::OpChanged(int idx) {
Q_UNUSED(idx);
// Determine the currently selected operator
SmartPlaylistSearchTerm::Operator op = static_cast<SmartPlaylistSearchTerm::Operator>(
// This uses the operatorss index in the combobox to get its enum value
ui_->op->itemData(ui_->op->currentIndex()).toInt()
);
// We need to change the page only in the following case
if ((ui_->value_stack->currentWidget() == ui_->page_text) || (ui_->value_stack->currentWidget() == ui_->page_empty)) {
QWidget* page = nullptr;
if (op == SmartPlaylistSearchTerm::Op_Empty || op == SmartPlaylistSearchTerm::Op_NotEmpty) {
page = ui_->page_empty;
}
else {
page = ui_->page_text;
}
ui_->value_stack->setCurrentWidget(page);
}
else if (
(ui_->value_stack->currentWidget() == ui_->page_date) ||
(ui_->value_stack->currentWidget() == ui_->page_date_numeric) ||
(ui_->value_stack->currentWidget() == ui_->page_date_relative)
) {
QWidget* page = nullptr;
if (op == SmartPlaylistSearchTerm::Op_NumericDate || op == SmartPlaylistSearchTerm::Op_NumericDateNot) {
page = ui_->page_date_numeric;
}
else if (op == SmartPlaylistSearchTerm::Op_RelativeDate) {
page = ui_->page_date_relative;
}
else {
page = ui_->page_date;
}
ui_->value_stack->setCurrentWidget(page);
}
emit Changed();
}
void SmartPlaylistSearchTermWidget::SetActive(bool active) {
active_ = active;
if (overlay_) {
delete overlay_;
overlay_ = nullptr;
}
ui_->container->setEnabled(active);
if (!active) {
overlay_ = new Overlay(this);
}
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void SmartPlaylistSearchTermWidget::enterEvent(QEnterEvent*) {
#else
void SmartPlaylistSearchTermWidget::enterEvent(QEvent*) {
#endif
if (!overlay_ || !isEnabled()) return;
animation_->stop();
animation_->setEndValue(1.0);
animation_->setDuration(80);
animation_->start();
}
void SmartPlaylistSearchTermWidget::leaveEvent(QEvent*) {
if (!overlay_) return;
animation_->stop();
animation_->setEndValue(0.0);
animation_->setDuration(160);
animation_->start();
}
void SmartPlaylistSearchTermWidget::resizeEvent(QResizeEvent* e) {
QWidget::resizeEvent(e);
if (overlay_ && overlay_->isVisible()) {
QTimer::singleShot(0, this, SLOT(Grab()));
}
}
void SmartPlaylistSearchTermWidget::showEvent(QShowEvent* e) {
QWidget::showEvent(e);
if (overlay_) {
QTimer::singleShot(0, this, SLOT(Grab()));
}
}
void SmartPlaylistSearchTermWidget::Grab() { overlay_->Grab(); }
void SmartPlaylistSearchTermWidget::set_overlay_opacity(float opacity) {
if (overlay_) overlay_->SetOpacity(opacity);
}
float SmartPlaylistSearchTermWidget::overlay_opacity() const {
return overlay_ ? overlay_->opacity() : 0.0;
}
void SmartPlaylistSearchTermWidget::SetTerm(const SmartPlaylistSearchTerm &term) {
ui_->field->setCurrentIndex(ui_->field->findData(term.field_));
ui_->op->setCurrentIndex(ui_->op->findData(term.operator_));
// The value depends on the data type
switch (SmartPlaylistSearchTerm::TypeOf(term.field_)) {
case SmartPlaylistSearchTerm::Type_Text:
if (ui_->value_stack->currentWidget() == ui_->page_empty) {
ui_->value_text->setText("");
} else {
ui_->value_text->setText(term.value_.toString());
}
break;
case SmartPlaylistSearchTerm::Type_Number:
ui_->value_number->setValue(term.value_.toInt());
break;
case SmartPlaylistSearchTerm::Type_Date:
if (ui_->value_stack->currentWidget() == ui_->page_date_numeric) {
ui_->value_date_numeric->setValue(term.value_.toInt());
ui_->date_type->setCurrentIndex(term.date_);
}
else if (ui_->value_stack->currentWidget() == ui_->page_date_relative) {
ui_->value_date_numeric1->setValue(term.value_.toInt());
ui_->value_date_numeric2->setValue(term.second_value_.toInt());
ui_->date_type_relative->setCurrentIndex(term.date_);
}
else if (ui_->value_stack->currentWidget() == ui_->page_date) {
ui_->value_date->setDateTime(QDateTime::fromSecsSinceEpoch(term.value_.toInt()));
}
break;
case SmartPlaylistSearchTerm::Type_Time:
ui_->value_time->setTime(QTime(0, 0).addSecs(term.value_.toInt()));
break;
case SmartPlaylistSearchTerm::Type_Rating:
ui_->value_rating->set_rating(term.value_.toFloat());
break;
case SmartPlaylistSearchTerm::Type_Invalid:
break;
}
}
SmartPlaylistSearchTerm SmartPlaylistSearchTermWidget::Term() const {
const int field = ui_->field->itemData(ui_->field->currentIndex()).toInt();
const int op = ui_->op->itemData(ui_->op->currentIndex()).toInt();
SmartPlaylistSearchTerm ret;
ret.field_ = SmartPlaylistSearchTerm::Field(field);
ret.operator_ = SmartPlaylistSearchTerm::Operator(op);
// The value depends on the data type
const QWidget *value_page = ui_->value_stack->currentWidget();
if (value_page == ui_->page_text) {
ret.value_ = ui_->value_text->text();
}
else if (value_page == ui_->page_empty) {
ret.value_ = "";
}
else if (value_page == ui_->page_number) {
ret.value_ = ui_->value_number->value();
}
else if (value_page == ui_->page_date) {
ret.value_ = ui_->value_date->dateTime().toSecsSinceEpoch();
}
else if (value_page == ui_->page_time) {
ret.value_ = QTime(0, 0).secsTo(ui_->value_time->time());
}
else if (value_page == ui_->page_date_numeric) {
ret.date_ = SmartPlaylistSearchTerm::DateType(ui_->date_type->currentIndex());
ret.value_ = ui_->value_date_numeric->value();
}
else if (value_page == ui_->page_date_relative) {
ret.date_ = SmartPlaylistSearchTerm::DateType(ui_->date_type_relative->currentIndex());
ret.value_ = ui_->value_date_numeric1->value();
ret.second_value_ = ui_->value_date_numeric2->value();
}
else if (value_page == ui_->page_rating) {
ret.value_ = ui_->value_rating->rating();
}
return ret;
}
void SmartPlaylistSearchTermWidget::RelativeValueChanged() {
// Don't check for validity when creating the widget
if (!initialized_) {
initialized_ = true;
return;
}
// Explain the user why he can't proceed
if (ui_->value_date_numeric1->value() >= ui_->value_date_numeric2->value()) {
QMessageBox::warning(this, tr("Strawberry"), tr("The second value must be greater than the first one!"));
}
// Emit the signal in any case, so the Next button will be disabled
emit Changed();
}
SmartPlaylistSearchTermWidget::Overlay::Overlay(SmartPlaylistSearchTermWidget *parent)
: QWidget(parent),
parent_(parent),
opacity_(0.0),
text_(tr("Add search term")),
icon_(IconLoader::Load("list-add").pixmap(kIconSize)) {
raise();
setFocusPolicy(Qt::TabFocus);
}
void SmartPlaylistSearchTermWidget::Overlay::SetOpacity(const float opacity) {
opacity_ = opacity;
update();
}
void SmartPlaylistSearchTermWidget::Overlay::Grab() {
hide();
// Take a "screenshot" of the window
QPixmap pixmap = parent_->grab();
QImage image = pixmap.toImage();
// Blur it
QImage blurred(image.size(), QImage::Format_ARGB32_Premultiplied);
blurred.fill(Qt::transparent);
QPainter blur_painter(&blurred);
qt_blurImage(&blur_painter, image, 10.0, true, false);
blur_painter.end();
pixmap_ = QPixmap::fromImage(blurred);
resize(parent_->size());
show();
update();
}
void SmartPlaylistSearchTermWidget::Overlay::paintEvent(QPaintEvent*) {
QPainter p(this);
// Background
p.fillRect(rect(), palette().window());
// Blurred parent widget
p.setOpacity(0.25 + opacity_ * 0.25);
p.drawPixmap(0, 0, pixmap_);
// Draw a frame
p.setOpacity(1.0);
p.setPen(palette().color(QPalette::Mid));
p.setRenderHint(QPainter::Antialiasing);
p.drawRoundedRect(rect(), 5, 5);
// Geometry
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
const QSize contents_size(kIconSize + kSpacing + fontMetrics().horizontalAdvance(text_), qMax(kIconSize, fontMetrics().height()));
#else
const QSize contents_size(kIconSize + kSpacing + fontMetrics().width(text_), qMax(kIconSize, fontMetrics().height()));
#endif
const QRect contents(QPoint((width() - contents_size.width()) / 2, (height() - contents_size.height()) / 2), contents_size);
const QRect icon(contents.topLeft(), QSize(kIconSize, kIconSize));
const QRect text(icon.right() + kSpacing, icon.top(), contents.width() - kSpacing - kIconSize, contents.height());
// Icon and text
p.setPen(palette().color(QPalette::Text));
p.drawPixmap(icon, icon_);
p.drawText(text, Qt::TextDontClip | Qt::AlignVCenter, text_);
}
void SmartPlaylistSearchTermWidget::Overlay::mouseReleaseEvent(QMouseEvent*) {
emit parent_->Clicked();
}
void SmartPlaylistSearchTermWidget::Overlay::keyReleaseEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Space) emit parent_->Clicked();
}

View File

@@ -0,0 +1,94 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSEARCHTERMWIDGET_H
#define SMARTPLAYLISTSEARCHTERMWIDGET_H
#include "config.h"
#include <QWidget>
#include <QPushButton>
#include "smartplaylistsearchterm.h"
class QPropertyAnimation;
class QEvent;
class QShowEvent;
class QEnterEvent;
class QResizeEvent;
class CollectionBackend;
class Ui_SmartPlaylistSearchTermWidget;
class SmartPlaylistSearchTermWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(float overlay_opacity READ overlay_opacity WRITE set_overlay_opacity)
public:
explicit SmartPlaylistSearchTermWidget(CollectionBackend *collection, QWidget *parent);
~SmartPlaylistSearchTermWidget();
void SetActive(const bool active);
float overlay_opacity() const;
void set_overlay_opacity(const float opacity);
void SetTerm(const SmartPlaylistSearchTerm& term);
SmartPlaylistSearchTerm Term() const;
signals:
void Clicked();
void RemoveClicked();
void Changed();
protected:
void showEvent(QShowEvent*) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEnterEvent*) override;
#else
void enterEvent(QEvent*) override;
#endif
void leaveEvent(QEvent*) override;
void resizeEvent(QResizeEvent*) override;
private slots:
void FieldChanged(const int index);
void OpChanged(const int index);
void RelativeValueChanged();
void Grab();
private:
class Overlay;
friend class Overlay;
Ui_SmartPlaylistSearchTermWidget *ui_;
CollectionBackend *collection_;
Overlay *overlay_;
QPropertyAnimation *animation_;
bool active_;
bool initialized_;
SmartPlaylistSearchTerm::Type current_field_type_;
};
#endif // SMARTPLAYLISTSEARCHTERMWIDGET_H

View File

@@ -0,0 +1,350 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistSearchTermWidget</class>
<widget class="QWidget" name="SmartPlaylistSearchTermWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>38</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="layout_smartplaylistsearchtermwidget">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="layout_frame">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="container">
<layout class="QHBoxLayout" name="layout_container">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="field"/>
</item>
<item>
<widget class="QComboBox" name="op"/>
</item>
<item>
<widget class="QStackedWidget" name="value_stack">
<widget class="QWidget" name="page_text">
<layout class="QVBoxLayout" name="layout_page_text">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="value_text"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_empty">
<layout class="QVBoxLayout" name="layout_page_empty">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="QWidget" name="page_rating">
<layout class="QVBoxLayout" name="layout_page_rating">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="RatingWidget" name="value_rating" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_time">
<layout class="QVBoxLayout" name="layout_page_time">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTimeEdit" name="value_time">
<property name="displayFormat">
<string notr="true">mm:ss</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_number">
<layout class="QVBoxLayout" name="layout_page_number">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSpinBox" name="value_number">
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_date">
<layout class="QVBoxLayout" name="layout_page_date">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QDateEdit" name="value_date">
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_date_numeric">
<layout class="QHBoxLayout" name="layout_page_numeric">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSpinBox" name="value_date_numeric">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999</number>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="date_type"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_date_relative">
<layout class="QHBoxLayout" name="layout_page_date_relative">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSpinBox" name="value_date_numeric1">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_1">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>and</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="value_date_numeric2">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="date_type_relative"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>ago</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RatingWidget</class>
<extends>QWidget</extends>
<header>widgets/ratingwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,45 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSITEM_H
#define SMARTPLAYLISTSITEM_H
#include "config.h"
#include <QByteArray>
#include "core/simpletreeitem.h"
#include "playlistgenerator.h"
class SmartPlaylistsItem : public SimpleTreeItem<SmartPlaylistsItem> {
public:
enum Type {
Type_Root,
Type_SmartPlaylist,
};
SmartPlaylistsItem(SimpleTreeModel<SmartPlaylistsItem> *_model) : SimpleTreeItem<SmartPlaylistsItem>(Type_Root, _model) {}
SmartPlaylistsItem(const Type _type, SmartPlaylistsItem *_parent = nullptr) : SimpleTreeItem<SmartPlaylistsItem>(_type, _parent) {}
PlaylistGenerator::Type smart_playlist_type;
QByteArray smart_playlist_data;
};
#endif // SMARTPLAYLISTSITEM_H

View File

@@ -0,0 +1,312 @@
/*
* Strawberry Music Player
* This code was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QAbstractListModel>
#include <QVariant>
#include <QStringList>
#include <QUrl>
#include <QMimeData>
#include <QSettings>
#include "core/application.h"
#include "core/database.h"
#include "core/logging.h"
#include "core/iconloader.h"
#include "core/simpletreemodel.h"
#include "collection/collectionbackend.h"
#include "playlist/songmimedata.h"
#include "smartplaylistsitem.h"
#include "smartplaylistsmodel.h"
#include "smartplaylistsview.h"
#include "smartplaylistsearch.h"
#include "playlistgenerator.h"
#include "playlistgeneratormimedata.h"
#include "playlistquerygenerator.h"
const char *SmartPlaylistsModel::kSettingsGroup = "SerializedSmartPlaylists";
const char *SmartPlaylistsModel::kSmartPlaylistsMimeType = "application/x-strawberry-smart-playlist-generator";
const int SmartPlaylistsModel::kSmartPlaylistsVersion = 1;
SmartPlaylistsModel::SmartPlaylistsModel(CollectionBackend *backend, QObject *parent)
: SimpleTreeModel<SmartPlaylistsItem>(new SmartPlaylistsItem(this), parent),
backend_(backend),
icon_(IconLoader::Load("view-media-playlist")) {
root_->lazy_loaded = true;
}
SmartPlaylistsModel::~SmartPlaylistsModel() { delete root_; }
void SmartPlaylistsModel::Init() {
default_smart_playlists_ =
SmartPlaylistsModel::DefaultGenerators()
<< (SmartPlaylistsModel::GeneratorList()
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Newest tracks"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(),
SmartPlaylistSearch::Sort_FieldDesc,
SmartPlaylistSearchTerm::Field_DateCreated)
)
)
<< PlaylistGeneratorPtr(new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "50 random tracks"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_Random, SmartPlaylistSearchTerm::Field_Title, 50)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Ever played"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_And, SmartPlaylistSearch::TermList() << SmartPlaylistSearchTerm( SmartPlaylistSearchTerm::Field_PlayCount, SmartPlaylistSearchTerm::Op_GreaterThan, 0), SmartPlaylistSearch::Sort_Random, SmartPlaylistSearchTerm::Field_Title)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Never played"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_And, SmartPlaylistSearch::TermList() << SmartPlaylistSearchTerm(SmartPlaylistSearchTerm::Field_PlayCount, SmartPlaylistSearchTerm::Op_Equals, 0), SmartPlaylistSearch::Sort_Random, SmartPlaylistSearchTerm::Field_Title)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Last played"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldDesc, SmartPlaylistSearchTerm::Field_LastPlayed)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Most played"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldDesc, SmartPlaylistSearchTerm::Field_PlayCount)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("SmartPlaylists", "Favourite tracks"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldDesc, SmartPlaylistSearchTerm::Field_Rating)
)
)
<< PlaylistGeneratorPtr(
new PlaylistQueryGenerator(
QT_TRANSLATE_NOOP("Library", "Least favourite tracks"),
SmartPlaylistSearch(SmartPlaylistSearch::Type_Or, SmartPlaylistSearch::TermList()
<< SmartPlaylistSearchTerm(SmartPlaylistSearchTerm::Field_Rating, SmartPlaylistSearchTerm::Op_LessThan, 0.5)
<< SmartPlaylistSearchTerm(SmartPlaylistSearchTerm::Field_SkipCount, SmartPlaylistSearchTerm::Op_GreaterThan, 4), SmartPlaylistSearch::Sort_FieldDesc, SmartPlaylistSearchTerm::Field_SkipCount)
)
)
)
<< (SmartPlaylistsModel::GeneratorList() << PlaylistGeneratorPtr(new PlaylistQueryGenerator(QT_TRANSLATE_NOOP("SmartPlaylists", "All tracks"), SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldAsc, SmartPlaylistSearchTerm::Field_Artist, -1))))
<< (SmartPlaylistsModel::GeneratorList() << PlaylistGeneratorPtr(new PlaylistQueryGenerator( QT_TRANSLATE_NOOP("SmartPlaylists", "Dynamic random mix"), SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_Random, SmartPlaylistSearchTerm::Field_Title), true)));
QSettings s;
s.beginGroup(kSettingsGroup);
int version = s.value(backend_->songs_table() + "_version", 0).toInt();
// How many defaults do we have to write?
int unwritten_defaults = 0;
for (int i = version; i < default_smart_playlists_.count(); ++i) {
unwritten_defaults += default_smart_playlists_[i].count();
}
// Save the defaults if there are any unwritten ones
if (unwritten_defaults) {
// How many items are stored already?
int playlist_index = s.beginReadArray(backend_->songs_table());
s.endArray();
// Append the new ones
s.beginWriteArray(backend_->songs_table(), playlist_index + unwritten_defaults);
for (; version < default_smart_playlists_.count(); ++version) {
for (PlaylistGeneratorPtr gen : default_smart_playlists_[version]) {
SaveGenerator(&s, playlist_index++, gen);
}
}
s.endArray();
}
s.setValue(backend_->songs_table() + "_version", version);
const int count = s.beginReadArray(backend_->songs_table());
for (int i = 0; i < count; ++i) {
s.setArrayIndex(i);
ItemFromSmartPlaylist(s, false);
}
s.endArray();
s.endGroup();
}
void SmartPlaylistsModel::ItemFromSmartPlaylist(const QSettings &s, const bool notify) {
SmartPlaylistsItem *item = new SmartPlaylistsItem(SmartPlaylistsItem::Type_SmartPlaylist, notify ? nullptr : root_);
item->display_text = tr(qPrintable(s.value("name").toString()));
item->sort_text = item->display_text;
item->smart_playlist_type = PlaylistGenerator::Type(s.value("type").toInt());
item->smart_playlist_data = s.value("data").toByteArray();
item->lazy_loaded = true;
if (notify) item->InsertNotify(root_);
}
void SmartPlaylistsModel::AddGenerator(PlaylistGeneratorPtr gen) {
QSettings s;
s.beginGroup(kSettingsGroup);
// Count the existing items
const int count = s.beginReadArray(backend_->songs_table());
s.endArray();
// Add this one to the end
s.beginWriteArray(backend_->songs_table(), count + 1);
SaveGenerator(&s, count, gen);
// Add it to the model
ItemFromSmartPlaylist(s, true);
s.endArray();
s.endGroup();
}
void SmartPlaylistsModel::UpdateGenerator(const QModelIndex &idx, PlaylistGeneratorPtr gen) {
if (idx.parent() != ItemToIndex(root_)) return;
SmartPlaylistsItem *item = IndexToItem(idx);
if (!item) return;
// Update the config
QSettings s;
s.beginGroup(kSettingsGroup);
// Count the existing items
const int count = s.beginReadArray(backend_->songs_table());
s.endArray();
s.beginWriteArray(backend_->songs_table(), count);
SaveGenerator(&s, idx.row(), gen);
s.endGroup();
// Update the text of the item
item->display_text = gen->name();
item->sort_text = item->display_text;
item->smart_playlist_type = gen->type();
item->smart_playlist_data = gen->Save();
item->ChangedNotify();
}
void SmartPlaylistsModel::DeleteGenerator(const QModelIndex &idx) {
if (idx.parent() != ItemToIndex(root_)) return;
// Remove the item from the tree
root_->DeleteNotify(idx.row());
QSettings s;
s.beginGroup(kSettingsGroup);
// Rewrite all the items to the settings
s.beginWriteArray(backend_->songs_table(), root_->children.count());
int i = 0;
for (SmartPlaylistsItem *item : root_->children) {
s.setArrayIndex(i++);
s.setValue("name", item->display_text);
s.setValue("type", item->smart_playlist_type);
s.setValue("data", item->smart_playlist_data);
}
s.endArray();
s.endGroup();
}
void SmartPlaylistsModel::SaveGenerator(QSettings *s, const int i, PlaylistGeneratorPtr generator) const {
s->setArrayIndex(i);
s->setValue("name", generator->name());
s->setValue("type", generator->type());
s->setValue("data", generator->Save());
}
PlaylistGeneratorPtr SmartPlaylistsModel::CreateGenerator(const QModelIndex &idx) const {
PlaylistGeneratorPtr ret;
const SmartPlaylistsItem *item = IndexToItem(idx);
if (!item || item->type != SmartPlaylistsItem::Type_SmartPlaylist) return ret;
ret = PlaylistGenerator::Create(item->smart_playlist_type);
if (!ret) return ret;
ret->set_name(item->display_text);
ret->set_collection(backend_);
ret->Load(item->smart_playlist_data);
return ret;
}
QVariant SmartPlaylistsModel::data(const QModelIndex &idx, const int role) const {
if (!idx.isValid()) return QVariant();
const SmartPlaylistsItem *item = IndexToItem(idx);
if (!item) return QVariant();
switch (role) {
case Qt::DecorationRole:
return icon_;
case Qt::DisplayRole:
case Qt::ToolTipRole:
return item->DisplayText();
}
return QVariant();
}
void SmartPlaylistsModel::LazyPopulate(SmartPlaylistsItem *parent, const bool signal) {
Q_UNUSED(parent);
Q_UNUSED(signal);
}
QStringList SmartPlaylistsModel::mimeTypes() const {
return QStringList() << "text/uri-list";
}
QMimeData *SmartPlaylistsModel::mimeData(const QModelIndexList &indexes) const {
if (indexes.isEmpty()) return nullptr;
PlaylistGeneratorPtr generator = CreateGenerator(indexes.first());
if (!generator) return nullptr;
PlaylistGeneratorMimeData *data = new PlaylistGeneratorMimeData(generator);
data->setData(kSmartPlaylistsMimeType, QByteArray());
data->name_for_new_playlist_ = this->data(indexes.first()).toString();
return data;
}

View File

@@ -0,0 +1,94 @@
/*
* Strawberry Music Player
* This code was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSMODEL_H
#define SMARTPLAYLISTSMODEL_H
#include "config.h"
#include <QAbstractItemModel>
#include <QList>
#include <QVariant>
#include <QStringList>
#include <QSettings>
#include <QIcon>
#include "core/simpletreemodel.h"
#include "smartplaylistsitem.h"
#include "playlistgenerator_fwd.h"
class Application;
class CollectionBackend;
class QModelIndex;
class QMimeData;
class SmartPlaylistsModel : public SimpleTreeModel<SmartPlaylistsItem> {
Q_OBJECT
public:
explicit SmartPlaylistsModel(CollectionBackend *backend, QObject *parent = nullptr);
~SmartPlaylistsModel();
void Init();
enum Role {
Role_Type = Qt::UserRole + 1,
Role_SortText,
Role_DisplayText,
Role_Smartplaylist,
LastRole
};
typedef QList<PlaylistGeneratorPtr> GeneratorList;
typedef QList<GeneratorList> DefaultGenerators;
PlaylistGeneratorPtr CreateGenerator(const QModelIndex &idx) const;
void AddGenerator(PlaylistGeneratorPtr gen);
void UpdateGenerator(const QModelIndex &idx, PlaylistGeneratorPtr gen);
void DeleteGenerator(const QModelIndex &idx);
private:
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
protected:
void LazyPopulate(SmartPlaylistsItem *item) override { LazyPopulate(item, true); }
void LazyPopulate(SmartPlaylistsItem *item, bool signal);
private:
static const char *kSettingsGroup;
static const char *kSmartPlaylistsMimeType;
static const int kSmartPlaylistsVersion;
void SaveGenerator(QSettings *s, const int i, PlaylistGeneratorPtr generator) const;
void ItemFromSmartPlaylist(const QSettings &s, const bool notify);
private:
CollectionBackend *backend_;
QIcon icon_;
DefaultGenerators default_smart_playlists_;
QList<SmartPlaylistsItem*> items_;
};
#endif // SMARTPLAYLISTSMODEL_H

View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QListView>
#include <QContextMenuEvent>
#include "core/application.h"
#include "core/logging.h"
#include "core/mimedata.h"
#include "core/iconloader.h"
#include "smartplaylistsmodel.h"
#include "smartplaylistsview.h"
#include "smartplaylistwizard.h"
SmartPlaylistsView::SmartPlaylistsView(QWidget *_parent) : QListView(_parent) {
setAttribute(Qt::WA_MacShowFocusRect, false);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragOnly);
setSelectionMode(QAbstractItemView::SingleSelection);
}
SmartPlaylistsView::~SmartPlaylistsView() {}
void SmartPlaylistsView::selectionChanged(const QItemSelection&, const QItemSelection&) {
emit ItemsSelectedChanged();
}
void SmartPlaylistsView::contextMenuEvent(QContextMenuEvent *e) {
emit RightClicked(e->globalPos(), indexAt(e->pos()));
e->accept();
}

View File

@@ -0,0 +1,47 @@
/*
* Strawberry Music Player
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSVIEW_H
#define SMARTPLAYLISTSVIEW_H
#include "config.h"
#include <QListView>
#include <QItemSelection>
#include <QModelIndex>
#include <QPoint>
class SmartPlaylistsView : public QListView {
Q_OBJECT
public:
explicit SmartPlaylistsView(QWidget *parent = nullptr);
~SmartPlaylistsView();
protected:
void selectionChanged(const QItemSelection&, const QItemSelection&) override;
void contextMenuEvent(QContextMenuEvent *e) override;
signals:
void ItemsSelectedChanged();
void RightClicked(QPoint global_pos, QModelIndex idx);
};
#endif // SMARTPLAYLISTSVIEW_H

View File

@@ -0,0 +1,283 @@
/*
* Strawberry Music Player
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QWidget>
#include <QMenu>
#include <QSettings>
#include <QShowEvent>
#include <QContextMenuEvent>
#include "core/application.h"
#include "core/logging.h"
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "collection/collectionbackend.h"
#include "settings/appearancesettingspage.h"
#include "smartplaylistsviewcontainer.h"
#include "smartplaylistsmodel.h"
#include "smartplaylistsview.h"
#include "smartplaylistsearchterm.h"
#include "smartplaylistwizard.h"
#include "playlistquerygenerator.h"
#include "playlistgenerator_fwd.h"
#include "ui_smartplaylistsviewcontainer.h"
SmartPlaylistsViewContainer::SmartPlaylistsViewContainer(Application *app, QWidget *parent)
: QWidget(parent),
ui_(new Ui_SmartPlaylistsViewContainer),
app_(app),
context_menu_(new QMenu(this)),
context_menu_selected_(new QMenu(this)),
action_new_smart_playlist_(nullptr),
action_edit_smart_playlist_(nullptr),
action_delete_smart_playlist_(nullptr),
action_append_to_playlist_(nullptr),
action_replace_current_playlist_(nullptr),
action_open_in_new_playlist_(nullptr),
action_add_to_playlist_enqueue_(nullptr),
action_add_to_playlist_enqueue_next_(nullptr)
{
ui_->setupUi(this);
model_ = new SmartPlaylistsModel(app_->collection_backend(), this);
ui_->view->setModel(model_);
model_->Init();
action_new_smart_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("New smart playlist..."), this, SLOT(NewSmartPlaylist()));
action_append_to_playlist_ = context_menu_selected_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AppendToPlaylist()));
action_replace_current_playlist_ = context_menu_selected_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(ReplaceCurrentPlaylist()));
action_open_in_new_playlist_ = context_menu_selected_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
context_menu_selected_->addSeparator();
action_add_to_playlist_enqueue_ = context_menu_selected_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
action_add_to_playlist_enqueue_next_ = context_menu_selected_->addAction(IconLoader::Load("go-next"), tr("Play next"), this, SLOT(AddToPlaylistEnqueueNext()));
context_menu_selected_->addSeparator();
context_menu_selected_->addSeparator();
context_menu_selected_->addActions(QList<QAction*>() << action_new_smart_playlist_);
action_edit_smart_playlist_ = context_menu_selected_->addAction(IconLoader::Load("edit-rename"), tr("Edit smart playlist..."), this, SLOT(EditSmartPlaylistFromContext()));
action_delete_smart_playlist_ = context_menu_selected_->addAction(IconLoader::Load("edit-delete"), tr("Delete smart playlist"), this, SLOT(DeleteSmartPlaylistFromContext()));
context_menu_selected_->addSeparator();
ui_->new_->setDefaultAction(action_new_smart_playlist_);
ui_->edit_->setIcon(IconLoader::Load("edit-rename"));
ui_->delete_->setIcon(IconLoader::Load("edit-delete"));
connect(ui_->edit_, SIGNAL(clicked()), SLOT(EditSmartPlaylistFromButton()));
connect(ui_->delete_, SIGNAL(clicked()), SLOT(DeleteSmartPlaylistFromButton()));
connect(ui_->view, SIGNAL(ItemsSelectedChanged()), SLOT(ItemsSelectedChanged()));
connect(ui_->view, SIGNAL(doubleClicked(QModelIndex)), SLOT(ItemDoubleClicked(QModelIndex)));
connect(ui_->view, SIGNAL(RightClicked(QPoint, QModelIndex)), SLOT(RightClicked(QPoint, QModelIndex)));
ReloadSettings();
ItemsSelectedChanged();
}
SmartPlaylistsViewContainer::~SmartPlaylistsViewContainer() { delete ui_; }
SmartPlaylistsView *SmartPlaylistsViewContainer::view() const { return ui_->view; }
void SmartPlaylistsViewContainer::showEvent(QShowEvent *e) {
ItemsSelectedChanged();
QWidget::showEvent(e);
}
void SmartPlaylistsViewContainer::ReloadSettings() {
QSettings s;
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
int iconsize = s.value(AppearanceSettingsPage::kIconSizeLeftPanelButtons, 22).toInt();
s.endGroup();
ui_->new_->setIconSize(QSize(iconsize, iconsize));
ui_->delete_->setIconSize(QSize(iconsize, iconsize));
ui_->edit_->setIconSize(QSize(iconsize, iconsize));
}
void SmartPlaylistsViewContainer::ItemsSelectedChanged() {
ui_->edit_->setEnabled(ui_->view->selectionModel()->selectedRows().count() > 0);
ui_->delete_->setEnabled(ui_->view->selectionModel()->selectedRows().count() > 0);
}
void SmartPlaylistsViewContainer::RightClicked(const QPoint &global_pos, const QModelIndex &idx) {
context_menu_index_ = idx;
if (context_menu_index_.isValid()) {
context_menu_selected_->popup(global_pos);
}
else {
context_menu_->popup(global_pos);
}
}
void SmartPlaylistsViewContainer::ReplaceCurrentPlaylist() {
QMimeData *q_mimedata = ui_->view->model()->mimeData(ui_->view->selectionModel()->selectedIndexes());
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
mimedata->clear_first_ = true;
}
emit AddToPlaylist(q_mimedata);
}
void SmartPlaylistsViewContainer::AppendToPlaylist() {
emit AddToPlaylist(ui_->view->model()->mimeData(ui_->view->selectionModel()->selectedIndexes()));
}
void SmartPlaylistsViewContainer::OpenInNewPlaylist() {
QMimeData *q_mimedata = ui_->view->model()->mimeData(ui_->view->selectionModel()->selectedIndexes());
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
mimedata->open_in_new_playlist_ = true;
}
emit AddToPlaylist(q_mimedata);
}
void SmartPlaylistsViewContainer::AddToPlaylistEnqueue() {
QMimeData *q_mimedata = ui_->view->model()->mimeData(ui_->view->selectionModel()->selectedIndexes());
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
mimedata->enqueue_now_ = true;
}
emit AddToPlaylist(q_mimedata);
}
void SmartPlaylistsViewContainer::AddToPlaylistEnqueueNext() {
QMimeData *q_mimedata = ui_->view->model()->mimeData(ui_->view->selectionModel()->selectedIndexes());
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
mimedata->enqueue_next_now_ = true;
}
emit AddToPlaylist(q_mimedata);
}
void SmartPlaylistsViewContainer::NewSmartPlaylist() {
SmartPlaylistWizard *wizard = new SmartPlaylistWizard(app_, app_->collection_backend(), this);
wizard->setAttribute(Qt::WA_DeleteOnClose);
connect(wizard, SIGNAL(accepted()), SLOT(NewSmartPlaylistFinished()));
wizard->show();
}
void SmartPlaylistsViewContainer::EditSmartPlaylist(const QModelIndex &idx) {
if (!idx.isValid()) return;
SmartPlaylistWizard *wizard = new SmartPlaylistWizard(app_, app_->collection_backend(), this);
wizard->setAttribute(Qt::WA_DeleteOnClose);
connect(wizard, SIGNAL(accepted()), SLOT(EditSmartPlaylistFinished()));
wizard->show();
wizard->SetGenerator(model_->CreateGenerator(idx));
}
void SmartPlaylistsViewContainer::EditSmartPlaylistFromContext() {
if (!context_menu_index_.isValid()) return;
EditSmartPlaylist(context_menu_index_);
}
void SmartPlaylistsViewContainer::EditSmartPlaylistFromButton() {
if (ui_->view->selectionModel()->selectedIndexes().count() == 0) return;
EditSmartPlaylist(ui_->view->selectionModel()->selectedIndexes().first());
}
void SmartPlaylistsViewContainer::DeleteSmartPlaylist(const QModelIndex &idx) {
if (!idx.isValid()) return;
model_->DeleteGenerator(idx);
}
void SmartPlaylistsViewContainer::DeleteSmartPlaylistFromContext() {
if (!context_menu_index_.isValid()) return;
DeleteSmartPlaylist(context_menu_index_);
}
void SmartPlaylistsViewContainer::DeleteSmartPlaylistFromButton() {
if (ui_->view->selectionModel()->selectedIndexes().count() == 0) return;
DeleteSmartPlaylist(ui_->view->selectionModel()->selectedIndexes().first());
}
void SmartPlaylistsViewContainer::NewSmartPlaylistFinished() {
SmartPlaylistWizard *wizard = qobject_cast<SmartPlaylistWizard*>(sender());
if (!wizard) return;
disconnect(wizard, SIGNAL(accepted()), this, SLOT(NewSmartPlaylistFinished()));
model_->AddGenerator(wizard->CreateGenerator());
}
void SmartPlaylistsViewContainer::EditSmartPlaylistFinished() {
if (!context_menu_index_.isValid()) return;
const SmartPlaylistWizard *wizard = qobject_cast<SmartPlaylistWizard*>(sender());
if (!wizard) return;
disconnect(wizard, SIGNAL(accepted()), this, SLOT(EditSmartPlaylistFinished()));
model_->UpdateGenerator(context_menu_index_, wizard->CreateGenerator());
}
void SmartPlaylistsViewContainer::ItemDoubleClicked(const QModelIndex &idx) {
QMimeData *q_mimedata = ui_->view->model()->mimeData(QModelIndexList() << idx);
if (MimeData *mimedata = qobject_cast<MimeData*>(q_mimedata)) {
mimedata->from_doubleclick_ = true;
}
emit AddToPlaylist(q_mimedata);
}

View File

@@ -0,0 +1,101 @@
/*
* Strawberry Music Player
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTSVIEWCONTAINER_H
#define SMARTPLAYLISTSVIEWCONTAINER_H
#include "config.h"
#include <QWidget>
#include <QModelIndex>
class QMimeData;
class QMenu;
class QAction;
class QShowEvent;
class Application;
class SmartPlaylistsModel;
class SmartPlaylistsView;
class Ui_SmartPlaylistsViewContainer;
class SmartPlaylistsViewContainer : public QWidget {
Q_OBJECT
public:
explicit SmartPlaylistsViewContainer(Application *app, QWidget *parent = nullptr);
~SmartPlaylistsViewContainer();
SmartPlaylistsView *view() const;
void ReloadSettings();
protected:
void showEvent(QShowEvent *e) override;
private slots:
void ItemsSelectedChanged();
void ItemDoubleClicked(const QModelIndex &idx);
void RightClicked(const QPoint &global_pos, const QModelIndex &idx);
void AppendToPlaylist();
void ReplaceCurrentPlaylist();
void OpenInNewPlaylist();
void AddToPlaylistEnqueue();
void AddToPlaylistEnqueueNext();
void NewSmartPlaylist();
void EditSmartPlaylist(const QModelIndex &idx);
void DeleteSmartPlaylist(const QModelIndex &idx);
void EditSmartPlaylistFromButton();
void DeleteSmartPlaylistFromButton();
void EditSmartPlaylistFromContext();
void DeleteSmartPlaylistFromContext();
void NewSmartPlaylistFinished();
void EditSmartPlaylistFinished();
signals:
void AddToPlaylist(QMimeData *data);
void ItemsSelectedChanged(bool);
private:
Ui_SmartPlaylistsViewContainer *ui_;
Application *app_;
SmartPlaylistsModel *model_;
QMenu *context_menu_;
QMenu *context_menu_selected_;
QAction *action_new_smart_playlist_;
QAction *action_edit_smart_playlist_;
QAction *action_delete_smart_playlist_;
QAction *action_append_to_playlist_;
QAction *action_replace_current_playlist_;
QAction *action_open_in_new_playlist_;
QAction *action_add_to_playlist_enqueue_;
QAction *action_add_to_playlist_enqueue_next_;
QModelIndex context_menu_index_;
};
#endif // SMARTPLAYLISTSVIEWCONTAINER_H

View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistsViewContainer</class>
<widget class="QWidget" name="SmartPlaylistsViewContainer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>415</width>
<height>495</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="new_">
<property name="toolTip">
<string>New smart playlist</string>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="edit_">
<property name="toolTip">
<string>Edit smart playlist</string>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="delete_">
<property name="toolTip">
<string>Delete smart playlist</string>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="spacer_buttons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>70</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="SmartPlaylistsView" name="view" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SmartPlaylistsView</class>
<extends>QWidget</extends>
<header>smartplaylists/smartplaylistsview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,179 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QWizard>
#include <QWizardPage>
#include <QLabel>
#include <QRadioButton>
#include <QVBoxLayout>
#include "core/logging.h"
#include "core/iconloader.h"
#include "smartplaylistquerywizardplugin.h"
#include "smartplaylistwizard.h"
#include "smartplaylistwizardplugin.h"
#include "ui_smartplaylistwizardfinishpage.h"
class SmartPlaylistWizard::TypePage : public QWizardPage {
public:
TypePage(QWidget *parent) : QWizardPage(parent), next_id_(-1) {}
int nextId() const { return next_id_; }
int next_id_;
};
class SmartPlaylistWizard::FinishPage : public QWizardPage {
public:
FinishPage(QWidget *parent) : QWizardPage(parent), ui_(new Ui_SmartPlaylistWizardFinishPage) {
ui_->setupUi(this);
connect(ui_->name, SIGNAL(textChanged(QString)), SIGNAL(completeChanged()));
}
~FinishPage() { delete ui_; }
int nextId() const { return -1; }
bool isComplete() const { return !ui_->name->text().isEmpty(); }
Ui_SmartPlaylistWizardFinishPage *ui_;
};
SmartPlaylistWizard::SmartPlaylistWizard(Application *app, CollectionBackend *collection, QWidget *parent)
: QWizard(parent),
app_(app),
collection_(collection),
type_page_(new TypePage(this)),
finish_page_(new FinishPage(this)),
type_index_(-1) {
setWindowIcon(IconLoader::Load("strawberry"));
setWindowTitle(tr("Smart playlist"));
resize(788, 628);
#ifdef Q_OS_MACOS
// MacStyle leaves an ugly empty space on the left side of the dialog.
setWizardStyle(QWizard::ClassicStyle);
#endif
// Type page
type_page_->setTitle(tr("Playlist type"));
type_page_->setSubTitle(tr("A smart playlist is a dynamic list of songs that come from your collection. There are different types of smart playlist that offer different ways of selecting songs."));
type_page_->setStyleSheet("QRadioButton { font-weight: bold; } QLabel { margin-bottom: 1em; margin-left: 24px; }");
addPage(type_page_);
// Finish page
finish_page_->setTitle(tr("Finish"));
finish_page_->setSubTitle(tr("Choose a name for your smart playlist"));
finish_id_ = addPage(finish_page_);
new QVBoxLayout(type_page_);
AddPlugin(new SmartPlaylistQueryWizardPlugin(app_, collection_, this));
// Skip the type page - remove this when we have more than one type
setStartId(2);
}
SmartPlaylistWizard::~SmartPlaylistWizard() {
qDeleteAll(plugins_);
}
void SmartPlaylistWizard::SetGenerator(PlaylistGeneratorPtr gen) {
// Find the right type and jump to the start page
for (int i = 0; i < plugins_.count(); ++i) {
if (plugins_[i]->type() == gen->type()) {
TypeChanged(i);
// TODO: Put this back in when the setStartId is removed from the ctor next();
break;
}
}
if (type_index_ == -1) {
qLog(Error) << "Plugin was not found for generator type" << gen->type();
return;
}
// Set the name
if (!gen->name().isEmpty()) {
setWindowTitle(windowTitle() + " - " + gen->name());
}
finish_page_->ui_->name->setText(gen->name());
finish_page_->ui_->dynamic->setChecked(gen->is_dynamic());
// Tell the plugin to load
plugins_[type_index_]->SetGenerator(gen);
}
void SmartPlaylistWizard::AddPlugin(SmartPlaylistWizardPlugin *plugin) {
const int index = plugins_.count();
plugins_ << plugin;
plugin->Init(this, finish_id_);
// Create the radio button
QRadioButton *radio_button = new QRadioButton(plugin->name(), type_page_);
QLabel *description = new QLabel(plugin->description(), type_page_);
type_page_->layout()->addWidget(radio_button);
type_page_->layout()->addWidget(description);
connect(radio_button, &QRadioButton::clicked, [this, index]() { TypeChanged(index); } );
if (index == 0) {
radio_button->setChecked(true);
TypeChanged(0);
}
}
void SmartPlaylistWizard::TypeChanged(const int index) {
type_index_ = index;
type_page_->next_id_ = plugins_[type_index_]->start_page();
}
PlaylistGeneratorPtr SmartPlaylistWizard::CreateGenerator() const {
PlaylistGeneratorPtr ret;
if (type_index_ == -1) return ret;
ret = plugins_[type_index_]->CreateGenerator();
if (!ret) return ret;
ret->set_name(finish_page_->ui_->name->text());
ret->set_dynamic(finish_page_->ui_->dynamic->isChecked());
return ret;
}
void SmartPlaylistWizard::initializePage(const int id) {
if (id == finish_id_) {
finish_page_->ui_->dynamic_container->setEnabled(plugins_[type_index_]->is_dynamic());
}
QWizard::initializePage(id);
}

View File

@@ -0,0 +1,68 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTWIZARD_H
#define SMARTPLAYLISTWIZARD_H
#include "config.h"
#include <QWizard>
#include "playlistgenerator_fwd.h"
class Application;
class CollectionBackend;
class SmartPlaylistWizardPlugin;
class SmartPlaylistWizard : public QWizard {
Q_OBJECT
public:
explicit SmartPlaylistWizard(Application *app, CollectionBackend *collection, QWidget *parent);
~SmartPlaylistWizard();
void SetGenerator(PlaylistGeneratorPtr gen);
PlaylistGeneratorPtr CreateGenerator() const;
protected:
void initializePage(const int id);
private:
class TypePage;
class FinishPage;
void AddPlugin(SmartPlaylistWizardPlugin *plugin);
private slots:
void TypeChanged(const int index);
private:
Application *app_;
CollectionBackend *collection_;
TypePage *type_page_;
FinishPage *finish_page_;
int finish_id_;
int type_index_;
QList<SmartPlaylistWizardPlugin*> plugins_;
};
#endif // SMARTPLAYLISTWIZARD_H

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SmartPlaylistWizardFinishPage</class>
<widget class="QWidget" name="SmartPlaylistWizardFinishPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>583</width>
<height>370</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="dynamic_container" native="true">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="dynamic">
<property name="text">
<string>Use dynamic mode</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>In dynamic mode new tracks will be chosen and added to the playlist every time a song finishes.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="indent">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,32 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QObject>
#include <QWizard>
#include "smartplaylistwizardplugin.h"
SmartPlaylistWizardPlugin::SmartPlaylistWizardPlugin(Application *app, CollectionBackend *collection, QObject *parent) : QObject(parent), app_(app), collection_(collection), start_page_(-1) {}
void SmartPlaylistWizardPlugin::Init(QWizard *wizard, const int finish_page_id) {
start_page_ = CreatePages(wizard, finish_page_id);
}

View File

@@ -0,0 +1,60 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef SMARTPLAYLISTWIZARDPLUGIN_H
#define SMARTPLAYLISTWIZARDPLUGIN_H
#include <QObject>
#include "playlistgenerator_fwd.h"
class QWizard;
class Application;
class CollectionBackend;
class SmartPlaylistWizardPlugin : public QObject {
Q_OBJECT
public:
explicit SmartPlaylistWizardPlugin(Application *app, CollectionBackend *collection, QObject *parent);
virtual QString type() const = 0;
virtual QString name() const = 0;
virtual QString description() const = 0;
virtual bool is_dynamic() const { return false; }
int start_page() const { return start_page_; }
virtual void SetGenerator(PlaylistGeneratorPtr gen) = 0;
virtual PlaylistGeneratorPtr CreateGenerator() const = 0;
void Init(QWizard *wizard, const int finish_page_id);
protected:
virtual int CreatePages(QWizard *wizard, const int finish_page_id) = 0;
Application *app_;
CollectionBackend *collection_;
private:
int start_page_;
};
#endif // SMARTPLAYLISTWIZARDPLUGIN_H