PyQt4 的图片切割编辑器

一、 编辑器功能明确

允许用户加载图片、选择切割模式、对切割后的图片片段进行操作(如移动、复制、粘贴、删除等),并支持撤销和重做操作。

环境:Py2.7  PyQt 4.11

二、导入模块介绍

sys: 用于访问与 Python 解释器强相关的变量和函数。
os: 提供操作系统相关功能,如文件路径操作。
random: 用于生成随机数,主要用于自动保存文件名。
json: 用于数据序列化和反序列化,方便保存和加载编辑状态。
glob: 用于文件路径匹配,例如检查自动保存文件是否存在。
PyQt4: 用于创建图形用户界面(GUI)。

三、构造函数分析

def __init__(self):super(MainWindow, self).__init__()  # 调用父类的构造函数self.setWindowTitle('Picture Cutting Editor')  # 设置窗口标题self.undoStack = QtGui.QUndoStack(self)  # 创建一个QUndoStack对象,用于撤销和重做self.initUI()  # 初始化界面self.initSignal()  # 初始化信号连接self.image = QtGui.QPixmap()  # 初始化图片对象self.center()  # 居中显示窗口self.xx = 5self.yy = 5self._m_cut_type_str = ''  # 初始化切割模式字符串self.curFile = ''  # 当前文件路径self.locationList = []  # 存储图片位置的列表self.cuttingWidgetList = []  # 存储切割小部件的列表self.posList = []  # 存储位置信息的列表self.location_pixmap_dict = {}  # 存储位置和图片的字典self.data = [...]  # 存储数据的列表self.auto_save_data = [...]  # 自动保存数据self.savepath_autopath_dict = {}  # 保存路径和自动保存路径的连接字典self.autoName = ''  # 自动保存文件名

self.undoStack: 用于支持撤销和重做操作。
self.image: 存储当前加载的图片。
self.data: 存储编辑状态的数据,方便保存和加载。
self.auto_save_data: 存储自动保存的数据,防止数据丢失。

四、扩展的类

  1. QtGui.QUndoStack(self) # 创建一个QUndoStack对象,用于撤销和重做
  2. QtGui.QPixmap() # 存储当前加载的图片,QtGui.QPixmap()可以从文件、资源或内存中加载图像,并可以用于显示图像
  3. QtGui.QFileDialog.getOpenFileName: 打开文件选择对话框,让用户选择图片文件。

五、小技巧

1. & 符号

fileMenu = menubar.addMenu(u'文件(&F)') # 添加“文件”菜单
# 在 PyQt 中,菜单项或按钮的文本中添加 & 符号是一种特殊的语法,用于定义快捷键(Accelerator)。& 符号后面的字母会变成该菜单项或按钮的快捷键

2. undoStack

undoStack的宏操作(相当于数据库中的事务):将多个操作绑定在一起,一并撤销:
有了undoStack来实现指令的撤销和恢复真的太容易了。

undoStack.beginMacro('bind1'):开始一个宏操作,允许将多个命令组合在一起。
undoStack.push(self.cmd1):将命令推入撤销栈,可以推多个命令进入栈中。
undoStack.endMacro():结束宏操作,将所有推入的命令组合为一个。

 3. 创建一个新的编辑窗口(MainWindow 的实例),并将其显示在屏幕上(QT多窗口模板):

    def newImageFile(self):newWindow = MainWindow()  # 创建一个新的 MainWindow 实例# 将新窗口实例添加到 windowList 中,以便程序可以统一管理所有打开的窗口。(这是在做商城大作业时的收获,无论是红点对象还是复用滚动框中的滚动对象都建议使用一个列表保存起来。(1. 避免重复创建,减少性能消耗  2.统一管理)MainWindow.windowList.append(newWindow)  # 将新窗口添加到窗口列表中newWindow.move(self.x() + 50, self.y() + 50)  # 设置新窗口的位置newWindow.show()  # 显示新窗口

 4. Py2获取当前年月日时分秒,并生成一个年-月-日-时-分-秒的字符串

# 案例:根据当前时间生成新的自动保存文件名from datetime import datetime# 获取当前时间now = datetime.now()# 格式化为 "年-月-日-时-分-秒" 的字符串formatted_time = now.strftime("%Y-%m-%d-%H-%M-%S")autoName = 'auto_save/' + formatted_time + '.json'

 5. Py2 解析Json文件:

        with open(fileName) as fp:load_data = json.load(fp)  # 使用 json.load 从文件中加载 JSON 数据,并将其存储到 self.data 中。

六、图片切割编辑器案例 

