Сделал лабы + контрольную

This commit is contained in:
2025-12-11 14:26:39 +03:00
parent fe28b9a58c
commit 7fd78c0de2
7 changed files with 2012 additions and 0 deletions

430
secondLabVisualProg/main.py Normal file
View File

@@ -0,0 +1,430 @@
import sys
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QComboBox, QSlider, QFileDialog,
QMessageBox, QColorDialog, QAction, QToolBar, QStatusBar
)
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import (
QImage, QPainter, QPen, QColor, QPixmap, QIcon
)
class Canvas(QWidget):
"""Холст для рисования"""
def __init__(self, parent=None):
super().__init__(parent)
self.setMinimumSize(800, 600)
# Создаём изображение для рисования
self.image = QImage(800, 600, QImage.Format_RGB32)
self.image.fill(Qt.white)
# Параметры рисования
self.drawing = False
self.last_point = QPoint()
self.pen_color = QColor(Qt.black)
self.pen_width = 3
self.pen_style = Qt.SolidLine
self.eraser_width = 20
# История для undo/redo
self.undo_stack = []
self.redo_stack = []
self.save_state() # Сохраняем начальное состояние
def save_state(self):
"""Сохранить текущее состояние для undo"""
self.undo_stack.append(self.image.copy())
self.redo_stack.clear() # Очищаем redo при новом действии
# Ограничиваем размер истории
if len(self.undo_stack) > 50:
self.undo_stack.pop(0)
def undo(self):
"""Отменить последнее действие"""
if len(self.undo_stack) > 1:
# Сохраняем текущее состояние в redo
self.redo_stack.append(self.undo_stack.pop())
# Восстанавливаем предыдущее состояние
self.image = self.undo_stack[-1].copy()
self.update()
return True
return False
def redo(self):
"""Повторить отменённое действие"""
if self.redo_stack:
state = self.redo_stack.pop()
self.undo_stack.append(state)
self.image = state.copy()
self.update()
return True
return False
def clear_canvas(self):
"""Очистить холст"""
self.save_state()
self.image.fill(Qt.white)
self.update()
def new_image(self, width=800, height=600):
"""Создать новое изображение"""
self.image = QImage(width, height, QImage.Format_RGB32)
self.image.fill(Qt.white)
self.undo_stack.clear()
self.redo_stack.clear()
self.save_state()
self.setMinimumSize(width, height)
self.update()
def load_image(self, file_path):
"""Загрузить изображение из файла"""
loaded_image = QImage(file_path)
if loaded_image.isNull():
return False
self.image = loaded_image.convertToFormat(QImage.Format_RGB32)
self.setMinimumSize(self.image.width(), self.image.height())
self.undo_stack.clear()
self.redo_stack.clear()
self.save_state()
self.update()
return True
def save_image(self, file_path):
"""Сохранить изображение в файл"""
return self.image.save(file_path)
def set_pen_color(self, color):
"""Установить цвет кисти"""
self.pen_color = color
def set_pen_width(self, width):
"""Установить толщину кисти"""
self.pen_width = width
def set_pen_style(self, style):
"""Установить стиль линии"""
self.pen_style = style
def set_eraser_width(self, width):
"""Установить размер ластика"""
self.eraser_width = width
def paintEvent(self, event):
"""Отрисовка холста"""
painter = QPainter(self)
painter.drawImage(0, 0, self.image)
def mousePressEvent(self, event):
"""Обработка нажатия кнопки мыши"""
if event.button() == Qt.LeftButton or event.button() == Qt.RightButton:
self.drawing = True
self.last_point = event.pos()
self.save_state() # Сохраняем состояние перед началом рисования
def mouseMoveEvent(self, event):
"""Обработка движения мыши"""
if self.drawing:
painter = QPainter(self.image)
if event.buttons() & Qt.RightButton:
# Правая кнопка - ластик (стираем белым цветом)
pen = QPen(Qt.white, self.eraser_width, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
else:
# Левая кнопка - рисуем
pen = QPen(self.pen_color, self.pen_width, self.pen_style, Qt.RoundCap, Qt.RoundJoin)
painter.setPen(pen)
painter.drawLine(self.last_point, event.pos())
self.last_point = event.pos()
self.update()
def mouseReleaseEvent(self, event):
"""Обработка отпускания кнопки мыши"""
if event.button() == Qt.LeftButton or event.button() == Qt.RightButton:
self.drawing = False
class MainWindow(QMainWindow):
"""Главное окно графического редактора"""
def __init__(self):
super().__init__()
self.setWindowTitle("Лабораторная работа №2 - Графический редактор")
self.setMinimumSize(1000, 750)
# Создаём холст
self.canvas = Canvas()
# Центральный виджет с холстом
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.addWidget(self.canvas)
# Создаём меню
self.create_menu()
# Создаём панель инструментов
self.create_toolbar()
# Создаём статусбар
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
self.statusBar.showMessage("Готово")
def create_menu(self):
"""Создание меню"""
menubar = self.menuBar()
# Меню "Файл"
file_menu = menubar.addMenu("Файл")
new_action = QAction("Создать", self)
new_action.setShortcut("Ctrl+N")
new_action.triggered.connect(self.new_file)
file_menu.addAction(new_action)
open_action = QAction("Открыть", self)
open_action.setShortcut("Ctrl+O")
open_action.triggered.connect(self.open_file)
file_menu.addAction(open_action)
save_action = QAction("Сохранить", self)
save_action.setShortcut("Ctrl+S")
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
save_as_action = QAction("Сохранить как...", self)
save_as_action.setShortcut("Ctrl+Shift+S")
save_as_action.triggered.connect(self.save_file_as)
file_menu.addAction(save_as_action)
file_menu.addSeparator()
exit_action = QAction("Выход", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# Меню "Редактирование"
edit_menu = menubar.addMenu("Редактирование")
undo_action = QAction("Отменить", self)
undo_action.setShortcut("Ctrl+Z")
undo_action.triggered.connect(self.undo)
edit_menu.addAction(undo_action)
redo_action = QAction("Повторить", self)
redo_action.setShortcut("Ctrl+Y")
redo_action.triggered.connect(self.redo)
edit_menu.addAction(redo_action)
edit_menu.addSeparator()
clear_action = QAction("Очистить", self)
clear_action.setShortcut("Ctrl+Delete")
clear_action.triggered.connect(self.clear_canvas)
edit_menu.addAction(clear_action)
# Меню "Инструменты"
tools_menu = menubar.addMenu("Инструменты")
color_action = QAction("Выбрать цвет...", self)
color_action.triggered.connect(self.choose_color)
tools_menu.addAction(color_action)
def create_toolbar(self):
"""Создание панели инструментов"""
toolbar = QToolBar("Инструменты")
toolbar.setMovable(False)
self.addToolBar(toolbar)
# Кнопки файловых операций
btn_new = QPushButton("Создать")
btn_new.clicked.connect(self.new_file)
toolbar.addWidget(btn_new)
btn_open = QPushButton("Открыть")
btn_open.clicked.connect(self.open_file)
toolbar.addWidget(btn_open)
btn_save = QPushButton("Сохранить")
btn_save.clicked.connect(self.save_file)
toolbar.addWidget(btn_save)
toolbar.addSeparator()
# Кнопки undo/redo
btn_undo = QPushButton("↶ Отменить")
btn_undo.clicked.connect(self.undo)
toolbar.addWidget(btn_undo)
btn_redo = QPushButton("↷ Повторить")
btn_redo.clicked.connect(self.redo)
toolbar.addWidget(btn_redo)
toolbar.addSeparator()
# Выбор цвета
toolbar.addWidget(QLabel("Цвет: "))
self.color_btn = QPushButton()
self.color_btn.setFixedSize(30, 30)
self.color_btn.setStyleSheet("background-color: black;")
self.color_btn.clicked.connect(self.choose_color)
toolbar.addWidget(self.color_btn)
toolbar.addSeparator()
# Толщина линии (TrackBar - QSlider)
toolbar.addWidget(QLabel("Толщина: "))
self.width_slider = QSlider(Qt.Horizontal)
self.width_slider.setMinimum(1)
self.width_slider.setMaximum(50)
self.width_slider.setValue(3)
self.width_slider.setFixedWidth(100)
self.width_slider.valueChanged.connect(self.change_pen_width)
toolbar.addWidget(self.width_slider)
self.width_label = QLabel("3")
toolbar.addWidget(self.width_label)
toolbar.addSeparator()
# Стиль линии (ComboBox)
toolbar.addWidget(QLabel("Стиль: "))
self.style_combo = QComboBox()
self.style_combo.addItem("Сплошная", Qt.SolidLine)
self.style_combo.addItem("Штриховая", Qt.DashLine)
self.style_combo.addItem("Пунктирная", Qt.DotLine)
self.style_combo.addItem("Штрих-пунктир", Qt.DashDotLine)
self.style_combo.addItem("Штрих-две точки", Qt.DashDotDotLine)
self.style_combo.currentIndexChanged.connect(self.change_pen_style)
toolbar.addWidget(self.style_combo)
toolbar.addSeparator()
# Размер ластика
toolbar.addWidget(QLabel("Ластик: "))
self.eraser_slider = QSlider(Qt.Horizontal)
self.eraser_slider.setMinimum(5)
self.eraser_slider.setMaximum(100)
self.eraser_slider.setValue(20)
self.eraser_slider.setFixedWidth(100)
self.eraser_slider.valueChanged.connect(self.change_eraser_width)
toolbar.addWidget(self.eraser_slider)
self.eraser_label = QLabel("20")
toolbar.addWidget(self.eraser_label)
toolbar.addSeparator()
# Кнопка очистки
btn_clear = QPushButton("Очистить")
btn_clear.clicked.connect(self.clear_canvas)
toolbar.addWidget(btn_clear)
def new_file(self):
"""Создать новое изображение"""
reply = QMessageBox.question(
self, "Новый файл",
"Создать новое изображение? Несохранённые изменения будут потеряны.",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
self.canvas.new_image()
self.current_file = None
self.statusBar.showMessage("Создано новое изображение")
def open_file(self):
"""Открыть изображение"""
file_path, _ = QFileDialog.getOpenFileName(
self, "Открыть изображение", "",
"Изображения (*.png *.jpg *.jpeg *.bmp *.gif);;Все файлы (*)"
)
if file_path:
if self.canvas.load_image(file_path):
self.current_file = file_path
self.statusBar.showMessage(f"Открыто: {file_path}")
else:
QMessageBox.critical(self, "Ошибка", "Не удалось открыть изображение")
def save_file(self):
"""Сохранить изображение"""
if hasattr(self, 'current_file') and self.current_file:
if self.canvas.save_image(self.current_file):
self.statusBar.showMessage(f"Сохранено: {self.current_file}")
else:
QMessageBox.critical(self, "Ошибка", "Не удалось сохранить изображение")
else:
self.save_file_as()
def save_file_as(self):
"""Сохранить изображение как..."""
file_path, _ = QFileDialog.getSaveFileName(
self, "Сохранить изображение", "",
"PNG (*.png);;JPEG (*.jpg *.jpeg);;BMP (*.bmp);;Все файлы (*)"
)
if file_path:
if self.canvas.save_image(file_path):
self.current_file = file_path
self.statusBar.showMessage(f"Сохранено: {file_path}")
else:
QMessageBox.critical(self, "Ошибка", "Не удалось сохранить изображение")
def undo(self):
"""Отменить действие"""
if self.canvas.undo():
self.statusBar.showMessage("Отменено")
else:
self.statusBar.showMessage("Нечего отменять")
def redo(self):
"""Повторить действие"""
if self.canvas.redo():
self.statusBar.showMessage("Повторено")
else:
self.statusBar.showMessage("Нечего повторять")
def clear_canvas(self):
"""Очистить холст"""
reply = QMessageBox.question(
self, "Очистить",
"Очистить холст?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
)
if reply == QMessageBox.Yes:
self.canvas.clear_canvas()
self.statusBar.showMessage("Холст очищен")
def choose_color(self):
"""Выбрать цвет кисти"""
color = QColorDialog.getColor(self.canvas.pen_color, self, "Выберите цвет")
if color.isValid():
self.canvas.set_pen_color(color)
self.color_btn.setStyleSheet(f"background-color: {color.name()};")
self.statusBar.showMessage(f"Выбран цвет: {color.name()}")
def change_pen_width(self, value):
"""Изменить толщину кисти"""
self.canvas.set_pen_width(value)
self.width_label.setText(str(value))
def change_pen_style(self, index):
"""Изменить стиль линии"""
style = self.style_combo.itemData(index)
self.canvas.set_pen_style(style)
def change_eraser_width(self, value):
"""Изменить размер ластика"""
self.canvas.set_eraser_width(value)
self.eraser_label.setText(str(value))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())