summaryrefslogblamecommitdiffstats
path: root/src/dialog.cpp
blob: 4f10897c6adfe709772e14e5b70eb76327e5fcc1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                   
                   







                         
                    
                         
                    
 



                     
                      

                            
                     
                       
                           
                          
 

                                                    

                                                                                                                                         
                               
                                           
 
                                                      
                                                                    
                                                       
                            
                                 
                                  
 
                      
 


                                                       
 



                                                                                             



                                                                         
                                                                          
                              
 

                                                
                                                           
 



                                        
                            
 

                                       

                                                                 
                                                                             
                                                                                                                      
                                                                                           
      
 

                                                                     
 

                                                                                       
                    
                                           

                                              




                                                           

                                             
     
 
                                                                       
                                        
 






                                                                      

                                           

                                                                                    
 



                   



                                     
                                              
                                


     
                                                                
 
                           
                                                                        



                                                                           
                        

                                                
                                      
                                                                       

                   



                                                                                 
                                                      
                                     




                                                   
     
                                                                         
                                                  
            
                                                   

     





                                                            
                  
                   
                                         
                                                                                    
                                                  

                                                                          

                                                                                         

                                                                

                                                     

                                                        
             



















                                                                              

                                                                                              



                                                        
                                     
         

                                                                                            
                                                              
                                                                                                      

                          
                              


                                                                             

 
                                                             

                                  

                                                                           
                                                                                    



                                    
                                                                



                                                              
                                     
                                    
                                                                                       







                                                                        

                                          


                                                      
                                                 


             
                                         
                                                               


                                        
                                      
                              


                                            


                                    

 
                                                

                                         





                                                                                            

 
                                                   

                                         





                                                                                            

 




                                           
                           
                                                                                    

 
                                          


                                                                
                                                                   

                                                


                                                    

                                                                                                                                  
                                                               
                                                                
                                   
                         

                                                                      

                                     
                                                                                               
                                                   
                                   

                             



                                                                                                   
                 

             
     
 










                                                                                                




                 
                                      



                                                                                                        
                                           

                                                               



                                                  
     

                                                                     
                                       
                                                  


                                   

         
                                                           
                                              
                                                            
                                          
            

                                                                  
                             
                                             
     





                                                
                         


                                                                             
 
                                      
               
 


                                                                           
 

                                                                       
               
     
 



                                                                         
 

                                                                           
 
                                                   
 




                                                       

                                               

                                              

 
 
                              
                                      



                                                                             
                             





                                                                                        


                             
                                                  
                                    
                                                                                 
                                    
                                                  
                                    
         


     



                                                       


                                                                  
                                 
 


                                                         
                                           





                                                                                                       


                                                                                                                               
 




                                                    
                                                     
             
                
                                                                       
                                                  
                                                                                               








                                                                                                     


                         
 





                                


                                                                                                                                                     
                                                                                                               


                                                                  
                                                                          



                                                      
                                                               


                                                                                                                                                        
                                                                                                                                         


                                                                                   
 

                                                                                                                                                          




                                           
                                                          


                    


                                                                                                                                                     



                                                                                                               

                                                                                                                                           
                                                              
         


       
 

                                                   
                           

 


                                                                                        
                          


                                            


                                      
                                        
                                                         
                                            


               
                        
                                         
                                                                  
 

                                                                            
 

                                                                       
 

                                                                                      
 

                                                                                            
 





                                                               
            
                                       
                                          
                                                                                                 

                                             
     


                                                                  
 

                                          

                                   


                                              
                           
                                           


                                               

                                    


                                          
                                      


                          


                                                                                    

                             

                                    
     

                                       
                                         
                                       


                                
                            




                                          
                           
 
                                      
                                            
                         
 

 



                                         
                                          
                                                          
                                         

                               
                                                                                                    
                                            



                                                                                              
                              
                                      
     
                              

                               

 
                                                    
                                      
                                                                                                                                                             
                                                                  
            
                                                                                     
                                    
     


                                                                                             
                                  
                              




                                                                                                        



                                                     
     
                         

                           
 
                              

                                                                                          
 



                                                                                                                                                      
     

 
                                          
                                   





                            
 
 
                                              


                                                                      
                                                                     
                   
     
                                  
 

                                                            

                                                                 
                                             
                                      
 
 
                           


                                            
                                                        


                          
                            


                                
                                                        


                          









                                                                             






                                                                                           
 





                                                                    
                                      
                                                              




                                               

                                              











                                              
                        
         

                                                    

                 
 

                                     
                                                   







                                                                                                                         
 








                                                                                         












                                                                                                    






                                                                             


































                                                                                                                                        
#include "dialog.h"
#include "config.h"

