Skip to content

Commit c651b82

Browse files
committed
add LoadingIndicator, update PyRunner
automatically get level from trle.net on the working thread
1 parent e7ead98 commit c651b82

13 files changed

+346
-92
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ set(SOURCES_MC
9191
src/FileManager.cpp
9292
src/GameFileTree.hpp
9393
src/GameFileTree.cpp
94+
src/LoadingIndicator.hpp
95+
src/LoadingIndicator.cpp
9496
src/Model.hpp
9597
src/Model.cpp
9698
src/Network.hpp

src/Controller.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ void Controller::initializeThread() {
5050
model.setupGame(id);
5151
});
5252

53+
connect(this, &Controller::updateLevelThreadSignal,
54+
this, [this](int id) {
55+
model.updateLevel(id);
56+
});
57+
58+
connect(this, &Controller::syncLevelsThreadSignal,
59+
this, [this]() {
60+
model.syncLevels();
61+
});
62+
5363
// Comming back from model or other model objects
5464
auto tickSignal = [this](){ emit controllerTickSignal(); };
5565
connect(&model, &Model::modelTickSignal,
@@ -75,10 +85,11 @@ void Controller::initializeThread() {
7585
this, [this]() {
7686
emit controllerReloadLevelList();
7787
}, Qt::QueuedConnection);
78-
}
7988

80-
void Controller::checkCommonFiles() {
81-
emit checkCommonFilesThreadSignal();
89+
connect(&model, &Model::modelLoadingDoneSignal,
90+
this, [this]() {
91+
emit controllerLoadingDone();
92+
}, Qt::QueuedConnection);
8293
}
8394