# -*- coding: utf-8 -*-
import sys
import os
import random
import json
import glob
from PyQt4 import QtGui
from PyQt4 import QtCore#定义主窗口类
class MainWindow(QtGui.QMainWindow):windowList = []#用于存储所有打开的窗口def __init__(self):super(MainWindow, self).__init__()  #调用父类的构造函数self.setWindowTitle('Picture Cutting Editor')  # 设置窗口标题self.undoStack = QtGui.QUndoStack(self)  # 创建一个QUndoStack对象,用于撤销和重做self.initUI()  # 初始化界面,设置菜单、工具栏等。self.initSignal()  # 初始化信号连接,绑定事件处理函数。self.image = QtGui.QPixmap() # 存储当前加载的图片,QtGui.QPixmap()可以从文件、资源或内存中加载图像,并可以用于显示图像self.center()  # 居中显示窗口self.xx = 5self.yy = 5  # 初始化切割网格的行列数,默认为5x5。self._m_cut_type_str = ''  # 初始化切割模式字符串,用于描述当前的切割方式。self.curFile = ''  # 当前编辑的文件路径。self.locationList = []  # 存储图片片段的位置信息。self.cuttingWidgetList = []  # 存储切割后的小部件(如图片片段)。self.posList = []  # 存储图片片段的位置信息。self.location_pixmap_dict = {}  # 存储位置与图片片段的映射关系。self.data = [  # 存储编辑状态的数据,方便保存和加载。{'path': [],  # 存储文件路径'type': [],  # 存储切割类型'location_list' : [],  # 存储位置列表'square_list' : [],  # 存储切割小部件列表'pos_list':[],  # 存储位置信息列表}]self.auto_save_data = [  # 存储自动保存的数据,防止数据丢失。{'autoSavePath':[],  # 自动保存路径列表'connectDict':{},  # 自动保存路径和文件路径的连接字典}]self.savepath_autopath_dict = {}  # 保存路径和自动保存路径的连接字典self.autoName = ''  # 自动保存文件名def init(self):# 初始化、清空数据结构,重置状态self.locationList = []  # 清空图片位置信息列表self.cuttingWidgetList = []  # 清空切割小部件列表self.posList = []  # 清空位置信息列表self.location_pixmap_dict = {}  # 清空位置与图片片段的映射字典self.data = [  # 重置存储编辑状态的数据结构{'path':[],  # 清空文件路径列表'type':[],  # 清空切割类型列表'location_list':[],  # 清空位置列表'square_list':[],  # 清空切割小部件列表'pos_list':[],   # 清空位置信息列表}]self.auto_save_data = [   # 重置自动保存数据结构{'autoSavePath':[],  # 清空自动保存路径列表'connectDict':{} # 清空自动保存路径与文件路径的映射字典}]self.savepath_autopath_dict = {}  # 清空保存路径与自动保存路径的映射字典self.piecesList.clear()  # 清空图片片段列表组件self.piecesList.locationList = []  # 清空图片片段位置列表self.picWidget.cuttingWidgetList = []  # 清空图片编辑区域的切割小部件列表self.picWidget.pixmapList = []  # 清空图片编辑区域的图片片段列表self.picWidget.posList = []  # 清空图片编辑区域的位置信息列表self.picWidget.update()  # 更新图片编辑区域的显示# 初始化用户界面(UI)def initUI(self):self.statusBar()  # 创建状态栏,用于显示状态信息self.setFocus()  # 设置窗口获取焦点, 确保窗口获取焦点。self.setWidgets()  # 调用setWidgets方法,设置主窗口的中心部件# 创建菜单栏和导航栏的选项openFile = QtGui.QAction(QtGui.QIcon('resources/openFile.png'), 'Open', self)openFile.setShortcut('Ctrl+O')  # 设置快捷键openFile.setStatusTip('Open new file')  # 设置状态栏提示self.connect(openFile, QtCore.SIGNAL('triggered()'), self.openFile)  # 将动作与槽函数连接newFile = QtGui.QAction(QtGui.QIcon('resources/new.png'), 'New', self)newFile.setShortcut('Ctrl+N')newFile.setStatusTip('New file')self.connect(newFile, QtCore.SIGNAL('triggered()'), self.newImageFile)# 导入图片,选择网格大小importImage = QtGui.QAction('Import Image', self)importImage.setStatusTip('Import image')self.connect(importImage, QtCore.SIGNAL('triggered()'), self.showDialog)saveFile = QtGui.QAction(QtGui.QIcon('resources/save.png'), 'Save', self)saveFile.setShortcut('Ctrl+S')saveFile.setStatusTip('Save file')self.connect(saveFile, QtCore.SIGNAL('triggered()'), self.saveImageFile)save_as = QtGui.QAction(QtGui.QIcon('resources/save_as.png'), 'Save As', self)save_as.setShortcut('Ctrl+Shift+S')save_as.setStatusTip('Save file as')self.connect(save_as, QtCore.SIGNAL('triggered()'), self.saveImageAs)undo = QtGui.QAction(QtGui.QIcon('resources/Undo.png'), 'Undo', self)undo.setShortcut('Ctrl+Z')undo.setStatusTip('Undo')self.connect(undo, QtCore.SIGNAL('triggered()'), self.undoImage)redo = QtGui.QAction(QtGui.QIcon('resources/Redo.png'), 'Redo', self)redo.setShortcut('Ctrl+Y')redo.setStatusTip('Redo')self.connect(redo, QtCore.SIGNAL('triggered()'), self.redoImage)cut =QtGui.QAction(QtGui.QIcon('resources/cut.png'), 'Cut', self)cut.setShortcut('Ctrl+X')cut.setStatusTip('Cut')self.connect(cut, QtCore.SIGNAL('triggered()'), self.cutImage)copyImage =QtGui.QAction(QtGui.QIcon('resources/copy.png'),'Copy',self)copyImage.setShortcut('Ctrl+C')copyImage.setStatusTip('Copy')self.connect(copyImage, QtCore.SIGNAL('triggered()'),self.copyImage)pasteImage =QtGui.QAction(QtGui.QIcon('resources/paste.png'), 'Paste', self)pasteImage.setShortcut('Ctrl+V')pasteImage.setStatusTip('Paste')self.connect(pasteImage,QtCore.SIGNAL('triggered()'),self.pasteImage)deleteImage =QtGui.QAction(QtGui.QIcon('resources/delete.png'), 'Delete', self)deleteImage.setShortcut('Delete')deleteImage.setStatusTip('delete')self.connect(deleteImage,QtCore.SIGNAL('triggered()'), self.deleteImage)# 创建菜单栏menubar = self.menuBar()   # 获取窗口的菜单栏# 文件fileMenu = menubar.addMenu(u'文件(&F)')  # 添加“文件”菜单# 在 PyQt 中,菜单项或按钮的文本中添加 & 符号是一种特殊的语法,用于定义快捷键(Accelerator)。& 符号后面的字母会变成该菜单项或按钮的快捷键fileMenu.addAction(openFile) # 添加“打开”选项fileMenu.addAction(newFile)fileMenu.addAction(importImage)fileMenu.addAction(saveFile)fileMenu.addAction(save_as)# 编辑editMenu = menubar.addMenu(u'编辑(&E)')  # 添加“编辑”菜单editMenu.addAction(undo)  # 添加“撤销”选项editMenu.addAction(redo)editMenu.addAction(cut)editMenu.addAction(copyImage)editMenu.addAction(pasteImage)# 网格(设置网格大小 和 长宽)# 创建工具栏self.Toolbar = self.addToolBar(u'工具栏(&T)')  # 添加工具栏self.Toolbar.addAction(undo)self.Toolbar.addAction(redo)self.Toolbar.addAction(openFile)   # 添加“打开”按钮self.Toolbar.addAction(newFile)self.Toolbar.addAction(saveFile)self.Toolbar.addAction(save_as)self.Toolbar.addAction(cut)self.Toolbar.addAction(copyImage)self.Toolbar.addAction(pasteImage)self.Toolbar.addAction(deleteImage)# 使用 self.setGeometry() 设置窗口的初始位置和大小,确保窗口在屏幕上合适的位置显示。self.setGeometry(300, 300, 1024, 768)  # 设置窗口的位置和大小# 参数解释:窗口左上角的X坐标、Y坐标,窗口宽度、窗口高度# 初始化信号连接: 将自定义信号连接到对应的槽函数,处理拖拽、保存等操作。# 信号与槽是 PyQt 中实现组件间通信的核心机制,通过这种方式可以将组件的事件(如按钮点击、信号发射)与相应的处理函数(槽函数)绑定起来,从而实现交互逻辑。def initSignal(self):# piecesList: 自定义组件,用于显示图片片段的列表。self.piecesList.upSignal.connect(self.upListWidget)  # 自定义信号,。self.piecesList.downSignal.connect(self.downListWidget)  # 自定义信号,。self.piecesList.saveSignal.connect(self.curSave)  # 自定义信号,。# picWidget: 自定义组件,用于显示和编辑图片的区域。self.picWidget.upSignal.connect(self.upWidget)  # 自定义信号,。self.picWidget.downSignal.connect(self.downWidget)self.picWidget.saveSignal.connect(self.curSave)# 处理用户从列表中拖拽出图片片段的操作。# 使用 QUndoStack 的宏功能,将“移除”和“插入”两个操作组合在一起,以便用户可以通过一次撤销操作撤销整个拖拽过程。def upListWidget(self):item = self.piecesList.currentItem()  # 获取当前选中的图片片段row = self.piecesList.row(item)  # 获取该项在列表中的行号command=CommandDragFromListWidget(self.piecesList,item,row)  # 自定义的撤销命令类,用于处理从列表中拖拽图片片段的操作。self.cmd = command  # 将命令对象存储在类变量中,供后续使用# 处理用户将图片片段拖拽回列表的操作。def downListWidget(self):command =CommandDragToListWidget(self.piecesList)  # 一个自定义的撤销命令类,用于处理将图片片段拖拽到列表中的操作。它包含将图片片段插入到新位置的逻辑。self.undoStack.beginMacro('bind2')  # 开始一个宏操作,允许将多个命令组合在一起。这样,用户可以通过一次撤销操作撤销整个宏操作,而不是逐个撤销每个命令。self.undoStack.push(self.cmd)  # 将之前在 upListWidget 中创建的命令推入撤销栈。这个命令表示从列表中移除图片片段的操作。self.undoStack.push(command)  # 将当前命令(表示将图片片段插入到列表的操作)推入撤销栈。self.undoStack.endMacro( )  # 结束宏操作,将所有推入的命令组合为一个完整的操作。# 处理用户从图片编辑区域中拖拽出图片片段的操作。def upWidget(self):command = CommandDragFromWidget(self.picWidget)  # 一个自定义的撤销命令类,用于处理从图片编辑区域(picWidget)中拖拽出图片片段的操作。self.cmd = command  # 将创建的命令对象存储在类变量 self.cmd 中,以便后续的 downWidget 方法可以使用它。# 处理用户将图片片段拖拽回图片编辑区域的操作。def downWidget(self):command = CommandDragToWidget(self.picWidget)  # 自定义的撤销命令类,用于处理将图片片段拖拽到图片编辑区域(picWidget)中的操作。self.undoStack.beginMacro('bind1')  # 开始一个宏操作,允许将多个命令组合在一起。self.undoStack.push(self.cmd)  # 将之前在 upWidget 中创建的命令推入撤销栈。self.undoStack.push(command)  # 将当前命令(表示将图片片段插入到图片编辑区域的操作)推入撤销栈。self.undoStack.endMacro()  # 结束宏操作,将所有推入的命令组合为一个完整的操作。# 实现了一个对话框,用于让用户选择图片的切割模式def showDialog(self):  self.dialog = QtGui.QDialog()  # 创建一个对话框窗口label = QtGui.QLabel(u'请选择切割模式', self.dialog)  # 创建一个标签,提示用户选择切割模式combo = QtGui.QComboBox(self.dialog)  # 创建一个下拉组合框,用于选择切割模式combo.addItem('8 * 8')  # 向组合框中添加“8 * 8”选项combo.addItem('8 * 4')label.move(50, 30)  # 设置标签的位置combo.move(50, 50)  # 设置组合框的位置self.dialog.setVisible(True)  # 显示对话框# 将下拉框的选项连接到信号与槽self.connect(combo, QtCore.SIGNAL('activated(QString)'), self.onActivated)  # 根据选择设置切割方式self.connect(combo, QtCore.SIGNAL('activated(QString)'), self.openImage)  # 加载切割后的图片def onActivated(self, text):if text == '8 * 8':  #  如果用户选择了“8 * 8”,则设置 self.xx 和 self.yy 为 8,表示切割图片为 8x8 张。self.xx = 8self.yy = 8self._m_cut_type_str = '8 * 8'  # 选择的模式存储在 self._m_cut_type_str 中,用于后续的逻辑处理。elif text == '8 * 4':self.xx = 8self.yy = 4self._m_cut_type_str = '8 * 4'self.dialog.setVisible(False)  # 关闭对话框。def openImage(self):  # 如果用户选择了文件autoName = 'auto_save/' + str(random.randint(1, 1000))+'.json'  # 生成随机的自动保存文件名self.autoName = autoName  # 将自动生成的文件名存储在类变量中# QtGui.QFileDialog.getOpenFileName: 打开文件选择对话框,让用户选择图片文件。filename = QtGui.QFileDialog.getOpenFileName(self, "Open Image", 'resources', "Image Files (*.png *.jpg *.bmp)")if filename :  # 调用loadImage方法加载图片并初始化编辑状态self.loadImage(filename)  # self.loadImage: 加载图片并初始化编辑状态。def loadImage(self, filename):self.init()  # 初始化或重置程序状态,清空之前的编辑数据self.undoStack.clear()  # 清空撤销栈,移除之前的撤销和重做记录image = QtGui.QPixmap(filename)  # 加载图像文件, QPixmap 是一个轻量级的图像处理类,主要用于在屏幕上显示图像。self.image = image  # 将加载的图像存储到类变量中self.setupPic(self.xx, self.yy)  # self.setupPic: 根据切割模式(如 8x8 或 8x4)对图片进行切割,并初始化显示。self.data[0]['path'].append(str(filename))  # 将图片路径保存起来# 创建一个新的图片编辑窗口(MainWindow 的实例),并将其显示在屏幕上。def newImageFile(self):newWindow = MainWindow()  # 创建一个新的 MainWindow 实例# 将新窗口实例添加到 windowList 中,以便程序可以统一管理所有打开的窗口。MainWindow.windowList.append(newWindow)  # 将新窗口添加到窗口列表中newWindow.move(self.x() + 50, self.y() + 50)  # 设置新窗口的位置newWindow.show()  # 显示新窗口def openFile(self):# 检查自动保存文件:if glob.glob('auto_save/autosave.json')!= []:  # 使用 glob.glob 检查是否存在自动保存文件autosave.jsonwith open('auto_save/autosave.json') as fp:load_data = json.load(fp)self.auto_save_data = load_data  # 如果存在,加载该文件并将其内容存储到 self.auto_save_data 中。# 根据当前时间生成新的自动保存文件名from datetime import datetime# 获取当前时间now = datetime.now()# 格式化为 "年-月-日-时-分-秒" 的字符串formatted_time = now.strftime("%Y-%m-%d-%H-%M-%S")autoName = 'auto_save/' + formatted_time + '.json'self.autoName = autoName# 打开文件选择对话框:fileName = QtGui.QFileDialog.getOpenFileName(self, directory='save')  # 使用 QtGui.QFileDialog.getOpenFileName 打开文件选择对话框,让用户选择一个保存的编辑状态文件。if fileName:# 如果选择的文件在自动保存数据中存在对应的自动保存路径,弹出一个消息框询问用户是否加载自动保存的数据。# 如果用户选择“是”,则加载自动保存的数据并覆盖原文件。if self.auto_save_data[0]['connectDict'].has_key(str(fileName)):rep = QtGui.QMessageBox.question(self, u'存在上一局游戏', u"该图片被打开过,是否加载上一局游戏?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)if rep == QtGui.QMessageBox.Yes:with open(self.auto_save_data[0]['connectDict'][str(fileName)]) as fpp:data =json.load(fpp)with open(fileName,'w')as fp:json.dump(data,fp,indent=2)self.loadFile(fileName)  # 加载文件def loadFile(self,fileName):# 初始化状态, 清空当前的编辑状态和撤销栈。self.init()self.undoStack.clear()# 加载用户选择的文件内容,并将其存储到 self.data 中。with open(fileName) as fp:load_data = json.load(fp)  # 使用 json.load 从文件中加载 JSON 数据,并将其存储到 self.data 中。self.data = load_dataself.image = QtGui.QPixmap(QtCore.QString(self.data[0]['path'][0]))  # 使用 QtGui.QPixmap 加载图片文件路径(从 self.data 中获取)。(QtCore.QString 是 PyQt4 中用于处理字符串的类)#  根据保存的切割模式设置 self.xx 和 self.yy。if self.data[0]['type'][0]== '8 * 8':self.xx=8self.yy=8if self.data[0]['type'][0]=='8 * 4':self.xx= 8self.yy =4# 切图: 先将图片并缩放到固定大小(400x400, 再切割图片# 使用 scaled 方法将图片缩放到固定大小(400x400),忽略纵横比,并使用平滑变换以获得更好的视觉效果。size = min(self.image.width(), self.image.height())self.image = self.image.copy((self.image.width()-size)/2, (self.image.height() - size)/2, size, size).scaled(400, 400, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation)self.piecesList.clear()  # 清空图片片段列表,移除所有之前的图片片段。# 根据切割模式(self.xx 和 self.yy),将图片分割成多个片段。每个片段的大小为 400 / self.xx 和 400 / self.yy。将每个片段存储到 self.location_pixmap_dict 中,键为片段的位置(如 (1, 1) 表示左上角的片段)。for y in range(self.xx):for x in range(self.yy):pieceImage = self.image.copy(x*400/self.yy, y*400/self.xx, 400/self.xx, 400/self.yy)self.location_pixmap_dict[(x+1, y+1)] = pieceImage# 加载数据: 从 self.data 中恢复位置列表、切割小部件列表和位置信息列表。locationList = self.data[0]['location_list']print len(self.data[0]['location_list'])cuttingWidgetList = self.data[0]['square_list']posList = self.data[0]['pos_list']# 遍历位置列表,将每个位置恢复为 QtCore.QPoint 对象,并存储到 self.locationList 中。# QtCore.QPoint 是 PyQt4 中的一个类,用于表示二维空间中的一个点。它由两个主要属性组成:x 和 y,分别表示点的横坐标和纵坐标。for i in range(len(locationList)):x = locationList[i][0]y = locationList[i][1]self.locationList.append(QtCore.QPoint(x, y))# 遍历切割小部件列表,将每个小部件恢复为 QtCore.QRect 对象,并存储到 self.cuttingWidgetList 中。# QtCore.QRect 是 PyQt4 中的一个类,用于表示一个矩形区域。它通常用于定义控件的位置和大小,或者在绘图和布局管理中指定一个矩形范围。for j in range(len(cuttingWidgetList)):x = cuttingWidgetList[j][0]y = cuttingWidgetList[j][1]width = cuttingWidgetList[j][2]height = cuttingWidgetList[j][3]self.cuttingWidgetList.append(QtCore.QRect(x, y, width, height))# 遍历位置信息列表,将每个位置恢复为 QtCore.QPoint 对象,并存储到 self.posList 中。for k in range(len(posList)):x = posList[k][0]y = posList[k][1]self.posList.append(QtCore.QPoint(x, y))# 清空图片片段列表,并根据恢复的数据重新添加图片片段。# 遍历位置列表,根据每个位置从 self.location_pixmap_dict 中获取对应的图片片段,并将其添加到图片片段列表中。cnt = 0for location in self.locationList:pixmap = self.location_pixmap_dict[(location.x(), location.y())]self.piecesList.addPiece(pixmap, location)# 根据位置信息列表生成图片片段列表。pixmapList = []for pos in self.posList:pixmap_r = self.location_pixmap_dict[(pos.x(), pos.y())]pixmapList.append(pixmap_r)# 设置图片编辑区域# 将恢复的切割小部件列表、位置信息列表和图片片段列表设置到图片编辑区域。self.picWidget.cuttingWidgetList = self.cuttingWidgetListself.picWidget.posList = self.posListself.picWidget.pixmapList = pixmapListself.picWidget.update()  # 调用 update() 方法,重新绘制图片编辑区域。# 调用 setCurrentFile 方法,设置当前文件路径并更新窗口标题。self.setCurrentFile(fileName)def saveImageFile(self):# 如果 self.curFile(当前文件路径)不为空,调用 self.saveFile(self.curFile) 方法,将当前编辑状态保存到已有的文件中。if self.curFile:return self.saveFile(self.curFile)return self.saveImageAs()def saveImageAs(self):# 使用 QtGui.QFileDialog.getSaveFileName 打开文件保存对话框,提示用户选择保存路径。fileName = QtGui.QFileDialog.getSaveFileName(self, directory='save')# 如果用户选择了文件路径(fileName 不为空),调用 self.saveFile(fileName) 方法保存文件。if fileName:return self.saveFile(fileName)return Falsedef saveFile(self,fileName):# 清空保存的数据结构,准备重新填充当前编辑状态。self.data[0]['location_list'] = []self.data[0]['square_list'] = []self.data[0]['pos_list'] = []# 遍历 self.locationList,将每个位置(QPoint)的坐标保存到 self.data[0]['location_list'] 中。for i in range(len(self.locationList)):self.data[0]['location_list'].append([self.locationList[i].x(), self.locationList[i].y()])# 遍历 self.cuttingWidgetList,将每个小部件(QRect)的坐标和尺寸保存到 self.data[0]['square_list'] 中。for j in range(len(self.cuttingWidgetList)):self.data[0]['square_list'].append([self.cuttingWidgetList[j].x(), self.cuttingWidgetList[j].y(), self.cuttingWidgetList[j].width(), self.cuttingWidgetList[j].height()])# 遍历 self.posList,将每个位置的坐标保存到 self.data[0]['pos_list'] 中。for k in range(len(self.posList)):self.data[0][ 'pos_list'].append([self.posList[k].x(), self.posList[k].y()])# 如果 self.data[0]['type'] 为空,添加当前切割模式(self._m_cut_type_str)。if self.data[0]['type']== []:self.data[0]['type'].append(self._m_cut_type_str)else :self.data[0]['type'][0] =  self._m_cut_type_str  # 如果不为空,更新当前切割模式。# 使用 json.dump 将当前编辑状态(self.data)序列化为 JSON 格式,并保存到指定文件中。with open(fileName,'w') as fp:json.dump(self.data, fp, indent=2)# 调用 setCurrentFile 方法,更新当前文件路径并刷新窗口标题。self.setCurrentFile(fileName)# 返回 True,表示保存操作成功。return Truedef setCurrentFile(self,fileName):self.curFile = fileNameif self.curFile:shownName = self.strippedName(self.curFile)  # 如果 self.curFile 不为空,调用 self.strippedName 方法获取文件名(不包含路径)。else:shownName = 'untitled.json' # 如果为空,设置默认文件名为 'untitled.json'。# 更新窗口标题,显示当前文件名和应用程序名称。self.setWindowTitle("%s[*] - Application" % shownName)def strippedName(self, fullFileName):return QtCore.QFileInfo(fullFileName).fileName()  # 使用 QtCore.QFileInfo 获取文件名(不包含路径)。fullFileName 是完整的文件路径,fileName() 方法返回文件名部分。# 实现自动保存功能# 用于触发当前编辑状态的自动保存def curSave(self):# 从 piecesList 和 picWidget 中获取当前的编辑状态(位置列表、切割小部件列表、位置信息列表),并更新到主窗口类的属性中。self.locationList = self.piecesList.locationListself.cuttingWidgetList = self.picWidget.cuttingWidgetListself.posList = self.picWidget.posList# 如果当前文件路径(self.curFile)存在,调用 autoSave 方法,将当前编辑状态保存到自动保存文件中(文件名由 self.autoName 指定)。if self.curFile:self.autoSave(self.autoName)# 具体执行保存操作,包括管理自动保存文件的路径列表、保存当前编辑状态到文件中,以及更新自动保存的元数据。def autoSave(self, autoName):# 如果自动保存路径列表超过 5 个文件,删除最早的自动保存文件(列表的第一个元素),并从列表中移除该路径。if len(self.auto_save_data[0]['autoSavePath']) > 5:os.remove(self.auto_save_data[0]['autoSavePath'][0])del self.auto_save_data[0]['autoSavePath'][0]# 如果新的自动保存文件名(autoName)不在路径列表中,且列表长度小于等于 5,直接将新路径添加到列表中。# 如果列表长度超过 5,删除最早的路径并添加新路径。if autoName not in self.auto_save_data[0]['autoSavePath']:if len(self.auto_save_data[0]['autoSavePath']) <= 5:self.auto_save_data[0]['autoSavePath'].append(autoName)elif len(self.auto_save_data[0]['autoSavePath']) > 5:os.remove(self.auto_save_data[0]['autoSavePath'][0])del self.auto_save_data[0]['autoSavePath'][0]self.auto_save_data[0]['autoSavePath'].append(autoName)# 将当前的编辑状态(位置列表、切割小部件列表、位置信息列表)转换为简单的列表格式,便于序列化。location = []square = []pos = []for i in range(len(self.locationList)):location.append([self.locationList[i].x(), self.locationList[i].y()])for j in range(len(self.cuttingWidgetList)):square.append([self.cuttingWidgetList[j].x(), self.cuttingWidgetList[j].y(), self.cuttingWidgetList[j].width(), self.cuttingWidgetList[j].height()])for k in range(len(self.posList)):pos.append([self.posList[k].x(), self.posList[k].y()])# 更新 self.data 中的编辑状态数据,包括位置列表、切割小部件列表、位置信息列表和切割模式。 self.data[0]['location_list'] = locationself.data[0]['square_list'] = squareself.data[0]['pos_list'] = posif self.data[0]['type'] == []:self.data[0]['type'].append(self._m_cut_type_str)else:self.data[0]['type'][0] = self._m_cut_type_str# 如果当前文件路径存在,将自动保存路径与当前文件路径关联起来,便于后续恢复。if self.curFile:self.savepath_autopath_dict[str(self.curFile)]= autoNameself.auto_save_data[0]['connectDict']= self.savepath_autopath_dict# 将当前编辑状态(self.data)序列化为 JSON 格式,并保存到指定的自动保存文件中。with open(autoName,'w') as fp:json.dump(self.data, fp, indent=2)# 将自动保存路径列表和其他元数据(self.auto_save_data)保存到 autosave.json 文件中。with open('auto_save/autosave.json','w') as fpp:json.dump(self.auto_save_data, fpp, indent=2)# 调用 QUndoStack 的 undo() 方法,执行最近一次的撤销操作。def undoImage(self):self.undoStack.undo()# 调用 QUndoStack 的 redo() 方法,执行最近一次的重做操作。def redoImage(self):self.undoStack.redo()def cutImage(self):item = self.piecesList.currentItem()  # 从 piecesList(图片片段列表)中获取当前选中的项。self.copyImage()  # 调用 copyImage 方法,将当前选中的图片片段复制到剪贴板。command =CommandCut(self.piecesList, item)  # 创建一个 CommandCut 对象,这是一个自定义的命令类,用于实现剪切操作的逻辑。self.undoStack.push(command)  # 将剪切命令推入 QUndoStack,以便支持撤销操作。def copyImage(self):item = self.piecesList.currentItem()  # 获取当前选中的图片片段项itemData = QtCore.QByteArray()  # 创建一个 QDataStream 对象,用于序列化数据dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly)  # 创建一个 QDataStream 对象,用于序列化数据pixmap = QtGui.QPixmap(item.data(QtCore.Qt.UserRole))  # 从选中的项中获取 QPixmap 对象location = item.data(QtCore.Qt.UserRole + 1).toPoint()  # 从选中的项中获取位置信息(QPoint)print "-----copy=-----"print locationdataStream << pixmap << location  # 将 QPixmap 和位置信息序列化到 QByteArray 中mimeData = QtCore.QMimeData()  # 创建一个 QMimeData 对象,用于存储剪贴板数据mimeData.setData('image/pic', itemData)  # 将 QByteArray 数据存储到 QMimeData 中,指定 MIME 类型为 'image/pic'self.clipBoard =QtGui.QApplication.clipboard()  # 获取系统剪贴板self.clipBoard.setMimeData(mimeData)  # 将 QMimeData 设置到剪贴板def pasteImage(self):picData=self.clipBoard.mimeData().data('image/pic')  # 从剪贴板获取 MIME 类型为 'image/pic' 的数据dataStream = QtCore.QDataStream(picData, QtCore.QIODevice.ReadOnly)  # 创建一个 QDataStream 对象,用于反序列化数据pixmap = QtGui.QPixmap()  # 创建一个 QPixmap 对象,用于存储图片location = QtCore.QPoint()  # 创建一个 QPoint 对象,用于存储位置信息dataStream >> pixmap >> location  # 从数据流中反序列化 QPixmap 和 QPointcommand =CommandPaste(self.piecesList, pixmap, location)  # 创建一个粘贴命令对象self.undoStack.push(command)  # 将粘贴命令推入撤销栈def deleteImage(self):item = self.piecesList.currentItem()  # 获取当前选中的图片片段项command = CommandDelete(self.piecesList, item)  # 创建一个删除命令对象self.undoStack.push(command)  # 将删除命令推入撤销栈# 将图片缩放到固定大小:400*400px, 然后进行切割再保存到图片片段列表中去def setupPic(self, xx, yy):# 将图片缩放到固定大小:400*400pxsize = min(self.image.width(), self.image.height())self.image = self.image.copy((self.image.width() - size)/2, (self.image.height() - size)/2, size, size).scaled(400, 400, QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation)# 清空 piecesList,移除所有之前的图片片段。self.piecesList.clear()# 然后进行切割再保存到图片片段列表中去for x in range(xx):for y in range(yy):pieceImage = self.image.copy(x*400/self.xx, y*400/self.yy, 400/self.xx, 400/self.yy)self.location_pixmap_dict[(x + 1, y + 1)]= pieceImageself.piecesList.addPiece(pieceImage, QtCore.QPoint(x + 1, y + 1))# 初始化主窗口的布局和组件def setWidgets(self):frame =QtGui.QFrame()  # 创建一个框架组件frameLayout =QtGui.QHBoxLayout(frame)  # 创建一个水平布局管理器self.piecesList =PicturesList()  # 创建一个图片片段列表组件self.picWidget=PictureWidget()  # 创建一个图片编辑区域组件frameLayout.addWidget(self.piecesList)  # 将图片片段列表组件添加到布局中frameLayout.addWidget(self.picWidget)  # 将图片编辑区域组件添加到布局中self.setCentralWidget(frame)  # 将框架组件设置为主窗口的中心部件# 处理窗口关闭事件(退出游戏)def closeEvent(self, event):reply = QtGui.QMessageBox.question(self, u'退出游戏', u"是否退出游戏?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)  # 使用 QtGui.QMessageBox.question 弹出一个消息框,提示用户是否确定退出。reply 变量存储用户的选择结果。if reply == QtGui.QMessageBox.Yes:event.accept()else:event.ignore()# 窗口居中显示def center(self):screen = QtGui.QDesktopWidget().screenGeometry()  # 获取屏幕的几何信息size = self.geometry()  # 获取窗口的几何信息self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)  # 将窗口移动到屏幕中央# PicturesList 类,继承自 QtGui.QListWidget。它用于显示和管理图片片段的列表,并支持拖拽操作。
class PicturesList(QtGui.QListWidget):upSignal = QtCore.pyqtSignal()  # 自定义信号downSignal =QtCore.pyqtSignal()saveSignal = QtCore.pyqtSignal()def __init__(self, parent=None):super(PicturesList,self).__init__(parent)  # 调用父类的构造函数self.setDragEnabled(True)  # 启用拖拽功能self.setViewMode(QtGui.QListView.IconMode)  # 设置视图模式为图标模式self.setIconSize(QtCore.QSize(60, 60))  # 设置图标大小为60x60 像素self.setSpacing(10)  # 设置图标之间的间距为 10 像素self.setAcceptDrops(True)  # 启用接受拖拽操作self.setDropIndicatorShown(True)  # 显示拖拽指示器self.pixmapList =[]  # 存储图片片段(存储切图)self.locationList=[]  # 存储位置信息# 添加图片到左侧的资源列表中def addPiece(self,pixmap, location):self.pixmapList.append(pixmap)  # 将图片片段添加到 pixmapList 中self.locationList.append(location)  # 将位置信息添加到 locationList 中pieceItem = QtGui.QListWidgetItem(self)  # 创建一个 QListWidgetItem 对象pieceItem.setIcon(QtGui.QIcon(pixmap))  # 使用 QIcon 将传入的 pixmap 设置为列表项的图标。pieceItem.setData(QtCore.Qt.UserRole, pixmap)  # 将 QPixmap 对象存储为用户数据pieceItem.setData(QtCore.Qt.UserRole +1, location)  # 将位置信息存储为用户数据pieceItem.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled)  # 设置列表项的标志,使其可启用、可选择、可拖拽# 可启用(ItemIsEnabled):用户可以与之交互。# 可选择(ItemIsSelectable):用户可以选中该条目。# 可拖拽(ItemIsDragEnabled):用户可以拖拽该条目。def delete_piece(self, item):self.takeItem(self.row(item))  # 从 PicturesList 中移除指定的图片片段。def startDrag(self, supportedActions):item = self.currentItem()  # 获取QListWidget当前选中的 QListWidgetItem 对象。itemData = QtCore.QByteArray()  # 创建一个 QByteArray 对象,用于存储序列化后的数据。dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly)  # 创建一个 QDataStream 对象,用于将数据写入 QByteArray。# 从列表项中获取存储的 QPixmap 对象和位置信息(QPoint)。self.pixmap = QtGui.QPixmap(item.data(QtCore.Qt.UserRole))self.location = item.data(QtCore.Qt.UserRole + 1).toPoint()# 使用 QDataStream 将 QPixmap 和 QPoint 序列化到 QByteArray 中。dataStream << self.pixmap << self.location# 创建一个 QMimeData 对象,用于存储拖拽数据。mimeData = QtCore.QMimeData()mimeData.setData('image/pic', itemData)  # 将序列化后的数据存储到 QMimeData 中,指定 MIME 类型为 'image/pic'。# 创建一个 QDrag 对象,用于处理拖拽操作。# 并设置拖拽对象的 MIME 数据、热点位置(拖拽的起始点)和拖拽时显示的图标。drag = QtGui.QDrag(self)drag.setMimeData(mimeData)drag.setHotSpot(QtCore.QPoint(self.pixmap.width()/2, self.pixmap.height()/2))drag.setPixmap(self.pixmap)# 发射 upSignal 信号,通知其他组件拖拽操作开始。self.upSignal.emit()# 调用 drag.exec_() 方法,执行拖拽操作,支持移动操作(Qt.MoveAction)。如果拖拽操作成功完成,从列表中移除当前选中的项。if drag.exec_(QtCore.Qt.MoveAction) == QtCore.Qt.MoveAction:self.takeItem(self.row(item))def dragEnterEvent(self,event):# 如果拖拽数据包含 'image/pic' 格式,调用 event.accept(),表示接受拖拽操作。否则,调用 event.ignore(),忽略拖拽操作。if event.mimeData().hasFormat('image/pic'):  # 检查拖拽事件的 MIME 数据是否包含 'image/pic' 格式。event.accept()else:  event.ignore()def dragMoveEvent(self, event):# 检查拖拽事件的 MIME 数据是否包含 'image/pic' 格式。如果数据格式匹配,设置拖拽操作类型为 MoveAction,表示允许移动操作。调用 event.accept(),表示接受拖拽操作。if event.mimeData().hasFormat('image/pic'):event.setDropAction(QtCore.Qt.MoveAction)event.accept()else:event.ignore()  # 忽略不支持的拖拽事件# 用于响应拖拽释放事件。通过这个方法,PicturesList 组件可以处理拖拽释放时的数据接收,并通知其他组件拖拽操作完成。def dropEvent(self,event):if event.mimeData().hasFormat('image/pic'):picData = event.mimeData().data('image/pic')  # 获取 MIME 数据中的图片数据dataStream = QtCore.QDataStream(picData,QtCore.QIODevice.ReadOnly)  # 创建一个 QDataStream 对象,用于反序列化数据self.pixmap = QtGui.QPixmap()  # 创建一个 QPixmap 对象,用于存储图片self.location = QtCore.QPoint()  # 创建一个 QPoint 对象,用于存储位置信息dataStream >> self.pixmap >> self.location  # 从数据流中反序列化 QPixmap 和 QPointself.downSignal.emit()  # 发射 downSignal 信号,通知其他组件拖拽操作完成event.setDropAction(QtCore.Qt.MoveAction)  # 设置拖拽操作类型为 MoveActionevent.accept()  # 接受拖拽操作else :event.ignore()  # 忽略不支持的拖拽事件# PictureWidget 类,继承自 QtGui.QWidget。它用于显示和管理图片编辑区域,并支持拖拽操作。
class PictureWidget(QtGui.QWidget):upSignal = QtCore.pyqtSignal()  # 自定义信号downSignal = QtCore.pyqtSignal()saveSignal = QtCore.pyqtSignal()def __init__(self,parent=None):super(PictureWidget, self).__init__(parent)self.setMinimumSize(640, 670)  # 设置最小尺寸self.setMaximumSize(640, 670)  # 设置最大尺寸self.highlightedRect = QtCore.QRect()  # 创建一个 QRect 对象,用于表示高亮显示的矩形区域。初始值为空矩形。self.setAcceptDrops(True)  # 启用组件接受拖拽操作,允许用户将图片片段拖拽到该区域。self.cuttingWidgetList = []  # 初始化切割小部件列表self.pixmapList = []  # 初始化图片片段列表self.posList = []  # 初始化位置信息列表self.drop_success_flag = True  # 初始化一个布尔标志,用于指示拖拽操作是否成功完成。# 处理拖拽进入事件和拖拽离开事件def dragEnterEvent(self, event):if event.mimeData().hasFormat('image/pic'):event.accept()else:event.ignore()def dragLeaveEvent(self, event):updateRect =self.highlightedRect  # 获取当前高亮显示的矩形区域self.highlightedRect = QtCore.QRect()  # 清空高亮显示的矩形区域self.update(updateRect)  # 更新组件,重新绘制高亮区域event.accept()  # 接受拖拽离开事件# 处理拖拽移动事件和拖拽释放事件def dragMoveEvent(self, event):updateRect = self.highlightedRect.unite(self.targetSquare(event.pos()))  # 使用 unite 方法计算当前高亮矩形区域和目标矩形区域的联合区域,确保更新整个相关区域。if event.mimeData().hasFormat('image/pic'):  # 检查拖拽事件的 MIME 数据是否包含 'image/pic' 格式。self.highlightedRect = self.targetSquare(event.pos())  # 如果数据格式匹配,调用 self.targetSquare(event.pos()) 获取目标矩形区域,并将其设置为高亮显示区域。event.setDropAction(QtCore.Qt.MoveAction)  # 设置拖拽操作类型为 MoveAction,表示允许移动操作。event.accept()  # 调用 event.accept(),表示接受拖拽操作。else:self.highlightedRect =QtCore.QRect()  # 如果数据格式不匹配,清空高亮显示的矩形区域,并调用 event.ignore(),忽略拖拽操作。event.ignore()self.update(updateRect)  # 调用 self.update(updateRect),重新绘制需要更新的矩形区域。def dropEvent(self,event):if event.mimeData().hasFormat('image/pic'):  # 检查拖拽事件的 MIME 数据是否包含 'image/pic' 格式。picData = event.mimeData().data('image/pic')  # 从 MIME 数据中获取 'image/pic' 格式的数据。dataStream = QtCore.QDataStream(picData, QtCore.QIODevice.ReadOnly)  # 创建一个 QDataStream 对象,用于从数据中反序列化 QPixmap 和 QPoint。self.square = self.targetSquare(event.pos())  # 调用 self.targetSquare(event.pos()) 获取目标矩形区域。self.pixmap = QtGui.QPixmap()self.location = QtCore.QPoint()dataStream >> self.pixmap >> self.locationself.hightlightedRect = QtCore.QRect()  # 清空高亮显示的矩形区域。self.downSignal.emit()  # 发射 downSignal 信号,通知其他组件拖拽操作完成。event.setDropAction(QtCore.Qt.MoveAction)  # 设置拖拽操作类型为 MoveAction。 event.accept()  # 调用 event.accept(),表示接受拖拽操作。else:self.hightlightedRect = QtCore.QRect()  # 如果数据格式不匹配,清空高亮显示的矩形区域event.ignore()# 查找特定的切割小部件(矩形区域): 使用 index 方法在 cuttingWidgetList 中查找 pieceRect 的索引。如果找到,返回其索引值。def findPiece(self, pieceRect):try:return self.cuttingWidgetList.index(pieceRect)except ValueError:return -1  # 如果 pieceRect 未在 cuttingWidgetList 中找到,捕获 ValueError 异常并返回 -1。# 处理鼠标按下事件,从而实现拖拽操作的开始。def mousePressEvent(self, event):self.square = self.targetSquare(event.pos())  # 获取鼠标点击位置对应的目标矩形区域found = self.findPiece(self.square)  # 查找该矩形区域在 cuttingWidgetList 中的索引if found == -1:return  # 如果未找到,直接返回self.location = self.posList[found]  # 获取该矩形区域的位置信息self.pixmap = self.pixmapList[found]  # 获取该矩形区域的图片片段# del self.posList[found]# del self.pixmapList[found]# del self.cuttingWidgetList[found]self.upSignal.emit()  # 发射 upSignal 信号,通知其他组件拖拽操作开始itemData = QtCore.QByteArray()  # 创建一个 QByteArray 对象,用于存储数据dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly)  # 创建一个 QDataStream 对象,用于序列化数据dataStream << self.pixmap << self.location  # 将 QPixmap 和 QPoint 序列化到 QByteArray 中mimeData = QtCore.QMimeData()  # 创建一个 QMimeData 对象,用于存储 MIME 类型的数据mimeData.setData('image/pic', itemData)  # 将 QByteArray 数据存储到 QMimeData 中,指定 MIME 类型为 'image/pic'drag = QtGui.QDrag(self)  # 创建一个 QDrag 对象,用于处理拖拽操作drag.setMimeData(mimeData)  # 将 QMimeData 设置到拖拽对象中drag.setHotSpot(event.pos() - self.square.topLeft())  # 设置拖拽的热点位置drag.setPixmap(self .pixmap)  # 设置拖拽时显示的图标if drag.exec_(QtCore.Qt.MoveAction) != QtCore.Qt.MoveAction:self.posList.insert(found, self.location)self.pixmapList.insert(found, self.pixmap)self.cuttingWidgetList.insert(found, self.square)self.update(self.square)def paintEvent(self, event):painter = QtGui.QPainter()  # 创建一个 QPainter 对象,用于绘制图形。painter.begin(self)  # 调用 begin(self) 方法,指定绘制目标为当前组件。painter.fillRect(event.rect(), QtCore.Qt.white)  # 使用 fillRect 方法,将整个组件区域填充为白色。if self.highlightedRect.isValid():  # 检查 highlightedRect 是否有效。painter.setBrush(QtGui.QColor("#ffcccc"))  # 如果有效,设置画刷颜色为浅红色(#ffcccc),画笔为无边框。painter.setPen(QtCore.Qt.NoPen)painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))  # 使用 drawRect 方法绘制高亮矩形区域,adjusted(0, 0, -1, -1) 用于调整矩形的大小,使其不包含边界。# 使用 zip 函数同时遍历 cuttingWidgetList 和 pixmapList。在每个矩形区域内绘制对应的图片片段。for rect, pixmap in zip(self.cuttingWidgetList, self.pixmapList):painter.drawPixmap(rect, pixmap)painter.end() # 调用 end() 方法,结束绘制。# targetSquare 方法用于计算鼠标位置对应的目标矩形区域, 该方法将鼠标位置对齐到 80x80 的网格上,确保拖拽操作的目标区域是固定的网格单元。def targetSquare(self, position):# 创建一个 QRect 对象,表示目标矩形区域。矩形的左上角坐标为对齐后的坐标,宽度和高度均为 80 像素。return QtCore.QRect(position.x() // 80 * 80, position.y() // 80 * 80, 80, 80)# 自定义不同情况的撤销命令类,支持撤销和重做操作。
# 自定义的撤销命令类 CommandCut,继承自 QtGui.QUndoCommand
class CommandCut(QtGui.QUndoCommand):def __init__(self, piecesList, item):super(CommandCut, self).__init__()  # 调用父类的构造函数self.piecesList = piecesList  # 存储图片片段列表组件self.item = item  # 存储当前操作的图片片段项self.row = self.piecesList.row(self.item)  # 获取该项在列表中的行号def redo(self):self.piecesList.takeItem(self.row)  # 从列表中移除该项self.piecesList.saveSignal.emit()  # 发射 saveSignal 信号,通知其他组件保存状态def undo(self):self.piecesList.insertItem(self.row, self.item)  # 将该项重新插入到列表中self.piecesList.saveSignal.emit()  # 发射 saveSignal 信号,通知其他组件保存状态class CommandPaste(QtGui.QUndoCommand):def __init__(self,piecesList, pixmap, location):super(CommandPaste,self).__init__()self.piecesList = piecesListself.pixmap = pixmapself.location =locationdef redo(self):self.piecesList.addPiece(self.pixmap, self.location)self.piecesList.saveSignal.emit()def undo(self):count = self.piecesList.count()self.piecesList.takeItem(self.piecesList.row(self.piecesList.item(count - 1)))self.piecesList.saveSignal.emit()class CommandDragToWidget(QtGui.QUndoCommand):def __init__(self, widget):super(CommandDragToWidget, self).__init__()self.widget = widgetself.square = self.widget.squareself.pixmap = self.widget.pixmapself.location =self.widget.locationdef redo(self):self.widget.cuttingWidgetList.append(self.square)self.widget.pixmapList.append(self.pixmap)self.widget.posList.append(self.location)self.widget.update(self.square)self.widget.saveSignal.emit()def undo(self):found = self.widget.findPiece(self.square)if found == -1:returndel self.widget.posList[found]del self.widget.pixmapList[found]del self.widget.cuttingWidgetList[found]self.widget.highlightedRect = QtCore.QRect()self.widget.update(self.square)self.widget.saveSignal.emit()class CommandDragToListWidget(QtGui.QUndoCommand):def __init__(self, lstWidget):super(CommandDragToListWidget, self).__init__()self.lstWidget = lstWidgetself.pixmap = self.lstWidget.pixmapself.location = self.lstWidget.locationdef redo(self):self.lstWidget.addPiece(self.pixmap, self.location)self.lstWidget.saveSignal.emit()def undo(self):count = self.lstWidget.count()self.lstWidget.takeItem(self.lstWidget.row(self.lstWidget.item(count - 1)))self.lstWidget.saveSignal.emit()class CommandDragFromWidget(QtGui.QUndoCommand):def __init__(self,widget):super(CommandDragFromWidget, self).__init__()self.widget = widgetself.square = self.widget.squareself.pixmap = self.widget.pixmapself.location = self.widget.locationself.found = self.widget.findPiece(self.square)def redo(self):if self.found == -1:returndel self.widget.posList[self.found]del self.widget.pixmapList[self.found]del self.widget.cuttingWidgetList[self.found]self.widget.highlightedRect = QtCore.QRect()self.widget.update(self.square)self.widget.saveSignal.emit()def undo(self):self.widget.posList.insert(self.found, self.location)self .widget.pixmapList.insert(self.found, self.pixmap)self.widget.cuttingWidgetList.insert(self.found, self.square)self.widget.update(self.square)self.widget.saveSignal.emit()class CommandDragFromListWidget(QtGui.QUndoCommand):def __init__(self, lstWidget, item, row):super(CommandDragFromListWidget, self).__init__()self.row = rowself.lstWidget = lstWidgetself.item = itemdef redo(self):tmp = self.lstWidget.item(self.row)tmp_location = Nonetmp_index = Nonefor i in self.lstWidget.locationList:if i.x() == tmp.data(QtCore.Qt.UserRole + 1).toPoint().x() and i.y() == tmp.data(QtCore.Qt.UserRole + 1).toPoint().y():tmp_index = self.lstWidget.locationList.index(i)tmp_location = ibreakif tmp_index != None and tmp_location != None:self.lstWidget.pixmapList.pop(tmp_index)self.lstWidget.locationList.remove(tmp_location)self.lstWidget.takeItem(self.row)self.lstWidget.saveSignal.emit()def undo(self):self.lstWidget.insertItem(self.row, self.item)self.lstWidget.saveSignal.emit()# 自定义的撤销命令类 CommandDelete,继承自 QtGui.QUndoCommand。这个类用于实现“删除”操作的撤销和重做功能。
class CommandDelete(QtGui.QUndoCommand):def __init__(self, lstWidget, item):super(CommandDelete, self).__init__()  # 调用父类的构造函数self.lstWidget = lstWidget  # 存储图片片段列表组件self.item = item  # 存储当前操作的图片片段项self.row = self.lstWidget.row(self.item)  # 获取该项在列表中的行号def redo(self):self.lstWidget.delete_piece(self.item)  # 从列表中删除该项self.lstWidget.saveSignal.emit()  # 发射 saveSignal 信号,通知其他组件保存状态def undo(self):self.lstWidget.insertItem(self.row, self.item)  # 将该项重新插入到列表中self.lstWidget.saveSignal.emit()  # 发射 saveSignal 信号,通知其他组件保存状态if __name__ == '__main__':  # 如果是主程序运行,执行以下代码;如果是被导入为模块,则跳过以下代码。app = QtGui.QApplication(sys.argv)  # QtGui.QApplication: PyQt 的应用程序类,负责管理应用程序的资源,如窗口、图标、菜单等。ex = MainWindow()  # 创建一个 MainWindow 类的实例,MainWindow 是自定义的主窗口类,继承自 QtGui.QMainWindow。ex.show()  # 调用 show() 方法,将主窗口显示在屏幕上。app.exec_()  # 调用 exec_() 方法,启动应用程序的事件循环。

 ------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[MySQL]数据库表内容的增删查改操作大全

