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, ""