Album cover art manager. So far this only displays cover art that was loaded from disk.

This commit is contained in:
David Sansome 2010-02-28 18:04:50 +00:00
parent f9ad923f3a
commit 12273256e5
22 changed files with 946 additions and 20 deletions

View File

@ -62,5 +62,7 @@
<file>schema-1.sql</file>
<file>schema-2.sql</file>
<file>nocover.png</file>
<file>view-choose.png</file>
<file>download.png</file>
</qresource>
</RCC>

BIN
data/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
data/view-choose.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

View File

@ -52,6 +52,8 @@ set(CLEMENTINE-SOURCES
savedradio.cpp
stylesheetloader.cpp
shortcutsdialog.cpp
albumcovermanager.cpp
albumcoverloader.cpp
)
# Header files that have Q_OBJECT in
@ -96,6 +98,8 @@ set(CLEMENTINE-MOC-HEADERS
addstreamdialog.h
savedradio.h
shortcutsdialog.h
albumcovermanager.h
albumcoverloader.h
)
# UI files
@ -114,6 +118,7 @@ set(CLEMENTINE-UI
about.ui
addstreamdialog.ui
shortcutsdialog.ui
albumcovermanager.ui
)
# Resource files

70
src/albumcoverloader.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "albumcoverloader.h"
#include <QPainter>
AlbumCoverLoader::AlbumCoverLoader(QObject* parent)
: QObject(parent),
height_(120),
next_id_(0)
{
}
void AlbumCoverLoader::Clear() {
QMutexLocker l(&mutex_);
tasks_.clear();
}
quint64 AlbumCoverLoader::LoadImageAsync(const QString& art_automatic,
const QString& art_manual) {
Task task;
task.art_automatic = art_automatic;
task.art_manual = art_manual;
{
QMutexLocker l(&mutex_);
task.id = next_id_ ++;
tasks_.enqueue(task);
}
metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
return task.id;
}
void AlbumCoverLoader::ProcessTasks() {
forever {
// Get the next task
Task task;
{
QMutexLocker l(&mutex_);
if (tasks_.isEmpty())
return;
task = tasks_.dequeue();
}
// Try to load the image
QImage image;
if (!task.art_manual.isEmpty())
image.load(task.art_manual);
if (!task.art_automatic.isEmpty() && image.isNull())
image.load(task.art_automatic);
if (!image.isNull()) {
// Scale the image down
image = image.scaled(QSize(height_, height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
// Pad the image to height_ x height_
QImage bigger_image(height_, height_, QImage::Format_ARGB32);
bigger_image.fill(0);
QPainter p(&bigger_image);
p.drawImage((height_ - image.width()) / 2, (height_ - image.height()) / 2,
image);
p.end();
image = bigger_image;
}
emit ImageLoaded(task.id, image);
}
}

42
src/albumcoverloader.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef ALBUMCOVERLOADER_H
#define ALBUMCOVERLOADER_H
#include "backgroundthread.h"
#include <QObject>
#include <QImage>
#include <QMutex>
#include <QQueue>
class AlbumCoverLoader : public QObject {
Q_OBJECT
public:
AlbumCoverLoader(QObject* parent = 0);
void SetDesiredHeight(int height) { height_ = height; }
quint64 LoadImageAsync(const QString& art_automatic, const QString& art_manual);
void Clear();
signals:
void ImageLoaded(quint64 id, const QImage& image);
private slots:
void ProcessTasks();
private:
struct Task {
quint64 id;
QString art_automatic;
QString art_manual;
};
int height_;
QMutex mutex_;
QQueue<Task> tasks_;
quint64 next_id_;
};
#endif // ALBUMCOVERLOADER_H

160
src/albumcovermanager.cpp Normal file
View File

@ -0,0 +1,160 @@
#include "albumcovermanager.h"
#include "librarybackend.h"
#include "libraryquery.h"
#include <QSettings>
#include <QPainter>
#include <QMenu>
#include <QActionGroup>
const char* AlbumCoverManager::kSettingsGroup = "CoverManager";
AlbumCoverManager::AlbumCoverManager(QWidget *parent)
: QDialog(parent),
cover_loader_(new BackgroundThread<AlbumCoverLoader>(this)),
artist_icon_(":/artist.png"),
all_artists_icon_(":/album.png")
{
ui_.setupUi(this);
// Get a square version of nocover.png
QImage nocover(":/nocover.png");
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage square_nocover(120, 120, QImage::Format_ARGB32);
square_nocover.fill(0);
QPainter p(&square_nocover);
p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover);
p.end();
no_cover_icon_ = QPixmap::fromImage(square_nocover);
// View menu
QActionGroup* filter_group = new QActionGroup(this);
filter_all_ = filter_group->addAction("All albums");
filter_with_covers_ = filter_group->addAction("Albums with covers");
filter_without_covers_ = filter_group->addAction("Albums without covers");
filter_all_->setCheckable(true);
filter_with_covers_->setCheckable(true);
filter_without_covers_->setCheckable(true);
filter_group->setExclusive(true);
filter_all_->setChecked(true);
QMenu* view_menu = new QMenu(this);
view_menu->addActions(filter_group->actions());
ui_.view->setMenu(view_menu);
// Connections
connect(cover_loader_, SIGNAL(Initialised()), SLOT(CoverLoaderInitialised()));
connect(ui_.artists, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
SLOT(ArtistChanged(QListWidgetItem*)));
connect(ui_.filter, SIGNAL(textChanged(QString)), SLOT(UpdateFilter()));
connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter()));
connect(ui_.view, SIGNAL(clicked()), ui_.view, SLOT(showMenu()));
// Restore settings
QSettings s;
s.beginGroup(kSettingsGroup);
restoreGeometry(s.value("geometry").toByteArray());
if (!ui_.splitter->restoreState(s.value("splitter_state").toByteArray())) {
// Sensible default size for the artists view
ui_.splitter->setSizes(QList<int>() << 200 << width() - 200);
}
cover_loader_->start();
}
void AlbumCoverManager::CoverLoaderInitialised() {
connect(cover_loader_->Worker().get(), SIGNAL(ImageLoaded(quint64,QImage)),
SLOT(CoverImageLoaded(quint64,QImage)));
}
void AlbumCoverManager::SetBackend(boost::shared_ptr<LibraryBackend> backend) {
backend_ = backend;
if (isVisible())
Reset();
}
void AlbumCoverManager::showEvent(QShowEvent *) {
Reset();
}
void AlbumCoverManager::closeEvent(QCloseEvent *) {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("geometry", saveGeometry());
s.setValue("splitter_state", ui_.splitter->saveState());
}
void AlbumCoverManager::Reset() {
if (!backend_)
return;
ui_.artists->clear();
new QListWidgetItem(all_artists_icon_, "All artists", ui_.artists, All_Artists);
foreach (const QString& artist, backend_->GetAllArtists()) {
if (artist.isEmpty())
continue;
new QListWidgetItem(artist_icon_, artist, ui_.artists, Specific_Artist);
}
}
void AlbumCoverManager::ArtistChanged(QListWidgetItem* current) {
if (!backend_ || !cover_loader_->Worker())
return;
if (!current)
return;
QString artist;
if (current->type() == Specific_Artist)
artist = current->text();
ui_.albums->clear();
cover_loading_tasks_.clear();
cover_loader_->Worker()->Clear();
foreach (const LibraryBackend::AlbumArtInfo& info, backend_->GetAlbumArtInfo(artist)) {
QListWidgetItem* item = new QListWidgetItem(no_cover_icon_, info.album_name, ui_.albums);
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
quint64 id = cover_loader_->Worker()->LoadImageAsync(
info.art_automatic, info.art_manual);
cover_loading_tasks_[id] = item;
}
}
UpdateFilter();
}
void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) {
if (!cover_loading_tasks_.contains(id))
return;
QListWidgetItem* item = cover_loading_tasks_.take(id);
if (image.isNull())
return;
item->setIcon(QPixmap::fromImage(image));
UpdateFilter();
}
void AlbumCoverManager::UpdateFilter() {
const QString filter = ui_.filter->text().toLower();
const bool hide_with_covers = filter_without_covers_->isChecked();
const bool hide_without_covers = filter_with_covers_->isChecked();
for (int i=0 ; i<ui_.albums->count() ; ++i) {
QListWidgetItem* item = ui_.albums->item(i);
QString text = item->text();
bool has_cover = item->icon().cacheKey() != no_cover_icon_.cacheKey();
item->setHidden((!filter.isEmpty() && !text.toLower().contains(filter)) ||
(has_cover && hide_with_covers) ||
(!has_cover && hide_without_covers));
}
}

