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








                         
                    
                         
 



                     
                      

                            
                     
                            


                                           



                                             
                      
 


                                       
 



                                                                                             



                                                                         
                                                                          
                              
 

                                              
 

                        
 

                                                                     

                                       

                                                                 
                                                                                                                      
                                                                                           
      

                                


                                           



                   


                     












                                     
                                                         
 
                                                                        



                                                                           


                                                












                                                                          
                                                                      




































                                                                                       

 
                                                                


                             
                                         
                                                               


                                        
                              

 
                                                

                                         



                                                          

                            

 
                                                   

                                         



                                                          

                            

 




                                           
                                                                                    

 


                                                 
                                        














                                                                                           


                                                                                                    


                            
     
 



                                      


                                                                





                                                                                      
                                                   





                                                
                         


                                                                             
 
                                
 

                                                                           
 
                                             

 



                                                                         
 

                                                                           
 
               
 






                                                                                        
                                                 

                                              

 
                              

                                                   
                                                                                            









                                                                                                                         

                                                                    




                                                                                    
                                                             












                                                                                    

                                                  























                                                                              
                                          




                                                                                           
                                            

     


                             

                            

 


                                                                                        
                             


                                      


                                            




                                                         
                                           
 

                                                                                   
 

                                                                            
 

                                                                  
 



                                                                                              
 
                                              

            

                                                       

                                                                                                                                                            

     

                                          
                          


                                              
                          


                                               
                          


                                          









                                       
                                       


                                
                            


                                          
                   
                                                                     
 

                              
                           

                                                     
                                                                                                                                       

                                                                                                                                                                    


                                          




                                                                                               
                                                                                          
            
                                      

                           


                                                      




                                                                                                                         
 
                              
                                                                                                                    
                                                                                           
 



                                                                                                                                                      
     

 
                                          






                                   
 

                                                         


























                                                                                                                         



                                                                           

                                                                                                                                                           

                                                                           
 
                                                                                   

                                       



                                                                                    
 

















                                                                                                                                                 


                                                         



















































                                                                                                                                                 
 
 
                                              




                                                                      
 
#include "dialog.h"

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

#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(QWidget *parent)
    : QDialog(parent), ui(new Ui::Dialog) {
    model_[0] = new SessionTreeModel(parent);
    model_[1] = new SessionTreeModel(parent);
    model_[2] = new SessionTreeModel(parent);

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

    activeTab_ = 2;
    ui->tabButtonAllClasses->setChecked(true);

    ui->helpBox->hide();
    ui->newsBox->hide();

    oldRow_ = 0; // this is the old row (when switching to a new tab)

    this->addStatusString(STR_LOADING);

    /*
     * TODO: why connect signal/slot when no item was loaded yet?
    QObject::connect(ui->treeView->selectionModel(), SIGNAL(currentChanged ( const QModelIndex&, const QModelIndex&)),
            this, SLOT(treeView_selectionChanged(const QModelIndex&, const QModelIndex&)));
    */

    this->onTabButtonChanged(2);

    // TODO: Implement bug report dialog :)
    ui->buttonBugReport->setEnabled(false);
}

Dialog::~Dialog() {
    delete ui;
    delete model_[0];
    delete model_[1];
    delete model_[2];
}

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(QModelIndex index)
{
    // this method gets called when a Session has been selected to start

    SessionTreeItem* item =
                    static_cast<SessionTreeItem*>(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);
            setenv("SESSION_NAME", s->shortDescription().toUtf8(), 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::addItems(const QList<Session*>& entries, int tab) {
    if (tab < 0 || tab > 2) {
        return;
    }
    this->model_[tab]->addItems(entries);
    tabs_[tab]->setEnabled(this->model_[tab]->rowCount() != 0);
    if (tab == activeTab_) {
        setListModel(this->model_[tab]);
    }
    model_[tab]->updateView();
}

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() {
    this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex());
}

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

    for (int tab = 0; tab <= 2; ++tab) {
        for (int i = 0; i < model_[tab]->rowCount(root); ++i) {
            QModelIndex index = model_[tab]->index(i, 0, root);
            if (!index.isValid()) {
                break;
            }
            SessionTreeItem* item = static_cast<SessionTreeItem*>(index.internalPointer());
            const Session* s(item->session());
            if (!s) {
                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);
                return true;
            }
        }
    }

    return false;
}

void Dialog::selectPreviousSession() {
    if (ChooserSettings::getSetting("last-session").isEmpty()) {
        this->onTabButtonChanged(2);
    } else {
        ui->treeView->clearSelection();
        if (!selectSession(ChooserSettings::getSetting("last-session"))) {
            // could not find last session, change to last used tab
            this->onTabButtonChanged(ChooserSettings::getSetting("last-tab").toInt());
        }
    }
    //setListModel(this->model_[this->activeTab_]);
}

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);
    //qDebug() << label_r_style << label_l_style;
    ui->label_l->setStyleSheet(label_l_style);
    ui->label_r->setStyleSheet(label_r_style);
}

