Files
Homer 3aca43b372 Give the app icons, tooltips and a finished theme
There was no icon system at all. Transport buttons called
QStyle.standardIcon(SP_MediaPlay), which returns the platform theme's dark
monochrome glyph -- painted onto the app's saturated red buttons at a fixed
36px with no text, that is the black square. Everything else called an icon
was an emoji baked into en.json, which is tofu wherever the emoji font is
missing. Qt stylesheets cannot recolour a QIcon, so the colour is an argument
to the new helper; that is the whole fix.

The SVGs are drawn here rather than vendored, which keeps a third-party
licence out of the tree, and they live in the module rather than as asset
files, which keeps them out of package-data and safe in a frozen build.

Tooltips were the other half: three widgets in the entire application had one
and nothing set an accessible name. Filling that in at the call sites would
have meant editing well over a hundred of them, most in upstream-owned files.
Instead one application-level event filter handles QEvent.Polish, which Qt
sends to every widget once before it is shown -- so it also reaches dialogs
built by upstream code, and survives the next merge. It maps placeholder
emoji to icons, fills empty tooltips from the button text, and logs icon-only
buttons that still have none so the gaps are findable.

StyleSheet.MAIN styles the window, inputs, buttons and tables and nothing
else, so the main tab bar, combos, sliders, lists, menus, splitters, tooltips
and the horizontal scrollbar fell through to the platform style. EXTRA_QSS
covers them, in a fork-owned module appended at the one application site.
SmoothTabWidget names its frame "tabContent" with the comment "We draw border
on content instead" -- that rule existed only inside two dialogs, and now
exists for the main window too.

Two corrections to rules that were already there: the pressed style changed
the padding, shifting every label two pixels and clipping fixed-width icon
buttons, and checkboxes were fully rounded, which reads as a radio button
rather than an on/off toggle.

Verified by screenshot on the real display: tab bar, buttons, combo carets
and checkboxes all render as intended, and all 35 icons rasterise non-empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 00:58:38 +02:00

78 lines
2.8 KiB
Python

import sys
from PySide6.QtCore import QCoreApplication, Qt
from PySide6.QtGui import QSurfaceFormat
from PySide6.QtWidgets import QApplication, QMessageBox
from .utils.ytsage_logger import logger
from .gui.ytsage_gui_main import YTSageApp # Import the main application class from ytsage_gui_main
def _configure_opengl() -> None:
"""
Must run before QApplication exists -- both settings are ignored afterwards.
AA_ShareOpenGLContexts puts every widget context in one sharing group, so
a reparent no longer loses GL *resources*. It does not stop Qt destroying
and recreating the widget's own context (MpvRenderWidget handles that);
it removes a whole second class of failure around it.
The surface format is deliberately minimal. mpv renders into our FBO and
needs no depth, stencil or alpha from the default framebuffer, and asking
for them is what produced `OpenGL error INVALID_ENUM` on some drivers. No
GL version or profile is requested on purpose: pinning a core profile
breaks software and GLES stacks that libmpv would otherwise accept.
"""
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_ShareOpenGLContexts, True)
fmt = QSurfaceFormat()
fmt.setSwapBehavior(QSurfaceFormat.SwapBehavior.DoubleBuffer)
fmt.setSwapInterval(1)
fmt.setDepthBufferSize(0)
fmt.setStencilBufferSize(0)
fmt.setAlphaBufferSize(0)
QSurfaceFormat.setDefaultFormat(fmt)
def show_error_dialog(message):
# A QMessageBox needs a live QApplication; if startup failed before (or
# while) creating one, constructing the dialog would abort the process
# and swallow the real error.
if QApplication.instance() is None:
print(f"Application Error: {message}", file=sys.stderr)
return
error_dialog = QMessageBox()
error_dialog.setIcon(QMessageBox.Icon.Critical)
error_dialog.setText("Application Error")
error_dialog.setInformativeText(message)
error_dialog.setWindowTitle("Error")
error_dialog.exec()
def main():
try:
logger.info("Starting SageTube application")
_configure_opengl()
app = QApplication(sys.argv)
app.setApplicationName("SageTube")
app.setDesktopFileName("sagetube")
# Before the main window: this retro-fits icons, tooltips and
# accessible names onto every button as it is first polished.
from .gui.ytsage_ui_polish import install as install_ui_polish
install_ui_polish(app)
window = YTSageApp() # Instantiate the main application class
window.show()
logger.info("Application window shown, entering main loop")
sys.exit(app.exec())
except Exception as e:
logger.critical(f"Critical application error: {e}", exc_info=True)
show_error_dialog(f"Critical error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()