5
\$\begingroup\$

I've recently released my first ever Qt application, and would be glad if someone will look into it and tell me if there are some flagrant anti-patterns and likes.

This grabs some screenshots (paths) from the user and copies them to respective Steam directories, converting them to JPEG if necessary. After the copying it registers them in Steam's special screenshots.vdf file. The interface is shown here. After all this is done, the user can open Steam, go to the Screenshot Uploader there and find all the new screenshots available for uploading to the Steam Cloud.

I haven't designed my app. I just started to write it and the design started to form. This is horrendous, I know, but that's how I did it.

Anyway, the tool works on every supported platform and seems like doing its job well.

The code is hosted on GitHub.

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QNetworkReply>
#include <QSettings>


namespace Ui {
class MainWindow;
}


class MainWindow : public QMainWindow
{
    Q_OBJECT


public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();


protected:
    void showEvent(QShowEvent *event) Q_DECL_OVERRIDE;
    void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;


private slots:
    void warnOnMissingVDF();
    void getGameNames(QNetworkReply *reply);
    void on_pushButtonLocateSteamDir_clicked();
    void on_pushButtonAddScreenshots_clicked();
    void on_pushButtonClearQueue_clicked();
    void on_pushButtonCopyScreenshots_clicked();
    void on_pushButtonPrepare_clicked();


signals:
    void vdfIsMissing();


private:
    Ui::MainWindow *ui;
    void setUserDataPaths(QString dir);
    void writeSettings();
    void readSettings();
    void populateScreenshotQueue(QStringList screenshotPathsList);
    void disableAllOnMissingSteamDir();
    void pushScreenshots();
    void toggleLabelInfo(bool isVisible);
    QString convertSlashes(QString str);
    QStringList readVDF();
    void writeVDF(QStringList lines);

    #if defined(Q_OS_WIN32)
    const QString os = "Windows";
    #elif defined(Q_OS_LINUX)
    const QString os = "Linux";
    #elif defined(Q_OS_OSX)
    const QString os = "macOS";
    #endif

    bool isUnixLikeOS;
    bool isFirstStart;
    const QString vdfFilename = "screenshots.vdf";
    QString selectedUserID;
    QString userDataDir;
    QString defaultSteamDir;
    QSettings *settings;
    QString steamDir;
    QStringList vdfPaths;
    QString userID;
    QString someID;
    QStringList gameIDs;
    QHash<QString, QString> games;
    QStringList screenshotPathsPool;
    QStringList lines;
    QString selectedGameID;
    QString lastSelectedScreenshotDir;
    QString lastSelectedUserID;
    QString lastSelectedGameID;
    QStringList copiedGames;
    bool nothingAddedToVDF = true;
    int copiedScreenshotsNum = 0;
    int copiedDirsToNum = 0;
};

#endif // MAINWINDOW_H

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QDir>
#include <QDirIterator>
#include <QListIterator>
#include <QList>
#include <QString>
#include <QStringList>
#include <QTreeWidgetItem>
#include <QDateTime>
#include <QSize>
#include <QImage>
#include <QIODevice>
#include <QTextStream>
#include <QRegularExpression>
#include <QMessageBox>
#include <QObject>
#include <QImageWriter>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonArray>
#include <QTimer>
#include <QSettings>
#include <QCloseEvent>
#include <QRect>
#include <QDesktopWidget>


MainWindow::MainWindow(QWidget *parent) :

    QMainWindow(parent),
    ui(new Ui::MainWindow)

