24#include <glibmm/i18n.h>
64 auto action = gapp->lookup_action(
"object-align-on-canvas");
66 show_output(
"object_align_on_canvas: action missing!");
70 auto saction = std::dynamic_pointer_cast<Gio::SimpleAction>(action);
72 show_output(
"object_align_on_canvas: action not SimpleAction!");
78 saction->get_state(state);
80 saction->change_state(state);
84 prefs->
setBool(
"/dialogs/align/oncanvas", state);
91 Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
92 std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(
" ", s.get());
95 bool anchor = std::find(tokens.begin(), tokens.end(),
"anchor") != tokens.end();
111 if (std::find(tokens.begin(), tokens.end(),
"pref") != tokens.end()) {
112 group = prefs->
getBool(
"/dialogs/align/sel-as-groups",
false);
113 tokens.push_back(prefs->
getString(
"/dialogs/align/objects-align-to",
"selection"));
117 for (
auto const &token : tokens) {
129 else if (token ==
"group") group =
true;
133 if (token ==
"left" ) { mx0 = 1.0; mx1 = 0.0; sx0 = 1.0; sx1 = 0.0; }
134 else if (token ==
"hcenter" ) { mx0 = 0.5; mx1 = 0.5; sx0 = 0.5; sx1 = 0.5; }
135 else if (token ==
"right" ) { mx0 = 0.0; mx1 = 1.0; sx0 = 0.0; sx1 = 1.0; }
137 else if (token ==
"top" ) { my0 = 1.0; my1 = 0.0; sy0 = 1.0; sy1 = 0.0; }
138 else if (token ==
"vcenter" ) { my0 = 0.5; my1 = 0.5; sy0 = 0.5; sy1 = 0.5; }
139 else if (token ==
"bottom" ) { my0 = 0.0; my1 = 1.0; sy0 = 0.0; sy1 = 1.0; }
141 if (token ==
"left" ) { mx0 = 0.0; mx1 = 1.0; sx0 = 1.0; sx1 = 0.0; }
142 else if (token ==
"hcenter" )
show_output(
"'anchor' cannot be used with 'hcenter'");
143 else if (token ==
"right" ) { mx0 = 1.0; mx1 = 0.0; sx0 = 0.0; sx1 = 1.0; }
145 else if (token ==
"top" ) { my0 = 0.0; my1 = 1.0; sy0 = 1.0; sy1 = 0.0; }
146 else if (token ==
"vcenter" )
show_output(
"'anchor' cannot be used with 'vcenter'");
147 else if (token ==
"bottom" ) { my0 = 1.0; my1 = 0.0; sy0 = 0.0; sy1 = 1.0; }
156 selection->setDocument(document);
159 auto list = selection->items();
160 std::size_t total = std::distance(list.begin(), list.end());
161 std::vector<SPItem *> selected;
162 std::vector<Inkscape::LivePathEffect::Effect *> bools;
163 for (
auto item : list) {
165 auto lpeitem = cast<SPLPEItem>(
item);
168 if (!g_strcmp0(lpe->getRepr()->attribute(
"is_visible"),
"true")) {
169 lpe->getRepr()->setAttribute(
"is_visible",
"false");
170 bools.emplace_back(lpe);
177 selected.emplace_back(
item);
181 if (selected.empty())
return;
193 focus = selected.back();
196 focus = selected.front();
199 focus = selection->largestItem(direction);
202 focus = selection->smallestItem(direction);
205 b = document->pageBounds();
208 b = document->getRoot()->desktopPreferredBounds();
211 b = selection->preferredBounds();
214 g_assert_not_reached ();
237 copy.add(selection->objects().begin(), selection->objects().end());
239 b = copy.preferredBounds();
242 b = selection->preferredBounds();
247 bool changed =
false;
248 for (
auto item : selected) {
249 document->ensureUpToDate();
252 b = (
item)->desktopPreferredBounds();
255 if (b && (!focus || (
item) != focus)) {
259 if (LInfty(mp_rel) > 1e-9) {
274 Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
275 auto token = s.get();
281 selection->setDocument(document);
283 std::vector<SPItem*> selected(selection->items().begin(), selection->items().end());
284 if (selected.size() < 2) {
293 if (token ==
"hgap" ) { gap =
true; orientation =
Geom::X; a = 0.5, b = 0.5; }
294 else if (token ==
"left" ) { gap =
false; orientation =
Geom::X; a = 1.0, b = 0.0; }
295 else if (token ==
"hcenter" ) { gap =
false; orientation =
Geom::X; a = 0.5, b = 0.5; }
296 else if (token ==
"right" ) { gap =
false; orientation =
Geom::X; a = 0.0, b = 1.0; }
297 else if (token ==
"vgap" ) { gap =
true; orientation =
Geom::Y; a = 0.5, b = 0.5; }
298 else if (token ==
"top" ) { gap =
false; orientation =
Geom::Y; a = 1.0, b = 0.0; }
299 else if (token ==
"vcenter" ) { gap =
false; orientation =
Geom::Y; a = 0.5, b = 0.5; }
300 else if (token ==
"bottom" ) { gap =
false; orientation =
Geom::Y; a = 0.0, b = 1.0; }
305 int prefs_bbox = prefs->
getBool(
"/tools/bounding_box");
308 std::vector<BBoxSort> sorted;
309 for (
auto item : selected) {
312 sorted.emplace_back(
item, *bbox, orientation, a, b);
315 std::stable_sort(sorted.begin(), sorted.end());
321 bool changed =
false;
326 double dist = (sorted.back().bbox.max()[orientation] - sorted.front().bbox.min()[orientation]);
330 for (
auto bbox : sorted) {
331 span += bbox.bbox[orientation].extent();
335 double step = (dist - span) / (sorted.size() - 1);
336 double pos = sorted.front().bbox.min()[orientation];
337 for (
auto bbox : sorted) {
344 t[orientation] = pos - bbox.bbox.min()[orientation];
351 pos += bbox.bbox[orientation].extent();
358 double dist = sorted.back().anchor - sorted.front().anchor;
361 double step = dist / (sorted.size() - 1);
363 for (
unsigned int i = 0; i < sorted.size() ; i++) {
367 double pos = sorted.front().anchor + i * step;
374 t[orientation] = pos - it.
anchor;
384 prefs->
setInt(
"/options/clonecompensation/value", saved_compensation);
397 , _orientation (orientation)
404static bool operator< (
const Baseline &a,
const Baseline &b)
406 return (a._base[a._orientation] < b._base[b._orientation]);
412 Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
413 auto token = s.
get();
416 if (token.find(
"vertical") != Glib::ustring::npos) {
421 if (selection->size() < 2) {
427 selection->setDocument(document);
429 std::vector<Baseline> baselines;
433 for (
auto item : selection->items()) {
434 if (is<SPText>(
item) || cast<SPFlowtext>(
item)) {
443 baselines.emplace_back(
item, base, orientation);
448 if (baselines.size() < 2) {
452 std::stable_sort(baselines.begin(), baselines.end());
454 double step = (b_max[orientation] - b_min[orientation])/(baselines.size() - 1);
456 for (
auto& baseline : baselines) {
458 t[orientation] = b_min[orientation] + (step * i) - baseline._base[orientation];
470 Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
471 std::vector<Glib::ustring> tokens = Glib::Regex::split_simple(
" ", s.get());
480 if (std::find(tokens.begin(), tokens.end(),
"pref") != tokens.end()) {
481 tokens.push_back(prefs->
getString(
"/dialogs/align/objects-align-to",
"selection"));
484 for (
auto const &token : tokens) {
496 if (token ==
"vertical" ) {
504 if (selection->items().empty()) {
510 selection->setDocument(document);
521 focus = selection->items().back();
524 focus = selection->items().front();
527 focus = selection->largestItem(direction);
530 focus = selection->smallestItem(direction);
533 b = document->pageBounds();
536 b = document->getRoot()->desktopPreferredBounds();
539 b = selection->preferredBounds();
542 g_assert_not_reached ();
548 if (is<SPText>(focus) || cast<SPFlowtext>(focus)) {
554 ref_point = b->min();
557 for (
auto item : selection->items()) {
558 if (is<SPText>(
item) || cast<SPFlowtext>(
item)) {
564 t[orientation] = ref_point[orientation] - base[orientation];
578 RotateCompare(
Geom::Point& center) : center(center) {}
587 if (angle_a != angle_b)
return (angle_a < angle_b);
609 std::vector<SPItem*>
items(selection->
items().begin(), selection->
items().end());
619 auto center = selection->
center();
621 std::sort(
items.begin(),
items.end(), RotateCompare(*center));
643 std::vector<SPItem*>
items(selection->
items().begin(), selection->
items().end());
646 for (
int i = 0; i < 2; i++) {
649 double min = std::numeric_limits<double>::max();
650 double max = std::numeric_limits<double>::min();
664 int nitems =
items.size();
665 int imin = rand() % nitems;
666 int imax = rand() % nitems;
667 while (imin == imax) {
668 imax = rand() % nitems;
678 }
else if (
index == imax) {
681 z = g_random_double_range(min, max);
698 Glib::Variant<Glib::ustring> s = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring> >(value);
699 auto token = s.get();
705 selection->setDocument(document);
707 std::vector<SPItem*>
items(selection->items().begin(), selection->items().end());
708 if (
items.size() < 2) {
721 else if (token ==
"randomize" ) {
randomize(selection); }
724 show_output(Glib::ustring(
"object_rearrange: unhandled argument: ") + token.raw());
729 prefs->
setInt(
"/options/clonecompensation/value", saved_compensation);
742 selection->setDocument(document);
744 std::vector<SPItem*>
items(selection->items().begin(), selection->items().end());
745 if (
items.size() < 2) {
750 if (value.get_type_string() !=
"(dd)") {
751 show_output(Glib::ustring(
"object_remove_overlaps: wrong variant type: ") + Glib::ustring::format(value.get_type_string()) +
" (should be '(dd)')");
754 auto tuple = Glib::VariantBase::cast_dynamic<Glib::Variant<std::tuple<double, double>>>(value);
755 auto [hgap, vgap] = tuple.get();
764 prefs->
setInt(
"/options/clonecompensation/value", saved_compensation);
769const Glib::ustring
SECTION = NC_(
"Action Section",
"Object");
774 {
"app.object-align-on-canvas", N_(
"Enable on-canvas alignment"),
SECTION, N_(
"Enable on-canvas alignment handles" )},
776 {
"app.object-align", N_(
"Align objects"),
SECTION, N_(
"Align selected objects; usage: [[left|hcenter|right] || [top|vcenter|bottom]] [last|first|biggest|smallest|page|drawing|selection|pref]? group? anchor?")},
778 {
"app.object-align('left pref')", N_(
"Align to left edge"),
SECTION, N_(
"Align selection horizontally to left edge" )},
779 {
"app.object-align('hcenter pref')", N_(
"Align to horizontal center"),
SECTION, N_(
"Align selection horizontally to the center" )},
780 {
"app.object-align('right pref')", N_(
"Align to right edge"),
SECTION, N_(
"Align selection horizontally to right edge" )},
781 {
"app.object-align('top pref')", N_(
"Align to top edge"),
SECTION, N_(
"Align selection vertically to top edge" )},
782 {
"app.object-align('bottom pref')", N_(
"Align to bottom edge"),
SECTION, N_(
"Align selection vertically to bottom edge" )},
783 {
"app.object-align('vcenter pref')", N_(
"Align to vertical center"),
SECTION, N_(
"Align selection vertically to the center" )},
784 {
"app.object-align('hcenter vcenter pref')", N_(
"Align to center"),
SECTION, N_(
"Align selection to the center" )},
785 {
"app.object-align-text", N_(
"Align text objects"),
SECTION, N_(
"Align selected text anchors; usage: [[vertical | horizontal] [last|first|biggest|smallest|page|drawing|selection]?" )},
787 {
"app.object-distribute", N_(
"Distribute objects"),
SECTION, N_(
"Distribute selected objects; usage: [hgap | left | hcenter | right | vgap | top | vcenter | bottom]" )},
788 {
"app.object-distribute('hgap')", N_(
"Even horizontal gaps"),
SECTION, N_(
"Distribute horizontally with even horizontal gaps" )},
789 {
"app.object-distribute('left')", N_(
"Even left edges"),
SECTION, N_(
"Distribute horizontally with even spacing between left edges" )},
790 {
"app.object-distribute('hcenter')", N_(
"Even horizontal centers"),
SECTION, N_(
"Distribute horizontally with even spacing between centers" )},
791 {
"app.object-distribute('right')", N_(
"Even right edges"),
SECTION, N_(
"Distribute horizontally with even spacing between right edges" )},
792 {
"app.object-distribute('vgap')", N_(
"Even vertical gaps"),
SECTION, N_(
"Distribute vertically with even vertical gaps" )},
793 {
"app.object-distribute('top')", N_(
"Even top edges"),
SECTION, N_(
"Distribute vertically with even spacing between top edges" )},
794 {
"app.object-distribute('vcenter')", N_(
"Even vertical centers"),
SECTION, N_(
"Distribute vertically with even spacing between centers" )},
795 {
"app.object-distribute('bottom')", N_(
"Even bottom edges"),
SECTION, N_(
"Distribute vertically with even spacing between bottom edges" )},
797 {
"app.object-distribute-text", N_(
"Distribute text objects"),
SECTION, N_(
"Distribute text anchors; usage [vertical | horizontal]" )},
798 {
"app.object-distribute-text('horizontal')", N_(
"Distribute text objects"),
SECTION, N_(
"Distribute text anchors horizontally" )},
799 {
"app.object-distribute-text('vertical')", N_(
"Distribute text objects"),
SECTION, N_(
"Distribute text anchors vertically" )},
801 {
"app.object-rearrange", N_(
"Rearrange objects"),
SECTION, N_(
"Rearrange selected objects; usage: [graph | exchange | exchangez | rotate | randomize | unclump]" )},
802 {
"app.object-rearrange('graph')", N_(
"Rearrange as graph"),
SECTION, N_(
"Nicely arrange selected connector network" )},
803 {
"app.object-rearrange('exchange')", N_(
"Exchange in selection order"),
SECTION, N_(
"Exchange positions of selected objects - selection order" )},
804 {
"app.object-rearrange('exchangez')", N_(
"Exchange in z-order"),
SECTION, N_(
"Exchange positions of selected objects - stacking order" )},
805 {
"app.object-rearrange('rotate')", N_(
"Exchange around center"),
SECTION, N_(
"Exchange positions of selected objects - rotate around center point" )},
806 {
"app.object-rearrange('randomize')", N_(
"Random exchange"),
SECTION, N_(
"Randomize centers in both dimensions" )},
807 {
"app.object-rearrange('unclump')", N_(
"Unclump"),
SECTION, N_(
"Unclump objects: try to equalize edge-to-edge distances" )},
809 {
"app.object-remove-overlaps", N_(
"Remove overlaps"),
SECTION, N_(
"Remove overlaps between objects: requires two comma separated numbers (horizontal and vertical gaps)" )},
816 {
"app.object-align", N_(
"Enter anchor<space>alignment<space>optional second alignment. Possible anchors: last, first, biggest, smallest, page, drawing, selection, pref; possible alignments: left, hcenter, right, top, vcenter, bottom.")},
817 {
"app.object-distribute", N_(
"Enter distribution type. Possible values: left, hcenter, right, top, vcenter, bottom, hgap, vgap.") },
818 {
"app.object-rearrange", N_(
"Enter arrange method. Possible values: graph, exchange, exchangez, rotate, randomize, unclump.") },
819 {
"app.object-remove-overlaps", N_(
"Enter two comma-separated numbers: horizontal,vertical") },
826 Glib::VariantType String(Glib::VARIANT_TYPE_STRING);
827 std::vector<Glib::VariantType> dd = {Glib::VARIANT_TYPE_DOUBLE, Glib::VARIANT_TYPE_DOUBLE};
828 Glib::VariantType Tuple_DD = Glib::VariantType::create_tuple(dd);
833 bool on_canvas = prefs->getBool(
"/dialogs/align/oncanvas");
836 gapp->add_action_bool(
"object-align-on-canvas", sigc::bind(sigc::ptr_fun(&
object_align_on_canvas), app), on_canvas);
837 gapp->add_action_with_parameter(
"object-align", String, sigc::bind(sigc::ptr_fun(&
object_align), app));
838 gapp->add_action_with_parameter(
"object-align-text", String, sigc::bind(sigc::ptr_fun(&
object_align_text), app));
839 gapp->add_action_with_parameter(
"object-distribute", String, sigc::bind(sigc::ptr_fun(&
object_distribute), app));
840 gapp->add_action_with_parameter(
"object-distribute-text", String, sigc::bind(sigc::ptr_fun(&
object_distribute_text), app));
841 gapp->add_action_with_parameter(
"object-rearrange", String, sigc::bind(sigc::ptr_fun(&
object_rearrange), app));
842 gapp->add_action_with_parameter(
"object-remove-overlaps", Tuple_DD, sigc::bind(sigc::ptr_fun(&
object_remove_overlaps), app));
void show_output(Glib::ustring const &data, bool const is_cerr)
void object_rearrange(const Glib::VariantBase &value, InkscapeApplication *app)
std::vector< std::vector< Glib::ustring > > hint_data_object_align
void randomize(Inkscape::Selection *selection)
const Glib::ustring SECTION
void exchange(Inkscape::Selection *selection, SortOrder order)
std::vector< std::vector< Glib::ustring > > raw_data_object_align
static bool operator<(const Baseline &a, const Baseline &b)
void object_distribute(const Glib::VariantBase &value, InkscapeApplication *app)
static bool PositionCompare(const SPItem *a, const SPItem *b)
void object_align_text(const Glib::VariantBase &value, InkscapeApplication *app)
void object_align(const Glib::VariantBase &value, InkscapeApplication *app)
void object_distribute_text(const Glib::VariantBase &value, InkscapeApplication *app)
void object_remove_overlaps(const Glib::VariantBase &value, InkscapeApplication *app)
void object_align_on_canvas(InkscapeApplication *app)
void add_actions_object_align(InkscapeApplication *app)
Simple helper class for sorting objects based on their bounding boxes.
Axis-aligned rectangle that can be empty.
Two-dimensional point that doubles as a vector.
constexpr Coord get() const
Coord length() const
Compute the distance from origin.
void add_data(std::vector< std::vector< Glib::ustring > > &raw_data)
InkActionExtraData & get_action_extra_data()
Gio::Application * gio_app()
The Gio application instance, never NULL.
SPDocument * get_active_document()
InkActionHintData & get_action_hint_data()
Inkscape::Selection * get_active_selection()
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
SPItemRange items()
Returns a range of selected SPItems.
std::optional< Geom::Point > center() const
Returns the rotation/skew center of the selection.
Preference storage class.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
Generates the layout for either wrapped or non-wrapped text and stores the result.
std::optional< Geom::Point > baselineAnchorPoint() const
For left aligned text, the leftmost end of the baseline For rightmost text, the rightmost....
bool is_yaxisdown() const
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Base class for visual SVG elements.
Geom::OptRect desktopPreferredBounds() const
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Geom::Point getCenter(bool ensure_uptodate=true) const
void move_rel(Geom::Translate const &tr)
TODO: insert short description here.
@ SP_CLONE_COMPENSATION_UNMOVED
bool has_hidder_filter(SPObject const *item)
void graphlayout(std::vector< SPItem * > const &items)
Takes a list of inkscape items, extracts the graph defined by connectors between them,...
Dim2
2D axis enumeration (X or Y).
Macro for icon names used in Inkscape.
double atan2(Point const &p)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Singleton class to access the preferences file in a convenient way.
void removeoverlap(std::vector< SPItem * > const &items, double const xGap, double const yGap)
Takes a list of inkscape items and moves them as little as possible such that rectangular bounding bo...
Remove overlaps function.
TODO: insert short description here.
int sp_item_repr_compare_position(SPItem const *first, SPItem const *second)
SPRoot: SVG <svg> implementation.
Inkscape::Text::Layout const * te_get_layout(SPItem const *item)
void unclump(std::vector< SPItem * > &items)
Unclumps the items in items, reducing local unevenness in their distribution.