Add actions for rearranging selected elements

Implements four new actions to move selected elements to front, to back, forward
by one, or backward by one. Currently, these actions can be accessed through
a new Edit > Arrange menu item.
upstream-master
Bryan Tan 5 years ago committed by Bryan Tan
parent 9158345001
commit 36b5dbfd10
  1. 36
      src/control/Control.cpp
  2. 2
      src/control/Control.h
  3. 59
      src/control/tools/EditSelection.cpp
  4. 13
      src/control/tools/EditSelection.h
  5. 5
      src/enums/ActionType.enum.h
  6. 32
      src/enums/generated/ActionType.generated.cpp
  7. 44
      src/undo/ArrangeUndoAction.cpp
  8. 45
      src/undo/ArrangeUndoAction.h
  9. 50
      ui/main.glade

@ -6,6 +6,7 @@
#include <glib/gstdio.h>
#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
*

@ -205,6 +205,8 @@ public:
void clearSelectionEndText();
void reorderSelection(ActionType type);
void setToolSize(ToolSize size);
void setLineStyle(const string& style);

@ -1,6 +1,8 @@
#include "EditSelection.h"
#include <algorithm>
#include <cmath>
#include <limits>
#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::deque<std::pair<Element*, Lay
return this->contents->getInsertOrder();
}
auto EditSelection::rearrangeInsertOrder(const OrderChange change) -> UndoActionPtr {
using InsertOrder = std::deque<std::pair<Element*, Layer::ElementIndex>>;
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<Layer::ElementIndex>::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<ArrangeUndoAction>(page, page->getSelectedLayer(), desc, std::move(oldOrd),
std::move(newOrd));
}
/**
* Finish the current movement
* (should be called in the mouse-button-released event handler)

@ -170,6 +170,19 @@ public:
* Returns the insert order of this selection
*/
std::deque<std::pair<Element*, Layer::ElementIndex>> 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)

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

@ -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";
}

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

@ -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 <deque>
#include "model/Layer.h"
#include "UndoAction.h"
class ArrangeUndoAction: public UndoAction {
public:
using InsertOrder = std::deque<std::pair<Element*, Layer::ElementIndex>>;
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<Element*> elements;
Layer* layer;
/** Description of the ordering action. */
std::string description;
// These track the ordering of elements
InsertOrder oldOrder;
InsertOrder newOrder;
};

@ -295,6 +295,56 @@
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="menuArrange">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Arrange</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="menuArrangeBringToFront">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bring to Front</property>
<property name="use-underline">True</property>
<signal name="activate" handler="ACTION_ARRANGE_BRING_TO_FRONT" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuArrangeBringForward">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Bring Forward</property>
<property name="use-underline">True</property>
<signal name="activate" handler="ACTION_ARRANGE_BRING_FORWARD" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuArrangeSendBackward">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Send Backward</property>
<property name="use-underline">True</property>
<signal name="activate" handler="ACTION_ARRANGE_SEND_BACKWARD" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuArrangeSendToBack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Send to Back</property>
<property name="use-underline">True</property>
<signal name="activate" handler="ACTION_ARRANGE_SEND_TO_BACK" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menuEditUndo">
<property name="label">gtk-undo</property>

Loading…
Cancel
Save