{
    ui->setupUi(this);

    QList<QPushButton*> buttonList; // list of buttons for setting a different padding for each OS
    buttonList << ui->pushButtonClearQueue << ui->pushButtonCopyScreenshots << ui->pushButtonAddScreenshots << ui->pushButtonPrepare;

    if ( (os == "Linux") | (os == "macOS") ) {
        isUnixLikeOS = true;
        if ( os == "Linux" ) {
            settings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, "Foyl", "SteaScree");
            defaultSteamDir = QDir::homePath() + "/.steam/steam";
            foreach (QPushButton *button, buttonList)
                button->setStyleSheet("padding: 3px 13px");
        } else {
            settings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, "foyl.io", "SteaScree");
            defaultSteamDir = QDir::homePath() + "/Library/Application Support/Steam";
        };
    } else {
        isUnixLikeOS = false;
        settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, "Foyl", "SteaScree");
        if ( QSysInfo::currentCpuArchitecture() == "x86_64" )
            defaultSteamDir = "C:/Program Files (x86)/Steam";
        else
            defaultSteamDir = "C:/Program Files/Steam";
        foreach (QPushButton *button, buttonList)
            button->setStyleSheet("padding: 4px 14px");
    };

    ui->progressBarScreenshotsUploading->setVisible(false); // initial widget states setting
    ui->pushButtonClearQueue->setDisabled(true);
    ui->pushButtonCopyScreenshots->setDisabled(true);
    ui->pushButtonPrepare->setDisabled(true);

    QSizePolicy sp_retain = ui->labelInfoScreenshots->sizePolicy(); // hack to prevent layout size change on a widget visibility changing events
    sp_retain.setRetainSizeWhenHidden(true);
    ui->labelInfoScreenshots->setSizePolicy(sp_retain);
    ui->progressBarScreenshotsUploading->setSizePolicy(sp_retain);

    toggleLabelInfo(false); // information labels are hidden at start

    readSettings(); // read settings from the file, if any

    if ( !screenshotPathsPool.isEmpty() ) {
        populateScreenshotQueue(screenshotPathsPool);
        ui->pushButtonClearQueue->setDisabled(false);
        ui->pushButtonCopyScreenshots->setDisabled(false);
    };

    if ( !steamDir.isNull() ) {
        userDataDir = steamDir + "/userdata";
        setUserDataPaths(steamDir);
    };

    QObject::connect(this, &MainWindow::vdfIsMissing, // if vdf file isn't found in last/default location, warning message box appears
                     this, &MainWindow::warnOnMissingVDF);
}


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


void MainWindow::showEvent(QShowEvent *event) // hack to show message boxes only after the main window is shown
{
    QMainWindow::showEvent(event);
    QTimer::singleShot(50, this, SLOT(warnOnMissingVDF()));    
}


void MainWindow::closeEvent(QCloseEvent *event)
{
    writeSettings();
    event->accept();
}


void MainWindow::readSettings()
{
    settings->beginGroup("Main");
    isFirstStart = settings->value("FirstStart", true).toBool();
    settings->endGroup();

    settings->beginGroup("WindowGeometry");
    QRect rec = QApplication::desktop()->availableGeometry();
    resize(settings->value("Size", QSize(800, 800)).toSize());
    move(settings->value("Position", QPoint((rec.width()-800)/2, (rec.height()-800)/2)).toPoint());
    settings->endGroup();

    settings->beginGroup("LastSelection");
    steamDir = settings->value("SteamDir", defaultSteamDir).toString();

    if ( QDir(steamDir).exists() )
        ui->labelSteamDirValue->setText(convertSlashes(steamDir));
    else
        ui->labelSteamDirValue->setText("Not found, please locate manually");

    lastSelectedScreenshotDir = settings->value("Screenshots", QDir::currentPath()).toString();
    lastSelectedUserID = settings->value("UserID").toString();
    lastSelectedGameID = settings->value("GameID").toString();

    settings->endGroup();

    settings->beginGroup("Screenshots");
    screenshotPathsPool = settings->value("Queue").toStringList();
    settings->endGroup();
}


void MainWindow::writeSettings()
{
    settings->beginGroup("Main");
    settings->setValue("FirstStart", isFirstStart);
    settings->endGroup();

    settings->beginGroup("WindowGeometry");
    settings->setValue("Size", size());
    settings->setValue("Position", pos());
    settings->endGroup();

    settings->beginGroup("LastSelection");
    settings->setValue("SteamDir", steamDir.replace("\\", "/"));
    settings->setValue("Screenshots", lastSelectedScreenshotDir.replace("\\", "/"));
    if ( !ui->comboBoxUserID->currentText().isEmpty() )
        settings->setValue("UserID", ui->comboBoxUserID->currentText());
    if ( !ui->comboBoxGameID->currentText().isEmpty() )
        settings->setValue("GameID", ui->comboBoxGameID->currentText().remove(QRegularExpression(" <.+>$"))); // sanitize game ID before saving to settings file
    settings->endGroup();

    settings->beginGroup("Screenshots");
    settings->setValue("Queue", screenshotPathsPool);
    settings->endGroup();
}