59
src/albumcovermanager.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef ALBUMCOVERMANAGER_H
#define ALBUMCOVERMANAGER_H
#include <QDialog>
#include <QIcon>
#include <boost/shared_ptr.hpp>
#include "ui_covermanager.h"
#include "backgroundthread.h"
#include "albumcoverloader.h"
class LibraryBackend;
class AlbumCoverManager : public QDialog {
Q_OBJECT
public:
AlbumCoverManager(QWidget *parent = 0);
static const char* kSettingsGroup;
void Reset();
public slots:
void SetBackend(boost::shared_ptr<LibraryBackend> backend);
protected:
void showEvent(QShowEvent *);
void closeEvent(QCloseEvent *);
private slots:
void ArtistChanged(QListWidgetItem* current);
void CoverLoaderInitialised();
void CoverImageLoaded(quint64 id, const QImage& image);
void UpdateFilter();
private:
enum ArtistItemType {
All_Artists,
Specific_Artist,
};
private:
Ui::CoverManager ui_;
boost::shared_ptr<LibraryBackend> backend_;
QAction* filter_all_;
QAction* filter_with_covers_;
QAction* filter_without_covers_;
BackgroundThread<AlbumCoverLoader>* cover_loader_;
QMap<quint64, QListWidgetItem*> cover_loading_tasks_;
QIcon artist_icon_;
QIcon all_artists_icon_;
QIcon no_cover_icon_;
};
#endif // ALBUMCOVERMANAGER_H