#include <QMessageBox>
#include <QDebug>
#include <QRegExp>
#include <QFile>
#include <QProcess>
#include <QTimer>
#include <QDesktopWidget>
#include <QDateTime>
#include <QTemporaryFile>
#include <QUrlQuery>

#include "unistd.h"
#include "stdio.h"
#include "sys/wait.h"

#include "ui_dialog.h"
#include "sessiontreeitem.h"
#include "globals.h"
#include "vsession.h"
#include "userconfig.h"
#include "filedownloader.h"
#include "windowmanager.h"

static bool isProcessRunning(const QString &binary);

static QDomDocument toDomDocument(const QString& what, const QByteArray& data, const QString& backupFile, const QString& mandatoryChild);

Dialog::Dialog(QWidget *parent)
    : QDialog(parent), ui(new Ui::Dialog) {

    model_[TAB_NATIVE] = new SessionTreeModel(parent);
    model_[TAB_RECENT_COURSES] = new SessionTreeModel(parent, true);
    model_[TAB_ALL_VMS] = new SessionTreeModel(parent);
    userInteracted_ = false;
    genericExpandedOnce_ = false;
    autoQuit_ = g_autoQuitSeconds;

    ui->setupUi(this);

    tabs_[TAB_NATIVE] = ui->tabButtonLocal;
    tabs_[TAB_RECENT_COURSES] = ui->tabButtonMyClasses;
    tabs_[TAB_ALL_VMS] = 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_, &QTimer::timeout, this, &Dialog::onCenterTimer);
    centerTimer_->start(1000);

    ui->treeView->setFocusProxy(ui->filterEdit);
    // ui->filterEdit->installEventFilter(this);
    QCoreApplication::instance()->installEventFilter(this);

    if (!UserConfig::isNewsHelpOpen()) {
        ui->helpBox->hide();
        ui->newsBox->hide();
    }
    ui->lblAutoQuit->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&)));
    */

    ui->PVS_checkbox->setVisible(Config::isSet(Config::PVS));
    ui->PVS_checkbox->setChecked(Config::isSet(Config::PVS_CHECKED));

    ui->chkNoScreenSaver->setVisible(Config::isSet(Config::ALLOW_SCREENSAVER_DISABLE));

    activeTab_ = -1;
    if (Config::isSet(Config::EXAM_MODE)) {
        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);
    }

    ui->chkAdminMode->setVisible(Config::isSet(Config::ALLOW_VM_EDIT));
    ui->chkAdminMode->setEnabled(false);

	ui->btnScreenSetup->setVisible(isProcessRunning("beamergui"));
	if (QApplication::screens().size() > 1) {
		QFont copy = ui->btnScreenSetup->font();
		copy.setBold(true);
		ui->btnScreenSetup->setFont(copy);
	}

    // TODO: Implement bug report dialog :)
    ui->buttonBugReport->setEnabled(false);
    QObject::connect(SessionsIconHolder::get(), &SessionsIconHolder::iconDownloaded,
            this, &Dialog::iconDownloaded);

}

Dialog::~Dialog() {
    delete ui;
}