目录 一、增加表数据 1.全列插入与指定列插入 2.多行数据插入 3.更新与替换插入 二、查看表数据 1.全列查询与指定列查询 2.查询表达式字段 3.为查询结果起别名 4.结果去重 5.WHERE条件 6.结果排序 7.筛选分页结果 8.插入查询的结果 9.group by子句 三、修改表数…

在 Windows 11 中为 SMB 3.x 文件共享协议提供 RDMA 支持

注&#xff1a;机翻&#xff0c;未校。 Enable SMB Direct in Windows 11 在 Windows 11 中启用 SMB Direct Provides RDMA support for the SMB 3.x file sharing protocol 为 SMB 3.x 文件共享协议提供 RDMA 支持 Vigneshwaran Vijayakumar November 3, 2024 Last Updat…

electron打包客户端在rk3588上支持h265硬解

目录 前言 chromium是如何支持h265硬解 electron/chromium第一次编译 electron/chromium第二次编译 前言 我们的客户端程序是用electron打包的前端程序&#xff0c;其在rk3588主机上的linux环境运行。之前使用客户端查看h264编码的视频直播是没有问题的&#xff0c;但视频源…

什么是网络爬虫?Python爬虫到底怎么学?

最近我在研究 Python 网络爬虫&#xff0c;发现这玩意儿真是有趣&#xff0c;干脆和大家聊聊我的心得吧&#xff01;咱们都知道&#xff0c;网络上的信息多得就像大海里的水&#xff0c;而网络爬虫就像一个勤劳的小矿工&#xff0c;能帮我们从这片浩瀚的信息海洋中挖掘出需要的…

