all repos — mgba @ 0065b62633ab133442538a96155fbead02eed130

mGBA Game Boy Advance Emulator

Qt: Add bug report tool
Vicki Pfau vi@endrift.com
Wed, 09 Dec 2020 18:30:36 -0800
commit

0065b62633ab133442538a96155fbead02eed130

parent

a5f3718f81fb62fa956e2994d5c98dcedc2c21ae

M CHANGESCHANGES

@@ -5,6 +5,7 @@ - WebP and APNG recording

- Separate overrides for GBC games that can also run on SGB or regular GB - Game Boy Player features can be enabled by default for all compatible games - Frame viewer support for Game Boy + - Bug report tool for gathering information helpful for reporting bugs - Mute option in homebrew ports - Status indicators for fast-forward and mute in homebrew ports - VBA bug compatibility mode for ROM hacks that don't work on real hardware
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -100,6 +100,7 @@ OverrideView.cpp

PaletteView.cpp PlacementControl.cpp RegisterView.cpp + ReportView.cpp ROMInfo.cpp RotatedHeaderView.cpp SavestateButton.cpp

@@ -139,6 +140,7 @@ OverrideView.ui

PaletteView.ui PlacementControl.ui PrinterView.ui + ReportView.ui ROMInfo.ui SensorView.ui SettingsView.ui
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -27,12 +27,8 @@

public: enum class Driver { QT = 0, -#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) OPENGL = 1, -#endif -#ifdef BUILD_GL OPENGL1 = 2, -#endif }; Display(QWidget* parent = nullptr);
M src/platform/qt/GBAApp.hsrc/platform/qt/GBAApp.h

@@ -56,6 +56,7 @@ static GBAApp* app();

static QString dataDir(); + QList<Window*> windows() { return m_windows; } Window* newWindow(); QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {});
A src/platform/qt/ReportView.cpp

@@ -0,0 +1,436 @@

