Merge branch 'master' into qt5
This commit is contained in:
commit
4273c03132
45
.travis.yml
45
.travis.yml
@ -1,40 +1,13 @@
|
||||
sudo: required
|
||||
language: cpp
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- cmake
|
||||
- gettext
|
||||
- libboost-dev
|
||||
- libboost-serialization-dev
|
||||
- libcdio-dev
|
||||
- libchromaprint-dev
|
||||
- libcrypto++-dev
|
||||
- libechonest-dev
|
||||
- libfftw3-dev
|
||||
- libglew1.5-dev
|
||||
- libgpod-dev
|
||||
- libgstreamer1.0-dev
|
||||
- libgstreamer-plugins-base1.0-dev
|
||||
- liblastfm-dev
|
||||
- libmtp-dev
|
||||
- libplist-dev
|
||||
- libprotobuf-dev
|
||||
- libpulse-dev
|
||||
- libqca2-dev
|
||||
- libqca2-plugin-ossl
|
||||
- libqjson-dev
|
||||
- libqt4-dev
|
||||
- libqt4-opengl-dev
|
||||
- libqtwebkit-dev
|
||||
- libsparsehash-dev
|
||||
- libsqlite3-dev
|
||||
- libtag1-dev
|
||||
- libusbmuxd-dev
|
||||
- protobuf-compiler
|
||||
- qt4-dev-tools
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_script:
|
||||
- sudo docker pull gcr.io/clementine-data/slave-ubuntu-bionic-64
|
||||
|
||||
script:
|
||||
- cd bin
|
||||
- cmake ..
|
||||
- make
|
||||
- >
|
||||
sudo docker run --entrypoint=/bin/bash -v ${TRAVIS_BUILD_DIR}:/build gcr.io/clementine-data/slave-ubuntu-bionic-64
|
||||
-c 'cd /build/bin && cmake .. && make'
|
||||
|
47
README-TURKISH.md
Normal file
47
README-TURKISH.md
Normal file
@ -0,0 +1,47 @@
|
||||
Clementine
|
||||
==========
|
||||
|
||||
Clementine; Windows, Linux ve macOS için bir müzik çalar ve kütüphane organizatörüdür..
|
||||
|
||||
- Website: http://www.clementine-player.org/
|
||||
- Github: https://github.com/clementine-player/Clementine
|
||||
- Buildbot: http://buildbot.clementine-player.org/grid
|
||||
- Son Geliştirici Çıktıları: http://builds.clementine-player.org/
|
||||
|
||||
Hata/İstek Belirtmek
|
||||
--------------------
|
||||
### Yeni bir özellik için istekte bulunmak
|
||||
|
||||
Lütfen:
|
||||
* İstediğiniz özelliğin daha önce eklenip eklenmediğini kontrol edin. (Sürüm notlarından)
|
||||
* Daha önce birisinin bu isteği oluşturmadığından emin olun.
|
||||
* Eğer bu istek daha önce yapılmışsa tekrar yorumlamanız işleri hızlandırmaz. Onun yerine, bu isteğe abone olabilirsiniz. Abone olduğunuzda bu başlık altındaki gelişmelerden haberdar olursunuz.
|
||||
|
||||
### Hata raporlamak
|
||||
|
||||
Lütfen:
|
||||
|
||||
* Son geliştirici çıktısınından (http://builds.clementine-player.org/) hatanın geçerliliğini kontrol edin. (**Dikkat**, Bu çıktılar henüz kararlı çıktılar değildir, bu yüzden doğru çalışmayabilir ve kullanıcı ayarlarınıza zarar verebilir). Eğer her şey doğru çalışıyorsa lütfen hata raporu altında bu hatanın giderildiğini açıklayın.
|
||||
* Kopyalardan kaçınmak için, birinin daha önce bu hatayı belirtmediğinden emin olun.
|
||||
* Eğer hata daha önce belirtilmişse kendi, hatanızın ayrıntılarını yorum olarak veya sizinle aynı hatayla karşılaşanları doğrulayın.
|
||||
* Eğer yukarıdaki iki durum geçerli değilse yeni bir hata raporu açın ve detaylarınızı (İşletim Sistemi, Clementine sürümü, hatanın nasıl meydana geldiğini) mutlaka belirtin.
|
||||
* Hata kodları ve günlük çıktıları için lütfen http://pastebin.com/ kullanın.
|
||||
|
||||
Eğer cevap alamazsanız bu demektir ki, oluşturduğunuz istek/hata raporuna vakit ayıramıyoruz. Yani hatayı yeniden işleyemiyoruz veya bunun için zamanımız yok :o.
|
||||
|
||||
Kaynaktan Derleme
|
||||
-----------------
|
||||
|
||||
Kodu alın (eğer hala almadıysanız):
|
||||
|
||||
git clone https://github.com/clementine-player/Clementine.git && cd Clementine
|
||||
|
||||
Derleyin ve kurun:
|
||||
|
||||
cd bin
|
||||
cmake ..
|
||||
make -j8
|
||||
sudo make install
|
||||
|
||||
Daha fazla bilgi ve bağımlılık listesi için Wikiyi ziyaret edebilirsiniz:
|
||||
https://github.com/clementine-player/Clementine/wiki/Compiling-from-Source
|
@ -1,4 +1,4 @@
|
||||
Clementine
|
||||
Clementine [](https://travis-ci.org/clementine-player/Clementine)
|
||||
==========
|
||||
|
||||
Clementine is a modern music player and library organizer for Windows, Linux and macOS.
|
||||
|
9
dist/clementine.desktop
vendored
9
dist/clementine.desktop
vendored
@ -3,6 +3,7 @@ Version=1.0
|
||||
Type=Application
|
||||
Name=Clementine
|
||||
Name[hi]=क्लेमेंटैन्
|
||||
Name[is]=Klementína
|
||||
Name[sr]=Клементина
|
||||
Name[sr@ijekavian]=Клементина
|
||||
Name[sr@ijekavianlatin]=Klementina
|
||||
@ -11,6 +12,7 @@ GenericName=Clementine Music Player
|
||||
GenericName[ca]=Reproductor de música Clementine
|
||||
GenericName[es]=Reproductor de música Clementine
|
||||
GenericName[hi]=क्लेमेंटैन् संगीत वादक
|
||||
GenericName[is]=Clementine tónlistarspilarinn
|
||||
GenericName[pl]=Odtwarzacz muzyki Clementine
|
||||
GenericName[pt]=Reprodutor de músicas Clementine
|
||||
GenericName[sl]=Predvajalnik glasbe Clementine
|
||||
@ -21,6 +23,7 @@ GenericName[sr@latin]=Klementina muzički plejer
|
||||
Comment=Plays music and last.fm streams
|
||||
Comment[ca]=Reproducció de música i fluxos de Last.fm
|
||||
Comment[es]=Reproducción de música y flujos de Last.fm
|
||||
Comment[is]=Spilar tónlist og streymi frá last.fm
|
||||
Comment[pl]=Odtwarzanie muzyki i strumieni last.fm
|
||||
Comment[pt]=Reprodução de músicas e emissões last.fm
|
||||
Comment[sl]=Predvaja glasbo in pretoke last.fm
|
||||
@ -61,6 +64,7 @@ Name[he]=נגינה
|
||||
Name[hi]=गाना बजाएं
|
||||
Name[hr]=Pokreni reprodukciju
|
||||
Name[hu]=Lejátszás
|
||||
Name[is]=Spila
|
||||
Name[it]=Riproduci
|
||||
Name[ja]=再生
|
||||
Name[kk]=Ойнату
|
||||
@ -108,6 +112,7 @@ Name[he]=השהייה
|
||||
Name[hi]=गाना रोकें
|
||||
Name[hr]=Pauza
|
||||
Name[hu]=Szünet
|
||||
Name[is]=Setja í bið
|
||||
Name[it]=Pausa
|
||||
Name[ja]=一時停止
|
||||
Name[kk]=Аялдату
|
||||
@ -155,6 +160,7 @@ Name[he]=הפסקה
|
||||
Name[hi]=गाना बंद करे
|
||||
Name[hr]=Zaustavi reprodukciju
|
||||
Name[hu]=Leállít
|
||||
Name[is]=Stöðva
|
||||
Name[it]=Ferma
|
||||
Name[ja]=停止
|
||||
Name[kk]=Тоқтату
|
||||
@ -205,6 +211,7 @@ Name[he]=הפסקה אחרי רצועה זו
|
||||
Name[hi]=इस गाने के बाद बंद करे
|
||||
Name[hr]=Zaustavi reprodukciju nakon ove pjesme
|
||||
Name[hu]=Leállítás az aktuális szám után
|
||||
Name[is]=Stöðva eftir þetta lag
|
||||
Name[it]=Ferma dopo questa traccia
|
||||
Name[ja]=このトラック後に停止
|
||||
Name[ko]=이번 트랙 이후 정지
|
||||
@ -252,6 +259,7 @@ Name[he]=הקודם
|
||||
Name[hi]=पिछला गाना
|
||||
Name[hr]=Prijašnje
|
||||
Name[hu]=Előző
|
||||
Name[is]=Fyrra
|
||||
Name[it]=Precedente
|
||||
Name[ja]=前へ
|
||||
Name[lt]=Atgal
|
||||
@ -301,6 +309,7 @@ Name[hi]=अगला गाना
|
||||
Name[hr]=Sljedeće
|
||||
Name[hu]=Következő
|
||||
Name[id]=Berikut
|
||||
Name[is]=Næsta
|
||||
Name[it]=Successivo
|
||||
Name[ja]=次へ
|
||||
Name[ko]=다음
|
||||
|
@ -127,9 +127,6 @@ void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
|
||||
|
||||
void SkydriveService::ListFiles(const QString& folder) {
|
||||
QUrl url(QString(kSkydriveBase) + folder + "/files");
|
||||
QUrlQuery url_query;
|
||||
url_query.addQueryItem("filter", "audio,folders");
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest request(url);
|
||||
AddAuthorizationHeader(&request);
|
||||
|
||||
@ -145,7 +142,9 @@ void SkydriveService::ListFilesFinished(QNetworkReply* reply) {
|
||||
QJsonArray files = json_response["data"].toArray();
|
||||
for (const QJsonValue& f : files) {
|
||||
QJsonObject file = f.toObject();
|
||||
if (file["type"].toString() == "audio") {
|
||||
if (file["type"].toString() == "folder") {
|
||||
ListFiles(file["id"].toString());
|
||||
} else {
|
||||
QString mime_type = GuessMimeTypeForFile(file["name"].toString());
|
||||
QUrl url;
|
||||
url.setScheme("skydrive");
|
||||
@ -164,8 +163,6 @@ void SkydriveService::ListFilesFinished(QNetworkReply* reply) {
|
||||
// Fortunately, just changing the scheme to HTTP works.
|
||||
download_url.setScheme("http");
|
||||
MaybeAddFileToDatabase(song, mime_type, download_url, QString::null);
|
||||
} else if (file["type"].toString() == "folder") {
|
||||
ListFiles(file["id"].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,17 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
class PlaylistListSortFilterModel : public QSortFilterProxyModel {
|
||||
#include <iostream>
|
||||
|
||||
/* This filter proxy will:
|
||||
- Accept all ancestors if at least a single child matches
|
||||
- Accept all children if at least a single ancestor matches
|
||||
|
||||
The tree is then expanded only to the level at which the match occurs
|
||||
*/
|
||||
class PlaylistListFilterProxyModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
explicit PlaylistListSortFilterModel(QObject* parent)
|
||||
explicit PlaylistListFilterProxyModel(QObject* parent)
|
||||
: QSortFilterProxyModel(parent) {}
|
||||
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||
@ -49,6 +57,83 @@ class PlaylistListSortFilterModel : public QSortFilterProxyModel {
|
||||
// deterministic sorting even when two items are named the same.
|
||||
return left.row() < right.row();
|
||||
}
|
||||
|
||||
QList<QModelIndex> expandList;
|
||||
|
||||
void setFilterRegExp(const QRegExp & regExp) {
|
||||
expandList.clear();
|
||||
QSortFilterProxyModel::setFilterRegExp(regExp);
|
||||
}
|
||||
|
||||
void refreshExpanded(QTreeView *tree) {
|
||||
tree->collapseAll();
|
||||
for(QModelIndex sourceIndex : expandList ) {
|
||||
QModelIndex mappedIndex = mapFromSource( sourceIndex );
|
||||
tree->setExpanded( mappedIndex, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Depth first search of all the items
|
||||
bool hasAcceptedChildren(int source_row, const QModelIndex &source_parent) const {
|
||||
QModelIndex item = sourceModel()->index(source_row,0,source_parent);
|
||||
if (!item.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//check if there are children
|
||||
int childCount = item.model()->rowCount(item);
|
||||
if (childCount == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
if (filterAcceptsRowItself(i, item))
|
||||
return true;
|
||||
|
||||
if (hasAcceptedChildren(i, item))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool filterAcceptsRowItself(int source_row, const QModelIndex &source_parent) const {
|
||||
bool rv = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
if(rv) {
|
||||
if(sourceModel()->hasIndex(source_row,0,source_parent)) {
|
||||
QModelIndex idx = sourceModel()->index(source_row,0,source_parent);
|
||||
|
||||
// Bit of a hack to get around the const in this function
|
||||
auto * me = const_cast<PlaylistListFilterProxyModel*>(this);
|
||||
|
||||
QModelIndex pidx = sourceModel()->parent(idx);
|
||||
while(pidx.isValid()) {
|
||||
me->expandList.append(pidx);
|
||||
pidx = sourceModel()->parent(pidx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
|
||||
if (filterAcceptsRowItself(source_row, source_parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//accept if any of the parents is accepted on it's own merits
|
||||
QModelIndex parent = source_parent;
|
||||
while (parent.isValid()) {
|
||||
if (filterAcceptsRowItself(parent.row(), parent.parent()))
|
||||
return true;
|
||||
parent = parent.parent();
|
||||
}
|
||||
|
||||
//accept if any of the children is accepted on it's own merits
|
||||
if (hasAcceptedChildren(source_row, source_parent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
PlaylistListContainer::PlaylistListContainer(QWidget* parent)
|
||||
@ -60,11 +145,12 @@ PlaylistListContainer::PlaylistListContainer(QWidget* parent)
|
||||
action_remove_(new QAction(this)),
|
||||
action_save_playlist_(new QAction(this)),
|
||||
model_(new PlaylistListModel(this)),
|
||||
proxy_(new PlaylistListSortFilterModel(this)),
|
||||
proxy_(new PlaylistListFilterProxyModel(this)),
|
||||
loaded_icons_(false),
|
||||
active_playlist_id_(-1) {
|
||||
ui_->setupUi(this);
|
||||
ui_->tree->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->tree->SetAutoOpen(false);
|
||||
|
||||
action_new_folder_->setText(tr("New folder"));
|
||||
action_remove_->setText(tr("Delete"));
|
||||
@ -91,6 +177,8 @@ PlaylistListContainer::PlaylistListContainer(QWidget* parent)
|
||||
|
||||
model_->invisibleRootItem()->setData(PlaylistListModel::Type_Folder,
|
||||
PlaylistListModel::Role_Type);
|
||||
|
||||
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(SearchTextEdited(QString)));
|
||||
}
|
||||
|
||||
PlaylistListContainer::~PlaylistListContainer() { delete ui_; }
|
||||
@ -128,6 +216,10 @@ void PlaylistListContainer::RecursivelySetIcons(QStandardItem* parent) const {
|
||||
case PlaylistListModel::Type_Playlist:
|
||||
child->setIcon(model_->playlist_icon());
|
||||
break;
|
||||
|
||||
case PlaylistListModel::Type_Track:
|
||||
child->setIcon(model_->track_icon());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,9 +250,7 @@ void PlaylistListContainer::SetApplication(Application* app) {
|
||||
// Get all playlists, even ones that are hidden in the UI.
|
||||
for (const PlaylistBackend::Playlist& p :
|
||||
app->playlist_backend()->GetAllFavoritePlaylists()) {
|
||||
QStandardItem* playlist_item = model_->NewPlaylist(p.name, p.id);
|
||||
QStandardItem* parent_folder = model_->FolderByPath(p.ui_path);
|
||||
parent_folder->appendRow(playlist_item);
|
||||
AddPlaylist(p.id,p.name,true,&p.ui_path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,7 +267,7 @@ void PlaylistListContainer::NewFolderClicked() {
|
||||
}
|
||||
|
||||
void PlaylistListContainer::AddPlaylist(int id, const QString& name,
|
||||
bool favorite) {
|
||||
bool favorite, const QString *ui_path) {
|
||||
if (!favorite) {
|
||||
return;
|
||||
}
|
||||
@ -188,11 +278,18 @@ void PlaylistListContainer::AddPlaylist(int id, const QString& name,
|
||||
return;
|
||||
}
|
||||
|
||||
const QString& ui_path = app_->playlist_manager()->playlist(id)->ui_path();
|
||||
if(ui_path == nullptr)
|
||||
ui_path = &app_->playlist_manager()->playlist(id)->ui_path();
|
||||
|
||||
QStandardItem* playlist_item = model_->NewPlaylist(name, id);
|
||||
QStandardItem* parent_folder = model_->FolderByPath(ui_path);
|
||||
QStandardItem* parent_folder = model_->FolderByPath(*ui_path);
|
||||
parent_folder->appendRow(playlist_item);
|
||||
for (const Song s : app_->playlist_backend()->GetPlaylistSongs(id)) {
|
||||
QStandardItem* track_item = model_->NewTrack(s);
|
||||
track_item->setDragEnabled(false);
|
||||
playlist_item->appendRow(track_item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListContainer::PlaylistRenamed(int id, const QString& new_name) {
|
||||
@ -267,6 +364,19 @@ void PlaylistListContainer::CurrentChanged(Playlist* new_playlist) {
|
||||
ui_->tree->scrollTo(index);
|
||||
}
|
||||
|
||||
void PlaylistListContainer::SearchTextEdited(const QString& text) {
|
||||
QRegExp regexp(text);
|
||||
regexp.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
proxy_->setFilterRegExp(regexp);
|
||||
|
||||
if(regexp.isEmpty()) {
|
||||
ui_->tree->collapseAll();
|
||||
} else {
|
||||
proxy_->refreshExpanded(ui_->tree);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistListContainer::PlaylistPathChanged(int id,
|
||||
const QString& new_path) {
|
||||
// Update the path in the database
|
||||
|
@ -31,6 +31,8 @@ class Playlist;
|
||||
class PlaylistListModel;
|
||||
class Ui_PlaylistListContainer;
|
||||
|
||||
class PlaylistListFilterProxyModel;
|
||||
|
||||
class PlaylistListContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@ -49,6 +51,7 @@ class PlaylistListContainer : public QWidget {
|
||||
void NewFolderClicked();
|
||||
void DeleteClicked();
|
||||
void ItemDoubleClicked(const QModelIndex& index);
|
||||
void SearchTextEdited(const QString& text);
|
||||
|
||||
// From the model
|
||||
void PlaylistPathChanged(int id, const QString& new_path);
|
||||
@ -56,7 +59,7 @@ class PlaylistListContainer : public QWidget {
|
||||
// From the PlaylistManager
|
||||
void PlaylistRenamed(int id, const QString& new_name);
|
||||
// Add playlist if favorite == true
|
||||
void AddPlaylist(int id, const QString& name, bool favorite);
|
||||
void AddPlaylist(int id, const QString& name, bool favorite, const QString *ui_path = nullptr);
|
||||
void RemovePlaylist(int id);
|
||||
void SavePlaylist();
|
||||
void PlaylistFavoriteStateChanged(int id, bool favorite);
|
||||
@ -87,7 +90,7 @@ class PlaylistListContainer : public QWidget {
|
||||
QAction* action_save_playlist_;
|
||||
|
||||
PlaylistListModel* model_;
|
||||
QSortFilterProxyModel* proxy_;
|
||||
PlaylistListFilterProxyModel* proxy_;
|
||||
|
||||
bool loaded_icons_;
|
||||
QIcon padded_play_icon_;
|
||||
|
@ -41,6 +41,13 @@
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSearchField" name="search" native="true">
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Search for anything</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="new_folder">
|
||||
<property name="toolTip">
|
||||
@ -72,19 +79,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<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>
|
||||
@ -125,6 +119,11 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>3rdparty/qocoa/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AutoExpandingTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
|
@ -71,6 +71,12 @@ void PlaylistListModel::AddRowMappings(const QModelIndex& begin,
|
||||
void PlaylistListModel::AddRowItem(QStandardItem* item,
|
||||
const QString& parent_path) {
|
||||
switch (item->data(Role_Type).toInt()) {
|
||||
case Type_Track: {
|
||||
// const int id = item->data(Role_TrackId).toInt();
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
|
||||
case Type_Playlist: {
|
||||
const int id = item->data(Role_PlaylistId).toInt();
|
||||
playlists_by_id_[id] = item;
|
||||
@ -172,6 +178,16 @@ QStandardItem* PlaylistListModel::NewPlaylist(const QString& name,
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStandardItem* PlaylistListModel::NewTrack(const Song& song) const {
|
||||
QStandardItem* ret = new QStandardItem;
|
||||
ret->setText(song.artist() + " - " + song.title());
|
||||
ret->setData(PlaylistListModel::Type_Track, PlaylistListModel::Role_Type);
|
||||
ret->setData(song.id(), PlaylistListModel::Role_TrackId);
|
||||
ret->setIcon(track_icon_);
|
||||
ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PlaylistListModel::setData(const QModelIndex& index, const QVariant& value,
|
||||
int role) {
|
||||
if (!QStandardItemModel::setData(index, value, role)) {
|
||||
|
@ -2,16 +2,16 @@
|
||||
#define PLAYLISTLISTMODEL_H
|
||||
|
||||
#include <QStandardItemModel>
|
||||
|
||||
#include "core/song.h"
|
||||
class PlaylistListModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PlaylistListModel(QObject* parent = nullptr);
|
||||
|
||||
enum Types { Type_Folder, Type_Playlist };
|
||||
enum Types { Type_Folder, Type_Playlist, Type_Track };
|
||||
|
||||
enum Roles { Role_Type = Qt::UserRole, Role_PlaylistId };
|
||||
enum Roles { Role_Type = Qt::UserRole, Role_PlaylistId, Role_TrackId };
|
||||
|
||||
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row,
|
||||
int column, const QModelIndex& parent);
|
||||
@ -19,6 +19,7 @@ class PlaylistListModel : public QStandardItemModel {
|
||||
// These icons will be used for newly created playlists and folders.
|
||||
// The caller will need to set these icons on existing items if there are any.
|
||||
void SetIcons(const QIcon& playlist_icon, const QIcon& folder_icon);
|
||||
const QIcon& track_icon() const { return track_icon_; }
|
||||
const QIcon& playlist_icon() const { return playlist_icon_; }
|
||||
const QIcon& folder_icon() const { return folder_icon_; }
|
||||
|
||||
@ -41,6 +42,9 @@ class PlaylistListModel : public QStandardItemModel {
|
||||
// added to the model yet.
|
||||
QStandardItem* NewPlaylist(const QString& name, int id) const;
|
||||
|
||||
// Returns a new track item. The item isn't added to the model yet.
|
||||
QStandardItem* NewTrack(const Song& song) const;
|
||||
|
||||
// QStandardItemModel
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role);
|
||||
|
||||
@ -61,6 +65,7 @@ signals:
|
||||
private:
|
||||
bool dropping_rows_;
|
||||
|
||||
QIcon track_icon_;
|
||||
QIcon playlist_icon_;
|
||||
QIcon folder_icon_;
|
||||
|
||||
|
@ -128,6 +128,10 @@ QString SearchTerm::ToSql() const {
|
||||
} else {
|
||||
return col + " <> " + value;
|
||||
}
|
||||
case Op_Empty:
|
||||
return col + " = ''";
|
||||
case Op_NotEmpty:
|
||||
return col + " <> ''";
|
||||
}
|
||||
|
||||
return QString();
|
||||
@ -143,6 +147,11 @@ bool SearchTerm::is_valid() const {
|
||||
|
||||
switch (TypeOf(field_)) {
|
||||
case Type_Text:
|
||||
if (operator_ == SearchTerm::Op_Empty || operator_ == SearchTerm::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;
|
||||
@ -199,7 +208,8 @@ OperatorList SearchTerm::OperatorsForType(Type type) {
|
||||
switch (type) {
|
||||
case Type_Text:
|
||||
return OperatorList() << Op_Contains << Op_NotContains << Op_Equals
|
||||
<< Op_NotEquals << Op_StartsWith << Op_EndsWith;
|
||||
<< 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
|
||||
@ -249,6 +259,10 @@ QString SearchTerm::OperatorText(Type type, Operator op) {
|
||||
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();
|
||||
}
|
||||
|
@ -80,7 +80,10 @@ class SearchTerm {
|
||||
// For numeric dates (e.g. not in the last X days)
|
||||
Op_NumericDateNot = 10,
|
||||
|
||||
// Next value = 11
|
||||
Op_Empty = 11,
|
||||
Op_NotEmpty = 12,
|
||||
|
||||
// Next value = 13
|
||||
};
|
||||
|
||||
enum Type {
|
||||
|
@ -149,6 +149,9 @@ void SearchTermWidget::FieldChanged(int index) {
|
||||
|
||||
// Show the correct value editor
|
||||
QWidget* page = nullptr;
|
||||
SearchTerm::Operator op = static_cast<SearchTerm::Operator>(
|
||||
ui_->op->itemData(ui_->op->currentIndex()).toInt()
|
||||
);
|
||||
switch (type) {
|
||||
case SearchTerm::Type_Time:
|
||||
page = ui_->page_time;
|
||||
@ -163,7 +166,11 @@ void SearchTermWidget::FieldChanged(int index) {
|
||||
page = ui_->page_rating;
|
||||
break;
|
||||
case SearchTerm::Type_Text:
|
||||
page = ui_->page_text;
|
||||
if (op == SearchTerm::Op_Empty || op == SearchTerm::Op_NotEmpty) {
|
||||
page = ui_->page_empty;
|
||||
} else {
|
||||
page = ui_->page_text;
|
||||
}
|
||||
break;
|
||||
case SearchTerm::Type_Invalid:
|
||||
page = nullptr;
|
||||
@ -189,20 +196,36 @@ void SearchTermWidget::FieldChanged(int index) {
|
||||
}
|
||||
|
||||
void SearchTermWidget::OpChanged(int index) {
|
||||
// Determine the currently selected operator
|
||||
SearchTerm::Operator op = static_cast<SearchTerm::Operator>(
|
||||
// This uses the operators’s 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_date) ||
|
||||
if ((ui_->value_stack->currentWidget() == ui_->page_text) ||
|
||||
(ui_->value_stack->currentWidget() == ui_->page_empty)) {
|
||||
QWidget* page = nullptr;
|
||||
if (op == SearchTerm::Op_Empty || op == SearchTerm::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 (index == 4 || index == 5) {
|
||||
if (op == SearchTerm::Op_NumericDate || op == SearchTerm::Op_NumericDateNot) {
|
||||
page = ui_->page_date_numeric;
|
||||
} else if (index == 6) {
|
||||
} else if (op == SearchTerm::Op_RelativeDate) {
|
||||
page = ui_->page_date_relative;
|
||||
} else {
|
||||
page = ui_->page_date;
|
||||
}
|
||||
ui_->value_stack->setCurrentWidget(page);
|
||||
}
|
||||
|
||||
emit Changed();
|
||||
}
|
||||
|
||||
@ -267,7 +290,11 @@ void SearchTermWidget::SetTerm(const SearchTerm& term) {
|
||||
// The value depends on the data type
|
||||
switch (SearchTerm::TypeOf(term.field_)) {
|
||||
case SearchTerm::Type_Text:
|
||||
ui_->value_text->setText(term.value_.toString());
|
||||
if (ui_->value_stack->currentWidget() == ui_->page_empty) {
|
||||
ui_->value_text->setText("");
|
||||
} else {
|
||||
ui_->value_text->setText(term.value_.toString());
|
||||
}
|
||||
break;
|
||||
|
||||
case SearchTerm::Type_Number:
|
||||
@ -313,6 +340,8 @@ SearchTerm SearchTermWidget::Term() const {
|
||||
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) {
|
||||
|
@ -71,6 +71,16 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_empty">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_rating">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="spacing">
|
||||
|
Loading…
x
Reference in New Issue
Block a user