void Dialog::onCenterTimer() {
    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;
    }
    // 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) {
        if (debugMode) {
            qDebug() << "Error reading from URL: " << reply->error();
        }

        QFile backup_file(xml_filename);

        if (!backup_file.open(QIODevice::ReadOnly)) {
            if (debugMode) {
                qDebug() << "Cannot read backup file " << xml_filename << " either";
            }
            this->removeStatusString(STR_LOADING);
            this->addStatusString(STR_URL_ERROR);
            return;
        }
        if (debugMode) {
            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<Session*> sessions = VSession::readXmlFile(temp_filename);

    this->removeStatusString(STR_LOADING);

    if (!sessions.isEmpty()) {
        qSort(sessions.begin(), sessions.end(), myLessThan);
        this->addItems(sessions, 2); // TODO: No magic number; handle user specific classes
    } else {
        this->addStatusString(STR_NO_ITEMS);
    }

    // give focus to treeView
    ui->treeView->setFocus();

    // select last-session
    selectPreviousSession();
}

void Dialog::treeView_selectionChanged(const QModelIndex& current, const QModelIndex&) {
    SessionTreeItem* item =
            static_cast<SessionTreeItem*>(current.internalPointer());
    if (item == NULL) return;

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

    if (s->type() == Session::VSESSION) {
        const VSession* vs = (VSession*) s;

        ui->label_name->setText(vs->getAttribute("short_description", "param"));
        ui->label_name->setToolTip(vs->getAttribute("short_description", "param"));

        ui->label_creator->setText(vs->getAttribute("creator", "param"));
        ui->label_creator->setToolTip(vs->getAttribute("creator", "param"));

        ui->label_os->setText(vs->getAttribute("os", "param"));
        ui->label_os->setToolTip(vs->getAttribute("os", "param"));

        QString description(vs->getAttribute("long_description", "param") + "\n\nKeywords: ");
        for (int i = 0; i < vs->keywords().length(); ++i) {
            description += vs->keywords()[i] + ", ";
        }

        ui->textBrowser->setText(description);

    } else {
        ui->label_name->setText(s->shortDescription());
        ui->label_creator->setText("");
        ui->label_os->setText(QCoreApplication::instance()->translate("Dialog", "Native Linux"));
        ui->textBrowser->setPlainText(QCoreApplication::instance()->translate("Dialog", "Linux X session running on this machine without virtualization."));
    }
}

void Dialog::on_tabButtonLocal_clicked() {
    onTabButtonChanged(0);
}

void Dialog::on_tabButtonMyClasses_clicked() {
    onTabButtonChanged(1);
}

void Dialog::on_tabButtonAllClasses_clicked() {
    onTabButtonChanged(2);
}

void Dialog::onTabButtonChanged(int tab) {
    if (tab < 0 || tab > 2) {
        // no valid button
        return;
    }

    // give focus to treeView
    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("");
    }

    // save old row
    //oldRow_ = ui->treeView->selectionModel()->currentIndex().row();

    // load the new list
    setListModel(model_[tab]);
    this->activeTab_ = tab;
    ui->treeView->selectionModel()->clear();
    ui->treeView->selectionModel()->clearSelection();
    ui->treeView->selectionModel()->select(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select);
    ui->treeView->selectionModel()->setCurrentIndex(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select);
    //treeView_selectionChanged(ui->treeView->model()->index(oldRow_, 0, ui->treeView->rootIndex()), ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()));
}

void Dialog::on_filterEdit_textChanged() {
    SessionTreeModel *newModel;

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

void Dialog::setListModel(QAbstractItemModel *model) {
    if (ui->treeView->model() == model_[0] || ui->treeView->model() == model_[1] || ui->treeView->model() == model_[2]) {
    } else {
        ui->treeView->model()->deleteLater();
    }
    ui->treeView->setModel(model);

    // 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");
    QDateTime timestamp;
    timestamp.setTime_t(newsNode.firstChildElement("date").text().toInt());

    // format and print news
    ui->newsTextBrowser->setText(QString("<p style='font-size:16px; margin-bottom: 2px;'>" + newsNode.firstChildElement("headline").text() + "</p> <small>"
            + timestamp.toString(Qt::SystemLocaleShortDate) + "</small><p>"
            + newsNode.firstChildElement("info").text() + "</p>"));

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

    ui->helpTextBrowser->setText(QString(data));

    // 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;
    }
}