294 lines
10 KiB
C++
294 lines
10 KiB
C++
#include "samplelistmodel.h"
|
|
using Xybrid::UI::SampleListModel;
|
|
|
|
#include "data/sample.h"
|
|
using Xybrid::Data::Project;
|
|
using Xybrid::Data::Pattern;
|
|
using Xybrid::Data::Sample;
|
|
|
|
#include "uisocket.h"
|
|
|
|
#include "editing/projectcommands.h"
|
|
#include "editing/patterncommands.h"
|
|
using namespace Xybrid::Editing;
|
|
|
|
#include <QTreeView>
|
|
|
|
#include <QDebug>
|
|
#include <QTimer>
|
|
#include <QStringBuilder>
|
|
#include <QMimeData>
|
|
#include <QUrl>
|
|
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include "mainwindow.h"
|
|
|
|
|
|
SampleListModel::SampleListModel(QObject* parent, MainWindow* window) : QAbstractItemModel (parent) {
|
|
this->window = window;
|
|
root = std::make_shared<DirectoryNode>();
|
|
auto view = static_cast<QTreeView*>(parent);
|
|
|
|
connect(window, &MainWindow::projectLoaded, this, [this, view] {
|
|
view->setCurrentIndex(index(-1, -1)); // select none
|
|
refresh();
|
|
});
|
|
|
|
view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(view, &QTreeView::customContextMenuRequested, this, [this, view](const QPoint& pt) {
|
|
auto dn = static_cast<DirectoryNode*>(view->indexAt(pt).internalPointer());
|
|
if (!dn) return; // no items applicable yet
|
|
|
|
auto menu = new QMenu(view);
|
|
if (dn) {
|
|
menu->addAction("Rename...", this, [this, view, dn] { view->edit(createIndex(dn->index, 0, dn)); });
|
|
if (dn->isDirectory()) {
|
|
menu->addAction("Unpack Folder", this, [this, dn] {
|
|
dn->name = "";
|
|
propagateSampleNames(dn);
|
|
refresh();
|
|
});
|
|
menu->addAction("Delete Folder", this, [this, dn, view] {
|
|
if (QMessageBox::warning(view, "Are you sure?", QString("Remove folder \"%1\"?\n(This cannot be undone!)").arg(dn->name), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
|
auto* project = this->window->getProject().get();
|
|
dn->treeExec([project](DirectoryNode* dn) {
|
|
if (dn->isDirectory()) return;
|
|
auto smp = project->samples[dn->data.toUuid()];
|
|
smp->project->samples.remove(smp->uuid);
|
|
smp->project = nullptr;
|
|
});
|
|
refresh();
|
|
});
|
|
} else {
|
|
auto* project = this->window->getProject().get();
|
|
auto smp = project->samples[dn->data.toUuid()];
|
|
menu->addAction("Delete Sample", this, [this, dn, smp, view] {
|
|
if (QMessageBox::warning(view, "Are you sure?", QString("Remove sample \"%1\"?\n(This cannot be undone!)").arg(dn->name), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) return;
|
|
smp->project->samples.remove(smp->uuid);
|
|
smp->project = nullptr;
|
|
refresh();
|
|
});
|
|
}
|
|
}
|
|
|
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
menu->popup(view->mapToGlobal(pt));
|
|
|
|
});
|
|
|
|
connect(window->uiSocket(), &UISocket::updatePatternLists, this, [this] { refresh(); });
|
|
}
|
|
|
|
std::shared_ptr<Sample> SampleListModel::itemAt(const QModelIndex& index) {
|
|
if (!index.isValid() || index.model() != this || !index.internalPointer()) return nullptr;
|
|
auto dn = static_cast<DirectoryNode*>(index.internalPointer());
|
|
if (dn->isDirectory()) return nullptr;
|
|
auto* project = window->getProject().get();
|
|
if (!project) return nullptr;
|
|
return project->samples[dn->data.toUuid()];
|
|
}
|
|
|
|
void SampleListModel::refresh() {
|
|
DirectoryNode* dn = nullptr;
|
|
auto view = static_cast<QTreeView*>(QObject::parent());
|
|
if (auto ind = view->currentIndex(); ind.isValid()) dn = static_cast<DirectoryNode*>(ind.internalPointer());
|
|
|
|
hold = root; // hold old tree alive until next refresh
|
|
root = std::make_shared<DirectoryNode>();
|
|
auto* project = window->getProject().get();
|
|
if (!project) return;
|
|
for (auto s : project->samples) root->placeData(s->name, s->uuid);
|
|
root->sortTree();
|
|
|
|
view->setCurrentIndex(QModelIndex());
|
|
|
|
emit layoutChanged();
|
|
|
|
// find previously selected path
|
|
if (dn) if (auto f = root->findPath(dn->path()); f) view->setCurrentIndex(createIndex(f->index, 0, f));
|
|
|
|
auto ii = view->currentIndex();
|
|
while (ii.isValid()) { // expand down to selected
|
|
view->expand(ii);
|
|
ii = ii.parent();
|
|
}
|
|
}
|
|
|
|
int SampleListModel::rowCount(const QModelIndex& parent [[maybe_unused]]) const {
|
|
auto dn = const_cast<DirectoryNode*>(root.get());
|
|
if (parent.isValid()) dn = static_cast<DirectoryNode*>(parent.internalPointer());
|
|
return dn->children.size();
|
|
}
|
|
|
|
int SampleListModel::columnCount(const QModelIndex &parent [[maybe_unused]]) const {
|
|
return 1;
|
|
}
|
|
|
|
QVariant SampleListModel::data(const QModelIndex &index, int role) const {
|
|
auto* project = window->getProject().get();
|
|
if (!project) return QVariant();
|
|
if (!index.internalPointer()) return QVariant();
|
|
if (role == Qt::DisplayRole) {
|
|
auto dn = static_cast<DirectoryNode*>(index.internalPointer());
|
|
auto c = dn->name;
|
|
if (c.isEmpty()) return "(unnamed)";
|
|
return c;
|
|
}
|
|
if (role == Qt::EditRole) {
|
|
auto dn = static_cast<DirectoryNode*>(index.internalPointer());
|
|
return dn->name;
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
void SampleListModel::propagateSampleNames(DirectoryNode* dn) {
|
|
if (!dn->data.isNull()) {
|
|
auto* project = window->getProject().get();
|
|
project->samples[dn->data.toUuid()]->name = dn->path();
|
|
} else for (auto c : dn->children) propagateSampleNames(c);
|
|
}
|
|
|
|
bool SampleListModel::setData(const QModelIndex& index, const QVariant& value, int role) {
|
|
if (role == Qt::EditRole) {
|
|
auto dn = static_cast<DirectoryNode*>(index.internalPointer());
|
|
dn->name = value.toString();
|
|
propagateSampleNames(dn);
|
|
refresh();
|
|
/*if (auto smp = const_cast<SampleListModel*>(this)->itemAt(index); smp) {
|
|
smp->name = value.toString();
|
|
refresh();
|
|
} else { // directory
|
|
// TODO
|
|
}*/
|
|
}
|
|
/*if (role == Qt::EditRole) {
|
|
auto* project = window->getProject().get();
|
|
if (!project) return true;
|
|
auto smp = itemAt(index);
|
|
smp->name = value.toString();
|
|
refresh();
|
|
}*/
|
|
return true;
|
|
}
|
|
|
|
Qt::ItemFlags SampleListModel::flags(const QModelIndex &index) const {
|
|
return Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | QAbstractItemModel::flags(index);
|
|
}
|
|
|
|
QModelIndex SampleListModel::index(int row, int column, const QModelIndex& parent) const {
|
|
DirectoryNode* dn = nullptr;
|
|
if (parent.isValid()) dn = static_cast<DirectoryNode*>(parent.internalPointer());
|
|
if (!dn) dn = const_cast<DirectoryNode*>(root.get());
|
|
|
|
if (row >= 0 && row < dn->children.size()) return createIndex(row, column, dn->children[row]);
|
|
|
|
return QModelIndex();
|
|
}
|
|
|
|
QModelIndex SampleListModel::parent(const QModelIndex& index) const {
|
|
if (!index.isValid()) return QModelIndex();
|
|
auto dn = static_cast<DirectoryNode*>(index.internalPointer());
|
|
if (!dn->parent || dn->parent == root.get()) return QModelIndex();
|
|
return createIndex(dn->parent->index, 0, dn->parent);
|
|
}
|
|
|
|
Qt::DropActions SampleListModel::supportedDropActions() const {
|
|
return Qt::CopyAction;
|
|
}
|
|
|
|
QStringList SampleListModel::mimeTypes() const {
|
|
QStringList types;
|
|
//types << "xybrid-internal/x-pattern-index";
|
|
return types;
|
|
}
|
|
|
|
QMimeData* SampleListModel::mimeData(const QModelIndexList& indexes) const {
|
|
if (indexes.empty()) return new QMimeData();
|
|
auto dn = static_cast<DirectoryNode*>(indexes.first().internalPointer());
|
|
if (!dn) return new QMimeData();
|
|
|
|
auto d = new QMimeData();
|
|
QByteArray b;
|
|
b.resize(sizeof(void*)*2);
|
|
QDataStream s(&b, QIODevice::WriteOnly);
|
|
s << reinterpret_cast<qintptr>(this);
|
|
s << reinterpret_cast<qintptr>(dn);
|
|
|
|
d->setData("xybrid-internal/x-sample-entry-move", b);
|
|
return d;
|
|
}
|
|
|
|
bool SampleListModel::canDropMimeData(const QMimeData *data, Qt::DropAction action [[maybe_unused]], int row [[maybe_unused]], int column [[maybe_unused]], const QModelIndex &parent [[maybe_unused]]) const {
|
|
return data->hasUrls() || data->hasFormat("xybrid-internal/x-sample-entry-move");
|
|
}
|
|
|
|
|
|
bool SampleListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row [[maybe_unused]], int column [[maybe_unused]], const QModelIndex &parent [[maybe_unused]]) {
|
|
if (data->hasUrls()) {
|
|
if (action == Qt::IgnoreAction) return true; // can accept type
|
|
|
|
auto tdn = reinterpret_cast<DirectoryNode*>(parent.internalPointer());
|
|
if (!tdn) tdn = root.get();
|
|
if (!tdn->isDirectory()) tdn = tdn->parent;
|
|
QString p = tdn->path();
|
|
|
|
|
|
QList<QUrl> urls = data->urls();
|
|
bool success = false;
|
|
for (auto u : urls) {
|
|
if (!u.isLocalFile()) continue;
|
|
auto smp = Sample::fromFile(u.toLocalFile());
|
|
if (smp) { // valid sample returned
|
|
auto prj = window->getProject();
|
|
smp->project = prj.get();
|
|
prj->samples[smp->uuid] = smp;
|
|
if (!p.isEmpty()) smp->name = p % '/' % smp->name; // place in folder
|
|
|
|
success = true;
|
|
}
|
|
}
|
|
if (success) {
|
|
auto view = static_cast<QTreeView*>(QObject::parent());
|
|
view->setCurrentIndex(parent); // focus wherever you dragged
|
|
refresh();
|
|
}
|
|
return success;
|
|
} else if (data->hasFormat("xybrid-internal/x-sample-entry-move")) {
|
|
if (action == Qt::IgnoreAction) return true; // can accept type
|
|
|
|
QByteArray b = data->data("xybrid-internal/x-sample-entry-move");
|
|
QDataStream s(&b, QIODevice::ReadOnly);
|
|
qintptr p;
|
|
s >> p;
|
|
if (p != reinterpret_cast<qintptr>(this)) return false;
|
|
s >> p;
|
|
auto dn = reinterpret_cast<DirectoryNode*>(p);
|
|
if (!dn) return false;
|
|
|
|
//auto ti = index(row, column, parent);
|
|
auto tdn = reinterpret_cast<DirectoryNode*>(parent.internalPointer());
|
|
if (!tdn) tdn = root.get();
|
|
if (!tdn->isDirectory()) tdn = tdn->parent;
|
|
|
|
if (tdn->isChildOf(dn)) return false; // can't drag a folder within itself
|
|
|
|
dn->parent = tdn;
|
|
propagateSampleNames(dn);
|
|
refresh();
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|