diff options
Diffstat (limited to 'src/citra_qt')
-rw-r--r-- | src/citra_qt/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/citra_qt/configuration/config.cpp | 2 | ||||
-rw-r--r-- | src/citra_qt/configuration/configure_input.cpp | 8 | ||||
-rw-r--r-- | src/citra_qt/configuration/configure_system.cpp | 30 | ||||
-rw-r--r-- | src/citra_qt/configuration/configure_system.h | 1 | ||||
-rw-r--r-- | src/citra_qt/configuration/configure_system.ui | 23 | ||||
-rw-r--r-- | src/citra_qt/debugger/callstack.cpp | 85 | ||||
-rw-r--r-- | src/citra_qt/debugger/callstack.h | 28 | ||||
-rw-r--r-- | src/citra_qt/debugger/callstack.ui | 39 | ||||
-rw-r--r-- | src/citra_qt/debugger/disassembler.cpp | 272 | ||||
-rw-r--r-- | src/citra_qt/debugger/disassembler.h | 76 | ||||
-rw-r--r-- | src/citra_qt/debugger/disassembler.ui | 81 | ||||
-rw-r--r-- | src/citra_qt/game_list.cpp | 278 | ||||
-rw-r--r-- | src/citra_qt/game_list.h | 52 | ||||
-rw-r--r-- | src/citra_qt/game_list_p.h | 8 | ||||
-rw-r--r-- | src/citra_qt/main.cpp | 62 | ||||
-rw-r--r-- | src/citra_qt/main.h | 8 | ||||
-rw-r--r-- | src/citra_qt/main.ui | 10 | ||||
-rw-r--r-- | src/citra_qt/ui_settings.h | 1 |
19 files changed, 379 insertions, 691 deletions
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 3e6106f0a..4e837668e 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -11,8 +11,6 @@ set(SRCS configuration/configure_graphics.cpp configuration/configure_input.cpp configuration/configure_system.cpp - debugger/callstack.cpp - debugger/disassembler.cpp debugger/graphics/graphics.cpp debugger/graphics/graphics_breakpoint_observer.cpp debugger/graphics/graphics_breakpoints.cpp @@ -43,8 +41,6 @@ set(HEADERS configuration/configure_graphics.h configuration/configure_input.h configuration/configure_system.h - debugger/callstack.h - debugger/disassembler.h debugger/graphics/graphics.h debugger/graphics/graphics_breakpoint_observer.h debugger/graphics/graphics_breakpoints.h @@ -74,8 +70,6 @@ set(UIS configuration/configure_graphics.ui configuration/configure_input.ui configuration/configure_system.ui - debugger/callstack.ui - debugger/disassembler.ui debugger/registers.ui hotkeys.ui main.ui diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 0b9b73f9e..2b99447ec 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -177,6 +177,7 @@ void Config::ReadValues() { UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); + UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool(); UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); @@ -295,6 +296,7 @@ void Config::SaveValues() { qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); + qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar); qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); qt_config->setValue("firstStart", UISettings::values.first_start); diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index daac9b63a..116a6330f 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -102,14 +102,8 @@ ConfigureInput::ConfigureInput(QWidget* parent) this->loadConfiguration(); - // TODO(wwylele): enable these when the input emulation for them is implemented - ui->buttonZL->setEnabled(false); - ui->buttonZR->setEnabled(false); + // TODO(wwylele): enable this when we actually emulate it ui->buttonHome->setEnabled(false); - ui->buttonCStickUp->setEnabled(false); - ui->buttonCStickDown->setEnabled(false); - ui->buttonCStickLeft->setEnabled(false); - ui->buttonCStickRight->setEnabled(false); } void ConfigureInput::applyConfiguration() { diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index a3a9015a4..9b1e6711d 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QMessageBox> #include "citra_qt/configuration/configure_system.h" #include "citra_qt/ui_settings.h" #include "core/core.h" @@ -15,8 +16,11 @@ static const std::array<int, 12> days_in_month = {{ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { ui->setupUi(this); - connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), - SLOT(updateBirthdayComboBox(int))); + connect(ui->combo_birthmonth, + static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, + &ConfigureSystem::updateBirthdayComboBox); + connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, + &ConfigureSystem::refreshConsoleID); this->setConfiguration(); } @@ -71,6 +75,10 @@ void ConfigureSystem::ReadSystemSettings() { // set sound output mode sound_index = Service::CFG::GetSoundOutputMode(); ui->combo_sound->setCurrentIndex(sound_index); + + // set the console id + u64 console_id = Service::CFG::GetConsoleUniqueId(); + ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); } void ConfigureSystem::applyConfiguration() { @@ -140,3 +148,21 @@ void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) { // restore the day selection ui->combo_birthday->setCurrentIndex(birthday_index); } + +void ConfigureSystem::refreshConsoleID() { + QMessageBox::StandardButton reply; + QString warning_text = tr("This will replace your current virtual 3DS with a new one. " + "Your current virtual 3DS will not be recoverable. " + "This might have unexpected effects in games. This might fail, " + "if you use an outdated config savegame. Continue?"); + reply = QMessageBox::critical(this, tr("Warning"), warning_text, + QMessageBox::No | QMessageBox::Yes); + if (reply == QMessageBox::No) + return; + u32 random_number; + u64 console_id; + Service::CFG::GenerateConsoleUniqueId(random_number, console_id); + Service::CFG::SetConsoleUniqueId(random_number, console_id); + Service::CFG::UpdateConfigNANDSavegame(); + ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); +} diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index db0ead13c..f13de17d4 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -23,6 +23,7 @@ public: public slots: void updateBirthdayComboBox(int birthmonth_index); + void refreshConsoleID(); private: void ReadSystemSettings(); diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index cc54fa37f..8caf49623 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -220,6 +220,29 @@ </item> </widget> </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_console_id"> + <property name="text"> + <string>Console ID:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="button_regenerate_console_id"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="layoutDirection"> + <enum>Qt::RightToLeft</enum> + </property> + <property name="text"> + <string>Regenerate</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp deleted file mode 100644 index 08d2e7a22..000000000 --- a/src/citra_qt/debugger/callstack.cpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QStandardItemModel> -#include "citra_qt/debugger/callstack.h" -#include "common/common_types.h" -#include "common/symbols.h" -#include "core/arm/arm_interface.h" -#include "core/arm/disassembler/arm_disasm.h" -#include "core/core.h" -#include "core/memory.h" - -CallstackWidget::CallstackWidget(QWidget* parent) : QDockWidget(parent) { - ui.setupUi(this); - - callstack_model = new QStandardItemModel(this); - callstack_model->setColumnCount(4); - callstack_model->setHeaderData(0, Qt::Horizontal, "Stack Pointer"); - callstack_model->setHeaderData(2, Qt::Horizontal, "Return Address"); - callstack_model->setHeaderData(1, Qt::Horizontal, "Call Address"); - callstack_model->setHeaderData(3, Qt::Horizontal, "Function"); - ui.treeView->setModel(callstack_model); -} - -void CallstackWidget::OnDebugModeEntered() { - // Stack pointer - const u32 sp = Core::CPU().GetReg(13); - - Clear(); - - int counter = 0; - for (u32 addr = 0x10000000; addr >= sp; addr -= 4) { - if (!Memory::IsValidVirtualAddress(addr)) - break; - - const u32 ret_addr = Memory::Read32(addr); - const u32 call_addr = ret_addr - 4; // get call address??? - - if (!Memory::IsValidVirtualAddress(call_addr)) - break; - - /* TODO (mattvail) clean me, move to debugger interface */ - u32 insn = Memory::Read32(call_addr); - if (ARM_Disasm::Decode(insn) == OP_BL) { - std::string name; - // ripped from disasm - u32 i_offset = insn & 0xffffff; - // Sign-extend the 24-bit offset - if ((i_offset >> 23) & 1) - i_offset |= 0xff000000; - - // Pre-compute the left-shift and the prefetch offset - i_offset <<= 2; - i_offset += 8; - const u32 func_addr = call_addr + i_offset; - - callstack_model->setItem( - counter, 0, new QStandardItem(QString("0x%1").arg(addr, 8, 16, QLatin1Char('0')))); - callstack_model->setItem(counter, 1, new QStandardItem(QString("0x%1").arg( - ret_addr, 8, 16, QLatin1Char('0')))); - callstack_model->setItem(counter, 2, new QStandardItem(QString("0x%1").arg( - call_addr, 8, 16, QLatin1Char('0')))); - - name = Symbols::HasSymbol(func_addr) ? Symbols::GetSymbol(func_addr).name : "unknown"; - callstack_model->setItem( - counter, 3, new QStandardItem( - QString("%1_%2") - .arg(QString::fromStdString(name)) - .arg(QString("0x%1").arg(func_addr, 8, 16, QLatin1Char('0'))))); - - counter++; - } - } -} - -void CallstackWidget::OnDebugModeLeft() {} - -void CallstackWidget::Clear() { - for (int row = 0; row < callstack_model->rowCount(); row++) { - for (int column = 0; column < callstack_model->columnCount(); column++) { - callstack_model->setItem(row, column, new QStandardItem()); - } - } -} diff --git a/src/citra_qt/debugger/callstack.h b/src/citra_qt/debugger/callstack.h deleted file mode 100644 index f04ab9c7e..000000000 --- a/src/citra_qt/debugger/callstack.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QDockWidget> -#include "ui_callstack.h" - -class QStandardItemModel; - -class CallstackWidget : public QDockWidget { - Q_OBJECT - -public: - explicit CallstackWidget(QWidget* parent = nullptr); - -public slots: - void OnDebugModeEntered(); - void OnDebugModeLeft(); - -private: - Ui::CallStack ui; - QStandardItemModel* callstack_model; - - /// Clears the callstack widget while keeping the column widths the same - void Clear(); -}; diff --git a/src/citra_qt/debugger/callstack.ui b/src/citra_qt/debugger/callstack.ui deleted file mode 100644 index 248ea3dd7..000000000 --- a/src/citra_qt/debugger/callstack.ui +++ /dev/null @@ -1,39 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>CallStack</class> - <widget class="QDockWidget" name="CallStack"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>300</height> - </rect> - </property> - <property name="windowTitle"> - <string>Call Stack</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTreeView" name="treeView"> - <property name="editTriggers"> - <set>QAbstractItemView::NoEditTriggers</set> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <property name="itemsExpandable"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/debugger/disassembler.cpp b/src/citra_qt/debugger/disassembler.cpp deleted file mode 100644 index e9c8ad858..000000000 --- a/src/citra_qt/debugger/disassembler.cpp +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QShortcut> -#include "citra_qt/bootmanager.h" -#include "citra_qt/debugger/disassembler.h" -#include "citra_qt/hotkeys.h" -#include "citra_qt/util/util.h" -#include "common/break_points.h" -#include "common/symbols.h" -#include "core/arm/arm_interface.h" -#include "core/arm/disassembler/arm_disasm.h" -#include "core/core.h" -#include "core/memory.h" - -DisassemblerModel::DisassemblerModel(QObject* parent) - : QAbstractListModel(parent), base_address(0), code_size(0), program_counter(0), - selection(QModelIndex()) {} - -int DisassemblerModel::columnCount(const QModelIndex& parent) const { - return 3; -} - -int DisassemblerModel::rowCount(const QModelIndex& parent) const { - return code_size; -} - -QVariant DisassemblerModel::data(const QModelIndex& index, int role) const { - switch (role) { - case Qt::DisplayRole: { - u32 address = base_address + index.row() * 4; - u32 instr = Memory::Read32(address); - std::string disassembly = ARM_Disasm::Disassemble(address, instr); - - if (index.column() == 0) { - return QString("0x%1").arg((uint)(address), 8, 16, QLatin1Char('0')); - } else if (index.column() == 1) { - return QString::fromStdString(disassembly); - } else if (index.column() == 2) { - if (Symbols::HasSymbol(address)) { - TSymbol symbol = Symbols::GetSymbol(address); - return QString("%1 - Size:%2") - .arg(QString::fromStdString(symbol.name)) - .arg(symbol.size / 4); // divide by 4 to get instruction count - } else if (ARM_Disasm::Decode(instr) == OP_BL) { - u32 offset = instr & 0xFFFFFF; - - // Sign-extend the 24-bit offset - if ((offset >> 23) & 1) - offset |= 0xFF000000; - - // Pre-compute the left-shift and the prefetch offset - offset <<= 2; - offset += 8; - - TSymbol symbol = Symbols::GetSymbol(address + offset); - return QString(" --> %1").arg(QString::fromStdString(symbol.name)); - } - } - - break; - } - - case Qt::BackgroundRole: { - unsigned int address = base_address + 4 * index.row(); - - if (breakpoints.IsAddressBreakPoint(address)) - return QBrush(QColor(0xFF, 0xC0, 0xC0)); - else if (address == program_counter) - return QBrush(QColor(0xC0, 0xC0, 0xFF)); - - break; - } - - case Qt::FontRole: { - if (index.column() == 0 || index.column() == 1) { // 2 is the symbols column - return GetMonospaceFont(); - } - break; - } - - default: - break; - } - - return QVariant(); -} - -QModelIndex DisassemblerModel::IndexFromAbsoluteAddress(unsigned int address) const { - return index((address - base_address) / 4, 0); -} - -const BreakPoints& DisassemblerModel::GetBreakPoints() const { - return breakpoints; -} - -void DisassemblerModel::ParseFromAddress(unsigned int address) { - - // NOTE: A too large value causes lagging when scrolling the disassembly - const unsigned int chunk_size = 1000 * 500; - - // If we haven't loaded anything yet, initialize base address to the parameter address - if (code_size == 0) - base_address = address; - - // If the new area is already loaded, just continue - if (base_address + code_size > address + chunk_size && base_address <= address) - return; - - // Insert rows before currently loaded data - if (base_address > address) { - unsigned int num_rows = (address - base_address) / 4; - - beginInsertRows(QModelIndex(), 0, num_rows); - code_size += num_rows; - base_address = address; - - endInsertRows(); - } - - // Insert rows after currently loaded data - if (base_address + code_size < address + chunk_size) { - unsigned int num_rows = (base_address + chunk_size - code_size - address) / 4; - - beginInsertRows(QModelIndex(), 0, num_rows); - code_size += num_rows; - endInsertRows(); - } - - SetNextInstruction(address); -} - -void DisassemblerModel::OnSelectionChanged(const QModelIndex& new_selection) { - selection = new_selection; -} - -void DisassemblerModel::OnSetOrUnsetBreakpoint() { - if (!selection.isValid()) - return; - - unsigned int address = base_address + selection.row() * 4; - - if (breakpoints.IsAddressBreakPoint(address)) { - breakpoints.Remove(address); - } else { - breakpoints.Add(address); - } - - emit dataChanged(selection, selection); -} - -void DisassemblerModel::SetNextInstruction(unsigned int address) { - QModelIndex cur_index = IndexFromAbsoluteAddress(program_counter); - QModelIndex prev_index = IndexFromAbsoluteAddress(address); - - program_counter = address; - - emit dataChanged(cur_index, cur_index); - emit dataChanged(prev_index, prev_index); -} - -DisassemblerWidget::DisassemblerWidget(QWidget* parent, EmuThread* emu_thread) - : QDockWidget(parent), base_addr(0), emu_thread(emu_thread) { - - disasm_ui.setupUi(this); - - RegisterHotkey("Disassembler", "Start/Stop", QKeySequence(Qt::Key_F5), Qt::ApplicationShortcut); - RegisterHotkey("Disassembler", "Step", QKeySequence(Qt::Key_F10), Qt::ApplicationShortcut); - RegisterHotkey("Disassembler", "Step into", QKeySequence(Qt::Key_F11), Qt::ApplicationShortcut); - RegisterHotkey("Disassembler", "Set Breakpoint", QKeySequence(Qt::Key_F9), - Qt::ApplicationShortcut); - - connect(disasm_ui.button_step, SIGNAL(clicked()), this, SLOT(OnStep())); - connect(disasm_ui.button_pause, SIGNAL(clicked()), this, SLOT(OnPause())); - connect(disasm_ui.button_continue, SIGNAL(clicked()), this, SLOT(OnContinue())); - - connect(GetHotkey("Disassembler", "Start/Stop", this), SIGNAL(activated()), this, - SLOT(OnToggleStartStop())); - connect(GetHotkey("Disassembler", "Step", this), SIGNAL(activated()), this, SLOT(OnStep())); - connect(GetHotkey("Disassembler", "Step into", this), SIGNAL(activated()), this, - SLOT(OnStepInto())); - - setEnabled(false); -} - -void DisassemblerWidget::Init() { - model->ParseFromAddress(Core::CPU().GetPC()); - - disasm_ui.treeView->resizeColumnToContents(0); - disasm_ui.treeView->resizeColumnToContents(1); - disasm_ui.treeView->resizeColumnToContents(2); - - QModelIndex model_index = model->IndexFromAbsoluteAddress(Core::CPU().GetPC()); - disasm_ui.treeView->scrollTo(model_index); - disasm_ui.treeView->selectionModel()->setCurrentIndex( - model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - -void DisassemblerWidget::OnContinue() { - emu_thread->SetRunning(true); -} - -void DisassemblerWidget::OnStep() { - OnStepInto(); // change later -} - -void DisassemblerWidget::OnStepInto() { - emu_thread->SetRunning(false); - emu_thread->ExecStep(); -} - -void DisassemblerWidget::OnPause() { - emu_thread->SetRunning(false); - - // TODO: By now, the CPU might not have actually stopped... - if (Core::System::GetInstance().IsPoweredOn()) { - model->SetNextInstruction(Core::CPU().GetPC()); - } -} - -void DisassemblerWidget::OnToggleStartStop() { - emu_thread->SetRunning(!emu_thread->IsRunning()); -} - -void DisassemblerWidget::OnDebugModeEntered() { - u32 next_instr = Core::CPU().GetPC(); - - if (model->GetBreakPoints().IsAddressBreakPoint(next_instr)) - emu_thread->SetRunning(false); - - model->SetNextInstruction(next_instr); - - QModelIndex model_index = model->IndexFromAbsoluteAddress(next_instr); - disasm_ui.treeView->scrollTo(model_index); - disasm_ui.treeView->selectionModel()->setCurrentIndex( - model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); -} - -void DisassemblerWidget::OnDebugModeLeft() {} - -int DisassemblerWidget::SelectedRow() { - QModelIndex index = disasm_ui.treeView->selectionModel()->currentIndex(); - if (!index.isValid()) - return -1; - - return disasm_ui.treeView->selectionModel()->currentIndex().row(); -} - -void DisassemblerWidget::OnEmulationStarting(EmuThread* emu_thread) { - this->emu_thread = emu_thread; - - model = new DisassemblerModel(this); - disasm_ui.treeView->setModel(model); - - connect(disasm_ui.treeView->selectionModel(), - SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), model, - SLOT(OnSelectionChanged(const QModelIndex&))); - connect(disasm_ui.button_breakpoint, SIGNAL(clicked()), model, SLOT(OnSetOrUnsetBreakpoint())); - connect(GetHotkey("Disassembler", "Set Breakpoint", this), SIGNAL(activated()), model, - SLOT(OnSetOrUnsetBreakpoint())); - - Init(); - setEnabled(true); -} - -void DisassemblerWidget::OnEmulationStopping() { - disasm_ui.treeView->setModel(nullptr); - delete model; - emu_thread = nullptr; - setEnabled(false); -} diff --git a/src/citra_qt/debugger/disassembler.h b/src/citra_qt/debugger/disassembler.h deleted file mode 100644 index a6e59515c..000000000 --- a/src/citra_qt/debugger/disassembler.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QAbstractListModel> -#include <QDockWidget> -#include "common/break_points.h" -#include "common/common_types.h" -#include "ui_disassembler.h" - -class QAction; -class EmuThread; - -class DisassemblerModel : public QAbstractListModel { - Q_OBJECT - -public: - explicit DisassemblerModel(QObject* parent); - - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - - QModelIndex IndexFromAbsoluteAddress(unsigned int address) const; - const BreakPoints& GetBreakPoints() const; - -public slots: - void ParseFromAddress(unsigned int address); - void OnSelectionChanged(const QModelIndex&); - void OnSetOrUnsetBreakpoint(); - void SetNextInstruction(unsigned int address); - -private: - unsigned int base_address; - unsigned int code_size; - unsigned int program_counter; - - QModelIndex selection; - BreakPoints breakpoints; -}; - -class DisassemblerWidget : public QDockWidget { - Q_OBJECT - -public: - DisassemblerWidget(QWidget* parent, EmuThread* emu_thread); - - void Init(); - -public slots: - void OnContinue(); - void OnStep(); - void OnStepInto(); - void OnPause(); - void OnToggleStartStop(); - - void OnDebugModeEntered(); - void OnDebugModeLeft(); - - void OnEmulationStarting(EmuThread* emu_thread); - void OnEmulationStopping(); - -private: - // returns -1 if no row is selected - int SelectedRow(); - - Ui::DockWidget disasm_ui; - - DisassemblerModel* model; - - u32 base_addr; - - EmuThread* emu_thread; -}; diff --git a/src/citra_qt/debugger/disassembler.ui b/src/citra_qt/debugger/disassembler.ui deleted file mode 100644 index 5ca6dc5d2..000000000 --- a/src/citra_qt/debugger/disassembler.ui +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>DockWidget</class> - <widget class="QDockWidget" name="DockWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>430</width> - <height>401</height> - </rect> - </property> - <property name="windowTitle"> - <string>Disassembly</string> - </property> - <widget class="QWidget" name="dockWidgetContents"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QPushButton" name="button_step"> - <property name="text"> - <string>Step</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="button_pause"> - <property name="text"> - <string>Pause</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="button_continue"> - <property name="text"> - <string>Continue</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="pushButton"> - <property name="text"> - <string>Step Into</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="button_breakpoint"> - <property name="text"> - <string>Set Breakpoint</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTreeView" name="treeView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="indentation"> - <number>20</number> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <property name="uniformRowHeights"> - <bool>true</bool> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </widget> - </widget> - <resources/> - <connections/> -</ui> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index a9ec9e830..a8e3541cd 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -2,11 +2,12 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QApplication> #include <QFileInfo> #include <QHeaderView> +#include <QKeyEvent> #include <QMenu> #include <QThreadPool> -#include <QVBoxLayout> #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -15,10 +16,192 @@ #include "game_list_p.h" #include "ui_settings.h" -GameList::GameList(QWidget* parent) : QWidget{parent} { - QVBoxLayout* layout = new QVBoxLayout; +GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) { + this->gamelist = gamelist; + edit_filter_text_old = ""; +} + +// EventFilter in order to process systemkeys while editing the searchfield +bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { + // If it isn't a KeyRelease event then continue with standard event processing + if (event->type() != QEvent::KeyRelease) + return QObject::eventFilter(obj, event); + + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + int rowCount = gamelist->tree_view->model()->rowCount(); + QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); + + // If the searchfield's text hasn't changed special function keys get checked + // If no function key changes the searchfield's text the filter doesn't need to get reloaded + if (edit_filter_text == edit_filter_text_old) { + switch (keyEvent->key()) { + // Escape: Resets the searchfield + case Qt::Key_Escape: { + if (edit_filter_text_old.isEmpty()) { + return QObject::eventFilter(obj, event); + } else { + gamelist->search_field->edit_filter->clear(); + edit_filter_text = ""; + } + break; + } + // Return and Enter + // If the enter key gets pressed first checks how many and which entry is visible + // If there is only one result launch this game + case Qt::Key_Return: + case Qt::Key_Enter: { + QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); + QModelIndex root_index = item_model->invisibleRootItem()->index(); + QStandardItem* child_file; + QString file_path; + int resultCount = 0; + for (int i = 0; i < rowCount; ++i) { + if (!gamelist->tree_view->isRowHidden(i, root_index)) { + ++resultCount; + child_file = gamelist->item_model->item(i, 0); + file_path = child_file->data(GameListItemPath::FullPathRole).toString(); + } + } + if (resultCount == 1) { + // To avoid loading error dialog loops while confirming them using enter + // Also users usually want to run a diffrent game after closing one + gamelist->search_field->edit_filter->setText(""); + edit_filter_text = ""; + emit gamelist->GameChosen(file_path); + } else { + return QObject::eventFilter(obj, event); + } + break; + } + default: + return QObject::eventFilter(obj, event); + } + } + edit_filter_text_old = edit_filter_text; + return QObject::eventFilter(obj, event); +} + +void GameList::SearchField::setFilterResult(int visible, int total) { + QString result_of_text = tr("of"); + QString result_text; + if (total == 1) { + result_text = tr("result"); + } else { + result_text = tr("results"); + } + label_filter_result->setText( + QString("%1 %2 %3 %4").arg(visible).arg(result_of_text).arg(total).arg(result_text)); +} + +void GameList::SearchField::clear() { + edit_filter->setText(""); +} + +void GameList::SearchField::setFocus() { + if (edit_filter->isVisible()) { + edit_filter->setFocus(); + } +} + +GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { + KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); + layout_filter = new QHBoxLayout; + layout_filter->setMargin(8); + label_filter = new QLabel; + label_filter->setText(tr("Filter:")); + edit_filter = new QLineEdit; + edit_filter->setText(""); + edit_filter->setPlaceholderText(tr("Enter pattern to filter")); + edit_filter->installEventFilter(keyReleaseEater); + edit_filter->setClearButtonEnabled(true); + connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, + SLOT(onTextChanged(const QString&))); + label_filter_result = new QLabel; + button_filter_close = new QToolButton(this); + button_filter_close->setText("X"); + button_filter_close->setCursor(Qt::ArrowCursor); + button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " + "#000000; font-weight: bold; background: #F0F0F0; }" + "QToolButton:hover{ border: none; padding: 0px; color: " + "#EEEEEE; font-weight: bold; background: #E81123}"); + connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); + layout_filter->setSpacing(10); + layout_filter->addWidget(label_filter); + layout_filter->addWidget(edit_filter); + layout_filter->addWidget(label_filter_result); + layout_filter->addWidget(button_filter_close); + setLayout(layout_filter); +} + +/** + * Checks if all words separated by spaces are contained in another string + * This offers a word order insensitive search function + * + * @param String that gets checked if it contains all words of the userinput string + * @param String containing all words getting checked + * @return true if the haystack contains all words of userinput + */ +bool GameList::containsAllWords(QString haystack, QString userinput) { + QStringList userinput_split = userinput.split(" ", QString::SplitBehavior::SkipEmptyParts); + return std::all_of(userinput_split.begin(), userinput_split.end(), + [haystack](QString s) { return haystack.contains(s); }); +} + +// Event in order to filter the gamelist after editing the searchfield +void GameList::onTextChanged(const QString& newText) { + int rowCount = tree_view->model()->rowCount(); + QString edit_filter_text = newText.toLower(); + + QModelIndex root_index = item_model->invisibleRootItem()->index(); + + // If the searchfield is empty every item is visible + // Otherwise the filter gets applied + if (edit_filter_text.isEmpty()) { + for (int i = 0; i < rowCount; ++i) { + tree_view->setRowHidden(i, root_index, false); + } + search_field->setFilterResult(rowCount, rowCount); + } else { + QStandardItem* child_file; + QString file_path, file_name, file_title, file_programmid; + int result_count = 0; + for (int i = 0; i < rowCount; ++i) { + child_file = item_model->item(i, 0); + file_path = child_file->data(GameListItemPath::FullPathRole).toString().toLower(); + file_name = file_path.mid(file_path.lastIndexOf("/") + 1); + file_title = child_file->data(GameListItemPath::TitleRole).toString().toLower(); + file_programmid = + child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); + + // Only items which filename in combination with its title contains all words + // that are in the searchfiel will be visible in the gamelist + // The search is case insensitive because of toLower() + // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent + // multiple conversions of edit_filter_text for each game in the gamelist + if (containsAllWords(file_name.append(" ").append(file_title), edit_filter_text) || + (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { + tree_view->setRowHidden(i, root_index, false); + ++result_count; + } else { + tree_view->setRowHidden(i, root_index, true); + } + search_field->setFilterResult(result_count, rowCount); + } + } +} + +void GameList::onFilterCloseClicked() { + main_window->filterBarSetChecked(false); +} +GameList::GameList(GMainWindow* parent) : QWidget{parent} { + watcher = new QFileSystemWatcher(this); + connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); + + this->main_window = parent; + layout = new QVBoxLayout; tree_view = new QTreeView; + search_field = new SearchField(this); item_model = new QStandardItemModel(tree_view); tree_view->setModel(item_model); @@ -39,14 +222,15 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); - connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // We must register all custom types with the Qt Automoc system so that we are able to use it // with signals/slots. In this case, QList falls under the umbrells of custom types. qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); layout->addWidget(tree_view); + layout->addWidget(search_field); setLayout(layout); } @@ -54,6 +238,20 @@ GameList::~GameList() { emit ShouldCancelWorker(); } +void GameList::setFilterFocus() { + if (tree_view->model()->rowCount() > 0) { + search_field->setFocus(); + } +} + +void GameList::setFilterVisible(bool visibility) { + search_field->setVisible(visibility); +} + +void GameList::clearFilter() { + search_field->clear(); +} + void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { item_model->invisibleRootItem()->appendRow(entry_items); } @@ -69,11 +267,33 @@ void GameList::ValidateEntry(const QModelIndex& item) { std::string std_file_path(file_path.toStdString()); if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) return; + // Users usually want to run a diffrent game after closing one + search_field->clear(); emit GameChosen(file_path); } -void GameList::DonePopulating() { +void GameList::DonePopulating(QStringList watch_list) { + // Clear out the old directories to watch for changes and add the new ones + auto watch_dirs = watcher->directories(); + if (!watch_dirs.isEmpty()) { + watcher->removePaths(watch_dirs); + } + // Workaround: Add the watch paths in chunks to allow the gui to refresh + // This prevents the UI from stalling when a large number of watch paths are added + // Also artificially caps the watcher to a certain number of directories + constexpr int LIMIT_WATCH_DIRECTORIES = 5000; + constexpr int SLICE_SIZE = 25; + int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES); + for (int i = 0; i < len; i += SLICE_SIZE) { + watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE)); + QCoreApplication::processEvents(); + } tree_view->setEnabled(true); + int rowCount = tree_view->model()->rowCount(); + search_field->setFilterResult(rowCount, rowCount); + if (rowCount > 0) { + search_field->setFocus(); + } } void GameList::PopupContextMenu(const QPoint& menu_location) { @@ -97,6 +317,7 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { if (!FileUtil::Exists(dir_path.toStdString()) || !FileUtil::IsDirectory(dir_path.toStdString())) { LOG_ERROR(Frontend, "Could not find game list folder at %s", dir_path.toLocal8Bit().data()); + search_field->setFilterResult(0, 0); return; } @@ -106,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { emit ShouldCancelWorker(); - auto watch_dirs = watcher.directories(); - if (!watch_dirs.isEmpty()) { - watcher.removePaths(watch_dirs); - } - UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -151,42 +367,11 @@ static bool HasSupportedFileExtension(const std::string& file_name) { void GameList::RefreshGameDirectory() { if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + search_field->clear(); PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); } } -/** - * Adds the game list folder to the QFileSystemWatcher to check for updates. - * - * The file watcher will fire off an update to the game list when a change is detected in the game - * list folder. - * - * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and - * this function is fast enough to not stall the UI thread. If performance is an issue, it should - * be moved to another thread and properly locked to prevent concurrency issues. - * - * @param dir folder to check for changes in - * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each - * directory recursively to the watcher and will update the file list if any of the folders - * change. The number determines how deep the recursion should traverse. - */ -void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { - const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (FileUtil::IsDirectory(physical_name)) { - UpdateWatcherList(physical_name, recursion - 1); - } - return true; - }; - - watcher.addPath(QString::fromStdString(dir)); - if (recursion > 0) { - FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); - } -} - void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -195,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign if (stop_processing) return false; // Breaks the callback loop. - if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { + bool is_dir = FileUtil::IsDirectory(physical_name); + if (!is_dir && HasSupportedFileExtension(physical_name)) { std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); if (!loader) return true; @@ -212,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), new GameListItemSize(FileUtil::GetSize(physical_name)), }); - } else if (recursion > 0) { + } else if (is_dir && recursion > 0) { + watch_list.append(QString::fromStdString(physical_name)); AddFstEntriesToGameList(physical_name, recursion - 1); } @@ -224,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign void GameListWorker::run() { stop_processing = false; + watch_list.append(dir_path); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); - emit Finished(); + emit Finished(watch_list); } void GameListWorker::Cancel() { diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index b141fa3a5..4823a1296 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -5,13 +5,19 @@ #pragma once #include <QFileSystemWatcher> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> #include <QModelIndex> #include <QSettings> #include <QStandardItem> #include <QStandardItemModel> #include <QString> +#include <QToolButton> #include <QTreeView> +#include <QVBoxLayout> #include <QWidget> +#include "main.h" class GameListWorker; @@ -26,9 +32,40 @@ public: COLUMN_COUNT, // Number of columns }; - explicit GameList(QWidget* parent = nullptr); + class SearchField : public QWidget { + public: + void setFilterResult(int visible, int total); + void clear(); + void setFocus(); + explicit SearchField(GameList* parent = nullptr); + + private: + class KeyReleaseEater : public QObject { + public: + explicit KeyReleaseEater(GameList* gamelist); + + private: + GameList* gamelist = nullptr; + QString edit_filter_text_old; + + protected: + bool eventFilter(QObject* obj, QEvent* event); + }; + QHBoxLayout* layout_filter = nullptr; + QTreeView* tree_view = nullptr; + QLabel* label_filter = nullptr; + QLineEdit* edit_filter = nullptr; + QLabel* label_filter_result = nullptr; + QToolButton* button_filter_close = nullptr; + }; + + explicit GameList(GMainWindow* parent = nullptr); ~GameList() override; + void clearFilter(); + void setFilterFocus(); + void setFilterVisible(bool visibility); + void PopulateAsync(const QString& dir_path, bool deep_scan); void SaveInterfaceLayout(); @@ -41,17 +78,24 @@ signals: void ShouldCancelWorker(); void OpenSaveFolderRequested(u64 program_id); +private slots: + void onTextChanged(const QString& newText); + void onFilterCloseClicked(); + private: void AddEntry(const QList<QStandardItem*>& entry_items); void ValidateEntry(const QModelIndex& item); - void DonePopulating(); + void DonePopulating(QStringList watch_list); void PopupContextMenu(const QPoint& menu_location); - void UpdateWatcherList(const std::string& path, unsigned int recursion); void RefreshGameDirectory(); + bool containsAllWords(QString haystack, QString userinput); + SearchField* search_field; + GMainWindow* main_window = nullptr; + QVBoxLayout* layout = nullptr; QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; - QFileSystemWatcher watcher; + QFileSystemWatcher* watcher = nullptr; }; diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h index 3c11b6dd1..d1118ff7f 100644 --- a/src/citra_qt/game_list_p.h +++ b/src/citra_qt/game_list_p.h @@ -170,9 +170,15 @@ signals: * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. */ void EntryReady(QList<QStandardItem*> entry_items); - void Finished(); + + /** + * After the worker has traversed the game directory looking for entries, this signal is emmited + * with a list of folders that should be watched for changes as well. + */ + void Finished(QStringList watch_list); private: + QStringList watch_list; QString dir_path; bool deep_scan; std::atomic_bool stop_processing; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 73b4dd34f..d7fad555f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -16,8 +16,6 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/configuration/config.h" #include "citra_qt/configuration/configure_dialog.h" -#include "citra_qt/debugger/callstack.h" -#include "citra_qt/debugger/disassembler.h" #include "citra_qt/debugger/graphics/graphics.h" #include "citra_qt/debugger/graphics/graphics_breakpoints.h" #include "citra_qt/debugger/graphics/graphics_cmdlists.h" @@ -40,7 +38,6 @@ #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/string_util.h" -#include "core/arm/disassembler/load_symbol_map.h" #include "core/core.h" #include "core/file_sys/archive_source_sd_savedata.h" #include "core/gdbstub/gdbstub.h" @@ -93,7 +90,7 @@ void GMainWindow::InitializeWidgets() { render_window = new GRenderWindow(this, emu_thread.get()); render_window->hide(); - game_list = new GameList(); + game_list = new GameList(this); ui.horizontalLayout->addWidget(game_list); // Create status bar @@ -115,6 +112,7 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(label); } statusBar()->setVisible(true); + setStyleSheet("QStatusBar::item{border: none;}"); } void GMainWindow::InitializeDebugWidgets() { @@ -129,15 +127,6 @@ void GMainWindow::InitializeDebugWidgets() { debug_menu->addAction(microProfileDialog->toggleViewAction()); #endif - disasmWidget = new DisassemblerWidget(this, emu_thread.get()); - addDockWidget(Qt::BottomDockWidgetArea, disasmWidget); - disasmWidget->hide(); - debug_menu->addAction(disasmWidget->toggleViewAction()); - connect(this, &GMainWindow::EmulationStarting, disasmWidget, - &DisassemblerWidget::OnEmulationStarting); - connect(this, &GMainWindow::EmulationStopping, disasmWidget, - &DisassemblerWidget::OnEmulationStopping); - registersWidget = new RegistersWidget(this); addDockWidget(Qt::RightDockWidgetArea, registersWidget); registersWidget->hide(); @@ -147,11 +136,6 @@ void GMainWindow::InitializeDebugWidgets() { connect(this, &GMainWindow::EmulationStopping, registersWidget, &RegistersWidget::OnEmulationStopping); - callstackWidget = new CallstackWidget(this); - addDockWidget(Qt::RightDockWidgetArea, callstackWidget); - callstackWidget->hide(); - debug_menu->addAction(callstackWidget->toggleViewAction()); - graphicsWidget = new GPUCommandStreamWidget(this); addDockWidget(Qt::RightDockWidgetArea, graphicsWidget); graphicsWidget->hide(); @@ -246,6 +230,9 @@ void GMainWindow::RestoreUIState() { ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); + ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); + game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); + ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); } @@ -265,8 +252,6 @@ void GMainWindow::ConnectWidgetEvents() { void GMainWindow::ConnectMenuEvents() { // File connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); - connect(ui.action_Load_Symbol_Map, &QAction::triggered, this, - &GMainWindow::OnMenuLoadSymbolMap); connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, &GMainWindow::OnMenuSelectGameListRoot); connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); @@ -282,6 +267,8 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::ToggleWindowMode); connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, &GMainWindow::OnDisplayTitleBars); + ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F")); + connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); } @@ -385,26 +372,17 @@ void GMainWindow::BootGame(const QString& filename) { connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame())); // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views // before the CPU continues - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), disasmWidget, SLOT(OnDebugModeEntered()), - Qt::BlockingQueuedConnection); connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeEntered()), callstackWidget, - SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget, SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), disasmWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); - connect(emu_thread.get(), SIGNAL(DebugModeLeft()), callstackWidget, SLOT(OnDebugModeLeft()), - Qt::BlockingQueuedConnection); connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()), Qt::BlockingQueuedConnection); // Update the GUI registersWidget->OnDebugModeEntered(); - callstackWidget->OnDebugModeEntered(); if (ui.action_Single_Window_Mode->isChecked()) { game_list->hide(); } @@ -443,6 +421,7 @@ void GMainWindow::ShutdownGame() { ui.action_Stop->setEnabled(false); render_window->hide(); game_list->show(); + game_list->setFilterFocus(); // Disable status bar updates status_bar_update_timer.stop(); @@ -524,16 +503,6 @@ void GMainWindow::OnMenuLoadFile() { } } -void GMainWindow::OnMenuLoadSymbolMap() { - QString filename = QFileDialog::getOpenFileName( - this, tr("Load Symbol Map"), UISettings::values.symbols_path, tr("Symbol Map (*.*)")); - if (!filename.isEmpty()) { - UISettings::values.symbols_path = QFileInfo(filename).path(); - - LoadSymbolMap(filename.toStdString()); - } -} - void GMainWindow::OnMenuSelectGameListRoot() { QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); if (!dir_path.isEmpty()) { @@ -616,6 +585,15 @@ void GMainWindow::OnConfigure() { } } +void GMainWindow::OnToggleFilterBar() { + game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); + if (ui.action_Show_Filter_Bar->isChecked()) { + game_list->setFilterFocus(); + } else { + game_list->clearFilter(); + } +} + void GMainWindow::OnSwapScreens() { Settings::values.swap_screen = !Settings::values.swap_screen; Settings::Apply(); @@ -670,6 +648,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { #endif UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); + UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); UISettings::values.first_start = false; @@ -719,6 +698,11 @@ bool GMainWindow::ConfirmChangeGame() { return answer != QMessageBox::No; } +void GMainWindow::filterBarSetChecked(bool state) { + ui.action_Show_Filter_Bar->setChecked(state); + emit(OnToggleFilterBar()); +} + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index ec841eaa5..cb2e87cbd 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -7,11 +7,10 @@ #include <memory> #include <QMainWindow> +#include <QTimer> #include "ui_main.h" -class CallstackWidget; class Config; -class DisassemblerWidget; class EmuThread; class GameList; class GImageInfo; @@ -41,6 +40,7 @@ class GMainWindow : public QMainWindow { }; public: + void filterBarSetChecked(bool state); GMainWindow(); ~GMainWindow(); @@ -116,12 +116,12 @@ private slots: void OnGameListLoadFile(QString game_path); void OnGameListOpenSaveFolder(u64 program_id); void OnMenuLoadFile(); - void OnMenuLoadSymbolMap(); /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); void OnMenuRecentFile(); void OnSwapScreens(); void OnConfigure(); + void OnToggleFilterBar(); void OnDisplayTitleBars(bool); void ToggleWindowMode(); void OnCreateGraphicsSurfaceViewer(); @@ -149,9 +149,7 @@ private: // Debugger panes ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; - DisassemblerWidget* disasmWidget; RegistersWidget* registersWidget; - CallstackWidget* callstackWidget; GPUCommandStreamWidget* graphicsWidget; GPUCommandListWidget* graphicsCommandsWidget; GraphicsBreakPointsWidget* graphicsBreakpointsWidget; diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 47dbb6ef7..b13d578f5 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -58,7 +58,6 @@ </property> </widget> <addaction name="action_Load_File"/> - <addaction name="action_Load_Symbol_Map"/> <addaction name="separator"/> <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> @@ -88,6 +87,7 @@ </widget> <addaction name="action_Single_Window_Mode"/> <addaction name="action_Display_Dock_Widget_Headers"/> + <addaction name="action_Show_Filter_Bar"/> <addaction name="action_Show_Status_Bar"/> <addaction name="menu_View_Debugging"/> </widget> @@ -167,6 +167,14 @@ <string>Display Dock Widget Headers</string> </property> </action> + <action name="action_Show_Filter_Bar"> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="text"> + <string>Show Filter Bar</string> + </property> + </action> <action name="action_Show_Status_Bar"> <property name="checkable"> <bool>true</bool> diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index 6408ece2b..bc37f81c5 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -27,6 +27,7 @@ struct Values { bool single_window_mode; bool display_titlebar; + bool show_filter_bar; bool show_status_bar; bool confirm_before_closing; |