175
src/albumcovermanager.ui Normal file
View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CoverManager</class>
<widget class="QDialog" name="CoverManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>827</width>
<height>662</height>
</rect>
</property>
<property name="windowTitle">
<string>Cover Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListWidget" name="artists">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="clear">
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/clear.png</normaloff>:/clear.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="LineEdit" name="filter">
<property name="hint" stdset="0">
<string>Enter search terms here</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="view">
<property name="text">
<string>View</string>
</property>
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/view-choose.png</normaloff>:/view-choose.png</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fetch">
<property name="text">
<string>Fetch Missing Covers</string>
</property>
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/download.png</normaloff>:/download.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="albums">
<property name="alternatingRowColors">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>120</width>
<height>120</height>
</size>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="flow">
<enum>QListView::LeftToRight</enum>
</property>
<property name="isWrapping" stdset="0">
<bool>true</bool>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="spacing">
<number>2</number>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>lineedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../data/data.qrc"/>
</resources>
<connections>
<connection>
<sender>clear</sender>
<signal>clicked()</signal>
<receiver>filter</receiver>
<slot>clear()</slot>
<hints>
<hint type="sourcelabel">
<x>329</x>
<y>13</y>
</hint>
<hint type="destinationlabel">
<x>367</x>
<y>14</y>
</hint>
</hints>
</connection>
<connection>
<sender>clear</sender>
<signal>clicked()</signal>
<receiver>filter</receiver>
<slot>setFocus()</slot>
<hints>
<hint type="sourcelabel">
<x>334</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>401</x>
<y>13</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -567,6 +567,10 @@ p, li { white-space: pre-wrap; }
<source>&amp;Hide tray icon</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Configure &amp;Global Shortcuts...</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiLoadingIndicator</name>
@ -768,6 +772,109 @@ p, li { white-space: pre-wrap; }
<source>Show a notification when I change the volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Include album art in the notification</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ShortcutsDialog</name>
<message>
<source>Configure Shortcuts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Play</source>
<translation type="unfinished">Αναπαραγωγή</translation>
</message>
<message>
<source>Pause</source>
<translation type="unfinished">Παύση</translation>
</message>
<message>
<source>Play/Pause</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop</source>
<translation type="unfinished">Σταμάτημα</translation>
</message>
<message>
<source>Stop Playing After Current Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Next Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Previous Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Increase Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Decrease Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Mute Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Forwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Backwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Alternate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Defaults</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut for Selected Action</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>De&amp;fault</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Custom</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Non&amp;e</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Default key:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SomaFMService</name>

View File

@ -550,6 +550,10 @@ p, li { white-space: pre-wrap; }
<source>&amp;Hide tray icon</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Configure &amp;Global Shortcuts...</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiLoadingIndicator</name>
@ -751,6 +755,109 @@ p, li { white-space: pre-wrap; }
<source>Show a notification when I change the volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Include album art in the notification</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ShortcutsDialog</name>
<message>
<source>Configure Shortcuts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Play</source>
<translation type="unfinished">Reproducir</translation>
</message>
<message>
<source>Pause</source>
<translation type="unfinished">Pausa</translation>
</message>
<message>
<source>Play/Pause</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop</source>
<translation type="unfinished">Detener</translation>
</message>
<message>
<source>Stop Playing After Current Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Next Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Previous Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Increase Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Decrease Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Mute Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Forwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Backwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Alternate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Defaults</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut for Selected Action</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>De&amp;fault</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Custom</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Non&amp;e</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Default key:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SomaFMService</name>

