156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
# -*- 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 |