Clementine-audio-player-Mac.../src/playlist/playlistlistmodel.cpp

228 lines
6.9 KiB
C++

#include "playlistlistmodel.h"
#include <QMimeData>
#include "core/logging.h"
PlaylistListModel::PlaylistListModel(QObject* parent)
: QStandardItemModel(parent), dropping_rows_(false) {
connect(this, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
SLOT(RowsChanged(QModelIndex, QModelIndex)));
connect(this, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)),
SLOT(RowsAboutToBeRemoved(QModelIndex, int, int)));
connect(this, SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(RowsInserted(QModelIndex, int, int)));
}
void PlaylistListModel::SetIcons(const QIcon& playlist_icon,
const QIcon& folder_icon) {
playlist_icon_ = playlist_icon;
folder_icon_ = folder_icon;
}
bool PlaylistListModel::dropMimeData(const QMimeData* data,
Qt::DropAction action, int row, int column,
const QModelIndex& parent) {
dropping_rows_ = true;
bool ret =
QStandardItemModel::dropMimeData(data, action, row, column, parent);
dropping_rows_ = false;
return ret;
}
QString PlaylistListModel::ItemPath(const QStandardItem* item) const {
QStringList components;
const QStandardItem* current = item;
while (current) {
if (current->data(Role_Type).toInt() == Type_Folder) {
components.insert(0, current->data(Qt::DisplayRole).toString());
}
current = current->parent();
}
return components.join("/");
}
void PlaylistListModel::RowsChanged(const QModelIndex& begin,
const QModelIndex& end) {
AddRowMappings(begin, end);
}
void PlaylistListModel::RowsInserted(const QModelIndex& parent, int start,
int end) {
// RowsChanged will take care of these when dropping.
if (!dropping_rows_) {
AddRowMappings(index(start, 0, parent), index(end, 0, parent));
}
}
void PlaylistListModel::AddRowMappings(const QModelIndex& begin,
const QModelIndex& end) {
const QString parent_path = ItemPath(itemFromIndex(begin));
for (int i = begin.row(); i <= end.row(); ++i) {
const QModelIndex index = begin.sibling(i, 0);
QStandardItem* item = itemFromIndex(index);
AddRowItem(item, parent_path);
}
}
void PlaylistListModel::AddRowItem(QStandardItem* item,
const QString& parent_path) {
switch (item->data(Role_Type).toInt()) {
case Type_Track: {
// const int id = item->data(Role_TrackId).toInt();
// TODO
break;
}
case Type_Playlist: {
const int id = item->data(Role_PlaylistId).toInt();
playlists_by_id_[id] = item;
if (dropping_rows_) {
emit PlaylistPathChanged(id, parent_path);
}
break;
}
case Type_Folder:
for (int j = 0; j < item->rowCount(); ++j) {
QStandardItem* child_item = item->child(j);
AddRowItem(child_item, parent_path);
}
break;
}
}
void PlaylistListModel::RowsAboutToBeRemoved(const QModelIndex& parent,
int start, int end) {
for (int i = start; i <= end; ++i) {
const QModelIndex idx = index(i, 0, parent);
const QStandardItem* item = itemFromIndex(idx);
switch (idx.data(Role_Type).toInt()) {
case Type_Playlist: {
const int id = idx.data(Role_PlaylistId).toInt();
QMap<int, QStandardItem*>::Iterator it = playlists_by_id_.find(id);
if (it != playlists_by_id_.end() && it.value() == item) {
playlists_by_id_.erase(it);
}
break;
}
case Type_Folder:
break;
}
}
}
QStandardItem* PlaylistListModel::PlaylistById(int id) const {
return playlists_by_id_[id];
}
QStandardItem* PlaylistListModel::FolderByPath(const QString& path) {
if (path.isEmpty()) {
return invisibleRootItem();
}
// Walk down from the root until we find the target folder. This is pretty
// inefficient but maintaining a path -> item map is difficult.
QStandardItem* parent = invisibleRootItem();
const QStringList parts = path.split('/', QString::SkipEmptyParts);
for (const QString& part : parts) {
QStandardItem* matching_child = nullptr;
const int child_count = parent->rowCount();
for (int i = 0; i < child_count; ++i) {
if (parent->child(i)->data(Qt::DisplayRole).toString() == part) {
matching_child = parent->child(i);
break;
}
}
// Does this folder exist already?
if (matching_child) {
parent = matching_child;
} else {
QStandardItem* child = NewFolder(part);
parent->appendRow(child);
parent = child;
}
}
return parent;
}
QStandardItem* PlaylistListModel::NewFolder(const QString& name) const {
QStandardItem* ret = new QStandardItem;
ret->setText(name);
ret->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type);
ret->setIcon(folder_icon_);
ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
return ret;
}
QStandardItem* PlaylistListModel::NewPlaylist(const QString& name,
int id) const {
QStandardItem* ret = new QStandardItem;
ret->setText(name);
ret->setData(PlaylistListModel::Type_Playlist, PlaylistListModel::Role_Type);
ret->setData(id, PlaylistListModel::Role_PlaylistId);
ret->setIcon(playlist_icon_);
ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsEnabled |
Qt::ItemIsSelectable | Qt::ItemIsEditable);
return ret;
}
QStandardItem* PlaylistListModel::NewTrack(const Song& song) const {
QStandardItem* ret = new QStandardItem;
ret->setText(song.artist() + " - " + song.title());
ret->setData(PlaylistListModel::Type_Track, PlaylistListModel::Role_Type);
ret->setData(song.id(), PlaylistListModel::Role_TrackId);
ret->setIcon(track_icon_);
ret->setFlags(Qt::ItemIsDragEnabled | Qt::ItemIsEnabled |
Qt::ItemIsSelectable);
return ret;
}
bool PlaylistListModel::setData(const QModelIndex& index, const QVariant& value,
int role) {
if (!QStandardItemModel::setData(index, value, role)) {
return false;
}
switch (index.data(Role_Type).toInt()) {
case Type_Playlist:
emit PlaylistRenamed(index.data(Role_PlaylistId).toInt(),
value.toString());
break;
case Type_Folder:
// Walk all the children and modify their paths.
UpdatePathsRecursive(index);
break;
}
return true;
}
void PlaylistListModel::UpdatePathsRecursive(const QModelIndex& parent) {
switch (parent.data(Role_Type).toInt()) {
case Type_Playlist:
emit PlaylistPathChanged(parent.data(Role_PlaylistId).toInt(),
ItemPath(itemFromIndex(parent)));
break;
case Type_Folder:
for (int i = 0; i < rowCount(parent); ++i) {
UpdatePathsRecursive(index(i, 0, parent));
}
break;
}
}