You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							487 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
	
	
							487 lines
						
					
					
						
							14 KiB
						
					
					
				#include "selfdrive/ui/qt/setup/setup.h"
 | 
						|
 | 
						|
#include <cstdio>
 | 
						|
#include <cstdlib>
 | 
						|
#include <sstream>
 | 
						|
#include <string>
 | 
						|
 | 
						|
#include <QApplication>
 | 
						|
#include <QLabel>
 | 
						|
#include <QVBoxLayout>
 | 
						|
 | 
						|
#include <curl/curl.h>
 | 
						|
 | 
						|
#include "common/util.h"
 | 
						|
#include "system/hardware/hw.h"
 | 
						|
#include "selfdrive/ui/qt/api.h"
 | 
						|
#include "selfdrive/ui/qt/qt_window.h"
 | 
						|
#include "selfdrive/ui/qt/network/networking.h"
 | 
						|
#include "selfdrive/ui/qt/util.h"
 | 
						|
#include "selfdrive/ui/qt/widgets/input.h"
 | 
						|
 | 
						|
const std::string USER_AGENT = "AGNOSSetup-";
 | 
						|
const QString OPENPILOT_URL = "https://openpilot.comma.ai";
 | 
						|
 | 
						|
bool is_elf(char *fname) {
 | 
						|
  FILE *fp = fopen(fname, "rb");
 | 
						|
  if (fp == NULL) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
  char buf[4];
 | 
						|
  size_t n = fread(buf, 1, 4, fp);
 | 
						|
  fclose(fp);
 | 
						|
  return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F';
 | 
						|
}
 | 
						|
 | 
						|
