From 7e25a7c7e458320dce592f1e0262bf611dad81ac Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Mon, 21 Jan 2019 10:06:48 +0100 Subject: [PATCH] Convert devices manager(model) to QAbstractItemModel (#6260) --- src/CMakeLists.txt | 2 + src/devices/connecteddevice.cpp | 9 +- src/devices/deviceinfo.cpp | 124 ++++++++++ src/devices/deviceinfo.h | 108 +++++++++ src/devices/devicemanager.cpp | 387 ++++++++++++------------------- src/devices/devicemanager.h | 68 +----- src/devices/deviceproperties.cpp | 2 +- src/devices/deviceview.cpp | 4 +- src/devices/filesystemdevice.cpp | 6 +- 9 files changed, 411 insertions(+), 299 deletions(-) create mode 100644 src/devices/deviceinfo.cpp create mode 100644 src/devices/deviceinfo.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index beac29dc01..35cb0d7afc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,6 +137,7 @@ set(SOURCES devices/deviceview.cpp devices/deviceviewcontainer.cpp devices/filesystemdevice.cpp + devices/deviceinfo.cpp engines/devicefinder.cpp engines/enginebase.cpp @@ -454,6 +455,7 @@ set(HEADERS devices/deviceview.h devices/deviceviewcontainer.h devices/filesystemdevice.h + devices/deviceinfo.h engines/enginebase.h engines/gstengine.h diff --git a/src/devices/connecteddevice.cpp b/src/devices/connecteddevice.cpp index 59e23e0e74..ddd2cdf88b 100644 --- a/src/devices/connecteddevice.cpp +++ b/src/devices/connecteddevice.cpp @@ -105,13 +105,16 @@ void ConnectedDevice::FinishDelete(bool) { MusicStorage::TranscodeMode ConnectedDevice::GetTranscodeMode() const { int index = manager_->FindDeviceById(unique_id_); return MusicStorage::TranscodeMode( - manager_->index(index).data(DeviceManager::Role_TranscodeMode).toInt()); + manager_->index(index, 0, QModelIndex()) + .data(DeviceManager::Role_TranscodeMode) + .toInt()); } Song::FileType ConnectedDevice::GetTranscodeFormat() const { int index = manager_->FindDeviceById(unique_id_); - return Song::FileType( - manager_->index(index).data(DeviceManager::Role_TranscodeFormat).toInt()); + return Song::FileType(manager_->index(index, 0, QModelIndex()) + .data(DeviceManager::Role_TranscodeFormat) + .toInt()); } void ConnectedDevice::BackendTotalSongCountUpdated(int count) { diff --git a/src/devices/deviceinfo.cpp b/src/devices/deviceinfo.cpp new file mode 100644 index 0000000000..9c1d83dac8 --- /dev/null +++ b/src/devices/deviceinfo.cpp @@ -0,0 +1,124 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#include "devicemanager.h" + +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "core/logging.h" +#include "core/simpletreemodel.h" +#include "ui/iconloader.h" + +#include "devicedatabasebackend.h" +#include "deviceinfo.h" +#include "devicelister.h" + +DeviceDatabaseBackend::Device DeviceInfo::SaveToDb() const { + DeviceDatabaseBackend::Device ret; + ret.friendly_name_ = friendly_name_; + ret.size_ = size_; + ret.id_ = database_id_; + ret.icon_name_ = icon_name_; + ret.transcode_mode_ = transcode_mode_; + ret.transcode_format_ = transcode_format_; + + QStringList unique_ids; + for (const Backend& backend : backends_) { + unique_ids << backend.unique_id_; + } + ret.unique_id_ = unique_ids.join(","); + + return ret; +} + +void DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device& dev) { + database_id_ = dev.id_; + friendly_name_ = dev.friendly_name_; + size_ = dev.size_; + transcode_mode_ = dev.transcode_mode_; + transcode_format_ = dev.transcode_format_; + + QStringList icon_names = dev.icon_name_.split(','); + QVariantList icons; + for (const QString& icon_name : icon_names) { + icons << icon_name; + } + + LoadIcon(icons, friendly_name_); + + QStringList unique_ids = dev.unique_id_.split(','); + for (const QString& id : unique_ids) { + backends_ << Backend(nullptr, id); + } +} + +const DeviceInfo::Backend* DeviceInfo::BestBackend() const { + int best_priority = -1; + const Backend* ret = nullptr; + + for (int i = 0; i < backends_.count(); ++i) { + if (backends_[i].lister_ && + backends_[i].lister_->priority() > best_priority) { + best_priority = backends_[i].lister_->priority(); + ret = &(backends_[i]); + } + } + + if (!ret && !backends_.isEmpty()) return &(backends_[0]); + return ret; +} + +void DeviceInfo::LoadIcon(const QVariantList& icons, const QString& name_hint) { + if (icons.isEmpty()) { + icon_name_ = "drive-removable-media-usb-pendrive"; + icon_ = IconLoader::Load(icon_name_, IconLoader::Base); + return; + } + + // Try to load the icon with that exact name first + for (const QVariant& icon : icons) { + if (!icon.value().isNull()) { + icon_ = QIcon(icon.value()); + return; + } else { + icon_ = IconLoader::Load(icon.toString(), IconLoader::Base); + if (!icon_.isNull()) { + icon_name_ = icon.toString(); + return; + } + } + } + + QString hint = QString(icons.first().toString() + name_hint).toLower(); + + // If that failed than try to guess if it's a phone or ipod. Fall back on + // a usb memory stick icon. + if (hint.contains("phone")) + icon_name_ = "phone"; + else if (hint.contains("ipod") || hint.contains("apple")) + icon_name_ = "multimedia-player-ipod-standard-monochrome"; + else + icon_name_ = "drive-removable-media-usb-pendrive"; + icon_ = IconLoader::Load(icon_name_, IconLoader::Base); +} diff --git a/src/devices/deviceinfo.h b/src/devices/deviceinfo.h new file mode 100644 index 0000000000..e7e4a4a56a --- /dev/null +++ b/src/devices/deviceinfo.h @@ -0,0 +1,108 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + 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 . +*/ + +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include + +#include +#include +#include +#include + +#include "core/musicstorage.h" +#include "core/simpletreeitem.h" +#include "core/simpletreemodel.h" +#include "core/song.h" +#include "devicedatabasebackend.h" +#include "library/librarymodel.h" + +class ConnectedDevice; +class DeviceLister; + +// Devices can be in three different states: +// 1) Remembered in the database but not physically connected at the moment. +// database_id valid, lister null, device null +// 2) Physically connected but the user hasn't "connected" it to Clementine +// yet. +// database_id == -1, lister valid, device null +// 3) Physically connected and connected to Clementine +// database_id valid, lister valid, device valid +// Devices in all states will have a unique_id. +class DeviceInfo : public SimpleTreeItem { + public: + enum Type { + Type_Root, + Type_Device, + }; + + DeviceInfo(SimpleTreeModel* model) + : SimpleTreeItem(Type_Root, model), + database_id_(-1), + size_(0), + transcode_mode_(MusicStorage::Transcode_Unsupported), + transcode_format_(Song::Type_Unknown), + task_percentage_(-1) {} + + DeviceInfo(Type type, DeviceInfo* parent = nullptr) + : SimpleTreeItem(type, parent), + database_id_(-1), + size_(0), + transcode_mode_(MusicStorage::Transcode_Unsupported), + transcode_format_(Song::Type_Unknown), + task_percentage_(-1) {} + + // A device can be discovered in different ways (devicekit, gio, etc.) + // Sometimes the same device is discovered more than once. In this case + // the device will have multiple "backends". + struct Backend { + Backend(DeviceLister* lister = nullptr, const QString& id = QString()) + : lister_(lister), unique_id_(id) {} + + DeviceLister* lister_; // nullptr if not physically connected + QString unique_id_; + }; + + // Serialising to the database + void InitFromDb(const DeviceDatabaseBackend::Device& dev); + DeviceDatabaseBackend::Device SaveToDb() const; + + // Tries to load a good icon for the device. Sets icon_name_ and icon_. + void LoadIcon(const QVariantList& icons, const QString& name_hint); + + // Gets the best backend available (the one with the highest priority) + const Backend* BestBackend() const; + + int database_id_; // -1 if not remembered in the database + std::shared_ptr + device_; // nullptr if not connected to clementine + QList backends_; + + QString friendly_name_; + quint64 size_; + + QString icon_name_; + QIcon icon_; + + MusicStorage::TranscodeMode transcode_mode_; + Song::FileType transcode_format_; + + int task_percentage_; +}; + +#endif // DEVICEINFO_H diff --git a/src/devices/devicemanager.cpp b/src/devices/devicemanager.cpp index 06bf93393c..7c8e01ceeb 100644 --- a/src/devices/devicemanager.cpp +++ b/src/devices/devicemanager.cpp @@ -34,9 +34,11 @@ #include "core/database.h" #include "core/logging.h" #include "core/musicstorage.h" +#include "core/simpletreemodel.h" #include "core/taskmanager.h" #include "core/utilities.h" #include "devicedatabasebackend.h" +#include "deviceinfo.h" #include "devicekitlister.h" #include "devicestatefiltermodel.h" #include "filesystemdevice.h" @@ -68,106 +70,8 @@ using std::bind; const int DeviceManager::kDeviceIconSize = 32; const int DeviceManager::kDeviceIconOverlaySize = 16; -DeviceManager::DeviceInfo::DeviceInfo() - : database_id_(-1), - transcode_mode_(MusicStorage::Transcode_Unsupported), - transcode_format_(Song::Type_Unknown), - task_percentage_(-1) {} - -DeviceDatabaseBackend::Device DeviceManager::DeviceInfo::SaveToDb() const { - DeviceDatabaseBackend::Device ret; - ret.friendly_name_ = friendly_name_; - ret.size_ = size_; - ret.id_ = database_id_; - ret.icon_name_ = icon_name_; - ret.transcode_mode_ = transcode_mode_; - ret.transcode_format_ = transcode_format_; - - QStringList unique_ids; - for (const Backend& backend : backends_) { - unique_ids << backend.unique_id_; - } - ret.unique_id_ = unique_ids.join(","); - - return ret; -} - -void DeviceManager::DeviceInfo::InitFromDb( - const DeviceDatabaseBackend::Device& dev) { - database_id_ = dev.id_; - friendly_name_ = dev.friendly_name_; - size_ = dev.size_; - transcode_mode_ = dev.transcode_mode_; - transcode_format_ = dev.transcode_format_; - - QStringList icon_names = dev.icon_name_.split(','); - QVariantList icons; - for (const QString& icon_name : icon_names) { - icons << icon_name; - } - - LoadIcon(icons, friendly_name_); - - QStringList unique_ids = dev.unique_id_.split(','); - for (const QString& id : unique_ids) { - backends_ << Backend(nullptr, id); - } -} - -void DeviceManager::DeviceInfo::LoadIcon(const QVariantList& icons, - const QString& name_hint) { - if (icons.isEmpty()) { - icon_name_ = "drive-removable-media-usb-pendrive"; - icon_ = IconLoader::Load(icon_name_, IconLoader::Base); - return; - } - - // Try to load the icon with that exact name first - for (const QVariant& icon : icons) { - if (!icon.value().isNull()) { - icon_ = QIcon(icon.value()); - return; - } else { - icon_ = IconLoader::Load(icon.toString(), IconLoader::Base); - if (!icon_.isNull()) { - icon_name_ = icon.toString(); - return; - } - } - } - - QString hint = QString(icons.first().toString() + name_hint).toLower(); - - // If that failed than try to guess if it's a phone or ipod. Fall back on - // a usb memory stick icon. - if (hint.contains("phone")) - icon_name_ = "phone"; - else if (hint.contains("ipod") || hint.contains("apple")) - icon_name_ = "multimedia-player-ipod-standard-monochrome"; - else - icon_name_ = "drive-removable-media-usb-pendrive"; - icon_ = IconLoader::Load(icon_name_, IconLoader::Base); -} - -const DeviceManager::DeviceInfo::Backend* -DeviceManager::DeviceInfo::BestBackend() const { - int best_priority = -1; - const Backend* ret = nullptr; - - for (int i = 0; i < backends_.count(); ++i) { - if (backends_[i].lister_ && - backends_[i].lister_->priority() > best_priority) { - best_priority = backends_[i].lister_->priority(); - ret = &(backends_[i]); - } - } - - if (!ret && !backends_.isEmpty()) return &(backends_[0]); - return ret; -} - DeviceManager::DeviceManager(Application* app, QObject* parent) - : QAbstractListModel(parent), + : SimpleTreeModel(new DeviceInfo(this), parent), app_(app), not_connected_overlay_( IconLoader::Load("edit-delete", IconLoader::Base)) { @@ -227,14 +131,15 @@ DeviceManager::~DeviceManager() { } backend_->deleteLater(); + delete root_; } void DeviceManager::LoadAllDevices() { Q_ASSERT(QThread::currentThread() != qApp->thread()); DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices(); for (const DeviceDatabaseBackend::Device& device : devices) { - DeviceInfo info; - info.InitFromDb(device); + DeviceInfo* info = new DeviceInfo(DeviceInfo::Type_Root, root_); + info->InitFromDb(device); beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); devices_ << info; @@ -242,33 +147,30 @@ void DeviceManager::LoadAllDevices() { } } -int DeviceManager::rowCount(const QModelIndex&) const { - return devices_.count(); -} - QVariant DeviceManager::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.column() != 0) return QVariant(); - const DeviceInfo& info = devices_[index.row()]; + const DeviceInfo* info = IndexToItem(index); + if (!info) return QVariant(); switch (role) { case Qt::DisplayRole: { QString text; - if (!info.friendly_name_.isEmpty()) - text = info.friendly_name_; - else - text = info.BestBackend()->unique_id_; - - if (info.size_) - text = text + QString(" (%1)").arg(Utilities::PrettySize(info.size_)); - if (info.device_.get()) info.device_->Refresh(); + if (!info->friendly_name_.isEmpty()) + text = info->friendly_name_; + else if (info->BestBackend()) + text = info->BestBackend()->unique_id_; + + if (info->size_) + text = text + QString(" (%1)").arg(Utilities::PrettySize(info->size_)); + if (info->device_.get()) info->device_->Refresh(); return text; } case Qt::DecorationRole: { - QPixmap pixmap = info.icon_.pixmap(kDeviceIconSize); + QPixmap pixmap = info->icon_.pixmap(kDeviceIconSize); - if (info.backends_.isEmpty() || !info.BestBackend()->lister_) { + if (info->backends_.isEmpty() || !info->BestBackend()->lister_) { // Disconnected but remembered QPainter p(&pixmap); p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, @@ -280,52 +182,53 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const { } case Role_FriendlyName: - return info.friendly_name_; + return info->friendly_name_; case Role_UniqueId: - return info.BestBackend()->unique_id_; + if (!info->BestBackend()) return QString(); + return info->BestBackend()->unique_id_; case Role_IconName: - return info.icon_name_; + return info->icon_name_; case Role_Capacity: case MusicStorage::Role_Capacity: - return info.size_; + return info->size_; case Role_FreeSpace: case MusicStorage::Role_FreeSpace: - return info.BestBackend()->lister_ - ? info.BestBackend()->lister_->DeviceFreeSpace( - info.BestBackend()->unique_id_) + return info->BestBackend() && info->BestBackend()->lister_ + ? info->BestBackend()->lister_->DeviceFreeSpace( + info->BestBackend()->unique_id_) : QVariant(); case Role_State: - if (info.device_) return State_Connected; - if (info.BestBackend()->lister_) { - if (info.BestBackend()->lister_->DeviceNeedsMount( - info.BestBackend()->unique_id_)) + if (info->device_) return State_Connected; + if (info->BestBackend() && info->BestBackend()->lister_) { + if (info->BestBackend()->lister_->DeviceNeedsMount( + info->BestBackend()->unique_id_)) return State_NotMounted; return State_NotConnected; } return State_Remembered; case Role_UpdatingPercentage: - if (info.task_percentage_ == -1) return QVariant(); - return info.task_percentage_; + if (info->task_percentage_ == -1) return QVariant(); + return info->task_percentage_; case MusicStorage::Role_Storage: - if (!info.device_ && info.database_id_ != -1) + if (!info->device_ && info->database_id_ != -1) const_cast(this)->Connect(index.row()); - if (!info.device_) return QVariant(); - return QVariant::fromValue>(info.device_); + if (!info->device_) return QVariant(); + return QVariant::fromValue>(info->device_); case MusicStorage::Role_StorageForceConnect: - if (!info.device_) { - if (info.database_id_ == -1 && - !info.BestBackend()->lister_->DeviceNeedsMount( - info.BestBackend()->unique_id_)) { - if (info.BestBackend()->lister_->AskForScan( - info.BestBackend()->unique_id_)) { + if (!info->device_) { + if (info->database_id_ == -1 && info->BestBackend() && + !info->BestBackend()->lister_->DeviceNeedsMount( + info->BestBackend()->unique_id_)) { + if (info->BestBackend()->lister_->AskForScan( + info->BestBackend()->unique_id_)) { std::unique_ptr dialog(new QMessageBox( QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. " @@ -342,13 +245,13 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const { const_cast(this)->Connect(index.row()); } - if (!info.device_) return QVariant(); - return QVariant::fromValue>(info.device_); + if (!info->device_) return QVariant(); + return QVariant::fromValue>(info->device_); case Role_MountPath: { - if (!info.device_) return QVariant(); + if (!info->device_) return QVariant(); - QString ret = info.device_->url().path(); + QString ret = info->device_->url().path(); #ifdef Q_OS_WIN32 if (ret.startsWith('/')) ret.remove(0, 1); #endif @@ -356,14 +259,14 @@ QVariant DeviceManager::data(const QModelIndex& index, int role) const { } case Role_TranscodeMode: - return info.transcode_mode_; + return info->transcode_mode_; case Role_TranscodeFormat: - return info.transcode_format_; + return info->transcode_format_; case Role_SongCount: - if (!info.device_) return QVariant(); - return info.device_->song_count(); + if (!info->device_) return QVariant(); + return info->device_->song_count(); default: return QVariant(); @@ -384,7 +287,7 @@ void DeviceManager::AddLister(DeviceLister* lister) { int DeviceManager::FindDeviceById(const QString& id) const { for (int i = 0; i < devices_.count(); ++i) { - for (const DeviceInfo::Backend& backend : devices_[i].backends_) { + for (const DeviceInfo::Backend& backend : devices_[i]->backends_) { if (backend.unique_id_ == id) return i; } } @@ -395,7 +298,7 @@ int DeviceManager::FindDeviceByUrl(const QList& urls) const { if (urls.isEmpty()) return -1; for (int i = 0; i < devices_.count(); ++i) { - for (const DeviceInfo::Backend& backend : devices_[i].backends_) { + for (const DeviceInfo::Backend& backend : devices_[i]->backends_) { if (!backend.lister_) continue; QList device_urls = @@ -416,11 +319,11 @@ void DeviceManager::PhysicalDeviceAdded(const QString& id) { // Do we have this device already? int i = FindDeviceById(id); if (i != -1) { - DeviceInfo& info = devices_[i]; - for (int backend_index = 0; backend_index < info.backends_.count(); + DeviceInfo* info = devices_[i]; + for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_[backend_index].lister_ = lister; + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_[backend_index].lister_ = lister; break; } } @@ -431,25 +334,25 @@ void DeviceManager::PhysicalDeviceAdded(const QString& id) { i = FindDeviceByUrl(lister->MakeDeviceUrls(id)); if (i != -1) { // Add this device's lister to the existing device - DeviceInfo& info = devices_[i]; - info.backends_ << DeviceInfo::Backend(lister, id); + DeviceInfo* info = devices_[i]; + info->backends_ << DeviceInfo::Backend(lister, id); // If the user hasn't saved the device in the DB yet then overwrite the // device's name and icon etc. - if (info.database_id_ == -1 && info.BestBackend()->lister_ == lister) { - info.friendly_name_ = lister->MakeFriendlyName(id); - info.size_ = lister->DeviceCapacity(id); - info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + if (info->database_id_ == -1 && info->BestBackend()->lister_ == lister) { + info->friendly_name_ = lister->MakeFriendlyName(id); + info->size_ = lister->DeviceCapacity(id); + info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); } emit dataChanged(index(i, 0), index(i, 0)); } else { // It's a completely new device - DeviceInfo info; - info.backends_ << DeviceInfo::Backend(lister, id); - info.friendly_name_ = lister->MakeFriendlyName(id); - info.size_ = lister->DeviceCapacity(id); - info.LoadIcon(lister->DeviceIcons(id), info.friendly_name_); + DeviceInfo* info = new DeviceInfo(DeviceInfo::Type_Root, root_); + info->backends_ << DeviceInfo::Backend(lister, id); + info->friendly_name_ = lister->MakeFriendlyName(id); + info->size_ = lister->DeviceCapacity(id); + info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_); beginInsertRows(QModelIndex(), devices_.count(), devices_.count()); devices_ << info; @@ -469,34 +372,35 @@ void DeviceManager::PhysicalDeviceRemoved(const QString& id) { return; } - DeviceInfo& info = devices_[i]; + DeviceInfo* info = devices_[i]; - if (info.database_id_ != -1) { + if (info->database_id_ != -1) { // Keep the structure around, but just "disconnect" it - for (int backend_index = 0; backend_index < info.backends_.count(); + for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_[backend_index].lister_ = nullptr; + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_[backend_index].lister_ = nullptr; break; } } - if (info.device_ && info.device_->lister() == lister) info.device_.reset(); + if (info->device_ && info->device_->lister() == lister) + info->device_.reset(); - if (!info.device_) emit DeviceDisconnected(i); + if (!info->device_) emit DeviceDisconnected(i); emit dataChanged(index(i, 0), index(i, 0)); } else { // If this was the last lister for the device then remove it from the model - for (int backend_index = 0; backend_index < info.backends_.count(); + for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) { - if (info.backends_[backend_index].unique_id_ == id) { - info.backends_.removeAt(backend_index); + if (info->backends_[backend_index].unique_id_ == id) { + info->backends_.removeAt(backend_index); break; } } - if (info.backends_.isEmpty()) { + if (info->backends_.isEmpty()) { beginRemoveRows(QModelIndex(), i, i); devices_.removeAt(i); @@ -526,31 +430,31 @@ void DeviceManager::PhysicalDeviceChanged(const QString& id) { } std::shared_ptr DeviceManager::Connect(int row) { - DeviceInfo& info = devices_[row]; - if (info.device_) // Already connected - return info.device_; + DeviceInfo* info = devices_[row]; + if (info->device_) // Already connected + return info->device_; std::shared_ptr ret; - if (!info.BestBackend()->lister_) // Not physically connected + if (!info->BestBackend()->lister_) // Not physically connected return ret; - if (info.BestBackend()->lister_->DeviceNeedsMount( - info.BestBackend()->unique_id_)) { + if (info->BestBackend()->lister_->DeviceNeedsMount( + info->BestBackend()->unique_id_)) { // Mount the device - info.BestBackend()->lister_->MountDevice(info.BestBackend()->unique_id_); + info->BestBackend()->lister_->MountDevice(info->BestBackend()->unique_id_); return ret; } - bool first_time = (info.database_id_ == -1); + bool first_time = (info->database_id_ == -1); if (first_time) { // We haven't stored this device in the database before - info.database_id_ = backend_->AddDevice(info.SaveToDb()); + info->database_id_ = backend_->AddDevice(info->SaveToDb()); } // Get the device URLs - QList urls = info.BestBackend()->lister_->MakeDeviceUrls( - info.BestBackend()->unique_id_); + QList urls = info->BestBackend()->lister_->MakeDeviceUrls( + info->BestBackend()->unique_id_); if (urls.isEmpty()) return ret; // Take the first URL that we have a handler for @@ -605,10 +509,10 @@ std::shared_ptr DeviceManager::Connect(int row) { QMetaObject meta_object = device_classes_.value(device_url.scheme()); QObject* instance = meta_object.newInstance( Q_ARG(QUrl, device_url), - Q_ARG(DeviceLister*, info.BestBackend()->lister_), - Q_ARG(QString, info.BestBackend()->unique_id_), + Q_ARG(DeviceLister*, info->BestBackend()->lister_), + Q_ARG(QString, info->BestBackend()->unique_id_), Q_ARG(DeviceManager*, this), Q_ARG(Application*, app_), - Q_ARG(int, info.database_id_), Q_ARG(bool, first_time)); + Q_ARG(int, info->database_id_), Q_ARG(bool, first_time)); ret.reset(static_cast(instance)); if (!ret) { @@ -616,13 +520,15 @@ std::shared_ptr DeviceManager::Connect(int row) { } else { ret->Init(); - info.device_ = ret; - emit dataChanged(index(row), index(row)); - connect(info.device_.get(), SIGNAL(TaskStarted(int)), + info->device_ = ret; + QModelIndex index = ItemToIndex(info); + if (!index.isValid()) return ret; + emit dataChanged(index, index); + connect(info->device_.get(), SIGNAL(TaskStarted(int)), SLOT(DeviceTaskStarted(int))); - connect(info.device_.get(), SIGNAL(SongCountUpdated(int)), + connect(info->device_.get(), SIGNAL(SongCountUpdated(int)), SLOT(DeviceSongCountUpdated(int))); - connect(info.device_.get(), SIGNAL(ConnectFinished(const QString&, bool)), + connect(info->device_.get(), SIGNAL(ConnectFinished(const QString&, bool)), SLOT(DeviceConnectFinished(const QString&, bool))); ret->ConnectAsync(); } @@ -636,44 +542,46 @@ void DeviceManager::DeviceConnectFinished(const QString& id, bool success) { if (success) { emit DeviceConnected(row); } else { - devices_[row].device_.reset(); + devices_[row]->device_.reset(); } } } std::shared_ptr DeviceManager::GetConnectedDevice( int row) const { - return devices_[row].device_; + return devices_[row]->device_; } int DeviceManager::GetDatabaseId(int row) const { - return devices_[row].database_id_; + return devices_[row]->database_id_; } DeviceLister* DeviceManager::GetLister(int row) const { - return devices_[row].BestBackend()->lister_; + return devices_[row]->BestBackend()->lister_; } void DeviceManager::Disconnect(int row) { - DeviceInfo& info = devices_[row]; - if (!info.device_) // Already disconnected + DeviceInfo* info = devices_[row]; + if (!info->device_) // Already disconnected return; - info.device_.reset(); + info->device_.reset(); emit DeviceDisconnected(row); - emit dataChanged(index(row), index(row)); + QModelIndex index = ItemToIndex(info); + if (!index.isValid()) return; + emit dataChanged(index, index); } void DeviceManager::Forget(int row) { - DeviceInfo& info = devices_[row]; - if (info.database_id_ == -1) return; + DeviceInfo* info = devices_[row]; + if (info->database_id_ == -1) return; - if (info.device_) Disconnect(row); + if (info->device_) Disconnect(row); - backend_->RemoveDevice(info.database_id_); - info.database_id_ = -1; + backend_->RemoveDevice(info->database_id_); + info->database_id_ = -1; - if (!info.BestBackend()->lister_) { + if (!info->BestBackend()->lister_) { // It's not attached any more so remove it from the list beginRemoveRows(QModelIndex(), row, row); devices_.removeAt(row); @@ -689,11 +597,11 @@ void DeviceManager::Forget(int row) { } else { // It's still attached, set the name and icon back to what they were // originally - const QString id = info.BestBackend()->unique_id_; + const QString id = info->BestBackend()->unique_id_; - info.friendly_name_ = info.BestBackend()->lister_->MakeFriendlyName(id); - info.LoadIcon(info.BestBackend()->lister_->DeviceIcons(id), - info.friendly_name_); + info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id); + info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id), + info->friendly_name_); dataChanged(index(row, 0), index(row, 0)); } @@ -703,16 +611,16 @@ void DeviceManager::SetDeviceOptions(int row, const QString& friendly_name, const QString& icon_name, MusicStorage::TranscodeMode mode, Song::FileType format) { - DeviceInfo& info = devices_[row]; - info.friendly_name_ = friendly_name; - info.LoadIcon(QVariantList() << icon_name, friendly_name); - info.transcode_mode_ = mode; - info.transcode_format_ = format; + DeviceInfo* info = devices_[row]; + info->friendly_name_ = friendly_name; + info->LoadIcon(QVariantList() << icon_name, friendly_name); + info->transcode_mode_ = mode; + info->transcode_format_ = format; emit dataChanged(index(row, 0), index(row, 0)); - if (info.database_id_ != -1) - backend_->SetDeviceOptions(info.database_id_, friendly_name, icon_name, + if (info->database_id_ != -1) + backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format); } @@ -721,11 +629,13 @@ void DeviceManager::DeviceTaskStarted(int id) { if (!device) return; for (int i = 0; i < devices_.count(); ++i) { - DeviceInfo& info = devices_[i]; - if (info.device_.get() == device) { - active_tasks_[id] = index(i); - info.task_percentage_ = 0; - emit dataChanged(index(i), index(i)); + DeviceInfo* info = devices_[i]; + if (info->device_.get() == device) { + QModelIndex index = ItemToIndex(info); + if (!index.isValid()) continue; + active_tasks_[id] = index; + info->task_percentage_ = 0; + emit dataChanged(index, index); return; } } @@ -741,11 +651,11 @@ void DeviceManager::TasksChanged() { QPersistentModelIndex index = active_tasks_[task.id]; if (!index.isValid()) continue; - DeviceInfo& info = devices_[index.row()]; + DeviceInfo* info = IndexToItem(index); if (task.progress_max) - info.task_percentage_ = float(task.progress) / task.progress_max * 100; + info->task_percentage_ = float(task.progress) / task.progress_max * 100; else - info.task_percentage_ = 0; + info->task_percentage_ = 0; emit dataChanged(index, index); finished_tasks.removeAll(index); } @@ -753,8 +663,8 @@ void DeviceManager::TasksChanged() { for (const QPersistentModelIndex& index : finished_tasks) { if (!index.isValid()) continue; - DeviceInfo& info = devices_[index.row()]; - info.task_percentage_ = -1; + DeviceInfo* info = devices_[index.row()]; + info->task_percentage_ = -1; emit dataChanged(index, index); active_tasks_.remove(active_tasks_.key(index)); @@ -766,13 +676,14 @@ void DeviceManager::UnmountAsync(int row) { } void DeviceManager::Unmount(int row) { - DeviceInfo& info = devices_[row]; - if (info.database_id_ != -1 && !info.device_) return; + DeviceInfo* info = devices_[row]; + if (info->database_id_ != -1 && !info->device_) return; - if (info.device_) Disconnect(row); + if (info->device_) Disconnect(row); - if (info.BestBackend()->lister_) - info.BestBackend()->lister_->UnmountDevice(info.BestBackend()->unique_id_); + if (info->BestBackend()->lister_) + info->BestBackend()->lister_->UnmountDevice( + info->BestBackend()->unique_id_); } void DeviceManager::DeviceSongCountUpdated(int count) { @@ -782,5 +693,13 @@ void DeviceManager::DeviceSongCountUpdated(int count) { int row = FindDeviceById(device->unique_id()); if (row == -1) return; - emit dataChanged(index(row), index(row)); + QModelIndex index = ItemToIndex(devices_[row]); + if (!index.isValid()) return; + + emit dataChanged(index, index); +} + +void DeviceManager::LazyPopulate(DeviceInfo* parent, bool signal) { + if (parent->lazy_loaded) return; + parent->lazy_loaded = true; } diff --git a/src/devices/devicemanager.h b/src/devices/devicemanager.h index 3138831d56..8a13aa9fc1 100644 --- a/src/devices/devicemanager.h +++ b/src/devices/devicemanager.h @@ -22,20 +22,20 @@ #include -#include +#include #include #include +#include "core/simpletreemodel.h" +#include "deviceinfo.h" #include "library/librarymodel.h" class Application; class ConnectedDevice; -class Database; class DeviceLister; class DeviceStateFilterModel; -class TaskManager; -class DeviceManager : public QAbstractListModel { +class DeviceManager : public SimpleTreeModel { Q_OBJECT public: @@ -90,9 +90,8 @@ class DeviceManager : public QAbstractListModel { MusicStorage::TranscodeMode mode, Song::FileType format); - // QAbstractListModel - int rowCount(const QModelIndex& parent) const; - QVariant data(const QModelIndex& index, int role) const; + // QAbstractItemModel + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; public slots: void Unmount(int row); @@ -111,56 +110,11 @@ class DeviceManager : public QAbstractListModel { void LoadAllDevices(); void DeviceConnectFinished(const QString& id, bool success); + protected: + void LazyPopulate(DeviceInfo* item) { LazyPopulate(item, true); } + void LazyPopulate(DeviceInfo* item, bool signal); + private: - // Devices can be in three different states: - // 1) Remembered in the database but not physically connected at the moment. - // database_id valid, lister null, device null - // 2) Physically connected but the user hasn't "connected" it to Clementine - // yet. - // database_id == -1, lister valid, device null - // 3) Physically connected and connected to Clementine - // database_id valid, lister valid, device valid - // Devices in all states will have a unique_id. - struct DeviceInfo { - DeviceInfo(); - - // A device can be discovered in different ways (devicekit, gio, etc.) - // Sometimes the same device is discovered more than once. In this case - // the device will have multiple "backends". - struct Backend { - Backend(DeviceLister* lister = nullptr, const QString& id = QString()) - : lister_(lister), unique_id_(id) {} - - DeviceLister* lister_; // nullptr if not physically connected - QString unique_id_; - }; - - // Serialising to the database - void InitFromDb(const DeviceDatabaseBackend::Device& dev); - DeviceDatabaseBackend::Device SaveToDb() const; - - // Tries to load a good icon for the device. Sets icon_name_ and icon_. - void LoadIcon(const QVariantList& icons, const QString& name_hint); - - // Gets the best backend available (the one with the highest priority) - const Backend* BestBackend() const; - - int database_id_; // -1 if not remembered in the database - std::shared_ptr - device_; // nullptr if not connected to clementine - QList backends_; - - QString friendly_name_; - quint64 size_; - - QString icon_name_; - QIcon icon_; - - MusicStorage::TranscodeMode transcode_mode_; - Song::FileType transcode_format_; - - int task_percentage_; - }; void AddLister(DeviceLister* lister); template @@ -178,7 +132,7 @@ class DeviceManager : public QAbstractListModel { QIcon not_connected_overlay_; QList listers_; - QList devices_; + QList devices_; QMultiMap device_classes_; diff --git a/src/devices/deviceproperties.cpp b/src/devices/deviceproperties.cpp index 4ed0f84123..a469525fbc 100644 --- a/src/devices/deviceproperties.cpp +++ b/src/devices/deviceproperties.cpp @@ -97,7 +97,7 @@ void DeviceProperties::ShowDevice(int row) { ui_->transcode_format->model()->sort(0); } - index_ = manager_->index(row); + index_ = manager_->index(row, 0, QModelIndex()); // Basic information ui_->name->setText(index_.data(DeviceManager::Role_FriendlyName).toString()); diff --git a/src/devices/deviceview.cpp b/src/devices/deviceview.cpp index 7d62571aa2..b12e1f7f8d 100644 --- a/src/devices/deviceview.cpp +++ b/src/devices/deviceview.cpp @@ -304,7 +304,7 @@ void DeviceView::DeviceConnected(int row) { if (!device) return; QModelIndex sort_idx = - sort_model_->mapFromSource(app_->device_manager()->index(row)); + sort_model_->mapFromSource(app_->device_manager()->index(row, 0)); QSortFilterProxyModel* sort_model = new QSortFilterProxyModel(device->model()); @@ -319,7 +319,7 @@ void DeviceView::DeviceConnected(int row) { void DeviceView::DeviceDisconnected(int row) { merged_model_->RemoveSubModel( - sort_model_->mapFromSource(app_->device_manager()->index(row))); + sort_model_->mapFromSource(app_->device_manager()->index(row, 0))); } void DeviceView::Forget() { diff --git a/src/devices/filesystemdevice.cpp b/src/devices/filesystemdevice.cpp index 4805cab86d..17ced6e27a 100644 --- a/src/devices/filesystemdevice.cpp +++ b/src/devices/filesystemdevice.cpp @@ -39,8 +39,10 @@ FilesystemDevice::FilesystemDevice(const QUrl& url, DeviceLister* lister, watcher_thread_->start(QThread::IdlePriority); watcher_->set_device_name( - manager->data(manager->index(manager->FindDeviceById(unique_id)), - DeviceManager::Role_FriendlyName).toString()); + manager + ->data(manager->index(manager->FindDeviceById(unique_id), 0), + DeviceManager::Role_FriendlyName) + .toString()); watcher_->set_backend(backend_); watcher_->set_task_manager(app_->task_manager());