Browse Source

Completed functions for downloading and updating patches, improved status frame, updated LotroDat library to version 7.2.0

Ivan Arkhipov 5 years ago
parent
commit
7db40da633
46 changed files with 1892 additions and 523 deletions
  1. 2 0
      import/LotroDat/DatSubsystems/DatIO.h
  2. 6 0
      import/LotroDat/DatSubsystems/DatLocaleManager.h
  3. 1 1
      import/LotroDat/DatSubsystems/DatPatcher.h
  4. 1 1
      import/LotroDat/SubFile.h
  5. 1 1
      import/LotroDat/SubfileData.h
  6. 1 1
      import/LotroDat/Subfiles/DdsSubFile.h
  7. 1 1
      import/LotroDat/Subfiles/FontSubFile.h
  8. 1 1
      import/LotroDat/Subfiles/JpgSubFile.h
  9. 1 1
      import/LotroDat/Subfiles/OggSubFile.h
  10. 1 2
      import/LotroDat/Subfiles/TextSubFile.h
  11. 1 1
      import/LotroDat/Subfiles/UnknownSubFile.h
  12. 1 1
      import/LotroDat/Subfiles/WavSubFile.h
  13. 5 2
      src/Launcher/Launcher.pro
  14. 1 11
      src/Launcher/launcher_plugin_import.cpp
  15. 31 11
      src/Launcher/main.cpp
  16. 1 1
      src/Legacy-advanced/Legacy-advanced.pro
  17. 1 1
      src/Legacy-advanced/Legacy-advanced_resource.rc
  18. 24 1
      src/Legacy-advanced/models/lotromanager.cpp
  19. 4 0
      src/Legacy-advanced/models/lotromanager.h
  20. 1 1
      src/Legacy-advanced/widgets/mainwindow.cpp
  21. 35 1
      src/Legacy-advanced/widgets/managewidget.cpp
  22. 6 0
      src/Legacy-advanced/widgets/managewidget.h
  23. 4 4
      src/Legacy-advanced/widgets/managewidget.ui
  24. 10 5
      src/Legacy/Legacy.pro
  25. 134 3
      src/Legacy/models/downloader.cpp
  26. 25 2
      src/Legacy/models/downloader.h
  27. 3 2
      src/Legacy/models/filesystem.cpp
  28. 213 0
      src/Legacy/models/patchdownloader.cpp
  29. 66 0
      src/Legacy/models/patchdownloader.h
  30. 6 2
      src/Legacy/object_script.Legacy.Debug
  31. 6 2
      src/Legacy/object_script.Legacy.Release
  32. 48 4
      src/Legacy/widgets/mainwindow.cpp
  33. 10 3
      src/Legacy/widgets/mainwindow.h
  34. 3 2
      src/Legacy/widgets/menuentry.cpp
  35. 8 7
      src/Legacy/widgets/newslistwidget.cpp
  36. 1 1
      src/Legacy/widgets/newslistwidget.h
  37. 4 4
      src/Legacy/widgets/newspiece.ui
  38. 29 59
      src/Legacy/widgets/serverstatuswidget.cpp
  39. 20 12
      src/Legacy/widgets/serverstatuswidget.h
  40. 278 0
      src/Legacy/widgets/serverstatuswidget.ui
  41. 100 0
      src/Legacy/widgets/statusflagwidget.cpp
  42. 38 0
      src/Legacy/widgets/statusflagwidget.h
  43. 44 2
      src/Legacy/widgets/statuswidget.cpp
  44. 12 1
      src/Legacy/widgets/statuswidget.h
  45. 700 367
      src/Legacy/widgets/statuswidget.ui
  46. 3 1
      src/Legacy/widgets/weeklycodewidget.cpp

+ 2 - 0
import/LotroDat/DatSubsystems/DatIO.h