void Dialog::changeEvent(QEvent *e) {
    QDialog::changeEvent(e);
    if (e->type() == QEvent::LanguageChange) {
        ui->retranslateUi(this);
    }
}

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<SessionTreeItem*>(index.internalPointer());

    if (item == nullptr)
            return; // do nothing if cast failed

    const Session* s(item->session());
    if (s == nullptr) // no valid session has been selected, do nothing
            return;

    // Check preconditions. The method might show error messages and return false
    if (!s->canRun(true))
        return;

    // These two are up here in case run-virt cares...
    if (Config::isSet(Config::PVS)) {
        if (ui->PVS_checkbox->isChecked()) {
            setenv("PVS_AUTO_CONNECT", "TRUE", 1);
        } else {
            setenv("PVS_AUTO_CONNECT", "FALSE", 1);
        }
    }
    if (ui->chkAdminMode->isEnabled() && ui->chkAdminMode->isChecked()) {
        setenv("VMCHOOSER_ADMIN_MODE", "TRUE", 1);
    } else {
        setenv("VMCHOOSER_ADMIN_MODE", "FALSE", 1);
    }

    if (ui->chkNoScreenSaver->isChecked()) {
        setenv("VMCHOOSER_DISABLE_SCREENSAVER", "TRUE", 1);
    } else {
        setenv("VMCHOOSER_DISABLE_SCREENSAVER", "FALSE", 1);
    }

    // Run session
    if (s->run()) {
        WindowManager::stopOwnInstance();
        // Run session start script if the session could be initialized successfully
        if (QFile::exists(SESSION_START_SCRIPT)) {
            // Use the current environment variables and add the necessary
            // information for the startUpScipt.
            // 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 args[] = { strdup(VMCHOOSER_SESSION_START_SCRIPT), nullptr };
                    execv(VMCHOOSER_SESSION_START_SCRIPT, args);
                }
                _exit(0); // Dont use exit hooks
            }
            // Wait and cleanup the intermediate process
            waitpid(pid, nullptr, 0);
        }
        UserConfig::addLastSession(s->uuid().isEmpty() ? s->shortDescription() : s->uuid());
        UserConfig::setLastTab(activeTab_);
        UserConfig::setNewsHelpOpen(!ui->helpBox->isHidden());
        centerTimer_->stop(); // Stop the auto-center/auto-quit timer, so we don't kill the session :>
        setVisible(false);
    } else {
        QMessageBox::critical(
                    this, trUtf8("vmchooser"),
                    trUtf8("Vmchooser failed to run the selected session!"));
    }
}

void Dialog::on_treeView_expanded(const QModelIndex& index) {
    if (activeTab_ != TAB_ALL_VMS)
        return;
    SessionTreeItem* item =
                    static_cast<SessionTreeItem*>(index.internalPointer());
    if (item->session() == nullptr && item->sectionType() != SECTION_FOR_LOCATION) {
        genericExpandedOnce_ = true;
    }
}

void Dialog::addItems(const QList<Session*>& entries, int tab) {
    if (tab < 0 || tab >= TAB_COUNT)
        return; // Invalid index
    if (Config::isSet(Config::EXAM_MODE) && tab == TAB_NATIVE)
        return; // Exam mode -- keep native sessions empty
    ui->treeView->blockSignals(true);
    if (tab != TAB_RECENT_COURSES) {
        // If tab is not the recent courses tab, check the list for any recent sessions
        auto prev = UserConfig::getLastSessions();
        if (!prev.isEmpty()) {
            QList<Session*> matches;
            for (auto it : entries) {
                if ((!it->uuid().isEmpty() && prev.contains(it->uuid()))
                        || prev.contains(it->shortDescription())) {
                    matches.append(it);
                }
                // Also check for validity
                it->checkCanRun();
            }
            if (!matches.isEmpty()) {
                addItems(matches, TAB_RECENT_COURSES);
                ui->treeView->blockSignals(true);
            }
        }
    }
    this->model_[tab]->addItems(entries);
    tabs_[tab]->setEnabled(this->model_[tab]->rowCount() != 0);
    if (tab == activeTab_) {
        setListModel(this->model_[tab]);
    }
    ui->treeView->blockSignals(false);
    model_[tab]->updateView();
    if (!ui->filterEdit->text().isEmpty()) {
        on_filterEdit_textChanged();
    }
    if (tab != TAB_RECENT_COURSES) {
        selectPreviousSession();
    }
}

