Skip to content

Split home file - #26

Merged
TolisSth merged 17 commits into
open-source-uom:mainfrom
YatesMold:SplitHomeFile
Oct 8, 2026
Merged

TolisSth merged 17 commits into
open-source-uom:mainfrom
YatesMold:SplitHomeFile

Conversation

@YatesMold

Copy link
Copy Markdown
Contributor

Summary

This PR splits src/unidesk/home.py (555 lines) into helpers/, ui/, and styles/, following the structure suggested in the linked issue. home.py no longer exists; its contents now live across 15 focused modules.

Closes #25

Why

home.py mixed data loading, filesystem logic, widget construction, page building, the main window, and ~20 inline QSS strings in one file. This made it hard to navigate and review. The maintainer asked for the split to happen incrementally, with architectural decisions discussed in the PR rather than decided unilaterally. This PR follows that approach: 7 small, independently reviewable commits, each verified before the next began.

What changed

helpers/ (data and system-interaction logic)

  • text_data.py, academic_config.py moved as-is from the package root.
  • New autostart.py: extracted the autostart toggle logic (AUTOSTART_PATH, is_autostart_disabled(), set_autostart()) that was previously inlined in home.py.

ui/ (widget construction)

  • widgets.py: shared helpers (qlabel, divider, back_bar, scroll_page, footer, NavButton), made public (dropped the leading underscore).
  • ui/pages/: one module per page builder (text_page.py, credits_page.py, links_page.py, academic_config_page.py), each importing only what it needs.
  • ui/main_window.py: the UniOSWelcome class, moved as a 1:1 relocation (not further decomposed into smaller methods, to keep the one commit that also deletes home.py as low-risk as possible).

styles/ (QSS extraction)

  • Every static setStyleSheet(...) call across ui/ was moved into its own .qss file under styles/{widgets,main_window,pages}/, loaded via styles/loader.py (load_qss(filename), using importlib.resources and @cache, consistent with the existing text_data.load() pattern).
  • One .qss file per literal, not three files for the whole package. Most of the original strings are selector-less declarations (e.g. "background: #110d1a;"). Concatenating these into a handful of files and handing the whole file to every widget would have caused Qt stylesheet parse failures and cross-widget bleeding (the last background: declaration wins, parent stylesheets cascade to children). This was caught and fixed before any code was written, see Verification for how the resulting split was validated against the original behavior.
  • setup.py package_data updated ("unidesk.styles": ["*/*.qss"]) and confirmed by building a wheel locally and inspecting its contents. All 16 .qss files are packaged correctly.

Bug fixes (small, isolated)

  • Removed a duplicated cfg_btn = QPushButton(...) line (the first instance was constructed and immediately discarded).
  • Removed the unused align_right parameter from NavButton.__init__ (accepted at both call sites, used nowhere in the class body).

Docs

  • README.md's project structure diagram updated to reflect helpers/, ui/ (with pages/), and styles/.

Verification

Every commit was checked before the next began:

  • ruff check src/unidesk/: same 3 pre-existing errors throughout (SIM115, UP031 in the tempfile code, I001 in main.py), zero new ones introduced across the whole series.
  • An offscreen smoke test (QT_QPA_PLATFORM=offscreen, constructing UniOSWelcome and checking page count) after every commit.
  • For the widget/page/main-window moves: byte-level diffs of the relocated code against the pre-move version, confirming the logic is unchanged beyond renaming.
  • For the QSS extraction: a script parsed the original string literals directly out of the AST at HEAD, wrote them to .qss files, and then asserted byte-identity between what's loaded at runtime and the original literal, for all 20 extracted strings. stdout/stderr from the smoke test was also diffed against a HEAD checkout to confirm no new Qt stylesheet parse warnings were introduced.
  • python -m build --wheel run locally; unzip -l on the resulting wheel confirms all .qss files are present under unidesk/styles/.
  • Manual click-through: every page opens correctly, the autostart checkbox still writes/removes ~/.config/autostart/unidesk.desktop, and the academic config page still saves to ~/.unios/academicConfig.json.

@YatesMold
YatesMold requested a review from TolisSth October 1, 2026 15:23
Comment thread src/unidesk/ui/pages/academic_config_page.py
Comment on lines +47 to +54
def refresh_departments():
university = university_combo.currentText()
department_combo.clear()
if university in UNIVERSITIES:
department_combo.addItems(UNIVERSITIES[university])
department_combo.setCurrentIndex(-1)

university_combo.currentIndexChanged.connect(lambda _: refresh_departments())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain to me why is there a need to refresh the departments here after detecting a configuration ?

