Source code for pymodaq_gui.examples.pattern_completer_demo

"""
PatternCompleter Examples - PyQt6 Auto-completion System

This demonstrates various use cases for the PatternCompleter mixin class.
All examples are accessible through tabs in a single window.
"""

from qtpy.QtWidgets import (
    QApplication,
    QMainWindow,
    QVBoxLayout,
    QWidget,
    QLabel,
    QTableWidget,
    QTableWidgetItem,
    QTabWidget,
    QHBoxLayout,
    QPushButton,
)
from qtpy.QtCore import QTimer
from pymodaq_gui.utils.widgets.pattern_completer import (
    PatternLineEdit,
    PatternTextEdit,
    PatternPlainTextEdit,
    PatternCompleterDelegate,
)
import sys


# ============================================================================
# EXAMPLE 1: Basic Usage with QLineEdit
# ============================================================================
[docs] def create_basic_example(): """Simple mention system with @ trigger""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 1: Basic Usage</b>")) layout.addWidget(QLabel("Type @ to mention someone")) # Create a line edit with pattern completion line_edit = PatternLineEdit() # Add @ mentions completer users = ["Alice Johnson", "Bob Smith", "Charlie Brown", "Diana Prince"] line_edit.add_completer("@", users) line_edit.setPlaceholderText("Type @ to mention someone...") layout.addWidget(line_edit) layout.addStretch() return widget
# ============================================================================ # EXAMPLE 2: Multiple Patterns # ============================================================================
[docs] def create_multiple_patterns_example(): """Text editor with multiple completion triggers""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 2: Multiple Patterns</b>")) layout.addWidget(QLabel("Try typing @ for mentions, # for hashtags, :: for emojis")) text_edit = PatternTextEdit() # @ for user mentions users = ["Alice", "Bob", "Charlie", "Diana"] text_edit.add_completer("@", users) # # for hashtags tags = ["python", "pyqt6", "programming", "development", "tutorial"] text_edit.add_completer("#", tags) # :: for simple symbols # Using only simple single-character symbols to avoid cursor positioning issues symbols = [ "check ✓", "cross ✗", "star ★", "arrow →", "bullet •", "circle ○", "square ■", "plus ✚", ] text_edit.add_completer("::", symbols) text_edit.setPlaceholderText( "Try typing:\n @ for mentions\n # for hashtags\n :: for symbols", ) layout.addWidget(text_edit) return widget
# ============================================================================ # EXAMPLE 3: Global Configuration # ============================================================================
[docs] def create_global_config_example(): """Configure appearance and behavior globally""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 3: Global Configuration</b>")) layout.addWidget( QLabel("Notice the green border when typing @ (visual indicator enabled)"), ) # Initialize with global settings line_edit = PatternLineEdit( min_width=200, # Minimum popup width max_width=600, # Maximum popup width visual_indicator=True, # Show green border when active case_sensitive=False, # Case-insensitive by default auto_resize=True, # Auto-resize popup to fit content word_wrap=False, # Don't wrap long items ) countries = ["United States", "United Kingdom", "Canada", "Australia", "Germany"] line_edit.add_completer("@", countries) line_edit.setPlaceholderText("Type @ - notice the green border!") layout.addWidget(line_edit) layout.addStretch() return widget
# ============================================================================ # EXAMPLE 4: Per-Pattern Configuration # ============================================================================
[docs] def create_per_pattern_config_example(): """Different settings for each pattern""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 4: Per-Pattern Configuration</b>")) layout.addWidget(QLabel("@ is case-insensitive, :: is case-sensitive")) text_edit = PatternTextEdit() # Case-insensitive user mentions with visual indicator users = ["Alice", "Bob", "Charlie"] text_edit.add_completer("@", users, visual_indicator=True, case_sensitive=False) # Case-sensitive programming keywords keywords = ["def", "class", "import", "return", "if", "else"] text_edit.add_completer( "::", keywords, case_sensitive=True, # Exact case matching min_width=150, max_width=300, ) text_edit.setPlaceholderText( "@ mentions are case-insensitive (try '@ali' or '@ALI')\n" ":: keywords are case-sensitive (try '::def' vs '::DEF')", ) layout.addWidget(text_edit) return widget
# ============================================================================ # EXAMPLE 5: Word Wrap Example - Side by Side Comparison # ============================================================================
[docs] def create_word_wrap_example(): """ Demonstrates word wrap feature for long completion items with side-by-side comparison """ widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 5: Word Wrap - Side by Side Comparison</b>")) info_label = QLabel( "<b>What is Word Wrap?</b><br>" "• <b>word_wrap=False</b> (left): Long items are truncated or need scrolling<br>" "• <b>word_wrap=True</b> (right): Long items wrap to multiple lines in popup<br><br>" "Type @ in either field to see the difference side-by-side!<br>" "Both popups will appear simultaneously for comparison.", ) layout.addWidget(info_label) # Create side-by-side layout compare_layout = QHBoxLayout() # Left side - WITHOUT word wrap left_layout = QVBoxLayout() left_layout.addWidget(QLabel("<b>WITHOUT Word Wrap</b>")) line_edit_no_wrap = PatternLineEdit() # Long completion items that benefit from word wrap long_items = [ "Alice Johnson - Senior Developer at Tech Corp", "Bob Smith - Product Manager with 10 years experience", "Charlie Brown - UX Designer specializing in mobile applications", "Diana Prince - Data Scientist working on machine learning projects", "Eve Martinez - Full Stack Engineer with cloud expertise", ] line_edit_no_wrap.add_completer("@", long_items, word_wrap=False, max_width=300) line_edit_no_wrap.setPlaceholderText("Type @ - items truncated") left_layout.addWidget(line_edit_no_wrap) # Right side - WITH word wrap right_layout = QVBoxLayout() right_layout.addWidget(QLabel("<b>WITH Word Wrap</b>")) line_edit_with_wrap = PatternLineEdit() line_edit_with_wrap.add_completer("@", long_items, word_wrap=True, max_width=300) line_edit_with_wrap.setPlaceholderText("Type @ - items wrap") right_layout.addWidget(line_edit_with_wrap) # Mirror the text between both fields def sync_left_to_right(text): if line_edit_with_wrap.text() != text: line_edit_with_wrap.setText(text) line_edit_with_wrap.setCursorPosition(line_edit_no_wrap.cursorPosition()) line_edit_no_wrap.setFocus() def sync_right_to_left(text): if line_edit_no_wrap.text() != text: line_edit_no_wrap.setText(text) line_edit_no_wrap.setCursorPosition(line_edit_with_wrap.cursorPosition()) line_edit_with_wrap.setFocus() line_edit_no_wrap.textChanged.connect(sync_left_to_right) line_edit_with_wrap.textChanged.connect(sync_right_to_left) # Add both to compare layout compare_layout.addLayout(left_layout) compare_layout.addLayout(right_layout) layout.addLayout(compare_layout) # Add instruction label instruction_label = QLabel( "<i>Notice: The first item is automatically highlighted (selected) in both popups.<br>" "Press Tab or Enter to accept the highlighted suggestion!</i>", ) instruction_label.setWordWrap(True) layout.addWidget(instruction_label) layout.addStretch() return widget
# ============================================================================ # EXAMPLE 6: Dynamic Updates # ============================================================================
[docs] def create_dynamic_updates_example(): """Update completions dynamically""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 6: Dynamic Updates</b>")) counter_label = QLabel("Completions update every 2 seconds!") layout.addWidget(counter_label) text_edit = PatternTextEdit() text_edit.add_completer("@", ["Alice", "Bob"]) text_edit.setPlaceholderText("Type @ to see completions. Watch them change!") layout.addWidget(text_edit) # Simulate dynamic updates counter = [0] def update_users(): counter[0] += 1 new_users = [f"User{i}" for i in range(counter[0], counter[0] + 5)] text_edit.update_completions("@", new_users) counter_label.setText(f"Update #{counter[0]}: {', '.join(new_users)}") timer = QTimer(widget) timer.timeout.connect(update_users) timer.start(2000) return widget
# ============================================================================ # EXAMPLE 7: Table Widget with Delegate # ============================================================================
[docs] def create_table_delegate_example(): """Use PatternCompleter in table cells""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 7: Table Delegate</b>")) layout.addWidget(QLabel("Double-click cells and type @ for users or # for tags")) table = QTableWidget(5, 2) table.setHorizontalHeaderLabels(["Assigned To", "Tags"]) # Create delegate with pattern completion delegate = PatternCompleterDelegate(min_width=200, visual_indicator=True) # Add completers for the delegate users = ["Alice", "Bob", "Charlie", "Diana"] tags = ["urgent", "review", "bug", "feature", "documentation"] delegate.add_completer("@", users) delegate.add_completer("#", tags) # Apply delegate to both columns table.setItemDelegateForColumn(0, delegate) table.setItemDelegateForColumn(1, delegate) # It is possible to use different delegates for each column but one has to keep the instance alive (with self. ...) # Add some sample data for i in range(5): table.setItem(i, 0, QTableWidgetItem(f"Task {i + 1}")) table.setItem(i, 1, QTableWidgetItem("")) layout.addWidget(table) return widget
# ============================================================================ # EXAMPLE 8: Code Editor Style # ============================================================================
[docs] def create_code_editor_example(): """IDE-like code completion""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("<b>Example 8: Code Editor Style</b>")) layout.addWidget(QLabel("Python-style completion: :keyword or ::builtin")) editor = PatternPlainTextEdit( min_width=250, max_width=500, case_sensitive=True, auto_resize=True, ) # Python keywords keywords = [ "def", "class", "import", "from", "return", "if", "else", "elif", "for", "while", "try", "except", "with", "as", "pass", "break", ] # Built-in functions builtins = [ "print()", "len()", "range()", "enumerate()", "zip()", "map()", "filter()", "sorted()", "sum()", "max()", "min()", ] editor.add_completer(":", keywords, case_sensitive=True) editor.add_completer("::", builtins) editor.setPlaceholderText( "Python-style completion:\n" " :def → keywords\n" " ::print → built-in functions\n\n" "Try typing ':for' or '::pri'", ) layout.addWidget(editor) return widget
# ============================================================================ # Main Application with Tabs # ============================================================================
[docs] class PatternCompleterDemo(QMainWindow): """Main demo window with all examples in tabs""" def __init__(self): super().__init__() self.setWindowTitle("PatternCompleter Examples - Interactive Demo") self.setGeometry(100, 100, 900, 600) # Create tab widget tabs = QTabWidget() # Add all example tabs tabs.addTab(create_basic_example(), "1. Basic") tabs.addTab(create_multiple_patterns_example(), "2. Multiple Patterns") tabs.addTab(create_global_config_example(), "3. Global Config") tabs.addTab(create_per_pattern_config_example(), "4. Per-Pattern Config") tabs.addTab(create_word_wrap_example(), "5. Word Wrap") tabs.addTab(create_dynamic_updates_example(), "6. Dynamic Updates") tabs.addTab(create_table_delegate_example(), "7. Table Delegate") tabs.addTab(create_code_editor_example(), "8. Code Editor") #Not working currently self.setCentralWidget(tabs)
# ============================================================================ # Run Application # ============================================================================ if __name__ == "__main__": app = QApplication(sys.argv) # Set application style app.setStyle("Fusion") # Create and show demo window demo = PatternCompleterDemo() demo.show() sys.exit(app.exec())