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
|