@@ -53,6 +53,8 @@ namespace LOTRO_DAT {
         unsigned int getHeaderHash();
     private:
 
+        void ClearData();
+
         //------------------------------------------------//
         // PRIVATE INIT SECTION
         //------------------------------------------------//

+ 6 - 0
import/LotroDat/DatSubsystems/DatLocaleManager.h

@@ -67,11 +67,17 @@ namespace LOTRO_DAT {
 
         void UpdateCategory(long long file_id, long long category);
 
+        DatOperationResult<> EnableCategory(long long category);
+
+        DatOperationResult<> DisableCategory(long long category);
+
         const std::set<long long>& GetInactiveCategories();
 
     private:
         std::map<long long, SubFile> &GetLocaleDictReference(LOCALE locale);
 
+        void ClearData();
+
     private:
         DatFile *dat;
         std::map<long long, SubFile> orig_dict_;

+ 1 - 1
import/LotroDat/DatSubsystems/DatPatcher.h

@@ -5,7 +5,7 @@
 #ifndef LOTRO_DAT_LIBRARY_DATPATCHER_H
 #define LOTRO_DAT_LIBRARY_DATPATCHER_H
 
-#include "yaml-cpp/yaml.h"
+#include <yaml-cpp/yaml.h>
 #include "../DatOperationResult.h"
 
 extern "C++"

+ 1 - 1
import/LotroDat/SubFile.h

@@ -7,7 +7,7 @@
 
 #include <string>
 #include <vector>
-#include "yaml-cpp/yaml.h"
+#include <yaml-cpp/yaml.h>
 
 extern "C++"
 {

+ 1 - 1
import/LotroDat/SubfileData.h

@@ -10,7 +10,7 @@
 #include "BinaryData.h"
 
 namespace LOTRO_DAT {
-    struct SubfileData {
+    class SubfileData {
     public:
         SubfileData() {
             binary_data = BinaryData(0);

+ 1 - 1
import/LotroDat/Subfiles/DdsSubFile.h

@@ -5,7 +5,7 @@
 #ifndef LOTRO_DAT_LIBRARY_DDSSUBFILE_H
 #define LOTRO_DAT_LIBRARY_DDSSUBFILE_H
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class DdsSubFile : public SubFile {

+ 1 - 1
import/LotroDat/Subfiles/FontSubFile.h

@@ -6,7 +6,7 @@
 #define LOTRO_DAT_LIBRARY_FONTSUBFILE_H
 
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class FontSubFile : public SubFile {

+ 1 - 1
import/LotroDat/Subfiles/JpgSubFile.h

@@ -6,7 +6,7 @@
 #define LOTRO_DAT_LIBRARY_JPGSUBFILE_H
 
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class JpgSubFile : public SubFile {

+ 1 - 1
import/LotroDat/Subfiles/OggSubFile.h

@@ -5,7 +5,7 @@
 #ifndef LOTRO_DAT_LIBRARY_OGGSUBFILE_H
 #define LOTRO_DAT_LIBRARY_OGGSUBFILE_H
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class OggSubFile : public SubFile {

+ 1 - 2
import/LotroDat/Subfiles/TextSubFile.h

@@ -29,8 +29,7 @@ namespace LOTRO_DAT {
         explicit TextSubFile(const TextSubFile &other) = delete;
         SubFile &operator =(const TextSubFile &other) = delete;
 
-
-        TextSubFile(SubFile preinit_file);
+        explicit TextSubFile(SubFile preinit_file);
 
         FILE_TYPE FileType() const override;
 

+ 1 - 1
import/LotroDat/Subfiles/UnknownSubFile.h

@@ -5,7 +5,7 @@
 #ifndef LOTRO_DAT_LIBRARY_UNKNOWNSUBFILE_H
 #define LOTRO_DAT_LIBRARY_UNKNOWNSUBFILE_H
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class UnknownSubFile : public SubFile {

+ 1 - 1
import/LotroDat/Subfiles/WavSubFile.h

@@ -5,7 +5,7 @@
 #ifndef LOTRO_DAT_LIBRARY_WAVSUBFILE_H
 #define LOTRO_DAT_LIBRARY_WAVSUBFILE_H
 
-#include "SubFile.h"
+#include "../SubFile.h"
 
 namespace LOTRO_DAT {
     class WavSubFile : public SubFile {

+ 5 - 2
src/Launcher/Launcher.pro

@@ -1,7 +1,7 @@
 include( ../../common.pri )
 include( ../../app.pri )
 
-QT += quick
+QT = core widgets
 CONFIG += c++11
 
 # The following define makes your compiler emit warnings if you use
@@ -18,7 +18,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
 SOURCES += \
         main.cpp
 
-RESOURCES += qml.qrc
+RESOURCES +=
 
 # Additional import path used to resolve QML modules in Qt Creator's code model
 QML_IMPORT_PATH =
@@ -30,3 +30,6 @@ QML_DESIGNER_IMPORT_PATH =
 qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
+
+
+win32:RC_ICONS = $${PROJECT_ROOT_PATH}/resources/icon.ico

+ 1 - 11
src/Launcher/launcher_plugin_import.cpp

@@ -1,6 +1,7 @@
 // This file is autogenerated by qmake. It imports static plugin classes for
 // static plugins specified using QTPLUGIN and QT_PLUGIN_CLASS.<plugin> variables.
 #include <QtPlugin>
+Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin)
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
 Q_IMPORT_PLUGIN(QGifPlugin)
 Q_IMPORT_PLUGIN(QICNSPlugin)
@@ -10,14 +11,3 @@ Q_IMPORT_PLUGIN(QTgaPlugin)
 Q_IMPORT_PLUGIN(QTiffPlugin)
 Q_IMPORT_PLUGIN(QWbmpPlugin)
 Q_IMPORT_PLUGIN(QWebpPlugin)
-Q_IMPORT_PLUGIN(QQmlDebuggerServiceFactory)
-Q_IMPORT_PLUGIN(QQmlInspectorServiceFactory)
-Q_IMPORT_PLUGIN(QLocalClientConnectionFactory)
-Q_IMPORT_PLUGIN(QDebugMessageServiceFactory)
-Q_IMPORT_PLUGIN(QQmlNativeDebugConnectorFactory)
-Q_IMPORT_PLUGIN(QQmlNativeDebugServiceFactory)
-Q_IMPORT_PLUGIN(QQmlProfilerServiceFactory)
-Q_IMPORT_PLUGIN(QQuickProfilerAdapterFactory)
-Q_IMPORT_PLUGIN(QQmlDebugServerFactory)
-Q_IMPORT_PLUGIN(QTcpServerConnectionFactory)
-Q_IMPORT_PLUGIN(QGenericEnginePlugin)

+ 31 - 11
src/Launcher/main.cpp

@@ -1,16 +1,36 @@
-#include <QGuiApplication>
-#include <QQmlApplicationEngine>
+#include <QFile>
+#include <QProcess>
+#include <QCoreApplication>
+#include <QDebug>
+#include <QMessageBox>
+#include <QApplication>
 
-int main(int argc, char *argv[])
-{
-    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+int main(int argc, char *argv[]) {
+    QApplication app(argc, argv);
+    QStringList args = app.arguments();
 
-    QGuiApplication app(argc, argv);
+    if (args.contains("launched")) {
+        QProcess process;
+        process.setProgram("lotro_ru.exe");
+        process.setArguments({{"-skiprawdownload", "-disablePatch"}});
+        if (!process.startDetached())
+            QMessageBox::critical(nullptr, "Ошибка запуска!", "Не удалось запустить русифицированную версию игры! Ошибка 0x0!");
+        else
+            QMessageBox::information(nullptr, "Успех!", "Добро пожаловать в русифицированную версию Властелин Колец Онлайн!");
+    } else {
+        QFile file("legacy_path.txt");
+        file.open(QIODevice::ReadOnly);
+        QString legacy_path = file.readLine();
+        file.close();
 
-    QQmlApplicationEngine engine;
-    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
-    if (engine.rootObjects().isEmpty())
-        return -1;
+        if (legacy_path.isEmpty())
+            legacy_path = "Legacy.exe";
 
-    return app.exec();
+        QProcess process;
+        process.setProgram(legacy_path);
+        if (!process.startDetached())
+            QMessageBox::critical(nullptr, "Ошибка запуска!", "Не удалось запустить русифицированную версию игры! Ошибка 0x1!");
+    }
+
+    return 0;
 }

+ 1 - 1
src/Legacy-advanced/Legacy-advanced.pro

@@ -61,7 +61,7 @@ FORMS += \
     widgets\messagedialog.ui \
     widgets\lotroprogresswidget.ui
 
-win32:RC_ICONS = $${PROJECT_ROOT_PATH}/resources/appicon.ico
+win32:RC_ICONS = $${PROJECT_ROOT_PATH}/resources/icon_anvil.ico
 
 INCLUDEPATH += $$PWD/src
 INCLUDEPATH += $$PWD/widgets

+ 1 - 1
src/Legacy-advanced/Legacy-advanced_resource.rc

@@ -1,6 +1,6 @@
 #include <windows.h>
 
-IDI_ICON1	ICON	DISCARDABLE	"D:\\Programming\\SourceRepos\\Legacy_v2\\resources\\appicon.ico"
+IDI_ICON1	ICON	DISCARDABLE	"D:\\Programming\\SourceRepos\\Legacy_v2\\resources\\icon_anvil.ico"
 
 VS_VERSION_INFO VERSIONINFO
 	FILEVERSION 0,0,0,0

+ 24 - 1
src/Legacy-advanced/models/lotromanager.cpp

@@ -5,6 +5,7 @@
 #include <QtConcurrent/QtConcurrent>
 #include <QFontDatabase>
 #include <QMessageBox>
+#include <QDebug>
 
 #include <string>
 #include <iostream>
@@ -392,9 +393,11 @@ void LotroManager::getUnactiveCategories() {
 
     QStringList result;
     for (long long category : categories) {
-        result << QString::number(category);
+        result.append(QString::number(category));
     }
 
+    qDebug() << "Received category set: " << result;
+
     emit unactiveCategoriesReceived(result);
     emit processFinished("getUnactiveCategories", {"Success"});
 }
@@ -489,6 +492,26 @@ void LotroManager::getFileInfo(long long file_id) {
     emit processFinished("getFileInfo", {"Success"});
 }
 
+void LotroManager::disableCategory(long long category_id)
+{
+    emit processStarted(QString("disableCategory"), {category_id});
+    file.GetLocaleManager().DisableCategory(category_id);
+    file.GetLocaleManager().CommitLocales();
+    file.GetFileSystem().CommitDirectories();
+    getUnactiveCategories();
+    emit processFinished("disableCategory", {"Success"});
+}
+
+void LotroManager::enableCategory(long long category_id)
+{
+    emit processStarted(QString("enableCategory"), {category_id});
+    file.GetLocaleManager().EnableCategory(category_id);
+    file.GetLocaleManager().CommitLocales();
+    file.GetFileSystem().CommitDirectories();
+    getUnactiveCategories();
+    emit processFinished("enableCategory", {"Success"});
+}
+
 LOTRO_DAT::DatStatus *LotroManager::getStatusModule()
 {
     return &file.GetStatusModule();

+ 4 - 0
src/Legacy-advanced/models/lotromanager.h

@@ -55,6 +55,10 @@ public slots:
 
     void getFileInfo(long long file_id);
 
+    void disableCategory(long long category_id);
+
+    void enableCategory(long long category_id);
+
     LOTRO_DAT::DatStatus *getStatusModule();
 
 signals:

+ 1 - 1
src/Legacy-advanced/widgets/mainwindow.cpp

@@ -85,7 +85,7 @@ MainWindow::MainWindow(QWidget *parent) :
     qDebug() << "Starting dat file initialisation";
     QMetaObject::invokeMethod(lotro_manager, "initialiseDatFile", Qt::QueuedConnection,
                               Q_ARG(QString, settings->value("advanced/dat_path").toString()));
-
+    QMetaObject::invokeMethod(lotro_manager, "getUnactiveCategories", Qt::QueuedConnection);
 }
 
 void MainWindow::mousePressEvent(QMouseEvent *event)

+ 35 - 1
src/Legacy-advanced/widgets/managewidget.cpp

@@ -12,6 +12,7 @@ ManageWidget::ManageWidget(LotroManager* mgr, QSettings* settings, QWidget *pare
 {
     ui->setupUi(this);
     connect(lotro_manager, &LotroManager::processFinished, this, &ManageWidget::onLotroManagerProcessFinished, Qt::QueuedConnection);
+    connect(lotro_manager, &LotroManager::unactiveCategoriesReceived, this, &ManageWidget::onLotroManagerInactiveCateroiesListUpdated);
 }
 
 ManageWidget::~ManageWidget()
@@ -82,11 +83,19 @@ void ManageWidget::onLotroManagerProcessFinished(QString proc_name, QVector<QVar
     updateUI();
 }
 
+void ManageWidget::onLotroManagerInactiveCateroiesListUpdated(QStringList categories)
+{
+    if (categories.empty())
+        ui->inactive_categories_list->setText("(нет)");
+    else
+        ui->inactive_categories_list->setText(categories.join(", "));
+}
+
 void ManageWidget::on_change_folder_button_clicked()
 {
     QStringList known_paths = FileSystem::recognizeRegistryLotroPath();
     QString template_path = known_paths.size() > 0 ? known_paths[0] : "";
-    QString fileName = QFileDialog::getOpenFileName(0, "Расположение файла .dat", template_path, "*.dat");
+    QString fileName = QFileDialog::getOpenFileName(0, "Расположение файла .dat", template_path, "Файлы ресурсов (*.dat *.datx)");
 
     if (fileName.isEmpty())
             return;
@@ -127,3 +136,28 @@ void ManageWidget::on_deinitButton_common_clicked()
 {
     QMetaObject::invokeMethod(lotro_manager, "deinitialiseDatFile", Qt::QueuedConnection);
 }
+
+void ManageWidget::on_activate_category_button_clicked()
+{
+    if (ui->activate_category_label->text().toLongLong() == 0) {
+        QMessageBox::warning(this->parentWidget(), "Не указан ID категории!", "Укажите, пожалуйста, корректный (числовой) ID категории для активации!");
+        return;
+    }
+
+    QMetaObject::invokeMethod(lotro_manager, "enableCategory", Qt::QueuedConnection,
+                              Q_ARG(long long, ui->activate_category_label->text().toLongLong())
+                              );
+
+}
+
+void ManageWidget::on_deactivate_category_button_clicked()
+{
+    if (ui->deactivate_category_label->text().toLongLong() == 0) {
+        QMessageBox::warning(this->parentWidget(), "Не указан ID категории!", "Укажите, пожалуйста, корректный (числовой) ID категории для деактивации!");
+        return;
+    }
+
+    QMetaObject::invokeMethod(lotro_manager, "disableCategory", Qt::QueuedConnection,
+                              Q_ARG(long long, ui->deactivate_category_label->text().toLongLong())
+                              );
+}

+ 6 - 0
src/Legacy-advanced/widgets/managewidget.h

@@ -29,6 +29,8 @@ public slots:
 private slots:
     void onLotroManagerProcessFinished(QString, QVector<QVariant>);
 
+    void onLotroManagerInactiveCateroiesListUpdated(QStringList);
+
     void on_change_folder_button_clicked();
 
     void on_changeLocaleButton_common_clicked();
@@ -39,6 +41,10 @@ private slots:
 
     void on_deinitButton_common_clicked();
 
+    void on_activate_category_button_clicked();
+
+    void on_deactivate_category_button_clicked();
+
 private:
     LotroManager* lotro_manager;
     QSettings* settings;

+ 4 - 4
src/Legacy-advanced/widgets/managewidget.ui

@@ -245,9 +245,9 @@ color:gold;</string>
      </property>
      <layout class="QGridLayout" name="gridLayout_13">
       <item row="0" column="1">
-       <widget class="QLabel" name="label">
+       <widget class="QLabel" name="inactive_categories_list">
         <property name="text">
-         <string>(нет) (функция пока недоступна)</string>
+         <string>(нет)</string>
         </property>
        </widget>
       </item>
@@ -306,7 +306,7 @@ background-color: none;</string>
           </widget>
          </item>
          <item>
-          <widget class="QLineEdit" name="lineEdit_2_common_2">
+          <widget class="QLineEdit" name="deactivate_category_label">
            <property name="maxLength">
             <number>9</number>
            </property>
@@ -363,7 +363,7 @@ background-color: none;</string>
           </widget>
          </item>
          <item>
-          <widget class="QLineEdit" name="lineEdit_common">
+          <widget class="QLineEdit" name="activate_category_label">
            <property name="inputMask">
             <string/>
            </property>

+ 10 - 5
src/Legacy/Legacy.pro

@@ -25,10 +25,12 @@ SOURCES += \
     widgets/rusificationwidget.cpp \
     widgets/settingswidget.cpp \
     widgets/statuswidget.cpp \
-    widgets/serverstatuswidget.cpp \
     widgets/weeklycodewidget.cpp \
     widgets/newslistwidget.cpp \
-    widgets/newspiece.cpp
+    widgets/newspiece.cpp \
+    widgets/statusflagwidget.cpp \
+    widgets/serverstatuswidget.cpp \
+    models/patchdownloader.cpp
 
 HEADERS += \
     models/downloader.h \
@@ -41,10 +43,12 @@ HEADERS += \
     widgets/rusificationwidget.h \
     widgets/settingswidget.h \
     widgets/statuswidget.h \
-    widgets/serverstatuswidget.h \
     widgets/weeklycodewidget.h \
     widgets/newslistwidget.h \
-    widgets/newspiece.h
+    widgets/newspiece.h \
+    widgets/statusflagwidget.h \
+    widgets/serverstatuswidget.h \
+    models/patchdownloader.h
 
 FORMS += \
     widgets/helpwidget.ui \
@@ -52,7 +56,8 @@ FORMS += \
     widgets/rusificationwidget.ui \
     widgets/settingswidget.ui \
     widgets/statuswidget.ui \
-    widgets/newspiece.ui
+    widgets/newspiece.ui \
+    widgets/serverstatuswidget.ui
 
 win32:RC_ICONS = $${PROJECT_ROOT_PATH}/resources/appicon.ico
 

+ 134 - 3
src/Legacy/models/downloader.cpp

@@ -2,6 +2,7 @@
 #include <QEventLoop>
 #include <QApplication>
 #include <QDebug>
+#include <QTime>
 
 Downloader::Downloader(QObject *parent) :QObject(parent), busy(false)
 {
@@ -28,6 +29,110 @@ void Downloader::waitForDownloaded()
     loop.exec();
 }
 
+double Downloader::getPercent()
+{
+    return double(std::ceil(((double)bytes_downloaded/ bytes_total) * 100 * 10)) / 10.0;
+}
+
+quint64 Downloader::getBytesTotal()
+{
+    return bytes_total;
+}
+
+quint64 Downloader::getBytesDownloaded()
+{
+    return bytes_downloaded;
+}
+
+quint64 Downloader::getElapsedTime()
+{
+    return (getBytesTotal() - getBytesDownloaded()) / qMax(quint64(1), getSpeed());
+}
+
+quint64 Downloader::getSpeed()
+{
+    return download_speed;
+}
+
+QString Downloader::getSpeedFormatted(quint64 speed_bytes_per_sec)
+{
+    quint64 speed = speed_bytes_per_sec;
+
+    QString unit;
+    if (speed < 1024) {
+        unit = "bytes/sec";
+    } else if (speed < 1024*1024) {
+        speed /= 1024;
+        unit = "kB/s";
+    } else {
+        speed /= 1024*1024;
+        unit = "MB/s";
+    }
+    return QString::number(speed) + " " + unit;
+}
+
+QString Downloader::getElapsedTimeFormatted(quint64 elapsed_time_secs)
+{
+    qint64 secs = elapsed_time_secs;
+    qint64 mins = 0;
+    qint64 hours = 0;
+    qint64 days = 0;
+
+    if (secs > 60) {
+        mins = secs / 60;
+        secs %= 60;
+    }
+
+    if (mins > 60) {
+        hours = mins / 60;
+        mins %= 60;
+    }
+
+    if (hours > 24) {
+        days = hours / 24;
+        hours %= 24;
+    }
+
+    if (days > 0)
+        return " очень много (низкая скорость)";
+
+    QString result = "";
+
+    if (hours > 0) {
+        result += QString::number(hours);
+        if (hours % 10 == 1 && hours / 10 != 1)
+            result += " час ";
+        else if (hours % 10 > 1 && hours % 10 < 5 && hours / 10 != 1)
+            result += " часа ";
+        else
+            result += " часов ";
+    }
+
+    if (mins > 0) {
+        result += QString::number(mins);
+        if (mins % 10 == 1 && mins / 10 != 1)
+            result += " минута ";
+        else if (mins % 10 > 1 && mins % 10 < 5 && mins / 10 != 1)
+            result += " минуты ";
+        else
+            result += " минут ";
+    }
+
+    if (secs > 0 && hours == 0) {
+        result += QString::number(secs);
+        if (secs % 10 == 1 && secs / 10 != 1)
+            result += " секунда ";
+        else if (secs % 10 > 1 && secs % 10 < 5 && secs / 10 != 1)
+            result += " секунды ";
+        else
+            result += " секунд ";
+    }
+
+    if (result == "")
+        result = "совсем чуть-чуть";
+    return result;
+}
+
 void Downloader::start()
 {
     if (busy) {
@@ -36,12 +141,19 @@ void Downloader::start()
     }
     qDebug() << "Starting download " << url;
 
+    downloadTime.restart();
+    bytes_downloaded = 0;
+    bytes_total = 0;
+    download_speed = 0;
+    bytesReceivedBeforeSecond = 0;
+    timeElapsedBeforeSecond = 0;
+
     busy = true;
     QNetworkRequest request(url);
     m_CurrentReply = m_WebCtrl.get(request);
     m_CurrentReply->setReadBufferSize(download_speed_limit);
     connect(m_CurrentReply, &QNetworkReply::readyRead, this, &Downloader::onReadyRead);
-    connect(m_CurrentReply, &QNetworkReply::downloadProgress, this, &Downloader::progressChanged);
+    connect(m_CurrentReply, &QNetworkReply::downloadProgress, this, &Downloader::onDownloadProgressChanged);
 }
 
 void Downloader::updateDownloadSpeedLimit(int bytes_per_sec)
@@ -51,9 +163,27 @@ void Downloader::updateDownloadSpeedLimit(int bytes_per_sec)
         m_CurrentReply->setReadBufferSize(bytes_per_sec);
 }
 
+void Downloader::onDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal)
+{
+    qDebug() << "Downloaded " << bytesReceived << " of " << bytesTotal << " from url " << getUrl();
+
+    bytes_downloaded = bytesReceived;
+    bytes_total = bytesTotal;
+
+    if (downloadTime.elapsed() - timeElapsedBeforeSecond >= 1000) {
+        download_speed = (bytes_downloaded - bytesReceivedBeforeSecond) * 1000.0 / (downloadTime.elapsed() - timeElapsedBeforeSecond);
+        timeElapsedBeforeSecond = downloadTime.elapsed();
+        bytesReceivedBeforeSecond = bytes_downloaded;
+    }
+
+    emit progressChanged(this);
+}
+
 void Downloader::stop()
 {
-    m_CurrentReply->abort();
+    if (m_CurrentReply) {
+        m_CurrentReply->abort();
+    }
     busy = false;
 }
 
@@ -62,7 +192,8 @@ void Downloader::onDownloadFinished(QNetworkReply*) {
         m_CurrentReply->deleteLater();
 
     busy = false;
-    emit downloadFinished();
+
+    emit downloadFinished(this);
 }
 
 void Downloader::onReadyRead()

+ 25 - 2
src/Legacy/models/downloader.h

@@ -6,6 +6,7 @@
 #include <QNetworkRequest>
 #include <QNetworkReply>
 #include <QFile>
+#include <QTime>
 #include <QByteArray>
 
 class Downloader : public QObject
@@ -14,15 +15,27 @@ class Downloader : public QObject
 
 public:
     explicit Downloader(QObject *parent = 0);
+private:
+    Q_DISABLE_COPY(Downloader)
+
+public:
     virtual ~Downloader();
 
     QUrl getUrl();
     void setUrl(const QUrl& _url);
     void waitForDownloaded();
 
+    double getPercent();
+    quint64 getBytesTotal();
+    quint64 getBytesDownloaded();
+    quint64 getElapsedTime();
+    quint64 getSpeed();
+    static QString getSpeedFormatted(quint64 speed_bytes_per_sec);
+    static QString getElapsedTimeFormatted(quint64 elapsed_time_secs);
+
 signals:
-    void downloadFinished();
-    void progressChanged(qint64 bytesReceived, qint64 bytesTotal);
+    void downloadFinished(Downloader* this_downloader_ptr);
+    void progressChanged(Downloader* this_downloader_ptr);
 
 public slots:
     void start();
@@ -30,6 +43,7 @@ public slots:
     void stop();
 
 private slots:
+    void onDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal);
     void onDownloadFinished(QNetworkReply* pReply);
     void onReadyRead();
 
@@ -38,6 +52,15 @@ public:
     QByteArray* targetBytearray {nullptr};
 
 private:
+    QTime downloadTime;
+    quint64 download_speed; // bytes per second
+
+    quint64 bytes_total;
+    quint64 bytes_downloaded;
+
+    quint64 bytesReceivedBeforeSecond; // Нужны для подсчёта текущей скорости скачивания, а не
+    quint64 timeElapsedBeforeSecond;   // средней за всё время.
+
     bool busy;
     QUrl url;
     QNetworkReply* m_CurrentReply {nullptr};

+ 3 - 2
src/Legacy/models/filesystem.cpp

@@ -15,8 +15,9 @@ bool FileSystem::fileExists(QString path) {
 QString FileSystem::fileHash(const QString &fileName, QCryptographicHash::Algorithm hashAlgorithm){
     QFile file(fileName);
     if (file.open(QIODevice::ReadOnly)) {
-        QByteArray fileData = file.readAll();
-        QByteArray hashData = QCryptographicHash::hash(fileData, hashAlgorithm);
+        QCryptographicHash hash(hashAlgorithm);
+        hash.addData(&file);
+        QByteArray hashData = hash.result();
         return hashData.toHex();
     }
     return QByteArray();

+ 213 - 0
src/Legacy/models/patchdownloader.cpp

@@ -0,0 +1,213 @@
+#include "patchdownloader.h"
+#include "models/filesystem.h"
+#include <QApplication>
+#include <QDir>
+#include <QStringList>
+#include <QUrlQuery>
+#include <QVariant>
+#include <QDebug>
+
+PatchDownloader::PatchDownloader(QObject *parent) : QObject(parent)
+{
+    patch_download_dir = QDir(QApplication::applicationDirPath() + "/data");
+
+    connect(&update_check_timer, &QTimer::timeout, this, &PatchDownloader::checkForUpdates);
+    update_check_timer.setInterval(1000 * 60 * 5); // 5 minutes
+    update_check_timer.start();
+}
+
+PatchDownloader::~PatchDownloader()
+{
+    clearDownloadQueue();
+}
+
+void PatchDownloader::checkForUpdates()
+{
+    if (!download_queue.empty()) {
+        qDebug() << "PatchDownloader: downloads are not ready yet, passing checkForUpdates";
+        return;
+    }
+
+    if (!updatePatchList())
+        return;
+    removeOldPatchesFromDirecrory();
+    addMissingPatchesToDownloadList();
+}
+
+void PatchDownloader::onDownloaderCompleted(Downloader *downloader_ptr)
+{
+    downloader_ptr->targetFile->close();
+    downloader_ptr->targetFile->deleteLater();
+}
+
+void PatchDownloader::onDownloaderProgressChanged(Downloader*)
+{
+    quint64 totalSize = 0;
+    quint64 downloadedSize = 0;
+    quint64 summary_speed = 0;
+    quint64 time_elapsed = 0;
+
+    foreach (Downloader* downloader, download_queue) {
+        totalSize += downloader->getBytesTotal();
+        downloadedSize += downloader->getBytesDownloaded();
+
+        if (downloader->getBytesTotal() != downloader->getBytesDownloaded()) {
+            summary_speed += downloader->getSpeed();
+        }
+    }
+
+    time_elapsed = (totalSize - downloadedSize) / qMax(quint64(1), summary_speed);
+
+    if (totalSize == downloadedSize)
+        emit downloadCompleted();
+    else
+        emit progressChanged(downloadedSize, totalSize,
+                             Downloader::getSpeedFormatted(summary_speed),
+                             Downloader::getElapsedTimeFormatted(time_elapsed));
+}
+
+int PatchDownloader::versionFromPatchFilename(QString filename)
+{
+    int version = 0;
+    for (int i = filename.indexOf("_v") + 2; i < filename.indexOf("_v") + 7; i += 2) {
+        version = version * 10 + (filename.at(i).toLatin1() - '0');
+    }
+    return version;
+}
+
+bool PatchDownloader::updatePatchList()
+{
+    QUrlQuery query; // query for building GET-request aka patch-version
+
+    QStringList names;
+    names  << "sound" << "text" << "image" << "loadscreen" << "texture" << "font";
+
+    foreach(QString patch_name, names) {
+        query.addQueryItem(patch_name, "100");
+    }
+
+    QUrl target_url;
+    target_url.setUrl("http://translate.lotros.ru/groupware/check_updates");
+    target_url.setQuery(query);
+
+    QByteArray target_array;
+    Downloader downloader;
+    downloader.setUrl(target_url);
+    downloader.targetBytearray = &target_array;
+    downloader.start();
+    downloader.waitForDownloaded();
+
+    if (target_array.isEmpty()) {
+        qDebug() << __FUNCTION__ << "Cannot download, target_array is empty!";
+        emit getPatchListError();
+        return false;
+    }
+
+    QStringList entry_list = QString(target_array).split('|');
+
+    if (entry_list.size() != names.size()) {
+        qDebug() << __FUNCTION__ << "Entry list size is not equal to patch names size!" << QString(target_array);
+        emit getPatchListError();
+        return false;
+    }
+
+    patches.clear();
+    for (int i = 0; i < entry_list.size(); ++i) {
+        QStringList patch_data = entry_list[i].split(":::");
+        if (patch_data.size() != 3) {
+            qDebug() << __FUNCTION__ << "Incorrect patch entry size! Entry: " << entry_list[i];
+            emit getPatchListError();
+            return false;
+        }
+        patches.append({patch_data[0], patch_data[1], patch_data[2]});
+    }
+    return true;
+}
+
+bool PatchDownloader::removeOldPatchesFromDirecrory()
+{
+    QStringList actual_hash_list;
+    foreach (Patch patch, patches) {
+        actual_hash_list.append(patch.md5_hash);
+    }
+
+    qDebug() << "Removing old patches. Current hash list " << actual_hash_list;
+
+    QStringList paths = patch_download_dir.entryList(QDir::Files);
+
+    foreach (QString filename, paths) {
+        QString hash = FileSystem::fileHash(patch_download_dir.absolutePath() + "/" + filename, QCryptographicHash::Md5);
+        if (!actual_hash_list.contains(hash)) {
+            qDebug() << "File " << filename << " with hash " << hash << "seems outdated, deleting!";
+            if (!QFile::remove(patch_download_dir.absolutePath() + "/" +filename)) {
+                qDebug() << __FUNCTION__ << "Unable to remove file " << filename;
+                emit removeFileError(patch_download_dir.absolutePath() + "/" + filename);
+            }
+        }
+    }
+
+    return true;
+}
+
+bool PatchDownloader::addMissingPatchesToDownloadList()
+{
+    QStringList file_hashes;
+    QStringList paths = patch_download_dir.entryList(QDir::Files);
+
+    QDir dir(patch_download_dir);
+    if (!dir.exists())
+        QDir().mkdir(patch_download_dir.absolutePath());
+
+    foreach (QString filename, paths) {
+        file_hashes << FileSystem::fileHash(patch_download_dir.absolutePath() + "/" + filename, QCryptographicHash::Md5);
+    }
+
+    bool download_started = false;
+
+    foreach (Patch patch, patches) {
+        if (file_hashes.contains(patch.md5_hash))
+            continue;
+
+        QString patch_filepath = patch_download_dir.absolutePath() + "/" + patch.url.fileName();
+
+        if (FileSystem::fileExists(patch_filepath) && !QFile::remove(patch_filepath)) {
+            qDebug() << __FUNCTION__ << "Unable to remove file " << patch_filepath;
+            emit removeFileError(patch_filepath);
+            continue;
+        }
+
+        if (!download_started) {
+            download_started = true;
+            emit downloadStarted();
+        }
+
+        qDebug() << "Starting download of file " << patch_filepath << " from url " << patch.url;
+
+        Downloader* downloader = new Downloader();
+        connect(downloader, &Downloader::progressChanged, this, &PatchDownloader::onDownloaderProgressChanged);
+        connect(downloader, &Downloader::downloadFinished, this, &PatchDownloader::onDownloaderCompleted);
+        downloader->setUrl(patch.url);
+        downloader->targetFile = new QFile(patch_filepath, downloader);
+        downloader->targetFile->open(QIODevice::ReadWrite);
+        downloader->start();
+        download_queue.append(downloader);
+    }
+    return true;
+}
+
+void PatchDownloader::clearDownloadQueue()
+{
+    for (int i = 0; i < download_queue.size(); ++i) {
+        Downloader* downloader = download_queue[i];
+
+        if (download_queue.indexOf(downloader) != i)
+            continue; // this downloader was already stopped and deleted in previous iterations.
+                      // this shouldn't normally happen because it means that file was being downloaded many times
+
+        downloader->stop();
+        downloader->targetFile->close();
+        downloader->targetFile->deleteLater();
+        downloader->deleteLater();
+    }
+    download_queue.clear();
+}

+ 66 - 0
src/Legacy/models/patchdownloader.h

@@ -0,0 +1,66 @@
+#ifndef PATCHDOWNLOADER_H
+#define PATCHDOWNLOADER_H
+
+#include <QObject>
+#include <QTimer>
+#include <QDate>
+#include <QDir>
+#include <QSettings>
+#include <QMap>
+#include "models/downloader.h"
+
+struct Patch {
+    QUrl url;
+    QString md5_hash;
+    QString datetime;
+};
+
+class PatchDownloader : public QObject
+{
+    Q_OBJECT
+public:
+    explicit PatchDownloader(QObject *parent = nullptr);
+    ~PatchDownloader();
+    void enableAutoUpdate();
+    void disableAutoUpdate();
+    void stopAllDownloads();
+    void startAllDownloads();
+
+public slots:
+    void checkForUpdates();
+
+private slots:
+    void onDownloaderCompleted(Downloader* downloader_ptr);
+    void onDownloaderProgressChanged(Downloader* downloader_ptr);
+
+signals:
+    void getPatchListError();
+    void removeFileError(QString filename);
+    void downloadNewFilesError();
+
+    void downloadCompleted();
+    void downloadStarted();
+    void progressChanged(quint64 bytesDownloaded, quint64 bytesTotal,
+                         QString download_speed_formatted,
+                         QString elapsed_time_formatted);
+
+    void patchDateChanged(QString patch_name, QDate patch_date);
+
+private:
+    int versionFromPatchFilename(QString filename);
+    bool updatePatchList();
+    bool removeOldPatchesFromDirecrory();
+    bool addMissingPatchesToDownloadList();
+    void clearDownloadQueue();
+
+private:
+    QTimer auto_update_timer;
+
+    QDir patch_download_dir;
+    QList<Patch> patches;
+
+    QTimer update_check_timer;
+    QList<Downloader*> download_queue;
+};
+
+#endif // PATCHDOWNLOADER_H

+ 6 - 2
src/Legacy/object_script.Legacy.Debug

@@ -12,10 +12,12 @@ INPUT(
 ./..\..\build\debug\Legacy\obj\rusificationwidget.o
 ./..\..\build\debug\Legacy\obj\settingswidget.o
 ./..\..\build\debug\Legacy\obj\statuswidget.o
-./..\..\build\debug\Legacy\obj\serverstatuswidget.o
 ./..\..\build\debug\Legacy\obj\weeklycodewidget.o
 ./..\..\build\debug\Legacy\obj\newslistwidget.o
 ./..\..\build\debug\Legacy\obj\newspiece.o
+./..\..\build\debug\Legacy\obj\statusflagwidget.o
+./..\..\build\debug\Legacy\obj\serverstatuswidget.o
+./..\..\build\debug\Legacy\obj\patchdownloader.o
 ./..\..\build\debug\Legacy\obj\legacy_plugin_import.o
 ./..\..\build\debug\Legacy\obj\moc_downloader.o
 ./..\..\build\debug\Legacy\obj\moc_filesystem.o
@@ -26,8 +28,10 @@ INPUT(
 ./..\..\build\debug\Legacy\obj\moc_rusificationwidget.o
 ./..\..\build\debug\Legacy\obj\moc_settingswidget.o
 ./..\..\build\debug\Legacy\obj\moc_statuswidget.o
-./..\..\build\debug\Legacy\obj\moc_serverstatuswidget.o
 ./..\..\build\debug\Legacy\obj\moc_weeklycodewidget.o
 ./..\..\build\debug\Legacy\obj\moc_newslistwidget.o
 ./..\..\build\debug\Legacy\obj\moc_newspiece.o
+./..\..\build\debug\Legacy\obj\moc_statusflagwidget.o
+./..\..\build\debug\Legacy\obj\moc_serverstatuswidget.o
+./..\..\build\debug\Legacy\obj\moc_patchdownloader.o
 );

+ 6 - 2
src/Legacy/object_script.Legacy.Release

@@ -10,10 +10,12 @@ INPUT(
 ./..\..\build\release\Legacy\obj\rusificationwidget.o
 ./..\..\build\release\Legacy\obj\settingswidget.o
 ./..\..\build\release\Legacy\obj\statuswidget.o
-./..\..\build\release\Legacy\obj\serverstatuswidget.o
 ./..\..\build\release\Legacy\obj\weeklycodewidget.o
 ./..\..\build\release\Legacy\obj\newslistwidget.o
 ./..\..\build\release\Legacy\obj\newspiece.o
+./..\..\build\release\Legacy\obj\statusflagwidget.o
+./..\..\build\release\Legacy\obj\serverstatuswidget.o
+./..\..\build\release\Legacy\obj\patchdownloader.o
 ./..\..\build\release\Legacy\obj\legacy_plugin_import.o
 ./..\..\build\release\Legacy\obj\moc_downloader.o
 ./..\..\build\release\Legacy\obj\moc_filesystem.o
@@ -24,8 +26,10 @@ INPUT(
 ./..\..\build\release\Legacy\obj\moc_rusificationwidget.o
 ./..\..\build\release\Legacy\obj\moc_settingswidget.o
 ./..\..\build\release\Legacy\obj\moc_statuswidget.o
-./..\..\build\release\Legacy\obj\moc_serverstatuswidget.o
 ./..\..\build\release\Legacy\obj\moc_weeklycodewidget.o
 ./..\..\build\release\Legacy\obj\moc_newslistwidget.o
 ./..\..\build\release\Legacy\obj\moc_newspiece.o
+./..\..\build\release\Legacy\obj\moc_statusflagwidget.o
+./..\..\build\release\Legacy\obj\moc_serverstatuswidget.o
+./..\..\build\release\Legacy\obj\moc_patchdownloader.o
 );

+ 48 - 4
src/Legacy/widgets/mainwindow.cpp

@@ -1,6 +1,8 @@
 #include "mainwindow.h"
 #include "ui_mainwindow.h"
 
+#include "models/patchdownloader.h"
+
 #include <QBitmap>
 #include <QPainter>
 #include <QPixmap>
@@ -20,7 +22,17 @@ MainWindow::MainWindow(QWidget *parent) :
     qDebug() << "Initialising Settings module";
     QSettings *settings = new QSettings(qApp->applicationDirPath() + "/legacy_v2.ini", QSettings::IniFormat);
 
-    status_widget = new StatusWidget(this);
+    qDebug() << "Creating patch downloader instance & thread";
+    patch_updater_thread = new QThread();
+    patch_updater = new PatchDownloader();
+    QObject::connect(patch_updater_thread, &QThread::finished, patch_updater, &QObject::deleteLater, Qt::QueuedConnection);
+    patch_updater->moveToThread(patch_updater_thread);
+    patch_updater_thread->start();
+
+    qDebug() << "Starting check for patch updates...";
+    QMetaObject::invokeMethod(patch_updater, "checkForUpdates", Qt::QueuedConnection);
+
+    status_widget = new StatusWidget(patch_updater, this);
     rusification_widget = new RusificationWidget(this);
     settings_widget = new SettingsWidget(this);
     help_widget = new HelpWidget(this);
@@ -56,6 +68,9 @@ MainWindow::MainWindow(QWidget *parent) :
     qDebug() << "Initialising main window connections";
     makeConnections();
 
+    qDebug() << "Installing event filters to clickable objects";
+    setEventFilterRecursive(this);
+
     qDebug() << "Finishing main frame initialisation";
     show();
 }
@@ -89,7 +104,6 @@ void MainWindow::resizeEvent(QResizeEvent * event)
     setupWindowBackgroundAndMask(current_bg);
 }
 
-
 void MainWindow::randomChangeBackground()
 {
     if (!qApp)
@@ -128,7 +142,6 @@ void MainWindow::randomChangeBackground()
     });
 }
 
-
 MainWindow::~MainWindow()
 {
     delete ui;
@@ -158,7 +171,6 @@ void MainWindow::on_menuentry_4_clicked()
     help_widget->show();
 }
 
-
 void MainWindow::onHoverMenuentry()
 {
     moveMenuHoverWidget(MenuEntry::getHoverLabel());
@@ -278,3 +290,35 @@ void MainWindow::on_minimizeButton_clicked()
 {
     setWindowState(Qt::WindowMinimized);
 }
+
+void MainWindow::setEventFilterRecursive(QObject *widget)
+{
+    if (!widget)
+        return;
+
+    QStringList classes_to_set = {
+        "QPushButton",
+        "QCheckBox",
+        "QComboBox"
+    };
+
+    if (classes_to_set.contains(widget->metaObject()->className())) {
+        widget->installEventFilter(this);
+    }
+
+    foreach (QObject* child, widget->children()) {
+        setEventFilterRecursive(child);
+    }
+}
+
+bool MainWindow::eventFilter(QObject *watched, QEvent *event)
+{
+    if (event->type() == QEvent::MouseButtonPress) {
+        mousePressEvent((QMouseEvent*)(event));
+    }
+
+    if (event->type() == QEvent::MouseMove) {
+        mouseMoveEvent((QMouseEvent*)(event));
+    }
+    return false;
+}

+ 10 - 3
src/Legacy/widgets/mainwindow.h

@@ -19,8 +19,7 @@ class MainWindow;
 class MenuEntry;
 // class LotroManager;
 // class NetworkUpdater;
-// class PatchDownloader;
-// class NewsDownloader;
+class PatchDownloader;
 
 class MainWindow : public QMainWindow
 {
@@ -35,6 +34,7 @@ protected:
     void mouseMoveEvent(QMouseEvent *event) override;
     void mousePressEvent(QMouseEvent *event) override;
     void resizeEvent(QResizeEvent *event) override;
+    bool eventFilter(QObject *watched, QEvent *event) override;
 
 private slots:
     void randomChangeBackground();
@@ -66,7 +66,13 @@ private:
 
     void hideAllContentWidgets();
 
-private:
+    bool buttonsMouseMoveEventFilter(QObject *obj, QEvent *event);
+
+    void setEventFilterRecursive(QObject* widget);
+private:    
+    PatchDownloader* patch_updater;
+    QThread *patch_updater_thread;
+
     Ui::MainWindow *ui;
 
     StatusWidget *status_widget;
@@ -96,4 +102,5 @@ private:
     const size_t bigbutton_font_size = 22;
 };
 
+
 #endif // MAINWINDOW_H

+ 3 - 2
src/Legacy/widgets/menuentry.cpp

@@ -34,7 +34,7 @@ void MenuEntry::setHoverLabel(MenuEntry *label)
 }
 
 void MenuEntry::mousePressEvent(QMouseEvent* event) {
-    event->accept();
+    event->ignore();
     if (active_label != this) {
         active_label = this;
         emit active_label_changed();
@@ -42,8 +42,9 @@ void MenuEntry::mousePressEvent(QMouseEvent* event) {
     }
 }
 
-void MenuEntry::mouseMoveEvent(QMouseEvent *)
+void MenuEntry::mouseMoveEvent(QMouseEvent *event)
 {
+    event->ignore();
     if (hover_label != this) {
         hover_label = this;
         emit hover_label_changed();

+ 8 - 7
src/Legacy/widgets/newslistwidget.cpp

@@ -8,7 +8,7 @@
 NewsListWidget::NewsListWidget(QWidget *parent) : QWidget(parent)
 {
     news_downloader.targetBytearray = &news_data;
-    news_downloader.setUrl(QUrl("http://translate.lotros.ru/groupware/launcher_news"));
+    news_downloader.setUrl(QUrl("http://translate.lotros.ru/groupware/launcher_news/50/1"));
 
     news_layout = new QVBoxLayout(this);
     news_layout->setSpacing(7);
@@ -18,7 +18,7 @@ NewsListWidget::NewsListWidget(QWidget *parent) : QWidget(parent)
     connect(&news_update_timer, &QTimer::timeout, &news_downloader, &Downloader::start);
     connect(&news_downloader, &Downloader::downloadFinished, this, &NewsListWidget::updateNews);
     emit news_downloader.start();
-    news_update_timer.setInterval(1000 * 5); // 60 seconds;
+    news_update_timer.setInterval(1000 * 60); // 60 seconds;
     news_update_timer.start();
 }
 
@@ -33,7 +33,7 @@ void NewsListWidget::updateNews()
         return;
 
     if (news_data.size() == 0) {
-        constructNewsPiece(0, "Не могу загрузить новости", "Загрузка новостей не удалась. Чтобы просмотреть новости, перейдите на <a href='http://translate.lotros.ru/news'>http://translate.lotros.ru/news</a>", "http://translate.lotros.ru/news");
+        constructNewsPiece(0, "Не могу загрузить новости", "Загрузка новостей не удалась. Чтобы просмотреть новости, перейдите на <a href='http://translate.lotros.ru/news'>http://translate.lotros.ru/news</a>", "http://translate.lotros.ru/news", "");
         return;
     }
 
@@ -47,12 +47,13 @@ void NewsListWidget::updateNews()
         QString news_title = news_piece[1];
         QString news_desrc = news_piece[2];
         QString news_src = news_piece[3];
+        QString news_date = news_piece[4];
 
-        constructNewsPiece(i, news_title, news_desrc, news_src);
+        constructNewsPiece(i, news_title, news_desrc, news_src, news_date);
 
         QtConcurrent::run([i, this, img_src](){
             Downloader img_dwnld;
-            QByteArray img;
+            QByteArray img = "";
             img_dwnld.setUrl(QUrl(img_src));
             img_dwnld.targetBytearray = &img;
             img_dwnld.start();
@@ -61,7 +62,6 @@ void NewsListWidget::updateNews()
             QPixmap img_pixmap;
             img_pixmap.loadFromData(img);
             QMetaObject::invokeMethod(this, "setImgToNewsPiece", Qt::QueuedConnection, Q_ARG(int, i), Q_ARG(QPixmap, img_pixmap));
-//            setImgToNewsPiece(i, img_pixmap);
         });
     }
 
@@ -69,7 +69,7 @@ void NewsListWidget::updateNews()
     news_layout->addItem(verticalSpacer);
 }
 
-void NewsListWidget::constructNewsPiece(int piece_id, QString title, QString text, QString news_src)
+void NewsListWidget::constructNewsPiece(int piece_id, QString title, QString text, QString news_src, QString news_date)
 {
     NewsPiece* old_piece = findChild<NewsPiece*>("news_piece_" + QString::number(piece_id));
 
@@ -83,6 +83,7 @@ void NewsListWidget::constructNewsPiece(int piece_id, QString title, QString tex
     news_piece->setIcon(QPixmap(QString::fromUtf8(":/appicon.ico")).scaled(40, 40));
     news_piece->setTitle(title, news_src);
     news_piece->setContents(text);
+    news_piece->setDate(news_date);
 
     news_layout->addWidget(news_piece, piece_id, 0);
 }

+ 1 - 1
src/Legacy/widgets/newslistwidget.h

@@ -23,7 +23,7 @@ private slots:
     void setImgToNewsPiece(int piece_id, QPixmap img);
 
 private:
-    void constructNewsPiece(int piece_id, QString title, QString text, QString news_src);
+    void constructNewsPiece(int piece_id, QString title, QString text, QString news_src, QString news_date);
 
 private:
     QVBoxLayout* news_layout;

+ 4 - 4
src/Legacy/widgets/newspiece.ui

@@ -88,10 +88,10 @@ background-color:rgba(0,0,0,0);
         </property>
         <property name="font">
          <font>
-          <family>Trajan Pro 3</family>
-          <pointsize>8</pointsize>
-          <weight>75</weight>
-          <bold>true</bold>
+          <family>Aniron</family>
+          <pointsize>7</pointsize>
+          <weight>50</weight>
+          <bold>false</bold>
          </font>
         </property>
         <property name="styleSheet">

+ 29 - 59
src/Legacy/widgets/serverstatuswidget.cpp

@@ -1,72 +1,42 @@
 #include "serverstatuswidget.h"
-#include <QApplication>
-#include <QtConcurrent/QtConcurrent>
-#include <QPainter>
-#include <QPaintEvent>
+#include "ui_serverstatuswidget.h"
 
-ServerStatusWidget::ServerStatusWidget(QWidget *parent) : QWidget(parent)
-{
-    setAttribute(Qt::WA_Hover);
-    setMouseTracking(true);
-    current_bg = QPixmap(":/flags/flag2.png");
-    changeImageSrc(":/flags/flag2.png");
-}
+#include <QDebug>
 
-void ServerStatusWidget::changeImageSrc(const QString &src)
+ServerStatusWidget::ServerStatusWidget(QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::ServerStatusWidget)
 {
-    if (!qApp)
-        return;
-
-    img_src = src;
-    QPixmap *cur_bg = new QPixmap(current_bg);
-    QPixmap *new_bg= new QPixmap(QPixmap(src));
-
-    QtConcurrent::run([cur_bg, new_bg, src, this](){
-        const int iterations_num = 100;
-        const int iteration_sleep = 20;
-
-        for (int i = 0; i < iterations_num && qApp; i++) {
-            if (img_src != src)
-                break;
+    ui->setupUi(this);
 
-            QPainter painter;
-            painter.begin(cur_bg);
-            painter.setOpacity(double(10 + i) / double(iterations_num));
-            painter.setCompositionMode(QPainter::CompositionMode_Source);
-            painter.drawPixmap(0,0, *new_bg);
-            painter.end();
-            bg_lock.lock();
-            current_bg = *cur_bg;
-            bg_lock.unlock();
-            QMetaObject::invokeMethod(this, "repaint", Qt::QueuedConnection);
-            QThread::msleep(iteration_sleep);
-        }
-        delete cur_bg;
-        delete new_bg;
-    });
+    downloader.targetBytearray = &downloaded_data;
+    downloader.setUrl(QUrl("http://translate.lotros.ru/servers.txt"));
 
+    connect(&update_timer, &QTimer::timeout, &downloader, &Downloader::start);
+    connect(&downloader, &Downloader::downloadFinished, this, &ServerStatusWidget::updateStatus, Qt::QueuedConnection);
+    emit downloader.start();
+    update_timer.setInterval(1000 * 60); // 1 minute;
+    update_timer.start();
 }
 
-void ServerStatusWidget::enterEvent(QEvent * event)
+ServerStatusWidget::~ServerStatusWidget()
 {
-    qDebug() << Q_FUNC_INFO << this->objectName();
-    changeImageSrc(":/flags/flag2_hover.png");
-    QWidget::enterEvent(event);
+    delete ui;
 }
 
-void ServerStatusWidget::paintEvent(QPaintEvent *event)
+void ServerStatusWidget::updateStatus()
 {
-    Q_UNUSED(event);
-    QPainter painter;
-    painter.begin(this);
-    painter.drawPixmap(0,0, current_bg);
-    painter.end();
+    QStringList servers = QString(downloaded_data).split("|||");
+    foreach (QString server, servers) {
+        QStringList data = server.split(":::");
+        QWidget* widget = findChild<QWidget*>(QString(data[0]).toLower() + "_common");
+        qDebug() << data;
+        if (!widget)
+            continue;
+        if (data.size() > 1 && data[1] == "on")
+            widget->setStyleSheet(open_color);
+        else
+            widget->setStyleSheet(closed_color);
+    }
+    downloaded_data = "";
 }
-
-void ServerStatusWidget::leaveEvent(QEvent * event)
-{
-    qDebug() << Q_FUNC_INFO << this->objectName();
-    changeImageSrc(":/flags/flag2.png");
-    QWidget::leaveEvent(event);
-}
-

+ 20 - 12
src/Legacy/widgets/serverstatuswidget.h

@@ -2,27 +2,35 @@
 #define SERVERSTATUSWIDGET_H
 
 #include <QWidget>
-#include <QMutex>
+#include <QTimer>
+#include <QByteArray>
+#include "models/downloader.h"
+
+namespace Ui {
+class ServerStatusWidget;
+}
 
 class ServerStatusWidget : public QWidget
 {
     Q_OBJECT
-public:
-    explicit ServerStatusWidget(QWidget *parent = nullptr);
 
-protected:
-    virtual void leaveEvent(QEvent * event) override;
-    virtual void enterEvent(QEvent * event) override;
-    virtual void paintEvent(QPaintEvent *event) override;
-signals:
+public:
+    explicit ServerStatusWidget(QWidget *parent = 0);
+    ~ServerStatusWidget();
 
 public slots:
-    void changeImageSrc(const QString& src);
+    void updateStatus();
+
+private:
+    QByteArray downloaded_data;
+    QTimer update_timer;
+    Downloader downloader;
+
+    const QString open_color = "color: rgb(0, 170, 0);";
+    const QString closed_color = "color: rgb(255, 0, 0);";
 
 private:
-    QMutex bg_lock;
-    QString img_src;
-    QPixmap current_bg;
+    Ui::ServerStatusWidget *ui;
 };
 
 #endif // SERVERSTATUSWIDGET_H

+ 278 - 0
src/Legacy/widgets/serverstatuswidget.ui

@@ -0,0 +1,278 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ServerStatusWidget</class>
+ <widget class="QWidget" name="ServerStatusWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>418</width>
+    <height>188</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item row="0" column="0">
+    <widget class="QWidget" name="us_cluster" native="true">
+     <property name="styleSheet">
+      <string notr="true">color:white;</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_7">
+      <item>
+       <widget class="QLabel" name="us_label_common">
+        <property name="font">
+         <font>
+          <family>Trajan Pro 3</family>
+         </font>
+        </property>
+        <property name="text">
+         <string>US-кластер</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="arkenstone_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Arkenstone [US]</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="brandywine_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Brandywine [US]</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="crickhollow_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Crickhollow [US]</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="gladden_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Gladden</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="landroval_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Landroval</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QWidget" name="eu_cluster" native="true">
+     <property name="styleSheet">
+      <string notr="true">color:white;</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_8">
+      <item>
+       <widget class="QLabel" name="eu_label_common">
+        <property name="font">
+         <font>
+          <family>Trajan Pro 3</family>
+         </font>
+        </property>
+        <property name="text">
+         <string>EU-кластер</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line_2">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="belegaer_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Belegaer</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="evernight_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Evernight</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="gwaihir_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Gwaihir</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="laurelin_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Laurelin</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="sirannon_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Sirannon</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <widget class="QWidget" name="other_servers" native="true">
+     <property name="styleSheet">
+      <string notr="true">color:white;</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_9">
+      <item>
+       <widget class="QLabel" name="legendary_servers_label_common">
+        <property name="font">
+         <font>
+          <family>Trajan Pro 3</family>
+         </font>
+        </property>
+        <property name="text">
+         <string>Legendary Servers</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line_3">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="anor_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Anor</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="ithil_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Ithil</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="pts_label_common">
+        <property name="font">
+         <font>
+          <family>Trajan Pro 3</family>
+         </font>
+        </property>
+        <property name="text">
+         <string>Public Test Servers</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="Line" name="line_5">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="bullroarer_common">
+        <property name="styleSheet">
+         <string notr="true">color: rgb(255, 255, 127);</string>
+        </property>
+        <property name="text">
+         <string>Bullroarer</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 100 - 0
src/Legacy/widgets/statusflagwidget.cpp

@@ -0,0 +1,100 @@
+#include "statusflagwidget.h"
+#include <QApplication>
+#include <QtConcurrent/QtConcurrent>
+#include <QPainter>
+#include <QPaintEvent>
+
+StatusFlagWidget::StatusFlagWidget(QWidget *parent) : QWidget(parent)
+{
+    setAttribute(Qt::WA_Hover);
+    setMouseTracking(true);
+    changeFlagId(3);
+    changeImageSrcInstantly(current_flag_src);
+}
+
+void StatusFlagWidget::changeImageSrcAnimated(const QString &src)
+{
+    if (!qApp)
+        return;
+
+    QPixmap *cur_bg = new QPixmap(current_bg);
+    QPixmap *new_bg= new QPixmap(QPixmap(src));
+
+    QtConcurrent::run([cur_bg, new_bg, src, this](){
+        const int iterations_num = 100;
+        const int iteration_sleep = 20;
+
+        for (int i = 0; i < iterations_num && qApp; i++) {
+            if (!(is_hovered && current_flag_hover_src == src) && !(!is_hovered && current_flag_src == src))
+                break;
+
+            QPainter painter;
+            painter.begin(cur_bg);
+            painter.setOpacity(double(10 + i) / double(iterations_num));
+            painter.setCompositionMode(QPainter::CompositionMode_Source);
+            painter.drawPixmap(0,0, *new_bg);
+            painter.end();
+            bg_lock.lock();
+            current_bg = *cur_bg;
+            bg_lock.unlock();
+            QMetaObject::invokeMethod(this, "repaint", Qt::QueuedConnection);
+            QThread::msleep(iteration_sleep);
+        }
+        delete cur_bg;
+        delete new_bg;
+    });
+}
+
+void StatusFlagWidget::changeImageSrcInstantly(const QString &src)
+{
+    current_bg = QPixmap(src);
+    repaint();
+}
+
+void StatusFlagWidget::changeFlagId(int new_flag_id)
+{
+    if (new_flag_id == flag_id)
+        return;
+    flag_id = new_flag_id;
+    current_flag_src = ":/flags/flag" + QString::number(flag_id) + ".png";
+    current_flag_hover_src = ":/flags/flag" + QString::number(flag_id) + "_hover.png";
+    emit flagIdChanged(flag_id);
+}
+
+void StatusFlagWidget::enterEvent(QEvent * event)
+{
+    is_hovered = true;
+    changeImageSrcAnimated(current_flag_hover_src);
+    QWidget::enterEvent(event);
+    event->ignore();
+}
+
+void StatusFlagWidget::paintEvent(QPaintEvent *event)
+{
+    Q_UNUSED(event);
+    QPainter painter;
+    painter.begin(this);
+    painter.drawPixmap(0,0, current_bg);
+    painter.end();
+}
+
+void StatusFlagWidget::leaveEvent(QEvent * event)
+{
+    is_hovered = false;
+    changeImageSrcAnimated(current_flag_src);
+    QWidget::leaveEvent(event);
+    event->ignore();
+}
+
+
+void StatusFlagWidget::mousePressEvent(QMouseEvent *ev)
+{
+    if (flag_id == 2) {
+        changeFlagId(3);
+        changeImageSrcInstantly(current_flag_hover_src);
+    } else {
+        changeFlagId(2);
+        changeImageSrcInstantly(current_flag_hover_src);
+    }
+    QWidget::mousePressEvent(ev);
+}

+ 38 - 0
src/Legacy/widgets/statusflagwidget.h

@@ -0,0 +1,38 @@
+#ifndef STATUSFLAGWIDGET_H
+#define STATUSFLAGWIDGET_H
+
+#include <QWidget>
+#include <QMutex>
+
+class StatusFlagWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    explicit StatusFlagWidget(QWidget *parent = nullptr);
+
+protected:
+    virtual void leaveEvent(QEvent * event) override;
+    virtual void enterEvent(QEvent * event) override;
+    virtual void paintEvent(QPaintEvent *event) override;
+    virtual void mousePressEvent(QMouseEvent *ev) override;
+
+signals:
+    void flagIdChanged(int flag_id);
+
+public slots:
+    void changeImageSrcAnimated(const QString& src);
+    void changeImageSrcInstantly(const QString& src);
+    void changeFlagId(int new_flag_id);
+
+private:
+    QMutex bg_lock;
+    int flag_id;
+    bool is_hovered;
+
+    QString current_flag_src;
+    QString current_flag_hover_src;
+
+    QPixmap current_bg;
+};
+
+#endif // STATUSFLAGWIDGET_H

+ 44 - 2
src/Legacy/widgets/statuswidget.cpp

@@ -1,18 +1,27 @@
 #include "statuswidget.h"
 #include "ui_statuswidget.h"
 #include "widgets/mainwindow.h"
+#include "models/patchdownloader.h"
 
 #include <QDesktopServices>
 #include <QUrl>
 #include <QDebug>
 #include <QMessageBox>
 
-StatusWidget::StatusWidget(QWidget *parent) :
+StatusWidget::StatusWidget(PatchDownloader* patch_downloader, QWidget *parent) :
     QWidget(parent),
     ui(new Ui::StatusWidget)
 {
+
     ui->setupUi(this);
     connect(ui->announcements_list, &NewsListWidget::newsUpdated, this, &StatusWidget::invokeUpdateFontSize);
+    connect(ui->server_status_flag, &StatusFlagWidget::flagIdChanged, this, &StatusWidget::changeCentralWidget);
+    ui->status_widget->hide();
+
+    patch_updater = patch_downloader;
+    connect(patch_updater, &PatchDownloader::downloadStarted, this, &StatusWidget::onPatchDownloaderStarted, Qt::QueuedConnection);
+    connect(patch_updater, &PatchDownloader::progressChanged, this, &StatusWidget::onPatchDownloaderProgressChanged, Qt::QueuedConnection);
+    connect(patch_updater, &PatchDownloader::downloadCompleted, this, &StatusWidget::onPatchDownloaderFinished, Qt::QueuedConnection);
 }
 
 StatusWidget::~StatusWidget()
@@ -20,6 +29,39 @@ StatusWidget::~StatusWidget()
     delete ui;
 }
 
+void StatusWidget::onPatchDownloaderStarted()
+{
+    ui->process_label->setText("Загрузка обновлений патчей...");
+    ui->progressBar->setValue(0);
+}
+
+void StatusWidget::onPatchDownloaderFinished()
+{
+    ui->process_label->setText("Загрузка обновлений патчей завершена...");
+    ui->progressBar->setValue(100);
+}
+
+void StatusWidget::onPatchDownloaderProgressChanged(quint64 bytesDownloaded, quint64 bytesTotal, QString download_speed_formatted, QString elapsed_time_formatted)
+{
+    ui->process_label->setText("Загрузка " + download_speed_formatted + ". Загружено "
+                               + QString::number(bytesDownloaded) + " из " + QString::number(bytesTotal)
+                               + " (" + QString::number(bytesDownloaded * 100 / bytesTotal) + "%) "
+                               + "\nОставшееся время: " + elapsed_time_formatted);
+
+    ui->progressBar->setValue(bytesDownloaded * 100 / bytesTotal);
+}
+
+void StatusWidget::changeCentralWidget()
+{
+    if (ui->announcements_widget->isHidden()) {
+        ui->announcements_widget->show();
+        ui->status_widget->hide();
+    } else {
+        ui->announcements_widget->hide();
+        ui->status_widget->show();
+    }
+}
+
 void StatusWidget::invokeUpdateFontSize()
 {
     MainWindow* window = qobject_cast<MainWindow*>(parentWidget()->parentWidget()->parentWidget());
@@ -53,5 +95,5 @@ void StatusWidget::on_bugreport_link_button_clicked()
 
 void StatusWidget::on_donate_link_button_clicked()
 {
-    QMessageBox::information(this, "Страницы не существует!", "Нужно создать страницу для доната!");
+    QDesktopServices::openUrl(QUrl(" http://translate.lotros.ru/donate"));
 }

+ 12 - 1
src/Legacy/widgets/statuswidget.h

@@ -7,15 +7,24 @@ namespace Ui {
 class StatusWidget;
 }
 
+class PatchDownloader;
+
 class StatusWidget : public QWidget
 {
     Q_OBJECT
 
 public:
-    explicit StatusWidget(QWidget *parent = 0);
+    explicit StatusWidget(PatchDownloader* patch_downloader, QWidget *parent = 0);
     ~StatusWidget();
 
 private slots:
+    void onPatchDownloaderStarted();
+    void onPatchDownloaderFinished();
+    void onPatchDownloaderProgressChanged(quint64 bytesDownloaded, quint64 bytesTotal,
+                                          QString download_speed_formatted,
+                                          QString elapsed_time_formatted);
+
+    void changeCentralWidget();
 
     void invokeUpdateFontSize();
 
@@ -34,6 +43,8 @@ private slots:
 private:
     Ui::StatusWidget *ui;
 
+    PatchDownloader* patch_updater;
+
     const QColor inWorkColor = QColor(85, 170, 255);
     const QColor readyColor = QColor(0, 170, 0);
     const QColor errorColor = QColor(255, 85, 0);

File diff suppressed because it is too large
+ 700 - 367
src/Legacy/widgets/statuswidget.ui


+ 3 - 1
src/Legacy/widgets/weeklycodewidget.cpp

@@ -63,6 +63,7 @@ void WeeklyCodeWidget::enterEvent(QEvent * event)
 {
     changeImageSrc(":/buttons/lotr_circle_hover.png");
     QWidget::enterEvent(event);
+    event->ignore();
 }
 
 void WeeklyCodeWidget::paintEvent(QPaintEvent *event)
@@ -77,12 +78,14 @@ void WeeklyCodeWidget::paintEvent(QPaintEvent *event)
 void WeeklyCodeWidget::mousePressEvent(QMouseEvent *ev)
 {
     setStyleSheet("color: rgb(255, 150, 0);");
+    ev->ignore();
 }
 
 void WeeklyCodeWidget::mouseReleaseEvent(QMouseEvent *ev)
 {
     setStyleSheet("color: rgb(255, 180, 0);");
     QApplication::clipboard()->setText(text());
+    ev->ignore();
 }
 
 void WeeklyCodeWidget::updateCode()
@@ -97,6 +100,5 @@ void WeeklyCodeWidget::leaveEvent(QEvent * event)
 {
     qDebug() << Q_FUNC_INFO << this->objectName();
     changeImageSrc(":/buttons/lotr_circle.png");
-    QWidget::leaveEvent(event);
 }
 

Some files were not shown because too many files changed in this diff