Merge commit '4f73289602742a7d0ed7a54f50dfef9d538dccb5' into Thunder

This commit is contained in:
2025-11-10 15:32:49 +08:00
17 changed files with 757 additions and 93 deletions

View File

@@ -19,19 +19,21 @@ set(PROJECT_SOURCES
src/mainwindow.ui
src/aptssupdater.h
src/aptssupdater.cpp
src/appdelegate.h # 添加这一行
src/appdelegate.cpp # 添加这一行
src/icons.qrc
src/appdelegate.h
src/appdelegate.cpp
src/applistmodel.h
src/applistmodel.cpp
src/downloadmanager.h
src/downloadmanager.cpp
src/ignoreconfig.h
src/ignoreconfig.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(spark-update-tool
MANUAL_FINALIZATION
${PROJECT_SOURCES}
src/icons.qrc
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET spark-update-tool APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR

View File

@@ -1,3 +1,21 @@
spark-update-tool (1.0.3) unstable; urgency=low
* 修复默认图标加载失败的问题
* 修复更新器在安装阶段强制关闭窗口后再次更新无法安装软件包的问题。
-- momen <vmomenv@gmail.com> Fri, 17 Oct 2025 00:00:00 +0000
spark-update-tool (1.0.2) unstable; urgency=low
* 添加复选框,选择多个包更新
* 修复缩放问题
* 添加忽略应用功能
-- momen <vmomenv@gmail.com> Mon, 29 Sep 2025 00:00:00 +0000
spark-update-tool (1.0.1) unstable; urgency=low
* 修复窗口调整大小时的错误
-- momen <vmomenv@gmail.com> Wed, 18 Jun 2025 00:00:00 +0000
spark-update-tool (1.0.0) unstable; urgency=low
* Initial release.

View File

@@ -1 +1 @@
9
13

View File

@@ -1,7 +1,27 @@
#!/usr/bin/make -f
%:
dh $@
# 声明兼容性级别
export DH_VERBOSE=1
%:
dh $@ --buildsystem=cmake
# 确保使用CMake进行配置
override_dh_auto_configure:
dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr
# 确保使用CMake进行构建
override_dh_auto_build:
dh_auto_build
# 确保使用CMake进行安装
override_dh_auto_install:
dh_auto_install
# 确保使用CMake进行清理
override_dh_auto_clean:
dh_auto_clean
# 确保使用CMake进行依赖解析
override_dh_shlibdeps:
dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -7,6 +7,7 @@
#include <QPushButton>
#include <QPainter>
#include <QMouseEvent>
#include <QFile>
AppDelegate::AppDelegate(QObject *parent)
: QStyledItemDelegate(parent), m_downloadManager(new DownloadManager(this)), m_installProcess(nullptr) {
@@ -31,7 +32,11 @@ AppDelegate::AppDelegate(QObject *parent)
emit updateDisplay(packageName); // 实时刷新进度条
}
});
m_spinnerTimer.start();
// m_spinnerTimer.start(); // 移除这行
// 新增:初始化和连接 QTimer
m_spinnerUpdateTimer.setInterval(20); // 刷新间隔,可以调整
connect(&m_spinnerUpdateTimer, &QTimer::timeout, this, &AppDelegate::updateSpinner);
}
void AppDelegate::setModel(QAbstractItemModel *model) {
@@ -41,11 +46,33 @@ void AppDelegate::setModel(QAbstractItemModel *model) {
void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
painter->save();
// 检查是否为忽略状态
bool isIgnored = index.data(Qt::UserRole + 8).toBool();
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
else
painter->fillRect(option.rect, QColor("#F3F4F6"));
// 绘制复选框
QString packageName = index.data(Qt::UserRole + 1).toString();
bool isSelected = m_selectedPackages.contains(packageName);
QRect checkboxRect(option.rect.left() + 10, option.rect.top() + (option.rect.height() - 20) / 2, 20, 20);
// 绘制复选框边框
QColor checkboxColor = isIgnored ? QColor("#CCCCCC") : QColor("#888888");
painter->setPen(checkboxColor);
painter->setBrush(Qt::NoBrush);
painter->drawRect(checkboxRect);
// 如果选中,绘制勾选标记
if (isSelected && !isIgnored) {
painter->setPen(QPen(QColor("#2563EB"), 2));
painter->setBrush(QColor("#2563EB"));
painter->drawRect(checkboxRect.adjusted(4, 4, -4, -4));
}
QFont boldFont = option.font;
boldFont.setBold(true);
QFont normalFont = option.font;
@@ -60,36 +87,68 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c
QRect rect = option.rect;
int margin = 10, spacing = 6, iconSize = 40;
QRect iconRect(rect.left() + margin, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize);
QIcon(iconPath).paint(painter, iconRect);
// 调整图标位置,为复选框留出空间
QRect iconRect(rect.left() + 40, rect.top() + (rect.height() - iconSize) / 2, iconSize, iconSize);
// 如果是忽略状态,绘制灰色图标
if (isIgnored) {
// 创建灰度效果
QPixmap originalPixmap = QIcon(iconPath).pixmap(iconSize, iconSize);
QPixmap grayPixmap(originalPixmap.size());
grayPixmap.fill(Qt::transparent);
QPainter grayPainter(&grayPixmap);
grayPainter.setOpacity(0.3); // 设置透明度使其变灰
grayPainter.drawPixmap(0, 0, originalPixmap);
grayPainter.end();
painter->drawPixmap(iconRect, grayPixmap);
} else {
QIcon(iconPath).paint(painter, iconRect);
}
int textX = iconRect.right() + margin;
int textWidth = rect.width() - textX - 100;
QRect nameRect(textX, rect.top() + margin, textWidth, 20);
painter->setFont(boldFont);
painter->setPen(QColor("#333333"));
QColor nameColor = isIgnored ? QColor("#999999") : QColor("#333333");
painter->setPen(nameColor);
painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, name);
QRect versionRect(textX, nameRect.bottom() + spacing, textWidth, 20);
painter->setFont(normalFont);
painter->setPen(QColor("#888888"));
QColor versionColor = isIgnored ? QColor("#AAAAAA") : QColor("#888888");
painter->setPen(versionColor);
painter->drawText(versionRect, Qt::AlignLeft | Qt::AlignVCenter,
QString("当前版本: %1 → 新版本: %2").arg(currentVersion, newVersion));
QRect descRect(textX, versionRect.bottom() + spacing, textWidth, 40);
painter->setFont(normalFont);
painter->setPen(QColor("#AAAAAA"));
QColor descColor = isIgnored ? QColor("#CCCCCC") : QColor("#AAAAAA");
painter->setPen(descColor);
painter->drawText(descRect, Qt::TextWordWrap,
QString("包大小:%1 MB").arg(QString::number(size.toDouble() / (1024 * 1024), 'f', 2)));
QString packageName = index.data(Qt::UserRole + 1).toString();
bool isDownloading = m_downloads.contains(packageName) && m_downloads[packageName].isDownloading;
int progress = m_downloads.value(packageName, DownloadInfo{0, false}).progress;
bool isInstalled = m_downloads.value(packageName).isInstalled;
bool isInstalling = m_downloads.value(packageName).isInstalling;
if (isDownloading) {
// 如果是忽略状态,显示"已忽略"文本和"取消忽略"按钮
if (isIgnored) {
QRect ignoredTextRect(rect.right() - 170, rect.top() + (rect.height() - 30) / 2, 80, 30);
painter->setPen(QColor("#999999"));
painter->setFont(option.font);
painter->drawText(ignoredTextRect, Qt::AlignCenter, "已忽略");
// 绘制取消忽略按钮
QRect unignoreButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor("#F3F4F6"));
painter->drawRoundedRect(unignoreButtonRect, 4, 4);
painter->setPen(QColor("#6B7280"));
painter->drawText(unignoreButtonRect, Qt::AlignCenter, "取消忽略");
} else if (isDownloading) {
QRect progressRect(rect.right() - 270, rect.top() + (rect.height() - 20) / 2, 150, 20);
QStyleOptionProgressBar progressBarOption;
progressBarOption.rect = progressRect;
@@ -100,49 +159,55 @@ void AppDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, c
progressBarOption.textVisible = true;
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
// 修改后的取消按钮绘制代码
QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30);
QRect cancelButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor("#ff4444")); // 红色背景
painter->drawRoundedRect(buttonRect, 4, 4); // 圆角矩形
painter->setBrush(QColor("#ff4444"));
painter->drawRoundedRect(cancelButtonRect, 4, 4);
painter->setPen(Qt::white); // 白色文字
painter->setPen(Qt::white);
painter->setFont(option.font);
painter->drawText(buttonRect, Qt::AlignCenter, "取消");
painter->drawText(cancelButtonRect, Qt::AlignCenter, "取消");
} else if (isInstalling) {
// 安装中:显示转圈和文字
QRect spinnerRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 30, 30);
int angle = (m_spinnerTimer.elapsed() / 10) % 360;
// 修改:使用 m_spinnerAngle
QPen pen(QColor("#2563EB"), 3);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing, true);
QRectF arcRect = spinnerRect.adjusted(3, 3, -3, -3);
painter->drawArc(arcRect, angle * 16, 120 * 16); // 120度弧
painter->drawArc(arcRect, m_spinnerAngle * 16, 120 * 16); // 120度弧
QRect textRect(option.rect.right() - 120, option.rect.top() + (option.rect.height() - 30) / 2, 110, 30);
painter->setPen(QColor("#2563EB"));
painter->setFont(option.font);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, "正在安装中");
} else {
QRect buttonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30);
// 绘制忽略按钮
QRect ignoreButtonRect(option.rect.right() - 160, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30);
painter->setPen(Qt::NoPen);
painter->setBrush(QColor("#F3F4F6"));
painter->drawRoundedRect(ignoreButtonRect, 4, 4);
painter->setPen(QColor("#6B7280"));
painter->drawText(ignoreButtonRect, Qt::AlignCenter, "忽略");
// 绘制更新按钮
QRect updateButtonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30);
painter->setPen(Qt::NoPen);
if (isInstalled) {
painter->setBrush(QColor("#10B981"));
painter->drawRoundedRect(buttonRect, 4, 4);
painter->drawRoundedRect(updateButtonRect, 4, 4);
painter->setPen(Qt::white);
painter->drawText(buttonRect, Qt::AlignCenter, "已安装");
painter->drawText(updateButtonRect, Qt::AlignCenter, "已安装");
} else if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) {
// 下载完成,按钮绿色,样式不变
painter->setBrush(QColor("#10B981"));
painter->drawRoundedRect(buttonRect, 4, 4);
painter->drawRoundedRect(updateButtonRect, 4, 4);
painter->setPen(Qt::white);
painter->drawText(buttonRect, Qt::AlignCenter, "下载完成");
// 不需要特殊处理样式,交互在 editorEvent 控制
painter->drawText(updateButtonRect, Qt::AlignCenter, "下载完成");
} else {
painter->setBrush(QColor("#e9effd"));
painter->drawRoundedRect(buttonRect, 4, 4);
painter->drawRoundedRect(updateButtonRect, 4, 4);
painter->setPen(QColor("#2563EB"));
painter->drawText(buttonRect, Qt::AlignCenter, "更新");
painter->drawText(updateButtonRect, Qt::AlignCenter, "更新");
}
}
@@ -159,6 +224,30 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
QRect rect = option.rect;
QString packageName = index.data(Qt::UserRole + 1).toString();
// 检查是否为忽略状态,如果是则只允许取消忽略按钮的交互
bool isIgnored = index.data(Qt::UserRole + 8).toBool();
if (isIgnored) {
QRect unignoreButtonRect(option.rect.right() - 80, option.rect.top() + (option.rect.height() - 30) / 2, 70, 30);
if (unignoreButtonRect.contains(mouseEvent->pos())) {
// 发送取消忽略信号
emit unignoreApp(packageName);
return true;
}
return true; // 消耗其他事件,不允许其他交互
}
// 检查是否点击了复选框
QRect checkboxRect(rect.left() + 10, rect.top() + (rect.height() - 20) / 2, 20, 20);
if (checkboxRect.contains(mouseEvent->pos())) {
if (m_selectedPackages.contains(packageName)) {
m_selectedPackages.remove(packageName);
} else {
m_selectedPackages.insert(packageName);
}
emit updateDisplay(packageName);
return true;
}
if (m_downloads.contains(packageName) && m_downloads[packageName].isDownloading) {
QRect cancelButtonRect(rect.right() - 70, rect.top() + (rect.height() - 20) / 2, 60, 20);
@@ -169,19 +258,47 @@ bool AppDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
return true;
}
} else {
QRect buttonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30);
if (buttonRect.contains(mouseEvent->pos())) {
// 判断是否为“下载完成”状态,如果是则不响应点击
// 检查是否点击了忽略按钮
QRect ignoreButtonRect(rect.right() - 160, rect.top() + (rect.height() - 30) / 2, 70, 30);
if (ignoreButtonRect.contains(mouseEvent->pos())) {
QString currentVersion = index.data(Qt::UserRole + 2).toString();
emit ignoreApp(packageName, currentVersion);
return true;
}
// 检查是否点击了更新按钮
QRect updateButtonRect(rect.right() - 80, rect.top() + (rect.height() - 30) / 2, 70, 30);
if (updateButtonRect.contains(mouseEvent->pos())) {
if (m_downloads.contains(packageName) && !m_downloads[packageName].isDownloading) {
// “下载完成”状态,按钮失效,点击无效
return false;
}
QString downloadUrl = index.data(Qt::UserRole + 7).toString();
QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName);
// 检查/tmp目录下是否已经存在deb包
QDir tempDir(QDir::tempPath());
QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName), QDir::Files);
QString debPath;
if (!debs.isEmpty()) {
debPath = tempDir.absoluteFilePath(debs.first());
} else {
debs = tempDir.entryList(QStringList() << QString("%1*.deb").arg(packageName), QDir::Files);
if (!debs.isEmpty()) {
debPath = tempDir.absoluteFilePath(debs.first());
}
}
// 如果存在deb包直接进行安装
if (!debPath.isEmpty() && QFile::exists(debPath)) {
qDebug() << "发现已存在的deb包直接进行安装:" << debPath;
enqueueInstall(packageName);
} else {
// 否则触发下载流程
QString downloadUrl = index.data(Qt::UserRole + 7).toString();
QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName);
m_downloads[packageName] = {0, true};
m_downloadManager->startDownload(packageName, downloadUrl, outputPath);
emit updateDisplay(packageName);
m_downloads[packageName] = {0, true};
m_downloadManager->startDownload(packageName, downloadUrl, outputPath);
emit updateDisplay(packageName);
}
return true;
}
}
@@ -196,7 +313,7 @@ void AppDelegate::startDownloadForAll() {
QModelIndex index = m_model->index(row, 0);
QString packageName = index.data(Qt::UserRole + 1).toString();
if (m_downloads.contains(packageName) && (m_downloads[packageName].isDownloading || m_downloads[packageName].isInstalled))
continue; // 跳过正在下载或已安装的
continue;
QString downloadUrl = index.data(Qt::UserRole + 7).toString();
QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName);
m_downloads[packageName] = {0, true, false};
@@ -205,7 +322,6 @@ void AppDelegate::startDownloadForAll() {
}
}
// 新增:安装队列相关实现
void AppDelegate::enqueueInstall(const QString &packageName) {
m_installQueue.enqueue(packageName);
if (!m_isInstalling) {
@@ -217,15 +333,16 @@ void AppDelegate::startNextInstall() {
if (m_installQueue.isEmpty()) {
m_isInstalling = false;
m_installingPackage.clear();
m_spinnerUpdateTimer.stop(); // 新增:停止定时器
return;
}
m_isInstalling = true;
QString packageName = m_installQueue.dequeue();
m_installingPackage = packageName;
m_downloads[packageName].isInstalling = true;
m_spinnerUpdateTimer.start(); // 新增:启动定时器
emit updateDisplay(packageName);
// 查找 /tmp 下以包名开头的 .deb 文件
QDir tempDir(QDir::tempPath());
QStringList debs = tempDir.entryList(QStringList() << QString("%1_*.deb").arg(packageName), QDir::Files);
QString debPath;
@@ -249,11 +366,9 @@ void AppDelegate::startNextInstall() {
m_installProcess = new QProcess(this);
// 新增:准备安装日志文件
QString logPath = QString("/tmp/%1_install.log").arg(packageName);
QFile *logFile = new QFile(logPath, m_installProcess);
if (logFile->open(QIODevice::Append | QIODevice::Text)) {
// 设置权限为777
QFile::setPermissions(logPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup |
QFile::ReadOther | QFile::WriteOther | QFile::ExeOther);
@@ -263,7 +378,6 @@ void AppDelegate::startNextInstall() {
logFile->flush();
QString text = QString::fromLocal8Bit(out);
qDebug().noquote() << text;
// 检查“软件包已安装”关键字
if (text.contains(QStringLiteral("软件包已安装"))) {
m_downloads[packageName].isInstalling = false;
m_downloads[packageName].isInstalled = true;
@@ -277,11 +391,20 @@ void AppDelegate::startNextInstall() {
qDebug().noquote() << QString::fromLocal8Bit(err);
});
connect(m_installProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, packageName, logFile](int exitCode, QProcess::ExitStatus status) {
this, [this, packageName, logFile, debPath](int exitCode, QProcess::ExitStatus status) {
if (logFile) logFile->close();
m_downloads[packageName].isInstalling = false;
if (exitCode == 0) {
m_downloads[packageName].isInstalled = true; // 安装成功
m_downloads[packageName].isInstalled = true;
// 安装成功后删除deb包
if (QFile::exists(debPath)) {
if (QFile::remove(debPath)) {
qDebug() << "已删除deb包:" << debPath;
} else {
qWarning() << "删除deb包失败:" << debPath;
}
}
}
emit updateDisplay(packageName);
m_installProcess->deleteLater();
@@ -290,14 +413,23 @@ void AppDelegate::startNextInstall() {
startNextInstall();
});
} else {
// 日志文件无法打开时,仍然要连接原有信号
connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName]() {
connect(m_installProcess, &QProcess::readyReadStandardOutput, this, [this, packageName, debPath]() {
QByteArray out = m_installProcess->readAllStandardOutput();
QString text = QString::fromLocal8Bit(out);
qDebug().noquote() << text;
if (text.contains(QStringLiteral("软件包已安装"))) {
m_downloads[packageName].isInstalling = false;
m_downloads[packageName].isInstalled = true;
// 安装成功后删除deb包
if (QFile::exists(debPath)) {
if (QFile::remove(debPath)) {
qDebug() << "已删除deb包:" << debPath;
} else {
qWarning() << "删除deb包失败:" << debPath;
}
}
emit updateDisplay(packageName);
}
});
@@ -306,7 +438,16 @@ void AppDelegate::startNextInstall() {
qDebug().noquote() << QString::fromLocal8Bit(err);
});
connect(m_installProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this, packageName](int /*exitCode*/, QProcess::ExitStatus /*status*/) {
this, [this, packageName, debPath](int exitCode, QProcess::ExitStatus /*status*/) {
// 如果通过退出码判断安装成功也删除deb包
if (exitCode == 0 && QFile::exists(debPath)) {
if (QFile::remove(debPath)) {
qDebug() << "已删除deb包:" << debPath;
} else {
qWarning() << "删除deb包失败:" << debPath;
}
}
emit updateDisplay(packageName);
m_installProcess->deleteLater();
m_installProcess = nullptr;
@@ -315,8 +456,51 @@ void AppDelegate::startNextInstall() {
});
}
// 注意参数顺序deb路径在前--no-create-desktop-entry在后
QStringList args;
args << debPath << "--no-create-desktop-entry" << "--delete-after-install";
m_installProcess->start("ssinstall", args);
}
// 新增槽函数,用于更新旋转角度并触发刷新
void AppDelegate::updateSpinner() {
m_spinnerAngle = (m_spinnerAngle + 10) % 360; // 每次增加10度
emit updateDisplay(m_installingPackage); // 仅刷新当前正在安装的项
}
// 新增:更新选中应用的方法
void AppDelegate::startDownloadForSelected() {
if (!m_model) return;
for (int row = 0; row < m_model->rowCount(); ++row) {
QModelIndex index = m_model->index(row, 0);
QString packageName = index.data(Qt::UserRole + 1).toString();
// 只下载选中的应用
if (m_selectedPackages.contains(packageName)) {
if (m_downloads.contains(packageName) && (m_downloads[packageName].isDownloading || m_downloads[packageName].isInstalled))
continue;
QString downloadUrl = index.data(Qt::UserRole + 7).toString();
QString outputPath = QString("%1/%2.metalink").arg(QDir::tempPath(), packageName);
m_downloads[packageName] = {0, true, false};
m_downloadManager->startDownload(packageName, downloadUrl, outputPath);
emit updateDisplay(packageName);
}
}
}
// 复选框相关方法实现
void AppDelegate::setSelectedPackages(const QSet<QString> &selected) {
m_selectedPackages = selected;
}
QSet<QString> AppDelegate::getSelectedPackages() const {
return m_selectedPackages;
}
void AppDelegate::clearSelection() {
m_selectedPackages.clear();
}
// 实现获取下载状态信息的方法
const QHash<QString, DownloadInfo>& AppDelegate::getDownloads() const {
return m_downloads;
}

View File

@@ -5,13 +5,16 @@
#include <QQueue>
#include <QProcess>
#include <QElapsedTimer>
#include <QTimer>
#include <QSet>
#include "downloadmanager.h"
struct DownloadInfo {
int progress = 0;
bool isDownloading = false;
bool isInstalled = false;
bool isInstalling = false; // 新增:标记是否正在安装
bool isInstalling = false;
};
class AppDelegate : public QStyledItemDelegate {
@@ -26,21 +29,43 @@ public:
bool editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index) override;
void startDownloadForAll();
void startDownloadForSelected();
// 复选框相关方法
void setSelectedPackages(const QSet<QString> &selected);
QSet<QString> getSelectedPackages() const;
void clearSelection();
// 获取下载状态信息
const QHash<QString, DownloadInfo>& getDownloads() const;
signals:
void updateDisplay(const QString &packageName);
void updateFinished(bool success); //传递是否完成更新
void ignoreApp(const QString &packageName, const QString &version); // 新增:忽略应用信号
void unignoreApp(const QString &packageName); // 新增:取消忽略应用信号
private slots:
void updateSpinner(); // 新增槽函数
private:
DownloadManager *m_downloadManager;
QHash<QString, DownloadInfo> m_downloads;
QAbstractItemModel *m_model = nullptr;
// 复选框相关成员变量
QSet<QString> m_selectedPackages;
QQueue<QString> m_installQueue;
bool m_isInstalling = false;
QProcess *m_installProcess = nullptr;
QString m_installingPackage; // 当前正在安装的包名
QElapsedTimer m_spinnerTimer; // 用于转圈动画
QString m_installingPackage;
QElapsedTimer m_spinnerTimer;
QTimer m_spinnerUpdateTimer; // 新增定时器
int m_spinnerAngle = 0; // 新增角度变量
void enqueueInstall(const QString &packageName);
void startNextInstall();
};
};

View File

@@ -32,6 +32,8 @@ QVariant AppListModel::data(const QModelIndex &index, int role) const
return map.value("description");
case Qt::UserRole + 7: // 下载 URL
return map.value("download_url"); // 返回下载 URL
case Qt::UserRole + 8: // 忽略状态
return map.value("ignored");
default:
return QVariant();
}
@@ -52,11 +54,21 @@ void AppListModel::setUpdateData(const QJsonArray &updateInfo)
map["icon"] = obj["icon"].toString();
map["size"] = obj["size"].toString();
map["download_url"] = obj["download_url"].toString(); // 确保设置下载 URL
map["ignored"] = obj["ignored"].toBool(); // 设置忽略状态
m_data.append(map); // 添加到 QList<QVariantMap>
qDebug() << "设置到模型的包名:" << map["package"].toString();
qDebug() << "设置到模型的包名:" << map["package"].toString() << "忽略状态:" << map["ignored"].toBool();
qDebug() << "设置到模型的下载 URL:" << map["download_url"].toString(); // 检查设置的数据
}
endResetModel();
}
bool AppListModel::isAppIgnored(const QModelIndex &index) const
{
if (!index.isValid() || index.row() >= m_data.size())
return false;
const QVariantMap &map = m_data.at(index.row());
return map.value("ignored").toBool();
}