void MainWindow::toggleLabelInfo(bool isVisible) // info labels show/hide toggle
{
    QList<QLabel*> labelInfoList;
    labelInfoList << ui->labelInfoScreenshots << ui->labelInfo1 << ui->labelInfo2 << ui->labelInfoDirectories;
    foreach (QLabel *label, labelInfoList)
        label->setVisible(isVisible);
}


void MainWindow::populateScreenshotQueue(QStringList screenshotPathsList) // function to populate screenshot list with visible entries
{
    if ( !screenshotPathsList.isEmpty() ) {

        QTreeWidgetItem *item = NULL;
        QListIterator<QString> i(screenshotPathsList);
        while ( i.hasNext() ) {
            QString current = i.next();
            if ( QFile(current).exists() ) {
                item = new QTreeWidgetItem;
                item->setText(0, current.section('/', -1));
                item->setText(1, QFileInfo(QFile(current)).lastModified().toString("yyyy/MM/dd hh:mm:ss"));
                QString path;
                path = convertSlashes(current.section('/', 0, -2));
                item->setText(2, path);
                ui->treeWidgetScreenshotList->addTopLevelItem(item);
            } else
                screenshotPathsPool.removeOne(current);
        };

        ui->treeWidgetScreenshotList->resizeColumnToContents(0); // after all has been added, resize columns for a better appearance
        ui->treeWidgetScreenshotList->resizeColumnToContents(1);

    };
}


void MainWindow::setUserDataPaths(QString dir) // function to validate and set data paths and IDs
{
    QStringList userIDsCombined;

    vdfPaths.clear(); // there may be multiple Steam installations in the system and thus multiple VDFs
    userID.clear();
    someID.clear();
    gameIDs.clear();

    ui->comboBoxUserID->clear();
    ui->comboBoxGameID->clear();
    ui->labelStatusError->clear();

    QList<QWidget*> widgetList; // list of widgets for easier disabling/enabling
    widgetList << ui->labelUserID << ui->comboBoxUserID << ui->labelGameID << ui->comboBoxGameID
               << ui->groupBoxScreenshotQueue;

    foreach (QWidget *widget, widgetList)
        widget->setDisabled(true);

    if ( QDir(dir + "/userdata").exists() ) {

        QDirIterator i(userDataDir, QStringList() << vdfFilename, QDir::Files, QDirIterator::Subdirectories);
        while ( i.hasNext() ) {
            vdfPaths << i.next();
        };

        if ( !vdfPaths.isEmpty() ) {

            foreach (QWidget *widget, widgetList)
                widget->setDisabled(false);

            ui->groupBoxScreenshotQueue->setDisabled(false);
            ui->treeWidgetScreenshotList->setDisabled(false);
            ui->labelSteamDirValue->setText(convertSlashes(dir));
            steamDir = dir;

            QListIterator<QString> i(vdfPaths);
            while ( i.hasNext() ) {
                QString current = i.next();
                QStringList splitted = current.split('/');
                userID = splitted.takeAt(splitted.length() - 3);
                someID = splitted.takeAt(splitted.length() - 2);
                userIDsCombined << userID + "/" + someID;
            };

            QStringList items;
            if ( isUnixLikeOS )
                items = userIDsCombined;
            else
                items = userIDsCombined.replaceInStrings("/", "\\");
            ui->comboBoxUserID->insertItems(0, items);

            if ( !isFirstStart )
                ui->comboBoxUserID->setCurrentIndex(ui->comboBoxUserID->findText(lastSelectedUserID));

            isFirstStart = false;

            ui->comboBoxGameID->insertItem(0, "loading...");

            QNetworkAccessManager *nam = new QNetworkAccessManager(this);

            QObject::connect(nam, &QNetworkAccessManager::finished,
                             this, &MainWindow::getGameNames);

            nam->get(QNetworkRequest(QUrl("http://api.steampowered.com/ISteamApps/GetAppList/v2")));

        } else {
            ui->labelStatusError->setText("Steam userdata directory is found, but " + vdfFilename + " is missing.");
            ui->labelSteamDirValue->setText("not found");
            ui->labelSteamDirValue->setStyleSheet("color: gray;");
        };

    } else {
        ui->labelStatusError->setText("Steam userdata directory is missing. Please locate correct Steam directory.");
        ui->labelSteamDirValue->setText("not found");
        ui->labelSteamDirValue->setStyleSheet("color: gray;");
    };
}


