Source code for zooui.windows.dialogwindows.stringinputdialog

## 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/>.

"""String input 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.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]


[docs] class OpenNewStringInputDialog: """ Constructor : OpenNewStringInputDialog() Parameters : None OpenNewStringInputDialog() --> 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, initial_text: str = "") -> None: """ Constructor : OpenNewStringInputDialog(initial_text) Parameters : initial_text : str Optional text to pre-fill the text edit widget (e.g., OCR output). OpenNewStringInputDialog(initial_text="") --> None Create a new OpenNewStringInputDialog for gathering string input with color selection. If *initial_text* is provided, the text edit will be pre-filled with that content. This is used by the OCR screenshot feature to seed the dialog with tesseract output. Initializes the dialog with empty string color, loads previously used colors from the color store file, or creates default colors (red, green, blue) if no color history exists. """ 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._initial_text: str = initial_text 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("ffffff") f.write("ffffff\n") 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("ffffff") f.write("ffffff\n") 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 : OpenNewStringInputDialog._color_square(color_code) Parameters : color_code : str OpenNewStringInputDialog._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 : OpenNewStringInputDialog._color_button_click(color) Parameters : color : str OpenNewStringInputDialog._color_button_click(color) --> None Handles color button click event. """ self.string_color = color
[docs] def _color_button(self, color_code: ColorCode) -> QWidget: """ Method : OpenNewStringInputDialog._color_button(color_code) Parameters : color_code : str OpenNewStringInputDialog._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 : OpenNewStringInputDialog._main_dialog() Parameters : None OpenNewStringInputDialog._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) if self._initial_text: self.text_edit.setPlainText(self._initial_text) # 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 : OpenNewStringInputDialog._run_dialog() Parameters : None OpenNewStringInputDialog._run_dialog() --> Tuple[bool, str] Runs the dialog and returns the result. Returns (ok, uri) where ok is True if accepted, uri is the formatted string. """ dialog = self._main_dialog() # Run dialog and get result if dialog.exec() == QDialog.DialogCode.Accepted: # Determine final color final_color = self.string_color if len(final_color) != 6: # Try custom color input if self.custom_color_input: color_text = self.custom_color_input.text().strip() if color_text: if color_text.startswith("#"): color_text = color_text[1:] if len(color_text) == 6: final_color = color_text # If still no valid color, default to white if len(final_color) != 6: final_color = "ffffff" # Append to history if not already present if final_color not in self.color_codes: self.color_codes.append(final_color) # Save color list with open(self.color_dir + "/color_list.txt", "w") as f: for code in self.color_codes: f.write(str(code) + "\n") self.string_color = final_color if self.text_edit: uri = "string:" + str(self.string_color) + ":" + str(self.text_edit.toPlainText()) else: uri = "string:" + str(self.string_color) + ":" ok = True return ok, uri else: ok = False return ok, ""