//========================================================================== // QTENV.CC - part of // // OMNeT++/OMNEST // Discrete System Simulation in C++ // // contains: Qtenv member functions // //========================================================================== /*--------------------------------------------------------------* Copyright (C) 1992-2017 Andras Varga Copyright (C) 2006-2017 OpenSim Ltd. This file is distributed WITHOUT ANY WARRANTY. See the file `license' for details on this and other legal matters. *--------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/stringutil.h" #include "common/stringtokenizer.h" #include "common/fileutil.h" #include "common/ver.h" #include "common/unitconversion.h" #include "envir/appreg.h" #include "envir/speedometer.h" #include "envir/matchableobject.h" #include "omnetpp/csimplemodule.h" #include "omnetpp/cmessage.h" #include "omnetpp/cscheduler.h" #include "omnetpp/ccomponenttype.h" #include "omnetpp/csimulation.h" #include "omnetpp/cconfigoption.h" #include "omnetpp/regmacros.h" #include "omnetpp/cproperties.h" #include "omnetpp/cproperty.h" #include "omnetpp/cfutureeventset.h" #include "omnetpp/platdep/platmisc.h" #include "qtenvdefs.h" #include "qtenv.h" #include "inspector.h" #include "inspectorfactory.h" #include "inspectorutil.h" #include "moduleinspector.h" #include "loginspector.h" #include "gateinspector.h" #include "genericobjectinspector.h" #include "figurerenderers.h" #include "watchinspector.h" #include "mainwindow.h" #include "treeitemmodel.h" #include "timelineinspector.h" #include "objecttreeinspector.h" #include "canvasinspector.h" #include "iosgviewer.h" #include "messageanimator.h" #include "displayupdatecontroller.h" #include "messageanimator.h" #include "runselectiondialog.h" #ifdef Q_OS_MAC #include // for the TransformProcessType magic on startup #endif #define emit // Q_INIT_RESOURCE is necessary when building static lib from qtenv. In dynamic linking this method // runs automatocally. // As the resource initializers generated by rcc are declared in the global namespace, this call to // Q_INIT_RESOURCE() also need to be done outside of any namespace. void loadResource() { Q_INIT_RESOURCE(icons); } namespace omnetpp { using namespace common; using namespace envir; namespace qtenv { // // Register the Qtenv user interface // #ifdef PREFER_QTENV #define QTENV_PRIORITY 30 #else #define QTENV_PRIORITY 15 #endif Register_OmnetApp("Qtenv", Qtenv, QTENV_PRIORITY, "Qt-based graphical user interface"); // note: priority to be changed to 30 when Qtenv becomes the default // // The following function can be used to force linking with Qtenv; specify // -u _qtenv_lib (gcc) or /include:_qtenv_lib (vc++) in the link command. // extern "C" QTENV_API void qtenv_lib() {} // on some compilers (e.g. linux gcc 4.2) the functions are generated without _ extern "C" QTENV_API void _qtenv_lib() {} Register_GlobalConfigOptionU(CFGID_QTENV_EXTRA_STACK, "qtenv-extra-stack", "B", "80KiB", "Specifies the extra amount of stack that is reserved for each `activity()` simple module when the simulation is run under Qtenv."); Register_GlobalConfigOption(CFGID_QTENV_DEFAULT_CONFIG, "qtenv-default-config", CFG_STRING, nullptr, "Specifies which config Qtenv should set up automatically on startup. The default is to ask the user."); Register_GlobalConfigOption(CFGID_QTENV_DEFAULT_RUN, "qtenv-default-run", CFG_STRING, nullptr, "Specifies which run (of the default config, see `qtenv-default-config`) Qtenv should set up automatically on startup. A run filter is also accepted. The default is to ask the user."); // utility function static bool moduleContains(cModule *potentialparent, cModule *mod) { while (mod) { if (mod == potentialparent) return true; mod = mod->getParentModule(); } return false; } bool Qtenv::isLocalPrefKey(const QString& key) { return (key == "last-configname") || (key == "last-runnumber") || key.startsWith("RunModeProfiles"); } void Qtenv::storeOptsInPrefs() { setPref("updatefreq_express_ms", QVariant::fromValue(opt->updateFreqExpress)); setPref("event_banners", opt->printEventBanners); setPref("init_banners", opt->printInitBanners); setPref("short_banners", opt->shortBanners); setPref("animation_enabled", opt->animationEnabled); setPref("nexteventmarkers", opt->showNextEventMarkers); setPref("senddirect_arrows", opt->showSendDirectArrows); setPref("anim_methodcalls", opt->animateMethodCalls); setPref("methodcalls_duration", opt->methodCallAnimDuration); setPref("animation_msgnames", opt->animationMsgNames); setPref("animation_msgclassnames", opt->animationMsgClassNames); setPref("animation_msgcolors", opt->animationMsgColors); setPref("silent_event_filters", getSilentEventFilters()); setPref("penguin_mode", opt->penguinMode); setPref("showlayouting", opt->showLayouting); QString layouterChoiceString; switch (opt->layouterChoice) { case LAYOUTER_FAST: layouterChoiceString = "fast"; break; case LAYOUTER_ADVANCED: layouterChoiceString = "advanced"; break; case LAYOUTER_AUTO: layouterChoiceString = "auto"; break; } setPref("layouterchoice", layouterChoiceString); setPref("arrangevectorconnections", opt->arrangeVectorConnections); setPref("bubbles", opt->showBubbles); setPref("expressmode_autoupdate", opt->autoupdateInExpress); QString stripNamespaceString; switch (opt->stripNamespace) { case STRIPNAMESPACE_NONE: stripNamespaceString = "none"; break; case STRIPNAMESPACE_OMNETPP: stripNamespaceString = "omnetpp"; break; case STRIPNAMESPACE_ALL: stripNamespaceString = "all"; break; } setPref("stripnamespace", stripNamespaceString); setPref("logformat", opt->logFormat.c_str()); setPref("loglevel", cLog::getLogLevelName(opt->logLevel)); setPref("scrollbacklimit", opt->scrollbackLimit); setPref("logbuffer_maxnumevents", logBuffer.getMaxNumEntries()); } void Qtenv::restoreOptsFromPrefs() { auto pref = getPref("updatefreq_express_ms"); if (pref.isValid()) opt->updateFreqExpress = pref.toLongLong(); pref = getPref("event_banners"); if (pref.isValid()) opt->printEventBanners = pref.toBool(); pref = getPref("init_banners"); if (pref.isValid()) opt->printInitBanners = pref.toBool(); pref = getPref("short_banners"); if (pref.isValid()) opt->shortBanners = pref.toBool(); pref = getPref("animation_enabled"); if (pref.isValid()) opt->animationEnabled = pref.toBool(); pref = getPref("nexteventmarkers"); if (pref.isValid()) opt->showNextEventMarkers = pref.toBool(); pref = getPref("senddirect_arrows"); if (pref.isValid()) opt->showSendDirectArrows = pref.toBool(); pref = getPref("anim_methodcalls"); if (pref.isValid()) opt->animateMethodCalls = pref.toBool(); pref = getPref("methodcalls_duration"); if (pref.isValid()) opt->methodCallAnimDuration = pref.toInt(); pref = getPref("animation_msgnames"); if (pref.isValid()) opt->animationMsgNames = pref.toBool(); pref = getPref("animation_msgclassnames"); if (pref.isValid()) opt->animationMsgClassNames = pref.toBool(); pref = getPref("animation_msgcolors"); if (pref.isValid()) opt->animationMsgColors = pref.toBool(); pref = getPref("silent_event_filters"); if (pref.isValid()) setSilentEventFilters(pref.toString().toStdString().c_str()); pref = getPref("penguin_mode"); if (pref.isValid()) opt->penguinMode = pref.toBool(); pref = getPref("showlayouting"); if (pref.isValid()) opt->showLayouting = pref.toBool(); pref = getPref("layouterchoice"); if (pref.isValid()) { QString layouterChoiceString = pref.toString(); if (layouterChoiceString == "fast") { opt->layouterChoice = LAYOUTER_FAST; } else if (layouterChoiceString == "advanced") { opt->layouterChoice = LAYOUTER_ADVANCED; } else if (layouterChoiceString == "auto") { opt->layouterChoice = LAYOUTER_AUTO; } } pref = getPref("arrangevectorconnections"); if (pref.isValid()) opt->arrangeVectorConnections = pref.toBool(); pref = getPref("bubbles"); if (pref.isValid()) opt->showBubbles = pref.toBool(); pref = getPref("expressmode_autoupdate"); if (pref.isValid()) opt->autoupdateInExpress = pref.toBool(); pref = getPref("stripnamespace"); if (pref.isValid()) { QString stripNamespaceString = pref.toString(); if (stripNamespaceString == "none") { opt->stripNamespace = STRIPNAMESPACE_NONE; } else if (stripNamespaceString == "omnetpp") { opt->stripNamespace = STRIPNAMESPACE_OMNETPP; } else if (stripNamespaceString == "all") { opt->stripNamespace = STRIPNAMESPACE_ALL; } } // overriding opts from prefs, but using the current ones (factory defaults) as fallback if no pref was stored yet opt->logFormat = getPref("logformat", opt->logFormat.c_str()).toString().toStdString(); try { // level is stored as string, so we have to convert there and back. also have to store in opt, also forward to cLog via setter opt->logLevel = cLog::resolveLogLevel(getPref("loglevel", cLog::getLogLevelName(opt->logLevel)).toByteArray()); } catch (cRuntimeError &) { } // resolveLogLevel might throw, but we can ignore it, and not change the factory default pref = getPref("scrollbacklimit"); if (pref.isValid()) opt->scrollbackLimit = pref.toInt(); pref = getPref("logbuffer_maxnumevents"); if (pref.isValid()) logBuffer.setMaxNumEntries(pref.toInt()); } void Qtenv::storeInspectors(bool closeThem) { // erasing the previously stored inspectors from the rc file QStringList groups = localPrefs->childGroups(); for (auto group : groups) { if (group.startsWith("Inspector")) localPrefs->remove(group); } std::vector toBeClosed; int index = 0; // no particular meaning, just a unique identifier for (Inspector *insp : inspectors) { if (insp->isToplevelInspector()) { cObject *obj = insp->getObject(); if (!obj || obj->getFullPath().empty()) continue; localPrefs->beginGroup(QString("Inspector-") + QString::number(index)); localPrefs->setValue("object", obj->getFullPath().c_str()); localPrefs->setValue("classname", getObjectShortTypeName(obj, STRIPNAMESPACE_NONE)); localPrefs->setValue("id", QVariant::fromValue(getObjectId(obj))); // TODO use qRegisterMetaTypeStreamOperators to set operator which can serialize InspectorType localPrefs->setValue("type", insp->getType()); localPrefs->setValue("geom", insp->geometry()); localPrefs->setValue("fullscreen", insp->windowState().testFlag(Qt::WindowFullScreen)); localPrefs->endGroup(); if (closeThem) { toBeClosed.push_back(insp); } index++; } } for (auto i : toBeClosed) { deleteInspector(i); } } void Qtenv::updateStoredInspector(cObject *newObject, cObject *oldObject) { if (!newObject || !oldObject) return; Inspector *inspector = dynamic_cast(sender()); ASSERT(inspector); QStringList groups = localPrefs->childGroups(); for (auto group : groups) { if (group.startsWith("Inspector")) { bool ok = true; localPrefs->beginGroup(group); QVariant v = localPrefs->value("object"); ok = ok && v.canConvert(); QString object = v.value(); v = localPrefs->value("classname"); ok = ok && v.canConvert(); QString classname = v.value(); v = localPrefs->value("id"); ok = ok && v.canConvert(); long objectId = v.value(); v = localPrefs->value("type"); ok = ok && v.canConvert(); InspectorType type = (InspectorType)v.value(); if (!ok) { localPrefs->endGroup(); continue; } if (object == oldObject->getFullPath().c_str() && classname == getObjectShortTypeName(oldObject, STRIPNAMESPACE_NONE) && objectId == getObjectId(oldObject) && type == inspector->getType()) { localPrefs->setValue("object", newObject->getFullPath().c_str()); localPrefs->setValue("classname", getObjectShortTypeName(newObject, STRIPNAMESPACE_NONE)); localPrefs->setValue("id", QVariant::fromValue(getObjectId(newObject))); } localPrefs->endGroup(); } } } void Qtenv::restoreInspectors() { QStringList groups = localPrefs->childGroups(); for (auto group : groups) { if (group.startsWith("Inspector")) { bool ok = true; localPrefs->beginGroup(group); QVariant v = localPrefs->value("object"); ok = ok && v.canConvert(); QString object = v.value(); v = localPrefs->value("classname"); ok = ok && v.canConvert(); QString classname = v.value(); v = localPrefs->value("id"); ok = ok && v.canConvert(); int objectId = v.value(); v = localPrefs->value("type"); ok = ok && v.canConvert(); InspectorType type = (InspectorType)v.value(); v = localPrefs->value("geom"); ok = ok && v.canConvert(); QRect geom = v.value(); v = localPrefs->value("fullscreen"); ok = ok && v.canConvert(); bool fullscreen = v.value(); if (!ok) { localPrefs->endGroup(); continue; } auto o = object.toUtf8(); // we have to save these to variables auto c = classname.toUtf8(); // otherwise they are temporary cFindByPathVisitor visitor(o, c, objectId); visitor.process(getSimulation()); for (int i = 0; i < visitor.getArraySize(); ++i) { if (!findFirstInspector(visitor.getArray()[i], type, true)) { Inspector *insp = inspect(visitor.getArray()[i], type, true); if (fullscreen) insp->setWindowState(insp->windowState() | Qt::WindowFullScreen); else { insp->setWindowState(insp->windowState() & ~Qt::WindowFullScreen); insp->setGeometry(geom); } } } localPrefs->endGroup(); } } } double Qtenv::computeModelAnimationSpeedRequest() { double animSpeed = DBL_MAX; bool wasNan = false; for (auto i : inspectors) { if (auto mi = dynamic_cast(i)) if (auto mod = dynamic_cast(mi->getObject())) if (auto canv = mod->getCanvasIfExists()) { const std::map& speedMap = canv->getAnimationSpeedMap(); for (const auto& s : speedMap) { if (std::isnan(s.second)) wasNan = true; animSpeed = std::min(animSpeed, s.second); } } // TODO //if (auto ci = dynamic_cast(i)) { // //} } if (animSpeed == DBL_MAX) animSpeed = wasNan ? NAN : 0.0; // TODO: some smarter default return animSpeed; } double Qtenv::computeModelHoldEndTime() { double holdEndTime = -1; for (auto i : inspectors) { if (auto mi = dynamic_cast(i)) if (auto mod = dynamic_cast(mi->getObject())) if (auto canv = mod->getCanvasIfExists()) holdEndTime = std::max(holdEndTime, canv->getAnimationHoldEndTime()); // TODO //if (auto ci = dynamic_cast(i)) { // //} } return holdEndTime; } Qtenv::Qtenv() : opt((QtenvOptions *&)EnvirBase::opt), icons(out) { // Note: ctor should only contain trivial initializations, because // the class may be instantiated only for the purpose of calling // printUISpecificHelp() on it simulationState = SIM_NONET; stopSimulationFlag = false; animating = false; isConfigRun = false; runUntil.msg = nullptr; // deactivate corresponding checks in eventCancelled()/objectDeleted() runUntil.stopOnMsgCancel = true; // set the name here, to prevent warning from cStringPool on shutdown when Cmdenv runs inspectorfactories.getInstance()->setName("inspectorfactories"); loadResource(); } Qtenv::~Qtenv() { delete messageAnimator; delete localPrefs; // will sync it to disk delete globalPrefs; // will sync it to disk for (auto & silentEventFilter : silentEventFilters) delete silentEventFilter; } static void signalHandler(int signum) { cStaticFlag::setExiting(); Qtenv *qtenv = getQtenv(); // The DisplayUpdateController loops check for this flag: if (qtenv) qtenv->setStopSimulationFlag(); QApplication::exit(2); } void Qtenv::doRun() { // // SETUP // try { // set signal handler signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); icons.loadImages(opt->imagePath.c_str()); // we need to flush streams, otherwise output written from Tcl tends to overtake // output written from C++ so far, at least in the IDE's console view fflush(stdout); fflush(stderr); // these three have to be available for the whole lifetime of the application static int argc = 1; static char arg[] = { 'a', 'p', 'p', 0 }; static char *argv[] = { arg, nullptr }; app = new QApplication(argc, argv); #ifdef Q_OS_MAC ProcessSerialNumber psn; GetCurrentProcess(&psn); // This dance is necessary to make the Apple Menu work immediately after launch. TransformProcessType(&psn, kProcessTransformToBackgroundApplication); TransformProcessType(&psn, kProcessTransformToForegroundApplication); #endif globalPrefs = new QSettings(QDir::homePath() + "/.qtenvrc", QSettings::IniFormat); localPrefs = new QSettings(".qtenvrc", QSettings::IniFormat); restoreOptsFromPrefs(); setLogLevel(opt->logLevel); // we have to tell cLog the level we want // create windowtitle prefix if (getParsimNumPartitions() > 0) { char tmp[32]; sprintf(tmp, "Proc %d/%d - ", getParsimProcId(), getParsimNumPartitions()); windowTitlePrefix = tmp; } messageAnimator = new MessageAnimator(); displayUpdateController = new DisplayUpdateController(); mainWindow = new MainWindow(this); initFonts(); updateQtFonts(); moduleLayouter.loadSeeds(); mainInspector = static_cast(addEmbeddedInspector(InspectorFactory::get("GenericObjectInspectorFactory"), mainWindow->getObjectInspectorArea())); mainNetworkView = static_cast(addEmbeddedInspector(InspectorFactory::get("ModuleInspectorFactory"), mainWindow->getMainInspectorArea())); mainLogView = static_cast(addEmbeddedInspector(InspectorFactory::get("LogInspectorFactory"), mainWindow->getLogInspectorArea())); mainTimeLine = static_cast(addEmbeddedInspector(InspectorFactory::get("TimeLineInspectorFactory"), mainWindow->getTimeLineArea())); mainObjectTree = static_cast(addEmbeddedInspector(InspectorFactory::get("ObjectTreeInspectorFactory"), mainWindow->getObjectTreeArea())); connect(mainNetworkView, SIGNAL(inspectedObjectChanged(cObject *,cObject *)), mainLogView, SLOT(setObject(cObject *))); connect(mainNetworkView, SIGNAL(inspectedObjectChanged(cObject *,cObject *)), mainInspector, SLOT(setObject(cObject *))); connect(&moduleLayouter, &ModuleLayouter::layoutVisualisationStarts, mainWindow, &MainWindow::enterLayoutingMode); connect(&moduleLayouter, &ModuleLayouter::layoutVisualisationEnds, mainWindow, &MainWindow::exitLayoutingMode); connect(mainWindow, &MainWindow::closed, &moduleLayouter, &ModuleLayouter::stop); connect(mainWindow->getStopAction(), &QAction::triggered, &moduleLayouter, &ModuleLayouter::stop); QApplication::processEvents(); // Part of the hack for Apple Menu functionality, see a few lines up. mainWindow->show(); mainWindow->raise(); // Part of the hack for Apple Menu functionality, see a few lines up. mainWindow->restoreGeometry(); mainInspector->setFocus(); mainWindow->activateWindow(); // We have to wait a bit for the window manager to process the trauma of having to show a window, // and only then pop up the RunSelectionDialog. If done instantly, our request to place it // centered over the MainWindow might get ignored/overridden. QTimer::singleShot(500, this, &Qtenv::initialSetUpConfiguration); // needs to be set here too, the setting in the Designer wasn't enough on Mac QApplication::setWindowIcon(QIcon(":/logo/logo128m")); setLogFormat(opt->logFormat.c_str()); // // RUN // exitCode = QApplication::exec(); } catch (std::exception& e) { throw; } // // SHUTDOWN // // saving the open toplevel inspectors to the .qtenvrc file storeInspectors(false); // close all inspectors before exiting for (auto insp : inspectors) { delete insp; } inspectors.clear(); IOsgViewer::uninit(); // clear log logBuffer.clear(); moduleLayouter.saveSeeds(); delete messageAnimator; messageAnimator = nullptr; delete displayUpdateController; displayUpdateController = nullptr; // delete network if not yet done if (simulationState != SIM_NONET && simulationState != SIM_FINISHCALLED) endRun(); getSimulation()->deleteNetwork(); // pull down inspector factories inspectorfactories.clear(); FigureRenderer::clearRendererCache(); mainWindow->storeGeometry(); saveFonts(); delete mainWindow; mainWindow = nullptr; storeOptsInPrefs(); delete globalPrefs; globalPrefs = nullptr; delete localPrefs; localPrefs = nullptr; delete app; app = nullptr; } void Qtenv::printUISpecificHelp() { out << "\n"; out << "Qtenv-specific information:\n"; out << " Qtenv allows the user to select a simulation run interactively.\n"; out << " The -c and -r options only serve as hints or default values for\n"; out << " the GUI.\n"; } void Qtenv::rebuildSim() { if (isConfigRun) newRun(std::string(getConfigEx()->getActiveConfigName()).c_str(), getConfigEx()->getActiveRunNumber()); else if (getSimulation()->getNetworkType() != nullptr) newNetwork(getSimulation()->getNetworkType()->getName()); else confirm(INFO, "Choose File|New Network or File|New Run."); } void Qtenv::runSimulation(RunMode mode, simtime_t until_time, eventnumber_t until_eventnum, cMessage *until_msg, cModule *until_module, bool stopOnMsgCancel) { ASSERT(simulationState == SIM_NEW || simulationState == SIM_READY); setSimulationRunMode(mode); runUntil.time = until_time; runUntil.eventNumber = until_eventnum; runUntil.msg = until_msg; runUntil.module = until_module; // Note: this is NOT supported with RUNMODE_EXPRESS runUntil.stopOnMsgCancel = stopOnMsgCancel; stopSimulationFlag = false; simulationState = SIM_RUNNING; // if there's some animating to do before the event, only do that if stepping. doNextEventInStep = getSimulation()->isTrapOnNextEventRequested() || displayUpdateController->rightBeforeEvent(); updateStatusDisplay(); QApplication::processEvents(); startClock(); notifyLifecycleListeners(LF_ON_SIMULATION_RESUME); try { // funky while loop to handle switching to and from EXPRESS mode.... bool cont = true; while (cont) { if (runMode == RUNMODE_EXPRESS) cont = doRunSimulationExpress(); else cont = doRunSimulation(); } if (runMode != RUNMODE_NORMAL) { // in NORMAL mode, doRunSimulation() already calls refreshDisplay() after each event messageAnimator->updateAnimations(); callRefreshDisplay(); } simulationState = SIM_READY; notifyLifecycleListeners(LF_ON_SIMULATION_PAUSE); } catch (cTerminationException& e) { simulationState = SIM_TERMINATED; stoppedWithTerminationException(e); notifyLifecycleListeners(LF_ON_SIMULATION_SUCCESS); displayException(e); } catch (std::exception& e) { simulationState = SIM_ERROR; stoppedWithException(e); notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); } stopClock(); stopSimulationFlag = false; animating = true; loggingEnabled = true; recordEventlog = false; runUntil.msg = nullptr; runMode = RUNMODE_NOT_RUNNING; displayUpdateController->setRunMode(runMode); if (!messageAnimator->isHoldActive()) messageAnimator->setMarkedModule(getSimulation()->guessNextModule()); if (simulationState == SIM_TERMINATED) { // call wrapper around simulation.callFinish() and simulation.endRun() // // NOTE: if the simulation is in SIM_ERROR, we don't want endRun() to be // called yet, because we want to allow the user to force finish() from // the GUI -- and finish() has to precede endRun(). endRun() will be called // just before a new network gets set up, or on Qtenv shutdown. // finishSimulation(); } updateStatusDisplay(); callRefreshInspectors(); } void Qtenv::setSimulationRunMode(RunMode mode) { if (mode == RUNMODE_STEP) { // if the user wants to step, // first quitting any holding animations in progress endAnimations(); // then if we are in between events, not stopped right before the next event, jumping there displayUpdateController->skipToNextEvent(); // finally we indicate that the next event should be executed, and we should not stop before that doNextEventInStep = true; } runMode = mode; } void Qtenv::setSimulationRunUntil(simtime_t until_time, eventnumber_t until_eventnum, cMessage *until_msg, bool stopOnMsgCancel) { runUntil.time = until_time; runUntil.eventNumber = until_eventnum; runUntil.msg = until_msg; runUntil.stopOnMsgCancel = stopOnMsgCancel; } void Qtenv::setSimulationRunUntilModule(cModule *until_module) { runUntil.module = until_module; } // note: if restart is true and the interval did elapse, also updates "since" (sets it to the current time) inline bool elapsed(long millis, int64_t& since, bool restart) { int64_t now = opp_get_monotonic_clock_usecs(); bool ret = (now - since) >= millis * 1000; if (ret && restart) since = now; return ret; } bool Qtenv::doRunSimulation() { // // IMPORTANT: // The following variables may change during execution (as a result of user interaction // during QApplication::processEvents() // - runmode, runUntil.time, runUntil.eventNumber, runUntil.msg, runUntil.module; // - stopsimulation_flag // cSimulation *sim = getSimulation(); speedometer.start(sim->getSimTime()); loggingEnabled = true; bool firstevent = true; while (true) { if (runMode == RUNMODE_EXPRESS) return true; // should continue, but in a different mode displayUpdateController->setRunMode(runMode); bool reached = displayUpdateController->animateUntilNextEvent(); performAnimations(); // if there is no event, we have to let the control through to // takeNextEvent, and it will terminate the simulation with an exception. if ((!reached || messageAnimator->isHoldActive()) && sim->guessNextEvent()) break; // if there is no event, we have to let the control through to // takeNextEvent, and it will terminate the simulation with an exception. if (runMode == RUNMODE_STEP && !doNextEventInStep && sim->guessNextEvent()) break; // query which module will execute the next event cEvent *event = sim->takeNextEvent(); if (!event) break; // takeNextEvent() interrupted (parsim) // "run until message": stop if desired event was reached if (runUntil.msg && event == runUntil.msg) { sim->putBackEvent(event); break; } // if stepping locally in module, we stop both immediately // *before* and *after* executing the event in that module, // but we always execute at least one event cModule *mod = event->isMessage() ? static_cast(event)->getArrivalModule() : nullptr; bool untilmodule_reached = runUntil.module && moduleContains(runUntil.module, mod); if (untilmodule_reached && !firstevent) { sim->putBackEvent(event); break; } firstevent = false; ASSERT(simTime() <= event->getArrivalTime()); sim->setSimTime(event->getArrivalTime()); animating = (runMode == RUNMODE_NORMAL || runMode == RUNMODE_STEP) || untilmodule_reached; speedometer.addEvent(sim->getSimTime()); doNextEventInStep = false; // do a simulation step sim->executeEvent(event); inspectorsFresh = false; if (animating) performAnimations(); messageAnimator->setMarkedModule(sim->guessNextModule()); // flush so that output from different modules don't get mixed cLogProxy::flushLastLine(); // exit conditions if (stopSimulationFlag) break; if (runUntil.time > SIMTIME_ZERO && sim->guessNextSimtime() >= runUntil.time) break; // TODO: animate until the target simtime if (untilmodule_reached // run until module or message reached: || (runUntil.eventNumber > 0 && sim->getEventNumber() + 1 >= runUntil.eventNumber)) { displayUpdateController->animateUntilNextEvent(); callRefreshInspectors(); break; } checkTimeLimits(); } return false; } bool Qtenv::doRunSimulationExpress() { // // IMPORTANT: // The following variables may change during execution (as a result of user interaction // during QApplication::processEvents(): // - runMode, runUntil.time, runUntil.eventNumber, runUntil.msg, runUntil.module; // - stopSimulationFlag // - opt->autoupdateInExpress // // EXPRESS does not support runUntil.module! // char info[128]; sprintf(info, "** Running in Express mode from event #%" PRId64 " t=%s ...\n", getSimulation()->getEventNumber(), SIMTIME_STR(getSimulation()->getSimTime())); logBuffer.addInfo(info); // update, just to get the above notice displayed callRefreshInspectors(); QApplication::processEvents(); // OK, let's begin speedometer.start(getSimulation()->getSimTime()); loggingEnabled = false; animating = false; messageAnimator->clear(); int64_t last_update = opp_get_monotonic_clock_usecs(); bool result = false; do { cEvent *event = getSimulation()->takeNextEvent(); if (!event) break; // takeNextEvent() interrupted (parsim) // "run until message": stop if desired event was reached if (runUntil.msg && event == runUntil.msg) { getSimulation()->putBackEvent(event); break; } speedometer.addEvent(getSimulation()->getSimTime()); getSimulation()->executeEvent(event); // only on every 256. event to make it fast if ((getSimulation()->getEventNumber() & 0xff) == 0) { // to make the stopDialog more responsive if (elapsed(100, last_update, false)) { inspectorsFresh = false; QApplication::processEvents(); } if (elapsed(opt->updateFreqExpress, last_update, true)) { inspectorsFresh = false; speedometer.beginNewInterval(); // should precede updateStatusDisplay() if (opt->autoupdateInExpress) { callRefreshDisplay(); callRefreshInspectors(); } updateStatusDisplay(); QApplication::processEvents(); last_update = opp_get_monotonic_clock_usecs(); // exclude UI update time [bug #52] if (runMode != RUNMODE_EXPRESS) { result = true; // should continue, but in a different mode break; } } } checkTimeLimits(); } while (!stopSimulationFlag && (runUntil.time <= SIMTIME_ZERO || getSimulation()->guessNextSimtime() < runUntil.time) && (runUntil.eventNumber <= 0 || getSimulation()->getEventNumber() + 1 < runUntil.eventNumber) ); inspectorsFresh = false; sprintf(info, "** Leaving Express mode at event #%" PRId64 " t=%s\n", getSimulation()->getEventNumber(), SIMTIME_STR(getSimulation()->getSimTime())); logBuffer.addInfo(info); return result; } void Qtenv::startAll() { confirm(INFO, "Not implemented."); } void Qtenv::finishSimulation() { // strictly speaking, we shouldn't allow callFinish() after SIM_ERROR, but it comes handy in practice... ASSERT(simulationState != SIM_NONET && simulationState != SIM_FINISHCALLED); if (simulationState == SIM_NEW || simulationState == SIM_READY) { cTerminationException e("The user has finished the simulation"); stoppedWithTerminationException(e); } logBuffer.addInfo("** Calling finish() methods of modules\n"); // now really call finish() try { getSimulation()->callFinish(); callRefreshDisplaySafe(); cLogProxy::flushLastLine(); checkFingerprint(); } catch (std::exception& e) { stoppedWithException(e); notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); } // then endrun try { endRun(); } catch (std::exception& e) { notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); } // we have to cheat if finish() is called after an error (which is already dubious) if (simulationState != SIM_ERROR) simulationState = SIM_FINISHCALLED; updateStatusDisplay(); callRefreshInspectors(); } bool Qtenv::checkRunning() { if (getSimulationState() == Qtenv::SIM_RUNNING) { QMessageBox::warning(mainWindow, tr("Warning"), tr("Sorry, you cannot do this while the simulation is running. Please stop it first."), QMessageBox::Ok); return true; } if (getSimulationState() == Qtenv::SIM_BUSY) { QMessageBox::warning(mainWindow, tr("Warning"), tr("The simulation is waiting for external synchronization -- press STOP to interrupt it."), QMessageBox::Ok); return true; } return false; } std::vector Qtenv::resolveRunFilter(const char *configName, const char *runFilter) { return EnvirBase::resolveRunFilter(configName, runFilter); } void Qtenv::loadNedFile(const char *fname, const char *expectedPackage, bool isXML) { try { getSimulation()->loadNedFile(fname, expectedPackage, isXML); } catch (std::exception& e) { displayException(e); } } // XXX too similar to newRun void Qtenv::newNetwork(const char *networkname) { try { refreshDisplayCount = 0; messageAnimator->clear(); displayUpdateController->reset(); answers.clear(); logBuffer.clear(); componentHistory.clear(); // finish & cleanup previous run if we haven't done so yet if (simulationState != SIM_NONET) { storeInspectors(true); if (simulationState != SIM_FINISHCALLED) endRun(); getSimulation()->deleteNetwork(); simulationState = SIM_NONET; } cModuleType *network = resolveNetwork(networkname); ASSERT(network); // set up new network with config General. isConfigRun = false; getConfigEx()->activateConfig("General", 0); readPerRunOptions(); opt->networkName = network->getName(); // override config setting setupNetwork(network); startRun(); simulationState = SIM_NEW; callRefreshDisplay(); // the one without exception handling! } catch (std::exception& e) { notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); simulationState = SIM_ERROR; } // update GUI auto module = getSimulation()->getSystemModule(); mainNetworkView->setObject(module); mainInspector->setObject(module); displayUpdateController->skipToNextEvent(); // should be changed to animate at some point... animating = true; // affects how network graphics is drawn! messageAnimator->redrawMessages(); messageAnimator->setMarkedModule(getSimulation()->guessNextModule()); updateNetworkRunDisplay(); updateStatusDisplay(); callRefreshInspectors(); } // XXX too similar to newNetwork void Qtenv::newRun(const char *configname, int runnumber) { try { refreshDisplayCount = 0; messageAnimator->clear(); displayUpdateController->reset(); answers.clear(); logBuffer.clear(); componentHistory.clear(); // finish & cleanup previous run if we haven't done so yet if (simulationState != SIM_NONET) { storeInspectors(true); if (simulationState != SIM_FINISHCALLED) endRun(); getSimulation()->deleteNetwork(); simulationState = SIM_NONET; } // set up new network isConfigRun = true; getConfigEx()->activateConfig(configname, runnumber); readPerRunOptions(); if (opt->networkName.empty()) { confirm(ERROR, "No network specified in the configuration."); return; } cModuleType *network = resolveNetwork(opt->networkName.c_str()); ASSERT(network); setupNetwork(network); startRun(); simulationState = SIM_NEW; callRefreshDisplay(); // the one without exception handling! } catch (std::exception& e) { notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); simulationState = SIM_ERROR; } // update GUI auto module = getSimulation()->getSystemModule(); mainNetworkView->setObject(module); mainInspector->setObject(module); displayUpdateController->skipToNextEvent(); // should be changed to animate at some point... animating = true; // affects how network graphics is drawn! messageAnimator->redrawMessages(); messageAnimator->setMarkedModule(getSimulation()->guessNextModule()); updateNetworkRunDisplay(); updateStatusDisplay(); callRefreshInspectors(); } void Qtenv::setupNetwork(cModuleType *network) { EnvirBase::setupNetwork(network); // collapsing all nodes in the object tree, because even if a new network is // loaded, there is a chance that some objects will be on the same place // (have the same pointer) as some of the old ones, so random nodes may // be expanded in the new tree depending on what was expanded before // TODO this should be done in the tree view inspector // mainwindow->getObjectTree()->collapseAll(); } Inspector *Qtenv::inspect(cObject *obj, InspectorType type, bool ignoreEmbedded) { // first, try finding and displaying existing inspector Inspector *inspector = findFirstInspector(obj, type, ignoreEmbedded); if (inspector) { if (inspector->isToplevelInspector()) inspector->showWindow(); return inspector; } InspectorFactory *factory = findInspectorFactoryFor(obj, type); if (!factory) { confirm(ERROR, opp_stringf("Class '%s' has no associated inspectors.", obj->getClassName()).c_str()); return nullptr; } InspectorType actualType = factory->getInspectorType(); inspector = findFirstInspector(obj, actualType, ignoreEmbedded); if (inspector) { if (inspector->isToplevelInspector()) inspector->showWindow(); return inspector; } // create inspector inspector = factory->createInspector(mainWindow, true); if (!inspector) { // message: object has no such inspector confirm(ERROR, opp_stringf("Class '%s' has no '%s' inspector.", obj->getClassName(), insptypeNameFromCode(type)).c_str()); return nullptr; } connect(inspector, SIGNAL(selectionChanged(cObject *)), this, SLOT(onSelectionChanged(cObject *))); connect(inspector, SIGNAL(objectDoubleClicked(cObject *)), this, SLOT(onObjectDoubleClicked(cObject *))); connect(inspector, SIGNAL(inspectedObjectChanged(cObject *,cObject *)), this, SLOT(updateStoredInspector(cObject *,cObject *))); // everything ok, finish inspector inspectors.push_back(inspector); // TODO geometry // insp->createWindow(Inspector::makeWindowName().c_str(), geometry); inspector->setObject(obj); return inspector; } Inspector *Qtenv::addEmbeddedInspector(InspectorFactory *factory, QWidget *parent) { Inspector *insp = factory->createInspector(parent, false); inspectors.push_back(insp); connect(insp, SIGNAL(selectionChanged(cObject *)), this, SLOT(onSelectionChanged(cObject *))); connect(insp, SIGNAL(objectDoubleClicked(cObject *)), this, SLOT(onObjectDoubleClicked(cObject *))); insp->refresh(); return insp; } Inspector *Qtenv::findFirstInspector(const cObject *obj, InspectorType type, bool ignoreEmbedded) { for (auto insp : inspectors) { if (insp->getObject() == obj && insp->getType() == type && (!ignoreEmbedded || insp->isToplevelInspector())) return insp; } return nullptr; } void Qtenv::deleteInspector(Inspector *insp) { inspectors.remove(insp); delete insp; } void Qtenv::callRefreshDisplay() { ASSERT(simulationState != SIM_ERROR && simulationState != SIM_NONET); getSimulation()->getSystemModule()->callRefreshDisplay(); ++refreshDisplayCount; inspectorsFresh = false; } void Qtenv::callRefreshDisplaySafe() { try { // if we are _in_ a callback, inside deleteNetwork, the state might not have been updated yet... if (simulationState != SIM_ERROR && simulationState != SIM_NONET && getSimulation()->getSystemModule()) callRefreshDisplay(); } catch (std::exception& e) { ASSERT(simulationState != SIM_ERROR); // the exception must have come from refreshDisplay calls in the model simulationState = SIM_ERROR; stoppedWithException(e); notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); } } void Qtenv::refreshInspectors() { // update inspectors for (auto it : inspectors) it->refresh(); messageAnimator->redrawMessages(); // clear the change flags on all inspected canvases for (auto it : inspectors) it->postRefresh(); // try opening "pending" inspectors restoreInspectors(); inspectorsFresh = true; } void Qtenv::callRefreshInspectors() { try { refreshInspectors(); } catch (std::exception& e) { ASSERT(simulationState != SIM_ERROR); // the exception must have come from refreshDisplay calls in the model simulationState = SIM_ERROR; stoppedWithException(e); notifyLifecycleListeners(LF_ON_SIMULATION_ERROR); displayException(e); // have to call it again, this time it should not throw, because the state is now SIM_ERROR refreshInspectors(); } } void Qtenv::createSnapshot(const char *label) { getSimulation()->snapshot(getSimulation(), label); } void Qtenv::performAnimations() { displayUpdateController->setRunMode(runMode); messageAnimator->updateAnimations(); displayUpdateController->animateUntilHoldEnds(); } void Qtenv::endAnimations() { // TODO messageAnimator->skipCurrentHoldingAnims(); displayUpdateController->skipHold(); messageAnimator->updateAnimations(); } std::string Qtenv::getWindowTitle() { const char *configName = getConfigEx()->getActiveConfigName(); int runNumber = getConfigEx()->getActiveRunNumber(); const char *inifile = getConfigEx()->getFileName(); #ifdef NDEBUG bool ndebug = true; #else bool ndebug = false; #endif std::stringstream os; os << OMNETPP_PRODUCT "/Qtenv (" << (ndebug ? "release" : "debug") << ") - " << getWindowTitlePrefix(); if (opp_isempty(configName)) os << "No network"; else os << configName << " #" << runNumber; if (!opp_isempty(inifile)) os << " - " << inifile; os << " - " << getWorkingDir(); return os.str(); } void Qtenv::updateNetworkRunDisplay() { mainWindow->updateNetworkRunDisplay(); mainWindow->setWindowTitle(getWindowTitle().c_str()); } void Qtenv::updateSimtimeDisplay() { mainWindow->updateSimtimeDisplay(); } void Qtenv::updateStatusDisplay() { mainWindow->updateStatusDisplay(); } void Qtenv::addEventToLog(cEvent *event) { cObject *target = event->getTargetObject(); cMessage *msg = event->isMessage() ? static_cast(event) : nullptr; cModule *module = msg ? msg->getArrivalModule() : nullptr; char banner[2*MAX_OBJECTFULLPATH+2*MAX_CLASSNAME+60]; banner[0] = '\0'; // produce banner text if enabled if (opt->printEventBanners) { char *p = banner; p += sprintf(p, "** Event #%" PRId64 " t=%s ", getSimulation()->getEventNumber(), SIMTIME_STR(getSimulation()->getSimTime())); if (opt->shortBanners) { // just object names if (target) p += sprintf(p, "%s ", target->getFullPath().c_str()); p += sprintf(p, "on %s", event->getFullName()); } else { // print event and module type names and IDs, too if (module) p += sprintf(p, "%s (%s, id=%d) ", module->getFullPath().c_str(), module->getComponentType()->getName(), module->getId()); else if (target) p += sprintf(p, "%s (%s) ", target->getFullPath().c_str(), target->getClassName()); if (msg) p += sprintf(p, " on %s%s (%s, id=%ld)", msg->isSelfMessage() ? "selfmsg " : "", msg->getFullName(), msg->getClassName(), msg->getId()); else p += sprintf(p, " on %s (%s)", event->getFullName(), event->getClassName()); } strcpy(p, "\n"); } // insert into log buffer logBuffer.addEvent(getSimulation()->getEventNumber(), getSimulation()->getSimTime(), module, banner); } void Qtenv::displayException(std::exception& ex) { // print exception text into main window cException *e = dynamic_cast(&ex); if (e && e->getSimulationStage() != CTX_NONE) { std::string txt = opp_stringf(" %s\n", e->getFormattedMessage().c_str()); logBuffer.addInfo(txt.c_str()); } if (cRuntimeError *runtimeError = dynamic_cast(&ex)) // do not pop up dialog if this error was already displayed // (by the dialog that asks the user if they want a debugger) if (runtimeError->displayed) return; confirm(ERROR, getFormattedMessage(ex).c_str()); } void Qtenv::componentInitBegin(cComponent *component, int stage) { auto logLevel = getPref(QString("ComponentLogLevels/") + component->getFullPath().c_str()); if (logLevel.isValid() && logLevel.canConvert(QVariant::Int)) setComponentLogLevel(component, (LogLevel)logLevel.toInt()); if (!opt->printInitBanners || runMode == RUNMODE_EXPRESS) return; // produce banner text char banner[MAX_OBJECTFULLPATH+60]; sprintf(banner, "Initializing %s %s, stage %d\n", component->isModule() ? "module" : "channel", component->getFullPath().c_str(), stage); // insert into log buffer logBuffer.addInitialize(component, banner); } void Qtenv::setSilentEventFilters(const char *filterLines) { // parse into tmp MatchExpressions tmp; try { StringTokenizer tokenizer(filterLines, "\n"); while (tokenizer.hasMoreTokens()) { const char *line = tokenizer.nextToken(); if (!opp_isblank(line)) { tmp.push_back(new MatchExpression()); tmp.back()->setPattern(line, false, true, true); } } } catch (std::exception& e) { // parse error for (auto & i : tmp) delete i; throw; } // parsing successful, store the result for (auto & silentEventFilter : silentEventFilters) delete silentEventFilter; silentEventFilterLines = opp_trim(filterLines) + "\n"; silentEventFilters = tmp; } bool Qtenv::isSilentEvent(cMessage *msg) { MatchableObjectAdapter wrappedMsg(MatchableObjectAdapter::FULLNAME, msg); for (auto & silentEventFilter : silentEventFilters) if (silentEventFilter->matches(&wrappedMsg)) return true; return false; } //========================================================================= void Qtenv::readOptions() { EnvirBase::readOptions(); cConfiguration *cfg = getConfig(); opt->extraStack = (size_t)cfg->getAsDouble(CFGID_QTENV_EXTRA_STACK); const char *s = args->optionValue('c'); opt->defaultConfig = s ? s : cfg->getAsString(CFGID_QTENV_DEFAULT_CONFIG); const char *r = args->optionValue('r'); opt->runFilter = r ? r : cfg->getAsString(CFGID_QTENV_DEFAULT_RUN); } void Qtenv::initialSetUpConfiguration() { if (checkRunning()) return; std::string config; int run = -1; auto conf = getConfigEx(); if (conf->getConfigNames().empty()) { mainWindow->configureNetwork(); return; } else { try { // defaultConfig and runFilter are what were specified in either the omnetpp.ini file or as a command line argument RunSelectionDialog dialog(conf, opt->defaultConfig, opt->runFilter, mainWindow); #ifdef QT_OS_MAC // Makes the Apple Menu work on Mac (together with TransformProcessType) right // after launch even if there is no need to actually pick a configuration. // Even if the dialog doesn't really appear on the screen (in fact I hope it // doesn't, that would cause flickering), if shown first, it will do some // magic with window focus passing when destroyed, which is similar to // switching apps, which then makes the global menu work for some reason. dialog.show(); #endif // only show if needed, but if cancelled, stop. if (dialog.needsShowing() && !dialog.exec()) return; config = dialog.getConfigName(); run = dialog.getRunNumber(); } catch (std::exception& e) { // if nonexistent config was given as argument or the run filter couldn't be applied, etc... displayException(e); return; } } newRun(config.c_str(), run); mainWindow->reflectRecordEventlog(); QTimer::singleShot(0, mainWindow, &MainWindow::activateWindow); } void Qtenv::askParameter(cPar *par, bool unassigned) { // use a value entered by the user earlier ("[x] use this value for similar parameters") std::string key = std::string(((cComponent *)par->getOwner())->getNedTypeName()) + ":" + par->getName(); if (answers.find(key) != answers.end()) { std::string answer = answers[key]; par->parse(answer.c_str()); return; } // really ask bool success = false; bool useForAll = false; while (!success) { cProperties *props = par->getProperties(); cProperty *prop = props->get("prompt"); std::string prompt = prop ? prop->getValue(cProperty::DEFAULTKEY) : ""; if (prompt.empty()) prompt = std::string("Enter parameter '") + par->getFullPath() + "':"; std::string reply; std::string title = unassigned ? "Unassigned Parameter" : "Requested to Ask Parameter"; bool ok = inputDialog(title.c_str(), prompt.c_str(), "Use this value for all similar parameters", par->str().c_str(), reply, useForAll); if (!ok) throw cRuntimeError(E_CANCEL); try { par->parse(reply.c_str()); success = true; if (useForAll) answers[key] = reply; } catch (std::exception& e) { printfmsg("%s -- please try again.", e.what()); } } } bool Qtenv::idle() { // process UI events eState origsimstate = simulationState; simulationState = SIM_BUSY; displayUpdateController->idle(); simulationState = origsimstate; return stopSimulationFlag; } bool Qtenv::ensureDebugger(cRuntimeError *error) { bool debuggerPresent = detectDebugger() == DebuggerPresence::PRESENT; QString title; QString message; if (error) { title = "Runtime Error"; message = QString("A runtime error occurred:\n\n") + error->getFormattedMessage().c_str(); error->displayed = true; if (debuggerPresent) message += "\n\nDebug now?"; } else if (!debuggerPresent) { title = "Debugging Requested"; message = "You requested debugging."; } if (!debuggerPresent) { std::string debuggerCommand = makeDebuggerCommand(); if (!debuggerCommand.empty()) message += QString("\n\nLaunch a debugger with the following command?\n\n") + debuggerCommand.c_str(); } QMessageBox messageBox(QMessageBox::Icon::Critical, title, message, QMessageBox::NoButton, getMainWindow()); QPushButton *acceptButton; if (debuggerPresent) acceptButton = messageBox.addButton("Break into debugger", QMessageBox::AcceptRole); else { acceptButton = messageBox.addButton("Launch debugger then break", QMessageBox::AcceptRole); messageBox.addButton("Just break (likely crash)", QMessageBox::DestructiveRole); } messageBox.addButton(error ? "Ignore" : "Cancel", QMessageBox::RejectRole); messageBox.setDefaultButton(acceptButton); QMessageBox::ButtonRole clickedRole = QMessageBox::AcceptRole; if (!message.isEmpty()) { messageBox.exec(); clickedRole = messageBox.buttonRole(messageBox.clickedButton()); } if (clickedRole == QMessageBox::RejectRole) return false; // the user doesn't want to debug now else if (debuggerPresent || clickedRole == QMessageBox::DestructiveRole) return true; // either we can safely TRAP, or the user told us to do it (even if we didn't detect a debugger) else if (debuggerAttachmentPermitted() != DebuggerAttachmentPermission::DENIED) attachDebugger(); else { // no debugger, and can't attach either QMessageBox(QMessageBox::Icon::Critical, "Debugger Attachment Blocked", "No attached debugger was detected, and your current system setup does not " "permit attaching a debugger to a non-child process.\nStart your simulation " "in a debugger, or see this for how to allow on-demand attachment:\n\n" "https://askubuntu.com/questions/41629/after-upgrade-gdb-wont-attach-to-process/41656#41656", QMessageBox::StandardButton::Close, getMainWindow()).exec(); // The user might have allowed attachment and attached a debugger // while the dialog was up, so let's check again. } return detectDebugger() != DebuggerPresence::NOT_PRESENT; } void Qtenv::objectDeleted(cObject *object) { if (object == runUntil.msg) { // message to "run until" deleted -- stop the simulation by other means runUntil.msg = nullptr; runUntil.eventNumber = getSimulation()->getEventNumber(); if (simulationState == SIM_RUNNING || simulationState == SIM_BUSY) confirm(INFO, "Message to run until has just been deleted."); } emit objectDeletedSignal(object); // TODO: use signals for (InspectorList::iterator it = inspectors.begin(); it != inspectors.end(); ) { InspectorList::iterator next = it; ++next; Inspector *insp = *it; insp->objectDeleted(object); it = next; } } void Qtenv::simulationEvent(cEvent *event) { EnvirBase::simulationEvent(event); if (loggingEnabled) addEventToLog(event); // must be done here, because eventnum and simtime are updated inside executeEvent() displayUpdateController->simulationEvent(); if (animating && opt->animationEnabled) { if (event->isMessage()) { cMessage *msg = static_cast(event); cGate *arrivalGate = msg->getArrivalGate(); if (!arrivalGate) return; if (loggingEnabled) logBuffer.delivery(msg); // if arrivalgate is connected, msg arrived on a connection, otherwise via sendDirect() if (arrivalGate->getPreviousGate()) messageAnimator->delivery(msg); else messageAnimator->deliveryDirect(msg); // deliveries must be played immediately, since we // are right before the processing of the message, // and it would disappear otherwise performAnimations(); } } } void Qtenv::messageScheduled(cMessage *msg) { EnvirBase::messageScheduled(msg); } void Qtenv::messageCancelled(cMessage *msg) { if (msg == runUntil.msg && runUntil.stopOnMsgCancel) { if (simulationState == SIM_RUNNING || simulationState == SIM_BUSY) confirm(INFO, opp_stringf("Run-until message '%s' got cancelled.", msg->getName()).c_str()); runUntil.msg = nullptr; runUntil.eventNumber = getSimulation()->getEventNumber(); // stop the simulation using the event number limit } EnvirBase::messageCancelled(msg); } void Qtenv::beginSend(cMessage *msg) { EnvirBase::beginSend(msg); if (loggingEnabled) logBuffer.beginSend(msg); if (animating && opt->animationEnabled && !isSilentEvent(msg)) messageAnimator->beginSend(msg); } void Qtenv::messageSendDirect(cMessage *msg, cGate *toGate, simtime_t propagationDelay, simtime_t transmissionDelay) { EnvirBase::messageSendDirect(msg, toGate, propagationDelay, transmissionDelay); if (loggingEnabled) logBuffer.messageSendDirect(msg, toGate, propagationDelay, transmissionDelay); if (animating && opt->animationEnabled && !isSilentEvent(msg)) messageAnimator->sendDirect(msg, msg->getSenderModule(), toGate, propagationDelay, transmissionDelay); } void Qtenv::messageSendHop(cMessage *msg, cGate *srcGate) { EnvirBase::messageSendHop(msg, srcGate); if (loggingEnabled) logBuffer.messageSendHop(msg, srcGate); if (animating && opt->animationEnabled && !isSilentEvent(msg)) { bool isLastHop = srcGate->getNextGate() == msg->getArrivalGate(); messageAnimator->sendHop(msg, srcGate, isLastHop); } } void Qtenv::messageSendHop(cMessage *msg, cGate *srcGate, simtime_t propagationDelay, simtime_t transmissionDelay, bool discard) { EnvirBase::messageSendHop(msg, srcGate, propagationDelay, transmissionDelay, discard); if (loggingEnabled) logBuffer.messageSendHop(msg, srcGate, propagationDelay, transmissionDelay, discard); if (animating && opt->animationEnabled && !isSilentEvent(msg)) { bool isLastHop = srcGate->getNextGate() == msg->getArrivalGate(); messageAnimator->sendHop(msg, srcGate, isLastHop, propagationDelay, transmissionDelay, discard); } } void Qtenv::endSend(cMessage *msg) { EnvirBase::endSend(msg); if (loggingEnabled) logBuffer.endSend(msg); if (animating && opt->animationEnabled && !isSilentEvent(msg)) messageAnimator->endSend(msg); } void Qtenv::messageDeleted(cMessage *msg) { EnvirBase::messageDeleted(msg); if (messageAnimator) messageAnimator->removeMessagePointer(msg); } void Qtenv::componentMethodBegin(cComponent *fromComp, cComponent *toComp, const char *methodFmt, va_list va, bool silent) { va_list va2; va_copy(va2, va); // see bug #107 EnvirBase::componentMethodBegin(fromComp, toComp, methodFmt, va2, silent); va_end(va2); if (animating && opt->animateMethodCalls && messageAnimator) { static char methodText[MAX_METHODCALL]; vsnprintf(methodText, MAX_METHODCALL, opp_nulltoempty(methodFmt), va); methodText[MAX_METHODCALL-1] = '\0'; messageAnimator->methodcallBegin(fromComp, toComp, methodText, silent); } } void Qtenv::componentMethodEnd() { EnvirBase::componentMethodEnd(); if (animating && opt->animateMethodCalls && messageAnimator) messageAnimator->methodcallEnd(); } void Qtenv::moduleCreated(cModule *newmodule) { EnvirBase::moduleCreated(newmodule); cModule *mod = newmodule->getParentModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(mod, inspector); if (insp) insp->submoduleCreated(newmodule); } } void Qtenv::moduleDeleted(cModule *module) { EnvirBase::moduleDeleted(module); componentHistory.componentDeleted(module); moduleLayouter.clearLayout(module); moduleLayouter.forgetPosition(module); cModule *mod = module->getParentModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(mod, inspector); if (insp) insp->submoduleDeleted(module); } } void Qtenv::moduleReparented(cModule *module, cModule *oldParent, int oldId) { EnvirBase::moduleReparented(module, oldParent, oldId); componentHistory.componentReparented(module, oldParent, oldId); // pretend it got deleted from under the 1st module, and got created under the 2nd for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(oldParent, inspector); if (insp) insp->submoduleDeleted(module); } cModule *mod = module->getParentModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(mod, inspector); if (insp) insp->submoduleCreated(module); } } void Qtenv::connectionCreated(cGate *srcgate) { EnvirBase::connectionCreated(srcgate); // notify compound module where the connection (whose source is this gate) is displayed cModule *notifymodule = nullptr; if (srcgate->getType() == cGate::OUTPUT) notifymodule = srcgate->getOwnerModule()->getParentModule(); else notifymodule = srcgate->getOwnerModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(notifymodule, inspector); if (insp) insp->connectionCreated(srcgate); } } void Qtenv::connectionDeleted(cGate *srcgate) { EnvirBase::connectionDeleted(srcgate); if (srcgate->getChannel()) componentHistory.componentDeleted(srcgate->getChannel()); // notify compound module where the connection (whose source is this gate) is displayed // note: almost the same code as above cModule *notifymodule; if (srcgate->getType() == cGate::OUTPUT) notifymodule = srcgate->getOwnerModule()->getParentModule(); else notifymodule = srcgate->getOwnerModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(notifymodule, inspector); if (insp) insp->connectionDeleted(srcgate); } } void Qtenv::displayStringChanged(cComponent *component) { EnvirBase::displayStringChanged(component); if (cModule *module = dynamic_cast(component)) moduleDisplayStringChanged(module); else if (cChannel *channel = dynamic_cast(component)) channelDisplayStringChanged(channel); } void Qtenv::getImageSize(const char *imageName, double& outWidth, double& outHeight) { auto size = icons.getImage(imageName)->size(); outWidth = size.width(); outHeight = size.height(); } void Qtenv::getTextExtent(const cFigure::Font& font, const char *text, double& outWidth, double& outHeight, double& outAscent) { if (!*text) { outWidth = outHeight = outAscent = 0; return; } std::string typeFace = font.typeface.c_str(); if (typeFace.empty()) typeFace = canvasFont.family().toStdString(); int pointSize = font.pointSize; if (pointSize <= 0) pointSize = canvasFont.pointSize(); QFont f(typeFace.c_str(), pointSize); f.setBold(font.style & cFigure::FONT_BOLD); f.setItalic(font.style & cFigure::FONT_ITALIC); f.setUnderline(font.style & cFigure::FONT_UNDERLINE); QFontMetricsF metrics(f); QStringList lines = QString(text).split(QChar('\n')); double w = 0; for (const auto &l : lines) w = std::max(metrics.width(l), w); outWidth = w; // No need to account for interline leading, or use lineSpacing, // as the default QGraphicsSimpleTextItem (wrongly) uses line // height to advance the baseline, so this is "correct". outHeight = lines.length() * metrics.height(); outAscent = metrics.ascent(); } void Qtenv::appendToImagePath(const char *directory) { icons.loadImages(directory); } void Qtenv::loadImage(const char *fileName, const char *imageName) { icons.loadImage(fileName, imageName); } cFigure::Rectangle Qtenv::getSubmoduleBounds(const cModule *submodule) { cObject *parentObject = static_cast(submodule->getParentModule()); // If no inspector inspects the parent of submodule, will fall back to these, which is reasonable methinks. double zoomFactor = 1.0; double iconScale = 1.0; // This is the inspector (if any) that will convert the submodule rectangle // into canvas coordinates. It will only be guaranteed to be correct when // viewed in this inspector, since the zoom level and the icon scaling // both affect the submodule rectangle relative to the canvas, and they // are not shared among different inspector instances viewing the same // module, in contrast with the layout, which is global (now...). // All other inspectors will display the canvas the same way as this one, // except the submodule boundary in those will not necessarily match up with // the rectangle on the canvas that we're going to return from here right now. // The one embedded module inspector takes precedence, but if it's not // suitable, we try finding another one, a top-level. ModuleInspector *primaryInsp = (mainNetworkView->getObject() == parentObject) ? mainNetworkView : dynamic_cast(findFirstInspector(parentObject, INSP_GRAPHICAL, true)); if (primaryInsp) { zoomFactor = primaryInsp->getZoomFactor(); iconScale = primaryInsp->getImageSizeFactor(); } // If we passed these two parameters directly to the layouter, it would return the rectangle in scene // coordinates. (This is really useful for us to draw the non-canvas parts of the network, like // connections and messages, but not quite for the model.) Those are the ones that get bigger when // zoomed in, unlike canvas coords, which stay the same, only the "output" is transformed with zoom. // So to make both the size and the position match perfectly, we have to pretend that there is no zoom. // This way when the rectangle is mapped back again from canvas coordinates into scene coordinates with a // simple scaling by the zoom factor (in both position and size of course), everyone will be happy. // Note though that the ratio of the icon factor and the zoom factor is still important, because the submodule // can have the appearance of one or both of an icon and a shape (box or oval). The shape scales only with // zoom, so dividing that with the zoom level obviously gives one, but the icon is scaled independently, // with the icon size factor, so that's why the division is there. QRectF r = moduleLayouter.getModuleRectangle(const_cast(submodule), 1.0, iconScale / zoomFactor); return cFigure::Rectangle(r.x(), r.y(), r.width(), r.height()); } double Qtenv::getZoomLevel(const cModule *module) { const cObject *object = static_cast(module); // This is the inspector (if any) which we will ask for the zoom level. // It will only be guaranteed to be correct in this inspector, since the // zoom level is not shared among different inspector instances viewing the // same module. The one embedded module inspector takes precedence, but if // it's not suitable, we try finding another one, a top-level. ModuleInspector *primaryInsp = (mainNetworkView->getObject() == object) ? mainNetworkView : dynamic_cast(findFirstInspector(object, INSP_GRAPHICAL, true)); return primaryInsp ? primaryInsp->getZoomFactor() : NAN; } double Qtenv::getAnimationTime() const { return displayUpdateController->getAnimationTime(); } double Qtenv::getAnimationSpeed() const { return displayUpdateController->getAnimationSpeed(); } double Qtenv::getRemainingAnimationHoldTime() const { return std::max(0.0, displayUpdateController->getAnimationHoldEndTime() - displayUpdateController->getAnimationTime()); } void Qtenv::channelDisplayStringChanged(cChannel *channel) { cGate *gate = channel->getSourceGate(); // notify module inspector which displays connection cModule *notifymodule; if (gate->getType() == cGate::OUTPUT) notifymodule = gate->getOwnerModule()->getParentModule(); else notifymodule = gate->getOwnerModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(notifymodule, inspector); if (insp) insp->displayStringChanged(gate); } // graphical gate inspector windows: normally a user doesn't have many such windows open // (typically, none at all), so we can afford simply refreshing all of them for (auto insp : inspectors) { GateInspector *gateinsp = dynamic_cast(insp); if (gateinsp) gateinsp->displayStringChanged(gate); } } void Qtenv::moduleDisplayStringChanged(cModule *module) { // refresh inspector where this module is a submodule cModule *parentmodule = module->getParentModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(parentmodule, inspector); if (insp) insp->displayStringChanged(module); } // refresh inspector where this module is the parent (i.e. this is a // background display string change) for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(module, inspector); if (insp) insp->displayStringChanged(); } } void Qtenv::onSelectionChanged(cObject *object) { mainInspector->setObject(object); } void Qtenv::onObjectDoubleClicked(cObject *object) { if (cModule *module = dynamic_cast(object)) { mainNetworkView->setObject(module); } else { inspect(object, INSP_DEFAULT, true); } } void Qtenv::bubble(cComponent *component, const char *text) { EnvirBase::bubble(component, text); if (!opt->showBubbles) return; if (component->getParentModule()) { cModule *enclosingmod = component->getParentModule(); for (auto & inspector : inspectors) { ModuleInspector *insp = isModuleInspectorFor(enclosingmod, inspector); if (insp) insp->bubble(component, text); } } } void Qtenv::confirm(DialogKind kind, const char *msg) { if (!mainWindow) { // fallback in case Qt didn't fire up correctly const char *prefix = kind==ERROR ? "Error: " : kind==WARNING ? "Warning: " : ""; out << "\n " << prefix << msg << endl << endl; } else { switch (kind) { case INFO: QMessageBox::information(mainWindow, "Confirm", msg, QMessageBox::StandardButton::Ok); break; case WARNING: QMessageBox::warning(mainWindow, "Confirm", msg, QMessageBox::StandardButton::Ok); break; case ERROR: QMessageBox::critical(mainWindow, "Confirm", msg, QMessageBox::StandardButton::Ok); break; } } } void Qtenv::alert(const char *msg) { confirm(WARNING, msg); } void Qtenv::log(cLogEntry *entry) { EnvirBase::log(entry); if (!loggingEnabled) return; std::string prefix = logFormatter.formatPrefix(entry); const char *s = entry->text; int n = entry->textLength; // rough guard against forgotten "\n"'s in the code const int maxLen = 5000; if (n > maxLen) { const char *ellipsis = "... [line too long, truncated]\n"; strcpy(const_cast(s) + maxLen - strlen(ellipsis), ellipsis); // khmm... n = maxLen; } // insert into log buffer cModule *module = getSimulation()->getContextModule(); if (module) logBuffer.addLogLine(prefix.c_str(), s, n); else logBuffer.addInfo(s, n); } bool Qtenv::inputDialog(const char *title, const char *prompt, const char *checkboxLabel, const char *defaultValue, std::string& outResult, bool& inoutCheckState) { QDialog *dialog = new QDialog(mainWindow); dialog->setFont(boldFont); dialog->setWindowTitle(title); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(new QLabel(prompt)); QLineEdit *edit = new QLineEdit(defaultValue); layout->addWidget(edit); QCheckBox *checkBox = nullptr; if (checkboxLabel) { checkBox = new QCheckBox(checkboxLabel); layout->addWidget(checkBox); } QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); layout->addWidget(buttonBox); dialog->setLayout(layout); if (dialog->exec() == QDialog::Rejected) return false; outResult = edit->text().toStdString(); if (checkBox) inoutCheckState = checkBox->isChecked(); delete dialog; return true; } std::string Qtenv::gets(const char *prompt, const char *defaultReply) { cModule *mod = getSimulation()->getContextModule(); std::string title = mod ? mod->getFullPath() : getSimulation()->getNetworkType()->getName(); std::string result; bool dummy; bool ok = inputDialog(title.c_str(), prompt, nullptr, defaultReply, result, dummy); if (!ok) throw cRuntimeError(E_CANCEL); return result; } bool Qtenv::askYesNo(const char *question) { cModule *mod = getSimulation()->getContextModule(); std::string title = mod ? mod->getFullPath() : getSimulation()->getNetworkType()->getName(); switch (QMessageBox::question(getMainWindow(), title.c_str(), question, QMessageBox::Yes | QMessageBox::No)) { // XXX: should allow cancel? case QMessageBox::Yes: return true; case QMessageBox::No: return false; default: throw cRuntimeError(E_CANCEL); } } unsigned Qtenv::getExtraStackForEnvir() const { return opt->extraStack; } QPoint Qtenv::getDefaultStopDialogCorner(const QPoint& offset) { auto insp = getQtenv()->getMainModuleInspector(); return insp->mapToGlobal(insp->contentsRect().topRight() + offset); // not covering the toolbar } void Qtenv::setPref(const QString& key, const QVariant& value) { QSettings *settings = (isLocalPrefKey(key) ? localPrefs : globalPrefs); if (value.isValid()) settings->setValue(key, value); else settings->remove(key); } QVariant Qtenv::getPref(const QString& key, const QVariant& defaultValue) { QSettings *settings = (isLocalPrefKey(key) ? localPrefs : globalPrefs); return settings->value(key, defaultValue); } QStringList Qtenv::getKeysInPrefGroup(const QString &prefGroup) { globalPrefs->beginGroup(prefGroup); auto keys = globalPrefs->allKeys(); globalPrefs->endGroup(); return keys; } void Qtenv::runSimulationLocal(RunMode runMode, cObject *object, Inspector *insp) { MainWindow *mainWindow = getQtenv()->getMainWindow(); if (mainWindow->isRunning()) { mainWindow->setGuiForRunmode(runMode, true); getQtenv()->setSimulationRunMode(runMode); mainWindow->setRunUntilModule(insp); } else { if (!mainWindow->networkReady()) return; mainWindow->setGuiForRunmode(runMode, true); if (object == nullptr && insp) object = insp->getObject(); cModule *mod = dynamic_cast(object); if (!mod) { // TODO log "object is not a module" return; } getQtenv()->runSimulation(runMode, 0, 0, nullptr, mod); mainWindow->setGuiForRunmode(RUNMODE_NOT_RUNNING); } } void Qtenv::refOsgNode(osg::Node *scene) { IOsgViewer::refNode(scene); } void Qtenv::unrefOsgNode(osg::Node *scene) { IOsgViewer::unrefNode(scene); } void Qtenv::inspect() { QVariant variant = static_cast(QObject::sender())->data(); if (variant.isValid()) { auto data = variant.value(); inspect(data.object, data.type, true); } } void Qtenv::runUntilModule() { QVariant variant = static_cast(QObject::sender())->data(); if (variant.isValid()) { RunUntilNextEventActionData data = variant.value(); runSimulationLocal(data.runMode, data.object, data.insp); } } void Qtenv::runUntilMessage() { QVariant variant = static_cast(QObject::sender())->data(); if (variant.isValid()) { auto data = variant.value(); getQtenv()->getMainWindow()->runUntilMsg(static_cast(data.object), data.runMode); } } void Qtenv::excludeMessage() { QVariant variant = static_cast(QObject::sender())->data(); if (variant.isValid()) getQtenv()->getMainWindow()->excludeMessageFromAnimation(variant.value()); } void Qtenv::utilitiesSubMenu() { auto action = dynamic_cast(sender()); if (action) { auto variant = action->data(); if (variant.isValid()) { CopyActionData data = variant.value(); InspectorUtil::copyToClipboard(static_cast(data.object), data.copy); } } } void Qtenv::setComponentLogLevel() { auto action = dynamic_cast(sender()); if (action) { auto variant = action->data(); if (variant.isValid() && variant.canConvert()) { auto data = variant.value(); setComponentLogLevel(data.component, data.logLevel, true); } } } // the save parameter will be false when restoring the levels from the prefs // without it, the loglevels of all children would be erased when restoring // the level of one of its ancestor components void Qtenv::setComponentLogLevel(cComponent *component, LogLevel level, bool save) { cCollectObjectsOfTypeVisitor v; // should include the component itself v.process(component); cComponent **objs = (cComponent **)v.getArray(); for (int i = 0; i < v.getArraySize(); ++i) { // have to remove the explicitly saved loglevels of the children, so // they won't have their old levels restored in the next session if (save) setPref(QString("ComponentLogLevels/") + objs[i]->getFullPath().c_str(), QVariant()); objs[i]->setLogLevel(level); } // only saving the pref for the one which got explicitly set, the restoring // part will take care of the descendants (and the ini file won't grow too much) // have to do this after the removal up there, because component is in objs if (save) setPref(QString("ComponentLogLevels/") + component->getFullPath().c_str(), level); } void Qtenv::initFonts() { // TODO Check default time font in Windows and Mac #ifdef Q_OS_WIN // Windows defaultFonts.boldFont = getFirstAvailableFontFamily({ "Segoe UI", "MS Sans Serif", "Arial" }, 9); defaultFonts.canvasFont = defaultFonts.boldFont; defaultFonts.timelineFont = getFirstAvailableFontFamily({ "Segoe Condensed", "Gill Sans MT Condensed", "Liberation Sans Narrow" }, defaultFonts.boldFont.pointSize(), defaultFonts.boldFont); defaultFonts.logFont = getFirstAvailableFontFamily({ "DejaVu Sans Mono", "Courier New", "Consolas", "Terminal" }, 9); defaultFonts.timeFont = defaultFonts.boldFont; defaultFonts.timeFont.setPointSize(12); #elif defined(Q_OS_MAC) // Mac defaultFonts.boldFont = getFirstAvailableFontFamily({ "Lucida Grande", "Helvetica" }, 13); defaultFonts.canvasFont = defaultFonts.boldFont; defaultFonts.timelineFont = getFirstAvailableFontFamily({ "Arial Narrow" }, defaultFonts.boldFont.pointSize(), defaultFonts.boldFont); defaultFonts.logFont = getFirstAvailableFontFamily({ "Monaco", "Courier" }, 13); defaultFonts.timeFont = defaultFonts.boldFont; defaultFonts.timeFont.setPointSize(16); #else // Linux and other systems defaultFonts.boldFont = getFirstAvailableFontFamily({ "Ubuntu", "Arial", "Verdana", "Helvetica", "Tahoma", "DejaVu Sans", "Nimbus Sans L", "FreeSans", "Sans" }, 9); defaultFonts.canvasFont = defaultFonts.boldFont; defaultFonts.timelineFont = getFirstAvailableFontFamily({ "Ubuntu Condensed", "Arial Narrow", "DejaVu Sans Condensed" }, defaultFonts.boldFont.pointSize(), defaultFonts.boldFont); defaultFonts.logFont = getFirstAvailableFontFamily({ "Ubuntu Mono", "DejaVu Sans Mono", "Courier New", "FreeMono", "Courier" }, 9); defaultFonts.timeFont = defaultFonts.boldFont; defaultFonts.timeFont.setPointSize(12); #endif auto initFont = [this](const QString &key, QFont &font, const QFont &defaultFont) { QStringList sl = getPref("Fonts/" + key).toStringList(); if (sl.length() == 2) { bool ok = false; int size = sl[1].toInt(&ok); if (ok) { font = QFont(sl[0], size); return; } } font = defaultFont; }; initFont("bold", boldFont, defaultFonts.boldFont); initFont("canvas", canvasFont, defaultFonts.canvasFont); initFont("timeline", timelineFont, defaultFonts.timelineFont); initFont("log", logFont, defaultFonts.logFont); initFont("time", timeFont, defaultFonts.timeFont); } // Returns the first font family from the given preference list that is // available on the system. If none are available, returns defaultValue. QFont Qtenv::getFirstAvailableFontFamily(std::initializer_list preferenceList, int pointSize, QFont defaultValue) { for (QString str : preferenceList) { QFontDatabase fontDb; QFont font = fontDb.font(str, "Normal", pointSize); if (font != QFont()) return font; } return defaultValue; } void Qtenv::saveFonts() { auto saveFont = [this](const QString &key, const QFont &font) { setPref("Fonts/" + key, QStringList() << font.family() << QString::number(font.pointSize())); }; saveFont("bold", boldFont); saveFont("canvas", canvasFont); saveFont("timeline", timelineFont); saveFont("log", logFont); saveFont("time", timeFont); } void Qtenv::updateQtFonts() { emit fontChanged(); mainWindow->setStyleSheet( // if we dont reapply the font here, it will be overwritten with the default, because Qt. "* { font: " + QString::number(boldFont.pointSize()) + "pt " + boldFont.family() + "; } " "QLabel#simTimeLabel, QLabel#eventNumLabel { font: " + QString::number(timeFont.pointSize()) + "pt " + timeFont.family() + ";" "background-color: palette(base); border: 1px solid palette(mid); }" // avoids too tall toolbars on Mac "QToolButton { height: 19px; margin: 0px; }" // makes tool buttons tighty packed, background explicitly painted, frame disabled "QToolBar { spacing: 0px; background: palette(window); border-style: none; }" #ifdef Q_WS_MAC // Mac-specific workarounds // replacing the ugly default gradient "QToolBar::separator { background: palette(window); }" "QToolButton { background: palette(window); }" // as a workaround to a Qt4 bug, should be unnecessary with Qt5 // (vertical splitter handles had white background without this, // but this makes the little dots on them disappear...) "QSplitter::handle { background-color: palette(window); }" #endif ); } //====================================================================== // dummy function to force Unix linkers collect all symbols needed void _dummy_for_genericobjectinspector(); void _dummy_for_watchinspector(); void _dummy_for_moduleinspector(); void _dummy_for_loginspector(); void _dummy_for_gateinspector(); void _dummy_for_histograminspector(); void _dummy_for_outputvectorinspector(); void _dummy_for_objecttreeinspector(); void _dummy_func() { _dummy_for_genericobjectinspector(); _dummy_for_watchinspector(); _dummy_for_moduleinspector(); _dummy_for_loginspector(); _dummy_for_gateinspector(); _dummy_for_histograminspector(); _dummy_for_outputvectorinspector(); _dummy_for_objecttreeinspector(); } } // namespace qtenv } // namespace omnetpp