void Dialog::addStatusString(const int status) {
    if (status < 0 || status >= STR__MAX)
        return;
    this->model_[TAB_RECENT_COURSES]->addLabelItem(strings_[status]);
    this->model_[TAB_ALL_VMS]->addLabelItem(strings_[status]);
    tabs_[TAB_RECENT_COURSES]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1);
    tabs_[TAB_ALL_VMS]->setEnabled(this->model_[TAB_ALL_VMS]->rowCount() > 1);
    model_[TAB_RECENT_COURSES]->updateView();
    model_[TAB_ALL_VMS]->updateView();
}

void Dialog::removeStatusString(const int status) {
    if (status < 0 || status >= STR__MAX)
        return;
    this->model_[TAB_RECENT_COURSES]->removeItem(strings_[status]);
    this->model_[TAB_ALL_VMS]->removeItem(strings_[status]);
    tabs_[TAB_RECENT_COURSES]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1);
    tabs_[TAB_ALL_VMS]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1);
    model_[TAB_RECENT_COURSES]->updateView();
    model_[TAB_ALL_VMS]->updateView();
}

void Dialog::on_pushButtonAbort_clicked() {
    close();
}

void Dialog::on_pushButtonStart_clicked() {
    userInteracted_ = true;
    this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex());
}

void Dialog::on_btnScreenSetup_clicked() {
	QProcess::startDetached("beamergui", QStringList("-w"));
}

bool Dialog::selectSession(const QString& name, int preferredTab) {
    QModelIndex root(ui->treeView->rootIndex());

    int bestTab = -1;
    QModelIndex bestIndex;
    for (int tab = TAB_COUNT - 1; tab >= 0; --tab) {
        if (bestTab != -1 && preferredTab != tab) // We already have a potential match, only keep going if this is the desired tab
            continue;
        for (int i = 0; i < model_[tab]->rowCount(root); ++i) {
            QModelIndex section(model_[tab]->index(i, 0, root));
            if (!section.isValid())
                continue;
            for (int j = 0; j < model_[tab]->rowCount(section); ++j) {
                QModelIndex index(model_[tab]->index(j, 0, section));
                if (!index.isValid())
                    continue;
                SessionTreeItem* item = static_cast<SessionTreeItem*>(index.internalPointer());
                const Session* s = item->session();
                if (s == nullptr) {
                    continue;
                }
                if ((!s->uuid().isEmpty() && s->uuid() == name) || s->shortDescription() == name) {
                    bestTab = tab;
                    bestIndex = index;
                    break; // Break inner, keep checking other tabs
                }
            }
        }
    }

    if (bestTab != -1) {
        // change the tab
        qDebug() << "Best tab is" << bestTab;
        onTabButtonChanged(bestTab);
        // set selection
        ui->treeView->selectionModel()->clearSelection();
        ui->treeView->selectionModel()->clear();
        ui->treeView->selectionModel()->select(bestIndex, QItemSelectionModel::Select);
        ui->treeView->selectionModel()->setCurrentIndex(bestIndex, QItemSelectionModel::Select);
        ui->treeView->scrollTo(bestIndex);
        return true;
    }

    return false;
}

