o cleanup

This commit is contained in:
optixx
2009-04-22 20:04:28 +02:00
parent 55e3468f74
commit 0c378a9f7c
1078 changed files with 0 additions and 0 deletions

85
tools/bsnes/ui_qt/Makefile Executable file
View 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)

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

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

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

View 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
tools/bsnes/ui_qt/base/loader.cpp Executable file
View 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();
}

View 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
tools/bsnes/ui_qt/base/main.cpp Executable file
View 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
tools/bsnes/ui_qt/base/main.hpp Executable file
View 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
tools/bsnes/ui_qt/config.cpp Executable file
View 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;

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

View 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
tools/bsnes/ui_qt/input/input.cpp Executable file
View 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) {
}

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

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

View 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
tools/bsnes/ui_qt/interface.cpp Executable file
View 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
tools/bsnes/ui_qt/main.cpp Executable file
View 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
tools/bsnes/ui_qt/main.hpp Executable file
View 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
tools/bsnes/ui_qt/platform.cpp Executable file
View 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

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

View File

@@ -0,0 +1,2 @@
1 24 "data/bsnes.Manifest"
IDI_ICON1 ICON DISCARDABLE "data/bsnes.ico"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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
tools/bsnes/ui_qt/ui.cpp Executable file
View 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();
}

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

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

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

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