void MainWindow::warnOnMissingVDF()
{
    if ( vdfPaths.isEmpty() ) {

        QMessageBox msgBox(this);
        msgBox.setIcon(QMessageBox::Warning);

        if ( !QDir(steamDir + "/userdata").exists() ) {
            msgBox.setText("SteaScree has been unable to find a Steam userdata directory in the current location.");
            msgBox.setInformativeText("Please choose an existing Steam directory.");
        } else {
            msgBox.setText("Steam userdata directory is found, but there is no " + vdfFilename);
            msgBox.setInformativeText("Please start Steam, make some screenshots with it and try again.");
        };

        msgBox.exec();

    };
}


void MainWindow::getGameNames(QNetworkReply *reply)
{
    if ( games.isEmpty() ) {

        if ( reply->error() == QNetworkReply::NoError ) {

            QByteArray raw = reply->readAll();
            QJsonDocument doc(QJsonDocument::fromJson(raw));
            QJsonArray apps = doc.object().value("applist").toObject().value("apps").toArray();

            foreach (QJsonValue app, apps) {
                QJsonObject obj = app.toObject();
                QString appID = QString::number(static_cast<int>(obj.value("appid").toDouble()));
                QString name = obj.value("name").toString();
                games[appID] = name;
            };
        };

        selectedUserID = ui->comboBoxUserID->currentText();
        QStringList lines = readVDF();
        int shortcutNamesHeaderPos = lines.indexOf("\t\"shortcutnames\""); // if there are any non-Steam games, get names for them too, from the VDF
        int shortcutNamesEndPos = lines.indexOf("\t}", shortcutNamesHeaderPos);
        QStringList shortcutNamesSection = lines.mid(shortcutNamesHeaderPos, shortcutNamesEndPos - shortcutNamesHeaderPos);
        QRegularExpression re("^\t\t\"[0-9]+\"\t\t\".+\"$");

        if ( shortcutNamesSection.indexOf(re) != -1 ) {

            int entryPos = 0;

            while ( (entryPos <= shortcutNamesSection.length() - 1) && (entryPos != -1) ) {

                entryPos = shortcutNamesSection.indexOf(re, entryPos + 1);

                if ( entryPos != -1 ) {
                    QString gameID = shortcutNamesSection[entryPos].section("\t\t", 1, 1).remove(QRegularExpression("(^\")|(\"$)"));
                    QString gameName = shortcutNamesSection[entryPos].section("\t\t", 2, 2).remove(QRegularExpression("(^\")|(\"$)"));
                    games[gameID] = gameName;
                };
            };
        };
    };

    QDirIterator i(userDataDir + "/" + userID + "/" + someID + "/remote", QDir::Dirs | QDir::NoDotAndDotDot);
    while ( i.hasNext() ) {

        QString gameID = i.next().section('/', -1);

        if ( !games[gameID].isEmpty() )
            gameIDs << gameID + " <" + games[gameID] + ">";
        else
            gameIDs << gameID;

    };

    ui->comboBoxGameID->clear();

    if ( !gameIDs.isEmpty() )
        ui->comboBoxGameID->insertItems(0, gameIDs);

    if ( !lastSelectedGameID.isEmpty() )
        ui->comboBoxGameID->setCurrentIndex(ui->comboBoxGameID->findText(lastSelectedGameID, Qt::MatchStartsWith));

    ui->pushButtonAddScreenshots->setDisabled(false);
}


