Move DolphinQt2 to DolphinQt

This commit is contained in:
spycrab
2018-07-07 00:40:15 +02:00
parent 059880bb16
commit 13ba24c5a6
233 changed files with 392 additions and 392 deletions

View 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);
}

View 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);
};

View 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()

View 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();
}

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

View 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("&lt;"), QStringLiteral("<"))
.replace(QStringLiteral("&gt;"), 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 ? &current_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);
}

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

View 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();
}

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

View 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);
}

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

View 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();
}

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

View 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."));
}

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

View 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));
}
}

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

View 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("&lt;"), QStringLiteral("<"))
.replace(QStringLiteral("&gt;"), 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)));
}

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

View 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
}

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

View 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();
}

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

View 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);
}

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

View 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));
}

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

View 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);
}

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

View 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);
}

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

View 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);
}

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

View 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);
}

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

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

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

View 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);
}

View 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();
};

View File

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

View File

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

View 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);
}

View File

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

View 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);
}

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

View 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);
}

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

View 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);
}

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

View 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();
}

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

View 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();
}

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

View 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();
}

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

View File

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

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

View 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();
}

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

View 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();
}

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

View 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();
}

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

View 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();
}

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

View 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();
}

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

View 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();
}

View 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();
};

View 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();
}

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

View 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();
}

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

View 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);
});
}

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

View 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());
}

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

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

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

View 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("");
}
}

View 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);
}

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

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

View 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);
}

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

View 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());
}

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

View 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();
}

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

View 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();
}

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

View 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);
}

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

View 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();
}

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

View File

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

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

View 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();
}

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

View 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