【Jave全栈】Java与JavaScript比较

文章目录 前言一、Java1、 历史与背景2、语言特点3、应用场景4、生态系统 二、JavaScript1、历史与背景2、语言特点3、应用场景4、 生态系统 三、相同点四、不同点1、语言类型2、用途3、语法和结构4、性能5、生态系统6、开发模式 前言 Java和JavaScript是两种不同的编程语言&a…

GitCode 助力 AutoTable:共创 MyBatis 生态的自动表格管理新篇章

项目仓库https://gitcode.com/dromara/auto-table 解放双手&#xff0c;专注业务&#xff1a;MyBatis 生态的“自动表格”创新 AutoTable 是一款致力于为 MyBatis 生态赋予“自动表格”功能的创新插件。其核心理念是通过 Java 实体类自动生成和维护数据库的表结构&#xff0c…

【MCU】DFU、IAP、OTA

我发现很多人把几个概念都学混了&#xff0c;只记得一个升级了 DFU DFU (device firmware update)是指的 USB DFU&#xff0c;这个是 USB 的一个机制&#xff0c;可以升级设备的固件&#xff0c;可以去 USB-IF 查看规范文件。 OTA 全称为 Over-the-air update&#xff0c;利…

ThinkPHP 8 操作JSON数据

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

GD32F303 GCC 环境搭建

