Qt: Revamp BattleChipView, add drag and drop

This commit is contained in:
Vicki Pfau 2019-02-25 20:15:50 -08:00
parent b45f30c58a
commit d1c6bcacd9
6 changed files with 264 additions and 94 deletions

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -5,18 +5,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BattleChipView.h" #include "BattleChipView.h"
#include "ConfigController.h"
#include "CoreController.h" #include "CoreController.h"
#include "GBAApp.h"
#include "ShortcutController.h" #include "ShortcutController.h"
#include "Window.h" #include "Window.h"
#include <QtAlgorithms> #include <QtAlgorithms>
#include <QFile>
#include <QFontMetrics> #include <QFontMetrics>
#include <QMessageBox> #include <QMessageBox>
#include <QMultiMap> #include <QMultiMap>
#include <QResource>
#include <QSettings> #include <QSettings>
#include <QStringList> #include <QStringList>
@ -27,10 +23,8 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
, m_controller(controller) , m_controller(controller)
, m_window(window) , m_window(window)
{ {
QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe");
QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe");
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.chipList->setModel(&m_model);
char title[9]; char title[9];
CoreController::Interrupter interrupter(m_controller); CoreController::Interrupter interrupter(m_controller);
@ -51,7 +45,7 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
m_ui.inserted->setChecked(Qt::Unchecked); m_ui.inserted->setChecked(Qt::Unchecked);
}); });
connect(m_ui.chipName, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), m_ui.chipId, [this](int id) { 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); connect(m_ui.inserted, &QAbstractButton::toggled, this, &BattleChipView::insertChip);
@ -61,7 +55,7 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck);
connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck); 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) { connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) {
if (on) { if (on) {
@ -85,13 +79,14 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter); connect(m_controller.get(), &CoreController::frameAvailable, this, &BattleChipView::advanceFrameCounter);
connect(m_ui.chipList, &QListWidget::itemClicked, this, [this](QListWidgetItem* item) { connect(m_ui.chipList, &QAbstractItemView::clicked, this, [this](const QModelIndex& index) {
QVariant chip = item->data(Qt::UserRole); QVariant chip = m_model.data(index, Qt::UserRole);
bool blocked = m_ui.chipId->blockSignals(true); bool blocked = m_ui.chipId->blockSignals(true);
m_ui.chipId->setValue(chip.toInt()); m_ui.chipId->setValue(chip.toInt());
m_ui.chipId->blockSignals(blocked); m_ui.chipId->blockSignals(blocked);
reinsert(); reinsert();
}); });
connect(m_ui.chipList, &QListView::indexesMoved, this, &BattleChipView::resort);
m_controller->attachBattleChipGate(); m_controller->attachBattleChipGate();
setFlavor(4); setFlavor(4);
@ -110,7 +105,8 @@ BattleChipView::~BattleChipView() {
void BattleChipView::setFlavor(int flavor) { void BattleChipView::setFlavor(int flavor) {
m_controller->setBattleChipFlavor(flavor); m_controller->setBattleChipFlavor(flavor);
loadChipNames(flavor); m_model.setFlavor(flavor);
m_ui.chipName->addItems(m_model.chipNames().values());
} }
void BattleChipView::insertChip(bool inserted) { void BattleChipView::insertChip(bool inserted) {
@ -141,55 +137,13 @@ void BattleChipView::addChip() {
if (insertedChip < 1) { if (insertedChip < 1) {
return; return;
} }
addChipId(insertedChip); m_model.addChip(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);
} }
void BattleChipView::removeChip() { void BattleChipView::removeChip() {
qDeleteAll(m_ui.chipList->selectedItems()); for (const auto& index : m_ui.chipList->selectionModel()->selectedIndexes()) {
} m_model.removeChip(index);
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;
} }
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() { void BattleChipView::advanceFrameCounter() {
@ -208,14 +162,14 @@ void BattleChipView::saveDeck() {
} }
QStringList deck; QStringList deck;
for (int i = 0; i < m_ui.chipList->count(); ++i) { for (int i = 0; i < m_model.rowCount(); ++i) {
deck.append(m_ui.chipList->item(i)->data(Qt::UserRole).toString()); deck.append(m_model.data(m_model.index(i, 0), Qt::UserRole).toString());
} }
QSettings ini(filename, QSettings::IniFormat); QSettings ini(filename, QSettings::IniFormat);
ini.clear(); ini.clear();
ini.beginGroup("BattleChipDeck"); ini.beginGroup("BattleChipDeck");
ini.setValue("version", m_flavor); ini.setValue("version", m_model.flavor());
ini.setValue("deck", deck.join(',')); ini.setValue("deck", deck.join(','));
ini.sync(); ini.sync();
} }
@ -229,7 +183,7 @@ void BattleChipView::loadDeck() {
QSettings ini(filename, QSettings::IniFormat); QSettings ini(filename, QSettings::IniFormat);
ini.beginGroup("BattleChipDeck"); ini.beginGroup("BattleChipDeck");
int flavor = ini.value("version").toInt(); int flavor = ini.value("version").toInt();
if (flavor != m_flavor) { if (flavor != m_model.flavor()) {
QMessageBox* error = new QMessageBox(this); QMessageBox* error = new QMessageBox(this);
error->setIcon(QMessageBox::Warning); error->setIcon(QMessageBox::Warning);
error->setStandardButtons(QMessageBox::Ok); error->setStandardButtons(QMessageBox::Ok);
@ -240,13 +194,30 @@ void BattleChipView::loadDeck() {
return; return;
} }
m_ui.chipList->clear(); QList<int> newDeck;
QStringList deck = ini.value("deck").toString().split(','); QStringList deck = ini.value("deck").toString().split(',');
for (const auto& item : deck) { for (const auto& item : deck) {
bool ok; bool ok;
int id = item.toInt(&ok); int id = item.toInt(&ok);
if (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());
}

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once #pragma once
#include "BattleChipModel.h"
#include <QDialog> #include <QDialog>
#include <memory> #include <memory>
@ -29,11 +31,11 @@ public slots:
void setFlavor(int); void setFlavor(int);
void insertChip(bool); void insertChip(bool);
void reinsert(); void reinsert();
void resort();
private slots: private slots:
void advanceFrameCounter(); void advanceFrameCounter();
void addChip(); void addChip();
void addChipId(int);
void removeChip(); void removeChip();
void saveDeck(); void saveDeck();
@ -46,10 +48,8 @@ private:
Ui::BattleChipView m_ui; Ui::BattleChipView m_ui;
QMap<int, int> m_chipIndexToId; BattleChipModel m_model;
QMap<int, QString> m_chipIdToName;
std::shared_ptr<CoreController> m_controller; std::shared_ptr<CoreController> m_controller;
int m_flavor;
int m_frameCounter = -1; int m_frameCounter = -1;
bool m_next = false; bool m_next = false;

View File

@ -15,7 +15,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0"> <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,0,0,0">
<item> <item>
<widget class="QListWidget" name="chipList"> <widget class="QListView" name="chipList">
<property name="acceptDrops"> <property name="acceptDrops">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -35,7 +35,7 @@
</size> </size>
</property> </property>
<property name="movement"> <property name="movement">
<enum>QListView::Static</enum> <enum>QListView::Free</enum>
</property> </property>
<property name="isWrapping" stdset="0"> <property name="isWrapping" stdset="0">
<bool>true</bool> <bool>true</bool>
@ -52,12 +52,6 @@
<property name="viewMode"> <property name="viewMode">
<enum>QListView::IconMode</enum> <enum>QListView::IconMode</enum>
</property> </property>
<property name="uniformItemSizes">
<bool>false</bool>
</property>
<property name="selectionRectVisible">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -132,9 +126,9 @@
</item> </item>
<item> <item>
<widget class="QWidget" name="advanced" native="true"> <widget class="QWidget" name="advanced" native="true">
<property name="visible"> <property name="visible">
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<layout class="QFormLayout" name="formLayout_2"> <layout class="QFormLayout" name="formLayout_2">
@ -260,21 +254,5 @@
</hint> </hint>
</hints> </hints>
</connection> </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>
</connections> </connections>
</ui> </ui>

View File

@ -147,6 +147,7 @@ set(UI_FILES
VideoView.ui) VideoView.ui)
set(GBA_SRC set(GBA_SRC
BattleChipModel.cpp
BattleChipView.cpp BattleChipView.cpp
GBAOverride.cpp) GBAOverride.cpp)