o cleanup
This commit is contained in:
163
tools/bsnes/ui_qt/utility/cartridge.cpp
Executable file
163
tools/bsnes/ui_qt/utility/cartridge.cpp
Executable file
@@ -0,0 +1,163 @@
|
||||
string Utility::selectCartridge() {
|
||||
audio.clear();
|
||||
QString filename = QFileDialog::getOpenFileName(0,
|
||||
"Load Cartridge",
|
||||
utf8() << (snes.config.path.rom != "" ? snes.config.path.rom : snes.config.path.current),
|
||||
"SNES images (*.smc *.sfc *.swc *.fig *.bs *.st"
|
||||
#if defined(GZIP_SUPPORT)
|
||||
" *.zip *.gz"
|
||||
#endif
|
||||
#if defined(JMA_SUPPORT)
|
||||
" *.jma"
|
||||
#endif
|
||||
");;"
|
||||
"All files (*)"
|
||||
);
|
||||
return string() << filename.toUtf8().constData();
|
||||
}
|
||||
|
||||
string Utility::selectFolder(const char *title) {
|
||||
audio.clear();
|
||||
QString pathname = QFileDialog::getExistingDirectory(0,
|
||||
title, utf8() << snes.config.path.current,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
return string() << pathname.toUtf8().constData();
|
||||
}
|
||||
|
||||
void Utility::loadCartridge(const char *filename) {
|
||||
switch(cartridge.detect_image_type(filename)) {
|
||||
case Cartridge::TypeNormal: loadCartridgeNormal(filename); break;
|
||||
case Cartridge::TypeBsxSlotted: winLoader->loadBsxSlottedCartridge(filename, ""); break;
|
||||
case Cartridge::TypeBsxBios: winLoader->loadBsxCartridge(filename, ""); break;
|
||||
case Cartridge::TypeBsx: winLoader->loadBsxCartridge(snes.config.path.bsx, filename); break;
|
||||
case Cartridge::TypeSufamiTurboBios: winLoader->loadSufamiTurboCartridge(filename, "", ""); break;
|
||||
case Cartridge::TypeSufamiTurbo: winLoader->loadSufamiTurboCartridge(snes.config.path.st, filename, ""); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeNormal(const char *base) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_normal(base);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeBsxSlotted(const char *base, const char *slot) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_bsx_slotted(base, slot);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeBsx(const char *base, const char *slot) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_bsx(base, slot);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utility::loadCartridgeSufamiTurbo(const char *base, const char *slotA, const char *slotB) {
|
||||
if(!*base) return false;
|
||||
unloadCartridge();
|
||||
cartridge.load_sufami_turbo(base, slotA, slotB);
|
||||
modifySystemState(LoadCartridge);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Utility::unloadCartridge() {
|
||||
if(cartridge.loaded()) {
|
||||
cartridge.unload();
|
||||
modifySystemState(UnloadCartridge);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::modifySystemState(system_state_t state) {
|
||||
video.clear();
|
||||
audio.clear();
|
||||
|
||||
switch(state) {
|
||||
case LoadCartridge: {
|
||||
//must call cartridge.load_cart_...() before calling modifySystemState(LoadCartridge)
|
||||
if(cartridge.loaded() == false) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
//warn if unsupported hardware detected
|
||||
string chip;
|
||||
if(cartridge.has_superfx()) chip = "SuperFX";
|
||||
else if(cartridge.has_sa1()) chip = "SA-1";
|
||||
else if(cartridge.has_st011()) chip = "ST011";
|
||||
else if(cartridge.has_st018()) chip = "ST018";
|
||||
else if(cartridge.has_dsp3()) chip = "DSP-3";
|
||||
if(chip != "") {
|
||||
QMessageBox::warning(winMain->window, "Warning", utf8()
|
||||
<< "<p><b>Warning:</b><br>Unsupported " << chip << " chip detected. "
|
||||
<< "It is unlikely that this title will work properly.</p>");
|
||||
}
|
||||
|
||||
showMessage(utf8()
|
||||
<< "Loaded " << cartridge.name()
|
||||
<< (cartridge.patched() ? ", and applied UPS patch." : "."));
|
||||
winMain->window->setWindowTitle(utf8() << BSNES_TITLE << " - " << cartridge.name());
|
||||
} break;
|
||||
|
||||
case UnloadCartridge: {
|
||||
if(cartridge.loaded() == false) break; //no cart to unload?
|
||||
cartridge.unload();
|
||||
|
||||
application.power = false;
|
||||
application.pause = true;
|
||||
|
||||
showMessage(utf8() << "Unloaded " << cartridge.name() << ".");
|
||||
winMain->window->setWindowTitle(utf8() << BSNES_TITLE);
|
||||
} break;
|
||||
|
||||
case PowerOn: {
|
||||
if(cartridge.loaded() == false || application.power == true) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
showMessage("Power on.");
|
||||
} break;
|
||||
|
||||
case PowerOff: {
|
||||
if(cartridge.loaded() == false || application.power == false) break;
|
||||
|
||||
application.power = false;
|
||||
application.pause = true;
|
||||
|
||||
showMessage("Power off.");
|
||||
} break;
|
||||
|
||||
case PowerCycle: {
|
||||
if(cartridge.loaded() == false) break;
|
||||
|
||||
application.power = true;
|
||||
application.pause = false;
|
||||
snes.power();
|
||||
|
||||
showMessage("System power was cycled.");
|
||||
} break;
|
||||
|
||||
case Reset: {
|
||||
if(cartridge.loaded() == false || application.power == false) break;
|
||||
|
||||
application.pause = false;
|
||||
snes.reset();
|
||||
|
||||
showMessage("System was reset.");
|
||||
} break;
|
||||
}
|
||||
|
||||
winMain->syncUi();
|
||||
winCodeEditor->dismiss();
|
||||
winCheatEditor->reloadList();
|
||||
winCheatEditor->syncUi();
|
||||
}
|
||||
218
tools/bsnes/ui_qt/utility/utility.cpp
Executable file
218
tools/bsnes/ui_qt/utility/utility.cpp
Executable file
@@ -0,0 +1,218 @@
|
||||
#include "cartridge.cpp"
|
||||
#include "window.cpp"
|
||||
|
||||
//returns true if requested code is a button, and it has just been pressed down
|
||||
bool Utility::isButtonDown(uint16_t inputCode, InputObject &object) {
|
||||
if(inputCode != object.code) return false;
|
||||
|
||||
if(object.codetype != InputCode::KeyboardButton
|
||||
&& object.codetype != InputCode::MouseButton
|
||||
&& object.codetype != InputCode::JoypadHat
|
||||
&& object.codetype != InputCode::JoypadAxis
|
||||
&& object.codetype != InputCode::JoypadButton) return false;
|
||||
|
||||
int16_t state = inputManager.state(object.code);
|
||||
int16_t lastState = inputManager.lastState(object.code);
|
||||
|
||||
if(object.codetype == InputCode::JoypadHat) {
|
||||
switch(object.modifier) {
|
||||
case InputObject::Up: return (state & joypad<>::hat_up ) && !(lastState & joypad<>::hat_up );
|
||||
case InputObject::Down: return (state & joypad<>::hat_down ) && !(lastState & joypad<>::hat_down );
|
||||
case InputObject::Left: return (state & joypad<>::hat_left ) && !(lastState & joypad<>::hat_left );
|
||||
case InputObject::Right: return (state & joypad<>::hat_right) && !(lastState & joypad<>::hat_right);
|
||||
}
|
||||
} else if(object.codetype == InputCode::JoypadAxis) {
|
||||
switch(object.modifier) {
|
||||
case InputObject::Lo: return (state < -16384) && !(lastState < -16384);
|
||||
case InputObject::Hi: return (state > +16384) && !(lastState > +16384);
|
||||
case InputObject::Trigger: return (state < 0) && !(lastState < 0);
|
||||
}
|
||||
} else {
|
||||
return (state == 1) && !(lastState == 1);
|
||||
}
|
||||
|
||||
return false; //fall-through for modifier-less hats / axes
|
||||
}
|
||||
|
||||
void Utility::inputEvent(uint16_t code) {
|
||||
//forward key-press event
|
||||
//(in case window is currently active and capturing a new button / axis assignment)
|
||||
winInputCapture->inputEvent(code);
|
||||
|
||||
//if escape key is pressed on *any* keyboard; release the mouse if it is acquired
|
||||
for(unsigned i = 0; i < keyboard<>::count; i++) {
|
||||
if(code == keyboard<>::index(i, keyboard<>::escape) && inputManager.state(code) && input.acquired()) {
|
||||
input.unacquire();
|
||||
return; //do not trigger other UI actions that may be bound to escape key
|
||||
}
|
||||
}
|
||||
|
||||
if(winMain->window->isActiveWindow()) {
|
||||
bool resizeWindow = false;
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.loadCartridge)) {
|
||||
string filename = selectCartridge();
|
||||
if(filename.length() > 0) loadCartridge(filename);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.pauseEmulation)) {
|
||||
application.pause = !application.pause;
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.resetSystem)) {
|
||||
modifySystemState(Reset);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.powerCycleSystem)) {
|
||||
modifySystemState(PowerCycle);
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.lowerSpeed)) {
|
||||
config.system.speed--;
|
||||
updateEmulationSpeed();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.raiseSpeed)) {
|
||||
config.system.speed++;
|
||||
updateEmulationSpeed();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleCheatSystem)) {
|
||||
if(cheat.enabled() == false) {
|
||||
cheat.enable();
|
||||
showMessage("Cheat system enabled.");
|
||||
} else {
|
||||
cheat.disable();
|
||||
showMessage("Cheat system disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleFullscreen)) {
|
||||
config.video.isFullscreen = !config.video.isFullscreen;
|
||||
updateFullscreenState();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleMenu)) {
|
||||
winMain->window->menuBar()->setVisible(!winMain->window->menuBar()->isVisibleTo(winMain->window));
|
||||
resizeWindow = true;
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.toggleStatus)) {
|
||||
winMain->window->statusBar()->setVisible(!winMain->window->statusBar()->isVisibleTo(winMain->window));
|
||||
resizeWindow = true;
|
||||
}
|
||||
|
||||
//prevent calling twice when toggleMenu == toggleStatus
|
||||
if(resizeWindow == true) {
|
||||
resizeMainWindow();
|
||||
}
|
||||
|
||||
if(isButtonDown(code, inputUiGeneral.exitEmulator)) {
|
||||
application.terminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//display message in main window statusbar area for three seconds
|
||||
void Utility::showMessage(const char *message) {
|
||||
winMain->window->statusBar()->showMessage(utf8() << message, 3000);
|
||||
}
|
||||
|
||||
//updates system state text at bottom-right of main window statusbar
|
||||
void Utility::updateSystemState() {
|
||||
string text;
|
||||
|
||||
if(cartridge.loaded() == false) {
|
||||
text = "No cartridge loaded";
|
||||
} else if(application.power == false) {
|
||||
text = "Power off";
|
||||
} else if(application.pause == true || application.autopause == true) {
|
||||
text = "Paused";
|
||||
} else if(ppu.status.frames_updated == true) {
|
||||
ppu.status.frames_updated = false;
|
||||
text << (int)ppu.status.frames_executed;
|
||||
text << " fps";
|
||||
} else {
|
||||
//nothing to update
|
||||
return;
|
||||
}
|
||||
|
||||
winMain->systemState->setText(utf8() << text);
|
||||
}
|
||||
|
||||
void Utility::acquireMouse() {
|
||||
if(cartridge.loaded()) {
|
||||
if(snes.config.controller_port1 == SNES::Input::DeviceMouse
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceMouse
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceSuperScope
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceJustifier
|
||||
|| snes.config.controller_port2 == SNES::Input::DeviceJustifiers
|
||||
) input.acquire();
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::unacquireMouse() {
|
||||
input.unacquire();
|
||||
}
|
||||
|
||||
void Utility::updateAvSync() {
|
||||
video.set(Video::Synchronize, config.video.synchronize);
|
||||
audio.set(Audio::Synchronize, config.audio.synchronize);
|
||||
}
|
||||
|
||||
void Utility::updateVideoMode() {
|
||||
if(config.video.context->region == 0) {
|
||||
snes.video.set_mode(SNES::Video::ModeNTSC);
|
||||
} else {
|
||||
snes.video.set_mode(SNES::Video::ModePAL);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateColorFilter() {
|
||||
libfilter::colortable.set_format(libfilter::Colortable::RGB888);
|
||||
libfilter::colortable.set_contrast(config.video.contrastAdjust);
|
||||
libfilter::colortable.set_brightness(config.video.brightnessAdjust);
|
||||
libfilter::colortable.set_gamma(100 + config.video.gammaAdjust);
|
||||
libfilter::colortable.enable_gamma_ramp(config.video.enableGammaRamp);
|
||||
libfilter::colortable.update();
|
||||
}
|
||||
|
||||
void Utility::updateHardwareFilter() {
|
||||
video.set(Video::Filter, config.video.context->hwFilter);
|
||||
}
|
||||
|
||||
void Utility::updateSoftwareFilter() {
|
||||
libfilter::FilterInterface::FilterType type;
|
||||
switch(config.video.context->swFilter) { default:
|
||||
case 0: type = libfilter::FilterInterface::Direct; break;
|
||||
case 1: type = libfilter::FilterInterface::Scanline; break;
|
||||
case 2: type = libfilter::FilterInterface::Scale2x; break;
|
||||
case 3: type = libfilter::FilterInterface::HQ2x; break;
|
||||
case 4: type = libfilter::FilterInterface::NTSC; break;
|
||||
}
|
||||
libfilter::filter.set(type);
|
||||
|
||||
if(type == libfilter::FilterInterface::NTSC) {
|
||||
libfilter::filter_ntsc.adjust(0, 0, 0, 0, 0, config.video.enableNtscMergeFields);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateEmulationSpeed() {
|
||||
config.system.speed = max(0, min(4, (signed)config.system.speed));
|
||||
|
||||
double scale[] = { 0.50, 0.75, 1.00, 1.50, 2.00 };
|
||||
unsigned outfreq = config.audio.outputFrequency;
|
||||
unsigned infreq = config.audio.inputFrequency * scale[config.system.speed] + 0.5;
|
||||
|
||||
audio.set(Audio::Resample, outfreq != infreq); //only resample when necessary
|
||||
audio.set(Audio::ResampleOutputFrequency, outfreq);
|
||||
audio.set(Audio::ResampleInputFrequency, infreq);
|
||||
}
|
||||
|
||||
void Utility::updateControllers() {
|
||||
snes.input.port_set_device(0, snes.config.controller_port1);
|
||||
snes.input.port_set_device(1, snes.config.controller_port2);
|
||||
}
|
||||
37
tools/bsnes/ui_qt/utility/utility.hpp
Executable file
37
tools/bsnes/ui_qt/utility/utility.hpp
Executable file
@@ -0,0 +1,37 @@
|
||||
class Utility {
|
||||
public:
|
||||
//utility.cpp
|
||||
bool isButtonDown(uint16_t inputCode, InputObject &object);
|
||||
void inputEvent(uint16_t code);
|
||||
void showMessage(const char *message);
|
||||
void updateSystemState();
|
||||
void acquireMouse();
|
||||
void unacquireMouse();
|
||||
|
||||
void updateAvSync();
|
||||
void updateVideoMode();
|
||||
void updateColorFilter();
|
||||
void updateHardwareFilter();
|
||||
void updateSoftwareFilter();
|
||||
void updateEmulationSpeed();
|
||||
void updateControllers();
|
||||
|
||||
//cartridge.cpp
|
||||
string selectCartridge();
|
||||
string selectFolder(const char *title);
|
||||
void loadCartridge(const char*);
|
||||
bool loadCartridgeNormal(const char*);
|
||||
bool loadCartridgeBsxSlotted(const char*, const char*);
|
||||
bool loadCartridgeBsx(const char*, const char*);
|
||||
bool loadCartridgeSufamiTurbo(const char*, const char *, const char*);
|
||||
void unloadCartridge();
|
||||
|
||||
enum system_state_t { LoadCartridge, UnloadCartridge, PowerOn, PowerOff, PowerCycle, Reset };
|
||||
void modifySystemState(system_state_t state);
|
||||
|
||||
//window.cpp
|
||||
void showCentered(QWidget *window);
|
||||
void updateFullscreenState();
|
||||
void constrainSize(unsigned &x, unsigned &y, unsigned max);
|
||||
void resizeMainWindow();
|
||||
} utility;
|
||||
123
tools/bsnes/ui_qt/utility/window.cpp
Executable file
123
tools/bsnes/ui_qt/utility/window.cpp
Executable file
@@ -0,0 +1,123 @@
|
||||
//show specified window in the center of the screen.
|
||||
void Utility::showCentered(QWidget *window) {
|
||||
QRect deskRect = QApplication::desktop()->availableGeometry(window);
|
||||
|
||||
//place window offscreen, so that it can be shown to get proper frameSize()
|
||||
window->move(std::numeric_limits<signed>::max(), std::numeric_limits<signed>::max());
|
||||
window->showNormal();
|
||||
|
||||
//force-resize window to be as small as minimumSize() will allow, and then center it
|
||||
window->resize(0, 0);
|
||||
window->move(
|
||||
deskRect.center().x() - (window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (window->frameSize().height() / 2)
|
||||
);
|
||||
|
||||
#ifndef _WIN32
|
||||
//Xlib frame size (excluding inside) is QSize(0, 0) at first, wait for it to be valid
|
||||
for(unsigned counter = 0; counter < 4096; counter++) {
|
||||
if(window->frameSize() != window->size()) break;
|
||||
application.processEvents();
|
||||
}
|
||||
#endif
|
||||
|
||||
//in case frame size changed, recenter window
|
||||
window->move(
|
||||
deskRect.center().x() - (window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (window->frameSize().height() / 2)
|
||||
);
|
||||
|
||||
//ensure window has focus
|
||||
application.processEvents();
|
||||
window->activateWindow();
|
||||
window->raise();
|
||||
}
|
||||
|
||||
void Utility::updateFullscreenState() {
|
||||
video.clear();
|
||||
|
||||
if(config.video.isFullscreen == false) {
|
||||
config.video.context = &config.video.windowed;
|
||||
winMain->window->showNormal();
|
||||
} else {
|
||||
config.video.context = &config.video.fullscreen;
|
||||
winMain->window->showFullScreen();
|
||||
}
|
||||
|
||||
//Xlib requires time to propogate fullscreen state change message to window manager;
|
||||
//if window is resized before this occurs, canvas may not resize correctly
|
||||
application.processEvents();
|
||||
usleep(50000);
|
||||
|
||||
//refresh options that are unique to each video context
|
||||
resizeMainWindow();
|
||||
updateVideoMode();
|
||||
updateHardwareFilter();
|
||||
updateSoftwareFilter();
|
||||
winMain->syncUi();
|
||||
}
|
||||
|
||||
//if max exceeds x: x is set to max, and y is scaled down to keep proportion to x
|
||||
void Utility::constrainSize(unsigned &x, unsigned &y, unsigned max) {
|
||||
if(x > max) {
|
||||
double scalar = (double)max / (double)x;
|
||||
y = (unsigned)((double)y * (double)scalar);
|
||||
x = max;
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::resizeMainWindow() {
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
unsigned multiplier = config.video.context->multiplier;
|
||||
unsigned width = 256 * config.video.context->multiplier;
|
||||
unsigned height = (config.video.context->region == 0 ? 224 : 239) * config.video.context->multiplier;
|
||||
|
||||
if(config.video.context->correctAspectRatio) {
|
||||
if(config.video.context->region == 0) {
|
||||
width = (double)width * config.video.ntscAspectRatio + 0.5; //NTSC adjust
|
||||
} else {
|
||||
width = (double)width * config.video.palAspectRatio + 0.5; //PAL adjust
|
||||
}
|
||||
}
|
||||
|
||||
if(config.video.isFullscreen == false) {
|
||||
//get effective desktop work area region (ignore Windows taskbar, OS X doc, etc.)
|
||||
QRect deskRect = QApplication::desktop()->availableGeometry(winMain->window);
|
||||
|
||||
//calculate frame geometry (window border + menubar + statusbar)
|
||||
unsigned frameWidth = winMain->window->frameSize().width() - winMain->canvasContainer->size().width();
|
||||
unsigned frameHeight = winMain->window->frameSize().height() - winMain->canvasContainer->size().height();
|
||||
|
||||
//ensure window size will not be larger than viewable desktop area
|
||||
constrainSize(height, width, deskRect.height() - frameHeight);
|
||||
constrainSize(width, height, deskRect.width() - frameWidth );
|
||||
|
||||
//resize window such that it is as small as possible to hold canvas of size (width, height)
|
||||
winMain->canvas->setFixedSize(width, height);
|
||||
winMain->window->resize(width, height);
|
||||
|
||||
//center window onscreen
|
||||
winMain->window->move(
|
||||
deskRect.center().x() - (winMain->window->frameSize().width() / 2),
|
||||
deskRect.center().y() - (winMain->window->frameSize().height() / 2)
|
||||
);
|
||||
} else {
|
||||
constrainSize(height, width, winMain->canvasContainer->size().height());
|
||||
constrainSize(width, height, winMain->canvasContainer->size().width());
|
||||
|
||||
//center canvas onscreen; ensure it is not larger than viewable area
|
||||
winMain->canvas->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
winMain->canvas->setMaximumSize(width, height);
|
||||
}
|
||||
|
||||
application.processEvents();
|
||||
usleep(2000);
|
||||
}
|
||||
|
||||
//work around for Qt/Xlib bug:
|
||||
//if window resize occurs with cursor over it, Qt shows Qt::Size*DiagCursor;
|
||||
//so force it to show Qt::ArrowCursor, as expected
|
||||
winMain->window->setCursor(Qt::ArrowCursor);
|
||||
winMain->canvasContainer->setCursor(Qt::ArrowCursor);
|
||||
winMain->canvas->setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
Reference in New Issue
Block a user