#include "frogpilot/ui/qt/offroad/lateral_settings.h" FrogPilotLateralPanel::FrogPilotLateralPanel(FrogPilotSettingsWindow *parent) : FrogPilotListWidget(parent), parent(parent) { QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object(); QString className = this->metaObject()->className(); if (!shownDescriptions.value(className).toBool(false)) { forceOpenDescriptions = true; shownDescriptions.insert(className, true); params.put("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString()); } QStackedLayout *lateralLayout = new QStackedLayout(); addItem(lateralLayout); FrogPilotListWidget *lateralList = new FrogPilotListWidget(this); ScrollView *lateralPanel = new ScrollView(lateralList, this); lateralLayout->addWidget(lateralPanel); FrogPilotListWidget *advancedLateralTuneList = new FrogPilotListWidget(this); FrogPilotListWidget *aolList = new FrogPilotListWidget(this); FrogPilotListWidget *laneChangeList = new FrogPilotListWidget(this); FrogPilotListWidget *lateralTuneList = new FrogPilotListWidget(this); FrogPilotListWidget *qolList = new FrogPilotListWidget(this); ScrollView *advancedLateralTunePanel = new ScrollView(advancedLateralTuneList, this); ScrollView *aolPanel = new ScrollView(aolList, this); ScrollView *laneChangePanel = new ScrollView(laneChangeList, this); ScrollView *lateralTunePanel = new ScrollView(lateralTuneList, this); ScrollView *qolPanel = new ScrollView(qolList, this); lateralLayout->addWidget(advancedLateralTunePanel); lateralLayout->addWidget(aolPanel); lateralLayout->addWidget(laneChangePanel); lateralLayout->addWidget(lateralTunePanel); lateralLayout->addWidget(qolPanel); const std::vector> lateralToggles { {"AdvancedLateralTune", tr("Advanced Lateral Tuning"), tr("Advanced steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_advanced_lateral_tune.png"}, {"SteerDelay", parent->steerActuatorDelay != 0 ? QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2)) : tr("Actuator Delay"), tr("The time between openpilot's steering command and the vehicle's response. Increase if the vehicle reacts late; decrease if it feels jumpy. Auto-learned by default."), ""}, {"SteerFriction", parent->friction != 0 ? QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2)) : tr("Friction"), tr("Compensates for steering friction. Increase if the wheel sticks near center; decrease if it jitters. Auto-learned by default."), ""}, {"SteerKP", parent->steerKp != 0 ? QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2)) : tr("Kp Factor"), tr("How strongly openpilot corrects lane position. Higher is tighter but twitchier; lower is smoother but slower. Auto-learned by default."), ""}, {"SteerLatAccel", parent->latAccelFactor != 0 ? QString(tr("Lateral Acceleration (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2)) : tr("Lateral Acceleration"), tr("Maps steering torque to turning response. Increase for sharper turns; decrease for gentler steering. Auto-learned by default."), ""}, {"SteerRatio", parent->steerRatio != 0 ? QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2)) : tr("Steer Ratio"), tr("The relationship between steering wheel rotation and road wheel angle. Increase if steering feels too quick or twitchy; decrease if it feels too slow or weak. Auto-learned by default."), ""}, {"ForceAutoTune", tr("Force Auto-Tune On"), tr("Force-enable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\"."), ""}, {"ForceAutoTuneOff", tr("Force Auto-Tune Off"), tr("Force-disable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\" and use the set value instead."), ""}, {"ForceTorqueController", tr("Force Torque Controller"), tr("Use torque-based steering control instead of angle-based control for smoother lane keeping, especially in curves."), ""}, {"AlwaysOnLateral", tr("Always On Lateral"), tr("openpilot's steering remains active even when the accelerator or brake pedals are pressed."), "../../frogpilot/assets/toggle_icons/icon_always_on_lateral.png"}, {"AlwaysOnLateralMain", tr("Enable With Cruise Control"), tr("Enable \"Always On Lateral\" whenever \"Cruise Control\" is on, even when openpilot is not engaged."), ""}, {"AlwaysOnLateralLKAS", tr("Enable With LKAS"), tr("Enable \"Always On Lateral\" whenever \"LKAS\" is on, even when openpilot is not engaged."), ""}, {"PauseAOLOnBrake", tr("Pause on Brake Press Below"), tr("Pause \"Always On Lateral\" below the set speed while the brake pedal is pressed."), ""}, {"LaneChanges", tr("Lane Changes"), tr("Allow openpilot to change lanes."), "../../frogpilot/assets/toggle_icons/icon_lane.png"}, {"NudgelessLaneChange", tr("Automatic Lane Changes"), tr("When the turn signal is on, openpilot will automatically change lanes. No steering-wheel nudge required!"), ""}, {"LaneChangeTime", tr("Lane Change Delay"), tr("Delay between turn signal activation and the start of an automatic lane change."), ""}, {"MinimumLaneChangeSpeed", tr("Minimum Lane Change Speed"), tr("Lowest speed at which openpilot will change lanes."), ""}, {"LaneDetectionWidth", tr("Minimum Lane Width"), tr("Prevent automatic lane changes into lanes narrower than the set width."), ""}, {"OneLaneChange", tr("One Lane Change Per Signal"), tr("Limit automatic lane changes to one per turn-signal activation."), ""}, {"LateralTune", tr("Lateral Tuning"), tr("Miscellaneous steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_lateral_tune.png"}, {"TurnDesires", tr("Force Turn Desires Below Lane Change Speed"), tr("While driving below the minimum lane change speed with an active turn signal, instruct openpilot to turn left/right."), ""}, {"NNFF", tr("Neural Network Feedforward (NNFF)"), tr("Twilsonco's \"Neural Network FeedForward\" model controller for smoother, model-based steering trained on your vehicle's data."), ""}, {"NNFFLite", tr("Smooth Curve Handling"), tr("Twilsonco's torque-based adjustments to smoothen out steering in curves."), ""}, {"QOLLateral", tr("Quality of Life"), tr("Steering control changes to fine-tune how openpilot drives."), "../../frogpilot/assets/toggle_icons/icon_quality_of_life.png"}, {"PauseLateralSpeed", tr("Pause Steering Below"), tr("Pause steering below the set speed."), ""}, {"IgnoreMe", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""}, {"IgnoreMe2", "Ignore Me", "This is simply used to fix the layout when the user opens the descriptions and the menu gets wonky. No idea why it happens, but I can't be asked to properly fix it so whatever. Sue me.", ""} }; for (const auto &[param, title, desc, icon] : lateralToggles) { AbstractControl *lateralToggle; if (param == "AdvancedLateralTune") { FrogPilotManageControl *advancedLateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon); QObject::connect(advancedLateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, advancedLateralTunePanel]() { lateralLayout->setCurrentWidget(advancedLateralTunePanel); }); lateralToggle = advancedLateralTuneToggle; } else if (param == "SteerDelay") { std::vector steerDelayButton{"Reset"}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0.01, 1, QString(), std::map(), 0.01, false, {}, steerDelayButton, false, false); } else if (param == "SteerFriction") { std::vector steerFrictionButton{"Reset"}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 0.5, QString(), std::map(), 0.01, false, {}, steerFrictionButton, false, false); } else if (param == "SteerKP") { std::vector steerKPButton{"Reset"}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerKp * 0.5, parent->steerKp * 1.5, QString(), std::map(), 0.01, false, {}, steerKPButton, false, false); } else if (param == "SteerLatAccel") { std::vector steerLatAccelButton{"Reset"}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->latAccelFactor * 0.75, parent->latAccelFactor * 1.25, QString(), std::map(), 0.01, false, {}, steerLatAccelButton, false, false); } else if (param == "SteerRatio") { std::vector steerRatioButton{"Reset"}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, parent->steerRatio * 0.5, parent->steerRatio * 1.5, QString(), std::map(), 0.01, false, {}, steerRatioButton, false, false); } else if (param == "AlwaysOnLateral") { FrogPilotManageControl *aolToggle = new FrogPilotManageControl(param, title, desc, icon); QObject::connect(aolToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, aolPanel]() { lateralLayout->setCurrentWidget(aolPanel); }); lateralToggle = aolToggle; } else if (param == "PauseAOLOnBrake") { lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true); } else if (param == "LaneChanges") { FrogPilotManageControl *laneChangeToggle = new FrogPilotManageControl(param, title, desc, icon); QObject::connect(laneChangeToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, laneChangePanel]() { lateralLayout->setCurrentWidget(laneChangePanel); }); lateralToggle = laneChangeToggle; } else if (param == "LaneChangeTime") { std::map laneChangeTimeLabels; for (float i = 0; i <= 5; i += 0.1) { laneChangeTimeLabels[i] = i == 0 ? tr("Instant") : std::lround(i / 0.1) == 1 / 0.1 ? QString::number(i, 'f', 1) + tr(" second") : QString::number(i, 'f', 1) + tr(" seconds"); } lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 5, QString(), laneChangeTimeLabels, 0.1); } else if (param == "LaneDetectionWidth") { lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 15, QString(), std::map(), 0.1, true); } else if (param == "MinimumLaneChangeSpeed") { lateralToggle = new FrogPilotParamValueControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true); } else if (param == "LateralTune") { FrogPilotManageControl *lateralTuneToggle = new FrogPilotManageControl(param, title, desc, icon); QObject::connect(lateralTuneToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, lateralTunePanel]() { lateralLayout->setCurrentWidget(lateralTunePanel); }); lateralToggle = lateralTuneToggle; } else if (param == "QOLLateral") { FrogPilotManageControl *qolLateralToggle = new FrogPilotManageControl(param, title, desc, icon); QObject::connect(qolLateralToggle, &FrogPilotManageControl::manageButtonClicked, [lateralLayout, qolPanel]() { lateralLayout->setCurrentWidget(qolPanel); }); lateralToggle = qolLateralToggle; } else if (param == "PauseLateralSpeed") { std::vector pauseLateralToggles{"PauseLateralOnSignal"}; std::vector pauseLateralToggleNames{tr("Turn Signal Only")}; lateralToggle = new FrogPilotParamValueButtonControl(param, title, desc, icon, 0, 99, QString(), std::map(), 1, true, pauseLateralToggles, pauseLateralToggleNames, true); } else { lateralToggle = new ParamControl(param, title, desc, icon); } toggles[param] = lateralToggle; if (advancedLateralTuneKeys.contains(param)) { advancedLateralTuneList->addItem(lateralToggle); } else if (aolKeys.contains(param)) { aolList->addItem(lateralToggle); } else if (laneChangeKeys.contains(param)) { laneChangeList->addItem(lateralToggle); } else if (lateralTuneKeys.contains(param)) { lateralTuneList->addItem(lateralToggle); } else if (qolKeys.contains(param)) { qolList ->addItem(lateralToggle); } else { lateralList->addItem(lateralToggle); parentKeys.insert(param); } if (FrogPilotManageControl *frogPilotManageToggle = qobject_cast(lateralToggle)) { QObject::connect(frogPilotManageToggle, &FrogPilotManageControl::manageButtonClicked, [this]() { emit openSubPanel(); openDescriptions(forceOpenDescriptions, toggles); }); } QObject::connect(lateralToggle, &AbstractControl::hideDescriptionEvent, [this]() { update(); }); QObject::connect(lateralToggle, &AbstractControl::showDescriptionEvent, [this]() { update(); }); } QSet forceUpdateKeys = {"ForceAutoTune", "ForceAutoTuneOff", "LateralTune", "NNFF", "NudgelessLaneChange"}; for (const QString &key : forceUpdateKeys) { QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotLateralPanel::updateToggles); } QSet rebootKeys = {"AlwaysOnLateral", "ForceTorqueController", "NNFF", "NNFFLite"}; for (const QString &key : rebootKeys) { QObject::connect(static_cast(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) { if (started) { if (key == "AlwaysOnLateral" && state) { if (FrogPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } } else if (key != "AlwaysOnLateral") { if (FrogPilotConfirmationDialog::toggleReboot(this)) { Hardware::reboot(); } } } }); } steerDelayToggle = static_cast(toggles["SteerDelay"]); QObject::connect(steerDelayToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { if (FrogPilotConfirmationDialog::yesorno(tr("Reset Actuator Delay to its default value?"), this)) { params.putFloat("SteerDelay", parent->steerActuatorDelay); steerDelayToggle->refresh(); } }); steerFrictionToggle = static_cast(toggles["SteerFriction"]); QObject::connect(steerFrictionToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { if (FrogPilotConfirmationDialog::yesorno(tr("Reset Friction to its default value?"), this)) { params.putFloat("SteerFriction", parent->friction); steerFrictionToggle->refresh(); } }); steerKPToggle = static_cast(toggles["SteerKP"]); QObject::connect(steerKPToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { if (FrogPilotConfirmationDialog::yesorno(tr("Reset Kp Factor to its default value?"), this)) { params.putFloat("SteerKP", parent->steerKp); steerKPToggle->refresh(); } }); steerLatAccelToggle = static_cast(toggles["SteerLatAccel"]); QObject::connect(steerLatAccelToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { if (FrogPilotConfirmationDialog::yesorno(tr("Reset Lateral Accel to its default value?"), this)) { params.putFloat("SteerLatAccel", parent->latAccelFactor); steerLatAccelToggle->refresh(); } }); steerRatioToggle = static_cast(toggles["SteerRatio"]); QObject::connect(steerRatioToggle, &FrogPilotParamValueButtonControl::buttonClicked, [parent, this]() { if (FrogPilotConfirmationDialog::yesorno(tr("Reset Steer Ratio to its default value?"), this)) { params.putFloat("SteerRatio", parent->steerRatio); steerRatioToggle->refresh(); } }); openDescriptions(forceOpenDescriptions, toggles); QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [lateralLayout, lateralPanel, this] { openDescriptions(forceOpenDescriptions, toggles); lateralLayout->setCurrentWidget(lateralPanel); }); QObject::connect(parent, &FrogPilotSettingsWindow::updateMetric, this, &FrogPilotLateralPanel::updateMetric); QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotLateralPanel::updateState); } void FrogPilotLateralPanel::showEvent(QShowEvent *event) { frogpilotToggleLevels = parent->frogpilotToggleLevels; steerDelayToggle->setTitle(QString(tr("Actuator Delay (Default: %1)")).arg(QString::number(parent->steerActuatorDelay, 'f', 2))); steerFrictionToggle->setTitle(QString(tr("Friction (Default: %1)")).arg(QString::number(parent->friction, 'f', 2))); steerKPToggle->setTitle(QString(tr("Kp Factor (Default: %1)")).arg(QString::number(parent->steerKp, 'f', 2))); steerKPToggle->updateControl(parent->steerKp * 0.5, parent->steerKp * 1.5); steerLatAccelToggle->setTitle(QString(tr("Lateral Accel (Default: %1)")).arg(QString::number(parent->latAccelFactor, 'f', 2))); steerLatAccelToggle->updateControl(parent->latAccelFactor * 0.75, parent->latAccelFactor * 1.25); steerRatioToggle->setTitle(QString(tr("Steer Ratio (Default: %1)")).arg(QString::number(parent->steerRatio, 'f', 2))); steerRatioToggle->updateControl(parent->steerRatio * 0.5, parent->steerRatio * 1.5); updateToggles(); } void FrogPilotLateralPanel::updateState(const UIState &s) { if (!isVisible()) return; started = s.scene.started; } void FrogPilotLateralPanel::updateMetric(bool metric, bool bootRun) { static bool previousMetric; if (metric != previousMetric && !bootRun) { double distanceConversion = metric ? FOOT_TO_METER : METER_TO_FOOT; double speedConversion = metric ? MILE_TO_KM : KM_TO_MILE; params.putFloatNonBlocking("LaneDetectionWidth", params.getFloat("LaneDetectionWidth") * distanceConversion); params.putIntNonBlocking("MinimumLaneChangeSpeed", params.getInt("MinimumLaneChangeSpeed") * speedConversion); params.putIntNonBlocking("PauseAOLOnBrake", params.getInt("PauseAOLOnBrake") * speedConversion); params.putIntNonBlocking("PauseLateralSpeed", params.getInt("PauseLateralSpeed") * speedConversion); } previousMetric = metric; static std::map imperialDistanceLabels; static std::map imperialSpeedLabels; static std::map metricDistanceLabels; static std::map metricSpeedLabels; static bool labelsInitialized = false; if (!labelsInitialized) { for (int i = 0; i <= 150; ++i) { float key = i / 10.0f; imperialDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" foot") : QString::number(key, 'f', 1) + tr(" feet"); } for (int i = 0; i <= 99; ++i) { imperialSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" mph"); } for (int i = 0; i <= 50; ++i) { float key = i / 10.0f; metricDistanceLabels[key] = key == 0 ? tr("Off") : i == 1 ? QString::number(i) + tr(" meter") : QString::number(key, 'f', 1) + tr(" meters"); } for (int i = 0; i <= 150; ++i) { metricSpeedLabels[i] = i == 0 ? tr("Off") : QString::number(i) + tr(" km/h"); } labelsInitialized = true; } FrogPilotParamValueControl *laneWidthToggle = static_cast(toggles["LaneDetectionWidth"]); FrogPilotParamValueControl *minimumLaneChangeSpeedToggle = static_cast(toggles["MinimumLaneChangeSpeed"]); FrogPilotParamValueControl *pauseAOLOnBrakeToggle = static_cast(toggles["PauseAOLOnBrake"]); FrogPilotParamValueControl *pauseLateralToggle = static_cast(toggles["PauseLateralSpeed"]); if (metric) { laneWidthToggle->updateControl(0, 5, metricDistanceLabels); minimumLaneChangeSpeedToggle->updateControl(0, 150, metricSpeedLabels); pauseAOLOnBrakeToggle->updateControl(0, 150, metricSpeedLabels); pauseLateralToggle->updateControl(0, 150, metricSpeedLabels); } else { laneWidthToggle->updateControl(0, 15, imperialDistanceLabels); minimumLaneChangeSpeedToggle->updateControl(0, 99, imperialSpeedLabels); pauseAOLOnBrakeToggle->updateControl(0, 99, imperialSpeedLabels); pauseLateralToggle->updateControl(0, 99, imperialSpeedLabels); } } void FrogPilotLateralPanel::updateToggles() { for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { toggle->setVisible(false); } } bool forcingAutoTune = !parent->hasAutoTune && params.getBool("ForceAutoTune"); bool forcingAutoTuneOff = parent->hasAutoTune && params.getBool("ForceAutoTuneOff"); bool forcingTorqueController = !parent->isAngleCar && params.getBool("ForceTorqueController"); bool usingNNFF = parent->hasNNFFLog && params.getBool("LateralTune") && params.getBool("NNFF"); for (auto &[key, toggle] : toggles) { if (parentKeys.contains(key)) { continue; } bool setVisible = parent->tuningLevel >= frogpilotToggleLevels[key].toDouble(); if (key == "AlwaysOnLateralLKAS") { setVisible &= parent->isHKGCanFd; setVisible &= !parent->hasOpenpilotLongitudinal; } else if (key == "AlwaysOnLateralMain") { setVisible &= !parent->isHKGCanFd; setVisible |= parent->hasOpenpilotLongitudinal; } else if (key == "ForceAutoTune") { setVisible &= !parent->hasAutoTune; setVisible &= !parent->isAngleCar; setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; } else if (key == "ForceAutoTuneOff") { setVisible &= parent->hasAutoTune; } else if (key == "ForceTorqueController") { setVisible &= !parent->isAngleCar; setVisible &= !parent->isTorqueCar; } else if (key == "LaneChangeTime") { setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange"); } else if (key == "LaneDetectionWidth") { setVisible &= params.getBool("LaneChanges") && params.getBool("NudgelessLaneChange"); } else if (key == "NNFF") { setVisible &= parent->hasNNFFLog; setVisible &= !parent->isAngleCar; } else if (key == "NNFFLite") { setVisible &= !usingNNFF; setVisible &= !parent->isAngleCar; } else if (key == "SteerDelay") { setVisible &= parent->steerActuatorDelay != 0; } else if (key == "SteerFriction") { setVisible &= parent->friction != 0; setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; setVisible &= !usingNNFF; } else if (key == "SteerKP") { setVisible &= parent->steerKp != 0; setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; setVisible &= !parent->isAngleCar; } else if (key == "SteerLatAccel") { setVisible &= parent->latAccelFactor != 0; setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; setVisible &= parent->isTorqueCar || forcingTorqueController || usingNNFF; setVisible &= !usingNNFF; } else if (key == "SteerRatio") { setVisible &= parent->steerRatio != 0; setVisible &= parent->hasAutoTune ? forcingAutoTuneOff : !forcingAutoTune; } toggle->setVisible(setVisible); if (setVisible) { if (advancedLateralTuneKeys.contains(key)) { toggles["AdvancedLateralTune"]->setVisible(true); } else if (aolKeys.contains(key)) { toggles["AlwaysOnLateral"]->setVisible(true); } else if (laneChangeKeys.contains(key)) { toggles["LaneChanges"]->setVisible(true); } else if (lateralTuneKeys.contains(key)) { toggles["LateralTune"]->setVisible(true); } else if (qolKeys.contains(key)) { toggles["QOLLateral"]->setVisible(true); } } } openDescriptions(forceOpenDescriptions, toggles); update(); }