import sys import re import multiprocessing import sys import contextlib import io import json from scanVars import run_scan from VariableTable import VariableTableWidget, rows from PySide2.QtWidgets import ( QApplication, QWidget, QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton, QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit, QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView, QProgressBar ) from PySide2.QtGui import QTextCursor, QKeyEvent from PySide2.QtCore import Qt, QProcess, QObject, Signal, QTimer class EmittingStream(QObject): text_written = Signal(str) progress_updated = Signal(str, int, int) # bar_name, current, total def __init__(self): super().__init__() self._buffer = "" self._current_bar_name = None def write(self, text): self._buffer += text while '\n' in self._buffer: line, self._buffer = self._buffer.split('\n', 1) if line.startswith('Progress: "') and line.endswith('"'): bar_name = self._extract_bar_name(line) if bar_name: self._current_bar_name = bar_name continue # не выводим строку elif self._is_progress_line(line): current, total = self._extract_progress(line) if current is not None and total is not None: name = self._current_bar_name if self._current_bar_name else "Progress" self.progress_updated.emit(name, current, total) continue # не выводим строку # если не прогресс и не имя — выводим self.text_written.emit(line) def _extract_bar_name(self, line): # точно обрезаем вручную prefix = 'Progress: "' suffix = '"' if line.startswith(prefix) and line.endswith(suffix): return line[len(prefix):-1] return None def _is_progress_line(self, line): return re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line) is not None def _extract_progress(self, line): match = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line) if match: return int(match.group(1)), int(match.group(2)) return None, None def flush(self): if self._buffer: line = self._buffer.strip() if not self._is_progress_line(line) and not self._extract_bar_name(line): self.text_written.emit(line) self._buffer = "" class ProcessOutputWindow(QDialog): def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None): super().__init__() self.setWindowTitle("Поиск переменных...") self.resize(600, 480) self.setModal(True) self.proj_path = proj_path self.makefile_path = makefile_path self.xml_path = xml_path self._on_done_callback = on_done_callback self.layout = QVBoxLayout(self) self.output_edit = QTextEdit() self.output_edit.setReadOnly(True) self.layout.addWidget(self.output_edit) self.progress_label = QLabel("Progress:") self.layout.addWidget(self.progress_label) self.progress_bar = QProgressBar() self.progress_bar.setMinimum(0) self.progress_bar.setValue(0) self.layout.addWidget(self.progress_bar) self.btn_close = QPushButton("Закрыть") self.btn_close.setEnabled(False) self.layout.addWidget(self.btn_close) self.btn_close.clicked.connect(self.__handle_done) self.queue = None self.proc = None def start_scan(self): self.queue = multiprocessing.Queue() self.proc = multiprocessing.Process( target=run_scan_process, args=(self.proj_path, self.makefile_path, self.xml_path, self.queue), daemon=True) self.proc.start() self.timer = QTimer(self) self.timer.timeout.connect(self.poll_queue) self.timer.start(100) self.show() def poll_queue(self): try: while True: msg = self.queue.get_nowait() if msg is None: # Конец процесса self.btn_close.setEnabled(True) self.append_text("\n--- Анализ завершён ---") self.timer.stop() return # Пытаемся разобрать JSON-сообщение if isinstance(msg, str) and msg.startswith("PROGRESS_MSG:"): try: data = json.loads(msg[len("PROGRESS_MSG:"):]) self.update_progress(data["bar_name"], data["current"], data["total"]) except Exception: # Если не удалось распарсить, выводим как текст self.append_text(msg) else: self.append_text(msg) except Exception: pass # Очередь пустая def append_text(self, text): cursor = self.output_edit.textCursor() cursor.movePosition(QTextCursor.End) if not text.endswith('\n'): text += '\n' cursor.insertText(text) self.output_edit.setTextCursor(cursor) self.output_edit.ensureCursorVisible() def update_progress(self, bar_name, current, total): self.progress_label.setText(f"{bar_name}") self.progress_bar.setMaximum(total) self.progress_bar.setValue(current) def __handle_done(self): self.close() def closeEvent(self, event): if self.proc and self.proc.is_alive(): self.proc.terminate() self.proc.join() self.btn_close.setEnabled(True) self.append_text("Сканирование прервано.") def run_scan_process(proj_path, makefile_path, xml_path, queue): class QueueWriter(io.TextIOBase): def __init__(self): self._buffer = "" self._current_bar_name = None def write(self, txt): self._buffer += txt while '\n' in self._buffer: line, self._buffer = self._buffer.split('\n', 1) # Обработка прогресса if line.startswith('Progress: "') and line.endswith('"'): # Название прогресс-бара bar_name = line[len('Progress: "'):-1] self._current_bar_name = bar_name elif re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line): m = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line) if m: current = int(m.group(1)) total = int(m.group(2)) bar_name = self._current_bar_name or "Progress" # Отправляем специальное сообщение в очередь в формате JSON msg = { "bar_name": bar_name, "current": current, "total": total } queue.put("PROGRESS_MSG:" + json.dumps(msg)) else: # Обычный вывод queue.put(line) def flush(self): if self._buffer.strip(): queue.put(self._buffer.strip()) self._buffer = "" sys.stdout = QueueWriter() sys.stderr = sys.stdout try: run_scan(proj_path, makefile_path, xml_path) except Exception as e: queue.put(f"[ОШИБКА] {e}") finally: queue.put(None) # сигнал окончания