一、引言 在嵌入式开发领域&#xff0c;GD32F303 微控制器以其出色的性能和丰富的功能被广泛应用。为了充分发挥其潜力&#xff0c;搭建一个高效的开发环境并深入理解项目构建过程至关重要。本文将详细介绍如何基于 GCC 工具链搭建 GD32F303 的开发环境&#xff0c;重点聚焦于…

【C++】详细讲解继承(下)

本篇来继续说说继承。上篇可移步至【C】详细讲解继承&#xff08;上&#xff09; 1.继承与友元 友元关系不能继承 &#xff0c;也就是说基类友元不能访问派⽣类私有和保护成员。 class Student;//前置声明class Same //基类 { public:friend void Fun(const Same& p, con…

考研机试题:今年的第几天

描述 输入年、月、日&#xff0c;计算该天是本年的第几天。 输入描述: 包括三个整数年(1<Y<3000)、月(1<M<12)、日(1<D<31)。 输出描述: 输入可能有多组测试数据&#xff0c;对于每一组测试数据&#xff0c; 输出一个整数&#xff0c;代表Input中的年、…

解锁罗技键盘新技能:轻松锁定功能键(罗技K580)

在使用罗技键盘的过程中&#xff0c;你是否曾因 F11、F12 功能键的默认设置与实际需求不符而感到困扰&#xff1f; 别担心&#xff0c;今天就为大家分享一个简单实用的小技巧 —— 锁定罗技键盘的 F11、F12 功能键&#xff0c;让你的操作更加得心应手&#xff01; 通常情况下…

