Add a MergedProxyModel that lets us merge two models into one...
This commit is contained in:
parent
1b00aaa8b3
commit
6b8d6c93f9
@ -84,6 +84,7 @@ set(CLEMENTINE-SOURCES
|
|||||||
database.cpp
|
database.cpp
|
||||||
librarymodel.cpp
|
librarymodel.cpp
|
||||||
playlistbackend.cpp
|
playlistbackend.cpp
|
||||||
|
mergedproxymodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Header files that have Q_OBJECT in
|
# Header files that have Q_OBJECT in
|
||||||
@ -153,6 +154,7 @@ set(CLEMENTINE-MOC-HEADERS
|
|||||||
librarymodel.h
|
librarymodel.h
|
||||||
playlistbackend.h
|
playlistbackend.h
|
||||||
database.h
|
database.h
|
||||||
|
mergedproxymodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# lists of engine source files
|
# lists of engine source files
|
||||||
|
@ -47,7 +47,7 @@ const char* LastFMService::kAudioscrobblerClientId = "tng";
|
|||||||
const char* LastFMService::kApiKey = "75d20fb472be99275392aefa2760ea09";
|
const char* LastFMService::kApiKey = "75d20fb472be99275392aefa2760ea09";
|
||||||
const char* LastFMService::kSecret = "d3072b60ae626be12be69448f5c46e70";
|
const char* LastFMService::kSecret = "d3072b60ae626be12be69448f5c46e70";
|
||||||
|
|
||||||
LastFMService::LastFMService(QObject* parent)
|
LastFMService::LastFMService(RadioModel* parent)
|
||||||
: RadioService(kServiceName, parent),
|
: RadioService(kServiceName, parent),
|
||||||
scrobbler_(NULL),
|
scrobbler_(NULL),
|
||||||
station_dialog_(new LastFMStationDialog),
|
station_dialog_(new LastFMStationDialog),
|
||||||
|
@ -47,7 +47,7 @@ class LastFMService : public RadioService {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LastFMService(QObject* parent = 0);
|
LastFMService(RadioModel* parent);
|
||||||
~LastFMService();
|
~LastFMService();
|
||||||
|
|
||||||
static const char* kServiceName;
|
static const char* kServiceName;
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
#include "magnatuneservice.h"
|
#include "magnatuneservice.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
|
#include "radiomodel.h"
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
#include "librarymodel.h"
|
||||||
|
#include "librarybackend.h"
|
||||||
|
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
@ -29,16 +33,24 @@ const char* MagnatuneService::kServiceName = "Magnatune";
|
|||||||
const char* MagnatuneService::kDatabaseUrl =
|
const char* MagnatuneService::kDatabaseUrl =
|
||||||
"http://magnatune.com/info/song_info2_xml.gz";
|
"http://magnatune.com/info/song_info2_xml.gz";
|
||||||
|
|
||||||
MagnatuneService::MagnatuneService(QObject* parent)
|
MagnatuneService::MagnatuneService(RadioModel* parent)
|
||||||
: RadioService(kServiceName, parent),
|
: RadioService(kServiceName, parent),
|
||||||
root_(NULL),
|
root_(NULL),
|
||||||
|
library_backend_(new LibraryBackend(parent->db(), "songs", "", "", this)),
|
||||||
|
library_model_(new LibraryModel(library_backend_, this)),
|
||||||
network_(new QNetworkAccessManager(this))
|
network_(new QNetworkAccessManager(this))
|
||||||
{
|
{
|
||||||
|
library_model_->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
RadioItem* MagnatuneService::CreateRootItem(RadioItem *parent) {
|
RadioItem* MagnatuneService::CreateRootItem(RadioItem *parent) {
|
||||||
root_ = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
|
root_ = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
|
||||||
root_->icon = QIcon(":magnatune.png");
|
root_->icon = QIcon(":magnatune.png");
|
||||||
|
|
||||||
|
model()->merged_model()->AddSubModel(
|
||||||
|
model()->index(root_->row, 0, model()->ItemToIndex(parent)),
|
||||||
|
library_model_);
|
||||||
|
|
||||||
return root_;
|
return root_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,14 @@
|
|||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
|
|
||||||
|
class LibraryBackend;
|
||||||
|
class LibraryModel;
|
||||||
|
|
||||||
class MagnatuneService : public RadioService {
|
class MagnatuneService : public RadioService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MagnatuneService(QObject* parent = 0);
|
MagnatuneService(RadioModel* parent);
|
||||||
|
|
||||||
static const char* kServiceName;
|
static const char* kServiceName;
|
||||||
static const char* kDatabaseUrl;
|
static const char* kDatabaseUrl;
|
||||||
@ -46,6 +49,8 @@ class MagnatuneService : public RadioService {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
RadioItem* root_;
|
RadioItem* root_;
|
||||||
|
LibraryBackend* library_backend_;
|
||||||
|
LibraryModel* library_model_;
|
||||||
|
|
||||||
QNetworkAccessManager* network_;
|
QNetworkAccessManager* network_;
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
#include "transcodedialog.h"
|
#include "transcodedialog.h"
|
||||||
#include "playlistbackend.h"
|
#include "playlistbackend.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
|
||||||
#include "globalshortcuts/globalshortcuts.h"
|
#include "globalshortcuts/globalshortcuts.h"
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ MainWindow::MainWindow(QNetworkAccessManager* network, Engine::Type engine, QWid
|
|||||||
library_config_dialog_(new LibraryConfigDialog),
|
library_config_dialog_(new LibraryConfigDialog),
|
||||||
about_dialog_(new About),
|
about_dialog_(new About),
|
||||||
database_(new Database(this)),
|
database_(new Database(this)),
|
||||||
radio_model_(new RadioModel(this)),
|
radio_model_(new RadioModel(database_, this)),
|
||||||
playlist_backend_(new PlaylistBackend(database_, this)),
|
playlist_backend_(new PlaylistBackend(database_, this)),
|
||||||
playlist_(new Playlist(playlist_backend_, this)),
|
playlist_(new Playlist(playlist_backend_, this)),
|
||||||
player_(new Player(playlist_, radio_model_->GetLastFMService(), engine, this)),
|
player_(new Player(playlist_, radio_model_->GetLastFMService(), engine, this)),
|
||||||
@ -143,7 +144,7 @@ MainWindow::MainWindow(QNetworkAccessManager* network, Engine::Type engine, QWid
|
|||||||
library_config_dialog_->SetModel(library_->model()->directory_model());
|
library_config_dialog_->SetModel(library_->model()->directory_model());
|
||||||
settings_dialog_->SetLibraryDirectoryModel(library_->model()->directory_model());
|
settings_dialog_->SetLibraryDirectoryModel(library_->model()->directory_model());
|
||||||
|
|
||||||
ui_.radio_view->setModel(radio_model_);
|
ui_.radio_view->setModel(radio_model_->merged_model());
|
||||||
|
|
||||||
cover_manager_->Init();
|
cover_manager_->Init();
|
||||||
|
|
||||||
|
227
src/mergedproxymodel.cpp
Normal file
227
src/mergedproxymodel.cpp
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
/* This file is part of Clementine.
|
||||||
|
|
||||||
|
Clementine 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.
|
||||||
|
|
||||||
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
|
||||||
|
std::size_t hash_value(const QModelIndex& index) {
|
||||||
|
return qHash(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
MergedProxyModel::MergedProxyModel(QObject* parent)
|
||||||
|
: QAbstractProxyModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::AddSubModel(const QModelIndex& source_parent,
|
||||||
|
const QAbstractItemModel* submodel) {
|
||||||
|
merge_points_.insert(submodel, source_parent);
|
||||||
|
|
||||||
|
connect(submodel, SIGNAL(modelReset()), this, SLOT(SubModelReset()));
|
||||||
|
connect(submodel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
|
||||||
|
connect(submodel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||||
|
connect(submodel, SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsInserted(QModelIndex,int,int)));
|
||||||
|
connect(submodel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsRemoved(QModelIndex,int,int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::setSourceModel(QAbstractItemModel* source_model) {
|
||||||
|
if (sourceModel()) {
|
||||||
|
disconnect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
|
||||||
|
disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
|
||||||
|
disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||||
|
disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsInserted(QModelIndex,int,int)));
|
||||||
|
disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsRemoved(QModelIndex,int,int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QAbstractProxyModel::setSourceModel(source_model);
|
||||||
|
|
||||||
|
connect(sourceModel(), SIGNAL(modelReset()), this, SLOT(SourceModelReset()));
|
||||||
|
connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeInserted(QModelIndex,int,int)));
|
||||||
|
connect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsAboutToBeRemoved(QModelIndex,int,int)));
|
||||||
|
connect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsInserted(QModelIndex,int,int)));
|
||||||
|
connect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
|
||||||
|
this, SLOT(RowsRemoved(QModelIndex,int,int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::SourceModelReset() {
|
||||||
|
// Delete all mappings
|
||||||
|
MappingContainer::index<tag_by_pointer>::type::iterator begin =
|
||||||
|
mappings_.get<tag_by_pointer>().begin();
|
||||||
|
MappingContainer::index<tag_by_pointer>::type::iterator end =
|
||||||
|
mappings_.get<tag_by_pointer>().end();
|
||||||
|
qDeleteAll(begin, end);
|
||||||
|
|
||||||
|
// Clear the containers
|
||||||
|
mappings_.clear();
|
||||||
|
merge_points_.clear();
|
||||||
|
|
||||||
|
// Reset the proxy
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::SubModelReset() {
|
||||||
|
const QAbstractItemModel* submodel = static_cast<const QAbstractItemModel*>(sender());
|
||||||
|
|
||||||
|
// Delete all the mappings that reference the submodel
|
||||||
|
MappingContainer::index<tag_by_pointer>::type::iterator it =
|
||||||
|
mappings_.get<tag_by_pointer>().begin();
|
||||||
|
MappingContainer::index<tag_by_pointer>::type::iterator end =
|
||||||
|
mappings_.get<tag_by_pointer>().end();
|
||||||
|
|
||||||
|
while (it != end) {
|
||||||
|
if ((*it)->source_index.model() == submodel) {
|
||||||
|
delete *it;
|
||||||
|
it = mappings_.get<tag_by_pointer>().erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the proxy
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MergedProxyModel::GetActualSourceParent(const QModelIndex& source_parent,
|
||||||
|
const QAbstractItemModel* model) const {
|
||||||
|
if (!source_parent.isValid() && model != sourceModel())
|
||||||
|
return merge_points_.value(model);
|
||||||
|
return source_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::RowsAboutToBeInserted(const QModelIndex& source_parent,
|
||||||
|
int start, int end) {
|
||||||
|
beginInsertRows(GetActualSourceParent(
|
||||||
|
source_parent, static_cast<const QAbstractItemModel*>(sender())),
|
||||||
|
start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::RowsInserted(const QModelIndex&, int, int) {
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::RowsAboutToBeRemoved(const QModelIndex& source_parent,
|
||||||
|
int start, int end) {
|
||||||
|
beginRemoveRows(GetActualSourceParent(
|
||||||
|
source_parent, static_cast<const QAbstractItemModel*>(sender())),
|
||||||
|
start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MergedProxyModel::RowsRemoved(const QModelIndex&, int, int) {
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MergedProxyModel::mapToSource(const QModelIndex& proxy_index) const {
|
||||||
|
if (!proxy_index.isValid())
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
Mapping* mapping = static_cast<Mapping*>(proxy_index.internalPointer());
|
||||||
|
return mapping->source_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MergedProxyModel::mapFromSource(const QModelIndex& source_index) const {
|
||||||
|
if (!source_index.isValid())
|
||||||
|
return QModelIndex();
|
||||||
|
|
||||||
|
// Add a mapping if we don't have one already
|
||||||
|
MappingContainer::index<tag_by_source>::type::iterator it =
|
||||||
|
mappings_.get<tag_by_source>().find(source_index);
|
||||||
|
Mapping* mapping;
|
||||||
|
if (it != mappings_.get<tag_by_source>().end()) {
|
||||||
|
mapping = *it;
|
||||||
|
} else {
|
||||||
|
mapping = new Mapping(source_index);
|
||||||
|
const_cast<MergedProxyModel*>(this)->mappings_.insert(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createIndex(source_index.row(), source_index.column(), mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MergedProxyModel::index(int row, int column, const QModelIndex &parent) const {
|
||||||
|
QModelIndex source_index;
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
source_index = sourceModel()->index(row, column, QModelIndex());
|
||||||
|
} else {
|
||||||
|
QModelIndex source_parent = mapToSource(parent);
|
||||||
|
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||||
|
|
||||||
|
if (child_model)
|
||||||
|
source_index = child_model->index(row, column, QModelIndex());
|
||||||
|
else
|
||||||
|
source_index = source_parent.model()->index(row, column, source_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapFromSource(source_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex MergedProxyModel::parent(const QModelIndex &child) const {
|
||||||
|
QModelIndex source_child = mapToSource(child);
|
||||||
|
if (source_child.model() == sourceModel())
|
||||||
|
return mapFromSource(source_child.parent());
|
||||||
|
|
||||||
|
if (!source_child.parent().isValid())
|
||||||
|
return mapFromSource(merge_points_.value(source_child.model()));
|
||||||
|
return mapFromSource(source_child.parent());
|
||||||
|
}
|
||||||
|
|
||||||
|
int MergedProxyModel::rowCount(const QModelIndex &parent) const {
|
||||||
|
if (!parent.isValid())
|
||||||
|
return sourceModel()->rowCount(QModelIndex());
|
||||||
|
|
||||||
|
QModelIndex source_parent = mapToSource(parent);
|
||||||
|
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||||
|
if (child_model)
|
||||||
|
return child_model->rowCount(QModelIndex());
|
||||||
|
return source_parent.model()->rowCount(source_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MergedProxyModel::columnCount(const QModelIndex &parent) const {
|
||||||
|
if (!parent.isValid())
|
||||||
|
return sourceModel()->columnCount(QModelIndex());
|
||||||
|
|
||||||
|
QModelIndex source_parent = mapToSource(parent);
|
||||||
|
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||||
|
if (child_model)
|
||||||
|
return child_model->columnCount(QModelIndex());
|
||||||
|
return source_parent.model()->columnCount(source_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MergedProxyModel::hasChildren(const QModelIndex &parent) const {
|
||||||
|
if (!parent.isValid())
|
||||||
|
return sourceModel()->hasChildren(QModelIndex());
|
||||||
|
|
||||||
|
QModelIndex source_parent = mapToSource(parent);
|
||||||
|
const QAbstractItemModel* child_model = merge_points_.key(source_parent);
|
||||||
|
|
||||||
|
if (child_model)
|
||||||
|
return child_model->hasChildren(QModelIndex());
|
||||||
|
return source_parent.model()->hasChildren(source_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant MergedProxyModel::data(const QModelIndex &proxyIndex, int role) const {
|
||||||
|
QModelIndex source_index = mapToSource(proxyIndex);
|
||||||
|
return source_index.model()->data(source_index, role);
|
||||||
|
}
|
92
src/mergedproxymodel.h
Normal file
92
src/mergedproxymodel.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/* This file is part of Clementine.
|
||||||
|
|
||||||
|
Clementine 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.
|
||||||
|
|
||||||
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MERGEDPROXYMODEL_H
|
||||||
|
#define MERGEDPROXYMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractProxyModel>
|
||||||
|
|
||||||
|
#include <boost/multi_index_container.hpp>
|
||||||
|
#include <boost/multi_index/member.hpp>
|
||||||
|
#include <boost/multi_index/ordered_index.hpp>
|
||||||
|
#include <boost/multi_index/hashed_index.hpp>
|
||||||
|
|
||||||
|
using boost::multi_index::multi_index_container;
|
||||||
|
using boost::multi_index::indexed_by;
|
||||||
|
using boost::multi_index::hashed_unique;
|
||||||
|
using boost::multi_index::ordered_unique;
|
||||||
|
using boost::multi_index::tag;
|
||||||
|
using boost::multi_index::member;
|
||||||
|
using boost::multi_index::identity;
|
||||||
|
|
||||||
|
std::size_t hash_value(const QModelIndex& index);
|
||||||
|
|
||||||
|
class MergedProxyModel : public QAbstractProxyModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MergedProxyModel(QObject* parent = 0);
|
||||||
|
|
||||||
|
void AddSubModel(const QModelIndex& source_parent, const QAbstractItemModel* submodel);
|
||||||
|
|
||||||
|
// QAbstractItemModel
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent) const;
|
||||||
|
QModelIndex parent(const QModelIndex &child) const;
|
||||||
|
int rowCount(const QModelIndex &parent) const;
|
||||||
|
int columnCount(const QModelIndex &parent) const;
|
||||||
|
QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const;
|
||||||
|
bool hasChildren(const QModelIndex &parent) const;
|
||||||
|
|
||||||
|
// QAbstractProxyModel
|
||||||
|
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
|
||||||
|
QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
|
||||||
|
void setSourceModel(QAbstractItemModel *sourceModel);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void SourceModelReset();
|
||||||
|
void SubModelReset();
|
||||||
|
|
||||||
|
void RowsAboutToBeInserted(const QModelIndex& source_parent, int start, int end);
|
||||||
|
void RowsInserted(const QModelIndex& source_parent, int start, int end);
|
||||||
|
void RowsAboutToBeRemoved(const QModelIndex& source_parent, int start, int end);
|
||||||
|
void RowsRemoved(const QModelIndex& source_parent, int start, int end);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QModelIndex GetActualSourceParent(const QModelIndex& source_parent,
|
||||||
|
const QAbstractItemModel* model) const;
|
||||||
|
|
||||||
|
struct Mapping {
|
||||||
|
Mapping(const QModelIndex& _source_index) : source_index(_source_index) {}
|
||||||
|
QModelIndex source_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tag_by_source {};
|
||||||
|
struct tag_by_pointer {};
|
||||||
|
typedef multi_index_container<
|
||||||
|
Mapping*,
|
||||||
|
indexed_by<
|
||||||
|
hashed_unique<tag<tag_by_source>,
|
||||||
|
member<Mapping, QModelIndex, &Mapping::source_index> >,
|
||||||
|
ordered_unique<tag<tag_by_pointer>,
|
||||||
|
identity<Mapping*> >
|
||||||
|
>
|
||||||
|
> MappingContainer;
|
||||||
|
|
||||||
|
MappingContainer mappings_;
|
||||||
|
QMap<const QAbstractItemModel*, QModelIndex> merge_points_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MERGEDPROXYMODEL_H
|
@ -21,18 +21,22 @@
|
|||||||
#include "radiomimedata.h"
|
#include "radiomimedata.h"
|
||||||
#include "savedradio.h"
|
#include "savedradio.h"
|
||||||
#include "magnatuneservice.h"
|
#include "magnatuneservice.h"
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
QMap<QString, RadioService*> RadioModel::sServices;
|
QMap<QString, RadioService*> RadioModel::sServices;
|
||||||
|
|
||||||
RadioModel::RadioModel(QObject* parent)
|
RadioModel::RadioModel(Database* db, QObject* parent)
|
||||||
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent)
|
: SimpleTreeModel<RadioItem>(new RadioItem(this), parent),
|
||||||
|
db_(db),
|
||||||
|
merged_model_(new MergedProxyModel(this))
|
||||||
{
|
{
|
||||||
Q_ASSERT(sServices.isEmpty());
|
Q_ASSERT(sServices.isEmpty());
|
||||||
|
|
||||||
root_->lazy_loaded = true;
|
root_->lazy_loaded = true;
|
||||||
|
merged_model_->setSourceModel(this);
|
||||||
|
|
||||||
AddService(new LastFMService(this));
|
AddService(new LastFMService(this));
|
||||||
AddService(new SomaFMService(this));
|
AddService(new SomaFMService(this));
|
||||||
|
@ -24,12 +24,14 @@
|
|||||||
class RadioService;
|
class RadioService;
|
||||||
class LastFMService;
|
class LastFMService;
|
||||||
class Song;
|
class Song;
|
||||||
|
class MergedProxyModel;
|
||||||
|
class Database;
|
||||||
|
|
||||||
class RadioModel : public SimpleTreeModel<RadioItem> {
|
class RadioModel : public SimpleTreeModel<RadioItem> {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RadioModel(QObject* parent = 0);
|
RadioModel(Database* db, QObject* parent = 0);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
Role_Type = Qt::UserRole + 1,
|
Role_Type = Qt::UserRole + 1,
|
||||||
@ -52,6 +54,9 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
|
|||||||
void ShowContextMenu(RadioItem* item, const QPoint& global_pos);
|
void ShowContextMenu(RadioItem* item, const QPoint& global_pos);
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
|
Database* db() const { return db_; }
|
||||||
|
MergedProxyModel* merged_model() const { return merged_model_; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void TaskStarted(MultiLoadingIndicator::TaskType);
|
void TaskStarted(MultiLoadingIndicator::TaskType);
|
||||||
void TaskFinished(MultiLoadingIndicator::TaskType);
|
void TaskFinished(MultiLoadingIndicator::TaskType);
|
||||||
@ -71,6 +76,8 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static QMap<QString, RadioService*> sServices;
|
static QMap<QString, RadioService*> sServices;
|
||||||
|
Database* db_;
|
||||||
|
MergedProxyModel* merged_model_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RADIOMODEL_H
|
#endif // RADIOMODEL_H
|
||||||
|
@ -15,9 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "radioservice.h"
|
#include "radioservice.h"
|
||||||
|
#include "radiomodel.h"
|
||||||
|
|
||||||
RadioService::RadioService(const QString& name, QObject *parent)
|
RadioService::RadioService(const QString& name, RadioModel* model)
|
||||||
: QObject(parent),
|
: QObject(model),
|
||||||
|
model_(model),
|
||||||
name_(name)
|
name_(name)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,17 @@
|
|||||||
#include "multiloadingindicator.h"
|
#include "multiloadingindicator.h"
|
||||||
|
|
||||||
class Song;
|
class Song;
|
||||||
|
class RadioModel;
|
||||||
|
|
||||||
class RadioService : public QObject {
|
class RadioService : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RadioService(const QString& name, QObject* parent = 0);
|
RadioService(const QString& name, RadioModel* model);
|
||||||
virtual ~RadioService() {}
|
virtual ~RadioService() {}
|
||||||
|
|
||||||
QString name() const { return name_; }
|
QString name() const { return name_; }
|
||||||
|
RadioModel* model() const { return model_; }
|
||||||
|
|
||||||
virtual RadioItem* CreateRootItem(RadioItem* parent) = 0;
|
virtual RadioItem* CreateRootItem(RadioItem* parent) = 0;
|
||||||
virtual void LazyPopulate(RadioItem* item) = 0;
|
virtual void LazyPopulate(RadioItem* item) = 0;
|
||||||
@ -67,6 +69,7 @@ class RadioService : public QObject {
|
|||||||
void AddItemToPlaylist(RadioItem* item);
|
void AddItemToPlaylist(RadioItem* item);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
RadioModel* model_;
|
||||||
QString name_;
|
QString name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "radioview.h"
|
#include "radioview.h"
|
||||||
#include "radiomodel.h"
|
#include "radiomodel.h"
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
|
||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
|
|
||||||
@ -29,6 +30,10 @@ void RadioView::contextMenuEvent(QContextMenuEvent* e) {
|
|||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RadioModel* radio_model = static_cast<RadioModel*>(model());
|
MergedProxyModel* merged_model = static_cast<MergedProxyModel*>(model());
|
||||||
radio_model->ShowContextMenu(radio_model->IndexToItem(index), e->globalPos());
|
RadioModel* radio_model = static_cast<RadioModel*>(merged_model->sourceModel());
|
||||||
|
|
||||||
|
radio_model->ShowContextMenu(
|
||||||
|
radio_model->IndexToItem(merged_model->mapToSource(index)),
|
||||||
|
e->globalPos());
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
const char* SavedRadio::kServiceName = "SavedRadio";
|
const char* SavedRadio::kServiceName = "SavedRadio";
|
||||||
const char* SavedRadio::kSettingsGroup = "SavedRadio";
|
const char* SavedRadio::kSettingsGroup = "SavedRadio";
|
||||||
|
|
||||||
SavedRadio::SavedRadio(QObject* parent)
|
SavedRadio::SavedRadio(RadioModel* parent)
|
||||||
: RadioService(kServiceName, parent),
|
: RadioService(kServiceName, parent),
|
||||||
root_(NULL),
|
root_(NULL),
|
||||||
context_menu_(new QMenu)
|
context_menu_(new QMenu)
|
||||||
|
@ -25,7 +25,7 @@ class SavedRadio : public RadioService {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SavedRadio(QObject* parent = 0);
|
SavedRadio(RadioModel* parent);
|
||||||
~SavedRadio();
|
~SavedRadio();
|
||||||
|
|
||||||
enum ItemType {
|
enum ItemType {
|
||||||
|
@ -50,6 +50,7 @@ class SimpleTreeItem {
|
|||||||
|
|
||||||
T* parent;
|
T* parent;
|
||||||
QList<T*> children;
|
QList<T*> children;
|
||||||
|
QAbstractItemModel* child_model;
|
||||||
|
|
||||||
SimpleTreeModel<T>* model;
|
SimpleTreeModel<T>* model;
|
||||||
};
|
};
|
||||||
@ -60,6 +61,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
|
|||||||
row(0),
|
row(0),
|
||||||
lazy_loaded(true),
|
lazy_loaded(true),
|
||||||
parent(NULL),
|
parent(NULL),
|
||||||
|
child_model(NULL),
|
||||||
model(_model)
|
model(_model)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -70,6 +72,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
|
|||||||
key(_key),
|
key(_key),
|
||||||
lazy_loaded(false),
|
lazy_loaded(false),
|
||||||
parent(_parent),
|
parent(_parent),
|
||||||
|
child_model(NULL),
|
||||||
model(_parent ? _parent->model : NULL)
|
model(_parent ? _parent->model : NULL)
|
||||||
{
|
{
|
||||||
if (parent) {
|
if (parent) {
|
||||||
@ -83,6 +86,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, T* _parent)
|
|||||||
: type(_type),
|
: type(_type),
|
||||||
lazy_loaded(false),
|
lazy_loaded(false),
|
||||||
parent(_parent),
|
parent(_parent),
|
||||||
|
child_model(NULL),
|
||||||
model(_parent ? _parent->model : NULL)
|
model(_parent ? _parent->model : NULL)
|
||||||
{
|
{
|
||||||
if (parent) {
|
if (parent) {
|
||||||
|
@ -31,7 +31,7 @@ const char* SomaFMService::kServiceName = "SomaFM";
|
|||||||
const char* SomaFMService::kChannelListUrl = "http://somafm.com/channels.xml";
|
const char* SomaFMService::kChannelListUrl = "http://somafm.com/channels.xml";
|
||||||
const char* SomaFMService::kHomepage = "http://somafm.com";
|
const char* SomaFMService::kHomepage = "http://somafm.com";
|
||||||
|
|
||||||
SomaFMService::SomaFMService(QObject* parent)
|
SomaFMService::SomaFMService(RadioModel* parent)
|
||||||
: RadioService(kServiceName, parent),
|
: RadioService(kServiceName, parent),
|
||||||
root_(NULL),
|
root_(NULL),
|
||||||
context_menu_(new QMenu),
|
context_menu_(new QMenu),
|
||||||
|
@ -28,7 +28,7 @@ class SomaFMService : public RadioService {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SomaFMService(QObject* parent = 0);
|
SomaFMService(RadioModel* parent);
|
||||||
~SomaFMService();
|
~SomaFMService();
|
||||||
|
|
||||||
enum ItemType {
|
enum ItemType {
|
||||||
|
@ -102,3 +102,4 @@ add_test_file(translations_test.cpp false)
|
|||||||
add_test_file(playlist_test.cpp true)
|
add_test_file(playlist_test.cpp true)
|
||||||
add_test_file(scopedtransaction_test.cpp false)
|
add_test_file(scopedtransaction_test.cpp false)
|
||||||
add_test_file(fileformats_test.cpp false)
|
add_test_file(fileformats_test.cpp false)
|
||||||
|
add_test_file(mergedproxymodel_test.cpp false)
|
||||||
|
84
tests/mergedproxymodel_test.cpp
Normal file
84
tests/mergedproxymodel_test.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/* This file is part of Clementine.
|
||||||
|
|
||||||
|
Clementine 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.
|
||||||
|
|
||||||
|
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include "mergedproxymodel.h"
|
||||||
|
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
|
class MergedProxyModelTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() {
|
||||||
|
merged_.setSourceModel(&source_);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStandardItemModel source_;
|
||||||
|
MergedProxyModel merged_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(MergedProxyModelTest, Flat) {
|
||||||
|
source_.appendRow(new QStandardItem("one"));
|
||||||
|
source_.appendRow(new QStandardItem("two"));
|
||||||
|
|
||||||
|
ASSERT_EQ(2, merged_.rowCount(QModelIndex()));
|
||||||
|
QModelIndex one_i = merged_.index(0, 0, QModelIndex());
|
||||||
|
QModelIndex two_i = merged_.index(1, 0, QModelIndex());
|
||||||
|
|
||||||
|
EXPECT_EQ("one", one_i.data().toString());
|
||||||
|
EXPECT_EQ("two", two_i.data().toString());
|
||||||
|
EXPECT_FALSE(merged_.parent(one_i).isValid());
|
||||||
|
EXPECT_FALSE(merged_.hasChildren(one_i));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MergedProxyModelTest, Tree) {
|
||||||
|
QStandardItem* one = new QStandardItem("one");
|
||||||
|
QStandardItem* two = new QStandardItem("two");
|
||||||
|
source_.appendRow(one);
|
||||||
|
one->appendRow(two);
|
||||||
|
|
||||||
|
ASSERT_EQ(1, merged_.rowCount(QModelIndex()));
|
||||||
|
QModelIndex one_i = merged_.index(0, 0, QModelIndex());
|
||||||
|
|
||||||
|
ASSERT_EQ(1, merged_.rowCount(one_i));
|
||||||
|
QModelIndex two_i = merged_.index(0, 0, one_i);
|
||||||
|
|
||||||
|
EXPECT_EQ("one", one_i.data().toString());
|
||||||
|
EXPECT_EQ("two", two_i.data().toString());
|
||||||
|
EXPECT_EQ("one", two_i.parent().data().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MergedProxyModelTest, Merged) {
|
||||||
|
source_.appendRow(new QStandardItem("one"));
|
||||||
|
|
||||||
|
QStandardItemModel submodel;
|
||||||
|
submodel.appendRow(new QStandardItem("two"));
|
||||||
|
|
||||||
|
merged_.AddModel(source_.index(0, 0, QModelIndex()), &submodel);
|
||||||
|
|
||||||
|
ASSERT_EQ(1, merged_.rowCount(QModelIndex()));
|
||||||
|
QModelIndex one_i = merged_.index(0, 0, QModelIndex());
|
||||||
|
|
||||||
|
EXPECT_EQ("one", merged_.data(one_i).toString());
|
||||||
|
EXPECT_TRUE(merged_.hasChildren(one_i));
|
||||||
|
|
||||||
|
ASSERT_EQ(1, merged_.rowCount(one_i));
|
||||||
|
QModelIndex two_i = merged_.index(0, 0, one_i);
|
||||||
|
|
||||||
|
EXPECT_EQ("two", merged_.data(two_i).toString());
|
||||||
|
EXPECT_EQ(0, merged_.rowCount(two_i));
|
||||||
|
EXPECT_FALSE(merged_.hasChildren(two_i));
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user