mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-11-30 04:20:32 +00:00
Update to v106r37 release.
byuu says: Changelog: - bsnes: cheat code “enabled” option changed to “enable” - bsnes: connected “Cancel” action on add/edit cheat code window - hiro: improved BrowserDialog::selectFolder() behavior - can choose “Select” inside of a target folder when no items are selected - bsnes: implemented state manager - bsnes: save a recovery state before loading a state, quitting, or changing drivers - bsnes: input settings, hotkey settings, cheat editor, state manager entries are now batchable - this allows bulk clearing/deleting of entries - bsnes: cheat code list now auto-sorts alphabetically instead of using up/down move arrows I know most people will probably prefer to order cheat codes the way they want, but the issue is that the state manager can't really work this way. Each state is a file on disk. So yes, we could store a states-manifest.bml to track the order of the states, or try to insert numbers into the filenames and do bulk filesystem rename operations on sorting, but then we would run into oddities when users delete state files manually. And really, manual sorting is just clumsy. If you really want a specific ordering, you can prefix cheats/states with numeric indices instead.
This commit is contained in:
parent
ec9729a9e1
commit
173a5d67bc
@ -13,7 +13,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "106.36";
|
||||
static const string Version = "106.37";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
@ -10,20 +10,20 @@ auto InputManager::bindHotkeys() -> void {
|
||||
}));
|
||||
|
||||
hotkeys.append(InputHotkey("Save State").onPress([&] {
|
||||
program->saveState(stateSlot);
|
||||
program->saveState({"Quick/Slot ", stateSlot});
|
||||
}));
|
||||
|
||||
hotkeys.append(InputHotkey("Load State").onPress([&] {
|
||||
program->loadState(stateSlot);
|
||||
program->loadState({"Quick/Slot ", stateSlot});
|
||||
}));
|
||||
|
||||
hotkeys.append(InputHotkey("Increment State Slot").onPress([&] {
|
||||
if(--stateSlot < 1) stateSlot = 5;
|
||||
if(--stateSlot < 1) stateSlot = 9;
|
||||
program->showMessage({"Selected state slot ", stateSlot});
|
||||
}));
|
||||
|
||||
hotkeys.append(InputHotkey("Decrement State Slot").onPress([&] {
|
||||
if(++stateSlot > 5) stateSlot = 1;
|
||||
if(++stateSlot > 9) stateSlot = 1;
|
||||
program->showMessage({"Selected state slot ", stateSlot});
|
||||
}));
|
||||
|
||||
|
@ -106,21 +106,31 @@ Presentation::Presentation() {
|
||||
|
||||
toolsMenu.setText("Tools").setVisible(false);
|
||||
saveState.setText("Save State");
|
||||
saveState1.setText("Slot 1").onActivate([&] { program->saveState(1); });
|
||||
saveState2.setText("Slot 2").onActivate([&] { program->saveState(2); });
|
||||
saveState3.setText("Slot 3").onActivate([&] { program->saveState(3); });
|
||||
saveState4.setText("Slot 4").onActivate([&] { program->saveState(4); });
|
||||
saveState5.setText("Slot 5").onActivate([&] { program->saveState(5); });
|
||||
saveState1.setText("Slot 1").onActivate([&] { program->saveState("Quick/Slot 1"); });
|
||||
saveState2.setText("Slot 2").onActivate([&] { program->saveState("Quick/Slot 2"); });
|
||||
saveState3.setText("Slot 3").onActivate([&] { program->saveState("Quick/Slot 3"); });
|
||||
saveState4.setText("Slot 4").onActivate([&] { program->saveState("Quick/Slot 4"); });
|
||||
saveState5.setText("Slot 5").onActivate([&] { program->saveState("Quick/Slot 5"); });
|
||||
saveState6.setText("Slot 6").onActivate([&] { program->saveState("Quick/Slot 6"); });
|
||||
saveState7.setText("Slot 7").onActivate([&] { program->saveState("Quick/Slot 7"); });
|
||||
saveState8.setText("Slot 8").onActivate([&] { program->saveState("Quick/Slot 8"); });
|
||||
saveState9.setText("Slot 9").onActivate([&] { program->saveState("Quick/Slot 9"); });
|
||||
loadState.setText("Load State");
|
||||
loadState1.setText("Slot 1").onActivate([&] { program->loadState(1); });
|
||||
loadState2.setText("Slot 2").onActivate([&] { program->loadState(2); });
|
||||
loadState3.setText("Slot 3").onActivate([&] { program->loadState(3); });
|
||||
loadState4.setText("Slot 4").onActivate([&] { program->loadState(4); });
|
||||
loadState5.setText("Slot 5").onActivate([&] { program->loadState(5); });
|
||||
loadState1.setText("Slot 1").onActivate([&] { program->loadState("Quick/Slot 1"); });
|
||||
loadState2.setText("Slot 2").onActivate([&] { program->loadState("Quick/Slot 2"); });
|
||||
loadState3.setText("Slot 3").onActivate([&] { program->loadState("Quick/Slot 3"); });
|
||||
loadState4.setText("Slot 4").onActivate([&] { program->loadState("Quick/Slot 4"); });
|
||||
loadState5.setText("Slot 5").onActivate([&] { program->loadState("Quick/Slot 5"); });
|
||||
loadState6.setText("Slot 6").onActivate([&] { program->loadState("Quick/Slot 6"); });
|
||||
loadState7.setText("Slot 7").onActivate([&] { program->loadState("Quick/Slot 7"); });
|
||||
loadState8.setText("Slot 8").onActivate([&] { program->loadState("Quick/Slot 8"); });
|
||||
loadState9.setText("Slot 9").onActivate([&] { program->loadState("Quick/Slot 9"); });
|
||||
loadState0.setText("Recovery Slot").onActivate([&] { program->loadState("Quick/Recovery Slot"); });
|
||||
pauseEmulation.setText("Pause Emulation").onToggle([&] {
|
||||
if(pauseEmulation.checked()) audio->clear();
|
||||
});
|
||||
cheatEditor.setText("Cheat Editor ...").onActivate([&] { toolsWindow->show(0); });
|
||||
stateManager.setText("State Manager ...").onActivate([&] { toolsWindow->show(1); });
|
||||
|
||||
helpMenu.setText("Help");
|
||||
about.setText("About ...").onActivate([&] {
|
||||
|
@ -58,15 +58,26 @@ struct Presentation : Window {
|
||||
MenuItem saveState3{&saveState};
|
||||
MenuItem saveState4{&saveState};
|
||||
MenuItem saveState5{&saveState};
|
||||
MenuItem saveState6{&saveState};
|
||||
MenuItem saveState7{&saveState};
|
||||
MenuItem saveState8{&saveState};
|
||||
MenuItem saveState9{&saveState};
|
||||
Menu loadState{&toolsMenu};
|
||||
MenuItem loadState1{&loadState};
|
||||
MenuItem loadState2{&loadState};
|
||||
MenuItem loadState3{&loadState};
|
||||
MenuItem loadState4{&loadState};
|
||||
MenuItem loadState5{&loadState};
|
||||
MenuItem loadState6{&loadState};
|
||||
MenuItem loadState7{&loadState};
|
||||
MenuItem loadState8{&loadState};
|
||||
MenuItem loadState9{&loadState};
|
||||
MenuSeparator loadStateSeparator{&loadState};
|
||||
MenuItem loadState0{&loadState};
|
||||
MenuCheckItem pauseEmulation{&toolsMenu};
|
||||
MenuSeparator toolsSeparator{&toolsMenu};
|
||||
MenuItem cheatEditor{&toolsMenu};
|
||||
MenuItem stateManager{&toolsMenu};
|
||||
Menu helpMenu{&menuBar};
|
||||
MenuItem about{&helpMenu};
|
||||
|
||||
|
@ -16,6 +16,7 @@ auto Program::load() -> void {
|
||||
presentation->pauseEmulation.setChecked(false);
|
||||
presentation->resizeViewport();
|
||||
toolsWindow->cheatEditor.loadCheats();
|
||||
toolsWindow->stateManager.loadStates();
|
||||
|
||||
string locations = superNintendo.location;
|
||||
if(auto location = gameBoy.location) locations.append("|", location);
|
||||
@ -96,6 +97,7 @@ auto Program::unload() -> void {
|
||||
if(!emulator->loaded()) return;
|
||||
toolsWindow->cheatEditor.saveCheats();
|
||||
toolsWindow->setVisible(false);
|
||||
saveRecoveryState();
|
||||
emulator->unload();
|
||||
superNintendo = {};
|
||||
gameBoy = {};
|
||||
|
@ -2,7 +2,7 @@ auto Program::path(string type, string location, string extension) -> string {
|
||||
auto pathname = Location::path(location);
|
||||
auto filename = Location::file(location);
|
||||
auto prefix = Location::prefix(filename);
|
||||
auto suffix = Location::suffix(filename);
|
||||
auto suffix = extension;
|
||||
|
||||
if(type == "Games") {
|
||||
if(auto path = settings["Path/Games"].text()) {
|
||||
@ -34,9 +34,5 @@ auto Program::path(string type, string location, string extension) -> string {
|
||||
}
|
||||
}
|
||||
|
||||
if(extension) {
|
||||
suffix = extension;
|
||||
}
|
||||
|
||||
return {pathname, prefix, suffix};
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ Program::Program(string_vector arguments) {
|
||||
new SettingsWindow;
|
||||
new CheatDatabase;
|
||||
new CheatWindow;
|
||||
new StateWindow;
|
||||
new ToolsWindow;
|
||||
new AboutWindow;
|
||||
|
||||
|
@ -24,8 +24,10 @@ struct Program : Emulator::Platform {
|
||||
auto path(string type, string location, string extension = "") -> string;
|
||||
|
||||
//state.cpp
|
||||
auto loadState(uint slot) -> bool;
|
||||
auto saveState(uint slot) -> bool;
|
||||
auto statePath() -> string;
|
||||
auto loadState(string filename) -> bool;
|
||||
auto saveState(string filename) -> bool;
|
||||
auto saveRecoveryState() -> bool;
|
||||
|
||||
//utility.cpp
|
||||
auto initializeVideoDriver() -> void;
|
||||
|
@ -1,18 +1,36 @@
|
||||
auto Program::loadState(uint slot) -> bool {
|
||||
if(!emulator->loaded()) return false;
|
||||
auto location = path("States", superNintendo.location, {".bs", slot});
|
||||
if(!file::exists(location)) return showMessage({"Slot ", slot, " state does not exist"}), false;
|
||||
auto memory = file::read(location);
|
||||
serializer s{memory.data(), memory.size()};
|
||||
if(!emulator->unserialize(s)) return showMessage({"Slot ", slot, " state is incompatible"}), false;
|
||||
return showMessage({"Loaded state from slot ", slot}), true;
|
||||
auto Program::statePath() -> string {
|
||||
if(!emulator->loaded()) return "";
|
||||
return path("States", superNintendo.location, ".bst/");
|
||||
}
|
||||
|
||||
auto Program::saveState(uint slot) -> bool {
|
||||
auto Program::loadState(string filename) -> bool {
|
||||
if(!emulator->loaded()) return false;
|
||||
auto location = path("States", superNintendo.location, {".bs", slot});
|
||||
serializer s = emulator->serialize();
|
||||
if(!s.size()) return showMessage({"Failed to save state to slot ", slot}), false;
|
||||
if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write state to slot ", slot}), false;
|
||||
return showMessage({"Saved state to slot ", slot}), true;
|
||||
string location = {statePath(), filename, ".bst"};
|
||||
string prefix = Location::prefix(location);
|
||||
if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false;
|
||||
if(filename != "Quick/Recovery Slot") saveRecoveryState();
|
||||
auto memory = file::read(location);
|
||||
serializer s{memory.data(), memory.size()};
|
||||
if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false;
|
||||
return showMessage({"Loaded [", prefix, "]"}), true;
|
||||
}
|
||||
|
||||
auto Program::saveState(string filename) -> bool {
|
||||
if(!emulator->loaded()) return false;
|
||||
directory::create({statePath(), "Quick/"});
|
||||
string location = {statePath(), filename, ".bst"};
|
||||
string prefix = Location::prefix(location);
|
||||
serializer s = emulator->serialize();
|
||||
if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false;
|
||||
if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false;
|
||||
return showMessage({"Saved [", prefix, "]"}), true;
|
||||
}
|
||||
|
||||
auto Program::saveRecoveryState() -> bool {
|
||||
if(!emulator->loaded()) return false;
|
||||
directory::create({statePath(), "Quick/"});
|
||||
serializer s = emulator->serialize();
|
||||
if(!s.size()) return false;
|
||||
if(!file::write({statePath(), "Quick/Recovery Slot.bst"}, s.data(), s.size())) return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||
"It is highly recommended you unload your game first to avoid data loss.\n"
|
||||
"Do you wish to proceed with the video driver change now anyway?"
|
||||
).setParent(*settingsWindow).question() == "Yes") {
|
||||
program->saveRecoveryState();
|
||||
settings["Crashed"].setValue(true);
|
||||
settings.save();
|
||||
program->initializeVideoDriver();
|
||||
@ -61,6 +62,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||
"It is highly recommended you unload your game first to avoid data loss.\n"
|
||||
"Do you wish to proceed with the audio driver change now anyway?"
|
||||
).setParent(*settingsWindow).question() == "Yes") {
|
||||
program->saveRecoveryState();
|
||||
settings["Crashed"].setValue(true);
|
||||
settings.save();
|
||||
program->initializeAudioDriver();
|
||||
@ -99,6 +101,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||
"It is highly recommended you unload your game first to avoid data loss.\n"
|
||||
"Do you wish to proceed with the input driver change now anyway?"
|
||||
).setParent(*settingsWindow).question() == "Yes") {
|
||||
program->saveRecoveryState();
|
||||
settings["Crashed"].setValue(true);
|
||||
settings.save();
|
||||
program->initializeInputDriver();
|
||||
|
@ -3,19 +3,23 @@ HotkeySettings::HotkeySettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||
setText("Hotkeys");
|
||||
|
||||
layout.setMargin(5);
|
||||
mappingList.onActivate([&] { assignMapping(); });
|
||||
mappingList.onChange([&] { eraseButton.setEnabled((bool)mappingList.selected()); });
|
||||
resetButton.setText("Reset").onActivate([&] {
|
||||
if(MessageDialog("Are you sure you want to erase all hotkey mappings?").setParent(*settingsWindow).question() == "Yes") {
|
||||
for(auto& mapping : inputManager->hotkeys) mapping.unbind();
|
||||
refreshMappings();
|
||||
}
|
||||
mappingList.setBatchable();
|
||||
mappingList.onActivate([&] {
|
||||
if(assignButton.enabled()) assignButton.doActivate();
|
||||
});
|
||||
eraseButton.setText("Erase").onActivate([&] {
|
||||
if(auto item = mappingList.selected()) {
|
||||
mappingList.onChange([&] {
|
||||
auto batched = mappingList.batched();
|
||||
assignButton.setEnabled(batched.size() == 1);
|
||||
clearButton.setEnabled(batched.size() >= 1);
|
||||
});
|
||||
assignButton.setText("Assign").onActivate([&] {
|
||||
assignMapping();
|
||||
});
|
||||
clearButton.setText("Clear").onActivate([&] {
|
||||
for(auto item : mappingList.batched()) {
|
||||
inputManager->hotkeys[item.offset()].unbind();
|
||||
refreshMappings();
|
||||
}
|
||||
refreshMappings();
|
||||
});
|
||||
|
||||
reloadMappings();
|
||||
@ -67,6 +71,7 @@ auto HotkeySettings::inputEvent(shared_pointer<HID::Device> device, uint group,
|
||||
timer.setEnabled(false);
|
||||
settingsWindow->statusBar.setText();
|
||||
settingsWindow->layout.setEnabled();
|
||||
settingsWindow->doSize();
|
||||
}).setInterval(200).setEnabled();
|
||||
}
|
||||
}
|
||||
|
@ -20,36 +20,35 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
|
||||
portList.onChange([&] { reloadDevices(); });
|
||||
deviceLabel.setText("Device:");
|
||||
deviceList.onChange([&] { reloadMappings(); });
|
||||
mappingList.onActivate([&] { assignMapping(); });
|
||||
mappingList.setBatchable();
|
||||
mappingList.onActivate([&] { if(assignButton.enabled()) assignButton.doActivate(); });
|
||||
mappingList.onChange([&] { updateControls(); });
|
||||
assignMouse1.onActivate([&] { assignMouseInput(0); });
|
||||
assignMouse2.onActivate([&] { assignMouseInput(1); });
|
||||
assignMouse3.onActivate([&] { assignMouseInput(2); });
|
||||
resetButton.setText("Reset").onActivate([&] {
|
||||
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsWindow).question() == "Yes") {
|
||||
for(auto& mapping : activeDevice().mappings) mapping.unbind();
|
||||
refreshMappings();
|
||||
}
|
||||
assignButton.setText("Assign").onActivate([&] {
|
||||
assignMapping();
|
||||
});
|
||||
eraseButton.setText("Erase").onActivate([&] {
|
||||
if(auto mapping = mappingList.selected()) {
|
||||
clearButton.setText("Clear").onActivate([&] {
|
||||
for(auto mapping : mappingList.batched()) {
|
||||
activeDevice().mappings[mapping.offset()].unbind();
|
||||
refreshMappings();
|
||||
}
|
||||
refreshMappings();
|
||||
});
|
||||
|
||||
reloadPorts();
|
||||
}
|
||||
|
||||
auto InputSettings::updateControls() -> void {
|
||||
eraseButton.setEnabled((bool)mappingList.selected());
|
||||
auto batched = mappingList.batched();
|
||||
assignButton.setEnabled(batched.size() == 1);
|
||||
clearButton.setEnabled(batched.size() >= 1);
|
||||
assignMouse1.setVisible(false);
|
||||
assignMouse2.setVisible(false);
|
||||
assignMouse3.setVisible(false);
|
||||
|
||||
if(auto mapping = mappingList.selected()) {
|
||||
auto& input = activeDevice().mappings[mapping.offset()];
|
||||
|
||||
if(batched.size() == 1) {
|
||||
auto& input = activeDevice().mappings[batched.left().offset()];
|
||||
if(input.isDigital()) {
|
||||
assignMouse1.setVisible().setText("Mouse Left");
|
||||
assignMouse2.setVisible().setText("Mouse Middle");
|
||||
@ -118,7 +117,7 @@ auto InputSettings::refreshMappings() -> void {
|
||||
auto InputSettings::assignMapping() -> void {
|
||||
inputManager->poll(); //clear any pending events first
|
||||
|
||||
if(auto mapping = mappingList.selected()) {
|
||||
for(auto mapping : mappingList.batched()) {
|
||||
activeMapping = activeDevice().mappings[mapping.offset()];
|
||||
settingsWindow->layout.setEnabled(false);
|
||||
settingsWindow->statusBar.setText({"Press a key or button to map [", activeMapping->name, "] ..."});
|
||||
@ -150,6 +149,7 @@ auto InputSettings::inputEvent(shared_pointer<HID::Device> device, uint group, u
|
||||
timer.setEnabled(false);
|
||||
settingsWindow->statusBar.setText();
|
||||
settingsWindow->layout.setEnabled();
|
||||
settingsWindow->doSize();
|
||||
}).setInterval(200).setEnabled();
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ public:
|
||||
Button assignMouse2{&controlLayout, Size{100, 0}};
|
||||
Button assignMouse3{&controlLayout, Size{100, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{~0, 0}};
|
||||
Button resetButton{&controlLayout, Size{80, 0}};
|
||||
Button eraseButton{&controlLayout, Size{80, 0}};
|
||||
Button assignButton{&controlLayout, Size{80, 0}};
|
||||
Button clearButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct HotkeySettings : TabFrameItem {
|
||||
@ -57,8 +57,8 @@ public:
|
||||
TableView mappingList{&layout, Size{~0, ~0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Widget controlSpacer{&controlLayout, Size{~0, 0}};
|
||||
Button resetButton{&controlLayout, Size{80, 0}};
|
||||
Button eraseButton{&controlLayout, Size{80, 0}};
|
||||
Button assignButton{&controlLayout, Size{80, 0}};
|
||||
Button clearButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct PathSettings : TabFrameItem {
|
||||
|
@ -44,11 +44,10 @@ auto CheatDatabase::findCheats() -> void {
|
||||
auto CheatDatabase::addCheats() -> void {
|
||||
for(auto item : cheatList.items()) {
|
||||
if(item.checked()) {
|
||||
toolsWindow->cheatEditor.addCheat(item.text(), item.property("code"));
|
||||
toolsWindow->cheatEditor.addCheat({item.text(), item.property("code"), false});
|
||||
}
|
||||
}
|
||||
setVisible(false);
|
||||
toolsWindow->cheatEditor.synchronizeCodes();
|
||||
}
|
||||
|
||||
//
|
||||
@ -58,29 +57,30 @@ CheatWindow::CheatWindow() {
|
||||
|
||||
layout.setMargin(5);
|
||||
nameLabel.setText("Name:");
|
||||
nameValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); });
|
||||
nameValue.onChange([&] { doChange(); });
|
||||
codeLabel.setText("Code:");
|
||||
codeValue.onActivate([&] { if(acceptButton.enabled()) acceptButton.doActivate(); });
|
||||
codeValue.onChange([&] { doChange(); });
|
||||
enabledOption.setText("Enabled");
|
||||
enableOption.setText("Enable");
|
||||
acceptButton.onActivate([&] { doAccept(); });
|
||||
cancelButton.setText("Cancel");
|
||||
cancelButton.setText("Cancel").onActivate([&] { setVisible(false); });
|
||||
|
||||
setSize({480, layout.minimumSize().height()});
|
||||
setSize({400, layout.minimumSize().height()});
|
||||
setDismissable();
|
||||
}
|
||||
|
||||
auto CheatWindow::show(TableViewItem item) -> void {
|
||||
this->item = item;
|
||||
nameValue.setText(item.cell(1).text());
|
||||
codeValue.setText(item.property("code"));
|
||||
enabledOption.setChecked(item.cell(0).checked());
|
||||
auto CheatWindow::show(Cheat cheat) -> void {
|
||||
nameValue.setText(cheat.name);
|
||||
codeValue.setText(cheat.code);
|
||||
enableOption.setChecked(cheat.enable);
|
||||
doChange();
|
||||
setTitle(!item ? "Add Cheat Code" : "Edit Cheat Code");
|
||||
setTitle(!cheat.name ? "Add Cheat" : "Edit Cheat");
|
||||
setCentered(*toolsWindow);
|
||||
setVisible();
|
||||
setFocused();
|
||||
nameValue.setFocused();
|
||||
acceptButton.setText(!item ? "Add" : "Edit");
|
||||
acceptButton.setText(!cheat.name ? "Add" : "Edit");
|
||||
}
|
||||
|
||||
auto CheatWindow::doChange() -> void {
|
||||
@ -91,12 +91,11 @@ auto CheatWindow::doChange() -> void {
|
||||
}
|
||||
|
||||
auto CheatWindow::doAccept() -> void {
|
||||
if(item) {
|
||||
item.cell(0).setChecked(enabledOption.checked());
|
||||
item.cell(1).setText(nameValue.text());
|
||||
item.setProperty("code", codeValue.text());
|
||||
Cheat cheat = {nameValue.text().strip(), codeValue.text().strip(), enableOption.checked()};
|
||||
if(acceptButton.text() == "Add") {
|
||||
toolsWindow->cheatEditor.addCheat(cheat);
|
||||
} else {
|
||||
toolsWindow->cheatEditor.addCheat(nameValue.text(), codeValue.text(), enabledOption.checked());
|
||||
toolsWindow->cheatEditor.editCheat(cheat);
|
||||
}
|
||||
setVisible(false);
|
||||
}
|
||||
@ -108,111 +107,108 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) {
|
||||
setText("Cheat Editor");
|
||||
|
||||
layout.setMargin(5);
|
||||
cheatList.onActivate([&] { modifyButton.doActivate(); });
|
||||
cheatList.setBatchable();
|
||||
cheatList.onActivate([&] {
|
||||
editButton.doActivate();
|
||||
});
|
||||
cheatList.onChange([&] {
|
||||
auto selected = cheatList.selected();
|
||||
upButton.setEnabled((bool)selected && selected.offset() != 0);
|
||||
downButton.setEnabled((bool)selected && selected.offset() != cheatList.itemCount() - 1);
|
||||
modifyButton.setEnabled((bool)selected);
|
||||
removeButton.setEnabled((bool)selected);
|
||||
auto batched = cheatList.batched();
|
||||
editButton.setEnabled(batched.size() == 1);
|
||||
removeButton.setEnabled(batched.size() >= 1);
|
||||
});
|
||||
cheatList.onToggle([&](TableViewCell) { synchronizeCodes(); });
|
||||
upButton.setIcon(Icon::Go::Up).onActivate([&] {
|
||||
if(auto item = cheatList.selected()) {
|
||||
swap(item.offset(), item.offset() - 1);
|
||||
}
|
||||
});
|
||||
downButton.setIcon(Icon::Go::Down).onActivate([&] {
|
||||
if(auto item = cheatList.selected()) {
|
||||
swap(item.offset(), item.offset() + 1);
|
||||
}
|
||||
cheatList.onToggle([&](TableViewCell) {
|
||||
synchronizeCodes();
|
||||
});
|
||||
findCheatsButton.setText("Find Cheats ...").onActivate([&] {
|
||||
cheatDatabase->findCheats();
|
||||
});
|
||||
resetButton.setText("Reset").onActivate([&] {
|
||||
if(MessageDialog("Are you sure you want to permanently erase all cheat codes?").setParent(*toolsWindow).question() == "Yes") {
|
||||
cheatList.reset().append(TableViewHeader()
|
||||
.append(TableViewColumn())
|
||||
.append(TableViewColumn().setExpandable())
|
||||
.setVisible(false)
|
||||
);
|
||||
synchronizeCodes();
|
||||
}
|
||||
});
|
||||
appendButton.setText("Add").onActivate([&] {
|
||||
addButton.setText("Add").onActivate([&] {
|
||||
cheatWindow->show();
|
||||
});
|
||||
modifyButton.setText("Edit").onActivate([&] {
|
||||
editButton.setText("Edit").onActivate([&] {
|
||||
if(auto item = cheatList.selected()) {
|
||||
cheatWindow->show(item);
|
||||
cheatWindow->show(cheats[item.offset()]);
|
||||
}
|
||||
});
|
||||
removeButton.setText("Remove").onActivate([&] {
|
||||
if(auto item = cheatList.selected()) {
|
||||
cheatList.remove(item).doChange();
|
||||
}
|
||||
removeCheats();
|
||||
});
|
||||
|
||||
//do not display "Find Cheats" button if there is no cheat database available
|
||||
if(!file::exists(locate("cheats.bml"))) findCheatsButton.setVisible(false);
|
||||
}
|
||||
|
||||
auto CheatEditor::swap(uint x, uint y) -> void {
|
||||
auto itemX = cheatList.item(x);
|
||||
auto itemY = cheatList.item(y);
|
||||
auto enabled = itemX.cell(0).checked();
|
||||
auto name = itemX.cell(1).text();
|
||||
auto code = itemX.property("code");
|
||||
itemX.cell(0).setChecked(itemY.cell(0).checked());
|
||||
itemX.cell(1).setText(itemY.cell(1).text());
|
||||
itemX.setProperty("code", itemY.property("code"));
|
||||
itemY.cell(0).setChecked(enabled);
|
||||
itemY.cell(1).setText(name);
|
||||
itemY.setProperty("code", code);
|
||||
itemY.setSelected();
|
||||
cheatList.doChange();
|
||||
}
|
||||
|
||||
auto CheatEditor::synchronizeCodes() -> void {
|
||||
string_vector codes;
|
||||
for(auto item : cheatList.items()) {
|
||||
if(item.cell(0).checked()) codes.append(item.property("code"));
|
||||
}
|
||||
emulator->cheatSet(codes);
|
||||
}
|
||||
|
||||
auto CheatEditor::addCheat(string name, string code, bool enabled) -> void {
|
||||
cheatList.append(TableViewItem()
|
||||
.append(TableViewCell().setChecked(enabled))
|
||||
.append(TableViewCell().setText(name))
|
||||
.setProperty("code", code)
|
||||
).resizeColumns();
|
||||
}
|
||||
|
||||
auto CheatEditor::loadCheats() -> void {
|
||||
cheatList.reset().append(TableViewHeader()
|
||||
auto CheatEditor::refresh() -> void {
|
||||
cheatList.reset();
|
||||
cheatList.append(TableViewHeader().setVisible(false)
|
||||
.append(TableViewColumn())
|
||||
.append(TableViewColumn().setExpandable())
|
||||
.setVisible(false)
|
||||
);
|
||||
auto location = program->path("Cheats", program->superNintendo.location, ".cht");
|
||||
auto document = BML::unserialize(string::read(location));
|
||||
for(auto cheat : document.find("cheat")) {
|
||||
addCheat(cheat["name"].text(), cheat["code"].text(), (bool)cheat["enabled"]);
|
||||
for(auto& cheat : cheats) {
|
||||
cheatList.append(TableViewItem()
|
||||
.append(TableViewCell().setCheckable().setChecked(cheat.enable))
|
||||
.append(TableViewCell().setText(cheat.name))
|
||||
);
|
||||
}
|
||||
cheatList.resizeColumns().doChange();
|
||||
}
|
||||
|
||||
auto CheatEditor::addCheat(Cheat cheat) -> void {
|
||||
cheats.append(cheat);
|
||||
cheats.sort();
|
||||
refresh();
|
||||
for(uint index : range(cheats)) {
|
||||
if(cheats[index] == cheat) { cheatList.item(index).setSelected(); break; }
|
||||
}
|
||||
cheatList.doChange();
|
||||
synchronizeCodes();
|
||||
}
|
||||
|
||||
auto CheatEditor::editCheat(Cheat cheat) -> void {
|
||||
if(auto item = cheatList.selected()) {
|
||||
cheats[item.offset()] = cheat;
|
||||
cheats.sort();
|
||||
refresh();
|
||||
for(uint index : range(cheats)) {
|
||||
if(cheats[index] == cheat) { cheatList.item(index).setSelected(); break; }
|
||||
}
|
||||
cheatList.doChange();
|
||||
synchronizeCodes();
|
||||
}
|
||||
}
|
||||
|
||||
auto CheatEditor::removeCheats() -> void {
|
||||
if(auto batched = cheatList.batched()) {
|
||||
if(MessageDialog("Are you sure you want to permanently remove the selected cheat(s)?")
|
||||
.setParent(*toolsWindow).question() == "Yes") {
|
||||
for(uint index : rrange(batched)) cheats.remove(batched[index].offset());
|
||||
cheats.sort();
|
||||
refresh();
|
||||
synchronizeCodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto CheatEditor::loadCheats() -> void {
|
||||
cheats.reset();
|
||||
auto location = program->path("Cheats", program->superNintendo.location, ".cht");
|
||||
auto document = BML::unserialize(string::read(location));
|
||||
for(auto cheat : document.find("cheat")) {
|
||||
cheats.append({cheat["name"].text(), cheat["code"].text(), (bool)cheat["enable"]});
|
||||
}
|
||||
cheats.sort();
|
||||
refresh();
|
||||
synchronizeCodes();
|
||||
}
|
||||
|
||||
auto CheatEditor::saveCheats() -> void {
|
||||
string document;
|
||||
for(auto item : cheatList.items()) {
|
||||
for(auto cheat : cheats) {
|
||||
document.append("cheat\n");
|
||||
document.append(" name: ", item.cell(1).text(), "\n");
|
||||
document.append(" code: ", item.property("code"), "\n");
|
||||
if(item.cell(0).checked())
|
||||
document.append(" enabled\n");
|
||||
document.append(" name: ", cheat.name, "\n");
|
||||
document.append(" code: ", cheat.code, "\n");
|
||||
if(cheat.enable)
|
||||
document.append(" enable\n");
|
||||
document.append("\n");
|
||||
}
|
||||
auto location = program->path("Cheats", program->superNintendo.location, ".cht");
|
||||
@ -222,3 +218,11 @@ auto CheatEditor::saveCheats() -> void {
|
||||
file::remove(location);
|
||||
}
|
||||
}
|
||||
|
||||
auto CheatEditor::synchronizeCodes() -> void {
|
||||
string_vector codes;
|
||||
for(auto& cheat : cheats) {
|
||||
if(cheat.enable) codes.append(cheat.code);
|
||||
}
|
||||
emulator->cheatSet(codes);
|
||||
}
|
||||
|
147
higan/target-bsnes/tools/state-manager.cpp
Normal file
147
higan/target-bsnes/tools/state-manager.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
StateWindow::StateWindow() {
|
||||
stateWindow = this;
|
||||
|
||||
layout.setMargin(5);
|
||||
nameLabel.setText("Name:");
|
||||
nameValue.onActivate([&] {
|
||||
if(acceptButton.enabled()) acceptButton.doActivate();
|
||||
});
|
||||
nameValue.onChange([&] {
|
||||
doChange();
|
||||
});
|
||||
acceptButton.onActivate([&] {
|
||||
doAccept();
|
||||
});
|
||||
cancelButton.setText("Cancel").onActivate([&] {
|
||||
setVisible(false);
|
||||
});
|
||||
|
||||
setSize({400, layout.minimumSize().height()});
|
||||
setDismissable();
|
||||
}
|
||||
|
||||
auto StateWindow::show(string name) -> void {
|
||||
nameValue.setText(name).setProperty("input", name);
|
||||
doChange();
|
||||
setTitle(!name ? "Add State" : "Edit State");
|
||||
setCentered(*toolsWindow);
|
||||
setVisible();
|
||||
setFocused();
|
||||
nameValue.setFocused();
|
||||
acceptButton.setText(!name ? "Add" : "Edit");
|
||||
}
|
||||
|
||||
auto StateWindow::doChange() -> void {
|
||||
bool valid = true;
|
||||
auto name = nameValue.text().strip();
|
||||
if(!name) valid = false;
|
||||
for(auto c : name) {
|
||||
if(c == '\\'
|
||||
|| c == '\"'
|
||||
|| c == '\t'
|
||||
|| c == '/'
|
||||
|| c == ':'
|
||||
|| c == '*'
|
||||
|| c == '?'
|
||||
|| c == '<'
|
||||
|| c == '>'
|
||||
|| c == '|') valid = false;
|
||||
}
|
||||
if(auto input = nameValue.property("input")) {
|
||||
if(name != input && file::exists({program->statePath(), name, ".bst"})) valid = false;
|
||||
}
|
||||
nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224});
|
||||
acceptButton.setEnabled(valid);
|
||||
}
|
||||
|
||||
auto StateWindow::doAccept() -> void {
|
||||
if(acceptButton.text() == "Add") {
|
||||
toolsWindow->stateManager.createState(nameValue.text());
|
||||
} else {
|
||||
toolsWindow->stateManager.modifyState(nameValue.text());
|
||||
}
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) {
|
||||
setIcon(Icon::Application::FileManager);
|
||||
setText("State Manager");
|
||||
|
||||
layout.setMargin(5);
|
||||
stateList.setBatchable();
|
||||
stateList.onActivate([&] {
|
||||
editButton.doActivate();
|
||||
});
|
||||
stateList.onChange([&] {
|
||||
auto batched = stateList.batched();
|
||||
loadButton.setEnabled(batched.size() == 1);
|
||||
editButton.setEnabled(batched.size() == 1);
|
||||
removeButton.setEnabled(batched.size() >= 1);
|
||||
});
|
||||
loadButton.setText("Load").onActivate([&] {
|
||||
if(auto item = stateList.selected()) {
|
||||
program->loadState(item.cell(0).text());
|
||||
}
|
||||
});
|
||||
saveButton.setText("Save").onActivate([&] {
|
||||
stateWindow->show();
|
||||
});
|
||||
editButton.setText("Edit").onActivate([&] {
|
||||
if(auto item = stateList.selected()) {
|
||||
stateWindow->show(item.cell(0).text());
|
||||
}
|
||||
});
|
||||
removeButton.setText("Remove").onActivate([&] {
|
||||
removeStates();
|
||||
});
|
||||
}
|
||||
|
||||
auto StateManager::loadStates() -> void {
|
||||
stateList.reset();
|
||||
stateList.append(TableViewHeader().setVisible(false)
|
||||
.append(TableViewColumn().setExpandable())
|
||||
);
|
||||
for(auto filename : directory::ifiles(program->statePath(), "*.bst")) {
|
||||
stateList.append(TableViewItem()
|
||||
.append(TableViewCell().setText(filename.trimRight(".bst", 1L)))
|
||||
);
|
||||
}
|
||||
stateList.resizeColumns().doChange();
|
||||
}
|
||||
|
||||
auto StateManager::createState(string name) -> void {
|
||||
program->saveState(name);
|
||||
loadStates();
|
||||
for(auto item : stateList.items()) {
|
||||
if(item.cell(0).text() == name) item.setSelected();
|
||||
}
|
||||
stateList.doChange();
|
||||
}
|
||||
|
||||
auto StateManager::modifyState(string name) -> void {
|
||||
if(auto item = stateList.selected()) {
|
||||
string from = {program->statePath(), item.cell(0).text(), ".bst"};
|
||||
string to = {program->statePath(), name, ".bst"};
|
||||
if(from != to) {
|
||||
file::rename(from, to);
|
||||
loadStates();
|
||||
for(auto item : stateList.items()) {
|
||||
if(item.cell(0).text() == name) item.setSelected();
|
||||
}
|
||||
stateList.doChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto StateManager::removeStates() -> void {
|
||||
if(auto batched = stateList.batched()) {
|
||||
if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?")
|
||||
.setParent(*toolsWindow).question() == "Yes") {
|
||||
for(auto item : batched) {
|
||||
string location = {program->statePath(), item.cell(0).text(), ".bst"};
|
||||
file::remove(location);
|
||||
}
|
||||
loadStates();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
#include "../bsnes.hpp"
|
||||
#include "cheat-editor.cpp"
|
||||
#include "state-manager.cpp"
|
||||
unique_pointer<CheatDatabase> cheatDatabase;
|
||||
unique_pointer<CheatWindow> cheatWindow;
|
||||
unique_pointer<StateWindow> stateWindow;
|
||||
unique_pointer<ToolsWindow> toolsWindow;
|
||||
|
||||
ToolsWindow::ToolsWindow() {
|
||||
@ -16,6 +18,7 @@ ToolsWindow::ToolsWindow() {
|
||||
|
||||
onSize([&] {
|
||||
cheatEditor.cheatList.resizeColumns();
|
||||
stateManager.stateList.resizeColumns();
|
||||
});
|
||||
|
||||
onClose([&] {
|
||||
@ -28,6 +31,7 @@ auto ToolsWindow::setVisible(bool visible) -> ToolsWindow& {
|
||||
if(!visible) {
|
||||
cheatDatabase->setVisible(false);
|
||||
cheatWindow->setVisible(false);
|
||||
stateWindow->setVisible(false);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -1,3 +1,17 @@
|
||||
struct Cheat {
|
||||
auto operator==(const Cheat& compare) const -> bool {
|
||||
return name == compare.name && code == compare.code && enable == compare.enable;
|
||||
}
|
||||
|
||||
auto operator<(const Cheat& compare) const -> bool {
|
||||
return string::icompare(name, compare.name) < 0;
|
||||
}
|
||||
|
||||
string name;
|
||||
string code;
|
||||
bool enable;
|
||||
};
|
||||
|
||||
struct CheatDatabase : Window {
|
||||
CheatDatabase();
|
||||
auto findCheats() -> void;
|
||||
@ -15,13 +29,11 @@ public:
|
||||
|
||||
struct CheatWindow : Window {
|
||||
CheatWindow();
|
||||
auto show(TableViewItem item = {}) -> void;
|
||||
auto show(Cheat cheat = {}) -> void;
|
||||
auto doChange() -> void;
|
||||
auto doAccept() -> void;
|
||||
|
||||
public:
|
||||
TableViewItem item;
|
||||
|
||||
VerticalLayout layout{this};
|
||||
HorizontalLayout nameLayout{&layout, Size{~0, 0}};
|
||||
Label nameLabel{&nameLayout, Size{40, 0}};
|
||||
@ -31,71 +43,68 @@ public:
|
||||
LineEdit codeValue{&codeLayout, Size{~0, 0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Widget spacer{&controlLayout, Size{40, 0}};
|
||||
CheckLabel enabledOption{&controlLayout, Size{~0, 0}};
|
||||
CheckLabel enableOption{&controlLayout, Size{~0, 0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct CheatEditor : TabFrameItem {
|
||||
CheatEditor(TabFrame*);
|
||||
auto swap(uint x, uint y) -> void;
|
||||
auto synchronizeCodes() -> void;
|
||||
auto addCheat(string name, string code, bool enabled = false) -> void;
|
||||
auto refresh() -> void;
|
||||
auto addCheat(Cheat cheat) -> void;
|
||||
auto editCheat(Cheat cheat) -> void;
|
||||
auto removeCheats() -> void;
|
||||
auto loadCheats() -> void;
|
||||
auto saveCheats() -> void;
|
||||
auto synchronizeCodes() -> void;
|
||||
|
||||
public:
|
||||
vector<Cheat> cheats;
|
||||
|
||||
VerticalLayout layout{this};
|
||||
TableView cheatList{&layout, Size{~0, ~0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Button upButton{&controlLayout, Size{0, 0}};
|
||||
Button downButton{&controlLayout, Size{0, 0}};
|
||||
Button findCheatsButton{&controlLayout, Size{120, 0}};
|
||||
Widget spacer{&controlLayout, Size{~0, 0}};
|
||||
Button resetButton{&controlLayout, Size{80, 0}};
|
||||
Button appendButton{&controlLayout, Size{80, 0}};
|
||||
Button modifyButton{&controlLayout, Size{80, 0}};
|
||||
Button addButton{&controlLayout, Size{80, 0}};
|
||||
Button editButton{&controlLayout, Size{80, 0}};
|
||||
Button removeButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
/*
|
||||
struct CheatEditor : TabFrameItem {
|
||||
enum : uint { Slots = 128 };
|
||||
|
||||
CheatEditor(TabFrame*);
|
||||
auto doChangeSelected() -> void;
|
||||
auto doModify() -> void;
|
||||
auto doRefresh() -> void;
|
||||
auto doReset(bool force = false) -> void;
|
||||
auto doErase() -> void;
|
||||
auto synchronizeCodes() -> void;
|
||||
auto addCode(bool enabled, string code, string description) -> bool;
|
||||
auto loadCheats() -> void;
|
||||
auto saveCheats() -> void;
|
||||
struct StateWindow : Window {
|
||||
StateWindow();
|
||||
auto show(string name = {}) -> void;
|
||||
auto doChange() -> void;
|
||||
auto doAccept() -> void;
|
||||
|
||||
public:
|
||||
struct Cheat {
|
||||
bool enabled = false;
|
||||
string code;
|
||||
string description;
|
||||
};
|
||||
Cheat cheats[Slots];
|
||||
|
||||
VerticalLayout layout{this};
|
||||
TableView cheatList{&layout, Size{~0, ~0}};
|
||||
HorizontalLayout codeLayout{&layout, Size{~0, 0}};
|
||||
Label codeLabel{&codeLayout, Size{70, 0}};
|
||||
LineEdit codeValue{&codeLayout, Size{~0, 0}};
|
||||
HorizontalLayout descriptionLayout{&layout, Size{~0, 0}};
|
||||
Label descriptionLabel{&descriptionLayout, Size{70, 0}};
|
||||
LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}};
|
||||
HorizontalLayout nameLayout{&layout, Size{~0, 0}};
|
||||
Label nameLabel{&nameLayout, Size{40, 0}};
|
||||
LineEdit nameValue{&nameLayout, Size{~0, 0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Button findCodesButton{&controlLayout, Size{120, 0}};
|
||||
Widget spacer{&controlLayout, Size{~0, 0}};
|
||||
Button resetButton{&controlLayout, Size{80, 0}};
|
||||
Button eraseButton{&controlLayout, Size{80, 0}};
|
||||
Button acceptButton{&controlLayout, Size{80, 0}};
|
||||
Button cancelButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
|
||||
struct StateManager : TabFrameItem {
|
||||
StateManager(TabFrame*);
|
||||
auto loadStates() -> void;
|
||||
auto createState(string name) -> void;
|
||||
auto modifyState(string name) -> void;
|
||||
auto removeStates() -> void;
|
||||
|
||||
public:
|
||||
VerticalLayout layout{this};
|
||||
TableView stateList{&layout, Size{~0, ~0}};
|
||||
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
|
||||
Button loadButton{&controlLayout, Size{80, 0}};
|
||||
Widget spacer{&controlLayout, Size{~0, 0}};
|
||||
Button saveButton{&controlLayout, Size{80, 0}};
|
||||
Button editButton{&controlLayout, Size{80, 0}};
|
||||
Button removeButton{&controlLayout, Size{80, 0}};
|
||||
};
|
||||
*/
|
||||
|
||||
struct ToolsWindow : Window {
|
||||
ToolsWindow();
|
||||
@ -106,8 +115,10 @@ public:
|
||||
VerticalLayout layout{this};
|
||||
TabFrame panel{&layout, Size{~0, ~0}};
|
||||
CheatEditor cheatEditor{&panel};
|
||||
StateManager stateManager{&panel};
|
||||
};
|
||||
|
||||
extern unique_pointer<CheatDatabase> cheatDatabase;
|
||||
extern unique_pointer<CheatWindow> cheatWindow;
|
||||
extern unique_pointer<StateWindow> stateWindow;
|
||||
extern unique_pointer<ToolsWindow> toolsWindow;
|
||||
|
@ -65,9 +65,13 @@ auto BrowserDialogWindow::accept() -> void {
|
||||
response.selected.append(string{state.path, name});
|
||||
}
|
||||
|
||||
if(state.action == "selectFolder" && batched) {
|
||||
string name = batched.left()->cell(0)->text();
|
||||
if(isFolder(name)) response.selected.append(string{state.path, name, "/"});
|
||||
if(state.action == "selectFolder") {
|
||||
if(batched) {
|
||||
string name = batched.left()->cell(0)->text();
|
||||
if(isFolder(name)) response.selected.append(string{state.path, name, "/"});
|
||||
} else {
|
||||
response.selected.append(state.path);
|
||||
}
|
||||
}
|
||||
|
||||
if(response.selected) window.setModal(false);
|
||||
|
Loading…
Reference in New Issue
Block a user