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

ModuleComponents UsedPurpose
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 Lifecycle: ┌─────────────┐ ┌──────────────────────┐ ┌──────────────┐ │ Load QSS │ │ In-Memory Edit │ │ Save QSS │ │ from File │────►│ │────►│ to File │ │ │ │ self.stylesheet │ │ │ └─────────────┘ │ (raw string) │ └──────────────┘ │ │ │ Operations: │ │ ├─ add_property │ │ ├─ remove_stylesheet │ │ ├─ add_stylesheet │ │ ├─ replace_placeholder│ │ └─ validate │ │ │ │ Accent Color System: │ │ ├─ check placeholder │ │ ├─ extract value │ │ └─ replace token │ └──────────────────────┘ Accent Color Pattern in QSS: /* @accent-color: #0078d7; Define the accent color placeholder */ Usage Flow: 1. load_stylesheet("theme.qss") 2. check_accent_color_placeholder() → (True, "#0078d7") 3. replace_placeholder("#6750A4") → replaces all @accent-color 4. add_property_to_widget("QPushButton", "border", "2px solid") 5. validate() → True 6. save_stylesheet() → writes back to theme.qss

QtStyleSheetManager

QtStyleSheetManager

Standalone class (no base class)

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.

ParameterTypeDefaultDescription
accent_color_templatestr'@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().
AttributeTypeDefaultDescription
stylesheetOptional[str]NoneThe in-memory QSS content. None until a file is loaded.
accent_color_templatestr'@accent-color'The accent color placeholder token.
qss_filestrPath 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.

ParameterTypeDescription
qss_pathstrFilesystem 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.

ParameterTypeDefaultDescription
qss_filestr''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.

ParameterTypeDescription
widget_namestrThe CSS selector name (e.g., "QPushButton", "QLabel#title").
property_namestrThe CSS property name (e.g., "color", "border-radius").
property_valuestrThe 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.

ParameterTypeDescription
namestrThe widget name to remove. Matches the base selector and all pseudo-states (e.g., "QPushButton" removes QPushButton, QPushButton:hover, QPushButton:pressed, etc.).
Returns: str — The updated stylesheet content (also modified in-place on self.stylesheet). Returns an empty string if no stylesheet is loaded.
Removal Example: Before: QPushButton { color: blue; } QPushButton:hover { color: darkblue; } QPushButton:pressed { color: navy; } QLabel { color: black; } remove_stylesheet_by_name("QPushButton") After: QLabel { color: black; }

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.

ParameterTypeDefaultDescription
stylesheetstrNoneThe 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.

Accent Color Definition Formats: Format 1: Commented (recommended) /* @accent-color: #0078d7; Define the accent color placeholder */ Format 2: Uncommented @accent-color: #0078d7; Usage in QSS: QPushButton { background-color: @accent-color; border: 2px solid @accent-color; } After replace_placeholder("#6750A4"): QPushButton { background-color: #6750A4; border: 2px solid #6750A4; }

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:

  1. Stylesheet loaded?: If self.stylesheet is None or the QSS file doesn't exist, returns (False, '').
  2. Placeholder present?: Searches for the accent_color_template string in the stylesheet content.
  3. 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 ScenarioReturn ValueMeaning
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.

Returns: 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.

ParameterTypeDescription
accent_colorstrThe 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).

Returns: 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.

Validation Pipeline: Input: stylesheet string │ ▼ ┌──────────────────────┐ │ stylesheet is None? │──Yes──► return False └──────────┬───────────┘ │ No ▼ ┌──────────────────────┐ │ { count == } count? │──No───► return False └──────────┬───────────┘ │ Yes ▼ ┌──────────────────────────────────┐ │ Preprocess: │ │ • Remove CSS comments │ │ • Replace @accent-color token │ └──────────┬───────────────────────┘ │ ▼ ┌──────────────────────┐ │ For each style block:│ │ Properties found? │──No───► return False └──────────┬───────────┘ │ Yes (for all) ▼ ┌──────────────────────┐ │ Invalid characters? │──Yes──► return False └──────────┬───────────┘ │ No ▼ return True

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

MethodVisibilityCategoryBrief Description
__init__PublicConstructorInitialize with accent color placeholder token
load_stylesheetPublicFile I/OLoad QSS file into memory
save_stylesheetPublicFile I/OSave stylesheet back to original file
saveas_stylesheetPublicFile I/OSave stylesheet to a different file path
add_property_to_widgetPublicEditingAdd/update CSS property in a widget block (auto-saves)
remove_stylesheet_by_namePublicEditingRemove all blocks for a widget including pseudo-states
add_stylesheetPublicEditingAppend raw QSS content to the stylesheet
check_accent_color_placeholderPublicAccent ColorCheck if placeholder exists and extract its value
extract_accent_color_valuePrivateAccent ColorParse hex color from commented/uncommented definition
replace_placeholderPublicAccent ColorReplace all placeholder tokens with a color value
validatePublicValidationValidate in-memory stylesheet
validate_stylesheetStaticValidationValidate 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.