QString MainWindow::convertSlashes(QString str)
{
    QString converted;

    if ( isUnixLikeOS )
        converted = str;
    else
        converted = str.replace("/", "\\");

    return converted;
}


QStringList MainWindow::readVDF() // read text from the VDF and return it in the form of list of strings for easy manipulating
{
    QFile vdf(userDataDir + "/" + selectedUserID + "/" + vdfFilename);
    vdf.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream text(&vdf);
    QStringList lines;

    while ( !text.atEnd() ) {
        QString line = text.readLine();
        lines << line;
    };

    vdf.close();
    return lines;
}


void MainWindow::writeVDF(QStringList lines) // write to VDF from list of strings. previous contents are discarded
{
    QFile vdf(userDataDir + "/" + selectedUserID + "/" + vdfFilename);
    vdf.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QTextStream text(&vdf);

    QListIterator<QString> i(lines);
    while ( i.hasNext() ) {
        QString current = i.next();
        text << current + "\n";
    };

    vdf.close();
}


void MainWindow::pushScreenshots() // this routine copies screenshots to the respective folders and manipulates a string list copy of the VDF. VDF is not written
{
    QString path = userDataDir + "/" + selectedUserID + "/remote/" + selectedGameID + "/screenshots/thumbnails";
    if ( !QDir().exists(path) )
        QDir().mkpath(path);

    QRegularExpression re("^\t\"" + selectedGameID + "\"$"); // calculate a location for inserting new screenshot metadata
    int header = lines.indexOf(re, 0);

    int opening, closing;

    if ( header == -1 ) {

        QList<int> headers;

        int pos = 0;
        re.setPattern("^\t\"[0-9]+\"$");
        while ( lines.indexOf(re, pos) != -1 ) {
            pos = lines.indexOf(re, pos + 1);
            if ( pos == -1 )
                break;
            QString h = lines[pos].section('"', 1, -2);
            headers.append(h.toInt());
        };

        int before = -1;

        if ( !headers.isEmpty() ) {

            bool unorderedHeaders = false;

            for ( int i = 0; i < headers.length() - 1; i++ ) {
                if (headers[i] > headers[i + 1])
                    unorderedHeaders = true;
            };

            if ( !unorderedHeaders ) { // proceed only if all headers in the VDF are ordered, e.g. game ID 1000 is before 2000
                QListIterator<int> i(headers);
                while ( i.hasNext() ) {
                    int current = i.next();
                    if ( selectedGameID.toInt() < current) {
                        before = current;
                        break;
                    };
                };
            };
        };

        int insertPos;
        if ( before != -1 ) {
            QRegularExpression re("^\t\"" + QString::number(before) + "\"$");
            insertPos = lines.indexOf(re, 0);
        } else
            insertPos = lines.indexOf("\t\"shortcutnames\"");

        lines.insert(insertPos, "\t}");
        lines.insert(insertPos, "\t{");
        lines.insert(insertPos, "\t\"" + selectedGameID +"\"");
        header = insertPos;
        opening = insertPos + 1;
        closing = insertPos + 2;

    };

    if ( lines[header + 1] == "\t{" ) {

        opening = header + 1;
        closing = lines.indexOf("\t}", opening);

        if ( (header != -1) & (opening != -1) & (closing != -1) ) {

            int lastEntryPos = -1;
            int lastEntryValue;

            if ( closing - opening != 1 ) {
                QRegularExpression re("^\t\t\"[0-9]+\"$");
                lastEntryPos = lines.lastIndexOf(re, closing);
            } else
                lastEntryValue = -1;

            if ( lastEntryPos == -1 )
                lastEntryValue = -1;

            if ( lastEntryValue != -1 )
                lastEntryValue = lines[lastEntryPos].section('"', 1, -2).toInt();

            QString copyDest = userDataDir + "/" + selectedUserID + "/remote/" + selectedGameID + "/screenshots/";

            if ( !copiedGames.contains(selectedGameID) )
                ui->labelInfoDirectories->setText(QString::number(++copiedDirsToNum));

            nothingAddedToVDF = true;

            // routine to detect timestamp overlapping
            QMap<QString, int> repeatingTimestamps;
            QList<QStringList> screenshotPool;
            {
                QListIterator<QString> i(screenshotPathsPool);
                while ( i.hasNext() ) {

                    QString path = i.next();
                    QString timestamp = QFileInfo(QFile(path)).lastModified().toString("yyyyMMddhhmmss");
                    QString filename;
                    int inc = 1;

                    for ( int j = screenshotPathsPool.indexOf(path); j < screenshotPathsPool.length(); ++j ) {

                        QString comparedTimestamp = QFileInfo(QFile(screenshotPathsPool[j])).lastModified().toString("yyyyMMddhhmmss");

                        if ( timestamp == comparedTimestamp ) { 

                            if ( !repeatingTimestamps.contains(timestamp) )
                                repeatingTimestamps[timestamp] = 0;
                            else
                                ++repeatingTimestamps[timestamp]; // if timestamp is non-unique for this set, increment the int in the end of a filename

                            inc = repeatingTimestamps[timestamp] + 1;

                            break;
                        };
                    };

                    filename = timestamp + "_" + QString::number(inc) + ".jpg";
                    screenshotPool << ( QStringList() << path << filename );
                };
            }

            QListIterator<QStringList> i(screenshotPool); // when insertion location is determined, proceed to the insertion and file conversion/copying
            while ( i.hasNext() ) {

                QStringList current = i.next();
                QString path = current[0];
                QString filename = current[1];

                // files
                QImage screenshot(path);
                QFile file(path);
                QString extension = path.section('.', -1).toLower();

                if ( !(QFile(copyDest + filename + "_1.jpg").exists()) ) {

                    if ( (extension == "jpg") | (extension == "jpeg") )
                        file.copy(copyDest + filename);
                    else
                        screenshot.save(copyDest + filename, "jpg", 95);

                    ui->labelInfoScreenshots->setText(QString::number(++copiedScreenshotsNum));

                };


                int width = QImage(screenshot).size().width();
                int heigth = QImage(screenshot).size().height();

                int tnWidth = 200;
                int tnHeigth = (tnWidth * heigth) / width;

                screenshot.scaled(QSize(tnWidth, tnHeigth), Qt::IgnoreAspectRatio).save(copyDest + "/thumbnails/" +
                                                                                        filename, "jpg", 95);
                QDateTime lm = QFileInfo(file).lastModified();
                qint64 epoch = lm.toMSecsSinceEpoch();
                QString creation = QString::number(epoch/1000);


                // vdf
                if ( lines.mid(opening, closing - opening )
                     .contains("\t\t\t\"filename\"\t\t\"" + selectedGameID + "/screenshots/" + filename + "\"") )
                    continue;

                lines.insert(closing++, "\t\t\"" + QString::number(++lastEntryValue) + "\"");
                lines.insert(closing++, "\t\t{");
                lines.insert(closing++, "\t\t\t\"type\"\t\t\"1\"");
                lines.insert(closing++, "\t\t\t\"filename\"\t\t\"" + selectedGameID + "/screenshots/" + filename + "\"");
                lines.insert(closing++, "\t\t\t\"thumbnail\"\t\t\"" + selectedGameID + "/screenshots/thumbnails/" + filename + "\"");
                lines.insert(closing++, "\t\t\t\"vrfilename\"\t\t\"\"");
                lines.insert(closing++, "\t\t\t\"imported\"\t\t\"0\"");
                lines.insert(closing++, "\t\t\t\"width\"\t\t\"" + QString::number(width) + "\"");
                lines.insert(closing++, "\t\t\t\"heigth\"\t\t\"" + QString::number(heigth) + "\"");
                lines.insert(closing++, "\t\t\t\"gameid\"\t\t\"" + selectedGameID + "\"");
                lines.insert(closing++, "\t\t\t\"creation\"\t\t\"" + creation + "\"");
                lines.insert(closing++, "\t\t\t\"caption\"\t\t\"\"");
                lines.insert(closing++, "\t\t\t\"Permissions\"\t\t\"\"");
                lines.insert(closing++, "\t\t\t\"hscreenshot\"\t\t\"\"");
                lines.insert(closing++, "\t\t}");

                ui->progressBarScreenshotsUploading->setValue(screenshotPathsPool.indexOf(path));

                QTreeWidgetItem *item = ui->treeWidgetScreenshotList->findItems(QFileInfo(file).lastModified()
                                                                                .toString("yyyy/MM/dd hh:mm:ss"), Qt::MatchExactly, 1)[0];
                delete item;

                if ( !copiedGames.contains(selectedGameID) )
                    copiedGames << selectedGameID;

                nothingAddedToVDF = false;

                QCoreApplication::processEvents();

            };
        };
    };
}


