iso-scalpel/iso_scalpel/app.py

138 lines
5.5 KiB
Python

"""QApplication bootstrap and global styling."""
# This file is part of ISO Scalpel.
# Copyright (C) 2025 Jeremy Anderson <info@dcos.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>
# or write to the Free Software Foundation, Inc., 51 Franklin Street,
# Fifth Floor, Boston, MA 02110-1301 USA.
from __future__ import annotations
import os
import sys
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication
from . import __app_name__
from .main_window import MainWindow
# Clean, modern stylesheet: neutral greys with an emerald accent (no
# indigo/blue), tuned for ISO Scalpel's split-nav interface.
STYLE = """
QMainWindow, QWidget { background: #f7f7f8; }
QMenuBar { background: #e9eaee; border-bottom: 1px solid #d4d6dc; }
QMenuBar::item { padding: 4px 10px; background: transparent; }
QMenuBar::item:selected { background: #dfe2e8; }
QMenu { background: #ffffff; border: 1px solid #c8ccd2; }
QMenu::item { padding: 5px 22px 5px 22px; }
QMenu::item:selected { background: #d4f1e0; color: #065f46; }
QToolBar { background: #e9eaee; border: 0; border-bottom: 1px solid #d4d6dc; spacing: 2px; padding: 2px; }
QToolBar QToolButton { padding: 4px 6px; border-radius: 4px; }
QToolBar QToolButton:hover { background: #dfe2e8; }
QTreeView { background: #ffffff; alternate-background-color: #f3f4f7; border: 1px solid #d4d6dc;
selection-background-color: #a7e8c5; selection-color: #064e3b; }
QTreeView::item { padding: 2px 0; }
QHeaderView::section { background: #eef0f3; border: 0; border-right: 1px solid #d4d6dc;
border-bottom: 1px solid #d4d6dc; padding: 4px 6px; font-weight: 600; }
QStatusBar { background: #e9eaee; border-top: 1px solid #d4d6dc; }
QStatusBar::item { border: 0; }
QSplitter::handle { background: #d4d6dc; }
QSplitter::handle:horizontal { width: 3px; }
QGroupBox { border: 1px solid #d4d6dc; border-radius: 4px; margin-top: 10px; padding-top: 8px; }
QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 4px; }
QPushButton { padding: 5px 14px; border: 1px solid #b8bdc6; border-radius: 4px; background: #ffffff; }
QPushButton:hover { background: #eef5f0; }
QPushButton:pressed { background: #d4ead9; }
QPushButton:default { border-color: #34a96b; }
QLineEdit, QComboBox, QSpinBox { padding: 3px 6px; border: 1px solid #b8bdc6; border-radius: 4px; background: #fff; }
QComboBox::drop-down { border: 0; width: 18px; }
QComboBox QAbstractItemView { border: 1px solid #b8bdc6; selection-background-color: #a7e8c5; }
QDialogButtonBox QPushButton { min-width: 78px; }
QProgressBar { border: 1px solid #b8bdc6; border-radius: 4px; text-align: center; background: #fff; }
QProgressBar::chunk { background: #34a96b; }
QTabWidget::pane { border: 1px solid #d4d6dc; border-top: 0; }
QTabBar::tab { padding: 4px 10px; background: #eef0f3; border: 1px solid #d4d6dc; border-bottom: 0;
border-top-left-radius: 4px; border-top-right-radius: 4px; margin-right: 2px; }
QTabBar::tab:selected { background: #ffffff; border-bottom: 2px solid #34a96b; }
QTabBar::tab:hover:!selected { background: #e4e8ee; }
QFrame#Breadcrumb { background: #ffffff; border: 1px solid #d4d6dc; border-radius: 4px; }
"""
def run(argv: list[str] | None = None, *, debug_layout: bool = False) -> int:
if argv is None:
argv = sys.argv
app = QApplication.instance() or QApplication(argv)
app.setApplicationName(__app_name__)
app.setOrganizationName("ISO Scalpel")
app.setApplicationDisplayName(__app_name__)
app.setStyle("Fusion")
app.setStyleSheet(STYLE)
# default font
f = QFont()
f.setPointSize(10)
app.setFont(f)
win = MainWindow()
win.show()
# Open a file passed on the command line, if any.
for arg in argv[1:]:
if arg.startswith("-"):
continue
if os.path.isfile(arg):
win._open_path(arg)
break
if debug_layout:
_dump_widget_tree(win)
return app.exec()
def _dump_widget_tree(widget, indent: int = 0) -> None:
"""Print the widget tree with visibility + size info.
Used by ``--debug-layout`` to diagnose rendering issues where a
widget exists but doesn't appear on screen.
"""
prefix = " " * indent
name = widget.__class__.__name__
obj_name = widget.objectName() or "-"
w = widget.width()
h = widget.height()
vis = widget.isVisible()
min_hint = widget.minimumSizeHint()
print(f"{prefix}{name}({obj_name}) size={w}x{h} visible={vis} "
f"minHint={min_hint.width()}x{min_hint.height()}")
for child in widget.children():
if child.isWidgetType():
_dump_widget_tree(child, indent + 1)
if __name__ == "__main__": # pragma: no cover
raise SystemExit(run())
def run_argv() -> int:
"""Console-script entry point used by ``pipx install iso-scalpel``.
Equivalent to :func:`run`, but reads :data:`sys.argv` directly so it can
be wired up as a ``[project.scripts]`` target in ``pyproject.toml``.
"""
return run(list(sys.argv))