8495
void Controller::setup(const QString& level, const QString& game) {
@@ -97,6 +108,15 @@ void Controller::setupLevel(int id) {
97108
emit setupLevelThreadSignal(id);
98109
}
99110

111+
void Controller::updateLevel(int id) {
112+
emit updateLevelThreadSignal(id);
113+
}
114+
115+
void Controller::syncLevels() {
116+
emit syncLevelsThreadSignal();
117+
}
118+
119+
100120
// Using the GUI Threads
101121
int Controller::checkGameDirectory(int id) {
102122
return model.checkGameDirectory(id);
@@ -114,10 +134,6 @@ const QString Controller::getWalkthrough(int id) {
114134
return model.getWalkthrough(id);
115135
}
116136

117-
bool Controller::updateLevel(int id) {
118-
return model.updateLevel(id);
119-
}
120-
121137
bool Controller::link(int id) {
122138
return model.setLink(id);
123139
}

src/Controller.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,29 @@ class Controller : public QObject {
3232
}
3333

3434
int checkGameDirectory(int id);
35-
void checkCommonFiles();
35+
// void checkCommonFiles();
3636
void setup(const QString& level, const QString& game);
3737
void setupGame(int id);
3838
void setupLevel(int id);
39+
void updateLevel(int id);
40+
void syncLevels();
3941

4042
void getList(QVector<ListItemData>* list);
4143
void getCoverList(QVector<ListItemData*>* items);
4244
const InfoData getInfo(int id);
4345
const QString getWalkthrough(int id);
4446
bool link(int id);
45-
bool updateLevel(int id);
4647
int getItemState(int id);
4748

4849
signals:
4950
void controllerGenerateList(const QList<int>& availableGames);
5051
void controllerTickSignal();
5152
void controllerDownloadError(int status);
5253
void controllerReloadLevelList();
54+
void controllerLoadingDone();
5355

54-
void checkCommonFilesThreadSignal();
56+
void updateLevelThreadSignal(int id);
57+
void syncLevelsThreadSignal();
5558
void setupThreadSignal(const QString& level, const QString& game);
5659
void setupGameThreadSignal(int id);
5760
void setupLevelThreadSignal(int id);

src/LoadingIndicator.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* TombRaiderLinuxLauncher
2+
* Martin Bångens Copyright (C) 2025
3+
* This program is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*/
13+
14+
#include "../src/LoadingIndicator.hpp"
15+
#include <QPainter>
16+
#include <QtMath>
17+
18+
LoadingIndicator::LoadingIndicator(QWidget* parent) : QWidget(parent) {
19+
setAttribute(Qt::WA_TransparentForMouseEvents);
20+
setAttribute(Qt::WA_OpaquePaintEvent, false);
21+
22+
m_anim = new QPropertyAnimation(this, "angle");
23+
m_anim->setStartValue(0);
24+
m_anim->setEndValue(360);
25+
m_anim->setDuration(1000);
26+
m_anim->setLoopCount(-1);
27+
}
28+
29+
void LoadingIndicator::paintEvent(QPaintEvent*) {
30+
QPainter p(this);
31+
p.setRenderHint(QPainter::Antialiasing);
32+
33+
int count = 12;
34+
qreal radius = qMin(width(), height()) / 2.5;
35+
QPointF center = rect().center();
36+
37+
for (int i = 0; i < count; ++i) {
38+
qreal theta = (360.0 / count) * i + m_angle;
39+
qreal alpha = 255.0 * (i + 1) / count;
40+
41+
QPointF dotPos = center + QPointF(
42+
radius * std::cos(qDegreesToRadians(theta)),
43+
radius * std::sin(qDegreesToRadians(theta)));
44+
45+
QColor color = Qt::gray;
46+
color.setAlphaF(alpha / 255.0);
47+
48+
p.setBrush(color);
49+
p.setPen(Qt::NoPen);
50+
p.drawEllipse(dotPos, 4, 4);
51+
}
52+
}
53+
void LoadingIndicator::showEvent(QShowEvent* event) {
54+
QWidget::showEvent(event);
55+
m_anim->start();
56+
}
57+
58+
void LoadingIndicator::hideEvent(QHideEvent* event) {
59+
QWidget::hideEvent(event);
60+
m_anim->stop();
61+
}

src/LoadingIndicator.hpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* TombRaiderLinuxLauncher
2+
* Martin Bångens Copyright (C) 2025
3+
* This program is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*/
13+
14+
#pragma once
15+
16+
#include <QWidget>
17+
#include <QPropertyAnimation>
18+
19+
class LoadingIndicator : public QWidget {
20+
Q_OBJECT
21+
Q_PROPERTY(qreal angle READ angle WRITE setAngle)
22+
23+
public:
24+
explicit LoadingIndicator(QWidget* parent = nullptr);
25+
QSize sizeHint() const override { return QSize(64, 64); }
26+
27+
qreal angle() const { return m_angle; }
28+
void setAngle(qreal a) { m_angle = a; update(); }
29+
30+
protected:
31+
void paintEvent(QPaintEvent* event) override;
32+
void showEvent(QShowEvent* event) override;
33+
void hideEvent(QHideEvent* event) override;
34+
35+
private:
36+
qreal m_angle = 0;
37+
QPropertyAnimation* m_anim;
38+
};
39+

src/Model.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ bool Model::setupDirectories(const QString& level, const QString& game) {
2121
if (fileManager.setUpCamp(level, game) &&
2222
downloader.setUpCamp(level) &&
2323
data.initializeDatabase(level) &&
24-
m_pyRunner.setUpCamp(level.toStdString())) {
24+
m_pyRunner.setUpCamp(level)) {
2525
status = true;
2626
}
2727
return status;
@@ -206,8 +206,16 @@ void Model::setupGame(int id) {
206206
}
207207
}
208208

209-
bool Model::updateLevel(const int id) {
210-
return m_pyRunner.updateLevel(id);
209+
void Model::updateLevel(const int id) {
210+
// qint64 status = m_pyRunner.updateLevel(id);
211+
(void)m_pyRunner.updateLevel(id);
212+
emit this->modelLoadingDoneSignal();
213+
}
214+
215+
void Model::syncLevels() {
216+
// qint64 status = m_pyRunner.syncCards();
217+
(void)m_pyRunner.syncCards();
218+
emit this->modelLoadingDoneSignal();
211219
}
212220

213221
bool Model::unpackLevel(const int id, const QString& name, const QString& exe) {

src/Model.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@ class Model : public QObject {
7777
const QString getWalkthrough(int id);
7878
bool setupDirectories(const QString& level, const QString& game);
7979
void setup(const QString& level, const QString& game);
80-
bool updateLevel(const int id);
80+
void updateLevel(const int id);
81+
void syncLevels();
8182

8283
signals:
8384
void generateListSignal(QList<int> availableGames);
8485
void modelTickSignal();
8586
void modelReloadLevelListSignal();
87+
void modelLoadingDoneSignal();
8688

8789
private:
8890
bool getLevelHaveFile(

src/PyRunner.cpp

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,94 +12,104 @@
1212
*/
1313

1414
#include "../src/PyRunner.hpp"
15-
#include <Python.h>
16-
#include <vector>
17-
#include <string>
18-
#include <iostream>
19-
#include <filesystem>
15+
#include <QDebug>
16+
#include <QString>
17+
#include <QVector>
18+
#include <QFile>
2019
#include <cstdio>
2120

22-
namespace fs = std::filesystem;
21+
#undef slots
22+
#undef signals
23+
#include <Python.h>
24+
PyThreadState *g_mainState = nullptr;
2325

2426
PyRunner::PyRunner() : m_status(0) {
2527
Py_Initialize();
28+
g_mainState = PyEval_SaveThread();
2629
}
2730

28-
PyRunner::PyRunner(const std::string& cwd) : m_cwd(cwd), m_status(0) {
31+
PyRunner::PyRunner(const QString& cwd) : m_cwd(cwd), m_status(0) {
2932
Py_Initialize();
33+
g_mainState = PyEval_SaveThread();
3034
}
3135

3236
PyRunner::~PyRunner() {
33-
Py_Finalize();
37+
PyEval_RestoreThread(g_mainState);
38+
Py_FinalizeEx();
3439
}
3540

36-
bool PyRunner::setUpCamp(const std::string& level) {
37-
if (!fs::exists(level)) return false;
41+
bool PyRunner::setUpCamp(const QString& level) {
42+
bool status = true;
43+
if (!QFile::exists(level)) {
44+
status = false;
45+
}
3846
m_cwd = level;
39-
return true;
47+
return status;
4048
}
4149

42-
void PyRunner::run(const std::string& script,
43-
const std::vector<std::string>& args) {
50+
void PyRunner::run(const QString& script,
51+
const QVector<QString>& args) {
4452
if (!Py_IsInitialized()) {
45-
std::cerr << "Python not initialized!" << std::endl;
53+
qDebug() << "Python not initialized!";
4654
m_status = 1;
4755
return;
4856
}
4957

50-
std::cerr << "[PyRunner] Executing script file: " << script << std::endl;
58+
PyGILState_STATE gil = PyGILState_Ensure();
59+
qDebug() << "[PyRunner] Executing script file: " << script << Qt::endl;
5160

5261
// Add working dir to sys.path
53-
if (!m_cwd.empty()) {
62+
if (!m_cwd.isEmpty()) {
5463
PyObject *sysPath = PySys_GetObject("path");
55-
PyObject *cwdPath = PyUnicode_FromString(m_cwd.c_str());
64+
PyObject *cwdPath = PyUnicode_FromString(m_cwd.toStdString().c_str());
5665
PyList_Insert(sysPath, 0, cwdPath);
5766
Py_DECREF(cwdPath);
5867
}
5968

6069
// Set sys.argv
6170
PyObject *argvList = PyList_New(args.size());
6271
for (size_t i = 0; i < args.size(); ++i) {
63-
PyObject *arg = PyUnicode_FromString(args[i].c_str());
72+
PyObject *arg = PyUnicode_FromString(args[i].toStdString().c_str());
6473
PyList_SET_ITEM(argvList, i, arg); // steals ref
6574
}
6675
PySys_SetObject("argv", argvList);
6776
Py_DECREF(argvList);
6877

69-
std::string fullpath =
70-
m_cwd.empty() ? script : (m_cwd + "/" + script + ".py");
71-
FILE* fp = fopen(fullpath.c_str(), "r");
78+
QString fullpath =
79+
m_cwd.isEmpty() ? script : (m_cwd + "/" + script + ".py");
80+
FILE* fp = fopen(fullpath.toStdString().c_str(), "r");
7281
if (!fp) {
73-
std::cerr << "[PyRunner] Failed to open script file: "
74-
<< fullpath << std::endl;
82+
qDebug() << "[PyRunner] Failed to open script file: "
83+
<< fullpath << Qt::endl;
7584
m_status = 1;
7685
return;
7786
}
7887

79-
int result = PyRun_SimpleFile(fp, fullpath.c_str());
88+
int result = PyRun_SimpleFile(fp, fullpath.toStdString().c_str());
8089
fclose(fp);
90+
PyGILState_Release(gil);
8191

8292
if (result != 0) {
83-
std::cerr << "[PyRunner] Python script returned error.\n";
93+
qDebug() << "[PyRunner] Python script returned error.\n";
8494
m_status = 1;
8595
} else {
86-
std::cerr << "[PyRunner] Script executed successfully.\n";
96+
qDebug() << "[PyRunner] Script executed successfully.\n";
8797
m_status = 0;
8898
}
8999
}
90100

91-
int64_t PyRunner::updateLevel(int64_t lid) {
101+
qint64 PyRunner::updateLevel(qint64 lid) {
92102
run("tombll_manage_data",
93-
{"tombll_manage_data.py", "-u", std::to_string(lid)});
103+
{"tombll_manage_data.py", "-u", QString("%1").arg(lid)});
94104
return m_status;
95105
}
96106

97-
int64_t PyRunner::syncCards(int64_t lid) {
107+
qint64 PyRunner::syncCards() {
98108
run("tombll_manage_data", {"tombll_manage_data.py", "-sc"});
99109
return m_status;
100110
}
101111

102-
int64_t PyRunner::getStatus() const {
112+
qint64 PyRunner::getStatus() const {
103113
return m_status;
104114
}
105115

0 commit comments

Comments
 (0)