Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
align-and-distribute.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Tavmjong Bah
7 *
8 * Based on dialog by:
9 * Bryce W. Harrington <bryce@bryceharrington.org>
10 * Aubanel MONNIER <aubi@libertysurf.fr>
11 * Frank Felfe <innerspace@iname.com>
12 * Lauris Kaplinski <lauris@kaplinski.com>
13 *
14 * Copyright (C) 2021 Tavmjong Bah
15 *
16 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 */
18
19#include "align-and-distribute.h" // Widget
20
21#include <gtkmm/combobox.h>
22#include <gtkmm/frame.h>
23#include <gtkmm/spinbutton.h>
24#include <gtkmm/togglebutton.h>
25#include <gtkmm/treemodelfilter.h>
26
27#include "actions/actions-tools.h" // Tool switching.
28#include "desktop.h" // Tool switching.
29#include "inkscape-window.h" // Activate window action.
30#include "io/resource.h"
31#include "selection.h"
32#include "ui/builder-utils.h"
33#include "ui/dialog/dialog-base.h" // Tool switching.
34#include "ui/util.h"
35
36namespace Inkscape::UI::Dialog {
37
40
42 : Gtk::Box(Gtk::Orientation::VERTICAL)
43 , builder(create_builder("align-and-distribute.ui"))
44 , align_and_distribute_box(get_widget<Gtk::Box>(builder, "align-and-distribute-box"))
45 , align_and_distribute_object(get_widget<Gtk::Box>(builder, "align-and-distribute-object"))
46 , remove_overlap_frame(get_widget<Gtk::Frame>(builder, "remove-overlap-frame"))
47 , align_and_distribute_node(get_widget<Gtk::Box>(builder, "align-and-distribute-node"))
48
49 // Object align
50 , align_relative_object(get_widget<Gtk::ComboBox>(builder, "align-relative-object"))
51 , align_move_as_group(get_widget<Gtk::ToggleButton>(builder, "align-move-as-group"))
52
53 // Remove overlap
54 , remove_overlap_button(get_widget<Gtk::Button>(builder, "remove-overlap-button"))
55 , remove_overlap_hgap(get_widget<Gtk::SpinButton>(builder, "remove-overlap-hgap"))
56 , remove_overlap_vgap(get_widget<Gtk::SpinButton>(builder, "remove-overlap-vgap"))
57
58 // Node
59 , align_relative_node(get_widget<Gtk::ComboBox>(builder, "align-relative-node"))
60
61{
62 set_name("AlignAndDistribute");
63
65
67
68 // ------------ Object Align -------------
69
70 std::string align_to = prefs->getString("/dialogs/align/objects-align-to", "selection");
71 multi_selection_align_to = align_to;
72
73 auto filtered_store = Gtk::TreeModelFilter::create(align_relative_object.get_model());
74 filtered_store->set_visible_func([=, this](const Gtk::TreeModel::const_iterator &it) {
75 if (single_item) {
76 Glib::ustring name;
77 it->get_value(1, name);
79 }
80 return true;
81 });
82
83 if (auto win = InkscapeApplication::instance()->get_active_window()) {
84 if (auto desktop = win->get_desktop()) {
85 if (auto selection = desktop->getSelection()) {
86 single_item = selection->singleItem();
87 sel_changed = selection->connectChanged([this, filtered_store](Inkscape::Selection *selection) {
88 single_item = selection->singleItem();
90 filtered_store->refilter();
91 align_relative_object.set_active_id(active_id);
92 });
93 }
94 }
95 }
96
97 align_relative_object.set_model(filtered_store);
99 align_relative_object.set_active_id(active_id);
100 align_relative_object.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_align_relative_object_changed));
101
102 bool sel_as_group = prefs->getBool("/dialogs/align/sel-as-groups");
103 align_move_as_group.set_active(sel_as_group);
104 align_move_as_group.signal_clicked().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_align_as_group_clicked));
105
106 // clang-format off
107 std::vector<std::pair<const char*, const char*>> align_buttons = {
108 {"align-horizontal-right-to-anchor", "right anchor" },
109 {"align-horizontal-left", "left" },
110 {"align-horizontal-center", "hcenter" },
111 {"align-horizontal-right", "right" },
112 {"align-horizontal-left-to-anchor", "left anchor" },
113 {"align-horizontal-baseline", "horizontal" },
114 {"align-vertical-bottom-to-anchor", "bottom anchor" },
115 {"align-vertical-top", "top" },
116 {"align-vertical-center", "vcenter" },
117 {"align-vertical-bottom", "bottom" },
118 {"align-vertical-top-to-anchor", "top anchor" },
119 {"align-vertical-baseline", "vertical" }
120 };
121 // clang-format on
122
123 for (auto align_button : align_buttons) {
124 auto &button = get_widget<Gtk::Button>(builder, align_button.first);
125 button.signal_clicked().connect(
126 sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_align_clicked), align_button.second));
127 }
128
129 // ------------ Remove overlap -------------
130
131 remove_overlap_button.signal_clicked().connect(
132 sigc::mem_fun(*this, &AlignAndDistribute::on_remove_overlap_clicked));
133
134 // ------------ Node Align -------------
135
136 std::string align_nodes_to = prefs->getString("/dialogs/align/nodes-align-to", "first");
137 align_relative_node.set_active_id(align_nodes_to);
138 align_relative_node.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_align_relative_node_changed));
139
140 std::vector<std::pair<const char*, const char*>> align_node_buttons = {
141 {"align-node-horizontal", "horizontal"},
142 {"align-node-vertical", "vertical" }
143 };
144
145 for (auto align_button: align_node_buttons) {
146 auto &button = get_widget<Gtk::Button>(builder, align_button.first);
147 button.signal_clicked().connect(
148 sigc::bind(sigc::mem_fun(*this, &AlignAndDistribute::on_align_node_clicked), align_button.second));
149 }
150
151 // ------------ Set initial values ------------
152
153 // Normal or node alignment?
154 auto desktop = dlg->getDesktop();
155 if (desktop) {
157 }
158
159 auto set_icon_size_prefs = [prefs, this]() {
160 int size = prefs->getIntLimited("/toolbox/tools/iconsize", -1, 16, 48);
162 };
163
164 // For now we are going to track the toolbox icon size, in the future we will have our own
165 // dialog based icon sizes, perhaps done via css instead.
166 _icon_sizes_changed = prefs->createObserver("/toolbox/tools/iconsize", set_icon_size_prefs);
167 set_icon_size_prefs();
168}
169
170void
180
181void
183{
184 bool const is_node = get_active_tool(desktop) == "Node";
185 align_and_distribute_node .set_visible( is_node);
186 align_and_distribute_object.set_visible(!is_node);
187 remove_overlap_frame.set_visible(!is_node);
188}
189
190void
195
196
197void
199{
200 bool state = align_move_as_group.get_active();
202 prefs->setBool("/dialogs/align/sel-as-groups", state);
203}
204
205void
207{
209 auto align_to = align_relative_object.get_active_id();
210 prefs->setString("/dialogs/align/objects-align-to", align_to);
211
212 if (auto win = InkscapeApplication::instance()->get_active_window()) {
213 if (auto desktop = win->get_desktop()) {
214 if (auto selection = desktop->getSelection()) {
215 if (selection->singleItem()) {
216 single_selection_align_to = align_to;
217 } else {
218 multi_selection_align_to = align_to;
219 }
220 }
221 }
222 }
223}
224
225void
227{
229 prefs->setString("/dialogs/align/nodes-align-to", align_relative_node.get_active_id());
230}
231
232void
233AlignAndDistribute::on_align_clicked(std::string const &align_to)
234{
235 Glib::ustring argument = align_to;
236
237 argument += " " + align_relative_object.get_active_id();
238
239 if (align_move_as_group.get_active()) {
240 argument += " group";
241 }
242
243 auto variant = Glib::Variant<Glib::ustring>::create(argument);
244 auto app = Gio::Application::get_default();
245
246 if (align_to.find("vertical") != Glib::ustring::npos or align_to.find("horizontal") != Glib::ustring::npos) {
247 app->activate_action("object-align-text", variant);
248 } else {
249 app->activate_action("object-align", variant);
250 }
251}
252
253void
255{
256 double hgap = remove_overlap_hgap.get_value();
257 double vgap = remove_overlap_vgap.get_value();
258
259 auto variant = Glib::Variant<std::tuple<double, double>>::create(std::tuple<double, double>(hgap, vgap));
260 auto app = Gio::Application::get_default();
261 app->activate_action("object-remove-overlaps", variant);
262}
263
264void
266{
267 Glib::ustring argument = align_relative_node.get_active_id();
268
269 auto variant = Glib::Variant<Glib::ustring>::create(argument);
271
272 if (!win) {
273 return;
274 }
275
276 if (direction == "horizontal") {
277 win->activate_action("win.node-align-horizontal", variant);
278 } else {
279 win->activate_action("win.node-align-vertical", variant);
280 }
281}
282
283} // namespace Inkscape::UI::Dialog
284
285/*
286 Local Variables:
287 mode:c++
288 c-file-style:"stroustrup"
289 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
290 indent-tabs-mode:nil
291 fill-column:99
292 End:
293*/
294// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Glib::ustring get_active_tool(InkscapeWindow *win)
Align and Distribute widget.
Gtk builder utilities.
InkscapeWindow * get_active_window()
static InkscapeApplication * instance()
Singleton instance.
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
Definition preferences.h:61
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.
std::unique_ptr< PreferencesObserver > createObserver(Glib::ustring path, std::function< void(const Preferences::Entry &new_value)> callback)
Create an observer watching preference 'path' and calling provided function when preference changes.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
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.
Definition selection.h:80
AlignAndDistribute(Inkscape::UI::Dialog::DialogBase *dlg)
void on_align_clicked(std::string const &align_to)
void on_align_node_clicked(std::string const &align_to)
std::set< Glib::ustring > single_selection_relative_categories
void tool_changed_callback(SPDesktop *desktop, Inkscape::UI::Tools::ToolBase *tool)
DialogBase is the base class for the dialog system.
Definition dialog-base.h:40
SPDesktop * getDesktop() const
Definition dialog-base.h:77
Base class for Event processors.
Definition tool-base.h:95
Creates a Gnome HIG style indented frame with bold label See http://developer.gnome....
Definition frame.h:24
SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMen...
Definition spinbutton.h:52
To do: update description of desktop.
Definition desktop.h:149
Inkscape::Selection * getSelection() const
Definition desktop.h:188
sigc::connection connectEventContextChanged(F &&slot)
Definition desktop.h:261
Editable view implementation.
A base class for all dialogs.
Inkscape - An SVG editor.
Definition desktop.h:50
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
Dialog code.
Definition desktop.h:117
void set_icon_sizes(Gtk::Widget *parent, int pixel_size)
Recursively set all the icon sizes inside this parent widget.
Definition util.cpp:98
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
static Geom::Point direction(Geom::Point const &first, Geom::Point const &second)
Computes an unit vector of the direction from first to second control point.
Definition node.cpp:164
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
static void append(std::vector< T > &target, std::vector< T > &&source)
Inkscape::IO::Resource - simple resource API.
SPDesktop * desktop
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder