mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Rework selection code part 1
This commit is contained in:
parent
eea5adb416
commit
667239b8b6
6 changed files with 89 additions and 46 deletions
|
|
@ -43,6 +43,8 @@ def handle_sqlite_errors(func):
|
|||
func(self, *args, **kwargs)
|
||||
except sqlite3.Error as e:
|
||||
logger.exception(f'Error while reading/writing {self.filename}')
|
||||
if self.progress:
|
||||
self.progress.setValue(self.progress.maximum())
|
||||
raise BeeFileIOError(msg=str(e), filename=self.filename) from e
|
||||
|
||||
return wrapper
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
|
|||
|
||||
logger.debug(f'Setting scale for image "{self.filename}" to {factor}')
|
||||
super().setScale(factor)
|
||||
SelectionItem.update_selection(self)
|
||||
|
||||
def set_pos_center(self, x, y):
|
||||
"""Sets the position using the item's center as the origin point."""
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
|
|||
Items to be saved are items that have an save_id attribute.
|
||||
"""
|
||||
|
||||
return filter(lambda i: hasattr(i, 'save_id'), self.items())
|
||||
return filter(lambda i: hasattr(i, 'save_id'),
|
||||
self.items(order=Qt.SortOrder.AscendingOrder))
|
||||
|
||||
def clear_save_ids(self):
|
||||
for item in self.items_for_save():
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from collections.abc import Iterable
|
||||
import logging
|
||||
|
||||
from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
|
@ -27,9 +28,10 @@ logger = logging.getLogger('BeeRef')
|
|||
class SelectionItem(QtWidgets.QGraphicsItem):
|
||||
|
||||
color = QtGui.QColor(116, 234, 231, 255)
|
||||
LINE_WIDTH = 3
|
||||
HANDLE_SIZE = 10 # scaling handles
|
||||
RESIZE_SIZE = 30 # area for scaling hover events
|
||||
LINE_WIDTH = 4
|
||||
HANDLE_SIZE = 15 # scale handles
|
||||
RESIZE_SIZE = 30 # area for scale hover events
|
||||
ROTATE_SIZE = 30 # area for rotation hover events
|
||||
|
||||
debug = False
|
||||
|
||||
|
|
@ -41,86 +43,102 @@ class SelectionItem(QtWidgets.QGraphicsItem):
|
|||
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable
|
||||
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable)
|
||||
self.scale_active = False
|
||||
self.previous_scale = None
|
||||
self.setZValue(1)
|
||||
|
||||
@property
|
||||
def bottom_right_scale_bounds(self):
|
||||
"""The intercatable shape of the bottom right scale handle"""
|
||||
bounds = self.parentItem().boundingRect()
|
||||
pos = bounds.bottomRight()
|
||||
"""The interactable shape of the bottom right scale handle"""
|
||||
return QtCore.QRectF(
|
||||
pos.x() - self.resize_size/2,
|
||||
pos.y() - self.resize_size/2,
|
||||
self.parentItem().width - self.resize_size/2,
|
||||
self.parentItem().height - self.resize_size/2,
|
||||
self.resize_size,
|
||||
self.resize_size)
|
||||
|
||||
@property
|
||||
def bottom_right_rotate_bounds(self):
|
||||
"""The interactable shape of the bottom right rotate handle"""
|
||||
return QtCore.QRectF(
|
||||
self.parentItem().width + self.resize_size / 2,
|
||||
self.parentItem().height + self.resize_size / 2,
|
||||
self.rotate_size, self.rotate_size)
|
||||
|
||||
def scale_with_view(self, value):
|
||||
"""The handles and line thickness should always stay the same size on
|
||||
"""The interactable areas should always stay the same size on
|
||||
the screen so we need to adjust the values according to the scale
|
||||
factor of the view."""
|
||||
|
||||
scale = self.parentItem().scene().views()[0].get_scale()
|
||||
scale = self.scene().views()[0].get_scale()
|
||||
return value / scale / self.parentItem().scale()
|
||||
|
||||
def boundingRect(self):
|
||||
bounds = self.parentItem().boundingRect()
|
||||
margin = self.resize_size / 2 + self.rotate_size
|
||||
return QtCore.QRectF(
|
||||
bounds.topLeft().x() - self.resize_size / 2,
|
||||
bounds.topLeft().y() - self.resize_size / 2,
|
||||
bounds.bottomRight().x() + self.resize_size,
|
||||
bounds.bottomRight().y() + self.resize_size)
|
||||
|
||||
@property
|
||||
def handle_size(self):
|
||||
return self.scale_with_view(self.HANDLE_SIZE)
|
||||
bounds.topLeft().x() - margin,
|
||||
bounds.topLeft().y() - margin,
|
||||
bounds.bottomRight().x() + 2 * margin,
|
||||
bounds.bottomRight().y() + 2 * margin)
|
||||
|
||||
@property
|
||||
def resize_size(self):
|
||||
return self.scale_with_view(self.RESIZE_SIZE)
|
||||
|
||||
@property
|
||||
def line_width(self):
|
||||
return self.scale_with_view(self.LINE_WIDTH)
|
||||
def rotate_size(self):
|
||||
return self.scale_with_view(self.ROTATE_SIZE)
|
||||
|
||||
def shape(self):
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRect(self.bottom_right_scale_bounds)
|
||||
path.addRect(self.bottom_right_rotate_bounds)
|
||||
return path
|
||||
|
||||
def draw_debug_rect(self, painter, rect):
|
||||
pen = QtGui.QPen(QtGui.QColor('red'))
|
||||
pen.setWidth(self.line_width / 2)
|
||||
painter.setPen(pen)
|
||||
painter.drawRect(rect)
|
||||
def draw_debug_shape(self, painter, shape):
|
||||
color = QtGui.QColor(0, 255, 0, 20)
|
||||
if isinstance(shape, QtCore.QRectF):
|
||||
painter.fillRect(shape, color)
|
||||
else:
|
||||
painter.fillPath(shape, color)
|
||||
|
||||
def update_geometry(self):
|
||||
current_scale = self.scale_with_view(1)
|
||||
if current_scale != self.previous_scale:
|
||||
logger.debug('Selection geometry update')
|
||||
self.prepareGeometryChange()
|
||||
self.update()
|
||||
self.previous_scale = current_scale
|
||||
|
||||
def paint(self, painter, option, widget):
|
||||
pen = QtGui.QPen(self.color)
|
||||
pen.setWidth(self.line_width)
|
||||
pen.setWidth(self.LINE_WIDTH)
|
||||
pen.setCosmetic(True)
|
||||
painter.setPen(pen)
|
||||
|
||||
# Draw the main selection rectangle
|
||||
bounds = self.parentItem().boundingRect()
|
||||
painter.drawRect(bounds)
|
||||
painter.drawRect(
|
||||
0, 0, self.parentItem().width, self.parentItem().height)
|
||||
|
||||
single_select_mode = self.parentItem().scene().has_single_selection()
|
||||
single_select_mode = self.scene().has_single_selection()
|
||||
self.setEnabled(single_select_mode)
|
||||
|
||||
# If it's a single selection, draw the handles:
|
||||
if single_select_mode:
|
||||
pos = bounds.bottomRight()
|
||||
painter.fillRect(pos.x() - self.handle_size/2,
|
||||
pos.y() - self.handle_size/2,
|
||||
self.handle_size,
|
||||
self.handle_size,
|
||||
self.color)
|
||||
pen.setWidth(self.HANDLE_SIZE)
|
||||
painter.setPen(pen)
|
||||
painter.drawPoint(
|
||||
self.parentItem().width, self.parentItem().height)
|
||||
|
||||
if self.debug:
|
||||
self.draw_debug_rect(painter, self.boundingRect())
|
||||
self.draw_debug_rect(painter, self.bottom_right_scale_bounds)
|
||||
self.draw_debug_shape(painter, self.boundingRect())
|
||||
self.draw_debug_shape(painter, self.shape())
|
||||
|
||||
def hoverMoveEvent(self, event):
|
||||
# In bottomright scale area?
|
||||
if self.bottom_right_scale_bounds.contains(event.pos()):
|
||||
self.setCursor(Qt.CursorShape.SizeFDiagCursor)
|
||||
elif self.bottom_right_rotate_bounds.contains(event.pos()):
|
||||
self.setCursor(Qt.CursorShape.ForbiddenCursor)
|
||||
else:
|
||||
self.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
|
|
@ -141,7 +159,7 @@ class SelectionItem(QtWidgets.QGraphicsItem):
|
|||
self.parentItem().setScale(self.orig_scale_factor + delta)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self.parentItem().scene().undo_stack.push(
|
||||
self.scene().undo_stack.push(
|
||||
commands.ScaleItemsBy(self.scene().selectedItems(),
|
||||
self.get_scale_delta(event),
|
||||
ignore_first_redo=True))
|
||||
|
|
@ -163,3 +181,11 @@ class SelectionItem(QtWidgets.QGraphicsItem):
|
|||
# Deleting them might have been the cause of segfaults when
|
||||
# they are in the middle of receiving events...
|
||||
item.childItems()[0].setVisible(False)
|
||||
|
||||
@classmethod
|
||||
def update_selection(cls, items):
|
||||
if not isinstance(items, Iterable):
|
||||
items = [items]
|
||||
for item in items:
|
||||
if item.childItems():
|
||||
item.childItems()[0].update_geometry()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from beeref import fileio
|
|||
from beeref.gui import BeeProgressDialog, WelcomeOverlay
|
||||
from beeref.items import BeePixmapItem
|
||||
from beeref.scene import BeeGraphicsScene
|
||||
|
||||
from beeref import selection
|
||||
|
||||
logger = logging.getLogger('BeeRef')
|
||||
|
||||
|
|
@ -317,6 +317,10 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
|
|||
for i, filename in enumerate(filenames):
|
||||
logger.info(f'Loading image from file {filename}')
|
||||
img = QtGui.QImage(filename)
|
||||
if progress:
|
||||
progress.setValue(i)
|
||||
if progress.wasCanceled():
|
||||
break
|
||||
if img.isNull():
|
||||
errors.append(filename)
|
||||
continue
|
||||
|
|
@ -325,10 +329,6 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
|
|||
items.append(item)
|
||||
pos.setX(pos.x() + 50)
|
||||
pos.setY(pos.y() + 50)
|
||||
if progress:
|
||||
progress.setValue(i)
|
||||
if progress.wasCanceled():
|
||||
break
|
||||
|
||||
self.undo_stack.push(commands.InsertItems(self.scene, items))
|
||||
|
||||
|
|
@ -362,7 +362,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
|
|||
len(self.scene.selectedItems()))
|
||||
for action in self.actions_active_when_selection:
|
||||
action.setEnabled(self.scene.has_selection())
|
||||
self.viewport().repaint()
|
||||
selection.SelectionItem.update_selection(self.scene.selectedItems())
|
||||
|
||||
def recalc_scene_rect(self):
|
||||
"""Resize the scene rectangle so that it is always one view width
|
||||
|
|
@ -408,6 +408,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
|
|||
def scale(self, *args, **kwargs):
|
||||
super().scale(*args, **kwargs)
|
||||
self.recalc_scene_rect()
|
||||
selection.SelectionItem.update_selection(self.scene.selectedItems())
|
||||
|
||||
def get_scale(self):
|
||||
return self.transform().m11()
|
||||
|
|
|
|||
|
|
@ -63,8 +63,20 @@ class BeeGraphicsSceneNormalizeTestCase(BeeTestCase):
|
|||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item3 = QtWidgets.QGraphicsRectItem()
|
||||
self.scene.clear_save_ids()
|
||||
self.scene.addItem(item3)
|
||||
|
||||
self.scene.clear_save_ids()
|
||||
assert item1.save_id is None
|
||||
assert item2.save_id is None
|
||||
assert hasattr(item3, 'save_id') is False
|
||||
|
||||
def test_items_for_save(self):
|
||||
item1 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item1)
|
||||
item2 = BeePixmapItem(QtGui.QImage())
|
||||
self.scene.addItem(item2)
|
||||
item3 = QtWidgets.QGraphicsRectItem()
|
||||
self.scene.addItem(item3)
|
||||
|
||||
items = list(self.scene.items_for_save())
|
||||
assert items == [item1, item2]
|
||||
|
|
|
|||
Loading…
Reference in a new issue