mirror of https://github.com/mgba-emu/mgba.git
Core: Memory search repeat and refresh
This commit is contained in:
parent
f2db707bb2
commit
cf7017dd86
|
@ -41,6 +41,7 @@ DECLARE_VECTOR(mCoreMemorySearchResults, struct mCoreMemorySearchResult);
|
||||||
|
|
||||||
struct mCore;
|
struct mCore;
|
||||||
void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit);
|
void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* out, size_t limit);
|
||||||
|
void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout);
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
||||||
|
|
|
@ -284,3 +284,73 @@ void mCoreMemorySearch(struct mCore* core, const struct mCoreMemorySearchParams*
|
||||||
found += _search(mem, size, block, params, out, limit ? limit - found : 0);
|
found += _search(mem, size, block, params, out, limit ? limit - found : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mCoreMemorySearchRepeat(struct mCore* core, const struct mCoreMemorySearchParams* params, struct mCoreMemorySearchResults* inout) {
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < mCoreMemorySearchResultsSize(inout); ++i) {
|
||||||
|
struct mCoreMemorySearchResult* res = mCoreMemorySearchResultsGetPointer(inout, i);
|
||||||
|
switch (res->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
|
switch (params->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
|
if (core->rawRead8(core, res->address, res->segment) != params->value8) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
if (core->rawRead8(core, res->address, res->segment) != params->value16) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
if (core->rawRead32(core, res->address, res->segment) != params->value32) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
switch (params->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
|
if (core->rawRead16(core, res->address, res->segment) != params->value16) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
if (core->rawRead32(core, res->address, res->segment) != params->value32) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
switch (params->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
|
if (core->rawRead32(core, res->address, res->segment) != params->value32) {
|
||||||
|
mCoreMemorySearchResultsShift(inout, i, 1);
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_STRING:
|
||||||
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
|
// TOOD
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "MemorySearch.h"
|
#include "MemorySearch.h"
|
||||||
|
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
#include <mgba/core/mem-search.h>
|
|
||||||
|
|
||||||
#include "GameController.h"
|
#include "GameController.h"
|
||||||
|
|
||||||
|
@ -19,15 +18,89 @@ MemorySearch::MemorySearch(GameController* controller, QWidget* parent)
|
||||||
{
|
{
|
||||||
m_ui.setupUi(this);
|
m_ui.setupUi(this);
|
||||||
|
|
||||||
|
mCoreMemorySearchResultsInit(&m_results, 0);
|
||||||
connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search);
|
connect(m_ui.search, &QPushButton::clicked, this, &MemorySearch::search);
|
||||||
|
connect(m_ui.searchWithin, &QPushButton::clicked, this, &MemorySearch::searchWithin);
|
||||||
|
connect(m_ui.refresh, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||||
|
connect(m_ui.numHex, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||||
|
connect(m_ui.numDec, &QPushButton::clicked, this, &MemorySearch::refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemorySearch::~MemorySearch() {
|
||||||
|
mCoreMemorySearchResultsDeinit(&m_results);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemorySearch::createParams(mCoreMemorySearchParams* params) {
|
||||||
|
params->memoryFlags = mCORE_MEMORY_RW;
|
||||||
|
mCore* core = m_controller->thread()->core;
|
||||||
|
|
||||||
|
QByteArray string;
|
||||||
|
bool ok = false;
|
||||||
|
if (m_ui.typeNum->isChecked()) {
|
||||||
|
if (m_ui.bits8->isChecked()) {
|
||||||
|
params->type = mCORE_MEMORY_SEARCH_8;
|
||||||
|
}
|
||||||
|
if (m_ui.bits16->isChecked()) {
|
||||||
|
params->type = mCORE_MEMORY_SEARCH_16;
|
||||||
|
}
|
||||||
|
if (m_ui.bits32->isChecked()) {
|
||||||
|
params->type = mCORE_MEMORY_SEARCH_32;
|
||||||
|
}
|
||||||
|
if (m_ui.numHex->isChecked()) {
|
||||||
|
bool ok;
|
||||||
|
uint32_t v = m_ui.value->text().toUInt(&ok, 16);
|
||||||
|
if (ok) {
|
||||||
|
switch (params->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
|
ok = v < 0x100;
|
||||||
|
params->value8 = v;
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
ok = v < 0x10000;
|
||||||
|
params->value16 = v;
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
params->value32 = v;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_ui.numDec->isChecked()) {
|
||||||
|
uint32_t v = m_ui.value->text().toUInt(&ok, 10);
|
||||||
|
if (ok) {
|
||||||
|
switch (params->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
|
ok = v < 0x100;
|
||||||
|
params->value8 = v;
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
ok = v < 0x10000;
|
||||||
|
params->value16 = v;
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
params->value32 = v;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_ui.typeStr->isChecked()) {
|
||||||
|
params->type = mCORE_MEMORY_SEARCH_STRING;
|
||||||
|
m_string = m_ui.value->text().toLocal8Bit();
|
||||||
|
params->valueStr = m_string.constData();
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemorySearch::search() {
|
void MemorySearch::search() {
|
||||||
mCoreMemorySearchResults res;
|
mCoreMemorySearchResultsClear(&m_results);
|
||||||
mCoreMemorySearchResultsInit(&res, 0);
|
|
||||||
|
|
||||||
mCoreMemorySearchParams params;
|
mCoreMemorySearchParams params;
|
||||||
params.memoryFlags = mCORE_MEMORY_RW;
|
|
||||||
|
|
||||||
GameController::Interrupter interrupter(m_controller);
|
GameController::Interrupter interrupter(m_controller);
|
||||||
if (!m_controller->isLoaded()) {
|
if (!m_controller->isLoaded()) {
|
||||||
|
@ -35,96 +108,74 @@ void MemorySearch::search() {
|
||||||
}
|
}
|
||||||
mCore* core = m_controller->thread()->core;
|
mCore* core = m_controller->thread()->core;
|
||||||
|
|
||||||
QByteArray string;
|
if (createParams(¶ms)) {
|
||||||
if (m_ui.typeNum->isChecked()) {
|
mCoreMemorySearch(core, ¶ms, &m_results, LIMIT);
|
||||||
if (m_ui.bits8->isChecked()) {
|
|
||||||
params.type = mCORE_MEMORY_SEARCH_8;
|
|
||||||
}
|
|
||||||
if (m_ui.bits16->isChecked()) {
|
|
||||||
params.type = mCORE_MEMORY_SEARCH_16;
|
|
||||||
}
|
|
||||||
if (m_ui.bits32->isChecked()) {
|
|
||||||
params.type = mCORE_MEMORY_SEARCH_32;
|
|
||||||
}
|
|
||||||
if (m_ui.numHex->isChecked()) {
|
|
||||||
bool ok;
|
|
||||||
uint32_t v = m_ui.value->text().toUInt(&ok, 16);
|
|
||||||
if (ok) {
|
|
||||||
switch (params.type) {
|
|
||||||
case mCORE_MEMORY_SEARCH_8:
|
|
||||||
ok = v < 0x100;
|
|
||||||
params.value8 = v;
|
|
||||||
break;
|
|
||||||
case mCORE_MEMORY_SEARCH_16:
|
|
||||||
ok = v < 0x10000;
|
|
||||||
params.value16 = v;
|
|
||||||
break;
|
|
||||||
case mCORE_MEMORY_SEARCH_32:
|
|
||||||
params.value32 = v;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ok) {
|
|
||||||
mCoreMemorySearch(core, ¶ms, &res, 10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_ui.numDec->isChecked()) {
|
|
||||||
bool ok;
|
|
||||||
uint32_t v = m_ui.value->text().toUInt(&ok, 10);
|
|
||||||
if (ok) {
|
|
||||||
switch (params.type) {
|
|
||||||
case mCORE_MEMORY_SEARCH_8:
|
|
||||||
ok = v < 0x100;
|
|
||||||
params.value8 = v;
|
|
||||||
break;
|
|
||||||
case mCORE_MEMORY_SEARCH_16:
|
|
||||||
ok = v < 0x10000;
|
|
||||||
params.value16 = v;
|
|
||||||
break;
|
|
||||||
case mCORE_MEMORY_SEARCH_32:
|
|
||||||
params.value32 = v;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ok) {
|
|
||||||
mCoreMemorySearch(core, ¶ms, &res, 10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_ui.typeStr->isChecked()) {
|
|
||||||
params.type = mCORE_MEMORY_SEARCH_STRING;
|
|
||||||
string = m_ui.value->text().toLocal8Bit();
|
|
||||||
params.valueStr = string;
|
|
||||||
mCoreMemorySearch(core, ¶ms, &res, 10000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui.results->clear();
|
refresh();
|
||||||
m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&res));
|
}
|
||||||
for (size_t i = 0; i < mCoreMemorySearchResultsSize(&res); ++i) {
|
|
||||||
mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&res, i);
|
void MemorySearch::searchWithin() {
|
||||||
|
mCoreMemorySearchParams params;
|
||||||
|
|
||||||
|
GameController::Interrupter interrupter(m_controller);
|
||||||
|
if (!m_controller->isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCore* core = m_controller->thread()->core;
|
||||||
|
|
||||||
|
if (createParams(¶ms)) {
|
||||||
|
mCoreMemorySearchRepeat(core, ¶ms, &m_results);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemorySearch::refresh() {
|
||||||
|
GameController::Interrupter interrupter(m_controller);
|
||||||
|
if (!m_controller->isLoaded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCore* core = m_controller->thread()->core;
|
||||||
|
|
||||||
|
m_ui.results->clearContents();
|
||||||
|
m_ui.results->setRowCount(mCoreMemorySearchResultsSize(&m_results));
|
||||||
|
for (size_t i = 0; i < mCoreMemorySearchResultsSize(&m_results); ++i) {
|
||||||
|
mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i);
|
||||||
QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0')));
|
QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0')));
|
||||||
m_ui.results->setItem(i, 0, item);
|
m_ui.results->setItem(i, 0, item);
|
||||||
|
if (m_ui.numHex->isChecked()) {
|
||||||
switch (result->type) {
|
switch (result->type) {
|
||||||
case mCORE_MEMORY_SEARCH_8:
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, -1), 2, 16, QChar('0')));
|
item = new QTableWidgetItem(QString("%1").arg(core->rawRead8(core, result->address, result->segment), 2, 16, QChar('0')));
|
||||||
break;
|
break;
|
||||||
case mCORE_MEMORY_SEARCH_16:
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, -1), 4, 16, QChar('0')));
|
item = new QTableWidgetItem(QString("%1").arg(core->rawRead16(core, result->address, result->segment), 4, 16, QChar('0')));
|
||||||
break;
|
break;
|
||||||
case mCORE_MEMORY_SEARCH_GUESS:
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
case mCORE_MEMORY_SEARCH_32:
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, -1), 8, 16, QChar('0')));
|
item = new QTableWidgetItem(QString("%1").arg(core->rawRead32(core, result->address, result->segment), 8, 16, QChar('0')));
|
||||||
break;
|
break;
|
||||||
case mCORE_MEMORY_SEARCH_STRING:
|
case mCORE_MEMORY_SEARCH_STRING:
|
||||||
item = new QTableWidgetItem(params.valueStr); // TODO
|
item = new QTableWidgetItem("?"); // TODO
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (result->type) {
|
||||||
|
case mCORE_MEMORY_SEARCH_8:
|
||||||
|
item = new QTableWidgetItem(QString::number(core->rawRead8(core, result->address, result->segment)));
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_16:
|
||||||
|
item = new QTableWidgetItem(QString::number(core->rawRead16(core, result->address, result->segment)));
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_GUESS:
|
||||||
|
case mCORE_MEMORY_SEARCH_32:
|
||||||
|
item = new QTableWidgetItem(QString::number(core->rawRead32(core, result->address, result->segment)));
|
||||||
|
break;
|
||||||
|
case mCORE_MEMORY_SEARCH_STRING:
|
||||||
|
item = new QTableWidgetItem("?"); // TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_ui.results->setItem(i, 1, item);
|
m_ui.results->setItem(i, 1, item);
|
||||||
}
|
}
|
||||||
m_ui.results->sortItems(0);
|
m_ui.results->sortItems(0);
|
||||||
|
|
||||||
mCoreMemorySearchResultsDeinit(&res);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
#include "ui_MemorySearch.h"
|
#include "ui_MemorySearch.h"
|
||||||
|
|
||||||
|
#include <mgba/core/mem-search.h>
|
||||||
|
|
||||||
namespace QGBA {
|
namespace QGBA {
|
||||||
|
|
||||||
class GameController;
|
class GameController;
|
||||||
|
@ -16,15 +18,25 @@ class MemorySearch : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static constexpr size_t LIMIT = 10000;
|
||||||
|
|
||||||
MemorySearch(GameController* controller, QWidget* parent = nullptr);
|
MemorySearch(GameController* controller, QWidget* parent = nullptr);
|
||||||
|
~MemorySearch();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void refresh();
|
||||||
void search();
|
void search();
|
||||||
|
void searchWithin();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool createParams(mCoreMemorySearchParams*);
|
||||||
|
|
||||||
Ui::MemorySearch m_ui;
|
Ui::MemorySearch m_ui;
|
||||||
|
|
||||||
GameController* m_controller;
|
GameController* m_controller;
|
||||||
|
|
||||||
|
mCoreMemorySearchResults m_results;
|
||||||
|
QByteArray m_string;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
<property name="showGrid">
|
<property name="showGrid">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
@ -75,6 +78,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Numeric</string>
|
<string>Numeric</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<attribute name="buttonGroup">
|
<attribute name="buttonGroup">
|
||||||
<string notr="true">type</string>
|
<string notr="true">type</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
@ -122,6 +128,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>4 Bytes (32-bit)</string>
|
<string>4 Bytes (32-bit)</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<attribute name="buttonGroup">
|
<attribute name="buttonGroup">
|
||||||
<string notr="true">width</string>
|
<string notr="true">width</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
@ -135,14 +144,17 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QCheckBox" name="numHex">
|
<widget class="QRadioButton" name="numHex">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Hexadecimal</string>
|
<string>Hexadecimal</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="7" column="1">
|
||||||
<widget class="QCheckBox" name="numDec">
|
<widget class="QRadioButton" name="numDec">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Decimal</string>
|
<string>Decimal</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -168,9 +180,6 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="searchWithin">
|
<widget class="QPushButton" name="searchWithin">
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Search Within</string>
|
<string>Search Within</string>
|
||||||
</property>
|
</property>
|
||||||
|
@ -186,6 +195,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="refresh">
|
||||||
|
<property name="text">
|
||||||
|
<string>Refresh</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -195,5 +211,6 @@
|
||||||
<buttongroups>
|
<buttongroups>
|
||||||
<buttongroup name="type"/>
|
<buttongroup name="type"/>
|
||||||
<buttongroup name="width"/>
|
<buttongroup name="width"/>
|
||||||
|
<buttongroup name="numType"/>
|
||||||
</buttongroups>
|
</buttongroups>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
Loading…
Reference in New Issue