Rework selection code part 1

This commit is contained in:
Rebecca Breu 2021-03-29 13:56:47 +02:00
parent eea5adb416
commit 667239b8b6
6 changed files with 89 additions and 46 deletions

View file

@ -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

View file

@ -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."""

View file

@ -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():

View file

@ -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()

View file

@ -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()

View file

@ -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]