o add bsnes

This commit is contained in:
optixx
2009-04-08 21:29:36 +02:00
parent bcb4a055e9
commit 27b58a09f2
413 changed files with 71887 additions and 0 deletions

183
bsnes/ui_qt/settings/advanced.cpp Executable file
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;

129
bsnes/ui_qt/settings/audio.cpp Executable file
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();
}

28
bsnes/ui_qt/settings/audio.hpp Executable file
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;

174
bsnes/ui_qt/settings/input.cpp Executable file
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);
}
}
}
}

32
bsnes/ui_qt/settings/input.hpp Executable file
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;

200
bsnes/ui_qt/settings/paths.cpp Executable file
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();
}

49
bsnes/ui_qt/settings/paths.hpp Executable file
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;

119
bsnes/ui_qt/settings/video.cpp Executable file
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();
}

29
bsnes/ui_qt/settings/video.hpp Executable file
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;