diff --git a/beeref/actions/actions.py b/beeref/actions/actions.py index 83d8083..d39f1cf 100644 --- a/beeref/actions/actions.py +++ b/beeref/actions/actions.py @@ -85,6 +85,20 @@ actions = [ 'callback': 'on_action_delete_items', 'group': 'active_when_selection', }, + { + 'id': 'raise_to_top', + 'text': '&Raise to Top', + 'shortcuts': ['PgUp'], + 'callback': 'on_action_raise_to_top', + 'group': 'active_when_selection', + }, + { + 'id': 'lower_to_bottom', + 'text': 'Lower to Bottom', + 'shortcuts': ['PgDown'], + 'callback': 'on_action_lower_to_bottom', + 'group': 'active_when_selection', + }, { 'id': 'normalize_height', 'text': '&Height', diff --git a/beeref/actions/menu_structure.py b/beeref/actions/menu_structure.py index 9f99d54..a25405b 100644 --- a/beeref/actions/menu_structure.py +++ b/beeref/actions/menu_structure.py @@ -45,6 +45,9 @@ menu_structure = [ 'copy', 'paste', 'delete', + MENU_SEPARATOR, + 'raise_to_top', + 'lower_to_bottom', ], }, { diff --git a/beeref/scene.py b/beeref/scene.py index 7036de0..3de5eec 100644 --- a/beeref/scene.py +++ b/beeref/scene.py @@ -37,6 +37,8 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): self.rubberband_active = False self.undo_stack = undo_stack self.max_z = 0 + self.min_z = 0 + self.Z_STEP = 0.001 self.multi_select_item = MultiSelectItem() self.rubberband_item = RubberbandItem() self.selectionChanged.connect(self.on_selection_change) @@ -65,6 +67,23 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): copies.append(copy) self.undo_stack.push(commands.InsertItems(self, copies, position)) + def raise_to_top(self): + items = self.selectedItems(user_only=True) + z_values = map(lambda i: i.zValue(), items) + delta = self.max_z + self.Z_STEP - min(z_values) + logger.debug(f'Raise to top, delta: {delta}') + for item in items: + item.setZValue(item.zValue() + delta) + + def lower_to_bottom(self): + items = self.selectedItems(user_only=True) + z_values = map(lambda i: i.zValue(), items) + delta = self.min_z - self.Z_STEP - max(z_values) + logger.debug(f'Lower to bottom, delta: {delta}') + + for item in items: + item.setZValue(item.zValue() + delta) + def normalize_width_or_height(self, mode): """Scale the selected images to have the same width or height, as specified by ``mode``. @@ -377,3 +396,4 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene): item.setSelected(True) item.bring_to_front() self.max_z = max(self.max_z, item.zValue()) + self.min_z = min(self.min_z, item.zValue()) diff --git a/beeref/selection.py b/beeref/selection.py index 55aa17b..ca5d1b2 100644 --- a/beeref/selection.py +++ b/beeref/selection.py @@ -74,9 +74,10 @@ class BaseItemMixin: super().setZValue(value) if self.scene(): self.scene().max_z = max(self.scene().max_z, value) + self.scene().min_z = min(self.scene().min_z, value) def bring_to_front(self): - self.setZValue(self.scene().max_z + 0.001) + self.setZValue(self.scene().max_z + self.scene().Z_STEP) @with_anchor def setRotation(self, value): diff --git a/beeref/view.py b/beeref/view.py index c5a8798..487532a 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -232,6 +232,12 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): commands.DeleteItems( self.scene, self.scene.selectedItems(user_only=True))) + def on_action_raise_to_top(self): + self.scene.raise_to_top() + + def on_action_lower_to_bottom(self): + self.scene.lower_to_bottom() + def on_action_normalize_height(self): self.scene.normalize_height() diff --git a/tests/selection/test_base_item_mixin.py b/tests/selection/test_base_item_mixin.py index 919112f..3b84729 100644 --- a/tests/selection/test_base_item_mixin.py +++ b/tests/selection/test_base_item_mixin.py @@ -36,14 +36,15 @@ def test_set_zvalue_sets_new_max(view, item): item.setZValue(1.1) assert item.zValue() == 1.1 assert view.scene.max_z == 1.1 + assert view.scene.min_z == 0 -def test_set_zvalue_keeps_old_max(view, item): +def test_set_zvalue_sets_new_min(view, item): view.scene.addItem(item) - view.scene.max_z = 3.3 - item.setZValue(1.1) - assert item.zValue() == 1.1 - assert view.scene.max_z == 3.3 + item.setZValue(-1.1) + assert item.zValue() == -1.1 + assert view.scene.max_z == 0 + assert view.scene.min_z == -1.1 def test_bring_to_front(view, item): diff --git a/tests/test_scene.py b/tests/test_scene.py index e9197e2..7fb32cc 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -17,6 +17,75 @@ def test_add_remove_item(view, item): assert view.scene.items() == [] +def test_copy_selection_to_internal_clipboard(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + + view.scene.copy_selection_to_internal_clipboard() + assert set(view.scene.internal_clipboard) == {item1, item2} + assert set(view.scene.items_for_save()) == {item1, item2, item3} + + +def test_paste_from_internal_clipboard(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setScale(3.3) + view.scene.internal_clipboard = [item2] + + view.scene.paste_from_internal_clipboard(None) + assert len(list(view.scene.items_for_save())) == 2 + assert item1.isSelected() is False + new_item = view.scene.selectedItems(user_only=True)[0] + assert new_item.scale() == 3.3 + assert new_item is not item2 + + +def test_raise_to_top(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setZValue(0.06) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setZValue(0.02) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + item3.setZValue(0.07) + + view.scene.raise_to_top() + assert item1.zValue() == 0.11 + view.scene.Z_STEP + assert item2.zValue() == 0.07 + view.scene.Z_STEP + assert item3.zValue() == 0.07 + + +def test_lower_to_bottom(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setZValue(-0.06) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setZValue(-0.02) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + item3.setZValue(-0.07) + + view.scene.lower_to_bottom() + assert item1.zValue() == -0.11 - view.scene.Z_STEP + assert item2.zValue() == -0.07 - view.scene.Z_STEP + assert item3.zValue() == -0.07 + + def test_normalize_height(view): item1 = BeePixmapItem(QtGui.QImage()) view.scene.addItem(item1) @@ -897,34 +966,3 @@ def test_add_queued_items_selected(view, item): def test_add_queued_items_when_no_items(view): view.scene.add_queued_items() assert view.scene.items() == [] - - -def test_copy_selection_to_internal_clipboard(view): - item1 = BeePixmapItem(QtGui.QImage()) - view.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - view.scene.addItem(item2) - item2.setSelected(True) - item3 = BeePixmapItem(QtGui.QImage()) - view.scene.addItem(item3) - - view.scene.copy_selection_to_internal_clipboard() - assert set(view.scene.internal_clipboard) == {item1, item2} - assert set(view.scene.items_for_save()) == {item1, item2, item3} - - -def test_paste_from_internal_clipboard(view): - item1 = BeePixmapItem(QtGui.QImage()) - view.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setScale(3.3) - view.scene.internal_clipboard = [item2] - - view.scene.paste_from_internal_clipboard(None) - assert len(list(view.scene.items_for_save())) == 2 - assert item1.isSelected() is False - new_item = view.scene.selectedItems(user_only=True)[0] - assert new_item.scale() == 3.3 - assert new_item is not item2