#include "dialog.h" #include #include #include #include #include #include #include #include #include #include "unistd.h" #include "stdio.h" #include "sys/wait.h" #include "ui_dialog.h" #include "sessiontreeitem.h" #include "globals.h" #include "vsession.h" #include "choosersettings.h" Dialog::Dialog(int defaultTab, bool examMode, QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { model_[0] = new SessionTreeModel(parent); model_[1] = new SessionTreeModel(parent); model_[2] = new SessionTreeModel(parent); if (defaultTab < 0 || defaultTab > TAB_COUNT) defaultTab = TAB_ALL_VMS; defaultTab_ = defaultTab; qDebug() << "Default tab: " << defaultTab; userInteracted_ = false; genericExpandedOnce_ = false; examMode_ = examMode; ui->setupUi(this); tabs_[0] = ui->tabButtonLocal; tabs_[1] = ui->tabButtonMyClasses; tabs_[2] = ui->tabButtonAllClasses; strings_[STR_LOADING] = QCoreApplication::instance()->translate("Dialog", "Loading..."); strings_[STR_URL_ERROR] = QCoreApplication::instance()->translate("Dialog", "URL Error"); strings_[STR_NO_ITEMS] = QCoreApplication::instance()->translate("Dialog", "No Items"); // Re-center dialog every second to account for resolution changes QRect desktopRect = QApplication::desktop()->availableGeometry(this); oldCenter_ = desktopRect.center(); centerTimer_ = new QTimer(this); connect(centerTimer_, SIGNAL(timeout()), this, SLOT(onCenterTimer())); centerTimer_->start(1000); ui->treeView->setFocusProxy(ui->filterEdit); // ui->filterEdit->installEventFilter(this); qApp->installEventFilter(this); ui->helpBox->hide(); ui->newsBox->hide(); this->addStatusString(STR_LOADING); /* * TODO: why connect signal/slot when no item was loaded yet? * TODO: Change "TODO" in the above line to "QUESTION", as it's just that QObject::connect(ui->treeView->selectionModel(), SIGNAL(currentChanged ( const QModelIndex&, const QModelIndex&)), this, SLOT(treeView_selectionChanged(const QModelIndex&, const QModelIndex&))); */ if (examMode_) { ui->tabButtonLocal->setEnabled(false); this->onTabButtonChanged(TAB_ALL_VMS); /* modify the pvs checkbox */ ui->PVS_checkbox->setText(tr("join PVS(limited)")); ui->PVS_checkbox->setEnabled(false); ui->PVS_checkbox->setChecked(true); } else { this->onTabButtonChanged(TAB_NATIVE); this->selectPreviousSession(); } //activeTab_ = 2; //ui->tabButtonAllClasses->setChecked(true); // TODO: Implement bug report dialog :) ui->buttonBugReport->setEnabled(false); QObject::connect(SessionsIconHolder::get(), SIGNAL(iconDownloaded(const QUrl&, const QIcon&)), this, SLOT(iconDownloaded(const QUrl&, const QIcon&))); } Dialog::~Dialog() { delete ui; } void Dialog::changeEvent(QEvent *e) { QDialog::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } void Dialog::on_treeView_doubleClicked(const QModelIndex& index) { userInteracted_ = true; // this method gets called when a Session has been selected to start SessionTreeItem* item = static_cast(index.internalPointer()); if (item == NULL) return; // do nothing if cast failed const Session* s(item->session()); if (s == NULL) // no valid session has been selected, do nothing return; if (s->run()) { // Run session start script if (QFile::exists(sessionStartScript)) { // Use the current environment variables and add the necessary // information for the startUpScipt. if (ui->PVS_checkbox->isChecked()) setenv("PVS_AUTO_CONNECT", "TRUE", 1); else setenv("PVS_AUTO_CONNECT", "FALSE", 1); // export session information to the environment of the process to be exec'ed setenv("SESSION_NAME", s->shortDescription().toUtf8(), 1); setenv("SESSION_UUID", s->uuid().toUtf8(), 1); setenv("SESSION_CMD", s->execCommand().toUtf8(), 1); if (s->type() == Session::VSESSION) { setenv("SESSION_TYPE", "VSESSION", 1); } else if (s->type() == Session::XSESSION) { setenv("SESSION_TYPE", "XSESSION", 1); } // Fork this process twice to detach pid_t pid = fork(); if (pid == 0) { // Close all filedescriptors for (int i = 3; i < 1024; ++i) ::close(i); // Change process group setsid(); // Reopen standard pipes freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); pid = fork(); if (pid == 0) { // At this point we are executing as the 2nd child process // Replace this clone with the process we'd like to start char const * const args[] = {VMCHOOSER_SESSION_START_SCRIPT, NULL}; execv(VMCHOOSER_SESSION_START_SCRIPT, (char**)args); } _exit(0); // Dont use exit hooks } // Wait and cleanup the intermediate process waitpid(pid, NULL, 0); } ChooserSettings::setSetting("last-session", (s->shortDescription())); ChooserSettings::setSetting("last-tab", QString::number(activeTab_)); setVisible(false); } else { QMessageBox::warning( this, trUtf8("vmchooser"), trUtf8("Vmchooser failed to run the selected session!")); } } void Dialog::on_treeView_expanded(const QModelIndex& index) { SessionTreeItem* item = static_cast(index.internalPointer()); if (item->session() == NULL && item->sectionType() != SECTION_FOR_LOCATION) { genericExpandedOnce_ = true; } } void Dialog::addItems(const QList& entries, int tab) { if (tab < 0 || tab > 2) { return; } if (examMode_ && tab == TAB_NATIVE) return; this->model_[tab]->addItems(entries); tabs_[tab]->setEnabled(this->model_[tab]->rowCount() != 0); if (tab == activeTab_) { setListModel(this->model_[tab]); } model_[tab]->updateView(); selectPreviousSession(); on_filterEdit_textChanged(); } void Dialog::addStatusString(const int status) { if (status < 0 || status >= STR__MAX) return; this->model_[1]->addLabelItem(strings_[status]); this->model_[2]->addLabelItem(strings_[status]); tabs_[1]->setEnabled(this->model_[1]->rowCount() > 1); tabs_[2]->setEnabled(this->model_[2]->rowCount() > 1); model_[1]->updateView(); model_[2]->updateView(); } void Dialog::removeStatusString(const int status) { if (status < 0 || status >= STR__MAX) return; this->model_[1]->removeItem(strings_[status]); this->model_[2]->removeItem(strings_[status]); tabs_[1]->setEnabled(this->model_[1]->rowCount() > 1); tabs_[2]->setEnabled(this->model_[1]->rowCount() > 1); model_[1]->updateView(); model_[2]->updateView(); } void Dialog::on_pushButtonAbort_clicked() { close(); } void Dialog::on_pushButtonStart_clicked() { userInteracted_ = true; this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex()); } bool Dialog::selectSession(const QString& name) { QModelIndex root(ui->treeView->rootIndex()); for (int tab = 0; tab <= TAB_COUNT; ++tab) { for (int i = 0; i < model_[tab]->rowCount(root); ++i) { QModelIndex section(model_[tab]->index(i, 0, root)); if (!section.isValid()) { break; } for (int j = 0; j < model_[tab]->rowCount(section); ++j) { QModelIndex index(model_[tab]->index(j, 0, section)); SessionTreeItem* item = static_cast(index.internalPointer()); const Session* s(item->session()); if (s == NULL) { continue; } if (s->shortDescription() == name) { // change the tab onTabButtonChanged(tab); // set selection ui->treeView->selectionModel()->clearSelection(); ui->treeView->selectionModel()->clear(); ui->treeView->selectionModel()->select(index, QItemSelectionModel::Select); ui->treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); ui->treeView->scrollTo(index); return true; } } } } return false; } bool Dialog::selectSessionByUuid(const QString& name) { QModelIndex root(ui->treeView->rootIndex()); for (int tab = 0; tab <= TAB_COUNT; ++tab) { for (int i = 0; i < model_[tab]->rowCount(root); ++i) { QModelIndex section(model_[tab]->index(i, 0, root)); if (!section.isValid()) { break; } for (int j = 0; j < model_[tab]->rowCount(section); ++j) { QModelIndex index(model_[tab]->index(j, 0, section)); SessionTreeItem* item = static_cast(index.internalPointer()); const Session* s(item->session()); if (s == NULL) { continue; } /* TODO: implement this here */ /* check if it is a vsession, then access the uuid and compare with name*/ if (s->type() == Session::VSESSION) { /* cast to vsession */ const VSession* v = static_cast(s); if (v->uuid() == name) { // change the tab onTabButtonChanged(tab); // set selection ui->treeView->selectionModel()->clearSelection(); ui->treeView->selectionModel()->clear(); ui->treeView->selectionModel()->select(index, QItemSelectionModel::Select); ui->treeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); ui->treeView->scrollTo(index); return true; } } } } } return false; } void Dialog::selectPreviousSession() { if (userInteracted_) { qDebug() << "Not selecting previous session as user interacted or session was already selected"; return; } if (!ChooserSettings::getSetting("last-session").isEmpty()) { qDebug() << "Trying to select last session: " << ChooserSettings::getSetting("last-session"); ui->treeView->clearSelection(); if (selectSession(ChooserSettings::getSetting("last-session"))) { qDebug() << "Success"; userInteracted_ = true; return; } } // could not find last session, change to last used tab if (!ChooserSettings::getSetting("last-tab").isEmpty()) { qDebug() << "Trying to select last tab " << ChooserSettings::getSetting("last-tab"); this->onTabButtonChanged(ChooserSettings::getSetting("last-tab").toInt()); } else { qDebug() << "Selected default tab " << defaultTab_; // Select default tab this->onTabButtonChanged(defaultTab_); } } void Dialog::startSession(const QString& name) { autoStartEntry_ = name; } void Dialog::setTheme() { QString label_l_style, label_r_style; QString backgroundColor, imageLeft, imageRight; QString themePathBase, themePathIni, themePathImgLeft, themePathImgRight; if (theme.isEmpty()) return; themePathBase = QString("%1/%2/").arg(VMCHOOSER_THEME_BASE).arg(theme); themePathIni = QString("%1%2.ini").arg(themePathBase).arg(theme); if (!QFile::exists(themePathIni)) return; QSettings themeSettings(themePathIni, QSettings::IniFormat); backgroundColor = themeSettings.value("background-color").toString(); imageLeft = themeSettings.value("image-left").toString(); imageRight = themeSettings.value("image-right").toString(); themePathImgLeft = QString("%1%2").arg(themePathBase).arg(imageLeft); themePathImgRight = QString("%1%2").arg(themePathBase).arg(imageRight); QRegExp re; ui->label_l->setPixmap(QPixmap(themePathImgLeft)); ui->label_r->setPixmap(QPixmap(themePathImgRight)); label_l_style = ui->label_l->styleSheet(); label_r_style = ui->label_r->styleSheet(); backgroundColor.prepend("\\1").append("\\2"); label_l_style.replace(QRegExp("(.*background-color:)#[^;]*(;.*)"), backgroundColor); label_r_style.replace(QRegExp("(.*background-color:)#[^;]*(;.*)"), backgroundColor); ui->label_l->setStyleSheet(label_l_style); ui->label_r->setStyleSheet(label_r_style); } void Dialog::onCenterTimer() { // center dialog on primary screen QRect desktopRect = QApplication::desktop()->availableGeometry(this); QPoint center = desktopRect.center(); if (center != oldCenter_) { if (_fullscreen) this->resize(desktopRect.width(), desktopRect.height()); this->move(center.x() - this->width() / 2, center.y() - this->height() / 2); oldCenter_ = center; } } void Dialog::addSessionsAfterDownload(QNetworkReply* reply) { QString temp_filename; if (reply->error() != QNetworkReply::NoError) { qDebug() << "Error reading from URL: " << reply->error(); QFile backup_file(xml_filename); if (!backup_file.open(QIODevice::ReadOnly)) { qDebug() << "Cannot read backup file " << xml_filename << " either"; this->removeStatusString(STR_LOADING); this->addStatusString(STR_URL_ERROR); return; } qDebug() << "Using backup file " << xml_filename; backup_file.close(); temp_filename = xml_filename; } else { QByteArray data = reply->readAll(); // write xml to temporary file temp_filename = QDir::tempPath() + "/vmchooser2/vmchooser-XXXXXX.xml"; QTemporaryFile tmpfile(temp_filename); if (!tmpfile.open() || tmpfile.write(data) == -1) { return; } tmpfile.close(); tmpfile.setAutoRemove(false); temp_filename = tmpfile.fileName(); } QList sessions = VSession::readXmlFile(temp_filename); this->removeStatusString(STR_LOADING); if (!sessions.isEmpty()) { qSort(sessions.begin(), sessions.end(), myLessThan); this->addItems(sessions, TAB_ALL_VMS); // TODO: Filter user's classes and add to tab[TAB_MY_COURSES] } else { this->addStatusString(STR_NO_ITEMS); } checkAutostart(); // select last-session selectPreviousSession(); userInteracted_ = true; } void Dialog::mousePressEvent(QMouseEvent * event) { QDialog::mousePressEvent(event); userInteracted_ = true; } void Dialog::treeView_selectionChanged(const QModelIndex& current, const QModelIndex&) { SessionTreeItem* item = static_cast(current.internalPointer()); if (item == NULL) return; const Session* s(item->session()); if (!s) { qDebug() << "invalid selection"; // no valid session has been selected, do nothing return; } QString description; if (s->type() == Session::VSESSION) { const VSession* vs = (VSession*) s; ui->label_creator->setText(vs->getAttribute("creator", "param")); ui->label_creator->setToolTip(vs->getAttribute("creator", "param")); ui->label_os->setText(vs->getAttribute("os_name", "param")); ui->label_os->setToolTip(vs->getAttribute("os_name", "param")); ui->label_platform->setText(vs->getAttribute("virtualizer_name", "param")); ui->label_platform->setToolTip(vs->getAttribute("virtualizer_name", "param")); if (vs->keywords().length() > 0) { description = "\n\nKeywords: "; for (int i = 0; i < vs->keywords().length(); ++i) { description += vs->keywords()[i] + ", "; } } } else { ui->label_creator->setText(""); ui->label_creator->setToolTip(""); ui->label_os->setText(QCoreApplication::instance()->translate("Dialog", "Native Linux")); ui->label_platform->setText("Linux"); ui->label_platform->setToolTip(""); } ui->label_name->setText(s->shortDescription()); ui->label_name->setToolTip(s->shortDescription()); ui->textBrowser->setPlainText(s->description() + description); } void Dialog::on_tabButtonLocal_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_NATIVE); } void Dialog::on_tabButtonMyClasses_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_MY_COURSES); } void Dialog::on_tabButtonAllClasses_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_ALL_VMS); } void Dialog::onTabButtonChanged(int tab) { if (tab < 0 || tab > 2) { // no valid button return; } // give focus to treeView if (!ui->treeView->hasFocus()) { ui->treeView->setFocus(); } // Update pressed status of buttons for (int i = 0; i < 3; ++i) { tabs_[i]->setChecked(tab == i); } // clear filter if necessary if (activeTab_ != tab) { this->ui->filterEdit->setText(""); } // load the new list setListModel(model_[tab]); this->activeTab_ = tab; /* get the first element ad select it */ selectFirstElement(); } void Dialog::on_filterEdit_textEdited() { userInteracted_ = true; } void Dialog::on_filterEdit_textChanged() { if (activeTab_ < 0 || activeTab_ >= TAB_COUNT) return; SessionTreeModel *newModel = NULL; // filter the current model if (!ui->filterEdit->text().isEmpty() && ui->filterEdit->text().replace(" ", "").length() > 2) { if (model_[activeTab_] != NULL) { newModel = new SessionTreeModel(this); newModel->addItems(this->model_[activeTab_]->lookForItem(ui->filterEdit->text())); } } if (newModel == NULL) { newModel = model_[activeTab_]; } if (newModel != NULL) { setListModel(newModel); } } void Dialog::setListModel(SessionTreeModel *model) { QAbstractItemModel *old = NULL; if (ui->treeView->model() == model_[0] || ui->treeView->model() == model_[1] || ui->treeView->model() == model_[2]) { } else { old = ui->treeView->model(); } ui->treeView->setModel(model); ui->treeView->expandAll(); if (!genericExpandedOnce_ && g_forLocationHandling == LOCATION_EXCLUSIVE && model->rowCount() > 1) { QModelIndex index = model->getSection(SECTION_GENERIC); if (index.isValid()) { ui->treeView->collapse(index); } index = model->getSection(SECTION_TEMPLATES); if (index.isValid()) { ui->treeView->collapse(index); } } if (old != NULL) { old->deleteLater(); } // reconnect the treeModel QObject::connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(treeView_selectionChanged(const QModelIndex&, const QModelIndex&))); // select the first element, if no element is selected if (ui->treeView->selectionModel()->selectedRows().count() == 0) { //ui->treeView->selectionModel()->setCurrentIndex(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select); ui->treeView->selectionModel()->select(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select); } } void Dialog::on_helpNewsButton_clicked() { if (ui->helpBox->isVisible()) { ui->helpBox->hide(); ui->newsBox->hide(); } else { ui->helpBox->show(); ui->newsBox->show(); } } void Dialog::addNewsAfterDownload(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { if (debugMode) { qDebug() << "Could not get news. Try to get cached news."; } // try to get cached news QFile backup_file(news_backup_filename); if (!backup_file.open(QIODevice::ReadOnly)) { if (debugMode) { qDebug() << "Cannot read backup file " << news_backup_filename << " either"; } this->ui->newsTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get news.")); return; } if (debugMode) { qDebug() << "Used backup file " << news_backup_filename; } backup_file.close(); return; } QByteArray data = reply->readAll(); QDomDocument doc; if (!doc.setContent(data)) { qDebug() << "News XML contains errors."; return; } QDomElement newsNode = doc.firstChildElement("news"); QDomElement timeNode = newsNode.firstChildElement("date"); QDomElement infoNode = newsNode.firstChildElement("info"); QDateTime timestamp; timestamp.setTime_t(timeNode.text().toInt()); if (timeNode.isNull() || infoNode.isNull()) { ui->newsTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get news. (//news/date or //news/info missing)")); } else { // format and print news ui->newsTextBrowser->setText(QString("

