StyleManagers.py PySideAbdhUI — QSS Stylesheet Manager with Accent Color & Validation
StyleManagers.py defines the QtStyleSheetManager class, a comprehensive
in-memory stylesheet manager for Qt applications. It provides a programmatic API for loading,
editing, validating, and persisting QSS (Qt Style Sheet) files. Unlike the simpler
ThemeManager in utils.py which only performs placeholder substitution,
QtStyleSheetManager offers fine-grained control over individual CSS properties within
widget blocks, supports accent-color placeholder extraction and replacement, includes a
validation engine that checks for common QSS syntax errors, and can remove entire widget
style blocks including their pseudo-states.
The class operates on an in-memory self.stylesheet string that represents the
entire QSS file content. All modifications (adding properties, removing blocks, replacing
placeholders) are performed on this string using regular expressions, and the modified
content can be saved back to the original file or to a new location. The manager is designed
to be used as a single-instance utility that maintains a one-to-one relationship with a QSS
file on disk.
Dependencies
| Module | Components Used | Purpose |
|---|---|---|
os |
os.path.exists |
Checking if QSS files exist before reading or writing, preventing FileNotFoundError. |
re |
re.compile, re.search, re.sub, re.findall |
Regular expression operations for matching widget blocks, extracting accent color values, removing stylesheet definitions, and validating syntax. |
logging |
logging.basicConfig, logging.getLogger |
Structured logging at INFO and WARNING/ERROR levels for all operations. Configured at module level with basicConfig(level=logging.INFO). |
typing |
Optional |
Type annotation for the optional stylesheet attribute that can be None before a file is loaded. |
Architecture
The QtStyleSheetManager follows a load-modify-save pattern where the QSS file
content is read into memory as a single string, modified through regex-based operations,
and written back to disk. The class does not maintain a parsed representation of the
stylesheet — it works directly on the raw string. This approach is simple and effective
for the targeted operations, but has inherent limitations with complex CSS structures.
QtStyleSheetManager
QtStyleSheetManager
A programmatic QSS stylesheet manager that provides a complete API for loading, editing, validating, and saving Qt Style Sheets. The class manages a single QSS file at a time, keeping its content as a raw string in memory and using regular expressions for all modification operations. It supports three categories of operations:
- File I/O: Loading from and saving to QSS files.
- Property & block editing: Adding/updating individual properties within widget blocks, removing entire widget definitions (including pseudo-states), and appending raw stylesheet content.
- Accent color management: A placeholder-based system for defining and replacing a theme accent color throughout the stylesheet, with support for both commented and uncommented placeholder definitions.
- Validation: Checking the stylesheet for common syntax errors including unclosed braces, missing semicolons, and invalid characters.
Constructor
QtStyleSheetManager.__init__(self, accent_color_template: str = '@accent-color')
Public
Initializes the stylesheet manager with an optional accent color placeholder token.
The stylesheet string is set to None initially — no file is loaded until
load_stylesheet() is called. The accent color template defaults to
'@accent-color', which follows CSS at-rule syntax. This token is expected
to appear in the QSS file as a placeholder that will be replaced with an actual
hex color value at runtime.
| Parameter | Type | Default | Description |
|---|---|---|---|
accent_color_template | str | '@accent-color' | The placeholder token used in the QSS file to mark where the accent color should be inserted. This token is searched for and replaced by replace_placeholder(). |
| Attribute | Type | Default | Description |
|---|---|---|---|
stylesheet | Optional[str] | None | The in-memory QSS content. None until a file is loaded. |
accent_color_template | str | '@accent-color' | The accent color placeholder token. |
qss_file | str | — | Path to the loaded QSS file. Set by load_stylesheet(). |
File I/O
The file I/O methods handle reading QSS files into memory and writing the modified
stylesheet back to disk. The manager maintains a reference to the source file path
(self.qss_file) so that save_stylesheet() can write back to the
same location without requiring the path again.
load_stylesheet(self, qss_path: str) -> str
Public
Loads a QSS file from the given path into the self.stylesheet string.
The file path is stored as self.qss_file for later use by
save_stylesheet(). The file is read with UTF-8 encoding. If the file
does not exist, an error is logged and self.stylesheet remains unchanged
(still None if this is the first load, or retains the previous content
if a file was previously loaded).
After loading, the self.stylesheet attribute contains the complete raw
content of the QSS file, including comments, placeholder tokens, and all widget
definitions. No parsing or preprocessing is performed at load time.
| Parameter | Type | Description |
|---|---|---|
qss_path | str | Filesystem path to the QSS file to load. |
Note: The method signature declares a return type of str, but the method does not explicitly return anything. It only sets self.stylesheet and self.qss_file. The return type annotation is misleading — the function implicitly returns None.
save_stylesheet(self)
Public
Saves the current self.stylesheet content back to the file at
self.qss_file (the path stored by the most recent call to
load_stylesheet()). The file is overwritten completely with the
current in-memory content, written with UTF-8 encoding. If the file does not
exist at the stored path, an error is logged and no save is performed.
This method is automatically called by add_property_to_widget() after
each property modification, ensuring that the file on disk always reflects the
in-memory state. This auto-save behavior means every property change immediately
persists to disk.
Auto-Save Side Effect: Because add_property_to_widget() calls save_stylesheet() internally, every property addition or update triggers a file write. For batch operations involving multiple property changes, this can result in numerous disk writes. Consider refactoring to defer the save until all changes are complete.
saveas_stylesheet(self, qss_file: str = '')
Public
Saves the current stylesheet content to a different file path, analogous to a
"Save As" operation. Unlike save_stylesheet(), this method does not
update self.qss_file — subsequent calls to save_stylesheet()
will still write to the original file. The file must already exist at the specified
path; if it does not, an error is logged and no save is performed.
| Parameter | Type | Default | Description |
|---|---|---|---|
qss_file | str | '' | The destination file path for the "Save As" operation. |
Limitation: The method requires the destination file to already exist (os.path.exists(qss_file)). This prevents creating new files via "Save As". To save to a new file, the destination must be created first (e.g., by touch or writing an empty file).
Property Editing
The property editing methods provide programmatic control over the QSS content at varying granularities: individual CSS properties within a widget block, entire widget blocks (including pseudo-states), and raw stylesheet appending.
add_property_to_widget
add_property_to_widget(self, widget_name: str, property_name: str, property_value: str)
Public
Adds or updates a CSS property within a specific widget's style block in the stylesheet. This is the primary method for fine-grained stylesheet customization. It operates in three modes depending on the current state of the stylesheet:
Mode 1: Update existing property
If the widget block exists and the property already exists within it, the property
value is replaced. For example, if QPushButton { color: red; } exists
and add_property_to_widget("QPushButton", "color", "blue") is called,
the result is QPushButton { color: blue; }.
Mode 2: Append new property to existing block
If the widget block exists but the property does not, the new property is appended
before the closing brace with 4-space indentation. For example, if
QPushButton { color: red; } exists and
add_property_to_widget("QPushButton", "border", "1px solid") is called,
the result is:
QPushButton {
color: red;
border: 1px solid;
}
Mode 3: Create new widget block
If no block for the widget exists at all, a new block is created and appended to
the end of the stylesheet. For example, calling
add_property_to_widget("QLabel", "font-size", "14px") when no
QLabel block exists produces:
QLabel {
font-size: 14px;
}
After any modification, save_stylesheet() is called automatically to
persist the change to disk.
| Parameter | Type | Description |
|---|---|---|
widget_name | str | The CSS selector name (e.g., "QPushButton", "QLabel#title"). |
property_name | str | The CSS property name (e.g., "color", "border-radius"). |
property_value | str | The CSS property value (e.g., "red", "8px"). |
Regex Limitation: The widget matching pattern widget_name\s*\{[^}]*\} does not support nested braces. This means widget blocks containing pseudo-state selectors (e.g., QPushButton:hover { ... }) or sub-selectors will not be matched correctly. The pattern only matches the first brace-enclosed block up to the first }, which may be the closing brace of a nested selector rather than the widget block itself.
remove_stylesheet_by_name
remove_stylesheet_by_name(self, name: str) -> str
Public
Removes all stylesheet blocks matching the given widget name, including any
pseudo-state variants. The regex pattern name(:\w+)?\s*\{[\s\S]*?\}
matches both the base widget (QPushButton { ... }) and any pseudo-states
(QPushButton:hover { ... }, QPushButton:pressed { ... }).
This is useful for completely removing a widget's styling from the stylesheet
without affecting other widgets.
The pattern uses a non-greedy match ([\s\S]*?) to find the shortest
possible content between braces, which works correctly for simple blocks but may
produce unexpected results for blocks containing nested braces.
| Parameter | Type | Description |
|---|---|---|
name | str | The widget name to remove. Matches the base selector and all pseudo-states (e.g., "QPushButton" removes QPushButton, QPushButton:hover, QPushButton:pressed, etc.). |
str — The updated stylesheet content (also modified in-place on self.stylesheet). Returns an empty string if no stylesheet is loaded.Note: Unlike add_property_to_widget(), this method does not call save_stylesheet() automatically. The caller must explicitly save if persistence is desired.
add_stylesheet
add_stylesheet(self, stylesheet: str = None)
Public
Appends raw QSS content to the existing stylesheet string. The new content is joined with a newline separator. This is the simplest way to add stylesheet content — it performs no parsing, validation, or deduplication. The caller is responsible for ensuring the appended content is valid QSS and does not conflict with existing rules.
| Parameter | Type | Default | Description |
|---|---|---|---|
stylesheet | str | None | The QSS string to append. |
Bug: If stylesheet is None (the default), the expression self.stylesheet + '\n' + stylesheet will raise a TypeError because you cannot concatenate a string with None. Additionally, if self.stylesheet is None (no file loaded yet), the same error occurs. This method lacks the guard checks present in other methods.
Accent Color System
The accent color system provides a simple but flexible mechanism for defining a theme accent color in a QSS file using a placeholder token. The system supports two formats for defining the accent color in the QSS file — commented and uncommented — and provides methods for checking, extracting, and replacing the placeholder throughout the stylesheet.
check_accent_color_placeholder
check_accent_color_placeholder(self) -> tuple[bool, str]
Public
Checks whether the accent color placeholder exists in the loaded stylesheet and, if it does, attempts to extract its value. The method returns a tuple where the first element is a boolean indicating whether the placeholder was found, and the second element is the extracted hex color value (or an empty string if extraction failed).
The method performs three checks:
- Stylesheet loaded?: If
self.stylesheetisNoneor the QSS file doesn't exist, returns(False, ''). - Placeholder present?: Searches for the
accent_color_templatestring in the stylesheet content. - Value extractable?: If the placeholder exists, calls
extract_accent_color_value()to parse the hex color. Returns(True, color)on success,(True, '')if the placeholder exists but the value couldn't be extracted.
| Return Scenario | Return Value | Meaning |
|---|---|---|
| No stylesheet loaded | (False, '') | The stylesheet has not been loaded or the file is missing. |
| Placeholder not found | (False, '') | The @accent-color token does not appear anywhere in the QSS. |
| Placeholder found, value extracted | (True, '#0078d7') | The placeholder exists and its hex value was successfully parsed. |
| Placeholder found, extraction failed | (True, '') | The token exists but the definition line is malformed. |
extract_accent_color_value
extract_accent_color_value(self) -> str
Private
Extracts the hex color value assigned to the accent color placeholder in the stylesheet. The method handles both commented and uncommented definition formats using a single regex with two alternation branches:
- Commented format:
/* @accent-color: #0078d7; */— The regex matches the comment delimiters, the placeholder name, and captures the hex value. - Uncommented format:
@accent-color: #0078d7;— The regex matches the plain definition and captures the hex value.
The regex captures 3-digit (#FFF) and 6-digit (#FFFFFF) hex
color values. If a match is found, the first non-None capture group is returned
(using a generator expression with next()). If no match is found, an
empty string is returned.
str — The extracted hex color value, or an empty string if not found.Tip: The commented format is recommended because it doesn't affect QSS parsing — Qt's style engine ignores CSS comments, so the placeholder definition line won't interfere with the stylesheet's validity. The uncommented format may cause Qt to emit warnings about unknown at-rules.
replace_placeholder
replace_placeholder(self, accent_color: str) -> str
Public
Replaces all occurrences of the accent color placeholder token in the stylesheet
with the provided hex color string. The replacement uses Python's
str.replace(), which performs a global (all occurrences) replacement.
This means every instance of @accent-color in the stylesheet — whether
used as a CSS value (color: @accent-color;) or in a definition
(@accent-color: #0078d7;) — will be replaced.
| Parameter | Type | Description |
|---|---|---|
accent_color | str | The hex color value to substitute (e.g., "#6750A4"). |
Side Effect: Because str.replace() replaces ALL occurrences, the accent color definition line itself will also be modified. For example, @accent-color: #0078d7; becomes #6750A4: #0078d7;, which is syntactically invalid. If you need to preserve the definition line, extract the value first, then use a more targeted replacement approach.
Validation
The validation system checks the stylesheet for common syntax errors that could cause Qt's style engine to silently ignore rules or produce unexpected visual results. The validation is not exhaustive — it focuses on the most frequent mistakes that are difficult to debug visually.
validate(self) -> bool
Public
Convenience method that validates the in-memory self.stylesheet string.
Delegates to the static method validate_stylesheet(self.stylesheet).
True if the stylesheet passes all validation checks; False otherwise.validate_stylesheet(stylesheet: str) -> bool
Static
A static method that performs syntax validation on a given stylesheet string.
The validation pipeline consists of three sequential checks, each of which can
cause the method to return False early:
1. Null Check
If the stylesheet is None, a warning is logged and the method returns
False. This prevents AttributeError when calling string
methods on None.
2. Unmatched Curly Braces
Counts the number of { and } characters in the stylesheet.
If they don't match, an error is logged and False is returned. This
catches the most common QSS error — a missing closing brace that causes all
subsequent rules to be absorbed into the unclosed block.
3. Missing Semicolons
Before checking for semicolons, the method preprocesses the stylesheet by removing
CSS comments (to avoid false positives) and temporarily replacing the
@accent-color placeholder with a valid hex color. Then, each style
block is extracted and checked for at least one property ending with a semicolon.
A block with zero semicolon-terminated properties is flagged as having missing
semicolons.
4. Invalid Characters
Checks for characters outside the allowed QSS character set. The regex
[^\w\s:\-;#.%{}(),@"] defines the whitelist of valid characters:
word characters, whitespace, colons, hyphens, semicolons, hash, period, percent,
braces, parentheses, commas, at-signs, and double quotes. Any other character
is flagged as invalid.
Limitation: The invalid character check uses a fixed whitelist that may be too restrictive for some valid QSS syntax. For example, single quotes ('), angle brackets (<>), and square brackets ([]) are not in the whitelist but may appear in valid QSS values. The regex also uses a raw double-quote character in the character class, which may need escaping depending on the context.
Full Method Index
| Method | Visibility | Category | Brief Description |
|---|---|---|---|
__init__ | Public | Constructor | Initialize with accent color placeholder token |
load_stylesheet | Public | File I/O | Load QSS file into memory |
save_stylesheet | Public | File I/O | Save stylesheet back to original file |
saveas_stylesheet | Public | File I/O | Save stylesheet to a different file path |
add_property_to_widget | Public | Editing | Add/update CSS property in a widget block (auto-saves) |
remove_stylesheet_by_name | Public | Editing | Remove all blocks for a widget including pseudo-states |
add_stylesheet | Public | Editing | Append raw QSS content to the stylesheet |
check_accent_color_placeholder | Public | Accent Color | Check if placeholder exists and extract its value |
extract_accent_color_value | Private | Accent Color | Parse hex color from commented/uncommented definition |
replace_placeholder | Public | Accent Color | Replace all placeholder tokens with a color value |
validate | Public | Validation | Validate in-memory stylesheet |
validate_stylesheet | Static | Validation | Validate any stylesheet string for syntax errors |
Comparison with utils.py ThemeManager: The QtStyleSheetManager and the ThemeManager in utils.py share a similar add_property_to_widget() method with nearly identical regex patterns. However, they differ in scope: ThemeManager works with a JSON-based theme system and performs QSS template placeholder substitution (--name-- tokens), while QtStyleSheetManager works directly on raw QSS strings with an accent-color token (@accent-color). The QtStyleSheetManager additionally provides validation, block removal, and file persistence, which ThemeManager does not.