+/* Copyright (c) 2013-2020 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 "ReportView.h" + +#include <QBuffer> +#include <QDesktopServices> +#include <QOffscreenSurface> +#include <QScreen> +#include <QSysInfo> + +#include <mgba/core/version.h> +#include <mgba-util/vfs.h> + +#include "CoreController.h" +#include "GBAApp.h" +#include "Window.h" + +#include "ui_ReportView.h" + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define USE_CPUID +#include <cpuid.h> +#endif +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#define USE_CPUID +#endif + +#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) || defined(USE_EPOXY) +#define DISPLAY_GL_INFO + +#include "DisplayGL.h" + +#include <QOpenGLFunctions> +#endif + +#ifdef USE_SQLITE3 +#include "feature/sqlite3/no-intro.h" +#endif + +using namespace QGBA; + +static const QLatin1String yesNo[2] = { + QLatin1String("No"), + QLatin1String("Yes") +}; + +#ifdef USE_CPUID +unsigned ReportView::s_cpuidMax = 0xFFFFFFFF; +unsigned ReportView::s_cpuidExtMax = 0xFFFFFFFF; +#endif + +ReportView::ReportView(QWidget* parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) +{ + m_ui.setupUi(this); + + QString description = m_ui.description->text(); + description.replace("{projectName}", QLatin1String(projectName)); + m_ui.description->setText(description); + + connect(m_ui.fileList, &QListWidget::currentTextChanged, this, &ReportView::setShownReport); +} + +void ReportView::generateReport() { + m_displayOrder.clear(); + m_reports.clear(); + + QDir configDir(ConfigController::configDir()); + + QStringList swReport; + swReport << QString("Name: %1").arg(QLatin1String(projectName)); + swReport << QString("Executable location: %1").arg(redact(QCoreApplication::applicationFilePath())); + swReport << QString("Portable: %1").arg(yesNo[ConfigController::isPortable()]); + swReport << QString("Configuration directory: %1").arg(redact(configDir.path())); + swReport << QString("Version: %1").arg(QLatin1String(projectVersion)); + swReport << QString("Git branch: %1").arg(QLatin1String(gitBranch)); + swReport << QString("Git commit: %1").arg(QLatin1String(gitCommit)); + swReport << QString("Git revision: %1").arg(gitRevision); + swReport << QString("OS: %1").arg(QSysInfo::prettyProductName()); + swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture()); + swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture()); + swReport << QString("Qt version: %1").arg(QLatin1String(qVersion())); + addReport(QString("System info"), swReport.join('\n')); + + QStringList hwReport; + addCpuInfo(hwReport); + addGLInfo(hwReport); + addReport(QString("Hardware info"), hwReport.join('\n')); + + QList<QScreen*> screens = QGuiApplication::screens(); + std::sort(screens.begin(), screens.end(), [](const QScreen* a, const QScreen* b) { + if (a->geometry().y() < b->geometry().y()) { + return true; + } + if (a->geometry().x() < b->geometry().x()) { + return true; + } + return false; + }); + + int screenId = 0; + for (const QScreen* screen : screens) { + ++screenId; + QStringList screenReport; + addScreenInfo(screenReport, screen); + addReport(QString("Screen %1").arg(screenId), screenReport.join('\n')); + } + + QList<QPair<QString, QByteArray>> deferredBinaries; + QList<ConfigController*> configs; + int winId = 0; + for (auto window : GBAApp::app()->windows()) { + ++winId; + QStringList windowReport; + auto controller = window->controller(); + ConfigController* config = window->config(); + if (configs.indexOf(config) < 0) { + configs.append(config); + } + + windowReport << QString("Window size: %1x%2").arg(window->width()).arg(window->height()); + windowReport << QString("Window location: %1, %2").arg(window->x()).arg(window->y()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QScreen* screen = window->screen(); +#else + QScreen* screen = NULL; + if (window->windowHandle()) { + screen = window->windowHandle()->screen(); + } +#endif + if (screen && screens.contains(screen)) { + windowReport << QString("Screen: %1").arg(screens.contains(screen) + 1); + } else { + windowReport << QString("Screen: Unknown"); + } + if (controller) { + windowReport << QString("ROM open: Yes"); + + { + CoreController::Interrupter interrupter(controller); + addROMInfo(windowReport, controller.get()); + + if (m_ui.includeSave->isChecked() && !m_ui.includeState->isChecked()) { + // Only do the save separately if savestates aren't enabled, to guarantee consistency + mCore* core = controller->thread()->core; + void* sram = NULL; + size_t size = core->savedataClone(core, &sram); + if (sram) { + QByteArray save(static_cast<const char*>(sram), size); + free(sram); + deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save)); + } + } + } + if (m_ui.includeState->isChecked()) { + QBuffer state; + int flags = SAVESTATE_SCREENSHOT | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA; + if (m_ui.includeSave->isChecked()) { + flags |= SAVESTATE_SAVEDATA; + } + controller->saveState(&state, flags); + deferredBinaries.append(qMakePair(QString("State %1").arg(winId), state.buffer())); + if (m_ui.includeSave->isChecked()) { + VFile* vf = VFileDevice::wrap(&state, QIODevice::ReadOnly); + mStateExtdata extdata; + mStateExtdataItem savedata; + mStateExtdataInit(&extdata); + if (mCoreExtractExtdata(controller->thread()->core, vf, &extdata) && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &savedata)) { + QByteArray save(static_cast<const char*>(savedata.data), savedata.size); + deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save)); + } + mStateExtdataDeinit(&extdata); + } + } + } else { + windowReport << QString("ROM open: No"); + } + windowReport << QString("Configuration: %1").arg(configs.indexOf(config) + 1); + addReport(QString("Window %1").arg(winId), windowReport.join('\n')); + } + for (ConfigController* config : configs) { + VFile* vf = VFileDevice::openMemory(); + mCoreConfigSaveVFile(config->config(), vf); + void* contents = vf->map(vf, vf->size(vf), MAP_READ); + if (contents) { + QString report(QString::fromUtf8(static_cast<const char*>(contents), vf->size(vf))); + addReport(QString("Configuration %1").arg(configs.indexOf(config) + 1), redact(report)); + vf->unmap(vf, contents, vf->size(vf)); + } + vf->close(vf); + } + + QFile qtIni(configDir.filePath("qt.ini")); + if (qtIni.open(QIODevice::ReadOnly | QIODevice::Text)) { + addReport(QString("Qt Configuration"), redact(QString::fromUtf8(qtIni.readAll()))); + qtIni.close(); + } + + std::sort(deferredBinaries.begin(), deferredBinaries.end()); + for (auto& pair : deferredBinaries) { + addBinary(pair.first, pair.second); + } + + rebuildModel(); +} + +void ReportView::save() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Bug report archive"), tr("ZIP archive (*.zip)")); + if (filename.isNull()) { + return; + } + VDir* zip = VDirOpenZip(filename.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC); + if (!zip) { + return; + } + for (const auto& filename : m_displayOrder) { + VFileDevice vf(zip->openFile(zip, filename.toLocal8Bit().constData(), O_WRONLY)); + if (m_reports.contains(filename)) { + vf.setTextModeEnabled(true); + vf.write(m_reports[filename].toUtf8()); + } else if (m_binaries.contains(filename)) { + vf.write(m_binaries[filename]); + } + vf.close(); + } + zip->close(zip); +} + +void ReportView::setShownReport(const QString& filename) { + m_ui.fileView->setPlainText(m_reports[filename]); +} + +void ReportView::rebuildModel() { + m_ui.fileList->clear(); + for (const auto& filename : m_displayOrder) { + QListWidgetItem* item = new QListWidgetItem(filename); + if (m_binaries.contains(filename)) { + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + } + m_ui.fileList->addItem(item); + } + m_ui.save->setEnabled(true); + m_ui.fileList->setEnabled(true); + m_ui.fileView->setEnabled(true); + m_ui.openList->setEnabled(true); + m_ui.fileList->setCurrentRow(0); + m_ui.fileView->installEventFilter(this); +} + +void ReportView::openBugReportPage() { + QDesktopServices::openUrl(QUrl("https://mgba.io/i/")); +} + +void ReportView::addCpuInfo(QStringList& report) { +#ifdef USE_CPUID + std::array<unsigned, 4> regs; + if (!cpuid(0, regs)) { + return; + } + unsigned vendor[4] = { regs[1], regs[3], regs[2], 0 }; + auto testBit = [](unsigned bit, unsigned reg) { + return yesNo[bool(reg & (1 << bit))]; + }; + QStringList features; + report << QString("CPU manufacturer: %1").arg(QLatin1String(reinterpret_cast<char*>(vendor))); + cpuid(1, regs); + unsigned family = ((regs[0] >> 8) & 0xF) | ((regs[0] >> 16) & 0xFF0); + unsigned model = ((regs[0] >> 4) & 0xF) | ((regs[0] >> 12) & 0xF0); + report << QString("CPU family: %1h").arg(family, 2, 16, QChar('0')); + report << QString("CPU model: %1h").arg(model, 2, 16, QChar('0')); + features << QString("Supports SSE: %1").arg(testBit(25, regs[3])); + features << QString("Supports SSE2: %1").arg(testBit(26, regs[3])); + features << QString("Supports SSE3: %1").arg(testBit(0, regs[2])); + features << QString("Supports SSSE3: %1").arg(testBit(9, regs[2])); + features << QString("Supports SSE4.1: %1").arg(testBit(19, regs[2])); + features << QString("Supports SSE4.2: %1").arg(testBit(20, regs[2])); + features << QString("Supports MOVBE: %1").arg(testBit(22, regs[2])); + features << QString("Supports POPCNT: %1").arg(testBit(23, regs[2])); + features << QString("Supports RDRAND: %1").arg(testBit(30, regs[2])); + features << QString("Supports AVX: %1").arg(testBit(28, regs[2])); + cpuid(7, 0, regs); + features << QString("Supports AVX2: %1").arg(testBit(5, regs[1])); + features << QString("Supports BMI1: %1").arg(testBit(3, regs[1])); + features << QString("Supports BMI2: %1").arg(testBit(8, regs[1])); + cpuid(0x80000001, regs); + features << QString("Supports ABM: %1").arg(testBit(5, regs[2])); + features << QString("Supports SSE4a: %1").arg(testBit(6, regs[2])); + features.sort(); + report << features; +#endif +} + +void ReportView::addGLInfo(QStringList& report) { +#ifdef DISPLAY_GL_INFO + QSurfaceFormat format; + + report << QString("OpenGL type: %1").arg(QLatin1String(QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? "OpenGL" : "OpenGL|ES")); + + format.setVersion(1, 4); + report << QString("OpenGL supports legacy (1.x) contexts: %1").arg(yesNo[DisplayGL::supportsFormat(format)]); + + if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { + format.setVersion(2, 0); + } else { + format.setVersion(3, 2); + } + format.setProfile(QSurfaceFormat::CoreProfile); + report << QString("OpenGL supports core contexts: %1").arg(yesNo[DisplayGL::supportsFormat(format)]); + + QOpenGLContext context; + if (context.create()) { + QOffscreenSurface surface; + surface.create(); + context.makeCurrent(&surface); + report << QString("OpenGL renderer: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_RENDERER)))); + report << QString("OpenGL vendor: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_VENDOR)))); + report << QString("OpenGL version string: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_VERSION)))); + } +#else + report << QString("OpenGL support disabled at compilation time"); +#endif +} + +void ReportView::addROMInfo(QStringList& report, CoreController* controller) { + report << QString("Currently paused: %1").arg(yesNo[controller->isPaused()]); + + mCore* core = controller->thread()->core; + char title[17] = {}; + core->getGameTitle(core, title); + report << QString("Internal title: %1").arg(QLatin1String(title)); + + title[8] = '\0'; + core->getGameCode(core, title); + if (title[0]) { + report << QString("Game code: %1").arg(QLatin1String(title)); + } else { + report << QString("Invalid game code"); + } + + uint32_t crc32 = 0; + core->checksum(core, &crc32, CHECKSUM_CRC32); + report << QString("CRC32: %1").arg(crc32, 8, 16, QChar('0')); + +#ifdef USE_SQLITE3 + const NoIntroDB* db = GBAApp::app()->gameDB(); + if (db && crc32) { + NoIntroGame game{}; + if (NoIntroDBLookupGameByCRC(db, crc32, &game)) { + report << QString("No-Intro name: %1").arg(game.name); + } else { + report << QString("Not present in No-Intro database").arg(game.name); + } + } +#endif +} + +void ReportView::addScreenInfo(QStringList& report, const QScreen* screen) { + QRect geometry = screen->geometry(); + + report << QString("Size: %1x%2").arg(geometry.width()).arg(geometry.height()); + report << QString("Location: %1, %2").arg(geometry.x()).arg(geometry.y()); + report << QString("Refresh rate: %1 Hz").arg(screen->refreshRate()); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) + report << QString("Pixel ratio: %1").arg(screen->devicePixelRatio()); +#endif + report << QString("Logical DPI: %1x%2").arg(screen->logicalDotsPerInchX()).arg(screen->logicalDotsPerInchY()); + report << QString("Physical DPI: %1x%2").arg(screen->physicalDotsPerInchX()).arg(screen->physicalDotsPerInchY()); +} + +void ReportView::addReport(const QString& filename, const QString& report) { + m_reports[filename] = report; + m_displayOrder.append(filename); +} + +void ReportView::addBinary(const QString& filename, const QByteArray& binary) { + m_binaries[filename] = binary; + m_displayOrder.append(filename); +} + +QString ReportView::redact(const QString& text) { + static QRegularExpression home(R"((?:\b|^)[A-Z]:[\\/](?:Users|Documents and Settings)[\\/][^\\/]+|(?:/usr)?/home/[^/]+)", + QRegularExpression::MultilineOption | QRegularExpression::CaseInsensitiveOption); + QString redacted = text; + redacted.replace(home, QString("[Home directory]")); + return redacted; +} + +bool ReportView::eventFilter(QObject*, QEvent* event) { + if (event->type() != QEvent::FocusOut) { + QListWidgetItem* currentReport = m_ui.fileList->currentItem(); + if (currentReport && !currentReport->text().isNull()) { + m_reports[currentReport->text()] = m_ui.fileView->toPlainText(); + } + } + return false; +} + +#ifdef USE_CPUID +bool ReportView::cpuid(unsigned id, std::array<unsigned, 4>& regs) { + return cpuid(id, 0, regs); +} + +bool ReportView::cpuid(unsigned id, unsigned sub, std::array<unsigned, 4>& regs) { + if (s_cpuidMax == 0xFFFFFFFF) { +#ifdef _MSC_VER + __cpuid(reinterpret_cast<int*>(regs.data()), 0); + s_cpuidMax = regs[0]; + __cpuid(reinterpret_cast<int*>(regs.data()), 0x80000000); + s_cpuidExtMax = regs[0]; +#else + s_cpuidMax = __get_cpuid_max(0, nullptr); + s_cpuidExtMax = __get_cpuid_max(0x80000000, nullptr); +#endif + } + regs[0] = 0; + regs[1] = 0; + regs[2] = 0; + regs[3] = 0; + if (!(id & 0x80000000) && id > s_cpuidMax) { + return false; + } + if ((id & 0x80000000) && id > s_cpuidExtMax) { + return false; + } + +#ifdef _MSC_VER + __cpuidex(reinterpret_cast<int*>(regs.data()), id, sub); +#else + __cpuid_count(id, sub, regs[0], regs[1], regs[2], regs[3]); +#endif + return true; +} +#endif
A src/platform/qt/ReportView.h

@@ -0,0 +1,68 @@

+/* Copyright (c) 2013-2020 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 <QDialog> +#include <QHash> + +#include <array> +#include <memory> + +#include "ConfigController.h" + +#include "ui_ReportView.h" + +namespace QGBA { + +class ConfigController; +class CoreController; + +class ReportView : public QDialog { +Q_OBJECT + +public: + ReportView(QWidget* parent = nullptr); + +public slots: + void generateReport(); + void save(); + +private slots: + void setShownReport(const QString&); + void rebuildModel(); + void openBugReportPage(); + +protected: + bool eventFilter(QObject* obj, QEvent* event) override; + +private: + void addCpuInfo(QStringList&); + void addGLInfo(QStringList&); + void addROMInfo(QStringList&, CoreController*); + void addScreenInfo(QStringList&, const QScreen*); + + void addReport(const QString& filename, const QString& report); + void addBinary(const QString& filename, const QByteArray& report); + QString redact(const QString& text); + +#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) + static bool cpuid(unsigned id, std::array<unsigned, 4>& regs); + static bool cpuid(unsigned id, unsigned sub, std::array<unsigned, 4>& regs); + + static unsigned s_cpuidMax; + static unsigned s_cpuidExtMax; +#endif + + ConfigController* m_config; + + QStringList m_displayOrder; + QHash<QString, QString> m_reports; + QHash<QString, QByteArray> m_binaries; + + Ui::ReportView m_ui; +}; + +}
A src/platform/qt/ReportView.ui

@@ -0,0 +1,222 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ReportView</class> + <widget class="QDialog" name="ReportView"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>855</width> + <height>474</height> + </rect> + </property> + <property name="windowTitle"> + <string>Generate Bug Report</string> + </property> + <layout class="QGridLayout" name="gridLayout" columnstretch="3,2,7,0"> + <item row="1" column="1" rowspan="3"> + <widget class="QListWidget" name="fileList"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeAdjustPolicy"> + <enum>QAbstractScrollArea::AdjustToContents</enum> + </property> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="showDropIndicator" stdset="0"> + <bool>false</bool> + </property> + <property name="horizontalScrollMode"> + <enum>QAbstractItemView::ScrollPerPixel</enum> + </property> + </widget> + </item> + <item row="1" column="2" rowspan="3"> + <widget class="QPlainTextEdit" name="fileView"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>Monospace</family> + </font> + </property> + <property name="textInteractionFlags"> + <set>Qt::TextEditorInteraction</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="description"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;To file a bug report, please first generate a report file to attach to the bug report you're about to file. It is recommended that you include the save files, as these often help with debugging issues. This will collect some information about the version of {projectName} you're running, your configuration, your computer, and the game you currently have open (if any). Once this collection is completed you can review all of the information gathered below and save it to a zip file. The collection will automatically attempt to redact any personal information, such as your username if it's in any of the paths gathered, but just in case you can edit it afterwards. After you have generated and saved it, please click the button below or go to &lt;a href=&quot;https://mgba.io/i/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;mgba.io/i&lt;/span&gt;&lt;/a&gt; to file the bug report on GitHub. Make sure to attach the report you generated!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1" rowspan="2" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="generate"> + <property name="text"> + <string>Generate report</string> + </property> + <property name="icon"> + <iconset theme="view-refresh"> + <normaloff>../../../../../../</normaloff>../../../../../../</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="save"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save</string> + </property> + <property name="icon"> + <iconset theme="document-save"> + <normaloff>../../../../../../</normaloff>../../../../../../</iconset> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="openList"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Open issue list in browser</string> + </property> + <property name="icon"> + <iconset theme="document-send"> + <normaloff>../../../../../../</normaloff>../../../../../../</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" rowspan="4"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="includeSave"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Include save file</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="includeState"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Create and include savestate</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>openList</sender> + <signal>clicked()</signal> + <receiver>ReportView</receiver> + <slot>openBugReportPage()</slot> + <hints> + <hint type="sourcelabel"> + <x>593</x> + <y>442</y> + </hint> + <hint type="destinationlabel"> + <x>357</x> + <y>234</y> + </hint> + </hints> + </connection> + <connection> + <sender>generate</sender> + <signal>clicked()</signal> + <receiver>ReportView</receiver> + <slot>generateReport()</slot> + <hints> + <hint type="sourcelabel"> + <x>121</x> + <y>432</y> + </hint> + <hint type="destinationlabel"> + <x>357</x> + <y>229</y> + </hint> + </hints> + </connection> + <connection> + <sender>save</sender> + <signal>clicked()</signal> + <receiver>ReportView</receiver> + <slot>save()</slot> + <hints> + <hint type="sourcelabel"> + <x>357</x> + <y>432</y> + </hint> + <hint type="destinationlabel"> + <x>357</x> + <y>229</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>generateReport()</slot> + <slot>save()</slot> + <slot>openBugReportPage()</slot> + </slots> +</ui>
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -47,6 +47,7 @@ #include "ObjView.h"

#include "PaletteView.h" #include "PlacementControl.h" #include "PrinterView.h" +#include "ReportView.h" #include "ROMInfo.h" #include "SensorView.h" #include "SettingsView.h"

@@ -1219,6 +1220,7 @@ #ifndef Q_OS_MAC

m_actions.addSeparator("file"); #endif + m_actions.addAction(tr("Report bug..."), "bugReport", openTView<ReportView>(), "file"); m_actions.addAction(tr("About..."), "about", openTView<AboutScreen>(), "file"); #ifndef Q_OS_MAC
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -54,6 +54,8 @@

std::shared_ptr<CoreController> controller() { return m_controller; } void setConfig(ConfigController*); + ConfigController* config() { return m_config; } + void argumentsPassed(mArguments*); void resizeFrame(const QSize& size);