void Dialog::selectPreviousSession() {
    if (userInteracted_) {
        qDebug() << "Not selecting previous session as user interacted or session was already selected";
        return;
    }
    int lastTab = UserConfig::getLastTab();
    QString lastSession = Config::get(Config::DEFAULT_SESSION);
    if (lastSession.isEmpty()) {
        auto list = UserConfig::getLastSessions();
        if (!list.isEmpty()) {
            lastSession = list.back();
        }
    }
    if (!lastSession.isEmpty()) {
        qDebug() << "Trying to select last session: " << lastSession;
        ui->treeView->clearSelection();
        if (selectSession(lastSession, lastTab)) {
            qDebug() << "Success";
            userInteracted_ = true;
            return;
        }
    }
    // could not find last session, change to last used tab
    if (lastTab >= 0 && lastTab < TAB_COUNT) {
        qDebug() << "Trying to select last tab " << lastTab;
        this->onTabButtonChanged(lastTab);
    } else {
        int defaultTab = Config::get(Config::DEFAULT_TAB).toInt();
        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 (!Config::isSet(Config::THEME))
        return;

    QString theme(Config::get(Config::THEME));
    themePathBase = QString("%1/%2/").arg(VMCHOOSER_THEME_BASE).arg(theme);
    themePathIni = QString("%1%2.ini").arg(themePathBase).arg(theme);

    if (!QFile::exists(themePathIni)) {
        qDebug() << "Theme config" << themePathIni << "does not exist";
        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("(.*background-color:)#[^;]*(;.*)");

    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(re, backgroundColor);
    label_r_style.replace(re, backgroundColor);
    ui->label_l->setStyleSheet(label_l_style);
    ui->label_r->setStyleSheet(label_r_style);
}


void Dialog::onCenterTimer() {
    // center dialog on primary screen
    if (isVisible()) {
        QRect desktopRect = QApplication::desktop()->availableGeometry(this);
        QPoint center = desktopRect.center();
        if (center != oldCenter_) {
            if (g_fullscreen)
                this->resize(desktopRect.width(), desktopRect.height());
            this->move(center.x() - this->width() / 2, center.y() - this->height() / 2);
            oldCenter_ = center;
        }
    }
    // Handle auto-quit timeout
    if (autoQuit_ > 0) {
        autoQuit_--;
        if (autoQuit_ == 0) {
            QCoreApplication::instance()->exit(0);
        } else if (autoQuit_ < 60) {
            ui->lblAutoQuit->setText(trUtf8("Auto logout in %1").arg(autoQuit_));
            ui->lblAutoQuit->show();
        } else if (!ui->lblAutoQuit->isHidden()) {
            ui->lblAutoQuit->hide();
        }
    }
}

/**
 * Download lecture list, news and help
 */
void Dialog::downloadData(const QString& locationIds) {
    QUrl listUrl(Config::isSet(Config::URL_LIST)
                 ? Config::get(Config::URL_LIST)
                 : Config::get(Config::URL_BASE).append("/list"));
    QUrlQuery listQuery(listUrl);

    if (!locationIds.isEmpty()) {
        listQuery.addQueryItem("locations", locationIds);
    }
    if (Config::isSet(Config::EXAM_MODE)) {
        listQuery.addQueryItem("exams", "exam-mode");
    }
    listUrl.setQuery(listQuery);
    //
    // Download lecture XML
    FileDownloader::download(listUrl, [this](QNetworkReply::NetworkError err, const QByteArray& data) {
        QList<Session*> sessions;
        QDomDocument doc = toDomDocument(QStringLiteral("lecture list"), data, TEMP_PATH_XML_LIST, QStringLiteral("settings"));
        sessions = VSession::loadFromXmlDocument(doc);

        this->removeStatusString(STR_LOADING);
        if (sessions.isEmpty()) {
            if (err == QNetworkReply::NoError) {
                this->addStatusString(STR_NO_ITEMS);
            } else {
                this->addStatusString(STR_URL_ERROR);
            }
        } else {
            qSort(sessions.begin(), sessions.end(), sessionComparator);
            this->addItems(sessions, TAB_ALL_VMS);
            bool showEdit = false; // Only show edit button if at least one lecture is editable
            for (QList<Session*>::const_iterator it = sessions.begin(); it != sessions.end(); ++it) {
                if (reinterpret_cast<VSession*>(*it)->canEdit()) {
                    showEdit = true;
                    break;
                }
            }
            if (showEdit) {
                ui->chkAdminMode->setVisible(true);
            }
        }

        checkAutostart();

        // select last-session
        selectPreviousSession();
        userInteracted_ = true;
    });
    //
    // News
    FileDownloader::download(QUrl(Config::isSet(Config::URL_NEWS)
                                  ? Config::get(Config::URL_NEWS)
                                  : Config::get(Config::URL_BASE).append("/news")), [this](QNetworkReply::NetworkError err, const QByteArray& data) {
        QDomDocument doc = toDomDocument(QStringLiteral("news"), data, TEMP_PATH_NEWS, QStringLiteral("news"));
        QDomElement newsNode = doc.firstChildElement("news");
        QDomElement timeNode = newsNode.firstChildElement("date");
        QDomElement infoNode = newsNode.firstChildElement("info");
        QDomElement headlineNode = newsNode.firstChildElement("headline");
        QDateTime timestamp;
        timestamp.setTime_t(timeNode.text().toUInt());

        if (timeNode.isNull() || infoNode.isNull()) {
            qDebug() << "Could not load news. Network:" << err;
            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("<p style='font-size:16px; margin-bottom: 2px;'>" + headlineNode.text() + "</p> <small>"
                    + timestamp.toString(Qt::SystemLocaleShortDate) + "</small><p>"
                    + infoNode.text() + "</p>"));
        }

        if (ui->helpBox->isHidden()
                && (UserConfig::getLastNewsTime() < timestamp.toTime_t() || UserConfig::getLastNewsTime() > QDateTime::currentMSecsSinceEpoch() / 1000)) {
            // show news if not seen before
            on_helpNewsButton_clicked();
        }

        // update ini
        UserConfig::setLastNewsTime(timestamp.toTime_t());
    });
    //
    // Download help
    FileDownloader::download(QUrl(Config::isSet(Config::URL_HELP)
                                  ? Config::get(Config::URL_HELP)
                                  : Config::get(Config::URL_BASE).append("/help")), [this](QNetworkReply::NetworkError err, const QByteArray& data) {
        QDomDocument doc = toDomDocument(QStringLiteral("help"), data, TEMP_PATH_HELP, QStringLiteral("news"));
        QDomElement helpTextNode = doc.firstChildElement("news").firstChildElement("info");
        if (helpTextNode.isNull()) {
            qDebug() << "Could not load help. Network:" << err;
            ui->helpTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get help (XML has no //news/info)"));
        } else {
            ui->helpTextBrowser->setText(helpTextNode.text());
        }
    });
    //
}

void Dialog::mousePressEvent(QMouseEvent * event) {
    QDialog::mousePressEvent(event);
    userInteracted_ = true;
}

void Dialog::treeView_selectionChanged(const QModelIndex& current, const QModelIndex&) {
    SessionTreeItem* item =
            static_cast<SessionTreeItem*>(current.internalPointer());
    if (item == nullptr) {
        ui->chkAdminMode->setEnabled(false);
        return;
    }

    const Session* s(item->session());
    if (!s) {
        qDebug() << "invalid selection";
        // no valid session has been selected, do nothing
        ui->chkAdminMode->setEnabled(false);
        return;
    }

    QString description;
    if (s->type() == Session::VSESSION) {
        const VSession* vs = reinterpret_cast<const 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"));

        // TODO: This is a bug? vs->canEdit() seems completely pointless right now...
        ui->chkAdminMode->setEnabled(vs->canEdit() || Config::isSet(Config::ALLOW_VM_EDIT));

        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_RECENT_COURSES);
}

void Dialog::on_tabButtonAllClasses_clicked() {
    userInteracted_ = true;
    onTabButtonChanged(TAB_ALL_VMS);
}

void Dialog::onTabButtonChanged(int tab) {
    if (tab < 0 || tab >= TAB_COUNT) {
        // no valid button
        return;
    }
    // Block this so it won't trigger the expand signal while refilling, which would
    // interfere with LOCATION_EXCLUSIVE mode
    ui->treeView->blockSignals(true);

    // give focus to treeView
    if (!ui->treeView->hasFocus()) {
        ui->treeView->setFocus();
    }

    // Update pressed status of buttons
    for (int i = 0; i < TAB_COUNT; ++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;

    ui->treeView->blockSignals(false);
    /* 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 = nullptr;

    // filter the current model
    if (!ui->filterEdit->text().isEmpty() && ui->filterEdit->text().replace(" ", "").length() > 2) {
        if (model_[activeTab_] != nullptr) {
            newModel = new SessionTreeModel(this);
            newModel->addItems(this->model_[activeTab_]->lookForItem(ui->filterEdit->text()));
        }
    }
    if (newModel == nullptr) {
        newModel = model_[activeTab_];
    }
    if (newModel != nullptr) {
        setListModel(newModel);
    }
}

void Dialog::setListModel(SessionTreeModel *model) {
    QAbstractItemModel *old = nullptr;
    if (ui->treeView->model() == model_[TAB_NATIVE] || ui->treeView->model() == model_[TAB_RECENT_COURSES] || ui->treeView->model() == model_[TAB_ALL_VMS]) {
        // A default model is currently being set; don't delete it
    } else {
        // Remember currently set model so we can delete it after setting the new one
        old = ui->treeView->model();
    }
    // Disconnect current model
    QObject::disconnect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged,
            this, &Dialog::treeView_selectionChanged);
    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 != nullptr) {
        old->deleteLater();
    }

    // reconnect the treeModel
    QObject::connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged,
            this, &Dialog::treeView_selectionChanged);

    // 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->isHidden()) {
        ui->helpBox->hide();
        ui->newsBox->hide();
    } else {
        ui->helpBox->show();
        ui->newsBox->show();
    }
}

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;
    default: 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_RECENT_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() && i != activeTab_);
    onTabButtonChanged(i);
}

void Dialog::on_rightKey() {
    int i = activeTab_;
    do {
        i = (i + 1) % TAB_COUNT;
    } while (!tabs_[i]->isEnabled() && i != activeTab_);
    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) {
        autoQuit_ = g_autoQuitSeconds;
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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;
        }
    } else if (event->type() == QEvent::MouseMove) {
        autoQuit_ = g_autoQuitSeconds;
    }
    return false;
}
void Dialog::checkAutostart() {
    if (!autoStartEntry_.isEmpty()) {
        if (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;
    }
}

void Dialog::showEvent(QShowEvent *e)
{
    QDialog::showEvent(e);
    // Make sure a window manager is running, so any modals or other popups work properly
    QTimer::singleShot(10, []() {
        WindowManager::ensureRunning();
    });
}

static bool isProcessRunning(const QString &binary)
{
	bool full = binary.contains('/');
	QDir proc("/proc");
	for (auto entry : proc.entryInfoList(QStringList(), QDir::NoDotAndDotDot | QDir::AllDirs)) {
		if (!entry.isReadable())
			continue;
		QDir dest = QDir(entry.filePath() + "/exe");
		if (!dest.isReadable())
			continue;
		QString wurst = dest.canonicalPath();
		if (wurst.isEmpty())
			continue;
		if (full && wurst == binary)
			return true;
		if (!full && wurst.mid(wurst.lastIndexOf('/') + 1) == binary)
			return true;
	}
	return false;
}

static QDomDocument toDomDocument(const QString& what, const QByteArray& data, const QString& backupFile, const QString& mandatoryChild)
{
    QDomDocument doc;
    if (!data.isEmpty()) {
        if (!doc.setContent(data)) {
            qDebug() << "Downloaded" << what << "XML contains errors.";
        }
    } else {
        qDebug() << "No content downloaded for" << what;
    }
    QFile backup(backupFile);
    if (doc.isNull() || !doc.hasChildNodes()) {
        if (backup.open(QFile::ReadOnly)) {
            if (!doc.setContent(&backup)) {
                qDebug() << "Could not load" << what << "backup.";
            }
        }
    }
    if (doc.isNull() || !doc.hasChildNodes())
        return QDomDocument(); // Above methods failed
    if (doc.firstChildElement(mandatoryChild).isNull()) {
        qDebug() << "Downloaded" << what << "xml doesn't contain mandatory root node" << mandatoryChild;
        return QDomDocument();
    }
    if (!backup.isOpen()) {
        // If it were already open we'd have read the backup, so no need to write it out again...
        if (!backup.open(QFile::WriteOnly)) {
            qDebug() << "Cannot open" << what << "backup file" << backupFile << "for writing";
        } else {
            backup.write(data);
        }
    }
    return doc;
}