void MainWindow::on_pushButtonLocateSteamDir_clicked()
{
    QString steamDirLocated = QFileDialog::getExistingDirectory(this,
                                                         "Locate Steam directory",
                                                         steamDir,
                                                         QFileDialog::ShowDirsOnly | QFileDialog::ReadOnly);
    if ( !steamDirLocated.isEmpty() ) {

        steamDirLocated.remove(QRegularExpression("/userdata$"));
        userDataDir = steamDirLocated + "/userdata";
        setUserDataPaths(steamDirLocated);

    };
}


void MainWindow::on_pushButtonAddScreenshots_clicked()
{
    QStringList screenshotsSelected = QFileDialog::getOpenFileNames(this,
                                                            "Select one or more screenshots",
                                                            lastSelectedScreenshotDir,
                                                            "Images (*.jpg *.jpeg *.png *.bmp *.tif *.tiff)");
    QListIterator<QString> i(screenshotsSelected);
    while ( i.hasNext() ) {
        QString current = i.next();
        if ( screenshotPathsPool.contains(current) )
            screenshotsSelected.removeOne(current); // copies are removed from the list
    };

    if ( !screenshotsSelected.isEmpty() ) {

        lastSelectedScreenshotDir = screenshotsSelected.last().section('/', 0, -2);

        populateScreenshotQueue(screenshotsSelected);

        screenshotPathsPool << screenshotsSelected;
        ui->pushButtonClearQueue->setDisabled(false);
        ui->pushButtonCopyScreenshots->setDisabled(false);

    };
}


