2026-02-23 14:12:45 +00:00
|
|
|
import os
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
from PySide6.QtCore import Qt
|
|
|
|
|
from PySide6.QtGui import QColor, QFont, QFontInfo, QIcon, QPalette
|
|
|
|
|
from PySide6.QtWidgets import (
|
|
|
|
|
QApplication,
|
|
|
|
|
QGridLayout,
|
|
|
|
|
QHBoxLayout,
|
|
|
|
|
QLabel,
|
|
|
|
|
QMainWindow,
|
|
|
|
|
QPushButton,
|
|
|
|
|
QWidget,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from .autoslicer import Autoslicer, AutoslicerParams
|
|
|
|
|
from .controller import ScanSlicerControllerMixin
|
|
|
|
|
from .ui_builders import ScanSlicerUiBuilderMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# _unwrap_qvariant: function definition for ScanSlicer behavior.
|
2026-02-23 14:29:20 +00:00
|
|
|
def _unwrap_qvariant(value):
|
|
|
|
|
unwrapped = value
|
|
|
|
|
for _ in range(4):
|
|
|
|
|
if hasattr(unwrapped, "variant"):
|
|
|
|
|
try:
|
|
|
|
|
candidate = unwrapped.variant()
|
|
|
|
|
except Exception:
|
|
|
|
|
candidate = unwrapped
|
|
|
|
|
if candidate is not unwrapped:
|
|
|
|
|
unwrapped = candidate
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if hasattr(unwrapped, "value"):
|
|
|
|
|
try:
|
|
|
|
|
candidate = unwrapped.value()
|
|
|
|
|
except TypeError:
|
|
|
|
|
candidate = unwrapped.value
|
|
|
|
|
except Exception:
|
|
|
|
|
candidate = unwrapped
|
|
|
|
|
if candidate is not unwrapped:
|
|
|
|
|
unwrapped = candidate
|
|
|
|
|
continue
|
|
|
|
|
break
|
|
|
|
|
return unwrapped
|
2026-02-23 14:34:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# _read_portal_dark_preference: function definition for ScanSlicer behavior.
|
|
|
|
|
def _read_portal_dark_preference():
|
|
|
|
|
try:
|
|
|
|
|
from PySide6.QtDBus import QDBusConnection, QDBusInterface, QDBusMessage
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
bus = QDBusConnection.sessionBus()
|
|
|
|
|
if not bus.isConnected():
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
iface = QDBusInterface(
|
|
|
|
|
"org.freedesktop.portal.Desktop",
|
|
|
|
|
"/org/freedesktop/portal/desktop",
|
|
|
|
|
"org.freedesktop.portal.Settings",
|
|
|
|
|
bus,
|
|
|
|
|
)
|
|
|
|
|
if not iface.isValid():
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
for method in ("ReadOne", "Read"):
|
|
|
|
|
reply = iface.call(method, "org.freedesktop.appearance", "color-scheme")
|
|
|
|
|
if reply.type() == QDBusMessage.MessageType.ErrorMessage:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
args = reply.arguments()
|
|
|
|
|
if not args:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
raw_value = _unwrap_qvariant(args[0])
|
|
|
|
|
if isinstance(raw_value, dict) and "color-scheme" in raw_value:
|
|
|
|
|
raw_value = _unwrap_qvariant(raw_value["color-scheme"])
|
|
|
|
|
if isinstance(raw_value, (list, tuple)) and raw_value:
|
|
|
|
|
raw_value = _unwrap_qvariant(raw_value[0])
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
color_scheme = int(raw_value)
|
|
|
|
|
except Exception:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# XDG portal color-scheme values:
|
|
|
|
|
# 0 = no preference, 1 = prefer dark, 2 = prefer light
|
|
|
|
|
if color_scheme == 1:
|
|
|
|
|
return True
|
|
|
|
|
if color_scheme == 2:
|
|
|
|
|
return False
|
|
|
|
|
return None
|
2026-02-23 14:39:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# apply_dark_theme_if_requested: function definition for ScanSlicer behavior.
|
|
|
|
|
def apply_dark_theme_if_requested(app):
|
|
|
|
|
theme_value = os.environ.get("GTK_THEME", "").lower()
|
|
|
|
|
dark_theme_requested = "dark" in theme_value
|
|
|
|
|
if not dark_theme_requested:
|
|
|
|
|
portal_pref = _read_portal_dark_preference()
|
|
|
|
|
if portal_pref is not None:
|
|
|
|
|
dark_theme_requested = portal_pref
|
|
|
|
|
|
|
|
|
|
if not dark_theme_requested:
|
|
|
|
|
try:
|
|
|
|
|
dark_theme_requested = app.styleHints().colorScheme() == Qt.ColorScheme.Dark
|
|
|
|
|
except Exception:
|
|
|
|
|
dark_theme_requested = False
|
|
|
|
|
|
|
|
|
|
if not dark_theme_requested:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Some container/desktop combinations do not propagate GTK dark theme
|
|
|
|
|
# correctly to Qt widgets; enforce a dark palette when dark mode is requested.
|
|
|
|
|
app.setStyle("Fusion")
|
|
|
|
|
palette = QPalette()
|
|
|
|
|
palette.setColor(QPalette.Window, QColor(53, 53, 53))
|
|
|
|
|
palette.setColor(QPalette.WindowText, QColor(255, 255, 255))
|
|
|
|
|
palette.setColor(QPalette.Base, QColor(35, 35, 35))
|
|
|
|
|
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
|
|
|
|
|
palette.setColor(QPalette.ToolTipBase, QColor(35, 35, 35))
|
|
|
|
|
palette.setColor(QPalette.ToolTipText, QColor(255, 255, 255))
|
|
|
|
|
palette.setColor(QPalette.Text, QColor(255, 255, 255))
|
|
|
|
|
palette.setColor(QPalette.Button, QColor(53, 53, 53))
|
|
|
|
|
palette.setColor(QPalette.ButtonText, QColor(255, 255, 255))
|
|
|
|
|
palette.setColor(QPalette.BrightText, QColor(255, 0, 0))
|
|
|
|
|
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
|
|
|
|
|
palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255))
|
|
|
|
|
app.setPalette(palette)
|
2026-02-23 14:44:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# apply_global_text_size_offset: function definition for ScanSlicer behavior.
|
|
|
|
|
def apply_global_text_size_offset(app, pixel_offset=2):
|
|
|
|
|
font = app.font()
|
|
|
|
|
info = QFontInfo(font)
|
|
|
|
|
pixel_size = info.pixelSize()
|
|
|
|
|
|
|
|
|
|
if pixel_size > 0:
|
|
|
|
|
font.setPixelSize(pixel_size + pixel_offset)
|
|
|
|
|
else:
|
|
|
|
|
point_size = info.pointSizeF()
|
|
|
|
|
if point_size <= 0:
|
|
|
|
|
point_size = 10.0
|
|
|
|
|
font.setPointSizeF(point_size + (pixel_offset * 0.75))
|
|
|
|
|
|
|
|
|
|
app.setFont(font)
|
2026-02-23 14:49:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# resolve_app_icon: function definition for ScanSlicer behavior.
|
|
|
|
|
def resolve_app_icon():
|
|
|
|
|
icon = QIcon.fromTheme("scanslicer")
|
|
|
|
|
if not icon.isNull():
|
|
|
|
|
return icon
|
|
|
|
|
|
|
|
|
|
appdir = os.environ.get("APPDIR", "").strip()
|
|
|
|
|
if not appdir:
|
|
|
|
|
return QIcon()
|
|
|
|
|
|
|
|
|
|
candidates = (
|
|
|
|
|
os.path.join(appdir, "usr", "share", "icons", "scanslicer.png"),
|
|
|
|
|
os.path.join(appdir, "usr", "share", "icons", "scanslicer.svg"),
|
|
|
|
|
os.path.join(appdir, "usr", "share", "pixmaps", "scanslicer.png"),
|
|
|
|
|
os.path.join(appdir, "usr", "share", "pixmaps", "scanslicer.svg"),
|
|
|
|
|
os.path.join(appdir, ".DirIcon"),
|
|
|
|
|
)
|
|
|
|
|
for candidate_path in candidates:
|
|
|
|
|
if not os.path.exists(candidate_path):
|
|
|
|
|
continue
|
|
|
|
|
candidate_icon = QIcon(candidate_path)
|
|
|
|
|
if not candidate_icon.isNull():
|
|
|
|
|
return candidate_icon
|
|
|
|
|
|
|
|
|
|
return QIcon()
|