#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 #include #include #include #include #include #include #include #include "mainwindow.h" SampleListModel::SampleListModel(QObject* parent, MainWindow* window) : QAbstractItemModel (parent) { this->window = window; root = std::make_shared(); auto view = static_cast(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(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 SampleListModel::itemAt(const QModelIndex& index) { if (!index.isValid() || index.model() != this || !index.internalPointer()) return nullptr; auto dn = static_cast(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(QObject::parent()); if (auto ind = view->currentIndex(); ind.isValid()) dn = static_cast(ind.internalPointer()); hold = root; // hold old tree alive until next refresh root = std::make_shared(); 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(root.get()); if (parent.isValid()) dn = static_cast(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(index.internalPointer()); auto c = dn->name; if (c.isEmpty()) return "(unnamed)"; return c; } if (role == Qt::EditRole) { auto dn = static_cast(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(index.internalPointer()); dn->name = value.toString(); propagateSampleNames(dn); refresh(); /*if (auto smp = const_cast(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(parent.internalPointer()); if (!dn) dn = const_cast(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(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(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(this); s << reinterpret_cast(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(parent.internalPointer()); if (!tdn) tdn = root.get(); if (!tdn->isDirectory()) tdn = tdn->parent; QString p = tdn->path(); QList 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(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(this)) return false; s >> p; auto dn = reinterpret_cast(p); if (!dn) return false; //auto ti = index(row, column, parent); auto tdn = reinterpret_cast(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; } //