" + newsNode.firstChildElement("headline").text() + "

" + timestamp.toString(Qt::SystemLocaleShortDate) + "

" + infoNode.text() + "

")); } if (ChooserSettings::getSetting("last-news").toUInt() < timestamp.toTime_t()) { // show news if not seen before on_helpNewsButton_clicked(); } // update ini ChooserSettings::setSetting("last-news", QString::number(timestamp.toTime_t())); // make backup QFile file(news_backup_filename); if (!file.open(QIODevice::WriteOnly)) { if (debugMode) { qDebug() << "Could not write XML to " << news_backup_filename; } return; } if (file.write(data) != data.length()) { return; } if (!file.setPermissions(QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) { if (debugMode) { qDebug() << "Could not change permissions of file: " << news_backup_filename; } } file.close(); } void Dialog::addHelpAfterDownload(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { if (debugMode) { qDebug() << "Could not get help xml. Try to get cached help..."; } // try to get cached news QFile backup_file(help_backup_filename); if (!backup_file.open(QIODevice::ReadOnly)) { if (debugMode) { qDebug() << "Cannot read backup file " << help_backup_filename << " either"; } this->ui->helpTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get help.")); return; } if (debugMode) { qDebug() << "Used backup file " << help_backup_filename; } backup_file.close(); return; } QByteArray data = reply->readAll(); QDomDocument doc; if (!doc.setContent(data)) { if (debugMode) { qDebug() << "Help file contains errors."; } return; } QDomElement newsNode = doc.firstChildElement("news").firstChildElement("info"); if (newsNode.isNull()) { ui->helpTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get help (XML has no //news/info)")); } else { ui->helpTextBrowser->setText(newsNode.text()); } // make backup QFile file(help_backup_filename); if (!file.open(QIODevice::WriteOnly)) { if (debugMode) { qDebug() << "Could not write XML to " << help_backup_filename; } return; } if (file.write(data) != data.length()) { return; } if (!file.setPermissions(QFile::ReadUser | QFile::ReadGroup | QFile::ReadOther | QFile::WriteUser | QFile::WriteGroup | QFile::WriteOther)) { if (debugMode) { qDebug() << "Could not change permissions of file: " << help_backup_filename; } } file.close(); } void Dialog::keyPressEvent(QKeyEvent* event) { switch(event->key()) { case Qt::Key_Return: this->on_pushButtonStart_clicked(); break; case Qt::Key_Escape: this->on_pushButtonAbort_clicked(); break; case Qt::Key_H: this->on_helpNewsButton_clicked(); break; } QDialog::keyPressEvent(event); } void Dialog::iconDownloaded(const QUrl& url, const QIcon&) { qDebug() << "Icon downloaded... (" << url << ")"; // TODO: Check which model(s) contain an entry with this icon model_[TAB_MY_COURSES]->updateView(); model_[TAB_ALL_VMS]->updateView(); } void Dialog::on_leftKey() { int i = activeTab_; do { i = (i - 1 + TAB_COUNT) % TAB_COUNT; } while (!tabs_[i]->isEnabled()); onTabButtonChanged(i); } void Dialog::on_rightKey() { int i = activeTab_; do { i = (i + 1) % TAB_COUNT; } while (!tabs_[i]->isEnabled()); onTabButtonChanged(i); } /* when the user presses the space key, the current selection in the treeview * should be collapsed/expanded. */ void Dialog::on_spaceKey() { QModelIndex index = ui->treeView->selectionModel()->currentIndex(); if (ui->treeView->isExpanded(index)) { ui->treeView->collapse(index); } else { ui->treeView->expand(index); } } void Dialog::selectFirstElement() { QModelIndex root(ui->treeView->rootIndex()); QModelIndex section(model_[activeTab_]->index(0, 0, root)); QModelIndex newIndex = QModelIndex(model_[activeTab_]->index(0, 0, section)); ui->treeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select); ui->treeView->setFocus(); } /* install this filter to the filterEdit to listen for arrow keys */ bool Dialog::eventFilter(QObject*, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); bool fortv = false; if (keyEvent->key() == Qt::Key_Right) { on_rightKey(); fortv = true; } if (keyEvent->key() == Qt::Key_Left) { on_leftKey(); fortv = true; } if (keyEvent->key() == Qt::Key_Up) { ui->treeView->cursorUp(); fortv = true; } if (keyEvent->key() == Qt::Key_Down) { ui->treeView->cursorDown(); fortv = true; } if (fortv) { ui->treeView->setFocus(); return true; } } return false; } void Dialog::checkAutostart() { if (!autoStartEntry_.isEmpty()) { if (this->selectSessionByUuid(autoStartEntry_) || this->selectSession(autoStartEntry_)) { this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex()); } else { QMessageBox::critical(this, "Autostart", QString::fromUtf8("Konnte %1 nicht starten.").arg(autoStartEntry_)); } autoStartEntry_.clear(); return; } }