View File

@@ -5,7 +5,7 @@
#include <QJsonArray>
// 添加 QJsonObject 头文件
#include <QJsonObject>
#include <QDebug>
class AppListModel : public QAbstractListModel
{
Q_OBJECT
@@ -18,9 +18,12 @@ public:
// 设置更新数据
void setUpdateData(const QJsonArray &data);
// 获取忽略状态
bool isAppIgnored(const QModelIndex &index) const;
private:
QList<QVariantMap> m_data; // 修改类型为 QList<QVariantMap>
};
#endif // APPLISTMODEL_H
#endif // APPLISTMODEL_H

View File

@@ -18,8 +18,9 @@ QStringList aptssUpdater::getUpdateablePackages()
QString command = R"(env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist="/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/sparkstore.list" -o Dir::Etc::sourceparts="/dev/null" -o APT::Get::List-Cleanup="0" | awk 'NR>1')";
process.start("bash", QStringList() << "-c" << command);
if (!process.waitForFinished()) {
qWarning() << "Process failed to finish.";
if (!process.waitForFinished(30000)) { // 30秒超时
qWarning() << "Process failed to finish within 30 seconds.";
process.kill();
return packageDetails;
}
@@ -68,7 +69,6 @@ QStringList aptssUpdater::getUpdateablePackages()
QStringList aptssUpdater::getPackageSizes()
{
QStringList packageDetails;
QProcess process;
// 获取可更新包名列表
QStringList updateablePackages;
@@ -77,14 +77,17 @@ QStringList aptssUpdater::getPackageSizes()
}
foreach (const QString &packageName, updateablePackages) {
QProcess process; // 在循环内部创建新的QProcess实例
// 构建新命令(包含包名参数)
QString command = QString("/usr/bin/apt download %1 --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf "
"-o Dir::Etc::sourcelist=\"/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/sparkstore.list\" "
"-o Dir::Etc::sourceparts=\"/dev/null\"").arg(packageName);
process.start("bash", QStringList() << "-c" << command);
if (!process.waitForFinished()) {
qWarning() << "获取包信息失败:" << packageName;
if (!process.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包信息失败:" << packageName << "(超时)";
process.kill();
continue;
}
@@ -116,7 +119,6 @@ QStringList aptssUpdater::getPackageSizes()
QStringList aptssUpdater::getDesktopAppNames()
{
QStringList appNames;
QProcess dpkgProcess;
// 获取当前系统语言环境
QString lang = QLocale().name().replace("_", "-");
@@ -125,12 +127,18 @@ QStringList aptssUpdater::getDesktopAppNames()
QStringList packages = packageName;
foreach (const QString &package, packages) {
QProcess dpkgProcess; // 在循环内部创建新的QProcess实例
QString packageName = package.split(":")[0];
QString finalName = packageName; // 默认使用包名
// 获取包文件列表
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
dpkgProcess.waitForFinished();
if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包文件列表失败:" << packageName << "(超时)";
dpkgProcess.kill();
continue;
}
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts);
// 先检查常规应用目录
@@ -225,18 +233,24 @@ bool aptssUpdater::checkDesktopFiles(const QStringList &desktopFiles, QString &a
QStringList aptssUpdater::getPackageIcons()
{
QStringList packageIcons;
QProcess dpkgProcess;
// 遍历所有可更新包
QStringList packages = packageName;
foreach (const QString &package, packages) {
QProcess dpkgProcess; // 在循环内部创建新的QProcess实例
QString packageName = package.split(":")[0];
QString iconPath = ":/resources/default_icon.svg"; // 默认图标
QString iconPath = ":/resources/default_icon.png"; // 默认图标
// 获取包文件列表
dpkgProcess.start("dpkg", QStringList() << "-L" << packageName);
dpkgProcess.waitForFinished();
if (!dpkgProcess.waitForFinished(30000)) { // 30秒超时
qWarning() << "获取包文件列表失败:" << packageName << "(超时)";
dpkgProcess.kill();
packageIcons << QString("%1: %2").arg(packageName, iconPath);
continue;
}
QStringList files = QString(dpkgProcess.readAllStandardOutput()).split('\n', Qt::SkipEmptyParts);
// 查找.desktop文件
@@ -265,6 +279,7 @@ QStringList aptssUpdater::getPackageIcons()
foreach (const QString &path, iconPaths) {
if (QFile::exists(path)) {
iconPath = path;
qDebug() << "找到图标文件:" << path;
break;
}
}
@@ -272,6 +287,7 @@ QStringList aptssUpdater::getPackageIcons()
// 已经是绝对路径
if (QFile::exists(iconName)) {
iconPath = iconName;
qDebug() << "使用绝对路径图标文件:" << iconName;
}
}
break;
@@ -282,10 +298,14 @@ QStringList aptssUpdater::getPackageIcons()
}
// 如果.desktop中没有找到图标尝试直接查找包中的图标文件
if (iconPath == ":/resources/default_icon.svg") {
if (iconPath == ":/resources/default_icon.png") {
qDebug() << "未在.desktop文件中找到图标尝试直接查找包中的图标文件";
QStringList iconFiles = files.filter(QRegularExpression("/(usr/share/pixmaps|usr/share/icons|opt/apps/.*/entries/icons)/.*\\.(png|svg)$"));
if (!iconFiles.isEmpty()) {
iconPath = iconFiles.first();
qDebug() << "从包中找到图标文件:" << iconPath;
} else {
qDebug() << "未在包中找到图标文件,使用默认图标";
}
}
@@ -393,6 +413,4 @@ QJsonArray aptssUpdater::getUpdateInfoAsJson()
}
qDebug()<<jsonArray;
return jsonArray;
}
}

View File

@@ -4,5 +4,6 @@
<file>../resources/default_icon.svg</file>
<file>../resources/spark-update-tool.svg</file>
<file>../resources/128*128/spark-update-tool.png</file>
<file>../resources/default_icon.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,148 @@
#include "ignoreconfig.h"
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <unistd.h> // for geteuid
IgnoreConfig::IgnoreConfig(QObject *parent)
: QObject(parent)
{
// 设置配置文件路径
QString configDir;
// 检查是否以 root 权限运行
if (geteuid() == 0) {
// 首先检查是否有 SUDO_USER_HOME 环境变量(表示是通过 pkexec 提权的普通用户)
QByteArray sudoUserHomeEnv = qgetenv("SUDO_USER_HOME");
if (!sudoUserHomeEnv.isEmpty()) {
// 通过 pkexec 提权的普通用户,使用原用户的配置目录
QString sudoUserHomePath = QString::fromLocal8Bit(sudoUserHomeEnv);
configDir = sudoUserHomePath + "/.config";
} else {
// 获取实际的 HOME 目录来判断是真正的 root 用户还是其他方式提权的用户
QByteArray homeEnv = qgetenv("HOME");
QString homePath = QString::fromLocal8Bit(homeEnv);
if (homePath == "/root") {
// 真正的 root 用户,使用 /root/.config
configDir = "/root/.config";
} else {
// 其他方式提权的用户,使用 HOME 目录下的配置
configDir = homePath + "/.config";
}
}
} else {
// 普通用户,使用标准配置目录
configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
}
QDir dir(configDir);
if (!dir.exists()) {
dir.mkpath(".");
}
m_configFilePath = dir.filePath("spark-update-tool/ignored_apps.conf");
// 确保目录存在
QFileInfo fileInfo(m_configFilePath);
QDir configDirPath = fileInfo.dir();
if (!configDirPath.exists()) {
configDirPath.mkpath(".");
}
// 加载现有配置
loadConfig();
// 输出忽略列表到 qDebug
printIgnoredApps();
}
void IgnoreConfig::addIgnoredApp(const QString &packageName, const QString &version)
{
m_ignoredApps.insert(qMakePair(packageName, version));
saveConfig();
}
void IgnoreConfig::removeIgnoredApp(const QString &packageName)
{
// 移除所有该包名的版本
auto it = m_ignoredApps.begin();
while (it != m_ignoredApps.end()) {
if (it->first == packageName) {
it = m_ignoredApps.erase(it);
} else {
++it;
}
}
saveConfig();
}
bool IgnoreConfig::isAppIgnored(const QString &packageName, const QString &version) const
{
return m_ignoredApps.contains(qMakePair(packageName, version));
}
QSet<QPair<QString, QString>> IgnoreConfig::getIgnoredApps() const
{
return m_ignoredApps;
}
void IgnoreConfig::printIgnoredApps() const
{
qDebug() << "=== 忽略的应用列表 ===";
qDebug() << "配置文件路径:" << m_configFilePath;
if (m_ignoredApps.isEmpty()) {
qDebug() << "没有忽略的应用";
} else {
for (const auto &app : m_ignoredApps) {
qDebug() << "忽略的应用:" << app.first << "版本:" << app.second;
}
}
qDebug() << "====================";
}
bool IgnoreConfig::saveConfig()
{
QFile file(m_configFilePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << "无法打开配置文件进行写入:" << m_configFilePath;
return false;
}
QTextStream out(&file);
for (const auto &app : m_ignoredApps) {
out << app.first << "|" << app.second << "\n";
}
file.close();
return true;
}
bool IgnoreConfig::loadConfig()
{
QFile file(m_configFilePath);
if (!file.exists()) {
// 配置文件不存在这是正常的返回true表示没有错误
return true;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "无法打开配置文件进行读取:" << m_configFilePath;
return false;
}
m_ignoredApps.clear();
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (!line.isEmpty()) {
QStringList parts = line.split('|');
if (parts.size() == 2) {
m_ignoredApps.insert(qMakePair(parts[0], parts[1]));
}
}
}
file.close();
return true;
}

View File

@@ -0,0 +1,42 @@
#ifndef IGNORECONFIG_H
#define IGNORECONFIG_H
#include <QObject>
#include <QSet>
#include <QString>
#include <QPair>
class IgnoreConfig : public QObject
{
Q_OBJECT
public:
explicit IgnoreConfig(QObject *parent = nullptr);
// 添加忽略的应用(包名和版本号)
void addIgnoredApp(const QString &packageName, const QString &version);
// 移除忽略的应用
void removeIgnoredApp(const QString &packageName);
// 检查应用是否被忽略
bool isAppIgnored(const QString &packageName, const QString &version) const;
// 获取所有被忽略的应用
QSet<QPair<QString, QString>> getIgnoredApps() const;
// 输出所有被忽略的应用到 qDebug
void printIgnoredApps() const;
// 保存配置到文件
bool saveConfig();
// 从文件加载配置
bool loadConfig();
private:
QSet<QPair<QString, QString>> m_ignoredApps;
QString m_configFilePath;
};
#endif // IGNORECONFIG_H

View File

@@ -16,11 +16,13 @@ bool elevateToRoot() {
QByteArray display = qgetenv("DISPLAY");
QByteArray xauthority = qgetenv("XAUTHORITY");
QByteArray home = qgetenv("HOME"); // 获取原始用户的 HOME 目境变量
QStringList args;
args << "env"
<< "DISPLAY=" + display
<< "XAUTHORITY=" + xauthority
<< "SUDO_USER_HOME=" + home // 传递原始用户的 HOME 路径
<< program;
QProcess process;

View File

@@ -7,11 +7,13 @@
#include <QFutureWatcher> // 新增
#include <QIcon>
#include <qicon.h>
#include <unistd.h> // for geteuid
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_model(new AppListModel(this))
, m_delegate(new AppDelegate(this))
, m_ignoreConfig(new IgnoreConfig(this))
{
QIcon icon(":/resources/128*128/spark-update-tool.png");
setWindowIcon(icon);
@@ -53,11 +55,26 @@ MainWindow::MainWindow(QWidget *parent)
}
});
// 连接应用委托的信号
connect(m_delegate, &AppDelegate::ignoreApp, this, &MainWindow::onIgnoreApp);
connect(m_delegate, &AppDelegate::unignoreApp, this, &MainWindow::onUnignoreApp);
// 新增:点击“更新全部”按钮批量下载
connect(ui->updatePushButton, &QPushButton::clicked, this, [=](){
qDebug()<<"更新全部按钮被点击";
m_delegate->startDownloadForAll();
qDebug()<<"更新按钮被点击";
if (m_delegate->getSelectedPackages().isEmpty()) {
// 没有选中任何应用,更新全部
m_delegate->startDownloadForAll();
} else {
// 有选中应用,更新选中
m_delegate->startDownloadForSelected();
m_delegate->clearSelection();
updateButtonText();
}
});
// 新增:监听选择变化
connect(m_delegate, &AppDelegate::updateDisplay, this, &MainWindow::handleSelectionChanged);
checkUpdates();
// 新增:监听搜索框文本变化
@@ -213,12 +230,42 @@ void MainWindow::checkUpdates()
{
aptssUpdater updater;
QJsonArray updateInfo = updater.getUpdateInfoAsJson();
m_allApps = updateInfo; // 保存所有应用数据
m_model->setUpdateData(updateInfo);
// 分离正常应用和忽略应用
QJsonArray normalApps;
QJsonArray ignoredApps;
for (const auto &item : updateInfo) {
QJsonObject obj = item.toObject();
qDebug() << "模型设置的包名:" << obj["package"].toString();
QString packageName = obj["package"].toString();
QString currentVersion = obj["current_version"].toString();
// 检查应用是否被忽略
if (m_ignoreConfig->isAppIgnored(packageName, currentVersion)) {
// 标记为忽略状态
obj["ignored"] = true;
ignoredApps.append(obj);
} else {
obj["ignored"] = false;
normalApps.append(obj);
}
}
// 合并数组:正常应用在前,忽略应用在后
QJsonArray finalApps;
for (const auto &item : normalApps) {
finalApps.append(item);
}
for (const auto &item : ignoredApps) {
finalApps.append(item);
}
m_allApps = finalApps; // 保存所有应用数据
m_model->setUpdateData(finalApps);
for (const auto &item : finalApps) {
QJsonObject obj = item.toObject();
qDebug() << "模型设置的包名:" << obj["package"].toString() << "忽略状态:" << obj["ignored"].toBool();
qDebug() << "模型设置的下载 URL:" << obj["download_url"].toString(); // 检查模型数据
}
}
@@ -230,7 +277,11 @@ void MainWindow::filterAppsByKeyword(const QString &keyword)
m_model->setUpdateData(m_allApps);
return;
}
QJsonArray filtered;
// 分离正常应用和忽略应用
QJsonArray normalApps;
QJsonArray ignoredApps;
for (const auto &item : m_allApps) {
QJsonObject obj = item.toObject();
// 可根据需要匹配更多字段
@@ -238,31 +289,157 @@ void MainWindow::filterAppsByKeyword(const QString &keyword)
QString package = obj.value("package").toString();
if (name.contains(keyword, Qt::CaseInsensitive) ||
package.contains(keyword, Qt::CaseInsensitive)) {
filtered.append(item);
// 检查是否为忽略状态
if (obj.value("ignored").toBool()) {
ignoredApps.append(item);
} else {
normalApps.append(item);
}
}
}
// 合并数组:正常应用在前,忽略应用在后
QJsonArray filtered;
for (const auto &item : normalApps) {
filtered.append(item);
}
for (const auto &item : ignoredApps) {
filtered.append(item);
}
m_model->setUpdateData(filtered);
}
void MainWindow::runAptssUpgrade()
{
QProcess process;
QStringList args;
args << "sudo" <<"aptss" << "ssupdate";
process.start("sudo", args);
// 检查是否已经是root用户如果是则直接执行命令否则使用sudo
if (geteuid() == 0) {
// root用户直接执行
process.start("aptss", QStringList() << "ssupdate");
} else {
// 非root用户使用sudo
process.start("sudo", QStringList() << "aptss" << "ssupdate");
}
if (!process.waitForStarted(5000)) {
QMessageBox::warning(this, "升级失败", "无法启动 sudo aptss ssupdate");
QMessageBox::warning(this, "升级失败", "无法启动 aptss ssupdate");
return;
}
process.write("n\n");
process.closeWriteChannel();
process.waitForFinished(-1);
if (process.exitCode() != 0) {
QMessageBox::warning(this, "升级失败", "执行 sudo aptss ssupdate 失败,请检查系统环境。");
// 设置超时时间,避免无限等待
if (!process.waitForFinished(30000)) { // 30秒超时
qDebug() << "aptss ssupdate 执行超时";
process.kill(); // 强制终止进程
return;
}
if (process.exitCode() != 0) {
QMessageBox::warning(this, "升级失败", "执行 aptss ssupdate 失败,请检查系统环境或稍后再试。");
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
// 检查是否正在进行更新
bool isUpdating = false;
// 通过AppDelegate检查是否有正在下载或安装的应用
const QHash<QString, DownloadInfo>& downloads = m_delegate->getDownloads();
for (auto it = downloads.constBegin(); it != downloads.constEnd(); ++it) {
if (it.value().isDownloading || it.value().isInstalling) {
isUpdating = true;
break;
}
}
// 如果正在更新,才显示确认对话框
if (isUpdating) {
QMessageBox::StandardButton reply = QMessageBox::question(this, "确认关闭", "正在更新,是否确认关闭窗口?", QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
event->accept();
} else {
event->ignore();
}
} else {
// 如果没有更新,直接关闭窗口
event->accept();
}
}
void MainWindow::handleUpdateFinished(bool success)
{
if (success) {
// 更新成功时的处理逻辑
QMessageBox::information(this, "更新完成", "软件更新已成功完成!");
} else {
// 更新失败时的处理逻辑
QMessageBox::warning(this, "更新失败", "软件更新过程中出现错误,请稍后再试。");
}
// 刷新应用列表
checkUpdates();
}
MainWindow::~MainWindow()
{
delete ui;
}
// 新增:更新按钮文本
void MainWindow::updateButtonText() {
int selectedCount = m_delegate->getSelectedPackages().size();
if (selectedCount > 0) {
ui->updatePushButton->setText(QString("更新选中(%1)").arg(selectedCount));
} else {
ui->updatePushButton->setText("更新全部");
}
}
// 新增:处理选择变化
void MainWindow::handleSelectionChanged() {
updateButtonText();
}
// 新增:处理忽略应用的槽函数
void MainWindow::onIgnoreApp(const QString &packageName, const QString &version) {
// 将应用添加到忽略配置中
m_ignoreConfig->addIgnoredApp(packageName, version);
// 更新模型中应用的状态,而不是移除
QJsonArray updatedApps;
for (const auto &item : m_allApps) {
QJsonObject obj = item.toObject();
if (obj["package"].toString() == packageName) {
obj["ignored"] = true; // 标记为忽略状态
}
updatedApps.append(obj);
}
m_allApps = updatedApps;
// 重新排序:正常应用在前,忽略应用在后
checkUpdates();
}
// 新增:处理取消忽略应用的槽函数
void MainWindow::onUnignoreApp(const QString &packageName) {
// 从忽略配置中移除应用
m_ignoreConfig->removeIgnoredApp(packageName);
// 更新模型中应用的状态
QJsonArray updatedApps;
for (const auto &item : m_allApps) {
QJsonObject obj = item.toObject();
if (obj["package"].toString() == packageName) {
obj["ignored"] = false; // 标记为非忽略状态
}
updatedApps.append(obj);
}
m_allApps = updatedApps;
// 重新排序:正常应用在前,忽略应用在后
checkUpdates();
}

View File

@@ -5,6 +5,7 @@
#include "aptssupdater.h"
#include "applistmodel.h"
#include "appdelegate.h"
#include "ignoreconfig.h"
#include <QListView>
#include <QJsonArray> // 添加头文件
#include <QScreen>
@@ -22,6 +23,9 @@ public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void closeEvent(QCloseEvent *event) override;
private:
Ui::MainWindow *ui;
void checkUpdates();
@@ -29,8 +33,16 @@ private:
void runAptssUpgrade();
AppListModel *m_model;
AppDelegate *m_delegate;
IgnoreConfig *m_ignoreConfig; // 新增:忽略配置管理
QListView *listView; // 声明 QListView 指针
QJsonArray m_allApps; // 新增:保存所有应用数据
void filterAppsByKeyword(const QString &keyword); // 新增:搜索过滤函数声明
void updateButtonText(); // 新增:更新按钮文本
private slots:
void handleUpdateFinished(bool success); // 新增:处理更新完成的槽函数
void handleSelectionChanged(); // 新增:处理选择变化的槽函数
void onIgnoreApp(const QString &packageName, const QString &version); // 新增:处理忽略应用的槽函数
void onUnignoreApp(const QString &packageName); // 新增:处理取消忽略应用
};
#endif // MAINWINDOW_H
#endif // MAINWINDOW_H

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1440</width>
<height>858</height>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
@@ -253,4 +253,4 @@
</widget>
<resources/>
<connections/>
</ui>
</ui>