Flink把kafa数据写入Doris的N种方法及对比。

用Flink+Doris来开发实时数仓,首要解决是如何接入kafka实时流,下面是参考Doris官方文档和代码,在自己项目开发的实践中总结,包括一些容易踩坑的细节。 目录 Routine Load方法 接入kafka实时数据 踩坑的问题细节 Flink Doris Connector方法 完整示例 Routine Load方法…

小识JVM堆内存管理的优化机制TLAB

JVM&#xff08;Java虚拟机&#xff09;在堆内存分配空间时&#xff0c;TLAB&#xff08;Thread Local Allocation Buffer&#xff0c;线程本地分配缓存区&#xff09;是一种重要的内存管理优化技术。以下是对TLAB的详细解释&#xff1a; 一、TLAB的定义 TLAB是JVM堆内存管理…

(开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台

1 项目简介&#xff08;开源地址在文章结尾&#xff09; 系统旨在为了帮助鸟类爱好者、学者、动物保护协会等群体更好的了解和保护鸟类动物。用户群体可以通过平台采集野外鸟类的保护动物照片和视频&#xff0c;甄别分类、实况分析鸟类保护动物&#xff0c;与全世界各地的用户&…

【力扣Hot 100】普通数组2

3. 轮转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k **个位置&#xff0c;其中 k **是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出:[5,6,7,1,2,3,4]解释: 向右轮转 1 步:[7,1,2,3,4,5,6] 向右轮转 2 步:[6,7,1,2,3,4,5] 向右轮转 3 步:[…

专题三_穷举vs暴搜vs深搜vs回溯vs剪枝_全排列

dfs解决 全排列&子集 1.全排列 link:46. 全排列 - 力扣&#xff08;LeetCode&#xff09; 全局变量回溯 code class Solution { public:vector<vector<int>> ans;vector<int> cur;vector<bool> used;vector<vector<int>> permute…

2_高并发内存池_各层级的框架设计及ThreadCache(线程缓存)申请内存设计

一、高并发内存池框架设计 高并发池框架设计&#xff0c;特别是针对内存池的设计&#xff0c;需要充分考虑多线程环境下&#xff1a; 性能问题锁竞争问题内存碎片问题 高并发内存池的整体框架设计旨在提高内存的申请和释放效率&#xff0c;减少锁竞争和内存碎片。 高并发内存…

JAVA 使用反射比较对象属性的变化,记录修改日志。使用注解【策略模式】,来进行不同属性枚举值到中英文描述的切换,支持前端国际化。

1.首先定义一个接口&#xff0c;接口中有两个方法&#xff0c;分别是将属性转换成英文描述和中文描述。 其实就是将数据库中记录的 0 1 &#xff0c;转换成后面的描述 这边定义了中文转换为默认方法&#xff0c;是因为有些属性不需要进行中文转换&#xff0c;或者该属性的枚举…

webrtc入门系列(五)amazon-kinesis-video-streams-webrtc-sdk-c编译

《webrtc入门系列&#xff08;一&#xff09;easy_webrtc_server 入门环境搭建》 《webrtc入门系列&#xff08;二&#xff09;easy_webrtc_server 入门example测试》 《webrtc入门系列&#xff08;三&#xff09;云服务器coturn环境搭建》 《webrtc入门系列&#xff08;四&…