mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 22:29:39 -06:00
Move DolphinQt2 to DolphinQt
This commit is contained in:
85
Source/Core/DolphinQt/AboutDialog.cpp
Normal file
85
Source/Core/DolphinQt/AboutDialog.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/Version.h"
|
||||
|
||||
#include "DolphinQt/AboutDialog.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("About Dolphin"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
QString text = QStringLiteral("");
|
||||
QString small = QStringLiteral("<p style='margin-top:0; margin-bottom:0; font-size:small;'>");
|
||||
QString medium = QStringLiteral("<p style='margin-top:15px;'>");
|
||||
|
||||
text.append(QStringLiteral("<p style='font-size:38pt; font-weight:400; margin-bottom:0;'>") +
|
||||
tr("Dolphin") + QStringLiteral("</p>"));
|
||||
text.append(QStringLiteral("<p style='font-size:18pt; margin-top:0;'>%1</p>")
|
||||
.arg(QString::fromUtf8(Common::scm_desc_str.c_str())));
|
||||
|
||||
text.append(small + tr("Branch: ") + QString::fromUtf8(Common::scm_branch_str.c_str()) +
|
||||
QStringLiteral("</p>"));
|
||||
text.append(small + tr("Revision: ") + QString::fromUtf8(Common::scm_rev_git_str.c_str()) +
|
||||
QStringLiteral("</p>"));
|
||||
|
||||
text.append(medium + tr("Check for updates: ") +
|
||||
QStringLiteral(
|
||||
"<a href='https://dolphin-emu.org/download'>dolphin-emu.org/download</a></p>"));
|
||||
// i18n: The word "free" in the standard phrase "free and open source"
|
||||
// is "free" as in "freedom" - it refers to certain properties of the
|
||||
// software's license, not the software's price. (It is true that Dolphin
|
||||
// can be downloaded at no cost, but that's not what this message says.)
|
||||
text.append(medium + tr("Dolphin is a free and open-source GameCube and Wii emulator.") +
|
||||
QStringLiteral("</p>"));
|
||||
text.append(medium +
|
||||
tr("This software should not be used to play games you do not legally own.") +
|
||||
QStringLiteral("</p>"));
|
||||
text.append(
|
||||
medium +
|
||||
QStringLiteral(
|
||||
"<a href='https://github.com/dolphin-emu/dolphin/blob/master/license.txt'>%1</a> | "
|
||||
"<a href='https://github.com/dolphin-emu/dolphin/graphs/contributors'>%2</a> | "
|
||||
"<a href='https://forums.dolphin-emu.org/'>%3</a></p>")
|
||||
.arg(tr("License"))
|
||||
.arg(tr("Authors"))
|
||||
.arg(tr("Support")));
|
||||
|
||||
QLabel* text_label = new QLabel(text);
|
||||
text_label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
text_label->setOpenExternalLinks(true);
|
||||
|
||||
QLabel* copyright =
|
||||
new QLabel(small +
|
||||
// i18n: This message uses curly quotes in English. If you want to use curly quotes
|
||||
// in your translation, please use the type of curly quotes that's appropriate for
|
||||
// your language. If you aren't sure which type is appropriate, see
|
||||
// https://en.wikipedia.org/wiki/Quotation_mark#Specific_language_features
|
||||
tr("\u00A9 2003-2015+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are "
|
||||
"trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.") +
|
||||
QStringLiteral("</p>"));
|
||||
|
||||
QLabel* logo = new QLabel();
|
||||
logo->setPixmap(Resources::GetMisc(Resources::LOGO_LARGE));
|
||||
logo->setContentsMargins(30, 0, 30, 0);
|
||||
|
||||
QVBoxLayout* main_layout = new QVBoxLayout;
|
||||
QHBoxLayout* h_layout = new QHBoxLayout;
|
||||
|
||||
setLayout(main_layout);
|
||||
main_layout->addLayout(h_layout);
|
||||
main_layout->addWidget(copyright);
|
||||
copyright->setAlignment(Qt::AlignCenter);
|
||||
copyright->setContentsMargins(0, 15, 0, 0);
|
||||
|
||||
h_layout->setAlignment(Qt::AlignLeft);
|
||||
h_layout->addWidget(logo);
|
||||
h_layout->addWidget(text_label);
|
||||
}
|
14
Source/Core/DolphinQt/AboutDialog.h
Normal file
14
Source/Core/DolphinQt/AboutDialog.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class AboutDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AboutDialog(QWidget* parent = nullptr);
|
||||
};
|
227
Source/Core/DolphinQt/CMakeLists.txt
Normal file
227
Source/Core/DolphinQt/CMakeLists.txt
Normal file
@ -0,0 +1,227 @@
|
||||
find_package(Qt5 REQUIRED COMPONENTS Gui Widgets)
|
||||
|
||||
set_property(TARGET Qt5::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
||||
message(STATUS "Found Qt version ${Qt5Core_VERSION}")
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
add_executable(dolphin-emu
|
||||
AboutDialog.cpp
|
||||
CheatsManager.cpp
|
||||
FIFO/FIFOPlayerWindow.cpp
|
||||
FIFO/FIFOAnalyzer.cpp
|
||||
HotkeyScheduler.cpp
|
||||
Host.cpp
|
||||
Main.cpp
|
||||
MainWindow.cpp
|
||||
MenuBar.cpp
|
||||
RenderWidget.cpp
|
||||
Resources.cpp
|
||||
SearchBar.cpp
|
||||
Settings.cpp
|
||||
ToolBar.cpp
|
||||
Translation.cpp
|
||||
WiiUpdate.cpp
|
||||
WiiUpdate.h
|
||||
Config/CheatCodeEditor.cpp
|
||||
Config/ARCodeWidget.cpp
|
||||
Config/CheatWarningWidget.cpp
|
||||
Config/ControllersWindow.cpp
|
||||
Config/FilesystemWidget.cpp
|
||||
Config/GameConfigWidget.cpp
|
||||
Config/GeckoCodeWidget.cpp
|
||||
Config/Graphics/AdvancedWidget.cpp
|
||||
Config/Graphics/EnhancementsWidget.cpp
|
||||
Config/Graphics/GeneralWidget.cpp
|
||||
Config/Graphics/HacksWidget.cpp
|
||||
Config/Graphics/GraphicsBool.cpp
|
||||
Config/Graphics/GraphicsChoice.cpp
|
||||
Config/Graphics/GraphicsRadio.cpp
|
||||
Config/Graphics/GraphicsSlider.cpp
|
||||
Config/Graphics/GraphicsWidget.cpp
|
||||
Config/Graphics/GraphicsWindow.cpp
|
||||
Config/Graphics/PostProcessingConfigWindow.cpp
|
||||
Config/Graphics/SoftwareRendererWidget.cpp
|
||||
Config/InfoWidget.cpp
|
||||
Config/LogConfigWidget.cpp
|
||||
Config/LogWidget.cpp
|
||||
Config/Mapping/GCKeyboardEmu.cpp
|
||||
Config/Mapping/GCMicrophone.cpp
|
||||
Config/Mapping/GCPadEmu.cpp
|
||||
Config/Mapping/GCPadWiiUConfigDialog.cpp
|
||||
Config/Mapping/Hotkey3D.cpp
|
||||
Config/Mapping/HotkeyDebugging.cpp
|
||||
Config/Mapping/HotkeyGeneral.cpp
|
||||
Config/Mapping/HotkeyGraphics.cpp
|
||||
Config/Mapping/HotkeyStates.cpp
|
||||
Config/Mapping/HotkeyStatesOther.cpp
|
||||
Config/Mapping/HotkeyTAS.cpp
|
||||
Config/Mapping/HotkeyWii.cpp
|
||||
Config/Mapping/IOWindow.cpp
|
||||
Config/Mapping/MappingBool.cpp
|
||||
Config/Mapping/MappingButton.cpp
|
||||
Config/Mapping/MappingCommon.cpp
|
||||
Config/Mapping/MappingIndicator.cpp
|
||||
Config/Mapping/MappingNumeric.cpp
|
||||
Config/Mapping/MappingRadio.cpp
|
||||
Config/Mapping/MappingWidget.cpp
|
||||
Config/Mapping/MappingWindow.cpp
|
||||
Config/Mapping/WiimoteEmuExtension.cpp
|
||||
Config/Mapping/WiimoteEmuGeneral.cpp
|
||||
Config/Mapping/WiimoteEmuMotionControl.cpp
|
||||
Config/NewPatchDialog.cpp
|
||||
Config/PatchesWidget.cpp
|
||||
Config/PropertiesDialog.cpp
|
||||
Config/SettingsWindow.cpp
|
||||
Debugger/BreakpointWidget.cpp
|
||||
Debugger/CodeViewWidget.cpp
|
||||
Debugger/CodeWidget.cpp
|
||||
Debugger/JITWidget.cpp
|
||||
Debugger/MemoryViewWidget.cpp
|
||||
Debugger/MemoryWidget.cpp
|
||||
Debugger/NewBreakpointDialog.cpp
|
||||
Debugger/RegisterColumn.cpp
|
||||
Debugger/RegisterWidget.cpp
|
||||
Debugger/WatchWidget.cpp
|
||||
GameList/GameList.cpp
|
||||
GameList/GameListModel.cpp
|
||||
GameList/GameTracker.cpp
|
||||
GameList/GridProxyModel.cpp
|
||||
GameList/ListProxyModel.cpp
|
||||
GCMemcardManager.cpp
|
||||
QtUtils/BlockUserInputFilter.cpp
|
||||
NetPlay/GameListDialog.cpp
|
||||
NetPlay/MD5Dialog.cpp
|
||||
NetPlay/NetPlayDialog.cpp
|
||||
NetPlay/NetPlaySetupDialog.cpp
|
||||
NetPlay/PadMappingDialog.cpp
|
||||
QtUtils/DoubleClickEventFilter.cpp
|
||||
QtUtils/ElidedButton.cpp
|
||||
QtUtils/ImageConverter.cpp
|
||||
QtUtils/SignalDaemon.cpp
|
||||
QtUtils/WindowActivationEventFilter.cpp
|
||||
QtUtils/WinIconHelper.cpp
|
||||
QtUtils/WrapInScrollArea.cpp
|
||||
QtUtils/AspectRatioWidget.cpp
|
||||
Settings/AdvancedPane.cpp
|
||||
Settings/AudioPane.cpp
|
||||
Settings/GameCubePane.cpp
|
||||
Settings/GeneralPane.cpp
|
||||
Settings/InterfacePane.cpp
|
||||
Settings/PathPane.cpp
|
||||
Settings/WiiPane.cpp
|
||||
Settings/USBDeviceAddToWhitelistDialog.cpp
|
||||
TAS/GCTASInputWindow.cpp
|
||||
TAS/WiiTASInputWindow.cpp
|
||||
TAS/Shared.cpp
|
||||
TAS/StickWidget.cpp
|
||||
TAS/IRWidget.cpp
|
||||
Updater.cpp
|
||||
)
|
||||
|
||||
target_compile_definitions(dolphin-emu
|
||||
PRIVATE
|
||||
-DQT_USE_QSTRINGBUILDER
|
||||
-DQT_NO_CAST_FROM_ASCII
|
||||
-DQT_NO_CAST_TO_ASCII
|
||||
)
|
||||
|
||||
target_include_directories(dolphin-emu
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${Qt5Gui_PRIVATE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(dolphin-emu
|
||||
PRIVATE
|
||||
core
|
||||
Qt5::Widgets
|
||||
uicommon
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(dolphin-emu PRIVATE DolphinQt.manifest)
|
||||
endif()
|
||||
|
||||
# Handle localization
|
||||
find_package(Gettext)
|
||||
if(GETTEXT_MSGMERGE_EXECUTABLE AND GETTEXT_MSGFMT_EXECUTABLE)
|
||||
set(pot_file "${CMAKE_SOURCE_DIR}/Languages/po/dolphin-emu.pot")
|
||||
file(GLOB LINGUAS ${CMAKE_SOURCE_DIR}/Languages/po/*.po)
|
||||
|
||||
target_sources(dolphin-emu PRIVATE ${pot_file} ${LINGUAS})
|
||||
source_group("Localization" FILES ${LINGUAS})
|
||||
source_group("Localization\\\\Generated" FILES ${pot_file})
|
||||
|
||||
foreach(po ${LINGUAS})
|
||||
get_filename_component(lang ${po} NAME_WE)
|
||||
set(mo_dir ${CMAKE_CURRENT_BINARY_DIR}/${lang})
|
||||
set(mo ${mo_dir}/dolphin-emu.mo)
|
||||
|
||||
target_sources(dolphin-emu PRIVATE ${mo})
|
||||
source_group("Localization\\\\Generated" FILES ${mo})
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set_source_files_properties(${mo} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${lang}.lproj")
|
||||
else()
|
||||
install(FILES ${mo} DESTINATION share/locale/${lang}/LC_MESSAGES)
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT ${mo}
|
||||
COMMAND cmake -E make_directory ${mo_dir}
|
||||
COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} --quiet --update --backup=none -s ${po} ${pot_file}
|
||||
COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${mo} ${po}
|
||||
DEPENDS ${po}
|
||||
)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
include(BundleUtilities)
|
||||
set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Dolphin.app)
|
||||
|
||||
# Ask for an application bundle.
|
||||
set_target_properties(dolphin-emu PROPERTIES
|
||||
MACOSX_BUNDLE true
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||
OUTPUT_NAME Dolphin
|
||||
)
|
||||
|
||||
# Copy qt.conf into the bundle
|
||||
target_sources(dolphin-emu PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf")
|
||||
set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/qt.conf" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# Copy icon into the bundle
|
||||
target_sources(dolphin-emu PRIVATE "${CMAKE_SOURCE_DIR}/Data/Dolphin.icns")
|
||||
set_source_files_properties("${CMAKE_SOURCE_DIR}/Data/Dolphin.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# Copy Qt plugins into the bundle
|
||||
get_target_property(qtcocoa_location Qt5::QCocoaIntegrationPlugin LOCATION)
|
||||
target_sources(dolphin-emu PRIVATE "${qtcocoa_location}")
|
||||
set_source_files_properties("${qtcocoa_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/platforms)
|
||||
|
||||
get_target_property(qtmacstyle_location Qt5::QMacStylePlugin LOCATION)
|
||||
target_sources(dolphin-emu PRIVATE "${qtmacstyle_location}")
|
||||
set_source_files_properties("${qtmacstyle_location}" PROPERTIES MACOSX_PACKAGE_LOCATION MacOS/styles)
|
||||
|
||||
# Copy resources into the bundle
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/Data/Sys")
|
||||
file(GLOB_RECURSE resources RELATIVE "${CMAKE_SOURCE_DIR}/Data" "${CMAKE_SOURCE_DIR}/Data/Sys/*")
|
||||
foreach(res ${resources})
|
||||
target_sources(dolphin-emu PRIVATE "${CMAKE_SOURCE_DIR}/Data/${res}")
|
||||
get_filename_component(resdir "${res}" DIRECTORY)
|
||||
set_source_files_properties("${CMAKE_SOURCE_DIR}/Data/${res}" PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION "Resources/${resdir}")
|
||||
source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/Data/${res}")
|
||||
endforeach()
|
||||
|
||||
# Update library references to make the bundle portable
|
||||
include(DolphinPostprocessBundle)
|
||||
dolphin_postprocess_bundle(dolphin-emu)
|
||||
else()
|
||||
install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir})
|
||||
endif()
|
||||
|
||||
if(USE_DISCORD_PRESENCE)
|
||||
target_compile_definitions(dolphin-emu PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||
endif()
|
625
Source/Core/DolphinQt/CheatsManager.cpp
Normal file
625
Source/Core/DolphinQt/CheatsManager.cpp
Normal file
@ -0,0 +1,625 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/CheatsManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSplitter>
|
||||
#include <QTabWidget>
|
||||
#include <QTableWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/PPCDebugInterface.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
#include "DolphinQt/Config/ARCodeWidget.h"
|
||||
#include "DolphinQt/Config/GeckoCodeWidget.h"
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
constexpr u32 MAX_RESULTS = 50;
|
||||
|
||||
constexpr int INDEX_ROLE = Qt::UserRole;
|
||||
constexpr int COLUMN_ROLE = Qt::UserRole + 1;
|
||||
|
||||
CheatsManager::CheatsManager(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Cheats Manager"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
&CheatsManager::OnStateChanged);
|
||||
|
||||
OnStateChanged(Core::GetState());
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
Reset();
|
||||
Update();
|
||||
}
|
||||
|
||||
void CheatsManager::OnStateChanged(Core::State state)
|
||||
{
|
||||
if (state != Core::State::Running && state != Core::State::Paused)
|
||||
return;
|
||||
|
||||
auto* model = Settings::Instance().GetGameListModel();
|
||||
|
||||
for (int i = 0; i < model->rowCount(QModelIndex()); i++)
|
||||
{
|
||||
auto file = model->GetGameFile(i);
|
||||
|
||||
if (file->GetGameID() == SConfig::GetInstance().GetGameID())
|
||||
{
|
||||
m_game_file = file;
|
||||
if (m_tab_widget->count() == 3)
|
||||
{
|
||||
m_tab_widget->removeTab(0);
|
||||
m_tab_widget->removeTab(0);
|
||||
}
|
||||
|
||||
if (m_tab_widget->count() == 1)
|
||||
{
|
||||
if (m_ar_code)
|
||||
m_ar_code->deleteLater();
|
||||
|
||||
m_ar_code = new ARCodeWidget(*m_game_file, false);
|
||||
m_tab_widget->insertTab(0, m_ar_code, tr("AR Code"));
|
||||
m_tab_widget->insertTab(1, new GeckoCodeWidget(*m_game_file, false), tr("Gecko Codes"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatsManager::CreateWidgets()
|
||||
{
|
||||
m_tab_widget = new QTabWidget;
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
|
||||
m_cheat_search = CreateCheatSearch();
|
||||
|
||||
m_tab_widget->addTab(m_cheat_search, tr("Cheat Search"));
|
||||
|
||||
auto* layout = new QVBoxLayout;
|
||||
layout->addWidget(m_tab_widget);
|
||||
layout->addWidget(m_button_box);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void CheatsManager::ConnectWidgets()
|
||||
{
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(m_match_new, &QPushButton::pressed, this, &CheatsManager::NewSearch);
|
||||
connect(m_match_next, &QPushButton::pressed, this, &CheatsManager::NextSearch);
|
||||
connect(m_match_refresh, &QPushButton::pressed, this, &CheatsManager::Update);
|
||||
connect(m_match_reset, &QPushButton::pressed, this, &CheatsManager::Reset);
|
||||
|
||||
m_match_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_watch_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(m_match_table, &QTableWidget::customContextMenuRequested, this,
|
||||
&CheatsManager::OnMatchContextMenu);
|
||||
connect(m_watch_table, &QTableWidget::customContextMenuRequested, this,
|
||||
&CheatsManager::OnWatchContextMenu);
|
||||
connect(m_watch_table, &QTableWidget::itemChanged, this, &CheatsManager::OnWatchItemChanged);
|
||||
}
|
||||
|
||||
void CheatsManager::OnWatchContextMenu()
|
||||
{
|
||||
if (m_watch_table->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
AddAction(menu, tr("Remove from Watch"), this, [this] {
|
||||
auto* item = m_match_table->selectedItems()[0];
|
||||
|
||||
int index = item->data(INDEX_ROLE).toInt();
|
||||
|
||||
m_watch.erase(m_watch.begin() + index);
|
||||
|
||||
Update();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
AddAction(menu, tr("Generate Action Replay Code"), this, &CheatsManager::GenerateARCode);
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void CheatsManager::OnMatchContextMenu()
|
||||
{
|
||||
if (m_match_table->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
AddAction(menu, tr("Add to Watch"), this, [this] {
|
||||
auto* item = m_match_table->selectedItems()[0];
|
||||
|
||||
int index = item->data(INDEX_ROLE).toInt();
|
||||
|
||||
m_watch.push_back(m_results[index]);
|
||||
|
||||
Update();
|
||||
});
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
static ActionReplay::AREntry ResultToAREntry(Result result)
|
||||
{
|
||||
u8 cmd;
|
||||
|
||||
switch (result.type)
|
||||
{
|
||||
case DataType::Byte:
|
||||
cmd = 0x00;
|
||||
break;
|
||||
case DataType::Short:
|
||||
cmd = 0x02;
|
||||
break;
|
||||
default:
|
||||
case DataType::Int:
|
||||
cmd = 0x04;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 address = result.address & 0xffffff;
|
||||
|
||||
return ActionReplay::AREntry(cmd << 24 | address, result.locked_value);
|
||||
}
|
||||
|
||||
void CheatsManager::GenerateARCode()
|
||||
{
|
||||
if (!m_ar_code)
|
||||
return;
|
||||
|
||||
auto* item = m_match_table->selectedItems()[0];
|
||||
|
||||
int index = item->data(INDEX_ROLE).toInt();
|
||||
ActionReplay::ARCode ar_code;
|
||||
|
||||
ar_code.active = true;
|
||||
ar_code.user_defined = true;
|
||||
ar_code.name = tr("Generated by search (Address %1)")
|
||||
.arg(m_watch[index].address, 8, 16, QLatin1Char('0'))
|
||||
.toStdString();
|
||||
|
||||
ar_code.ops.push_back(ResultToAREntry(m_watch[index]));
|
||||
|
||||
m_ar_code->AddCode(ar_code);
|
||||
}
|
||||
|
||||
void CheatsManager::OnWatchItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
if (m_updating)
|
||||
return;
|
||||
|
||||
int index = item->data(INDEX_ROLE).toInt();
|
||||
int column = item->data(COLUMN_ROLE).toInt();
|
||||
|
||||
switch (column)
|
||||
{
|
||||
case 0:
|
||||
m_watch[index].name = item->text();
|
||||
break;
|
||||
case 3:
|
||||
m_watch[index].locked = item->checkState() == Qt::Checked;
|
||||
break;
|
||||
case 4:
|
||||
{
|
||||
const auto text = item->text();
|
||||
u32 value = 0;
|
||||
|
||||
switch (static_cast<DataType>(m_match_length->currentIndex()))
|
||||
{
|
||||
case DataType::Byte:
|
||||
value = text.toUShort(nullptr, 16) & 0xFF;
|
||||
break;
|
||||
case DataType::Short:
|
||||
value = text.toUShort(nullptr, 16);
|
||||
break;
|
||||
case DataType::Int:
|
||||
value = text.toUInt(nullptr, 16);
|
||||
break;
|
||||
case DataType::Float:
|
||||
{
|
||||
float f = text.toFloat();
|
||||
std::memcpy(&value, &f, sizeof(float));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_watch[index].locked_value = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
QWidget* CheatsManager::CreateCheatSearch()
|
||||
{
|
||||
m_match_table = new QTableWidget;
|
||||
m_watch_table = new QTableWidget;
|
||||
|
||||
m_match_table->verticalHeader()->hide();
|
||||
m_watch_table->verticalHeader()->hide();
|
||||
|
||||
m_match_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_watch_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
||||
// Options
|
||||
m_result_label = new QLabel;
|
||||
m_match_length = new QComboBox;
|
||||
m_match_operation = new QComboBox;
|
||||
m_match_value = new QLineEdit;
|
||||
m_match_new = new QPushButton(tr("New Search"));
|
||||
m_match_next = new QPushButton(tr("Next Search"));
|
||||
m_match_refresh = new QPushButton(tr("Refresh"));
|
||||
m_match_reset = new QPushButton(tr("Reset"));
|
||||
|
||||
auto* options = new QWidget;
|
||||
auto* layout = new QVBoxLayout;
|
||||
options->setLayout(layout);
|
||||
|
||||
for (const auto& option : {tr("8-bit Integer"), tr("16-bit Integer"), tr("32-bit Integer"),
|
||||
tr("Float"), tr("Double"), tr("String")})
|
||||
{
|
||||
m_match_length->addItem(option);
|
||||
}
|
||||
|
||||
for (const auto& option : {tr("Equals to"), tr("Not equals to"), tr("Less than"),
|
||||
tr("Less or equal to"), tr("More than"), tr("More or equal to")})
|
||||
{
|
||||
m_match_operation->addItem(option);
|
||||
}
|
||||
|
||||
auto* group_box = new QGroupBox(tr("Type"));
|
||||
auto* group_layout = new QHBoxLayout;
|
||||
group_box->setLayout(group_layout);
|
||||
|
||||
m_match_decimal = new QRadioButton(tr("Decimal"));
|
||||
m_match_hexadecimal = new QRadioButton(tr("Hexadecimal"));
|
||||
m_match_octal = new QRadioButton(tr("Octal"));
|
||||
|
||||
group_layout->addWidget(m_match_decimal);
|
||||
group_layout->addWidget(m_match_hexadecimal);
|
||||
group_layout->addWidget(m_match_octal);
|
||||
|
||||
layout->addWidget(m_result_label);
|
||||
layout->addWidget(m_match_length);
|
||||
layout->addWidget(m_match_operation);
|
||||
layout->addWidget(m_match_value);
|
||||
layout->addWidget(group_box);
|
||||
layout->addWidget(m_match_new);
|
||||
layout->addWidget(m_match_next);
|
||||
layout->addWidget(m_match_refresh);
|
||||
layout->addWidget(m_match_reset);
|
||||
|
||||
// Splitters
|
||||
m_option_splitter = new QSplitter(Qt::Horizontal);
|
||||
m_table_splitter = new QSplitter(Qt::Vertical);
|
||||
|
||||
m_table_splitter->addWidget(m_match_table);
|
||||
m_table_splitter->addWidget(m_watch_table);
|
||||
|
||||
m_option_splitter->addWidget(m_table_splitter);
|
||||
m_option_splitter->addWidget(options);
|
||||
|
||||
return m_option_splitter;
|
||||
}
|
||||
|
||||
size_t CheatsManager::GetTypeSize() const
|
||||
{
|
||||
switch (static_cast<DataType>(m_match_length->currentIndex()))
|
||||
{
|
||||
case DataType::Byte:
|
||||
return sizeof(u8);
|
||||
case DataType::Short:
|
||||
return sizeof(u16);
|
||||
case DataType::Int:
|
||||
return sizeof(u32);
|
||||
case DataType::Float:
|
||||
return sizeof(float);
|
||||
case DataType::Double:
|
||||
return sizeof(double);
|
||||
default:
|
||||
return m_match_value->text().toStdString().size();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool Compare(T mem_value, T value, CompareType op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case CompareType::Equal:
|
||||
return mem_value == value;
|
||||
case CompareType::NotEqual:
|
||||
return mem_value != value;
|
||||
case CompareType::Less:
|
||||
return mem_value < value;
|
||||
case CompareType::LessEqual:
|
||||
return mem_value <= mem_value;
|
||||
case CompareType::More:
|
||||
return value > mem_value;
|
||||
case CompareType::MoreEqual:
|
||||
return value >= mem_value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheatsManager::MatchesSearch(u32 addr) const
|
||||
{
|
||||
const auto text = m_match_value->text();
|
||||
const auto op = static_cast<CompareType>(m_match_operation->currentIndex());
|
||||
|
||||
const int base =
|
||||
(m_match_decimal->isChecked() ? 10 : (m_match_hexadecimal->isChecked() ? 16 : 8));
|
||||
|
||||
switch (static_cast<DataType>(m_match_length->currentIndex()))
|
||||
{
|
||||
case DataType::Byte:
|
||||
return Compare<u8>(PowerPC::HostRead_U8(addr), text.toUShort(nullptr, base) & 0xFF, op);
|
||||
case DataType::Short:
|
||||
return Compare(PowerPC::HostRead_U16(addr), text.toUShort(nullptr, base), op);
|
||||
case DataType::Int:
|
||||
return Compare(PowerPC::HostRead_U32(addr), text.toUInt(nullptr, base), op);
|
||||
case DataType::Float:
|
||||
return Compare(PowerPC::HostRead_F32(addr), text.toFloat(), op);
|
||||
case DataType::Double:
|
||||
return Compare(PowerPC::HostRead_F64(addr), text.toDouble(), op);
|
||||
case DataType::String:
|
||||
{
|
||||
bool is_equal = std::equal(text.toUtf8().cbegin(), text.toUtf8().cend(),
|
||||
reinterpret_cast<char*>(Memory::m_pRAM + addr - 0x80000000));
|
||||
|
||||
// String only supports equals and not equals comparisons because the other ones frankly don't
|
||||
// make any sense here
|
||||
switch (op)
|
||||
{
|
||||
case CompareType::Equal:
|
||||
return is_equal;
|
||||
case CompareType::NotEqual:
|
||||
return !is_equal;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheatsManager::NewSearch()
|
||||
{
|
||||
m_results.clear();
|
||||
const u32 base_address = 0x80000000;
|
||||
|
||||
if (!Memory::m_pRAM)
|
||||
{
|
||||
m_result_label->setText(tr("Memory Not Ready"));
|
||||
return;
|
||||
}
|
||||
|
||||
Core::RunAsCPUThread([&] {
|
||||
for (u32 i = 0; i < Memory::REALRAM_SIZE - GetTypeSize(); i++)
|
||||
{
|
||||
if (PowerPC::HostIsRAMAddress(base_address + i) && MatchesSearch(base_address + i))
|
||||
m_results.push_back(
|
||||
{base_address + i, static_cast<DataType>(m_match_length->currentIndex())});
|
||||
}
|
||||
});
|
||||
|
||||
m_match_next->setEnabled(true);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CheatsManager::NextSearch()
|
||||
{
|
||||
if (!Memory::m_pRAM)
|
||||
{
|
||||
m_result_label->setText(tr("Memory Not Ready"));
|
||||
return;
|
||||
}
|
||||
|
||||
Core::RunAsCPUThread([this] {
|
||||
m_results.erase(std::remove_if(m_results.begin(), m_results.end(),
|
||||
[this](Result r) {
|
||||
return !PowerPC::HostIsRAMAddress(r.address) ||
|
||||
!MatchesSearch(r.address);
|
||||
}),
|
||||
m_results.end());
|
||||
});
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CheatsManager::Update()
|
||||
{
|
||||
m_match_table->clear();
|
||||
m_watch_table->clear();
|
||||
m_match_table->setColumnCount(2);
|
||||
m_watch_table->setColumnCount(4);
|
||||
|
||||
m_match_table->setHorizontalHeaderLabels({tr("Address"), tr("Value")});
|
||||
m_watch_table->setHorizontalHeaderLabels({tr("Name"), tr("Address"), tr("Lock"), tr("Value")});
|
||||
|
||||
if (m_results.size() > MAX_RESULTS)
|
||||
{
|
||||
m_result_label->setText(tr("Too many matches to display (%1)").arg(m_results.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
m_result_label->setText(tr("%1 Match(es)").arg(m_results.size()));
|
||||
m_match_table->setRowCount(static_cast<int>(m_results.size()));
|
||||
|
||||
if (m_results.empty())
|
||||
return;
|
||||
|
||||
m_updating = true;
|
||||
|
||||
Core::RunAsCPUThread([this] {
|
||||
for (size_t i = 0; i < m_results.size(); i++)
|
||||
{
|
||||
auto* address_item = new QTableWidgetItem(
|
||||
QStringLiteral("%1").arg(m_results[i].address, 8, 16, QLatin1Char('0')));
|
||||
auto* value_item = new QTableWidgetItem;
|
||||
|
||||
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
|
||||
if (PowerPC::HostIsRAMAddress(m_results[i].address))
|
||||
{
|
||||
switch (m_results[i].type)
|
||||
{
|
||||
case DataType::Byte:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U8(m_results[i].address),
|
||||
2, 16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Short:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U16(m_results[i].address),
|
||||
4, 16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Int:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(m_results[i].address),
|
||||
8, 16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Float:
|
||||
value_item->setText(QString::number(PowerPC::HostRead_F32(m_results[i].address)));
|
||||
break;
|
||||
case DataType::Double:
|
||||
value_item->setText(QString::number(PowerPC::HostRead_F64(m_results[i].address)));
|
||||
break;
|
||||
case DataType::String:
|
||||
value_item->setText(tr("String Match"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value_item->setText(QStringLiteral("---"));
|
||||
}
|
||||
|
||||
address_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
value_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
|
||||
m_match_table->setItem(static_cast<int>(i), 0, address_item);
|
||||
m_match_table->setItem(static_cast<int>(i), 1, value_item);
|
||||
}
|
||||
|
||||
m_watch_table->setRowCount(static_cast<int>(m_watch.size()));
|
||||
|
||||
for (size_t i = 0; i < m_watch.size(); i++)
|
||||
{
|
||||
auto* name_item = new QTableWidgetItem(m_watch[i].name);
|
||||
auto* address_item = new QTableWidgetItem(
|
||||
QStringLiteral("%1").arg(m_watch[i].address, 8, 16, QLatin1Char('0')));
|
||||
auto* lock_item = new QTableWidgetItem;
|
||||
auto* value_item = new QTableWidgetItem;
|
||||
|
||||
if (PowerPC::HostIsRAMAddress(m_watch[i].address))
|
||||
{
|
||||
if (m_watch[i].locked)
|
||||
{
|
||||
PowerPC::debug_interface.SetPatch(m_watch[i].address, m_watch[i].locked_value);
|
||||
}
|
||||
|
||||
switch (m_watch[i].type)
|
||||
{
|
||||
case DataType::Byte:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U8(m_watch[i].address), 2,
|
||||
16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Short:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U16(m_watch[i].address), 4,
|
||||
16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Int:
|
||||
value_item->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(m_watch[i].address), 8,
|
||||
16, QLatin1Char('0')));
|
||||
break;
|
||||
case DataType::Float:
|
||||
value_item->setText(QString::number(PowerPC::HostRead_F32(m_watch[i].address)));
|
||||
break;
|
||||
case DataType::Double:
|
||||
value_item->setText(QString::number(PowerPC::HostRead_F64(m_watch[i].address)));
|
||||
break;
|
||||
case DataType::String:
|
||||
value_item->setText(tr("String Match"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value_item->setText(QStringLiteral("---"));
|
||||
}
|
||||
|
||||
name_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
name_item->setData(COLUMN_ROLE, 0);
|
||||
address_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
address_item->setData(COLUMN_ROLE, 1);
|
||||
value_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
value_item->setData(COLUMN_ROLE, 2);
|
||||
lock_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
lock_item->setData(COLUMN_ROLE, 3);
|
||||
value_item->setData(INDEX_ROLE, static_cast<int>(i));
|
||||
value_item->setData(COLUMN_ROLE, 4);
|
||||
|
||||
name_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
lock_item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
|
||||
value_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
|
||||
lock_item->setCheckState(m_watch[i].locked ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
m_watch_table->setItem(static_cast<int>(i), 0, name_item);
|
||||
m_watch_table->setItem(static_cast<int>(i), 1, address_item);
|
||||
m_watch_table->setItem(static_cast<int>(i), 2, lock_item);
|
||||
m_watch_table->setItem(static_cast<int>(i), 3, value_item);
|
||||
}
|
||||
});
|
||||
|
||||
m_updating = false;
|
||||
}
|
||||
|
||||
void CheatsManager::Reset()
|
||||
{
|
||||
m_results.clear();
|
||||
m_watch.clear();
|
||||
m_match_next->setEnabled(false);
|
||||
m_match_table->clear();
|
||||
m_watch_table->clear();
|
||||
m_match_decimal->setChecked(true);
|
||||
m_result_label->setText(QStringLiteral(""));
|
||||
|
||||
Update();
|
||||
}
|
117
Source/Core/DolphinQt/CheatsManager.h
Normal file
117
Source/Core/DolphinQt/CheatsManager.h
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class ARCodeWidget;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QRadioButton;
|
||||
class QSplitter;
|
||||
class QTabWidget;
|
||||
class QTableWidget;
|
||||
class QTableWidgetItem;
|
||||
class QLabel;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
namespace Core
|
||||
{
|
||||
enum class State;
|
||||
}
|
||||
|
||||
enum class CompareType : int
|
||||
{
|
||||
Equal = 0,
|
||||
NotEqual = 1,
|
||||
Less = 2,
|
||||
LessEqual = 3,
|
||||
More = 4,
|
||||
MoreEqual = 5
|
||||
};
|
||||
|
||||
enum class DataType : int
|
||||
{
|
||||
Byte = 0,
|
||||
Short = 1,
|
||||
Int = 2,
|
||||
Float = 3,
|
||||
Double = 4,
|
||||
String = 5
|
||||
};
|
||||
|
||||
struct Result
|
||||
{
|
||||
u32 address;
|
||||
DataType type;
|
||||
QString name;
|
||||
bool locked = false;
|
||||
u32 locked_value;
|
||||
};
|
||||
|
||||
class CheatsManager : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CheatsManager(QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QWidget* CreateCheatSearch();
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void OnStateChanged(Core::State state);
|
||||
|
||||
size_t GetTypeSize() const;
|
||||
bool MatchesSearch(u32 addr) const;
|
||||
|
||||
void Reset();
|
||||
void NewSearch();
|
||||
void NextSearch();
|
||||
void Update();
|
||||
void GenerateARCode();
|
||||
|
||||
void OnWatchContextMenu();
|
||||
void OnMatchContextMenu();
|
||||
void OnWatchItemChanged(QTableWidgetItem* item);
|
||||
|
||||
std::vector<Result> m_results;
|
||||
std::vector<Result> m_watch;
|
||||
std::shared_ptr<const UICommon::GameFile> m_game_file;
|
||||
QDialogButtonBox* m_button_box;
|
||||
QTabWidget* m_tab_widget = nullptr;
|
||||
|
||||
QWidget* m_cheat_search;
|
||||
ARCodeWidget* m_ar_code = nullptr;
|
||||
|
||||
QLabel* m_result_label;
|
||||
QTableWidget* m_match_table;
|
||||
QTableWidget* m_watch_table;
|
||||
QSplitter* m_option_splitter;
|
||||
QSplitter* m_table_splitter;
|
||||
QComboBox* m_match_length;
|
||||
QComboBox* m_match_operation;
|
||||
QLineEdit* m_match_value;
|
||||
QPushButton* m_match_new;
|
||||
QPushButton* m_match_next;
|
||||
QPushButton* m_match_refresh;
|
||||
QPushButton* m_match_reset;
|
||||
|
||||
QRadioButton* m_match_decimal;
|
||||
QRadioButton* m_match_hexadecimal;
|
||||
QRadioButton* m_match_octal;
|
||||
bool m_updating = false;
|
||||
};
|
198
Source/Core/DolphinQt/Config/ARCodeWidget.cpp
Normal file
198
Source/Core/DolphinQt/Config/ARCodeWidget.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/ARCodeWidget.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QHBoxLayout>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
#include "Core/ActionReplay.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/CheatCodeEditor.h"
|
||||
#include "DolphinQt/Config/CheatWarningWidget.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
ARCodeWidget::ARCodeWidget(const UICommon::GameFile& game, bool restart_required)
|
||||
: m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()),
|
||||
m_restart_required(restart_required)
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
IniFile game_ini_local;
|
||||
|
||||
// We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI
|
||||
// will always be stored in GS/${GAMEID}.ini
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision);
|
||||
m_ar_codes = ActionReplay::LoadCodes(game_ini_default, game_ini_local);
|
||||
|
||||
UpdateList();
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
void ARCodeWidget::CreateWidgets()
|
||||
{
|
||||
m_warning = new CheatWarningWidget(m_game_id, m_restart_required, this);
|
||||
m_code_list = new QListWidget;
|
||||
m_code_add = new QPushButton(tr("&Add New Code..."));
|
||||
m_code_edit = new QPushButton(tr("&Edit Code..."));
|
||||
m_code_remove = new QPushButton(tr("&Remove Code"));
|
||||
|
||||
auto* button_layout = new QHBoxLayout;
|
||||
|
||||
button_layout->addWidget(m_code_add);
|
||||
button_layout->addWidget(m_code_edit);
|
||||
button_layout->addWidget(m_code_remove);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
|
||||
layout->addWidget(m_warning);
|
||||
layout->addWidget(m_code_list);
|
||||
layout->addLayout(button_layout);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void ARCodeWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_warning, &CheatWarningWidget::OpenCheatEnableSettings, this,
|
||||
&ARCodeWidget::OpenGeneralSettings);
|
||||
connect(m_code_list, &QListWidget::itemChanged, this, &ARCodeWidget::OnItemChanged);
|
||||
connect(m_code_list, &QListWidget::itemSelectionChanged, this, &ARCodeWidget::OnSelectionChanged);
|
||||
|
||||
connect(m_code_add, &QPushButton::pressed, this, &ARCodeWidget::OnCodeAddPressed);
|
||||
connect(m_code_edit, &QPushButton::pressed, this, &ARCodeWidget::OnCodeEditPressed);
|
||||
connect(m_code_remove, &QPushButton::pressed, this, &ARCodeWidget::OnCodeRemovePressed);
|
||||
}
|
||||
|
||||
void ARCodeWidget::OnItemChanged(QListWidgetItem* item)
|
||||
{
|
||||
m_ar_codes[m_code_list->row(item)].active = (item->checkState() == Qt::Checked);
|
||||
|
||||
if (!m_restart_required)
|
||||
ActionReplay::ApplyCodes(m_ar_codes);
|
||||
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void ARCodeWidget::OnSelectionChanged()
|
||||
{
|
||||
auto items = m_code_list->selectedItems();
|
||||
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
const auto* selected = items[0];
|
||||
|
||||
bool user_defined = m_ar_codes[m_code_list->row(selected)].user_defined;
|
||||
|
||||
m_code_remove->setEnabled(user_defined);
|
||||
m_code_edit->setText(user_defined ? tr("&Edit Code...") : tr("Clone and &Edit Code..."));
|
||||
}
|
||||
|
||||
void ARCodeWidget::UpdateList()
|
||||
{
|
||||
m_code_list->clear();
|
||||
|
||||
for (const auto& ar : m_ar_codes)
|
||||
{
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(ar.name)
|
||||
.replace(QStringLiteral("<"), QStringLiteral("<"))
|
||||
.replace(QStringLiteral(">"), QStringLiteral(">")));
|
||||
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(ar.active ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
m_code_list->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ARCodeWidget::SaveCodes()
|
||||
{
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
ActionReplay::SaveCodes(&game_ini_local, m_ar_codes);
|
||||
|
||||
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
}
|
||||
|
||||
void ARCodeWidget::AddCode(ActionReplay::ARCode code)
|
||||
{
|
||||
m_ar_codes.push_back(std::move(code));
|
||||
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void ARCodeWidget::OnCodeAddPressed()
|
||||
{
|
||||
ActionReplay::ARCode ar;
|
||||
ar.active = true;
|
||||
|
||||
CheatCodeEditor ed(this);
|
||||
|
||||
ed.SetARCode(&ar);
|
||||
|
||||
if (ed.exec())
|
||||
{
|
||||
m_ar_codes.push_back(std::move(ar));
|
||||
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
}
|
||||
|
||||
void ARCodeWidget::OnCodeEditPressed()
|
||||
{
|
||||
auto items = m_code_list->selectedItems();
|
||||
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
const auto* selected = items[0];
|
||||
|
||||
auto& current_ar = m_ar_codes[m_code_list->row(selected)];
|
||||
|
||||
bool user_defined = current_ar.user_defined;
|
||||
|
||||
ActionReplay::ARCode ar = current_ar;
|
||||
|
||||
CheatCodeEditor ed(this);
|
||||
|
||||
ed.SetARCode(user_defined ? ¤t_ar : &ar);
|
||||
ed.exec();
|
||||
|
||||
if (!user_defined)
|
||||
m_ar_codes.push_back(ar);
|
||||
|
||||
SaveCodes();
|
||||
UpdateList();
|
||||
}
|
||||
|
||||
void ARCodeWidget::OnCodeRemovePressed()
|
||||
{
|
||||
auto items = m_code_list->selectedItems();
|
||||
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
const auto* selected = items[0];
|
||||
|
||||
m_ar_codes.erase(m_ar_codes.begin() + m_code_list->row(selected));
|
||||
|
||||
SaveCodes();
|
||||
UpdateList();
|
||||
|
||||
m_code_remove->setEnabled(false);
|
||||
}
|
62
Source/Core/DolphinQt/Config/ARCodeWidget.h
Normal file
62
Source/Core/DolphinQt/Config/ARCodeWidget.h
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/ActionReplay.h"
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class CheatWarningWidget;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QPushButton;
|
||||
|
||||
class ARCodeWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ARCodeWidget(const UICommon::GameFile& game, bool restart_required = true);
|
||||
|
||||
void AddCode(ActionReplay::ARCode code);
|
||||
|
||||
signals:
|
||||
void OpenGeneralSettings();
|
||||
|
||||
private:
|
||||
void OnSelectionChanged();
|
||||
void OnItemChanged(QListWidgetItem* item);
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void UpdateList();
|
||||
void SaveCodes();
|
||||
|
||||
void OnCodeAddPressed();
|
||||
void OnCodeEditPressed();
|
||||
void OnCodeRemovePressed();
|
||||
|
||||
const UICommon::GameFile& m_game;
|
||||
std::string m_game_id;
|
||||
u16 m_game_revision;
|
||||
|
||||
CheatWarningWidget* m_warning;
|
||||
QListWidget* m_code_list;
|
||||
QPushButton* m_code_add;
|
||||
QPushButton* m_code_edit;
|
||||
QPushButton* m_code_remove;
|
||||
|
||||
std::vector<ActionReplay::ARCode> m_ar_codes;
|
||||
bool m_restart_required;
|
||||
};
|
305
Source/Core/DolphinQt/Config/CheatCodeEditor.cpp
Normal file
305
Source/Core/DolphinQt/Config/CheatCodeEditor.cpp
Normal file
@ -0,0 +1,305 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/CheatCodeEditor.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFontDatabase>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QStringList>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include "Core/ARDecrypt.h"
|
||||
#include "Core/ActionReplay.h"
|
||||
#include "Core/GeckoCodeConfig.h"
|
||||
|
||||
CheatCodeEditor::CheatCodeEditor(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowTitle(tr("Cheat Code Editor"));
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
void CheatCodeEditor::SetARCode(ActionReplay::ARCode* code)
|
||||
{
|
||||
m_name_edit->setText(QString::fromStdString(code->name));
|
||||
|
||||
QString s;
|
||||
|
||||
for (ActionReplay::AREntry& e : code->ops)
|
||||
{
|
||||
s += QStringLiteral("%1 %2\n")
|
||||
.arg(e.cmd_addr, 8, 16, QLatin1Char('0'))
|
||||
.arg(e.value, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
m_code_edit->setText(s);
|
||||
|
||||
m_creator_label->setHidden(true);
|
||||
m_creator_edit->setHidden(true);
|
||||
m_notes_label->setHidden(true);
|
||||
m_notes_edit->setHidden(true);
|
||||
|
||||
m_ar_code = code;
|
||||
m_gecko_code = nullptr;
|
||||
}
|
||||
|
||||
void CheatCodeEditor::SetGeckoCode(Gecko::GeckoCode* code)
|
||||
{
|
||||
m_name_edit->setText(QString::fromStdString(code->name));
|
||||
m_creator_edit->setText(QString::fromStdString(code->creator));
|
||||
|
||||
QString code_string;
|
||||
|
||||
for (const auto& c : code->codes)
|
||||
code_string += QStringLiteral("%1 %2\n")
|
||||
.arg(c.address, 8, 16, QLatin1Char('0'))
|
||||
.arg(c.data, 8, 16, QLatin1Char('0'));
|
||||
|
||||
m_code_edit->setText(code_string);
|
||||
|
||||
QString notes_string;
|
||||
for (const auto& line : code->notes)
|
||||
notes_string += QStringLiteral("%1\n").arg(QString::fromStdString(line));
|
||||
|
||||
m_notes_edit->setText(notes_string);
|
||||
|
||||
m_creator_label->setHidden(false);
|
||||
m_creator_edit->setHidden(false);
|
||||
m_notes_label->setHidden(false);
|
||||
m_notes_edit->setHidden(false);
|
||||
|
||||
m_gecko_code = code;
|
||||
m_ar_code = nullptr;
|
||||
}
|
||||
|
||||
void CheatCodeEditor::CreateWidgets()
|
||||
{
|
||||
m_name_edit = new QLineEdit;
|
||||
m_creator_edit = new QLineEdit;
|
||||
m_notes_edit = new QTextEdit;
|
||||
m_code_edit = new QTextEdit;
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
|
||||
|
||||
m_creator_label = new QLabel(tr("Creator:"));
|
||||
m_notes_label = new QLabel(tr("Notes:"));
|
||||
|
||||
QGridLayout* grid_layout = new QGridLayout;
|
||||
|
||||
grid_layout->addWidget(new QLabel(tr("Name:")), 0, 0);
|
||||
grid_layout->addWidget(m_name_edit, 0, 1);
|
||||
grid_layout->addWidget(m_creator_label, 1, 0);
|
||||
grid_layout->addWidget(m_creator_edit, 1, 1);
|
||||
grid_layout->addWidget(m_notes_label, 2, 0);
|
||||
grid_layout->addWidget(m_notes_edit, 2, 1);
|
||||
grid_layout->addWidget(new QLabel(tr("Code:")), 3, 0);
|
||||
grid_layout->addWidget(m_code_edit, 3, 1);
|
||||
grid_layout->addWidget(m_button_box, 4, 1);
|
||||
|
||||
QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
|
||||
|
||||
m_code_edit->setFont(monospace);
|
||||
|
||||
m_code_edit->setAcceptRichText(false);
|
||||
m_notes_edit->setAcceptRichText(false);
|
||||
|
||||
setLayout(grid_layout);
|
||||
}
|
||||
|
||||
void CheatCodeEditor::ConnectWidgets()
|
||||
{
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &CheatCodeEditor::accept);
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
bool CheatCodeEditor::AcceptAR()
|
||||
{
|
||||
std::vector<ActionReplay::AREntry> entries;
|
||||
std::vector<std::string> encrypted_lines;
|
||||
|
||||
QStringList lines = m_code_edit->toPlainText().split(QStringLiteral("\n"));
|
||||
|
||||
for (int i = 0; i < lines.size(); i++)
|
||||
{
|
||||
QString line = lines[i];
|
||||
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
QStringList values = line.split(QStringLiteral(" "));
|
||||
|
||||
bool good = true;
|
||||
|
||||
u32 addr = 0;
|
||||
u32 value = 0;
|
||||
|
||||
if (values.size() == 2)
|
||||
{
|
||||
addr = values[0].toUInt(&good, 16);
|
||||
|
||||
if (good)
|
||||
value = values[1].toUInt(&good, 16);
|
||||
|
||||
if (good)
|
||||
entries.push_back(ActionReplay::AREntry(addr, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList blocks = line.split(QStringLiteral("-"));
|
||||
|
||||
if (blocks.size() == 3 && blocks[0].size() == 4 && blocks[1].size() == 4 &&
|
||||
blocks[2].size() == 5)
|
||||
{
|
||||
encrypted_lines.emplace_back(blocks[0].toStdString() + blocks[1].toStdString() +
|
||||
blocks[2].toStdString());
|
||||
}
|
||||
else
|
||||
{
|
||||
good = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!good)
|
||||
{
|
||||
auto result = QMessageBox::warning(
|
||||
this, tr("Parsing Error"),
|
||||
tr("Unable to parse line %1 of the entered AR code as a valid "
|
||||
"encrypted or decrypted code. Make sure you typed it correctly.\n\n"
|
||||
"Would you like to ignore this line and continue parsing?")
|
||||
.arg(i + 1),
|
||||
QMessageBox::Ok | QMessageBox::Abort);
|
||||
|
||||
if (result == QMessageBox::Abort)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!encrypted_lines.empty())
|
||||
{
|
||||
if (!entries.empty())
|
||||
{
|
||||
auto result = QMessageBox::warning(
|
||||
this, tr("Invalid Mixed Code"),
|
||||
tr("This Action Replay code contains both encrypted and unencrypted lines; "
|
||||
"you should check that you have entered it correctly.\n\n"
|
||||
"Do you want to discard all unencrypted lines?"),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
||||
|
||||
// YES = Discard the unencrypted lines then decrypt the encrypted ones instead.
|
||||
// NO = Discard the encrypted lines, keep the unencrypted ones
|
||||
// CANCEL = Stop and let the user go back to editing
|
||||
switch (result)
|
||||
{
|
||||
case QMessageBox::Yes:
|
||||
entries.clear();
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
encrypted_lines.clear();
|
||||
break;
|
||||
case QMessageBox::Cancel:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ActionReplay::DecryptARCode(encrypted_lines, &entries);
|
||||
}
|
||||
|
||||
if (entries.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The resulting decrypted AR code doesn't contain any lines."));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ar_code->name = m_name_edit->text().toStdString();
|
||||
m_ar_code->ops = std::move(entries);
|
||||
m_ar_code->user_defined = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheatCodeEditor::AcceptGecko()
|
||||
{
|
||||
std::vector<Gecko::GeckoCode::Code> entries;
|
||||
|
||||
QStringList lines = m_code_edit->toPlainText().split(QStringLiteral("\n"));
|
||||
|
||||
for (int i = 0; i < lines.size(); i++)
|
||||
{
|
||||
QString line = lines[i];
|
||||
|
||||
if (line.isEmpty())
|
||||
continue;
|
||||
|
||||
QStringList values = line.split(QStringLiteral(" "));
|
||||
|
||||
bool good = values.size() == 2;
|
||||
|
||||
u32 addr = 0;
|
||||
u32 value = 0;
|
||||
|
||||
if (good)
|
||||
addr = values[0].toUInt(&good, 16);
|
||||
|
||||
if (good)
|
||||
value = values[1].toUInt(&good, 16);
|
||||
|
||||
if (!good)
|
||||
{
|
||||
auto result =
|
||||
QMessageBox::warning(this, tr("Parsing Error"),
|
||||
tr("Unable to parse line %1 of the entered Gecko code as a valid "
|
||||
"code. Make sure you typed it correctly.\n\n"
|
||||
"Would you like to ignore this line and continue parsing?")
|
||||
.arg(i + 1),
|
||||
QMessageBox::Ok | QMessageBox::Abort);
|
||||
|
||||
if (result == QMessageBox::Abort)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Gecko::GeckoCode::Code c;
|
||||
c.address = addr;
|
||||
c.data = value;
|
||||
c.original_line = line.toStdString();
|
||||
|
||||
entries.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The resulting decrypted AR code doesn't contain any lines."));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_gecko_code->name = m_name_edit->text().toStdString();
|
||||
m_gecko_code->creator = m_creator_edit->text().toStdString();
|
||||
m_gecko_code->codes = std::move(entries);
|
||||
m_gecko_code->user_defined = true;
|
||||
|
||||
std::vector<std::string> note_lines;
|
||||
for (QString line : m_notes_edit->toPlainText().split(QStringLiteral("\n")))
|
||||
note_lines.push_back(line.toStdString());
|
||||
|
||||
m_gecko_code->notes = std::move(note_lines);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheatCodeEditor::accept()
|
||||
{
|
||||
bool success = m_gecko_code != nullptr ? AcceptGecko() : AcceptAR();
|
||||
|
||||
if (success)
|
||||
QDialog::accept();
|
||||
}
|
52
Source/Core/DolphinQt/Config/CheatCodeEditor.h
Normal file
52
Source/Core/DolphinQt/Config/CheatCodeEditor.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QTextEdit;
|
||||
|
||||
namespace ActionReplay
|
||||
{
|
||||
struct ARCode;
|
||||
}
|
||||
|
||||
namespace Gecko
|
||||
{
|
||||
class GeckoCode;
|
||||
}
|
||||
|
||||
class CheatCodeEditor : public QDialog
|
||||
{
|
||||
public:
|
||||
explicit CheatCodeEditor(QWidget* parent);
|
||||
|
||||
void SetARCode(ActionReplay::ARCode* code);
|
||||
void SetGeckoCode(Gecko::GeckoCode* code);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
bool AcceptAR();
|
||||
bool AcceptGecko();
|
||||
|
||||
void accept() override;
|
||||
|
||||
QLabel* m_creator_label;
|
||||
QLabel* m_notes_label;
|
||||
|
||||
QLineEdit* m_name_edit;
|
||||
QLineEdit* m_creator_edit;
|
||||
QTextEdit* m_notes_edit;
|
||||
QTextEdit* m_code_edit;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
ActionReplay::ARCode* m_ar_code = nullptr;
|
||||
Gecko::GeckoCode* m_gecko_code = nullptr;
|
||||
};
|
85
Source/Core/DolphinQt/Config/CheatWarningWidget.cpp
Executable file
85
Source/Core/DolphinQt/Config/CheatWarningWidget.cpp
Executable file
@ -0,0 +1,85 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/CheatWarningWidget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QStyle>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
CheatWarningWidget::CheatWarningWidget(const std::string& game_id, bool restart_required,
|
||||
QWidget* parent)
|
||||
: QWidget(parent), m_game_id(game_id), m_restart_required(restart_required)
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EnableCheatsChanged, this,
|
||||
[this] { Update(Core::IsRunning()); });
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[this](Core::State state) { Update(state == Core::State::Running); });
|
||||
|
||||
Update(Core::IsRunning());
|
||||
}
|
||||
|
||||
void CheatWarningWidget::CreateWidgets()
|
||||
{
|
||||
auto* icon = new QLabel;
|
||||
|
||||
const auto size = 1.5 * QFontMetrics(font()).height();
|
||||
|
||||
QPixmap warning_icon = style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(size, size);
|
||||
|
||||
icon->setPixmap(warning_icon);
|
||||
|
||||
m_text = new QLabel();
|
||||
m_config_button = new QPushButton(tr("Configure Dolphin"));
|
||||
|
||||
m_config_button->setHidden(true);
|
||||
|
||||
auto* layout = new QHBoxLayout;
|
||||
|
||||
layout->addWidget(icon);
|
||||
layout->addWidget(m_text, 1);
|
||||
layout->addWidget(m_config_button);
|
||||
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void CheatWarningWidget::Update(bool running)
|
||||
{
|
||||
bool hide_widget = true;
|
||||
bool hide_config_button = true;
|
||||
|
||||
if (running && SConfig::GetInstance().GetGameID() == m_game_id && m_restart_required)
|
||||
{
|
||||
hide_widget = false;
|
||||
m_text->setText(tr("Changing cheats will only take effect when the game is restarted."));
|
||||
}
|
||||
|
||||
if (!Settings::Instance().GetCheatsEnabled())
|
||||
{
|
||||
hide_widget = false;
|
||||
hide_config_button = false;
|
||||
m_text->setText(tr("Dolphin's cheat system is currently disabled."));
|
||||
}
|
||||
|
||||
setHidden(hide_widget);
|
||||
m_config_button->setHidden(hide_config_button);
|
||||
}
|
||||
|
||||
void CheatWarningWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_config_button, &QPushButton::pressed, this,
|
||||
&CheatWarningWidget::OpenCheatEnableSettings);
|
||||
}
|
33
Source/Core/DolphinQt/Config/CheatWarningWidget.h
Executable file
33
Source/Core/DolphinQt/Config/CheatWarningWidget.h
Executable file
@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <string>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
|
||||
class CheatWarningWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CheatWarningWidget(const std::string& game_id, bool restart_required, QWidget* parent);
|
||||
|
||||
signals:
|
||||
void OpenCheatEnableSettings();
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void Update(bool running);
|
||||
|
||||
QLabel* m_text;
|
||||
QPushButton* m_config_button;
|
||||
const std::string m_game_id;
|
||||
bool m_restart_required;
|
||||
};
|
540
Source/Core/DolphinQt/Config/ControllersWindow.cpp
Normal file
540
Source/Core/DolphinQt/Config/ControllersWindow.cpp
Normal file
@ -0,0 +1,540 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/ControllersWindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBoxLayout>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/HW/SI/SI.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
static const std::map<SerialInterface::SIDevices, int> s_gc_types = {
|
||||
{SerialInterface::SIDEVICE_NONE, 0}, {SerialInterface::SIDEVICE_GC_CONTROLLER, 1},
|
||||
{SerialInterface::SIDEVICE_WIIU_ADAPTER, 2}, {SerialInterface::SIDEVICE_GC_STEERING, 3},
|
||||
{SerialInterface::SIDEVICE_DANCEMAT, 4}, {SerialInterface::SIDEVICE_GC_TARUKONGA, 5},
|
||||
{SerialInterface::SIDEVICE_GC_GBA, 6}, {SerialInterface::SIDEVICE_GC_KEYBOARD, 7}};
|
||||
|
||||
static std::optional<int> ToGCMenuIndex(const SerialInterface::SIDevices sidevice)
|
||||
{
|
||||
auto it = s_gc_types.find(sidevice);
|
||||
return it != s_gc_types.end() ? it->second : std::optional<int>();
|
||||
}
|
||||
|
||||
static std::optional<SerialInterface::SIDevices> FromGCMenuIndex(const int menudevice)
|
||||
{
|
||||
auto it = std::find_if(s_gc_types.begin(), s_gc_types.end(),
|
||||
[=](auto pair) { return pair.second == menudevice; });
|
||||
return it != s_gc_types.end() ? it->first : std::optional<SerialInterface::SIDevices>();
|
||||
}
|
||||
|
||||
ControllersWindow::ControllersWindow(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Controller Settings"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateGamecubeLayout();
|
||||
CreateWiimoteLayout();
|
||||
CreateAdvancedLayout();
|
||||
CreateMainLayout();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
void ControllersWindow::CreateGamecubeLayout()
|
||||
{
|
||||
m_gc_box = new QGroupBox(tr("GameCube Controllers"));
|
||||
m_gc_layout = new QGridLayout();
|
||||
m_gc_layout->setVerticalSpacing(7);
|
||||
m_gc_layout->setColumnStretch(1, 1);
|
||||
|
||||
for (size_t i = 0; i < m_gc_groups.size(); i++)
|
||||
{
|
||||
auto* gc_label = new QLabel(tr("Port %1").arg(i + 1));
|
||||
auto* gc_box = m_gc_controller_boxes[i] = new QComboBox();
|
||||
auto* gc_button = m_gc_buttons[i] = new QPushButton(tr("Configure"));
|
||||
|
||||
for (const auto& item :
|
||||
{tr("None"), tr("Standard Controller"), tr("GameCube Adapter for Wii U"),
|
||||
tr("Steering Wheel"), tr("Dance Mat"), tr("DK Bongos"), tr("GBA"), tr("Keyboard")})
|
||||
{
|
||||
gc_box->addItem(item);
|
||||
}
|
||||
|
||||
int controller_row = m_gc_layout->rowCount();
|
||||
m_gc_layout->addWidget(gc_label, controller_row, 0);
|
||||
m_gc_layout->addWidget(gc_box, controller_row, 1);
|
||||
m_gc_layout->addWidget(gc_button, controller_row, 2);
|
||||
}
|
||||
m_gc_box->setLayout(m_gc_layout);
|
||||
}
|
||||
|
||||
static int GetRadioButtonIndicatorWidth()
|
||||
{
|
||||
const QStyle* style = QApplication::style();
|
||||
QStyleOptionButton opt;
|
||||
|
||||
// TODO: why does the macOS style act different? Is it because of the magic with
|
||||
// Cocoa widgets it does behind the scenes?
|
||||
if (style->objectName() == QStringLiteral("macintosh"))
|
||||
return style->subElementRect(QStyle::SE_RadioButtonIndicator, &opt).width();
|
||||
|
||||
return style->subElementRect(QStyle::SE_RadioButtonContents, &opt).left();
|
||||
}
|
||||
|
||||
static int GetLayoutHorizontalSpacing(const QGridLayout* layout)
|
||||
{
|
||||
// TODO: shouldn't layout->horizontalSpacing() do all this? Why does it return -1?
|
||||
int hspacing = layout->horizontalSpacing();
|
||||
if (hspacing >= 0)
|
||||
return hspacing;
|
||||
|
||||
// According to docs, this is the fallback if horizontalSpacing() isn't set.
|
||||
auto style = layout->parentWidget()->style();
|
||||
hspacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
|
||||
if (hspacing >= 0)
|
||||
return hspacing;
|
||||
|
||||
// Docs claim this is deprecated, but on macOS with Qt 5.8 this is the only one that actually
|
||||
// works.
|
||||
float pixel_ratio = QGuiApplication::primaryScreen()->devicePixelRatio();
|
||||
hspacing = pixel_ratio * style->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
|
||||
if (hspacing >= 0)
|
||||
return hspacing;
|
||||
|
||||
// Ripped from qtbase/src/widgets/styles/qcommonstyle.cpp
|
||||
return pixel_ratio * 6;
|
||||
}
|
||||
|
||||
void ControllersWindow::CreateWiimoteLayout()
|
||||
{
|
||||
m_wiimote_layout = new QGridLayout();
|
||||
m_wiimote_box = new QGroupBox(tr("Wii Remotes"));
|
||||
m_wiimote_box->setLayout(m_wiimote_layout);
|
||||
|
||||
m_wiimote_passthrough = new QRadioButton(tr("Passthrough a Bluetooth adapter"));
|
||||
m_wiimote_sync = new QPushButton(tr("Sync"));
|
||||
m_wiimote_reset = new QPushButton(tr("Reset"));
|
||||
m_wiimote_refresh = new QPushButton(tr("Refresh"));
|
||||
m_wiimote_pt_labels[0] = new QLabel(tr("Sync real Wii Remotes and pair them"));
|
||||
m_wiimote_pt_labels[1] = new QLabel(tr("Reset all saved Wii Remote pairings"));
|
||||
m_wiimote_emu = new QRadioButton(tr("Emulate the Wii's Bluetooth adapter"));
|
||||
m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning"));
|
||||
m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board"));
|
||||
m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data"));
|
||||
|
||||
m_wiimote_layout->setVerticalSpacing(7);
|
||||
m_wiimote_layout->setColumnMinimumWidth(0, GetRadioButtonIndicatorWidth() -
|
||||
GetLayoutHorizontalSpacing(m_wiimote_layout));
|
||||
m_wiimote_layout->setColumnStretch(2, 1);
|
||||
|
||||
// Passthrough BT
|
||||
m_wiimote_layout->addWidget(m_wiimote_passthrough, m_wiimote_layout->rowCount(), 0, 1, -1);
|
||||
|
||||
int sync_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(m_wiimote_pt_labels[0], sync_row, 1, 1, 2);
|
||||
m_wiimote_layout->addWidget(m_wiimote_sync, sync_row, 3);
|
||||
|
||||
int reset_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(m_wiimote_pt_labels[1], reset_row, 1, 1, 2);
|
||||
m_wiimote_layout->addWidget(m_wiimote_reset, reset_row, 3);
|
||||
|
||||
// Emulated BT
|
||||
m_wiimote_layout->addWidget(m_wiimote_emu, m_wiimote_layout->rowCount(), 0, 1, -1);
|
||||
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
auto* wm_label = m_wiimote_labels[i] = new QLabel(tr("Wii Remote %1").arg(i + 1));
|
||||
auto* wm_box = m_wiimote_boxes[i] = new QComboBox();
|
||||
auto* wm_button = m_wiimote_buttons[i] = new QPushButton(tr("Configure"));
|
||||
|
||||
for (const auto& item : {tr("None"), tr("Emulated Wii Remote"), tr("Real Wii Remote")})
|
||||
wm_box->addItem(item);
|
||||
|
||||
int wm_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(wm_label, wm_row, 1);
|
||||
m_wiimote_layout->addWidget(wm_box, wm_row, 2);
|
||||
m_wiimote_layout->addWidget(wm_button, wm_row, 3);
|
||||
}
|
||||
|
||||
int continuous_scanning_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(m_wiimote_continuous_scanning, continuous_scanning_row, 1, 1, 2);
|
||||
m_wiimote_layout->addWidget(m_wiimote_refresh, continuous_scanning_row, 3);
|
||||
|
||||
m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1);
|
||||
m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1);
|
||||
}
|
||||
|
||||
void ControllersWindow::CreateAdvancedLayout()
|
||||
{
|
||||
m_advanced_box = new QGroupBox(tr("Advanced"));
|
||||
m_advanced_layout = new QHBoxLayout();
|
||||
m_advanced_bg_input = new QCheckBox(tr("Background Input"));
|
||||
|
||||
m_advanced_layout->addWidget(m_advanced_bg_input);
|
||||
|
||||
m_advanced_box->setLayout(m_advanced_layout);
|
||||
}
|
||||
|
||||
void ControllersWindow::CreateMainLayout()
|
||||
{
|
||||
auto* layout = new QVBoxLayout();
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
|
||||
layout->addWidget(m_gc_box);
|
||||
layout->addWidget(m_wiimote_box);
|
||||
layout->addWidget(m_advanced_box);
|
||||
layout->addWidget(m_button_box);
|
||||
|
||||
WrapInScrollArea(this, layout);
|
||||
}
|
||||
|
||||
void ControllersWindow::ConnectWidgets()
|
||||
{
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
|
||||
|
||||
connect(m_wiimote_passthrough, &QRadioButton::toggled, this,
|
||||
&ControllersWindow::OnWiimoteModeChanged);
|
||||
|
||||
connect(m_advanced_bg_input, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
|
||||
connect(m_wiimote_continuous_scanning, &QCheckBox::toggled, this,
|
||||
&ControllersWindow::SaveSettings);
|
||||
connect(m_wiimote_real_balance_board, &QCheckBox::toggled, this,
|
||||
&ControllersWindow::SaveSettings);
|
||||
connect(m_wiimote_speaker_data, &QCheckBox::toggled, this, &ControllersWindow::SaveSettings);
|
||||
connect(m_wiimote_sync, &QPushButton::clicked, this,
|
||||
&ControllersWindow::OnBluetoothPassthroughSyncPressed);
|
||||
connect(m_wiimote_reset, &QPushButton::clicked, this,
|
||||
&ControllersWindow::OnBluetoothPassthroughResetPressed);
|
||||
connect(m_wiimote_refresh, &QPushButton::clicked, this,
|
||||
&ControllersWindow::OnWiimoteRefreshPressed);
|
||||
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
connect(m_wiimote_boxes[i],
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ControllersWindow::SaveSettings);
|
||||
connect(m_wiimote_boxes[i],
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ControllersWindow::OnWiimoteTypeChanged);
|
||||
connect(m_wiimote_buttons[i], &QPushButton::clicked, this,
|
||||
&ControllersWindow::OnWiimoteConfigure);
|
||||
|
||||
connect(m_gc_controller_boxes[i],
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ControllersWindow::SaveSettings);
|
||||
connect(m_gc_controller_boxes[i],
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ControllersWindow::OnGCTypeChanged);
|
||||
connect(m_gc_buttons[i], &QPushButton::clicked, this, &ControllersWindow::OnGCPadConfigure);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllersWindow::OnWiimoteModeChanged(bool passthrough)
|
||||
{
|
||||
SaveSettings();
|
||||
|
||||
m_wiimote_sync->setEnabled(passthrough);
|
||||
m_wiimote_reset->setEnabled(passthrough);
|
||||
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
const int index = m_wiimote_boxes[i]->currentIndex();
|
||||
|
||||
if (i < 2)
|
||||
m_wiimote_pt_labels[i]->setEnabled(passthrough);
|
||||
|
||||
m_wiimote_labels[i]->setEnabled(!passthrough);
|
||||
m_wiimote_boxes[i]->setEnabled(!passthrough);
|
||||
m_wiimote_buttons[i]->setEnabled(!passthrough && index != 0 && index != 2);
|
||||
}
|
||||
|
||||
m_wiimote_refresh->setEnabled(!passthrough);
|
||||
m_wiimote_real_balance_board->setEnabled(!passthrough);
|
||||
m_wiimote_speaker_data->setEnabled(!passthrough);
|
||||
m_wiimote_continuous_scanning->setEnabled(!passthrough);
|
||||
}
|
||||
|
||||
void ControllersWindow::OnWiimoteTypeChanged(int type)
|
||||
{
|
||||
const auto* box = static_cast<QComboBox*>(QObject::sender());
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
if (m_wiimote_boxes[i] == box)
|
||||
{
|
||||
const int index = box->currentIndex();
|
||||
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void ControllersWindow::OnGCTypeChanged(int type)
|
||||
{
|
||||
const auto* box = static_cast<QComboBox*>(QObject::sender());
|
||||
|
||||
for (size_t i = 0; i < m_gc_groups.size(); i++)
|
||||
{
|
||||
if (m_gc_controller_boxes[i] == box)
|
||||
{
|
||||
const int index = box->currentIndex();
|
||||
m_gc_buttons[i]->setEnabled(index != 0 && index != 6);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void ControllersWindow::OnBluetoothPassthroughResetPressed()
|
||||
{
|
||||
const auto ios = IOS::HLE::GetIOS();
|
||||
|
||||
if (!ios)
|
||||
{
|
||||
QMessageBox error(this);
|
||||
error.setIcon(QMessageBox::Warning);
|
||||
error.setWindowTitle(tr("Warning"));
|
||||
error.setText(tr("Saved Wii Remote pairings can only be reset when a Wii game is running."));
|
||||
error.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = ios->GetDeviceByName("/dev/usb/oh1/57e/305");
|
||||
if (device != nullptr)
|
||||
{
|
||||
std::static_pointer_cast<IOS::HLE::Device::BluetoothBase>(device)->TriggerSyncButtonHeldEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllersWindow::OnBluetoothPassthroughSyncPressed()
|
||||
{
|
||||
const auto ios = IOS::HLE::GetIOS();
|
||||
|
||||
if (!ios)
|
||||
{
|
||||
QMessageBox error(this);
|
||||
error.setIcon(QMessageBox::Warning);
|
||||
error.setWindowTitle(tr("Warning"));
|
||||
error.setText(tr("A sync can only be triggered when a Wii game is running."));
|
||||
error.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = ios->GetDeviceByName("/dev/usb/oh1/57e/305");
|
||||
|
||||
if (device != nullptr)
|
||||
{
|
||||
std::static_pointer_cast<IOS::HLE::Device::BluetoothBase>(device)
|
||||
->TriggerSyncButtonPressedEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void ControllersWindow::OnWiimoteRefreshPressed()
|
||||
{
|
||||
WiimoteReal::Refresh();
|
||||
}
|
||||
|
||||
void ControllersWindow::OnEmulationStateChanged(bool running)
|
||||
{
|
||||
if (!SConfig::GetInstance().bWii || NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
m_wiimote_sync->setEnabled(!running);
|
||||
m_wiimote_reset->setEnabled(!running);
|
||||
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
m_wiimote_boxes[i]->setEnabled(!running);
|
||||
}
|
||||
|
||||
m_wiimote_emu->setEnabled(!running);
|
||||
m_wiimote_passthrough->setEnabled(!running);
|
||||
|
||||
if (!SConfig::GetInstance().bWii)
|
||||
{
|
||||
m_wiimote_real_balance_board->setEnabled(!running);
|
||||
m_wiimote_continuous_scanning->setEnabled(!running);
|
||||
m_wiimote_speaker_data->setEnabled(!running);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllersWindow::OnGCPadConfigure()
|
||||
{
|
||||
size_t index;
|
||||
for (index = 0; index < m_gc_groups.size(); index++)
|
||||
{
|
||||
if (m_gc_buttons[index] == QObject::sender())
|
||||
break;
|
||||
}
|
||||
|
||||
MappingWindow::Type type;
|
||||
|
||||
switch (m_gc_controller_boxes[index]->currentIndex())
|
||||
{
|
||||
case 0: // None
|
||||
case 6: // GBA
|
||||
return;
|
||||
case 1: // Standard Controller
|
||||
type = MappingWindow::Type::MAPPING_GCPAD;
|
||||
break;
|
||||
case 2: // GameCube Adapter for Wii U
|
||||
GCPadWiiUConfigDialog(static_cast<int>(index), this).exec();
|
||||
return;
|
||||
case 3: // Steering Wheel
|
||||
type = MappingWindow::Type::MAPPING_GC_STEERINGWHEEL;
|
||||
break;
|
||||
case 4: // Dance Mat
|
||||
type = MappingWindow::Type::MAPPING_GC_DANCEMAT;
|
||||
break;
|
||||
case 5: // DK Bongos
|
||||
type = MappingWindow::Type::MAPPING_GC_BONGOS;
|
||||
break;
|
||||
case 7: // Keyboard
|
||||
type = MappingWindow::Type::MAPPING_GC_KEYBOARD;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
MappingWindow(this, type, static_cast<int>(index)).exec();
|
||||
}
|
||||
|
||||
void ControllersWindow::OnWiimoteConfigure()
|
||||
{
|
||||
size_t index;
|
||||
for (index = 0; index < m_wiimote_groups.size(); index++)
|
||||
{
|
||||
if (m_wiimote_buttons[index] == QObject::sender())
|
||||
break;
|
||||
}
|
||||
|
||||
MappingWindow::Type type;
|
||||
switch (m_wiimote_boxes[index]->currentIndex())
|
||||
{
|
||||
case 0: // None
|
||||
case 2: // Real Wii Remote
|
||||
return;
|
||||
case 1: // Emulated Wii Remote
|
||||
type = MappingWindow::Type::MAPPING_WIIMOTE_EMU;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
MappingWindow(this, type, static_cast<int>(index)).exec();
|
||||
}
|
||||
|
||||
void ControllersWindow::UnimplementedButton()
|
||||
{
|
||||
QMessageBox error_dialog(this);
|
||||
|
||||
error_dialog.setIcon(QMessageBox::Warning);
|
||||
error_dialog.setWindowTitle(tr("Unimplemented"));
|
||||
error_dialog.setText(tr("Not implemented yet."));
|
||||
error_dialog.exec();
|
||||
}
|
||||
|
||||
void ControllersWindow::LoadSettings()
|
||||
{
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
const std::optional<int> gc_index = ToGCMenuIndex(SConfig::GetInstance().m_SIDevice[i]);
|
||||
if (gc_index)
|
||||
m_gc_controller_boxes[i]->setCurrentIndex(*gc_index);
|
||||
m_wiimote_boxes[i]->setCurrentIndex(g_wiimote_sources[i]);
|
||||
}
|
||||
m_wiimote_real_balance_board->setChecked(g_wiimote_sources[WIIMOTE_BALANCE_BOARD] ==
|
||||
WIIMOTE_SRC_REAL);
|
||||
m_wiimote_speaker_data->setChecked(SConfig::GetInstance().m_WiimoteEnableSpeaker);
|
||||
m_wiimote_continuous_scanning->setChecked(SConfig::GetInstance().m_WiimoteContinuousScanning);
|
||||
|
||||
m_advanced_bg_input->setChecked(SConfig::GetInstance().m_BackgroundInput);
|
||||
|
||||
if (SConfig::GetInstance().m_bt_passthrough_enabled)
|
||||
m_wiimote_passthrough->setChecked(true);
|
||||
else
|
||||
m_wiimote_emu->setChecked(true);
|
||||
|
||||
OnWiimoteModeChanged(SConfig::GetInstance().m_bt_passthrough_enabled);
|
||||
}
|
||||
|
||||
void ControllersWindow::SaveSettings()
|
||||
{
|
||||
SConfig::GetInstance().m_WiimoteEnableSpeaker = m_wiimote_speaker_data->isChecked();
|
||||
SConfig::GetInstance().m_WiimoteContinuousScanning = m_wiimote_continuous_scanning->isChecked();
|
||||
SConfig::GetInstance().m_bt_passthrough_enabled = m_wiimote_passthrough->isChecked();
|
||||
SConfig::GetInstance().m_BackgroundInput = m_advanced_bg_input->isChecked();
|
||||
|
||||
WiimoteReal::ChangeWiimoteSource(WIIMOTE_BALANCE_BOARD,
|
||||
m_wiimote_real_balance_board->isChecked() ? WIIMOTE_SRC_REAL :
|
||||
WIIMOTE_SRC_NONE);
|
||||
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
const int index = m_wiimote_boxes[i]->currentIndex();
|
||||
g_wiimote_sources[i] = index;
|
||||
m_wiimote_buttons[i]->setEnabled(index != 0 && index != 2);
|
||||
|
||||
if (Core::IsRunning())
|
||||
WiimoteReal::ChangeWiimoteSource(static_cast<u32>(i), index);
|
||||
}
|
||||
|
||||
UICommon::SaveWiimoteSources();
|
||||
|
||||
for (size_t i = 0; i < m_gc_groups.size(); i++)
|
||||
{
|
||||
const int index = m_gc_controller_boxes[i]->currentIndex();
|
||||
const std::optional<SerialInterface::SIDevices> si_device = FromGCMenuIndex(index);
|
||||
if (si_device)
|
||||
{
|
||||
SConfig::GetInstance().m_SIDevice[i] = *si_device;
|
||||
|
||||
if (Core::IsRunning())
|
||||
SerialInterface::ChangeDevice(*si_device, static_cast<s32>(i));
|
||||
}
|
||||
|
||||
m_gc_buttons[i]->setEnabled(index != 0 && index != 6);
|
||||
}
|
||||
|
||||
if (GCAdapter::UseAdapter())
|
||||
GCAdapter::StartScanThread();
|
||||
else
|
||||
GCAdapter::StopScanThread();
|
||||
|
||||
SConfig::GetInstance().SaveSettings();
|
||||
}
|
81
Source/Core/DolphinQt/Config/ControllersWindow.h
Normal file
81
Source/Core/DolphinQt/Config/ControllersWindow.h
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <array>
|
||||
|
||||
class MappingWindow;
|
||||
class QDialogButtonBox;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QHBoxLayout;
|
||||
class QGridLayout;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QRadioButton;
|
||||
|
||||
class ControllersWindow final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ControllersWindow(QWidget* parent);
|
||||
|
||||
private:
|
||||
void OnEmulationStateChanged(bool running);
|
||||
void OnWiimoteModeChanged(bool passthrough);
|
||||
void OnWiimoteTypeChanged(int state);
|
||||
void OnGCTypeChanged(int state);
|
||||
void SaveSettings();
|
||||
void UnimplementedButton();
|
||||
void OnBluetoothPassthroughSyncPressed();
|
||||
void OnBluetoothPassthroughResetPressed();
|
||||
void OnWiimoteRefreshPressed();
|
||||
void OnGCPadConfigure();
|
||||
void OnWiimoteConfigure();
|
||||
|
||||
void CreateGamecubeLayout();
|
||||
void CreateWiimoteLayout();
|
||||
void CreateAdvancedLayout();
|
||||
void CreateMainLayout();
|
||||
void ConnectWidgets();
|
||||
void LoadSettings();
|
||||
|
||||
// Main
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
// Gamecube
|
||||
QGroupBox* m_gc_box;
|
||||
QGridLayout* m_gc_layout;
|
||||
std::array<QComboBox*, 4> m_gc_controller_boxes;
|
||||
std::array<QPushButton*, 4> m_gc_buttons;
|
||||
std::array<QHBoxLayout*, 4> m_gc_groups;
|
||||
|
||||
// Wii Remote
|
||||
QGroupBox* m_wiimote_box;
|
||||
QGridLayout* m_wiimote_layout;
|
||||
std::array<QLabel*, 4> m_wiimote_labels;
|
||||
std::array<QComboBox*, 4> m_wiimote_boxes;
|
||||
std::array<QPushButton*, 4> m_wiimote_buttons;
|
||||
std::array<QHBoxLayout*, 4> m_wiimote_groups;
|
||||
std::array<QLabel*, 2> m_wiimote_pt_labels;
|
||||
|
||||
QRadioButton* m_wiimote_emu;
|
||||
QRadioButton* m_wiimote_passthrough;
|
||||
QPushButton* m_wiimote_sync;
|
||||
QPushButton* m_wiimote_reset;
|
||||
QCheckBox* m_wiimote_continuous_scanning;
|
||||
QCheckBox* m_wiimote_real_balance_board;
|
||||
QCheckBox* m_wiimote_speaker_data;
|
||||
QPushButton* m_wiimote_refresh;
|
||||
|
||||
// Advanced
|
||||
QGroupBox* m_advanced_box;
|
||||
QHBoxLayout* m_advanced_layout;
|
||||
QCheckBox* m_advanced_bg_input;
|
||||
};
|
355
Source/Core/DolphinQt/Config/FilesystemWidget.cpp
Normal file
355
Source/Core/DolphinQt/Config/FilesystemWidget.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/FilesystemWidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStyleFactory>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <future>
|
||||
|
||||
#include "DiscIO/DiscExtractor.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
#include "DolphinQt/QtUtils/ActionHelper.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
constexpr int ENTRY_PARTITION = Qt::UserRole;
|
||||
constexpr int ENTRY_NAME = Qt::UserRole + 1;
|
||||
constexpr int ENTRY_TYPE = Qt::UserRole + 2;
|
||||
|
||||
enum class EntryType
|
||||
{
|
||||
Disc = -2,
|
||||
Partition = -1,
|
||||
File = 0,
|
||||
Dir = 1
|
||||
};
|
||||
Q_DECLARE_METATYPE(EntryType);
|
||||
|
||||
FilesystemWidget::FilesystemWidget(const UICommon::GameFile& game)
|
||||
: m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath()))
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
PopulateView();
|
||||
}
|
||||
|
||||
FilesystemWidget::~FilesystemWidget() = default;
|
||||
|
||||
void FilesystemWidget::CreateWidgets()
|
||||
{
|
||||
auto* layout = new QVBoxLayout;
|
||||
|
||||
m_tree_model = new QStandardItemModel(0, 2);
|
||||
m_tree_model->setHorizontalHeaderLabels({tr("Name"), tr("Size")});
|
||||
|
||||
m_tree_view = new QTreeView(this);
|
||||
m_tree_view->setModel(m_tree_model);
|
||||
m_tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
auto* header = m_tree_view->header();
|
||||
|
||||
header->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
header->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
header->setStretchLastSection(false);
|
||||
|
||||
// Windows: Set style to (old) windows, which draws branch lines
|
||||
#ifdef _WIN32
|
||||
if (QApplication::style()->objectName() == QStringLiteral("windowsvista"))
|
||||
m_tree_view->setStyle(QStyleFactory::create(QStringLiteral("windows")));
|
||||
#endif
|
||||
|
||||
layout->addWidget(m_tree_view);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void FilesystemWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_tree_view, &QTreeView::customContextMenuRequested, this,
|
||||
&FilesystemWidget::ShowContextMenu);
|
||||
}
|
||||
|
||||
void FilesystemWidget::PopulateView()
|
||||
{
|
||||
auto* disc = new QStandardItem(tr("Disc"));
|
||||
disc->setEditable(false);
|
||||
disc->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
|
||||
disc->setData(QVariant::fromValue(EntryType::Disc), ENTRY_TYPE);
|
||||
m_tree_model->appendRow(disc);
|
||||
m_tree_view->expand(disc->index());
|
||||
|
||||
const auto& partitions = m_volume->GetPartitions();
|
||||
|
||||
for (size_t i = 0; i < partitions.size(); i++)
|
||||
{
|
||||
auto* item = new QStandardItem(tr("Partition %1").arg(i));
|
||||
item->setEditable(false);
|
||||
|
||||
item->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
|
||||
item->setData(static_cast<qlonglong>(i), ENTRY_PARTITION);
|
||||
item->setData(QVariant::fromValue(EntryType::Partition), ENTRY_TYPE);
|
||||
|
||||
PopulateDirectory(static_cast<int>(i), item, partitions[i]);
|
||||
|
||||
disc->appendRow(item);
|
||||
|
||||
if (m_volume->GetGamePartition() == partitions[i])
|
||||
m_tree_view->expand(item->index());
|
||||
}
|
||||
|
||||
if (partitions.empty())
|
||||
PopulateDirectory(-1, disc, DiscIO::PARTITION_NONE);
|
||||
}
|
||||
|
||||
void FilesystemWidget::PopulateDirectory(int partition_id, QStandardItem* root,
|
||||
const DiscIO::Partition& partition)
|
||||
{
|
||||
const DiscIO::FileSystem* const file_system = m_volume->GetFileSystem(partition);
|
||||
if (file_system)
|
||||
PopulateDirectory(partition_id, root, file_system->GetRoot());
|
||||
}
|
||||
|
||||
void FilesystemWidget::PopulateDirectory(int partition_id, QStandardItem* root,
|
||||
const DiscIO::FileInfo& directory)
|
||||
{
|
||||
for (const auto& info : directory)
|
||||
{
|
||||
auto* item = new QStandardItem(QString::fromStdString(info.GetName()));
|
||||
item->setEditable(false);
|
||||
item->setIcon(Resources::GetScaledIcon(info.IsDirectory() ? "isoproperties_folder" :
|
||||
"isoproperties_file"));
|
||||
|
||||
if (info.IsDirectory())
|
||||
PopulateDirectory(partition_id, item, info);
|
||||
|
||||
item->setData(partition_id, ENTRY_PARTITION);
|
||||
item->setData(QString::fromStdString(info.GetPath()), ENTRY_NAME);
|
||||
item->setData(QVariant::fromValue(info.IsDirectory() ? EntryType::Dir : EntryType::File),
|
||||
ENTRY_TYPE);
|
||||
|
||||
const auto size = info.GetTotalSize();
|
||||
|
||||
auto* size_item = new QStandardItem(QString::fromStdString(UICommon::FormatSize(size)));
|
||||
size_item->setTextAlignment(Qt::AlignRight);
|
||||
size_item->setEditable(false);
|
||||
|
||||
root->appendRow({item, size_item});
|
||||
}
|
||||
}
|
||||
|
||||
QString FilesystemWidget::SelectFolder()
|
||||
{
|
||||
return QFileDialog::getExistingDirectory(this, QObject::tr("Choose the folder to extract to"));
|
||||
}
|
||||
|
||||
void FilesystemWidget::ShowContextMenu(const QPoint&)
|
||||
{
|
||||
auto* selection = m_tree_view->selectionModel();
|
||||
if (!selection->hasSelection())
|
||||
return;
|
||||
|
||||
auto* item = m_tree_model->itemFromIndex(selection->selectedIndexes()[0]);
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
EntryType type = item->data(ENTRY_TYPE).value<EntryType>();
|
||||
|
||||
DiscIO::Partition partition = type == EntryType::Disc ?
|
||||
DiscIO::PARTITION_NONE :
|
||||
GetPartitionFromID(item->data(ENTRY_PARTITION).toInt());
|
||||
QString path = item->data(ENTRY_NAME).toString();
|
||||
|
||||
const bool is_filesystem_root = (type == EntryType::Disc && m_volume->GetPartitions().empty()) ||
|
||||
type == EntryType::Partition;
|
||||
|
||||
if (type == EntryType::Dir || is_filesystem_root)
|
||||
{
|
||||
AddAction(menu, tr("Extract Files..."), this, [this, partition, path] {
|
||||
auto folder = SelectFolder();
|
||||
|
||||
if (!folder.isEmpty())
|
||||
ExtractDirectory(partition, path, folder);
|
||||
});
|
||||
}
|
||||
|
||||
if (is_filesystem_root)
|
||||
{
|
||||
AddAction(menu, tr("Extract System Data..."), this, [this, partition] {
|
||||
auto folder = SelectFolder();
|
||||
|
||||
if (folder.isEmpty())
|
||||
return;
|
||||
|
||||
if (ExtractSystemData(partition, folder))
|
||||
QMessageBox::information(this, tr("Success"), tr("Successfully extracted system data."));
|
||||
else
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to extract system data."));
|
||||
});
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case EntryType::Disc:
|
||||
AddAction(menu, tr("Extract Entire Disc..."), this, [this, path] {
|
||||
auto folder = SelectFolder();
|
||||
|
||||
if (folder.isEmpty())
|
||||
return;
|
||||
|
||||
if (m_volume->GetPartitions().empty())
|
||||
{
|
||||
ExtractPartition(DiscIO::PARTITION_NONE, folder);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (DiscIO::Partition& p : m_volume->GetPartitions())
|
||||
{
|
||||
if (const std::optional<u32> partition_type = m_volume->GetPartitionType(p))
|
||||
{
|
||||
const std::string partition_name =
|
||||
DiscIO::DirectoryNameForPartitionType(*partition_type);
|
||||
ExtractPartition(p, folder + QChar(u'/') + QString::fromStdString(partition_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
case EntryType::Partition:
|
||||
AddAction(menu, tr("Extract Entire Partition..."), this, [this, partition] {
|
||||
auto folder = SelectFolder();
|
||||
if (!folder.isEmpty())
|
||||
ExtractPartition(partition, folder);
|
||||
});
|
||||
if (m_volume->IsEncryptedAndHashed())
|
||||
{
|
||||
menu->addSeparator();
|
||||
AddAction(menu, tr("Check Partition Integrity"), this,
|
||||
[this, partition] { CheckIntegrity(partition); });
|
||||
}
|
||||
break;
|
||||
case EntryType::File:
|
||||
AddAction(menu, tr("Extract File..."), this, [this, partition, path] {
|
||||
auto dest =
|
||||
QFileDialog::getSaveFileName(this, tr("Save File to"), QFileInfo(path).fileName());
|
||||
|
||||
if (!dest.isEmpty())
|
||||
ExtractFile(partition, path, dest);
|
||||
});
|
||||
break;
|
||||
case EntryType::Dir:
|
||||
// Handled above the switch statement
|
||||
break;
|
||||
}
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
DiscIO::Partition FilesystemWidget::GetPartitionFromID(int id)
|
||||
{
|
||||
return id == -1 ? DiscIO::PARTITION_NONE : m_volume->GetPartitions()[id];
|
||||
}
|
||||
|
||||
void FilesystemWidget::ExtractPartition(const DiscIO::Partition& partition, const QString& out)
|
||||
{
|
||||
ExtractDirectory(partition, QStringLiteral(""), out + QStringLiteral("/files"));
|
||||
ExtractSystemData(partition, out);
|
||||
}
|
||||
|
||||
bool FilesystemWidget::ExtractSystemData(const DiscIO::Partition& partition, const QString& out)
|
||||
{
|
||||
return DiscIO::ExportSystemData(*m_volume, partition, out.toStdString());
|
||||
}
|
||||
|
||||
void FilesystemWidget::ExtractDirectory(const DiscIO::Partition& partition, const QString& path,
|
||||
const QString& out)
|
||||
{
|
||||
const DiscIO::FileSystem* filesystem = m_volume->GetFileSystem(partition);
|
||||
if (!filesystem)
|
||||
return;
|
||||
|
||||
std::unique_ptr<DiscIO::FileInfo> info = filesystem->FindFileInfo(path.toStdString());
|
||||
u32 size = info->GetTotalChildren();
|
||||
|
||||
QProgressDialog* dialog = new QProgressDialog(this);
|
||||
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
dialog->setMinimum(0);
|
||||
dialog->setMaximum(size);
|
||||
dialog->show();
|
||||
dialog->setWindowTitle(tr("Progress"));
|
||||
|
||||
bool all = path.isEmpty();
|
||||
|
||||
DiscIO::ExportDirectory(
|
||||
*m_volume, partition, *info, true, path.toStdString(), out.toStdString(),
|
||||
[all, dialog](const std::string& current) {
|
||||
dialog->setLabelText(
|
||||
(all ? QObject::tr("Extracting All Files...") : QObject::tr("Extracting Directory..."))
|
||||
.append(QStringLiteral(" %1").arg(QString::fromStdString(current))));
|
||||
dialog->setValue(dialog->value() + 1);
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
return dialog->wasCanceled();
|
||||
});
|
||||
|
||||
dialog->close();
|
||||
}
|
||||
|
||||
void FilesystemWidget::ExtractFile(const DiscIO::Partition& partition, const QString& path,
|
||||
const QString& out)
|
||||
{
|
||||
const DiscIO::FileSystem* filesystem = m_volume->GetFileSystem(partition);
|
||||
if (!filesystem)
|
||||
return;
|
||||
|
||||
bool success = DiscIO::ExportFile(
|
||||
*m_volume, partition, filesystem->FindFileInfo(path.toStdString()).get(), out.toStdString());
|
||||
|
||||
if (success)
|
||||
QMessageBox::information(this, tr("Success"), tr("Successfully extracted file."));
|
||||
else
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to extract file."));
|
||||
}
|
||||
|
||||
void FilesystemWidget::CheckIntegrity(const DiscIO::Partition& partition)
|
||||
{
|
||||
QProgressDialog* dialog = new QProgressDialog(this);
|
||||
std::future<bool> is_valid = std::async(
|
||||
std::launch::async, [this, partition] { return m_volume->CheckIntegrity(partition); });
|
||||
|
||||
dialog->setLabelText(tr("Verifying integrity of partition..."));
|
||||
dialog->setWindowFlags(dialog->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
dialog->setWindowTitle(tr("Verifying partition"));
|
||||
|
||||
dialog->setMinimum(0);
|
||||
dialog->setMaximum(0);
|
||||
dialog->show();
|
||||
|
||||
while (is_valid.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
dialog->close();
|
||||
|
||||
if (is_valid.get())
|
||||
QMessageBox::information(this, tr("Success"),
|
||||
tr("Integrity check completed. No errors have been found."));
|
||||
else
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Integrity check for partition failed. The disc image is most "
|
||||
"likely corrupted or has been patched incorrectly."));
|
||||
}
|
56
Source/Core/DolphinQt/Config/FilesystemWidget.h
Normal file
56
Source/Core/DolphinQt/Config/FilesystemWidget.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <memory>
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
class FileInfo;
|
||||
class Volume;
|
||||
|
||||
struct Partition;
|
||||
} // namespace DiscIO
|
||||
|
||||
class FilesystemWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FilesystemWidget(const UICommon::GameFile& game);
|
||||
~FilesystemWidget() override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void PopulateView();
|
||||
void PopulateDirectory(int partition_id, QStandardItem* root, const DiscIO::Partition& partition);
|
||||
void PopulateDirectory(int partition_id, QStandardItem* root, const DiscIO::FileInfo& directory);
|
||||
|
||||
QString SelectFolder();
|
||||
|
||||
void ShowContextMenu(const QPoint&);
|
||||
|
||||
void ExtractPartition(const DiscIO::Partition& partition, const QString& out);
|
||||
void ExtractDirectory(const DiscIO::Partition& partition, const QString& path,
|
||||
const QString& out);
|
||||
void ExtractFile(const DiscIO::Partition& partition, const QString& path, const QString& out);
|
||||
bool ExtractSystemData(const DiscIO::Partition& partition, const QString& out);
|
||||
void CheckIntegrity(const DiscIO::Partition& partition);
|
||||
|
||||
DiscIO::Partition GetPartitionFromID(int id);
|
||||
|
||||
QStandardItemModel* m_tree_model;
|
||||
QTreeView* m_tree_view;
|
||||
|
||||
UICommon::GameFile m_game;
|
||||
std::unique_ptr<DiscIO::Volume> m_volume;
|
||||
};
|
375
Source/Core/DolphinQt/Config/GameConfigWidget.cpp
Normal file
375
Source/Core/DolphinQt/Config/GameConfigWidget.cpp
Normal file
@ -0,0 +1,375 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/GameConfigWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QFile>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/ConfigLoaders/GameConfigLoader.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsSlider.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
constexpr int DETERMINISM_NOT_SET_INDEX = 0;
|
||||
constexpr int DETERMINISM_AUTO_INDEX = 1;
|
||||
constexpr int DETERMINISM_NONE_INDEX = 2;
|
||||
constexpr int DETERMINISM_FAKE_COMPLETION_INDEX = 3;
|
||||
|
||||
constexpr const char* DETERMINISM_NOT_SET_STRING = "";
|
||||
constexpr const char* DETERMINISM_AUTO_STRING = "auto";
|
||||
constexpr const char* DETERMINISM_NONE_STRING = "none";
|
||||
constexpr const char* DETERMINISM_FAKE_COMPLETION_STRING = "fake-completion";
|
||||
|
||||
GameConfigWidget::GameConfigWidget(const UICommon::GameFile& game) : m_game(game)
|
||||
{
|
||||
m_game_id = m_game.GetGameID();
|
||||
m_gameini_local_path =
|
||||
QString::fromStdString(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
void GameConfigWidget::CreateWidgets()
|
||||
{
|
||||
m_refresh_config = new QPushButton(tr("Refresh"));
|
||||
m_edit_user_config = new QPushButton(tr("Edit User Config"));
|
||||
m_view_default_config = new QPushButton(tr("View Default Config"));
|
||||
|
||||
// Core
|
||||
auto* core_box = new QGroupBox(tr("Core"));
|
||||
auto* core_layout = new QGridLayout;
|
||||
core_box->setLayout(core_layout);
|
||||
|
||||
m_enable_dual_core = new QCheckBox(tr("Enable Dual Core"));
|
||||
m_enable_mmu = new QCheckBox(tr("Enable MMU"));
|
||||
m_enable_fprf = new QCheckBox(tr("Enable FPRF"));
|
||||
m_sync_gpu = new QCheckBox(tr("Synchronize GPU thread"));
|
||||
m_enable_fast_disc = new QCheckBox(tr("Speed up Disc Transfer Rate"));
|
||||
m_use_dsp_hle = new QCheckBox(tr("DSP HLE Emulation (fast)"));
|
||||
m_deterministic_dual_core = new QComboBox;
|
||||
|
||||
for (const auto& item : {tr("Not Set"), tr("auto"), tr("none"), tr("fake-completion")})
|
||||
m_deterministic_dual_core->addItem(item);
|
||||
|
||||
m_enable_mmu->setToolTip(tr(
|
||||
"Enables the Memory Management Unit, needed for some games. (ON = Compatible, OFF = Fast)"));
|
||||
|
||||
m_enable_fprf->setToolTip(tr("Enables Floating Point Result Flag calculation, needed for a few "
|
||||
"games. (ON = Compatible, OFF = Fast)"));
|
||||
m_sync_gpu->setToolTip(tr("Synchronizes the GPU and CPU threads to help prevent random freezes "
|
||||
"in Dual core mode. (ON = Compatible, OFF = Fast)"));
|
||||
m_enable_fast_disc->setToolTip(tr("Enable fast disc access. This can cause crashes and other "
|
||||
"problems in some games. (ON = Fast, OFF = Compatible)"));
|
||||
|
||||
core_layout->addWidget(m_enable_dual_core, 0, 0);
|
||||
core_layout->addWidget(m_enable_mmu, 1, 0);
|
||||
core_layout->addWidget(m_enable_fprf, 2, 0);
|
||||
core_layout->addWidget(m_sync_gpu, 3, 0);
|
||||
core_layout->addWidget(m_enable_fast_disc, 4, 0);
|
||||
core_layout->addWidget(m_use_dsp_hle, 5, 0);
|
||||
core_layout->addWidget(new QLabel(tr("Deterministic dual core:")), 6, 0);
|
||||
core_layout->addWidget(m_deterministic_dual_core, 6, 1);
|
||||
|
||||
// Stereoscopy
|
||||
auto* stereoscopy_box = new QGroupBox(tr("Stereoscopy"));
|
||||
auto* stereoscopy_layout = new QGridLayout;
|
||||
stereoscopy_box->setLayout(stereoscopy_layout);
|
||||
|
||||
m_depth_slider = new QSlider(Qt::Horizontal);
|
||||
|
||||
m_depth_slider->setMinimum(100);
|
||||
m_depth_slider->setMaximum(200);
|
||||
|
||||
m_convergence_spin = new QSpinBox;
|
||||
m_convergence_spin->setMinimum(0);
|
||||
m_convergence_spin->setMaximum(INT32_MAX);
|
||||
m_use_monoscopic_shadows = new QCheckBox(tr("Monoscopic Shadows"));
|
||||
|
||||
m_depth_slider->setToolTip(
|
||||
tr("This value is multiplied with the depth set in the graphics configuration."));
|
||||
m_convergence_spin->setToolTip(
|
||||
tr("This value is added to the convergence value set in the graphics configuration."));
|
||||
m_use_monoscopic_shadows->setToolTip(
|
||||
tr("Use a single depth buffer for both eyes. Needed for a few games."));
|
||||
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Depth Percentage:")), 0, 0);
|
||||
stereoscopy_layout->addWidget(m_depth_slider, 0, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Convergence:")), 1, 0);
|
||||
stereoscopy_layout->addWidget(m_convergence_spin, 1, 1);
|
||||
stereoscopy_layout->addWidget(m_use_monoscopic_shadows, 2, 0);
|
||||
|
||||
auto* settings_box = new QGroupBox(tr("Game-Specific Settings"));
|
||||
auto* settings_layout = new QVBoxLayout;
|
||||
settings_box->setLayout(settings_layout);
|
||||
|
||||
settings_layout->addWidget(
|
||||
new QLabel(tr("These settings override core Dolphin settings.\nUndetermined means the game "
|
||||
"uses Dolphin's setting.")));
|
||||
settings_layout->addWidget(core_box);
|
||||
settings_layout->addWidget(stereoscopy_box);
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(settings_box, 0, 0, 1, -1);
|
||||
|
||||
auto* button_layout = new QHBoxLayout;
|
||||
button_layout->setMargin(0);
|
||||
|
||||
layout->addLayout(button_layout, 1, 0, 1, -1);
|
||||
|
||||
button_layout->addWidget(m_refresh_config);
|
||||
button_layout->addWidget(m_edit_user_config);
|
||||
button_layout->addWidget(m_view_default_config);
|
||||
|
||||
for (QCheckBox* item : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu,
|
||||
m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows})
|
||||
item->setTristate(true);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void GameConfigWidget::ConnectWidgets()
|
||||
{
|
||||
// Buttons
|
||||
connect(m_refresh_config, &QPushButton::pressed, this, &GameConfigWidget::LoadSettings);
|
||||
connect(m_edit_user_config, &QPushButton::pressed, this, &GameConfigWidget::EditUserConfig);
|
||||
connect(m_view_default_config, &QPushButton::pressed, this, &GameConfigWidget::ViewDefaultConfig);
|
||||
|
||||
for (QCheckBox* box : {m_enable_dual_core, m_enable_mmu, m_enable_fprf, m_sync_gpu,
|
||||
m_enable_fast_disc, m_use_dsp_hle, m_use_monoscopic_shadows})
|
||||
connect(box, &QCheckBox::stateChanged, this, &GameConfigWidget::SaveSettings);
|
||||
|
||||
connect(m_deterministic_dual_core,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&GameConfigWidget::SaveSettings);
|
||||
connect(m_depth_slider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), this,
|
||||
&GameConfigWidget::SaveSettings);
|
||||
connect(m_convergence_spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&GameConfigWidget::SaveSettings);
|
||||
}
|
||||
|
||||
void GameConfigWidget::LoadCheckBox(QCheckBox* checkbox, const std::string& section,
|
||||
const std::string& key)
|
||||
{
|
||||
bool checked;
|
||||
|
||||
if (m_gameini_local.GetOrCreateSection(section)->Get(key, &checked))
|
||||
return checkbox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
if (m_gameini_default.GetOrCreateSection(section)->Get(key, &checked))
|
||||
return checkbox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
checkbox->setCheckState(Qt::PartiallyChecked);
|
||||
}
|
||||
|
||||
void GameConfigWidget::SaveCheckBox(QCheckBox* checkbox, const std::string& section,
|
||||
const std::string& key)
|
||||
{
|
||||
// Delete any existing entries from the local gameini if checkbox is undetermined.
|
||||
// Otherwise, write the current value to the local gameini if the value differs from the default
|
||||
// gameini values.
|
||||
// Delete any existing entry from the local gameini if the value does not differ from the default
|
||||
// gameini value.
|
||||
|
||||
if (checkbox->checkState() == Qt::PartiallyChecked)
|
||||
{
|
||||
m_gameini_local.DeleteKey(section, key);
|
||||
return;
|
||||
}
|
||||
|
||||
bool checked = checkbox->checkState() == Qt::Checked;
|
||||
|
||||
if (m_gameini_default.Exists(section, key))
|
||||
{
|
||||
bool default_value;
|
||||
m_gameini_default.GetOrCreateSection(section)->Get(key, &default_value);
|
||||
|
||||
if (default_value != checked)
|
||||
m_gameini_local.GetOrCreateSection(section)->Set(key, checked);
|
||||
else
|
||||
m_gameini_local.DeleteKey(section, key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_gameini_local.GetOrCreateSection(section)->Set(key, checked);
|
||||
}
|
||||
|
||||
void GameConfigWidget::LoadSettings()
|
||||
{
|
||||
// Reload config
|
||||
m_gameini_local = SConfig::LoadLocalGameIni(m_game_id, m_game.GetRevision());
|
||||
m_gameini_default = SConfig::LoadDefaultGameIni(m_game_id, m_game.GetRevision());
|
||||
|
||||
// Load game-specific settings
|
||||
|
||||
// Core
|
||||
LoadCheckBox(m_enable_dual_core, "Core", "CPUThread");
|
||||
LoadCheckBox(m_enable_mmu, "Core", "MMU");
|
||||
LoadCheckBox(m_enable_fprf, "Core", "FPRF");
|
||||
LoadCheckBox(m_sync_gpu, "Core", "SyncGPU");
|
||||
LoadCheckBox(m_enable_fast_disc, "Core", "FastDiscSpeed");
|
||||
LoadCheckBox(m_use_dsp_hle, "Core", "DSPHLE");
|
||||
|
||||
std::string determinism_mode;
|
||||
|
||||
int determinism_index = DETERMINISM_NOT_SET_INDEX;
|
||||
|
||||
m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode);
|
||||
m_gameini_local.GetIfExists("Core", "GPUDeterminismMode", &determinism_mode);
|
||||
|
||||
if (determinism_mode == DETERMINISM_AUTO_STRING)
|
||||
{
|
||||
determinism_index = DETERMINISM_AUTO_INDEX;
|
||||
}
|
||||
else if (determinism_mode == DETERMINISM_NONE_STRING)
|
||||
{
|
||||
determinism_index = DETERMINISM_NONE_INDEX;
|
||||
}
|
||||
else if (determinism_mode == DETERMINISM_FAKE_COMPLETION_STRING)
|
||||
{
|
||||
determinism_index = DETERMINISM_FAKE_COMPLETION_INDEX;
|
||||
}
|
||||
|
||||
m_deterministic_dual_core->setCurrentIndex(determinism_index);
|
||||
|
||||
// Stereoscopy
|
||||
int depth_percentage = 100;
|
||||
|
||||
m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage);
|
||||
m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage", &depth_percentage);
|
||||
|
||||
m_depth_slider->setValue(depth_percentage);
|
||||
|
||||
int convergence = 0;
|
||||
|
||||
m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence);
|
||||
m_gameini_local.GetIfExists("Video_Stereoscopy", "StereoConvergence", &convergence);
|
||||
|
||||
m_convergence_spin->setValue(convergence);
|
||||
|
||||
LoadCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth");
|
||||
}
|
||||
|
||||
void GameConfigWidget::SaveSettings()
|
||||
{
|
||||
// Core
|
||||
SaveCheckBox(m_enable_dual_core, "Core", "CPUThread");
|
||||
SaveCheckBox(m_enable_mmu, "Core", "MMU");
|
||||
SaveCheckBox(m_enable_fprf, "Core", "FPRF");
|
||||
SaveCheckBox(m_sync_gpu, "Core", "SyncGPU");
|
||||
SaveCheckBox(m_enable_fast_disc, "Core", "FastDiscSpeed");
|
||||
SaveCheckBox(m_use_dsp_hle, "Core", "DSPHLE");
|
||||
|
||||
int determinism_num = m_deterministic_dual_core->currentIndex();
|
||||
|
||||
std::string determinism_mode = DETERMINISM_NOT_SET_STRING;
|
||||
|
||||
switch (determinism_num)
|
||||
{
|
||||
case DETERMINISM_AUTO_INDEX:
|
||||
determinism_mode = DETERMINISM_AUTO_STRING;
|
||||
break;
|
||||
case DETERMINISM_NONE_INDEX:
|
||||
determinism_mode = DETERMINISM_NONE_STRING;
|
||||
break;
|
||||
case DETERMINISM_FAKE_COMPLETION_INDEX:
|
||||
determinism_mode = DETERMINISM_FAKE_COMPLETION_STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (determinism_mode != DETERMINISM_NOT_SET_STRING)
|
||||
{
|
||||
std::string default_mode = DETERMINISM_NOT_SET_STRING;
|
||||
if (!(m_gameini_default.GetIfExists("Core", "GPUDeterminismMode", &default_mode) &&
|
||||
default_mode == determinism_mode))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Core")->Set("GPUDeterminismMode", determinism_mode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_gameini_local.DeleteKey("Core", "GPUDeterminismMode");
|
||||
}
|
||||
|
||||
// Stereoscopy
|
||||
int depth_percentage = m_depth_slider->value();
|
||||
|
||||
if (depth_percentage != 100)
|
||||
{
|
||||
int default_value = 0;
|
||||
if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoDepthPercentage",
|
||||
&default_value) &&
|
||||
default_value == depth_percentage))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Video_Stereoscopy")
|
||||
->Set("StereoDepthPercentage", depth_percentage);
|
||||
}
|
||||
}
|
||||
|
||||
int convergence = m_convergence_spin->value();
|
||||
if (convergence != 0)
|
||||
{
|
||||
int default_value = 0;
|
||||
if (!(m_gameini_default.GetIfExists("Video_Stereoscopy", "StereoConvergence", &default_value) &&
|
||||
default_value == convergence))
|
||||
{
|
||||
m_gameini_local.GetOrCreateSection("Video_Stereoscopy")
|
||||
->Set("StereoConvergence", convergence);
|
||||
}
|
||||
}
|
||||
|
||||
SaveCheckBox(m_use_monoscopic_shadows, "Video_Stereoscopy", "StereoEFBMonoDepth");
|
||||
|
||||
bool success = m_gameini_local.Save(m_gameini_local_path.toStdString());
|
||||
|
||||
// If the resulting file is empty, delete it. Kind of a hack, but meh.
|
||||
if (success && File::GetSize(m_gameini_local_path.toStdString()) == 0)
|
||||
File::Delete(m_gameini_local_path.toStdString());
|
||||
}
|
||||
|
||||
void GameConfigWidget::EditUserConfig()
|
||||
{
|
||||
QFile file(m_gameini_local_path);
|
||||
|
||||
if (!file.exists())
|
||||
{
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.close();
|
||||
}
|
||||
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(m_gameini_local_path));
|
||||
}
|
||||
|
||||
void GameConfigWidget::ViewDefaultConfig()
|
||||
{
|
||||
for (const std::string& filename :
|
||||
ConfigLoaders::GetGameIniFilenames(m_game_id, m_game.GetRevision()))
|
||||
{
|
||||
QString path =
|
||||
QString::fromStdString(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename);
|
||||
|
||||
if (QFile(path).exists())
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
}
|
||||
}
|
71
Source/Core/DolphinQt/Config/GameConfigWidget.h
Normal file
71
Source/Core/DolphinQt/Config/GameConfigWidget.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QSpinBox;
|
||||
class QVBoxLayout;
|
||||
|
||||
class GameConfigWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GameConfigWidget(const UICommon::GameFile& game);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void EditUserConfig();
|
||||
void ViewDefaultConfig();
|
||||
|
||||
void LoadCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key);
|
||||
void SaveCheckBox(QCheckBox* checkbox, const std::string& section, const std::string& key);
|
||||
|
||||
QPushButton* m_refresh_config;
|
||||
QPushButton* m_edit_user_config;
|
||||
QPushButton* m_view_default_config;
|
||||
|
||||
// Core
|
||||
QCheckBox* m_enable_dual_core;
|
||||
QCheckBox* m_enable_mmu;
|
||||
QCheckBox* m_enable_fprf;
|
||||
QCheckBox* m_sync_gpu;
|
||||
QCheckBox* m_enable_fast_disc;
|
||||
QCheckBox* m_use_dsp_hle;
|
||||
QComboBox* m_deterministic_dual_core;
|
||||
|
||||
// Stereoscopy
|
||||
QSlider* m_depth_slider;
|
||||
QSpinBox* m_convergence_spin;
|
||||
QCheckBox* m_use_monoscopic_shadows;
|
||||
|
||||
QString m_gameini_local_path;
|
||||
|
||||
IniFile m_gameini_local;
|
||||
IniFile m_gameini_default;
|
||||
|
||||
const UICommon::GameFile& m_game;
|
||||
std::string m_game_id;
|
||||
};
|
289
Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp
Executable file
289
Source/Core/DolphinQt/Config/GeckoCodeWidget.cpp
Executable file
@ -0,0 +1,289 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/GeckoCodeWidget.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/GeckoCodeConfig.h"
|
||||
|
||||
#include "DolphinQt/Config/CheatCodeEditor.h"
|
||||
#include "DolphinQt/Config/CheatWarningWidget.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
GeckoCodeWidget::GeckoCodeWidget(const UICommon::GameFile& game, bool restart_required)
|
||||
: m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision()),
|
||||
m_restart_required(restart_required)
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
IniFile game_ini_local;
|
||||
|
||||
// We don't use LoadLocalGameIni() here because user cheat codes that are installed via the UI
|
||||
// will always be stored in GS/${GAMEID}.ini
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision);
|
||||
m_gecko_codes = Gecko::LoadCodes(game_ini_default, game_ini_local);
|
||||
|
||||
UpdateList();
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::CreateWidgets()
|
||||
{
|
||||
m_warning = new CheatWarningWidget(m_game_id, m_restart_required, this);
|
||||
m_code_list = new QListWidget;
|
||||
m_name_label = new QLabel;
|
||||
m_creator_label = new QLabel;
|
||||
|
||||
m_code_list->setSortingEnabled(true);
|
||||
|
||||
QFont monospace(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
|
||||
|
||||
const auto line_height = QFontMetrics(font()).lineSpacing();
|
||||
|
||||
m_code_description = new QTextEdit;
|
||||
m_code_description->setFont(monospace);
|
||||
m_code_description->setReadOnly(true);
|
||||
m_code_description->setFixedHeight(line_height * 5);
|
||||
|
||||
m_code_view = new QTextEdit;
|
||||
m_code_view->setFont(monospace);
|
||||
m_code_view->setReadOnly(true);
|
||||
m_code_view->setFixedHeight(line_height * 10);
|
||||
|
||||
m_add_code = new QPushButton(tr("&Add New Code..."));
|
||||
m_edit_code = new QPushButton(tr("&Edit Code..."));
|
||||
m_remove_code = new QPushButton(tr("&Remove Code"));
|
||||
m_download_codes = new QPushButton(tr("Download Codes"));
|
||||
|
||||
m_download_codes->setToolTip(tr("Download Codes from the WiiRD Database"));
|
||||
|
||||
m_download_codes->setEnabled(!m_game_id.empty());
|
||||
m_edit_code->setEnabled(false);
|
||||
m_remove_code->setEnabled(false);
|
||||
|
||||
auto* layout = new QVBoxLayout;
|
||||
|
||||
layout->addWidget(m_warning);
|
||||
layout->addWidget(m_code_list);
|
||||
|
||||
auto* info_layout = new QFormLayout;
|
||||
|
||||
info_layout->addRow(tr("Name:"), m_name_label);
|
||||
info_layout->addRow(tr("Creator:"), m_creator_label);
|
||||
info_layout->addRow(tr("Description:"), static_cast<QWidget*>(nullptr));
|
||||
|
||||
info_layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
|
||||
for (QLabel* label : {m_name_label, m_creator_label})
|
||||
{
|
||||
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
label->setCursor(Qt::IBeamCursor);
|
||||
}
|
||||
|
||||
layout->addLayout(info_layout);
|
||||
layout->addWidget(m_code_description);
|
||||
layout->addWidget(m_code_view);
|
||||
|
||||
QHBoxLayout* btn_layout = new QHBoxLayout;
|
||||
|
||||
btn_layout->addWidget(m_add_code);
|
||||
btn_layout->addWidget(m_edit_code);
|
||||
btn_layout->addWidget(m_remove_code);
|
||||
btn_layout->addWidget(m_download_codes);
|
||||
|
||||
layout->addLayout(btn_layout);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_code_list, &QListWidget::itemSelectionChanged, this,
|
||||
&GeckoCodeWidget::OnSelectionChanged);
|
||||
connect(m_code_list, &QListWidget::itemChanged, this, &GeckoCodeWidget::OnItemChanged);
|
||||
|
||||
connect(m_add_code, &QPushButton::pressed, this, &GeckoCodeWidget::AddCode);
|
||||
connect(m_remove_code, &QPushButton::pressed, this, &GeckoCodeWidget::RemoveCode);
|
||||
connect(m_edit_code, &QPushButton::pressed, this, &GeckoCodeWidget::EditCode);
|
||||
connect(m_download_codes, &QPushButton::pressed, this, &GeckoCodeWidget::DownloadCodes);
|
||||
|
||||
connect(m_warning, &CheatWarningWidget::OpenCheatEnableSettings, this,
|
||||
&GeckoCodeWidget::OpenGeneralSettings);
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::OnSelectionChanged()
|
||||
{
|
||||
auto items = m_code_list->selectedItems();
|
||||
|
||||
m_edit_code->setEnabled(!items.empty());
|
||||
m_remove_code->setEnabled(!items.empty());
|
||||
|
||||
if (items.empty())
|
||||
return;
|
||||
|
||||
auto selected = items[0];
|
||||
|
||||
const int index = selected->data(Qt::UserRole).toInt();
|
||||
|
||||
const auto& code = m_gecko_codes[index];
|
||||
|
||||
m_name_label->setText(QString::fromStdString(code.name));
|
||||
m_creator_label->setText(QString::fromStdString(code.creator));
|
||||
|
||||
m_code_description->clear();
|
||||
|
||||
for (const auto& line : code.notes)
|
||||
m_code_description->append(QString::fromStdString(line));
|
||||
|
||||
m_code_view->clear();
|
||||
|
||||
for (const auto& c : code.codes)
|
||||
m_code_view->append(QStringLiteral("%1 %2")
|
||||
.arg(c.address, 8, 16, QLatin1Char('0'))
|
||||
.arg(c.data, 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::OnItemChanged(QListWidgetItem* item)
|
||||
{
|
||||
const int index = item->data(Qt::UserRole).toInt();
|
||||
m_gecko_codes[index].enabled = (item->checkState() == Qt::Checked);
|
||||
|
||||
if (!m_restart_required)
|
||||
Gecko::SetActiveCodes(m_gecko_codes);
|
||||
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::AddCode()
|
||||
{
|
||||
Gecko::GeckoCode code;
|
||||
code.enabled = true;
|
||||
|
||||
CheatCodeEditor ed(this);
|
||||
ed.SetGeckoCode(&code);
|
||||
|
||||
if (ed.exec())
|
||||
{
|
||||
m_gecko_codes.push_back(std::move(code));
|
||||
SaveCodes();
|
||||
UpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::EditCode()
|
||||
{
|
||||
const auto* item = m_code_list->currentItem();
|
||||
|
||||
if (item == nullptr)
|
||||
return;
|
||||
|
||||
const int index = item->data(Qt::UserRole).toInt();
|
||||
|
||||
CheatCodeEditor ed(this);
|
||||
|
||||
ed.SetGeckoCode(&m_gecko_codes[index]);
|
||||
|
||||
if (ed.exec())
|
||||
{
|
||||
SaveCodes();
|
||||
UpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::RemoveCode()
|
||||
{
|
||||
const auto* item = m_code_list->currentItem();
|
||||
|
||||
if (item == nullptr)
|
||||
return;
|
||||
|
||||
m_gecko_codes.erase(m_gecko_codes.begin() + item->data(Qt::UserRole).toInt());
|
||||
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::SaveCodes()
|
||||
{
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
Gecko::SaveCodes(game_ini_local, m_gecko_codes);
|
||||
|
||||
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::UpdateList()
|
||||
{
|
||||
m_code_list->clear();
|
||||
|
||||
for (size_t i = 0; i < m_gecko_codes.size(); i++)
|
||||
{
|
||||
const auto& code = m_gecko_codes[i];
|
||||
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(code.name)
|
||||
.replace(QStringLiteral("<"), QStringLiteral("<"))
|
||||
.replace(QStringLiteral(">"), QStringLiteral(">")));
|
||||
|
||||
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(code.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, static_cast<int>(i));
|
||||
|
||||
m_code_list->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void GeckoCodeWidget::DownloadCodes()
|
||||
{
|
||||
bool success;
|
||||
|
||||
std::vector<Gecko::GeckoCode> codes = Gecko::DownloadCodes(m_game_id, &success);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to download codes."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (codes.empty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("File contained no codes."));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t added_count = 0;
|
||||
|
||||
for (const auto& code : codes)
|
||||
{
|
||||
auto it = std::find(m_gecko_codes.begin(), m_gecko_codes.end(), code);
|
||||
|
||||
if (it == m_gecko_codes.end())
|
||||
{
|
||||
m_gecko_codes.push_back(code);
|
||||
added_count++;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateList();
|
||||
SaveCodes();
|
||||
|
||||
QMessageBox::information(this, tr("Download complete"),
|
||||
tr("Downloaded %1 codes. (added %2)")
|
||||
.arg(QString::number(codes.size()), QString::number(added_count)));
|
||||
}
|
65
Source/Core/DolphinQt/Config/GeckoCodeWidget.h
Normal file
65
Source/Core/DolphinQt/Config/GeckoCodeWidget.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/GeckoCode.h"
|
||||
|
||||
class CheatWarningWidget;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QTextEdit;
|
||||
class QPushButton;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class GeckoCodeWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GeckoCodeWidget(const UICommon::GameFile& game, bool restart_required = true);
|
||||
|
||||
signals:
|
||||
void OpenGeneralSettings();
|
||||
|
||||
private:
|
||||
void OnSelectionChanged();
|
||||
void OnItemChanged(QListWidgetItem* item);
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void UpdateList();
|
||||
void AddCode();
|
||||
void EditCode();
|
||||
void RemoveCode();
|
||||
void DownloadCodes();
|
||||
void SaveCodes();
|
||||
|
||||
const UICommon::GameFile& m_game;
|
||||
std::string m_game_id;
|
||||
u16 m_game_revision;
|
||||
|
||||
CheatWarningWidget* m_warning;
|
||||
QListWidget* m_code_list;
|
||||
QLabel* m_name_label;
|
||||
QLabel* m_creator_label;
|
||||
QTextEdit* m_code_description;
|
||||
QTextEdit* m_code_view;
|
||||
QPushButton* m_add_code;
|
||||
QPushButton* m_edit_code;
|
||||
QPushButton* m_remove_code;
|
||||
QPushButton* m_download_codes;
|
||||
std::vector<Gecko::GeckoCode> m_gecko_codes;
|
||||
bool m_restart_required;
|
||||
};
|
222
Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp
Normal file
222
Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/AdvancedWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Config/SYSCONFSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsChoice.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
AdvancedWidget::AdvancedWidget(GraphicsWindow* parent) : GraphicsWidget(parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
|
||||
connect(parent, &GraphicsWindow::BackendChanged, this, &AdvancedWidget::OnBackendChanged);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
|
||||
|
||||
OnBackendChanged();
|
||||
}
|
||||
|
||||
void AdvancedWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
// Debugging
|
||||
auto* debugging_box = new QGroupBox(tr("Debugging"));
|
||||
auto* debugging_layout = new QGridLayout();
|
||||
debugging_box->setLayout(debugging_layout);
|
||||
|
||||
m_enable_wireframe = new GraphicsBool(tr("Enable Wireframe"), Config::GFX_ENABLE_WIREFRAME);
|
||||
m_show_statistics = new GraphicsBool(tr("Show Statistics"), Config::GFX_OVERLAY_STATS);
|
||||
m_enable_format_overlay =
|
||||
new GraphicsBool(tr("Texture Format Overlay"), Config::GFX_TEXFMT_OVERLAY_ENABLE);
|
||||
m_enable_api_validation =
|
||||
new GraphicsBool(tr("Enable API Validation Layers"), Config::GFX_ENABLE_VALIDATION_LAYER);
|
||||
|
||||
debugging_layout->addWidget(m_enable_wireframe, 0, 0);
|
||||
debugging_layout->addWidget(m_show_statistics, 0, 1);
|
||||
debugging_layout->addWidget(m_enable_format_overlay, 1, 0);
|
||||
debugging_layout->addWidget(m_enable_api_validation, 1, 1);
|
||||
|
||||
// Utility
|
||||
auto* utility_box = new QGroupBox(tr("Utility"));
|
||||
auto* utility_layout = new QGridLayout();
|
||||
utility_box->setLayout(utility_layout);
|
||||
|
||||
m_dump_textures = new GraphicsBool(tr("Dump Textures"), Config::GFX_DUMP_TEXTURES);
|
||||
m_load_custom_textures = new GraphicsBool(tr("Load Custom Textures"), Config::GFX_HIRES_TEXTURES);
|
||||
m_prefetch_custom_textures =
|
||||
new GraphicsBool(tr("Prefetch Custom Textures"), Config::GFX_CACHE_HIRES_TEXTURES);
|
||||
m_use_fullres_framedumps = new GraphicsBool(tr("Internal Resolution Frame Dumps"),
|
||||
Config::GFX_INTERNAL_RESOLUTION_FRAME_DUMPS);
|
||||
m_dump_efb_target = new GraphicsBool(tr("Dump EFB Target"), Config::GFX_DUMP_EFB_TARGET);
|
||||
m_disable_vram_copies =
|
||||
new GraphicsBool(tr("Disable EFB VRAM Copies"), Config::GFX_HACK_DISABLE_COPY_TO_VRAM);
|
||||
m_enable_freelook = new GraphicsBool(tr("Free Look"), Config::GFX_FREE_LOOK);
|
||||
m_dump_use_ffv1 = new GraphicsBool(tr("Frame Dumps Use FFV1"), Config::GFX_USE_FFV1);
|
||||
|
||||
utility_layout->addWidget(m_dump_textures, 0, 0);
|
||||
utility_layout->addWidget(m_load_custom_textures, 0, 1);
|
||||
utility_layout->addWidget(m_prefetch_custom_textures, 1, 0);
|
||||
utility_layout->addWidget(m_use_fullres_framedumps, 1, 1);
|
||||
utility_layout->addWidget(m_dump_efb_target, 2, 0);
|
||||
utility_layout->addWidget(m_disable_vram_copies, 2, 1);
|
||||
utility_layout->addWidget(m_enable_freelook, 3, 0);
|
||||
#if defined(HAVE_FFMPEG)
|
||||
utility_layout->addWidget(m_dump_use_ffv1, 3, 1);
|
||||
#endif
|
||||
|
||||
// Misc.
|
||||
auto* misc_box = new QGroupBox(tr("Misc"));
|
||||
auto* misc_layout = new QGridLayout();
|
||||
misc_box->setLayout(misc_layout);
|
||||
|
||||
m_enable_cropping = new GraphicsBool(tr("Crop"), Config::GFX_CROP);
|
||||
m_enable_prog_scan = new QCheckBox(tr("Enable Progressive Scan"));
|
||||
|
||||
misc_layout->addWidget(m_enable_cropping, 0, 0);
|
||||
misc_layout->addWidget(m_enable_prog_scan, 0, 1);
|
||||
|
||||
#ifdef _WIN32
|
||||
m_borderless_fullscreen =
|
||||
new GraphicsBool(tr("Borderless Fullscreen"), Config::GFX_BORDERLESS_FULLSCREEN);
|
||||
|
||||
misc_layout->addWidget(m_borderless_fullscreen, 1, 0);
|
||||
#endif
|
||||
|
||||
main_layout->addWidget(debugging_box);
|
||||
main_layout->addWidget(utility_box);
|
||||
main_layout->addWidget(misc_box);
|
||||
main_layout->addStretch();
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void AdvancedWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_load_custom_textures, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
connect(m_enable_prog_scan, &QCheckBox::toggled, this, &AdvancedWidget::SaveSettings);
|
||||
}
|
||||
|
||||
void AdvancedWidget::LoadSettings()
|
||||
{
|
||||
m_prefetch_custom_textures->setEnabled(Config::Get(Config::GFX_HIRES_TEXTURES));
|
||||
m_enable_prog_scan->setChecked(Config::Get(Config::SYSCONF_PROGRESSIVE_SCAN));
|
||||
}
|
||||
|
||||
void AdvancedWidget::SaveSettings()
|
||||
{
|
||||
const auto hires_enabled = Config::Get(Config::GFX_HIRES_TEXTURES);
|
||||
m_prefetch_custom_textures->setEnabled(hires_enabled);
|
||||
|
||||
Config::SetBase(Config::SYSCONF_PROGRESSIVE_SCAN, m_enable_prog_scan->isChecked());
|
||||
}
|
||||
|
||||
void AdvancedWidget::OnBackendChanged()
|
||||
{
|
||||
}
|
||||
|
||||
void AdvancedWidget::OnEmulationStateChanged(bool running)
|
||||
{
|
||||
m_enable_prog_scan->setEnabled(!running);
|
||||
}
|
||||
|
||||
void AdvancedWidget::AddDescriptions()
|
||||
{
|
||||
static const char TR_WIREFRAME_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Render the scene as a wireframe.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_SHOW_STATS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Show various rendering statistics.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_TEXTURE_FORMAT_DECRIPTION[] =
|
||||
QT_TR_NOOP("Modify textures to show the format they're encoded in. Needs an emulation reset "
|
||||
"in most cases.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_VALIDATION_LAYER_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Enables validation of API calls made by the video backend, which may assist in "
|
||||
"debugging graphical issues.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_DUMP_TEXTURE_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump decoded game textures to User/Dump/Textures/<game_id>/.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
static const char TR_LOAD_CUSTOM_TEXTURE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Load custom textures from User/Load/Textures/<game_id>/.\n\nIf unsure, leave this "
|
||||
"unchecked.");
|
||||
static const char TR_CACHE_CUSTOM_TEXTURE_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Cache custom textures to system RAM on startup.\nThis can require exponentially "
|
||||
"more RAM but fixes possible stuttering.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_DUMP_EFB_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump the contents of EFB copies to User/Dump/Textures/.\n\nIf unsure, leave this "
|
||||
"unchecked.");
|
||||
static const char TR_DISABLE_VRAM_COPIES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Disables the VRAM copy of the EFB, forcing a round-trip to RAM. Inhibits all "
|
||||
"upscaling.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Create frame dumps and screenshots at the internal resolution of the renderer, rather than "
|
||||
"the size of the window it is displayed within. If the aspect ratio is widescreen, the "
|
||||
"output "
|
||||
"image will be scaled horizontally to preserve the vertical resolution.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
#if defined(HAVE_FFMPEG)
|
||||
static const char TR_USE_FFV1_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Encode frame dumps using the FFV1 codec.\n\nIf unsure, leave this unchecked.");
|
||||
#endif
|
||||
static const char TR_FREE_LOOK_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"This feature allows you to change the game's camera.\nMove the mouse while holding the "
|
||||
"right "
|
||||
"mouse button to pan and while holding the middle button to move.\nHold SHIFT and press "
|
||||
"one of "
|
||||
"the WASD keys to move the camera by a certain step distance (SHIFT+2 to move faster and "
|
||||
"SHIFT+1 to move slower). Press SHIFT+R to reset the camera and SHIFT+F to reset the "
|
||||
"speed.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_CROPPING_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Crop the picture from its native aspect ratio to 4:3 or "
|
||||
"16:9.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_PROGRESSIVE_SCAN_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Enables progressive scan if supported by the emulated software.\nMost games don't "
|
||||
"care about this.\n\nIf unsure, leave this unchecked.");
|
||||
#ifdef _WIN32
|
||||
static const char TR_BORDERLESS_FULLSCREEN_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Implement fullscreen mode with a borderless window spanning the whole screen instead of "
|
||||
"using "
|
||||
"exclusive mode.\nAllows for faster transitions between fullscreen and windowed mode, but "
|
||||
"slightly increases input latency, makes movement less smooth and slightly decreases "
|
||||
"performance.\nExclusive mode is required for Nvidia 3D Vision to work in the Direct3D "
|
||||
"backend.\n\nIf unsure, leave this unchecked.");
|
||||
#endif
|
||||
|
||||
AddDescription(m_enable_wireframe, TR_WIREFRAME_DESCRIPTION);
|
||||
AddDescription(m_show_statistics, TR_SHOW_STATS_DESCRIPTION);
|
||||
AddDescription(m_enable_format_overlay, TR_TEXTURE_FORMAT_DECRIPTION);
|
||||
AddDescription(m_enable_api_validation, TR_VALIDATION_LAYER_DESCRIPTION);
|
||||
AddDescription(m_dump_textures, TR_DUMP_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_load_custom_textures, TR_LOAD_CUSTOM_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_prefetch_custom_textures, TR_CACHE_CUSTOM_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_dump_efb_target, TR_DUMP_EFB_DESCRIPTION);
|
||||
AddDescription(m_disable_vram_copies, TR_DISABLE_VRAM_COPIES_DESCRIPTION);
|
||||
AddDescription(m_use_fullres_framedumps, TR_INTERNAL_RESOLUTION_FRAME_DUMPING_DESCRIPTION);
|
||||
#ifdef HAVE_FFMPEG
|
||||
AddDescription(m_dump_use_ffv1, TR_USE_FFV1_DESCRIPTION);
|
||||
#endif
|
||||
AddDescription(m_enable_cropping, TR_CROPPING_DESCRIPTION);
|
||||
AddDescription(m_enable_prog_scan, TR_PROGRESSIVE_SCAN_DESCRIPTION);
|
||||
AddDescription(m_enable_freelook, TR_FREE_LOOK_DESCRIPTION);
|
||||
#ifdef _WIN32
|
||||
AddDescription(m_borderless_fullscreen, TR_BORDERLESS_FULLSCREEN_DESCRIPTION);
|
||||
#endif
|
||||
}
|
48
Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h
Normal file
48
Source/Core/DolphinQt/Config/Graphics/AdvancedWidget.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
|
||||
class AdvancedWidget final : public GraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AdvancedWidget(GraphicsWindow* parent);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
void OnBackendChanged();
|
||||
void OnEmulationStateChanged(bool running);
|
||||
|
||||
// Debugging
|
||||
QCheckBox* m_enable_wireframe;
|
||||
QCheckBox* m_show_statistics;
|
||||
QCheckBox* m_enable_format_overlay;
|
||||
QCheckBox* m_enable_api_validation;
|
||||
|
||||
// Utility
|
||||
QCheckBox* m_dump_textures;
|
||||
QCheckBox* m_prefetch_custom_textures;
|
||||
QCheckBox* m_dump_efb_target;
|
||||
QCheckBox* m_disable_vram_copies;
|
||||
QCheckBox* m_dump_use_ffv1;
|
||||
QCheckBox* m_load_custom_textures;
|
||||
QCheckBox* m_use_fullres_framedumps;
|
||||
QCheckBox* m_enable_freelook;
|
||||
|
||||
// Misc
|
||||
QCheckBox* m_enable_cropping;
|
||||
QCheckBox* m_enable_prog_scan;
|
||||
QCheckBox* m_borderless_fullscreen;
|
||||
};
|
381
Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp
Normal file
381
Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.cpp
Normal file
@ -0,0 +1,381 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/EnhancementsWidget.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsChoice.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsSlider.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/VideoUtils.h"
|
||||
|
||||
#include "VideoCommon/PostProcessing.h"
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
EnhancementsWidget::EnhancementsWidget(GraphicsWindow* parent)
|
||||
: GraphicsWidget(parent), m_block_save(false)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
connect(parent, &GraphicsWindow::BackendChanged,
|
||||
[this](const QString& backend) { LoadSettings(); });
|
||||
}
|
||||
|
||||
void EnhancementsWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
// Enhancements
|
||||
auto* enhancements_box = new QGroupBox(tr("Enhancements"));
|
||||
auto* enhancements_layout = new QGridLayout();
|
||||
enhancements_box->setLayout(enhancements_layout);
|
||||
|
||||
m_ir_combo = new GraphicsChoice({tr("Auto (Multiple of 640x528)"), tr("Native (640x528)"),
|
||||
tr("2x Native (1280x1056) for 720p"),
|
||||
tr("3x Native (1920x1584) for 1080p"),
|
||||
tr("4x Native (2560x2112) for 1440p"),
|
||||
tr("5x Native (3200x2640)"), tr("6x Native (3840x3168) for 4K"),
|
||||
tr("7x Native (4480x3696)"), tr("8x Native (5120x4224) for 5K")},
|
||||
Config::GFX_EFB_SCALE);
|
||||
|
||||
if (g_Config.iEFBScale > 8)
|
||||
{
|
||||
m_ir_combo->addItem(tr("Custom"));
|
||||
m_ir_combo->setCurrentIndex(m_ir_combo->count() - 1);
|
||||
}
|
||||
|
||||
m_ir_combo->setMaxVisibleItems(m_ir_combo->count());
|
||||
|
||||
m_aa_combo = new QComboBox();
|
||||
m_af_combo = new GraphicsChoice({tr("1x"), tr("2x"), tr("4x"), tr("8x"), tr("16x")},
|
||||
Config::GFX_ENHANCE_MAX_ANISOTROPY);
|
||||
|
||||
m_pp_effect = new QComboBox();
|
||||
m_configure_pp_effect = new QPushButton(tr("Configure"));
|
||||
m_scaled_efb_copy = new GraphicsBool(tr("Scaled EFB Copy"), Config::GFX_HACK_COPY_EFB_SCALED);
|
||||
m_per_pixel_lighting =
|
||||
new GraphicsBool(tr("Per-Pixel Lighting"), Config::GFX_ENABLE_PIXEL_LIGHTING);
|
||||
m_force_texture_filtering =
|
||||
new GraphicsBool(tr("Force Texture Filtering"), Config::GFX_ENHANCE_FORCE_FILTERING);
|
||||
m_widescreen_hack = new GraphicsBool(tr("Widescreen Hack"), Config::GFX_WIDESCREEN_HACK);
|
||||
m_disable_fog = new GraphicsBool(tr("Disable Fog"), Config::GFX_DISABLE_FOG);
|
||||
m_force_24bit_color =
|
||||
new GraphicsBool(tr("Force 24-Bit Color"), Config::GFX_ENHANCE_FORCE_TRUE_COLOR);
|
||||
m_disable_copy_filter =
|
||||
new GraphicsBool(tr("Disable Copy Filter"), Config::GFX_ENHANCE_DISABLE_COPY_FILTER);
|
||||
m_arbitrary_mipmap_detection = new GraphicsBool(tr("Arbitrary Mipmap Detection"),
|
||||
Config::GFX_ENHANCE_ARBITRARY_MIPMAP_DETECTION);
|
||||
|
||||
enhancements_layout->addWidget(new QLabel(tr("Internal Resolution:")), 0, 0);
|
||||
enhancements_layout->addWidget(m_ir_combo, 0, 1, 1, -1);
|
||||
enhancements_layout->addWidget(new QLabel(tr("Anti-Aliasing:")), 1, 0);
|
||||
enhancements_layout->addWidget(m_aa_combo, 1, 1, 1, -1);
|
||||
enhancements_layout->addWidget(new QLabel(tr("Anisotropic Filtering:")), 2, 0);
|
||||
enhancements_layout->addWidget(m_af_combo, 2, 1, 1, -1);
|
||||
|
||||
enhancements_layout->addWidget(new QLabel(tr("Post-Processing Effect:")), 4, 0);
|
||||
enhancements_layout->addWidget(m_pp_effect, 4, 1);
|
||||
enhancements_layout->addWidget(m_configure_pp_effect, 4, 2);
|
||||
|
||||
enhancements_layout->addWidget(m_scaled_efb_copy, 5, 0);
|
||||
enhancements_layout->addWidget(m_per_pixel_lighting, 5, 1);
|
||||
enhancements_layout->addWidget(m_force_texture_filtering, 6, 0);
|
||||
enhancements_layout->addWidget(m_widescreen_hack, 6, 1);
|
||||
enhancements_layout->addWidget(m_disable_fog, 7, 0);
|
||||
enhancements_layout->addWidget(m_force_24bit_color, 7, 1);
|
||||
enhancements_layout->addWidget(m_disable_copy_filter, 8, 0);
|
||||
enhancements_layout->addWidget(m_arbitrary_mipmap_detection, 8, 1);
|
||||
|
||||
// Stereoscopy
|
||||
auto* stereoscopy_box = new QGroupBox(tr("Stereoscopy"));
|
||||
auto* stereoscopy_layout = new QGridLayout();
|
||||
stereoscopy_box->setLayout(stereoscopy_layout);
|
||||
|
||||
m_3d_mode = new GraphicsChoice(
|
||||
{tr("Off"), tr("Side-by-Side"), tr("Top-and-Bottom"), tr("Anaglyph"), tr("HDMI 3D")},
|
||||
Config::GFX_STEREO_MODE);
|
||||
m_3d_depth = new GraphicsSlider(0, 100, Config::GFX_STEREO_DEPTH);
|
||||
m_3d_convergence = new GraphicsSlider(0, 200, Config::GFX_STEREO_CONVERGENCE, 100);
|
||||
m_3d_swap_eyes = new GraphicsBool(tr("Swap Eyes"), Config::GFX_STEREO_SWAP_EYES);
|
||||
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Stereoscopic 3D Mode:")), 0, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_mode, 0, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Depth:")), 1, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_depth, 1, 1);
|
||||
stereoscopy_layout->addWidget(new QLabel(tr("Convergence:")), 2, 0);
|
||||
stereoscopy_layout->addWidget(m_3d_convergence, 2, 1);
|
||||
stereoscopy_layout->addWidget(m_3d_swap_eyes, 3, 0);
|
||||
|
||||
main_layout->addWidget(enhancements_box);
|
||||
main_layout->addWidget(stereoscopy_box);
|
||||
main_layout->addStretch();
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void EnhancementsWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_aa_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_pp_effect, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_3d_mode, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this] {
|
||||
m_block_save = true;
|
||||
LoadPPShaders();
|
||||
m_block_save = false;
|
||||
|
||||
SaveSettings();
|
||||
});
|
||||
connect(m_configure_pp_effect, &QPushButton::pressed, this,
|
||||
&EnhancementsWidget::ConfigurePostProcessingShader);
|
||||
}
|
||||
|
||||
void EnhancementsWidget::LoadPPShaders()
|
||||
{
|
||||
const bool anaglyph = g_Config.stereo_mode == StereoMode::Anaglyph;
|
||||
std::vector<std::string> shaders =
|
||||
g_Config.stereo_mode == StereoMode::Anaglyph ?
|
||||
PostProcessingShaderImplementation::GetAnaglyphShaderList(
|
||||
g_Config.backend_info.api_type) :
|
||||
PostProcessingShaderImplementation::GetShaderList(g_Config.backend_info.api_type);
|
||||
|
||||
m_pp_effect->clear();
|
||||
|
||||
if (!anaglyph)
|
||||
m_pp_effect->addItem(tr("(off)"));
|
||||
|
||||
auto selected_shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (const auto& shader : shaders)
|
||||
{
|
||||
m_pp_effect->addItem(QString::fromStdString(shader));
|
||||
if (selected_shader == shader)
|
||||
{
|
||||
m_pp_effect->setCurrentIndex(m_pp_effect->count() - 1);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anaglyph && !found)
|
||||
m_pp_effect->setCurrentIndex(m_pp_effect->findText(QStringLiteral("dubois")));
|
||||
|
||||
const bool supports_postprocessing = g_Config.backend_info.bSupportsPostProcessing;
|
||||
m_pp_effect->setEnabled(supports_postprocessing);
|
||||
|
||||
m_pp_effect->setToolTip(supports_postprocessing ?
|
||||
QStringLiteral("") :
|
||||
tr("%1 doesn't support this feature.")
|
||||
.arg(tr(g_video_backend->GetDisplayName().c_str())));
|
||||
|
||||
PostProcessingShaderConfiguration pp_shader;
|
||||
if (selected_shader != "(off)" && supports_postprocessing)
|
||||
{
|
||||
pp_shader.LoadShader(selected_shader);
|
||||
m_configure_pp_effect->setEnabled(pp_shader.HasOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_configure_pp_effect->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void EnhancementsWidget::LoadSettings()
|
||||
{
|
||||
m_block_save = true;
|
||||
// Anti-Aliasing
|
||||
|
||||
int aa_selection = Config::Get(Config::GFX_MSAA);
|
||||
bool ssaa = Config::Get(Config::GFX_SSAA);
|
||||
|
||||
m_aa_combo->clear();
|
||||
for (const auto& option : VideoUtils::GetAvailableAntialiasingModes(m_msaa_modes))
|
||||
m_aa_combo->addItem(option == "None" ? tr("None") : QString::fromStdString(option));
|
||||
|
||||
m_aa_combo->setCurrentText(
|
||||
QString::fromStdString(std::to_string(aa_selection) + "x " + (ssaa ? "SSAA" : "MSAA")));
|
||||
m_aa_combo->setEnabled(m_aa_combo->count() > 1);
|
||||
|
||||
// Post Processing Shader
|
||||
LoadPPShaders();
|
||||
|
||||
// Stereoscopy
|
||||
bool supports_stereoscopy = g_Config.backend_info.bSupportsGeometryShaders;
|
||||
bool supports_3dvision = g_Config.backend_info.bSupports3DVision;
|
||||
|
||||
bool has_3dvision = m_3d_mode->count() == 6;
|
||||
|
||||
if (has_3dvision && !supports_3dvision)
|
||||
m_3d_mode->removeItem(5);
|
||||
|
||||
if (!has_3dvision && supports_3dvision)
|
||||
m_3d_mode->addItem(tr("NVIDIA 3D Vision"));
|
||||
|
||||
m_3d_mode->setEnabled(supports_stereoscopy);
|
||||
m_3d_convergence->setEnabled(supports_stereoscopy);
|
||||
m_3d_depth->setEnabled(supports_stereoscopy);
|
||||
m_3d_swap_eyes->setEnabled(supports_stereoscopy);
|
||||
m_block_save = false;
|
||||
}
|
||||
|
||||
void EnhancementsWidget::SaveSettings()
|
||||
{
|
||||
if (m_block_save)
|
||||
return;
|
||||
|
||||
bool is_ssaa = m_aa_combo->currentText().endsWith(QStringLiteral("SSAA"));
|
||||
|
||||
int aa_value = m_aa_combo->currentIndex();
|
||||
|
||||
if (aa_value == 0)
|
||||
{
|
||||
aa_value = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aa_value > m_msaa_modes)
|
||||
aa_value -= m_msaa_modes;
|
||||
aa_value = std::pow(2, aa_value);
|
||||
}
|
||||
Config::SetBaseOrCurrent(Config::GFX_MSAA, static_cast<unsigned int>(aa_value));
|
||||
|
||||
Config::SetBaseOrCurrent(Config::GFX_SSAA, is_ssaa);
|
||||
|
||||
Config::SetBaseOrCurrent(
|
||||
Config::GFX_ENHANCE_POST_SHADER,
|
||||
m_pp_effect->currentIndex() == 0 ? "(off)" : m_pp_effect->currentText().toStdString());
|
||||
|
||||
PostProcessingShaderConfiguration pp_shader;
|
||||
if (Config::Get(Config::GFX_ENHANCE_POST_SHADER) != "(off)")
|
||||
{
|
||||
pp_shader.LoadShader(Config::Get(Config::GFX_ENHANCE_POST_SHADER));
|
||||
m_configure_pp_effect->setEnabled(pp_shader.HasOptions());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_configure_pp_effect->setEnabled(false);
|
||||
}
|
||||
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
void EnhancementsWidget::AddDescriptions()
|
||||
{
|
||||
static const char TR_INTERNAL_RESOLUTION_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Specifies the resolution used to render at. A high resolution greatly improves "
|
||||
"visual quality, but also greatly increases GPU load and can cause issues in "
|
||||
"certain games. Generally speaking, the lower the internal resolution is, the "
|
||||
"better your performance will be.\n\nIf unsure, select Native.");
|
||||
|
||||
static const char TR_ANTIALIAS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Reduces the amount of aliasing caused by rasterizing 3D graphics. This smooths "
|
||||
"out jagged edges on objects.\nIncreases GPU load and sometimes causes graphical "
|
||||
"issues. SSAA is significantly more demanding than MSAA, but provides top quality "
|
||||
"geometry anti-aliasing and also applies anti-aliasing to lighting, shader "
|
||||
"effects, and textures.\n\nIf unsure, select None.");
|
||||
|
||||
static const char TR_ANISOTROPIC_FILTERING_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Enable anisotropic filtering.\nEnhances visual quality of textures that are at oblique "
|
||||
"viewing angles.\nMight cause issues in a small number of games.\n\nIf unsure, select 1x.");
|
||||
|
||||
static const char TR_POSTPROCESSING_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Apply a post-processing effect after finishing a frame.\n\nIf unsure, select (off).");
|
||||
|
||||
static const char TR_SCALED_EFB_COPY_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Greatly increases quality of textures generated using render-to-texture "
|
||||
"effects.\nRaising the "
|
||||
"internal resolution will improve the effect of this setting.\nSlightly increases GPU "
|
||||
"load and "
|
||||
"causes relatively few graphical issues.\n\nIf unsure, leave this checked.");
|
||||
static const char TR_PER_PIXEL_LIGHTING_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Calculates lighting of 3D objects per-pixel rather than per-vertex, smoothing out the "
|
||||
"appearance of lit polygons and making individual triangles less noticeable.\nRarely causes "
|
||||
"slowdowns or graphical issues.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_WIDESCREEN_HACK_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Forces the game to output graphics for any aspect ratio.\nUse with \"Aspect Ratio\" set to "
|
||||
"\"Force 16:9\" to force 4:3-only games to run at 16:9.\nRarely produces good results and "
|
||||
"often partially breaks graphics and game UIs.\nUnnecessary (and detrimental) if using any "
|
||||
"AR/Gecko-code widescreen patches.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_REMOVE_FOG_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Makes distant objects more visible by removing fog, thus increasing the overall "
|
||||
"detail.\nDisabling fog will break some games which rely on proper fog "
|
||||
"emulation.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_3D_MODE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Selects the stereoscopic 3D mode. Stereoscopy allows you to get a better feeling "
|
||||
"of depth if you have the necessary hardware.\nSide-by-Side and Top-and-Bottom are "
|
||||
"used by most 3D TVs.\nAnaglyph is used for Red-Cyan colored glasses.\nHDMI 3D is "
|
||||
"used when your monitor supports 3D display resolutions.\nHeavily decreases "
|
||||
"emulation speed and sometimes causes issues.\n\nIf unsure, select Off.");
|
||||
static const char TR_3D_DEPTH_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Controls the separation distance between the virtual cameras.\nA higher value "
|
||||
"creates a stronger feeling of depth while a lower value is more comfortable.");
|
||||
static const char TR_3D_CONVERGENCE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Controls the distance of the convergence plane. This is the distance at which "
|
||||
"virtual objects will appear to be in front of the screen.\nA higher value creates "
|
||||
"stronger out-of-screen effects while a lower value is more comfortable.");
|
||||
static const char TR_3D_SWAP_EYES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Swaps the left and right eye. Mostly useful if you want to view side-by-side "
|
||||
"cross-eyed.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_FORCE_24BIT_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Forces the game to render the RGB color channels in 24-bit, thereby increasing "
|
||||
"quality by reducing color banding.\nIt has no impact on performance and causes "
|
||||
"few graphical issues.\n\nIf unsure, leave this checked.");
|
||||
static const char TR_FORCE_TEXTURE_FILTERING_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Filter all textures, including any that the game explicitly set as "
|
||||
"unfiltered.\nMay improve quality of certain textures in some games, but will "
|
||||
"cause issues in others.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_DISABLE_COPY_FILTER_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Disables the blending of adjacent rows when copying the EFB. This is known in "
|
||||
"some games as \"deflickering\" or \"smoothing\". Disabling the filter has no "
|
||||
"effect on performance, but may result in a sharper image, and causes few "
|
||||
"graphical issues.\n\nIf unsure, leave this checked.");
|
||||
static const char TR_ARBITRARY_MIPMAP_DETECTION_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Enables detection of arbitrary mipmaps, which some games use for special distance-based "
|
||||
"effects. May have false positives that result in blurry textures at increased internal "
|
||||
"resolution, such as in games that use very low resolution mipmaps.\nDisabling this can also "
|
||||
"reduce stutter in games that frequently load new textures.\nThis feature is not compatible "
|
||||
"with GPU Texture Decoding.\n\nIf unsure, leave this checked.");
|
||||
|
||||
AddDescription(m_ir_combo, TR_INTERNAL_RESOLUTION_DESCRIPTION);
|
||||
AddDescription(m_aa_combo, TR_ANTIALIAS_DESCRIPTION);
|
||||
AddDescription(m_af_combo, TR_ANISOTROPIC_FILTERING_DESCRIPTION);
|
||||
AddDescription(m_pp_effect, TR_POSTPROCESSING_DESCRIPTION);
|
||||
AddDescription(m_scaled_efb_copy, TR_SCALED_EFB_COPY_DESCRIPTION);
|
||||
AddDescription(m_per_pixel_lighting, TR_PER_PIXEL_LIGHTING_DESCRIPTION);
|
||||
AddDescription(m_widescreen_hack, TR_WIDESCREEN_HACK_DESCRIPTION);
|
||||
AddDescription(m_disable_fog, TR_REMOVE_FOG_DESCRIPTION);
|
||||
AddDescription(m_force_24bit_color, TR_FORCE_24BIT_DESCRIPTION);
|
||||
AddDescription(m_force_texture_filtering, TR_FORCE_TEXTURE_FILTERING_DESCRIPTION);
|
||||
AddDescription(m_disable_copy_filter, TR_DISABLE_COPY_FILTER_DESCRIPTION);
|
||||
AddDescription(m_arbitrary_mipmap_detection, TR_ARBITRARY_MIPMAP_DETECTION_DESCRIPTION);
|
||||
AddDescription(m_3d_mode, TR_3D_MODE_DESCRIPTION);
|
||||
AddDescription(m_3d_depth, TR_3D_DEPTH_DESCRIPTION);
|
||||
AddDescription(m_3d_convergence, TR_3D_CONVERGENCE_DESCRIPTION);
|
||||
AddDescription(m_3d_swap_eyes, TR_3D_SWAP_EYES_DESCRIPTION);
|
||||
}
|
||||
|
||||
void EnhancementsWidget::ConfigurePostProcessingShader()
|
||||
{
|
||||
const std::string shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
|
||||
PostProcessingConfigWindow(this, shader).exec();
|
||||
}
|
54
Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h
Normal file
54
Source/Core/DolphinQt/Config/Graphics/EnhancementsWidget.h
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
|
||||
class EnhancementsWidget final : public GraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EnhancementsWidget(GraphicsWindow* parent);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
void ConfigurePostProcessingShader();
|
||||
void LoadPPShaders();
|
||||
|
||||
// Enhancements
|
||||
QComboBox* m_ir_combo;
|
||||
QComboBox* m_aa_combo;
|
||||
QComboBox* m_af_combo;
|
||||
QComboBox* m_pp_effect;
|
||||
QPushButton* m_configure_pp_effect;
|
||||
QCheckBox* m_scaled_efb_copy;
|
||||
QCheckBox* m_per_pixel_lighting;
|
||||
QCheckBox* m_force_texture_filtering;
|
||||
QCheckBox* m_widescreen_hack;
|
||||
QCheckBox* m_disable_fog;
|
||||
QCheckBox* m_force_24bit_color;
|
||||
QCheckBox* m_disable_copy_filter;
|
||||
QCheckBox* m_arbitrary_mipmap_detection;
|
||||
|
||||
// Stereoscopy
|
||||
QComboBox* m_3d_mode;
|
||||
QSlider* m_3d_depth;
|
||||
QSlider* m_3d_convergence;
|
||||
QCheckBox* m_3d_swap_eyes;
|
||||
|
||||
int m_msaa_modes;
|
||||
bool m_block_save;
|
||||
};
|
322
Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp
Normal file
322
Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
// Copyright 2017 Dolphin Emulator Project5~5~5~
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GeneralWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QRadioButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsChoice.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsRadio.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/VideoUtils.h"
|
||||
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
GeneralWidget::GeneralWidget(X11Utils::XRRConfiguration* xrr_config, GraphicsWindow* parent)
|
||||
: GraphicsWidget(parent), m_xrr_config(xrr_config)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
emit BackendChanged(QString::fromStdString(SConfig::GetInstance().m_strVideoBackend));
|
||||
|
||||
connect(parent, &GraphicsWindow::BackendChanged, this, &GeneralWidget::OnBackendChanged);
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[=](Core::State state) { OnEmulationStateChanged(state != Core::State::Uninitialized); });
|
||||
}
|
||||
|
||||
void GeneralWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
// Basic Section
|
||||
auto* m_video_box = new QGroupBox(tr("Basic"));
|
||||
m_video_layout = new QGridLayout();
|
||||
|
||||
m_backend_combo = new QComboBox();
|
||||
m_aspect_combo =
|
||||
new GraphicsChoice({tr("Auto"), tr("Force 16:9"), tr("Force 4:3"), tr("Stretch to Window")},
|
||||
Config::GFX_ASPECT_RATIO);
|
||||
m_adapter_combo = new QComboBox;
|
||||
m_enable_vsync = new GraphicsBool(tr("V-Sync"), Config::GFX_VSYNC);
|
||||
m_enable_fullscreen = new QCheckBox(tr("Use Fullscreen"));
|
||||
|
||||
m_video_box->setLayout(m_video_layout);
|
||||
|
||||
for (auto& backend : g_available_video_backends)
|
||||
m_backend_combo->addItem(tr(backend->GetDisplayName().c_str()),
|
||||
QVariant(QString::fromStdString(backend->GetName())));
|
||||
|
||||
m_video_layout->addWidget(new QLabel(tr("Backend:")), 0, 0);
|
||||
m_video_layout->addWidget(m_backend_combo, 0, 1);
|
||||
|
||||
m_video_layout->addWidget(new QLabel(tr("Adapter:")), 1, 0);
|
||||
m_video_layout->addWidget(m_adapter_combo, 1, 1);
|
||||
|
||||
m_video_layout->addWidget(new QLabel(tr("Aspect Ratio:")), 3, 0);
|
||||
m_video_layout->addWidget(m_aspect_combo, 3, 1);
|
||||
|
||||
m_video_layout->addWidget(m_enable_vsync, 4, 0);
|
||||
m_video_layout->addWidget(m_enable_fullscreen, 4, 1);
|
||||
|
||||
// Other
|
||||
auto* m_options_box = new QGroupBox(tr("Other"));
|
||||
auto* m_options_layout = new QGridLayout();
|
||||
|
||||
m_show_fps = new GraphicsBool(tr("Show FPS"), Config::GFX_SHOW_FPS);
|
||||
m_show_ping = new GraphicsBool(tr("Show NetPlay Ping"), Config::GFX_SHOW_NETPLAY_PING);
|
||||
m_log_render_time =
|
||||
new GraphicsBool(tr("Log Render Time to File"), Config::GFX_LOG_RENDER_TIME_TO_FILE);
|
||||
m_autoadjust_window_size = new QCheckBox(tr("Auto-Adjust Window Size"));
|
||||
m_show_messages =
|
||||
new GraphicsBool(tr("Show NetPlay Messages"), Config::GFX_SHOW_NETPLAY_MESSAGES);
|
||||
m_render_main_window = new QCheckBox(tr("Render to Main Window"));
|
||||
|
||||
m_options_box->setLayout(m_options_layout);
|
||||
|
||||
m_options_layout->addWidget(m_show_fps, 0, 0);
|
||||
m_options_layout->addWidget(m_show_ping, 0, 1);
|
||||
|
||||
m_options_layout->addWidget(m_log_render_time, 1, 0);
|
||||
m_options_layout->addWidget(m_autoadjust_window_size, 1, 1);
|
||||
|
||||
m_options_layout->addWidget(m_show_messages, 2, 0);
|
||||
m_options_layout->addWidget(m_render_main_window, 2, 1);
|
||||
|
||||
// Other
|
||||
auto* shader_compilation_box = new QGroupBox(tr("Shader Compilation"));
|
||||
auto* shader_compilation_layout = new QGridLayout();
|
||||
|
||||
const std::array<const char*, 4> modes = {{
|
||||
"Synchronous",
|
||||
"Synchronous (Ubershaders)",
|
||||
"Asynchronous (Ubershaders)",
|
||||
"Asynchronous (Skip Drawing)",
|
||||
}};
|
||||
for (size_t i = 0; i < modes.size(); i++)
|
||||
{
|
||||
m_shader_compilation_mode[i] = new GraphicsRadioInt(
|
||||
tr(modes[i]), Config::GFX_SHADER_COMPILATION_MODE, static_cast<int>(i));
|
||||
shader_compilation_layout->addWidget(m_shader_compilation_mode[i], static_cast<int>(i / 2),
|
||||
static_cast<int>(i % 2));
|
||||
}
|
||||
m_wait_for_shaders = new GraphicsBool(tr("Compile Shaders Before Starting"),
|
||||
Config::GFX_WAIT_FOR_SHADERS_BEFORE_STARTING);
|
||||
shader_compilation_layout->addWidget(m_wait_for_shaders);
|
||||
shader_compilation_box->setLayout(shader_compilation_layout);
|
||||
|
||||
main_layout->addWidget(m_video_box);
|
||||
main_layout->addWidget(m_options_box);
|
||||
main_layout->addWidget(shader_compilation_box);
|
||||
main_layout->addStretch();
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void GeneralWidget::ConnectWidgets()
|
||||
{
|
||||
// Video Backend
|
||||
connect(m_backend_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &GeneralWidget::SaveSettings);
|
||||
connect(m_adapter_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, [](int index) {
|
||||
g_Config.iAdapter = index;
|
||||
Config::SetBaseOrCurrent(Config::GFX_ADAPTER, index);
|
||||
});
|
||||
|
||||
for (QCheckBox* checkbox : {m_enable_fullscreen, m_render_main_window, m_autoadjust_window_size})
|
||||
connect(checkbox, &QCheckBox::toggled, this, &GeneralWidget::SaveSettings);
|
||||
}
|
||||
|
||||
void GeneralWidget::LoadSettings()
|
||||
{
|
||||
// Video Backend
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(
|
||||
QVariant(QString::fromStdString(SConfig::GetInstance().m_strVideoBackend))));
|
||||
|
||||
// Enable Fullscreen
|
||||
m_enable_fullscreen->setChecked(SConfig::GetInstance().bFullscreen);
|
||||
|
||||
// Render to Main Window
|
||||
m_render_main_window->setChecked(SConfig::GetInstance().bRenderToMain);
|
||||
|
||||
// Autoadjust window size
|
||||
m_autoadjust_window_size->setChecked(SConfig::GetInstance().bRenderWindowAutoSize);
|
||||
}
|
||||
|
||||
void GeneralWidget::SaveSettings()
|
||||
{
|
||||
// Video Backend
|
||||
const auto current_backend = m_backend_combo->currentData().toString().toStdString();
|
||||
if (SConfig::GetInstance().m_strVideoBackend != current_backend)
|
||||
{
|
||||
if (current_backend == "Software Renderer")
|
||||
{
|
||||
QMessageBox confirm_sw(this);
|
||||
|
||||
confirm_sw.setIcon(QMessageBox::Warning);
|
||||
confirm_sw.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
confirm_sw.setWindowTitle(tr("Confirm backend change"));
|
||||
confirm_sw.setText(tr("Software rendering is an order of magnitude slower than using the "
|
||||
"other backends.\nIt's only useful for debugging purposes.\nDo you "
|
||||
"really want to enable software rendering? If unsure, select 'No'."));
|
||||
|
||||
if (confirm_sw.exec() != QMessageBox::Yes)
|
||||
{
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(
|
||||
QVariant(QString::fromStdString(SConfig::GetInstance().m_strVideoBackend))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit BackendChanged(QString::fromStdString(current_backend));
|
||||
}
|
||||
|
||||
// Enable Fullscreen
|
||||
SConfig::GetInstance().bFullscreen = m_enable_fullscreen->isChecked();
|
||||
// Autoadjust window size
|
||||
SConfig::GetInstance().bRenderWindowAutoSize = m_autoadjust_window_size->isChecked();
|
||||
// Render To Main
|
||||
SConfig::GetInstance().bRenderToMain = m_render_main_window->isChecked();
|
||||
}
|
||||
|
||||
void GeneralWidget::OnEmulationStateChanged(bool running)
|
||||
{
|
||||
m_backend_combo->setEnabled(!running);
|
||||
m_render_main_window->setEnabled(!running);
|
||||
|
||||
m_adapter_combo->setEnabled(!running);
|
||||
}
|
||||
|
||||
void GeneralWidget::AddDescriptions()
|
||||
{
|
||||
// We need QObject::tr
|
||||
#if defined(_WIN32)
|
||||
static const char TR_BACKEND_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Selects which graphics API to use internally.\n\nThe software renderer is extremely "
|
||||
"slow and only useful for debugging, so you will want to use either OpenGL, Direct3D, "
|
||||
"or Vulkan. Different games and different GPUs will behave differently on each "
|
||||
"backend, so for the best emulation experience it is recommended to try each and "
|
||||
"select the backend that is least problematic.\n\nIf unsure, select OpenGL.");
|
||||
#else
|
||||
static const char TR_BACKEND_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Selects what graphics API to use internally.\nThe software renderer is extremely "
|
||||
"slow and only useful for debugging, so unless you have a reason to use it you'll "
|
||||
"want to select OpenGL here.\n\nIf unsure, select OpenGL.");
|
||||
#endif
|
||||
static const char TR_ADAPTER_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Selects a hardware adapter to use.\n\nIf unsure, use the first one.");
|
||||
static const char TR_FULLSCREEN_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Enable this if you want the whole screen to be used for rendering.\nIf this is disabled, a "
|
||||
"render window will be created instead.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_AUTOSIZE_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Automatically adjusts the window size to your internal resolution.\n\nIf unsure, "
|
||||
"leave this unchecked.");
|
||||
|
||||
static const char TR_RENDER_TO_MAINWINDOW_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Enable this if you want to use the main Dolphin window for rendering rather than "
|
||||
"a separate render window.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_ASPECT_RATIO_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Select what aspect ratio to use when rendering:\nAuto: Use the native aspect "
|
||||
"ratio\nForce 16:9: Mimic an analog TV with a widescreen aspect ratio.\nForce 4:3: "
|
||||
"Mimic a standard 4:3 analog TV.\nStretch to Window: Stretch the picture to the "
|
||||
"window size.\n\nIf unsure, select Auto.");
|
||||
static const char TR_VSYNC_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Wait for vertical blanks in order to reduce tearing.\nDecreases performance if "
|
||||
"emulation speed is below 100%.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_SHOW_FPS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Show the number of frames rendered per second as a measure of "
|
||||
"emulation speed.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_SHOW_NETPLAY_PING_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Show the players' maximum Ping while playing on "
|
||||
"NetPlay.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_LOG_RENDERTIME_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Log the render time of every frame to User/Logs/render_time.txt. Use this "
|
||||
"feature when you want to measure the performance of Dolphin.\n\nIf "
|
||||
"unsure, leave this unchecked.");
|
||||
static const char TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("When playing on NetPlay, show chat messages, buffer changes and "
|
||||
"desync alerts.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_SHADER_COMPILE_SYNC_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Ubershaders are never used. Stuttering will occur during shader "
|
||||
"compilation, but GPU demands are low. Recommended for low-end hardware.\n\nIf "
|
||||
"unsure, select this mode.");
|
||||
static const char TR_SHADER_COMPILE_UBER_ONLY_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Ubershaders will always be used. Provides a near stutter-free experience at the cost of "
|
||||
"high GPU performance requirements. Only recommended for high-end systems.");
|
||||
static const char TR_SHADER_COMPILE_ASYNC_UBER_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Ubershaders will be used to prevent stuttering during shader compilation, but "
|
||||
"specialized shaders will be used when they will not cause stuttering. In the "
|
||||
"best case it eliminates shader compilation stuttering while having minimal "
|
||||
"performance impact, but results depend on video driver behavior.");
|
||||
static const char TR_SHADER_COMPILE_ASYNC_SKIP_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Prevents shader compilation stuttering by not rendering waiting objects. Can work in "
|
||||
"scenarios where Ubershaders doesn't, at the cost of introducing visual glitches and broken "
|
||||
"effects. Not recommended, only use if the other options give poor results on your system.");
|
||||
static const char TR_SHADER_COMPILE_BEFORE_START_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Waits for all shaders to finish compiling before starting a game. Enabling this "
|
||||
"option may reduce stuttering or hitching for a short time after the game is "
|
||||
"started, at the cost of a longer delay before the game starts. For systems with "
|
||||
"two or fewer cores, it is recommended to enable this option, as a large shader "
|
||||
"queue may reduce frame rates. Otherwise, if unsure, leave this unchecked.");
|
||||
|
||||
AddDescription(m_backend_combo, TR_BACKEND_DESCRIPTION);
|
||||
AddDescription(m_adapter_combo, TR_ADAPTER_DESCRIPTION);
|
||||
AddDescription(m_aspect_combo, TR_ASPECT_RATIO_DESCRIPTION);
|
||||
AddDescription(m_enable_vsync, TR_VSYNC_DESCRIPTION);
|
||||
AddDescription(m_enable_fullscreen, TR_FULLSCREEN_DESCRIPTION);
|
||||
AddDescription(m_show_fps, TR_SHOW_FPS_DESCRIPTION);
|
||||
AddDescription(m_show_ping, TR_SHOW_NETPLAY_PING_DESCRIPTION);
|
||||
AddDescription(m_log_render_time, TR_LOG_RENDERTIME_DESCRIPTION);
|
||||
AddDescription(m_autoadjust_window_size, TR_AUTOSIZE_DESCRIPTION);
|
||||
AddDescription(m_show_messages, TR_SHOW_NETPLAY_MESSAGES_DESCRIPTION);
|
||||
AddDescription(m_render_main_window, TR_RENDER_TO_MAINWINDOW_DESCRIPTION);
|
||||
AddDescription(m_shader_compilation_mode[0], TR_SHADER_COMPILE_SYNC_DESCRIPTION);
|
||||
AddDescription(m_shader_compilation_mode[1], TR_SHADER_COMPILE_UBER_ONLY_DESCRIPTION);
|
||||
AddDescription(m_shader_compilation_mode[2], TR_SHADER_COMPILE_ASYNC_UBER_DESCRIPTION);
|
||||
AddDescription(m_shader_compilation_mode[3], TR_SHADER_COMPILE_ASYNC_SKIP_DESCRIPTION);
|
||||
AddDescription(m_wait_for_shaders, TR_SHADER_COMPILE_BEFORE_START_DESCRIPTION);
|
||||
}
|
||||
|
||||
void GeneralWidget::OnBackendChanged(const QString& backend_name)
|
||||
{
|
||||
m_backend_combo->setCurrentIndex(m_backend_combo->findData(QVariant(backend_name)));
|
||||
|
||||
const bool old = m_adapter_combo->blockSignals(true);
|
||||
|
||||
m_adapter_combo->clear();
|
||||
|
||||
const auto& adapters = g_Config.backend_info.Adapters;
|
||||
|
||||
for (const auto& adapter : adapters)
|
||||
m_adapter_combo->addItem(QString::fromStdString(adapter));
|
||||
|
||||
const bool supports_adapters = !adapters.empty();
|
||||
|
||||
m_adapter_combo->setCurrentIndex(g_Config.iAdapter);
|
||||
m_adapter_combo->setEnabled(supports_adapters);
|
||||
|
||||
m_adapter_combo->setToolTip(supports_adapters ?
|
||||
QStringLiteral("") :
|
||||
tr("%1 doesn't support this feature.")
|
||||
.arg(tr(g_video_backend->GetDisplayName().c_str())));
|
||||
|
||||
m_adapter_combo->blockSignals(old);
|
||||
}
|
59
Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h
Normal file
59
Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QRadioButton;
|
||||
class QGridLayout;
|
||||
|
||||
namespace X11Utils
|
||||
{
|
||||
class XRRConfiguration;
|
||||
}
|
||||
|
||||
class GeneralWidget final : public GraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GeneralWidget(X11Utils::XRRConfiguration* xrr_config, GraphicsWindow* parent);
|
||||
signals:
|
||||
void BackendChanged(const QString& backend);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
|
||||
void OnBackendChanged(const QString& backend_name);
|
||||
void OnEmulationStateChanged(bool running);
|
||||
|
||||
// Video
|
||||
QGridLayout* m_video_layout;
|
||||
QComboBox* m_backend_combo;
|
||||
QComboBox* m_adapter_combo;
|
||||
QComboBox* m_aspect_combo;
|
||||
QCheckBox* m_enable_vsync;
|
||||
QCheckBox* m_enable_fullscreen;
|
||||
|
||||
// Options
|
||||
QCheckBox* m_show_fps;
|
||||
QCheckBox* m_show_ping;
|
||||
QCheckBox* m_log_render_time;
|
||||
QCheckBox* m_autoadjust_window_size;
|
||||
QCheckBox* m_show_messages;
|
||||
QCheckBox* m_render_main_window;
|
||||
std::array<QRadioButton*, 4> m_shader_compilation_mode{};
|
||||
QCheckBox* m_wait_for_shaders;
|
||||
|
||||
X11Utils::XRRConfiguration* m_xrr_config;
|
||||
};
|
54
Source/Core/DolphinQt/Config/Graphics/GraphicsBool.cpp
Normal file
54
Source/Core/DolphinQt/Config/Graphics/GraphicsBool.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
GraphicsBool::GraphicsBool(const QString& label, const Config::ConfigInfo<bool>& setting,
|
||||
bool reverse)
|
||||
: QCheckBox(label), m_setting(setting), m_reverse(reverse)
|
||||
{
|
||||
connect(this, &QCheckBox::toggled, this, &GraphicsBool::Update);
|
||||
setChecked(Config::Get(m_setting) ^ reverse);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
bool old = blockSignals(true);
|
||||
setChecked(Config::Get(m_setting) ^ m_reverse);
|
||||
blockSignals(old);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsBool::Update()
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, static_cast<bool>(isChecked() ^ m_reverse));
|
||||
}
|
||||
|
||||
GraphicsBoolEx::GraphicsBoolEx(const QString& label, const Config::ConfigInfo<bool>& setting,
|
||||
bool reverse)
|
||||
: QRadioButton(label), m_setting(setting), m_reverse(reverse)
|
||||
{
|
||||
connect(this, &QCheckBox::toggled, this, &GraphicsBoolEx::Update);
|
||||
setChecked(Config::Get(m_setting) ^ reverse);
|
||||
|
||||
if (Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base)
|
||||
{
|
||||
QFont bf = font();
|
||||
bf.setBold(true);
|
||||
setFont(bf);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsBoolEx::Update()
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, static_cast<bool>(isChecked() ^ m_reverse));
|
||||
}
|
41
Source/Core/DolphinQt/Config/Graphics/GraphicsBool.h
Normal file
41
Source/Core/DolphinQt/Config/Graphics/GraphicsBool.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QRadioButton>
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
struct ConfigInfo;
|
||||
}
|
||||
|
||||
class GraphicsBool : public QCheckBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsBool(const QString& label, const Config::ConfigInfo<bool>& setting, bool reverse = false);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
const Config::ConfigInfo<bool>& m_setting;
|
||||
bool m_reverse;
|
||||
};
|
||||
|
||||
class GraphicsBoolEx : public QRadioButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsBoolEx(const QString& label, const Config::ConfigInfo<bool>& setting,
|
||||
bool reverse = false);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
const Config::ConfigInfo<bool>& m_setting;
|
||||
bool m_reverse;
|
||||
};
|
33
Source/Core/DolphinQt/Config/Graphics/GraphicsChoice.cpp
Normal file
33
Source/Core/DolphinQt/Config/Graphics/GraphicsChoice.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsChoice.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
GraphicsChoice::GraphicsChoice(const QStringList& options, const Config::ConfigInfo<int>& setting)
|
||||
: m_setting(setting)
|
||||
{
|
||||
addItems(options);
|
||||
connect(this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsChoice::Update);
|
||||
setCurrentIndex(Config::Get(m_setting));
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
bool old = blockSignals(true);
|
||||
setCurrentIndex(Config::Get(m_setting));
|
||||
blockSignals(old);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsChoice::Update(int choice)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, choice);
|
||||
}
|
21
Source/Core/DolphinQt/Config/Graphics/GraphicsChoice.h
Normal file
21
Source/Core/DolphinQt/Config/Graphics/GraphicsChoice.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
class GraphicsChoice : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsChoice(const QStringList& options, const Config::ConfigInfo<int>& setting);
|
||||
|
||||
private:
|
||||
void Update(int choice);
|
||||
|
||||
Config::ConfigInfo<int> m_setting;
|
||||
};
|
35
Source/Core/DolphinQt/Config/Graphics/GraphicsRadio.cpp
Normal file
35
Source/Core/DolphinQt/Config/Graphics/GraphicsRadio.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsRadio.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
GraphicsRadioInt::GraphicsRadioInt(const QString& label, const Config::ConfigInfo<int>& setting,
|
||||
int value)
|
||||
: QRadioButton(label), m_setting(setting), m_value(value)
|
||||
{
|
||||
setChecked(Config::Get(m_setting) == m_value);
|
||||
connect(this, &QRadioButton::toggled, this, &GraphicsRadioInt::Update);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
bool old = blockSignals(true);
|
||||
setChecked(Config::Get(m_setting) == m_value);
|
||||
blockSignals(old);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsRadioInt::Update()
|
||||
{
|
||||
if (!isChecked())
|
||||
return;
|
||||
|
||||
Config::SetBaseOrCurrent(m_setting, m_value);
|
||||
}
|
22
Source/Core/DolphinQt/Config/Graphics/GraphicsRadio.h
Normal file
22
Source/Core/DolphinQt/Config/Graphics/GraphicsRadio.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
class GraphicsRadioInt : public QRadioButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsRadioInt(const QString& label, const Config::ConfigInfo<int>& setting, int value);
|
||||
|
||||
private:
|
||||
void Update();
|
||||
|
||||
Config::ConfigInfo<int> m_setting;
|
||||
int m_value;
|
||||
};
|
37
Source/Core/DolphinQt/Config/Graphics/GraphicsSlider.cpp
Normal file
37
Source/Core/DolphinQt/Config/Graphics/GraphicsSlider.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsSlider.h"
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
GraphicsSlider::GraphicsSlider(int minimum, int maximum, const Config::ConfigInfo<int>& setting,
|
||||
int tick)
|
||||
: QSlider(Qt::Horizontal), m_setting(setting)
|
||||
{
|
||||
setMinimum(minimum);
|
||||
setMaximum(maximum);
|
||||
setTickInterval(tick);
|
||||
|
||||
setValue(Config::Get(setting));
|
||||
|
||||
connect(this, &GraphicsSlider::valueChanged, this, &GraphicsSlider::Update);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, [this] {
|
||||
QFont bf = font();
|
||||
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
|
||||
setFont(bf);
|
||||
|
||||
bool old = blockSignals(true);
|
||||
setValue(Config::Get(m_setting));
|
||||
blockSignals(old);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsSlider::Update(int value)
|
||||
{
|
||||
Config::SetBaseOrCurrent(m_setting, value);
|
||||
}
|
24
Source/Core/DolphinQt/Config/Graphics/GraphicsSlider.h
Normal file
24
Source/Core/DolphinQt/Config/Graphics/GraphicsSlider.h
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSlider>
|
||||
|
||||
namespace Config
|
||||
{
|
||||
template <typename T>
|
||||
struct ConfigInfo;
|
||||
}
|
||||
|
||||
class GraphicsSlider : public QSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GraphicsSlider(int minimum, int maximum, const Config::ConfigInfo<int>& setting, int tick = 0);
|
||||
void Update(int value);
|
||||
|
||||
private:
|
||||
const Config::ConfigInfo<int>& m_setting;
|
||||
};
|
20
Source/Core/DolphinQt/Config/Graphics/GraphicsWidget.cpp
Normal file
20
Source/Core/DolphinQt/Config/Graphics/GraphicsWidget.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QLabel>
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
|
||||
GraphicsWidget::GraphicsWidget(GraphicsWindow* parent)
|
||||
{
|
||||
parent->RegisterWidget(this);
|
||||
}
|
||||
|
||||
void GraphicsWidget::AddDescription(QWidget* widget, const char* description)
|
||||
{
|
||||
emit DescriptionAdded(widget, description);
|
||||
}
|
31
Source/Core/DolphinQt/Config/Graphics/GraphicsWidget.h
Normal file
31
Source/Core/DolphinQt/Config/Graphics/GraphicsWidget.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class GraphicsWindow;
|
||||
class QFormLayout;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
|
||||
class GraphicsWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GraphicsWidget(GraphicsWindow* parent);
|
||||
|
||||
signals:
|
||||
void DescriptionAdded(QWidget* widget, const char* description);
|
||||
|
||||
protected:
|
||||
void AddDescription(QWidget* widget, const char* description);
|
||||
|
||||
virtual void LoadSettings() = 0;
|
||||
virtual void SaveSettings() = 0;
|
||||
|
||||
private:
|
||||
QFormLayout* m_main_layout;
|
||||
};
|
174
Source/Core/DolphinQt/Config/Graphics/GraphicsWindow.cpp
Normal file
174
Source/Core/DolphinQt/Config/Graphics/GraphicsWindow.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QEvent>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/AdvancedWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/EnhancementsWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/GeneralWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/HacksWidget.h"
|
||||
#include "DolphinQt/Config/Graphics/SoftwareRendererWidget.h"
|
||||
#include "DolphinQt/MainWindow.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
GraphicsWindow::GraphicsWindow(X11Utils::XRRConfiguration* xrr_config, MainWindow* parent)
|
||||
: QDialog(parent), m_xrr_config(xrr_config)
|
||||
{
|
||||
// GraphicsWindow initialization is heavy due to dependencies on the graphics subsystem.
|
||||
// To prevent blocking startup, we create the layout and children at first show time.
|
||||
}
|
||||
|
||||
void GraphicsWindow::Initialize()
|
||||
{
|
||||
if (m_lazy_initialized)
|
||||
return;
|
||||
|
||||
m_lazy_initialized = true;
|
||||
|
||||
g_Config.Refresh();
|
||||
g_video_backend->InitBackendInfo();
|
||||
|
||||
CreateMainLayout();
|
||||
|
||||
setWindowTitle(tr("Graphics"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
OnBackendChanged(QString::fromStdString(SConfig::GetInstance().m_strVideoBackend));
|
||||
}
|
||||
|
||||
void GraphicsWindow::CreateMainLayout()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout();
|
||||
auto* description_box = new QGroupBox(tr("Description"));
|
||||
auto* description_layout = new QVBoxLayout();
|
||||
m_description =
|
||||
new QLabel(tr("Move the mouse pointer over an option to display a detailed description."));
|
||||
m_tab_widget = new QTabWidget();
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
description_box->setLayout(description_layout);
|
||||
description_box->setFixedHeight(200);
|
||||
|
||||
m_description->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
m_description->setWordWrap(true);
|
||||
m_description->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
description_layout->addWidget(m_description);
|
||||
|
||||
main_layout->addWidget(m_tab_widget);
|
||||
main_layout->addWidget(description_box);
|
||||
main_layout->addWidget(m_button_box);
|
||||
|
||||
m_general_widget = new GeneralWidget(m_xrr_config, this);
|
||||
m_enhancements_widget = new EnhancementsWidget(this);
|
||||
m_hacks_widget = new HacksWidget(this);
|
||||
m_advanced_widget = new AdvancedWidget(this);
|
||||
m_software_renderer = new SoftwareRendererWidget(this);
|
||||
|
||||
connect(m_general_widget, &GeneralWidget::BackendChanged, this,
|
||||
&GraphicsWindow::OnBackendChanged);
|
||||
connect(m_software_renderer, &SoftwareRendererWidget::BackendChanged, this,
|
||||
&GraphicsWindow::OnBackendChanged);
|
||||
|
||||
m_wrapped_general = GetWrappedWidget(m_general_widget, this, 50, 305);
|
||||
m_wrapped_enhancements = GetWrappedWidget(m_enhancements_widget, this, 50, 305);
|
||||
m_wrapped_hacks = GetWrappedWidget(m_hacks_widget, this, 50, 305);
|
||||
m_wrapped_advanced = GetWrappedWidget(m_advanced_widget, this, 50, 305);
|
||||
m_wrapped_software = GetWrappedWidget(m_software_renderer, this, 50, 305);
|
||||
|
||||
if (SConfig::GetInstance().m_strVideoBackend != "Software Renderer")
|
||||
{
|
||||
m_tab_widget->addTab(m_wrapped_general, tr("General"));
|
||||
m_tab_widget->addTab(m_wrapped_enhancements, tr("Enhancements"));
|
||||
m_tab_widget->addTab(m_wrapped_hacks, tr("Hacks"));
|
||||
m_tab_widget->addTab(m_wrapped_advanced, tr("Advanced"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tab_widget->addTab(m_wrapped_software, tr("Software Renderer"));
|
||||
}
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void GraphicsWindow::OnBackendChanged(const QString& backend_name)
|
||||
{
|
||||
SConfig::GetInstance().m_strVideoBackend = backend_name.toStdString();
|
||||
|
||||
for (const auto& backend : g_available_video_backends)
|
||||
{
|
||||
if (backend->GetName() == backend_name.toStdString())
|
||||
{
|
||||
g_Config.Refresh();
|
||||
|
||||
g_video_backend = backend.get();
|
||||
g_video_backend->InitBackendInfo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setWindowTitle(
|
||||
tr("%1 Graphics Configuration").arg(tr(g_video_backend->GetDisplayName().c_str())));
|
||||
if (backend_name == QStringLiteral("Software Renderer") && m_tab_widget->count() > 1)
|
||||
{
|
||||
m_tab_widget->clear();
|
||||
m_tab_widget->addTab(m_wrapped_software, tr("Software Renderer"));
|
||||
}
|
||||
|
||||
if (backend_name != QStringLiteral("Software Renderer") && m_tab_widget->count() == 1)
|
||||
{
|
||||
m_tab_widget->clear();
|
||||
m_tab_widget->addTab(m_wrapped_general, tr("General"));
|
||||
m_tab_widget->addTab(m_wrapped_enhancements, tr("Enhancements"));
|
||||
m_tab_widget->addTab(m_wrapped_hacks, tr("Hacks"));
|
||||
m_tab_widget->addTab(m_wrapped_advanced, tr("Advanced"));
|
||||
}
|
||||
|
||||
emit BackendChanged(backend_name);
|
||||
}
|
||||
|
||||
void GraphicsWindow::RegisterWidget(GraphicsWidget* widget)
|
||||
{
|
||||
connect(widget, &GraphicsWidget::DescriptionAdded, this, &GraphicsWindow::OnDescriptionAdded);
|
||||
}
|
||||
|
||||
void GraphicsWindow::OnDescriptionAdded(QWidget* widget, const char* description)
|
||||
{
|
||||
m_widget_descriptions[widget] = description;
|
||||
widget->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool GraphicsWindow::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if (!m_widget_descriptions.contains(object))
|
||||
return false;
|
||||
|
||||
if (event->type() == QEvent::Enter)
|
||||
{
|
||||
m_description->setText(tr(m_widget_descriptions[object]));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::Leave)
|
||||
{
|
||||
m_description->setText(
|
||||
tr("Move the mouse pointer over an option to display a detailed description."));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
65
Source/Core/DolphinQt/Config/Graphics/GraphicsWindow.h
Normal file
65
Source/Core/DolphinQt/Config/Graphics/GraphicsWindow.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
|
||||
class AdvancedWidget;
|
||||
class EnhancementsWidget;
|
||||
class HacksWidget;
|
||||
class GeneralWidget;
|
||||
class GraphicsWidget;
|
||||
class MainWindow;
|
||||
class QLabel;
|
||||
class QTabWidget;
|
||||
class QDialogButtonBox;
|
||||
class SoftwareRendererWidget;
|
||||
|
||||
namespace X11Utils
|
||||
{
|
||||
class XRRConfiguration;
|
||||
}
|
||||
|
||||
class GraphicsWindow final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GraphicsWindow(X11Utils::XRRConfiguration* xrr_config, MainWindow* parent);
|
||||
|
||||
void Initialize();
|
||||
|
||||
void RegisterWidget(GraphicsWidget* widget);
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
signals:
|
||||
void BackendChanged(const QString& backend);
|
||||
|
||||
private:
|
||||
void CreateMainLayout();
|
||||
void OnBackendChanged(const QString& backend);
|
||||
void OnDescriptionAdded(QWidget* widget, const char* description);
|
||||
|
||||
bool m_lazy_initialized = false;
|
||||
|
||||
QTabWidget* m_tab_widget;
|
||||
QLabel* m_description;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
AdvancedWidget* m_advanced_widget;
|
||||
EnhancementsWidget* m_enhancements_widget;
|
||||
HacksWidget* m_hacks_widget;
|
||||
GeneralWidget* m_general_widget;
|
||||
SoftwareRendererWidget* m_software_renderer;
|
||||
|
||||
QWidget* m_wrapped_advanced;
|
||||
QWidget* m_wrapped_enhancements;
|
||||
QWidget* m_wrapped_hacks;
|
||||
QWidget* m_wrapped_general;
|
||||
QWidget* m_wrapped_software;
|
||||
|
||||
X11Utils::XRRConfiguration* m_xrr_config;
|
||||
|
||||
QHash<QObject*, const char*> m_widget_descriptions;
|
||||
};
|
251
Source/Core/DolphinQt/Config/Graphics/HacksWidget.cpp
Normal file
251
Source/Core/DolphinQt/Config/Graphics/HacksWidget.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/HacksWidget.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsSlider.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
HacksWidget::HacksWidget(GraphicsWindow* parent) : GraphicsWidget(parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
|
||||
connect(parent, &GraphicsWindow::BackendChanged, this, &HacksWidget::OnBackendChanged);
|
||||
OnBackendChanged(QString::fromStdString(SConfig::GetInstance().m_strVideoBackend));
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this, &HacksWidget::LoadSettings);
|
||||
}
|
||||
|
||||
void HacksWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
// EFB
|
||||
auto* efb_box = new QGroupBox(tr("Embedded Frame Buffer (EFB)"));
|
||||
auto* efb_layout = new QGridLayout();
|
||||
efb_box->setLayout(efb_layout);
|
||||
m_skip_efb_cpu =
|
||||
new GraphicsBool(tr("Skip EFB Access from CPU"), Config::GFX_HACK_EFB_ACCESS_ENABLE, true);
|
||||
m_ignore_format_changes = new GraphicsBool(tr("Ignore Format Changes"),
|
||||
Config::GFX_HACK_EFB_EMULATE_FORMAT_CHANGES, true);
|
||||
m_store_efb_copies = new GraphicsBool(tr("Store EFB Copies to Texture Only"),
|
||||
Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM);
|
||||
|
||||
efb_layout->addWidget(m_skip_efb_cpu, 0, 0);
|
||||
efb_layout->addWidget(m_ignore_format_changes, 0, 1);
|
||||
efb_layout->addWidget(m_store_efb_copies, 1, 0);
|
||||
|
||||
// Texture Cache
|
||||
auto* texture_cache_box = new QGroupBox(tr("Texture Cache"));
|
||||
auto* texture_cache_layout = new QGridLayout();
|
||||
texture_cache_box->setLayout(texture_cache_layout);
|
||||
|
||||
m_accuracy = new QSlider(Qt::Horizontal);
|
||||
m_accuracy->setMinimum(0);
|
||||
m_accuracy->setMaximum(2);
|
||||
m_accuracy->setPageStep(1);
|
||||
m_accuracy->setTickPosition(QSlider::TicksBelow);
|
||||
m_gpu_texture_decoding =
|
||||
new GraphicsBool(tr("GPU Texture Decoding"), Config::GFX_ENABLE_GPU_TEXTURE_DECODING);
|
||||
|
||||
auto* safe_label = new QLabel(tr("Safe"));
|
||||
safe_label->setAlignment(Qt::AlignRight);
|
||||
|
||||
m_accuracy_label = new QLabel(tr("Accuracy:"));
|
||||
|
||||
texture_cache_layout->addWidget(m_accuracy_label, 0, 0);
|
||||
texture_cache_layout->addWidget(safe_label, 0, 1);
|
||||
texture_cache_layout->addWidget(m_accuracy, 0, 2);
|
||||
texture_cache_layout->addWidget(new QLabel(tr("Fast")), 0, 3);
|
||||
texture_cache_layout->addWidget(m_gpu_texture_decoding, 1, 0);
|
||||
|
||||
// XFB
|
||||
auto* xfb_box = new QGroupBox(tr("External Frame Buffer (XFB)"));
|
||||
auto* xfb_layout = new QVBoxLayout();
|
||||
xfb_box->setLayout(xfb_layout);
|
||||
|
||||
m_store_xfb_copies = new GraphicsBool(tr("Store XFB Copies to Texture Only"),
|
||||
Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM);
|
||||
m_immediate_xfb = new GraphicsBool(tr("Immediately Present XFB"), Config::GFX_HACK_IMMEDIATE_XFB);
|
||||
|
||||
xfb_layout->addWidget(m_store_xfb_copies);
|
||||
xfb_layout->addWidget(m_immediate_xfb);
|
||||
|
||||
// Other
|
||||
auto* other_box = new QGroupBox(tr("Other"));
|
||||
auto* other_layout = new QGridLayout();
|
||||
other_box->setLayout(other_layout);
|
||||
|
||||
m_fast_depth_calculation =
|
||||
new GraphicsBool(tr("Fast Depth Calculation"), Config::GFX_FAST_DEPTH_CALC);
|
||||
m_disable_bounding_box =
|
||||
new GraphicsBool(tr("Disable Bounding Box"), Config::GFX_HACK_BBOX_ENABLE, true);
|
||||
m_vertex_rounding = new GraphicsBool(tr("Vertex Rounding"), Config::GFX_HACK_VERTEX_ROUDING);
|
||||
|
||||
other_layout->addWidget(m_fast_depth_calculation, 0, 0);
|
||||
other_layout->addWidget(m_disable_bounding_box, 0, 1);
|
||||
other_layout->addWidget(m_vertex_rounding, 1, 0);
|
||||
|
||||
main_layout->addWidget(efb_box);
|
||||
main_layout->addWidget(texture_cache_box);
|
||||
main_layout->addWidget(xfb_box);
|
||||
main_layout->addWidget(other_box);
|
||||
main_layout->addStretch();
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void HacksWidget::OnBackendChanged(const QString& backend_name)
|
||||
{
|
||||
const bool bbox = g_Config.backend_info.bSupportsBBox;
|
||||
const bool gpu_texture_decoding = g_Config.backend_info.bSupportsGPUTextureDecoding;
|
||||
|
||||
m_gpu_texture_decoding->setEnabled(gpu_texture_decoding);
|
||||
m_disable_bounding_box->setEnabled(bbox);
|
||||
|
||||
if (!gpu_texture_decoding)
|
||||
m_gpu_texture_decoding->setToolTip(tr("%1 doesn't support this feature.").arg(backend_name));
|
||||
|
||||
if (!bbox)
|
||||
m_disable_bounding_box->setToolTip(tr("%1 doesn't support this feature.").arg(backend_name));
|
||||
}
|
||||
|
||||
void HacksWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_accuracy, &QSlider::valueChanged, [this](int) { SaveSettings(); });
|
||||
}
|
||||
|
||||
void HacksWidget::LoadSettings()
|
||||
{
|
||||
const bool old = m_accuracy->blockSignals(true);
|
||||
auto samples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES);
|
||||
|
||||
int slider_pos = 0;
|
||||
|
||||
switch (samples)
|
||||
{
|
||||
case 512:
|
||||
slider_pos = 1;
|
||||
break;
|
||||
case 128:
|
||||
slider_pos = 2;
|
||||
break;
|
||||
case 0:
|
||||
slider_pos = 0;
|
||||
break;
|
||||
// Custom values, ought not to be touched
|
||||
default:
|
||||
m_accuracy->setEnabled(false);
|
||||
}
|
||||
|
||||
m_accuracy->setValue(slider_pos);
|
||||
|
||||
QFont bf = m_accuracy_label->font();
|
||||
|
||||
bf.setBold(Config::GetActiveLayerForConfig(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES) !=
|
||||
Config::LayerType::Base);
|
||||
|
||||
m_accuracy_label->setFont(bf);
|
||||
|
||||
m_accuracy->blockSignals(old);
|
||||
}
|
||||
|
||||
void HacksWidget::SaveSettings()
|
||||
{
|
||||
int slider_pos = m_accuracy->value();
|
||||
|
||||
if (m_accuracy->isEnabled())
|
||||
{
|
||||
int samples = 0;
|
||||
switch (slider_pos)
|
||||
{
|
||||
case 0:
|
||||
samples = 0;
|
||||
break;
|
||||
case 1:
|
||||
samples = 512;
|
||||
break;
|
||||
case 2:
|
||||
samples = 128;
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES, samples);
|
||||
}
|
||||
}
|
||||
|
||||
void HacksWidget::AddDescriptions()
|
||||
{
|
||||
static const char TR_SKIP_EFB_CPU_ACCESS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Ignore any requests from the CPU to read from or write to the EFB.\nImproves "
|
||||
"performance in some games, but might disable some gameplay-related features or "
|
||||
"graphical effects.\n\nIf unsure, leave this unchecked.");
|
||||
static const char TR_IGNORE_FORMAT_CHANGE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Ignore any changes to the EFB format.\nImproves performance in many games without "
|
||||
"any negative effect. Causes graphical defects in a small number of other "
|
||||
"games.\n\nIf unsure, leave this checked.");
|
||||
static const char TR_STORE_EFB_TO_TEXTURE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Stores EFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects "
|
||||
"in a small number of games.\n\nEnabled = EFB Copies to Texture\nDisabled = EFB Copies to "
|
||||
"RAM "
|
||||
"(and Texture)\n\nIf unsure, leave this checked.");
|
||||
static const char TR_ACCUARCY_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"The \"Safe\" setting eliminates the likelihood of the GPU missing texture updates "
|
||||
"from RAM.\nLower accuracies cause in-game text to appear garbled in certain "
|
||||
"games.\n\nIf unsure, use the rightmost value.");
|
||||
|
||||
static const char TR_STORE_XFB_TO_TEXTURE_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Stores XFB Copies exclusively on the GPU, bypassing system memory. Causes graphical defects "
|
||||
"in a small number of games that need to readback from memory.\n\nEnabled = XFB Copies to "
|
||||
"Texture\nDisabled = XFB Copies to RAM "
|
||||
"(and Texture)\n\nIf unsure, leave this checked.");
|
||||
|
||||
static const char TR_IMMEDIATE_XFB_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Displays the XFB copies as soon as they are created, without waiting for "
|
||||
"scanout. Can cause graphical defects "
|
||||
"in some games if the game doesn't expect all XFB copies to be displayed. "
|
||||
"However, turning this setting on reduces latency."
|
||||
"\n\nIf unsure, leave this unchecked.");
|
||||
|
||||
static const char TR_GPU_DECODING_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Enables texture decoding using the GPU instead of the CPU. This may result in "
|
||||
"performance gains in some scenarios, or on systems where the CPU is the "
|
||||
"bottleneck.\n\nIf unsure, leave this unchecked.");
|
||||
|
||||
static const char TR_FAST_DEPTH_CALC_DESCRIPTION[] = QT_TR_NOOP(
|
||||
"Use a less accurate algorithm to calculate depth values.\nCauses issues in a few "
|
||||
"games, but can give a decent speedup depending on the game and/or your GPU.\n\nIf "
|
||||
"unsure, leave this checked.");
|
||||
static const char TR_DISABLE_BOUNDINGBOX_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Disable the bounding box emulation.\nThis may improve the GPU performance a lot, "
|
||||
"but some games will break.\n\nIf unsure, leave this checked.");
|
||||
static const char TR_VERTEX_ROUNDING_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Rounds 2D vertices to whole pixels. Fixes graphical problems in some games at "
|
||||
"higher internal resolutions. This setting has no effect when native internal "
|
||||
"resolution is used.\n\nIf unsure, leave this unchecked.");
|
||||
|
||||
AddDescription(m_skip_efb_cpu, TR_SKIP_EFB_CPU_ACCESS_DESCRIPTION);
|
||||
AddDescription(m_ignore_format_changes, TR_IGNORE_FORMAT_CHANGE_DESCRIPTION);
|
||||
AddDescription(m_store_efb_copies, TR_STORE_EFB_TO_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_accuracy, TR_ACCUARCY_DESCRIPTION);
|
||||
AddDescription(m_store_xfb_copies, TR_STORE_XFB_TO_TEXTURE_DESCRIPTION);
|
||||
AddDescription(m_immediate_xfb, TR_IMMEDIATE_XFB_DESCRIPTION);
|
||||
AddDescription(m_gpu_texture_decoding, TR_GPU_DECODING_DESCRIPTION);
|
||||
AddDescription(m_fast_depth_calculation, TR_FAST_DEPTH_CALC_DESCRIPTION);
|
||||
AddDescription(m_disable_bounding_box, TR_DISABLE_BOUNDINGBOX_DESCRIPTION);
|
||||
AddDescription(m_vertex_rounding, TR_VERTEX_ROUNDING_DESCRIPTION);
|
||||
}
|
49
Source/Core/DolphinQt/Config/Graphics/HacksWidget.h
Normal file
49
Source/Core/DolphinQt/Config/Graphics/HacksWidget.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QRadioButton;
|
||||
class QSlider;
|
||||
|
||||
class HacksWidget final : public GraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HacksWidget(GraphicsWindow* parent);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void OnBackendChanged(const QString& backend_name);
|
||||
|
||||
// EFB
|
||||
QCheckBox* m_skip_efb_cpu;
|
||||
QCheckBox* m_ignore_format_changes;
|
||||
QCheckBox* m_store_efb_copies;
|
||||
|
||||
// Texture Cache
|
||||
QLabel* m_accuracy_label;
|
||||
QSlider* m_accuracy;
|
||||
QCheckBox* m_gpu_texture_decoding;
|
||||
|
||||
// External Framebuffer
|
||||
QCheckBox* m_store_xfb_copies;
|
||||
QCheckBox* m_immediate_xfb;
|
||||
|
||||
// Other
|
||||
QCheckBox* m_fast_depth_calculation;
|
||||
QCheckBox* m_disable_bounding_box;
|
||||
QCheckBox* m_vertex_rounding;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
};
|
@ -0,0 +1,362 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/PostProcessingConfigWindow.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSlider>
|
||||
#include <QString>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "DolphinQt/Config/Graphics/EnhancementsWidget.h"
|
||||
|
||||
#include "VideoCommon/PostProcessing.h"
|
||||
#include "VideoCommon/RenderBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
using ConfigurationOption = PostProcessingShaderConfiguration::ConfigurationOption;
|
||||
using OptionType = ConfigurationOption::OptionType;
|
||||
|
||||
PostProcessingConfigWindow::PostProcessingConfigWindow(EnhancementsWidget* parent,
|
||||
const std::string& shader)
|
||||
: QDialog(parent), m_shader(shader)
|
||||
{
|
||||
if (g_renderer && g_renderer->GetPostProcessor())
|
||||
{
|
||||
m_post_processor = g_renderer->GetPostProcessor()->GetConfig();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_post_processor = new PostProcessingShaderConfiguration();
|
||||
m_post_processor->LoadShader(m_shader);
|
||||
}
|
||||
|
||||
setWindowTitle(tr("Post Processing Shader Configuration"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
PopulateGroups();
|
||||
Create();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
PostProcessingConfigWindow::~PostProcessingConfigWindow()
|
||||
{
|
||||
m_post_processor->SaveOptionsConfiguration();
|
||||
if (!(g_renderer && g_renderer->GetPostProcessor()))
|
||||
{
|
||||
delete m_post_processor;
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::PopulateGroups()
|
||||
{
|
||||
const PostProcessingShaderConfiguration::ConfigMap& config_map = m_post_processor->GetOptions();
|
||||
|
||||
auto config_groups = std::vector<std::unique_ptr<ConfigGroup>>();
|
||||
for (const auto& it : config_map)
|
||||
{
|
||||
auto config_group = std::make_unique<ConfigGroup>(&it.second);
|
||||
m_config_map[it.first] = config_group.get();
|
||||
config_groups.push_back(std::move(config_group));
|
||||
}
|
||||
|
||||
for (auto& config_group : config_groups)
|
||||
{
|
||||
const std::string& parent_name = config_group->GetParent();
|
||||
if (parent_name.empty())
|
||||
{
|
||||
m_config_groups.emplace_back(std::move(config_group));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_config_map[parent_name]->AddSubGroup(std::move(config_group));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::Create()
|
||||
{
|
||||
m_tabs = new QTabWidget();
|
||||
auto* const general = new QWidget(m_tabs);
|
||||
auto* const general_layout = new QGridLayout(general);
|
||||
|
||||
u32 row = 0;
|
||||
bool add_general_page = false;
|
||||
for (const auto& it : m_config_groups)
|
||||
{
|
||||
if (it->HasSubGroups())
|
||||
{
|
||||
auto* const tab = CreateDependentTab(it);
|
||||
m_tabs->addTab(tab, QString::fromStdString(it->GetGUIName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!add_general_page)
|
||||
{
|
||||
add_general_page = true;
|
||||
}
|
||||
row = it->AddWidgets(this, general_layout, row);
|
||||
}
|
||||
}
|
||||
|
||||
if (add_general_page)
|
||||
{
|
||||
m_tabs->insertTab(0, general, tr("General"));
|
||||
}
|
||||
|
||||
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
layout->addWidget(m_tabs);
|
||||
layout->addWidget(m_buttons);
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::ConnectWidgets()
|
||||
{
|
||||
connect(m_buttons, &QDialogButtonBox::accepted, this, &PostProcessingConfigWindow::accept);
|
||||
}
|
||||
|
||||
QWidget*
|
||||
PostProcessingConfigWindow::CreateDependentTab(const std::unique_ptr<ConfigGroup>& config_group)
|
||||
{
|
||||
auto* const tab = new QWidget(m_tabs);
|
||||
auto* const layout = new QGridLayout(tab);
|
||||
|
||||
u32 row = config_group->AddWidgets(this, layout, 0);
|
||||
for (const auto& child : config_group->GetSubGroups())
|
||||
{
|
||||
row = child->AddWidgets(this, layout, row);
|
||||
}
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::UpdateBool(ConfigGroup* const config_group, const bool state)
|
||||
{
|
||||
m_post_processor->SetOptionb(config_group->GetOptionName(), state);
|
||||
|
||||
config_group->EnableSuboptions(state);
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::UpdateInteger(ConfigGroup* const config_group, const int value)
|
||||
{
|
||||
const ConfigurationOption& config_option =
|
||||
m_post_processor->GetOption(config_group->GetOptionName());
|
||||
|
||||
const size_t vector_size = config_option.m_integer_values.size();
|
||||
|
||||
for (size_t i = 0; i < vector_size; ++i)
|
||||
{
|
||||
const int current_step = config_group->GetSliderValue(i);
|
||||
const s32 current_value = config_option.m_integer_step_values[i] * current_step +
|
||||
config_option.m_integer_min_values[i];
|
||||
m_post_processor->SetOptioni(config_option.m_option_name, static_cast<int>(i), current_value);
|
||||
config_group->SetSliderText(i, QString::number(current_value));
|
||||
}
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::UpdateFloat(ConfigGroup* const config_group, const int value)
|
||||
{
|
||||
const ConfigurationOption& config_option =
|
||||
m_post_processor->GetOption(config_group->GetOptionName());
|
||||
|
||||
const size_t vector_size = config_option.m_float_values.size();
|
||||
|
||||
for (size_t i = 0; i < vector_size; ++i)
|
||||
{
|
||||
const int current_step = config_group->GetSliderValue(static_cast<unsigned int>(i));
|
||||
const float current_value =
|
||||
config_option.m_float_step_values[i] * current_step + config_option.m_float_min_values[i];
|
||||
m_post_processor->SetOptionf(config_option.m_option_name, static_cast<int>(i), current_value);
|
||||
config_group->SetSliderText(i, QString::asprintf("%f", current_value));
|
||||
}
|
||||
}
|
||||
|
||||
PostProcessingConfigWindow::ConfigGroup::ConfigGroup(const ConfigurationOption* config_option)
|
||||
: m_config_option(config_option)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string& PostProcessingConfigWindow::ConfigGroup::GetGUIName() const noexcept
|
||||
{
|
||||
return m_config_option->m_gui_name;
|
||||
}
|
||||
|
||||
const std::string& PostProcessingConfigWindow::ConfigGroup::GetParent() const noexcept
|
||||
{
|
||||
return m_config_option->m_dependent_option;
|
||||
}
|
||||
|
||||
const std::string& PostProcessingConfigWindow::ConfigGroup::GetOptionName() const noexcept
|
||||
{
|
||||
return m_config_option->m_option_name;
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::ConfigGroup::AddSubGroup(std::unique_ptr<ConfigGroup>&& subgroup)
|
||||
{
|
||||
m_subgroups.emplace_back(std::move(subgroup));
|
||||
}
|
||||
|
||||
bool PostProcessingConfigWindow::ConfigGroup::HasSubGroups() const noexcept
|
||||
{
|
||||
return !m_subgroups.empty();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<PostProcessingConfigWindow::ConfigGroup>>&
|
||||
PostProcessingConfigWindow::ConfigGroup::GetSubGroups() const noexcept
|
||||
{
|
||||
return m_subgroups;
|
||||
}
|
||||
|
||||
u32 PostProcessingConfigWindow::ConfigGroup::AddWidgets(PostProcessingConfigWindow* const parent,
|
||||
QGridLayout* const grid, const u32 row)
|
||||
{
|
||||
auto* const name = new QLabel(QString::fromStdString(m_config_option->m_gui_name));
|
||||
grid->addWidget(name, row, 0);
|
||||
|
||||
switch (m_config_option->m_type)
|
||||
{
|
||||
case OptionType::OPTION_BOOL:
|
||||
return AddBool(parent, grid, row);
|
||||
case OptionType::OPTION_FLOAT:
|
||||
return AddFloat(parent, grid, row);
|
||||
case OptionType::OPTION_INTEGER:
|
||||
return AddInteger(parent, grid, row);
|
||||
default:
|
||||
// obviously shouldn't get here
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
u32 PostProcessingConfigWindow::ConfigGroup::AddBool(PostProcessingConfigWindow* const parent,
|
||||
QGridLayout* const grid, const u32 row)
|
||||
{
|
||||
m_checkbox = new QCheckBox();
|
||||
m_checkbox->setChecked(m_config_option->m_bool_value);
|
||||
QObject::connect(m_checkbox, &QCheckBox::toggled,
|
||||
[this, parent](bool checked) { parent->UpdateBool(this, checked); });
|
||||
grid->addWidget(m_checkbox, row, 2);
|
||||
|
||||
return row + 1;
|
||||
}
|
||||
|
||||
u32 PostProcessingConfigWindow::ConfigGroup::AddInteger(PostProcessingConfigWindow* const parent,
|
||||
QGridLayout* const grid, u32 row)
|
||||
{
|
||||
const size_t vector_size = m_config_option->m_integer_values.size();
|
||||
|
||||
for (size_t i = 0; i < vector_size; ++i)
|
||||
{
|
||||
const int current_value = m_config_option->m_integer_values[i];
|
||||
const double range =
|
||||
m_config_option->m_integer_max_values[i] - m_config_option->m_integer_min_values[i];
|
||||
// "How many steps we have is the range divided by the step interval configured.
|
||||
// This may not be 100% spot on accurate since developers can have odd stepping intervals
|
||||
// set.
|
||||
// Round up so if it is outside our range, then set it to the minimum or maximum"
|
||||
const int steps =
|
||||
std::ceil(range / static_cast<double>(m_config_option->m_integer_step_values[i]));
|
||||
|
||||
auto* const slider = new QSlider(Qt::Orientation::Horizontal);
|
||||
slider->setMinimum(0);
|
||||
slider->setMaximum(steps);
|
||||
slider->setValue(current_value);
|
||||
slider->setTickInterval(range / steps);
|
||||
QObject::connect(slider, &QSlider::valueChanged,
|
||||
[this, parent](int value) { parent->UpdateInteger(this, value); });
|
||||
|
||||
auto* const value_box = new QLineEdit(QString::number(current_value));
|
||||
value_box->setEnabled(false);
|
||||
|
||||
grid->addWidget(slider, row, 1);
|
||||
grid->addWidget(value_box, row, 2);
|
||||
|
||||
m_sliders.push_back(slider);
|
||||
m_value_boxes.push_back(value_box);
|
||||
if (vector_size > 1)
|
||||
{
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
return row + 1;
|
||||
}
|
||||
|
||||
u32 PostProcessingConfigWindow::ConfigGroup::AddFloat(PostProcessingConfigWindow* const parent,
|
||||
QGridLayout* const grid, u32 row)
|
||||
{
|
||||
const size_t vector_size = m_config_option->m_float_values.size();
|
||||
|
||||
for (size_t i = 0; i < vector_size; ++i)
|
||||
{
|
||||
const int current_value =
|
||||
m_config_option->m_float_values[i] / m_config_option->m_float_step_values[i];
|
||||
const float range =
|
||||
m_config_option->m_float_max_values[i] - m_config_option->m_float_min_values[i];
|
||||
const int steps = std::ceil(range / m_config_option->m_float_step_values[i]);
|
||||
|
||||
auto* const slider = new QSlider(Qt::Orientation::Horizontal);
|
||||
slider->setMinimum(0);
|
||||
slider->setMaximum(steps);
|
||||
slider->setValue(current_value);
|
||||
slider->setTickInterval(range / steps);
|
||||
QObject::connect(slider, &QSlider::valueChanged,
|
||||
[this, parent](int value) { parent->UpdateFloat(this, value); });
|
||||
|
||||
auto* const value_box =
|
||||
new QLineEdit(QString::asprintf("%f", m_config_option->m_float_values[i]));
|
||||
value_box->setEnabled(false);
|
||||
|
||||
grid->addWidget(slider, row, 1);
|
||||
grid->addWidget(value_box, row, 2);
|
||||
|
||||
m_sliders.push_back(slider);
|
||||
m_value_boxes.push_back(value_box);
|
||||
if (vector_size > 1)
|
||||
{
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
return row + 1;
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::ConfigGroup::EnableSuboptions(const bool state)
|
||||
{
|
||||
for (auto& it : m_subgroups)
|
||||
{
|
||||
if (it->m_config_option->m_type == OptionType::OPTION_BOOL)
|
||||
{
|
||||
it->m_checkbox->setEnabled(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& slider : it->m_sliders)
|
||||
{
|
||||
slider->setEnabled(state);
|
||||
}
|
||||
}
|
||||
it->EnableSuboptions(state);
|
||||
}
|
||||
}
|
||||
|
||||
int PostProcessingConfigWindow::ConfigGroup::GetSliderValue(size_t index) const
|
||||
{
|
||||
return m_sliders[index]->value();
|
||||
}
|
||||
|
||||
void PostProcessingConfigWindow::ConfigGroup::SetSliderText(size_t index, const QString& text)
|
||||
{
|
||||
m_value_boxes[index]->setText(text);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "VideoCommon/PostProcessing.h"
|
||||
|
||||
class EnhancementsWidget;
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QGridLayout;
|
||||
class QLineEdit;
|
||||
class QSlider;
|
||||
class QTabWidget;
|
||||
class QWidget;
|
||||
|
||||
class PostProcessingConfigWindow final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PostProcessingConfigWindow(EnhancementsWidget* parent, const std::string& shader);
|
||||
~PostProcessingConfigWindow();
|
||||
|
||||
private:
|
||||
class ConfigGroup final
|
||||
{
|
||||
public:
|
||||
explicit ConfigGroup(
|
||||
const PostProcessingShaderConfiguration::ConfigurationOption* config_option);
|
||||
|
||||
const std::string& GetGUIName() const noexcept;
|
||||
const std::string& GetParent() const noexcept;
|
||||
const std::string& GetOptionName() const noexcept;
|
||||
void AddSubGroup(std::unique_ptr<ConfigGroup>&& subgroup);
|
||||
bool HasSubGroups() const noexcept;
|
||||
const std::vector<std::unique_ptr<ConfigGroup>>& GetSubGroups() const noexcept;
|
||||
u32 AddWidgets(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row);
|
||||
void EnableSuboptions(bool state);
|
||||
int GetSliderValue(size_t index) const;
|
||||
void SetSliderText(size_t index, const QString& text);
|
||||
|
||||
private:
|
||||
u32 AddBool(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row);
|
||||
u32 AddInteger(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row);
|
||||
u32 AddFloat(PostProcessingConfigWindow* parent, QGridLayout* grid, u32 row);
|
||||
|
||||
QCheckBox* m_checkbox;
|
||||
std::vector<QSlider*> m_sliders;
|
||||
std::vector<QLineEdit*> m_value_boxes;
|
||||
|
||||
const PostProcessingShaderConfiguration::ConfigurationOption* m_config_option;
|
||||
std::vector<std::unique_ptr<ConfigGroup>> m_subgroups;
|
||||
};
|
||||
void Create();
|
||||
void ConnectWidgets();
|
||||
QWidget* CreateDependentTab(const std::unique_ptr<ConfigGroup>& config_group);
|
||||
void PopulateGroups();
|
||||
void UpdateBool(ConfigGroup* config_group, bool state);
|
||||
void UpdateInteger(ConfigGroup* config_group, int value);
|
||||
void UpdateFloat(ConfigGroup* config_group, int value);
|
||||
|
||||
QTabWidget* m_tabs;
|
||||
QDialogButtonBox* m_buttons;
|
||||
|
||||
const std::string& m_shader;
|
||||
PostProcessingShaderConfiguration* m_post_processor;
|
||||
std::unordered_map<std::string, ConfigGroup*> m_config_map;
|
||||
std::vector<std::unique_ptr<ConfigGroup>> m_config_groups;
|
||||
};
|
182
Source/Core/DolphinQt/Config/Graphics/SoftwareRendererWidget.cpp
Normal file
182
Source/Core/DolphinQt/Config/Graphics/SoftwareRendererWidget.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Graphics/SoftwareRendererWidget.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsBool.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/VideoUtils.h"
|
||||
|
||||
#include "VideoCommon/VideoBackendBase.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
SoftwareRendererWidget::SoftwareRendererWidget(GraphicsWindow* parent) : GraphicsWidget(parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
|
||||
connect(parent, &GraphicsWindow::BackendChanged, [this] { LoadSettings(); });
|
||||
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
AddDescriptions();
|
||||
}
|
||||
|
||||
void SoftwareRendererWidget::CreateWidgets()
|
||||
{
|
||||
auto* main_layout = new QVBoxLayout;
|
||||
|
||||
auto* rendering_box = new QGroupBox(tr("Rendering"));
|
||||
auto* rendering_layout = new QGridLayout();
|
||||
m_backend_combo = new QComboBox();
|
||||
|
||||
rendering_box->setLayout(rendering_layout);
|
||||
rendering_layout->addWidget(new QLabel(tr("Backend:")), 1, 1);
|
||||
rendering_layout->addWidget(m_backend_combo, 1, 2);
|
||||
|
||||
for (const auto& backend : g_available_video_backends)
|
||||
m_backend_combo->addItem(tr(backend->GetDisplayName().c_str()));
|
||||
|
||||
auto* overlay_box = new QGroupBox(tr("Overlay Information"));
|
||||
auto* overlay_layout = new QGridLayout();
|
||||
overlay_box->setLayout(overlay_layout);
|
||||
m_show_statistics = new GraphicsBool(tr("Show Statistics"), Config::GFX_OVERLAY_STATS);
|
||||
|
||||
overlay_layout->addWidget(m_show_statistics);
|
||||
|
||||
auto* utility_box = new QGroupBox(tr("Utility"));
|
||||
auto* utility_layout = new QGridLayout();
|
||||
m_dump_textures = new GraphicsBool(tr("Dump Textures"), Config::GFX_DUMP_TEXTURES);
|
||||
m_dump_objects = new GraphicsBool(tr("Dump Objects"), Config::GFX_SW_DUMP_OBJECTS);
|
||||
utility_box->setLayout(utility_layout);
|
||||
|
||||
utility_layout->addWidget(m_dump_textures, 1, 1);
|
||||
utility_layout->addWidget(m_dump_objects, 1, 2);
|
||||
|
||||
auto* debug_box = new QGroupBox(tr("Debug Only"));
|
||||
auto* debug_layout = new QGridLayout();
|
||||
m_dump_tev_stages = new GraphicsBool(tr("Dump TEV Stages"), Config::GFX_SW_DUMP_TEV_STAGES);
|
||||
m_dump_tev_fetches =
|
||||
new GraphicsBool(tr("Dump Texture Fetches"), Config::GFX_SW_DUMP_TEV_TEX_FETCHES);
|
||||
|
||||
debug_layout->addWidget(m_dump_tev_stages, 1, 1);
|
||||
debug_layout->addWidget(m_dump_tev_fetches, 1, 2);
|
||||
|
||||
debug_box->setLayout(debug_layout);
|
||||
|
||||
#ifdef _DEBUG
|
||||
utility_layout->addWidget(debug_box, 2, 1, 1, 2);
|
||||
#endif
|
||||
|
||||
auto* object_range_box = new QGroupBox(tr("Drawn Object Range"));
|
||||
auto* object_range_layout = new QGridLayout();
|
||||
m_object_range_min = new QSpinBox();
|
||||
m_object_range_max = new QSpinBox();
|
||||
|
||||
for (auto* spin : {m_object_range_min, m_object_range_max})
|
||||
{
|
||||
spin->setMinimum(0);
|
||||
spin->setMaximum(100000);
|
||||
}
|
||||
|
||||
object_range_box->setLayout(object_range_layout);
|
||||
|
||||
object_range_layout->addWidget(m_object_range_min, 1, 1);
|
||||
object_range_layout->addWidget(m_object_range_max, 1, 2);
|
||||
|
||||
main_layout->addWidget(rendering_box);
|
||||
main_layout->addWidget(overlay_box);
|
||||
main_layout->addWidget(utility_box);
|
||||
main_layout->addWidget(object_range_box);
|
||||
main_layout->addStretch();
|
||||
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
void SoftwareRendererWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_backend_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_object_range_min, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
[this](int) { SaveSettings(); });
|
||||
connect(m_object_range_max, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
[this](int) { SaveSettings(); });
|
||||
}
|
||||
|
||||
void SoftwareRendererWidget::LoadSettings()
|
||||
{
|
||||
for (const auto& backend : g_available_video_backends)
|
||||
{
|
||||
if (backend->GetName() == SConfig::GetInstance().m_strVideoBackend)
|
||||
m_backend_combo->setCurrentIndex(
|
||||
m_backend_combo->findText(tr(backend->GetDisplayName().c_str())));
|
||||
}
|
||||
|
||||
m_object_range_min->setValue(Config::Get(Config::GFX_SW_DRAW_START));
|
||||
m_object_range_max->setValue(Config::Get(Config::GFX_SW_DRAW_END));
|
||||
}
|
||||
|
||||
void SoftwareRendererWidget::SaveSettings()
|
||||
{
|
||||
for (const auto& backend : g_available_video_backends)
|
||||
{
|
||||
if (tr(backend->GetDisplayName().c_str()) == m_backend_combo->currentText())
|
||||
{
|
||||
const auto backend_name = backend->GetName();
|
||||
if (backend_name != SConfig::GetInstance().m_strVideoBackend)
|
||||
emit BackendChanged(QString::fromStdString(backend_name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Config::SetBaseOrCurrent(Config::GFX_SW_DRAW_START, m_object_range_min->value());
|
||||
Config::SetBaseOrCurrent(Config::GFX_SW_DRAW_END, m_object_range_max->value());
|
||||
}
|
||||
|
||||
void SoftwareRendererWidget::AddDescriptions()
|
||||
{
|
||||
static const char TR_BACKEND_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Selects what graphics API to use internally.\nThe software renderer is extremely "
|
||||
"slow and only useful for debugging, so you'll want to use either Direct3D or "
|
||||
"OpenGL. Different games and different GPUs will behave differently on each "
|
||||
"backend, so for the best emulation experience it's recommended to try both and "
|
||||
"choose the one that's less problematic.\n\nIf unsure, select OpenGL.");
|
||||
|
||||
static const char TR_SHOW_STATISTICS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Show various rendering statistics.\n\nIf unsure, leave this unchecked.");
|
||||
|
||||
static const char TR_DUMP_TEXTURES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump decoded game textures to User/Dump/Textures/<game_id>/.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
|
||||
static const char TR_DUMP_OBJECTS_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump objects to User/Dump/Objects/.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
|
||||
static const char TR_DUMP_TEV_STAGES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump TEV Stages to User/Dump/Objects/.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
|
||||
static const char TR_DUMP_TEV_FETCHES_DESCRIPTION[] =
|
||||
QT_TR_NOOP("Dump Texture Fetches to User/Dump/Objects/.\n\nIf unsure, leave "
|
||||
"this unchecked.");
|
||||
|
||||
AddDescription(m_backend_combo, TR_BACKEND_DESCRIPTION);
|
||||
AddDescription(m_show_statistics, TR_SHOW_STATISTICS_DESCRIPTION);
|
||||
AddDescription(m_dump_textures, TR_DUMP_TEXTURES_DESCRIPTION);
|
||||
AddDescription(m_dump_objects, TR_DUMP_OBJECTS_DESCRIPTION);
|
||||
AddDescription(m_dump_tev_stages, TR_DUMP_TEV_STAGES_DESCRIPTION);
|
||||
AddDescription(m_dump_tev_fetches, TR_DUMP_TEV_FETCHES_DESCRIPTION);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWidget.h"
|
||||
|
||||
class GraphicsWindow;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
|
||||
class SoftwareRendererWidget final : public GraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SoftwareRendererWidget(GraphicsWindow* parent);
|
||||
|
||||
signals:
|
||||
void BackendChanged(const QString& backend);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddDescriptions();
|
||||
|
||||
QComboBox* m_backend_combo;
|
||||
QCheckBox* m_show_statistics;
|
||||
QCheckBox* m_dump_textures;
|
||||
QCheckBox* m_dump_objects;
|
||||
QCheckBox* m_dump_tev_stages;
|
||||
QCheckBox* m_dump_tev_fetches;
|
||||
|
||||
QSpinBox* m_object_range_min;
|
||||
QSpinBox* m_object_range_max;
|
||||
};
|
245
Source/Core/DolphinQt/Config/InfoWidget.cpp
Normal file
245
Source/Core/DolphinQt/Config/InfoWidget.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QProgressDialog>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
#include "DolphinQt/Config/InfoWidget.h"
|
||||
#include "DolphinQt/QtUtils/ImageConverter.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
InfoWidget::InfoWidget(const UICommon::GameFile& game) : m_game(game)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
|
||||
layout->addWidget(CreateISODetails());
|
||||
|
||||
if (!game.GetLanguages().empty())
|
||||
layout->addWidget(CreateBannerDetails());
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
QGroupBox* InfoWidget::CreateISODetails()
|
||||
{
|
||||
const QString UNKNOWN_NAME = tr("Unknown");
|
||||
|
||||
QGroupBox* group = new QGroupBox(tr("ISO Details"));
|
||||
QFormLayout* layout = new QFormLayout;
|
||||
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
|
||||
QLineEdit* file_path = CreateValueDisplay(
|
||||
QStringLiteral("%1 (%2)")
|
||||
.arg(QDir::toNativeSeparators(QString::fromStdString(m_game.GetFilePath())))
|
||||
.arg(QString::fromStdString(UICommon::FormatSize(m_game.GetFileSize()))));
|
||||
|
||||
const QString game_name = QString::fromStdString(m_game.GetInternalName());
|
||||
|
||||
bool is_disc_based = m_game.GetPlatform() == DiscIO::Platform::GameCubeDisc ||
|
||||
m_game.GetPlatform() == DiscIO::Platform::WiiDisc;
|
||||
|
||||
QLineEdit* internal_name =
|
||||
CreateValueDisplay(is_disc_based ? tr("%1 (Disc %2, Revision %3)")
|
||||
.arg(game_name.isEmpty() ? UNKNOWN_NAME : game_name)
|
||||
.arg(m_game.GetDiscNumber())
|
||||
.arg(m_game.GetRevision()) :
|
||||
tr("%1 (Revision %3)")
|
||||
.arg(game_name.isEmpty() ? UNKNOWN_NAME : game_name)
|
||||
.arg(m_game.GetRevision()));
|
||||
|
||||
QString game_id_string = QString::fromStdString(m_game.GetGameID());
|
||||
|
||||
if (const u64 title_id = m_game.GetTitleID())
|
||||
game_id_string += QStringLiteral(" (%1)").arg(title_id, 16, 16, QLatin1Char('0'));
|
||||
|
||||
QLineEdit* game_id = CreateValueDisplay(game_id_string);
|
||||
|
||||
QLineEdit* country = CreateValueDisplay(DiscIO::GetName(m_game.GetCountry(), true));
|
||||
|
||||
const std::string game_maker = m_game.GetMaker();
|
||||
|
||||
QLineEdit* maker =
|
||||
CreateValueDisplay((game_maker.empty() ? UNKNOWN_NAME.toStdString() : game_maker) + " (" +
|
||||
m_game.GetMakerID() + ")");
|
||||
QWidget* checksum = CreateChecksumComputer();
|
||||
|
||||
layout->addRow(tr("Name:"), internal_name);
|
||||
layout->addRow(tr("File:"), file_path);
|
||||
layout->addRow(tr("Game ID:"), game_id);
|
||||
layout->addRow(tr("Country:"), country);
|
||||
layout->addRow(tr("Maker:"), maker);
|
||||
|
||||
if (!m_game.GetApploaderDate().empty())
|
||||
layout->addRow(tr("Apploader Date:"), CreateValueDisplay(m_game.GetApploaderDate()));
|
||||
|
||||
layout->addRow(tr("MD5 Checksum:"), checksum);
|
||||
|
||||
group->setLayout(layout);
|
||||
return group;
|
||||
}
|
||||
|
||||
QGroupBox* InfoWidget::CreateBannerDetails()
|
||||
{
|
||||
QGroupBox* group = new QGroupBox(tr("Banner Details"));
|
||||
QFormLayout* layout = new QFormLayout;
|
||||
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
|
||||
m_name = CreateValueDisplay();
|
||||
m_maker = CreateValueDisplay();
|
||||
m_description = new QTextEdit();
|
||||
m_description->setReadOnly(true);
|
||||
CreateLanguageSelector();
|
||||
|
||||
layout->addRow(tr("Show Language:"), m_language_selector);
|
||||
if (m_game.GetPlatform() == DiscIO::Platform::GameCubeDisc)
|
||||
{
|
||||
layout->addRow(tr("Name:"), m_name);
|
||||
layout->addRow(tr("Maker:"), m_maker);
|
||||
layout->addRow(tr("Description:"), m_description);
|
||||
}
|
||||
else if (DiscIO::IsWii(m_game.GetPlatform()))
|
||||
{
|
||||
layout->addRow(tr("Name:"), m_name);
|
||||
}
|
||||
|
||||
QPixmap banner = ToQPixmap(m_game.GetBannerImage());
|
||||
if (!banner.isNull())
|
||||
layout->addRow(tr("Banner:"), CreateBannerGraphic(banner));
|
||||
|
||||
group->setLayout(layout);
|
||||
return group;
|
||||
}
|
||||
|
||||
QWidget* InfoWidget::CreateBannerGraphic(const QPixmap& image)
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
QHBoxLayout* layout = new QHBoxLayout();
|
||||
|
||||
QLabel* banner = new QLabel();
|
||||
banner->setPixmap(image);
|
||||
QPushButton* save = new QPushButton(tr("Save as..."));
|
||||
connect(save, &QPushButton::clicked, this, &InfoWidget::SaveBanner);
|
||||
|
||||
layout->addWidget(banner);
|
||||
layout->addWidget(save);
|
||||
widget->setLayout(layout);
|
||||
return widget;
|
||||
}
|
||||
|
||||
void InfoWidget::SaveBanner()
|
||||
{
|
||||
QString path = QFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(),
|
||||
tr("PNG image file (*.png);; All Files (*)"));
|
||||
ToQPixmap(m_game.GetBannerImage()).save(path, "PNG");
|
||||
}
|
||||
|
||||
QLineEdit* InfoWidget::CreateValueDisplay(const QString& value)
|
||||
{
|
||||
QLineEdit* value_display = new QLineEdit(value);
|
||||
value_display->setReadOnly(true);
|
||||
value_display->setCursorPosition(0);
|
||||
return value_display;
|
||||
}
|
||||
|
||||
QLineEdit* InfoWidget::CreateValueDisplay(const std::string& value)
|
||||
{
|
||||
return CreateValueDisplay(QString::fromStdString(value));
|
||||
}
|
||||
|
||||
void InfoWidget::CreateLanguageSelector()
|
||||
{
|
||||
const bool is_wii = DiscIO::IsWii(m_game.GetPlatform());
|
||||
const DiscIO::Language preferred_language = SConfig::GetInstance().GetCurrentLanguage(is_wii);
|
||||
|
||||
m_language_selector = new QComboBox();
|
||||
for (DiscIO::Language language : m_game.GetLanguages())
|
||||
{
|
||||
m_language_selector->addItem(QString::fromStdString(DiscIO::GetName(language, true)),
|
||||
static_cast<int>(language));
|
||||
if (language == preferred_language)
|
||||
m_language_selector->setCurrentIndex(m_language_selector->count() - 1);
|
||||
}
|
||||
if (m_language_selector->count() == 1)
|
||||
m_language_selector->setDisabled(true);
|
||||
|
||||
connect(m_language_selector,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&InfoWidget::ChangeLanguage);
|
||||
ChangeLanguage();
|
||||
}
|
||||
|
||||
void InfoWidget::ChangeLanguage()
|
||||
{
|
||||
DiscIO::Language language =
|
||||
static_cast<DiscIO::Language>(m_language_selector->currentData().toInt());
|
||||
m_name->setText(QString::fromStdString(m_game.GetLongName(language)));
|
||||
m_maker->setText(QString::fromStdString(m_game.GetLongMaker(language)));
|
||||
m_description->setText(QString::fromStdString(m_game.GetDescription(language)));
|
||||
}
|
||||
|
||||
QWidget* InfoWidget::CreateChecksumComputer()
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
QHBoxLayout* layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_checksum_result = new QLineEdit();
|
||||
QPushButton* calculate = new QPushButton(tr("Compute"));
|
||||
connect(calculate, &QPushButton::clicked, this, &InfoWidget::ComputeChecksum);
|
||||
layout->addWidget(m_checksum_result);
|
||||
layout->addWidget(calculate);
|
||||
|
||||
widget->setLayout(layout);
|
||||
return widget;
|
||||
}
|
||||
|
||||
void InfoWidget::ComputeChecksum()
|
||||
{
|
||||
QCryptographicHash hash(QCryptographicHash::Md5);
|
||||
hash.reset();
|
||||
std::unique_ptr<DiscIO::BlobReader> file(DiscIO::CreateBlobReader(m_game.GetFilePath()));
|
||||
std::vector<u8> file_data(8 * 1080 * 1080); // read 1MB at a time
|
||||
u64 game_size = file->GetDataSize();
|
||||
u64 read_offset = 0;
|
||||
|
||||
// a maximum of 1000 is used instead of game_size because otherwise 8GB games overflow the int
|
||||
// typed maximum parameter
|
||||
QProgressDialog* progress =
|
||||
new QProgressDialog(tr("Computing MD5 Checksum"), tr("Cancel"), 0, 1000, this);
|
||||
progress->setWindowTitle(tr("Computing MD5 Checksum"));
|
||||
progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
progress->setMinimumDuration(500);
|
||||
progress->setWindowModality(Qt::WindowModal);
|
||||
while (read_offset < game_size)
|
||||
{
|
||||
progress->setValue(static_cast<double>(read_offset) / static_cast<double>(game_size) * 1000);
|
||||
if (progress->wasCanceled())
|
||||
return;
|
||||
|
||||
u64 read_size = std::min<u64>(file_data.size(), game_size - read_offset);
|
||||
file->Read(read_offset, read_size, file_data.data());
|
||||
hash.addData(reinterpret_cast<char*>(file_data.data()), read_size);
|
||||
read_offset += read_size;
|
||||
}
|
||||
m_checksum_result->setText(QString::fromUtf8(hash.result().toHex()));
|
||||
Q_ASSERT(read_offset == game_size);
|
||||
progress->setValue(1000);
|
||||
}
|
44
Source/Core/DolphinQt/Config/InfoWidget.h
Normal file
44
Source/Core/DolphinQt/Config/InfoWidget.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2016 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QLineEdit;
|
||||
class QPixmap;
|
||||
class QTextEdit;
|
||||
|
||||
class InfoWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InfoWidget(const UICommon::GameFile& game);
|
||||
|
||||
private:
|
||||
void ComputeChecksum();
|
||||
void ChangeLanguage();
|
||||
void SaveBanner();
|
||||
|
||||
QGroupBox* CreateBannerDetails();
|
||||
QGroupBox* CreateISODetails();
|
||||
QLineEdit* CreateValueDisplay(const QString& value);
|
||||
QLineEdit* CreateValueDisplay(const std::string& value = "");
|
||||
QWidget* CreateChecksumComputer();
|
||||
void CreateLanguageSelector();
|
||||
QWidget* CreateBannerGraphic(const QPixmap& image);
|
||||
|
||||
UICommon::GameFile m_game;
|
||||
QLineEdit* m_checksum_result;
|
||||
QComboBox* m_language_selector;
|
||||
QLineEdit* m_name;
|
||||
QLineEdit* m_maker;
|
||||
QTextEdit* m_description;
|
||||
};
|
206
Source/Core/DolphinQt/Config/LogConfigWidget.cpp
Normal file
206
Source/Core/DolphinQt/Config/LogConfigWidget.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/LogConfigWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGroupBox>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/LogManager.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
LogConfigWidget::LogConfigWidget(QWidget* parent) : QDockWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Log Configuration"));
|
||||
setObjectName(QStringLiteral("logconfig"));
|
||||
|
||||
setHidden(!Settings::Instance().IsLogConfigVisible());
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
LogConfigWidget::~LogConfigWidget()
|
||||
{
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void LogConfigWidget::CreateWidgets()
|
||||
{
|
||||
auto* layout = new QVBoxLayout;
|
||||
|
||||
auto* verbosity = new QGroupBox(tr("Verbosity"));
|
||||
auto* verbosity_layout = new QVBoxLayout;
|
||||
verbosity->setLayout(verbosity_layout);
|
||||
m_verbosity_notice = new QRadioButton(tr("Notice"));
|
||||
m_verbosity_error = new QRadioButton(tr("Error"));
|
||||
m_verbosity_warning = new QRadioButton(tr("Warning"));
|
||||
m_verbosity_info = new QRadioButton(tr("Info"));
|
||||
|
||||
auto* outputs = new QGroupBox(tr("Logger Outputs"));
|
||||
auto* outputs_layout = new QVBoxLayout;
|
||||
outputs->setLayout(outputs_layout);
|
||||
m_out_file = new QCheckBox(tr("Write to File"));
|
||||
m_out_console = new QCheckBox(tr("Write to Console"));
|
||||
m_out_window = new QCheckBox(tr("Write to Window"));
|
||||
|
||||
auto* types = new QGroupBox(tr("Log Types"));
|
||||
auto* types_layout = new QVBoxLayout;
|
||||
types->setLayout(types_layout);
|
||||
m_types_toggle = new QPushButton(tr("Toggle All Log Types"));
|
||||
m_types_list = new QListWidget;
|
||||
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++)
|
||||
{
|
||||
QListWidgetItem* widget = new QListWidgetItem(QString::fromStdString(
|
||||
LogManager::GetInstance()->GetFullName(static_cast<LogTypes::LOG_TYPE>(i))));
|
||||
widget->setCheckState(Qt::Unchecked);
|
||||
m_types_list->addItem(widget);
|
||||
}
|
||||
|
||||
layout->addWidget(verbosity);
|
||||
verbosity_layout->addWidget(m_verbosity_notice);
|
||||
verbosity_layout->addWidget(m_verbosity_error);
|
||||
verbosity_layout->addWidget(m_verbosity_warning);
|
||||
verbosity_layout->addWidget(m_verbosity_info);
|
||||
|
||||
layout->addWidget(outputs);
|
||||
outputs_layout->addWidget(m_out_file);
|
||||
outputs_layout->addWidget(m_out_console);
|
||||
outputs_layout->addWidget(m_out_window);
|
||||
|
||||
layout->addWidget(types);
|
||||
types_layout->addWidget(m_types_toggle);
|
||||
types_layout->addWidget(m_types_list);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
widget->setLayout(layout);
|
||||
|
||||
setWidget(widget);
|
||||
}
|
||||
|
||||
void LogConfigWidget::ConnectWidgets()
|
||||
{
|
||||
// Configuration
|
||||
connect(m_verbosity_notice, &QRadioButton::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
connect(m_verbosity_error, &QRadioButton::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
connect(m_verbosity_warning, &QRadioButton::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
connect(m_verbosity_info, &QRadioButton::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
|
||||
connect(m_out_file, &QCheckBox::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
connect(m_out_console, &QCheckBox::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
connect(m_out_window, &QCheckBox::toggled, this, &LogConfigWidget::SaveSettings);
|
||||
|
||||
connect(m_types_toggle, &QPushButton::clicked, [this] {
|
||||
m_all_enabled = !m_all_enabled;
|
||||
|
||||
// Don't save every time we change an item
|
||||
m_block_save = true;
|
||||
|
||||
for (int i = 0; i < m_types_list->count(); i++)
|
||||
m_types_list->item(i)->setCheckState(m_all_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
|
||||
m_block_save = false;
|
||||
|
||||
SaveSettings();
|
||||
});
|
||||
|
||||
connect(m_types_list, &QListWidget::itemChanged, this, &LogConfigWidget::SaveSettings);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::LogConfigVisibilityChanged, this,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
}
|
||||
|
||||
void LogConfigWidget::LoadSettings()
|
||||
{
|
||||
auto* logmanager = LogManager::GetInstance();
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("logconfigwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("logconfigwidget/floating")).toBool());
|
||||
|
||||
// Config - Verbosity
|
||||
int verbosity = logmanager->GetLogLevel();
|
||||
m_verbosity_notice->setChecked(verbosity == 1);
|
||||
m_verbosity_error->setChecked(verbosity == 2);
|
||||
m_verbosity_warning->setChecked(verbosity == 3);
|
||||
m_verbosity_info->setChecked(verbosity == 4);
|
||||
|
||||
// Config - Outputs
|
||||
m_out_file->setChecked(logmanager->IsListenerEnabled(LogListener::FILE_LISTENER));
|
||||
m_out_console->setChecked(logmanager->IsListenerEnabled(LogListener::CONSOLE_LISTENER));
|
||||
m_out_window->setChecked(logmanager->IsListenerEnabled(LogListener::LOG_WINDOW_LISTENER));
|
||||
|
||||
// Config - Log Types
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
||||
{
|
||||
bool log_enabled = LogManager::GetInstance()->IsEnabled(static_cast<LogTypes::LOG_TYPE>(i));
|
||||
|
||||
if (!log_enabled)
|
||||
m_all_enabled = false;
|
||||
|
||||
m_types_list->item(i)->setCheckState(log_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
|
||||
void LogConfigWidget::SaveSettings()
|
||||
{
|
||||
if (m_block_save)
|
||||
return;
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("logconfigwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("logconfigwidget/floating"), isFloating());
|
||||
|
||||
// Config - Verbosity
|
||||
int verbosity = 1;
|
||||
|
||||
if (m_verbosity_notice->isChecked())
|
||||
verbosity = 1;
|
||||
|
||||
if (m_verbosity_error->isChecked())
|
||||
verbosity = 2;
|
||||
|
||||
if (m_verbosity_warning->isChecked())
|
||||
verbosity = 3;
|
||||
|
||||
if (m_verbosity_info->isChecked())
|
||||
verbosity = 4;
|
||||
|
||||
// Config - Verbosity
|
||||
LogManager::GetInstance()->SetLogLevel(static_cast<LogTypes::LOG_LEVELS>(verbosity));
|
||||
|
||||
// Config - Outputs
|
||||
LogManager::GetInstance()->EnableListener(LogListener::FILE_LISTENER, m_out_file->isChecked());
|
||||
LogManager::GetInstance()->EnableListener(LogListener::CONSOLE_LISTENER,
|
||||
m_out_console->isChecked());
|
||||
LogManager::GetInstance()->EnableListener(LogListener::LOG_WINDOW_LISTENER,
|
||||
m_out_window->isChecked());
|
||||
// Config - Log Types
|
||||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i)
|
||||
{
|
||||
const auto type = static_cast<LogTypes::LOG_TYPE>(i);
|
||||
bool enabled = m_types_list->item(i)->checkState() == Qt::Checked;
|
||||
bool was_enabled = LogManager::GetInstance()->IsEnabled(type);
|
||||
|
||||
if (enabled != was_enabled)
|
||||
LogManager::GetInstance()->SetEnable(type, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void LogConfigWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetLogConfigVisible(false);
|
||||
}
|
44
Source/Core/DolphinQt/Config/LogConfigWidget.h
Normal file
44
Source/Core/DolphinQt/Config/LogConfigWidget.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QCloseEvent;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QRadioButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
class LogConfigWidget final : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogConfigWidget(QWidget* parent = nullptr);
|
||||
~LogConfigWidget();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
QRadioButton* m_verbosity_notice;
|
||||
QRadioButton* m_verbosity_error;
|
||||
QRadioButton* m_verbosity_warning;
|
||||
QRadioButton* m_verbosity_info;
|
||||
QCheckBox* m_out_file;
|
||||
QCheckBox* m_out_console;
|
||||
QCheckBox* m_out_window;
|
||||
QPushButton* m_types_toggle;
|
||||
QListWidget* m_types_list;
|
||||
|
||||
bool m_all_enabled = true;
|
||||
bool m_block_save = false;
|
||||
};
|
222
Source/Core/DolphinQt/Config/LogWidget.cpp
Normal file
222
Source/Core/DolphinQt/Config/LogWidget.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/LogWidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QGroupBox>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QTextEdit>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
// Delay in ms between calls of UpdateLog()
|
||||
constexpr int UPDATE_LOG_DELAY = 100;
|
||||
// Maximum lines to process at a time
|
||||
constexpr int MAX_LOG_LINES = 200;
|
||||
// Timestamp length
|
||||
constexpr int TIMESTAMP_LENGTH = 10;
|
||||
|
||||
LogWidget::LogWidget(QWidget* parent) : QDockWidget(parent), m_timer(new QTimer(this))
|
||||
{
|
||||
setWindowTitle(tr("Log"));
|
||||
setObjectName(QStringLiteral("log"));
|
||||
|
||||
setHidden(!Settings::Instance().IsLogVisible());
|
||||
setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
CreateWidgets();
|
||||
LoadSettings();
|
||||
|
||||
ConnectWidgets();
|
||||
|
||||
connect(m_timer, &QTimer::timeout, this, &LogWidget::UpdateLog);
|
||||
m_timer->start(UPDATE_LOG_DELAY);
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &LogWidget::UpdateFont);
|
||||
|
||||
LogManager::GetInstance()->RegisterListener(LogListener::LOG_WINDOW_LISTENER, this);
|
||||
}
|
||||
|
||||
LogWidget::~LogWidget()
|
||||
{
|
||||
SaveSettings();
|
||||
|
||||
LogManager::GetInstance()->RegisterListener(LogListener::LOG_WINDOW_LISTENER, nullptr);
|
||||
}
|
||||
|
||||
void LogWidget::UpdateLog()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_log_mutex);
|
||||
|
||||
if (m_log_queue.empty())
|
||||
return;
|
||||
|
||||
auto* vscroll = m_log_text->verticalScrollBar();
|
||||
auto* hscroll = m_log_text->horizontalScrollBar();
|
||||
|
||||
// If the vertical scrollbar is within 50 units of the maximum value, count it as being at the
|
||||
// bottom
|
||||
bool vscroll_bottom = vscroll->maximum() - vscroll->value() < 50;
|
||||
|
||||
int old_horizontal = hscroll->value();
|
||||
int old_vertical = vscroll->value();
|
||||
|
||||
for (int i = 0; !m_log_queue.empty() && i < MAX_LOG_LINES; i++)
|
||||
{
|
||||
m_log_text->append(m_log_queue.front());
|
||||
m_log_queue.pop();
|
||||
}
|
||||
|
||||
if (hscroll->value() != old_horizontal)
|
||||
hscroll->setValue(old_horizontal);
|
||||
|
||||
if (vscroll->value() != old_vertical)
|
||||
{
|
||||
if (vscroll_bottom)
|
||||
vscroll->setValue(vscroll->maximum());
|
||||
else
|
||||
vscroll->setValue(old_vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void LogWidget::UpdateFont()
|
||||
{
|
||||
QFont f;
|
||||
|
||||
switch (m_log_font->currentIndex())
|
||||
{
|
||||
case 0: // Default font
|
||||
break;
|
||||
case 1: // Monospace font
|
||||
f = QFont(QStringLiteral("Monospace"));
|
||||
f.setStyleHint(QFont::TypeWriter);
|
||||
break;
|
||||
case 2: // Debugger font
|
||||
f = Settings::Instance().GetDebugFont();
|
||||
break;
|
||||
}
|
||||
m_log_text->setFont(f);
|
||||
}
|
||||
|
||||
void LogWidget::CreateWidgets()
|
||||
{
|
||||
// Log
|
||||
m_tab_log = new QWidget;
|
||||
m_log_text = new QTextEdit;
|
||||
m_log_wrap = new QCheckBox(tr("Word Wrap"));
|
||||
m_log_font = new QComboBox;
|
||||
m_log_clear = new QPushButton(tr("Clear"));
|
||||
|
||||
m_log_font->addItems({tr("Default Font"), tr("Monospaced Font"), tr("Selected Font")});
|
||||
|
||||
auto* log_layout = new QGridLayout;
|
||||
m_tab_log->setLayout(log_layout);
|
||||
log_layout->addWidget(m_log_wrap, 0, 0);
|
||||
log_layout->addWidget(m_log_font, 0, 1);
|
||||
log_layout->addWidget(m_log_clear, 0, 2);
|
||||
log_layout->addWidget(m_log_text, 1, 0, 1, -1);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
widget->setLayout(log_layout);
|
||||
|
||||
setWidget(widget);
|
||||
|
||||
m_log_text->setReadOnly(true);
|
||||
|
||||
QPalette palette = m_log_text->palette();
|
||||
palette.setColor(QPalette::Base, Qt::black);
|
||||
palette.setColor(QPalette::Text, Qt::white);
|
||||
m_log_text->setPalette(palette);
|
||||
}
|
||||
|
||||
void LogWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_log_clear, &QPushButton::clicked, m_log_text, &QTextEdit::clear);
|
||||
connect(m_log_wrap, &QCheckBox::toggled, this, &LogWidget::SaveSettings);
|
||||
connect(m_log_font, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&LogWidget::SaveSettings);
|
||||
connect(this, &QDockWidget::topLevelChanged, this, &LogWidget::SaveSettings);
|
||||
connect(&Settings::Instance(), &Settings::LogVisibilityChanged, this,
|
||||
[this](bool visible) { setHidden(!visible); });
|
||||
}
|
||||
|
||||
void LogWidget::LoadSettings()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
restoreGeometry(settings.value(QStringLiteral("logwidget/geometry")).toByteArray());
|
||||
setFloating(settings.value(QStringLiteral("logwidget/floating")).toBool());
|
||||
|
||||
// Log - Wrap Lines
|
||||
m_log_wrap->setChecked(settings.value(QStringLiteral("logging/wraplines")).toBool());
|
||||
m_log_text->setLineWrapMode(m_log_wrap->isChecked() ? QTextEdit::WidgetWidth : QTextEdit::NoWrap);
|
||||
|
||||
// Log - Font Selection
|
||||
// Currently "Debugger Font" is not supported as there is no Qt Debugger, defaulting to Monospace
|
||||
m_log_font->setCurrentIndex(std::min(settings.value(QStringLiteral("logging/font")).toInt(), 1));
|
||||
UpdateFont();
|
||||
}
|
||||
|
||||
void LogWidget::SaveSettings()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("logwidget/geometry"), saveGeometry());
|
||||
settings.setValue(QStringLiteral("logwidget/floating"), isFloating());
|
||||
|
||||
// Log - Wrap Lines
|
||||
settings.setValue(QStringLiteral("logging/wraplines"), m_log_wrap->isChecked());
|
||||
m_log_text->setLineWrapMode(m_log_wrap->isChecked() ? QTextEdit::WidgetWidth : QTextEdit::NoWrap);
|
||||
|
||||
// Log - Font Selection
|
||||
settings.setValue(QStringLiteral("logging/font"), m_log_font->currentIndex());
|
||||
UpdateFont();
|
||||
}
|
||||
|
||||
void LogWidget::Log(LogTypes::LOG_LEVELS level, const char* text)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_log_mutex);
|
||||
|
||||
const char* color = "white";
|
||||
|
||||
switch (level)
|
||||
{
|
||||
case LogTypes::LOG_LEVELS::LERROR:
|
||||
color = "red";
|
||||
break;
|
||||
case LogTypes::LOG_LEVELS::LWARNING:
|
||||
color = "yellow";
|
||||
break;
|
||||
case LogTypes::LOG_LEVELS::LNOTICE:
|
||||
color = "lime";
|
||||
break;
|
||||
case LogTypes::LOG_LEVELS::LINFO:
|
||||
color = "cyan";
|
||||
break;
|
||||
case LogTypes::LOG_LEVELS::LDEBUG:
|
||||
color = "lightgrey";
|
||||
break;
|
||||
}
|
||||
|
||||
m_log_queue.push(QStringLiteral("%1 <font color='%2'>%3</font>")
|
||||
.arg(QString::fromStdString(std::string(text).substr(0, TIMESTAMP_LENGTH)),
|
||||
QString::fromStdString(color),
|
||||
QString::fromStdString(std::string(text).substr(TIMESTAMP_LENGTH))));
|
||||
}
|
||||
|
||||
void LogWidget::closeEvent(QCloseEvent*)
|
||||
{
|
||||
Settings::Instance().SetLogVisible(false);
|
||||
}
|
54
Source/Core/DolphinQt/Config/LogWidget.h
Normal file
54
Source/Core/DolphinQt/Config/LogWidget.h
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "Common/Logging/LogManager.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QCloseEvent;
|
||||
class QComboBox;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
class QTextEdit;
|
||||
class QTimer;
|
||||
|
||||
class LogWidget final : public QDockWidget, LogListener
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogWidget(QWidget* parent = nullptr);
|
||||
~LogWidget();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
void UpdateLog();
|
||||
void UpdateFont();
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void Log(LogTypes::LOG_LEVELS level, const char* text) override;
|
||||
|
||||
// Log
|
||||
QCheckBox* m_log_wrap;
|
||||
QComboBox* m_log_font;
|
||||
QPushButton* m_log_clear;
|
||||
QVBoxLayout* m_main_layout;
|
||||
QTextEdit* m_log_text;
|
||||
QWidget* m_tab_log;
|
||||
|
||||
QTimer* m_timer;
|
||||
|
||||
std::mutex m_log_mutex;
|
||||
std::queue<QString> m_log_queue;
|
||||
};
|
59
Source/Core/DolphinQt/Config/Mapping/GCKeyboardEmu.cpp
Normal file
59
Source/Core/DolphinQt/Config/Mapping/GCKeyboardEmu.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCKeyboardEmu.h"
|
||||
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
#include "Core/HW/GCKeyboard.h"
|
||||
#include "Core/HW/GCKeyboardEmu.h"
|
||||
|
||||
GCKeyboardEmu::GCKeyboardEmu(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void GCKeyboardEmu::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb0x)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb1x)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb2x)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb3x)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb4x)));
|
||||
|
||||
auto* vbox_layout = new QVBoxLayout();
|
||||
vbox_layout->addWidget(
|
||||
CreateGroupBox(QStringLiteral(""), Keyboard::GetGroup(GetPort(), KeyboardGroup::Kb5x)));
|
||||
|
||||
m_main_layout->addLayout(vbox_layout);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void GCKeyboardEmu::LoadSettings()
|
||||
{
|
||||
Keyboard::LoadConfig();
|
||||
}
|
||||
|
||||
void GCKeyboardEmu::SaveSettings()
|
||||
{
|
||||
Keyboard::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* GCKeyboardEmu::GetConfig()
|
||||
{
|
||||
return Keyboard::GetConfig();
|
||||
}
|
31
Source/Core/DolphinQt/Config/Mapping/GCKeyboardEmu.h
Normal file
31
Source/Core/DolphinQt/Config/Mapping/GCKeyboardEmu.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QFormLayout;
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
class GCKeyboardEmu final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GCKeyboardEmu(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
45
Source/Core/DolphinQt/Config/Mapping/GCMicrophone.cpp
Normal file
45
Source/Core/DolphinQt/Config/Mapping/GCMicrophone.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCMicrophone.h"
|
||||
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/GCPadEmu.h"
|
||||
|
||||
GCMicrophone::GCMicrophone(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void GCMicrophone::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Microphone"), Pad::GetGroup(GetPort(), PadGroup::Mic)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void GCMicrophone::LoadSettings()
|
||||
{
|
||||
Pad::LoadConfig();
|
||||
}
|
||||
|
||||
void GCMicrophone::SaveSettings()
|
||||
{
|
||||
Pad::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* GCMicrophone::GetConfig()
|
||||
{
|
||||
return Pad::GetConfig();
|
||||
}
|
30
Source/Core/DolphinQt/Config/Mapping/GCMicrophone.h
Normal file
30
Source/Core/DolphinQt/Config/Mapping/GCMicrophone.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QFormLayout;
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
class GCMicrophone final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GCMicrophone(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
63
Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp
Normal file
63
Source/Core/DolphinQt/Config/Mapping/GCPadEmu.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCPadEmu.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HW/GCPad.h"
|
||||
#include "Core/HW/GCPadEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
GCPadEmu::GCPadEmu(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void GCPadEmu::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Buttons"), Pad::GetGroup(GetPort(), PadGroup::Buttons)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Control Stick"), Pad::GetGroup(GetPort(), PadGroup::MainStick)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("C Stick"), Pad::GetGroup(GetPort(), PadGroup::CStick)));
|
||||
m_main_layout->addWidget(CreateGroupBox(tr("D-Pad"), Pad::GetGroup(GetPort(), PadGroup::DPad)));
|
||||
|
||||
auto* hbox_layout = new QVBoxLayout();
|
||||
|
||||
m_main_layout->addLayout(hbox_layout);
|
||||
|
||||
hbox_layout->addWidget(
|
||||
CreateGroupBox(tr("Triggers"), Pad::GetGroup(GetPort(), PadGroup::Triggers)));
|
||||
hbox_layout->addWidget(CreateGroupBox(tr("Rumble"), Pad::GetGroup(GetPort(), PadGroup::Rumble)));
|
||||
|
||||
hbox_layout->addWidget(
|
||||
CreateGroupBox(tr("Options"), Pad::GetGroup(GetPort(), PadGroup::Options)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void GCPadEmu::LoadSettings()
|
||||
{
|
||||
Pad::LoadConfig();
|
||||
}
|
||||
|
||||
void GCPadEmu::SaveSettings()
|
||||
{
|
||||
Pad::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* GCPadEmu::GetConfig()
|
||||
{
|
||||
return Pad::GetConfig();
|
||||
}
|
30
Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h
Normal file
30
Source/Core/DolphinQt/Config/Mapping/GCPadEmu.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QFormLayout;
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
class GCPadEmu final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GCPadEmu(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
|
||||
GCPadWiiUConfigDialog::GCPadWiiUConfigDialog(int port, QWidget* parent)
|
||||
: QDialog(parent), m_port{port}
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateLayout();
|
||||
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
}
|
||||
|
||||
void GCPadWiiUConfigDialog::CreateLayout()
|
||||
{
|
||||
setWindowTitle(tr("GameCube Adapter for Wii U at Port %1").arg(m_port + 1));
|
||||
|
||||
const bool detected = GCAdapter::IsDetected();
|
||||
m_layout = new QVBoxLayout();
|
||||
m_status_label = new QLabel(detected ? tr("Adapter Detected") : tr("No Adapter Detected"));
|
||||
m_rumble = new QCheckBox(tr("Enable Rumble"));
|
||||
m_simulate_bongos = new QCheckBox(tr("Simulate DK Bongos"));
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
|
||||
m_layout->addWidget(m_status_label);
|
||||
m_layout->addWidget(m_rumble);
|
||||
m_layout->addWidget(m_simulate_bongos);
|
||||
m_layout->addWidget(m_button_box);
|
||||
|
||||
if (!detected)
|
||||
{
|
||||
m_rumble->setEnabled(false);
|
||||
m_simulate_bongos->setEnabled(false);
|
||||
}
|
||||
|
||||
setLayout(m_layout);
|
||||
}
|
||||
|
||||
void GCPadWiiUConfigDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_rumble, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings);
|
||||
connect(m_simulate_bongos, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings);
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &GCPadWiiUConfigDialog::accept);
|
||||
}
|
||||
|
||||
void GCPadWiiUConfigDialog::LoadSettings()
|
||||
{
|
||||
m_rumble->setChecked(SConfig::GetInstance().m_AdapterRumble[m_port]);
|
||||
m_simulate_bongos->setChecked(SConfig::GetInstance().m_AdapterKonga[m_port]);
|
||||
}
|
||||
|
||||
void GCPadWiiUConfigDialog::SaveSettings()
|
||||
{
|
||||
SConfig::GetInstance().m_AdapterRumble[m_port] = m_rumble->isChecked();
|
||||
SConfig::GetInstance().m_AdapterKonga[m_port] = m_simulate_bongos->isChecked();
|
||||
}
|
36
Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h
Normal file
36
Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QDialogButtonBox;
|
||||
class QVBoxLayout;
|
||||
|
||||
class GCPadWiiUConfigDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GCPadWiiUConfigDialog(int port, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void CreateLayout();
|
||||
void ConnectWidgets();
|
||||
|
||||
int m_port;
|
||||
|
||||
QVBoxLayout* m_layout;
|
||||
QLabel* m_status_label;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
// Checkboxes
|
||||
QCheckBox* m_rumble;
|
||||
QCheckBox* m_simulate_bongos;
|
||||
};
|
42
Source/Core/DolphinQt/Config/Mapping/Hotkey3D.cpp
Normal file
42
Source/Core/DolphinQt/Config/Mapping/Hotkey3D.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/Hotkey3D.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
Hotkey3D::Hotkey3D(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void Hotkey3D::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("3D"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_3D_TOGGLE)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("3D Depth"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_3D_DEPTH)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* Hotkey3D::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void Hotkey3D::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void Hotkey3D::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/Hotkey3D.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/Hotkey3D.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class Hotkey3D final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Hotkey3D(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
47
Source/Core/DolphinQt/Config/Mapping/HotkeyDebugging.cpp
Normal file
47
Source/Core/DolphinQt/Config/Mapping/HotkeyDebugging.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyDebugging.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyDebugging::HotkeyDebugging(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyDebugging::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Stepping"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_STEPPING)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(tr("Program Counter"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_PC)));
|
||||
vbox->addWidget(
|
||||
CreateGroupBox(tr("Breakpoint"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_BREAKPOINT)));
|
||||
m_main_layout->addLayout(vbox);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyDebugging::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyDebugging::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyDebugging::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyDebugging.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyDebugging.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyDebugging final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyDebugging(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
47
Source/Core/DolphinQt/Config/Mapping/HotkeyGeneral.cpp
Normal file
47
Source/Core/DolphinQt/Config/Mapping/HotkeyGeneral.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyGeneral.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyGeneral::HotkeyGeneral(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyGeneral::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("General"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_GENERAL)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(tr("Volume"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_VOLUME)));
|
||||
vbox->addWidget(
|
||||
CreateGroupBox(tr("Emulation Speed"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_SPEED)));
|
||||
m_main_layout->addLayout(vbox);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyGeneral::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyGeneral::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyGeneral::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyGeneral.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyGeneral.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyGeneral final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyGeneral(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
48
Source/Core/DolphinQt/Config/Mapping/HotkeyGraphics.cpp
Normal file
48
Source/Core/DolphinQt/Config/Mapping/HotkeyGraphics.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyGraphics.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyGraphics::HotkeyGraphics(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyGraphics::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Freelook"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_FREELOOK)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(tr("Graphics Toggles"),
|
||||
HotkeyManagerEmu::GetHotkeyGroup(HKGP_GRAPHICS_TOGGLES)));
|
||||
vbox->addWidget(
|
||||
CreateGroupBox(tr("Internal Resolution"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_IR)));
|
||||
m_main_layout->addLayout(vbox);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyGraphics::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyGraphics::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyGraphics::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyGraphics.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyGraphics.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyGraphics final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyGraphics(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
42
Source/Core/DolphinQt/Config/Mapping/HotkeyStates.cpp
Normal file
42
Source/Core/DolphinQt/Config/Mapping/HotkeyStates.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyStates.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyStates::HotkeyStates(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyStates::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Save"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_SAVE_STATE)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Load"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_LOAD_STATE)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyStates::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyStates::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyStates::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyStates.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyStates.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyStates final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyStates(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
44
Source/Core/DolphinQt/Config/Mapping/HotkeyStatesOther.cpp
Normal file
44
Source/Core/DolphinQt/Config/Mapping/HotkeyStatesOther.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyStatesOther.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyStatesOther::HotkeyStatesOther(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyStatesOther::CreateMainLayout()
|
||||
{
|
||||
auto* layout = new QHBoxLayout;
|
||||
|
||||
layout->addWidget(
|
||||
CreateGroupBox(tr("Select Last State"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_SELECT_STATE)));
|
||||
layout->addWidget(CreateGroupBox(tr("Load Last State"),
|
||||
HotkeyManagerEmu::GetHotkeyGroup(HKGP_LOAD_LAST_STATE)));
|
||||
layout->addWidget(
|
||||
CreateGroupBox(tr("Other State Hotkeys"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_STATE_MISC)));
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyStatesOther::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyStatesOther::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyStatesOther::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
23
Source/Core/DolphinQt/Config/Mapping/HotkeyStatesOther.h
Normal file
23
Source/Core/DolphinQt/Config/Mapping/HotkeyStatesOther.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyStatesOther final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyStatesOther(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
};
|
42
Source/Core/DolphinQt/Config/Mapping/HotkeyTAS.cpp
Normal file
42
Source/Core/DolphinQt/Config/Mapping/HotkeyTAS.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyTAS.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyTAS::HotkeyTAS(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyTAS::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Frame Advance"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_FRAME_ADVANCE)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("Movie"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_MOVIE)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyTAS::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyTAS::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyTAS::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyTAS.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyTAS.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyTAS final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyTAS(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
39
Source/Core/DolphinQt/Config/Mapping/HotkeyWii.cpp
Normal file
39
Source/Core/DolphinQt/Config/Mapping/HotkeyWii.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/HotkeyWii.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "Core/HotkeyManager.h"
|
||||
|
||||
HotkeyWii::HotkeyWii(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void HotkeyWii::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(CreateGroupBox(tr("Wii"), HotkeyManagerEmu::GetHotkeyGroup(HKGP_WII)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
InputConfig* HotkeyWii::GetConfig()
|
||||
{
|
||||
return HotkeyManagerEmu::GetConfig();
|
||||
}
|
||||
|
||||
void HotkeyWii::LoadSettings()
|
||||
{
|
||||
HotkeyManagerEmu::LoadConfig();
|
||||
}
|
||||
|
||||
void HotkeyWii::SaveSettings()
|
||||
{
|
||||
HotkeyManagerEmu::GetConfig()->SaveConfig();
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/HotkeyWii.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/HotkeyWii.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QHBoxLayout;
|
||||
|
||||
class HotkeyWii final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HotkeyWii(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
262
Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp
Normal file
262
Source/Core/DolphinQt/Config/Mapping/IOWindow.cpp
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
constexpr int SLIDER_TICK_COUNT = 100;
|
||||
|
||||
IOWindow::IOWindow(QWidget* parent, ControllerEmu::EmulatedController* controller,
|
||||
ControlReference* ref, IOWindow::Type type)
|
||||
: QDialog(parent), m_reference(ref), m_controller(controller), m_type(type)
|
||||
{
|
||||
CreateMainLayout();
|
||||
ConnectWidgets();
|
||||
|
||||
setWindowTitle(type == IOWindow::Type::Input ? tr("Configure Input") : tr("Configure Output"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void IOWindow::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QVBoxLayout();
|
||||
|
||||
m_devices_combo = new QComboBox();
|
||||
m_option_list = new QListWidget();
|
||||
m_select_button = new QPushButton(tr("Select"));
|
||||
m_detect_button = new QPushButton(tr("Detect"));
|
||||
m_or_button = new QPushButton(tr("| OR"));
|
||||
m_and_button = new QPushButton(tr("&& AND"));
|
||||
m_add_button = new QPushButton(tr("+ ADD"));
|
||||
m_not_button = new QPushButton(tr("! NOT"));
|
||||
m_test_button = new QPushButton(tr("Test"));
|
||||
m_expression_text = new QPlainTextEdit();
|
||||
m_button_box = new QDialogButtonBox();
|
||||
m_clear_button = new QPushButton(tr("Clear"));
|
||||
m_apply_button = new QPushButton(tr("Apply"));
|
||||
m_range_slider = new QSlider(Qt::Horizontal);
|
||||
m_range_spinbox = new QSpinBox();
|
||||
|
||||
// Devices
|
||||
m_main_layout->addWidget(m_devices_combo);
|
||||
|
||||
// Range
|
||||
auto* range_hbox = new QHBoxLayout();
|
||||
range_hbox->addWidget(new QLabel(tr("Range")));
|
||||
range_hbox->addWidget(m_range_slider);
|
||||
range_hbox->addWidget(m_range_spinbox);
|
||||
m_range_slider->setMinimum(-500);
|
||||
m_range_slider->setMaximum(500);
|
||||
m_range_spinbox->setMinimum(-500);
|
||||
m_range_spinbox->setMaximum(500);
|
||||
m_main_layout->addLayout(range_hbox);
|
||||
|
||||
// Options (Buttons, Outputs) and action buttons
|
||||
for (QPushButton* button : {m_select_button, m_detect_button, m_or_button, m_and_button,
|
||||
m_add_button, m_not_button, m_test_button})
|
||||
{
|
||||
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
}
|
||||
|
||||
auto* hbox = new QHBoxLayout();
|
||||
auto* button_vbox = new QVBoxLayout();
|
||||
hbox->addWidget(m_option_list, 8);
|
||||
hbox->addLayout(button_vbox, 1);
|
||||
|
||||
button_vbox->addWidget(m_select_button);
|
||||
button_vbox->addWidget(m_type == Type::Input ? m_detect_button : m_test_button);
|
||||
button_vbox->addWidget(m_or_button);
|
||||
|
||||
if (m_type == Type::Input)
|
||||
{
|
||||
button_vbox->addWidget(m_and_button);
|
||||
button_vbox->addWidget(m_add_button);
|
||||
button_vbox->addWidget(m_not_button);
|
||||
}
|
||||
|
||||
m_main_layout->addLayout(hbox, 2);
|
||||
m_main_layout->addWidget(m_expression_text, 1);
|
||||
|
||||
// Button Box
|
||||
m_main_layout->addWidget(m_button_box);
|
||||
m_button_box->addButton(m_clear_button, QDialogButtonBox::ActionRole);
|
||||
m_button_box->addButton(m_apply_button, QDialogButtonBox::ActionRole);
|
||||
m_button_box->addButton(QDialogButtonBox::Ok);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void IOWindow::Update()
|
||||
{
|
||||
m_expression_text->setPlainText(QString::fromStdString(m_reference->GetExpression()));
|
||||
m_range_spinbox->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
||||
m_range_slider->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
||||
|
||||
m_devq = m_controller->GetDefaultDevice();
|
||||
|
||||
UpdateDeviceList();
|
||||
UpdateOptionList();
|
||||
}
|
||||
|
||||
void IOWindow::ConnectWidgets()
|
||||
{
|
||||
connect(m_select_button, &QPushButton::clicked, [this] { AppendSelectedOption(""); });
|
||||
connect(m_add_button, &QPushButton::clicked, [this] { AppendSelectedOption(" + "); });
|
||||
connect(m_and_button, &QPushButton::clicked, [this] { AppendSelectedOption(" & "); });
|
||||
connect(m_or_button, &QPushButton::clicked, [this] { AppendSelectedOption(" | "); });
|
||||
connect(m_not_button, &QPushButton::clicked, [this] { AppendSelectedOption("!"); });
|
||||
|
||||
connect(m_type == IOWindow::Type::Input ? m_detect_button : m_test_button, &QPushButton::clicked,
|
||||
this, &IOWindow::OnDetectButtonPressed);
|
||||
|
||||
connect(m_button_box, &QDialogButtonBox::clicked, this, &IOWindow::OnDialogButtonPressed);
|
||||
connect(m_devices_combo, &QComboBox::currentTextChanged, this, &IOWindow::OnDeviceChanged);
|
||||
connect(m_range_spinbox, static_cast<void (QSpinBox::*)(int value)>(&QSpinBox::valueChanged),
|
||||
this, &IOWindow::OnRangeChanged);
|
||||
connect(m_range_slider, static_cast<void (QSlider::*)(int value)>(&QSlider::valueChanged), this,
|
||||
&IOWindow::OnRangeChanged);
|
||||
}
|
||||
|
||||
void IOWindow::AppendSelectedOption(const std::string& prefix)
|
||||
{
|
||||
if (m_option_list->currentItem() == nullptr)
|
||||
return;
|
||||
|
||||
m_expression_text->insertPlainText(
|
||||
QString::fromStdString(prefix) +
|
||||
MappingCommon::GetExpressionForControl(m_option_list->currentItem()->text(), m_devq,
|
||||
m_controller->GetDefaultDevice()));
|
||||
}
|
||||
|
||||
void IOWindow::OnDeviceChanged(const QString& device)
|
||||
{
|
||||
m_devq.FromString(device.toStdString());
|
||||
UpdateOptionList();
|
||||
}
|
||||
|
||||
void IOWindow::OnDialogButtonPressed(QAbstractButton* button)
|
||||
{
|
||||
if (button == m_clear_button)
|
||||
{
|
||||
m_expression_text->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_reference->SetExpression(m_expression_text->toPlainText().toStdString());
|
||||
|
||||
if (button != m_apply_button)
|
||||
accept();
|
||||
}
|
||||
|
||||
void IOWindow::OnDetectButtonPressed()
|
||||
{
|
||||
installEventFilter(BlockUserInputFilter::Instance());
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
|
||||
std::thread([this] {
|
||||
auto* btn = m_type == IOWindow::Type::Input ? m_detect_button : m_test_button;
|
||||
const auto old_label = btn->text();
|
||||
|
||||
btn->setText(QStringLiteral("..."));
|
||||
|
||||
const auto expr = MappingCommon::DetectExpression(
|
||||
m_reference, g_controller_interface.FindDevice(m_devq).get(), m_devq);
|
||||
|
||||
btn->setText(old_label);
|
||||
|
||||
if (!expr.isEmpty())
|
||||
{
|
||||
const auto list = m_option_list->findItems(expr, Qt::MatchFixedString);
|
||||
|
||||
if (list.size() > 0)
|
||||
m_option_list->setCurrentItem(list[0]);
|
||||
}
|
||||
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
removeEventFilter(BlockUserInputFilter::Instance());
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
void IOWindow::OnRangeChanged(int value)
|
||||
{
|
||||
m_reference->range = static_cast<double>(value) / SLIDER_TICK_COUNT;
|
||||
m_range_spinbox->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
||||
m_range_slider->setValue(m_reference->range * SLIDER_TICK_COUNT);
|
||||
}
|
||||
|
||||
void IOWindow::UpdateOptionList()
|
||||
{
|
||||
m_option_list->clear();
|
||||
|
||||
const auto device = g_controller_interface.FindDevice(m_devq);
|
||||
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
if (m_reference->IsInput())
|
||||
{
|
||||
for (const auto* input : device->Inputs())
|
||||
{
|
||||
m_option_list->addItem(QString::fromStdString(input->GetName()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto* output : device->Outputs())
|
||||
{
|
||||
m_option_list->addItem(QString::fromStdString(output->GetName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IOWindow::UpdateDeviceList()
|
||||
{
|
||||
m_devices_combo->clear();
|
||||
|
||||
Core::RunAsCPUThread([&] {
|
||||
g_controller_interface.RefreshDevices();
|
||||
m_controller->UpdateReferences(g_controller_interface);
|
||||
|
||||
// Adding default device regardless if it's currently connected or not
|
||||
const auto default_device = m_controller->GetDefaultDevice().ToString();
|
||||
|
||||
m_devices_combo->addItem(QString::fromStdString(default_device));
|
||||
|
||||
for (const auto& name : g_controller_interface.GetAllDeviceStrings())
|
||||
{
|
||||
if (name != default_device)
|
||||
m_devices_combo->addItem(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
m_devices_combo->setCurrentIndex(0);
|
||||
});
|
||||
}
|
96
Source/Core/DolphinQt/Config/Mapping/IOWindow.h
Normal file
96
Source/Core/DolphinQt/Config/Mapping/IOWindow.h
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
|
||||
class ControlReference;
|
||||
class QAbstractButton;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QListWidget;
|
||||
class QVBoxLayout;
|
||||
class QWidget;
|
||||
class QPlainTextEdit;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QSpinBox;
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class EmulatedController;
|
||||
}
|
||||
|
||||
class IOWindow final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
};
|
||||
|
||||
explicit IOWindow(QWidget* parent, ControllerEmu::EmulatedController* m_controller,
|
||||
ControlReference* ref, Type type);
|
||||
|
||||
private:
|
||||
void CreateMainLayout();
|
||||
void ConnectWidgets();
|
||||
void Update();
|
||||
|
||||
void OnDialogButtonPressed(QAbstractButton* button);
|
||||
void OnDeviceChanged(const QString& device);
|
||||
void OnDetectButtonPressed();
|
||||
void OnRangeChanged(int range);
|
||||
|
||||
void AppendSelectedOption(const std::string& prefix);
|
||||
void UpdateOptionList();
|
||||
void UpdateDeviceList();
|
||||
|
||||
// Main Layout
|
||||
QVBoxLayout* m_main_layout;
|
||||
|
||||
// Devices
|
||||
QComboBox* m_devices_combo;
|
||||
|
||||
// Options
|
||||
QListWidget* m_option_list;
|
||||
|
||||
// Range
|
||||
QSlider* m_range_slider;
|
||||
QSpinBox* m_range_spinbox;
|
||||
|
||||
// Shared actions
|
||||
QPushButton* m_select_button;
|
||||
QPushButton* m_or_button;
|
||||
|
||||
// Input actions
|
||||
QPushButton* m_detect_button;
|
||||
QPushButton* m_and_button;
|
||||
QPushButton* m_not_button;
|
||||
QPushButton* m_add_button;
|
||||
|
||||
// Output actions
|
||||
QPushButton* m_test_button;
|
||||
|
||||
// Textarea
|
||||
QPlainTextEdit* m_expression_text;
|
||||
|
||||
// Buttonbox
|
||||
QDialogButtonBox* m_button_box;
|
||||
QPushButton* m_clear_button;
|
||||
QPushButton* m_apply_button;
|
||||
|
||||
ControlReference* m_reference;
|
||||
ControllerEmu::EmulatedController* m_controller;
|
||||
|
||||
ciface::Core::DeviceQualifier m_devq;
|
||||
Type m_type;
|
||||
};
|
39
Source/Core/DolphinQt/Config/Mapping/MappingBool.cpp
Normal file
39
Source/Core/DolphinQt/Config/Mapping/MappingBool.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingBool.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
MappingBool::MappingBool(MappingWidget* widget, ControllerEmu::BooleanSetting* setting)
|
||||
: QCheckBox(tr(setting->m_ui_name.c_str())), m_parent(widget), m_setting(setting)
|
||||
{
|
||||
Update();
|
||||
Connect();
|
||||
}
|
||||
|
||||
void MappingBool::Connect()
|
||||
{
|
||||
connect(this, &QCheckBox::stateChanged, this, [this](int value) {
|
||||
m_setting->SetValue(value);
|
||||
m_parent->SaveSettings();
|
||||
m_parent->GetController()->UpdateReferences(g_controller_interface);
|
||||
});
|
||||
}
|
||||
|
||||
void MappingBool::Clear()
|
||||
{
|
||||
m_setting->SetValue(false);
|
||||
m_parent->SaveSettings();
|
||||
Update();
|
||||
}
|
||||
|
||||
void MappingBool::Update()
|
||||
{
|
||||
setChecked(m_setting->GetValue());
|
||||
}
|
29
Source/Core/DolphinQt/Config/Mapping/MappingBool.h
Normal file
29
Source/Core/DolphinQt/Config/Mapping/MappingBool.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
class MappingWidget;
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class BooleanSetting;
|
||||
};
|
||||
|
||||
class MappingBool : public QCheckBox
|
||||
{
|
||||
public:
|
||||
MappingBool(MappingWidget* widget, ControllerEmu::BooleanSetting* setting);
|
||||
|
||||
void Clear();
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
|
||||
MappingWidget* m_parent;
|
||||
ControllerEmu::BooleanSetting* m_setting;
|
||||
};
|
235
Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp
Normal file
235
Source/Core/DolphinQt/Config/Mapping/MappingButton.cpp
Normal file
@ -0,0 +1,235 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFontMetrics>
|
||||
#include <QMouseEvent>
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingButton.h"
|
||||
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
|
||||
constexpr int SLIDER_TICK_COUNT = 100;
|
||||
constexpr int VERTICAL_PADDING = 2;
|
||||
|
||||
static QString EscapeAmpersand(QString&& string)
|
||||
{
|
||||
return string.replace(QStringLiteral("&"), QStringLiteral("&&"));
|
||||
}
|
||||
|
||||
bool MappingButton::IsInput() const
|
||||
{
|
||||
return m_reference->IsInput();
|
||||
}
|
||||
|
||||
MappingButton::MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator)
|
||||
: ElidedButton(EscapeAmpersand(QString::fromStdString(ref->GetExpression()))), m_parent(widget),
|
||||
m_reference(ref)
|
||||
{
|
||||
// Force all mapping buttons to use stay at a minimal height
|
||||
int height = QFontMetrics(qApp->font()).height() + 2 * VERTICAL_PADDING;
|
||||
|
||||
setMinimumHeight(height);
|
||||
|
||||
// macOS needs some wiggle room to always get round buttons
|
||||
setMaximumHeight(height + 8);
|
||||
|
||||
// Make sure that long entries don't throw our layout out of whack
|
||||
setMaximumWidth(115);
|
||||
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
Connect();
|
||||
setToolTip(
|
||||
tr("Left-click to detect input.\nMiddle-click to clear.\nRight-click for more options."));
|
||||
if (!m_reference->IsInput() || !indicator)
|
||||
return;
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
connect(m_timer, &QTimer::timeout, this, [this] {
|
||||
if (!isActiveWindow())
|
||||
return;
|
||||
|
||||
Settings::Instance().SetControllerStateNeeded(true);
|
||||
|
||||
if (Core::GetState() == Core::State::Uninitialized || Core::GetState() == Core::State::Paused)
|
||||
g_controller_interface.UpdateInput();
|
||||
|
||||
auto state = m_reference->State();
|
||||
|
||||
QFont f = m_parent->font();
|
||||
QPalette p = m_parent->palette();
|
||||
|
||||
if (state != 0)
|
||||
{
|
||||
f.setBold(true);
|
||||
p.setColor(QPalette::ButtonText, Qt::red);
|
||||
}
|
||||
|
||||
setFont(f);
|
||||
setPalette(p);
|
||||
|
||||
Settings::Instance().SetControllerStateNeeded(false);
|
||||
});
|
||||
|
||||
m_timer->start(1000 / 30);
|
||||
}
|
||||
|
||||
void MappingButton::Connect()
|
||||
{
|
||||
connect(this, &MappingButton::pressed, this, &MappingButton::Detect);
|
||||
}
|
||||
|
||||
void MappingButton::Detect()
|
||||
{
|
||||
if (m_parent->GetDevice() == nullptr || !m_reference->IsInput())
|
||||
return;
|
||||
|
||||
installEventFilter(BlockUserInputFilter::Instance());
|
||||
grabKeyboard();
|
||||
|
||||
// Make sure that we don't block event handling
|
||||
std::thread thread([this] {
|
||||
setText(QStringLiteral("..."));
|
||||
|
||||
// Avoid that the button press itself is registered as an event
|
||||
Common::SleepCurrentThread(100);
|
||||
|
||||
std::vector<std::future<std::pair<QString, QString>>> futures;
|
||||
QString expr;
|
||||
|
||||
if (m_parent->GetParent()->IsMappingAllDevices())
|
||||
{
|
||||
for (const std::string& device_str : g_controller_interface.GetAllDeviceStrings())
|
||||
{
|
||||
ciface::Core::DeviceQualifier devq;
|
||||
devq.FromString(device_str);
|
||||
|
||||
auto dev = g_controller_interface.FindDevice(devq);
|
||||
|
||||
auto future = std::async(std::launch::async, [this, devq, dev, device_str] {
|
||||
return std::make_pair(
|
||||
QString::fromStdString(device_str),
|
||||
MappingCommon::DetectExpression(m_reference, dev.get(),
|
||||
m_parent->GetController()->GetDefaultDevice()));
|
||||
});
|
||||
|
||||
futures.push_back(std::move(future));
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
for (auto& future : futures)
|
||||
{
|
||||
const auto status = future.wait_for(std::chrono::milliseconds(10));
|
||||
if (status == std::future_status::ready)
|
||||
{
|
||||
const auto pair = future.get();
|
||||
|
||||
done = true;
|
||||
|
||||
if (pair.second.isEmpty())
|
||||
break;
|
||||
|
||||
expr = QStringLiteral("`%1:%2`")
|
||||
.arg(pair.first)
|
||||
.arg(pair.second.startsWith(QLatin1Char('`')) ? pair.second.mid(1) :
|
||||
pair.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto dev = m_parent->GetDevice();
|
||||
expr = MappingCommon::DetectExpression(m_reference, dev.get(),
|
||||
m_parent->GetController()->GetDefaultDevice());
|
||||
}
|
||||
|
||||
releaseKeyboard();
|
||||
removeEventFilter(BlockUserInputFilter::Instance());
|
||||
|
||||
if (!expr.isEmpty())
|
||||
{
|
||||
m_reference->SetExpression(expr.toStdString());
|
||||
m_parent->SaveSettings();
|
||||
Update();
|
||||
m_parent->GetController()->UpdateReferences(g_controller_interface);
|
||||
|
||||
if (m_parent->IsIterativeInput())
|
||||
m_parent->NextButton(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnButtonTimeout();
|
||||
}
|
||||
});
|
||||
|
||||
thread.detach();
|
||||
}
|
||||
|
||||
void MappingButton::OnButtonTimeout()
|
||||
{
|
||||
setText(EscapeAmpersand(QString::fromStdString(m_reference->GetExpression())));
|
||||
}
|
||||
|
||||
void MappingButton::Clear()
|
||||
{
|
||||
m_reference->SetExpression("");
|
||||
m_reference->range = 100.0 / SLIDER_TICK_COUNT;
|
||||
m_parent->SaveSettings();
|
||||
Update();
|
||||
}
|
||||
|
||||
void MappingButton::Update()
|
||||
{
|
||||
const auto lock = ControllerEmu::EmulatedController::GetStateLock();
|
||||
m_reference->UpdateReference(g_controller_interface,
|
||||
m_parent->GetController()->GetDefaultDevice());
|
||||
setText(EscapeAmpersand(QString::fromStdString(m_reference->GetExpression())));
|
||||
}
|
||||
|
||||
void MappingButton::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
switch (event->button())
|
||||
{
|
||||
case Qt::MouseButton::LeftButton:
|
||||
if (m_reference->IsInput())
|
||||
QPushButton::mouseReleaseEvent(event);
|
||||
else
|
||||
emit AdvancedPressed();
|
||||
return;
|
||||
case Qt::MouseButton::MidButton:
|
||||
Clear();
|
||||
return;
|
||||
case Qt::MouseButton::RightButton:
|
||||
emit AdvancedPressed();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
39
Source/Core/DolphinQt/Config/Mapping/MappingButton.h
Normal file
39
Source/Core/DolphinQt/Config/Mapping/MappingButton.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/Flag.h"
|
||||
#include "DolphinQt/QtUtils/ElidedButton.h"
|
||||
|
||||
class ControlReference;
|
||||
class MappingWidget;
|
||||
class QEvent;
|
||||
class QMouseEvent;
|
||||
class QTimer;
|
||||
|
||||
class MappingButton : public ElidedButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MappingButton(MappingWidget* widget, ControlReference* ref, bool indicator);
|
||||
|
||||
void Clear();
|
||||
void Update();
|
||||
void Detect();
|
||||
bool IsInput() const;
|
||||
|
||||
signals:
|
||||
void AdvancedPressed();
|
||||
|
||||
private:
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
void OnButtonTimeout();
|
||||
void Connect();
|
||||
|
||||
MappingWidget* m_parent;
|
||||
ControlReference* m_reference;
|
||||
QTimer* m_timer;
|
||||
};
|
49
Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp
Normal file
49
Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingCommon.h"
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
|
||||
namespace MappingCommon
|
||||
{
|
||||
QString GetExpressionForControl(const QString& control_name,
|
||||
const ciface::Core::DeviceQualifier& control_device,
|
||||
const ciface::Core::DeviceQualifier& default_device)
|
||||
{
|
||||
QString expr;
|
||||
|
||||
// non-default device
|
||||
if (control_device != default_device)
|
||||
{
|
||||
expr += QString::fromStdString(control_device.ToString());
|
||||
expr += QStringLiteral(":");
|
||||
}
|
||||
|
||||
// append the control name
|
||||
expr += control_name;
|
||||
|
||||
QRegExp reg(QStringLiteral("[a-zA-Z]+"));
|
||||
if (!reg.exactMatch(expr))
|
||||
expr = QStringLiteral("`%1`").arg(expr);
|
||||
return expr;
|
||||
}
|
||||
|
||||
QString DetectExpression(ControlReference* reference, ciface::Core::Device* device,
|
||||
const ciface::Core::DeviceQualifier& default_device)
|
||||
{
|
||||
ciface::Core::Device::Control* const ctrl = reference->Detect(5000, device);
|
||||
|
||||
if (ctrl)
|
||||
{
|
||||
return MappingCommon::GetExpressionForControl(QString::fromStdString(ctrl->GetName()),
|
||||
default_device, default_device);
|
||||
}
|
||||
return QStringLiteral("");
|
||||
}
|
||||
}
|
26
Source/Core/DolphinQt/Config/Mapping/MappingCommon.h
Normal file
26
Source/Core/DolphinQt/Config/Mapping/MappingCommon.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
class QString;
|
||||
class ControlReference;
|
||||
|
||||
namespace ciface
|
||||
{
|
||||
namespace Core
|
||||
{
|
||||
class Device;
|
||||
class DeviceQualifier;
|
||||
}
|
||||
}
|
||||
|
||||
namespace MappingCommon
|
||||
{
|
||||
QString GetExpressionForControl(const QString& control_name,
|
||||
const ciface::Core::DeviceQualifier& control_device,
|
||||
const ciface::Core::DeviceQualifier& default_device);
|
||||
QString DetectExpression(ControlReference* reference, ciface::Core::Device* device,
|
||||
const ciface::Core::DeviceQualifier& default_device);
|
||||
}
|
292
Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
Normal file
292
Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingIndicator.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/Control/Control.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
MappingIndicator::MappingIndicator(ControllerEmu::ControlGroup* group) : m_group(group)
|
||||
{
|
||||
setMinimumHeight(128);
|
||||
|
||||
switch (m_group->type)
|
||||
{
|
||||
case ControllerEmu::GroupType::Cursor:
|
||||
BindCursorControls(false);
|
||||
break;
|
||||
case ControllerEmu::GroupType::Stick:
|
||||
BindStickControls();
|
||||
break;
|
||||
case ControllerEmu::GroupType::Tilt:
|
||||
BindCursorControls(true);
|
||||
break;
|
||||
case ControllerEmu::GroupType::MixedTriggers:
|
||||
BindMixedTriggersControls();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
connect(m_timer, &QTimer::timeout, this, [this] { repaint(); });
|
||||
m_timer->start(1000 / 30);
|
||||
}
|
||||
|
||||
void MappingIndicator::BindCursorControls(bool tilt)
|
||||
{
|
||||
m_cursor_up = m_group->controls[0]->control_ref.get();
|
||||
m_cursor_down = m_group->controls[1]->control_ref.get();
|
||||
m_cursor_left = m_group->controls[2]->control_ref.get();
|
||||
m_cursor_right = m_group->controls[3]->control_ref.get();
|
||||
|
||||
if (!tilt)
|
||||
{
|
||||
m_cursor_forward = m_group->controls[4]->control_ref.get();
|
||||
m_cursor_backward = m_group->controls[5]->control_ref.get();
|
||||
|
||||
m_cursor_center = m_group->numeric_settings[0].get();
|
||||
m_cursor_width = m_group->numeric_settings[1].get();
|
||||
m_cursor_height = m_group->numeric_settings[2].get();
|
||||
m_cursor_deadzone = m_group->numeric_settings[3].get();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cursor_deadzone = m_group->numeric_settings[0].get();
|
||||
}
|
||||
}
|
||||
|
||||
void MappingIndicator::BindStickControls()
|
||||
{
|
||||
m_stick_up = m_group->controls[0]->control_ref.get();
|
||||
m_stick_down = m_group->controls[1]->control_ref.get();
|
||||
m_stick_left = m_group->controls[2]->control_ref.get();
|
||||
m_stick_right = m_group->controls[3]->control_ref.get();
|
||||
m_stick_modifier = m_group->controls[4]->control_ref.get();
|
||||
|
||||
m_stick_radius = m_group->numeric_settings[0].get();
|
||||
m_stick_deadzone = m_group->numeric_settings[1].get();
|
||||
}
|
||||
|
||||
void MappingIndicator::BindMixedTriggersControls()
|
||||
{
|
||||
m_mixed_triggers_l_button = m_group->controls[0]->control_ref.get();
|
||||
m_mixed_triggers_r_button = m_group->controls[1]->control_ref.get();
|
||||
m_mixed_triggers_l_analog = m_group->controls[2]->control_ref.get();
|
||||
m_mixed_triggers_r_analog = m_group->controls[3]->control_ref.get();
|
||||
|
||||
m_mixed_triggers_threshold = m_group->numeric_settings[0].get();
|
||||
}
|
||||
|
||||
static ControlState PollControlState(ControlReference* ref)
|
||||
{
|
||||
Settings::Instance().SetControllerStateNeeded(true);
|
||||
|
||||
auto state = ref->State();
|
||||
|
||||
Settings::Instance().SetControllerStateNeeded(false);
|
||||
|
||||
if (state != 0)
|
||||
return state;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MappingIndicator::DrawCursor(bool tilt)
|
||||
{
|
||||
float centerx = width() / 2., centery = height() / 2.;
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
float width = 64, height = 64;
|
||||
float deadzone = m_cursor_deadzone->GetValue() * 48;
|
||||
|
||||
if (!tilt)
|
||||
{
|
||||
float depth = centery - PollControlState(m_cursor_forward) * this->height() / 2.5 +
|
||||
PollControlState(m_cursor_backward) * this->height() / 2.5;
|
||||
|
||||
p.fillRect(0, depth, this->width(), 4, Qt::gray);
|
||||
|
||||
width *= m_cursor_width->GetValue();
|
||||
height *= m_cursor_height->GetValue();
|
||||
}
|
||||
|
||||
float curx = centerx - 4 - std::min(PollControlState(m_cursor_left), 0.5) * width +
|
||||
std::min(PollControlState(m_cursor_right), 0.5) * width,
|
||||
cury = centery - 4 - std::min(PollControlState(m_cursor_up), 0.5) * height +
|
||||
std::min(PollControlState(m_cursor_down), 0.5) * height;
|
||||
|
||||
// Draw background
|
||||
p.setBrush(Qt::white);
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(centerx - (width / 2), centery - (height / 2), width, height);
|
||||
|
||||
// Draw deadzone
|
||||
p.setBrush(Qt::lightGray);
|
||||
p.drawEllipse(centerx - (deadzone / 2), centery - (deadzone / 2), deadzone, deadzone);
|
||||
|
||||
// Draw cursor
|
||||
p.fillRect(curx, cury, 8, 8, Qt::red);
|
||||
}
|
||||
|
||||
void MappingIndicator::DrawStick()
|
||||
{
|
||||
float centerx = width() / 2., centery = height() / 2.;
|
||||
|
||||
bool c_stick = m_group->name == "C-Stick";
|
||||
bool classic_controller = m_group->name == "Left Stick" || m_group->name == "Right Stick";
|
||||
|
||||
float ratio = 1;
|
||||
|
||||
if (c_stick)
|
||||
ratio = 1.;
|
||||
else if (classic_controller)
|
||||
ratio = 0.9f;
|
||||
|
||||
// Polled values
|
||||
float mod = PollControlState(m_stick_modifier) ? 0.5 : 1;
|
||||
float radius = m_stick_radius->GetValue();
|
||||
float curx = -PollControlState(m_stick_left) + PollControlState(m_stick_right),
|
||||
cury = -PollControlState(m_stick_up) + PollControlState(m_stick_down);
|
||||
// The maximum deadzone value covers 50% of the stick area
|
||||
float deadzone = m_stick_deadzone->GetValue() / 2.;
|
||||
|
||||
// Size parameters
|
||||
float max_size = (height() / 2.5) / ratio;
|
||||
float stick_size = (height() / 3.) / ratio;
|
||||
|
||||
// Emulated cursor position
|
||||
float virt_curx, virt_cury;
|
||||
|
||||
if (std::abs(curx) < deadzone && std::abs(cury) < deadzone)
|
||||
{
|
||||
virt_curx = virt_cury = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
virt_curx = curx * mod;
|
||||
virt_cury = cury * mod;
|
||||
}
|
||||
|
||||
// Coordinates for an octagon
|
||||
std::array<QPointF, 8> radius_octagon = {{
|
||||
QPointF(centerx, centery + stick_size), // Bottom
|
||||
QPointF(centerx + stick_size / sqrt(2), centery + stick_size / sqrt(2)), // Bottom Right
|
||||
QPointF(centerx + stick_size, centery), // Right
|
||||
QPointF(centerx + stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Right
|
||||
QPointF(centerx, centery - stick_size), // Top
|
||||
QPointF(centerx - stick_size / sqrt(2), centery - stick_size / sqrt(2)), // Top Left
|
||||
QPointF(centerx - stick_size, centery), // Left
|
||||
QPointF(centerx - stick_size / sqrt(2), centery + stick_size / sqrt(2)) // Bottom Left
|
||||
}};
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
// Draw maximum values
|
||||
p.setBrush(Qt::white);
|
||||
p.setPen(Qt::black);
|
||||
p.drawRect(centerx - max_size, centery - max_size, max_size * 2, max_size * 2);
|
||||
|
||||
// Draw radius
|
||||
p.setBrush(c_stick ? Qt::yellow : Qt::darkGray);
|
||||
p.drawPolygon(radius_octagon.data(), static_cast<int>(radius_octagon.size()));
|
||||
|
||||
// Draw deadzone
|
||||
p.setBrush(c_stick ? Qt::darkYellow : Qt::lightGray);
|
||||
p.drawEllipse(centerx - deadzone * stick_size, centery - deadzone * stick_size,
|
||||
deadzone * stick_size * 2, deadzone * stick_size * 2);
|
||||
|
||||
// Draw stick
|
||||
p.setBrush(Qt::black);
|
||||
p.drawEllipse(centerx - 4 + curx * max_size, centery - 4 + cury * max_size, 8, 8);
|
||||
|
||||
// Draw virtual stick
|
||||
p.setBrush(Qt::red);
|
||||
p.drawEllipse(centerx - 4 + virt_curx * max_size * radius,
|
||||
centery - 4 + virt_cury * max_size * radius, 8, 8);
|
||||
}
|
||||
|
||||
void MappingIndicator::DrawMixedTriggers()
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
p.setRenderHint(QPainter::TextAntialiasing, true);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
|
||||
// Polled values
|
||||
double r_analog = PollControlState(m_mixed_triggers_r_analog);
|
||||
double r_button = PollControlState(m_mixed_triggers_r_button);
|
||||
double l_analog = PollControlState(m_mixed_triggers_l_analog);
|
||||
double l_button = PollControlState(m_mixed_triggers_l_button);
|
||||
double threshold = m_mixed_triggers_threshold->GetValue();
|
||||
|
||||
double r_bar_percent = r_analog;
|
||||
double l_bar_percent = l_analog;
|
||||
|
||||
if ((r_button && r_button != r_analog) || (r_button == r_analog && r_analog > threshold))
|
||||
r_bar_percent = 1;
|
||||
else
|
||||
r_bar_percent *= 0.8;
|
||||
|
||||
if ((l_button && l_button != l_analog) || (l_button == l_analog && l_analog > threshold))
|
||||
l_bar_percent = 1;
|
||||
else
|
||||
l_bar_percent *= 0.8;
|
||||
|
||||
p.fillRect(0, 0, width(), 64, Qt::black);
|
||||
|
||||
p.fillRect(0, 0, l_bar_percent * width(), 32, Qt::red);
|
||||
p.fillRect(0, 32, r_bar_percent * width(), 32, Qt::red);
|
||||
|
||||
p.setPen(Qt::white);
|
||||
p.drawLine(width() * 0.8, 0, width() * 0.8, 63);
|
||||
p.drawLine(0, 32, width(), 32);
|
||||
|
||||
p.setPen(Qt::green);
|
||||
p.drawLine(width() * 0.8 * threshold, 0, width() * 0.8 * threshold, 63);
|
||||
|
||||
p.setBrush(Qt::black);
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(width() * 0.225, 20, tr("L-Analog"));
|
||||
p.drawText(width() * 0.8 + 16, 20, tr("L"));
|
||||
p.drawText(width() * 0.225, 52, tr("R-Analog"));
|
||||
p.drawText(width() * 0.8 + 16, 52, tr("R"));
|
||||
}
|
||||
|
||||
void MappingIndicator::paintEvent(QPaintEvent*)
|
||||
{
|
||||
switch (m_group->type)
|
||||
{
|
||||
case ControllerEmu::GroupType::Cursor:
|
||||
DrawCursor(false);
|
||||
break;
|
||||
case ControllerEmu::GroupType::Tilt:
|
||||
DrawCursor(true);
|
||||
break;
|
||||
case ControllerEmu::GroupType::Stick:
|
||||
DrawStick();
|
||||
break;
|
||||
case ControllerEmu::GroupType::MixedTriggers:
|
||||
DrawMixedTriggers();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
70
Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
Normal file
70
Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class Control;
|
||||
class ControlGroup;
|
||||
class NumericSetting;
|
||||
}
|
||||
|
||||
class QPaintEvent;
|
||||
class QTimer;
|
||||
|
||||
class ControlReference;
|
||||
|
||||
class MappingIndicator : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit MappingIndicator(ControllerEmu::ControlGroup* group);
|
||||
|
||||
private:
|
||||
void BindCursorControls(bool tilt);
|
||||
void BindStickControls();
|
||||
void BindMixedTriggersControls();
|
||||
|
||||
void DrawCursor(bool tilt);
|
||||
void DrawStick();
|
||||
void DrawMixedTriggers();
|
||||
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
ControllerEmu::ControlGroup* m_group;
|
||||
|
||||
// Stick settings
|
||||
ControlReference* m_stick_up;
|
||||
ControlReference* m_stick_down;
|
||||
ControlReference* m_stick_left;
|
||||
ControlReference* m_stick_right;
|
||||
ControlReference* m_stick_modifier;
|
||||
|
||||
ControllerEmu::NumericSetting* m_stick_radius;
|
||||
ControllerEmu::NumericSetting* m_stick_deadzone;
|
||||
|
||||
// Cursor settings
|
||||
ControlReference* m_cursor_up;
|
||||
ControlReference* m_cursor_down;
|
||||
ControlReference* m_cursor_left;
|
||||
ControlReference* m_cursor_right;
|
||||
ControlReference* m_cursor_forward;
|
||||
ControlReference* m_cursor_backward;
|
||||
|
||||
ControllerEmu::NumericSetting* m_cursor_center;
|
||||
ControllerEmu::NumericSetting* m_cursor_width;
|
||||
ControllerEmu::NumericSetting* m_cursor_height;
|
||||
ControllerEmu::NumericSetting* m_cursor_deadzone;
|
||||
|
||||
// Triggers settings
|
||||
ControlReference* m_mixed_triggers_r_analog;
|
||||
ControlReference* m_mixed_triggers_r_button;
|
||||
ControlReference* m_mixed_triggers_l_analog;
|
||||
ControlReference* m_mixed_triggers_l_button;
|
||||
|
||||
ControllerEmu::NumericSetting* m_mixed_triggers_threshold;
|
||||
|
||||
QTimer* m_timer;
|
||||
};
|
41
Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp
Normal file
41
Source/Core/DolphinQt/Config/Mapping/MappingNumeric.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingNumeric.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
MappingNumeric::MappingNumeric(MappingWidget* widget, ControllerEmu::NumericSetting* setting)
|
||||
: m_parent(widget), m_setting(setting)
|
||||
{
|
||||
setRange(setting->m_low, setting->m_high);
|
||||
Update();
|
||||
Connect();
|
||||
}
|
||||
|
||||
void MappingNumeric::Connect()
|
||||
{
|
||||
connect(this, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
[this](int value) {
|
||||
m_setting->SetValue(static_cast<double>(value) / 100);
|
||||
m_parent->SaveSettings();
|
||||
m_parent->GetController()->UpdateReferences(g_controller_interface);
|
||||
});
|
||||
}
|
||||
|
||||
void MappingNumeric::Clear()
|
||||
{
|
||||
m_setting->SetValue(m_setting->m_default_value);
|
||||
m_parent->SaveSettings();
|
||||
Update();
|
||||
}
|
||||
|
||||
void MappingNumeric::Update()
|
||||
{
|
||||
setValue(m_setting->GetValue() * 100);
|
||||
}
|
30
Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h
Normal file
30
Source/Core/DolphinQt/Config/Mapping/MappingNumeric.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QString>
|
||||
|
||||
class MappingWidget;
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class NumericSetting;
|
||||
}
|
||||
|
||||
class MappingNumeric : public QSpinBox
|
||||
{
|
||||
public:
|
||||
MappingNumeric(MappingWidget* widget, ControllerEmu::NumericSetting* ref);
|
||||
|
||||
void Clear();
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
|
||||
MappingWidget* m_parent;
|
||||
ControllerEmu::NumericSetting* m_setting;
|
||||
};
|
39
Source/Core/DolphinQt/Config/Mapping/MappingRadio.cpp
Normal file
39
Source/Core/DolphinQt/Config/Mapping/MappingRadio.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingRadio.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
MappingRadio::MappingRadio(MappingWidget* widget, ControllerEmu::BooleanSetting* setting)
|
||||
: QRadioButton(tr(setting->m_ui_name.c_str())), m_parent(widget), m_setting(setting)
|
||||
{
|
||||
Update();
|
||||
Connect();
|
||||
}
|
||||
|
||||
void MappingRadio::Connect()
|
||||
{
|
||||
connect(this, &QRadioButton::toggled, this, [this](int value) {
|
||||
m_setting->SetValue(value);
|
||||
m_parent->SaveSettings();
|
||||
m_parent->GetController()->UpdateReferences(g_controller_interface);
|
||||
});
|
||||
}
|
||||
|
||||
void MappingRadio::Clear()
|
||||
{
|
||||
m_setting->SetValue(false);
|
||||
m_parent->SaveSettings();
|
||||
Update();
|
||||
}
|
||||
|
||||
void MappingRadio::Update()
|
||||
{
|
||||
setChecked(m_setting->GetValue());
|
||||
}
|
29
Source/Core/DolphinQt/Config/Mapping/MappingRadio.h
Normal file
29
Source/Core/DolphinQt/Config/Mapping/MappingRadio.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QRadioButton>
|
||||
|
||||
class MappingWidget;
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class BooleanSetting;
|
||||
};
|
||||
|
||||
class MappingRadio : public QRadioButton
|
||||
{
|
||||
public:
|
||||
MappingRadio(MappingWidget* widget, ControllerEmu::BooleanSetting* setting);
|
||||
|
||||
void Clear();
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void Connect();
|
||||
|
||||
MappingWidget* m_parent;
|
||||
ControllerEmu::BooleanSetting* m_setting;
|
||||
};
|
179
Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp
Normal file
179
Source/Core/DolphinQt/Config/Mapping/MappingWidget.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "DolphinQt/Config/Mapping/IOWindow.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingBool.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingButton.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingIndicator.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingNumeric.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingRadio.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
|
||||
#include "InputCommon/ControlReference/ControlReference.h"
|
||||
#include "InputCommon/ControllerEmu/Control/Control.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
|
||||
|
||||
MappingWidget::MappingWidget(MappingWindow* window) : m_parent(window)
|
||||
{
|
||||
connect(window, &MappingWindow::ClearFields, this, &MappingWidget::OnClearFields);
|
||||
connect(window, &MappingWindow::Update, this, &MappingWidget::Update);
|
||||
connect(window, &MappingWindow::Save, this, &MappingWidget::SaveSettings);
|
||||
}
|
||||
|
||||
MappingWindow* MappingWidget::GetParent() const
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
bool MappingWidget::IsIterativeInput() const
|
||||
{
|
||||
return m_parent->IsIterativeInput();
|
||||
}
|
||||
|
||||
void MappingWidget::NextButton(MappingButton* button)
|
||||
{
|
||||
auto iterator = std::find(m_buttons.begin(), m_buttons.end(), button);
|
||||
|
||||
if (iterator == m_buttons.end())
|
||||
return;
|
||||
|
||||
if (++iterator == m_buttons.end())
|
||||
return;
|
||||
|
||||
MappingButton* next = *iterator;
|
||||
|
||||
if (next->IsInput() && next->isVisible())
|
||||
next->Detect();
|
||||
else
|
||||
NextButton(next);
|
||||
}
|
||||
|
||||
std::shared_ptr<ciface::Core::Device> MappingWidget::GetDevice() const
|
||||
{
|
||||
return m_parent->GetDevice();
|
||||
}
|
||||
|
||||
int MappingWidget::GetPort() const
|
||||
{
|
||||
return m_parent->GetPort();
|
||||
}
|
||||
|
||||
QGroupBox* MappingWidget::CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group)
|
||||
{
|
||||
QGroupBox* group_box = new QGroupBox(name);
|
||||
QFormLayout* form_layout = new QFormLayout();
|
||||
|
||||
group_box->setLayout(form_layout);
|
||||
|
||||
bool need_indicator = group->type == ControllerEmu::GroupType::Cursor ||
|
||||
group->type == ControllerEmu::GroupType::Stick ||
|
||||
group->type == ControllerEmu::GroupType::Tilt ||
|
||||
group->type == ControllerEmu::GroupType::MixedTriggers;
|
||||
|
||||
for (auto& control : group->controls)
|
||||
{
|
||||
auto* button = new MappingButton(this, control->control_ref.get(), !need_indicator);
|
||||
|
||||
button->setMinimumWidth(100);
|
||||
button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
const bool translate = control->translate == ControllerEmu::Translate;
|
||||
const QString translated_name =
|
||||
translate ? tr(control->ui_name.c_str()) : QString::fromStdString(control->ui_name);
|
||||
form_layout->addRow(translated_name, button);
|
||||
|
||||
auto* control_ref = control->control_ref.get();
|
||||
|
||||
connect(button, &MappingButton::AdvancedPressed, [this, button, control_ref] {
|
||||
if (m_parent->GetDevice() == nullptr)
|
||||
return;
|
||||
|
||||
IOWindow io(this, m_parent->GetController(), control_ref,
|
||||
control_ref->IsInput() ? IOWindow::Type::Input : IOWindow::Type::Output);
|
||||
io.exec();
|
||||
SaveSettings();
|
||||
button->Update();
|
||||
});
|
||||
|
||||
m_buttons.push_back(button);
|
||||
}
|
||||
|
||||
for (auto& numeric : group->numeric_settings)
|
||||
{
|
||||
auto* spinbox = new MappingNumeric(this, numeric.get());
|
||||
form_layout->addRow(tr(numeric->m_name.c_str()), spinbox);
|
||||
m_numerics.push_back(spinbox);
|
||||
}
|
||||
|
||||
for (auto& boolean : group->boolean_settings)
|
||||
{
|
||||
if (!boolean->IsExclusive())
|
||||
continue;
|
||||
|
||||
auto* checkbox = new MappingRadio(this, boolean.get());
|
||||
|
||||
form_layout->addRow(checkbox);
|
||||
m_radio.push_back(checkbox);
|
||||
}
|
||||
|
||||
for (auto& boolean : group->boolean_settings)
|
||||
{
|
||||
if (boolean->IsExclusive())
|
||||
continue;
|
||||
|
||||
auto* checkbox = new MappingBool(this, boolean.get());
|
||||
|
||||
form_layout->addRow(checkbox);
|
||||
m_bools.push_back(checkbox);
|
||||
}
|
||||
|
||||
if (need_indicator)
|
||||
form_layout->addRow(new MappingIndicator(group));
|
||||
|
||||
return group_box;
|
||||
}
|
||||
|
||||
void MappingWidget::OnClearFields()
|
||||
{
|
||||
for (auto* button : m_buttons)
|
||||
button->Clear();
|
||||
|
||||
for (auto* spinbox : m_numerics)
|
||||
spinbox->Clear();
|
||||
|
||||
for (auto* checkbox : m_bools)
|
||||
checkbox->Clear();
|
||||
|
||||
for (auto* radio : m_radio)
|
||||
radio->Clear();
|
||||
}
|
||||
|
||||
void MappingWidget::Update()
|
||||
{
|
||||
for (auto* button : m_buttons)
|
||||
button->Update();
|
||||
|
||||
for (auto* spinbox : m_numerics)
|
||||
spinbox->Update();
|
||||
|
||||
for (auto* checkbox : m_bools)
|
||||
checkbox->Update();
|
||||
|
||||
for (auto* radio : m_radio)
|
||||
radio->Update();
|
||||
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
ControllerEmu::EmulatedController* MappingWidget::GetController() const
|
||||
{
|
||||
return m_parent->GetController();
|
||||
}
|
71
Source/Core/DolphinQt/Config/Mapping/MappingWidget.h
Normal file
71
Source/Core/DolphinQt/Config/Mapping/MappingWidget.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
|
||||
class ControlGroupBox;
|
||||
class InputConfig;
|
||||
class IOWindow;
|
||||
class MappingBool;
|
||||
class MappingButton;
|
||||
class MappingNumeric;
|
||||
class MappingWindow;
|
||||
class MappingRadio;
|
||||
class QGroupBox;
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class Control;
|
||||
class ControlGroup;
|
||||
class EmulatedController;
|
||||
} // namespace ControllerEmu
|
||||
|
||||
namespace ciface
|
||||
{
|
||||
namespace Core
|
||||
{
|
||||
class Device;
|
||||
}
|
||||
} // namespace ciface
|
||||
|
||||
class MappingWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MappingWidget(MappingWindow* window);
|
||||
|
||||
ControllerEmu::EmulatedController* GetController() const;
|
||||
std::shared_ptr<ciface::Core::Device> GetDevice() const;
|
||||
|
||||
MappingWindow* GetParent() const;
|
||||
|
||||
bool IsIterativeInput() const;
|
||||
void NextButton(MappingButton* button);
|
||||
|
||||
virtual void LoadSettings() = 0;
|
||||
virtual void SaveSettings() = 0;
|
||||
virtual InputConfig* GetConfig() = 0;
|
||||
|
||||
void Update();
|
||||
|
||||
protected:
|
||||
int GetPort() const;
|
||||
QGroupBox* CreateGroupBox(const QString& name, ControllerEmu::ControlGroup* group);
|
||||
|
||||
private:
|
||||
void OnClearFields();
|
||||
|
||||
MappingWindow* m_parent;
|
||||
bool m_first = true;
|
||||
std::vector<MappingBool*> m_bools;
|
||||
std::vector<MappingRadio*> m_radio;
|
||||
std::vector<MappingButton*> m_buttons;
|
||||
std::vector<MappingNumeric*> m_numerics;
|
||||
};
|
374
Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
Normal file
374
Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp
Normal file
@ -0,0 +1,374 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Common/FileSearch.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/Core.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCKeyboardEmu.h"
|
||||
#include "DolphinQt/Config/Mapping/GCMicrophone.h"
|
||||
#include "DolphinQt/Config/Mapping/GCPadEmu.h"
|
||||
#include "DolphinQt/Config/Mapping/Hotkey3D.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyDebugging.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyGeneral.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyGraphics.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyStates.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyStatesOther.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyTAS.h"
|
||||
#include "DolphinQt/Config/Mapping/HotkeyWii.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuGeneral.h"
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControl.h"
|
||||
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControllerEmu.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
constexpr const char* PROFILES_DIR = "Profiles/";
|
||||
|
||||
MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
|
||||
: QDialog(parent), m_port(port_num)
|
||||
{
|
||||
setWindowTitle(tr("Port %1").arg(port_num + 1));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateDevicesLayout();
|
||||
CreateProfilesLayout();
|
||||
CreateResetLayout();
|
||||
CreateMainLayout();
|
||||
ConnectWidgets();
|
||||
SetMappingType(type);
|
||||
}
|
||||
|
||||
void MappingWindow::CreateDevicesLayout()
|
||||
{
|
||||
m_devices_layout = new QHBoxLayout();
|
||||
m_devices_box = new QGroupBox(tr("Device"));
|
||||
m_devices_combo = new QComboBox();
|
||||
m_devices_refresh = new QPushButton(tr("Refresh"));
|
||||
|
||||
m_devices_refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_devices_layout->addWidget(m_devices_combo);
|
||||
m_devices_layout->addWidget(m_devices_refresh);
|
||||
|
||||
m_devices_box->setLayout(m_devices_layout);
|
||||
}
|
||||
|
||||
void MappingWindow::CreateProfilesLayout()
|
||||
{
|
||||
m_profiles_layout = new QHBoxLayout();
|
||||
m_profiles_box = new QGroupBox(tr("Profile"));
|
||||
m_profiles_combo = new QComboBox();
|
||||
m_profiles_load = new QPushButton(tr("Load"));
|
||||
m_profiles_save = new QPushButton(tr("Save"));
|
||||
m_profiles_delete = new QPushButton(tr("Delete"));
|
||||
|
||||
auto* button_layout = new QHBoxLayout();
|
||||
|
||||
m_profiles_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
m_profiles_combo->setEditable(true);
|
||||
|
||||
m_profiles_layout->addWidget(m_profiles_combo);
|
||||
button_layout->addWidget(m_profiles_load);
|
||||
button_layout->addWidget(m_profiles_save);
|
||||
button_layout->addWidget(m_profiles_delete);
|
||||
m_profiles_layout->addLayout(button_layout);
|
||||
|
||||
m_profiles_box->setLayout(m_profiles_layout);
|
||||
}
|
||||
|
||||
void MappingWindow::CreateResetLayout()
|
||||
{
|
||||
m_reset_layout = new QHBoxLayout();
|
||||
m_reset_box = new QGroupBox(tr("Reset"));
|
||||
m_reset_clear = new QPushButton(tr("Clear"));
|
||||
m_reset_default = new QPushButton(tr("Default"));
|
||||
|
||||
m_reset_box->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
m_reset_layout->addWidget(m_reset_default);
|
||||
m_reset_layout->addWidget(m_reset_clear);
|
||||
|
||||
m_reset_box->setLayout(m_reset_layout);
|
||||
}
|
||||
|
||||
void MappingWindow::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QVBoxLayout();
|
||||
m_config_layout = new QHBoxLayout();
|
||||
m_iterative_input = new QCheckBox(tr("Iterative Input"));
|
||||
m_tab_widget = new QTabWidget();
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
|
||||
m_iterative_input->setToolTip(tr("Automatically progress one button after another during "
|
||||
"configuration. Useful for first-time setup."));
|
||||
|
||||
m_config_layout->addWidget(m_devices_box);
|
||||
m_config_layout->addWidget(m_reset_box);
|
||||
m_config_layout->addWidget(m_profiles_box);
|
||||
|
||||
m_main_layout->addLayout(m_config_layout);
|
||||
m_main_layout->addWidget(m_iterative_input);
|
||||
m_main_layout->addWidget(m_tab_widget);
|
||||
m_main_layout->addWidget(m_button_box);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void MappingWindow::ConnectWidgets()
|
||||
{
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_devices_refresh, &QPushButton::clicked, this, &MappingWindow::RefreshDevices);
|
||||
connect(m_devices_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &MappingWindow::OnDeviceChanged);
|
||||
connect(m_reset_clear, &QPushButton::clicked, this, [this] { emit ClearFields(); });
|
||||
connect(m_reset_default, &QPushButton::clicked, this, &MappingWindow::OnDefaultFieldsPressed);
|
||||
connect(m_profiles_save, &QPushButton::clicked, this, &MappingWindow::OnSaveProfilePressed);
|
||||
connect(m_profiles_load, &QPushButton::clicked, this, &MappingWindow::OnLoadProfilePressed);
|
||||
connect(m_profiles_delete, &QPushButton::clicked, this, &MappingWindow::OnDeleteProfilePressed);
|
||||
}
|
||||
|
||||
void MappingWindow::OnDeleteProfilePressed()
|
||||
{
|
||||
const QString profile_name = m_profiles_combo->currentText();
|
||||
const QString profile_path = m_profiles_combo->currentData().toString();
|
||||
|
||||
if (!File::Exists(profile_path.toStdString()))
|
||||
{
|
||||
QMessageBox error(this);
|
||||
error.setIcon(QMessageBox::Critical);
|
||||
error.setWindowTitle(tr("Error"));
|
||||
error.setText(tr("The profile '%1' does not exist").arg(profile_name));
|
||||
error.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox confirm(this);
|
||||
|
||||
confirm.setIcon(QMessageBox::Warning);
|
||||
confirm.setWindowTitle(tr("Confirm"));
|
||||
confirm.setText(tr("Are you sure that you want to delete '%1'?").arg(profile_name));
|
||||
confirm.setInformativeText(tr("This cannot be undone!"));
|
||||
confirm.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
|
||||
|
||||
if (confirm.exec() != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_profiles_combo->removeItem(m_profiles_combo->currentIndex());
|
||||
|
||||
File::Delete(profile_path.toStdString());
|
||||
|
||||
QMessageBox result(this);
|
||||
result.setIcon(QMessageBox::Information);
|
||||
result.setWindowTitle(tr("Success"));
|
||||
result.setText(tr("Successfully deleted '%1'.").arg(profile_name));
|
||||
}
|
||||
|
||||
void MappingWindow::OnLoadProfilePressed()
|
||||
{
|
||||
const QString profile_path = m_profiles_combo->currentData().toString();
|
||||
|
||||
if (m_profiles_combo->currentIndex() == 0)
|
||||
return;
|
||||
|
||||
IniFile ini;
|
||||
ini.Load(profile_path.toStdString());
|
||||
|
||||
m_controller->LoadConfig(ini.GetOrCreateSection("Profile"));
|
||||
m_controller->UpdateReferences(g_controller_interface);
|
||||
|
||||
emit Update();
|
||||
|
||||
RefreshDevices();
|
||||
}
|
||||
|
||||
void MappingWindow::OnSaveProfilePressed()
|
||||
{
|
||||
const QString profile_name = m_profiles_combo->currentText();
|
||||
const std::string profile_path = File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR +
|
||||
m_config->GetProfileName() + "/" + profile_name.toStdString() +
|
||||
".ini";
|
||||
|
||||
if (profile_name.isEmpty())
|
||||
return;
|
||||
|
||||
File::CreateFullPath(profile_path);
|
||||
|
||||
IniFile ini;
|
||||
|
||||
m_controller->SaveConfig(ini.GetOrCreateSection("Profile"));
|
||||
ini.Save(profile_path);
|
||||
|
||||
if (m_profiles_combo->currentIndex() == 0 || m_profiles_combo->findText(profile_name) == -1)
|
||||
{
|
||||
m_profiles_combo->addItem(profile_name, QString::fromStdString(profile_path));
|
||||
m_profiles_combo->setCurrentIndex(m_profiles_combo->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MappingWindow::OnDeviceChanged(int index)
|
||||
{
|
||||
if (IsMappingAllDevices())
|
||||
return;
|
||||
|
||||
const auto device = m_devices_combo->currentText().toStdString();
|
||||
m_controller->SetDefaultDevice(device);
|
||||
}
|
||||
|
||||
bool MappingWindow::IsMappingAllDevices() const
|
||||
{
|
||||
return m_devices_combo->currentIndex() == m_devices_combo->count() - 1;
|
||||
}
|
||||
|
||||
void MappingWindow::RefreshDevices()
|
||||
{
|
||||
m_devices_combo->clear();
|
||||
|
||||
Core::RunAsCPUThread([&] {
|
||||
g_controller_interface.RefreshDevices();
|
||||
m_controller->UpdateReferences(g_controller_interface);
|
||||
|
||||
const auto default_device = m_controller->GetDefaultDevice().ToString();
|
||||
|
||||
if (!default_device.empty())
|
||||
m_devices_combo->addItem(QString::fromStdString(default_device));
|
||||
|
||||
for (const auto& name : g_controller_interface.GetAllDeviceStrings())
|
||||
{
|
||||
if (name != default_device)
|
||||
m_devices_combo->addItem(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
m_devices_combo->addItem(tr("All devices"));
|
||||
|
||||
m_devices_combo->setCurrentIndex(0);
|
||||
});
|
||||
}
|
||||
|
||||
void MappingWindow::SetMappingType(MappingWindow::Type type)
|
||||
{
|
||||
MappingWidget* widget;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Type::MAPPING_GC_KEYBOARD:
|
||||
widget = new GCKeyboardEmu(this);
|
||||
AddWidget(tr("GameCube Keyboard"), widget);
|
||||
setWindowTitle(tr("GameCube Keyboard at Port %1").arg(GetPort() + 1));
|
||||
break;
|
||||
case Type::MAPPING_GC_BONGOS:
|
||||
case Type::MAPPING_GC_STEERINGWHEEL:
|
||||
case Type::MAPPING_GC_DANCEMAT:
|
||||
case Type::MAPPING_GCPAD:
|
||||
widget = new GCPadEmu(this);
|
||||
setWindowTitle(tr("GameCube Controller at Port %1").arg(GetPort() + 1));
|
||||
AddWidget(tr("GameCube Controller"), widget);
|
||||
break;
|
||||
case Type::MAPPING_GC_MICROPHONE:
|
||||
widget = new GCMicrophone(this);
|
||||
setWindowTitle(tr("GameCube Microphone Slot %1")
|
||||
.arg(GetPort() == 0 ? QStringLiteral("A") : QStringLiteral("B")));
|
||||
AddWidget(tr("Microphone"), widget);
|
||||
break;
|
||||
case Type::MAPPING_WIIMOTE_EMU:
|
||||
{
|
||||
auto* extension = new WiimoteEmuExtension(this);
|
||||
widget = new WiimoteEmuGeneral(this, extension);
|
||||
setWindowTitle(tr("Wii Remote %1").arg(GetPort() + 1));
|
||||
AddWidget(tr("General and Options"), widget);
|
||||
AddWidget(tr("Motion Controls and IR"), new WiimoteEmuMotionControl(this));
|
||||
AddWidget(tr("Extension"), extension);
|
||||
break;
|
||||
}
|
||||
case Type::MAPPING_HOTKEYS:
|
||||
{
|
||||
widget = new HotkeyGeneral(this);
|
||||
AddWidget(tr("General"), widget);
|
||||
AddWidget(tr("TAS Tools"), new HotkeyTAS(this));
|
||||
|
||||
AddWidget(tr("Debugging"), new HotkeyDebugging(this));
|
||||
|
||||
AddWidget(tr("Wii and Wii Remote"), new HotkeyWii(this));
|
||||
AddWidget(tr("Graphics"), new HotkeyGraphics(this));
|
||||
AddWidget(tr("3D"), new Hotkey3D(this));
|
||||
AddWidget(tr("Save and Load State"), new HotkeyStates(this));
|
||||
AddWidget(tr("Other State Management"), new HotkeyStatesOther(this));
|
||||
setWindowTitle(tr("Hotkey Settings"));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
widget->LoadSettings();
|
||||
|
||||
m_config = widget->GetConfig();
|
||||
|
||||
m_controller = m_config->GetController(GetPort());
|
||||
m_profiles_combo->addItem(QStringLiteral(""));
|
||||
|
||||
const std::string profiles_path =
|
||||
File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR + m_config->GetProfileName();
|
||||
for (const auto& filename : Common::DoFileSearch({profiles_path}, {".ini"}))
|
||||
{
|
||||
std::string basename;
|
||||
SplitPath(filename, nullptr, &basename, nullptr);
|
||||
m_profiles_combo->addItem(QString::fromStdString(basename), QString::fromStdString(filename));
|
||||
}
|
||||
|
||||
RefreshDevices();
|
||||
}
|
||||
|
||||
void MappingWindow::AddWidget(const QString& name, QWidget* widget)
|
||||
{
|
||||
m_tab_widget->addTab(GetWrappedWidget(widget, this, 150, 205), name);
|
||||
}
|
||||
|
||||
int MappingWindow::GetPort() const
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
ControllerEmu::EmulatedController* MappingWindow::GetController() const
|
||||
{
|
||||
return m_controller;
|
||||
}
|
||||
|
||||
std::shared_ptr<ciface::Core::Device> MappingWindow::GetDevice() const
|
||||
{
|
||||
return g_controller_interface.FindDevice(GetController()->GetDefaultDevice());
|
||||
}
|
||||
|
||||
void MappingWindow::OnDefaultFieldsPressed()
|
||||
{
|
||||
m_controller->LoadDefaults(g_controller_interface);
|
||||
m_controller->UpdateReferences(g_controller_interface);
|
||||
emit Update();
|
||||
emit Save();
|
||||
}
|
||||
|
||||
bool MappingWindow::IsIterativeInput() const
|
||||
{
|
||||
return m_iterative_input->isChecked();
|
||||
}
|
113
Source/Core/DolphinQt/Config/Mapping/MappingWindow.h
Normal file
113
Source/Core/DolphinQt/Config/Mapping/MappingWindow.h
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
#include "InputCommon/ControllerInterface/Device.h"
|
||||
|
||||
namespace ControllerEmu
|
||||
{
|
||||
class EmulatedController;
|
||||
}
|
||||
|
||||
class InputConfig;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QDialogButtonBox;
|
||||
class QEvent;
|
||||
class QHBoxLayout;
|
||||
class QGroupBox;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QTabWidget;
|
||||
class QWidget;
|
||||
|
||||
class MappingWindow final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
// GameCube
|
||||
MAPPING_GC_BONGOS,
|
||||
MAPPING_GC_DANCEMAT,
|
||||
MAPPING_GC_KEYBOARD,
|
||||
MAPPING_GCPAD,
|
||||
MAPPING_GC_STEERINGWHEEL,
|
||||
MAPPING_GC_MICROPHONE,
|
||||
// Wii
|
||||
MAPPING_WIIMOTE_EMU,
|
||||
// Hotkeys
|
||||
MAPPING_HOTKEYS
|
||||
};
|
||||
|
||||
explicit MappingWindow(QWidget* parent, Type type, int port_num);
|
||||
|
||||
int GetPort() const;
|
||||
std::shared_ptr<ciface::Core::Device> GetDevice() const;
|
||||
ControllerEmu::EmulatedController* GetController() const;
|
||||
bool IsIterativeInput() const;
|
||||
bool IsMappingAllDevices() const;
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
void ClearFields();
|
||||
void Save();
|
||||
|
||||
private:
|
||||
void SetMappingType(Type type);
|
||||
void CreateDevicesLayout();
|
||||
void CreateProfilesLayout();
|
||||
void CreateResetLayout();
|
||||
void CreateMainLayout();
|
||||
void ConnectWidgets();
|
||||
|
||||
void AddWidget(const QString& name, QWidget* widget);
|
||||
|
||||
void RefreshDevices();
|
||||
|
||||
void OnDeleteProfilePressed();
|
||||
void OnLoadProfilePressed();
|
||||
void OnSaveProfilePressed();
|
||||
void OnDefaultFieldsPressed();
|
||||
void OnDeviceChanged(int index);
|
||||
|
||||
ControllerEmu::EmulatedController* m_controller = nullptr;
|
||||
|
||||
// Main
|
||||
QCheckBox* m_iterative_input;
|
||||
QVBoxLayout* m_main_layout;
|
||||
QHBoxLayout* m_config_layout;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
// Devices
|
||||
QGroupBox* m_devices_box;
|
||||
QHBoxLayout* m_devices_layout;
|
||||
QComboBox* m_devices_combo;
|
||||
QPushButton* m_devices_refresh;
|
||||
|
||||
// Profiles
|
||||
QGroupBox* m_profiles_box;
|
||||
QHBoxLayout* m_profiles_layout;
|
||||
QComboBox* m_profiles_combo;
|
||||
QPushButton* m_profiles_load;
|
||||
QPushButton* m_profiles_save;
|
||||
QPushButton* m_profiles_delete;
|
||||
|
||||
// Reset
|
||||
QGroupBox* m_reset_box;
|
||||
QHBoxLayout* m_reset_layout;
|
||||
QPushButton* m_reset_default;
|
||||
QPushButton* m_reset_clear;
|
||||
|
||||
QTabWidget* m_tab_widget;
|
||||
|
||||
Type m_mapping_type;
|
||||
const int m_port;
|
||||
InputConfig* m_config;
|
||||
};
|
196
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuExtension.cpp
Normal file
196
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuExtension.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
WiimoteEmuExtension::WiimoteEmuExtension(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateClassicLayout();
|
||||
CreateDrumsLayout();
|
||||
CreateGuitarLayout();
|
||||
CreateNoneLayout();
|
||||
CreateNunchukLayout();
|
||||
CreateTurntableLayout();
|
||||
CreateMainLayout();
|
||||
|
||||
ChangeExtensionType(Type::NONE);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateClassicLayout()
|
||||
{
|
||||
auto* hbox = new QHBoxLayout();
|
||||
m_classic_box = new QGroupBox(tr("Classic Controller"), this);
|
||||
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetClassicGroup(GetPort(), WiimoteEmu::ClassicGroup::Buttons)));
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Left Stick"), Wiimote::GetClassicGroup(GetPort(), WiimoteEmu::ClassicGroup::LeftStick)));
|
||||
hbox->addWidget(
|
||||
CreateGroupBox(tr("Right Stick"),
|
||||
Wiimote::GetClassicGroup(GetPort(), WiimoteEmu::ClassicGroup::RightStick)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("D-Pad"), Wiimote::GetClassicGroup(GetPort(), WiimoteEmu::ClassicGroup::DPad)));
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Triggers"), Wiimote::GetClassicGroup(GetPort(), WiimoteEmu::ClassicGroup::Triggers)));
|
||||
hbox->addLayout(vbox);
|
||||
|
||||
m_classic_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateDrumsLayout()
|
||||
{
|
||||
auto* hbox = new QHBoxLayout();
|
||||
m_drums_box = new QGroupBox(tr("Drums"), this);
|
||||
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Buttons)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(
|
||||
CreateGroupBox(tr("Pads"), Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Pads)));
|
||||
vbox->addWidget(CreateGroupBox(tr("Stick"),
|
||||
Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Stick)));
|
||||
hbox->addLayout(vbox);
|
||||
|
||||
m_drums_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateNoneLayout()
|
||||
{
|
||||
m_none_box = new QGroupBox(this);
|
||||
auto* hbox = new QHBoxLayout();
|
||||
auto* label = new QLabel(tr("No extension selected."));
|
||||
|
||||
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
hbox->addWidget(label);
|
||||
m_none_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateNunchukLayout()
|
||||
{
|
||||
auto* hbox = new QHBoxLayout();
|
||||
m_nunchuk_box = new QGroupBox(tr("Nunchuk"), this);
|
||||
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Stick"), Wiimote::GetNunchukGroup(GetPort(), WiimoteEmu::NunchukGroup::Stick)));
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Tilt"), Wiimote::GetNunchukGroup(GetPort(), WiimoteEmu::NunchukGroup::Tilt)));
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Swing"), Wiimote::GetNunchukGroup(GetPort(), WiimoteEmu::NunchukGroup::Swing)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetNunchukGroup(GetPort(), WiimoteEmu::NunchukGroup::Buttons)));
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Shake"), Wiimote::GetNunchukGroup(GetPort(), WiimoteEmu::NunchukGroup::Shake)));
|
||||
hbox->addLayout(vbox);
|
||||
|
||||
m_nunchuk_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateGuitarLayout()
|
||||
{
|
||||
auto* hbox = new QHBoxLayout();
|
||||
m_guitar_box = new QGroupBox(tr("Guitar"), this);
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Buttons)));
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Stick"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Stick)));
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Slider Bar"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::SliderBar)));
|
||||
hbox->addLayout(vbox);
|
||||
|
||||
auto* vbox2 = new QVBoxLayout();
|
||||
vbox2->addWidget(CreateGroupBox(
|
||||
tr("Strum"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Strum)));
|
||||
vbox2->addWidget(CreateGroupBox(
|
||||
tr("Frets"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Frets)));
|
||||
vbox2->addWidget(CreateGroupBox(
|
||||
tr("Whammy"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Whammy)));
|
||||
hbox->addLayout(vbox2);
|
||||
|
||||
m_guitar_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateTurntableLayout()
|
||||
{
|
||||
auto* hbox = new QHBoxLayout();
|
||||
m_turntable_box = new QGroupBox(tr("Turntable"), this);
|
||||
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Stick"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Stick)));
|
||||
hbox->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Buttons)));
|
||||
|
||||
auto* vbox = new QVBoxLayout();
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
tr("Effect"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::EffectDial)));
|
||||
vbox->addWidget(
|
||||
// i18n: "Table" refers to a turntable
|
||||
CreateGroupBox(tr("Left Table"),
|
||||
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::LeftTable)));
|
||||
vbox->addWidget(CreateGroupBox(
|
||||
// i18n: "Table" refers to a turntable
|
||||
tr("Right Table"),
|
||||
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::RightTable)));
|
||||
vbox->addWidget(
|
||||
CreateGroupBox(tr("Crossfade"),
|
||||
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Crossfade)));
|
||||
hbox->addLayout(vbox);
|
||||
|
||||
m_turntable_box->setLayout(hbox);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(m_classic_box);
|
||||
m_main_layout->addWidget(m_drums_box);
|
||||
m_main_layout->addWidget(m_guitar_box);
|
||||
m_main_layout->addWidget(m_none_box);
|
||||
m_main_layout->addWidget(m_nunchuk_box);
|
||||
m_main_layout->addWidget(m_turntable_box);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::LoadSettings()
|
||||
{
|
||||
Wiimote::LoadConfig();
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::SaveSettings()
|
||||
{
|
||||
Wiimote::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* WiimoteEmuExtension::GetConfig()
|
||||
{
|
||||
return Wiimote::GetConfig();
|
||||
}
|
||||
|
||||
void WiimoteEmuExtension::ChangeExtensionType(WiimoteEmuExtension::Type type)
|
||||
{
|
||||
m_classic_box->setHidden(type != Type::CLASSIC_CONTROLLER);
|
||||
m_drums_box->setHidden(type != Type::DRUMS);
|
||||
m_guitar_box->setHidden(type != Type::GUITAR);
|
||||
m_none_box->setHidden(type != Type::NONE);
|
||||
m_nunchuk_box->setHidden(type != Type::NUNCHUK);
|
||||
m_turntable_box->setHidden(type != Type::TURNTABLE);
|
||||
}
|
52
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuExtension.h
Normal file
52
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuExtension.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
|
||||
class WiimoteEmuExtension final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
NONE,
|
||||
CLASSIC_CONTROLLER,
|
||||
DRUMS,
|
||||
GUITAR,
|
||||
NUNCHUK,
|
||||
TURNTABLE
|
||||
};
|
||||
|
||||
explicit WiimoteEmuExtension(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
void ChangeExtensionType(Type type);
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
|
||||
void CreateClassicLayout();
|
||||
void CreateDrumsLayout();
|
||||
void CreateGuitarLayout();
|
||||
void CreateNoneLayout();
|
||||
void CreateNunchukLayout();
|
||||
void CreateTurntableLayout();
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
QGroupBox* m_classic_box;
|
||||
QGroupBox* m_drums_box;
|
||||
QGroupBox* m_guitar_box;
|
||||
QGroupBox* m_none_box;
|
||||
QGroupBox* m_nunchuk_box;
|
||||
QGroupBox* m_turntable_box;
|
||||
};
|
114
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp
Normal file
114
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.cpp
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuGeneral.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuExtension.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Extension.h"
|
||||
#include "InputCommon/ControllerEmu/Setting/BooleanSetting.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
WiimoteEmuGeneral::WiimoteEmuGeneral(MappingWindow* window, WiimoteEmuExtension* extension)
|
||||
: MappingWidget(window), m_extension_widget(extension)
|
||||
{
|
||||
CreateMainLayout();
|
||||
Connect();
|
||||
}
|
||||
|
||||
void WiimoteEmuGeneral::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
auto* vbox_layout = new QVBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("Buttons"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Buttons)));
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("D-Pad"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::DPad)));
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("Hotkeys"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Hotkeys)));
|
||||
|
||||
auto* extension_group = Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension);
|
||||
auto* extension = CreateGroupBox(tr("Extension"), extension_group);
|
||||
auto* ce_extension = static_cast<ControllerEmu::Extension*>(extension_group);
|
||||
m_extension_combo = new QComboBox();
|
||||
|
||||
for (const auto& attachment : ce_extension->attachments)
|
||||
{
|
||||
// TODO: Figure out how to localize this
|
||||
m_extension_combo->addItem(QString::fromStdString(attachment->GetName()));
|
||||
}
|
||||
|
||||
extension->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
static_cast<QFormLayout*>(extension->layout())->addRow(m_extension_combo);
|
||||
|
||||
vbox_layout->addWidget(extension);
|
||||
vbox_layout->addWidget(CreateGroupBox(
|
||||
tr("Rumble"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Rumble)));
|
||||
|
||||
vbox_layout->addWidget(CreateGroupBox(
|
||||
tr("Options"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Options)));
|
||||
|
||||
m_main_layout->addLayout(vbox_layout);
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void WiimoteEmuGeneral::Connect()
|
||||
{
|
||||
connect(m_extension_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &WiimoteEmuGeneral::OnAttachmentChanged);
|
||||
}
|
||||
|
||||
void WiimoteEmuGeneral::OnAttachmentChanged(int extension)
|
||||
{
|
||||
const QString value = m_extension_combo->currentText();
|
||||
|
||||
static const QMap<QString, WiimoteEmuExtension::Type> value_map = {
|
||||
{QStringLiteral("None"), WiimoteEmuExtension::Type::NONE},
|
||||
{QStringLiteral("Classic"), WiimoteEmuExtension::Type::CLASSIC_CONTROLLER},
|
||||
{QStringLiteral("Drums"), WiimoteEmuExtension::Type::DRUMS},
|
||||
{QStringLiteral("Guitar"), WiimoteEmuExtension::Type::GUITAR},
|
||||
{QStringLiteral("Nunchuk"), WiimoteEmuExtension::Type::NUNCHUK},
|
||||
{QStringLiteral("Turntable"), WiimoteEmuExtension::Type::TURNTABLE}};
|
||||
|
||||
m_extension_widget->ChangeExtensionType(value_map[value]);
|
||||
|
||||
auto* ce_extension = static_cast<ControllerEmu::Extension*>(
|
||||
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension));
|
||||
ce_extension->switch_extension = extension;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
void WiimoteEmuGeneral::LoadSettings()
|
||||
{
|
||||
auto* ce_extension = static_cast<ControllerEmu::Extension*>(
|
||||
Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Extension));
|
||||
|
||||
m_extension_combo->setCurrentIndex(ce_extension->switch_extension);
|
||||
OnAttachmentChanged(ce_extension->switch_extension);
|
||||
|
||||
Wiimote::LoadConfig();
|
||||
}
|
||||
|
||||
void WiimoteEmuGeneral::SaveSettings()
|
||||
{
|
||||
Wiimote::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* WiimoteEmuGeneral::GetConfig()
|
||||
{
|
||||
return Wiimote::GetConfig();
|
||||
}
|
35
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.h
Normal file
35
Source/Core/DolphinQt/Config/Mapping/WiimoteEmuGeneral.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QComboBox;
|
||||
class QHBoxLayout;
|
||||
class WiimoteEmuExtension;
|
||||
|
||||
class WiimoteEmuGeneral final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WiimoteEmuGeneral(MappingWindow* window, WiimoteEmuExtension* extension);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
void Connect();
|
||||
void OnAttachmentChanged(int index);
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
|
||||
// Extensions
|
||||
QComboBox* m_extension_combo;
|
||||
|
||||
WiimoteEmuExtension* m_extension_widget;
|
||||
};
|
@ -0,0 +1,51 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/Mapping/WiimoteEmuMotionControl.h"
|
||||
|
||||
#include <QFormLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/InputConfig.h"
|
||||
|
||||
WiimoteEmuMotionControl::WiimoteEmuMotionControl(MappingWindow* window) : MappingWidget(window)
|
||||
{
|
||||
CreateMainLayout();
|
||||
}
|
||||
|
||||
void WiimoteEmuMotionControl::CreateMainLayout()
|
||||
{
|
||||
m_main_layout = new QHBoxLayout();
|
||||
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("Shake"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Shake)));
|
||||
m_main_layout->addWidget(
|
||||
CreateGroupBox(tr("IR"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::IR)));
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("Tilt"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Tilt)));
|
||||
m_main_layout->addWidget(CreateGroupBox(
|
||||
tr("Swing"), Wiimote::GetWiimoteGroup(GetPort(), WiimoteEmu::WiimoteGroup::Swing)));
|
||||
|
||||
setLayout(m_main_layout);
|
||||
}
|
||||
|
||||
void WiimoteEmuMotionControl::LoadSettings()
|
||||
{
|
||||
Wiimote::LoadConfig();
|
||||
}
|
||||
|
||||
void WiimoteEmuMotionControl::SaveSettings()
|
||||
{
|
||||
Wiimote::GetConfig()->SaveConfig();
|
||||
}
|
||||
|
||||
InputConfig* WiimoteEmuMotionControl::GetConfig()
|
||||
{
|
||||
return Wiimote::GetConfig();
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWidget.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QFormLayout;
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
class QLabel;
|
||||
class QVBoxLayout;
|
||||
|
||||
class WiimoteEmuMotionControl final : public MappingWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WiimoteEmuMotionControl(MappingWindow* window);
|
||||
|
||||
InputConfig* GetConfig() override;
|
||||
|
||||
private:
|
||||
void LoadSettings() override;
|
||||
void SaveSettings() override;
|
||||
void CreateMainLayout();
|
||||
|
||||
// Main
|
||||
QHBoxLayout* m_main_layout;
|
||||
};
|
216
Source/Core/DolphinQt/Config/NewPatchDialog.cpp
Normal file
216
Source/Core/DolphinQt/Config/NewPatchDialog.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/NewPatchDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Core/PatchEngine.h"
|
||||
|
||||
NewPatchDialog::NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch)
|
||||
: QDialog(parent), m_patch(patch)
|
||||
{
|
||||
setWindowTitle(tr("Patch Editor"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
for (size_t i = 0; i < m_patch.entries.size(); i++)
|
||||
{
|
||||
m_entry_layout->addWidget(CreateEntry(static_cast<int>(i)));
|
||||
}
|
||||
|
||||
if (m_patch.entries.empty())
|
||||
{
|
||||
AddEntry();
|
||||
m_patch.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NewPatchDialog::CreateWidgets()
|
||||
{
|
||||
m_name_edit = new QLineEdit;
|
||||
m_name_edit->setPlaceholderText(tr("Patch name"));
|
||||
m_name_edit->setText(QString::fromStdString(m_patch.name));
|
||||
|
||||
m_entry_widget = new QWidget;
|
||||
m_entry_layout = new QVBoxLayout;
|
||||
|
||||
auto* scroll_area = new QScrollArea;
|
||||
m_entry_widget->setLayout(m_entry_layout);
|
||||
scroll_area->setWidget(m_entry_widget);
|
||||
scroll_area->setWidgetResizable(true);
|
||||
|
||||
m_add_button = new QPushButton(tr("Add"));
|
||||
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(m_name_edit, 0, 0);
|
||||
layout->addWidget(scroll_area, 1, 0);
|
||||
layout->addWidget(m_add_button, 2, 0);
|
||||
layout->addWidget(m_button_box, 3, 0);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void NewPatchDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_name_edit, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this](const QString& name) { m_patch.name = name.toStdString(); });
|
||||
|
||||
connect(m_add_button, &QPushButton::pressed, this, &NewPatchDialog::AddEntry);
|
||||
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &NewPatchDialog::accept);
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void NewPatchDialog::AddEntry()
|
||||
{
|
||||
m_patch.entries.emplace_back();
|
||||
|
||||
m_entry_layout->addWidget(CreateEntry(static_cast<int>(m_patch.entries.size() - 1)));
|
||||
}
|
||||
|
||||
QGroupBox* NewPatchDialog::CreateEntry(int index)
|
||||
{
|
||||
QGroupBox* box = new QGroupBox();
|
||||
|
||||
auto* type = new QGroupBox(tr("Type"));
|
||||
auto* type_layout = new QHBoxLayout;
|
||||
auto* remove = new QPushButton(tr("Remove"));
|
||||
|
||||
auto* byte = new QRadioButton(tr("8-bit"));
|
||||
auto* word = new QRadioButton(tr("16-bit"));
|
||||
auto* dword = new QRadioButton(tr("32-bit"));
|
||||
|
||||
type_layout->addWidget(byte);
|
||||
type_layout->addWidget(word);
|
||||
type_layout->addWidget(dword);
|
||||
type->setLayout(type_layout);
|
||||
|
||||
auto* offset = new QLineEdit;
|
||||
auto* value = new QLineEdit;
|
||||
|
||||
m_edits.push_back(offset);
|
||||
m_edits.push_back(value);
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
layout->addWidget(type, 0, 0, 1, -1);
|
||||
layout->addWidget(new QLabel(tr("Offset:")), 1, 0);
|
||||
layout->addWidget(offset, 1, 1);
|
||||
layout->addWidget(new QLabel(tr("Value:")), 2, 0);
|
||||
layout->addWidget(value, 2, 1);
|
||||
layout->addWidget(remove, 3, 0, 1, -1);
|
||||
box->setLayout(layout);
|
||||
|
||||
connect(offset, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this, index, offset](const QString& text) {
|
||||
bool okay = true;
|
||||
m_patch.entries[index].address = text.toUInt(&okay, 16);
|
||||
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
font.setBold(!okay);
|
||||
|
||||
if (!okay)
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
|
||||
offset->setFont(font);
|
||||
offset->setPalette(palette);
|
||||
});
|
||||
|
||||
connect(value, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this, index, value](const QString& text) {
|
||||
bool okay;
|
||||
m_patch.entries[index].value = text.toUInt(&okay, 16);
|
||||
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
font.setBold(!okay);
|
||||
|
||||
if (!okay)
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
|
||||
value->setFont(font);
|
||||
value->setPalette(palette);
|
||||
});
|
||||
|
||||
connect(remove, &QPushButton::pressed, [this, box, index] {
|
||||
if (m_patch.entries.size() > 1)
|
||||
{
|
||||
m_entry_layout->removeWidget(box);
|
||||
m_patch.entries.erase(m_patch.entries.begin() + index);
|
||||
m_id--;
|
||||
}
|
||||
});
|
||||
|
||||
connect(byte, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PatchType::Patch8Bit;
|
||||
});
|
||||
|
||||
connect(word, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PatchType::Patch16Bit;
|
||||
});
|
||||
|
||||
connect(dword, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PatchType::Patch32Bit;
|
||||
});
|
||||
|
||||
auto entry_type = m_patch.entries[index].type;
|
||||
|
||||
byte->setChecked(entry_type == PatchEngine::PatchType::Patch8Bit);
|
||||
word->setChecked(entry_type == PatchEngine::PatchType::Patch16Bit);
|
||||
dword->setChecked(entry_type == PatchEngine::PatchType::Patch32Bit);
|
||||
|
||||
offset->setText(
|
||||
QStringLiteral("%1").arg(m_patch.entries[index].address, 10, 16, QLatin1Char('0')));
|
||||
value->setText(QStringLiteral("%1").arg(m_patch.entries[index].value, 10, 16, QLatin1Char('0')));
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
void NewPatchDialog::accept()
|
||||
{
|
||||
if (m_name_edit->text().isEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("You have to enter a name."));
|
||||
return;
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
|
||||
for (const auto* edit : m_edits)
|
||||
{
|
||||
edit->text().toUInt(&valid, 16);
|
||||
if (!valid)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Some values you provided are invalid.\nPlease check the highlighted values."));
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
47
Source/Core/DolphinQt/Config/NewPatchDialog.h
Normal file
47
Source/Core/DolphinQt/Config/NewPatchDialog.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
|
||||
namespace PatchEngine
|
||||
{
|
||||
struct Patch;
|
||||
}
|
||||
|
||||
class QDialogButtonBox;
|
||||
class QGroupBox;
|
||||
class QLineEdit;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
|
||||
class NewPatchDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
explicit NewPatchDialog(QWidget* parent, PatchEngine::Patch& patch);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddEntry();
|
||||
|
||||
void accept() override;
|
||||
|
||||
QGroupBox* CreateEntry(int index);
|
||||
|
||||
QLineEdit* m_name_edit;
|
||||
QWidget* m_entry_widget;
|
||||
QVBoxLayout* m_entry_layout;
|
||||
QPushButton* m_add_button;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
std::vector<QLineEdit*> m_edits;
|
||||
|
||||
PatchEngine::Patch& m_patch;
|
||||
int m_id = 0;
|
||||
};
|
185
Source/Core/DolphinQt/Config/PatchesWidget.cpp
Normal file
185
Source/Core/DolphinQt/Config/PatchesWidget.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/Config/PatchesWidget.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
|
||||
#include "DolphinQt/Config/NewPatchDialog.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
PatchesWidget::PatchesWidget(const UICommon::GameFile& game)
|
||||
: m_game(game), m_game_id(game.GetGameID()), m_game_revision(game.GetRevision())
|
||||
{
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision);
|
||||
|
||||
PatchEngine::LoadPatchSection("OnFrame", m_patches, game_ini_default, game_ini_local);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
Update();
|
||||
|
||||
UpdateActions();
|
||||
}
|
||||
|
||||
void PatchesWidget::CreateWidgets()
|
||||
{
|
||||
m_list = new QListWidget;
|
||||
m_add_button = new QPushButton(tr("&Add..."));
|
||||
m_edit_button = new QPushButton();
|
||||
m_remove_button = new QPushButton(tr("&Remove"));
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(m_list, 0, 0, 1, -1);
|
||||
layout->addWidget(m_add_button, 1, 0);
|
||||
layout->addWidget(m_edit_button, 1, 2);
|
||||
layout->addWidget(m_remove_button, 1, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void PatchesWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_list, &QListWidget::itemSelectionChanged, this, &PatchesWidget::UpdateActions);
|
||||
connect(m_list, &QListWidget::itemChanged, this, &PatchesWidget::OnItemChanged);
|
||||
connect(m_remove_button, &QPushButton::pressed, this, &PatchesWidget::OnRemove);
|
||||
connect(m_add_button, &QPushButton::pressed, this, &PatchesWidget::OnAdd);
|
||||
connect(m_edit_button, &QPushButton::pressed, this, &PatchesWidget::OnEdit);
|
||||
}
|
||||
|
||||
void PatchesWidget::OnItemChanged(QListWidgetItem* item)
|
||||
{
|
||||
m_patches[m_list->row(item)].active = (item->checkState() == Qt::Checked);
|
||||
SavePatches();
|
||||
}
|
||||
|
||||
void PatchesWidget::OnAdd()
|
||||
{
|
||||
PatchEngine::Patch patch;
|
||||
patch.user_defined = true;
|
||||
|
||||
if (NewPatchDialog(this, patch).exec())
|
||||
{
|
||||
m_patches.push_back(patch);
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::OnEdit()
|
||||
{
|
||||
if (m_list->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
auto* item = m_list->selectedItems()[0];
|
||||
|
||||
auto patch = m_patches[m_list->row(item)];
|
||||
|
||||
if (!patch.user_defined)
|
||||
{
|
||||
// i18n: If there is a pre-defined patch with the name %1 and the user wants to edit it,
|
||||
// a copy of it gets created with this name
|
||||
patch.name = tr("%1 (Copy)").arg(QString::fromStdString(patch.name)).toStdString();
|
||||
}
|
||||
|
||||
if (NewPatchDialog(this, patch).exec())
|
||||
{
|
||||
if (patch.user_defined)
|
||||
{
|
||||
m_patches[m_list->row(item)] = patch;
|
||||
}
|
||||
else
|
||||
{
|
||||
patch.user_defined = true;
|
||||
m_patches.push_back(patch);
|
||||
}
|
||||
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::OnRemove()
|
||||
{
|
||||
if (m_list->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
m_patches.erase(m_patches.begin() + m_list->row(m_list->selectedItems()[0]));
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
|
||||
void PatchesWidget::SavePatches()
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
std::vector<std::string> lines_enabled;
|
||||
|
||||
for (const auto& patch : m_patches)
|
||||
{
|
||||
if (patch.active)
|
||||
lines_enabled.push_back("$" + patch.name);
|
||||
|
||||
if (!patch.user_defined)
|
||||
continue;
|
||||
|
||||
lines.push_back("$" + patch.name);
|
||||
|
||||
for (const auto& entry : patch.entries)
|
||||
{
|
||||
lines.push_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address,
|
||||
PatchEngine::PatchTypeAsString(entry.type), entry.value));
|
||||
}
|
||||
}
|
||||
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
game_ini_local.SetLines("OnFrame_Enabled", lines_enabled);
|
||||
game_ini_local.SetLines("OnFrame", lines);
|
||||
|
||||
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
}
|
||||
|
||||
void PatchesWidget::Update()
|
||||
{
|
||||
m_list->clear();
|
||||
|
||||
for (const auto& patch : m_patches)
|
||||
{
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(patch.name));
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(patch.active ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, patch.user_defined);
|
||||
|
||||
m_list->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::UpdateActions()
|
||||
{
|
||||
bool selected = !m_list->selectedItems().isEmpty();
|
||||
|
||||
auto* item = selected ? m_list->selectedItems()[0] : nullptr;
|
||||
|
||||
bool user_defined = selected ? item->data(Qt::UserRole).toBool() : true;
|
||||
|
||||
m_edit_button->setEnabled(selected);
|
||||
m_edit_button->setText(user_defined ? tr("&Edit...") : tr("&Clone..."));
|
||||
m_remove_button->setEnabled(selected && user_defined);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user