diff --git a/src/control/Control.cpp b/src/control/Control.cpp index d5b501e3..7745c421 100644 --- a/src/control/Control.cpp +++ b/src/control/Control.cpp @@ -6,6 +6,7 @@ #include +#include "enums/ActionType.enum.h" #include "gui/TextEditor.h" #include "gui/XournalView.h" #include "gui/XournalppCursor.h" @@ -424,6 +425,13 @@ void Control::actionPerformed(ActionType type, ActionGroup group, GdkEvent* even showSettings(); break; + case ACTION_ARRANGE_BRING_TO_FRONT: + case ACTION_ARRANGE_BRING_FORWARD: + case ACTION_ARRANGE_SEND_BACKWARD: + case ACTION_ARRANGE_SEND_TO_BACK: + this->reorderSelection(type); + break; + // Menu Navigation case ACTION_GOTO_FIRST: scrollHandler->scrollToPage(0); @@ -992,6 +1000,34 @@ void Control::clearSelectionEndText() { } } +void Control::reorderSelection(const ActionType type) { + EditSelection* sel = win->getXournal()->getSelection(); + if (!sel) + return; + + EditSelection::OrderChange change; + switch (type) { + case ACTION_ARRANGE_BRING_TO_FRONT: + change = EditSelection::OrderChange::BringToFront; + break; + case ACTION_ARRANGE_BRING_FORWARD: + change = EditSelection::OrderChange::BringForward; + break; + case ACTION_ARRANGE_SEND_BACKWARD: + change = EditSelection::OrderChange::SendBackward; + break; + case ACTION_ARRANGE_SEND_TO_BACK: + change = EditSelection::OrderChange::SendToBack; + break; + default: + // Unknown selection order, do nothing. + return; + } + + auto undoAction = sel->rearrangeInsertOrder(change); + this->undoRedo->addUndoAction(std::move(undoAction)); +} + /** * Fire page selected, but first check if the page Number is valid * diff --git a/src/control/Control.h b/src/control/Control.h index 3123c005..4ba99a47 100644 --- a/src/control/Control.h +++ b/src/control/Control.h @@ -205,6 +205,8 @@ public: void clearSelectionEndText(); + void reorderSelection(ActionType type); + void setToolSize(ToolSize size); void setLineStyle(const string& style); diff --git a/src/control/tools/EditSelection.cpp b/src/control/tools/EditSelection.cpp index 8a0b3347..9a7692ef 100644 --- a/src/control/tools/EditSelection.cpp +++ b/src/control/tools/EditSelection.cpp @@ -1,6 +1,8 @@ #include "EditSelection.h" +#include #include +#include #include "control/Control.h" #include "gui/Layout.h" @@ -14,6 +16,7 @@ #include "model/Text.h" #include "serializing/ObjectInputStream.h" #include "serializing/ObjectOutputStream.h" +#include "undo/ArrangeUndoAction.h" #include "undo/ColorUndoAction.h" #include "undo/FontUndoAction.h" #include "undo/InsertUndoAction.h" @@ -23,6 +26,7 @@ #include "EditSelectionContents.h" #include "Selection.h" +#include "i18n.h" constexpr size_t MINPIXSIZE = 5; // smallest can scale down to, in pixels. @@ -328,6 +332,61 @@ auto EditSelection::getInsertOrder() const -> std::dequecontents->getInsertOrder(); } +auto EditSelection::rearrangeInsertOrder(const OrderChange change) -> UndoActionPtr { + using InsertOrder = std::deque>; + const InsertOrder oldOrd = this->getInsertOrder(); + InsertOrder newOrd; + std::string desc = _("Arrange"); + switch (change) { + case OrderChange::BringToFront: + // Set to largest positive signed integer + for (const auto& [e, _]: oldOrd) { + newOrd.emplace_back(e, std::numeric_limits::max()); + } + desc = _("Bring to front"); + break; + case OrderChange::BringForward: + // Set indices of elements to range from [max(indices) + 1, max(indices) + 1 + num elements) + newOrd = oldOrd; + std::stable_sort(newOrd.begin(), newOrd.end(), EditSelectionContents::insertOrderCmp); + if (!newOrd.empty()) { + Layer::ElementIndex i = newOrd.back().second + 1; + for (auto& it: newOrd) { + it.second = i++; + } + } + desc = _("Bring forward"); + break; + case OrderChange::SendBackward: + // Set indices of elements to range from [min(indices) - 1, min(indices) + num elements - 1) + newOrd = oldOrd; + std::stable_sort(newOrd.begin(), newOrd.end(), EditSelectionContents::insertOrderCmp); + if (!newOrd.empty()) { + Layer::ElementIndex i = newOrd.front().second; + i = i > 0 ? i - 1 : 0; + for (auto& it: newOrd) { + it.second = i++; + } + } + desc = _("Send backward"); + break; + case OrderChange::SendToBack: + Layer::ElementIndex i = 0; + for (const auto& [e, _]: oldOrd) { + newOrd.emplace_back(e, i); + i++; + } + desc = _("Send to back"); + break; + } + + this->contents->replaceInsertOrder(newOrd); + PageRef page = this->view->getPage(); + + return std::make_unique(page, page->getSelectedLayer(), desc, std::move(oldOrd), + std::move(newOrd)); +} + /** * Finish the current movement * (should be called in the mouse-button-released event handler) diff --git a/src/control/tools/EditSelection.h b/src/control/tools/EditSelection.h index 3fedbe27..2b58abc8 100644 --- a/src/control/tools/EditSelection.h +++ b/src/control/tools/EditSelection.h @@ -170,6 +170,19 @@ public: * Returns the insert order of this selection */ std::deque> const& getInsertOrder() const; + + enum class OrderChange { + BringToFront, + BringForward, + SendBackward, + SendToBack, + }; + + /** + * Change the insert order of this selection. + */ + UndoActionPtr rearrangeInsertOrder(const OrderChange change); + /** * Finish the current movement * (should be called in the mouse-button-released event handler) diff --git a/src/enums/ActionType.enum.h b/src/enums/ActionType.enum.h index 66cbc75b..6388217a 100644 --- a/src/enums/ActionType.enum.h +++ b/src/enums/ActionType.enum.h @@ -48,6 +48,11 @@ enum ActionType { ACTION_DELETE, ACTION_SETTINGS, + ACTION_ARRANGE_BRING_TO_FRONT = 220, + ACTION_ARRANGE_BRING_FORWARD, + ACTION_ARRANGE_SEND_BACKWARD, + ACTION_ARRANGE_SEND_TO_BACK, + // Menu navigation ACTION_GOTO_FIRST = 300, ACTION_GOTO_BACK, diff --git a/src/enums/generated/ActionType.generated.cpp b/src/enums/generated/ActionType.generated.cpp index 2c19b2e1..d4e8e498 100644 --- a/src/enums/generated/ActionType.generated.cpp +++ b/src/enums/generated/ActionType.generated.cpp @@ -88,6 +88,22 @@ auto ActionType_fromString(const string& value) -> ActionType { return ACTION_SETTINGS; } + if (value == "ACTION_ARRANGE_BRING_TO_FRONT") { + return ACTION_ARRANGE_BRING_TO_FRONT; + } + + if (value == "ACTION_ARRANGE_BRING_FORWARD") { + return ACTION_ARRANGE_BRING_FORWARD; + } + + if (value == "ACTION_ARRANGE_SEND_BACKWARD") { + return ACTION_ARRANGE_SEND_BACKWARD; + } + + if (value == "ACTION_ARRANGE_SEND_TO_BACK") { + return ACTION_ARRANGE_SEND_TO_BACK; + } + if (value == "ACTION_GOTO_FIRST") { return ACTION_GOTO_FIRST; } @@ -670,6 +686,22 @@ auto ActionType_toString(ActionType value) -> string { return "ACTION_SETTINGS"; } + if (value == ACTION_ARRANGE_BRING_TO_FRONT) { + return "ACTION_ARRANGE_BRING_TO_FRONT"; + } + + if (value == ACTION_ARRANGE_BRING_FORWARD) { + return "ACTION_ARRANGE_BRING_FORWARD"; + } + + if (value == ACTION_ARRANGE_SEND_BACKWARD) { + return "ACTION_ARRANGE_SEND_BACKWARD"; + } + + if (value == ACTION_ARRANGE_SEND_TO_BACK) { + return "ACTION_ARRANGE_SEND_TO_BACK"; + } + if (value == ACTION_GOTO_FIRST) { return "ACTION_GOTO_FIRST"; } diff --git a/src/undo/ArrangeUndoAction.cpp b/src/undo/ArrangeUndoAction.cpp new file mode 100644 index 00000000..1626b923 --- /dev/null +++ b/src/undo/ArrangeUndoAction.cpp @@ -0,0 +1,44 @@ +#include "ArrangeUndoAction.h" + +#include "model/Element.h" +#include "model/PageRef.h" + +#include "Range.h" + +ArrangeUndoAction::ArrangeUndoAction(const PageRef& page, Layer* layer, std::string desc, InsertOrder oldOrder, + InsertOrder newOrder): + UndoAction("ArrangeUndoAction"), layer(layer), description(desc), oldOrder(oldOrder), newOrder(newOrder) { + this->page = page; +} + +ArrangeUndoAction::~ArrangeUndoAction() { this->page = nullptr; } + +bool ArrangeUndoAction::undo(Control* control) { + this->undone = true; + applyRearrange(); + return true; +} + +bool ArrangeUndoAction::redo(Control* control) { + this->undone = false; + applyRearrange(); + return true; +} + +void ArrangeUndoAction::applyRearrange() { + // Convert source order into target order + const auto& srcOrder = this->undone ? this->newOrder : this->oldOrder; + const auto& tgtOrder = this->undone ? this->oldOrder : this->newOrder; + + for (const auto& [e, _]: srcOrder) { + layer->removeElement(e, false); + } + + for (const auto& [e, i]: tgtOrder) { + layer->insertElement(e, i); + } + + this->page->firePageChanged(); +} + +std::string ArrangeUndoAction::getText() { return this->description; } diff --git a/src/undo/ArrangeUndoAction.h b/src/undo/ArrangeUndoAction.h new file mode 100644 index 00000000..d3da090c --- /dev/null +++ b/src/undo/ArrangeUndoAction.h @@ -0,0 +1,45 @@ +/* + * Xournal++ + * + * Undo move action (EditSelection) + * + * @author Xournal++ Team + * https://github.com/xournalpp/xournalpp + * + * @license GNU GPLv2 or later + */ + +#pragma once + +#include + +#include "model/Layer.h" + +#include "UndoAction.h" + +class ArrangeUndoAction: public UndoAction { +public: + using InsertOrder = std::deque>; + + ArrangeUndoAction(const PageRef& page, Layer* layer, std::string desc, InsertOrder oldOrder, InsertOrder newOrder); + virtual ~ArrangeUndoAction() override; + +public: + virtual bool undo(Control* control) override; + virtual bool redo(Control* control) override; + virtual std::string getText() override; + +private: + void applyRearrange(); + +private: + std::vector elements; + Layer* layer; + + /** Description of the ordering action. */ + std::string description; + + // These track the ordering of elements + InsertOrder oldOrder; + InsertOrder newOrder; +}; diff --git a/ui/main.glade b/ui/main.glade index c36d4c1e..1ca7fae4 100644 --- a/ui/main.glade +++ b/ui/main.glade @@ -295,6 +295,56 @@ True False + + + True + False + Arrange + True + + + True + False + + + True + False + Bring to Front + True + + + + + + True + False + Bring Forward + True + + + + + + True + False + Send Backward + True + + + + + + True + False + Send to Back + True + + + + + + + gtk-undo