mirror of
https://github.com/pythonguis/pythonguis-examples.git
synced 2025-01-13 16:42:55 +08:00
b74592ea41
Break down examples into module files to make easier to read. Use full-definitions on Enums (PyQt6 compatible, better documenting). Add fixes for Qt6 versions & some general bugfixes.
275 lines
9.2 KiB
Python
275 lines
9.2 KiB
Python
import random
|
|
import sys
|
|
import types
|
|
|
|
import constants
|
|
import resources_rc
|
|
from canvas import Canvas
|
|
from MainWindow import Ui_MainWindow
|
|
from PyQt5.QtCore import QPoint, QRect, Qt, QTimer
|
|
from PyQt5.QtGui import (
|
|
QFont,
|
|
QIcon,
|
|
QImage,
|
|
QPixmap,
|
|
QTransform,
|
|
)
|
|
from PyQt5.QtWidgets import (
|
|
QApplication,
|
|
QButtonGroup,
|
|
QColorDialog,
|
|
QComboBox,
|
|
QFileDialog,
|
|
QFontComboBox,
|
|
QLabel,
|
|
QMainWindow,
|
|
QSlider,
|
|
)
|
|
|
|
# Not actually required, the import triggers this already.
|
|
resources_rc.qInitResources()
|
|
|
|
|
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setupUi(self)
|
|
|
|
# Replace canvas placeholder from QtDesigner.
|
|
self.horizontalLayout.removeWidget(self.canvas)
|
|
self.canvas = Canvas()
|
|
self.canvas.initialize()
|
|
# We need to enable mouse tracking to follow the mouse without the button pressed.
|
|
self.canvas.setMouseTracking(True)
|
|
# Enable focus to capture key inputs.
|
|
self.canvas.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
|
self.horizontalLayout.addWidget(self.canvas)
|
|
|
|
# Setup the mode buttons
|
|
mode_group = QButtonGroup(self)
|
|
mode_group.setExclusive(True)
|
|
|
|
for mode in constants.MODES:
|
|
btn = getattr(self, "%sButton" % mode)
|
|
btn.pressed.connect(lambda mode=mode: self.canvas.set_mode(mode))
|
|
mode_group.addButton(btn)
|
|
|
|
# Setup the color selection buttons.
|
|
self.primaryButton.pressed.connect(
|
|
lambda: self.choose_color(self.set_primary_color)
|
|
)
|
|
self.secondaryButton.pressed.connect(
|
|
lambda: self.choose_color(self.set_secondary_color)
|
|
)
|
|
|
|
# Initialize button colours.
|
|
for n, hex in enumerate(constants.COLORS, 1):
|
|
btn = getattr(self, "colorButton_%d" % n)
|
|
btn.setStyleSheet("QPushButton { background-color: %s; }" % hex)
|
|
btn.hex = hex # For use in the event below
|
|
|
|
def patch_mousePressEvent(self_, e):
|
|
if e.button() == Qt.MouseButton.LeftButton:
|
|
self.set_primary_color(self_.hex)
|
|
|
|
elif e.button() == Qt.MouseButton.RightButton:
|
|
self.set_secondary_color(self_.hex)
|
|
|
|
btn.mousePressEvent = types.MethodType(patch_mousePressEvent, btn)
|
|
|
|
# Setup up action signals
|
|
self.actionCopy.triggered.connect(self.copy_to_clipboard)
|
|
|
|
# Initialize animation timer.
|
|
self.timer = QTimer()
|
|
self.timer.timeout.connect(self.canvas.on_timer)
|
|
self.timer.setInterval(100)
|
|
self.timer.start()
|
|
|
|
# Setup to agree with Canvas.
|
|
self.set_primary_color("#000000")
|
|
self.set_secondary_color("#ffffff")
|
|
|
|
# Signals for canvas-initiated color changes (dropper).
|
|
self.canvas.primary_color_updated.connect(self.set_primary_color)
|
|
self.canvas.secondary_color_updated.connect(self.set_secondary_color)
|
|
|
|
# Setup the stamp state.
|
|
self.current_stamp_n = -1
|
|
self.next_stamp()
|
|
self.stampnextButton.pressed.connect(self.next_stamp)
|
|
|
|
# Menu options
|
|
self.actionNewImage.triggered.connect(self.canvas.initialize)
|
|
self.actionOpenImage.triggered.connect(self.open_file)
|
|
self.actionSaveImage.triggered.connect(self.save_file)
|
|
self.actionClearImage.triggered.connect(self.canvas.reset)
|
|
self.actionInvertColors.triggered.connect(self.invert)
|
|
self.actionFlipHorizontal.triggered.connect(self.flip_horizontal)
|
|
self.actionFlipVertical.triggered.connect(self.flip_vertical)
|
|
|
|
# Setup the drawing toolbar.
|
|
self.fontselect = QFontComboBox()
|
|
self.fontToolbar.addWidget(self.fontselect)
|
|
self.fontselect.currentFontChanged.connect(
|
|
lambda f: self.canvas.set_config("font", f)
|
|
)
|
|
self.fontselect.setCurrentFont(QFont("Times"))
|
|
|
|
self.fontsize = QComboBox()
|
|
self.fontsize.addItems([str(s) for s in constants.FONT_SIZES])
|
|
self.fontsize.currentTextChanged.connect(
|
|
lambda f: self.canvas.set_config("fontsize", int(f))
|
|
)
|
|
|
|
# Connect to the signal producing the text of the current selection. Convert the string to float
|
|
# and set as the pointsize. We could also use the index + retrieve from FONT_SIZES.
|
|
self.fontToolbar.addWidget(self.fontsize)
|
|
|
|
self.fontToolbar.addAction(self.actionBold)
|
|
self.actionBold.triggered.connect(lambda s: self.canvas.set_config("bold", s))
|
|
self.fontToolbar.addAction(self.actionItalic)
|
|
self.actionItalic.triggered.connect(
|
|
lambda s: self.canvas.set_config("italic", s)
|
|
)
|
|
self.fontToolbar.addAction(self.actionUnderline)
|
|
self.actionUnderline.triggered.connect(
|
|
lambda s: self.canvas.set_config("underline", s)
|
|
)
|
|
|
|
sizeicon = QLabel()
|
|
sizeicon.setPixmap(QPixmap(":/icons/border-weight.png"))
|
|
self.drawingToolbar.addWidget(sizeicon)
|
|
self.sizeselect = QSlider()
|
|
self.sizeselect.setRange(1, 20)
|
|
self.sizeselect.setOrientation(Qt.Orientation.Horizontal)
|
|
self.sizeselect.valueChanged.connect(
|
|
lambda s: self.canvas.set_config("size", s)
|
|
)
|
|
self.drawingToolbar.addWidget(self.sizeselect)
|
|
|
|
self.actionFillShapes.triggered.connect(
|
|
lambda s: self.canvas.set_config("fill", s)
|
|
)
|
|
self.drawingToolbar.addAction(self.actionFillShapes)
|
|
self.actionFillShapes.setChecked(True)
|
|
|
|
self.show()
|
|
|
|
def choose_color(self, callback):
|
|
dlg = QColorDialog()
|
|
if dlg.exec():
|
|
callback(dlg.selectedColor().name())
|
|
|
|
def set_primary_color(self, hex):
|
|
self.canvas.set_primary_color(hex)
|
|
self.primaryButton.setStyleSheet("QPushButton { background-color: %s; }" % hex)
|
|
|
|
def set_secondary_color(self, hex):
|
|
self.canvas.set_secondary_color(hex)
|
|
self.secondaryButton.setStyleSheet(
|
|
"QPushButton { background-color: %s; }" % hex
|
|
)
|
|
|
|
def next_stamp(self):
|
|
self.current_stamp_n += 1
|
|
if self.current_stamp_n >= len(constants.STAMPS):
|
|
self.current_stamp_n = 0
|
|
|
|
pixmap = QPixmap(constants.STAMPS[self.current_stamp_n])
|
|
self.stampnextButton.setIcon(QIcon(pixmap))
|
|
|
|
self.canvas.current_stamp = pixmap
|
|
|
|
def copy_to_clipboard(self):
|
|
clipboard = QApplication.clipboard()
|
|
|
|
if self.canvas.mode == "selectrect" and self.canvas.locked:
|
|
clipboard.setPixmap(self.canvas.selectrect_copy())
|
|
|
|
elif self.canvas.mode == "selectpoly" and self.canvas.locked:
|
|
clipboard.setPixmap(self.canvas.selectpoly_copy())
|
|
|
|
else:
|
|
clipboard.setPixmap(self.canvas.pixmap())
|
|
|
|
def open_file(self):
|
|
"""
|
|
Open image file for editing, scaling the smaller dimension and cropping the remainder.
|
|
:return:
|
|
"""
|
|
path, _ = QFileDialog.getOpenFileName(
|
|
self,
|
|
"Open file",
|
|
"",
|
|
"PNG image files (*.png); JPEG image files (*jpg); All files (*.*)",
|
|
)
|
|
|
|
if path:
|
|
pixmap = QPixmap()
|
|
pixmap.load(path)
|
|
|
|
# We need to crop down to the size of our canvas. Get the size of the loaded image.
|
|
iw = pixmap.width()
|
|
ih = pixmap.height()
|
|
|
|
# Get the size of the space we're filling.
|
|
cw, ch = constants.CANVAS_DIMENSIONS
|
|
|
|
if iw / cw < ih / ch: # The height is relatively bigger than the width.
|
|
pixmap = pixmap.scaledToWidth(cw)
|
|
hoff = (pixmap.height() - ch) // 2
|
|
pixmap = pixmap.copy(
|
|
QRect(
|
|
QPoint(0, hoff),
|
|
QPoint(cw, pixmap.height() - hoff),
|
|
)
|
|
)
|
|
|
|
elif iw / cw > ih / ch: # The height is relatively bigger than the width.
|
|
pixmap = pixmap.scaledToHeight(ch)
|
|
woff = (pixmap.width() - cw) // 2
|
|
pixmap = pixmap.copy(
|
|
QRect(
|
|
QPoint(woff, 0),
|
|
QPoint(pixmap.width() - woff, ch),
|
|
)
|
|
)
|
|
|
|
self.canvas.setPixmap(pixmap)
|
|
|
|
def save_file(self):
|
|
"""
|
|
Save active canvas to image file.
|
|
:return:
|
|
"""
|
|
path, _ = QFileDialog.getSaveFileName(
|
|
self, "Save file", "", "PNG Image file (*.png)"
|
|
)
|
|
|
|
if path:
|
|
pixmap = self.canvas.pixmap()
|
|
pixmap.save(path, "PNG")
|
|
|
|
def invert(self):
|
|
img = QImage(self.canvas.pixmap())
|
|
img.invertPixels()
|
|
pixmap = QPixmap()
|
|
pixmap.convertFromImage(img)
|
|
self.canvas.setPixmap(pixmap)
|
|
|
|
def flip_horizontal(self):
|
|
pixmap = self.canvas.pixmap()
|
|
self.canvas.setPixmap(pixmap.transformed(QTransform().scale(-1, 1)))
|
|
|
|
def flip_vertical(self):
|
|
pixmap = self.canvas.pixmap()
|
|
self.canvas.setPixmap(pixmap.transformed(QTransform().scale(1, -1)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
app.setWindowIcon(QIcon(":/icons/piecasso.ico"))
|
|
window = MainWindow()
|
|
app.exec_()
|