department = department_combo.currentText()
if not university or not department:
status.setText(UI["academic_error_incomplete"])
status.setStyleSheet(status.styleSheet().replace("#a6adc8", "#f38ba8"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there styling here ?

university=university, department=department
)
)
status.setStyleSheet(status.styleSheet().replace("#f38ba8", "#a6adc8"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again

Comment thread src/unidesk/ui/pages/credits_page.py Outdated
for person in CREDITS:
frame = QFrame()
frame.setFrameShape(QFrame.Shape.StyledPanel)
frame.setStyleSheet(load_qss("pages/card_frame.qss"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should not use so many qss files, it kinda hurts the I/O

Comment thread src/unidesk/ui/pages/credits_page.py Outdated
Comment on lines +22 to +43
name = qlabel(person["name"], size=13, color="#cdd6f4", bold=True)
name.setStyleSheet(
name.styleSheet() + " background: transparent; border: none;"
)
fl.addWidget(name)

role = qlabel(person["role"], size=11, color="#a6adc8")
role.setStyleSheet(
role.styleSheet() + " background: transparent; border: none;"
)
fl.addWidget(role)

if person.get("projects"):
proj = qlabel(
UI["projects_prefix"] + ", ".join(person["projects"]),
size=11,
color="#8b5897",
)
proj.setStyleSheet(
proj.styleSheet() + " background: transparent; border: none;"
)
fl.addWidget(proj)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of qss that should be moved

Comment on lines +3 to +12
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QCheckBox,
QHBoxLayout,
QMainWindow,
QPushButton,
QStackedWidget,
QVBoxLayout,
QWidget,
)

@TolisSth TolisSth Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should trigger ruff, check this with static analysis. Probably unused imports.

Comment thread src/unidesk/ui/pages/links_page.py Outdated
for link in LINKS:
frame = QFrame()
frame.setFrameShape(QFrame.Shape.StyledPanel)
frame.setStyleSheet(load_qss("pages/card_frame.qss"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I/O overhead

Comment thread src/unidesk/ui/pages/links_page.py Outdated

btn = QPushButton(UI["open_link_button"])
btn.setCursor(Qt.CursorShape.PointingHandCursor)
btn.setStyleSheet(load_qss("pages/link_button.qss"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment thread src/unidesk/ui/pages/text_page.py Outdated
data = PAGES[key]
widget, cl = scroll_page(on_back, key)

body = qlabel(data["body"], size=12, color="#a6adc8", wrap=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styling that belongs in qss

Comment thread src/unidesk/ui/main_window.py Outdated
Comment on lines +103 to +130
self._autostart_cb.setStyleSheet(
"""
QCheckBox {
color: #a6adc8;
font-size: 12px;
background: transparent;
spacing: 8px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
border: 1px solid #585b70;
border-radius: 4px;
background: transparent;
}
QCheckBox::indicator:hover {
border-color: #a6adc8;
}
QCheckBox::indicator:checked {
background-color: #8b5897;
border-color: #cba6f7;
image: url(%s);
}
QCheckBox::indicator:checked:hover {
background-color: #9b68a7;
}
"""
% _check_path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this should be removed also and be placed in styling

@TolisSth TolisSth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a solid start but let's continue the refactoring. Start with the comments above and continue with reducing the number of qss files, we should have one or two, not that many files, remember the components are resusable.

@YatesMold

Copy link
Copy Markdown
Contributor Author

Δεν έχω ξαναφάει τέτοιο ξύλο ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣟⣛⣻⢷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢠⣦⣠⣤⣤⣿⠟⠋⠉⠉⠛⢷⣌⢻⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣈⣿⣿⣿⣿⣿⣾⡿⠛⣡⣶⣤⣾⠋⠀⠀⠀⠀⠀⠈⢿⡌⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣶⣿⣿⣿⣿⣿⣿⠟⠋⠁⠀⠸⠿⣿⠏⠀⠀⠀⠀⠀⠀⠀⣸⣇⣿⠹⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⢀⣼⡏⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⡇⠀⢿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⢀⣿⠉⠀⣤⠀⠀⣀⣠⣴⠟⢿⣿⣿⣧⢀⣼⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⠀⠀⢠⣶⠿⣧⣄⠀⢿⢄⡀⢹⣏⠁⠀⠀⠹⠿⢿⡾⣫⣿⠿⣶⣄⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣀⣀⣀⣤⢤⣤⣤⣄⣀⣤⣼⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠘⠿⣾⠟⣻⣷⠶⣸⣿⣮⣿⠀⠀⠀⠀⢰⡟⣱⡿⠁⠀⠀⠉⠙⠿⣷⣾⣄⢄⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡴⢿⣯⣿⣟⡾⣻⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡧⣤⣄⡀⠀⠀⠀⠀⠛⠛⢻⣯⣴⠟⠁⠀⠀⠀⠀⠀⢠⡿⢱⡿⠁⠀⠀⠀⠀⣶⣶⣦⡝⣿⣷⡄⠀⠀⠀⠀
⠀⠀⠀⠰⣿⣿⣿⡿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡅⠹⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⢃⣿⠁⠀⠀⠀⠀⠀⠀⠛⠛⣿⣿⣿⣿⡀⠀⠀⠀
⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠙⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡏⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣷⡆⠀⠀
⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡇⣿⠁⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⡇⠀⠀
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣿⡀⠀⠀⠴⣿⣿⡂⠀⣾⣿⣿⣿⣿⣿⡿⠀⠀⠀
⠀⠀⠀⢽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣶⣶⢮⣿⣦⡀⠀⠀⠉⠁⣸⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀
⠀⠀⠀⠘⣿⣿⣿⣿⣿⠟⠛⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⣠⣤⣀⠀⢀⣠⣴⡿⠟⠛⠛⠛⠷⣮⡝⢿⣄⠀⢀⣸⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀
⠀⠀⠀⠀⠈⠉⠉⠉⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣤⣤⣤⣤⣤⣿⣭⡍⠛⠛⠋⠀⠀⠀⠀⠀⠀⠀⠈⢿⡌⣿⣿⣿⣿⣿⡿⠿⠿⠋⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠛⠛⠛⠛⠛⣿⣿⣿⠿⣟⠁⢀⣴⡆⠀⠀⠀⠀⠀⠀⠀⠀⣸⣷⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠛⠻⠿⠿⠿⠿⠛⠿⠿⣿⣷⣟⣛⣛⣿⣿⣷⣶⡿⣷⣾⠃⠀⣠⣄⠀⠀⠀⠀⣀⣴⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⢿⣿⣾⣿⣃⣴⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢛⠛⠋⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

@YatesMold

Copy link
Copy Markdown
Contributor Author

What changed

CI

  • fix: sort imports in main.py fixes the I001 ruff error.
  • fix: replace tempfile SVG hack with committed asset removes the temp file that was written on every launch. The checkmark is now styles/check.svg. This also clears the SIM115 and UP031 errors, so ruff check src/unidesk/ passes with 0 errors.

Fewer QSS files and reusable components

The 16 .qss files are now 3, and no stylesheet string is left in the Python code. Widgets no longer carry their own style. They declare a role with a property, and the .qss file decides how that role looks:

Property Used on File
role labels labels.qss
layout window, hero, bars, dividers, cards, scroll area layout.qss
button, control buttons, checkbox, combo boxes controls.qss

Because components share rules, all card frames use one QFrame[layout="card"] rule, and all labels of the same kind use one rule. load_qss() is cached, so each file is read from disk once per run.

Styling that was still in Python

  • qlabel(text, size, color, bold) is now qlabel(text, role). The 12 call sites map to 7 roles: title, body, field-label, card-title, muted, accent, footer.
  • The status color swap no longer rewrites the stylesheet with .replace(). It sets a state property and repolishes the label.
  • The checkbox and combo box styles moved into controls.qss. load_qss() resolves the checkmark path, so the page code has no .replace().

One visible change

The nav buttons were never styled as intended. nav_button.qss had doubled braces ({{ }}) left over from an f-string, so Qt rejected the whole sheet and the buttons rendered with default styling. The nav fix un-doubles the braces and moves the rules into controls.qss, so they now get the look from the original code. Before and after screenshots are attached.

This is the only visible change in the PR. It is its own commit, so it can be reverted alone if you prefer the old look.

How I checked it

After every step I compared screenshots before and after, pixel by pixel, offscreen, at 800x600 and 580x500:

  • all pages and widgets (436 images)
  • every button, normal and hovered (224 images)
  • checkbox states and combo popups (36 images)
  • the status label in its error, ok and error again states
  • the nav buttons in normal, hover and pressed states (50 images)

Every step gave 0 differences except the nav fix. There, 22 of the 436 images changed, and every changed pixel was inside the 8 nav button rectangles. I also ran the smoke test (window OK 10 pages) after each step.

Pylint

The new pylint step fails on this branch (5.45/10). I checked main as well, and it also fails (6.67/10), so the check was already red before this PR. The split adds per-file messages, mostly missing module docstrings and the E0611 false positives for PyQt6 imports, because pylint cannot read the compiled Qt modules. It also removes several of main's messages (the import errors, the long line, the unused argument).

I haven't changed any code for pylint, because I'd like your direction first. Would you like docstrings added here, a pylint config change (for example extension-pkg-allow-list=PyQt6), or a follow-up PR? And what score should the check require?

Comment on lines +1 to +6
/* Η σειρά έχει σημασία: ο κανόνας "descendant" του scroll (με *) πρέπει
να μένει πρώτος. Έχει ίδια ειδικότητα με τους κανόνες ρόλων, και
αν μπει μετά, κερδίζει αυτός και βάφει λάθος παιδιά (hero,
divider, footer, scroll content). Ο κανόνας "card" πρέπει να μένει
τελευταίος, μετά τον descendant κανόνα: το card frame είναι μέσα σε
scroll γονέα, οπότε το * το πιάνει με ίδια ειδικότητα. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments should be written in english

@TolisSth
TolisSth merged commit 5c79946 into open-source-uom:main Oct 8, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: split monolith into separate modules and reorganize directory structure

2 participants