mirror of
https://github.com/cjcliffe/CubicSDR.git
synced 2024-11-22 19:58:39 -05:00
Move Session stuff to SessionMgr
This commit is contained in:
parent
1f70f9189e
commit
a958912da6
@ -326,6 +326,7 @@ SET (cubicsdr_sources
|
||||
src/IOThread.cpp
|
||||
src/ModemProperties.cpp
|
||||
src/BookmarkMgr.cpp
|
||||
src/SessionMgr.cpp
|
||||
src/sdr/SDRDeviceInfo.cpp
|
||||
src/sdr/SDRPostThread.cpp
|
||||
src/sdr/SDREnumerator.cpp
|
||||
@ -433,6 +434,7 @@ SET (cubicsdr_headers
|
||||
src/IOThread.h
|
||||
src/ModemProperties.h
|
||||
src/BookmarkMgr.h
|
||||
src/SessionMgr.h
|
||||
src/sdr/SDRDeviceInfo.h
|
||||
src/sdr/SDRPostThread.h
|
||||
src/sdr/SDREnumerator.h
|
||||
|
226
src/AppFrame.cpp
226
src/AppFrame.cpp
@ -2500,42 +2500,7 @@ void AppFrame::OnAboutDialogClose(wxCommandEvent& /* event */) {
|
||||
}
|
||||
|
||||
void AppFrame::saveSession(std::string fileName) {
|
||||
|
||||
DataTree s("cubicsdr_session");
|
||||
DataNode *header = s.rootNode()->newChild("header");
|
||||
//save as wstring to prevent problems
|
||||
header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring());
|
||||
|
||||
*header->newChild("center_freq") = wxGetApp().getFrequency();
|
||||
*header->newChild("sample_rate") = wxGetApp().getSampleRate();
|
||||
*header->newChild("solo_mode") = wxGetApp().getSoloMode()?1:0;
|
||||
|
||||
if (waterfallCanvas->getViewState()) {
|
||||
DataNode *viewState = header->newChild("view_state");
|
||||
|
||||
*viewState->newChild("center_freq") = waterfallCanvas->getCenterFrequency();
|
||||
*viewState->newChild("bandwidth") = waterfallCanvas->getBandwidth();
|
||||
}
|
||||
|
||||
DataNode *demods = s.rootNode()->newChild("demodulators");
|
||||
|
||||
//make a local copy snapshot of the list
|
||||
std::vector<DemodulatorInstancePtr> instances = wxGetApp().getDemodMgr().getDemodulators();
|
||||
|
||||
for (auto instance : instances) {
|
||||
DataNode *demod = demods->newChild("demodulator");
|
||||
wxGetApp().getDemodMgr().saveInstance(demod, instance);
|
||||
} //end for demodulators
|
||||
|
||||
// Make sure the file name actually ends in .xml
|
||||
std::string lcFileName = fileName;
|
||||
std::transform(lcFileName.begin(), lcFileName.end(), lcFileName.begin(), ::tolower);
|
||||
|
||||
if (lcFileName.find_last_of(".xml") != lcFileName.length()-1) {
|
||||
fileName.append(".xml");
|
||||
}
|
||||
|
||||
s.SaveToFileXML(fileName);
|
||||
wxGetApp().getSessionMgr().saveSession(fileName);
|
||||
|
||||
currentSessionFile = fileName;
|
||||
std::string filePart = fileName.substr(fileName.find_last_of(filePathSeparator) + 1);
|
||||
@ -2544,167 +2509,31 @@ void AppFrame::saveSession(std::string fileName) {
|
||||
}
|
||||
|
||||
bool AppFrame::loadSession(std::string fileName) {
|
||||
bool result = wxGetApp().getSessionMgr().loadSession(fileName);
|
||||
|
||||
DataTree l;
|
||||
if (!l.LoadFromFileXML(fileName)) {
|
||||
return false;
|
||||
int sample_rate = wxGetApp().getSampleRate();
|
||||
|
||||
//scan the available sample rates and see if it matches a predifined one
|
||||
int menuIndex = -1;
|
||||
for (auto discreteRate : sampleRates) {
|
||||
if (discreteRate == sample_rate) {
|
||||
menuIndex++;
|
||||
//activate Bandwidth Menu entry matching this predefined sample_rate.
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_BASE + menuIndex]->Check(true);
|
||||
break;
|
||||
}
|
||||
} //end for
|
||||
//this is a manual entry
|
||||
if (menuIndex == -1) {
|
||||
manualSampleRate = sample_rate;
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Enable(true);
|
||||
// Apply the manual value, activate the menu entry
|
||||
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->SetItemLabel(wxString("Manual Entry : ") + frequencyToStr(sample_rate));
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Check(true);
|
||||
}
|
||||
|
||||
//Check if it is a session file, read the root node.
|
||||
if (l.rootNode()->getName() != "cubicsdr_session") {
|
||||
return false;
|
||||
}
|
||||
|
||||
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
|
||||
|
||||
wxGetApp().getDemodMgr().terminateAll();
|
||||
|
||||
try {
|
||||
if (!l.rootNode()->hasAnother("header")) {
|
||||
return false;
|
||||
}
|
||||
DataNode *header = l.rootNode()->getNext("header");
|
||||
|
||||
if (header->hasAnother("version")) {
|
||||
//"Force" the retreiving of the value as string, even if its look like a number internally ! (ex: "0.2.0")
|
||||
DataNode *versionNode = header->getNext("version");
|
||||
std::wstring version;
|
||||
try {
|
||||
versionNode->element()->get(version);
|
||||
|
||||
std::cout << "Loading session file version: '" << version << "'..." << std::endl;
|
||||
}
|
||||
catch (DataTypeMismatchException e) {
|
||||
//this is for managing the old session format NOT encoded as std:wstring,
|
||||
//force current version
|
||||
std::cout << "Warning while Loading session file version, probably old format :'" << e.what() << "' please consider re-saving the current session..." << std::endl << std::flush;
|
||||
version = wxString(CUBICSDR_VERSION).ToStdWstring();
|
||||
}
|
||||
}
|
||||
|
||||
if (header->hasAnother("sample_rate")) {
|
||||
|
||||
long sample_rate = *header->getNext("sample_rate");
|
||||
|
||||
SDRDeviceInfo *dev = wxGetApp().getSDRThread()->getDevice();
|
||||
if (dev) {
|
||||
//retreive the available sample rates. A valid previously chosen manual
|
||||
//value is constrained within these limits. If it doesn't behave, lets the device choose
|
||||
//for us.
|
||||
long minRate = MANUAL_SAMPLE_RATE_MIN;
|
||||
long maxRate = MANUAL_SAMPLE_RATE_MAX;
|
||||
|
||||
std::vector<long> sampleRates = dev->getSampleRates(SOAPY_SDR_RX, 0);
|
||||
|
||||
if (sampleRates.size()) {
|
||||
minRate = sampleRates.front();
|
||||
maxRate = sampleRates.back();
|
||||
}
|
||||
|
||||
//If it is beyond limits, make device choose a reasonable value
|
||||
if (sample_rate < minRate || sample_rate > maxRate) {
|
||||
sample_rate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sample_rate);
|
||||
}
|
||||
|
||||
//scan the available sample rates and see if it matches a predifined one
|
||||
int menuIndex = -1;
|
||||
for (auto discreteRate : sampleRates) {
|
||||
if (discreteRate == sample_rate) {
|
||||
menuIndex++;
|
||||
//activate Bandwidth Menu entry matching this predefined sample_rate.
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_BASE + menuIndex]->Check(true);
|
||||
break;
|
||||
}
|
||||
} //end for
|
||||
//this is a manual entry
|
||||
if (menuIndex == -1) {
|
||||
manualSampleRate = sample_rate;
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Enable(true);
|
||||
// Apply the manual value, activate the menu entry
|
||||
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->SetItemLabel(wxString("Manual Entry : ") + frequencyToStr(sample_rate));
|
||||
sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Check(true);
|
||||
}
|
||||
//update applied value
|
||||
wxGetApp().setSampleRate(sample_rate);
|
||||
deviceChanged.store(true);
|
||||
} else {
|
||||
wxGetApp().setSampleRate(sample_rate);
|
||||
}
|
||||
}
|
||||
|
||||
if (header->hasAnother("solo_mode")) {
|
||||
|
||||
int solo_mode_activated = *header->getNext("solo_mode");
|
||||
|
||||
wxGetApp().setSoloMode((solo_mode_activated > 0) ? true : false);
|
||||
}
|
||||
else {
|
||||
wxGetApp().setSoloMode(false);
|
||||
}
|
||||
|
||||
DemodulatorInstancePtr loadedActiveDemod = nullptr;
|
||||
DemodulatorInstancePtr newDemod = nullptr;
|
||||
|
||||
if (l.rootNode()->hasAnother("demodulators")) {
|
||||
|
||||
DataNode *demodulators = l.rootNode()->getNext("demodulators");
|
||||
|
||||
std::vector<DemodulatorInstancePtr> demodsLoaded;
|
||||
|
||||
while (demodulators->hasAnother("demodulator")) {
|
||||
DataNode *demod = demodulators->getNext("demodulator");
|
||||
|
||||
if (!demod->hasAnother("bandwidth") || !demod->hasAnother("frequency")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newDemod = wxGetApp().getDemodMgr().loadInstance(demod);
|
||||
|
||||
if (demod->hasAnother("active")) {
|
||||
loadedActiveDemod = newDemod;
|
||||
}
|
||||
|
||||
newDemod->run();
|
||||
newDemod->setActive(true);
|
||||
demodsLoaded.push_back(newDemod);
|
||||
}
|
||||
|
||||
if (demodsLoaded.size()) {
|
||||
wxGetApp().notifyDemodulatorsChanged();
|
||||
}
|
||||
|
||||
} // if l.rootNode()->hasAnother("demodulators")
|
||||
|
||||
if (header->hasAnother("center_freq")) {
|
||||
long long center_freq = *header->getNext("center_freq");
|
||||
wxGetApp().setFrequency(center_freq);
|
||||
// std::cout << "\tCenter Frequency: " << center_freq << std::endl;
|
||||
}
|
||||
|
||||
if (header->hasAnother("view_state")) {
|
||||
DataNode *viewState = header->getNext("view_state");
|
||||
|
||||
if (viewState->hasAnother("center_freq") && viewState->hasAnother("bandwidth")) {
|
||||
long long center_freq = *viewState->getNext("center_freq");
|
||||
int bandwidth = *viewState->getNext("bandwidth");
|
||||
spectrumCanvas->setView(center_freq, bandwidth);
|
||||
waterfallCanvas->setView(center_freq, bandwidth);
|
||||
}
|
||||
} else {
|
||||
spectrumCanvas->disableView();
|
||||
waterfallCanvas->disableView();
|
||||
spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
|
||||
waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
|
||||
}
|
||||
|
||||
if (loadedActiveDemod || newDemod) {
|
||||
wxGetApp().getDemodMgr().setActiveDemodulator(loadedActiveDemod?loadedActiveDemod:newDemod, false);
|
||||
}
|
||||
} catch (DataTypeMismatchException e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
deviceChanged.store(true);
|
||||
|
||||
currentSessionFile = fileName;
|
||||
|
||||
@ -2715,13 +2544,20 @@ bool AppFrame::loadSession(std::string fileName) {
|
||||
|
||||
wxGetApp().getBookmarkMgr().updateActiveList();
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
FFTVisualDataThread *AppFrame::getWaterfallDataThread() {
|
||||
return waterfallDataThread;
|
||||
}
|
||||
|
||||
WaterfallCanvas *AppFrame::getWaterfallCanvas() {
|
||||
return waterfallCanvas;
|
||||
}
|
||||
|
||||
SpectrumCanvas *AppFrame::getSpectrumCanvas() {
|
||||
return spectrumCanvas;
|
||||
}
|
||||
void AppFrame::notifyUpdateModemProperties() {
|
||||
|
||||
modemPropertiesUpdated.store(true);
|
||||
|
@ -122,6 +122,8 @@ public:
|
||||
bool loadSession(std::string fileName);
|
||||
|
||||
FFTVisualDataThread *getWaterfallDataThread();
|
||||
WaterfallCanvas *getWaterfallCanvas();
|
||||
SpectrumCanvas *getSpectrumCanvas();
|
||||
|
||||
void notifyUpdateModemProperties();
|
||||
void setMainWaterfallFFTSize(int fftSize);
|
||||
|
@ -864,12 +864,16 @@ DemodulatorThreadInputQueuePtr CubicSDR::getWaterfallVisualQueue() {
|
||||
return pipeWaterfallIQVisualData;
|
||||
}
|
||||
|
||||
BookmarkMgr &CubicSDR::getBookmarkMgr() {
|
||||
return bookmarkMgr;
|
||||
}
|
||||
|
||||
DemodulatorMgr &CubicSDR::getDemodMgr() {
|
||||
return demodMgr;
|
||||
}
|
||||
|
||||
BookmarkMgr &CubicSDR::getBookmarkMgr() {
|
||||
return bookmarkMgr;
|
||||
SessionMgr &CubicSDR::getSessionMgr() {
|
||||
return sessionMgr;
|
||||
}
|
||||
|
||||
SDRPostThread *CubicSDR::getSDRPostThread() {
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "FrequencyDialog.h"
|
||||
#include "DemodLabelDialog.h"
|
||||
#include "BookmarkMgr.h"
|
||||
#include "SessionMgr.h"
|
||||
|
||||
#include "ScopeVisualProcessor.h"
|
||||
#include "SpectrumVisualProcessor.h"
|
||||
@ -119,6 +120,7 @@ public:
|
||||
|
||||
DemodulatorMgr &getDemodMgr();
|
||||
BookmarkMgr &getBookmarkMgr();
|
||||
SessionMgr &getSessionMgr();
|
||||
|
||||
SDRPostThread *getSDRPostThread();
|
||||
SDRThread *getSDRThread();
|
||||
@ -194,6 +196,7 @@ private:
|
||||
|
||||
DemodulatorMgr demodMgr;
|
||||
BookmarkMgr bookmarkMgr;
|
||||
SessionMgr sessionMgr;
|
||||
|
||||
std::atomic_llong frequency;
|
||||
std::atomic_llong offset;
|
||||
|
195
src/SessionMgr.cpp
Normal file
195
src/SessionMgr.cpp
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#include "SessionMgr.h"
|
||||
#include "CubicSDR.h"
|
||||
|
||||
void SessionMgr::saveSession(std::string fileName) {
|
||||
|
||||
DataTree s("cubicsdr_session");
|
||||
DataNode *header = s.rootNode()->newChild("header");
|
||||
//save as wstring to prevent problems
|
||||
header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring());
|
||||
|
||||
*header->newChild("center_freq") = wxGetApp().getFrequency();
|
||||
*header->newChild("sample_rate") = wxGetApp().getSampleRate();
|
||||
*header->newChild("solo_mode") = wxGetApp().getSoloMode()?1:0;
|
||||
|
||||
WaterfallCanvas *waterfallCanvas = wxGetApp().getAppFrame()->getWaterfallCanvas();
|
||||
|
||||
if (waterfallCanvas->getViewState()) {
|
||||
DataNode *viewState = header->newChild("view_state");
|
||||
|
||||
*viewState->newChild("center_freq") = waterfallCanvas->getCenterFrequency();
|
||||
*viewState->newChild("bandwidth") = waterfallCanvas->getBandwidth();
|
||||
}
|
||||
|
||||
DataNode *demods = s.rootNode()->newChild("demodulators");
|
||||
|
||||
//make a local copy snapshot of the list
|
||||
std::vector<DemodulatorInstancePtr> instances = wxGetApp().getDemodMgr().getDemodulators();
|
||||
|
||||
for (const auto &instance : instances) {
|
||||
DataNode *demod = demods->newChild("demodulator");
|
||||
wxGetApp().getDemodMgr().saveInstance(demod, instance);
|
||||
} //end for demodulators
|
||||
|
||||
// Make sure the file name actually ends in .xml
|
||||
std::string lcFileName = fileName;
|
||||
std::transform(lcFileName.begin(), lcFileName.end(), lcFileName.begin(), ::tolower);
|
||||
|
||||
if (lcFileName.find_last_of(".xml") != lcFileName.length()-1) {
|
||||
fileName.append(".xml");
|
||||
}
|
||||
|
||||
s.SaveToFileXML(fileName);
|
||||
}
|
||||
|
||||
bool SessionMgr::loadSession(std::string fileName) {
|
||||
|
||||
DataTree l;
|
||||
if (!l.LoadFromFileXML(fileName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check if it is a session file, read the root node.
|
||||
if (l.rootNode()->getName() != "cubicsdr_session") {
|
||||
return false;
|
||||
}
|
||||
|
||||
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
|
||||
wxGetApp().getDemodMgr().terminateAll();
|
||||
|
||||
WaterfallCanvas *waterfallCanvas = wxGetApp().getAppFrame()->getWaterfallCanvas();
|
||||
SpectrumCanvas *spectrumCanvas = wxGetApp().getAppFrame()->getSpectrumCanvas();
|
||||
|
||||
try {
|
||||
if (!l.rootNode()->hasAnother("header")) {
|
||||
return false;
|
||||
}
|
||||
DataNode *header = l.rootNode()->getNext("header");
|
||||
|
||||
if (header->hasAnother("version")) {
|
||||
//"Force" the retreiving of the value as string, even if its look like a number internally ! (ex: "0.2.0")
|
||||
DataNode *versionNode = header->getNext("version");
|
||||
std::wstring version;
|
||||
try {
|
||||
versionNode->element()->get(version);
|
||||
|
||||
std::cout << "Loading session file version: '" << version << "'..." << std::endl;
|
||||
}
|
||||
catch (DataTypeMismatchException &e) {
|
||||
//this is for managing the old session format NOT encoded as std:wstring,
|
||||
//force current version
|
||||
std::cout << "Warning while Loading session file version, probably old format :'" << e.what() << "' please consider re-saving the current session..." << std::endl << std::flush;
|
||||
version = wxString(CUBICSDR_VERSION).ToStdWstring();
|
||||
}
|
||||
}
|
||||
|
||||
if (header->hasAnother("sample_rate")) {
|
||||
|
||||
long sample_rate = *header->getNext("sample_rate");
|
||||
|
||||
SDRDeviceInfo *dev = wxGetApp().getSDRThread()->getDevice();
|
||||
if (dev) {
|
||||
//retreive the available sample rates. A valid previously chosen manual
|
||||
//value is constrained within these limits. If it doesn't behave, lets the device choose
|
||||
//for us.
|
||||
long minRate = MANUAL_SAMPLE_RATE_MIN;
|
||||
long maxRate = MANUAL_SAMPLE_RATE_MAX;
|
||||
|
||||
std::vector<long> sampleRates = dev->getSampleRates(SOAPY_SDR_RX, 0);
|
||||
|
||||
if (!sampleRates.empty()) {
|
||||
minRate = sampleRates.front();
|
||||
maxRate = sampleRates.back();
|
||||
}
|
||||
|
||||
//If it is beyond limits, make device choose a reasonable value
|
||||
if (sample_rate < minRate || sample_rate > maxRate) {
|
||||
sample_rate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sample_rate);
|
||||
}
|
||||
|
||||
|
||||
//update applied value
|
||||
wxGetApp().setSampleRate(sample_rate);
|
||||
} else {
|
||||
wxGetApp().setSampleRate(sample_rate);
|
||||
}
|
||||
}
|
||||
|
||||
if (header->hasAnother("solo_mode")) {
|
||||
|
||||
int solo_mode_activated = *header->getNext("solo_mode");
|
||||
|
||||
wxGetApp().setSoloMode(solo_mode_activated > 0);
|
||||
}
|
||||
else {
|
||||
wxGetApp().setSoloMode(false);
|
||||
}
|
||||
|
||||
DemodulatorInstancePtr loadedActiveDemod = nullptr;
|
||||
DemodulatorInstancePtr newDemod = nullptr;
|
||||
|
||||
if (l.rootNode()->hasAnother("demodulators")) {
|
||||
|
||||
DataNode *demodulators = l.rootNode()->getNext("demodulators");
|
||||
|
||||
std::vector<DemodulatorInstancePtr> demodsLoaded;
|
||||
|
||||
while (demodulators->hasAnother("demodulator")) {
|
||||
DataNode *demod = demodulators->getNext("demodulator");
|
||||
|
||||
if (!demod->hasAnother("bandwidth") || !demod->hasAnother("frequency")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newDemod = wxGetApp().getDemodMgr().loadInstance(demod);
|
||||
|
||||
if (demod->hasAnother("active")) {
|
||||
loadedActiveDemod = newDemod;
|
||||
}
|
||||
|
||||
newDemod->run();
|
||||
newDemod->setActive(true);
|
||||
demodsLoaded.push_back(newDemod);
|
||||
}
|
||||
|
||||
if (!demodsLoaded.empty()) {
|
||||
wxGetApp().notifyDemodulatorsChanged();
|
||||
}
|
||||
|
||||
} // if l.rootNode()->hasAnother("demodulators")
|
||||
|
||||
if (header->hasAnother("center_freq")) {
|
||||
long long center_freq = *header->getNext("center_freq");
|
||||
wxGetApp().setFrequency(center_freq);
|
||||
// std::cout << "\tCenter Frequency: " << center_freq << std::endl;
|
||||
}
|
||||
|
||||
if (header->hasAnother("view_state")) {
|
||||
DataNode *viewState = header->getNext("view_state");
|
||||
|
||||
if (viewState->hasAnother("center_freq") && viewState->hasAnother("bandwidth")) {
|
||||
long long center_freq = *viewState->getNext("center_freq");
|
||||
int bandwidth = *viewState->getNext("bandwidth");
|
||||
spectrumCanvas->setView(center_freq, bandwidth);
|
||||
waterfallCanvas->setView(center_freq, bandwidth);
|
||||
}
|
||||
} else {
|
||||
spectrumCanvas->disableView();
|
||||
waterfallCanvas->disableView();
|
||||
spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
|
||||
waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
|
||||
}
|
||||
|
||||
if (loadedActiveDemod || newDemod) {
|
||||
wxGetApp().getDemodMgr().setActiveDemodulator(loadedActiveDemod?loadedActiveDemod:newDemod, false);
|
||||
}
|
||||
} catch (DataTypeMismatchException &e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
14
src/SessionMgr.h
Normal file
14
src/SessionMgr.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) Charles J. Cliffe
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DataTree.h"
|
||||
#include "AppFrame.h"
|
||||
|
||||
|
||||
class SessionMgr {
|
||||
public:
|
||||
void saveSession(std::string fileName);
|
||||
bool loadSession(std::string fileName);
|
||||
};
|
Loading…
Reference in New Issue
Block a user