Qt: Revamp BattleChipView, add drag and drop
@@ -0,0 +1,167 @@
+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "BattleChipModel.h" + +#include "ConfigController.h" +#include "GBAApp.h" + +#include <QFile> +#include <QMimeData> +#include <QResource> + +using namespace QGBA; + +BattleChipModel::BattleChipModel(QObject* parent) + : QAbstractListModel(parent) +{ + QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe"); + QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe"); +} + +int BattleChipModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) { + return 0; + } + return m_deck.count(); +} + +QVariant BattleChipModel::data(const QModelIndex& index, int role) const { + const BattleChip& item = m_deck[index.row()]; + + switch (role) { + case Qt::DisplayRole: + return item.name; + case Qt::DecorationRole: + return item.icon; + case Qt::UserRole: + return item.id; + } + return QVariant(); +} + +Qt::ItemFlags BattleChipModel::flags(const QModelIndex& index) const { + return Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; +} + +bool BattleChipModel::removeRows(int row, int count, const QModelIndex& parent) { + if (parent.isValid()) { + return false; + } + beginRemoveRows(QModelIndex(), row, row + count - 1); + for (size_t i = 0; i < count; ++i) { + m_deck.removeAt(row); + } + endRemoveRows(); + return true; +} + +QStringList BattleChipModel::mimeTypes() const { + return {"text/plain"}; +} + +Qt::DropActions BattleChipModel::supportedDropActions() const { + return Qt::MoveAction; +} + +QMimeData* BattleChipModel::mimeData(const QModelIndexList& indices) const { + QStringList deck; + for (const QModelIndex& index : indices) { + if (index.parent().isValid()) { + continue; + } + deck.append(QString::number(m_deck[index.row()].id)); + } + + QMimeData* mimeData = new QMimeData(); + mimeData->setData("text/plain", deck.join(',').toLocal8Bit()); + return mimeData; +} + +bool BattleChipModel::dropMimeData(const QMimeData* data, Qt::DropAction, int row, int, const QModelIndex& parent) { + if (parent.parent().isValid()) { + return false; + } + QStringList deck = QString::fromLocal8Bit(data->data("text/plain")).split(','); + if (deck.isEmpty()) { + return true; + } + + row = parent.row(); + beginInsertRows(QModelIndex(), row, row + deck.count() - 1); + for (int i = 0; i < deck.count(); ++i) { + int id = deck[i].toInt(); + m_deck.insert(row + i, createChip(id)); + } + endInsertRows(); + return true; +} + +void BattleChipModel::setFlavor(int flavor) { + m_chipIdToName.clear(); + if (flavor == GBA_FLAVOR_BEAST_LINK_GATE_US) { + flavor = GBA_FLAVOR_BEAST_LINK_GATE; + } + m_flavor = flavor; + + QFile file(QString(":/exe/exe%1/chip-names.txt").arg(flavor)); + file.open(QIODevice::ReadOnly | QIODevice::Text); + int id = 0; + while (true) { + QByteArray line = file.readLine(); + if (line.isEmpty()) { + break; + } + ++id; + if (line.trimmed().isEmpty()) { + continue; + } + QString name = QString::fromUtf8(line).trimmed(); + m_chipIdToName[id] = name; + } + +} + +void BattleChipModel::addChip(int id) { + beginInsertRows(QModelIndex(), m_deck.count(), m_deck.count()); + m_deck.append(createChip(id)); + endInsertRows(); +} + +void BattleChipModel::removeChip(const QModelIndex& index) { + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_deck.removeAt(index.row()); + endRemoveRows(); +} + +void BattleChipModel::setChips(QList<int> ids) { + beginResetModel(); + m_deck.clear(); + for (int id : ids) { + m_deck.append(createChip(id)); + } + endResetModel(); +} + +void BattleChipModel::clear() { + beginResetModel(); + m_deck.clear(); + endResetModel(); +} + +BattleChipModel::BattleChip BattleChipModel::createChip(int id) const { + QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0')); + if (!QFile(path).exists()) { + path = QString(":/exe/exe%1/placeholder.png").arg(m_flavor); + } + QIcon icon(path); + + BattleChip chip = { + id, + m_chipIdToName[id], + icon + }; + return chip; +}
@@ -0,0 +1,53 @@
+/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include <QAbstractListModel> +#include <QIcon> + +namespace QGBA { + +class BattleChipModel : public QAbstractListModel { +Q_OBJECT + +public: + BattleChipModel(QObject* parent = nullptr); + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; + virtual Qt::DropActions supportedDropActions() const override; + virtual QStringList mimeTypes() const override; + virtual QMimeData* mimeData(const QModelIndexList& indices) const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction, int row, int column, const QModelIndex& parent) override; + + int flavor() const { return m_flavor; } + QMap<int, QString> chipNames() const { return m_chipIdToName; } + +public slots: + void setFlavor(int); + void addChip(int id); + void removeChip(const QModelIndex&); + void setChips(QList<int> ids); + void clear(); + +private: + struct BattleChip { + int id; + QString name; + QIcon icon; + }; + + BattleChip createChip(int id) const; + + QMap<int, QString> m_chipIdToName; + int m_flavor; + + QList<BattleChip> m_deck; +}; + +}
@@ -5,18 +5,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BattleChipView.h" -#include "ConfigController.h" #include "CoreController.h" -#include "GBAApp.h" #include "ShortcutController.h" #include "Window.h" #include <QtAlgorithms> -#include <QFile> #include <QFontMetrics> #include <QMessageBox> #include <QMultiMap> -#include <QResource> #include <QSettings> #include <QStringList>@@ -27,10 +23,8 @@ : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
, m_controller(controller) , m_window(window) { - QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe"); - QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe"); - m_ui.setupUi(this); + m_ui.chipList->setModel(&m_model); char title[9]; CoreController::Interrupter interrupter(m_controller);@@ -51,7 +45,7 @@ connect(m_ui.chipId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_ui.inserted, [this]() {
m_ui.inserted->setChecked(Qt::Unchecked); }); connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) { - m_ui.chipId->setValue(m_chipIndexToId[id]); + m_ui.chipId->setValue(m_model.chipNames().keys()[id]); }); connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip);@@ -61,7 +55,7 @@ connect(m_ui.remove, &QAbstractButton::clicked, this, &BattleChipView::removeChip);
connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck); - connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, m_ui.chipList, &QListWidget::clear); + connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, &m_model, &BattleChipModel::clear); connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) { if (on) {@@ -85,13 +79,14 @@ });
connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter); - connect(m_ui.chipList, &QListWidget::itemClicked, this, [this](QListWidgetItem* item) { - QVariant chip = item->data(Qt::UserRole); + connect(m_ui.chipList, &QAbstractItemView::clicked, this, [this](const QModelIndex& index) { + QVariant chip = m_model.data(index, Qt::UserRole); bool blocked = m_ui.chipId->blockSignals(true); m_ui.chipId->setValue(chip.toInt()); m_ui.chipId->blockSignals(blocked); reinsert(); }); + connect(m_ui.chipList, &QListView::indexesMoved, this, &BattleChipView::resort); m_controller->attachBattleChipGate(); setFlavor(4);@@ -110,7 +105,8 @@ }
void BattleChipView::setFlavor(int flavor) { m_controller->setBattleChipFlavor(flavor); - loadChipNames(flavor); + m_model.setFlavor(flavor); + m_ui.chipName->addItems(m_model.chipNames().values()); } void BattleChipView::insertChip(bool inserted) {@@ -141,55 +137,13 @@ int insertedChip = m_ui.chipId->value();
if (insertedChip < 1) { return; } - addChipId(insertedChip); -} - -void BattleChipView::addChipId(int id) { - QListWidgetItem* add = new QListWidgetItem(m_chipIdToName[id]); - add->setData(Qt::UserRole, id); - QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0')); - if (!QFile(path).exists()) { - path = QString(":/exe/exe%1/placeholder.png").arg(m_flavor); - } - add->setIcon(QPixmap(path).scaled(m_ui.chipList->iconSize())); - m_ui.chipList->addItem(add); + m_model.addChip(insertedChip); } void BattleChipView::removeChip() { - qDeleteAll(m_ui.chipList->selectedItems()); -} - -void BattleChipView::loadChipNames(int flavor) { - QStringList chipNames; - chipNames.append(tr("(None)")); - - m_chipIndexToId.clear(); - m_chipIdToName.clear(); - if (flavor == GBA_FLAVOR_BEAST_LINK_GATE_US) { - flavor = GBA_FLAVOR_BEAST_LINK_GATE; + for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) { + m_model.removeChip(index); } - m_flavor = flavor; - - QFile file(QString(":/exe/exe%1/chip-names.txt").arg(flavor)); - file.open(QIODevice::ReadOnly | QIODevice::Text); - int id = 0; - while (true) { - QByteArray line = file.readLine(); - if (line.isEmpty()) { - break; - } - ++id; - if (line.trimmed().isEmpty()) { - continue; - } - QString name = QString::fromUtf8(line).trimmed(); - m_chipIndexToId[chipNames.length()] = id; - m_chipIdToName[id] = name; - chipNames.append(name); - } - - m_ui.chipName->clear(); - m_ui.chipName->addItems(chipNames); } void BattleChipView::advanceFrameCounter() {@@ -208,14 +162,14 @@ return;
} QStringList deck; - for (int i = 0; i < m_ui.chipList->count(); ++i) { - deck.append(m_ui.chipList->item(i)->data(Qt::UserRole).toString()); + for (int i = 0; i < m_model.rowCount(); ++i) { + deck.append(m_model.data(m_model.index(i, 0), Qt::UserRole).toString()); } QSettings ini(filename, QSettings::IniFormat); ini.clear(); ini.beginGroup("BattleChipDeck"); - ini.setValue("version", m_flavor); + ini.setValue("version", m_model.flavor()); ini.setValue("deck", deck.join(',')); ini.sync(); }@@ -229,7 +183,7 @@
QSettings ini(filename, QSettings::IniFormat); ini.beginGroup("BattleChipDeck"); int flavor = ini.value("version").toInt(); - if (flavor != m_flavor) { + if (flavor != m_model.flavor()) { QMessageBox* error = new QMessageBox(this); error->setIcon(QMessageBox::Warning); error->setStandardButtons(QMessageBox::Ok);@@ -240,13 +194,30 @@ error->show();
return; } - m_ui.chipList->clear(); + QList<int> newDeck; QStringList deck = ini.value("deck").toString().split(','); for (const auto& item : deck) { bool ok; int id = item.toInt(&ok); if (ok) { - addChipId(id); + newDeck.append(id); } } + m_model.setChips(newDeck); } + +void BattleChipView::resort() { + QMap<int, int> chips; + for (int i = 0; i < m_model.rowCount(); ++i) { + QModelIndex index = m_model.index(i, 0); + QRect visualRect = m_ui.chipList->visualRect(index); + QSize gridSize = m_ui.chipList->gridSize(); + int x = visualRect.y() / gridSize.height(); + x *= m_ui.chipList->viewport()->width(); + x += visualRect.x(); + x *= m_model.rowCount(); + x += index.row(); + chips[x] = m_model.data(index, Qt::UserRole).toInt(); + } + m_model.setChips(chips.values()); +}
@@ -5,6 +5,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once +#include "BattleChipModel.h" + #include <QDialog> #include <memory>@@ -29,11 +31,11 @@ public slots:
void setFlavor(int); void insertChip(bool); void reinsert(); + void resort(); private slots: void advanceFrameCounter(); void addChip(); - void addChipId(int); void removeChip(); void saveDeck();@@ -46,10 +48,8 @@ void loadChipNames(int);
Ui::BattleChipView m_ui; - QMap<int, int> m_chipIndexToId; - QMap<int, QString> m_chipIdToName; + BattleChipModel m_model; std::shared_ptr<CoreController> m_controller; - int m_flavor; int m_frameCounter = -1; bool m_next = false;
@@ -15,7 +15,7 @@ <string>BattleChip Gate</string>
</property> <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0"> <item> - <widget class="QListWidget" name="chipList"> + <widget class="QListView" name="chipList"> <property name="acceptDrops"> <bool>true</bool> </property>@@ -35,7 +35,7 @@ <height>48</height>
</size> </property> <property name="movement"> - <enum>QListView::Static</enum> + <enum>QListView::Free</enum> </property> <property name="isWrapping" stdset="0"> <bool>true</bool>@@ -51,12 +51,6 @@ </size>
</property> <property name="viewMode"> <enum>QListView::IconMode</enum> - </property> - <property name="uniformItemSizes"> - <bool>false</bool> - </property> - <property name="selectionRectVisible"> - <bool>true</bool> </property> </widget> </item>@@ -132,9 +126,9 @@ </layout>
</item> <item> <widget class="QWidget" name="advanced" native="true"> - <property name="visible"> - <bool>false</bool> - </property> + <property name="visible"> + <bool>false</bool> + </property> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> <layout class="QFormLayout" name="formLayout_2">@@ -257,22 +251,6 @@ </hint>
<hint type="destinationlabel"> <x>396</x> <y>654</y> - </hint> - </hints> - </connection> - <connection> - <sender>chipList</sender> - <signal>indexesMoved(QModelIndexList)</signal> - <receiver>chipList</receiver> - <slot>doItemsLayout()</slot> - <hints> - <hint type="sourcelabel"> - <x>314</x> - <y>203</y> - </hint> - <hint type="destinationlabel"> - <x>314</x> - <y>203</y> </hint> </hints> </connection>
@@ -147,6 +147,7 @@ TileView.ui
VideoView.ui) set(GBA_SRC + BattleChipModel.cpp BattleChipView.cpp GBAOverride.cpp)