o add bsnes
This commit is contained in:
85
bsnes/ui_qt/Makefile
Executable file
85
bsnes/ui_qt/Makefile
Executable file
@@ -0,0 +1,85 @@
|
||||
##############################
|
||||
### platform configuration ###
|
||||
##############################
|
||||
|
||||
objects := main $(if $(call streq,$(platform),win),resource) $(objects)
|
||||
|
||||
moc = moc
|
||||
rcc = rcc
|
||||
|
||||
ifeq ($(platform),x) # X11
|
||||
link += $(call mklib,Xtst)
|
||||
link += `pkg-config --libs QtCore QtGui`
|
||||
qtflags = `pkg-config --cflags QtCore QtGui`
|
||||
else ifeq ($(platform),win) # Windows
|
||||
qtdir = c:/qt450
|
||||
|
||||
link += $(call mklibpath,$(qtdir)/lib)
|
||||
link += $(call mklibpath,$(qtdir)/plugins/imageformats)
|
||||
|
||||
link += $(call mklib,mingw32)
|
||||
link += $(call mklib,qtmain)
|
||||
link += $(call mklib,QtGui)
|
||||
link += $(call mklib,comdlg32)
|
||||
link += $(call mklib,oleaut32)
|
||||
link += $(call mklib,imm32)
|
||||
link += $(call mklib,winmm)
|
||||
link += $(call mklib,winspool)
|
||||
link += $(call mklib,msimg32)
|
||||
link += $(call mklib,QtCore)
|
||||
link += $(call mklib,ole32)
|
||||
link += $(call mklib,advapi32)
|
||||
link += $(call mklib,ws2_32)
|
||||
link += $(call mklib,uuid)
|
||||
link += $(call mklib,gdi32)
|
||||
|
||||
# optional image-file support:
|
||||
# link += $(call mklib,qjpeg)
|
||||
# link += $(call mklib,qmng)
|
||||
|
||||
qtflags = $(call mkincpath,$(qtdir)/include)
|
||||
qtflags += $(call mkincpath,$(qtdir)/include/QtCore)
|
||||
qtflags += $(call mkincpath,$(qtdir)/include/QtGui)
|
||||
endif
|
||||
|
||||
moc_objects = \
|
||||
$(ui)/base/main.moc \
|
||||
$(ui)/base/loader.moc \
|
||||
$(ui)/base/htmlviewer.moc \
|
||||
$(ui)/base/about.moc \
|
||||
$(ui)/settings/settings.moc \
|
||||
$(ui)/settings/video.moc \
|
||||
$(ui)/settings/audio.moc \
|
||||
$(ui)/settings/input.moc \
|
||||
$(ui)/settings/paths.moc \
|
||||
$(ui)/settings/cheateditor.moc \
|
||||
$(ui)/settings/advanced.moc \
|
||||
$(ui)/settings/utility/inputcapture.moc \
|
||||
$(ui)/settings/utility/codeeditor.moc \
|
||||
|
||||
#############
|
||||
### rules ###
|
||||
#############
|
||||
|
||||
%.moc: $<; $(moc) $(patsubst %.moc,%.hpp,$@) -o $@
|
||||
$(foreach object,$(moc_objects),$(eval $(object): $(patsubst %.moc,%.hpp,$(object))))
|
||||
|
||||
obj/main.$(obj): $(ui)/main.cpp \
|
||||
$(ui)/* $(ui)/input/* $(ui)/utility/* $(ui)/base/* $(ui)/settings/* $(ui)/settings/utility/* \
|
||||
data/*
|
||||
$(call compile,$(qtflags))
|
||||
|
||||
$(ui)/resource/resource.rcc: $(ui)/resource/resource.qrc data/*
|
||||
$(rcc) $(ui)/resource/resource.qrc -o $(ui)/resource/resource.rcc
|
||||
|
||||
obj/resource.$(obj): $(ui)/resource/resource.rc
|
||||
windres $(ui)/resource/resource.rc obj/resource.$(obj)
|
||||
|
||||
###############
|
||||
### targets ###
|
||||
###############
|
||||
|
||||
ui_build: $(ui)/resource/resource.rcc $(moc_objects);
|
||||
ui_clean:
|
||||
-$(foreach object,$(moc_objects),@$(call delete,$(object)))
|
||||
-@$(call delete,$(ui)/resource/resource.rcc)
|
||||
35
bsnes/ui_qt/base/about.cpp
Executable file
35
bsnes/ui_qt/base/about.cpp
Executable file
@@ -0,0 +1,35 @@
|
||||
void AboutWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("about-window");
|
||||
window->setWindowTitle("About bsnes ...");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0); {
|
||||
logo = new Logo;
|
||||
logo->setFixedSize(600, 106);
|
||||
layout->addWidget(logo);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
info = new QLabel(
|
||||
"<table>"
|
||||
"<tr><td align='right'><b>Version: </b></td><td>" BSNES_VERSION "</td></tr>"
|
||||
"<tr><td align='right'><b>Author: </b></td><td>byuu</td></tr>"
|
||||
"<tr><td align='right'><b>Homepage: </b></td><td>http://byuu.org/</td></tr>"
|
||||
"</table>"
|
||||
);
|
||||
layout->addWidget(info);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
}
|
||||
|
||||
window->setLayout(layout);
|
||||
}
|
||||
|
||||
void AboutWindow::Logo::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
QPixmap pixmap(":/logo.png");
|
||||
painter.drawPixmap(0, 0, pixmap);
|
||||
}
|
||||
16
bsnes/ui_qt/base/about.hpp
Executable file
16
bsnes/ui_qt/base/about.hpp
Executable file
@@ -0,0 +1,16 @@
|
||||
class AboutWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *window;
|
||||
QVBoxLayout *layout;
|
||||
struct Logo : public QWidget {
|
||||
void paintEvent(QPaintEvent*);
|
||||
} *logo;
|
||||
QLabel *info;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
|
||||
public slots:
|
||||
} *winAbout;
|
||||
20
bsnes/ui_qt/base/htmlviewer.cpp
Executable file
20
bsnes/ui_qt/base/htmlviewer.cpp
Executable file
@@ -0,0 +1,20 @@
|
||||
void HtmlViewerWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("html-window");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0); {
|
||||
document = new QTextBrowser;
|
||||
layout->addWidget(document);
|
||||
}
|
||||
|
||||
window->setLayout(layout);
|
||||
window->setMinimumSize(560, 480);
|
||||
}
|
||||
|
||||
void HtmlViewerWindow::show(const char *title, const char *htmlData) {
|
||||
document->setHtml(utf8() << htmlData);
|
||||
window->setWindowTitle(title);
|
||||
utility.showCentered(window);
|
||||
}
|
||||
13
bsnes/ui_qt/base/htmlviewer.hpp
Executable file
13
bsnes/ui_qt/base/htmlviewer.hpp
Executable file
@@ -0,0 +1,13 @@
|
||||
class HtmlViewerWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *window;
|
||||
QVBoxLayout *layout;
|
||||
QTextBrowser *document;
|
||||
|
||||
void setup();
|
||||
void show(const char *title, const char *htmlData);
|
||||
|
||||
public slots:
|
||||
} *winHtmlViewer;
|
||||
198
bsnes/ui_qt/base/loader.cpp
Executable file
198
bsnes/ui_qt/base/loader.cpp
Executable file
@@ -0,0 +1,198 @@
|
||||
void LoaderWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("loader-window");
|
||||
window->setMinimumWidth(520);
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0);
|
||||
|
||||
grid = new QGridLayout; {
|
||||
baseLabel = new QLabel("Base cartridge:");
|
||||
grid->addWidget(baseLabel, 0, 0);
|
||||
|
||||
baseFile = new QLineEdit;
|
||||
baseFile->setReadOnly(true);
|
||||
grid->addWidget(baseFile, 0, 1);
|
||||
|
||||
baseBrowse = new QPushButton("Browse ...");
|
||||
grid->addWidget(baseBrowse, 0, 2);
|
||||
|
||||
baseClear = new QPushButton("Clear");
|
||||
grid->addWidget(baseClear, 0, 3);
|
||||
|
||||
slot1Label = new QLabel("Slot A cartridge:");
|
||||
grid->addWidget(slot1Label, 1, 0);
|
||||
|
||||
slot1File = new QLineEdit;
|
||||
slot1File->setReadOnly(true);
|
||||
grid->addWidget(slot1File, 1, 1);
|
||||
|
||||
slot1Browse = new QPushButton("Browse ...");
|
||||
grid->addWidget(slot1Browse, 1, 2);
|
||||
|
||||
slot1Clear = new QPushButton("Clear");
|
||||
grid->addWidget(slot1Clear, 1, 3);
|
||||
|
||||
slot2Label = new QLabel("Slot B cartridge:");
|
||||
grid->addWidget(slot2Label, 2, 0);
|
||||
|
||||
slot2File = new QLineEdit;
|
||||
slot2File->setReadOnly(true);
|
||||
grid->addWidget(slot2File, 2, 1);
|
||||
|
||||
slot2Browse = new QPushButton("Browse ...");
|
||||
grid->addWidget(slot2Browse, 2, 2);
|
||||
|
||||
slot2Clear = new QPushButton("Clear");
|
||||
grid->addWidget(slot2Clear, 2, 3);
|
||||
}
|
||||
grid->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(grid);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
controls = new QHBoxLayout; {
|
||||
load = new QPushButton("Load");
|
||||
controls->addWidget(load);
|
||||
|
||||
cancel = new QPushButton("Cancel");
|
||||
controls->addWidget(cancel);
|
||||
}
|
||||
controls->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(controls);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
window->setLayout(layout);
|
||||
connect(baseBrowse, SIGNAL(released()), this, SLOT(selectBaseCartridge()));
|
||||
connect(baseClear, SIGNAL(released()), this, SLOT(clearBaseCartridge()));
|
||||
connect(slot1Browse, SIGNAL(released()), this, SLOT(selectSlot1Cartridge()));
|
||||
connect(slot1Clear, SIGNAL(released()), this, SLOT(clearSlot1Cartridge()));
|
||||
connect(slot2Browse, SIGNAL(released()), this, SLOT(selectSlot2Cartridge()));
|
||||
connect(slot2Clear, SIGNAL(released()), this, SLOT(clearSlot2Cartridge()));
|
||||
connect(load, SIGNAL(released()), this, SLOT(onLoad()));
|
||||
connect(cancel, SIGNAL(released()), this, SLOT(onCancel()));
|
||||
}
|
||||
|
||||
void LoaderWindow::syncUi() {
|
||||
//only allow load when a base file is specified ...
|
||||
load->setEnabled(baseFile->text().length() > 0);
|
||||
}
|
||||
|
||||
void LoaderWindow::loadBsxSlottedCartridge(const char *filebase, const char *fileSlot1) {
|
||||
window->hide();
|
||||
baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show();
|
||||
slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show();
|
||||
slot2Label->hide(), slot2File->hide(), slot2Browse->hide(), slot2Clear->hide();
|
||||
|
||||
slot1Label->setText("Slot cartridge:");
|
||||
|
||||
baseFile->setText(filebase);
|
||||
slot1File->setText(fileSlot1);
|
||||
|
||||
syncUi();
|
||||
mode = ModeBsxSlotted;
|
||||
showWindow("Load BS-X Slotted Cartridge");
|
||||
}
|
||||
|
||||
void LoaderWindow::loadBsxCartridge(const char *fileBase, const char *fileSlot1) {
|
||||
window->hide();
|
||||
baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show();
|
||||
slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show();
|
||||
slot2Label->hide(), slot2File->hide(), slot2Browse->hide(), slot2Clear->hide();
|
||||
|
||||
slot1Label->setText("Slot cartridge:");
|
||||
|
||||
baseFile->setText(fileBase);
|
||||
slot1File->setText(fileSlot1);
|
||||
|
||||
syncUi();
|
||||
mode = ModeBsx;
|
||||
showWindow("Load BS-X Cartridge");
|
||||
}
|
||||
|
||||
void LoaderWindow::loadSufamiTurboCartridge(const char *fileBase, const char *fileSlot1, const char *fileSlot2) {
|
||||
window->hide();
|
||||
baseLabel->show(), baseFile->show(), baseBrowse->show(), baseClear->show();
|
||||
slot1Label->show(), slot1File->show(), slot1Browse->show(), slot1Clear->show();
|
||||
slot2Label->show(), slot2File->show(), slot2Browse->show(), slot2Clear->show();
|
||||
|
||||
slot1Label->setText("Slot A cartridge:");
|
||||
slot2Label->setText("Slot B cartridge:");
|
||||
|
||||
baseFile->setText(fileBase);
|
||||
slot1File->setText(fileSlot1);
|
||||
slot2File->setText(fileSlot2);
|
||||
|
||||
syncUi();
|
||||
mode = ModeSufamiTurbo;
|
||||
showWindow("Load Sufami Turbo Cartridge");
|
||||
}
|
||||
|
||||
void LoaderWindow::showWindow(const char *title) {
|
||||
window->setWindowTitle(title);
|
||||
utility.showCentered(window);
|
||||
load->setFocus();
|
||||
}
|
||||
|
||||
void LoaderWindow::selectBaseCartridge() {
|
||||
string filename = utility.selectCartridge();
|
||||
if(filename.length() > 0) baseFile->setText(utf8() << filename);
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::clearBaseCartridge() {
|
||||
baseFile->setText("");
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::selectSlot1Cartridge() {
|
||||
string filename = utility.selectCartridge();
|
||||
if(filename.length() > 0) slot1File->setText(utf8() << filename);
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::clearSlot1Cartridge() {
|
||||
slot1File->setText("");
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::selectSlot2Cartridge() {
|
||||
string filename = utility.selectCartridge();
|
||||
if(filename.length() > 0) slot2File->setText(utf8() << filename);
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::clearSlot2Cartridge() {
|
||||
slot2File->setText("");
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void LoaderWindow::onLoad() {
|
||||
window->hide();
|
||||
string base = baseFile->text().toUtf8().data();
|
||||
string slot1 = slot1File->text().toUtf8().data();
|
||||
string slot2 = slot2File->text().toUtf8().data();
|
||||
|
||||
switch(mode) {
|
||||
case ModeBsxSlotted: {
|
||||
utility.loadCartridgeBsxSlotted(base, slot1);
|
||||
} break;
|
||||
|
||||
case ModeBsx: {
|
||||
snes.config.path.bsx = base;
|
||||
utility.loadCartridgeBsx(base, slot1);
|
||||
} break;
|
||||
|
||||
case ModeSufamiTurbo: {
|
||||
snes.config.path.st = base;
|
||||
utility.loadCartridgeSufamiTurbo(base, slot1, slot2);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoaderWindow::onCancel() {
|
||||
window->hide();
|
||||
}
|
||||
45
bsnes/ui_qt/base/loader.hpp
Executable file
45
bsnes/ui_qt/base/loader.hpp
Executable file
@@ -0,0 +1,45 @@
|
||||
class LoaderWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *window;
|
||||
QVBoxLayout *layout;
|
||||
QGridLayout *grid;
|
||||
QLabel *baseLabel;
|
||||
QLineEdit *baseFile;
|
||||
QPushButton *baseBrowse;
|
||||
QPushButton *baseClear;
|
||||
QLabel *slot1Label;
|
||||
QLineEdit *slot1File;
|
||||
QPushButton *slot1Browse;
|
||||
QPushButton *slot1Clear;
|
||||
QLabel *slot2Label;
|
||||
QLineEdit *slot2File;
|
||||
QPushButton *slot2Browse;
|
||||
QPushButton *slot2Clear;
|
||||
QHBoxLayout *controls;
|
||||
QPushButton *load;
|
||||
QPushButton *cancel;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
void loadBsxSlottedCartridge(const char*, const char*);
|
||||
void loadBsxCartridge(const char*, const char*);
|
||||
void loadSufamiTurboCartridge(const char*, const char*, const char*);
|
||||
|
||||
public slots:
|
||||
void selectBaseCartridge();
|
||||
void clearBaseCartridge();
|
||||
void selectSlot1Cartridge();
|
||||
void clearSlot1Cartridge();
|
||||
void selectSlot2Cartridge();
|
||||
void clearSlot2Cartridge();
|
||||
|
||||
void onLoad();
|
||||
void onCancel();
|
||||
|
||||
private:
|
||||
enum mode_t { ModeBsxSlotted, ModeBsx, ModeSufamiTurbo } mode;
|
||||
void showWindow(const char *title);
|
||||
} *winLoader;
|
||||
371
bsnes/ui_qt/base/main.cpp
Executable file
371
bsnes/ui_qt/base/main.cpp
Executable file
@@ -0,0 +1,371 @@
|
||||
void MainWindow::setup() {
|
||||
window = new Window;
|
||||
window->setObjectName("main-window");
|
||||
window->setWindowTitle(BSNES_TITLE);
|
||||
|
||||
system = window->menuBar()->addMenu("&System");
|
||||
system_load = system->addAction("&Load Cartridge ...");
|
||||
system->addSeparator();
|
||||
system_power = system->addMenu("&Power");
|
||||
system_power_on = system_power->addAction("On");
|
||||
system_power_on->setCheckable(true);
|
||||
system_power_off = system_power->addAction("Off");
|
||||
system_power_off->setCheckable(true);
|
||||
system_reset = system->addAction("&Reset");
|
||||
system->addSeparator();
|
||||
system_port1 = system->addMenu("Controller Port 1");
|
||||
system_port1_none = system_port1->addAction("&None");
|
||||
system_port1_none->setCheckable(true);
|
||||
system_port1_joypad = system_port1->addAction("&Joypad");
|
||||
system_port1_joypad->setCheckable(true);
|
||||
system_port1_multitap = system_port1->addAction("&Multitap");
|
||||
system_port1_multitap->setCheckable(true);
|
||||
system_port1_mouse = system_port1->addAction("M&ouse");
|
||||
system_port1_mouse->setCheckable(true);
|
||||
system_port2 = system->addMenu("Controller Port 2");
|
||||
system_port2_none = system_port2->addAction("&None");
|
||||
system_port2_none->setCheckable(true);
|
||||
system_port2_joypad = system_port2->addAction("&Joypad");
|
||||
system_port2_joypad->setCheckable(true);
|
||||
system_port2_multitap = system_port2->addAction("&Multitap");
|
||||
system_port2_multitap->setCheckable(true);
|
||||
system_port2_mouse = system_port2->addAction("M&ouse");
|
||||
system_port2_mouse->setCheckable(true);
|
||||
system_port2_superscope = system_port2->addAction("&Super Scope");
|
||||
system_port2_superscope->setCheckable(true);
|
||||
system_port2_justifier = system_port2->addAction("&Justifier");
|
||||
system_port2_justifier->setCheckable(true);
|
||||
system_port2_justifiers = system_port2->addAction("&Two Justifiers");
|
||||
system_port2_justifiers->setCheckable(true);
|
||||
system->addSeparator();
|
||||
system_exit = system->addAction("E&xit");
|
||||
system_exit->setMenuRole(QAction::QuitRole);
|
||||
|
||||
settings = window->menuBar()->addMenu("S&ettings");
|
||||
settings_videoMode = settings->addMenu("&Video Mode");
|
||||
settings_videoMode_1x = settings_videoMode->addAction("Scale &1x");
|
||||
settings_videoMode_1x->setCheckable(true);
|
||||
settings_videoMode_2x = settings_videoMode->addAction("Scale &2x");
|
||||
settings_videoMode_2x->setCheckable(true);
|
||||
settings_videoMode_3x = settings_videoMode->addAction("Scale &3x");
|
||||
settings_videoMode_3x->setCheckable(true);
|
||||
settings_videoMode_4x = settings_videoMode->addAction("Scale &4x");
|
||||
settings_videoMode_4x->setCheckable(true);
|
||||
settings_videoMode_max = settings_videoMode->addAction("Scale &Max");
|
||||
settings_videoMode_max->setCheckable(true);
|
||||
settings_videoMode_max->setStatusTip("Scale video output to fill as much of the screen as possible");
|
||||
settings_videoMode->addSeparator();
|
||||
settings_videoMode_correctAspectRatio = settings_videoMode->addAction("&Correct Aspect Ratio");
|
||||
settings_videoMode_correctAspectRatio->setStatusTip("Match pixel width-to-height ratio of TV");
|
||||
settings_videoMode_correctAspectRatio->setCheckable(true);
|
||||
settings_videoMode_fullscreen = settings_videoMode->addAction("&Fullscreen");
|
||||
settings_videoMode_fullscreen->setCheckable(true);
|
||||
settings_videoMode->addSeparator();
|
||||
settings_videoMode_ntsc = settings_videoMode->addAction("&NTSC");
|
||||
settings_videoMode_ntsc->setCheckable(true);
|
||||
settings_videoMode_ntsc->setStatusTip("Size video output window to match NTSC TV spec");
|
||||
settings_videoMode_pal = settings_videoMode->addAction("&PAL");
|
||||
settings_videoMode_pal->setCheckable(true);
|
||||
settings_videoMode_pal->setStatusTip("Size video output window to match PAL TV spec");
|
||||
settings_videoFilter = settings->addMenu("Video &Filter");
|
||||
settings_videoFilter_point = settings_videoFilter->addAction("&Point");
|
||||
settings_videoFilter_point->setCheckable(true);
|
||||
settings_videoFilter_point->setStatusTip("Use pixellated hardware video scaling");
|
||||
settings_videoFilter_linear = settings_videoFilter->addAction("&Linear");
|
||||
settings_videoFilter_linear->setCheckable(true);
|
||||
settings_videoFilter_linear->setStatusTip("Use smoothed hardware video scaling");
|
||||
settings_videoFilter->addSeparator();
|
||||
settings_videoFilter_none = settings_videoFilter->addAction("&None");
|
||||
settings_videoFilter_none->setCheckable(true);
|
||||
settings_videoFilter_scanline = settings_videoFilter->addAction("&Scanline");
|
||||
settings_videoFilter_scanline->setCheckable(true);
|
||||
settings_videoFilter_scale2x = settings_videoFilter->addAction("S&cale2x");
|
||||
settings_videoFilter_scale2x->setCheckable(true);
|
||||
settings_videoFilter_hq2x = settings_videoFilter->addAction("&HQ2x");
|
||||
settings_videoFilter_hq2x->setCheckable(true);
|
||||
settings_videoFilter_ntsc = settings_videoFilter->addAction("N&TSC");
|
||||
settings_videoFilter_ntsc->setCheckable(true);
|
||||
settings->addSeparator();
|
||||
settings_muteAudio = settings->addAction("&Mute Audio Output");
|
||||
settings_muteAudio->setCheckable(true);
|
||||
settings->addSeparator();
|
||||
settings_emulationSpeed = settings->addMenu("&Emulation Speed");
|
||||
settings_emulationSpeed_slowest = settings_emulationSpeed->addAction("50%");
|
||||
settings_emulationSpeed_slowest->setCheckable(true);
|
||||
settings_emulationSpeed_slow = settings_emulationSpeed->addAction("75%");
|
||||
settings_emulationSpeed_slow->setCheckable(true);
|
||||
settings_emulationSpeed_normal = settings_emulationSpeed->addAction("100%");
|
||||
settings_emulationSpeed_normal->setCheckable(true);
|
||||
settings_emulationSpeed_fast = settings_emulationSpeed->addAction("150%");
|
||||
settings_emulationSpeed_fast->setCheckable(true);
|
||||
settings_emulationSpeed_fastest = settings_emulationSpeed->addAction("200%");
|
||||
settings_emulationSpeed_fastest->setCheckable(true);
|
||||
settings_emulationSpeed->addSeparator();
|
||||
settings_emulationSpeed_syncVideo = settings_emulationSpeed->addAction("Sync &Video");
|
||||
settings_emulationSpeed_syncVideo->setCheckable(true);
|
||||
settings_emulationSpeed_syncVideo->setStatusTip("Sync video output to vertical refresh rate");
|
||||
settings_emulationSpeed_syncAudio = settings_emulationSpeed->addAction("Sync &Audio");
|
||||
settings_emulationSpeed_syncAudio->setCheckable(true);
|
||||
settings_emulationSpeed_syncAudio->setStatusTip("Sync audio output to sound card output rate");
|
||||
settings_configuration = settings->addAction("&Configuration ...");
|
||||
settings_configuration->setMenuRole(QAction::PreferencesRole);
|
||||
|
||||
help = window->menuBar()->addMenu("&Help");
|
||||
help_documentation = help->addAction("Documentation ...");
|
||||
help_license = help->addAction("License ...");
|
||||
help->addSeparator();
|
||||
help_about = help->addAction("&About ...");
|
||||
help_about->setMenuRole(QAction::AboutRole);
|
||||
|
||||
canvasContainer = new CanvasObject;
|
||||
canvasContainer->setAcceptDrops(true); {
|
||||
canvasContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
canvasContainer->setObjectName("backdrop");
|
||||
|
||||
canvasLayout = new QVBoxLayout; {
|
||||
canvasLayout->setMargin(0);
|
||||
canvasLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
canvas = new CanvasWidget;
|
||||
canvas->setAcceptDrops(true);
|
||||
canvas->setAttribute(Qt::WA_PaintOnScreen, true); //disable Qt painting on focus / resize
|
||||
|
||||
QPalette palette;
|
||||
palette.setColor(QPalette::Window, QColor(0, 0, 0));
|
||||
canvas->setPalette(palette);
|
||||
canvas->setAutoFillBackground(true);
|
||||
}
|
||||
canvasLayout->addWidget(canvas);
|
||||
}
|
||||
canvasContainer->setLayout(canvasLayout);
|
||||
|
||||
window->statusBar()->showMessage("");
|
||||
systemState = new QLabel;
|
||||
window->statusBar()->addPermanentWidget(systemState);
|
||||
|
||||
window->setCentralWidget(canvasContainer);
|
||||
|
||||
//slots
|
||||
connect(system_load, SIGNAL(triggered()), this, SLOT(loadCartridge()));
|
||||
connect(system_power_on, SIGNAL(triggered()), this, SLOT(powerOn()));
|
||||
connect(system_power_off, SIGNAL(triggered()), this, SLOT(powerOff()));
|
||||
connect(system_reset, SIGNAL(triggered()), this, SLOT(reset()));
|
||||
connect(system_port1_none, SIGNAL(triggered()), this, SLOT(setPort1None()));
|
||||
connect(system_port1_joypad, SIGNAL(triggered()), this, SLOT(setPort1Joypad()));
|
||||
connect(system_port1_multitap, SIGNAL(triggered()), this, SLOT(setPort1Multitap()));
|
||||
connect(system_port1_mouse, SIGNAL(triggered()), this, SLOT(setPort1Mouse()));
|
||||
connect(system_port2_none, SIGNAL(triggered()), this, SLOT(setPort2None()));
|
||||
connect(system_port2_joypad, SIGNAL(triggered()), this, SLOT(setPort2Joypad()));
|
||||
connect(system_port2_multitap, SIGNAL(triggered()), this, SLOT(setPort2Multitap()));
|
||||
connect(system_port2_mouse, SIGNAL(triggered()), this, SLOT(setPort2Mouse()));
|
||||
connect(system_port2_superscope, SIGNAL(triggered()), this, SLOT(setPort2SuperScope()));
|
||||
connect(system_port2_justifier, SIGNAL(triggered()), this, SLOT(setPort2Justifier()));
|
||||
connect(system_port2_justifiers, SIGNAL(triggered()), this, SLOT(setPort2Justifiers()));
|
||||
connect(system_exit, SIGNAL(triggered()), this, SLOT(quit()));
|
||||
connect(settings_videoMode_1x, SIGNAL(triggered()), this, SLOT(setVideoMode1x()));
|
||||
connect(settings_videoMode_2x, SIGNAL(triggered()), this, SLOT(setVideoMode2x()));
|
||||
connect(settings_videoMode_3x, SIGNAL(triggered()), this, SLOT(setVideoMode3x()));
|
||||
connect(settings_videoMode_4x, SIGNAL(triggered()), this, SLOT(setVideoMode4x()));
|
||||
connect(settings_videoMode_max, SIGNAL(triggered()), this, SLOT(setVideoModeMax()));
|
||||
connect(settings_videoMode_correctAspectRatio, SIGNAL(triggered()), this, SLOT(toggleAspectCorrection()));
|
||||
connect(settings_videoMode_fullscreen, SIGNAL(triggered()), this, SLOT(toggleFullscreen()));
|
||||
connect(settings_videoMode_ntsc, SIGNAL(triggered()), this, SLOT(setVideoNtsc()));
|
||||
connect(settings_videoMode_pal, SIGNAL(triggered()), this, SLOT(setVideoPal()));
|
||||
connect(settings_videoFilter_point, SIGNAL(triggered()), this, SLOT(setPointFilter()));
|
||||
connect(settings_videoFilter_linear, SIGNAL(triggered()), this, SLOT(setLinearFilter()));
|
||||
connect(settings_videoFilter_none, SIGNAL(triggered()), this, SLOT(setNoFilter()));
|
||||
connect(settings_videoFilter_scanline, SIGNAL(triggered()), this, SLOT(setScanlineFilter()));
|
||||
connect(settings_videoFilter_scale2x, SIGNAL(triggered()), this, SLOT(setScale2xFilter()));
|
||||
connect(settings_videoFilter_hq2x, SIGNAL(triggered()), this, SLOT(setHq2xFilter()));
|
||||
connect(settings_videoFilter_ntsc, SIGNAL(triggered()), this, SLOT(setNtscFilter()));
|
||||
connect(settings_muteAudio, SIGNAL(triggered()), this, SLOT(muteAudio()));
|
||||
connect(settings_emulationSpeed_slowest, SIGNAL(triggered()), this, SLOT(setSpeedSlowest()));
|
||||
connect(settings_emulationSpeed_slow, SIGNAL(triggered()), this, SLOT(setSpeedSlow()));
|
||||
connect(settings_emulationSpeed_normal, SIGNAL(triggered()), this, SLOT(setSpeedNormal()));
|
||||
connect(settings_emulationSpeed_fast, SIGNAL(triggered()), this, SLOT(setSpeedFast()));
|
||||
connect(settings_emulationSpeed_fastest, SIGNAL(triggered()), this, SLOT(setSpeedFastest()));
|
||||
connect(settings_emulationSpeed_syncVideo, SIGNAL(triggered()), this, SLOT(syncVideo()));
|
||||
connect(settings_emulationSpeed_syncAudio, SIGNAL(triggered()), this, SLOT(syncAudio()));
|
||||
connect(settings_configuration, SIGNAL(triggered()), this, SLOT(showConfigWindow()));
|
||||
connect(help_documentation, SIGNAL(triggered()), this, SLOT(showDocumentation()));
|
||||
connect(help_license, SIGNAL(triggered()), this, SLOT(showLicense()));
|
||||
connect(help_about, SIGNAL(triggered()), this, SLOT(showAbout()));
|
||||
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void MainWindow::syncUi() {
|
||||
system_power->setEnabled(cartridge.loaded());
|
||||
system_power_on->setChecked (application.power == true);
|
||||
system_power_off->setChecked(application.power == false);
|
||||
system_reset->setEnabled(cartridge.loaded() && application.power);
|
||||
|
||||
system_port1_none->setChecked (snes.config.controller_port1 == SNES::Input::DeviceNone);
|
||||
system_port1_joypad->setChecked (snes.config.controller_port1 == SNES::Input::DeviceJoypad);
|
||||
system_port1_multitap->setChecked (snes.config.controller_port1 == SNES::Input::DeviceMultitap);
|
||||
system_port1_mouse->setChecked (snes.config.controller_port1 == SNES::Input::DeviceMouse);
|
||||
system_port2_none->setChecked (snes.config.controller_port2 == SNES::Input::DeviceNone);
|
||||
system_port2_joypad->setChecked (snes.config.controller_port2 == SNES::Input::DeviceJoypad);
|
||||
system_port2_multitap->setChecked (snes.config.controller_port2 == SNES::Input::DeviceMultitap);
|
||||
system_port2_mouse->setChecked (snes.config.controller_port2 == SNES::Input::DeviceMouse);
|
||||
system_port2_superscope->setChecked(snes.config.controller_port2 == SNES::Input::DeviceSuperScope);
|
||||
system_port2_justifier->setChecked (snes.config.controller_port2 == SNES::Input::DeviceJustifier);
|
||||
system_port2_justifiers->setChecked(snes.config.controller_port2 == SNES::Input::DeviceJustifiers);
|
||||
|
||||
settings_videoMode_1x->setChecked (config.video.context->multiplier == 1);
|
||||
settings_videoMode_2x->setChecked (config.video.context->multiplier == 2);
|
||||
settings_videoMode_3x->setChecked (config.video.context->multiplier == 3);
|
||||
settings_videoMode_4x->setChecked (config.video.context->multiplier == 4);
|
||||
settings_videoMode_max->setChecked(config.video.context->multiplier >= 5);
|
||||
|
||||
settings_videoMode_correctAspectRatio->setChecked(config.video.context->correctAspectRatio);
|
||||
settings_videoMode_fullscreen->setChecked(config.video.isFullscreen);
|
||||
settings_videoMode_ntsc->setChecked(config.video.context->region == 0);
|
||||
settings_videoMode_pal->setChecked (config.video.context->region == 1);
|
||||
|
||||
settings_videoFilter_point->setChecked (config.video.context->hwFilter == 0);
|
||||
settings_videoFilter_linear->setChecked (config.video.context->hwFilter == 1);
|
||||
settings_videoFilter_none->setChecked (config.video.context->swFilter == 0);
|
||||
settings_videoFilter_scanline->setChecked(config.video.context->swFilter == 1);
|
||||
settings_videoFilter_scale2x->setChecked (config.video.context->swFilter == 2);
|
||||
settings_videoFilter_hq2x->setChecked (config.video.context->swFilter == 3);
|
||||
settings_videoFilter_ntsc->setChecked (config.video.context->swFilter == 4);
|
||||
|
||||
settings_muteAudio->setChecked(config.audio.mute);
|
||||
|
||||
settings_emulationSpeed_slowest->setChecked(config.system.speed == 0);
|
||||
settings_emulationSpeed_slow->setChecked (config.system.speed == 1);
|
||||
settings_emulationSpeed_normal->setChecked (config.system.speed == 2);
|
||||
settings_emulationSpeed_fast->setChecked (config.system.speed == 3);
|
||||
settings_emulationSpeed_fastest->setChecked(config.system.speed == 4);
|
||||
|
||||
settings_emulationSpeed_syncVideo->setChecked(config.video.synchronize);
|
||||
settings_emulationSpeed_syncAudio->setChecked(config.audio.synchronize);
|
||||
}
|
||||
|
||||
void MainWindow::loadCartridge() {
|
||||
string filename = utility.selectCartridge();
|
||||
if(filename.length() > 0) utility.loadCartridge(filename);
|
||||
}
|
||||
|
||||
void MainWindow::powerOn() { utility.modifySystemState(Utility::PowerOn); }
|
||||
void MainWindow::powerOff() { utility.modifySystemState(Utility::PowerOff); }
|
||||
void MainWindow::reset() { utility.modifySystemState(Utility::Reset); }
|
||||
|
||||
void MainWindow::setPort1None() { snes.config.controller_port1 = SNES::Input::DeviceNone; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort1Joypad() { snes.config.controller_port1 = SNES::Input::DeviceJoypad; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort1Multitap() { snes.config.controller_port1 = SNES::Input::DeviceMultitap; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort1Mouse() { snes.config.controller_port1 = SNES::Input::DeviceMouse; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2None() { snes.config.controller_port2 = SNES::Input::DeviceNone; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2Joypad() { snes.config.controller_port2 = SNES::Input::DeviceJoypad; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2Multitap() { snes.config.controller_port2 = SNES::Input::DeviceMultitap; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2Mouse() { snes.config.controller_port2 = SNES::Input::DeviceMouse; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2SuperScope() { snes.config.controller_port2 = SNES::Input::DeviceSuperScope; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2Justifier() { snes.config.controller_port2 = SNES::Input::DeviceJustifier; utility.updateControllers(); syncUi(); }
|
||||
void MainWindow::setPort2Justifiers() { snes.config.controller_port2 = SNES::Input::DeviceJustifiers; utility.updateControllers(); syncUi(); }
|
||||
|
||||
void MainWindow::quit() {
|
||||
application.terminate = true;
|
||||
}
|
||||
|
||||
void MainWindow::setVideoMode1x() { config.video.context->multiplier = 1; utility.resizeMainWindow(); syncUi(); }
|
||||
void MainWindow::setVideoMode2x() { config.video.context->multiplier = 2; utility.resizeMainWindow(); syncUi(); }
|
||||
void MainWindow::setVideoMode3x() { config.video.context->multiplier = 3; utility.resizeMainWindow(); syncUi(); }
|
||||
void MainWindow::setVideoMode4x() { config.video.context->multiplier = 4; utility.resizeMainWindow(); syncUi(); }
|
||||
void MainWindow::setVideoModeMax() { config.video.context->multiplier = 9; utility.resizeMainWindow(); syncUi(); }
|
||||
|
||||
void MainWindow::toggleAspectCorrection() {
|
||||
config.video.context->correctAspectRatio = settings_videoMode_correctAspectRatio->isChecked();
|
||||
utility.resizeMainWindow();
|
||||
}
|
||||
|
||||
void MainWindow::toggleFullscreen() { config.video.isFullscreen = settings_videoMode_fullscreen->isChecked(); utility.updateFullscreenState(); syncUi(); }
|
||||
|
||||
void MainWindow::setVideoNtsc() { config.video.context->region = 0; utility.updateVideoMode(); utility.resizeMainWindow(); syncUi(); }
|
||||
void MainWindow::setVideoPal() { config.video.context->region = 1; utility.updateVideoMode(); utility.resizeMainWindow(); syncUi(); }
|
||||
|
||||
void MainWindow::setPointFilter() { config.video.context->hwFilter = 0; utility.updateHardwareFilter(); syncUi(); }
|
||||
void MainWindow::setLinearFilter() { config.video.context->hwFilter = 1; utility.updateHardwareFilter(); syncUi(); }
|
||||
void MainWindow::setNoFilter() { config.video.context->swFilter = 0; utility.updateSoftwareFilter(); syncUi(); }
|
||||
void MainWindow::setScanlineFilter() { config.video.context->swFilter = 1; utility.updateSoftwareFilter(); syncUi(); }
|
||||
void MainWindow::setScale2xFilter() { config.video.context->swFilter = 2; utility.updateSoftwareFilter(); syncUi(); }
|
||||
void MainWindow::setHq2xFilter() { config.video.context->swFilter = 3; utility.updateSoftwareFilter(); syncUi(); }
|
||||
void MainWindow::setNtscFilter() { config.video.context->swFilter = 4; utility.updateSoftwareFilter(); syncUi(); }
|
||||
|
||||
void MainWindow::muteAudio() { config.audio.mute = settings_muteAudio->isChecked(); }
|
||||
|
||||
void MainWindow::setSpeedSlowest() { config.system.speed = 0; utility.updateEmulationSpeed(); syncUi(); }
|
||||
void MainWindow::setSpeedSlow() { config.system.speed = 1; utility.updateEmulationSpeed(); syncUi(); }
|
||||
void MainWindow::setSpeedNormal() { config.system.speed = 2; utility.updateEmulationSpeed(); syncUi(); }
|
||||
void MainWindow::setSpeedFast() { config.system.speed = 3; utility.updateEmulationSpeed(); syncUi(); }
|
||||
void MainWindow::setSpeedFastest() { config.system.speed = 4; utility.updateEmulationSpeed(); syncUi(); }
|
||||
|
||||
void MainWindow::syncVideo() { config.video.synchronize = settings_emulationSpeed_syncVideo->isChecked(); utility.updateAvSync(); }
|
||||
void MainWindow::syncAudio() { config.audio.synchronize = settings_emulationSpeed_syncAudio->isChecked(); utility.updateAvSync(); }
|
||||
|
||||
void MainWindow::showConfigWindow() {
|
||||
utility.showCentered(winSettings->window);
|
||||
}
|
||||
|
||||
void MainWindow::showDocumentation() {
|
||||
QFile file(":/documentation.html");
|
||||
if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
winHtmlViewer->show("Usage Documentation", file.readAll().constData());
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::showLicense() {
|
||||
QFile file(":/license.html");
|
||||
if(file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
winHtmlViewer->show("License Agreement", file.readAll().constData());
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
void MainWindow::showAbout() {
|
||||
utility.showCentered(winAbout->window);
|
||||
}
|
||||
|
||||
void MainWindow::Window::closeEvent(QCloseEvent*) {
|
||||
winMain->quit();
|
||||
}
|
||||
|
||||
//============
|
||||
//CanvasObject
|
||||
//============
|
||||
//implement drag-and-drop support:
|
||||
//drag cartridge image onto main window canvas area to load
|
||||
|
||||
void CanvasObject::dragEnterEvent(QDragEnterEvent *event) {
|
||||
if(event->mimeData()->hasUrls()) {
|
||||
//do not accept multiple files at once
|
||||
if(event->mimeData()->urls().count() == 1) event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasObject::dropEvent(QDropEvent *event) {
|
||||
if(event->mimeData()->hasUrls()) {
|
||||
QList<QUrl> list = event->mimeData()->urls();
|
||||
if(list.count() == 1) utility.loadCartridge(list.at(0).toLocalFile().toUtf8().constData());
|
||||
}
|
||||
}
|
||||
|
||||
//===========
|
||||
//CanvasWidget
|
||||
//============
|
||||
//custom video render and mouse capture functionality
|
||||
|
||||
QPaintEngine* CanvasWidget::paintEngine() const {
|
||||
if(cartridge.loaded()) return 0;
|
||||
return QWidget::paintEngine();
|
||||
}
|
||||
|
||||
void CanvasWidget::mouseReleaseEvent(QMouseEvent *event) {
|
||||
//acquire exclusive mode access to mouse when video output widget is clicked
|
||||
//(will only acquire if cart is loaded, and mouse / lightgun is in use.)
|
||||
utility.acquireMouse();
|
||||
}
|
||||
|
||||
void CanvasWidget::paintEvent(QPaintEvent *event) {
|
||||
event->ignore();
|
||||
}
|
||||
129
bsnes/ui_qt/base/main.hpp
Executable file
129
bsnes/ui_qt/base/main.hpp
Executable file
@@ -0,0 +1,129 @@
|
||||
class CanvasObject : public QWidget {
|
||||
public:
|
||||
void dragEnterEvent(QDragEnterEvent*);
|
||||
void dropEvent(QDropEvent*);
|
||||
};
|
||||
|
||||
class CanvasWidget : public CanvasObject {
|
||||
public:
|
||||
QPaintEngine* paintEngine() const;
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
void paintEvent(QPaintEvent*);
|
||||
};
|
||||
|
||||
class MainWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Window : public QMainWindow {
|
||||
void closeEvent(QCloseEvent*);
|
||||
} *window;
|
||||
QVBoxLayout *layout;
|
||||
QMenu *system;
|
||||
QAction *system_load;
|
||||
QMenu *system_power;
|
||||
QAction *system_power_on;
|
||||
QAction *system_power_off;
|
||||
QAction *system_reset;
|
||||
QMenu *system_port1;
|
||||
QAction *system_port1_none;
|
||||
QAction *system_port1_joypad;
|
||||
QAction *system_port1_multitap;
|
||||
QAction *system_port1_mouse;
|
||||
QMenu *system_port2;
|
||||
QAction *system_port2_none;
|
||||
QAction *system_port2_joypad;
|
||||
QAction *system_port2_multitap;
|
||||
QAction *system_port2_mouse;
|
||||
QAction *system_port2_superscope;
|
||||
QAction *system_port2_justifier;
|
||||
QAction *system_port2_justifiers;
|
||||
QAction *system_exit;
|
||||
QMenu *settings;
|
||||
QMenu *settings_videoMode;
|
||||
QAction *settings_videoMode_1x;
|
||||
QAction *settings_videoMode_2x;
|
||||
QAction *settings_videoMode_3x;
|
||||
QAction *settings_videoMode_4x;
|
||||
QAction *settings_videoMode_max;
|
||||
QAction *settings_videoMode_correctAspectRatio;
|
||||
QAction *settings_videoMode_fullscreen;
|
||||
QAction *settings_videoMode_ntsc;
|
||||
QAction *settings_videoMode_pal;
|
||||
QMenu *settings_videoFilter;
|
||||
QAction *settings_videoFilter_point;
|
||||
QAction *settings_videoFilter_linear;
|
||||
QAction *settings_videoFilter_none;
|
||||
QAction *settings_videoFilter_scanline;
|
||||
QAction *settings_videoFilter_scale2x;
|
||||
QAction *settings_videoFilter_hq2x;
|
||||
QAction *settings_videoFilter_ntsc;
|
||||
QAction *settings_muteAudio;
|
||||
QMenu *settings_emulationSpeed;
|
||||
QAction *settings_emulationSpeed_slowest;
|
||||
QAction *settings_emulationSpeed_slow;
|
||||
QAction *settings_emulationSpeed_normal;
|
||||
QAction *settings_emulationSpeed_fast;
|
||||
QAction *settings_emulationSpeed_fastest;
|
||||
QAction *settings_emulationSpeed_syncVideo;
|
||||
QAction *settings_emulationSpeed_syncAudio;
|
||||
QAction *settings_configuration;
|
||||
QMenu *help;
|
||||
QAction *help_documentation;
|
||||
QAction *help_license;
|
||||
QAction *help_about;
|
||||
//
|
||||
CanvasObject *canvasContainer;
|
||||
QVBoxLayout *canvasLayout;
|
||||
CanvasWidget *canvas;
|
||||
QLabel *systemState;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
|
||||
public slots:
|
||||
void loadCartridge();
|
||||
void powerOn();
|
||||
void powerOff();
|
||||
void reset();
|
||||
void setPort1None();
|
||||
void setPort1Joypad();
|
||||
void setPort1Multitap();
|
||||
void setPort1Mouse();
|
||||
void setPort2None();
|
||||
void setPort2Joypad();
|
||||
void setPort2Multitap();
|
||||
void setPort2Mouse();
|
||||
void setPort2SuperScope();
|
||||
void setPort2Justifier();
|
||||
void setPort2Justifiers();
|
||||
void quit();
|
||||
void setVideoMode1x();
|
||||
void setVideoMode2x();
|
||||
void setVideoMode3x();
|
||||
void setVideoMode4x();
|
||||
void setVideoModeMax();
|
||||
void toggleAspectCorrection();
|
||||
void toggleFullscreen();
|
||||
void setVideoNtsc();
|
||||
void setVideoPal();
|
||||
void setPointFilter();
|
||||
void setLinearFilter();
|
||||
void setNoFilter();
|
||||
void setScanlineFilter();
|
||||
void setScale2xFilter();
|
||||
void setHq2xFilter();
|
||||
void setNtscFilter();
|
||||
void muteAudio();
|
||||
void setSpeedSlowest();
|
||||
void setSpeedSlow();
|
||||
void setSpeedNormal();
|
||||
void setSpeedFast();
|
||||
void setSpeedFastest();
|
||||
void syncVideo();
|
||||
void syncAudio();
|
||||
void showConfigWindow();
|
||||
void showDocumentation();
|
||||
void showLicense();
|
||||
void showAbout();
|
||||
} *winMain;
|
||||
237
bsnes/ui_qt/config.cpp
Executable file
237
bsnes/ui_qt/config.cpp
Executable file
@@ -0,0 +1,237 @@
|
||||
class Configuration : public configuration {
|
||||
public:
|
||||
struct System {
|
||||
string video, audio, input;
|
||||
bool crashedOnLastRun;
|
||||
unsigned speed;
|
||||
} system;
|
||||
|
||||
struct Video {
|
||||
bool isFullscreen;
|
||||
bool synchronize;
|
||||
signed contrastAdjust, brightnessAdjust, gammaAdjust;
|
||||
bool enableGammaRamp;
|
||||
bool enableNtscMergeFields;
|
||||
double ntscAspectRatio, palAspectRatio;
|
||||
|
||||
struct Context {
|
||||
bool correctAspectRatio;
|
||||
unsigned multiplier, region;
|
||||
unsigned hwFilter, swFilter;
|
||||
} *context, windowed, fullscreen;
|
||||
} video;
|
||||
|
||||
struct Audio {
|
||||
bool synchronize;
|
||||
bool mute;
|
||||
unsigned volume, latency, outputFrequency, inputFrequency;
|
||||
} audio;
|
||||
|
||||
struct Input {
|
||||
enum policy_t { FocusPolicyPauseEmulation, FocusPolicyIgnoreInput, FocusPolicyAllowInput };
|
||||
unsigned focusPolicy;
|
||||
bool allowInvalidInput;
|
||||
|
||||
struct Joypad {
|
||||
string up, down, left, right, a, b, x, y, l, r, select, start;
|
||||
} joypad1, joypad2,
|
||||
multitap1a, multitap1b, multitap1c, multitap1d,
|
||||
multitap2a, multitap2b, multitap2c, multitap2d;
|
||||
|
||||
struct Mouse {
|
||||
string x, y, left, right;
|
||||
} mouse1, mouse2;
|
||||
|
||||
struct SuperScope {
|
||||
string x, y, trigger, turbo, cursor, pause;
|
||||
} superscope;
|
||||
|
||||
struct Justifier {
|
||||
string x, y, trigger, start;
|
||||
} justifier1, justifier2;
|
||||
|
||||
struct UiGeneral {
|
||||
string loadCartridge;
|
||||
string pauseEmulation;
|
||||
string resetSystem;
|
||||
string powerCycleSystem;
|
||||
string lowerSpeed;
|
||||
string raiseSpeed;
|
||||
string toggleCheatSystem;
|
||||
string toggleFullscreen;
|
||||
string toggleMenu;
|
||||
string toggleStatus;
|
||||
string exitEmulator;
|
||||
} uiGeneral;
|
||||
} input;
|
||||
|
||||
Configuration() {
|
||||
//========
|
||||
//external
|
||||
//========
|
||||
|
||||
attach(snes.config.controller_port1 = SNES::Input::DeviceJoypad, "snes.controllerPort1");
|
||||
attach(snes.config.controller_port2 = SNES::Input::DeviceJoypad, "snes.controllerPort2");
|
||||
attach(snes.config.expansion_port = SNES::ExpansionBSX, "snes.expansionPort");
|
||||
attach(snes.config.region = SNES::Autodetect, "snes.region");
|
||||
|
||||
attach(snes.config.file.autodetect_type = false, "file.autodetectType");
|
||||
attach(snes.config.file.bypass_patch_crc32 = false, "file.bypassPatchCrc32");
|
||||
|
||||
attach(snes.config.path.rom = "", "path.rom");
|
||||
attach(snes.config.path.save = "", "path.save");
|
||||
attach(snes.config.path.patch = "", "path.patch");
|
||||
attach(snes.config.path.cheat = "", "path.cheat");
|
||||
attach(snes.config.path.data = "", "path.data");
|
||||
attach(snes.config.path.bsx = "", "path.bsx");
|
||||
attach(snes.config.path.st = "", "path.st");
|
||||
|
||||
attach(snes.config.cpu.version = 2, "cpu.version", "Valid version(s) are: 1, 2");
|
||||
attach(snes.config.cpu.ntsc_clock_rate = 21477272, "cpu.ntscClockRate");
|
||||
attach(snes.config.cpu.pal_clock_rate = 21281370, "cpu.palClockRate");
|
||||
attach(snes.config.cpu.alu_mul_delay = 2, "cpu.aluMulDelay");
|
||||
attach(snes.config.cpu.alu_div_delay = 2, "cpu.aluDivDelay");
|
||||
attach(snes.config.cpu.wram_init_value = 0x55, "cpu.wramInitValue");
|
||||
|
||||
attach(snes.config.smp.ntsc_clock_rate = 32041 * 768, "smp.ntscClockRate");
|
||||
attach(snes.config.smp.pal_clock_rate = 32041 * 768, "smp.palClockRate");
|
||||
|
||||
attach(snes.config.ppu1.version = 1, "ppu1.version", "Valid version(s) are: 1");
|
||||
attach(snes.config.ppu2.version = 3, "ppu2.version", "Valid version(s) are: 1, 2, 3");
|
||||
|
||||
//========
|
||||
//internal
|
||||
//========
|
||||
|
||||
attach(system.video = "", "system.video");
|
||||
attach(system.audio = "", "system.audio");
|
||||
attach(system.input = "", "system.input");
|
||||
attach(system.crashedOnLastRun = false, "system.crashedOnLastRun");
|
||||
attach(system.speed = 2, "system.speed");
|
||||
|
||||
video.context = &video.windowed;
|
||||
attach(video.isFullscreen = false, "video.isFullscreen");
|
||||
attach(video.synchronize = false, "video.synchronize");
|
||||
|
||||
attach(video.contrastAdjust = 0, "video.contrastAdjust");
|
||||
attach(video.brightnessAdjust = 0, "video.brightnessAdjust");
|
||||
attach(video.gammaAdjust = 0, "video.gammaAdjust");
|
||||
|
||||
attach(video.enableGammaRamp = true, "video.enableGammaRamp");
|
||||
attach(video.enableNtscMergeFields = false, "video.enableNtscMergeFields");
|
||||
|
||||
attach(video.ntscAspectRatio = 54.0 / 47.0, "video.ntscAspectRatio", "NTSC aspect ratio (x / y)");
|
||||
attach(video.palAspectRatio = 32.0 / 23.0, "video.palAspectRatio", "PAL aspect ratio (x / y)");
|
||||
|
||||
attach(video.windowed.correctAspectRatio = true, "video.windowed.correctAspectRatio");
|
||||
attach(video.windowed.multiplier = 2, "video.windowed.multiplier");
|
||||
attach(video.windowed.region = 0, "video.windowed.region");
|
||||
|
||||
attach(video.windowed.hwFilter = 1, "video.windowed.hwFilter");
|
||||
attach(video.windowed.swFilter = 0, "video.windowed.swFilter");
|
||||
|
||||
attach(video.fullscreen.correctAspectRatio = true, "video.fullscreen.correctAspectRatio");
|
||||
attach(video.fullscreen.multiplier = 9, "video.fullscreen.multiplier");
|
||||
attach(video.fullscreen.region = 0, "video.fullscreen.region");
|
||||
|
||||
attach(video.fullscreen.hwFilter = 1, "video.fullscreen.hwFilter");
|
||||
attach(video.fullscreen.swFilter = 0, "video.fullscreen.swFilter");
|
||||
|
||||
attach(audio.synchronize = true, "audio.synchronize");
|
||||
attach(audio.mute = false, "audio.mute");
|
||||
|
||||
attach(audio.volume = 100, "audio.volume");
|
||||
attach(audio.latency = 80, "audio.latency");
|
||||
attach(audio.outputFrequency = 48000, "audio.outputFrequency");
|
||||
attach(audio.inputFrequency = 32000, "audio.inputFrequency");
|
||||
|
||||
attach(input.focusPolicy = Input::FocusPolicyPauseEmulation, "input.focusPolicy");
|
||||
attach(input.allowInvalidInput = false, "input.allowInvalidInput", "Allow up+down / left+right combinations; may trigger bugs in some games");
|
||||
|
||||
attach(input.joypad1.up = "keyboard00.up", "input.joypad1.up");
|
||||
attach(input.joypad1.down = "keyboard00.down", "input.joypad1.down");
|
||||
attach(input.joypad1.left = "keyboard00.left", "input.joypad1.left");
|
||||
attach(input.joypad1.right = "keyboard00.right", "input.joypad1.right");
|
||||
attach(input.joypad1.a = "keyboard00.x", "input.joypad1.a");
|
||||
attach(input.joypad1.b = "keyboard00.z", "input.joypad1.b");
|
||||
attach(input.joypad1.x = "keyboard00.s", "input.joypad1.x");
|
||||
attach(input.joypad1.y = "keyboard00.a", "input.joypad1.y");
|
||||
attach(input.joypad1.l = "keyboard00.d", "input.joypad1.l");
|
||||
attach(input.joypad1.r = "keyboard00.c", "input.joypad1.r");
|
||||
attach(input.joypad1.select = "keyboard00.rshift", "input.joypad1.select");
|
||||
attach(input.joypad1.start = "keyboard00.return", "input.joypad1.start");
|
||||
|
||||
//these are all mapped to "none" by default
|
||||
attachJoypad(input.joypad2, "input.joypad2");
|
||||
attachJoypad(input.multitap1a, "input.multitap1a");
|
||||
attachJoypad(input.multitap1b, "input.multitap1b");
|
||||
attachJoypad(input.multitap1c, "input.multitap1c");
|
||||
attachJoypad(input.multitap1d, "input.multitap1d");
|
||||
attachJoypad(input.multitap2a, "input.multitap2a");
|
||||
attachJoypad(input.multitap2b, "input.multitap2b");
|
||||
attachJoypad(input.multitap2c, "input.multitap2c");
|
||||
attachJoypad(input.multitap2d, "input.multitap2d");
|
||||
|
||||
attach(input.mouse1.x = "mouse00.x", "input.mouse1.x");
|
||||
attach(input.mouse1.y = "mouse00.y", "input.mouse1.y");
|
||||
attach(input.mouse1.left = "mouse00.button00", "input.mouse1.left");
|
||||
attach(input.mouse1.right = "mouse00.button02", "input.mouse1.right");
|
||||
|
||||
//more likely a user will only use one mouse at a time, than for a system to have two mice
|
||||
attach(input.mouse2.x = "mouse00.x", "input.mouse2.x");
|
||||
attach(input.mouse2.y = "mouse00.y", "input.mouse2.y");
|
||||
attach(input.mouse2.left = "mouse00.button00", "input.mouse2.left");
|
||||
attach(input.mouse2.right = "mouse00.button02", "input.mouse2.right");
|
||||
|
||||
//unlikely a user will have a five-button mouse for turbo / pause mapping
|
||||
attach(input.superscope.x = "mouse00.x", "input.superscope.x");
|
||||
attach(input.superscope.y = "mouse00.y", "input.superscope.y");
|
||||
attach(input.superscope.trigger = "mouse00.button00", "input.superscope.trigger");
|
||||
attach(input.superscope.cursor = "mouse00.button02", "input.superscope.cursor");
|
||||
attach(input.superscope.turbo = "keyboard00.t", "input.superscope.turbo");
|
||||
attach(input.superscope.pause = "keyboard00.p", "input.superscope.pause");
|
||||
|
||||
attach(input.justifier1.x = "mouse00.x", "input.justifier1.x");
|
||||
attach(input.justifier1.y = "mouse00.y", "input.justifier1.y");
|
||||
attach(input.justifier1.trigger = "mouse00.button00", "input.justifier1.trigger");
|
||||
attach(input.justifier1.start = "mouse00.button02", "input.jusitifer1.start");
|
||||
|
||||
attach(input.justifier2.x = "mouse01.x", "input.justifier2.x");
|
||||
attach(input.justifier2.y = "mouse01.y", "input.justifier2.y");
|
||||
attach(input.justifier2.trigger = "mouse01.button00", "input.justifier2.trigger");
|
||||
attach(input.justifier2.start = "mouse01.button02", "input.justifier2.start");
|
||||
|
||||
attach(input.uiGeneral.loadCartridge = "none", "input.uiGeneral.loadCartridge");
|
||||
attach(input.uiGeneral.pauseEmulation = "keyboard00.pause", "input.uiGeneral.pauseEmulation");
|
||||
attach(input.uiGeneral.resetSystem = "none", "input.uiGeneral.resetSystem");
|
||||
attach(input.uiGeneral.powerCycleSystem = "none", "input.uiGeneral.powerCycleSystem");
|
||||
attach(input.uiGeneral.lowerSpeed = "keyboard00.divide", "input.uiGeneral.lowerSpeed");
|
||||
attach(input.uiGeneral.raiseSpeed = "keyboard00.multiply", "input.uiGeneral.raiseSpeed");
|
||||
attach(input.uiGeneral.toggleCheatSystem = "none", "input.uiGeneral.toggleCheatSystem");
|
||||
attach(input.uiGeneral.toggleFullscreen = "keyboard00.f11", "input.uiGeneral.toggleFullscreen");
|
||||
attach(input.uiGeneral.toggleMenu = "keyboard00.escape", "input.uiGeneral.toggleMenu");
|
||||
attach(input.uiGeneral.toggleStatus = "keyboard00.escape", "input.uiGeneral.toggleStatus");
|
||||
attach(input.uiGeneral.exitEmulator = "none", "input.uiGeneral.exitEmulator");
|
||||
}
|
||||
|
||||
void attachJoypad(Input::Joypad &joypad, const char *name) {
|
||||
attach(joypad.up = "none", string() << name << ".up");
|
||||
attach(joypad.down = "none", string() << name << ".down");
|
||||
attach(joypad.left = "none", string() << name << ".left");
|
||||
attach(joypad.right = "none", string() << name << ".right");
|
||||
attach(joypad.a = "none", string() << name << ".a");
|
||||
attach(joypad.b = "none", string() << name << ".b");
|
||||
attach(joypad.x = "none", string() << name << ".x");
|
||||
attach(joypad.y = "none", string() << name << ".y");
|
||||
attach(joypad.l = "none", string() << name << ".l");
|
||||
attach(joypad.r = "none", string() << name << ".r");
|
||||
attach(joypad.select = "none", string() << name << ".select");
|
||||
attach(joypad.start = "none", string() << name << ".start");
|
||||
}
|
||||
|
||||
bool load(const char *filename) {
|
||||
if(configuration::load(filename) == false) return false;
|
||||
video.context = (video.isFullscreen == false) ? &video.windowed : &video.fullscreen;
|
||||
return true;
|
||||
}
|
||||
} config;
|
||||
261
bsnes/ui_qt/input/device.cpp
Executable file
261
bsnes/ui_qt/input/device.cpp
Executable file
@@ -0,0 +1,261 @@
|
||||
//===========
|
||||
//InputDevice
|
||||
//===========
|
||||
|
||||
InputDevice::InputDevice(SNES::Input::DeviceID i, bool p, const char *n) : InputGroup(n), id(i), port(p) {
|
||||
}
|
||||
|
||||
//======
|
||||
//Joypad
|
||||
//======
|
||||
|
||||
int16_t Joypad::state(unsigned index) const {
|
||||
if(config.input.allowInvalidInput == false) {
|
||||
//SNES D-pads have central pivot point, making up+down or left+right combinations impossible.
|
||||
//some software programs rely on this, and will crash if these combinations are allowed.
|
||||
if(index == SNES::Input::JoypadDown && up.state ) return 0;
|
||||
if(index == SNES::Input::JoypadRight && left.state) return 0;
|
||||
}
|
||||
|
||||
switch(index) {
|
||||
case SNES::Input::JoypadUp: return up.state;
|
||||
case SNES::Input::JoypadDown: return down.state;
|
||||
case SNES::Input::JoypadLeft: return left.state;
|
||||
case SNES::Input::JoypadRight: return right.state;
|
||||
case SNES::Input::JoypadA: return a.state;
|
||||
case SNES::Input::JoypadB: return b.state;
|
||||
case SNES::Input::JoypadX: return x.state;
|
||||
case SNES::Input::JoypadY: return y.state;
|
||||
case SNES::Input::JoypadL: return l.state;
|
||||
case SNES::Input::JoypadR: return r.state;
|
||||
case SNES::Input::JoypadSelect: return select.state;
|
||||
case SNES::Input::JoypadStart: return start.state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Joypad::Joypad(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string &up_t, string &down_t, string &left_t, string &right_t, string &a_t, string &b_t,
|
||||
string &x_t, string &y_t, string &l_t, string &r_t, string &select_t, string &start_t
|
||||
) :
|
||||
InputDevice(id, port, name),
|
||||
up (InputObject::Button, "Up", up_t),
|
||||
down (InputObject::Button, "Down", down_t),
|
||||
left (InputObject::Button, "Left", left_t),
|
||||
right (InputObject::Button, "Right", right_t),
|
||||
a (InputObject::Button, "A", a_t),
|
||||
b (InputObject::Button, "B", b_t),
|
||||
x (InputObject::Button, "X", x_t),
|
||||
y (InputObject::Button, "Y", y_t),
|
||||
l (InputObject::Button, "L", l_t),
|
||||
r (InputObject::Button, "R", r_t),
|
||||
select(InputObject::Button, "Select", select_t),
|
||||
start (InputObject::Button, "Start", start_t) {
|
||||
attach(up); attach(down); attach(left); attach(right); attach(a); attach(b);
|
||||
attach(x); attach(y); attach(l); attach(r); attach(select); attach(start);
|
||||
}
|
||||
|
||||
//=====
|
||||
//Mouse
|
||||
//=====
|
||||
|
||||
int16_t Mouse::state(unsigned index) const {
|
||||
switch(index) {
|
||||
case SNES::Input::MouseX: return x.state;
|
||||
case SNES::Input::MouseY: return y.state;
|
||||
case SNES::Input::MouseLeft: return left.state;
|
||||
case SNES::Input::MouseRight: return right.state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Mouse::Mouse(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string &x_t, string &y_t, string &left_t, string &right_t
|
||||
) :
|
||||
InputDevice(id, port, name),
|
||||
x (InputObject::Axis, "X-axis", x_t),
|
||||
y (InputObject::Axis, "Y-axis", y_t),
|
||||
left (InputObject::Button, "Left button", left_t),
|
||||
right(InputObject::Button, "Right button", right_t) {
|
||||
attach(x); attach(y); attach(left); attach(right);
|
||||
}
|
||||
|
||||
//==========
|
||||
//SuperScope
|
||||
//==========
|
||||
|
||||
int16_t SuperScope::state(unsigned index) const {
|
||||
switch(index) {
|
||||
case SNES::Input::SuperScopeX: return x.state;
|
||||
case SNES::Input::SuperScopeY: return y.state;
|
||||
case SNES::Input::SuperScopeTrigger: return trigger.state;
|
||||
case SNES::Input::SuperScopeCursor: return cursor.state;
|
||||
case SNES::Input::SuperScopeTurbo: return turbo.state;
|
||||
case SNES::Input::SuperScopePause: return pause.state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SuperScope::SuperScope(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string &x_t, string &y_t, string &trigger_t, string &cursor_t, string &turbo_t, string &pause_t
|
||||
) :
|
||||
InputDevice(id, port, name),
|
||||
x (InputObject::Axis, "X-axis", x_t),
|
||||
y (InputObject::Axis, "Y-axis", y_t),
|
||||
trigger(InputObject::Button, "Trigger", trigger_t),
|
||||
cursor (InputObject::Button, "Cursor", cursor_t),
|
||||
turbo (InputObject::Button, "Turbo", turbo_t),
|
||||
pause (InputObject::Button, "Pause", pause_t) {
|
||||
attach(x); attach(y); attach(trigger); attach(cursor); attach(turbo); attach(pause);
|
||||
}
|
||||
|
||||
//=========
|
||||
//Justifier
|
||||
//=========
|
||||
|
||||
int16_t Justifier::state(unsigned index) const {
|
||||
switch(index) {
|
||||
case SNES::Input::JustifierX: return x.state;
|
||||
case SNES::Input::JustifierY: return y.state;
|
||||
case SNES::Input::JustifierTrigger: return trigger.state;
|
||||
case SNES::Input::JustifierStart: return start.state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Justifier::Justifier(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string &x_t, string &y_t, string &trigger_t, string &start_t
|
||||
) :
|
||||
InputDevice(id, port, name),
|
||||
x (InputObject::Axis, "X-axis", x_t),
|
||||
y (InputObject::Axis, "Y-axis", y_t),
|
||||
trigger(InputObject::Button, "Trigger", trigger_t),
|
||||
start (InputObject::Button, "Start", start_t) {
|
||||
attach(x); attach(y); attach(trigger); attach(start);
|
||||
}
|
||||
|
||||
//===============
|
||||
//InputDevicePool
|
||||
//===============
|
||||
|
||||
void InputDevicePool::attach(InputDevice &device) {
|
||||
list.add(&device);
|
||||
}
|
||||
|
||||
void InputDevicePool::bind() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->bind();
|
||||
}
|
||||
|
||||
void InputDevicePool::clear() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->clear();
|
||||
}
|
||||
|
||||
void InputDevicePool::poll(const int16_t *table) {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table);
|
||||
}
|
||||
|
||||
InputDevice* InputDevicePool::find(SNES::Input::DeviceID id) {
|
||||
for(unsigned i = 0; i < list.size(); i++) {
|
||||
if(list[i]->id == id) return list[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
InputDevicePool::InputDevicePool() : list(*this) {
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
Joypad joypad1(SNES::Input::DeviceIDJoypad1, InputDevice::Port1, "Joypad",
|
||||
config.input.joypad1.up, config.input.joypad1.down, config.input.joypad1.left, config.input.joypad1.right,
|
||||
config.input.joypad1.a, config.input.joypad1.b, config.input.joypad1.x, config.input.joypad1.y,
|
||||
config.input.joypad1.l, config.input.joypad1.r, config.input.joypad1.select, config.input.joypad1.start);
|
||||
|
||||
Joypad joypad2(SNES::Input::DeviceIDJoypad2, InputDevice::Port2, "Joypad",
|
||||
config.input.joypad2.up, config.input.joypad2.down, config.input.joypad2.left, config.input.joypad2.right,
|
||||
config.input.joypad2.a, config.input.joypad2.b, config.input.joypad2.x, config.input.joypad2.y,
|
||||
config.input.joypad2.l, config.input.joypad2.r, config.input.joypad2.select, config.input.joypad2.start);
|
||||
|
||||
Joypad multitap1a(SNES::Input::DeviceIDMultitap1A, InputDevice::Port1, "Multitap - Port 1",
|
||||
config.input.multitap1a.up, config.input.multitap1a.down, config.input.multitap1a.left, config.input.multitap1a.right,
|
||||
config.input.multitap1a.a, config.input.multitap1a.b, config.input.multitap1a.x, config.input.multitap1a.y,
|
||||
config.input.multitap1a.l, config.input.multitap1a.r, config.input.multitap1a.select, config.input.multitap1a.start);
|
||||
|
||||
Joypad multitap1b(SNES::Input::DeviceIDMultitap1B, InputDevice::Port1, "Multitap - Port 2",
|
||||
config.input.multitap1b.up, config.input.multitap1b.down, config.input.multitap1b.left, config.input.multitap1b.right,
|
||||
config.input.multitap1b.a, config.input.multitap1b.b, config.input.multitap1b.x, config.input.multitap1b.y,
|
||||
config.input.multitap1b.l, config.input.multitap1b.r, config.input.multitap1b.select, config.input.multitap1b.start);
|
||||
|
||||
Joypad multitap1c(SNES::Input::DeviceIDMultitap1C, InputDevice::Port1, "Multitap - Port 3",
|
||||
config.input.multitap1c.up, config.input.multitap1c.down, config.input.multitap1c.left, config.input.multitap1c.right,
|
||||
config.input.multitap1c.a, config.input.multitap1c.b, config.input.multitap1c.x, config.input.multitap1c.y,
|
||||
config.input.multitap1c.l, config.input.multitap1c.r, config.input.multitap1c.select, config.input.multitap1c.start);
|
||||
|
||||
Joypad multitap1d(SNES::Input::DeviceIDMultitap1D, InputDevice::Port1, "Multitap - Port 4",
|
||||
config.input.multitap1d.up, config.input.multitap1d.down, config.input.multitap1d.left, config.input.multitap1d.right,
|
||||
config.input.multitap1d.a, config.input.multitap1d.b, config.input.multitap1d.x, config.input.multitap1d.y,
|
||||
config.input.multitap1d.l, config.input.multitap1d.r, config.input.multitap1d.select, config.input.multitap1d.start);
|
||||
|
||||
Joypad multitap2a(SNES::Input::DeviceIDMultitap2A, InputDevice::Port2, "Multitap - Port 1",
|
||||
config.input.multitap2a.up, config.input.multitap2a.down, config.input.multitap2a.left, config.input.multitap2a.right,
|
||||
config.input.multitap2a.a, config.input.multitap2a.b, config.input.multitap2a.x, config.input.multitap2a.y,
|
||||
config.input.multitap2a.l, config.input.multitap2a.r, config.input.multitap2a.select, config.input.multitap2a.start);
|
||||
|
||||
Joypad multitap2b(SNES::Input::DeviceIDMultitap2B, InputDevice::Port2, "Multitap - Port 2",
|
||||
config.input.multitap2b.up, config.input.multitap2b.down, config.input.multitap2b.left, config.input.multitap2b.right,
|
||||
config.input.multitap2b.a, config.input.multitap2b.b, config.input.multitap2b.x, config.input.multitap2b.y,
|
||||
config.input.multitap2b.l, config.input.multitap2b.r, config.input.multitap2b.select, config.input.multitap2b.start);
|
||||
|
||||
Joypad multitap2c(SNES::Input::DeviceIDMultitap2C, InputDevice::Port2, "Multitap - Port 3",
|
||||
config.input.multitap2c.up, config.input.multitap2c.down, config.input.multitap2c.left, config.input.multitap2c.right,
|
||||
config.input.multitap2c.a, config.input.multitap2c.b, config.input.multitap2c.x, config.input.multitap2c.y,
|
||||
config.input.multitap2c.l, config.input.multitap2c.r, config.input.multitap2c.select, config.input.multitap2c.start);
|
||||
|
||||
Joypad multitap2d(SNES::Input::DeviceIDMultitap2D, InputDevice::Port2, "Multitap - Port 4",
|
||||
config.input.multitap2d.up, config.input.multitap2d.down, config.input.multitap2d.left, config.input.multitap2d.right,
|
||||
config.input.multitap2d.a, config.input.multitap2d.b, config.input.multitap2d.x, config.input.multitap2d.y,
|
||||
config.input.multitap2d.l, config.input.multitap2d.r, config.input.multitap2d.select, config.input.multitap2d.start);
|
||||
|
||||
Mouse mouse1(SNES::Input::DeviceIDMouse1, InputDevice::Port1, "Mouse",
|
||||
config.input.mouse1.x, config.input.mouse1.y, config.input.mouse1.left, config.input.mouse1.right);
|
||||
|
||||
Mouse mouse2(SNES::Input::DeviceIDMouse2, InputDevice::Port2, "Mouse",
|
||||
config.input.mouse2.x, config.input.mouse2.y, config.input.mouse2.left, config.input.mouse2.right);
|
||||
|
||||
SuperScope superscope(SNES::Input::DeviceIDSuperScope, InputDevice::Port2, "Super Scope",
|
||||
config.input.superscope.x, config.input.superscope.y,
|
||||
config.input.superscope.trigger, config.input.superscope.cursor,
|
||||
config.input.superscope.turbo, config.input.superscope.pause);
|
||||
|
||||
Justifier justifier1(SNES::Input::DeviceIDJustifier1, InputDevice::Port2, "Justifier 1",
|
||||
config.input.justifier1.x, config.input.justifier1.y,
|
||||
config.input.justifier1.trigger, config.input.justifier1.start);
|
||||
|
||||
Justifier justifier2(SNES::Input::DeviceIDJustifier2, InputDevice::Port2, "Justifier 2",
|
||||
config.input.justifier2.x, config.input.justifier2.y,
|
||||
config.input.justifier2.trigger, config.input.justifier2.start);
|
||||
|
||||
InputSnesDevicePool inputPool;
|
||||
|
||||
InputSnesDevicePool::InputSnesDevicePool() {
|
||||
attach(joypad1);
|
||||
attach(joypad2);
|
||||
attach(multitap1a);
|
||||
attach(multitap1b);
|
||||
attach(multitap1c);
|
||||
attach(multitap1d);
|
||||
attach(multitap2a);
|
||||
attach(multitap2b);
|
||||
attach(multitap2c);
|
||||
attach(multitap2d);
|
||||
attach(mouse1);
|
||||
attach(mouse2);
|
||||
attach(superscope);
|
||||
attach(justifier1);
|
||||
attach(justifier2);
|
||||
}
|
||||
73
bsnes/ui_qt/input/device.hpp
Executable file
73
bsnes/ui_qt/input/device.hpp
Executable file
@@ -0,0 +1,73 @@
|
||||
struct InputDevice : InputGroup {
|
||||
SNES::Input::DeviceID id;
|
||||
enum Port { Port1, Port2 };
|
||||
const bool port;
|
||||
|
||||
InputDevice(SNES::Input::DeviceID i, bool p, const char *n);
|
||||
};
|
||||
|
||||
struct Joypad : InputDevice {
|
||||
InputObject up, down, left, right, a, b, x, y, l, r, select, start;
|
||||
|
||||
int16_t state(unsigned index) const;
|
||||
Joypad(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string&, string&, string&, string&, string&, string&,
|
||||
string&, string&, string&, string&, string&, string&);
|
||||
};
|
||||
|
||||
struct Mouse : InputDevice {
|
||||
InputObject x, y, left, right;
|
||||
|
||||
int16_t state(unsigned index) const;
|
||||
Mouse(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string&, string&, string&, string&);
|
||||
};
|
||||
|
||||
struct SuperScope : InputDevice {
|
||||
InputObject x, y, trigger, cursor, turbo, pause;
|
||||
|
||||
int16_t state(unsigned index) const;
|
||||
SuperScope(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string&, string&, string&, string&, string&, string&);
|
||||
};
|
||||
|
||||
struct Justifier : InputDevice {
|
||||
InputObject x, y, trigger, start;
|
||||
|
||||
int16_t state(unsigned index) const;
|
||||
Justifier(SNES::Input::DeviceID id, bool port, const char *name,
|
||||
string&, string&, string&, string&);
|
||||
};
|
||||
|
||||
struct InputDevicePool : public array<InputDevice*> {
|
||||
void attach(InputDevice &device);
|
||||
void bind();
|
||||
void clear();
|
||||
void poll(const int16_t *table);
|
||||
InputDevice* find(SNES::Input::DeviceID id);
|
||||
InputDevicePool();
|
||||
|
||||
private:
|
||||
array<InputDevice*> &list;
|
||||
};
|
||||
|
||||
struct InputSnesDevicePool : public InputDevicePool {
|
||||
InputSnesDevicePool();
|
||||
};
|
||||
|
||||
extern Joypad joypad1;
|
||||
extern Joypad joypad2;
|
||||
extern Joypad multitap1a;
|
||||
extern Joypad multitap1b;
|
||||
extern Joypad multitap1c;
|
||||
extern Joypad multitap1d;
|
||||
extern Joypad multitap2a;
|
||||
extern Joypad multitap2b;
|
||||
extern Joypad multitap2c;
|
||||
extern Joypad multitap2d;
|
||||
extern Mouse mouse1;
|
||||
extern Mouse mouse2;
|
||||
extern SuperScope superscope;
|
||||
extern Justifier justifier1;
|
||||
extern Justifier justifier2;
|
||||
extern InputSnesDevicePool inputPool;
|
||||
269
bsnes/ui_qt/input/input.cpp
Executable file
269
bsnes/ui_qt/input/input.cpp
Executable file
@@ -0,0 +1,269 @@
|
||||
#include "device.cpp"
|
||||
#include "userinterface.cpp"
|
||||
|
||||
//=========
|
||||
//InputCode
|
||||
//=========
|
||||
|
||||
InputCode::type_t InputCode::type(uint16_t code) {
|
||||
for(unsigned i = 0; i < keyboard<>::count; i++) {
|
||||
unsigned index = keyboard<>::index(i, keyboard<>::none);
|
||||
if(code >= index && code < index + keyboard<>::length) return KeyboardButton;
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < mouse<>::count; i++) {
|
||||
unsigned index = mouse<>::index(i, mouse<>::none);
|
||||
if(code == index + mouse<>::x) return MouseAxis;
|
||||
if(code == index + mouse<>::y) return MouseAxis;
|
||||
if(code == index + mouse<>::z) return MouseAxis;
|
||||
|
||||
index = mouse<>::index(i, mouse<>::button);
|
||||
if(code >= index && code < index + mouse<>::buttons) return MouseButton;
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < joypad<>::count; i++) {
|
||||
unsigned index;
|
||||
|
||||
index = joypad<>::index(i, joypad<>::hat);
|
||||
if(code >= index && code < index + joypad<>::hats) return JoypadHat;
|
||||
|
||||
index = joypad<>::index(i, joypad<>::axis);
|
||||
if(code >= index && code < index + joypad<>::axes) return JoypadAxis;
|
||||
|
||||
index = joypad<>::index(i, joypad<>::button);
|
||||
if(code >= index && code < index + joypad<>::buttons) return JoypadButton;
|
||||
}
|
||||
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
InputCode::axistype_t InputCode::axisType(uint16_t code) {
|
||||
for(unsigned i = 0; i < joypad<>::count; i++) {
|
||||
unsigned index = joypad<>::index(i, joypad<>::axis);
|
||||
if(code >= index && code < index + joypad<>::axes) {
|
||||
return (InputCode::axistype_t)inputManager.axisType(i, code - index);
|
||||
}
|
||||
}
|
||||
return InvalidAxis;
|
||||
}
|
||||
|
||||
//finds what mouse# is associated with code, returns -1 if not a mouse code
|
||||
int InputCode::mouseNumber(uint16_t code) {
|
||||
for(unsigned i = 0; i < mouse<>::count; i++) {
|
||||
unsigned index = mouse<>::index(i, mouse<>::none);
|
||||
if(code >= index && code < index + mouse<>::length) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//finds what joypad# is associated with code, returns -1 if not a joypad code
|
||||
int InputCode::joypadNumber(uint16_t code) {
|
||||
for(unsigned i = 0; i < joypad<>::count; i++) {
|
||||
unsigned index = joypad<>::index(i, joypad<>::none);
|
||||
if(code >= index && code < index + joypad<>::length) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
//============
|
||||
//InputManager
|
||||
//============
|
||||
|
||||
void InputManager::bind() {
|
||||
inputPool.bind();
|
||||
inputUiPool.bind();
|
||||
}
|
||||
|
||||
void InputManager::poll() {
|
||||
if(config.input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput
|
||||
&& winMain->window->isActiveWindow() == false) {
|
||||
inputPool.clear();
|
||||
} else {
|
||||
inputPool.poll(stateTable[activeState]);
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::clear() {
|
||||
inputPool.clear();
|
||||
}
|
||||
|
||||
void InputManager::flush() {
|
||||
for(unsigned i = 0; i < nall::input_limit; i++) {
|
||||
stateTable[0][i] = 0;
|
||||
stateTable[1][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t InputManager::state(uint16_t code) const {
|
||||
return stateTable[ activeState][code];
|
||||
}
|
||||
|
||||
int16_t InputManager::lastState(uint16_t code) const {
|
||||
return stateTable[!activeState][code];
|
||||
}
|
||||
|
||||
int16_t InputManager::getStatus(unsigned deviceid, unsigned id) const {
|
||||
InputDevice *device = inputPool.find((SNES::Input::DeviceID)deviceid);
|
||||
if(device) return device->state(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void InputManager::refresh() {
|
||||
bool last = activeState;
|
||||
activeState = !activeState;
|
||||
bool next = activeState;
|
||||
|
||||
input.poll(stateTable[next]);
|
||||
for(unsigned i = 0; i < nall::input_limit; i++) {
|
||||
//alert via callback whenever input state changes for any ID ...
|
||||
if(onInput && stateTable[last][i] != stateTable[next][i]) onInput(i);
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::calibrate(unsigned joy) {
|
||||
unsigned index = joypad<>::index(joy, joypad<>::none);
|
||||
|
||||
for(unsigned axis = 0; axis < joypad<>::axes; axis++) {
|
||||
int16_t value = state(index + joypad<>::axis + axis);
|
||||
pad[joy].axis[axis] = (value >= -16384 && value <= +16384) ? InputCode::Stick : InputCode::Trigger;
|
||||
}
|
||||
|
||||
pad[joy].calibrated = true;
|
||||
}
|
||||
|
||||
bool InputManager::calibrated(unsigned joy) const {
|
||||
return pad[joy].calibrated;
|
||||
}
|
||||
|
||||
InputCode::axistype_t InputManager::axisType(unsigned joy, unsigned axis) const {
|
||||
return pad[joy].axis[axis];
|
||||
}
|
||||
|
||||
InputManager::InputManager() {
|
||||
activeState = 0;
|
||||
flush();
|
||||
|
||||
for(unsigned i = 0; i < joypad<>::count; i++) {
|
||||
pad[i].calibrated = false;
|
||||
for(unsigned n = 0; n < joypad<>::axes; n++) pad[i].axis[n] = InputCode::InvalidAxis;
|
||||
}
|
||||
}
|
||||
|
||||
//===========
|
||||
//InputObject
|
||||
//===========
|
||||
|
||||
void InputObject::bind() {
|
||||
lstring part;
|
||||
part.split("::", id);
|
||||
|
||||
code = nall::input_find((const char*)part[0]);
|
||||
codetype = InputCode::type(code);
|
||||
|
||||
modifier = None;
|
||||
if(part.size() > 1) {
|
||||
if(part[1] == "up" ) modifier = Up;
|
||||
if(part[1] == "down" ) modifier = Down;
|
||||
if(part[1] == "left" ) modifier = Left;
|
||||
if(part[1] == "right" ) modifier = Right;
|
||||
if(part[1] == "lo" ) modifier = Lo;
|
||||
if(part[1] == "hi" ) modifier = Hi;
|
||||
if(part[1] == "trigger") modifier = Trigger;
|
||||
}
|
||||
|
||||
if(code == nall::input_none) id = "none";
|
||||
}
|
||||
|
||||
void InputObject::bind(uint16_t newCode) {
|
||||
code = newCode;
|
||||
codetype = InputCode::type(code);
|
||||
|
||||
id = nall::input_find(newCode);
|
||||
modifier = None;
|
||||
|
||||
if(type == Button && codetype == InputCode::JoypadHat) {
|
||||
switch(inputManager.state(code)) {
|
||||
case joypad<>::hat_up: id << "::up"; modifier = Up; break;
|
||||
case joypad<>::hat_down: id << "::down"; modifier = Down; break;
|
||||
case joypad<>::hat_left: id << "::left"; modifier = Left; break;
|
||||
case joypad<>::hat_right: id << "::right"; modifier = Right; break;
|
||||
}
|
||||
} else if(type == Button && codetype == InputCode::JoypadAxis) {
|
||||
InputCode::axistype_t type = InputCode::axisType(code);
|
||||
int16_t state = inputManager.state(code);
|
||||
|
||||
if(type == InputCode::Stick) {
|
||||
if(state < 0) {
|
||||
id << "::lo";
|
||||
modifier = Lo;
|
||||
} else {
|
||||
id << "::hi";
|
||||
modifier = Hi;
|
||||
}
|
||||
} else if(type == InputCode::Trigger) {
|
||||
id << "::trigger";
|
||||
modifier = Trigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputObject::poll(int16_t newState) {
|
||||
if(type == Button && codetype == InputCode::JoypadHat) {
|
||||
//map 4-way hat input to button state
|
||||
state = (modifier == Up && (newState & joypad<>::hat_up ))
|
||||
|| (modifier == Down && (newState & joypad<>::hat_down ))
|
||||
|| (modifier == Left && (newState & joypad<>::hat_left ))
|
||||
|| (modifier == Right && (newState & joypad<>::hat_right));
|
||||
} else if(type == Button && codetype == InputCode::JoypadAxis) {
|
||||
//convert analog input to button state
|
||||
state = (modifier == Lo && newState < -16384)
|
||||
|| (modifier == Hi && newState > +16384)
|
||||
|| (modifier == Trigger && newState < 0);
|
||||
} else if(codetype == InputCode::MouseAxis && !input.acquired()) {
|
||||
//mouse must be acquired (locked to window) to move axes
|
||||
state = 0;
|
||||
} else if(0 && codetype == InputCode::MouseButton && !input.acquired()) {
|
||||
//same for buttons
|
||||
//note: disabled for now ... requiring exclusive access makes it much more difficult
|
||||
//to utilize mouse buttons for eg SNES joypad input.
|
||||
state = 0;
|
||||
} else if(codetype == InputCode::JoypadAxis) {
|
||||
//joypad axis range = -32768 to +32767, scale to -8 to +7 to roughly match mouse delta
|
||||
//todo: scale mouse up instead of joypad down? (would require mouse DPI / resolution)
|
||||
state = newState / 4096;
|
||||
} else {
|
||||
state = newState;
|
||||
}
|
||||
}
|
||||
|
||||
InputObject::InputObject(InputObject::type_t t, const char *n, string &s) : parent(0), type(t), name(n), id(s) {
|
||||
}
|
||||
|
||||
//==========
|
||||
//InputGroup
|
||||
//==========
|
||||
|
||||
void InputGroup::attach(InputObject &object) {
|
||||
list.add(&object);
|
||||
object.parent = this;
|
||||
}
|
||||
|
||||
void InputGroup::bind() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->bind();
|
||||
}
|
||||
|
||||
void InputGroup::clear() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->state = 0;
|
||||
}
|
||||
|
||||
void InputGroup::poll(const int16_t *table) {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table[list[i]->code]);
|
||||
}
|
||||
|
||||
int16_t InputGroup::state(unsigned index) const {
|
||||
if(index < list.size()) return list[index]->state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
InputGroup::InputGroup(const char *n) : list(*this), name(n) {
|
||||
}
|
||||
88
bsnes/ui_qt/input/input.hpp
Executable file
88
bsnes/ui_qt/input/input.hpp
Executable file
@@ -0,0 +1,88 @@
|
||||
struct InputCode {
|
||||
enum type_t {
|
||||
KeyboardButton,
|
||||
MouseAxis,
|
||||
MouseButton,
|
||||
JoypadHat,
|
||||
JoypadAxis,
|
||||
JoypadButton,
|
||||
Unknown,
|
||||
};
|
||||
enum axistype_t {
|
||||
Stick, //bi-directional, centered analog stick (min = -32768, inactive = 0, max = +32767)
|
||||
Trigger, //uni-directional, pressure-sensitive analog trigger button (min = -32768, inactive = max = +32767)
|
||||
InvalidAxis, //not a joypad axis code, or said joypad is not calibrated
|
||||
};
|
||||
static type_t type(uint16_t code);
|
||||
static axistype_t axisType(uint16_t code);
|
||||
static int mouseNumber(uint16_t code);
|
||||
static int joypadNumber(uint16_t code);
|
||||
};
|
||||
|
||||
class InputManager {
|
||||
public:
|
||||
void bind();
|
||||
void poll();
|
||||
void clear();
|
||||
void flush();
|
||||
|
||||
int16_t state(uint16_t code) const;
|
||||
int16_t lastState(uint16_t code) const;
|
||||
int16_t getStatus(unsigned deviceid, unsigned id) const;
|
||||
|
||||
void refresh();
|
||||
void calibrate(unsigned joypad);
|
||||
bool calibrated(unsigned joypad) const;
|
||||
InputCode::axistype_t axisType(unsigned joypad, unsigned axis) const;
|
||||
function<void (uint16_t)> onInput;
|
||||
|
||||
InputManager();
|
||||
|
||||
private:
|
||||
bool activeState;
|
||||
int16_t stateTable[2][nall::input_limit];
|
||||
|
||||
//joypad axis calibration data
|
||||
struct Pad {
|
||||
bool calibrated;
|
||||
InputCode::axistype_t axis[joypad<>::axes];
|
||||
} pad[joypad<>::count];
|
||||
} inputManager;
|
||||
|
||||
struct InputGroup;
|
||||
|
||||
struct InputObject {
|
||||
InputGroup *parent;
|
||||
enum type_t { Button, Axis };
|
||||
enum modifier_t { None, Up, Down, Left, Right, Lo, Hi, Trigger };
|
||||
|
||||
type_t type; //type of input this object is mapped to
|
||||
const char *name; //plain-text name ("Up", "Down", ... "Start")
|
||||
string &id; //config-item reference ("joypad1.start") name ("joypad00.button00")
|
||||
uint16_t code; //nall::input code ID
|
||||
InputCode::type_t codetype; //hardware button / axis type
|
||||
modifier_t modifier; //hardware specialization (joypad-axis "::lo", "::hi", etc)
|
||||
int16_t state; //code state as of last inputManager.poll()
|
||||
|
||||
void bind();
|
||||
void bind(uint16_t code);
|
||||
void poll(int16_t state);
|
||||
InputObject(type_t t, const char *n, string &s);
|
||||
};
|
||||
|
||||
struct InputGroup : public array<InputObject*> {
|
||||
const char *name;
|
||||
|
||||
void attach(InputObject &object);
|
||||
void bind();
|
||||
void clear();
|
||||
void poll(const int16_t *table);
|
||||
virtual int16_t state(unsigned index) const;
|
||||
InputGroup(const char *n);
|
||||
|
||||
private:
|
||||
array<InputObject*> &list;
|
||||
};
|
||||
|
||||
#include "device.hpp"
|
||||
#include "userinterface.hpp"
|
||||
56
bsnes/ui_qt/input/userinterface.cpp
Executable file
56
bsnes/ui_qt/input/userinterface.cpp
Executable file
@@ -0,0 +1,56 @@
|
||||
//==============
|
||||
//InputGroupPool
|
||||
//==============
|
||||
|
||||
void InputGroupPool::attach(InputGroup &group) {
|
||||
list.add(&group);
|
||||
}
|
||||
|
||||
void InputGroupPool::bind() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->bind();
|
||||
}
|
||||
|
||||
void InputGroupPool::clear() {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->clear();
|
||||
}
|
||||
|
||||
void InputGroupPool::poll(const int16_t *table) {
|
||||
for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table);
|
||||
}
|
||||
|
||||
InputGroupPool::InputGroupPool() : list(*this) {
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
InputUiGeneral inputUiGeneral;
|
||||
InputUiPool inputUiPool;
|
||||
|
||||
InputUiGeneral::InputUiGeneral() : InputGroup("General"),
|
||||
loadCartridge(InputObject::Button, "Load cartridge", config.input.uiGeneral.loadCartridge),
|
||||
pauseEmulation(InputObject::Button, "Pause emulation", config.input.uiGeneral.pauseEmulation),
|
||||
resetSystem(InputObject::Button, "Reset system", config.input.uiGeneral.resetSystem),
|
||||
powerCycleSystem(InputObject::Button, "Power cycle system", config.input.uiGeneral.powerCycleSystem),
|
||||
lowerSpeed(InputObject::Button, "Decrease emulation speed", config.input.uiGeneral.lowerSpeed),
|
||||
raiseSpeed(InputObject::Button, "Increase emulation speed", config.input.uiGeneral.raiseSpeed),
|
||||
toggleCheatSystem(InputObject::Button, "Toggle cheat system on or off", config.input.uiGeneral.toggleCheatSystem),
|
||||
toggleFullscreen(InputObject::Button, "Toggle fullscreen mode", config.input.uiGeneral.toggleFullscreen),
|
||||
toggleMenu(InputObject::Button, "Toggle menubar", config.input.uiGeneral.toggleMenu),
|
||||
toggleStatus(InputObject::Button, "Toggle statusbar", config.input.uiGeneral.toggleStatus),
|
||||
exitEmulator(InputObject::Button, "Exit emulator", config.input.uiGeneral.exitEmulator) {
|
||||
attach(loadCartridge);
|
||||
attach(pauseEmulation);
|
||||
attach(resetSystem);
|
||||
attach(powerCycleSystem);
|
||||
attach(lowerSpeed);
|
||||
attach(raiseSpeed);
|
||||
attach(toggleCheatSystem);
|
||||
attach(toggleFullscreen);
|
||||
attach(toggleMenu);
|
||||
attach(toggleStatus);
|
||||
attach(exitEmulator);
|
||||
}
|
||||
|
||||
InputUiPool::InputUiPool() {
|
||||
attach(inputUiGeneral);
|
||||
}
|
||||
33
bsnes/ui_qt/input/userinterface.hpp
Executable file
33
bsnes/ui_qt/input/userinterface.hpp
Executable file
@@ -0,0 +1,33 @@
|
||||
struct InputUiGeneral : public InputGroup {
|
||||
InputObject loadCartridge;
|
||||
InputObject pauseEmulation;
|
||||
InputObject resetSystem;
|
||||
InputObject powerCycleSystem;
|
||||
InputObject lowerSpeed;
|
||||
InputObject raiseSpeed;
|
||||
InputObject toggleCheatSystem;
|
||||
InputObject toggleFullscreen;
|
||||
InputObject toggleMenu;
|
||||
InputObject toggleStatus;
|
||||
InputObject exitEmulator;
|
||||
|
||||
InputUiGeneral();
|
||||
};
|
||||
|
||||
struct InputGroupPool : public array<InputGroup*> {
|
||||
void attach(InputGroup &group);
|
||||
void bind();
|
||||
void clear();
|
||||
void poll(const int16_t *table);
|
||||
InputGroupPool();
|
||||
|
||||
private:
|
||||
array<InputGroup*> &list;
|
||||
};
|
||||
|
||||
struct InputUiPool : public InputGroupPool {
|
||||
InputUiPool();
|
||||
};
|
||||
|
||||
extern InputUiGeneral inputUiGeneral;
|
||||
extern InputUiPool inputUiPool;
|
||||
31
bsnes/ui_qt/interface.cpp
Executable file
31
bsnes/ui_qt/interface.cpp
Executable file
@@ -0,0 +1,31 @@
|
||||
SNESInterface snesinterface;
|
||||
|
||||
void SNESInterface::video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height) {
|
||||
uint32_t *output;
|
||||
unsigned outpitch;
|
||||
if(video.lock(output, outpitch) == true) {
|
||||
unsigned outwidth, outheight;
|
||||
libfilter::filter.render(output, outpitch, outwidth, outheight, data, pitch, line, width, height);
|
||||
video.unlock();
|
||||
video.refresh(outwidth, outheight);
|
||||
}
|
||||
}
|
||||
|
||||
void SNESInterface::audio_sample(uint16_t left, uint16_t right) {
|
||||
if(config.audio.mute) left = right = 0;
|
||||
audio.sample(left, right);
|
||||
}
|
||||
|
||||
void SNESInterface::input_poll() {
|
||||
inputManager.poll();
|
||||
}
|
||||
|
||||
int16_t SNESInterface::input_poll(unsigned deviceid, unsigned id) {
|
||||
return inputManager.getStatus(deviceid, id);
|
||||
}
|
||||
|
||||
void SNESInterface::init() {
|
||||
}
|
||||
|
||||
void SNESInterface::term() {
|
||||
}
|
||||
162
bsnes/ui_qt/main.cpp
Executable file
162
bsnes/ui_qt/main.cpp
Executable file
@@ -0,0 +1,162 @@
|
||||
#include "main.hpp"
|
||||
#include "resource/resource.rcc"
|
||||
|
||||
//nall::string <> QString interface: allows string streaming; automatically converts to UTF-16
|
||||
class utf8 : public nall::string {
|
||||
public:
|
||||
template<typename T> utf8& operator<<(T t) { string::operator<<(t); return *this; }
|
||||
operator const QString() const { return QString::fromUtf8(*this); }
|
||||
};
|
||||
|
||||
#include "platform.cpp"
|
||||
#include "config.cpp"
|
||||
#include "interface.cpp"
|
||||
#include "ui.cpp"
|
||||
|
||||
#include "input/input.cpp"
|
||||
#include "utility/utility.cpp"
|
||||
|
||||
const char defaultStylesheet[] =
|
||||
"QLabel.title {"
|
||||
" background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 rgba(255, 0, 0, 48), stop: 1 rgba(128, 0, 0, 96));"
|
||||
" font-weight: bold;"
|
||||
" margin-bottom: 5px;"
|
||||
" padding: 3px;"
|
||||
"}"
|
||||
"\n"
|
||||
"#backdrop {"
|
||||
" background: #000000;"
|
||||
"}"
|
||||
"\n"
|
||||
"#mouse-capture-box {"
|
||||
" background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, "
|
||||
" stop: 0.00 rgba(128, 0, 0, 96), stop: 0.25 rgba(255, 0, 0, 48), "
|
||||
" stop: 0.75 rgba(255, 0, 0, 48), stop: 1.00 rgba(128, 0, 0, 96));"
|
||||
" color: #000000;"
|
||||
" font-weight: bold;"
|
||||
"}"
|
||||
"\n";
|
||||
|
||||
void Application::initPaths(const char *basename) {
|
||||
char temp[PATH_MAX];
|
||||
|
||||
if(realpath(basename, temp)) {
|
||||
//remove program name
|
||||
strtr(temp, "\\", "/");
|
||||
for(signed i = strlen(temp) - 1; i >= 0; i--) {
|
||||
if(temp[i] == '/') {
|
||||
temp[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(strend(temp, "/") == false) strcat(temp, "/");
|
||||
snes.config.path.base = temp;
|
||||
} else {
|
||||
snes.config.path.base = "";
|
||||
}
|
||||
|
||||
if(userpath(temp)) {
|
||||
strtr(temp, "\\", "/");
|
||||
if(strend(temp, "/") == false) strcat(temp, "/");
|
||||
snes.config.path.user = temp;
|
||||
} else {
|
||||
snes.config.path.user = "";
|
||||
}
|
||||
|
||||
char cwd[PATH_MAX];
|
||||
snes.config.path.current = getcwd(cwd);
|
||||
}
|
||||
|
||||
void Application::locateFile(string &filename, bool createDataDirectory) {
|
||||
//first, check if file exists in executable directory (single-user mode)
|
||||
string temp = string() << snes.config.path.base << filename;
|
||||
|
||||
if(file::exists(temp) == false) {
|
||||
//if not, use user data path (multi-user mode)
|
||||
temp = snes.config.path.user;
|
||||
temp << ".bsnes";
|
||||
if(createDataDirectory) mkdir(temp); //ensure directory exists
|
||||
temp << "/" << filename;
|
||||
}
|
||||
|
||||
filename = temp;
|
||||
}
|
||||
|
||||
int Application::main(int argc, char **argv) {
|
||||
app = new App(argc, argv);
|
||||
#if !defined(_WIN32)
|
||||
//Windows port uses 256x256 icon from resource file
|
||||
app->setWindowIcon(QIcon(":/bsnes.png"));
|
||||
#endif
|
||||
|
||||
initargs(argc, argv); //ensure argv[]s are in UTF-8 format
|
||||
initPaths(argv[0]);
|
||||
locateFile(configFilename = "bsnes.cfg", true);
|
||||
locateFile(styleSheetFilename = "style.qss", false);
|
||||
|
||||
string customStylesheet;
|
||||
if(customStylesheet.readfile(styleSheetFilename) == true) {
|
||||
app->setStyleSheet((const char*)customStylesheet);
|
||||
} else {
|
||||
app->setStyleSheet(defaultStylesheet);
|
||||
}
|
||||
|
||||
config.load(configFilename);
|
||||
init();
|
||||
snes.init();
|
||||
|
||||
if(argc == 2) {
|
||||
//if valid file was specified on the command-line, attempt to load it now
|
||||
utility.loadCartridge(argv[1]);
|
||||
}
|
||||
|
||||
while(terminate == false) {
|
||||
processEvents();
|
||||
utility.updateSystemState();
|
||||
inputManager.refresh();
|
||||
|
||||
if(config.input.focusPolicy == Configuration::Input::FocusPolicyPauseEmulation) {
|
||||
bool inactive = (winMain->window->isActiveWindow() == false);
|
||||
if(!autopause && inactive) {
|
||||
autopause = true;
|
||||
audio.clear();
|
||||
} else if(autopause && !inactive) {
|
||||
autopause = false;
|
||||
}
|
||||
} else {
|
||||
autopause = false;
|
||||
}
|
||||
|
||||
if(cartridge.loaded() && !pause && !autopause) {
|
||||
snes.runtoframe();
|
||||
} else {
|
||||
usleep(20 * 1000);
|
||||
}
|
||||
|
||||
supressScreenSaver();
|
||||
}
|
||||
|
||||
config.save(configFilename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Application::processEvents() {
|
||||
app->processEvents();
|
||||
}
|
||||
|
||||
Application::Application() {
|
||||
terminate = false;
|
||||
power = false;
|
||||
pause = false;
|
||||
autopause = false;
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
//deleting (QApplication)app will segfault the application upon exit
|
||||
//delete app;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return application.main(argc, argv);
|
||||
}
|
||||
62
bsnes/ui_qt/main.hpp
Executable file
62
bsnes/ui_qt/main.hpp
Executable file
@@ -0,0 +1,62 @@
|
||||
#define UNICODE
|
||||
#define QT_NO_DEBUG
|
||||
#define QT_CORE_LIB
|
||||
#define QT_GUI_LIB
|
||||
#define QT_THREAD_SUPPORT
|
||||
|
||||
#include <QApplication>
|
||||
#include <QtGui>
|
||||
//Q_IMPORT_PLUGIN(QJpegPlugin)
|
||||
//Q_IMPORT_PLUGIN(QMngPlugin)
|
||||
|
||||
#include <../base.hpp>
|
||||
#include <../cart/cart.hpp>
|
||||
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/input.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace ruby;
|
||||
|
||||
#include <libfilter/libfilter.hpp>
|
||||
|
||||
#include "input/input.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
|
||||
class Application {
|
||||
public:
|
||||
class App : public QApplication {
|
||||
public:
|
||||
#ifdef _WIN32
|
||||
bool winEventFilter(MSG *msg, long *result);
|
||||
#endif
|
||||
App(int argc, char **argv) : QApplication(argc, argv) {}
|
||||
} *app;
|
||||
|
||||
bool terminate; //set to true to terminate main() loop and exit emulator
|
||||
bool power;
|
||||
bool pause;
|
||||
bool autopause;
|
||||
|
||||
string configFilename;
|
||||
string styleSheetFilename;
|
||||
|
||||
int main(int argc, char **argv);
|
||||
void processEvents();
|
||||
void locateFile(string &filename, bool createDataDirectory = false);
|
||||
void initPaths(const char *basename);
|
||||
void init();
|
||||
|
||||
Application();
|
||||
~Application();
|
||||
} application;
|
||||
|
||||
struct Style {
|
||||
enum {
|
||||
WindowMargin = 5,
|
||||
WidgetSpacing = 5,
|
||||
SeparatorSpacing = 5,
|
||||
};
|
||||
};
|
||||
99
bsnes/ui_qt/platform.cpp
Executable file
99
bsnes/ui_qt/platform.cpp
Executable file
@@ -0,0 +1,99 @@
|
||||
//platform-specific wrappers
|
||||
|
||||
#if defined(_WIN32)
|
||||
//Windows 32-bit and 64-bit
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <nall/utf8.hpp>
|
||||
|
||||
char* realpath(const char *filename, char *resolvedname) {
|
||||
wchar_t fn[_MAX_PATH] = L"";
|
||||
_wfullpath(fn, nall::utf16_t(filename), _MAX_PATH);
|
||||
strcpy(resolvedname, nall::utf8_t(fn));
|
||||
return resolvedname;
|
||||
}
|
||||
|
||||
char* userpath(char *path) {
|
||||
wchar_t fp[_MAX_PATH] = L"";
|
||||
SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp);
|
||||
strcpy(path, nall::utf8_t(fp));
|
||||
return path;
|
||||
}
|
||||
|
||||
char* getcwd(char *path) {
|
||||
wchar_t fp[_MAX_PATH] = L"";
|
||||
_wgetcwd(fp, _MAX_PATH);
|
||||
strcpy(path, nall::utf8_t(fp));
|
||||
return path;
|
||||
}
|
||||
|
||||
int mkdir(const char *path) {
|
||||
return _wmkdir(nall::utf16_t(path));
|
||||
}
|
||||
|
||||
void initargs(int &argc, char **&argv) {
|
||||
wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
argv = new char*[argc];
|
||||
for(unsigned i = 0; i < argc; i++) {
|
||||
argv[i] = new char[_MAX_PATH];
|
||||
strcpy(argv[i], nall::utf8_t(wargv[i]));
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::App::winEventFilter(MSG *msg, long *result) {
|
||||
if(msg->message == WM_SYSCOMMAND) {
|
||||
if(msg->wParam == SC_SCREENSAVE || msg->wParam == SC_MONITORPOWER) {
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void supressScreenSaver() {
|
||||
//handled by event filter above
|
||||
}
|
||||
#else
|
||||
#define None XNone
|
||||
#define Window XWindow
|
||||
#include <X11/extensions/XTest.h>
|
||||
#undef None
|
||||
#undef Window
|
||||
|
||||
//POSIX-compatible (Linux, BSD, etc.)
|
||||
char* userpath(char *path) {
|
||||
*path = 0;
|
||||
struct passwd *userinfo = getpwuid(getuid());
|
||||
if(userinfo) strcpy(path, userinfo->pw_dir);
|
||||
return path;
|
||||
}
|
||||
|
||||
char *getcwd(char *path) {
|
||||
return getcwd(path, PATH_MAX);
|
||||
}
|
||||
|
||||
#define mkdir(path) (mkdir)(path, 0755)
|
||||
|
||||
void initargs(int &argc, char **&argv) {
|
||||
}
|
||||
|
||||
void supressScreenSaver() {
|
||||
static clock_t delta_x = 0, delta_y = 0;
|
||||
|
||||
delta_y = clock();
|
||||
if(delta_y - delta_x < CLOCKS_PER_SEC * 20) return;
|
||||
delta_x = delta_y;
|
||||
|
||||
//XSetScreenSaver(timeout = 0) does not work
|
||||
//XResetScreenSaver() does not work
|
||||
//XScreenSaverSuspend() does not work
|
||||
//DPMSDisable() does not work
|
||||
//XSendEvent(KeyPressMask) does not work
|
||||
//use XTest extension to send fake keypress every ~20 seconds.
|
||||
//keycode of 255 does not map to any actual key,
|
||||
//but it will block screensaver and power management.
|
||||
Display *display = XOpenDisplay(0);
|
||||
XTestFakeKeyEvent(display, 255, True, 0);
|
||||
XTestFakeKeyEvent(display, 255, False, 0);
|
||||
}
|
||||
#endif
|
||||
10
bsnes/ui_qt/resource/resource.qrc
Executable file
10
bsnes/ui_qt/resource/resource.qrc
Executable file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="bsnes.png">../../data/bsnes.png</file>
|
||||
<file alias="logo.png">../../data/logo.png</file>
|
||||
<file alias="joypad.png">../../data/joypad.png</file>
|
||||
<file alias="documentation.html">../../data/documentation.html</file>
|
||||
<file alias="license.html">../../data/license.html</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
2
bsnes/ui_qt/resource/resource.rc
Executable file
2
bsnes/ui_qt/resource/resource.rc
Executable file
@@ -0,0 +1,2 @@
|
||||
1 24 "data/bsnes.Manifest"
|
||||
IDI_ICON1 ICON DISCARDABLE "data/bsnes.ico"
|
||||
183
bsnes/ui_qt/settings/advanced.cpp
Executable file
183
bsnes/ui_qt/settings/advanced.cpp
Executable file
@@ -0,0 +1,183 @@
|
||||
void AdvancedSettingsWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Advanced Configuration Settings");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
driverLayout = new QGridLayout;
|
||||
driverLayout->setVerticalSpacing(0);
|
||||
driverLayout->setHorizontalSpacing(Style::WidgetSpacing); {
|
||||
videoLabel = new QLabel("Video driver:");
|
||||
driverLayout->addWidget(videoLabel, 0, 0);
|
||||
|
||||
audioLabel = new QLabel("Audio driver:");
|
||||
driverLayout->addWidget(audioLabel, 0, 1);
|
||||
|
||||
inputLabel = new QLabel("Input driver:");
|
||||
driverLayout->addWidget(inputLabel, 0, 2);
|
||||
|
||||
videoDriver = new QComboBox;
|
||||
driverLayout->addWidget(videoDriver, 1, 0);
|
||||
|
||||
audioDriver = new QComboBox;
|
||||
driverLayout->addWidget(audioDriver, 1, 1);
|
||||
|
||||
inputDriver = new QComboBox;
|
||||
driverLayout->addWidget(inputDriver, 1, 2);
|
||||
|
||||
driverInfo = new QLabel("<i>Note: driver changes require restart to take effect.</i>");
|
||||
driverLayout->addWidget(driverInfo, 2, 0, 1, 3);
|
||||
}
|
||||
layout->addLayout(driverLayout);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
regionTitle = new QLabel("Hardware region:");
|
||||
layout->addWidget(regionTitle);
|
||||
|
||||
regionLayout = new QHBoxLayout;
|
||||
regionLayout->setSpacing(Style::WidgetSpacing); {
|
||||
regionGroup = new QButtonGroup(panel);
|
||||
|
||||
regionAuto = new QRadioButton("Auto-detect");
|
||||
regionAuto->setToolTip("Automatically select hardware region on cartridge load");
|
||||
regionGroup->addButton(regionAuto);
|
||||
regionLayout->addWidget(regionAuto);
|
||||
|
||||
regionNTSC = new QRadioButton("NTSC");
|
||||
regionNTSC->setToolTip("Force NTSC region (Japan, Korea, US)");
|
||||
regionGroup->addButton(regionNTSC);
|
||||
regionLayout->addWidget(regionNTSC);
|
||||
|
||||
regionPAL = new QRadioButton("PAL");
|
||||
regionPAL->setToolTip("Force PAL region (Europe, ...)");
|
||||
regionGroup->addButton(regionPAL);
|
||||
regionLayout->addWidget(regionPAL);
|
||||
}
|
||||
layout->addLayout(regionLayout);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
portTitle = new QLabel("Expansion port device:");
|
||||
layout->addWidget(portTitle);
|
||||
|
||||
portLayout = new QHBoxLayout;
|
||||
portLayout->setSpacing(Style::WidgetSpacing); {
|
||||
portGroup = new QButtonGroup(panel);
|
||||
|
||||
portSatellaview = new QRadioButton("Satellaview");
|
||||
portGroup->addButton(portSatellaview);
|
||||
portLayout->addWidget(portSatellaview);
|
||||
|
||||
portNone = new QRadioButton("None");
|
||||
portGroup->addButton(portNone);
|
||||
portLayout->addWidget(portNone);
|
||||
|
||||
portSpacer = new QWidget;
|
||||
portLayout->addWidget(portSpacer);
|
||||
}
|
||||
layout->addLayout(portLayout);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
focusTitle = new QLabel("When main window does not have focus:");
|
||||
layout->addWidget(focusTitle);
|
||||
|
||||
focusLayout = new QHBoxLayout;
|
||||
focusLayout->setSpacing(Style::WidgetSpacing); {
|
||||
focusButtonGroup = new QButtonGroup(panel);
|
||||
|
||||
focusPause = new QRadioButton("Pause emulation");
|
||||
focusPause->setToolTip("Ideal for prolonged multi-tasking");
|
||||
focusButtonGroup->addButton(focusPause);
|
||||
focusLayout->addWidget(focusPause);
|
||||
|
||||
focusIgnore = new QRadioButton("Ignore input");
|
||||
focusIgnore->setToolTip("Ideal for light multi-tasking when using keyboard");
|
||||
focusButtonGroup->addButton(focusIgnore);
|
||||
focusLayout->addWidget(focusIgnore);
|
||||
|
||||
focusAllow = new QRadioButton("Allow input");
|
||||
focusAllow->setToolTip("Ideal for light multi-tasking when using joypad(s)");
|
||||
focusButtonGroup->addButton(focusAllow);
|
||||
focusLayout->addWidget(focusAllow);
|
||||
}
|
||||
layout->addLayout(focusLayout);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
panel->setLayout(layout);
|
||||
initializeUi();
|
||||
|
||||
connect(videoDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(videoDriverChange(int)));
|
||||
connect(audioDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(audioDriverChange(int)));
|
||||
connect(inputDriver, SIGNAL(currentIndexChanged(int)), this, SLOT(inputDriverChange(int)));
|
||||
connect(regionAuto, SIGNAL(pressed()), this, SLOT(setRegionAuto()));
|
||||
connect(regionNTSC, SIGNAL(pressed()), this, SLOT(setRegionNTSC()));
|
||||
connect(regionPAL, SIGNAL(pressed()), this, SLOT(setRegionPAL()));
|
||||
connect(portSatellaview, SIGNAL(pressed()), this, SLOT(setPortSatellaview()));
|
||||
connect(portNone, SIGNAL(pressed()), this, SLOT(setPortNone()));
|
||||
connect(focusPause, SIGNAL(pressed()), this, SLOT(pauseWithoutFocus()));
|
||||
connect(focusIgnore, SIGNAL(pressed()), this, SLOT(ignoreInputWithoutFocus()));
|
||||
connect(focusAllow, SIGNAL(pressed()), this, SLOT(allowInputWithoutFocus()));
|
||||
}
|
||||
|
||||
void AdvancedSettingsWindow::initializeUi() {
|
||||
lstring part;
|
||||
|
||||
part.split(";", video.driver_list());
|
||||
for(unsigned i = 0; i < part.size(); i++) {
|
||||
videoDriver->addItem(utf8() << part[i]);
|
||||
if(part[i] == config.system.video) videoDriver->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
part.split(";", audio.driver_list());
|
||||
for(unsigned i = 0; i < part.size(); i++) {
|
||||
audioDriver->addItem(utf8() << part[i]);
|
||||
if(part[i] == config.system.audio) audioDriver->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
part.split(";", input.driver_list());
|
||||
for(unsigned i = 0; i < part.size(); i++) {
|
||||
inputDriver->addItem(utf8() << part[i]);
|
||||
if(part[i] == config.system.input) inputDriver->setCurrentIndex(i);
|
||||
}
|
||||
|
||||
regionAuto->setChecked(snes.config.region == SNES::Autodetect);
|
||||
regionNTSC->setChecked(snes.config.region == SNES::NTSC);
|
||||
regionPAL->setChecked (snes.config.region == SNES::PAL);
|
||||
|
||||
portSatellaview->setChecked(snes.config.expansion_port == SNES::ExpansionBSX);
|
||||
portNone->setChecked (snes.config.expansion_port == SNES::ExpansionNone);
|
||||
|
||||
focusPause->setChecked (config.input.focusPolicy == Configuration::Input::FocusPolicyPauseEmulation);
|
||||
focusIgnore->setChecked(config.input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput);
|
||||
focusAllow->setChecked (config.input.focusPolicy == Configuration::Input::FocusPolicyAllowInput);
|
||||
}
|
||||
|
||||
void AdvancedSettingsWindow::videoDriverChange(int index) {
|
||||
if(index >= 0) config.system.video = videoDriver->itemText(index).toUtf8().data();
|
||||
}
|
||||
|
||||
void AdvancedSettingsWindow::audioDriverChange(int index) {
|
||||
if(index >= 0) config.system.audio = audioDriver->itemText(index).toUtf8().data();
|
||||
}
|
||||
|
||||
void AdvancedSettingsWindow::inputDriverChange(int index) {
|
||||
if(index >= 0) config.system.input = inputDriver->itemText(index).toUtf8().data();
|
||||
}
|
||||
|
||||
void AdvancedSettingsWindow::setRegionAuto() { snes.config.region = SNES::Autodetect; }
|
||||
void AdvancedSettingsWindow::setRegionNTSC() { snes.config.region = SNES::NTSC; }
|
||||
void AdvancedSettingsWindow::setRegionPAL() { snes.config.region = SNES::PAL; }
|
||||
|
||||
void AdvancedSettingsWindow::setPortSatellaview() { snes.config.expansion_port = SNES::ExpansionBSX; }
|
||||
void AdvancedSettingsWindow::setPortNone() { snes.config.expansion_port = SNES::ExpansionNone; }
|
||||
|
||||
void AdvancedSettingsWindow::pauseWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyPauseEmulation; }
|
||||
void AdvancedSettingsWindow::ignoreInputWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyIgnoreInput; }
|
||||
void AdvancedSettingsWindow::allowInputWithoutFocus() { config.input.focusPolicy = Configuration::Input::FocusPolicyAllowInput; }
|
||||
54
bsnes/ui_qt/settings/advanced.hpp
Executable file
54
bsnes/ui_qt/settings/advanced.hpp
Executable file
@@ -0,0 +1,54 @@
|
||||
class AdvancedSettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QGridLayout *driverLayout;
|
||||
QLabel *videoLabel;
|
||||
QLabel *audioLabel;
|
||||
QLabel *inputLabel;
|
||||
QComboBox *videoDriver;
|
||||
QComboBox *audioDriver;
|
||||
QComboBox *inputDriver;
|
||||
QLabel *driverInfo;
|
||||
|
||||
QLabel *regionTitle;
|
||||
QHBoxLayout *regionLayout;
|
||||
QButtonGroup *regionGroup;
|
||||
QRadioButton *regionAuto;
|
||||
QRadioButton *regionNTSC;
|
||||
QRadioButton *regionPAL;
|
||||
|
||||
QLabel *portTitle;
|
||||
QHBoxLayout *portLayout;
|
||||
QButtonGroup *portGroup;
|
||||
QRadioButton *portSatellaview;
|
||||
QRadioButton *portNone;
|
||||
QWidget *portSpacer;
|
||||
|
||||
QLabel *focusTitle;
|
||||
QHBoxLayout *focusLayout;
|
||||
QButtonGroup *focusButtonGroup;
|
||||
QRadioButton *focusPause;
|
||||
QRadioButton *focusIgnore;
|
||||
QRadioButton *focusAllow;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void initializeUi();
|
||||
|
||||
public slots:
|
||||
void videoDriverChange(int index);
|
||||
void audioDriverChange(int index);
|
||||
void inputDriverChange(int index);
|
||||
void setRegionAuto();
|
||||
void setRegionNTSC();
|
||||
void setRegionPAL();
|
||||
void setPortSatellaview();
|
||||
void setPortNone();
|
||||
void pauseWithoutFocus();
|
||||
void ignoreInputWithoutFocus();
|
||||
void allowInputWithoutFocus();
|
||||
} *winAdvancedSettings;
|
||||
129
bsnes/ui_qt/settings/audio.cpp
Executable file
129
bsnes/ui_qt/settings/audio.cpp
Executable file
@@ -0,0 +1,129 @@
|
||||
void AudioSettingsWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Audio Settings");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
boxes = new QHBoxLayout; {
|
||||
frequencyLabel = new QLabel("Frequency:");
|
||||
frequencyLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
boxes->addWidget(frequencyLabel);
|
||||
|
||||
frequency = new QComboBox;
|
||||
frequency->addItem("32000hz");
|
||||
frequency->addItem("44100hz");
|
||||
frequency->addItem("48000hz");
|
||||
frequency->addItem("96000hz");
|
||||
boxes->addWidget(frequency);
|
||||
|
||||
latencyLabel = new QLabel("Latency:");
|
||||
latencyLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
boxes->addWidget(latencyLabel);
|
||||
|
||||
latency = new QComboBox;
|
||||
latency->addItem("20ms");
|
||||
latency->addItem("40ms");
|
||||
latency->addItem("60ms");
|
||||
latency->addItem("80ms");
|
||||
latency->addItem("100ms");
|
||||
latency->addItem("120ms");
|
||||
boxes->addWidget(latency);
|
||||
}
|
||||
boxes->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(boxes);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
sliders = new QGridLayout; {
|
||||
volumeLabel = new QLabel("Volume: 100%");
|
||||
volumeLabel->setToolTip("Warning: any volume other than 100% will result in a slight audio quality loss");
|
||||
sliders->addWidget(volumeLabel, 0, 0);
|
||||
|
||||
volume = new QSlider(Qt::Horizontal);
|
||||
volume->setMinimum(0);
|
||||
volume->setMaximum(200);
|
||||
sliders->addWidget(volume, 0, 1);
|
||||
|
||||
frequencySkewLabel = new QLabel("Input frequency: 32000hz");
|
||||
frequencySkewLabel->setToolTip(
|
||||
"Adjusts audio resampling rate.\n"
|
||||
"When both video sync and audio sync are enabled, use this setting to fine-tune the output.\n"
|
||||
"Lower the input frequency to clean audio output, eliminating crackling / popping.\n"
|
||||
"Raise the input frequency to smooth video output, eliminating duplicated frames."
|
||||
);
|
||||
sliders->addWidget(frequencySkewLabel, 1, 0);
|
||||
|
||||
frequencySkew = new QSlider(Qt::Horizontal);
|
||||
frequencySkew->setMinimum(31800);
|
||||
frequencySkew->setMaximum(32200);
|
||||
sliders->addWidget(frequencySkew);
|
||||
}
|
||||
sliders->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(sliders);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
panel->setLayout(layout);
|
||||
connect(frequency, SIGNAL(currentIndexChanged(int)), this, SLOT(frequencyChange(int)));
|
||||
connect(latency, SIGNAL(currentIndexChanged(int)), this, SLOT(latencyChange(int)));
|
||||
connect(volume, SIGNAL(valueChanged(int)), this, SLOT(volumeAdjust(int)));
|
||||
connect(frequencySkew, SIGNAL(valueChanged(int)), this, SLOT(frequencySkewAdjust(int)));
|
||||
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void AudioSettingsWindow::syncUi() {
|
||||
int n;
|
||||
|
||||
n = config.audio.outputFrequency;
|
||||
if(n <= 32000) frequency->setCurrentIndex(0);
|
||||
else if(n <= 44100) frequency->setCurrentIndex(1);
|
||||
else if(n <= 48000) frequency->setCurrentIndex(2);
|
||||
else if(n <= 96000) frequency->setCurrentIndex(3);
|
||||
else frequency->setCurrentIndex(0);
|
||||
|
||||
n = config.audio.latency;
|
||||
latency->setCurrentIndex((n - 20) / 20);
|
||||
|
||||
n = config.audio.volume;
|
||||
volumeLabel->setText(utf8() << "Volume: " << n << "%");
|
||||
volume->setSliderPosition(n);
|
||||
|
||||
n = config.audio.inputFrequency;
|
||||
frequencySkewLabel->setText(utf8() << "Input frequency: " << n << "hz");
|
||||
frequencySkew->setSliderPosition(n);
|
||||
}
|
||||
|
||||
void AudioSettingsWindow::frequencyChange(int value) {
|
||||
switch(value) { default:
|
||||
case 0: config.audio.outputFrequency = 32000; break;
|
||||
case 1: config.audio.outputFrequency = 44100; break;
|
||||
case 2: config.audio.outputFrequency = 48000; break;
|
||||
case 3: config.audio.outputFrequency = 96000; break;
|
||||
}
|
||||
audio.set(Audio::Frequency, config.audio.outputFrequency);
|
||||
}
|
||||
|
||||
void AudioSettingsWindow::latencyChange(int value) {
|
||||
value = max(0, min(5, value));
|
||||
config.audio.latency = 20 + value * 20;
|
||||
audio.set(Audio::Latency, config.audio.latency);
|
||||
}
|
||||
|
||||
void AudioSettingsWindow::volumeAdjust(int value) {
|
||||
config.audio.volume = value;
|
||||
audio.set(Audio::Volume, config.audio.volume);
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void AudioSettingsWindow::frequencySkewAdjust(int value) {
|
||||
config.audio.inputFrequency = value;
|
||||
utility.updateEmulationSpeed();
|
||||
syncUi();
|
||||
}
|
||||
28
bsnes/ui_qt/settings/audio.hpp
Executable file
28
bsnes/ui_qt/settings/audio.hpp
Executable file
@@ -0,0 +1,28 @@
|
||||
class AudioSettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QHBoxLayout *boxes;
|
||||
QLabel *frequencyLabel;
|
||||
QComboBox *frequency;
|
||||
QLabel *latencyLabel;
|
||||
QComboBox *latency;
|
||||
QGridLayout *sliders;
|
||||
QLabel *volumeLabel;
|
||||
QSlider *volume;
|
||||
QLabel *frequencySkewLabel;
|
||||
QSlider *frequencySkew;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
|
||||
public slots:
|
||||
void frequencyChange(int value);
|
||||
void latencyChange(int value);
|
||||
void volumeAdjust(int value);
|
||||
void frequencySkewAdjust(int value);
|
||||
} *winAudioSettings;
|
||||
156
bsnes/ui_qt/settings/cheateditor.cpp
Executable file
156
bsnes/ui_qt/settings/cheateditor.cpp
Executable file
@@ -0,0 +1,156 @@
|
||||
void CheatEditorWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Cheat Code Editor");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
list = new QTreeWidget;
|
||||
list->setColumnCount(4);
|
||||
list->setHeaderLabels(QStringList() << "Hidden" << "" << "Code" << "Description");
|
||||
list->sortByColumn(0, Qt::AscendingOrder); //set initial sorting, preserve user setting after reloadList()
|
||||
list->hideColumn(0); //used for default sorting + hides child expansion box
|
||||
layout->addWidget(list);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
controls = new QHBoxLayout; {
|
||||
addCode = new QPushButton("Add Code ...");
|
||||
controls->addWidget(addCode);
|
||||
|
||||
editCode = new QPushButton("Edit Code ...");
|
||||
controls->addWidget(editCode);
|
||||
|
||||
deleteCode = new QPushButton("Delete Code");
|
||||
controls->addWidget(deleteCode);
|
||||
}
|
||||
controls->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(controls);
|
||||
|
||||
panel->setLayout(layout);
|
||||
connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
|
||||
connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(editSelectedCode()));
|
||||
connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged()));
|
||||
connect(addCode, SIGNAL(released()), this, SLOT(addNewCode()));
|
||||
connect(editCode, SIGNAL(released()), this, SLOT(editSelectedCode()));
|
||||
connect(deleteCode, SIGNAL(released()), this, SLOT(deleteSelectedCode()));
|
||||
|
||||
reloadList();
|
||||
}
|
||||
|
||||
void CheatEditorWindow::syncUi() {
|
||||
addCode->setEnabled(cartridge.loaded());
|
||||
QList<QTreeWidgetItem*> itemList = list->selectedItems();
|
||||
editCode->setEnabled(cartridge.loaded() && itemList.count() == 1);
|
||||
deleteCode->setEnabled(cartridge.loaded() && itemList.count() == 1);
|
||||
}
|
||||
|
||||
//called when loading a new game, or after adding / deleting a code:
|
||||
//synchronizes list with cheat class list
|
||||
void CheatEditorWindow::reloadList() {
|
||||
disconnect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
|
||||
|
||||
list->clear();
|
||||
list->setSortingEnabled(false);
|
||||
listItem.reset();
|
||||
|
||||
if(cartridge.loaded()) {
|
||||
for(unsigned i = 0; i < cheat.count(); i++) {
|
||||
Cheat::cheat_t code;
|
||||
cheat.get(i, code);
|
||||
|
||||
//only want to show one code / description line in list
|
||||
lstring lcode, ldesc;
|
||||
lcode.split("+", code.code);
|
||||
ldesc.split("\n", code.desc);
|
||||
if(lcode.size() > 1) lcode[0] << "+" << (int)(lcode.size() - 1);
|
||||
if(ldesc.size() > 1) ldesc[0] << " ...";
|
||||
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(list);
|
||||
item->setText(0, utf8() << (int)(1000000 + i));
|
||||
item->setCheckState(1, code.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
item->setText(2, utf8() << lcode[0]);
|
||||
item->setText(3, utf8() << ldesc[0]);
|
||||
listItem.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
list->setSortingEnabled(true);
|
||||
list->resizeColumnToContents(1); //shrink checkbox column width
|
||||
list->resizeColumnToContents(2); //shrink code column width
|
||||
connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
|
||||
syncUi();
|
||||
}
|
||||
|
||||
//called when modifying an existing code:
|
||||
//list items are all still valid, only need to update item codes + descriptions
|
||||
void CheatEditorWindow::updateList() {
|
||||
disconnect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
|
||||
|
||||
for(unsigned i = 0; i < listItem.size(); i++) {
|
||||
Cheat::cheat_t code;
|
||||
cheat.get(i, code);
|
||||
|
||||
//only want to show one code / description line in list
|
||||
lstring lcode, ldesc;
|
||||
lcode.split("+", code.code);
|
||||
ldesc.split("\n", code.desc);
|
||||
if(lcode.size() > 1) lcode[0] << "+" << (int)(lcode.size() - 1);
|
||||
if(ldesc.size() > 1) ldesc[0] << " ...";
|
||||
|
||||
QTreeWidgetItem *item = listItem[i];
|
||||
item->setCheckState(1, code.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
item->setText(2, utf8() << lcode[0]);
|
||||
item->setText(3, utf8() << ldesc[0]);
|
||||
}
|
||||
|
||||
list->resizeColumnToContents(1); //shrink checkbox column width
|
||||
list->resizeColumnToContents(2); //shrink code column width
|
||||
connect(list, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemChanged(QTreeWidgetItem*)));
|
||||
syncUi();
|
||||
}
|
||||
|
||||
//called when item enabled checkbox was clicked (eg cheat code enable state was toggled on or off)
|
||||
void CheatEditorWindow::itemChanged(QTreeWidgetItem *item) {
|
||||
signed i = listItem.find(item);
|
||||
if(i >= 0) item->checkState(1) == Qt::Checked ? cheat.enable(i) : cheat.disable(i);
|
||||
}
|
||||
|
||||
void CheatEditorWindow::listChanged() {
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void CheatEditorWindow::addNewCode() {
|
||||
if(cartridge.loaded()) winCodeEditor->addCode();
|
||||
}
|
||||
|
||||
void CheatEditorWindow::editSelectedCode() {
|
||||
if(cartridge.loaded()) {
|
||||
QTreeWidgetItem *item = list->currentItem();
|
||||
if(item && item->isSelected()) {
|
||||
signed i = listItem.find(item);
|
||||
if(i >= 0) winCodeEditor->editCode(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatEditorWindow::deleteSelectedCode() {
|
||||
if(cartridge.loaded()) {
|
||||
QTreeWidgetItem *item = list->currentItem();
|
||||
if(item && item->isSelected()) {
|
||||
signed i = listItem.find(item);
|
||||
if(i >= 0) {
|
||||
//if code editor is modifying an existing code, its index will be invalidated by delete;
|
||||
//therefore, the editor window must be closed first
|
||||
if(winCodeEditor->activeCode >= 0) winCodeEditor->dismiss();
|
||||
|
||||
//remove code, and resync listItem with cheat list
|
||||
cheat.remove(i);
|
||||
reloadList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
bsnes/ui_qt/settings/cheateditor.hpp
Executable file
28
bsnes/ui_qt/settings/cheateditor.hpp
Executable file
@@ -0,0 +1,28 @@
|
||||
class CheatEditorWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QTreeWidget *list;
|
||||
QHBoxLayout *controls;
|
||||
QPushButton *addCode;
|
||||
QPushButton *editCode;
|
||||
QPushButton *deleteCode;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
void reloadList();
|
||||
void updateList();
|
||||
|
||||
public slots:
|
||||
void itemChanged(QTreeWidgetItem *item);
|
||||
void listChanged();
|
||||
void addNewCode();
|
||||
void editSelectedCode();
|
||||
void deleteSelectedCode();
|
||||
|
||||
private:
|
||||
array<QTreeWidgetItem*> listItem;
|
||||
} *winCheatEditor;
|
||||
174
bsnes/ui_qt/settings/input.cpp
Executable file
174
bsnes/ui_qt/settings/input.cpp
Executable file
@@ -0,0 +1,174 @@
|
||||
void InputSettingsWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Input Configuration Editor");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
selection = new QHBoxLayout; {
|
||||
port = new QComboBox;
|
||||
port->addItem("Controller Port 1");
|
||||
port->addItem("Controller Port 2");
|
||||
port->addItem("User Interface");
|
||||
selection->addWidget(port);
|
||||
|
||||
device = new QComboBox;
|
||||
selection->addWidget(device);
|
||||
}
|
||||
selection->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(selection);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
list = new QTreeWidget;
|
||||
list->setColumnCount(3);
|
||||
list->setHeaderLabels(QStringList() << "Hidden" << "Name" << "Assignment");
|
||||
list->hideColumn(0); //used for default sorting + hides child expansion box
|
||||
layout->addWidget(list);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
controls = new QHBoxLayout; {
|
||||
assign = new QPushButton("Assign ...");
|
||||
controls->addWidget(assign);
|
||||
|
||||
assignAll = new QPushButton("Assign All ...");
|
||||
controls->addWidget(assignAll);
|
||||
|
||||
unassign = new QPushButton("Unassign");
|
||||
controls->addWidget(unassign);
|
||||
}
|
||||
controls->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(controls);
|
||||
|
||||
panel->setLayout(layout);
|
||||
connect(port, SIGNAL(currentIndexChanged(int)), this, SLOT(portChanged()));
|
||||
connect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList()));
|
||||
connect(list, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(assignKey()));
|
||||
connect(list, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged()));
|
||||
connect(assign, SIGNAL(released()), this, SLOT(assignKey()));
|
||||
connect(assignAll, SIGNAL(released()), this, SLOT(assignAllKeys()));
|
||||
connect(unassign, SIGNAL(released()), this, SLOT(unassignKey()));
|
||||
|
||||
portChanged();
|
||||
}
|
||||
|
||||
void InputSettingsWindow::syncUi() {
|
||||
QList<QTreeWidgetItem*> itemList = list->selectedItems();
|
||||
assign->setEnabled(itemList.count() == 1);
|
||||
//allow rapid assign for controllers from both ports, but not for UI shortcuts
|
||||
assignAll->setEnabled(port->currentIndex() < 2);
|
||||
unassign->setEnabled(itemList.count() == 1);
|
||||
}
|
||||
|
||||
//when port combobox item is changed, device list needs to be repopulated
|
||||
void InputSettingsWindow::portChanged() {
|
||||
disconnect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList()));
|
||||
|
||||
device->clear();
|
||||
deviceItem.reset();
|
||||
|
||||
int index = port->currentIndex();
|
||||
if(index < 2) {
|
||||
//this is a controller port
|
||||
for(unsigned i = 0; i < inputPool.size(); i++) {
|
||||
//only add devices for selected port
|
||||
if(inputPool[i]->port == index) {
|
||||
device->addItem(inputPool[i]->name);
|
||||
deviceItem.add(inputPool[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//user interface controls
|
||||
for(unsigned i = 0; i < inputUiPool.size(); i++) {
|
||||
device->addItem(inputUiPool[i]->name);
|
||||
deviceItem.add(inputUiPool[i]);
|
||||
}
|
||||
}
|
||||
|
||||
reloadList();
|
||||
connect(device, SIGNAL(currentIndexChanged(int)), this, SLOT(reloadList()));
|
||||
}
|
||||
|
||||
//when device combobox item is changed, object list needs to be repopulated
|
||||
void InputSettingsWindow::reloadList() {
|
||||
list->clear();
|
||||
list->setSortingEnabled(false);
|
||||
listItem.reset();
|
||||
|
||||
int index = device->currentIndex();
|
||||
if(index < deviceItem.size()) {
|
||||
InputGroup &group = *deviceItem[index];
|
||||
for(unsigned i = 0; i < group.size(); i++) {
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(list);
|
||||
item->setText(0, utf8() << (int)(1000000 + i));
|
||||
item->setText(1, group[i]->name);
|
||||
item->setText(2, (const char*)group[i]->id);
|
||||
listItem.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
list->setSortingEnabled(true);
|
||||
list->sortByColumn(0, Qt::AscendingOrder); //set default sorting on list change, overriding user setting
|
||||
list->resizeColumnToContents(1); //shrink name column
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void InputSettingsWindow::listChanged() {
|
||||
syncUi();
|
||||
}
|
||||
|
||||
//InputCaptureWindow calls this after a successful key assignment change:
|
||||
//need to update list of values to show new key assignment value.
|
||||
void InputSettingsWindow::updateList() {
|
||||
int index = device->currentIndex();
|
||||
if(index < deviceItem.size()) {
|
||||
InputGroup &group = *deviceItem[index];
|
||||
|
||||
for(unsigned i = 0; i < listItem.size(); i++) {
|
||||
listItem[i]->setText(2, (const char*)group[i]->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettingsWindow::assignKey() {
|
||||
int index = device->currentIndex();
|
||||
if(index < deviceItem.size()) {
|
||||
InputGroup &group = *deviceItem[index];
|
||||
|
||||
QTreeWidgetItem *item = list->currentItem();
|
||||
if(item && item->isSelected()) {
|
||||
signed i = listItem.find(item);
|
||||
if(i >= 0) winInputCapture->activate(group[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettingsWindow::assignAllKeys() {
|
||||
int index = port->currentIndex();
|
||||
if(index < 2) {
|
||||
index = device->currentIndex();
|
||||
if(index < deviceItem.size()) {
|
||||
winInputCapture->activate(deviceItem[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettingsWindow::unassignKey() {
|
||||
int index = device->currentIndex();
|
||||
if(index < deviceItem.size()) {
|
||||
InputGroup &group = *deviceItem[index];
|
||||
|
||||
QTreeWidgetItem *item = list->currentItem();
|
||||
if(item && item->isSelected()) {
|
||||
signed i = listItem.find(item);
|
||||
if(i >= 0) {
|
||||
group[i]->id = "none";
|
||||
inputManager.bind(); //update key bindings to reflect object ID change above
|
||||
item->setText(2, (const char*)group[i]->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
bsnes/ui_qt/settings/input.hpp
Executable file
32
bsnes/ui_qt/settings/input.hpp
Executable file
@@ -0,0 +1,32 @@
|
||||
class InputSettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QHBoxLayout *selection;
|
||||
QComboBox *port;
|
||||
QComboBox *device;
|
||||
QTreeWidget *list;
|
||||
QHBoxLayout *controls;
|
||||
QPushButton *assign;
|
||||
QPushButton *assignAll;
|
||||
QPushButton *unassign;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
|
||||
public slots:
|
||||
void portChanged();
|
||||
void reloadList();
|
||||
void listChanged();
|
||||
void updateList();
|
||||
void assignKey();
|
||||
void assignAllKeys();
|
||||
void unassignKey();
|
||||
|
||||
private:
|
||||
array<InputGroup*> deviceItem;
|
||||
array<QTreeWidgetItem*> listItem;
|
||||
} *winInputSettings;
|
||||
200
bsnes/ui_qt/settings/paths.cpp
Executable file
200
bsnes/ui_qt/settings/paths.cpp
Executable file
@@ -0,0 +1,200 @@
|
||||
void PathSettingsWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Default Folder Paths");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
gameLabel = new QLabel("Games:");
|
||||
layout->addWidget(gameLabel);
|
||||
|
||||
games = new QHBoxLayout; {
|
||||
games->setMargin(0);
|
||||
|
||||
gamePath = new QLineEdit;
|
||||
gamePath->setReadOnly(true);
|
||||
games->addWidget(gamePath);
|
||||
|
||||
gameSelect = new QPushButton("Select ...");
|
||||
games->addWidget(gameSelect);
|
||||
|
||||
gameDefault = new QPushButton("Default");
|
||||
games->addWidget(gameDefault);
|
||||
}
|
||||
games->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(games);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
saveLabel = new QLabel("Save RAM:");
|
||||
layout->addWidget(saveLabel);
|
||||
|
||||
saves = new QHBoxLayout; {
|
||||
saves->setMargin(0);
|
||||
|
||||
savePath = new QLineEdit;
|
||||
savePath->setReadOnly(true);
|
||||
saves->addWidget(savePath);
|
||||
|
||||
saveSelect = new QPushButton("Select ...");
|
||||
saves->addWidget(saveSelect);
|
||||
|
||||
saveDefault = new QPushButton("Default");
|
||||
saves->addWidget(saveDefault);
|
||||
}
|
||||
saves->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(saves);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
patchLabel = new QLabel("UPS patches:");
|
||||
layout->addWidget(patchLabel);
|
||||
|
||||
patches = new QHBoxLayout; {
|
||||
patches->setMargin(0);
|
||||
|
||||
patchPath = new QLineEdit;
|
||||
patchPath->setReadOnly(true);
|
||||
patches->addWidget(patchPath);
|
||||
|
||||
patchSelect = new QPushButton("Select ...");
|
||||
patches->addWidget(patchSelect);
|
||||
|
||||
patchDefault = new QPushButton("Default");
|
||||
patches->addWidget(patchDefault);
|
||||
}
|
||||
patches->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(patches);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
cheatLabel = new QLabel("Cheat codes:");
|
||||
layout->addWidget(cheatLabel);
|
||||
|
||||
cheats = new QHBoxLayout; {
|
||||
cheats->setMargin(0);
|
||||
|
||||
cheatPath = new QLineEdit;
|
||||
cheatPath->setReadOnly(true);
|
||||
cheats->addWidget(cheatPath);
|
||||
|
||||
cheatSelect = new QPushButton("Select ...");
|
||||
cheats->addWidget(cheatSelect);
|
||||
|
||||
cheatDefault = new QPushButton("Default");
|
||||
cheats->addWidget(cheatDefault);
|
||||
}
|
||||
cheats->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(cheats);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
dataLabel = new QLabel("Export data:");
|
||||
layout->addWidget(dataLabel);
|
||||
|
||||
data = new QHBoxLayout;
|
||||
data->setMargin(0);
|
||||
data->setSpacing(Style::WidgetSpacing); {
|
||||
dataPath = new QLineEdit;
|
||||
dataPath->setReadOnly(true);
|
||||
data->addWidget(dataPath);
|
||||
|
||||
dataSelect = new QPushButton("Select ...");
|
||||
data->addWidget(dataSelect);
|
||||
|
||||
dataDefault = new QPushButton("Default");
|
||||
data->addWidget(dataDefault);
|
||||
}
|
||||
layout->addLayout(data);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
syncUi();
|
||||
|
||||
panel->setLayout(layout);
|
||||
connect(gameSelect, SIGNAL(released()), this, SLOT(selectGamePath()));
|
||||
connect(gameDefault, SIGNAL(released()), this, SLOT(defaultGamePath()));
|
||||
connect(saveSelect, SIGNAL(released()), this, SLOT(selectSavePath()));
|
||||
connect(saveDefault, SIGNAL(released()), this, SLOT(defaultSavePath()));
|
||||
connect(patchSelect, SIGNAL(released()), this, SLOT(selectPatchPath()));
|
||||
connect(patchDefault, SIGNAL(released()), this, SLOT(defaultPatchPath()));
|
||||
connect(cheatSelect, SIGNAL(released()), this, SLOT(selectCheatPath()));
|
||||
connect(cheatDefault, SIGNAL(released()), this, SLOT(defaultCheatPath()));
|
||||
connect(dataSelect, SIGNAL(released()), this, SLOT(selectDataPath()));
|
||||
connect(dataDefault, SIGNAL(released()), this, SLOT(defaultDataPath()));
|
||||
}
|
||||
|
||||
void PathSettingsWindow::syncUi() {
|
||||
gamePath->setText (snes.config.path.rom == "" ? "<startup path>" : (const char*)snes.config.path.rom);
|
||||
savePath->setText (snes.config.path.save == "" ? "<same folder as loaded game>" : (const char*)snes.config.path.save);
|
||||
patchPath->setText(snes.config.path.patch == "" ? "<same folder as loaded game>" : (const char*)snes.config.path.patch);
|
||||
cheatPath->setText(snes.config.path.cheat == "" ? "<same folder as loaded game>" : (const char*)snes.config.path.cheat);
|
||||
dataPath->setText (snes.config.path.data == "" ? "<same folder as loaded game>" : (const char*)snes.config.path.data);
|
||||
}
|
||||
|
||||
void PathSettingsWindow::selectGamePath() {
|
||||
string path = utility.selectFolder("Default Game Path");
|
||||
if(path.length() > 0) {
|
||||
snes.config.path.rom = path;
|
||||
syncUi();
|
||||
}
|
||||
}
|
||||
|
||||
void PathSettingsWindow::defaultGamePath() {
|
||||
snes.config.path.rom = "";
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void PathSettingsWindow::selectSavePath() {
|
||||
string path = utility.selectFolder("Default Save RAM Path");
|
||||
if(path.length() > 0) {
|
||||
snes.config.path.save = path;
|
||||
syncUi();
|
||||
}
|
||||
}
|
||||
|
||||
void PathSettingsWindow::defaultSavePath() {
|
||||
snes.config.path.save = "";
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void PathSettingsWindow::selectPatchPath() {
|
||||
string path = utility.selectFolder("Default UPS Patch Path");
|
||||
if(path.length() > 0) {
|
||||
snes.config.path.patch = path;
|
||||
syncUi();
|
||||
}
|
||||
}
|
||||
|
||||
void PathSettingsWindow::defaultPatchPath() {
|
||||
snes.config.path.patch = "";
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void PathSettingsWindow::selectCheatPath() {
|
||||
string path = utility.selectFolder("Default Cheat File Path");
|
||||
if(path.length() > 0) {
|
||||
snes.config.path.cheat = path;
|
||||
syncUi();
|
||||
}
|
||||
}
|
||||
|
||||
void PathSettingsWindow::defaultCheatPath() {
|
||||
snes.config.path.cheat = "";
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void PathSettingsWindow::selectDataPath() {
|
||||
string path = utility.selectFolder("Default Export Data Path");
|
||||
if(path.length() > 0) {
|
||||
snes.config.path.data = path;
|
||||
syncUi();
|
||||
}
|
||||
}
|
||||
|
||||
void PathSettingsWindow::defaultDataPath() {
|
||||
snes.config.path.data = "";
|
||||
syncUi();
|
||||
}
|
||||
49
bsnes/ui_qt/settings/paths.hpp
Executable file
49
bsnes/ui_qt/settings/paths.hpp
Executable file
@@ -0,0 +1,49 @@
|
||||
class PathSettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QLabel *gameLabel;
|
||||
QHBoxLayout *games;
|
||||
QLineEdit *gamePath;
|
||||
QPushButton *gameSelect;
|
||||
QPushButton *gameDefault;
|
||||
QLabel *saveLabel;
|
||||
QHBoxLayout *saves;
|
||||
QLineEdit *savePath;
|
||||
QPushButton *saveSelect;
|
||||
QPushButton *saveDefault;
|
||||
QLabel *patchLabel;
|
||||
QHBoxLayout *patches;
|
||||
QLineEdit *patchPath;
|
||||
QPushButton *patchSelect;
|
||||
QPushButton *patchDefault;
|
||||
QLabel *cheatLabel;
|
||||
QHBoxLayout *cheats;
|
||||
QLineEdit *cheatPath;
|
||||
QPushButton *cheatSelect;
|
||||
QPushButton *cheatDefault;
|
||||
QLabel *dataLabel;
|
||||
QHBoxLayout *data;
|
||||
QLineEdit *dataPath;
|
||||
QPushButton *dataSelect;
|
||||
QPushButton *dataDefault;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
|
||||
public slots:
|
||||
void selectGamePath();
|
||||
void defaultGamePath();
|
||||
void selectSavePath();
|
||||
void defaultSavePath();
|
||||
void selectPatchPath();
|
||||
void defaultPatchPath();
|
||||
void selectCheatPath();
|
||||
void defaultCheatPath();
|
||||
void selectDataPath();
|
||||
void defaultDataPath();
|
||||
} *winPathSettings;
|
||||
84
bsnes/ui_qt/settings/settings.cpp
Executable file
84
bsnes/ui_qt/settings/settings.cpp
Executable file
@@ -0,0 +1,84 @@
|
||||
#include "video.cpp"
|
||||
#include "audio.cpp"
|
||||
#include "input.cpp"
|
||||
#include "paths.cpp"
|
||||
#include "cheateditor.cpp"
|
||||
#include "advanced.cpp"
|
||||
|
||||
#include "utility/inputcapture.cpp"
|
||||
#include "utility/codeeditor.cpp"
|
||||
|
||||
void SettingsWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("settings-window");
|
||||
window->setWindowTitle("Configuration Settings");
|
||||
|
||||
list = new QListWidget;
|
||||
video = new QListWidgetItem("Video");
|
||||
audio = new QListWidgetItem("Audio");
|
||||
input = new QListWidgetItem("Input");
|
||||
paths = new QListWidgetItem("Paths");
|
||||
cheatcodes = new QListWidgetItem("Cheat Codes");
|
||||
advanced = new QListWidgetItem("Advanced");
|
||||
list->addItem(video);
|
||||
list->addItem(audio);
|
||||
list->addItem(input);
|
||||
list->addItem(paths);
|
||||
list->addItem(cheatcodes);
|
||||
list->addItem(advanced);
|
||||
list->setCurrentItem(input); //select most frequently used panel by default
|
||||
list->setFixedWidth(135);
|
||||
list->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
|
||||
panel = new QWidget;
|
||||
panel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
layout = new QHBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(Style::WidgetSpacing);
|
||||
layout->addWidget(list);
|
||||
layout->addWidget(panel);
|
||||
window->setLayout(layout);
|
||||
|
||||
winVideoSettings = new VideoSettingsWindow;
|
||||
winAudioSettings = new AudioSettingsWindow;
|
||||
winInputSettings = new InputSettingsWindow;
|
||||
winPathSettings = new PathSettingsWindow;
|
||||
winCheatEditor = new CheatEditorWindow;
|
||||
winAdvancedSettings = new AdvancedSettingsWindow;
|
||||
winInputCapture = new InputCaptureWindow;
|
||||
winCodeEditor = new CodeEditorWindow;
|
||||
|
||||
winVideoSettings->setup();
|
||||
winAudioSettings->setup();
|
||||
winInputSettings->setup();
|
||||
winPathSettings->setup();
|
||||
winCheatEditor->setup();
|
||||
winAdvancedSettings->setup();
|
||||
winInputCapture->setup();
|
||||
winCodeEditor->setup();
|
||||
|
||||
panelLayout = new QStackedLayout(panel);
|
||||
panelLayout->addWidget(winVideoSettings->panel);
|
||||
panelLayout->addWidget(winAudioSettings->panel);
|
||||
panelLayout->addWidget(winInputSettings->panel);
|
||||
panelLayout->addWidget(winPathSettings->panel);
|
||||
panelLayout->addWidget(winCheatEditor->panel);
|
||||
panelLayout->addWidget(winAdvancedSettings->panel);
|
||||
panel->setLayout(panelLayout);
|
||||
|
||||
connect(list, SIGNAL(currentRowChanged(int)), this, SLOT(listChanged()));
|
||||
|
||||
listChanged();
|
||||
window->setMinimumSize(600, 360);
|
||||
}
|
||||
|
||||
void SettingsWindow::listChanged() {
|
||||
QListWidgetItem *item = list->currentItem();
|
||||
if(item == video) panelLayout->setCurrentWidget(winVideoSettings->panel);
|
||||
if(item == audio) panelLayout->setCurrentWidget(winAudioSettings->panel);
|
||||
if(item == input) panelLayout->setCurrentWidget(winInputSettings->panel);
|
||||
if(item == paths) panelLayout->setCurrentWidget(winPathSettings->panel);
|
||||
if(item == cheatcodes) panelLayout->setCurrentWidget(winCheatEditor->panel);
|
||||
if(item == advanced) panelLayout->setCurrentWidget(winAdvancedSettings->panel);
|
||||
}
|
||||
31
bsnes/ui_qt/settings/settings.hpp
Executable file
31
bsnes/ui_qt/settings/settings.hpp
Executable file
@@ -0,0 +1,31 @@
|
||||
#include "video.moc"
|
||||
#include "audio.moc"
|
||||
#include "input.moc"
|
||||
#include "paths.moc"
|
||||
#include "cheateditor.moc"
|
||||
#include "advanced.moc"
|
||||
|
||||
#include "utility/inputcapture.moc"
|
||||
#include "utility/codeeditor.moc"
|
||||
|
||||
class SettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *window;
|
||||
QHBoxLayout *layout;
|
||||
QListWidget *list;
|
||||
QListWidgetItem *video;
|
||||
QListWidgetItem *audio;
|
||||
QListWidgetItem *input;
|
||||
QListWidgetItem *paths;
|
||||
QListWidgetItem *cheatcodes;
|
||||
QListWidgetItem *advanced;
|
||||
QWidget *panel;
|
||||
QStackedLayout *panelLayout;
|
||||
|
||||
void setup();
|
||||
|
||||
public slots:
|
||||
void listChanged();
|
||||
} *winSettings;
|
||||
180
bsnes/ui_qt/settings/utility/codeeditor.cpp
Executable file
180
bsnes/ui_qt/settings/utility/codeeditor.cpp
Executable file
@@ -0,0 +1,180 @@
|
||||
void CodeEditorWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("code-editor-window");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0);
|
||||
|
||||
descLabel = new QLabel("Description:");
|
||||
layout->addWidget(descLabel);
|
||||
|
||||
description = new QTextEdit;
|
||||
layout->addWidget(description);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
codeLabel = new QLabel("Cheat code(s):");
|
||||
layout->addWidget(codeLabel);
|
||||
|
||||
codeLayout = new QHBoxLayout; {
|
||||
codeLayout->setMargin(0);
|
||||
|
||||
codeList = new QListWidget;
|
||||
codeLayout->addWidget(codeList);
|
||||
codeLayout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
controls = new QVBoxLayout; {
|
||||
controls->setMargin(0);
|
||||
|
||||
codeValue = new QLineEdit;
|
||||
controls->addWidget(codeValue);
|
||||
|
||||
codeAdd = new QPushButton("Add Code");
|
||||
controls->addWidget(codeAdd);
|
||||
|
||||
codeDelete = new QPushButton("Delete Code");
|
||||
controls->addWidget(codeDelete);
|
||||
|
||||
codeDeleteAll = new QPushButton("Delete All");
|
||||
controls->addWidget(codeDeleteAll);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
controls->addWidget(spacer);
|
||||
}
|
||||
codeLayout->addLayout(controls);
|
||||
}
|
||||
layout->addLayout(codeLayout);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
enabled = new QCheckBox("Enable this cheat code");
|
||||
layout->addWidget(enabled);
|
||||
|
||||
finishControls = new QHBoxLayout; {
|
||||
okButton = new QPushButton("Ok");
|
||||
finishControls->addWidget(okButton);
|
||||
|
||||
cancelButton = new QPushButton("Cancel");
|
||||
finishControls->addWidget(cancelButton);
|
||||
}
|
||||
finishControls->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(finishControls);
|
||||
|
||||
window->setLayout(layout);
|
||||
window->setMinimumSize(400, 375);
|
||||
|
||||
connect(codeList, SIGNAL(itemSelectionChanged()), this, SLOT(listChanged()));
|
||||
connect(codeValue, SIGNAL(textChanged(const QString&)), this, SLOT(codeChanged()));
|
||||
connect(codeAdd, SIGNAL(released()), this, SLOT(addCodeToList()));
|
||||
connect(codeDelete, SIGNAL(released()), this, SLOT(deleteCodeFromList()));
|
||||
connect(codeDeleteAll, SIGNAL(released()), this, SLOT(deleteAllCodesFromList()));
|
||||
connect(okButton, SIGNAL(released()), this, SLOT(accept()));
|
||||
connect(cancelButton, SIGNAL(released()), this, SLOT(dismiss()));
|
||||
}
|
||||
|
||||
void CodeEditorWindow::syncUi() {
|
||||
//only activate add button when code is valid
|
||||
string code = codeValue->text().toUtf8().data();
|
||||
Cheat::cheat_t temp;
|
||||
bool valid = cheat.decode(code, temp);
|
||||
codeAdd->setEnabled(valid);
|
||||
|
||||
//only activate delete button when a code is selected
|
||||
QListWidgetItem *item = codeList->currentItem();
|
||||
codeDelete->setEnabled(item && item->isSelected());
|
||||
|
||||
//only activate delete all / ok buttons when there are one or more codes entered
|
||||
codeDeleteAll->setEnabled(codeList->count() > 0);
|
||||
okButton->setEnabled(codeList->count() > 0);
|
||||
}
|
||||
|
||||
void CodeEditorWindow::listChanged() { syncUi(); }
|
||||
void CodeEditorWindow::codeChanged() { syncUi(); }
|
||||
|
||||
void CodeEditorWindow::addCodeToList() {
|
||||
string code = codeValue->text().toUtf8().data();
|
||||
Cheat::cheat_t temp;
|
||||
if(cheat.decode(code, temp) == true) codeList->addItem(utf8() << code);
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void CodeEditorWindow::deleteCodeFromList() {
|
||||
int index = codeList->currentRow();
|
||||
if(index >= 0) {
|
||||
QListWidgetItem *item = codeList->takeItem(index);
|
||||
delete item;
|
||||
}
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void CodeEditorWindow::deleteAllCodesFromList() {
|
||||
codeList->clear();
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void CodeEditorWindow::accept() {
|
||||
string desc = description->toPlainText().toUtf8().data();
|
||||
string code;
|
||||
for(unsigned i = 0; i < codeList->count(); i++) {
|
||||
code << (codeList->item(i)->text().toUtf8().data());
|
||||
if(i != codeList->count() - 1) code << "+";
|
||||
}
|
||||
|
||||
if(activeCode == -1) {
|
||||
//adding a new code
|
||||
cheat.add(enabled->isChecked(), code, desc);
|
||||
winCheatEditor->reloadList();
|
||||
} else if(codeList->count() > 0) {
|
||||
//editing an existing code
|
||||
cheat.edit(activeCode, enabled->isChecked(), code, desc);
|
||||
winCheatEditor->updateList();
|
||||
} else {
|
||||
//deleting an existing code
|
||||
cheat.remove(activeCode);
|
||||
winCheatEditor->reloadList();
|
||||
}
|
||||
|
||||
dismiss();
|
||||
}
|
||||
|
||||
void CodeEditorWindow::dismiss() {
|
||||
activeCode = -1;
|
||||
window->hide();
|
||||
}
|
||||
|
||||
void CodeEditorWindow::addCode() {
|
||||
activeCode = -1;
|
||||
description->setPlainText("");
|
||||
codeList->clear();
|
||||
codeValue->setText("");
|
||||
enabled->setCheckState(Qt::Unchecked);
|
||||
showWindow("Add New Cheat Code");
|
||||
}
|
||||
|
||||
void CodeEditorWindow::editCode(unsigned code) {
|
||||
activeCode = code;
|
||||
codeList->clear();
|
||||
codeValue->setText("");
|
||||
|
||||
Cheat::cheat_t item;
|
||||
cheat.get(activeCode, item);
|
||||
|
||||
description->setPlainText(utf8() << item.desc);
|
||||
|
||||
lstring part;
|
||||
part.split("+", item.code);
|
||||
|
||||
for(unsigned i = 0; i < item.count; i++) codeList->addItem(utf8() << part[i]);
|
||||
enabled->setCheckState(item.enabled ? Qt::Checked : Qt::Unchecked);
|
||||
showWindow("Edit Existing Cheat Code");
|
||||
}
|
||||
|
||||
void CodeEditorWindow::showWindow(const char *title) {
|
||||
syncUi();
|
||||
window->setWindowTitle(title);
|
||||
utility.showCentered(window);
|
||||
}
|
||||
|
||||
CodeEditorWindow::CodeEditorWindow() {
|
||||
activeCode = -1;
|
||||
}
|
||||
43
bsnes/ui_qt/settings/utility/codeeditor.hpp
Executable file
43
bsnes/ui_qt/settings/utility/codeeditor.hpp
Executable file
@@ -0,0 +1,43 @@
|
||||
class CodeEditorWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *window;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *descLabel;
|
||||
QTextEdit *description;
|
||||
QLabel *codeLabel;
|
||||
QHBoxLayout *codeLayout;
|
||||
QListWidget *codeList;
|
||||
QVBoxLayout *controls;
|
||||
QLineEdit *codeValue;
|
||||
QPushButton *codeAdd;
|
||||
QPushButton *codeDelete;
|
||||
QPushButton *codeDeleteAll;
|
||||
QWidget *spacer;
|
||||
QCheckBox *enabled;
|
||||
QHBoxLayout *finishControls;
|
||||
QPushButton *okButton;
|
||||
QPushButton *cancelButton;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
void addCode();
|
||||
void editCode(unsigned code);
|
||||
CodeEditorWindow();
|
||||
|
||||
public slots:
|
||||
void listChanged();
|
||||
void codeChanged();
|
||||
void addCodeToList();
|
||||
void deleteCodeFromList();
|
||||
void deleteAllCodesFromList();
|
||||
void accept();
|
||||
void dismiss();
|
||||
|
||||
private:
|
||||
signed activeCode;
|
||||
void showWindow(const char *title);
|
||||
|
||||
friend class CheatEditorWindow;
|
||||
} *winCodeEditor;
|
||||
454
bsnes/ui_qt/settings/utility/inputcapture.cpp
Executable file
454
bsnes/ui_qt/settings/utility/inputcapture.cpp
Executable file
@@ -0,0 +1,454 @@
|
||||
void InputCaptureWindow::setup() {
|
||||
window = new Window;
|
||||
window->setObjectName("input-capture-window");
|
||||
window->setWindowTitle("Input Capture");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0);
|
||||
|
||||
hlayout = new QHBoxLayout;
|
||||
hlayout->setSpacing(Style::WidgetSpacing); {
|
||||
title = new QLabel;
|
||||
hlayout->addWidget(title, 0, Qt::AlignTop);
|
||||
|
||||
mouseAxes = new QPushButton(" Assign Mouse Axis ");
|
||||
mouseAxes->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
hlayout->addWidget(mouseAxes, 0, Qt::AlignTop);
|
||||
|
||||
mouseButtons = new QPushButton(" Assign Mouse Button ");
|
||||
mouseButtons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
hlayout->addWidget(mouseButtons, 0, Qt::AlignTop);
|
||||
}
|
||||
layout->addLayout(hlayout);
|
||||
|
||||
imageSpacer = new QWidget;
|
||||
imageSpacer->setFixedSize(Style::WidgetSpacing, Style::WidgetSpacing);
|
||||
layout->addWidget(imageSpacer);
|
||||
|
||||
imageWidget = new ImageWidget;
|
||||
layout->addWidget(imageWidget, 0, Qt::AlignHCenter);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
window->setLayout(layout);
|
||||
connect(mouseAxes, SIGNAL(released()), this, SLOT(assignMouseAxis()));
|
||||
connect(mouseButtons, SIGNAL(released()), this, SLOT(assignMouseButton()));
|
||||
|
||||
winInputMouseCaptureWindow = new InputMouseCaptureWindow;
|
||||
winInputMouseCaptureWindow->setup();
|
||||
|
||||
winInputCalibration = new InputCalibrationWindow;
|
||||
winInputCalibration->setup();
|
||||
}
|
||||
|
||||
void InputCaptureWindow::activate(InputObject *object) {
|
||||
if(!activeGroup) window->hide();
|
||||
|
||||
utf8 info;
|
||||
info << "<b>ID:</b> ";
|
||||
if(object->parent) {
|
||||
InputDevice *device = dynamic_cast<InputDevice*>(object->parent);
|
||||
if(device) info << "Controller port " << (int)(device->port + 1) << " <b>::</b> ";
|
||||
else info << "User interface <b>::</b> ";
|
||||
info << object->parent->name << " <b>::</b> ";
|
||||
}
|
||||
info << object->name << "<br>";
|
||||
|
||||
activeObject = object;
|
||||
if(activeObject->type == InputObject::Button) {
|
||||
mouseAxes->hide();
|
||||
mouseButtons->show();
|
||||
|
||||
info << "Press any key or button to assign to this ID.";
|
||||
} else /*(activeObject->type == InputObject::Axis)*/ {
|
||||
mouseAxes->show();
|
||||
mouseButtons->hide();
|
||||
|
||||
info << "Move any axis to assign to this ID.";
|
||||
}
|
||||
|
||||
if(dynamic_cast<Joypad*>(activeObject->parent)) {
|
||||
imageSpacer->show();
|
||||
imageWidget->setFixedSize(480, 210);
|
||||
imageWidget->show();
|
||||
} else {
|
||||
imageSpacer->hide();
|
||||
imageWidget->hide();
|
||||
}
|
||||
|
||||
title->setText(info);
|
||||
utility.showCentered(window);
|
||||
}
|
||||
|
||||
void InputCaptureWindow::activate(InputGroup *group) {
|
||||
activeGroup = group;
|
||||
groupIndex = 0;
|
||||
window->hide();
|
||||
activate((*activeGroup)[groupIndex]);
|
||||
}
|
||||
|
||||
void InputCaptureWindow::inputEvent(uint16_t code, bool forceAssign /* = false */) {
|
||||
if(!activeObject || inputLock == true) return;
|
||||
|
||||
//input polling is global, need to block mouse actions that may be UI interactions.
|
||||
//custom controls on window allow mouse assignment instead.
|
||||
if(forceAssign == false) {
|
||||
if(winInputMouseCaptureWindow->window->isActiveWindow()) {
|
||||
winInputMouseCaptureWindow->inputEvent(code);
|
||||
return;
|
||||
}
|
||||
if(!window->isActiveWindow()) return;
|
||||
|
||||
//get as much info as possible about this code
|
||||
InputCode::type_t type = InputCode::type(code);
|
||||
InputCode::axistype_t axisType = InputCode::axisType(code);
|
||||
int joypadNumber = InputCode::joypadNumber(code);
|
||||
int16_t state = inputManager.state(code);
|
||||
unsigned distance = abs(state - inputManager.lastState(code));
|
||||
|
||||
if(type == InputCode::JoypadHat) {
|
||||
//hats can be in any of nine clock-wise states (4x direct, 4x angled and centered.)
|
||||
//only map when hat is moved to an explicit direction.
|
||||
if(state != joypad<>::hat_up && state != joypad<>::hat_down
|
||||
&& state != joypad<>::hat_left && state != joypad<>::hat_right) return;
|
||||
}
|
||||
|
||||
if(type == InputCode::JoypadAxis) {
|
||||
//require a degree of resistance to prevent accidental mapping
|
||||
if(axisType == InputCode::Stick) {
|
||||
//require axis to be pressed almost completely toward a specific direction
|
||||
if(state > -28672 && state < +28672) return;
|
||||
} else if(axisType == InputCode::Trigger) {
|
||||
//require trigger to be at least 75% pressed down
|
||||
if(state > -16384) return;
|
||||
} else {
|
||||
//invalid axis type: most likely the controller has yet to be calibrated
|
||||
if(joypadNumber < 0) return; //should never occur
|
||||
|
||||
//some analog triggers report phantom motion even when user does not touch gamepad
|
||||
if(distance < 64) return; //require some degree of force to trigger calibration
|
||||
if(state > -8192 && state < +8192) return; //ignore center motion
|
||||
|
||||
if(inputManager.calibrated(joypadNumber) == false) {
|
||||
winInputCalibration->activate(joypadNumber);
|
||||
}
|
||||
|
||||
//block assignment until controller is fully calibrated
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(activeObject->type == InputObject::Axis) {
|
||||
if(type == InputCode::KeyboardButton
|
||||
|| type == InputCode::MouseAxis
|
||||
|| type == InputCode::MouseButton
|
||||
|| type == InputCode::JoypadHat
|
||||
|| type == InputCode::JoypadButton
|
||||
) return;
|
||||
|
||||
//uni-directional trigger cannot map to bi-directional axis
|
||||
if(type == InputCode::JoypadAxis && axisType == InputCode::Trigger) return;
|
||||
}
|
||||
|
||||
if(activeObject->type == InputObject::Button) {
|
||||
if(type == InputCode::MouseAxis
|
||||
|| type == InputCode::MouseButton
|
||||
) return;
|
||||
|
||||
//only capture on button press, not release
|
||||
if(type != InputCode::JoypadAxis && state == false) return;
|
||||
}
|
||||
|
||||
//if assigning a complete controller set, ensure requested key has not been assigned
|
||||
//to a previous entry in the group already. this prevents slow motion of joypad axes
|
||||
//from assigning the same value to multiple entries in rapid succession.
|
||||
if(activeGroup && groupIndex > 0) {
|
||||
for(unsigned i = 0; i < groupIndex; i++) {
|
||||
if(code == (*activeGroup)[i]->code) {
|
||||
//joypad hats and axes have multiple states, and are differentiated by modifier.
|
||||
//allow mapping only if requested modifier is unique.
|
||||
if(type == InputCode::JoypadHat) {
|
||||
if(state == joypad<>::hat_up && (*activeGroup)[i]->modifier == InputObject::Up ) return;
|
||||
if(state == joypad<>::hat_down && (*activeGroup)[i]->modifier == InputObject::Down ) return;
|
||||
if(state == joypad<>::hat_left && (*activeGroup)[i]->modifier == InputObject::Left ) return;
|
||||
if(state == joypad<>::hat_right && (*activeGroup)[i]->modifier == InputObject::Right) return;
|
||||
} else if(type == InputCode::JoypadAxis) {
|
||||
if(axisType == InputCode::Stick) {
|
||||
if(state < 0 && (*activeGroup)[i]->modifier == InputObject::Lo) return;
|
||||
if(state >= 0 && (*activeGroup)[i]->modifier == InputObject::Hi) return;
|
||||
} else if(axisType == InputCode::Trigger) {
|
||||
if((*activeGroup)[i]->modifier == InputObject::Trigger) return;
|
||||
}
|
||||
} else {
|
||||
//this code has already been used, do not map it
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//bind code and update GUI input assignment list
|
||||
activeObject->bind(code);
|
||||
winInputSettings->updateList();
|
||||
activeObject = 0;
|
||||
|
||||
//ignore multiple simultaneous state changes.
|
||||
//this helps with joypads that only activate
|
||||
//analog inputs after the first button press.
|
||||
inputLock = true;
|
||||
for(unsigned i = 0; i < 2; i++) inputManager.refresh();
|
||||
inputLock = false;
|
||||
|
||||
if(!activeGroup) {
|
||||
window->hide();
|
||||
winInputMouseCaptureWindow->window->hide();
|
||||
} else {
|
||||
//try and map the next code in this input group
|
||||
groupIndex++;
|
||||
if(groupIndex < activeGroup->size()) {
|
||||
activate((*activeGroup)[groupIndex]);
|
||||
} else {
|
||||
//all group codes mapped
|
||||
window->hide();
|
||||
winInputMouseCaptureWindow->window->hide();
|
||||
activeGroup = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputCaptureWindow::assignMouseAxis() {
|
||||
//refresh input state so that mouse release event (from SIGNAL(released())
|
||||
//is not sent immediately after window is visible.
|
||||
inputManager.refresh();
|
||||
winInputMouseCaptureWindow->activate(InputMouseCaptureWindow::AxisMode);
|
||||
}
|
||||
|
||||
void InputCaptureWindow::assignMouseButton() {
|
||||
inputManager.refresh();
|
||||
winInputMouseCaptureWindow->activate(InputMouseCaptureWindow::ButtonMode);
|
||||
}
|
||||
|
||||
InputCaptureWindow::InputCaptureWindow() {
|
||||
activeObject = 0;
|
||||
activeGroup = 0;
|
||||
groupIndex = 0;
|
||||
inputLock = false;
|
||||
}
|
||||
|
||||
void InputCaptureWindow::Window::closeEvent(QCloseEvent*) {
|
||||
//window closed by user, cancel key assignment
|
||||
winInputCapture->activeObject = 0;
|
||||
winInputCapture->activeGroup = 0;
|
||||
}
|
||||
|
||||
void InputCaptureWindow::ImageWidget::paintEvent(QPaintEvent*) {
|
||||
//currently, there is only an image available for the joypad.
|
||||
//in the future, this routine should determine which type of
|
||||
//image to draw via activeObject->parent's derived class type.
|
||||
QPainter painter(this);
|
||||
QPixmap pixmap(":/joypad.png");
|
||||
painter.drawPixmap(0, 0, pixmap);
|
||||
}
|
||||
|
||||
//=======================
|
||||
//InputMouseCaptureWindow
|
||||
//=======================
|
||||
|
||||
void InputMouseCaptureWindow::setup() {
|
||||
window = new QWidget;
|
||||
window->setObjectName("input-mouse-capture-window");
|
||||
window->setWindowTitle("Mouse Input Capture");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0);
|
||||
|
||||
info = new QLabel;
|
||||
layout->addWidget(info);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
captureBox = new QLabel("[ capture box ]");
|
||||
captureBox->setObjectName("mouse-capture-box");
|
||||
captureBox->setAlignment(Qt::AlignCenter);
|
||||
captureBox->setFixedHeight(120);
|
||||
layout->addWidget(captureBox);
|
||||
|
||||
buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->setSpacing(Style::WidgetSpacing); {
|
||||
xAxis = new QPushButton("X-axis");
|
||||
buttonLayout->addWidget(xAxis);
|
||||
|
||||
yAxis = new QPushButton("Y-axis");
|
||||
buttonLayout->addWidget(yAxis);
|
||||
}
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
window->setLayout(layout);
|
||||
connect(xAxis, SIGNAL(released()), this, SLOT(assignAxisX()));
|
||||
connect(yAxis, SIGNAL(released()), this, SLOT(assignAxisY()));
|
||||
}
|
||||
|
||||
void InputMouseCaptureWindow::activate(InputMouseCaptureWindow::Mode mode_) {
|
||||
window->hide();
|
||||
activeMode = mode_;
|
||||
|
||||
if(activeMode == AxisMode) {
|
||||
captureBox->hide();
|
||||
xAxis->show();
|
||||
yAxis->show();
|
||||
|
||||
info->setText(utf8()
|
||||
<< "To assign a mouse axis to this ID, please click the desired axis button below,<br>"
|
||||
<< "using the mouse that you want the axis to be assigned to."
|
||||
);
|
||||
|
||||
activeMouse = -1;
|
||||
} else /*(activeMode == ButtonMode) */ {
|
||||
captureBox->show();
|
||||
xAxis->hide();
|
||||
yAxis->hide();
|
||||
|
||||
info->setText(utf8()
|
||||
<< "To assign a mouse button to this ID, please place the mouse you wish to map<br>"
|
||||
<< "over the capture box below, and then click any mouse button to set assignment."
|
||||
);
|
||||
}
|
||||
|
||||
utility.showCentered(window);
|
||||
}
|
||||
|
||||
//this is only called when isActiveWindow() == true
|
||||
void InputMouseCaptureWindow::inputEvent(uint16_t code) {
|
||||
InputCode::type_t type = InputCode::type(code);
|
||||
int16_t state = inputManager.state(code);
|
||||
|
||||
if(activeMode == AxisMode) {
|
||||
//when pressing down mouse button (eg to select "X-axis" or "Y-axis"),
|
||||
//record mouse index for later assignment
|
||||
if(type == InputCode::MouseButton && state == true) {
|
||||
activeMouse = InputCode::mouseNumber(code);
|
||||
return;
|
||||
}
|
||||
} else if(activeMode == ButtonMode) {
|
||||
//if this is a mouse button that is being released ...
|
||||
if(type == InputCode::MouseButton && state == false) {
|
||||
//ensure button was clicked inside active capture box
|
||||
QRect windowRect = window->geometry();
|
||||
QRect widgetRect = captureBox->geometry();
|
||||
unsigned wx = windowRect.left() + widgetRect.left();
|
||||
unsigned wy = windowRect.top() + widgetRect.top();
|
||||
unsigned px = QCursor::pos().x();
|
||||
unsigned py = QCursor::pos().y();
|
||||
|
||||
if(px < wx || px >= wx + widgetRect.size().width() ) return;
|
||||
if(py < wy || py >= wy + widgetRect.size().height()) return;
|
||||
|
||||
winInputCapture->inputEvent(code, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InputMouseCaptureWindow::assignAxisX() {
|
||||
if(activeMouse >= 0) {
|
||||
winInputCapture->inputEvent(mouse<>::index(activeMouse, mouse<>::x), true);
|
||||
}
|
||||
}
|
||||
|
||||
void InputMouseCaptureWindow::assignAxisY() {
|
||||
if(activeMouse >= 0) {
|
||||
winInputCapture->inputEvent(mouse<>::index(activeMouse, mouse<>::y), true);
|
||||
}
|
||||
}
|
||||
|
||||
//====================
|
||||
//InputCalibrateWindow
|
||||
//====================
|
||||
|
||||
//background:
|
||||
//===========
|
||||
//HID devices work by sending device state *changes* only. what this means is that when an application is started,
|
||||
//it does not know the current state of said device. the keyboard and mouse are exceptions, as the OS globally
|
||||
//tracks their states. but this does apply to joypads. once a button is pressed, or an axis is moved, the entire
|
||||
//joypad state will be sent in a message, that APIs such as DirectInput and SDL will buffer.
|
||||
//
|
||||
//to complicate matters, recent joypads have added pressure-sensitive buttons (triggers), in addition to the
|
||||
//existing analog sticks. but this functionality was not extended to the USB HID state or to various platform
|
||||
//input APIs. instead, they are treated exactly like axes.
|
||||
//
|
||||
//however, an application needs to treat these two input types distinctly:
|
||||
//a stick is a bi-directional input. the stick starts off centered, and can be moved left or right, or up or down.
|
||||
//a trigger is a uni-directional input. it can only be pushed down.
|
||||
//
|
||||
//a stick's default, unpressed state is centered (0), whereas a trigger's default state is fully depressed (+32767.)
|
||||
//but because the default state is not available until the user presses a button on a joypad, it is not possible to
|
||||
//calibrate a joypad on startup. all axes will report a value of 0, even if buttons are depressed.
|
||||
//
|
||||
//thusly, this class is needed. it will spawn a window upon the first attempt to map a joypad axis after startup.
|
||||
//by the point this window appears, an axis must have been moved, so the joypad state is now valid. but since it's
|
||||
//not possible to tell which button was pressed or which axis was moved, it's possible the axis that we're trying to
|
||||
//map was moved. so querying its state now might result in improper mapping. so instead, this window is shown, and
|
||||
//the user is asked not to press any buttons or move any axes. after hitting okay to confirm the joypad is idle,
|
||||
//the joypad can finally be calibrated properly.
|
||||
//
|
||||
//upon assignment, the calibration data is appended to the input assignment value (eg "joypad00.axis00::trigger"),
|
||||
//so that calibration is not necessary every time the emulator is run -- only when modifying input mapping on an axis.
|
||||
|
||||
void InputCalibrationWindow::activate(unsigned joy) {
|
||||
//do not override an already active calibration window
|
||||
if(window->isVisible()) return;
|
||||
|
||||
activeJoypad = joy;
|
||||
info->setText(utf8()
|
||||
<< "Joypad #" << joy << " needs to be calibrated before it can be mapped. "
|
||||
<< "Please ensure that<br>no buttons are pressed, "
|
||||
<< "and all axes are centered before pressing okay."
|
||||
);
|
||||
|
||||
utility.showCentered(window);
|
||||
ok->setFocus();
|
||||
}
|
||||
|
||||
void InputCalibrationWindow::setup() {
|
||||
window = new Window;
|
||||
window->setObjectName("input-calibrate-window");
|
||||
window->setWindowTitle("Joypad Calibration");
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(Style::WindowMargin);
|
||||
layout->setSpacing(0);
|
||||
|
||||
info = new QLabel;
|
||||
layout->addWidget(info);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
ok = new QPushButton("Ok");
|
||||
layout->addWidget(ok);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
window->setLayout(layout);
|
||||
connect(ok, SIGNAL(released()), this, SLOT(dismiss()));
|
||||
}
|
||||
|
||||
void InputCalibrationWindow::dismiss() {
|
||||
window->hide();
|
||||
if(activeJoypad != -1) {
|
||||
inputManager.calibrate(activeJoypad);
|
||||
activeJoypad = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void InputCalibrationWindow::Window::closeEvent(QCloseEvent*) {
|
||||
winInputCalibration->dismiss();
|
||||
}
|
||||
88
bsnes/ui_qt/settings/utility/inputcapture.hpp
Executable file
88
bsnes/ui_qt/settings/utility/inputcapture.hpp
Executable file
@@ -0,0 +1,88 @@
|
||||
class InputCaptureWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Window : public QWidget {
|
||||
void closeEvent(QCloseEvent*);
|
||||
} *window;
|
||||
QVBoxLayout *layout;
|
||||
QHBoxLayout *hlayout;
|
||||
QLabel *title;
|
||||
QPushButton *mouseAxes;
|
||||
QPushButton *mouseButtons;
|
||||
QWidget *imageSpacer;
|
||||
struct ImageWidget : public QWidget {
|
||||
void paintEvent(QPaintEvent*);
|
||||
} *imageWidget;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void activate(InputObject *object);
|
||||
void activate(InputGroup *group);
|
||||
void inputEvent(uint16_t code, bool forceAssign = false);
|
||||
InputCaptureWindow();
|
||||
|
||||
public slots:
|
||||
void assignMouseAxis();
|
||||
void assignMouseButton();
|
||||
|
||||
private:
|
||||
InputObject *activeObject;
|
||||
InputGroup *activeGroup;
|
||||
unsigned groupIndex;
|
||||
bool inputLock;
|
||||
|
||||
friend class InputCaptureWindow::Window;
|
||||
} *winInputCapture;
|
||||
|
||||
class InputMouseCaptureWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Mode { AxisMode, ButtonMode };
|
||||
|
||||
QWidget *window;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *info;
|
||||
QLabel *captureBox;
|
||||
QHBoxLayout *buttonLayout;
|
||||
QPushButton *xAxis;
|
||||
QPushButton *yAxis;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void activate(Mode);
|
||||
void inputEvent(uint16_t code);
|
||||
|
||||
public slots:
|
||||
void assignAxisX();
|
||||
void assignAxisY();
|
||||
|
||||
private:
|
||||
Mode activeMode;
|
||||
signed activeMouse;
|
||||
} *winInputMouseCaptureWindow;
|
||||
|
||||
class InputCalibrationWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Window : public QWidget {
|
||||
void closeEvent(QCloseEvent*);
|
||||
} *window;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *info;
|
||||
QPushButton *ok;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void activate(unsigned joy);
|
||||
|
||||
public slots:
|
||||
void dismiss();
|
||||
|
||||
private:
|
||||
int activeJoypad;
|
||||
|
||||
friend class InputCalibrationWindow::Window;
|
||||
} *winInputCalibration;
|
||||
119
bsnes/ui_qt/settings/video.cpp
Executable file
119
bsnes/ui_qt/settings/video.cpp
Executable file
@@ -0,0 +1,119 @@
|
||||
void VideoSettingsWindow::setup() {
|
||||
panel = new QWidget;
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
title = new QLabel("Video Settings");
|
||||
title->setProperty("class", "title");
|
||||
layout->addWidget(title);
|
||||
|
||||
sliders = new QGridLayout; {
|
||||
lcontrast = new QLabel("Contrast adjust: +100%");
|
||||
sliders->addWidget(lcontrast, 0, 0);
|
||||
|
||||
contrast = new QSlider(Qt::Horizontal);
|
||||
contrast->setMinimum(-95);
|
||||
contrast->setMaximum(+95);
|
||||
sliders->addWidget(contrast, 0, 1);
|
||||
|
||||
lbrightness = new QLabel("Brightness adjust: +100%");
|
||||
sliders->addWidget(lbrightness, 1, 0);
|
||||
|
||||
brightness = new QSlider(Qt::Horizontal);
|
||||
brightness->setMinimum(-95);
|
||||
brightness->setMaximum(+95);
|
||||
sliders->addWidget(brightness, 1, 1);
|
||||
|
||||
lgamma = new QLabel("Gamma adjust: +100%");
|
||||
sliders->addWidget(lgamma, 2, 0);
|
||||
|
||||
gamma = new QSlider(Qt::Horizontal);
|
||||
gamma->setMinimum(-95);
|
||||
gamma->setMaximum(+95);
|
||||
sliders->addWidget(gamma, 2, 1);
|
||||
}
|
||||
sliders->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(sliders);
|
||||
layout->addSpacing(Style::WidgetSpacing);
|
||||
|
||||
options = new QHBoxLayout; {
|
||||
options->setMargin(0);
|
||||
|
||||
enableGammaRamp = new QCheckBox("Simulate NTSC TV gamma ramp");
|
||||
enableGammaRamp->setToolTip("Lower monitor gamma to more accurately match a CRT television");
|
||||
options->addWidget(enableGammaRamp);
|
||||
|
||||
enableNtscMergeFields = new QCheckBox("Merge scan fields for NTSC filter");
|
||||
enableNtscMergeFields->setToolTip(
|
||||
"NTSC filter requires 60hz w/video sync to simulate alternating field effect.\n"
|
||||
"If this is not the case, this option should be enabled to prevent excessive video shimmering."
|
||||
);
|
||||
options->addWidget(enableNtscMergeFields);
|
||||
}
|
||||
options->setSpacing(Style::WidgetSpacing);
|
||||
layout->addLayout(options);
|
||||
|
||||
spacer = new QWidget;
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout->addWidget(spacer);
|
||||
|
||||
panel->setLayout(layout);
|
||||
connect(contrast, SIGNAL(valueChanged(int)), this, SLOT(contrastAdjust(int)));
|
||||
connect(brightness, SIGNAL(valueChanged(int)), this, SLOT(brightnessAdjust(int)));
|
||||
connect(gamma, SIGNAL(valueChanged(int)), this, SLOT(gammaAdjust(int)));
|
||||
connect(enableGammaRamp, SIGNAL(stateChanged(int)), this, SLOT(gammaRampToggle(int)));
|
||||
connect(enableNtscMergeFields, SIGNAL(stateChanged(int)), this, SLOT(ntscFieldsToggle(int)));
|
||||
|
||||
syncUi();
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::syncUi() {
|
||||
int n;
|
||||
|
||||
n = config.video.contrastAdjust;
|
||||
lcontrast->setText(utf8() << "Contrast adjust: " << (n > 0 ? "+" : "") << n << "%");
|
||||
contrast->setSliderPosition(n);
|
||||
|
||||
n = config.video.brightnessAdjust;
|
||||
lbrightness->setText(utf8() << "Brightness adjust: " << (n > 0 ? "+" : "") << n << "%");
|
||||
brightness->setSliderPosition(n);
|
||||
|
||||
n = config.video.gammaAdjust;
|
||||
lgamma->setText(utf8() << "Gamma adjust: " << (n > 0 ? "+" : "") << n << "%");
|
||||
gamma->setSliderPosition(n);
|
||||
|
||||
enableGammaRamp->setChecked(config.video.enableGammaRamp);
|
||||
enableNtscMergeFields->setChecked(config.video.enableNtscMergeFields);
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::gammaRampToggle(int state) {
|
||||
config.video.enableGammaRamp = (state == Qt::Checked);
|
||||
syncUi();
|
||||
utility.updateColorFilter();
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::ntscFieldsToggle(int state) {
|
||||
config.video.enableNtscMergeFields = (state == Qt::Checked);
|
||||
syncUi();
|
||||
utility.updateSoftwareFilter();
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::contrastAdjust(int value) {
|
||||
config.video.contrastAdjust = value;
|
||||
syncUi();
|
||||
utility.updateColorFilter();
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::brightnessAdjust(int value) {
|
||||
config.video.brightnessAdjust = value;
|
||||
syncUi();
|
||||
utility.updateColorFilter();
|
||||
}
|
||||
|
||||
void VideoSettingsWindow::gammaAdjust(int value) {
|
||||
config.video.gammaAdjust = value;
|
||||
syncUi();
|
||||
utility.updateColorFilter();
|
||||
}
|
||||
29
bsnes/ui_qt/settings/video.hpp
Executable file
29
bsnes/ui_qt/settings/video.hpp
Executable file
@@ -0,0 +1,29 @@
|
||||
class VideoSettingsWindow : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QWidget *panel;
|
||||
QVBoxLayout *layout;
|
||||
QLabel *title;
|
||||
QGridLayout *sliders;
|
||||
QLabel *lcontrast;
|
||||
QSlider *contrast;
|
||||
QLabel *lbrightness;
|
||||
QSlider *brightness;
|
||||
QLabel *lgamma;
|
||||
QSlider *gamma;
|
||||
QHBoxLayout *options;
|
||||
QCheckBox *enableGammaRamp;
|
||||
QCheckBox *enableNtscMergeFields;
|
||||
QWidget *spacer;
|
||||
|
||||
void setup();
|
||||
void syncUi();
|
||||
|
||||
public slots:
|
||||
void gammaRampToggle(int);
|
||||
void ntscFieldsToggle(int);
|
||||
void contrastAdjust(int);
|
||||
void brightnessAdjust(int);
|
||||
void gammaAdjust(int);
|
||||
} *winVideoSettings;
|
||||
122
bsnes/ui_qt/ui.cpp
Executable file
122
bsnes/ui_qt/ui.cpp
Executable file
@@ -0,0 +1,122 @@
|
||||
#include "base/main.moc"
|
||||
#include "base/loader.moc"
|
||||
#include "base/htmlviewer.moc"
|
||||
#include "base/about.moc"
|
||||
#include "settings/settings.moc"
|
||||
|
||||
#include "base/main.cpp"
|
||||
#include "base/loader.cpp"
|
||||
#include "base/htmlviewer.cpp"
|
||||
#include "base/about.cpp"
|
||||
#include "settings/settings.cpp"
|
||||
|
||||
void Application::init() {
|
||||
if(config.system.crashedOnLastRun == true) {
|
||||
//emulator crashed on last run, disable all drivers
|
||||
QMessageBox::warning(0, "bsnes Crash Notification", utf8() <<
|
||||
"<p><b>Warning:</b><br>bsnes crashed while attempting to initialize device "
|
||||
"drivers the last time it was run.</p>"
|
||||
"<p>To prevent this from occurring again, all drivers have been disabled. Please "
|
||||
"go to Settings->Configuration->Advanced and choose new driver settings, and then "
|
||||
"restart the emulator for the changes to take effect. <i>Video, audio and input "
|
||||
"will not work until you do this!</i></p>"
|
||||
"<p><b>Settings that caused failure on last run:</b><br>"
|
||||
<< "Video driver: " << config.system.video << "<br>"
|
||||
<< "Audio driver: " << config.system.audio << "<br>"
|
||||
<< "Input driver: " << config.system.input << "<br></p>"
|
||||
);
|
||||
|
||||
config.system.video = "None";
|
||||
config.system.audio = "None";
|
||||
config.system.input = "None";
|
||||
}
|
||||
|
||||
if(config.system.video == "") config.system.video = video.default_driver();
|
||||
if(config.system.audio == "") config.system.audio = audio.default_driver();
|
||||
if(config.system.input == "") config.system.input = input.default_driver();
|
||||
|
||||
winMain = new MainWindow;
|
||||
winMain->setup();
|
||||
|
||||
winLoader = new LoaderWindow;
|
||||
winLoader->setup();
|
||||
|
||||
winHtmlViewer = new HtmlViewerWindow;
|
||||
winHtmlViewer->setup();
|
||||
|
||||
winAbout = new AboutWindow;
|
||||
winAbout->setup();
|
||||
|
||||
//window must be onscreen and visible before initializing video interface
|
||||
utility.updateSystemState();
|
||||
utility.resizeMainWindow();
|
||||
utility.updateFullscreenState();
|
||||
application.processEvents();
|
||||
|
||||
winSettings = new SettingsWindow;
|
||||
winSettings->setup();
|
||||
|
||||
//if emulator crashes while initializing drivers, next run will disable them all.
|
||||
//this will allow user to choose different driver settings.
|
||||
config.system.crashedOnLastRun = true;
|
||||
config.save(configFilename);
|
||||
|
||||
video.driver(config.system.video);
|
||||
video.set(Video::Handle, (uintptr_t)winMain->canvas->winId());
|
||||
if(video.init() == false) {
|
||||
QMessageBox::warning(0, "bsnes", utf8() <<
|
||||
"<p><b>Warning:</b> " << config.system.video << " video driver failed to initialize. "
|
||||
"Video driver has been disabled.</p>"
|
||||
"<p>Please go to Settings->Configuration->Advanced and choose a different driver, and "
|
||||
"then restart the emulator for the changes to take effect.</p>"
|
||||
);
|
||||
video.driver("None");
|
||||
video.init();
|
||||
}
|
||||
|
||||
audio.driver(config.system.audio);
|
||||
audio.set(Audio::Handle, (uintptr_t)winMain->canvas->winId());
|
||||
audio.set(Audio::Frequency, config.audio.outputFrequency);
|
||||
audio.set(Audio::Latency, config.audio.latency);
|
||||
audio.set(Audio::Volume, config.audio.volume);
|
||||
if(audio.init() == false) {
|
||||
QMessageBox::warning(0, "bsnes", utf8() <<
|
||||
"<p><b>Warning:</b> " << config.system.audio << " audio driver failed to initialize. "
|
||||
"Audio driver has been disabled.</p>"
|
||||
"<p>Please go to Settings->Configuration->Advanced and choose a different driver, and "
|
||||
"then restart the emulator for the changes to take effect.</p>"
|
||||
);
|
||||
audio.driver("None");
|
||||
audio.init();
|
||||
}
|
||||
|
||||
input.driver(config.system.input);
|
||||
input.set(Input::Handle, (uintptr_t)winMain->canvas->winId());
|
||||
if(input.init() == false) {
|
||||
QMessageBox::warning(0, "bsnes", utf8() <<
|
||||
"<p><b>Warning:</b> " << config.system.input << " input driver failed to initialize. "
|
||||
"Input driver has been disabled.</p>"
|
||||
"<p>Please go to Settings->Configuration->Advanced and choose a different driver, and "
|
||||
"then restart the emulator for the changes to take effect.</p>"
|
||||
);
|
||||
input.driver("None");
|
||||
input.init();
|
||||
}
|
||||
|
||||
//didn't crash, note this in the config file now in case a different kind of crash occurs later
|
||||
config.system.crashedOnLastRun = false;
|
||||
config.save(configFilename);
|
||||
|
||||
inputManager.bind();
|
||||
inputManager.refresh();
|
||||
inputManager.refresh();
|
||||
inputManager.onInput = bind(&Utility::inputEvent, &utility);
|
||||
|
||||
utility.updateAvSync();
|
||||
utility.updateVideoMode();
|
||||
utility.updateColorFilter();
|
||||
utility.updateHardwareFilter();
|
||||
utility.updateSoftwareFilter();
|
||||
utility.updateEmulationSpeed();
|
||||
utility.updateControllers();
|
||||
}
|
||||
163
bsnes/ui_qt/utility/cartridge.cpp
Executable file
163
bsnes/ui_qt/utility/cartridge.cpp
Executable file
@@ -0,0 +1,163 @@
|
||||
string Utility::selectCartridge() {
|
||||
audio.clear();
|
||||
QString filename = QFileDialog::getOpenFileName(0,
|
||||
"Load Cartridge",
|
||||
utf8() << (snes.config.path.rom != "" ? snes.config.path.rom : snes.config.path.current),
|
||||
"SNES images (*.smc *.sfc *.swc *.fig *.bs *.st"
|
||||
#if defined(GZIP_SUPPORT)
|
||||
" *.zip *.gz"
|
||||
#endif
|
||||
#if defined(JMA_SUPPORT)
|
||||
" *.jma"
|
||||
#endif
|
||||
");;"
|
||||
"All files (*)"
|
||||
);
|
||||
return string() << filename.toUtf8().constData();
|
||||
}
|
||||
|
||||
string Utility::selectFolder(const char *title) {
|
||||
audio.clear();
|
||||
QString pathname = QFileDialog::getExistingDirectory(0,
|
||||
title, utf8() << snes.config.path.current,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
return string() << pathname.toUtf8().constData();
|
||||
}
|
||||
|
||||
void Utility::loadCartridge(const char *filename) {
|
||||
switch(cartridge.detect_image_type(filename)) {
|
||||
case Cartridge::TypeNormal: loadCartridgeNormal(filename); break;
|
||||
case Cartridge::TypeBsxSlotted: winLoader->loadBsxSlottedCartridge(filename, ""); break;
|
||||
case Cartridge::TypeBsxBios: winLoader->loadBsxCartridge(filename, ""); break;
|
||||
case Cartridge::TypeBsx: winLoader->loadBsxCartridge(snes.config.path.bsx, filename); break;
|
||||
case Cartridge::TypeSufamiTurboBios: winLoader->loadSufamiTurboCartridge(filename, "", ""); break;
|
||||
case Cartridge::TypeSufamiTurbo: winLoader->loadSufamiTurboCartridge(snes.config.path.st, filename, ""); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeNormal(const char *base) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_normal(base);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeBsxSlotted(const char *base, const char *slot) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_bsx_slotted(base, slot);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeBsx(const char *base, const char *slot) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_bsx(base, slot);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeSufamiTurbo(const char *base, const char *slotA, const char *slotB) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_sufami_turbo(base, slotA, slotB);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Utility::unloadCartridge() {
|
||||
if(cartridge.loaded()) {
|
||||
cartridge.unload();
|
||||
modifySystemState(UnloadCartridge);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::modifySystemState(system_state_t state) {
|
||||
video.clear();
|
||||
audio.clear();
|
||||
|
||||
switch(state) {
|
||||
case LoadCartridge: {
|
||||
//must call cartridge.load_cart_...() before calling modifySystemState(LoadCartridge)
|
||||
if(cartridge.loaded() == false) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
//warn if unsupported hardware detected
|
||||
string chip;
|
||||
if(cartridge.has_superfx()) chip = "SuperFX";
|
||||
else if(cartridge.has_sa1()) chip = "SA-1";
|
||||
else if(cartridge.has_st011()) chip = "ST011";
|
||||
else if(cartridge.has_st018()) chip = "ST018";
|
||||
else if(cartridge.has_dsp3()) chip = "DSP-3";
|
||||
if(chip != "") {
|
||||
QMessageBox::warning(winMain->window, "Warning", utf8()
|
||||
<< "<p><b>Warning:</b><br>Unsupported " << chip << " chip detected. "
|
||||
<< "It is unlikely that this title will work properly.</p>");
|
||||
}
|
||||
|
||||
showMessage(utf8()
|
||||
<< "Loaded " << cartridge.name()
|
||||
<< (cartridge.patched() ? ", and applied UPS patch." : "."));
|
||||
winMain->window->setWindowTitle(utf8() << BSNES_TITLE << " - " << cartridge.name());
|
||||
} break;
|
||||
|
||||
case UnloadCartridge: {
|
||||
if(cartridge.loaded() == false) break; //no cart to unload?
|
||||
cartridge.unload();
|
||||
|
||||
application.power = false;
|
||||
application.pause = true;
|
||||
|
||||
showMessage(utf8() << "Unloaded " << cartridge.name() << ".");
|
||||
winMain->window->setWindowTitle(utf8() << BSNES_TITLE);
|
||||
} break;
|
||||
|
||||
case PowerOn: {
|
||||
if(cartridge.loaded() == false || application.power == true) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
showMessage("Power on.");
|
||||
} break;
|
||||
|
||||
case PowerOff: {
|
||||
if(cartridge.loaded() == false || application.power == false) break;
|
||||
|
||||
application.power = false;
|
||||
application.pause = true;
|
||||
|
||||
showMessage("Power off.");
|
||||
} break;
|
||||
|
||||
case PowerCycle: {
|
||||
if(cartridge.loaded() == false) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
showMessage("System power was cycled.");
|
||||
} break;
|
||||
|
||||
case Reset: {
|
||||
if(cartridge.loaded() == false || application.power == false) break;
|
||||
|
||||
application.pause = false;
|
||||
snes.reset();
|
||||
|
||||
showMessage("System was reset.");
|
||||
} break;
|
||||
}
|
||||
|
||||
winMain->syncUi();
|
||||
winCodeEditor->dismiss();
|
||||
winCheatEditor->reloadList();
|
||||
winCheatEditor->syncUi();
|
||||
}
|
||||
218
bsnes/ui_qt/utility/utility.cpp
Executable file
218
bsnes/ui_qt/utility/utility.cpp
Executable file
@@ -0,0 +1,218 @@
|
||||
#include "cartridge.cpp"
|
||||
#include "window.cpp"
|
||||
|
||||
//returns true if requested code is a button, and it has just been pressed down
|
||||
bool Utility::isButtonDown(uint16_t inputCode, InputObject &object) {
|
||||
if(inputCode != object.code) return false;
|
||||
|
||||
if(object.codetype != InputCode::KeyboardButton
|
||||
&& object.codetype != InputCode::MouseButton
|
||||
&& object.codetype != InputCode::JoypadHat
|
||||
&& object.codetype != InputCode::JoypadAxis
|
||||
&& object.codetype != InputCode::JoypadButton) return false;
|
||||
|
||||
int16_t state = inputManager.state(object.code);
|
||||
int16_t lastState = inputManager.lastState(object.code);
|
||||
|
||||
if(object.codetype == InputCode::JoypadHat) {
|
||||
switch(object.modifier) {
|
||||
case InputObject::Up: return (state & joypad<>::hat_up ) && !(lastState & joypad<>::hat_up );
|
||||
case InputObject::Down: return (state & joypad<>::hat_down ) && !(lastState & joypad<>::hat_down );
|
||||
case InputObject::Left: return (state & joypad<>::hat_left ) && !(lastState & joypad<>::hat_left );
|
||||
case InputObject::Right: return (state & joypad<>::hat_right) && !(lastState & joypad<>::hat_right);
|
||||
}
|
||||
} else if(object.codetype == InputCode::JoypadAxis) {
|
||||
switch(object.modifier) {
|
||||
case InputObject::Lo: return (state < -16384) && !(lastState < -16384);
|
||||
case InputObject::Hi: return (state > +16384) && !(lastState > +16384);
|
||||
case InputObject::Trigger: return (state < 0) && !(lastState < 0);
|
||||
}
|
||||
} else {
|
||||
return (state == 1) && !(lastState == 1);
|
||||
}
|
||||
|
||||
return false; //fall-through for modifier-less hats / axes
|
||||
}
|
||||
|
||||
void Utility::inputEvent(uint16_t code) {
|
||||
//forward key-press event
|
||||
//(in case window is currently active and capturing a new button / axis assignment)
|
||||
winInputCapture->inputEvent(code);
|
||||
|
||||
//if escape key is pressed on *any* keyboard; release the mouse if it is acquired
|
||||
for(unsigned i = 0; i < keyboard<>::count; i++) {
|
||||
if(code == keyboard<>::index(i, keyboard<>::escape) && inputManager.state(code) && input.acquired()) {
|
||||
input.unacquire();
|
||||
return; //do not trigger other UI actions that may be bound to escape key
|
||||
}
|
||||
}
|
||||
|
||||
if(winMain->window->isActiveWindow()) {
|
||||
bool resizeWindow = false;
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.loadCartridge)) {
|
||||
string filename = selectCartridge();
|
||||
if(filename.length() > 0) loadCartridge(filename);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.pauseEmulation)) {
|
||||
application.pause = !application.pause;
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.resetSystem)) {
|
||||
modifySystemState(Reset);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.powerCycleSystem)) {
|
||||
modifySystemState(PowerCycle);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.lowerSpeed)) {
|
||||
config.system.speed--;
|
||||
updateEmulationSpeed();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.raiseSpeed)) {
|
||||
config.system.speed++;
|
||||
updateEmulationSpeed();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleCheatSystem)) {
|
||||
if(cheat.enabled() == false) {
|
||||
cheat.enable();
|
||||
showMessage("Cheat system enabled.");
|
||||
} else {
|
||||
cheat.disable();
|
||||
showMessage("Cheat system disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleFullscreen)) {
|
||||
config.video.isFullscreen = !config.video.isFullscreen;
|
||||
updateFullscreenState();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleMenu)) {
|
||||
winMain->window->menuBar()->setVisible(!winMain->window->menuBar()->isVisibleTo(winMain->window));
|
||||
resizeWindow = true;
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleStatus)) {
|
||||
winMain->window->statusBar()->setVisible(!winMain->window->statusBar()->isVisibleTo(winMain->window));
|
||||
resizeWindow = true;
|
||||
}
|
||||
|
||||
//prevent calling twice when toggleMenu == toggleStatus
|
||||
if(resizeWindow == true) {
|
||||
resizeMainWindow();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.exitEmulator)) {
|
||||
application.terminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//display message in main window statusbar area for three seconds
|
||||
void Utility::showMessage(const char *message) {
|
||||
winMain->window->statusBar()->showMessage(utf8() << message, 3000);
|
||||
}
|
||||
|
||||
//updates system state text at bottom-right of main window statusbar
|
||||
void Utility::updateSystemState() {
|
||||
string text;
|
||||
|
||||
if(cartridge.loaded() == false) {
|
||||
text = "No cartridge loaded";
|
||||
} else if(application.power == false) {
|
||||
text = "Power off";
|
||||
} else if(application.pause == true || application.autopause == true) {
|
||||
text = "Paused";
|
||||
} else if(ppu.status.frames_updated == true) {
|
||||
ppu.status.frames_updated = false;
|
||||
text << (int)ppu.status.frames_executed;
|
||||
text << " fps";
|
||||
} else {
|
||||
//nothing to update
|
||||
return;
|
||||
}
|
||||
|
||||
winMain->systemState->setText(utf8() << text);
|
||||
}
|
||||
|
||||
void Utility::acquireMouse() {
|
||||
if(cartridge.loaded()) {
|
||||
if(snes.config.controller_port1 == SNES::Input::DeviceMouse
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceMouse
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceSuperScope
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceJustifier
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceJustifiers
|
||||
) input.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::unacquireMouse() {
|
||||
input.unacquire();
|
||||
}
|
||||
|
||||
void Utility::updateAvSync() {
|
||||
video.set(Video::Synchronize, config.video.synchronize);
|
||||
audio.set(Audio::Synchronize, config.audio.synchronize);
|
||||
}
|
||||
|
||||
void Utility::updateVideoMode() {
|
||||
if(config.video.context->region == 0) {
|
||||
snes.video.set_mode(SNES::Video::ModeNTSC);
|
||||
} else {
|
||||
snes.video.set_mode(SNES::Video::ModePAL);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateColorFilter() {
|
||||
libfilter::colortable.set_format(libfilter::Colortable::RGB888);
|
||||
libfilter::colortable.set_contrast(config.video.contrastAdjust);
|
||||
libfilter::colortable.set_brightness(config.video.brightnessAdjust);
|
||||
libfilter::colortable.set_gamma(100 + config.video.gammaAdjust);
|
||||
libfilter::colortable.enable_gamma_ramp(config.video.enableGammaRamp);
|
||||
libfilter::colortable.update();
|
||||
}
|
||||
|
||||
void Utility::updateHardwareFilter() {
|
||||
video.set(Video::Filter, config.video.context->hwFilter);
|
||||
}
|
||||
|
||||
void Utility::updateSoftwareFilter() {
|
||||
libfilter::FilterInterface::FilterType type;
|
||||
switch(config.video.context->swFilter) { default:
|
||||
case 0: type = libfilter::FilterInterface::Direct; break;
|
||||
case 1: type = libfilter::FilterInterface::Scanline; break;
|
||||
case 2: type = libfilter::FilterInterface::Scale2x; break;
|
||||
case 3: type = libfilter::FilterInterface::HQ2x; break;
|
||||
case 4: type = libfilter::FilterInterface::NTSC; break;
|
||||
}
|
||||
libfilter::filter.set(type);
|
||||
|
||||
if(type == libfilter::FilterInterface::NTSC) {
|
||||
libfilter::filter_ntsc.adjust(0, 0, 0, 0, 0, config.video.enableNtscMergeFields);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateEmulationSpeed() {
|
||||
config.system.speed = max(0, min(4, (signed)config.system.speed));
|
||||
|
||||
double scale[] = { 0.50, 0.75, 1.00, 1.50, 2.00 };
|
||||
unsigned outfreq = config.audio.outputFrequency;
|
||||
unsigned infreq = config.audio.inputFrequency * scale[config.system.speed] + 0.5;
|
||||
|
||||
audio.set(Audio::Resample, outfreq != infreq); //only resample when necessary
|
||||
audio.set(Audio::ResampleOutputFrequency, outfreq);
|
||||
audio.set(Audio::ResampleInputFrequency, infreq);
|
||||
}
|
||||
|
||||
void Utility::updateControllers() {
|
||||
snes.input.port_set_device(0, snes.config.controller_port1);
|
||||
snes.input.port_set_device(1, snes.config.controller_port2);
|
||||
}
|
||||
37
bsnes/ui_qt/utility/utility.hpp
Executable file
37
bsnes/ui_qt/utility/utility.hpp
Executable file
@@ -0,0 +1,37 @@
|
||||
class Utility {
|
||||
public:
|
||||
//utility.cpp
|
||||
bool isButtonDown(uint16_t inputCode, InputObject &object);
|
||||
void inputEvent(uint16_t code);
|
||||
void showMessage(const char *message);
|
||||
void updateSystemState();
|
||||
void acquireMouse();
|
||||
void unacquireMouse();
|
||||
|
||||
void updateAvSync();
|
||||
void updateVideoMode();
|
||||
void updateColorFilter();
|
||||
void updateHardwareFilter();
|
||||
void updateSoftwareFilter();
|
||||
void updateEmulationSpeed();
|
||||
void updateControllers();
|
||||
|
||||
//cartridge.cpp
|
||||
string selectCartridge();
|
||||
string selectFolder(const char *title);
|
||||
void loadCartridge(const char*);
|
||||
bool loadCartridgeNormal(const char*);
|
||||
bool loadCartridgeBsxSlotted(const char*, const char*);
|
||||
bool loadCartridgeBsx(const char*, const char*);
|
||||
bool loadCartridgeSufamiTurbo(const char*, const char *, const char*);
|
||||
void unloadCartridge();
|
||||
|
||||
enum system_state_t { LoadCartridge, UnloadCartridge, PowerOn, PowerOff, PowerCycle, Reset };
|
||||
void modifySystemState(system_state_t state);
|
||||
|
||||
//window.cpp
|
||||
void showCentered(QWidget *window);
|
||||
void updateFullscreenState();
|
||||
void constrainSize(unsigned &x, unsigned &y, unsigned max);
|
||||
void resizeMainWindow();
|
||||
} utility;
|
||||
123
bsnes/ui_qt/utility/window.cpp
Executable file
123
bsnes/ui_qt/utility/window.cpp
Executable file
@@ -0,0 +1,123 @@
|
||||
//show specified window in the center of the screen.
|
||||
void Utility::showCentered(QWidget *window) {
|
||||
QRect deskRect = QApplication::desktop()->availableGeometry(window);
|
||||
|
||||
//place window offscreen, so that it can be shown to get proper frameSize()
|
||||
window->move(std::numeric_limits<signed>::max(), std::numeric_limits<signed>::max());
|
||||
window->showNormal();
|
||||
|
||||
//force-resize window to be as small as minimumSize() will allow, and then center it
|
||||
window->resize(0, 0);
|
||||
window->move(
|
||||
deskRect.center().x() - (window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (window->frameSize().height() / 2)
|
||||
);
|
||||
|
||||
#ifndef _WIN32
|
||||
//Xlib frame size (excluding inside) is QSize(0, 0) at first, wait for it to be valid
|
||||
for(unsigned counter = 0; counter < 4096; counter++) {
|
||||
if(window->frameSize() != window->size()) break;
|
||||
application.processEvents();
|
||||
}
|
||||
#endif
|
||||
|
||||
//in case frame size changed, recenter window
|
||||
window->move(
|
||||
deskRect.center().x() - (window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (window->frameSize().height() / 2)
|
||||
);
|
||||
|
||||
//ensure window has focus
|
||||
application.processEvents();
|
||||
window->activateWindow();
|
||||
window->raise();
|
||||
}
|
||||
|
||||
void Utility::updateFullscreenState() {
|
||||
video.clear();
|
||||
|
||||
if(config.video.isFullscreen == false) {
|
||||
config.video.context = &config.video.windowed;
|
||||
winMain->window->showNormal();
|
||||
} else {
|
||||
config.video.context = &config.video.fullscreen;
|
||||
winMain->window->showFullScreen();
|
||||
}
|
||||
|
||||
//Xlib requires time to propogate fullscreen state change message to window manager;
|
||||
//if window is resized before this occurs, canvas may not resize correctly
|
||||
application.processEvents();
|
||||
usleep(50000);
|
||||
|
||||
//refresh options that are unique to each video context
|
||||
resizeMainWindow();
|
||||
updateVideoMode();
|
||||
updateHardwareFilter();
|
||||
updateSoftwareFilter();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
//if max exceeds x: x is set to max, and y is scaled down to keep proportion to x
|
||||
void Utility::constrainSize(unsigned &x, unsigned &y, unsigned max) {
|
||||
if(x > max) {
|
||||
double scalar = (double)max / (double)x;
|
||||
y = (unsigned)((double)y * (double)scalar);
|
||||
x = max;
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::resizeMainWindow() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
unsigned multiplier = config.video.context->multiplier;
|
||||
unsigned width = 256 * config.video.context->multiplier;
|
||||
unsigned height = (config.video.context->region == 0 ? 224 : 239) * config.video.context->multiplier;
|
||||
|
||||
if(config.video.context->correctAspectRatio) {
|
||||
if(config.video.context->region == 0) {
|
||||
width = (double)width * config.video.ntscAspectRatio + 0.5; //NTSC adjust
|
||||
} else {
|
||||
width = (double)width * config.video.palAspectRatio + 0.5; //PAL adjust
|
||||
}
|
||||
}
|
||||
|
||||
if(config.video.isFullscreen == false) {
|
||||
//get effective desktop work area region (ignore Windows taskbar, OS X doc, etc.)
|
||||
QRect deskRect = QApplication::desktop()->availableGeometry(winMain->window);
|
||||
|
||||
//calculate frame geometry (window border + menubar + statusbar)
|
||||
unsigned frameWidth = winMain->window->frameSize().width() - winMain->canvasContainer->size().width();
|
||||
unsigned frameHeight = winMain->window->frameSize().height() - winMain->canvasContainer->size().height();
|
||||
|
||||
//ensure window size will not be larger than viewable desktop area
|
||||
constrainSize(height, width, deskRect.height() - frameHeight);
|
||||
constrainSize(width, height, deskRect.width() - frameWidth );
|
||||
|
||||
//resize window such that it is as small as possible to hold canvas of size (width, height)
|
||||
winMain->canvas->setFixedSize(width, height);
|
||||
winMain->window->resize(width, height);
|
||||
|
||||
//center window onscreen
|
||||
winMain->window->move(
|
||||
deskRect.center().x() - (winMain->window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (winMain->window->frameSize().height() / 2)
|
||||
);
|
||||
} else {
|
||||
constrainSize(height, width, winMain->canvasContainer->size().height());
|
||||
constrainSize(width, height, winMain->canvasContainer->size().width());
|
||||
|
||||
//center canvas onscreen; ensure it is not larger than viewable area
|
||||
winMain->canvas->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
winMain->canvas->setMaximumSize(width, height);
|
||||
}
|
||||
|
||||
application.processEvents();
|
||||
usleep(2000);
|
||||
}
|
||||
|
||||
//work around for Qt/Xlib bug:
|
||||
//if window resize occurs with cursor over it, Qt shows Qt::Size*DiagCursor;
|
||||
//so force it to show Qt::ArrowCursor, as expected
|
||||
winMain->window->setCursor(Qt::ArrowCursor);
|
||||
winMain->canvasContainer->setCursor(Qt::ArrowCursor);
|
||||
winMain->canvas->setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
Reference in New Issue
Block a user