View File

@ -545,6 +545,10 @@ p, li { white-space: pre-wrap; }
<source>&amp;Hide tray icon</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Configure &amp;Global Shortcuts...</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MultiLoadingIndicator</name>
@ -746,6 +750,109 @@ p, li { white-space: pre-wrap; }
<source>Show a notification when I change the volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Include album art in the notification</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ShortcutsDialog</name>
<message>
<source>Configure Shortcuts</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Play</source>
<translation type="unfinished">Воспроизвести</translation>
</message>
<message>
<source>Pause</source>
<translation type="unfinished">Пауза</translation>
</message>
<message>
<source>Play/Pause</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Stop</source>
<translation type="unfinished">Стоп</translation>
</message>
<message>
<source>Stop Playing After Current Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Next Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Previous Track</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Increase Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Decrease Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Mute Volume</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Forwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Seek Backwards</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Alternate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Defaults</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;OK</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Cancel</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Shortcut for Selected Action</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;None</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>De&amp;fault</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&amp;Custom</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Non&amp;e</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Default key:</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SomaFMService</name>

View File

@ -45,6 +45,8 @@ void Library::BackendInitialised() {
dir_model_->SetBackend(backend_->Worker());
emit BackendReady(backend_->Worker());
if (--waiting_for_threads_ == 0)
Initialise();
}
@ -339,17 +341,17 @@ void Library::LazyPopulate(LibraryItem* item) {
break;
case LibraryItem::Type_CompilationAlbum:
foreach (const Song& song, backend_->Worker()->GetCompilationSongs(query_options_, item->key))
foreach (const Song& song, backend_->Worker()->GetCompilationSongs(item->key, query_options_))
CreateSongNode(false, song, item);
break;
case LibraryItem::Type_Artist:
foreach (const QString& album, backend_->Worker()->GetAlbumsByArtist(query_options_, item->key))
foreach (const QString& album, backend_->Worker()->GetAlbumsByArtist(item->key, query_options_))
CreateAlbumNode(false, album, item, false);
break;
case LibraryItem::Type_Album:
foreach (const Song& song, backend_->Worker()->GetSongs(query_options_, item->parent->key, item->key))
foreach (const Song& song, backend_->Worker()->GetSongs(item->parent->key, item->key, query_options_))
CreateSongNode(false, song, item);
break;

View File

@ -50,6 +50,8 @@ class Library : public SimpleTreeModel<LibraryItem> {
void ScanStarted();
void ScanFinished();
void BackendReady(boost::shared_ptr<LibraryBackend> backend);
public slots:
void SetFilterAge(int age);
void SetFilterText(const QString& text);

View File

@ -321,11 +321,17 @@ QStringList LibraryBackend::GetAllArtists(const QueryOptions& opt) {
return ret;
}
QStringList LibraryBackend::GetAlbumsByArtist(const QueryOptions& opt, const QString& artist) {
QStringList LibraryBackend::GetAllAlbums(const QueryOptions &opt) {
return GetAlbumsByArtist(QString(), opt);
}
QStringList LibraryBackend::GetAlbumsByArtist(const QString& artist, const QueryOptions& opt) {
LibraryQuery query(opt);
query.SetColumnSpec("DISTINCT album");
query.AddCompilationRequirement(false);
query.AddWhere("artist", artist);
if (!artist.isNull())
query.AddWhere("artist", artist);
QSqlQuery q(query.Query(Connect()));
q.exec();
@ -338,7 +344,7 @@ QStringList LibraryBackend::GetAlbumsByArtist(const QueryOptions& opt, const QSt
return ret;
}
SongList LibraryBackend::GetSongs(const QueryOptions& opt, const QString& artist, const QString& album) {
SongList LibraryBackend::GetSongs(const QString& artist, const QString& album, const QueryOptions& opt) {
LibraryQuery query(opt);
query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
query.AddCompilationRequirement(false);
@ -402,7 +408,7 @@ QStringList LibraryBackend::GetCompilationAlbums(const QueryOptions& opt) {
return ret;
}
SongList LibraryBackend::GetCompilationSongs(const QueryOptions& opt, const QString& album) {
SongList LibraryBackend::GetCompilationSongs(const QString& album, const QueryOptions& opt) {
LibraryQuery query(opt);
query.SetColumnSpec("ROWID, " + QString(Song::kColumnSpec));
query.AddCompilationRequirement(true);
@ -515,4 +521,33 @@ void LibraryBackend::UpdateCompilations(QSqlQuery& find_songs, QSqlQuery& update
CheckErrors(update.lastError());
}
QList<LibraryBackend::AlbumArtInfo>
LibraryBackend::GetAlbumArtInfo(const QString& artist,
const QueryOptions& opt) {
QList<AlbumArtInfo> ret;
LibraryQuery query(opt);
query.SetColumnSpec("album, art_automatic, art_manual");
query.SetOrderBy("album");
if (!artist.isNull())
query.AddWhere("artist", artist);
QSqlQuery q(query.Query(Connect()));
q.exec();
if (CheckErrors(q.lastError())) return ret;
QString last_album;
while (q.next()) {
if (q.value(0).toString() == last_album)
continue;
AlbumArtInfo info;
info.album_name = q.value(0).toString();
info.art_automatic = q.value(1).toString();
info.art_manual = q.value(2).toString();
ret << info;
last_album = info.album_name;
}
return ret;
}

View File

@ -9,8 +9,7 @@
#include "directory.h"
#include "song.h"
struct QueryOptions;
#include "libraryquery.h"
class LibraryBackend : public QObject {
Q_OBJECT
@ -18,6 +17,12 @@ class LibraryBackend : public QObject {
public:
LibraryBackend(QObject* parent = 0);
struct AlbumArtInfo {
QString album_name;
QString art_automatic;
QString art_manual;
};
// This actually refers to the location of the sqlite database
static QString DefaultDirectory();
@ -29,13 +34,16 @@ class LibraryBackend : public QObject {
SongList FindSongsInDirectory(int id);
QStringList GetAllArtists(const QueryOptions& opt);
QStringList GetAlbumsByArtist(const QueryOptions& opt, const QString& artist);
SongList GetSongs(const QueryOptions& opt, const QString& artist, const QString& album);
QStringList GetAllArtists(const QueryOptions& opt = QueryOptions());
QStringList GetAllAlbums(const QueryOptions& opt = QueryOptions());
QStringList GetAlbumsByArtist(const QString& artist, const QueryOptions& opt = QueryOptions());
SongList GetSongs(const QString& artist, const QString& album, const QueryOptions& opt = QueryOptions());
bool HasCompilations(const QueryOptions& opt);
QStringList GetCompilationAlbums(const QueryOptions& opt);
SongList GetCompilationSongs(const QueryOptions& opt, const QString& album);
bool HasCompilations(const QueryOptions& opt = QueryOptions());
QStringList GetCompilationAlbums(const QueryOptions& opt = QueryOptions());
SongList GetCompilationSongs(const QString& album, const QueryOptions& opt = QueryOptions());
QList<AlbumArtInfo> GetAlbumArtInfo(const QString& artist = QString(), const QueryOptions& opt = QueryOptions());
Song GetSongById(int id);

View File

@ -53,6 +53,9 @@ QSqlQuery LibraryQuery::Query(QSqlDatabase db) const {
if (!where_clauses_.isEmpty())
sql += " WHERE " + where_clauses_.join(" AND ");
if (!order_by_.isEmpty())
sql += " ORDER BY " + order_by_;
QSqlQuery q(sql, db);
// Bind values

View File

@ -24,6 +24,7 @@ class LibraryQuery {
LibraryQuery(const QueryOptions& options);
void SetColumnSpec(const QString& spec) { column_spec_ = spec; }
void SetOrderBy(const QString& order_by) { order_by_ = order_by; }
void AddWhere(const QString& column, const QVariant& value);
void AddCompilationRequirement(bool compilation);
@ -31,6 +32,7 @@ class LibraryQuery {
private:
QString column_spec_;
QString order_by_;
QStringList where_clauses_;
QVariantList bound_values_;
};

View File

@ -18,6 +18,7 @@
#include "about.h"
#include "addstreamdialog.h"
#include "stylesheetloader.h"
#include "albumcovermanager.h"
#include "qxtglobalshortcut.h"
@ -54,6 +55,7 @@ MainWindow::MainWindow(QWidget *parent)
settings_dialog_(new SettingsDialog(this)),
add_stream_dialog_(new AddStreamDialog(this)),
shortcuts_dialog_(new ShortcutsDialog(this)),
cover_manager_(new AlbumCoverManager(this)),
playlist_menu_(new QMenu(this)),
library_sort_model_(new QSortFilterProxyModel(this)),
track_position_timer_(new QTimer(this))
@ -114,6 +116,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(ui_.action_add_stream, SIGNAL(triggered()), SLOT(AddStream()));
connect(ui_.action_hide_tray_icon, SIGNAL(triggered()), SLOT(HideShowTrayIcon()));
connect(ui_.action_global_shortcuts, SIGNAL(triggered()), shortcuts_dialog_, SLOT(show()));
connect(ui_.action_cover_manager, SIGNAL(triggered()), cover_manager_, SLOT(show()));
// Give actions to buttons
ui_.forward_button->setDefaultAction(ui_.action_next_track);
@ -165,6 +168,8 @@ MainWindow::MainWindow(QWidget *parent)
connect(library_, SIGNAL(TotalSongCountUpdated(int)), ui_.library_view, SLOT(TotalSongCountUpdated(int)));
connect(library_, SIGNAL(ScanStarted()), SLOT(LibraryScanStarted()));
connect(library_, SIGNAL(ScanFinished()), SLOT(LibraryScanFinished()));
connect(library_, SIGNAL(BackendReady(boost::shared_ptr<LibraryBackend>)),
cover_manager_, SLOT(SetBackend(boost::shared_ptr<LibraryBackend>)));
// Age filters
QActionGroup* filter_age_group = new QActionGroup(this);

View File

@ -22,6 +22,7 @@ class SettingsDialog;
class About;
class AddStreamDialog;
class ShortcutsDialog;
class AlbumCoverManager;
class QSortFilterProxyModel;
class SystemTrayIcon;
@ -104,6 +105,7 @@ class MainWindow : public QMainWindow {
SettingsDialog* settings_dialog_;
AddStreamDialog* add_stream_dialog_;
ShortcutsDialog* shortcuts_dialog_;
AlbumCoverManager* cover_manager_;
QMenu* playlist_menu_;
QAction* playlist_play_pause_;

View File

@ -309,7 +309,11 @@
</widget>
</item>
<item>
<widget class="QLineEdit" name="library_filter"/>
<widget class="LineEdit" name="library_filter">
<property name="hint" stdset="0">
<string>Enter search terms here</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="library_options">
@ -353,6 +357,9 @@
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
@ -434,7 +441,7 @@
<x>0</x>
<y>0</y>
<width>804</width>
<height>24</height>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuMusic">
@ -478,8 +485,15 @@
</property>
<addaction name="action_about"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="action_cover_manager"/>
</widget>
<addaction name="menuMusic"/>
<addaction name="menuPlaylist"/>
<addaction name="menuTools"/>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
@ -706,9 +720,23 @@
<string>Configure &amp;Global Shortcuts...</string>
</property>
</action>
<action name="action_cover_manager">
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/download.png</normaloff>:/download.png</iconset>
</property>
<property name="text">
<string>Cover Manager</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>LineEdit</class>
<extends>QLineEdit</extends>
<header>lineedit.h</header>
</customwidget>
<customwidget>
<class>BlockAnalyzer</class>
<extends>QWidget</extends>

View File

@ -56,7 +56,9 @@ SOURCES += main.cpp \
addstreamdialog.cpp \
savedradio.cpp \
stylesheetloader.cpp \
shortcutsdialog.cpp
shortcutsdialog.cpp \
covermanager.cpp \
coverloader.cpp
HEADERS += mainwindow.h \
player.h \
library.h \
@ -113,7 +115,9 @@ HEADERS += mainwindow.h \
addstreamdialog.h \
savedradio.h \
stylesheetloader.h \
shortcutsdialog.h
shortcutsdialog.h \
covermanager.h \
coverloader.h
FORMS += mainwindow.ui \
libraryconfig.ui \
fileview.ui \
@ -127,7 +131,8 @@ FORMS += mainwindow.ui \
lastfmconfigdialog.ui \
about.ui \
addstreamdialog.ui \
shortcutsdialog.ui
shortcutsdialog.ui \
covermanager.ui
RESOURCES += ../data/data.qrc \
translations.qrc
OTHER_FILES += ../data/schema.sql \