void MainWindow::on_pushButtonClearQueue_clicked()
{
    ui->treeWidgetScreenshotList->clear();
    screenshotPathsPool.clear();
    ui->pushButtonClearQueue->setDisabled(true);
    ui->pushButtonCopyScreenshots->setDisabled(true);
    ui->labelUserID->setDisabled(false);
    ui->comboBoxGameID->setDisabled(false);
    ui->labelGameID->setDisabled(false);
    ui->comboBoxUserID->setDisabled(false);
    ui->pushButtonLocateSteamDir->setDisabled(false);
}


void MainWindow::on_pushButtonCopyScreenshots_clicked()
{
    selectedUserID = ui->comboBoxUserID->currentText();
    selectedGameID = ui->comboBoxGameID->currentText();

    QRegularExpression re("^[0-9]+( <.+>)?$");

    if ( !selectedGameID.contains(re) )

        ui->labelStatusError->setText("Invalid Game ID, only numbers allowed");

    else {

        ui->labelStatusError->clear();

        selectedGameID = selectedGameID.remove(QRegularExpression(" <.+>$")); // it's possible to enter game ID by hand or left what was auto-generated (with <...>)

        if ( screenshotPathsPool.length() >= 10 ) {

            ui->progressBarScreenshotsUploading->setVisible(true);
            ui->progressBarScreenshotsUploading->setMinimum(0);
            ui->progressBarScreenshotsUploading->setMaximum(screenshotPathsPool.length());

        };

        ui->pushButtonClearQueue->setDisabled(true);
        ui->pushButtonAddScreenshots->setDisabled(true);
        ui->pushButtonCopyScreenshots->setDisabled(true);

        toggleLabelInfo(true);

        if ( lines.isEmpty() )
            lines = readVDF();

        pushScreenshots();

        ui->progressBarScreenshotsUploading->setVisible(false);
        ui->pushButtonAddScreenshots->setDisabled(false);
        ui->pushButtonLocateSteamDir->setDisabled(true);
        ui->progressBarScreenshotsUploading->reset();
        ui->treeWidgetScreenshotList->clear();
        screenshotPathsPool.clear();

        ui->pushButtonPrepare->setDisabled(false);

    };
}


