继续为scene.py添加中文注释

This commit is contained in:
ZMY051025 2025-10-18 13:35:42 +08:00
parent 6aa3251e81
commit 063a19b25f

View file

@ -265,302 +265,303 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): # 定义场景类,继承自
# We want the items to center around the selection's center,
# not (0, 0)
center = self.get_selection_center()
bounds = rpack.bbox_size(sizes, positions)
diff = center - QtCore.QPointF(bounds[0]/2, bounds[1]/2)
positions = [QtCore.QPointF(*pos) + diff for pos in positions]
center = self.get_selection_center() # 获取选择项目的中心位置
bounds = rpack.bbox_size(sizes, positions) # 计算打包后的边界矩形大小
diff = center - QtCore.QPointF(bounds[0]/2, bounds[1]/2) # 计算中心位置与边界矩形中心位置的差值
positions = [QtCore.QPointF(*pos) + diff for pos in positions] # 为每个项目计算新的位置,考虑中心位置的偏移
self.undo_stack.push(commands.ArrangeItems(self, items, positions))
self.undo_stack.push(commands.ArrangeItems(self, items, positions)) # 将排列操作添加到撤销栈中
def arrange_square(self):
self.cancel_active_modes()
max_width = 0
max_height = 0
gap = self.settings.valueOrDefault('Items/arrange_gap')
items = sort_by_filename(self.selectedItems(user_only=True))
def arrange_square(self): # 按正方形方式排列选择的项目
self.cancel_active_modes() # 取消所有活动模式,确保没有正在进行的操作干扰
max_width = 0 # 初始化最大宽度为0
max_height = 0 # 初始化最大高度为0
gap = self.settings.valueOrDefault('Items/arrange_gap') # 获取项目之间的间距
items = sort_by_filename(self.selectedItems(user_only=True)) # 获取当前用户选择的项目,并按文件名排序
if len(items) < 2:
return
if len(items) < 2: # 如果选择的项目数量小于2
return # 直接返回,无需排列
for item in items:
rect = self.itemsBoundingRect(items=[item])
max_width = max(max_width, rect.width() + gap)
max_height = max(max_height, rect.height() + gap)
for item in items: # 遍历选择的项目
rect = self.itemsBoundingRect(items=[item]) # 获取项目的边界矩形
max_width = max(max_width, rect.width() + gap) # 更新最大宽度,考虑项目宽度和间距
max_height = max(max_height, rect.height() + gap) # 更新最大高度,考虑项目高度和间距
# We want the items to center around the selection's center,
# not (0, 0)
num_rows = math.ceil(math.sqrt(len(items)))
center = self.get_selection_center()
diff = center - num_rows/2 * QtCore.QPointF(max_width, max_height)
num_rows = math.ceil(math.sqrt(len(items))) # 计算需要的行数,向上取整
center = self.get_selection_center() # 获取选择项目的中心位置
diff = center - num_rows/2 * QtCore.QPointF(max_width, max_height) # 计算中心位置与边界矩形中心位置的差值
iter_items = iter(items)
positions = []
for j in range(num_rows):
for i in range(num_rows):
iter_items = iter(items) # 创建一个迭代器,用于遍历选择的项目
positions = [] # 创建一个空列表,用于存储每个项目的新位置
for j in range(num_rows):# 遍历行数
for i in range(num_rows):# 遍历列数
try:
item = next(iter_items)
rect = self.itemsBoundingRect(items=[item])
point = QtCore.QPointF(
i * max_width + (max_width - rect.width())/2,
j * max_height + (max_height - rect.height())/2)
positions.append(point + diff)
except StopIteration:
break
item = next(iter_items) # 从迭代器中获取下一个项目
rect = self.itemsBoundingRect(items=[item]) # 获取项目的边界矩形
point = QtCore.QPointF( # 计算项目的新位置
i * max_width + (max_width - rect.width())/2, # 计算项目在当前行的水平位置
j * max_height + (max_height - rect.height())/2) # 计算项目在当前列的垂直位置
positions.append(point + diff) # 计算项目的新位置,考虑中心位置的偏移
except StopIteration:# 当迭代器没有更多项目时,跳出循环
break # 跳出当前列的循环,继续下一行
self.undo_stack.push(commands.ArrangeItems(self, items, positions))
self.undo_stack.push(commands.ArrangeItems(self, items, positions)) # 将排列操作添加到撤销栈中
def flip_items(self, vertical=False):
def flip_items(self, vertical=False): # 垂直或水平翻转选择的项目
"""Flip selected items."""
self.cancel_active_modes()
self.cancel_active_modes() # 取消所有活动模式,确保没有正在进行的操作干扰
self.undo_stack.push(
commands.FlipItems(self.selectedItems(user_only=True),
self.get_selection_center(),
vertical=vertical))
vertical=vertical)) # 将翻转操作添加到撤销栈中
def crop_items(self):
def crop_items(self): # 对选择的项目进行裁剪
"""Crop selected item."""
if self.crop_item:
if self.crop_item:## 如果当前有项目正在裁剪状态
return
if self.has_single_image_selection():
if self.has_single_image_selection():# 如果选择的项目是单个图像
item = self.selectedItems(user_only=True)[0]
if item.is_image:
item.enter_crop_mode()
item.enter_crop_mode() # 进入图像裁剪模式
def sample_color_at(self, position):
item_at_pos = self.itemAt(position, self.views()[0].transform())
def sample_color_at(self, position): # 采样指定位置的颜色
item_at_pos = self.itemAt(position, self.views()[0].transform()) # 获取指定位置上的项目
if item_at_pos:
return item_at_pos.sample_color_at(position)
return item_at_pos.sample_color_at(position) # 如果项目存在,采样该位置的颜色
def select_all_items(self):
self.cancel_active_modes()
path = QtGui.QPainterPath()
path.addRect(self.itemsBoundingRect())
def select_all_items(self): # 选择所有项目
self.cancel_active_modes() # 取消所有活动模式,确保没有正在进行的操作干扰
path = QtGui.QPainterPath() # 创建一个空的绘图路径
path.addRect(self.itemsBoundingRect()) # 创建一个矩形路径,包含所有项目的边界矩形
path.addRect(self.itemsBoundingRect())
path.addRect(self.itemsBoundingRect())
# This is faster than looping through all items and calling setSelected
self.setSelectionArea(path)
self.setSelectionArea(path)# 设置选择区域为创建的路径,包含所有项目的边界矩形
def deselect_all_items(self):
self.cancel_active_modes()
self.clearSelection()
def deselect_all_items(self):# 取消所有项目的选择
self.cancel_active_modes() # 取消所有活动模式,确保没有正在进行的操作干扰
self.clearSelection() # 清除所有项目的选择状态
def has_selection(self):
def has_selection(self):# 检查是否有项目被选择
"""Checks whether there are currently items selected."""
return bool(self.selectedItems(user_only=True))
return bool(self.selectedItems(user_only=True)) # 返回当前用户选择的项目是否为空
def has_single_selection(self):
def has_single_selection(self):# 检查是否有单个项目被选择
"""Checks whether there's currently exactly one item selected."""
return len(self.selectedItems(user_only=True)) == 1
return len(self.selectedItems(user_only=True)) == 1 # 返回当前用户选择的项目是否只有一个
def has_multi_selection(self):
def has_multi_selection(self):# 检查是否有多个项目被选择
"""Checks whether there are currently more than one items selected."""
return len(self.selectedItems(user_only=True)) > 1
return len(self.selectedItems(user_only=True)) > 1 # 返回当前用户选择的项目是否有多个
def has_single_image_selection(self):
def has_single_image_selection(self):# 检查是否有单个图像项目被选择
"""Checks whether the current selection is a single image."""
if self.has_single_selection():
return self.selectedItems(user_only=True)[0].is_image
return False
if self.has_single_selection(): # 如果只有一个项目被选择
return self.selectedItems(user_only=True)[0].is_image # 检查该项目是否为图像
return False # 如果不是图像或没有项目被选择返回False
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.RightButton:
def mousePressEvent(self, event):# 处理鼠标按下事件
if event.button() == Qt.MouseButton.RightButton: # 如果是右键点击
# Right-click invokes the context menu on the
# GraphicsView. We don't need it here.
return
if event.button() == Qt.MouseButton.LeftButton:
self.event_start = event.scenePos()
if event.button() == Qt.MouseButton.LeftButton: # 如果是左键点击
self.event_start = event.scenePos() # 记录鼠标按下时的场景位置
item_at_pos = self.itemAt(
event.scenePos(), self.views()[0].transform())
event.scenePos(), self.views()[0].transform()) # 获取当前鼠标位置上的项目
if self.edit_item:
if item_at_pos != self.edit_item:
self.edit_item.exit_edit_mode()
else:
super().mousePressEvent(event)
return
if self.crop_item:
if item_at_pos != self.crop_item:
self.cancel_crop_mode()
else:
super().mousePressEvent(event)
return
if item_at_pos:
self.active_mode = self.MOVE_MODE
elif self.items():
self.active_mode = self.RUBBERBAND_MODE
if self.edit_item and item_at_pos != self.edit_item: # 如果当前有项目正在编辑状态,且点击的项目不是当前编辑项目
self.edit_item.exit_edit_mode() # 退出当前编辑项目的编辑状态
elif self.edit_item and item_at_pos == self.edit_item: # 如果当前有项目正在编辑状态,且点击的项目是当前编辑项目
super().mousePressEvent(event) # 调用父类的鼠标按下事件处理方法,确保正常编辑操作
return # 结束当前方法,避免后续重复处理
if self.crop_item and item_at_pos != self.crop_item: # 如果当前有项目正在裁剪状态,且点击的项目不是当前裁剪项目
self.cancel_crop_mode() # 取消当前裁剪项目的裁剪状态
elif self.crop_item and item_at_pos == self.crop_item: # 如果当前有项目正在裁剪状态,且点击的项目是当前裁剪项目
super().mousePressEvent(event) # 调用父类的鼠标按下事件处理方法,确保正常裁剪操作
return # 结束当前方法,避免后续重复处理
if item_at_pos: # 如果点击的项目存在
self.active_mode = self.MOVE_MODE # 切换到移动模式
elif self.items(): # 如果场景中存在其他项目
self.active_mode = self.RUBBERBAND_MODE # 切换到橡皮筋选择模式
super().mousePressEvent(event)
super().mousePressEvent(event)# 调用父类的鼠标按下事件处理方法,确保正常处理
def mouseDoubleClickEvent(self, event):
self.cancel_active_modes()
item = self.itemAt(event.scenePos(), self.views()[0].transform())
if item:
if not item.isSelected():
item.setSelected(True)
if item.is_editable:
item.enter_edit_mode()
self.mousePressEvent(event)
else:
def mouseDoubleClickEvent(self, event):# 处理鼠标双击事件
self.cancel_active_modes() # 取消所有活动模式,确保没有正在进行的操作干扰
item = self.itemAt(event.scenePos(), self.views()[0].transform()) # 获取当前鼠标位置上的项目
if item: # 如果点击的项目存在
if not item.isSelected(): # 如果项目未被选择
item.setSelected(True) # 选择该项目
if item.is_editable: # 如果项目可编辑
item.enter_edit_mode() # 进入编辑模式
self.mousePressEvent(event) # 模拟鼠标按下事件,确保编辑模式正常启动
else: # 如果项目不可编辑
self.views()[0].fit_rect(
self.itemsBoundingRect(items=[item]),
toggle_item=item)
toggle_item=item) # 调整场景视图,确保点击的项目在可见区域
return
super().mouseDoubleClickEvent(event)
super().mouseDoubleClickEvent(event) # 调用父类的鼠标双击事件处理方法,确保正常处理
def mouseMoveEvent(self, event):
if self.active_mode == self.RUBBERBAND_MODE:
if not self.rubberband_item.scene():
logger.debug('Activating rubberband selection')
self.addItem(self.rubberband_item)
self.rubberband_item.bring_to_front()
self.rubberband_item.fit(self.event_start, event.scenePos())
self.setSelectionArea(self.rubberband_item.shape())
self.views()[0].reset_previous_transform()
super().mouseMoveEvent(event)
def mouseMoveEvent(self, event): # 处理鼠标移动事件
if self.active_mode == self.RUBBERBAND_MODE: # 如果当前模式是橡皮筋选择模式
if not self.rubberband_item.scene(): # 如果橡皮筋选择项目不在场景中
logger.debug('Activating rubberband selection') # 记录调试信息,激活橡皮筋选择模式
self.addItem(self.rubberband_item) # 将橡皮筋选择项目添加到场景中
self.rubberband_item.bring_to_front() # 将橡皮筋选择项目 bring to front确保可见
self.rubberband_item.fit(self.event_start, event.scenePos()) # 调整橡皮筋选择项目的形状,以适应鼠标移动
self.setSelectionArea(self.rubberband_item.shape()) # 设置场景的选择区域为橡皮筋选择项目的形状
self.views()[0].reset_previous_transform() # 重置场景视图的前一个变换,确保正常显示
super().mouseMoveEvent(event) # 调用父类的鼠标移动事件处理方法,确保正常处理
def mouseReleaseEvent(self, event):
if self.active_mode == self.RUBBERBAND_MODE:
self.end_rubberband_mode()
if (self.active_mode == self.MOVE_MODE
and self.has_selection()
and self.multi_select_item.active_mode is None
and self.selectedItems()[0].active_mode is None):
delta = event.scenePos() - self.event_start
if not delta.isNull():
def mouseReleaseEvent(self, event): # 处理鼠标释放事件
if self.active_mode == self.RUBBERBAND_MODE: # 如果当前模式是橡皮筋选择模式
self.end_rubberband_mode() # 结束橡皮筋选择模式
if (self.active_mode == self.MOVE_MODE # 如果当前模式是移动模式
and self.has_selection() # 如果场景中存在选择项目
and self.multi_select_item.active_mode is None # 如果多选项目的活动模式为空
and self.selectedItems()[0].active_mode is None): # 如果选择项目的活动模式为空
delta = event.scenePos() - self.event_start # 计算鼠标移动的距离
if not delta.isNull(): # 如果鼠标移动距离不为零
self.undo_stack.push(
commands.MoveItemsBy(self.selectedItems(),
delta,
ignore_first_redo=True))
self.active_mode = None
super().mouseReleaseEvent(event)
ignore_first_redo=True)) # 记录移动操作到撤销栈,忽略第一次重做
self.active_mode = None # 切换到无活动模式
super().mouseReleaseEvent(event) # 调用父类的鼠标释放事件处理方法,确保正常处理
def selectedItems(self, user_only=False):
def selectedItems(self, user_only=False): # 返回场景中当前选择的项目
"""If ``user_only`` is set to ``True``, only return items added
by the user (i.e. no multi select outlines and other UI items).
User items are items that have a ``save_id`` attribute.
"""
items = super().selectedItems()
items = super().selectedItems() # 获取场景中当前选择的所有项目
if user_only:
return list(filter(lambda i: hasattr(i, 'save_id'), items))
return items
return list(filter(lambda i: hasattr(i, 'save_id'), items)) # 如果只返回用户项目,过滤掉没有 save_id 属性的项目
return items # 返回所有选择项目
def items_by_type(self, itype):
def items_by_type(self, itype): # 返回场景中所有指定类型的项目
"""Returns all items of the given type."""
return filter(lambda i: getattr(i, 'TYPE', None) == itype,
self.items())
def items_for_save(self):
self.items()) # 返回所有类型为 itype 的项目
def items_for_save(self): # 返回场景中所有可保存的项目
"""Returns the items that are to be saved.
Items to be saved are items that have a save_id attribute.
"""
return filter(lambda i: hasattr(i, 'save_id'),
self.items(order=Qt.SortOrder.AscendingOrder))
self.items(order=Qt.SortOrder.AscendingOrder)) # 返回所有有 save_id 属性的项目
def clear_save_ids(self):
for item in self.items_for_save():
item.save_id = None
def clear_save_ids(self): # 清除场景中所有项目的 save_id 属性
for item in self.items_for_save():# 遍历所有可保存项目
item.save_id = None # 清除项目的 save_id 属性
def on_view_scale_change(self):
for item in self.selectedItems():
item.on_view_scale_change()
def on_view_scale_change(self): # 处理场景视图缩放变化事件
for item in self.selectedItems(): # 遍历当前选择的项目
item.on_view_scale_change() # 调用项目的视图缩放变化处理方法,确保正常显示
def itemsBoundingRect(self, selection_only=False, items=None):
def itemsBoundingRect(self, selection_only=False, items=None): # 返回场景中项目的边界矩形
"""Returns the bounding rect of the scene's items; either all of them
or only selected ones, or the items givin in ``items``.
Re-implemented to not include the items's selection handles.
"""
def filter_user_items(ilist):
return list(filter(lambda i: hasattr(i, 'save_id'), ilist))
def filter_user_items(ilist):# 过滤出场景中所有用户项目(有 save_id 属性的项目)
return list(filter(lambda i: hasattr(i, 'save_id'), ilist)) # 过滤出场景中所有用户项目(有 save_id 属性的项目)
if selection_only:
base = filter_user_items(self.selectedItems())
elif items:
base = items
else:
base = filter_user_items(self.items())
if not base:
return QtCore.QRectF(0, 0, 0, 0)
x = []
y = []
if selection_only: # 如果只返回选择项目的边界矩形
base = filter_user_items(self.selectedItems()) # 过滤出当前选择的用户项目
elif items: # 如果指定了项目列表
base = items # 使用指定的项目列表
else: # 如果没有指定项目列表
base = filter_user_items(self.items()) # 过滤出所有用户项目
for item in base:
for corner in item.corners_scene_coords:
x.append(corner.x())
y.append(corner.y())
if not base: # 如果没有项目可计算边界矩形
return QtCore.QRectF(0, 0, 0, 0) # 返回空矩形
x = [] # 存储所有项目的 x 坐标
y = [] # 存储所有项目的 y 坐标
for item in base: # 遍历所有项目
for corner in item.corners_scene_coords: # 遍历项目的所有场景坐标
x.append(corner.x()) # 存储项目的 x 坐标
y.append(corner.y()) # 存储项目的 y 坐标
return QtCore.QRectF(
QtCore.QPointF(min(x), min(y)),
QtCore.QPointF(max(x), max(y)))
QtCore.QPointF(min(x), min(y)), # 计算所有项目的最小坐标点
QtCore.QPointF(max(x), max(y))) # 计算所有项目的最大坐标点
def get_selection_center(self):
rect = self.itemsBoundingRect(selection_only=True)
return (rect.topLeft() + rect.bottomRight()) / 2
def get_selection_center(self): # 返回场景中当前选择项目的中心坐标
rect = self.itemsBoundingRect(selection_only=True) # 获取当前选择项目的边界矩形
return (rect.topLeft() + rect.bottomRight()) / 2 # 返回矩形的中心坐标
def on_selection_change(self):
def on_selection_change(self): # 处理场景选择变化事件
if self._clear_ongoing:
# Ignore events while clearing the scene since the
# multiselect item will get cleared, too
return
if self.has_multi_selection():
self.multi_select_item.fit_selection_area(
self.itemsBoundingRect(selection_only=True))
if self.has_multi_selection() and not self.multi_select_item.scene():
self.addItem(self.multi_select_item)
self.multi_select_item.bring_to_front()
if not self.has_multi_selection() and self.multi_select_item.scene():
self.removeItem(self.multi_select_item)
if self.has_multi_selection():# 如果有多个项目被选择
self.multi_select_item.fit_selection_area(
self.itemsBoundingRect(selection_only=True)) # 调整多选框的大小以适应选择项目的边界矩形
if self.has_multi_selection() and not self.multi_select_item.scene(): # 如果有多个项目被选择且多选框不在场景中
self.addItem(self.multi_select_item) # 将多选框添加到场景中
self.multi_select_item.bring_to_front() # 将多选框 bring_to_front 到最前面
if not self.has_multi_selection() and self.multi_select_item.scene(): # 如果没有项目被选择且多选框在场景中
self.removeItem(self.multi_select_item) # 从场景中移除多选框
def on_change(self, region):
if self._clear_ongoing:
def on_change(self, region): # 处理场景变化事件
if self._clear_ongoing: # 如果场景正在清除中
# Ignore events while clearing the scene since the
# multiselect item will get cleared, too
return
if (self.multi_select_item.scene()
and self.multi_select_item.active_mode is None):
if (self.multi_select_item.scene() # 如果多选框在场景中
and self.multi_select_item.active_mode is None): # 且多选框未激活任何模式
self.multi_select_item.fit_selection_area(
self.itemsBoundingRect(selection_only=True))
self.itemsBoundingRect(selection_only=True)) # 调整多选框的大小以适应选择项目的边界矩形
def add_item_later(self, itemdata, selected=False):
def add_item_later(self, itemdata, selected=False): # 保持一个项目稍后添加,通过 ``add_queued_items`` 添加
"""Keep an item for adding later via ``add_queued_items``
:param dict itemdata: Defines the item's data
:param bool selected: Whether the item is initialised as selected
"""
self.items_to_add.put((itemdata, selected))
self.items_to_add.put((itemdata, selected)) # 将项目数据和选择状态添加到稍后添加队列中
def add_queued_items(self):
def add_queued_items(self): # 添加稍后添加队列中的项目
"""Adds items added via ``add_item_later``"""
while not self.items_to_add.empty():
data, selected = self.items_to_add.get()
typ = data.pop('type')
cls = item_registry.get(typ)
if not cls:
while not self.items_to_add.empty(): # 循环直到稍后添加队列为空
data, selected = self.items_to_add.get() # 从稍后添加队列中获取项目数据和选择状态
typ = data.pop('type') # 从项目数据中弹出项目类型
cls = item_registry.get(typ) # 从项目类型中获取项目类
if not cls: # 如果项目类型未知
# Just in case we add new item types in future versions
logger.warning(f'Encountered item of unknown type: {typ}')
cls = BeeErrorItem
data['data'] = {'text': f'Item of unknown type: {typ}'}
item = cls.create_from_data(**data)
logger.warning(f'Encountered item of unknown type: {typ}') # 记录警告日志,提示未知项目类型
cls = BeeErrorItem # 默认使用 BeeErrorItem 类
data['data'] = {'text': f'Item of unknown type: {typ}'} # 为未知项目类型添加默认文本数据
item = cls.create_from_data(**data) # 创建项目实例
# Set the values common to all item types:
item.update_from_data(**data)
self.addItem(item)
item.update_from_data(**data) # 更新项目实例的属性值
self.addItem(item) # 将项目实例添加到场景中
# Force recalculation of min/max z values:
item.setZValue(item.zValue())
item.setZValue(item.zValue()) # 强制重新计算项目实例的 z 值
if selected:
item.setSelected(True)
item.bring_to_front()
item.setSelected(True) # 设置项目实例为选中状态
item.bring_to_front() # 将项目实例 bring_to_front 到最前面