#include "lts_app_manager.h" #include "AppChooser/app_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #ifndef LTS_VERSION_MAJOR #define LTS_VERSION_MAJOR "undefined" #endif #ifndef LTS_VERSION_MINOR #define LTS_VERSION_MINOR "undefined" #endif #ifndef LTS_VERSION_PATCH #define LTS_VERSION_PATCH "undefined" #endif #ifndef JENKINS_BUILD_NUMBER #define JENKINS_BUILD_NUMBER "unknown" #endif static const char ver_major[] = LTS_VERSION_MAJOR; static const char ver_minor[] = LTS_VERSION_MINOR; static const char ver_patch[] = LTS_VERSION_PATCH; static const char ver_jenkins[] = JENKINS_BUILD_NUMBER; namespace LtsAppManager { struct LtsAppManagerHandler { LtsAppManager ltsAppManager; }; Q_GLOBAL_STATIC(LtsAppManagerHandler, s_ltsAppManager) LtsAppManager *LtsAppManager::getInstance() { return &s_ltsAppManager->ltsAppManager; } LtsAppManager::LtsAppManager() : m_login(nullptr) { // used in queued connection qRegisterMetaType("LtsAppDescriptor::App"); slotStartDebugServer(); connect(qApp, &QCoreApplication::aboutToQuit, this, [this] { delete m_login; m_login = nullptr; }); } LtsAppManager::~LtsAppManager() { // should already be deleted to due QCoreApplication::aboutToQuit signal delete m_login; m_login = nullptr; } void LtsAppManager::setAvailableApps(const LtsAppDescriptor::AppDescriptionVector &availableApps) { m_availableApps = availableApps; // for (const LtsAppDescriptor::AppDescription &a : m_availableApps) // { // qWarning() << "setAvailableApps app" << (int)a.id; // } #ifndef LTS_DEBUG if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("PluginManager.CheckAppRequirements")) == QVariant(true)) { QMessageBox::information(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("Checking App Requirements now")); #else { #endif QStringList errors; LtsPluginManager::LtsPluginLoader::getInstance()->checkPlugins(availableApps, errors); if (!errors.isEmpty()) QMessageBox::critical(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("App Requirements check failed!
- %1").arg(errors.join(QStringLiteral("
- ")))); } if (m_availableApps.isEmpty()) // no apps available? { QMessageBox::critical(nullptr, tr("App Manager"), tr("There are no apps available for this project.\nPlease contact Support.")); abort(); } } void LtsAppManager::slotStartDebugServer() { QTcpServer *debugServer = new QTcpServer(this); if (!debugServer->listen(QHostAddress::Any, 20000)) { debugServer->deleteLater(); const int waitTryAgain = 5; // seconds #ifdef LTS_DEBUG /*WARNING_LOG(m_logger)*/ //qWarning() << "Could not bind debug server on port 20000, trying again in " << waitTryAgain << " seconds"; #endif QTimer::singleShot(waitTryAgain * 1000, this, &LtsAppManager::slotStartDebugServer); return; } connect(debugServer, &QTcpServer::newConnection, this, [this]() { QTcpServer *server = qobject_cast(sender()); if (server == nullptr) return; QTcpSocket *client = server->nextPendingConnection(); if (client == nullptr) return; connect(client, &QTcpSocket::readyRead, this, [this]() { QTcpSocket *client = qobject_cast(sender()); if (client == nullptr) return; QString commandStr(QString::fromUtf8(client->readLine(100)).remove(QStringLiteral("\r")).remove(QStringLiteral("\n"))); //command.remove(command.length() - 1); // rm \n QStringList argList = commandStr.split(QLatin1Char(' '), QString::SplitBehavior::SkipEmptyParts); if (argList.count() < 1) return; const QString &command = argList.at(0); LtsSessionData::LtsSessionData *sessionData = LtsSessionData::LtsSessionData::getInstance(); /*const */ static QStringList commands; commands << QStringLiteral("showSessions") << QStringLiteral("operator") << QStringLiteral("help") << QStringLiteral("logout") << QStringLiteral("flag") << QStringLiteral("setFlag") << QStringLiteral("start") << QStringLiteral("voiceCommand") << QStringLiteral("ftcmock") ; switch (commands.indexOf(command)) { case 0: // showSessions { sessionData->setDebugFlagValue(QStringLiteral("DeveloperTools.ShowSessionsOnce"), true); client->write("K"); break; } case 1: // operator { QString operatorName(sessionData->getOperatorName()); if (operatorName.isEmpty()) operatorName = QStringLiteral("(none)"); QString result(QStringLiteral("%1").arg(operatorName)); client->write(result.toUtf8()); break; } case 2: // help { QString result(QStringLiteral("Available Commands: %1").arg(commands.join(QStringLiteral(", ")))); client->write(result.toUtf8()); break; } case 3: // logout { client->write("..."); slotLogout(); client->write("\r\nK"); break; } case 4: // flag { if (argList.count() != 2) { client->write("Usage: flag "); break; } const QString &debugFlagName = argList.at(1); if (!sessionData->hasDebugFlag(debugFlagName)) { client->write("(unknown)"); break; } client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); break; } case 5: // setFlag { if (argList.count() < 3) { client->write("Usage: setFlag \r\nFlag types: int, string (no spaces allowed yet), boolean, double"); break; } static QStringList flagTypes; flagTypes << QStringLiteral("int") << QStringLiteral("string") << QStringLiteral("boolean") << QStringLiteral("double"); const QString &flagType = argList.at(2); bool k = false; const QString &flagName = argList.at(1); switch (flagTypes.indexOf(flagType)) { case 0: // int { int value = argList.at(3).toInt(&k); if (k) sessionData->setDebugFlagValue(flagName, value); else client->write("cannot convert value to integer"); break; } case 1: // string { sessionData->setDebugFlagValue(flagName, argList.at(3)); break; } case 2: // boolean { QString value(argList.at(3).toLower().simplified()); if (value == QStringLiteral("true")) sessionData->setDebugFlagValue(flagName, true); else if (value == QStringLiteral("false")) sessionData->setDebugFlagValue(flagName, false); else client->write("cannot convert value to boolean"); break; } case 3: // double { double value = argList.at(3).toDouble(&k); if (k) sessionData->setDebugFlagValue(flagName, value); else client->write("cannot convert value to double"); break; } default: { client->write("unknown flag type"); break; } } break; } case 6: // start { if (argList.count() < 3) { client->write("Usage: start "); break; } // LtsSession::DdsConnectionParams params(); // params.domainId = domainId; // params.exerciseId = exerciseId; // params.partitionId = partitionId; // params.partitionType = static_cast(partitionType); // params.tcpConnectionUrl = tcpUrl; // startApp(appId, ) client->write("not implemented yet"); break; } case 7: // execute voice commands { if (argList.count() < 2) { client->write("Usage: voiceCommand "); break; } static QStringList keywords; keywords << QStringLiteral("AddMarker") << QStringLiteral("SetMarker") << QStringLiteral("AddSnapshot") << QStringLiteral("SaveSnapshot") ; //TODO: add more voice commands const QString &keyword = argList.at(1); switch (keywords.indexOf(keyword)) { case 0: // AddMarker { auto ev = new LtsWidgets::AddMarkerEvent; ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 1: // SetMarker { auto ev = new LtsWidgets::SetMarkerEvent(QString()); if (argList.size() > 2) ev->setDescription(argList.at(2)); ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 2: // Add Snapshot { auto ev = new LtsWidgets::AddSnapshotEvent; ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 3: // Save Snapshot { auto ev = new LtsWidgets::SaveSnapshotEvent(QString()); if (argList.size() > 2) ev->setDescription(argList.at(2)); ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } default: break; } if (!sessionData->hasDebugFlag(command)) { client->write("(unknown)"); break; } client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); break; } case 8: // ftcmock { auto usage = [client]() { client->write("Usage: ftcmock start - See 'Session id' in DevDialog " "\nftcmock stop "); }; if (argList.count() < 3) { usage(); break; } bool k = false; switch (QStringList({QStringLiteral("start"), QStringLiteral("stop")}).indexOf(argList.at(1))) { case 0: // start { if (argList.count() < 6) { usage(); return; } const int min = argList.at(3).toInt(&k); if (!k || min < 0) { client->write("min must be a positive integer!"); return; } const int max = argList.at(4).toInt(&k); if (!k || max < 0) { client->write("max must be a positive integer!"); return; } const int timers = argList.at(5).toInt(&k); if (!k || timers < 1) { client->write("'timer count' must be a positive integer and greater than 0!"); return; } break; } case 1: // stop { break; } default: { usage(); return; } } const int sessionId = argList.at(2).toInt(&k); if (!k || sessionId < 0) { client->write("sessionId must be a positive integer!"); return; } emit signalCommand(client, argList); break; } default: { QString result(QStringLiteral("Unknown command '%1'").arg(command)); client->write(result.toUtf8()); break; } } client->write("\r\n> "); }); QString versionString = QStringLiteral("%1.%2-%3 (rev: %4)") .arg(QString::fromLatin1(ver_major), QString::fromLatin1(ver_minor), QString::fromLatin1(ver_jenkins), QString::fromLatin1(ver_patch)); QString motd(QStringLiteral("\r\nOperatorApplication - Version %1").arg(versionString)); client->write(motd.toUtf8()); client->write("\r\n> "); }); } void LtsAppManager::showLogin() { // wait until app.exec is called ! QTimer::singleShot(0, this, &LtsAppManager::slotShowLogin); } void LtsAppManager::slotShowLogin() { // if (m_login) // TODO: really necessary to delete it before reusing it ? // { // m_login->disconnect(this); // m_login->deleteLater(); // } if (m_login == nullptr) { m_login = new LtsLogin::LtsLogin; connect(m_login, &LtsLogin::LtsLogin::signalLoginDone, this, [&] { // on login we don't have connection params (except the ones from the command line) // -> use empty one, command line params are taken inside LtsPluginManager::startApp() #ifdef LTS_DEBUG // qWarning() << "app list length after login:" << m_appList.count(); #endif if (m_appList.isEmpty()) // do not start default app if savegames were started already NOTE: login is NOT inside m_appList if (!startAppOrLetUserChoose(m_app, {})) QTimer::singleShot(100, this, &LtsAppManager::slotLogout); }); connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, this, &LtsAppManager::signalLogoffDone); // TODO: is this safe? smells like recursion! connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, this, &LtsAppManager::slotShowLogin, Qt::QueuedConnection); connect(m_login, &LtsLogin::LtsLogin::signalLogout, this, &LtsAppManager::slotLogout); m_login->setLoginPlugin(m_loginPluginIid); } m_login->login(); } bool LtsAppManager::hasRightForApp(const LtsAppDescriptor::AppDescription &appDescription) const { const QSet &acls = LtsSessionData::LtsSessionData::getInstance()->getOperatorRights(); for (int reqRight : appDescription.requiredAcls) { if (!acls.contains(reqRight)) { QMessageBox::critical(nullptr, tr("App Manager"), tr("The minimal required operator rights are not met. Therefore, the app cannot be started.")); return false; } } return true; } void LtsAppManager::taskSwitch(QObject *_caller) { LtsSession::LtsSession *caller = qobject_cast(_caller); if (caller == nullptr) { Q_ASSERT(false); return; // bail } QVector sessions; for (const LtsPluginManager::LtsPluginManager *app : m_appList) sessions += app->sessions(); //qWarning() << "XXX: session list start:"; // for (LtsSession::LtsSession *x : sessions) // qWarning() <<"XXX: session ptr" << (quintptr)x << "is caller session?" << (caller == x); // qWarning() << "XXX: session list end!"; if (sessions.isEmpty()) return; int sessionCount = sessions.count(); //qWarning() << "session count" << sessionCount; if (sessionCount < 2) return; // no switching if there's nothing to switch to // reorder the vector... starting from current session and ending right before the current session // #1 auto sessionIt = std::find_if(sessions.begin(), sessions.end(), [caller](const LtsSession::LtsSession *session) { return (session == caller); // current session = start point }); if (sessionIt == sessions.end()) { // qWarning() << "XXX: bail......"; return; // bail } // #2 auto it = ++sessionIt; // skip this session // ... then loop until the end, start at the beginning until the session is found again // possible combinations: (S = some session; M = me (my session); E = end of list; *x* = current iterator position) // state after ...: // #1 #2 #3 #4 #3 #4 // case 1: S*M*SE => SM*S*E => SM*S*E => SMS*E* => *S*MSE => S*M*SE => stop // case 2: *M*SE => M*S*E => M*S*E => MS*E* => stop // case 3: *M*E (impossible, early exit!) // case 4: S*M*E => SM*E* => *S*ME => stop int x = 0; do { // qWarning() << "XXX: x =" << x; if (x > 100) { // qWarning() << "XXX bailing!"; return; // always bail on while loops :) } x++; // #3 if (it == sessions.end()) // already at the end? start from the front! { // qWarning() << "XXX at end!"; if (sessions.begin() == sessionIt) // ... BUT only if the front is not the caller session again... see case 2 { // qWarning() << "XXX begin == sessionIt !!!"; return; } it = sessions.begin(); // qWarning() << "XXX set to begin!"; } LtsSession::LtsSession *session = *it; // qWarning() << "XXX: session ptr" << (quintptr)session; // qWarning() << "XXX: session" << session; QWidget *blw = session->baseLayoutWidget(); if (blw == nullptr || session->isForeverInvisible() || session == caller) { // qWarning() << "XXX nope:" << (blw == nullptr) << (session->isForeverInvisible()) << (session == caller); continue; } // qWarning() << "XXX: !!!!! found @ step" << x; caller->baseLayoutWidget()->hide(); // hide *caller* session blw->showFullScreen(); // qWarning() << "XXX: raised" << blw << "- hide" << caller << "(caller)"; return; // done } // #4 while (++it != sessionIt); // stop if same session iterator was reached again } void LtsAppManager::hideAllOtherBLWs(QObject *_caller) { //qWarning() << "XXX2: go"; /* as this base layout was created just now, hide all other session's baseLayouts, * but only if they're not set as forever invisible, otherwise we could make a * script editor's replay's base layout while it was set visible on a presentation * screen. */ if (!LtsSessionData::LtsSessionData::getInstance()->isTablet()) return; // qWarning() << "XXX2: go2"; LtsSession::LtsSession *caller = qobject_cast(_caller); // can be nullptr (if session was deleted) QVector sessions; for (const LtsPluginManager::LtsPluginManager *app : m_appList) sessions += app->sessions(); for (LtsSession::LtsSession *session : sessions) { // qWarning() << "XXX2: session" << (quintptr)session; QWidget *blw = session->baseLayoutWidget(); // qWarning() << "hide attempt for" << session; if (blw != nullptr && !session->isForeverInvisible() && session != caller /* don't hide the calling */) { // qWarning() << "hiding!!"; blw->hide(); // hide *this* session } } // qWarning() << "XXX2: go3"; if (caller != nullptr) return; // qWarning() << "XXX2: go4"; // caller was deleted means we need to show some other session again, otherwise closing an app will result in all session being still hidden for (LtsSession::LtsSession *session : sessions) { // qWarning() << "XXX2: #2 session" << (quintptr)session; QWidget *blw = session->baseLayoutWidget(); if (blw == nullptr || session->isForeverInvisible()) { // qWarning() << "XXX2 nope:" << (blw == nullptr) << (session->isForeverInvisible()); continue; } // qWarning() << "XXX2: !!!!! found!!"; blw->showFullScreen(); return; } // qWarning() << "i came here, that's absolutely not good ..."; } bool LtsAppManager::startAppOrLetUserChoose(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, bool force) { return startAppOrLetUserChoose(getApp(appId), connParams, force); } bool LtsAppManager::startAppOrLetUserChoose(const LtsAppDescriptor::AppDescription &appDescription, const LtsSession::DdsConnectionParams &connParams, bool force) { if (!appDescription.active && !force) { // qCritical() << "cannot start inactive app!"; AppChooser *appChooser = new AppChooser(); // autodeletes itself appChooser->setAttribute(Qt::WA_DeleteOnClose); connect(appChooser, &AppChooser::signalLogout, this, &LtsAppManager::slotLogout); // Queued Connection -> copy params! connect(appChooser, &AppChooser::signalAppSelected, this, [this, connParams](LtsAppDescriptor::App appId) { if (createApp(appId, connParams) == nullptr) QTimer::singleShot(100, this, &LtsAppManager::slotLogout); }, Qt::QueuedConnection); // TODO: unlambda appChooser->show(); return true; // fake a working start, so that there is no quit if the AppChooser should be started } // qCritical() << "app"<= 3 && !force) { if (QMessageBox::Yes != QMessageBox::question(nullptr, tr("App Manager"), tr("Are you sure you want to launch another resource-intensive app?
" "Please note that you are currently running three apps containing the resource-intensive map componment.
" "It may lead to potential performance issues."))) { return nullptr; } } } auto *pluginManager = new LtsPluginManager::LtsPluginManager(); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalQuit, this, &LtsAppManager::slotQuit); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalSoftQuit, this, &LtsAppManager::slotSoftQuit); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalLogout, this, &LtsAppManager::slotLogout); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, this, &LtsAppManager::signalInitializationFinished); connect(pluginManager, &LtsPluginManager::LtsPluginManager::destroyed, this, &LtsAppManager::slotAppDestroyed); connect(pluginManager, &LtsPluginManager::LtsPluginManager::hideAllOtherBLWs, this, &LtsAppManager::hideAllOtherBLWs); connect(pluginManager, &LtsPluginManager::LtsPluginManager::taskSwitch, this, &LtsAppManager::taskSwitch); #ifdef LTS_DEBUG connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalStillAlive, this, [this]() { auto *app = qobject_cast(sender()); if (!app) return; for (auto i = m_appList.constBegin(); i != m_appList.constEnd(); ++i) { if (*i == app) return; } // qWarning() << "Previously deleted App" << app->appDescription().appName << "is still alive!"; }); #endif // TODO: impl // LtsWidgets::LtsGlobalWaitDialog::getInstance()->wait(); // connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, // this, [this]() // { // LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); // }); // connect(pluginManager, &LtsPluginManager::LtsPluginManager::destroyed, // this, [this]() // { // LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); // }); if (!pluginManager->startApp(appDescription, false, connParams, workspaceId, hidden)) { QMessageBox::critical(nullptr, tr("App Manager"), tr("The Plugin Manager failed to start the app due to plugin errors.\nPlease contact support.")); pluginManager->deleteLater(); //LtsWidgets::LtsGlobalWaitDialog::getInstance()->done(); return nullptr; } m_appList.append(pluginManager); return pluginManager; } bool LtsAppManager::hasApp(LtsAppDescriptor::App appId) const { return (std::find_if(m_availableApps.constBegin(), m_availableApps.constEnd(), [appId](const LtsAppDescriptor::AppDescription &app) { bool ret = (app.id == appId); //qWarning() << "App"<hasSituationMapInstance()) ++numberOfSituationMapInstances; } // for return numberOfSituationMapInstances; } LtsAppDescriptor::AppDescription LtsAppManager::getApp(LtsAppDescriptor::App appId) const { for (const LtsAppDescriptor::AppDescription &app : m_availableApps) { //qWarning() << "App"<isActive()) if (LtsSessionData::LtsSessionData::getInstance()->getOperatorId() == 0) // don't save state for Organisator return; // cleanup - rm zombie unsaved workspaces QStringList unsavedWorkspaces = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedWorkspace")); for (auto i = unsavedWorkspaces.constBegin(); i != unsavedWorkspaces.constEnd(); ++i) LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedWorkspace"), *i); QStringList unsavedFilterPreset = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedFilterPreset")); for (auto i = unsavedFilterPreset.constBegin(); i != unsavedFilterPreset.constEnd(); ++i) LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedFilterPreset"), *i); // save current state + workspace & filter preset QJsonArray root; for (const LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) { const QVector sessions = app->sessions(); const auto appId = app->appDescription().id; if (appId == LtsAppDescriptor::App::Login || sessions.count() < 1) // do not save for login app continue; QJsonObject appState; appState[QStringLiteral("id")] = static_cast(appId); QJsonArray sessionStates; for (LtsSession::LtsSession *session : sessions) { if (session->baseLayout() != nullptr && session->baseLayout()->isSecondaryWidget()) continue; // don't save secondaries QJsonObject sessionState; int screenId = session->screenId(); // if (screenId < 0) // qWarning() << "Screen ID for session is unavailable!"; int screenContainerId = session->screenContainerId(); // if (screenContainerId < 0) // qWarning() << "Screen Container ID for session is unavailable!"; sessionState[QStringLiteral("screenId")] = screenId; sessionState[QStringLiteral("screenContainerId")] = screenContainerId; sessionState[QStringLiteral("exerciseId")] = session->exerciseId(); sessionState[QStringLiteral("partitionId")] = session->partitionId(); sessionState[QStringLiteral("partitionType")] = int(session->partitionType()); sessionState[QStringLiteral("savegame")] = session->getSavegame(); // create new workspace (copy of the current workspace), as the SubSet handlers save the state of the given workspace - if a new workspace id is created here, the SUbSets save the workspace of the NEW workspace, which is ALWAYS DEFAULT (I dunno why it worked b4..) const QString newId(QUuid::createUuid().toString()); // new workspace id sessionState[QStringLiteral("workspaceId")] = newId; std::shared_ptr ifcOpm; if (session->getForeignGuiInterface(ifcOpm)) // TODO: use login session ! { // qWarning() << "Saved current workspace" << session->workspaceId() << "as new workspace" << newId; ifcOpm->callWorkspaceSaveUnsaved(newId); } // else // qWarning() << "Unable to save workspace" << session->workspaceId() << "at logout because of a missing ifc"; sessionStates.append(sessionState); } if (!sessionStates.isEmpty()) { appState[QStringLiteral("sessions")] = sessionStates; root.append(appState); } } QJsonDocument doc(root); //qWarning() << "last state config: " << QString::fromUtf8(doc.toJson(QJsonDocument::Compact)); LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("app"), QStringLiteral("LastAppStates"), doc.toJson(QJsonDocument::Compact)); } void LtsAppManager::slotLogout() { for (LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) { const QVector sessions = app->sessions(); for (LtsSession::LtsSession *session : sessions) { if (session->notifyUnsavedChanges() == false /* has unsaved changes (also notifies) */) return; // then stop here! } } saveState(); const QVector apps = m_appList; //m_appList.clear(); if (apps.isEmpty() && m_login != nullptr) m_login->logoff(); // XXX: is only masking the real problem.. maybe shared ptrs for (LtsPluginManager::LtsPluginManager *app : apps) app->deleteLater(); /*\ |*| |*| m_login->logoff(); <= |*| |*| qDeleteAll deletes all apps, but the Topic Handler's finalization is triggered only when |*| there is a destroyed-signal. Calling m_login->logoff directly after deleting apps |*| sets operatorId in LtsSessionData to -1, which renders all remaining Topic Handlers |*| useless. This makes topics locked by the Topic Handler on dialogs which are still open |*| on logout unusable for other users until it is clearly unlocked by the same user again |*| by closing the dialog directly. Only call m_login->logoff() when the m_appList is really |*| empty! |*| \*/ } void LtsAppManager::slotAppDestroyed() { LtsPluginManager::LtsPluginManager *app = static_cast(sender()); /*if (!*/m_appList.removeOne(app);/*)*/ //qCritical() << "WARNING: LtsAppManager: logout for app that does not exist!"; if (m_appList.isEmpty() && m_login != nullptr) // so the last app is that one that is currently being destroyed (and was removed from the list) { // if there is no app left then there is no need to call slotLogout, as it only saves alls app states and then deletes all apps - but there are no apps left! m_login->logoff(); // see big comment above ! } } void LtsAppManager::slotQuit() { //qWarning() << "LtsAppManager: slotQuit"; QObject *object = sender(); if (!object) qCritical() << "ERROR: LtsAppManager: slot called without object - so it cannot be deleted from the app assignments!"; LtsPluginManager::LtsPluginManager *app = qobject_cast(object); if (app == nullptr) { qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; return; } if (m_appList.count() == 1) // last app closed by user (not logging out), then save the state which is empty since no map is left in the list (because we threw it out in the first place to archive that) { m_appList.removeAll(app); saveState(); } app->deleteLater(); //m_appList.removeOne(app); NOTE: done by slotAppDestroyed } void LtsAppManager::slotSoftQuit() { //qWarning() << "LtsAppManager: slotSoftQuit"; QObject *object = sender(); // if (!object) // qCritical() << "ERROR: LtsAppManager: slot called without object - so it cannot be deleted from the app assignments!"; LtsPluginManager::LtsPluginManager *app = qobject_cast(object); if (app == nullptr) { qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; return; } app->_softQuit(); if (m_appList.count() == 1) // last app closed by user (not logging out), then save the state which is empty since no map is left in the list (because we threw it out in the first place to archive that) { m_appList.removeAll(app); saveState(); } //app->deleteLater(); //m_appList.removeOne(app); NOTE: done by slotAppDestroyed } bool LtsAppManager::otherSessionsResponsibility(LtsSession::LtsSession *thisSession, const QVector &requiredAppTypes, const QVector &requiredPartitionTypes, const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area) { const QVector apps = LtsAppManager::LtsAppManager::getInstance()->getAppList(); QVector worthyApps; for (LtsPluginManager::LtsPluginManager *app : apps) // find worthy apps (Situation apps) { if (!requiredAppTypes.contains(static_cast(app->appDescription().id))) // check app type continue; worthyApps.append(app); } // doesSomeoneElseHaveTheDialogOpen ? for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) { const QVector sessions = app->sessions(); for (const LtsSession::LtsSession *session : sessions) { if (session->widgetExists(menuGroup, identifier, area)) return true; } } // worthyMainScreenSessionAvailable ? LtsMultiMonitor::LtsMultiMonitor *mm = LtsMultiMonitor::LtsMultiMonitor::getInstance(); LtsMultiMonitor::LtsScreenBase *mainScreen = mm->mainScreen(); for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) { const QVector sessions = app->sessions(); for (LtsSession::LtsSession *session : sessions) { if (session != thisSession // not the requester's session && mm->widgetsScreen(session->baseLayoutWidget()) == mainScreen && requiredPartitionTypes.contains(session->partitionType())) return true; } } return false; } } // namespace LtsAppManager