# -*- coding: utf-8 -*- """ 文本输入模块 支持: - 控制台标准输入(后台线程读取,不阻塞 Qt 主循环) - 程序内直接注入(inject) - PyQt5 UI 文本框输入 """ import logging import sys from PyQt5.QtCore import QThread, pyqtSlot, Qt from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QTextEdit, QPushButton, QLabel ) from uas.io.base_io import BaseInput, IOType logger = logging.getLogger(__name__) # ───────────────────────────────────────────────────────────────── # 控制台输入(后台 QThread 读取 stdin) # ───────────────────────────────────────────────────────────────── class _StdinReaderThread(QThread): def __init__(self, callback, parent=None): super().__init__(parent) self._callback = callback self._running = False def run(self): self._running = True logger.info("stdin 读取线程启动") while self._running: try: line = sys.stdin.readline() if not line: break text = line.rstrip("\n") if text: self._callback(text) except Exception as e: logger.error(f"stdin 读取异常: {e}") break logger.info("stdin 读取线程退出") def stop(self): self._running = False class ConsoleTextInput(BaseInput): """ 控制台文本输入 在独立 QThread 中读取 stdin,通过信号槽安全回传主线程 """ def __init__(self, parent=None): super().__init__("console_stdin", IOType.TEXT, parent) self._thread = _StdinReaderThread(self._on_line_read, self) def start(self) -> None: self._running = True self._thread.start() logger.info("✅ ConsoleTextInput 已启动") def stop(self) -> None: self._running = False self._thread.stop() self._thread.wait(2000) logger.info("🛑 ConsoleTextInput 已停止") def _on_line_read(self, text: str): self._emit_input(text, metadata={"source": "stdin"}) def inject(self, text: str) -> None: """程序内直接注入文本(用于测试或 API 调用)""" self._emit_input(text, metadata={"source": "inject"}) # ───────────────────────────────────────────────────────────────── # PyQt5 UI 文本输入组件 # ───────────────────────────────────────────────────────────────── class TextInputWidget(QWidget, BaseInput): """ 带 UI 界面的文本输入组件 包含:单行输入框 + 多行文本框 + 发送按钮 """ def __init__(self, parent=None): QWidget.__init__(self, parent) BaseInput.__init__(self, "ui_text_input", IOType.TEXT) self._build_ui() def _build_ui(self): layout = QVBoxLayout(self) layout.setContentsMargins(8, 8, 8, 8) layout.setSpacing(6) # 标题 title = QLabel("📝 文本输入") title.setStyleSheet("font-weight: bold; font-size: 13px;") layout.addWidget(title) # 多行输入区 self._text_edit = QTextEdit() self._text_edit.setPlaceholderText("在此输入多行文本...") self._text_edit.setMinimumHeight(80) layout.addWidget(self._text_edit) # 单行 + 发送 row = QHBoxLayout() self._line_edit = QLineEdit() self._line_edit.setPlaceholderText("快速输入(回车发送)...") self._line_edit.returnPressed.connect(self._on_send_line) row.addWidget(self._line_edit) self._send_btn = QPushButton("发送") self._send_btn.setFixedWidth(60) self._send_btn.clicked.connect(self._on_send_line) row.addWidget(self._send_btn) self._send_multi_btn = QPushButton("发送全文") self._send_multi_btn.setFixedWidth(70) self._send_multi_btn.clicked.connect(self._on_send_multi) row.addWidget(self._send_multi_btn) layout.addLayout(row) # 清空按钮 clear_btn = QPushButton("清空") clear_btn.setFixedWidth(60) clear_btn.clicked.connect(self._text_edit.clear) layout.addWidget(clear_btn, alignment=Qt.AlignRight) @pyqtSlot() def _on_send_line(self): text = self._line_edit.text().strip() if text: self._emit_input(text, metadata={"source": "ui_line"}) self._line_edit.clear() @pyqtSlot() def _on_send_multi(self): text = self._text_edit.toPlainText().strip() if text: self._emit_input(text, metadata={"source": "ui_multiline"}) def start(self) -> None: self._running = True self.show() def stop(self) -> None: self._running = False