/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "gui/cloudconnectionwizard.h" #include "backends/cloud/cloudmanager.h" #ifdef USE_SDL_NET #include "backends/networking/sdl_net/localwebserver.h" #endif // USE_SDL_NET #include "common/formats/json.h" #include "common/file.h" #include "common/memstream.h" #include "common/tokenizer.h" #include "common/translation.h" #include "gui/browser.h" #include "gui/message.h" #include "gui/gui-manager.h" #include "gui/widget.h" #include "gui/widgets/edittext.h" #include "gui/widgets/scrollcontainer.h" namespace GUI { enum { kCloudConnectionWizardQuickModeButtonCmd = 'WQMb', kCloudConnectionWizardManualModeButtonCmd = 'WMMb', kCloudConnectionWizardBackButtonCmd = 'WSBb', kCloudConnectionWizardNextButtonCmd = 'WSNb', kCloudConnectionWizardRunServerButtonCmd = 'WRSb', kCloudConnectionWizardOpenUrlStorageCmd = 'WOUb', kCloudConnectionWizardReflowCmd = 'WRFb', kCloudConnectionWizardPasteCodeCmd = 'WPCb', kCloudConnectionWizardLoadCodeCmd = 'WLCd', }; CloudConnectionWizard::CloudConnectionWizard() : Dialog("GlobalOptions_Cloud_ConnectionWizard"), _currentStep(Step::NONE), _switchToSuccess(false), _switchToFailure(false), _connecting(false) { _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain; _headlineLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Headline", Common::U32String()); _closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.CancelButton", _("Cancel"), Common::U32String(), kCloseCmd); _prevStepButton = nullptr; _nextStepButton = nullptr; _label0 = nullptr; _label1 = nullptr; _label2 = nullptr; _label3 = nullptr; _button0 = nullptr; _button1 = nullptr; _container = nullptr; _quickModeButton = nullptr; _quickModeLabel = nullptr; _manualModeButton = nullptr; _codeBox = nullptr; showStep(Step::MODE_SELECT); _callback = new Common::Callback(this, &CloudConnectionWizard::storageConnectionCallback); } CloudConnectionWizard::~CloudConnectionWizard() { #ifdef USE_SDL_NET LocalServer.setStorageConnectionCallback(nullptr); #endif // USE_SDL_NET delete _callback; } void CloudConnectionWizard::showStep(Step newStep) { if (newStep == _currentStep) return; switch (_currentStep) { case Step::NONE: break; case Step::MODE_SELECT: hideStepModeSelect(); break; case Step::QUICK_MODE_STEP_1: hideStepQuickMode1(); break; case Step::QUICK_MODE_STEP_2: hideStepQuickMode2(); break; case Step::QUICK_MODE_SUCCESS: hideStepQuickModeSuccess(); break; case Step::MANUAL_MODE_STEP_1: hideStepManualMode1(); break; case Step::MANUAL_MODE_STEP_2: hideStepManualMode2(); break; case Step::MANUAL_MODE_FAILURE: hideStepManualModeFailure(); break; case Step::MANUAL_MODE_SUCCESS: hideStepManualModeSuccess(); break; } _currentStep = newStep; switch (_currentStep) { case Step::NONE: break; case Step::MODE_SELECT: showStepModeSelect(); break; case Step::QUICK_MODE_STEP_1: showStepQuickMode1(); break; case Step::QUICK_MODE_STEP_2: showStepQuickMode2(); break; case Step::QUICK_MODE_SUCCESS: showStepQuickModeSuccess(); break; case Step::MANUAL_MODE_STEP_1: showStepManualMode1(); break; case Step::MANUAL_MODE_STEP_2: showStepManualMode2(); break; case Step::MANUAL_MODE_FAILURE: showStepManualModeFailure(); break; case Step::MANUAL_MODE_SUCCESS: showStepManualModeSuccess(); break; } reflowLayout(); g_gui.scheduleTopDialogRedraw(); } // mode select void CloudConnectionWizard::showStepModeSelect() { _headlineLabel->setLabel(_("Cloud Connection Wizard")); showContainer("ConnectionWizard_ModeSelect"); _quickModeButton = new ButtonWidget(_container, "ConnectionWizard_ModeSelect.QuickModeButton", _("Quick mode"), Common::U32String(), kCloudConnectionWizardQuickModeButtonCmd); _manualModeButton = new ButtonWidget(_container, "ConnectionWizard_ModeSelect.ManualModeButton", _("Manual mode"), Common::U32String(), kCloudConnectionWizardManualModeButtonCmd); #ifdef USE_SDL_NET _quickModeLabel = new StaticTextWidget(_container, "ConnectionWizard_ModeSelect.QuickModeHint", _("Will ask you to run the Local Webserver")); #else _quickModeLabel = new StaticTextWidget(_container, "ConnectionWizard_ModeSelect.QuickModeHint", _("Requires the Local Webserver feature"), Common::U32String(), ThemeEngine::kFontStyleNormal); _quickModeLabel->setEnabled(false); _quickModeButton->setEnabled(false); #endif // USE_SDL_NET } void CloudConnectionWizard::hideStepModeSelect() { hideContainer(); removeWidgetChecked(_quickModeButton); removeWidgetChecked(_manualModeButton); removeWidgetChecked(_quickModeLabel); } // quick mode void CloudConnectionWizard::showStepQuickMode1() { _headlineLabel->setLabel(_("Quick Mode: Step 1")); showContainer("ConnectionWizard_QuickModeStep1"); showBackButton(); showNextButton(); Common::U32StringTokenizer tok(_("In this mode, the Local Webserver must be running,\n" "so your browser can forward data to ScummVM"), "\n"); Common::U32StringArray labels = tok.split(); if (labels.size() < 2) labels.push_back(Common::U32String()); _label0 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.Line1", labels[0]); _label1 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.Line2", labels[1]); _button0 = new ButtonWidget(_container, "ConnectionWizard_QuickModeStep1.RunServerButton", Common::U32String(), Common::U32String(), kCloudConnectionWizardRunServerButtonCmd); _label2 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.ServerInfoLabel", Common::U32String()); refreshStepQuickMode1(); } void CloudConnectionWizard::refreshStepQuickMode1(bool displayAsStopped) { bool serverIsRunning = false; #ifdef USE_SDL_NET serverIsRunning = LocalServer.isRunning(); #endif // USE_SDL_NET if (displayAsStopped) serverIsRunning = false; if (_nextStepButton) _nextStepButton->setEnabled(serverIsRunning); if (_button0) { _button0->setLabel(serverIsRunning ? _("Stop server") : _("Run server")); _button0->setTooltip(serverIsRunning ? _("Stop local webserver") : _("Run local webserver")); } if (_label2) { Common::U32String address; #ifdef USE_SDL_NET address = LocalServer.getAddress(); #endif // USE_SDL_NET _label2->setLabel(serverIsRunning ? address : _("Not running")); } } void CloudConnectionWizard::hideStepQuickMode1() { hideContainer(); hideBackButton(); hideNextButton(); removeWidgetChecked(_label0); removeWidgetChecked(_label1); removeWidgetChecked(_button0); removeWidgetChecked(_label2); } void CloudConnectionWizard::showStepQuickMode2() { _headlineLabel->setLabel(_("Quick Mode: Step 2")); showContainer("ConnectionWizard_QuickModeStep2"); showBackButton(); _label0 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line1", _("Now, open this link in your browser:")); _button0 = new ButtonWidget(_container, "ConnectionWizard_QuickModeStep2.OpenLinkButton", Common::U32String("https://cloud.scummvm.org/"), _("Open URL"), kCloudConnectionWizardOpenUrlStorageCmd); Common::U32StringTokenizer tok(_("It will automatically pass the data to ScummVM,\n" "and warn you should there be any errors."), "\n"); Common::U32StringArray labels = tok.split(); if (labels.size() < 2) labels.push_back(Common::U32String()); _label1 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line2", labels[0]); _label2 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line3", labels[1]); _label3 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line4", Common::U32String(), Common::U32String(), ThemeEngine::kFontStyleNormal); #ifdef USE_SDL_NET _label3->setLabel(_("Local Webserver address: ") + Common::U32String(LocalServer.getAddress())); _label3->setEnabled(false); #endif // USE_SDL_NET } void CloudConnectionWizard::hideStepQuickMode2() { hideContainer(); hideBackButton(); removeWidgetChecked(_label0); removeWidgetChecked(_button0); removeWidgetChecked(_label1); removeWidgetChecked(_label2); removeWidgetChecked(_label3); } void CloudConnectionWizard::showStepQuickModeSuccess() { _headlineLabel->setLabel(_("Quick Mode: Success")); showContainer("ConnectionWizard_Success"); _closeButton->setVisible(false); _label0 = new StaticTextWidget(_container, "ConnectionWizard_Success.Line1", _("Your cloud storage has been connected!")); _button0 = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.FinishButton", _("Finish"), Common::U32String(), kCloseCmd); } void CloudConnectionWizard::hideStepQuickModeSuccess() { hideContainer(); _closeButton->setVisible(true); removeWidgetChecked(_label0); removeWidgetChecked(_button0); } // manual mode void CloudConnectionWizard::showStepManualMode1() { _headlineLabel->setLabel(_("Manual Mode: Step 1")); showContainer("ConnectionWizard_ManualModeStep1"); showBackButton(); showNextButton(); _label0 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line1", _("Open this link in your browser:")); _button0 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep1.OpenLinkButton", Common::U32String("https://cloud.scummvm.org/"), _("Open URL"), kCloudConnectionWizardOpenUrlStorageCmd); Common::U32StringTokenizer tok(_("When it fails to pass the JSON code to ScummVM,\n" "find it on the Troubleshooting section of the page,\n" "and go to the next step here."), "\n"); Common::U32StringArray labels = tok.split(); if (labels.size() < 2) labels.push_back(Common::U32String()); if (labels.size() < 3) labels.push_back(Common::U32String()); _label1 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line2", labels[0]); _label2 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line3", labels[1]); _label3 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line4", labels[2]); } void CloudConnectionWizard::hideStepManualMode1() { hideContainer(); hideBackButton(); hideNextButton(); removeWidgetChecked(_label0); removeWidgetChecked(_button0); removeWidgetChecked(_label1); removeWidgetChecked(_label2); removeWidgetChecked(_label3); } void CloudConnectionWizard::showStepManualMode2() { _headlineLabel->setLabel(_("Manual Mode: Step 2")); showContainer("ConnectionWizard_ManualModeStep2"); showBackButton(); showNextButton(); _label0 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep2.Line1", _("Copy the JSON code from the browser here and press Next:")); _codeBox = new EditTextWidget(_container, "ConnectionWizard_ManualModeStep2.CodeBox", Common::U32String(), Common::U32String(), 0, 0, ThemeEngine::kFontStyleConsole); _button0 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep2.PasteButton", _("Paste"), _("Paste code from clipboard"), kCloudConnectionWizardPasteCodeCmd); _button1 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep2.LoadButton", _("Load"), _("Load code from file"), kCloudConnectionWizardLoadCodeCmd); _label1 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep2.Line2", Common::U32String()); } void CloudConnectionWizard::hideStepManualMode2() { hideContainer(); _closeButton->setEnabled(true); hideBackButton(); hideNextButton(); removeWidgetChecked(_label0); removeWidgetChecked(_codeBox); removeWidgetChecked(_button0); removeWidgetChecked(_button1); removeWidgetChecked(_label1); } void CloudConnectionWizard::showStepManualModeFailure() { _headlineLabel->setLabel(_("Manual Mode: Something went wrong")); showContainer("ConnectionWizard_Failure"); showBackButton(); _label0 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line1", _("Cloud storage was not connected.")); _label1 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line2", _("Make sure the JSON code was copied correctly and retry.")); _label2 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line3", _("If that doesn't work, try again from the beginning.")); _label3 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line4", _("Error message: ") + _errorMessage, Common::U32String(), ThemeEngine::kFontStyleNormal); _label3->setEnabled(false); } void CloudConnectionWizard::hideStepManualModeFailure() { hideContainer(); hideBackButton(); removeWidgetChecked(_label0); removeWidgetChecked(_label1); removeWidgetChecked(_label2); removeWidgetChecked(_label3); } void CloudConnectionWizard::showStepManualModeSuccess() { _headlineLabel->setLabel(_("Manual Mode: Success")); showContainer("ConnectionWizard_Success"); _closeButton->setVisible(false); _label0 = new StaticTextWidget(_container, "ConnectionWizard_Success.Line1", _("Your cloud storage has been connected!")); _button0 = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.FinishButton", _("Finish"), Common::U32String(), kCloseCmd); } void CloudConnectionWizard::hideStepManualModeSuccess() { hideContainer(); _closeButton->setVisible(true); removeWidgetChecked(_label0); removeWidgetChecked(_button0); } // utils void CloudConnectionWizard::showContainer(const Common::String &dialogName) { _container = new ScrollContainerWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Container", dialogName, kCloudConnectionWizardReflowCmd); _container->setTarget(this); _container->setBackgroundType(ThemeEngine::kWidgetBackgroundNo); } void CloudConnectionWizard::hideContainer() { removeWidgetChecked(_container); } void CloudConnectionWizard::showBackButton() { _prevStepButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.PrevButton", _("Back"), Common::U32String(), kCloudConnectionWizardBackButtonCmd); } void CloudConnectionWizard::hideBackButton() { removeWidgetChecked(_prevStepButton); } void CloudConnectionWizard::showNextButton() { _nextStepButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.NextButton", _("Next"), Common::U32String(), kCloudConnectionWizardNextButtonCmd); } void CloudConnectionWizard::hideNextButton() { removeWidgetChecked(_nextStepButton); } void CloudConnectionWizard::removeWidgetChecked(ScrollContainerWidget *&widget) { if (widget) { removeWidget(widget); widget = nullptr; } } void CloudConnectionWizard::removeWidgetChecked(ButtonWidget *&widget) { if (widget) { removeWidget(widget); widget = nullptr; } } void CloudConnectionWizard::removeWidgetChecked(StaticTextWidget *&widget) { if (widget) { removeWidget(widget); widget = nullptr; } } void CloudConnectionWizard::removeWidgetChecked(EditTextWidget *&widget) { if (widget) { removeWidget(widget); widget = nullptr; } } // logic void CloudConnectionWizard::storageConnectionCallback(const Networking::ErrorResponse &response) { if (response.failed || response.interrupted) { return; } _switchToSuccess = true; } void CloudConnectionWizard::manualModeConnect() { if (_connecting) return; if (_label1) _label1->setLabel(Common::U32String()); // get the code entered Common::String code; if (_codeBox) code = _codeBox->getEditString().encode(); if (code.size() == 0) return; // warn about other Storage working if (CloudMan.isWorking()) { bool cancel = true; MessageDialog alert(_("Another Storage is working right now. Do you want to interrupt it?"), _("Yes"), _("No")); if (alert.runModal() == GUI::kMessageOK) { if (CloudMan.isDownloading()) CloudMan.cancelDownload(); if (CloudMan.isSyncing()) CloudMan.cancelSync(); // I believe it still would return `true` here, but just in case if (CloudMan.isWorking()) { MessageDialog alert2(_("Wait until current Storage finishes and try again.")); alert2.runModal(); } else { cancel = false; } } if (cancel) { return; } } // parse JSON and display message if failed Common::MemoryWriteStreamDynamic jsonStream(DisposeAfterUse::YES); jsonStream.write(code.c_str(), code.size()); char *contents = Common::JSON::untaintContents(jsonStream); Common::JSONValue *json = Common::JSON::parse(contents); // pass JSON to the manager _connecting = true; Networking::ErrorCallback callback = new Common::Callback(this, &CloudConnectionWizard::manualModeStorageConnectionCallback); Networking::JsonResponse jsonResponse(nullptr, json); if (!CloudMan.connectStorage(jsonResponse, callback)) { // no "storage" in JSON (or invalid one) _connecting = false; delete json; delete callback; if (_label1) // I18N: JSON is name of the format, this message is displayed if user entered something incorrect to the text field _label1->setLabel(_("JSON code contents are malformed.")); return; } // disable UI if (_codeBox) _codeBox->setEnabled(false); if (_button0) _button0->setEnabled(false); if (_closeButton) _closeButton->setEnabled(false); if (_prevStepButton) _prevStepButton->setEnabled(false); if (_nextStepButton) _nextStepButton->setEnabled(false); } void CloudConnectionWizard::manualModeStorageConnectionCallback(const Networking::ErrorResponse &response) { if (response.failed || response.interrupted) { if (response.failed) { const char *knownErrorMessages[] = { _s("OK"), _s("Incorrect JSON.") // see "cloud/basestorage.cpp" }; (void)knownErrorMessages; _errorMessage = _(response.response.c_str()); } else { // I18N: error message displayed on 'Manual Mode: Failure' step of 'Cloud Connection Wizard', describing that storage connection process was interrupted _errorMessage = _("Interrupted."); } _switchToFailure = true; return; } _switchToSuccess = true; } // public void CloudConnectionWizard::open() { Dialog::open(); #ifdef USE_SDL_NET LocalServer.setStorageConnectionCallback(_callback); #endif // USE_SDL_NET } void CloudConnectionWizard::close() { #ifdef USE_SDL_NET LocalServer.setStorageConnectionCallback(nullptr); #endif // USE_SDL_NET Dialog::close(); } void CloudConnectionWizard::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { switch (cmd) { case kCloudConnectionWizardQuickModeButtonCmd: showStep(Step::QUICK_MODE_STEP_1); break; case kCloudConnectionWizardManualModeButtonCmd: showStep(Step::MANUAL_MODE_STEP_1); break; case kCloudConnectionWizardRunServerButtonCmd: #ifdef USE_SDL_NET if (LocalServer.isRunning()) { LocalServer.stopOnIdle(); refreshStepQuickMode1(true); } else { LocalServer.start(); refreshStepQuickMode1(); } #endif // USE_SDL_NET break; case kCloudConnectionWizardOpenUrlStorageCmd: if (!g_system->openUrl("https://cloud.scummvm.org/")) { MessageDialog alert(_("Failed to open URL!\nPlease navigate to this page manually.")); alert.runModal(); } break; case kCloudConnectionWizardPasteCodeCmd: if (g_system->hasTextInClipboard()) { Common::U32String message = g_system->getTextFromClipboard(); if (!message.empty()) { _codeBox->setEditString(message); } } break; case kCloudConnectionWizardLoadCodeCmd: { // I18N: JSON is a file format name BrowserDialog browser(_("Select JSON file copied from scummvm.org site"), false); if (browser.runModal() > 0) { // User made his choice... Common::File codeFile; if (!codeFile.open(browser.getResult())) { // I18N: JSON is a file format name MessageDialog alert(_("Failed to load JSON file")); alert.runModal(); break; } Common::String json = codeFile.readString(); _codeBox->setEditString(json); } break; } case kCloudConnectionWizardNextButtonCmd: switch (_currentStep) { case Step::QUICK_MODE_STEP_1: showStep(Step::QUICK_MODE_STEP_2); break; case Step::MANUAL_MODE_STEP_1: showStep(Step::MANUAL_MODE_STEP_2); break; case Step::MANUAL_MODE_STEP_2: manualModeConnect(); break; default: break; } break; case kCloudConnectionWizardBackButtonCmd: switch (_currentStep) { case Step::QUICK_MODE_STEP_1: case Step::MANUAL_MODE_STEP_1: showStep(Step::MODE_SELECT); break; case Step::QUICK_MODE_STEP_2: showStep(Step::QUICK_MODE_STEP_1); break; case Step::MANUAL_MODE_STEP_2: showStep(Step::MANUAL_MODE_STEP_1); break; case Step::MANUAL_MODE_FAILURE: showStep(Step::MANUAL_MODE_STEP_2); break; default: break; } break; default: Dialog::handleCommand(sender, cmd, data); } } void CloudConnectionWizard::handleTickle() { if (_connecting && _currentStep == Step::MANUAL_MODE_STEP_2) { bool switched = false; if (_switchToFailure) { showStep(Step::MANUAL_MODE_FAILURE); switched = true; } else if (_switchToSuccess) { showStep(Step::MANUAL_MODE_SUCCESS); switched = true; } if (switched) { _switchToFailure = false; _switchToSuccess = false; _connecting = false; } } if (_switchToSuccess) { showStep(Step::QUICK_MODE_SUCCESS); _switchToSuccess = false; } Dialog::handleTickle(); } } // End of namespace GUI