Source code for zooui.windows.dialogwindows.modifystringdialog

## ZooUI - Zooming User Interface
## Copyright (C) 2009 David Roberts <d@vidr.cc>
##
## 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 3
## 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/>.

"""Modify string dialog with color selection."""

import os
from collections import deque
from typing import TYPE_CHECKING

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QFont, QKeySequence, QPainter, QShortcut
from PySide6.QtWidgets import (
    QColorDialog,
    QDialog,
    QDialogButtonBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QPushButton,
    QSizePolicy,
    QTextEdit,
    QVBoxLayout,
    QWidget,
)

from zooui.logger import get_logger
from zooui.utils._xdg import get_colorstore_dir

if TYPE_CHECKING:
    from PySide6.QtGui import QPaintEvent
    from PySide6.QtWidgets import QLineEdit, QTextEdit

# Type aliases
ColorCode = str
DialogResult = tuple[bool, str, str, str]


[docs] class ModifyStringInputDialog: """ Constructor : ModifyStringInputDialog(media_id) Parameters : media_id : Optional[str] ModifyStringInputDialog(media_id) --> None Gather the string through a dialog and let select the color. Also gives a selection column of the last 20 used colors. """
[docs] def __init__(self, media_id: str | None) -> None: """ Constructor : ModifyStringInputDialog(media_id) Parameters : media_id : Optional[str] ModifyStringInputDialog(media_id) --> None Create a new ModifyStringInputDialog for editing an existing string media object. The media_id parameter contains the string URI in format 'string:RRGGBB:text'. If media_id is provided, the dialog is pre-populated with the existing color and text. Loads previously used colors from the color store file. """ self.start_string: str = "" self.string_color: str = "" self.passed_color: str = "" self.color_codes: deque[ColorCode] = deque(maxlen=24) self.text_edit: QTextEdit self.custom_color_input: QLineEdit self.__logger = get_logger("ModifyStringInputDialog") if media_id is None: pass elif media_id[:6] == "string": self.string_color = media_id[7:13] self.start_string = media_id[14:] else: pass self.color_dir = str(get_colorstore_dir()) if os.path.isfile(self.color_dir + "/color_list.txt"): with open(self.color_dir + "/color_list.txt") as f: for line in f: stripline = line.strip() stripline = stripline.lower() if len(stripline) == 6 and stripline not in self.color_codes: self.color_codes.append(stripline) else: if os.path.isdir(self.color_dir): f = open(self.color_dir + "/color_list.txt", "w") self.color_codes.append("ff0000") f.write("ff0000\n") self.color_codes.append("00ff00") f.write("00ff00\n") self.color_codes.append("0000ff") f.write("0000ff\n") f.close() else: os.mkdir(self.color_dir) f = open(self.color_dir + "/color_list.txt", "w") self.color_codes.append("ff0000") f.write("ff0000\n") self.color_codes.append("00ff00") f.write("00ff00\n") self.color_codes.append("0000ff") f.write("0000ff\n") f.close()
[docs] def _color_square(self, color_code: ColorCode) -> QWidget: """ Method : ModifyStringInputDialog._color_square(color_code) Parameters : color_code : str ModifyStringInputDialog._color_square(color_code) --> QWidget Creates a colored square widget. """ color_square = QWidget() color = QColor("#" + str(color_code)) color_square.setFixedSize(20, 20) def paintEvent(event: "QPaintEvent") -> None: painter = QPainter(color_square) painter.fillRect(color_square.rect(), color) # Explicit .end() is required: a QPainter left active on its # paint device can corrupt Qt's C++ paint engine state, # eventually causing a SIGSEGV crash in long-running sessions. painter.end() color_square.paintEvent = paintEvent return color_square
[docs] def _color_button_click(self, color: ColorCode) -> None: """ Method : ModifyStringInputDialog._color_button_click(color) Parameters : color : str ModifyStringInputDialog._color_button_click(color) --> None Handles color button click event. """ self.string_color = color
[docs] def _color_button(self, color_code: ColorCode) -> QWidget: """ Method : ModifyStringInputDialog._color_button(color_code) Parameters : color_code : str ModifyStringInputDialog._color_button(color_code) --> QWidget Creates a color selection button. """ color_widget = QWidget() layout = QHBoxLayout() layout.setContentsMargins(5, 2, 5, 2) layout.setSpacing(10) color_square = self._color_square(color_code) label = QLabel(color_code) # Create a QPushButton but use a QWidget wrapper to hold square + label button = QPushButton() button.setLayout(layout) button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) # Add widget and label to the layout inside the button layout.addWidget(color_square) layout.addWidget(label) layout.addStretch() # Make the whole widget act like a button by forwarding clicks button.clicked.connect(lambda: self._color_button_click(color_code)) # Our main layout for this widget is the button only main_layout = QHBoxLayout(color_widget) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(button) return color_widget
[docs] def _pick_color_from_dialog(self) -> None: """Open a QColorDialog to pick a custom color.""" color = QColorDialog.getColor() if color.isValid(): hex_color = color.name()[1:] self.string_color = hex_color self.custom_color_input.setText(hex_color)
[docs] def _main_dialog(self) -> QDialog: """ Method : ModifyStringInputDialog._main_dialog() Parameters : None ModifyStringInputDialog._main_dialog() --> QDialog Creates and configures the main dialog window. """ dialog = QDialog() dialog.setWindowTitle("String input:") dialog.resize(1000, 600) # Create text edit widget self.text_edit = QTextEdit(dialog) # Input string is going to be typed in here font = QFont() font.setPointSize(16) # Set desired font size self.text_edit.setFont(font) self.text_edit.setPlainText(self.start_string) # Align text to top-left (horizontal only by default) self.text_edit.setAlignment(Qt.AlignmentFlag.AlignLeft) # Create a text input field for custom color entry self.custom_color_input = QLineEdit(dialog) # Color code it's going to be typed here self.custom_color_input.setPlaceholderText("Enter custom color (e.g., #ff5733)") # Create OK/Cancel buttons buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, dialog) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) # Layout setup main_layout = QHBoxLayout(dialog) color_layout = QVBoxLayout() color_layout.setContentsMargins(0, 0, 0, 0) color_layout.setSpacing(2) for code in self.color_codes: btn = self._color_button(code) btn.setFixedWidth(120) color_layout.addWidget(btn) pick_color_btn = QPushButton("Pick Color...") pick_color_btn.clicked.connect(self._pick_color_from_dialog) color_layout.addWidget(pick_color_btn) color_layout.addStretch() text_layout = QVBoxLayout() text_layout.addWidget(self.text_edit) text_layout.addWidget(self.custom_color_input) text_layout.addWidget(buttons) main_layout.addLayout(text_layout) main_layout.addLayout(color_layout) QShortcut(QKeySequence("Ctrl+Return"), dialog, dialog.accept) QShortcut(QKeySequence("Ctrl+Enter"), dialog, dialog.accept) return dialog
[docs] def _run_dialog(self) -> DialogResult: """ Method : ModifyStringInputDialog._run_dialog() Parameters : None ModifyStringInputDialog._run_dialog() --> Tuple[bool, str, str, str] Runs the dialog and returns the result. Returns (ok, media_id, color, text) where ok is True if accepted. """ dialog = self._main_dialog() # Run dialog and get result if dialog.exec() == QDialog.DialogCode.Accepted: media_id = "" if len(self.custom_color_input.text()) == 6 or len(self.custom_color_input.text()) == 7: self.string_color = self.custom_color_input.text() if len(self.string_color) == 6: self.color_codes.append(self.string_color) f = open(self.color_dir + "/color_list.txt", "w") for i in self.color_codes: f.write(str(i) + "\n") f.close() if self.string_color[0] == "#": self.string_color = self.string_color[1:] self.color_codes.append(self.string_color) f = open(self.color_dir + "/color_list.txt", "w") for i in self.color_codes: f.write(str(i) + "\n") f.close() if len(self.string_color) == 7 and self.string_color[0] != "#": pass elif len(self.string_color) != 6: self.__logger.warning("Invalid string color length") try: media_id = "string:" + str(self.string_color) + ":" + str(self.text_edit.toPlainText()) except Exception as e: self.__logger.error("Error creating media_id: %s", e) ok = True return ok, media_id, self.string_color, self.text_edit.toPlainText() else: ok = False return ok, "", "", ""