mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Refactor comments to English for consistency
- Updated comments in `__main__.py`, `items.py`, `assets/__init__.py`, `image.py`, `draw_floating_menu.py`, and `floating_menu.py` to English for better readability and maintainability. - Ensured that all comments accurately describe the corresponding code functionality.
This commit is contained in:
parent
dc733cfc23
commit
c8b600a3c4
6 changed files with 129 additions and 129 deletions
|
|
@ -64,15 +64,15 @@ class BeeRefMainWindow(QtWidgets.QMainWindow):
|
|||
self.show()
|
||||
|
||||
def closeEvent(self, event):
|
||||
# Проверяем наличие несохраненных изменений
|
||||
# Check for unsaved changes
|
||||
confirm = self.view.get_confirmation_unsaved_changes(
|
||||
'There are unsaved changes. Are you sure you want to quit?')
|
||||
if not confirm:
|
||||
# Пользователь отменил закрытие
|
||||
# User cancelled closing
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
# Сохраняем геометрию окна
|
||||
# Save window geometry
|
||||
geom = self.saveGeometry()
|
||||
self.view.settings.setValue('MainWindow/geometry', geom)
|
||||
event.accept()
|
||||
|
|
|
|||
|
|
@ -59,20 +59,20 @@ class BeeAssets:
|
|||
pixmap, int(hotspot[0]/scaling), int(hotspot[1]/scaling))
|
||||
|
||||
def cursor_from_svg(self, filename, hotspot, size=24):
|
||||
"""Создает курсор из SVG файла."""
|
||||
"""Creates cursor from SVG file."""
|
||||
app = QtWidgets.QApplication.instance()
|
||||
scaling = app.primaryScreen().devicePixelRatio()
|
||||
|
||||
# Загружаем SVG
|
||||
# Load SVG
|
||||
svg_path = str(self.PATH.joinpath(filename))
|
||||
renderer = QtSvg.QSvgRenderer(svg_path)
|
||||
|
||||
# Создаем pixmap нужного размера
|
||||
# Create pixmap of required size
|
||||
pixmap_size = int(size * scaling)
|
||||
pixmap = QtGui.QPixmap(pixmap_size, pixmap_size)
|
||||
pixmap.fill(QtGui.QColor(0, 0, 0, 0)) # Прозрачный фон
|
||||
pixmap.fill(QtGui.QColor(0, 0, 0, 0)) # Transparent background
|
||||
|
||||
# Рендерим SVG в pixmap
|
||||
# Render SVG to pixmap
|
||||
painter = QtGui.QPainter(pixmap)
|
||||
renderer.render(painter)
|
||||
painter.end()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def is_gif_file(path):
|
||||
"""Проверяет, является ли файл GIF."""
|
||||
"""Checks if file is a GIF."""
|
||||
if isinstance(path, str):
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
return ext == '.gif'
|
||||
|
|
@ -95,9 +95,9 @@ def exif_rotated_image(path=None):
|
|||
def load_image(path):
|
||||
if isinstance(path, str):
|
||||
path = os.path.normpath(path)
|
||||
# Проверяем, является ли файл GIF
|
||||
# Check if file is a GIF
|
||||
if is_gif_file(path):
|
||||
return (None, path) # Возвращаем None для изображения, путь для GIF
|
||||
return (None, path) # Return None for image, path for GIF
|
||||
return (exif_rotated_image(path), path)
|
||||
if path.isLocalFile():
|
||||
path = os.path.normpath(path.toLocalFile())
|
||||
|
|
|
|||
120
beeref/items.py
120
beeref/items.py
|
|
@ -745,7 +745,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
"""Class for freehand drawing items."""
|
||||
|
||||
TYPE = 'draw'
|
||||
CLICKABLE_PADDING = 8.0 # Отступ вокруг линии для увеличения кликабельной области
|
||||
CLICKABLE_PADDING = 8.0 # Padding around line to increase clickable area
|
||||
|
||||
def __init__(self, path=None, **kwargs):
|
||||
super().__init__()
|
||||
|
|
@ -753,9 +753,9 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
logger.debug(f'Initialized {self}')
|
||||
self.is_image = False
|
||||
self.init_selectable()
|
||||
self.is_editable = False # Рисование не редактируется через двойной клик
|
||||
self.is_editable = False # Drawing is not editable via double-click
|
||||
|
||||
# Настройки пера по умолчанию
|
||||
# Default pen settings
|
||||
self.pen_color = QtGui.QColor(*COLORS['Scene:Text'])
|
||||
self.pen_width = 8
|
||||
self.pen_style = 'solid' # 'solid', 'dashed', 'arrow', '<-', '<->'
|
||||
|
|
@ -766,7 +766,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
self.setPath(path)
|
||||
|
||||
def setPath(self, path):
|
||||
"""Устанавливает путь и обновляет геометрию."""
|
||||
"""Sets path and updates geometry."""
|
||||
self.prepareGeometryChange()
|
||||
super().setPath(path)
|
||||
self.update()
|
||||
|
|
@ -776,7 +776,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
data = kwargs.get('data', {})
|
||||
item = cls()
|
||||
|
||||
# Восстановление пути из данных
|
||||
# Restore path from data
|
||||
if 'path' in data and data['path']:
|
||||
path = QtGui.QPainterPath()
|
||||
path_data = data['path']
|
||||
|
|
@ -800,7 +800,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
return f'Drawing ({self.path().elementCount()} points)'
|
||||
|
||||
def get_extra_save_data(self):
|
||||
"""Сохраняет данные рисунка для сериализации."""
|
||||
"""Saves drawing data for serialization."""
|
||||
path = self.path()
|
||||
path_data = []
|
||||
for i in range(path.elementCount()):
|
||||
|
|
@ -815,7 +815,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
}
|
||||
|
||||
def _update_pen(self):
|
||||
"""Обновляет перо с текущими настройками."""
|
||||
"""Updates pen with current settings."""
|
||||
if self.pen_style == 'dashed':
|
||||
pen_style = QtCore.Qt.PenStyle.DashLine
|
||||
else:
|
||||
|
|
@ -828,19 +828,19 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
self.setPen(pen)
|
||||
|
||||
def set_pen_color(self, color: QtGui.QColor):
|
||||
"""Устанавливает цвет пера."""
|
||||
"""Sets pen color."""
|
||||
self.pen_color = color
|
||||
self._update_pen()
|
||||
self.update()
|
||||
|
||||
def set_pen_width(self, width: int):
|
||||
"""Устанавливает толщину пера."""
|
||||
self.pen_width = max(1, min(width, 50)) # Ограничение 1-50
|
||||
"""Sets pen width."""
|
||||
self.pen_width = max(1, min(width, 50)) # Limit 1-50
|
||||
self._update_pen()
|
||||
self.update()
|
||||
|
||||
def set_pen_style(self, style: str):
|
||||
"""Устанавливает стиль линии: 'solid', 'dashed', 'arrow', '<-', '<->'."""
|
||||
"""Sets line style: 'solid', 'dashed', 'arrow', '<-', '<->'."""
|
||||
if style in ('solid', 'dashed', 'arrow', '<-', '<->'):
|
||||
self.pen_style = style
|
||||
self._update_pen()
|
||||
|
|
@ -861,31 +861,31 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
return item
|
||||
|
||||
def bounding_rect_unselected(self):
|
||||
"""Возвращает границы элемента без учета выделения."""
|
||||
"""Returns item bounds without selection."""
|
||||
path = self.path()
|
||||
if path.isEmpty():
|
||||
return QtCore.QRectF()
|
||||
|
||||
# Получаем boundingRect напрямую из пути
|
||||
# Get boundingRect directly from path
|
||||
base_rect = path.boundingRect()
|
||||
|
||||
# Добавляем отступ для толщины пера и кликабельной области
|
||||
# Add margin for pen width and clickable area
|
||||
margin = (self.pen_width / 2.0) + self.CLICKABLE_PADDING
|
||||
return base_rect.marginsAdded(
|
||||
QtCore.QMarginsF(margin, margin, margin, margin))
|
||||
|
||||
def shape(self):
|
||||
"""Возвращает прямоугольную кликабельную область, как в PureRef."""
|
||||
"""Returns rectangular clickable area, like in PureRef."""
|
||||
path = QtGui.QPainterPath()
|
||||
rect = self.bounding_rect_unselected()
|
||||
|
||||
# Если элемент выделен и есть ручки, добавляем области для ручек
|
||||
# If item is selected and has handles, add handle areas
|
||||
if self.has_selection_handles():
|
||||
margin = self.select_resize_size / 2
|
||||
rect = rect.marginsAdded(
|
||||
QtCore.QMarginsF(margin, margin, margin, margin))
|
||||
path.addRect(rect)
|
||||
# Добавляем области для ручек поворота в углах
|
||||
# Add rotation handle areas at corners
|
||||
for corner in self.corners:
|
||||
path.addPath(self.get_rotate_bounds(corner))
|
||||
else:
|
||||
|
|
@ -894,74 +894,74 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
return path
|
||||
|
||||
def contains(self, point):
|
||||
"""Проверяет, попадает ли точка в прямоугольную область линии."""
|
||||
# Используем boundingRect для прямоугольной области клика
|
||||
"""Checks if point falls within rectangular line area."""
|
||||
# Use boundingRect for rectangular click area
|
||||
return self.bounding_rect_unselected().contains(point)
|
||||
|
||||
|
||||
def _get_path_end_points(self, path):
|
||||
"""Получает последние две точки пути для определения направления стрелки."""
|
||||
"""Gets last two path points to determine arrow direction."""
|
||||
if path.elementCount() < 2:
|
||||
return None, None
|
||||
|
||||
# Получаем последнюю точку пути
|
||||
# Get last path point
|
||||
last_point = path.pointAtPercent(1.0)
|
||||
|
||||
# Получаем предпоследнюю точку (близко к концу)
|
||||
# Get second-to-last point (close to end)
|
||||
if path.elementCount() >= 2:
|
||||
prev_point = path.pointAtPercent(0.95) # 95% от пути
|
||||
prev_point = path.pointAtPercent(0.95) # 95% of path
|
||||
else:
|
||||
prev_point = path.pointAtPercent(0.0)
|
||||
|
||||
return prev_point, last_point
|
||||
|
||||
def _get_path_start_points(self, path):
|
||||
"""Получает первые две точки пути для определения направления стрелки."""
|
||||
"""Gets first two path points to determine arrow direction."""
|
||||
if path.elementCount() < 2:
|
||||
return None, None
|
||||
|
||||
# Получаем первую точку пути
|
||||
# Get first path point
|
||||
first_point = path.pointAtPercent(0.0)
|
||||
|
||||
# Получаем вторую точку (близко к началу)
|
||||
# Get second point (close to start)
|
||||
if path.elementCount() >= 2:
|
||||
second_point = path.pointAtPercent(0.05) # 5% от пути
|
||||
second_point = path.pointAtPercent(0.05) # 5% of path
|
||||
else:
|
||||
second_point = path.pointAtPercent(1.0)
|
||||
|
||||
return first_point, second_point
|
||||
|
||||
def _draw_arrow_right(self, painter, path):
|
||||
"""Рисует стрелку вправо в конце линии."""
|
||||
"""Draws arrow to the right at the end of line."""
|
||||
if path.elementCount() < 2:
|
||||
return
|
||||
|
||||
# Получаем последние две точки для определения направления
|
||||
# Get last two points to determine direction
|
||||
prev_point, last_point = self._get_path_end_points(path)
|
||||
if prev_point is None or last_point is None:
|
||||
return
|
||||
|
||||
# Вычисляем направление стрелки
|
||||
# Calculate arrow direction
|
||||
dx = last_point.x() - prev_point.x()
|
||||
dy = last_point.y() - prev_point.y()
|
||||
length = (dx * dx + dy * dy) ** 0.5
|
||||
if length == 0:
|
||||
return
|
||||
|
||||
# Нормализуем вектор направления
|
||||
# Normalize direction vector
|
||||
dx /= length
|
||||
dy /= length
|
||||
|
||||
# Размер стрелки зависит от толщины линии
|
||||
# Arrow size depends on line width
|
||||
arrow_size = max(self.pen_width * 3, 8)
|
||||
# Угол стрелки
|
||||
angle = 0.5 # примерно 30 градусов
|
||||
# Arrow angle
|
||||
angle = 0.5 # approximately 30 degrees
|
||||
|
||||
# Координаты конца стрелки
|
||||
# Arrow end coordinates
|
||||
end_x = last_point.x()
|
||||
end_y = last_point.y()
|
||||
|
||||
# Координаты боковых точек стрелки
|
||||
# Arrow side point coordinates
|
||||
perp_x = -dy
|
||||
perp_y = dx
|
||||
arrow_x1 = end_x - arrow_size * dx + arrow_size * angle * perp_x
|
||||
|
|
@ -969,7 +969,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
arrow_x2 = end_x - arrow_size * dx - arrow_size * angle * perp_x
|
||||
arrow_y2 = end_y - arrow_size * dy - arrow_size * angle * perp_y
|
||||
|
||||
# Рисуем стрелку
|
||||
# Draw arrow
|
||||
arrow_path = QtGui.QPainterPath()
|
||||
arrow_path.moveTo(end_x, end_y)
|
||||
arrow_path.lineTo(arrow_x1, arrow_y1)
|
||||
|
|
@ -983,36 +983,36 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
painter.drawPath(arrow_path)
|
||||
|
||||
def _draw_arrow_left(self, painter, path):
|
||||
"""Рисует стрелку влево в начале линии."""
|
||||
"""Draws arrow to the left at the start of line."""
|
||||
if path.elementCount() < 2:
|
||||
return
|
||||
|
||||
# Получаем первые две точки для определения направления
|
||||
# Get first two points to determine direction
|
||||
first_point, second_point = self._get_path_start_points(path)
|
||||
if first_point is None or second_point is None:
|
||||
return
|
||||
|
||||
# Вычисляем направление стрелки (от второй точки к первой)
|
||||
# Calculate arrow direction (from second point to first)
|
||||
dx = first_point.x() - second_point.x()
|
||||
dy = first_point.y() - second_point.y()
|
||||
length = (dx * dx + dy * dy) ** 0.5
|
||||
if length == 0:
|
||||
return
|
||||
|
||||
# Нормализуем вектор направления
|
||||
# Normalize direction vector
|
||||
dx /= length
|
||||
dy /= length
|
||||
|
||||
# Размер стрелки зависит от толщины линии
|
||||
# Arrow size depends on line width
|
||||
arrow_size = max(self.pen_width * 3, 8)
|
||||
# Угол стрелки
|
||||
angle = 0.5 # примерно 30 градусов
|
||||
# Arrow angle
|
||||
angle = 0.5 # approximately 30 degrees
|
||||
|
||||
# Координаты начала стрелки
|
||||
# Arrow start coordinates
|
||||
start_x = first_point.x()
|
||||
start_y = first_point.y()
|
||||
|
||||
# Координаты боковых точек стрелки
|
||||
# Arrow side point coordinates
|
||||
perp_x = -dy
|
||||
perp_y = dx
|
||||
arrow_x1 = start_x - arrow_size * dx + arrow_size * angle * perp_x
|
||||
|
|
@ -1020,7 +1020,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
arrow_x2 = start_x - arrow_size * dx - arrow_size * angle * perp_x
|
||||
arrow_y2 = start_y - arrow_size * dy - arrow_size * angle * perp_y
|
||||
|
||||
# Рисуем стрелку
|
||||
# Draw arrow
|
||||
arrow_path = QtGui.QPainterPath()
|
||||
arrow_path.moveTo(start_x, start_y)
|
||||
arrow_path.lineTo(arrow_x1, arrow_y1)
|
||||
|
|
@ -1034,11 +1034,11 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
painter.drawPath(arrow_path)
|
||||
|
||||
def _draw_arrow_both(self, painter, path):
|
||||
"""Рисует стрелки в обе стороны линии."""
|
||||
"""Draws arrows on both sides of line."""
|
||||
if path.elementCount() < 2:
|
||||
return
|
||||
|
||||
# Рисуем стрелку вправо (в конце)
|
||||
# Draw arrow to the right (at end)
|
||||
prev_point, last_point = self._get_path_end_points(path)
|
||||
if prev_point is not None and last_point is not None:
|
||||
dx = last_point.x() - prev_point.x()
|
||||
|
|
@ -1070,7 +1070,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
QtCore.Qt.PenJoinStyle.RoundJoin))
|
||||
painter.drawPath(arrow_path)
|
||||
|
||||
# Рисуем стрелку влево (в начале)
|
||||
# Draw arrow to the left (at start)
|
||||
first_point, second_point = self._get_path_start_points(path)
|
||||
if first_point is not None and second_point is not None:
|
||||
dx = first_point.x() - second_point.x()
|
||||
|
|
@ -1103,15 +1103,15 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
painter.drawPath(arrow_path)
|
||||
|
||||
def paint(self, painter, option, widget):
|
||||
"""Отрисовка пути с рамкой выделения."""
|
||||
# Отключаем стандартную отрисовку Qt для выделенных элементов
|
||||
"""Renders path with selection outline."""
|
||||
# Disable standard Qt rendering for selected items
|
||||
option.state &= ~QtWidgets.QStyle.StateFlag.State_Selected
|
||||
option.state &= ~QtWidgets.QStyle.StateFlag.State_HasFocus
|
||||
# Рисуем основную линию
|
||||
# Draw main line
|
||||
super().paint(painter, option, widget)
|
||||
|
||||
path = self.path()
|
||||
# Рисуем стрелку в зависимости от стиля
|
||||
# Draw arrow depending on style
|
||||
if self.pen_style == 'arrow':
|
||||
self._draw_arrow_right(painter, path)
|
||||
elif self.pen_style == '<-':
|
||||
|
|
@ -1122,13 +1122,13 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
self.paint_selectable(painter, option, widget)
|
||||
|
||||
def paint_debug(self, painter, option, widget):
|
||||
"""Переопределяем для полного отключения отладочной информации."""
|
||||
# Полностью отключаем отладочную информацию для линий
|
||||
"""Override to completely disable debug information."""
|
||||
# Completely disable debug information for lines
|
||||
pass
|
||||
|
||||
def paint_selectable(self, painter, option, widget):
|
||||
"""Переопределяем для убирания пунктирной обводки (отладочной информации)."""
|
||||
# Не вызываем paint_debug, чтобы убрать пунктирную обводку
|
||||
"""Override to remove dashed outline (debug information)."""
|
||||
# Don't call paint_debug to remove dashed outline
|
||||
# self.paint_debug(painter, option, widget)
|
||||
|
||||
if not self.has_selection_outline():
|
||||
|
|
@ -1152,7 +1152,7 @@ class BeeDrawItem(BeeItemMixin, QtWidgets.QGraphicsPathItem):
|
|||
painter.drawPoint(corner)
|
||||
|
||||
def copy_to_clipboard(self, clipboard):
|
||||
"""Для рисования копирование не поддерживается."""
|
||||
"""Copying is not supported for drawings."""
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -1231,6 +1231,6 @@ class BeeErrorItem(BeeItemMixin, QtWidgets.QGraphicsTextItem):
|
|||
clipboard.setText(self.toPlainText())
|
||||
|
||||
|
||||
# Импортируем GIF item для регистрации в item_registry
|
||||
# Import GIF item for registration in item_registry
|
||||
from beeref.gif_item import BeeGifItem # noqa: E402, F401
|
||||
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ class UpwardComboBox(QtWidgets.QComboBox):
|
|||
def showPopup(self):
|
||||
"""Override to show popup above the combobox."""
|
||||
super().showPopup()
|
||||
# Используем QTimer для получения popup после его создания
|
||||
# Use QTimer to get popup after it's created
|
||||
QtCore.QTimer.singleShot(0, self._reposition_popup)
|
||||
|
||||
def _reposition_popup(self):
|
||||
"""Перемещает popup выше combobox."""
|
||||
# Ищем активный popup виджет
|
||||
"""Moves popup above the combobox."""
|
||||
# Find active popup widget
|
||||
popup = QtWidgets.QApplication.activePopupWidget()
|
||||
if not popup:
|
||||
# Альтернативный способ - найти через view
|
||||
# Alternative method - find through view
|
||||
view = self.view()
|
||||
if view:
|
||||
popup = view.parent()
|
||||
|
|
@ -41,9 +41,9 @@ class UpwardComboBox(QtWidgets.QComboBox):
|
|||
popup = popup.parent()
|
||||
|
||||
if popup:
|
||||
# Получаем глобальную позицию combobox
|
||||
# Get global position of combobox
|
||||
global_pos = self.mapToGlobal(QtCore.QPoint(0, 0))
|
||||
# Вычисляем новую позицию выше combobox
|
||||
# Calculate new position above combobox
|
||||
popup_height = popup.height()
|
||||
new_y = global_pos.y() - popup_height
|
||||
popup.move(global_pos.x(), new_y)
|
||||
|
|
@ -56,7 +56,7 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
super().__init__(parent, view)
|
||||
self.current_item: Optional["BeeDrawItem"] = None
|
||||
|
||||
# Кнопка выбора цвета через colorpicker
|
||||
# Color selection button via colorpicker
|
||||
assets = BeeAssets()
|
||||
icons_path = assets.PATH.joinpath('icons')
|
||||
palette_icon = QtGui.QIcon(str(icons_path.joinpath('palette.svg')))
|
||||
|
|
@ -68,15 +68,15 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
)
|
||||
self.color_btn.setToolTip("Color")
|
||||
|
||||
# Combobox для выбора стиля линии
|
||||
# Combobox for line style selection
|
||||
self.add_separator()
|
||||
self.style_combo = self.add_style_combobox()
|
||||
|
||||
# Селектор толщины пера
|
||||
# Pen width selector
|
||||
self.add_separator()
|
||||
self.width_slider = self.add_width_slider()
|
||||
|
||||
# Кнопка закрытия меню (отмена режима рисования)
|
||||
# Close menu button (cancel drawing mode)
|
||||
self.add_separator()
|
||||
close_icon = QtGui.QIcon(str(icons_path.joinpath('close.svg')))
|
||||
self.close_button = self.add_button(
|
||||
|
|
@ -87,11 +87,11 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
self.close_button.setToolTip("Close")
|
||||
|
||||
def add_style_combobox(self):
|
||||
"""Добавляет combobox для выбора стиля линии."""
|
||||
"""Adds combobox for line style selection."""
|
||||
assets = BeeAssets()
|
||||
icons_path = assets.PATH.joinpath('icons')
|
||||
|
||||
# Загружаем иконки для стилей
|
||||
# Load icons for styles
|
||||
solid_icon = QtGui.QIcon(str(icons_path.joinpath('line-solid.svg')))
|
||||
dashed_icon = QtGui.QIcon(str(icons_path.joinpath('line-dashed.svg')))
|
||||
arrow_icon = QtGui.QIcon(str(icons_path.joinpath('line-arrow.svg')))
|
||||
|
|
@ -103,20 +103,20 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
combo.setMinimumWidth(40)
|
||||
combo.setIconSize(QtCore.QSize(32, 32))
|
||||
|
||||
# Добавляем элементы с иконками
|
||||
# Add items with icons
|
||||
combo.addItem(solid_icon, "", 'solid')
|
||||
combo.addItem(dashed_icon, "", 'dashed')
|
||||
combo.addItem(arrow_icon, "", 'arrow')
|
||||
combo.addItem(arrow_left_icon, "", '<-')
|
||||
combo.addItem(arrow_both_icon, "", '<->')
|
||||
# Устанавливаем иконки для элементов
|
||||
# Set icons for items
|
||||
combo.setItemIcon(0, solid_icon)
|
||||
combo.setItemIcon(1, dashed_icon)
|
||||
combo.setItemIcon(2, arrow_icon)
|
||||
combo.setItemIcon(3, arrow_left_icon)
|
||||
combo.setItemIcon(4, arrow_both_icon)
|
||||
|
||||
combo.setCurrentIndex(0) # По умолчанию solid
|
||||
combo.setCurrentIndex(0) # Default to solid
|
||||
combo.currentIndexChanged.connect(self._on_style_changed)
|
||||
combo.setToolTip("Line style")
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
return combo
|
||||
|
||||
def _on_color_clicked(self) -> None:
|
||||
"""Открывает диалог выбора цвета."""
|
||||
"""Opens color selection dialog."""
|
||||
if not self.current_item:
|
||||
return
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
self.set_pen_color(color)
|
||||
|
||||
def _on_style_changed(self, index: int) -> None:
|
||||
"""Обработчик изменения стиля линии."""
|
||||
"""Handler for line style change."""
|
||||
if not self.current_item:
|
||||
return
|
||||
style = self.style_combo.itemData(index)
|
||||
|
|
@ -144,7 +144,7 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
self.set_pen_style(style)
|
||||
|
||||
def add_width_slider(self):
|
||||
"""Добавляет слайдер для выбора толщины пера."""
|
||||
"""Adds slider for pen width selection."""
|
||||
container = QtWidgets.QWidget()
|
||||
layout = QtWidgets.QHBoxLayout(container)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -162,11 +162,11 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
return slider
|
||||
|
||||
def show_for_item(self, item: "BeeDrawItem") -> None:
|
||||
"""Показывает меню для выбранного элемента рисования."""
|
||||
"""Shows menu for selected drawing item."""
|
||||
self.current_item = item
|
||||
if item:
|
||||
self.width_slider.setValue(item.pen_width)
|
||||
# Устанавливаем текущий стиль в combobox
|
||||
# Set current style in combobox
|
||||
style_index = self.style_combo.findData(item.pen_style)
|
||||
if style_index >= 0:
|
||||
self.style_combo.blockSignals(True)
|
||||
|
|
@ -175,29 +175,29 @@ class DrawFloatingMenu(FloatingMenu):
|
|||
super().show_for_item(item)
|
||||
|
||||
def set_pen_color(self, color: QtGui.QColor):
|
||||
"""Устанавливает цвет пера для выбранного элемента."""
|
||||
"""Sets pen color for selected item."""
|
||||
if self.current_item:
|
||||
self.current_item.set_pen_color(color)
|
||||
|
||||
def set_pen_width(self, width: int):
|
||||
"""Устанавливает толщину пера для выбранного элемента."""
|
||||
"""Sets pen width for selected item."""
|
||||
if self.current_item:
|
||||
self.current_item.set_pen_width(width)
|
||||
|
||||
def set_pen_style(self, style: str):
|
||||
"""Устанавливает стиль линии для выбранного элемента."""
|
||||
"""Sets line style for selected item."""
|
||||
if self.current_item:
|
||||
self.current_item.set_pen_style(style)
|
||||
|
||||
def _on_close_clicked(self) -> None:
|
||||
"""Закрывает меню и отменяет режим рисования."""
|
||||
"""Closes menu and cancels drawing mode."""
|
||||
self.view.cancel_drawing_mode()
|
||||
self.hide_menu()
|
||||
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
|
||||
"""Обрабатывает нажатия клавиш."""
|
||||
"""Handles key press events."""
|
||||
if event.key() == QtCore.Qt.Key.Key_Escape:
|
||||
# ESC закрывает меню и отменяет режим рисования
|
||||
# ESC closes menu and cancels drawing mode
|
||||
self.view.cancel_drawing_mode()
|
||||
self.hide_menu()
|
||||
event.accept()
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
self._cached_viewport_size: Optional[QtCore.QSize] = None
|
||||
self._cached_window_pos: Optional[QtCore.QPoint] = None
|
||||
|
||||
# Таймер для отслеживания перемещения главного окна
|
||||
# Timer for tracking main window movement
|
||||
self._position_timer = QtCore.QTimer(self)
|
||||
self._position_timer.timeout.connect(self._check_window_position)
|
||||
self._position_timer.setInterval(50) # Проверка каждые 50мс
|
||||
self._position_timer.setInterval(50) # Check every 50ms
|
||||
|
||||
# Main layout with uniform spacing
|
||||
self._layout = QtWidgets.QHBoxLayout(self)
|
||||
|
|
@ -56,7 +56,7 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
self.hide()
|
||||
|
||||
def _check_window_position(self) -> None:
|
||||
"""Проверяет позицию viewport и обновляет позицию меню при необходимости."""
|
||||
"""Checks viewport position and updates menu position if needed."""
|
||||
if not self.isVisible():
|
||||
return
|
||||
|
||||
|
|
@ -68,11 +68,11 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
if viewport is None:
|
||||
return
|
||||
|
||||
# Получаем позицию viewport в глобальных координатах
|
||||
# Get viewport position in global coordinates
|
||||
view_rect = viewport.rect()
|
||||
current_viewport_pos = viewport.mapToGlobal(view_rect.topLeft())
|
||||
|
||||
# Если позиция viewport изменилась, обновляем позицию меню
|
||||
# If viewport position changed, update menu position
|
||||
if self._cached_window_pos is not None and self._cached_window_pos != current_viewport_pos:
|
||||
self.update_position()
|
||||
|
||||
|
|
@ -80,50 +80,50 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
|
||||
def _apply_rounded_mask(self) -> None:
|
||||
"""
|
||||
Применяет закругленную маску к виджету с антиалиасингом.
|
||||
Реализация на основе подхода из статьи VK Teams.
|
||||
Applies rounded mask to widget with antialiasing.
|
||||
Implementation based on approach from VK Teams article.
|
||||
"""
|
||||
size = self.size()
|
||||
if size.width() == 0 or size.height() == 0:
|
||||
return
|
||||
|
||||
# Для более плавного сглаживания используем QBitmap с антиалиасингом
|
||||
# Создаем изображение с увеличенным разрешением (как в статье)
|
||||
# Use QBitmap with antialiasing for smoother rendering
|
||||
# Create image with increased resolution (as in article)
|
||||
scale_factor = 2
|
||||
scaled_size = QtCore.QSize(
|
||||
int(size.width() * scale_factor),
|
||||
int(size.height() * scale_factor)
|
||||
)
|
||||
|
||||
# Создаем QPixmap для рисования с антиалиасингом
|
||||
# Create QPixmap for drawing with antialiasing
|
||||
pixmap = QtGui.QPixmap(scaled_size)
|
||||
pixmap.fill(QtCore.Qt.GlobalColor.transparent)
|
||||
|
||||
painter = QtGui.QPainter(pixmap)
|
||||
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True)
|
||||
painter.setPen(QtCore.Qt.PenStyle.NoPen)
|
||||
painter.setBrush(QtCore.Qt.GlobalColor.black) # Черный для маски
|
||||
painter.setBrush(QtCore.Qt.GlobalColor.black) # Black for mask
|
||||
|
||||
# Рисуем закругленный прямоугольник на увеличенном разрешении
|
||||
# Draw rounded rectangle at increased resolution
|
||||
scaled_rect = QtCore.QRectF(0, 0, scaled_size.width(), scaled_size.height())
|
||||
scaled_radius = self.CORNER_RADIUS * scale_factor
|
||||
painter.drawRoundedRect(scaled_rect, scaled_radius, scaled_radius)
|
||||
painter.end()
|
||||
|
||||
# Масштабируем обратно с сглаживанием
|
||||
# Scale back with smoothing
|
||||
pixmap = pixmap.scaled(
|
||||
size,
|
||||
QtCore.Qt.AspectRatioMode.IgnoreAspectRatio,
|
||||
QtCore.Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
|
||||
# Преобразуем в QBitmap для маски
|
||||
# В маске: непрозрачные пиксели = видимые области, прозрачные = невидимые
|
||||
# Convert to QBitmap for mask
|
||||
# In mask: opaque pixels = visible areas, transparent = invisible
|
||||
image = pixmap.toImage()
|
||||
# Создаем маску из непрозрачных пикселей
|
||||
# Create mask from opaque pixels
|
||||
bitmap = QtGui.QBitmap.fromImage(image.createAlphaMask())
|
||||
|
||||
# Применяем маску
|
||||
# Apply mask
|
||||
self.setMask(bitmap)
|
||||
|
||||
def add_widget(self, widget: QtWidgets.QWidget) -> QtWidgets.QWidget:
|
||||
|
|
@ -172,18 +172,18 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
self._cached_viewport_size = None
|
||||
self._cached_window_pos = None
|
||||
self.update_position()
|
||||
# Запускаем таймер для отслеживания перемещения окна
|
||||
# Start timer for tracking window movement
|
||||
self._position_timer.start()
|
||||
# Ensure menu is on top after positioning
|
||||
self.raise_()
|
||||
# Возвращаем фокус view, чтобы события клавиатуры обрабатывались правильно
|
||||
# FloatingMenu не должен перехватывать фокус, так как он имеет NoFocus
|
||||
# Return focus to view so keyboard events are handled correctly
|
||||
# FloatingMenu should not intercept focus as it has NoFocus
|
||||
if self.view:
|
||||
self.view.setFocus()
|
||||
|
||||
def hide_menu(self) -> None:
|
||||
self.current_item = None
|
||||
# Останавливаем таймер
|
||||
# Stop timer
|
||||
self._position_timer.stop()
|
||||
# Clear cache when hiding menu
|
||||
self._cached_position = None
|
||||
|
|
@ -207,7 +207,7 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
view_rect = viewport.rect()
|
||||
viewport_size = view_rect.size()
|
||||
|
||||
# Получаем текущую позицию viewport для проверки изменений
|
||||
# Get current viewport position for change detection
|
||||
current_viewport_pos = viewport.mapToGlobal(view_rect.topLeft())
|
||||
|
||||
# Check if viewport size and position changed
|
||||
|
|
@ -232,28 +232,28 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
bottom_right_global = viewport.mapToGlobal(view_rect.bottomRight())
|
||||
top_right_global = viewport.mapToGlobal(view_rect.topRight())
|
||||
|
||||
# Получаем границы viewport в глобальных координатах
|
||||
# Get viewport bounds in global coordinates
|
||||
viewport_left = top_left_global.x()
|
||||
viewport_right = top_right_global.x()
|
||||
viewport_top = top_left_global.y()
|
||||
viewport_bottom = bottom_left_global.y()
|
||||
viewport_width = viewport_right - viewport_left
|
||||
|
||||
# Вычисляем позицию по X (центрируем, но не выходим за границы)
|
||||
# Calculate X position (center, but don't go beyond boundaries)
|
||||
x = viewport_left + max(0, (viewport_width - width) // 2)
|
||||
# Ограничиваем, чтобы меню не выходило за левую и правую границы
|
||||
# Limit to prevent menu from going beyond left and right boundaries
|
||||
x = max(viewport_left, min(x, viewport_right - width))
|
||||
|
||||
# Вычисляем позицию по Y (снизу с отступом)
|
||||
# Calculate Y position (bottom with margin)
|
||||
y = viewport_bottom - height - self.BOTTOM_MARGIN
|
||||
# Ограничиваем, чтобы меню не выходило за верхнюю границу
|
||||
# Если меню не помещается снизу, размещаем его сверху
|
||||
# Limit to prevent menu from going beyond top boundary
|
||||
# If menu doesn't fit at bottom, place it at top
|
||||
if y < viewport_top:
|
||||
y = viewport_top + self.BOTTOM_MARGIN
|
||||
# Также проверяем, что меню не выходит за нижнюю границу
|
||||
# Also check that menu doesn't go beyond bottom boundary
|
||||
if y + height > viewport_bottom:
|
||||
y = viewport_bottom - height - self.BOTTOM_MARGIN
|
||||
# Если и так не помещается, размещаем сверху
|
||||
# If still doesn't fit, place at top
|
||||
if y < viewport_top:
|
||||
y = viewport_top + self.BOTTOM_MARGIN
|
||||
|
||||
|
|
@ -265,7 +265,7 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
self._cached_position = new_position
|
||||
self._cached_viewport_size = viewport_size
|
||||
self._cached_window_pos = current_viewport_pos
|
||||
# Обновляем маску после изменения размера/позиции
|
||||
# Update mask after size/position change
|
||||
self._apply_rounded_mask()
|
||||
|
||||
def parentWidget(self):
|
||||
|
|
@ -289,6 +289,6 @@ class FloatingMenu(QtWidgets.QWidget):
|
|||
super().mouseReleaseEvent(event)
|
||||
|
||||
def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
|
||||
"""Обновляет маску при изменении размера виджета."""
|
||||
"""Updates mask when widget size changes."""
|
||||
super().resizeEvent(event)
|
||||
self._apply_rounded_mask()
|
||||
|
|
|
|||
Loading…
Reference in a new issue