Compare commits

...

10 Commits

Author SHA1 Message Date
KeksNino 606c70efcc add unicode icons for pause/resume and fix action column size 2026-01-18 21:39:40 +01:00
KeksNino 9970086901 WIP: add pause/resume button in downloads tab 2026-01-18 02:03:31 +01:00
Vxrtrauter 0d631e0ab8 fix assets not showing up in build 2026-01-17 15:26:08 +01:00
Vxrtrauter 1c1ce3e69d update workflow again 2026-01-17 14:56:30 +01:00
Vxrtrauter b5931f5590 fix actions 2026-01-17 14:51:53 +01:00
Vxrtrauter 84ea030e2a Enhance asset handling: update icon loading to use dynamic path resolution 2026-01-17 14:46:07 +01:00
Vxrtrauter 356f3bf070 fix dependencies 2026-01-17 14:34:47 +01:00
Vxrtrauter 6ff29e2ea6 add logo to gui and assets 2026-01-17 14:30:48 +01:00
Vxrtrauter 847b50ab10 image overlay error message: add check for valid image path 2026-01-17 14:08:47 +01:00
Vxrtrauter 5bc81b5215 Enhance console logging: add option to print logs regardless of debug state 2026-01-17 02:00:16 +01:00
5 changed files with 132 additions and 34 deletions
+11 -1
View File
@@ -67,7 +67,7 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r requirements.txt pip install -r requirements.txt
pip install pyinstaller backports.tarfile pip install pyinstaller backports.tarfile pillow
- name: Generate version - name: Generate version
id: version id: version
@@ -103,8 +103,13 @@ jobs:
run: | run: |
pyinstaller --onefile --windowed \ pyinstaller --onefile --windowed \
--add-data "build_info.json;." \ --add-data "build_info.json;." \
--add-data "build_info.json;core" \
--add-data "build_info.json;core/interface" \
--add-data "deps/aria2c.exe;." \ --add-data "deps/aria2c.exe;." \
--add-data "core/interface/assets;core/interface/assets" \ --add-data "core/interface/assets;core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \ --hidden-import=backports.tarfile \
--hidden-import=backports \ --hidden-import=backports \
--hidden-import=jaraco.context \ --hidden-import=jaraco.context \
@@ -119,7 +124,12 @@ jobs:
run: | run: |
pyinstaller --onefile --windowed \ pyinstaller --onefile --windowed \
--add-data "build_info.json:." \ --add-data "build_info.json:." \
--add-data "build_info.json:core" \
--add-data "build_info.json:core/interface" \
--add-data "core/interface/assets:core/interface/assets" \ --add-data "core/interface/assets:core/interface/assets" \
--icon "core/interface/assets/logo.png" \
--exclude-module PyQt6 \
--exclude-module PyQt5 \
--hidden-import=backports.tarfile \ --hidden-import=backports.tarfile \
--hidden-import=backports \ --hidden-import=backports \
--hidden-import=jaraco.context \ --hidden-import=jaraco.context \
+4
View File
@@ -214,3 +214,7 @@ __marimo__/
*.sublime-project *.sublime-project
*.code-workspace *.code-workspace
*.swp *.swp
# Local build files
build-local.ps1
build-local.bat
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+115 -31
View File
@@ -1,6 +1,5 @@
from PySide6 import QtWidgets from PySide6 import QtWidgets
from PySide6 import QtCore from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal, QEvent
from PySide6.QtCore import Qt, QTimer, QModelIndex, QAbstractTableModel, Signal
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QLineEdit, QLineEdit,
QTableView, QTableView,
@@ -16,7 +15,11 @@ from PySide6.QtWidgets import (
QHeaderView, QHeaderView,
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTextEdit QTextEdit,
QStyle,
QStyleOptionButton,
QStyledItemDelegate,
QApplication,
) )
from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap from PySide6.QtGui import QIcon, QAction, QCloseEvent, QImage, QPixmap
@@ -45,7 +48,7 @@ def download_update(latest_version):
new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" new_filename = f"SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe" url = f"https://github.com/KeksPirates/SoftwareManager/releases/latest/download/SoftwareManager-dev-{latest_version.replace('-dev', '')}-windows.exe"
consoleLog("Downloading update...") consoleLog("Downloading update...", True)
response = r.get(url, allow_redirects=True) response = r.get(url, allow_redirects=True)
with open(new_filename, "wb") as f: with open(new_filename, "wb") as f:
f.write(response.content) f.write(response.content)
@@ -55,11 +58,11 @@ def download_update(latest_version):
time.sleep(0.5) time.sleep(0.5)
msg = QMessageBox() msg = QMessageBox()
msg.setIcon(QMessageBox.Information) msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("New Version") msg.setWindowTitle("New Version")
msg.setText("New version installed.") msg.setText("New version installed.")
msg.setInformativeText("Please remove the old exe.") msg.setInformativeText("Please remove the old exe.")
msg.setStandardButtons(QMessageBox.Ok) msg.setStandardButtons(QMessageBox.StandardButton.Ok)
sys.exit(0) sys.exit(0)
@@ -72,6 +75,29 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
MainWindow._instance = self MainWindow._instance = self
self.log_signal.connect(self._on_log_signal) self.log_signal.connect(self._on_log_signal)
def get_asset_path(filename):
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(__file__)
asset_path = os.path.join(base_path, 'core', 'interface', 'assets', filename)
if os.path.exists(asset_path):
return asset_path
return os.path.join('core', 'interface', 'assets', filename)
self.setWindowIcon(QIcon(get_asset_path("logo.png")))
if platform.system() == "Windows":
try:
import ctypes
myappid = 'SoftwareManager.App.1'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
except Exception as e:
consoleLog(f"Could not set app ID: {e}")
build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json") build_info_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "build_info.json")
if os.path.exists(build_info_path): if os.path.exists(build_info_path):
with open(build_info_path, "r") as f: with open(build_info_path, "r") as f:
@@ -87,14 +113,14 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
if assets: if assets:
msg = QMessageBox() msg = QMessageBox()
msg.setIcon(QMessageBox.Information) msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("Update Available") msg.setWindowTitle("Update Available")
msg.setText("A new version is available.") msg.setText("A new version is available.")
msg.setInformativeText("Press Ok to download the update.") msg.setInformativeText("Press Ok to download the update.")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Ignore) msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Ignore)
response = msg.exec_() response = msg.exec_()
if response == QMessageBox.Ok: if response == QMessageBox.StandardButton.Ok:
download_update(latest_version) download_update(latest_version)
self.setWindowTitle("Software Manager") self.setWindowTitle("Software Manager")
@@ -153,7 +179,7 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
class DownloadModel(QAbstractTableModel): class DownloadModel(QAbstractTableModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.headers = ["Name", "Status", "Progress", "Speed", "Size", "Total Size"] self.headers = ["Action", "Name", "Status", "Progress", "Speed", "Size", "Total Size"]
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent=QModelIndex()):
return len(state.downloads) return len(state.downloads)
@@ -167,32 +193,88 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
return None return None
def data(self, index, role=Qt.DisplayRole): def data(self, index, role=Qt.DisplayRole):
col = index.column()
download = state.downloads[index.row()]
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
download = state.downloads[index.row()]
col = index.column() col = index.column()
if col == 0: if col == 0:
return download.name return "▶︎" if download.is_paused else "⏸︎"
elif col == 1: elif col == 1:
return getattr(download, 'status', 'Downloading') return download.name
elif col == 2: elif col == 2:
return f"{int(download.progress)}%" return getattr(download, 'status', 'Downloading')
elif col == 3: elif col == 3:
return f"{download.download_speed_string()}" return f"{int(download.progress)}%"
elif col == 4: elif col == 4:
pass return f"{download.download_speed_string()}"
elif col == 5: elif col == 5:
pass
elif col == 6:
return f"{download.total_length_string()}" return f"{download.total_length_string()}"
if role == Qt.UserRole and col == 0:
return download.is_paused
return None return None
def toggle_pause_resume(self, row):
download = state.downloads[row]
if download.is_paused:
download.resume()
else:
download.pause()
idx = self.index(row, 0)
self.dataChanged.emit(idx, idx, [Qt.DisplayRole, Qt.UserRole])
class PauseResumeDelegate(QStyledItemDelegate):
clicked = Signal(int)
def paint(self, painter, option, index):
if index.column() != 0:
super().paint(painter, option, index)
return
paused = index.data(Qt.UserRole)
button = QStyleOptionButton()
button.rect = option.rect
button.text = "▶︎" if paused else "⏸︎"
button.state = QStyle.State_Enabled
QApplication.style().drawControl(
QStyle.CE_PushButton, button, painter
)
def editorEvent(self, event, model, option, index):
if index.column() == 0 and event.type() == QEvent.Type.MouseButtonPress:
self.clicked.emit(index.row())
return True
return False
self.download_model = DownloadModel() self.download_model = DownloadModel()
self.downloadList.setModel(self.download_model) self.downloadList.setModel(self.download_model)
self.downloadList.horizontalHeader().setStretchLastSection(False) self.downloadList.horizontalHeader().setStretchLastSection(False)
self.downloadList.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.downloadList.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self.downloadList.setSelectionBehavior(QTableView.SelectRows) self.downloadList.setSelectionBehavior(QTableView.SelectRows)
self.downloadList.setAttribute(Qt.WA_TranslucentBackground) self.downloadList.setAttribute(Qt.WA_TranslucentBackground)
self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground) self.downloadList.viewport().setAttribute(Qt.WA_TranslucentBackground)
download_model = self.download_model
def on_pause_resume_clicked(row):
download_model.toggle_pause_resume(row)
download = state.downloads[row]
if download.is_paused:
download.pause()
else:
download.resume()
delegate = PauseResumeDelegate(self.downloadList)
self.downloadList.setItemDelegateForColumn(0, delegate)
delegate.clicked.connect(on_pause_resume_clicked)
# download button triggers # download button triggers
self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles)))) self.dlbutton.clicked.connect(lambda: run_thread(threading.Thread(target=download_selected, args=(self.qtablewidget.currentItem(), state.posts, state.post_titles))))
@@ -210,18 +292,20 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None) self.tab2 = create_tab("Library", self.emptyLibrary, self.libraryList, self.tabs, None, None)
self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None) self.tab3 = create_tab("Downloads", self.emptyDownload, self.downloadList, self.tabs, None, None)
self.image = QImage(state.image_path) if state.image_path is not None and os.path.exists(state.image_path):
self.pixmap = QPixmap.fromImage(self.image) self.image = QImage(state.image_path)
self.overlay_label = QLabel(self) self.pixmap = QPixmap.fromImage(self.image)
self.overlay_label.setPixmap(self.pixmap) self.overlay_label = QLabel(self)
self.overlay_label.adjustSize() self.overlay_label.setPixmap(self.pixmap)
self.overlay_label.raise_() self.overlay_label.adjustSize()
self.overlay_label.raise_()
offset_x = -1350 offset_x = -1350
offset_y = -550 offset_y = -550
x = self.width() - self.overlay_label.width() - offset_x x = self.width() - self.overlay_label.width() - offset_x
y = self.height() - self.overlay_label.height() - offset_y y = self.height() - self.overlay_label.height() - offset_y
self.overlay_label.move(x, y) self.overlay_label.move(x, y)
# temporarily disabled # temporarily disabled
# state.image_changed.connect(self.update_image_overlay) # state.image_changed.connect(self.update_image_overlay)
@@ -238,9 +322,9 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
self.tracker_list.activated.connect(self.set_tracker) self.tracker_list.activated.connect(self.set_tracker)
if darkdetect.isDark(): if darkdetect.isDark():
settings_action = QAction(QIcon("core/interface/assets/settings_white.png"), "Settings", self) settings_action = QAction(QIcon(get_asset_path("settings_white.png")), "Settings", self)
else: else:
settings_action = QAction(QIcon("core/interface/assets/settings_black.png"), "Settings", self) settings_action = QAction(QIcon(get_asset_path("settings_black.png")), "Settings", self)
settings_action.triggered.connect(lambda: settings_dialog(self)) settings_action.triggered.connect(lambda: settings_dialog(self))
toolbar.addAction(settings_action) toolbar.addAction(settings_action)
@@ -312,4 +396,4 @@ class MainWindow(QtWidgets.QMainWindow, QWidget):
+2 -2
View File
@@ -165,7 +165,7 @@ def set_main_window(window):
global _main_window global _main_window
_main_window = window _main_window = window
def consoleLog(text): def consoleLog(text, printAnyways = False):
now = datetime.now() now = datetime.now()
current_time = now.strftime("%H:%M:%S") current_time = now.strftime("%H:%M:%S")
@@ -175,5 +175,5 @@ def consoleLog(text):
except Exception: except Exception:
pass pass
if state.debug: if state.debug or printAnyways:
print(f"[{current_time}] {text}") print(f"[{current_time}] {text}")