Check updates in the background
This commit is contained in:
parent
9349374ab9
commit
367e974b77
7 changed files with 311 additions and 63 deletions
15
START.bat
15
START.bat
|
|
@ -178,21 +178,6 @@ if errorlevel 1 (
|
||||||
)
|
)
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
:: RPG Maker Ace tools (RV2JSON + decrypter) — downloaded from GitHub, not stored in git
|
|
||||||
echo Checking RPG Maker Ace tools...
|
|
||||||
python -m util.ace.update_tools
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo WARNING: Ace tool download failed. Ace translation features may not work until fixed.
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
echo Checking Forge plugins...
|
|
||||||
python -m util.forge.update_tools
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy.
|
|
||||||
)
|
|
||||||
echo.
|
|
||||||
|
|
||||||
:: Launch the GUI
|
:: Launch the GUI
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo Launching DazedMTLTool GUI...
|
echo Launching DazedMTLTool GUI...
|
||||||
|
|
|
||||||
12
START.sh
12
START.sh
|
|
@ -220,18 +220,6 @@ else
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "Checking RPG Maker Ace tools..."
|
|
||||||
if ! python -m util.ace.update_tools; then
|
|
||||||
echo "WARNING: Ace tool download failed. Ace translation features may not work until fixed."
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo "Checking Forge plugins..."
|
|
||||||
if ! python -m util.forge.update_tools; then
|
|
||||||
echo "WARNING: Forge update failed. Playtest Forge features use the bundled upstream copy."
|
|
||||||
fi
|
|
||||||
echo
|
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " Launching DazedMTLTool GUI..."
|
echo " Launching DazedMTLTool GUI..."
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|
|
||||||
243
gui/main.py
243
gui/main.py
|
|
@ -40,6 +40,86 @@ def load_application_icon() -> QIcon:
|
||||||
return QIcon()
|
return QIcon()
|
||||||
|
|
||||||
|
|
||||||
|
def check_tool_update() -> str | None:
|
||||||
|
"""Return latest commit SHA when a tool update is available, else None."""
|
||||||
|
try:
|
||||||
|
api = (
|
||||||
|
f"https://gitgud.io/api/v4/projects/"
|
||||||
|
f"{UpdateThread.REPO_USER}%2F{UpdateThread.REPO_NAME}"
|
||||||
|
f"/repository/branches/{UpdateThread.REPO_BRANCH}"
|
||||||
|
)
|
||||||
|
req = urllib.request.Request(api, headers={"User-Agent": "DazedMTLTool"})
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
|
latest_sha = json.loads(resp.read())["commit"]["id"]
|
||||||
|
sha_path = Path(UpdateThread.SHA_FILE)
|
||||||
|
current_sha = sha_path.read_text().strip() if sha_path.is_file() else ""
|
||||||
|
if latest_sha != current_sha:
|
||||||
|
return latest_sha
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_all_updates() -> dict:
|
||||||
|
"""Check tool, Ace, and Forge for available updates."""
|
||||||
|
results = {"tool": None, "ace": False, "forge": False}
|
||||||
|
results["tool"] = check_tool_update()
|
||||||
|
try:
|
||||||
|
from util.ace.update_tools import check_ace_tools_update
|
||||||
|
results["ace"] = check_ace_tools_update()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
from util.forge.update_tools import check_forge_plugins_update
|
||||||
|
results["forge"] = check_forge_plugins_update()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def any_updates_available(results: dict) -> bool:
|
||||||
|
return bool(results.get("tool")) or results.get("ace") or results.get("forge")
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundUpdateCheckThread(QThread):
|
||||||
|
"""Checks for tool, Ace, and Forge updates without blocking the UI."""
|
||||||
|
|
||||||
|
finished = pyqtSignal(dict)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.finished.emit(check_all_updates())
|
||||||
|
|
||||||
|
|
||||||
|
class AuxToolsUpdateThread(QThread):
|
||||||
|
"""Download Ace and/or Forge updates without updating the main tool."""
|
||||||
|
|
||||||
|
progress = pyqtSignal(str)
|
||||||
|
finished = pyqtSignal(bool, str)
|
||||||
|
|
||||||
|
def __init__(self, update_ace: bool = False, update_forge: bool = False, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.update_ace = update_ace
|
||||||
|
self.update_forge = update_forge
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
if self.update_ace:
|
||||||
|
from util.ace.update_tools import ensure_ace_tools
|
||||||
|
self.progress.emit("Updating RPG Maker Ace tools…")
|
||||||
|
if not ensure_ace_tools(log_fn=lambda m: self.progress.emit(m)):
|
||||||
|
self.finished.emit(False, "Ace tool update failed.")
|
||||||
|
return
|
||||||
|
if self.update_forge:
|
||||||
|
from util.forge.update_tools import ensure_forge_plugins
|
||||||
|
self.progress.emit("Updating Forge plugins…")
|
||||||
|
if not ensure_forge_plugins(log_fn=lambda m: self.progress.emit(m)):
|
||||||
|
self.finished.emit(False, "Forge plugin update failed.")
|
||||||
|
return
|
||||||
|
self.finished.emit(True, "components_updated")
|
||||||
|
except Exception as exc:
|
||||||
|
self.finished.emit(False, str(exc))
|
||||||
|
|
||||||
|
|
||||||
class UpdateThread(QThread):
|
class UpdateThread(QThread):
|
||||||
"""Downloads and applies a tool update from the GitGud repository."""
|
"""Downloads and applies a tool update from the GitGud repository."""
|
||||||
|
|
||||||
|
|
@ -79,18 +159,22 @@ class UpdateThread(QThread):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.finished.emit(False, str(exc))
|
self.finished.emit(False, str(exc))
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
@classmethod
|
||||||
|
def fetch_latest_sha(cls) -> str:
|
||||||
def _fetch_latest_sha(self):
|
|
||||||
api = (
|
api = (
|
||||||
f"https://gitgud.io/api/v4/projects/"
|
f"https://gitgud.io/api/v4/projects/"
|
||||||
f"{self.REPO_USER}%2F{self.REPO_NAME}"
|
f"{cls.REPO_USER}%2F{cls.REPO_NAME}"
|
||||||
f"/repository/branches/{self.REPO_BRANCH}"
|
f"/repository/branches/{cls.REPO_BRANCH}"
|
||||||
)
|
)
|
||||||
req = urllib.request.Request(api, headers={"User-Agent": "DazedMTLTool"})
|
req = urllib.request.Request(api, headers={"User-Agent": "DazedMTLTool"})
|
||||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
return json.loads(resp.read())["commit"]["id"]
|
return json.loads(resp.read())["commit"]["id"]
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
def _fetch_latest_sha(self):
|
||||||
|
return self.fetch_latest_sha()
|
||||||
|
|
||||||
def _read_stored_sha(self):
|
def _read_stored_sha(self):
|
||||||
p = Path(self.SHA_FILE)
|
p = Path(self.SHA_FILE)
|
||||||
return p.read_text().strip() if p.exists() else ""
|
return p.read_text().strip() if p.exists() else ""
|
||||||
|
|
@ -150,7 +234,7 @@ class UpdateThread(QThread):
|
||||||
class UpdateDialog(QDialog):
|
class UpdateDialog(QDialog):
|
||||||
"""Modal dialog that shows update progress and result."""
|
"""Modal dialog that shows update progress and result."""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, pending_updates: dict | None = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("Tool Update")
|
self.setWindowTitle("Tool Update")
|
||||||
self.setMinimumWidth(420)
|
self.setMinimumWidth(420)
|
||||||
|
|
@ -172,13 +256,55 @@ class UpdateDialog(QDialog):
|
||||||
layout.addWidget(self.close_btn)
|
layout.addWidget(self.close_btn)
|
||||||
|
|
||||||
self._thread = None
|
self._thread = None
|
||||||
|
self._pending = pending_updates or {}
|
||||||
|
|
||||||
def start(self, check_only=False):
|
def start(self, check_only=False):
|
||||||
self._thread = UpdateThread(check_only=check_only, parent=self)
|
if self._pending:
|
||||||
self._thread.progress.connect(self.status_label.setText)
|
self._show_pending_updates()
|
||||||
self._thread.finished.connect(self._on_finished)
|
return
|
||||||
|
|
||||||
|
self._thread = BackgroundUpdateCheckThread(parent=self)
|
||||||
|
self._thread.finished.connect(self._on_check_finished)
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
|
def _update_labels(self) -> list[str]:
|
||||||
|
labels = []
|
||||||
|
if self._pending.get("tool"):
|
||||||
|
labels.append("DazedMTLTool")
|
||||||
|
if self._pending.get("ace"):
|
||||||
|
labels.append("RPG Maker Ace tools")
|
||||||
|
if self._pending.get("forge"):
|
||||||
|
labels.append("Forge plugins")
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def _show_pending_updates(self):
|
||||||
|
self.progress_bar.setRange(0, 1)
|
||||||
|
self.progress_bar.setValue(1)
|
||||||
|
self.close_btn.setText("Close")
|
||||||
|
|
||||||
|
labels = self._update_labels()
|
||||||
|
if not labels:
|
||||||
|
self.status_label.setText("✅ Already up to date.")
|
||||||
|
return
|
||||||
|
|
||||||
|
detail = "\n".join(f" • {label}" for label in labels)
|
||||||
|
tool_sha = self._pending.get("tool")
|
||||||
|
if tool_sha:
|
||||||
|
detail = f" • DazedMTLTool ({tool_sha[:8]})\n" + "\n".join(
|
||||||
|
f" • {label}" for label in labels if label != "DazedMTLTool"
|
||||||
|
)
|
||||||
|
self.status_label.setText(
|
||||||
|
f"🆕 Updates available:\n{detail}\n\nClick 'Update Now' to install."
|
||||||
|
)
|
||||||
|
self.close_btn.setText("Cancel")
|
||||||
|
update_btn = QPushButton("Update Now")
|
||||||
|
update_btn.clicked.connect(self._do_update)
|
||||||
|
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
||||||
|
|
||||||
|
def _on_check_finished(self, results: dict):
|
||||||
|
self._pending = results
|
||||||
|
self._show_pending_updates()
|
||||||
|
|
||||||
def _on_finished(self, success, message):
|
def _on_finished(self, success, message):
|
||||||
self.progress_bar.setRange(0, 1)
|
self.progress_bar.setRange(0, 1)
|
||||||
self.progress_bar.setValue(1)
|
self.progress_bar.setValue(1)
|
||||||
|
|
@ -190,6 +316,11 @@ class UpdateDialog(QDialog):
|
||||||
|
|
||||||
if message == "already_up_to_date":
|
if message == "already_up_to_date":
|
||||||
self.status_label.setText("✅ Already up to date.")
|
self.status_label.setText("✅ Already up to date.")
|
||||||
|
elif message == "components_updated":
|
||||||
|
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||||
|
self.status_label.setText("✅ Component updates installed.")
|
||||||
|
if isinstance(self.parent(), DazedMTLGUI):
|
||||||
|
self.parent().clear_update_indicator()
|
||||||
elif message.startswith("update_available:"):
|
elif message.startswith("update_available:"):
|
||||||
sha = message.split(":", 1)[1]
|
sha = message.split(":", 1)[1]
|
||||||
self.status_label.setText(
|
self.status_label.setText(
|
||||||
|
|
@ -201,12 +332,14 @@ class UpdateDialog(QDialog):
|
||||||
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
self.layout().insertWidget(self.layout().count() - 1, update_btn)
|
||||||
elif message.startswith("updated:"):
|
elif message.startswith("updated:"):
|
||||||
sha = message.split(":", 1)[1]
|
sha = message.split(":", 1)[1]
|
||||||
|
self._pending = {"tool": None, "ace": False, "forge": False}
|
||||||
self.status_label.setText(
|
self.status_label.setText(
|
||||||
f"✅ Updated to {sha}.\n\nPlease restart the tool for changes to take effect."
|
f"✅ Updated to {sha}.\n\nPlease restart the tool for changes to take effect."
|
||||||
)
|
)
|
||||||
|
if isinstance(self.parent(), DazedMTLGUI):
|
||||||
|
self.parent().clear_update_indicator()
|
||||||
|
|
||||||
def _do_update(self):
|
def _clear_extra_buttons(self):
|
||||||
# Remove any extra buttons so only the close button remains
|
|
||||||
for i in reversed(range(self.layout().count())):
|
for i in reversed(range(self.layout().count())):
|
||||||
item = self.layout().itemAt(i)
|
item = self.layout().itemAt(i)
|
||||||
if item and item.widget() and item.widget() is not self.close_btn:
|
if item and item.widget() and item.widget() is not self.close_btn:
|
||||||
|
|
@ -215,9 +348,37 @@ class UpdateDialog(QDialog):
|
||||||
if w.text() != "Close":
|
if w.text() != "Close":
|
||||||
self.layout().removeWidget(w)
|
self.layout().removeWidget(w)
|
||||||
w.deleteLater()
|
w.deleteLater()
|
||||||
|
|
||||||
|
def _do_update(self):
|
||||||
|
self._clear_extra_buttons()
|
||||||
self.progress_bar.setRange(0, 0)
|
self.progress_bar.setRange(0, 0)
|
||||||
self.status_label.setText("Downloading update…")
|
self.close_btn.setText("Cancel")
|
||||||
self.start(check_only=False)
|
|
||||||
|
if self._pending.get("tool"):
|
||||||
|
self.status_label.setText("Downloading update…")
|
||||||
|
self._thread = UpdateThread(check_only=False, parent=self)
|
||||||
|
self._thread.progress.connect(self.status_label.setText)
|
||||||
|
self._thread.finished.connect(self._on_finished)
|
||||||
|
self._thread.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
update_ace = bool(self._pending.get("ace"))
|
||||||
|
update_forge = bool(self._pending.get("forge"))
|
||||||
|
if update_ace or update_forge:
|
||||||
|
self.status_label.setText("Downloading component updates…")
|
||||||
|
self._thread = AuxToolsUpdateThread(
|
||||||
|
update_ace=update_ace,
|
||||||
|
update_forge=update_forge,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
self._thread.progress.connect(self.status_label.setText)
|
||||||
|
self._thread.finished.connect(self._on_finished)
|
||||||
|
self._thread.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.progress_bar.setRange(0, 1)
|
||||||
|
self.progress_bar.setValue(1)
|
||||||
|
self.status_label.setText("✅ Already up to date.")
|
||||||
|
|
||||||
# Import configuration widgets
|
# Import configuration widgets
|
||||||
from gui.config_tab import ConfigTab
|
from gui.config_tab import ConfigTab
|
||||||
|
|
@ -230,10 +391,15 @@ class DazedMTLGUI(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
||||||
|
self._pending_updates: dict = {}
|
||||||
|
self._update_check_thread = None
|
||||||
|
self._update_icon = "🔄"
|
||||||
|
self.btn_update = None
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self.setup_status_bar()
|
self.setup_status_bar()
|
||||||
self.setup_font_scaling()
|
self.setup_font_scaling()
|
||||||
self.restore_window_state()
|
self.restore_window_state()
|
||||||
|
QTimer.singleShot(500, self.start_background_update_check)
|
||||||
|
|
||||||
def restore_window_state(self):
|
def restore_window_state(self):
|
||||||
"""Restore window geometry and state from settings."""
|
"""Restore window geometry and state from settings."""
|
||||||
|
|
@ -427,10 +593,10 @@ class DazedMTLGUI(QMainWindow):
|
||||||
sidebar_layout.addStretch()
|
sidebar_layout.addStretch()
|
||||||
|
|
||||||
# Update button at the bottom of the sidebar
|
# Update button at the bottom of the sidebar
|
||||||
btn_update = self.create_nav_button("🔄", "Check for Updates")
|
self.btn_update = self.create_nav_button(self._update_icon, "Check for Updates")
|
||||||
btn_update.setCheckable(False)
|
self.btn_update.setCheckable(False)
|
||||||
btn_update.clicked.connect(self.show_update_dialog)
|
self.btn_update.clicked.connect(self.show_update_dialog)
|
||||||
sidebar_layout.addWidget(btn_update)
|
sidebar_layout.addWidget(self.btn_update)
|
||||||
|
|
||||||
sidebar.setLayout(sidebar_layout)
|
sidebar.setLayout(sidebar_layout)
|
||||||
|
|
||||||
|
|
@ -485,11 +651,52 @@ class DazedMTLGUI(QMainWindow):
|
||||||
self.config_tab.config_changed.connect(self.on_config_changed)
|
self.config_tab.config_changed.connect(self.on_config_changed)
|
||||||
self.content_stack.addWidget(self.config_tab)
|
self.content_stack.addWidget(self.config_tab)
|
||||||
|
|
||||||
|
def start_background_update_check(self):
|
||||||
|
"""Check for tool, Ace, and Forge updates after the GUI is visible."""
|
||||||
|
if self._update_check_thread and self._update_check_thread.isRunning():
|
||||||
|
return
|
||||||
|
self._update_check_thread = BackgroundUpdateCheckThread(self)
|
||||||
|
self._update_check_thread.finished.connect(self._on_background_update_check)
|
||||||
|
self._update_check_thread.start()
|
||||||
|
|
||||||
|
def _on_background_update_check(self, results: dict):
|
||||||
|
self._pending_updates = results
|
||||||
|
if any_updates_available(results):
|
||||||
|
self.set_update_indicator()
|
||||||
|
else:
|
||||||
|
self.clear_update_indicator()
|
||||||
|
|
||||||
|
def set_update_indicator(self):
|
||||||
|
"""Highlight the update button when updates are available."""
|
||||||
|
if not self.btn_update:
|
||||||
|
return
|
||||||
|
self.btn_update.setToolTip("Updates available — click to install")
|
||||||
|
from gui.platform_glyph import configure_nav_toolbutton
|
||||||
|
configure_nav_toolbutton(
|
||||||
|
self.btn_update, self._update_icon, horizontal=False, update_available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def clear_update_indicator(self):
|
||||||
|
"""Restore the default update button appearance."""
|
||||||
|
self._pending_updates = {}
|
||||||
|
if not self.btn_update:
|
||||||
|
return
|
||||||
|
self.btn_update.setToolTip("Check for Updates")
|
||||||
|
from gui.platform_glyph import configure_nav_toolbutton
|
||||||
|
configure_nav_toolbutton(
|
||||||
|
self.btn_update, self._update_icon, horizontal=False, update_available=False,
|
||||||
|
)
|
||||||
|
|
||||||
def show_update_dialog(self):
|
def show_update_dialog(self):
|
||||||
"""Open the update dialog and check for a newer version."""
|
"""Open the update dialog and check for a newer version."""
|
||||||
dlg = UpdateDialog(self)
|
dlg = UpdateDialog(self, pending_updates=self._pending_updates.copy())
|
||||||
dlg.start(check_only=True)
|
dlg.start(check_only=True)
|
||||||
dlg.exec_()
|
dlg.exec_()
|
||||||
|
self._pending_updates = dlg._pending
|
||||||
|
if any_updates_available(dlg._pending):
|
||||||
|
self.set_update_indicator()
|
||||||
|
else:
|
||||||
|
self.clear_update_indicator()
|
||||||
|
|
||||||
def on_config_changed(self):
|
def on_config_changed(self):
|
||||||
"""Handle configuration changes."""
|
"""Handle configuration changes."""
|
||||||
|
|
|
||||||
|
|
@ -84,12 +84,14 @@ def _linux_nav_pixmap(glyph: str, *, size: int, font_px: int, color: QColor) ->
|
||||||
return pixmap
|
return pixmap
|
||||||
|
|
||||||
|
|
||||||
def _linux_nav_icon(icon_text: str, *, size: int, font_px: int) -> QIcon:
|
def _linux_nav_icon(icon_text: str, *, size: int, font_px: int, normal_color: QColor | None = None) -> QIcon:
|
||||||
"""Render a nav glyph into a fixed-size pixmap so every tab aligns the same."""
|
"""Render a nav glyph into a fixed-size pixmap so every tab aligns the same."""
|
||||||
glyph = platform_nav_glyph(icon_text)
|
glyph = platform_nav_glyph(icon_text)
|
||||||
|
gray_color = normal_color or QColor("#cccccc")
|
||||||
|
white_color = QColor("#ffffff") if normal_color is None else normal_color.lighter(125)
|
||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
gray = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=QColor("#cccccc"))
|
gray = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=gray_color)
|
||||||
white = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=QColor("#ffffff"))
|
white = _linux_nav_pixmap(glyph, size=size, font_px=font_px, color=white_color)
|
||||||
icon.addPixmap(gray, QIcon.Normal, QIcon.Off)
|
icon.addPixmap(gray, QIcon.Normal, QIcon.Off)
|
||||||
icon.addPixmap(gray, QIcon.Disabled, QIcon.Off)
|
icon.addPixmap(gray, QIcon.Disabled, QIcon.Off)
|
||||||
icon.addPixmap(white, QIcon.Active, QIcon.Off)
|
icon.addPixmap(white, QIcon.Active, QIcon.Off)
|
||||||
|
|
@ -97,15 +99,18 @@ def _linux_nav_icon(icon_text: str, *, size: int, font_px: int) -> QIcon:
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
|
|
||||||
def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool, update_available: bool = False) -> str:
|
||||||
border_prop = "border-bottom" if horizontal else "border-left"
|
border_prop = "border-bottom" if horizontal else "border-left"
|
||||||
|
accent = "#ff5252" if update_available else "#007acc"
|
||||||
|
text_color = "#ff5252" if update_available else "#cccccc"
|
||||||
|
checked_color = "#ff8a80" if update_available else "#ffffff"
|
||||||
if icon_only:
|
if icon_only:
|
||||||
return f"""
|
return f"""
|
||||||
QToolButton {{
|
QToolButton {{
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
{border_prop}: 3px solid transparent;
|
{border_prop}: 3px solid transparent;
|
||||||
color: #cccccc;
|
color: {text_color};
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}}
|
}}
|
||||||
|
|
@ -114,8 +119,8 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
||||||
}}
|
}}
|
||||||
QToolButton:checked {{
|
QToolButton:checked {{
|
||||||
background-color: #37373d;
|
background-color: #37373d;
|
||||||
{border_prop}: 3px solid #007acc;
|
{border_prop}: 3px solid {accent};
|
||||||
color: #ffffff;
|
color: {checked_color};
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -125,7 +130,7 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
{border_prop}: 3px solid transparent;
|
{border_prop}: 3px solid transparent;
|
||||||
color: #cccccc;
|
color: {text_color};
|
||||||
font-size: {font_size};
|
font-size: {font_size};
|
||||||
font-family: 'DejaVu Sans Mono', monospace;
|
font-family: 'DejaVu Sans Mono', monospace;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|
@ -136,26 +141,37 @@ def _nav_toolbutton_stylesheet(*, horizontal: bool, icon_only: bool) -> str:
|
||||||
}}
|
}}
|
||||||
QToolButton:checked {{
|
QToolButton:checked {{
|
||||||
background-color: #37373d;
|
background-color: #37373d;
|
||||||
{border_prop}: 3px solid #007acc;
|
{border_prop}: 3px solid {accent};
|
||||||
color: #ffffff;
|
color: {checked_color};
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def configure_nav_toolbutton(btn, icon_text: str, *, horizontal: bool = False) -> None:
|
def configure_nav_toolbutton(
|
||||||
|
btn,
|
||||||
|
icon_text: str,
|
||||||
|
*,
|
||||||
|
horizontal: bool = False,
|
||||||
|
update_available: bool = False,
|
||||||
|
) -> None:
|
||||||
"""Apply nav icon — on Linux render BMP symbols into fixed pixmaps for alignment."""
|
"""Apply nav icon — on Linux render BMP symbols into fixed pixmaps for alignment."""
|
||||||
if sys.platform.startswith("linux"):
|
if sys.platform.startswith("linux"):
|
||||||
# Scale to the button cell (leave room for the 3px active border).
|
# Scale to the button cell (leave room for the 3px active border).
|
||||||
icon_dim = max(28, min(btn.width(), btn.height()) - 8)
|
icon_dim = max(28, min(btn.width(), btn.height()) - 8)
|
||||||
font_px = max(22, int(icon_dim * 0.78))
|
font_px = max(22, int(icon_dim * 0.78))
|
||||||
btn.setIcon(_linux_nav_icon(icon_text, size=icon_dim, font_px=font_px))
|
normal_color = QColor("#ff5252") if update_available else None
|
||||||
|
btn.setIcon(_linux_nav_icon(icon_text, size=icon_dim, font_px=font_px, normal_color=normal_color))
|
||||||
btn.setIconSize(QSize(icon_dim, icon_dim))
|
btn.setIconSize(QSize(icon_dim, icon_dim))
|
||||||
btn.setText("")
|
btn.setText("")
|
||||||
btn.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
btn.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(horizontal=horizontal, icon_only=True))
|
btn.setStyleSheet(_nav_toolbutton_stylesheet(
|
||||||
|
horizontal=horizontal, icon_only=True, update_available=update_available,
|
||||||
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
btn.setIcon(QIcon())
|
btn.setIcon(QIcon())
|
||||||
btn.setText(icon_text)
|
btn.setText(icon_text)
|
||||||
btn.setToolButtonStyle(Qt.ToolButtonTextOnly)
|
btn.setToolButtonStyle(Qt.ToolButtonTextOnly)
|
||||||
btn.setStyleSheet(_nav_toolbutton_stylesheet(horizontal=horizontal, icon_only=False))
|
btn.setStyleSheet(_nav_toolbutton_stylesheet(
|
||||||
|
horizontal=horizontal, icon_only=False, update_available=update_available,
|
||||||
|
))
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,14 @@ def main():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from util.ace.update_tools import ensure_ace_tools
|
from util.ace.update_tools import seed_ace_tools
|
||||||
ensure_ace_tools()
|
seed_ace_tools()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Warning: Ace tool setup failed ({exc}). Ace features may be unavailable.")
|
print(f"Warning: Ace tool setup failed ({exc}). Ace features may be unavailable.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from util.forge.update_tools import ensure_forge_plugins
|
from util.forge.update_tools import seed_forge_plugins
|
||||||
ensure_forge_plugins()
|
seed_forge_plugins()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Warning: Forge plugin setup failed ({exc}). Playtest Forge may be unavailable.")
|
print(f"Warning: Forge plugin setup failed ({exc}). Playtest Forge may be unavailable.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,38 @@ def ensure_decrypter(force: bool = False, log_fn=print) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def seed_ace_tools(log_fn=None) -> None:
|
||||||
|
"""Copy offline-bundled Ace tools if missing (no network)."""
|
||||||
|
_seed_from_offline(RV2JSON_LOCAL, "RV2JSON.exe", log_fn)
|
||||||
|
_seed_from_offline(DECRYPTER_LOCAL, DECRYPTER_ASSET, log_fn)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ace_tools_update() -> bool:
|
||||||
|
"""Return True if upstream Ace tools differ from the local copies."""
|
||||||
|
seed_ace_tools()
|
||||||
|
versions = _load_versions()
|
||||||
|
|
||||||
|
try:
|
||||||
|
remote_sha, _ = _rv2json_upstream()
|
||||||
|
if not RV2JSON_LOCAL.is_file() or versions.get("rv2json_sha", "") != remote_sha:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
if not RV2JSON_LOCAL.is_file():
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
tag, _ = _decrypter_upstream()
|
||||||
|
if not (DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file()):
|
||||||
|
return True
|
||||||
|
if DECRYPTER_LOCAL.is_file() and versions.get("decrypter_tag", "") != tag:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
if not (DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file()):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ensure_ace_tools(force: bool = False, log_fn=print) -> bool:
|
def ensure_ace_tools(force: bool = False, log_fn=print) -> bool:
|
||||||
"""Ensure both Ace tools are present (download / update if needed)."""
|
"""Ensure both Ace tools are present (download / update if needed)."""
|
||||||
ok_rv = ensure_rv2json(force=force, log_fn=log_fn)
|
ok_rv = ensure_rv2json(force=force, log_fn=log_fn)
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,26 @@ def refresh_forge_plugins(log_fn=print) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def seed_forge_plugins(log_fn=None) -> None:
|
||||||
|
"""Copy offline-bundled Forge plugins if missing (no network)."""
|
||||||
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
|
_seed_from_offline(engine, log_fn)
|
||||||
|
|
||||||
|
|
||||||
|
def check_forge_plugins_update() -> bool:
|
||||||
|
"""Return True if upstream Forge plugins differ from the local copies."""
|
||||||
|
seed_forge_plugins()
|
||||||
|
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
|
||||||
|
if missing:
|
||||||
|
return True
|
||||||
|
versions = _load_versions()
|
||||||
|
local_commit = versions.get("commit", "")
|
||||||
|
try:
|
||||||
|
return _upstream_commit() != local_commit
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
||||||
"""Ensure Forge plugins are present; fetch upstream when missing or stale."""
|
"""Ensure Forge plugins are present; fetch upstream when missing or stale."""
|
||||||
for engine in PLUGIN_BY_ENGINE:
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue