/
ProD
/
TestGenerator
Обзор
Документация
Войти
/
ProD
/
TestGenerator
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
gui.py
323 строки
14 KB
prod
Функции генерации перенесены в файл core.py
26 май 2026, 00:00
26 май 2026, 00:00
398bcf4
Код
Авторство
О чём код?
import sys import os from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QTextEdit, QLabel, QScrollArea, QFrame, QGridLayout, QLineEdit, QMessageBox, QSpinBox) from PyQt6.QtCore import Qt from PyQt6.QtGui import QFont, QPalette, QColor # Импортируем функции из основного модуля from core import read_text_from_docx, generate_test, save_test_to_docx class EditableCard(QWidget): """ Виджет для отображения и редактирования одного вопроса. """ def __init__(self, question_data, parent=None): super().__init__(parent) self.question_data = question_data self.init_ui() def init_ui(self): layout = QVBoxLayout() layout.setSpacing(10) # Заголовок карточки с номером вопроса self.header_label = QLabel(f"Вопрос {self.question_data.get('number', 'N')}:") self.header_label.setStyleSheet("font-weight: bold; font-size: 14px; color: #2c3e50; margin-bottom: 5px;") layout.addWidget(self.header_label) # Вопрос self.question_edit = QTextEdit() self.question_edit.setPlainText(self.question_data['question']) self.question_edit.setMaximumHeight(100) layout.addWidget(self.question_edit) # Варианты ответов options_layout = QGridLayout() self.option_edits = [] for i, opt in enumerate(self.question_data['options']): label = QLabel(f"{opt[0]}:") edit = QLineEdit(opt[3:]) # Убираем префикс "A) ", "B) " и т.д. self.option_edits.append(edit) options_layout.addWidget(label, i, 0) options_layout.addWidget(edit, i, 1) layout.addWidget(QLabel("Варианты ответов:")) layout.addLayout(options_layout) # Правильный ответ correct_layout = QHBoxLayout() correct_layout.addWidget(QLabel("Правильный ответ:")) self.correct_edit = QLineEdit(self.question_data['correct']) self.correct_edit.setFixedWidth(50) correct_layout.addWidget(self.correct_edit) layout.addLayout(correct_layout) self.setLayout(layout) # Стили self.setStyleSheet(""" QLabel { font-weight: bold; color: #2c3e50; } QTextEdit, QLineEdit { border: 1px solid #bdc3c7; border-radius: 4px; padding: 5px; background-color: white; } QLineEdit { height: 30px; } """) def get_data(self): """ Возвращает обновленные данные вопроса. """ # Обновляем данные из виджет��в self.question_data['question'] = self.question_edit.toPlainText().strip() # Обновляем варианты ответов for i, edit in enumerate(self.option_edits): letter = chr(65 + i) # A, B, C, D self.question_data['options'][i] = f"{letter}) {edit.text().strip()}" # Обновляем правильный ответ correct_text = self.correct_edit.text().strip().upper() if correct_text and correct_text[0] in 'ABCD': self.question_data['correct'] = correct_text[0] else: self.question_data['correct'] = 'A' # Значение по умолчанию return self.question_data class MainWindow(QMainWindow): """ Главное окно приложения. """ def __init__(self): super().__init__() self.text_content = "" self.questions = [] self.init_ui() def init_ui(self): self.setWindowTitle('Генератор тестов') self.setGeometry(100, 100, 1000, 700) # Центральный виджет central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QHBoxLayout(central_widget) # Левая панель (управление) left_panel = QWidget() left_layout = QVBoxLayout(left_panel) left_layout.setAlignment(Qt.AlignmentFlag.AlignTop) left_panel.setFixedWidth(250) left_panel.setStyleSheet("background-color: #ecf0f1; padding: 20px;") # Заголовок title = QLabel('Генератор тестов') title.setFont(QFont('Arial', 16, QFont.Weight.Bold)) title.setAlignment(Qt.AlignmentFlag.AlignCenter) title.setStyleSheet("color: #2c3e50; margin-bottom: 20px;") left_layout.addWidget(title) # Кнопка загрузки документа self.load_btn = QPushButton('Загрузить документ (.docx)') self.load_btn.clicked.connect(self.load_document) self.load_btn.setObjectName("action_button") left_layout.addWidget(self.load_btn) # Кнопка генерации теста self.generate_btn = QPushButton('Сгенерировать тест') self.generate_btn.clicked.connect(self.generate_test) self.generate_btn.setEnabled(False) self.generate_btn.setObjectName("action_button") left_layout.addWidget(self.generate_btn) # Поле для выбора количества вопросов count_layout = QHBoxLayout() count_layout.addWidget(QLabel("Количество вопросов:")) self.count_spinbox = QSpinBox() self.count_spinbox.setRange(1, 20) self.count_spinbox.setValue(5) self.count_spinbox.setFixedWidth(70) count_layout.addWidget(self.count_spinbox) left_layout.addLayout(count_layout) # Кнопка сохранения теста self.save_btn = QPushButton('Сохранить тест в .docx') self.save_btn.clicked.connect(self.save_test) self.save_btn.setEnabled(False) self.save_btn.setObjectName("action_button") left_layout.addWidget(self.save_btn) # Счетчик вопросов self.counter_label = QLabel('Вопросов: 0') self.counter_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.counter_label.setStyleSheet("font-style: italic; color: #7f8c8d; margin-top: 20px;") left_layout.addWidget(self.counter_label) # Добавляем левую панель в основной макет main_layout.addWidget(left_panel) # Правая панель (просмотр вопросов) right_panel = QScrollArea() right_panel.setWidgetResizable(True) right_panel.setStyleSheet("background-color: #ffffff;") self.questions_container = QWidget() self.questions_layout = QVBoxLayout(self.questions_container) self.questions_layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.questions_layout.setSpacing(15) right_panel.setWidget(self.questions_container) main_layout.addWidget(right_panel) # Стили для главного окна self.setStyleSheet(""" QMainWindow { background-color: #ffffff; } QPushButton { background-color: #ecf0f1; /* Светло-серый фон, как у левой панели */ color: black; /* Основной цвет текста для активных кнопок */ border: 2px solid #bdc3c7; /* Добавляем рамку */ padding: 12px; border-radius: 6px; font-size: 14px; /* Добавляем плавную анимацию для всех изменяемых свойств */ transition: all 0.3s ease; } QPushButton:hover { background-color: #d5dbdb; /* Более темный оттенок при наведении */ border-color: #95a5a6; /* Темнее рамка при наведении */ /* При наведении немного увеличиваем размер кнопки */ transform: scale(1.05); /* Добавляем тень для эффекта приподнятости */ box-shadow: 0 4px 8px rgba(0,0,0,0.2); } QPushButton:pressed { /* При нажатии кнопка немного утапливается */ transform: scale(0.98); box-shadow: 0 2px 4px rgba(0,0,0,0.2); } QPushButton:disabled { background-color: #95a5a6; color: #7f8c8d; /* Серый цвет текста для неактивных кнопок */ /* Убираем эффекты для неактивной кнопки */ transform: none; box-shadow: none; } """) def load_document(self): """ Загружает текст из выбранного .docx файла. """ file_path, _ = QFileDialog.getOpenFileName( self, "Открыть документ", "", "Документы Word (*.docx)") if file_path: try: self.text_content = read_text_from_docx(file_path) QMessageBox.information(self, "Успех", f"Документ загружен успешно!\n\n{self.text_content[:500]}...") self.generate_btn.setEnabled(True) except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить документ:\n{str(e)}") def generate_test(self): """ Генерирует тест на основе загруженного текста. """ if not self.text_content: QMessageBox.warning(self, "Предупреждение", "Сначала загрузите документ с текстом.") return # Получаем количество вопросов из спинбокса num_questions = self.count_spinbox.value() try: self.questions = generate_test(self.text_content, num_questions=num_questions) self.display_questions() self.save_btn.setEnabled(True) QMessageBox.information(self, "Успех", f"Тест из {len(self.questions)} вопросов сгенерирован!") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Ошибка при генерации теста:\n{str(e)}") def display_questions(self): """ Отображает сгенерированные вопросы в виде редактируемых карточек. """ # Очищаем предыдущие карточки for i in reversed(range(self.questions_layout.count())): widget = self.questions_layout.itemAt(i).widget() if widget: widget.setParent(None) # Добавляем новые карточки colors = [ "#f8f9fa", # Светло-серый "#e8f5e8", # Светло-зеленый "#e8f0fe", # Светло-голубой "#fef8e8", # Светло-желтый "#f8e8f5", # Светло-фиолетовый ] for i, q in enumerate(self.questions): # Добавляем номер вопроса в данные q['number'] = i + 1 card = EditableCard(q) card.setObjectName(f"question_card_{i}") color = colors[i % len(colors)] # Циклически выбираем цвет из списка card.setStyleSheet(""" QWidget#question_card_""" + str(i) + """ { background-color: """ + color + """; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px; margin: 5px 0px; } """) self.questions_layout.addWidget(card) # Обновляем счетчик self.counter_label.setText(f"Вопросов: {len(self.questions)}") def save_test(self): """ Сохраняет текущие (возможно, отредактированные) вопросы в .docx файл. """ if not self.questions: QMessageBox.warning(self, "Предупреждение", "Нечего сохранять. Сначала сгенерируйте тест.") return # Собираем обновленные данные из карточек updated_questions = [] for i in range(self.questions_layout.count()): widget = self.questions_layout.itemAt(i).widget() if isinstance(widget, EditableCard): updated_questions.append(widget.get_data()) # Диалог для выбора места сохранения file_path, _ = QFileDialog.getSaveFileName( self, "Сохранить тест", "test_output.docx", "Документы Word (*.docx)") if file_path: if not file_path.endswith('.docx'): file_path += '.docx' try: save_test_to_docx(updated_questions, file_path) QMessageBox.information(self, "Успех", f"Тест успешно сохранен в:\n{file_path}") except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить файл:\n{str(e)}") if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())