void Setup::download(QString url) {
 | 
						|
  // autocomplete incomplete urls
 | 
						|
  if (QRegularExpression("^([^/.]+)/([^/]+)$").match(url).hasMatch()) {
 | 
						|
    url.prepend("https://installer.comma.ai/");
 | 
						|
  }
 | 
						|
 | 
						|
  CURL *curl = curl_easy_init();
 | 
						|
  if (!curl) {
 | 
						|
    emit finished(url, tr("Something went wrong. Reboot the device."));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  auto version = util::read_file("/VERSION");
 | 
						|
 | 
						|
  struct curl_slist *list = NULL;
 | 
						|
  list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str());
 | 
						|
 | 
						|
  char tmpfile[] = "/tmp/installer_XXXXXX";
 | 
						|
  FILE *fp = fdopen(mkstemp(tmpfile), "w");
 | 
						|
 | 
						|
  curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str());
 | 
						|
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
 | 
						|
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
 | 
						|
  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
 | 
						|
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 | 
						|
  curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str());
 | 
						|
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
 | 
						|
  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
 | 
						|
 | 
						|
  int ret = curl_easy_perform(curl);
 | 
						|
  long res_status = 0;
 | 
						|
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status);
 | 
						|
 | 
						|
  if (ret != CURLE_OK || res_status != 200) {
 | 
						|
    emit finished(url, tr("Ensure the entered URL is valid, and the device’s internet connection is good."));
 | 
						|
  } else if (!is_elf(tmpfile)) {
 | 
						|
    emit finished(url, tr("No custom software found at this URL."));
 | 
						|
  } else {
 | 
						|
    rename(tmpfile, "/tmp/installer");
 | 
						|
 | 
						|
    FILE *fp_url = fopen("/tmp/installer_url", "w");
 | 
						|
    fprintf(fp_url, "%s", url.toStdString().c_str());
 | 
						|
    fclose(fp_url);
 | 
						|
 | 
						|
    emit finished(url);
 | 
						|
  }
 | 
						|
 | 
						|
  curl_slist_free_all(list);
 | 
						|
  curl_easy_cleanup(curl);
 | 
						|
  fclose(fp);
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::low_voltage() {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
  QVBoxLayout *main_layout = new QVBoxLayout(widget);
 | 
						|
  main_layout->setContentsMargins(55, 0, 55, 55);
 | 
						|
  main_layout->setSpacing(0);
 | 
						|
 | 
						|
  // inner text layout: warning icon, title, and body
 | 
						|
  QVBoxLayout *inner_layout = new QVBoxLayout();
 | 
						|
  inner_layout->setContentsMargins(110, 144, 365, 0);
 | 
						|
  main_layout->addLayout(inner_layout);
 | 
						|
 | 
						|
  QLabel *triangle = new QLabel();
 | 
						|
  triangle->setPixmap(QPixmap(ASSET_PATH + "offroad/icon_warning.png"));
 | 
						|
  inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft);
 | 
						|
  inner_layout->addSpacing(80);
 | 
						|
 | 
						|
  QLabel *title = new QLabel(tr("WARNING: Low Voltage"));
 | 
						|
  title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;");
 | 
						|
  inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
 | 
						|
 | 
						|
  inner_layout->addSpacing(25);
 | 
						|
 | 
						|
  QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk."));
 | 
						|
  body->setWordWrap(true);
 | 
						|
  body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
 | 
						|
  body->setStyleSheet("font-size: 80px; font-weight: 300;");
 | 
						|
  inner_layout->addWidget(body);
 | 
						|
 | 
						|
  inner_layout->addStretch();
 | 
						|
 | 
						|
  // power off + continue buttons
 | 
						|
  QHBoxLayout *blayout = new QHBoxLayout();
 | 
						|
  blayout->setSpacing(50);
 | 
						|
  main_layout->addLayout(blayout, 0);
 | 
						|
 | 
						|
  QPushButton *poweroff = new QPushButton(tr("Power off"));
 | 
						|
  poweroff->setObjectName("navBtn");
 | 
						|
  blayout->addWidget(poweroff);
 | 
						|
  QObject::connect(poweroff, &QPushButton::clicked, this, [=]() {
 | 
						|
    Hardware::poweroff();
 | 
						|
  });
 | 
						|
 | 
						|
  QPushButton *cont = new QPushButton(tr("Continue"));
 | 
						|
  cont->setObjectName("navBtn");
 | 
						|
  blayout->addWidget(cont);
 | 
						|
  QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
 | 
						|
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::getting_started() {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
 | 
						|
  QHBoxLayout *main_layout = new QHBoxLayout(widget);
 | 
						|
  main_layout->setMargin(0);
 | 
						|
 | 
						|
  QVBoxLayout *vlayout = new QVBoxLayout();
 | 
						|
  vlayout->setContentsMargins(165, 280, 100, 0);
 | 
						|
  main_layout->addLayout(vlayout);
 | 
						|
 | 
						|
  QLabel *title = new QLabel(tr("Getting Started"));
 | 
						|
  title->setStyleSheet("font-size: 90px; font-weight: 500;");
 | 
						|
  vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
 | 
						|
 | 
						|
  vlayout->addSpacing(90);
 | 
						|
  QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details."));
 | 
						|
  desc->setWordWrap(true);
 | 
						|
  desc->setStyleSheet("font-size: 80px; font-weight: 300;");
 | 
						|
  vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft);
 | 
						|
 | 
						|
  vlayout->addStretch();
 | 
						|
 | 
						|
  QPushButton *btn = new QPushButton();
 | 
						|
  btn->setIcon(QIcon(":/img_continue_triangle.svg"));
 | 
						|
  btn->setIconSize(QSize(54, 106));
 | 
						|
  btn->setFixedSize(310, 1080);
 | 
						|
  btn->setProperty("primary", true);
 | 
						|
  btn->setStyleSheet("border: none;");
 | 
						|
  main_layout->addWidget(btn, 0, Qt::AlignRight);
 | 
						|
  QObject::connect(btn, &QPushButton::clicked, this, &Setup::nextPage);
 | 
						|
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::network_setup() {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
  QVBoxLayout *main_layout = new QVBoxLayout(widget);
 | 
						|
  main_layout->setContentsMargins(55, 50, 55, 50);
 | 
						|
 | 
						|
  // title
 | 
						|
  QLabel *title = new QLabel(tr("Connect to Wi-Fi"));
 | 
						|
  title->setStyleSheet("font-size: 90px; font-weight: 500;");
 | 
						|
  main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
 | 
						|
 | 
						|
  main_layout->addSpacing(25);
 | 
						|
 | 
						|
  // wifi widget
 | 
						|
  Networking *networking = new Networking(this, false);
 | 
						|
  networking->setStyleSheet("Networking {background-color: #292929; border-radius: 13px;}");
 | 
						|
  main_layout->addWidget(networking, 1);
 | 
						|
 | 
						|
  main_layout->addSpacing(35);
 | 
						|
 | 
						|
  // back + continue buttons
 | 
						|
  QHBoxLayout *blayout = new QHBoxLayout;
 | 
						|
  main_layout->addLayout(blayout);
 | 
						|
  blayout->setSpacing(50);
 | 
						|
 | 
						|
  QPushButton *back = new QPushButton(tr("Back"));
 | 
						|
  back->setObjectName("navBtn");
 | 
						|
  QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
 | 
						|
  blayout->addWidget(back);
 | 
						|
 | 
						|
  QPushButton *cont = new QPushButton();
 | 
						|
  cont->setObjectName("navBtn");
 | 
						|
  cont->setProperty("primary", true);
 | 
						|
  QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage);
 | 
						|
  blayout->addWidget(cont);
 | 
						|
 | 
						|
  // setup timer for testing internet connection
 | 
						|
  HttpRequest *request = new HttpRequest(this, false, 2500);
 | 
						|
  QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) {
 | 
						|
    cont->setEnabled(success);
 | 
						|
    if (success) {
 | 
						|
      const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI;
 | 
						|
      cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi"));
 | 
						|
    } else {
 | 
						|
      cont->setText(tr("Waiting for internet"));
 | 
						|
    }
 | 
						|
    repaint();
 | 
						|
  });
 | 
						|
  request->sendRequest(OPENPILOT_URL);
 | 
						|
  QTimer *timer = new QTimer(this);
 | 
						|
  QObject::connect(timer, &QTimer::timeout, [=]() {
 | 
						|
    if (!request->active() && cont->isVisible()) {
 | 
						|
      request->sendRequest(OPENPILOT_URL);
 | 
						|
    }
 | 
						|
  });
 | 
						|
  timer->start(1000);
 | 
						|
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * radio_button(QString title, QButtonGroup *group) {
 | 
						|
  QPushButton *btn = new QPushButton(title);
 | 
						|
  btn->setCheckable(true);
 | 
						|
  group->addButton(btn);
 | 
						|
  btn->setStyleSheet(R"(
 | 
						|
    QPushButton {
 | 
						|
      height: 230;
 | 
						|
      padding-left: 100px;
 | 
						|
      padding-right: 100px;
 | 
						|
      text-align: left;
 | 
						|
      font-size: 80px;
 | 
						|
      font-weight: 400;
 | 
						|
      border-radius: 10px;
 | 
						|
      background-color: #4F4F4F;
 | 
						|
    }
 | 
						|
    QPushButton:checked {
 | 
						|
      background-color: #465BEA;
 | 
						|
    }
 | 
						|
  )");
 | 
						|
 | 
						|
  // checkmark icon
 | 
						|
  QPixmap pix(":/img_circled_check.svg");
 | 
						|
  btn->setIcon(pix);
 | 
						|
  btn->setIconSize(QSize(0, 0));
 | 
						|
  btn->setLayoutDirection(Qt::RightToLeft);
 | 
						|
  QObject::connect(btn, &QPushButton::toggled, [=](bool checked) {
 | 
						|
    btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0));
 | 
						|
  });
 | 
						|
  return btn;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::software_selection() {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
  QVBoxLayout *main_layout = new QVBoxLayout(widget);
 | 
						|
  main_layout->setContentsMargins(55, 50, 55, 50);
 | 
						|
  main_layout->setSpacing(0);
 | 
						|
 | 
						|
  // title
 | 
						|
  QLabel *title = new QLabel(tr("Choose Software to Install"));
 | 
						|
  title->setStyleSheet("font-size: 90px; font-weight: 500;");
 | 
						|
  main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop);
 | 
						|
 | 
						|
  main_layout->addSpacing(50);
 | 
						|
 | 
						|
  // openpilot + custom radio buttons
 | 
						|
  QButtonGroup *group = new QButtonGroup(widget);
 | 
						|
  group->setExclusive(true);
 | 
						|
 | 
						|
  QWidget *openpilot = radio_button(tr("openpilot"), group);
 | 
						|
  main_layout->addWidget(openpilot);
 | 
						|
 | 
						|
  main_layout->addSpacing(30);
 | 
						|
 | 
						|
  QWidget *custom = radio_button(tr("Custom Software"), group);
 | 
						|
  main_layout->addWidget(custom);
 | 
						|
 | 
						|
  main_layout->addStretch();
 | 
						|
 | 
						|
  // back + continue buttons
 | 
						|
  QHBoxLayout *blayout = new QHBoxLayout;
 | 
						|
  main_layout->addLayout(blayout);
 | 
						|
  blayout->setSpacing(50);
 | 
						|
 | 
						|
  QPushButton *back = new QPushButton(tr("Back"));
 | 
						|
  back->setObjectName("navBtn");
 | 
						|
  QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage);
 | 
						|
  blayout->addWidget(back);
 | 
						|
 | 
						|
  QPushButton *cont = new QPushButton(tr("Continue"));
 | 
						|
  cont->setObjectName("navBtn");
 | 
						|
  cont->setEnabled(false);
 | 
						|
  cont->setProperty("primary", true);
 | 
						|
  blayout->addWidget(cont);
 | 
						|
 | 
						|
  QObject::connect(cont, &QPushButton::clicked, [=]() {
 | 
						|
    auto w = currentWidget();
 | 
						|
    QTimer::singleShot(0, [=]() {
 | 
						|
      setCurrentWidget(downloading_widget);
 | 
						|
    });
 | 
						|
    QString url = OPENPILOT_URL;
 | 
						|
    if (group->checkedButton() != openpilot) {
 | 
						|
      url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software"));
 | 
						|
    }
 | 
						|
    if (!url.isEmpty()) {
 | 
						|
      QTimer::singleShot(1000, this, [=]() {
 | 
						|
        download(url);
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      setCurrentWidget(w);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  connect(group, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) {
 | 
						|
    btn->setChecked(true);
 | 
						|
    cont->setEnabled(true);
 | 
						|
  });
 | 
						|
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::downloading() {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
  QVBoxLayout *main_layout = new QVBoxLayout(widget);
 | 
						|
  QLabel *txt = new QLabel(tr("Downloading..."));
 | 
						|
  txt->setStyleSheet("font-size: 90px; font-weight: 500;");
 | 
						|
  main_layout->addWidget(txt, 0, Qt::AlignCenter);
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
QWidget * Setup::download_failed(QLabel *url, QLabel *body) {
 | 
						|
  QWidget *widget = new QWidget();
 | 
						|
  QVBoxLayout *main_layout = new QVBoxLayout(widget);
 | 
						|
  main_layout->setContentsMargins(55, 185, 55, 55);
 | 
						|
  main_layout->setSpacing(0);
 | 
						|
 | 
						|
  QLabel *title = new QLabel(tr("Download Failed"));
 | 
						|
  title->setStyleSheet("font-size: 90px; font-weight: 500;");
 | 
						|
  main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft);
 | 
						|
 | 
						|
  main_layout->addSpacing(67);
 | 
						|
 | 
						|
  url->setWordWrap(true);
 | 
						|
  url->setAlignment(Qt::AlignTop | Qt::AlignLeft);
 | 
						|
  url->setStyleSheet("font-family: \"JetBrains Mono\"; font-size: 64px; font-weight: 400; margin-right: 100px;");
 | 
						|
  main_layout->addWidget(url);
 | 
						|
 | 
						|
  main_layout->addSpacing(48);
 | 
						|
 | 
						|
  body->setWordWrap(true);
 | 
						|
  body->setAlignment(Qt::AlignTop | Qt::AlignLeft);
 | 
						|
  body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;");
 | 
						|
  main_layout->addWidget(body);
 | 
						|
 | 
						|
  main_layout->addStretch();
 | 
						|
 | 
						|
  // reboot + start over buttons
 | 
						|
  QHBoxLayout *blayout = new QHBoxLayout();
 | 
						|
  blayout->setSpacing(50);
 | 
						|
  main_layout->addLayout(blayout, 0);
 | 
						|
 | 
						|
  QPushButton *reboot = new QPushButton(tr("Reboot device"));
 | 
						|
  reboot->setObjectName("navBtn");
 | 
						|
  blayout->addWidget(reboot);
 | 
						|
  QObject::connect(reboot, &QPushButton::clicked, this, [=]() {
 | 
						|
    Hardware::reboot();
 | 
						|
  });
 | 
						|
 | 
						|
  QPushButton *restart = new QPushButton(tr("Start over"));
 | 
						|
  restart->setObjectName("navBtn");
 | 
						|
  restart->setProperty("primary", true);
 | 
						|
  blayout->addWidget(restart);
 | 
						|
  QObject::connect(restart, &QPushButton::clicked, this, [=]() {
 | 
						|
    setCurrentIndex(1);
 | 
						|
  });
 | 
						|
 | 
						|
  widget->setStyleSheet(R"(
 | 
						|
    QLabel {
 | 
						|
      margin-left: 117;
 | 
						|
    }
 | 
						|
  )");
 | 
						|
  return widget;
 | 
						|
}
 | 
						|
 | 
						|
void Setup::prevPage() {
 | 
						|
  setCurrentIndex(currentIndex() - 1);
 | 
						|
}
 | 
						|
 | 
						|
void Setup::nextPage() {
 | 
						|
  setCurrentIndex(currentIndex() + 1);
 | 
						|
}
 | 
						|
 | 
						|
Setup::Setup(QWidget *parent) : QStackedWidget(parent) {
 | 
						|
  if (std::getenv("MULTILANG")) {
 | 
						|
    selectLanguage();
 | 
						|
  }
 | 
						|
 | 
						|
  std::stringstream buffer;
 | 
						|
  buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf();
 | 
						|
  float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.;
 | 
						|
  if (voltage < 7) {
 | 
						|
    addWidget(low_voltage());
 | 
						|
  }
 | 
						|
 | 
						|
  addWidget(getting_started());
 | 
						|
  addWidget(network_setup());
 | 
						|
  addWidget(software_selection());
 | 
						|
 | 
						|
  downloading_widget = downloading();
 | 
						|
  addWidget(downloading_widget);
 | 
						|
 | 
						|
  QLabel *url_label = new QLabel();
 | 
						|
  QLabel *body_label = new QLabel();
 | 
						|
  failed_widget = download_failed(url_label, body_label);
 | 
						|
  addWidget(failed_widget);
 | 
						|
 | 
						|
  QObject::connect(this, &Setup::finished, [=](const QString &url, const QString &error) {
 | 
						|
    qDebug() << "finished" << url << error;
 | 
						|
    if (error.isEmpty()) {
 | 
						|
      // hide setup on success
 | 
						|
      QTimer::singleShot(3000, this, &QWidget::hide);
 | 
						|
    } else {
 | 
						|
      url_label->setText(url);
 | 
						|
      body_label->setText(error);
 | 
						|
      setCurrentWidget(failed_widget);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  // TODO: revisit pressed bg color
 | 
						|
  setStyleSheet(R"(
 | 
						|
    * {
 | 
						|
      color: white;
 | 
						|
      font-family: Inter;
 | 
						|
    }
 | 
						|
    Setup {
 | 
						|
      background-color: black;
 | 
						|
    }
 | 
						|
    QPushButton#navBtn {
 | 
						|
      height: 160;
 | 
						|
      font-size: 55px;
 | 
						|
      font-weight: 400;
 | 
						|
      border-radius: 10px;
 | 
						|
      background-color: #333333;
 | 
						|
    }
 | 
						|
    QPushButton#navBtn:disabled, QPushButton[primary='true']:disabled {
 | 
						|
      color: #808080;
 | 
						|
      background-color: #333333;
 | 
						|
    }
 | 
						|
    QPushButton#navBtn:pressed {
 | 
						|
      background-color: #444444;
 | 
						|
    }
 | 
						|
    QPushButton[primary='true'], #navBtn[primary='true'] {
 | 
						|
      background-color: #465BEA;
 | 
						|
    }
 | 
						|
    QPushButton[primary='true']:pressed, #navBtn:pressed[primary='true'] {
 | 
						|
      background-color: #3049F4;
 | 
						|
    }
 | 
						|
  )");
 | 
						|
}
 | 
						|
 | 
						|
void Setup::selectLanguage() {
 | 
						|
  QMap<QString, QString> langs = getSupportedLanguages();
 | 
						|
  QString selection = MultiOptionDialog::getSelection(tr("Select a language"), langs.keys(), "", this);
 | 
						|
  if (!selection.isEmpty()) {
 | 
						|
    QString selectedLang = langs[selection];
 | 
						|
    Params().put("LanguageSetting", selectedLang.toStdString());
 | 
						|
    if (translator.load(":/" + selectedLang)) {
 | 
						|
      qApp->installTranslator(&translator);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
int main(int argc, char *argv[]) {
 | 
						|
  QApplication a(argc, argv);
 | 
						|
  Setup setup;
 | 
						|
  setMainWindow(&setup);
 | 
						|
  return a.exec();
 | 
						|
}
 | 
						|
 |