void MainWindow::on_pushButtonPrepare_clicked()
{  
    if ( !nothingAddedToVDF ) {

        QMessageBox msgBox(this);
        msgBox.setIcon(QMessageBox::Warning);

        QString text = "Steam has to be quitted.";
        QString info = "This program only works when Steam exited. It will not try to determine if Steam is running or not, so you should be sure it is quitted. " +
                QString("If it is not, it is safe to exit Steam now. <br><br>Is Steam exited now?");
        msgBox.setText(text);
        msgBox.setInformativeText(info);
        msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        msgBox.setDefaultButton(QMessageBox::Yes);

        QGridLayout* layout = (QGridLayout*)msgBox.layout(); // hack to make wide message boxes
        layout->addItem(new QSpacerItem(500, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), layout->rowCount(), 0, 1, layout->columnCount());

        int ret = msgBox.exec();

        if ( ret == QMessageBox::Yes ) {

            toggleLabelInfo(false);
            ui->labelInfoScreenshots->setText("0");
            ui->labelInfoDirectories->setText("0");
            copiedScreenshotsNum = 0;
            copiedDirsToNum = 0;
            screenshotPathsPool.clear();
            ui->pushButtonPrepare->setDisabled(true);

            QString vdfPath = userDataDir + "/" + selectedUserID + "/" + vdfFilename;
            QFile(vdfPath).copy(vdfPath + ".bak"); // backup VDF just in case

            writeVDF(lines);

            QMessageBox msgBox(this);
            msgBox.setIcon(QMessageBox::Information);
            msgBox.setText("SteaScree has updated the VDF-file.");
            msgBox.setInformativeText("Now you can start Steam as usual and upload screenshots to the Steam Cloud.");
            msgBox.exec();

        };

    } else {

        QMessageBox msgBox(this);
        msgBox.setIcon(QMessageBox::Warning);
        msgBox.setText("All screenshots from the upload queue are already in the screenshots.vdf file.");
        msgBox.setInformativeText("Nothing has been added. Please add new screenshots and try again.");
        msgBox.exec();

        toggleLabelInfo(false);
        copiedScreenshotsNum = 0;
        copiedDirsToNum = 0;
        ui->pushButtonPrepare->setDisabled(true);

    };
}

One thing of which I can think is that probably MainWindow::pushScreenshots() function is too big and should be refactored to two smaller functions. Anyway, it's still pretty readable for me.

Any suggestions and opinions, including that I should not be allowed into development, are welcome!

\$\endgroup\$
2
\$\begingroup\$

The entire MainWindow class is too big. It's basically a God class that contains your whole program logic. As you've already tagged the question with design-patterns, one pattern to use would be Model-View-Controller or Model-View-Presenter.

Thus, the MainWindow only becomes responsible for displaying the UI and handling user inputs. Event handlers such as on_button_clicked do nothing more then notify a controller/presenter that something occurred. These in turn call methods on your model which to the actual stuff, and, optionally, notify the view to display something new in the UI.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your comment. I'll look into how it may be possible to separate things according to the MVC pattern. \$\endgroup\$ – TranslucentCloud Oct 9 '16 at 10:34
  • \$\begingroup\$ I've refactored the code, now it somehow conforms to MVC pattern, however I omitted Controller part in my re-implementation to make refactoring a bit easier. Here is the updated version. Not sure if I should take may time and implement Controller object, or if it is already good in this state. Any advice? \$\endgroup\$ – TranslucentCloud Oct 16 '16 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.