1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Connector aux toolbar
5 */
6 /* Authors:
7 * MenTaLguY <mental@rydia.net>
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * Frank Felfe <innerspace@iname.com>
11 * John Cliff <simarilius@yahoo.com>
12 * David Turner <novalis@gnu.org>
13 * Josh Andler <scislac@scislac.com>
14 * Jon A. Cruz <jon@joncruz.org>
15 * Maximilian Albert <maximilian.albert@gmail.com>
16 * Tavmjong Bah <tavmjong@free.fr>
17 * Abhishek Sharma
18 * Kris De Gussem <Kris.DeGussem@gmail.com>
19 *
20 * Copyright (C) 2004 David Turner
21 * Copyright (C) 2003 MenTaLguY
22 * Copyright (C) 1999-2011 authors
23 * Copyright (C) 2001-2002 Ximian, Inc.
24 *
25 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
26 */
27
28 #include "connector-toolbar.h"
29
30 #include <glibmm/i18n.h>
31
32 #include <gtkmm/separatortoolitem.h>
33
34 #include "conn-avoid-ref.h"
35
36 #include "desktop.h"
37 #include "document-undo.h"
38 #include "enums.h"
39 #include "graphlayout.h"
40 #include "selection.h"
41 #include "verbs.h"
42
43 #include "object/sp-namedview.h"
44 #include "object/sp-path.h"
45
46 #include "ui/icon-names.h"
47 #include "ui/tools/connector-tool.h"
48 #include "ui/uxmanager.h"
49 #include "ui/widget/canvas.h"
50 #include "ui/widget/spin-button-tool-item.h"
51
52 #include "xml/node-event-vector.h"
53
54 using Inkscape::UI::UXManager;
55 using Inkscape::DocumentUndo;
56
57 static Inkscape::XML::NodeEventVector connector_tb_repr_events = {
58 nullptr, /* child_added */
59 nullptr, /* child_removed */
60 Inkscape::UI::Toolbar::ConnectorToolbar::event_attr_changed,
61 nullptr, /* content_changed */
62 nullptr /* order_changed */
63 };
64
65 namespace Inkscape {
66 namespace UI {
67 namespace Toolbar {
ConnectorToolbar(SPDesktop * desktop)68 ConnectorToolbar::ConnectorToolbar(SPDesktop *desktop)
69 : Toolbar(desktop),
70 _freeze(false),
71 _repr(nullptr)
72 {
73 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
74
75 {
76 auto avoid_item = Gtk::manage(new Gtk::ToolButton(_("Avoid")));
77 avoid_item->set_tooltip_text(_("Make connectors avoid selected objects"));
78 avoid_item->set_icon_name(INKSCAPE_ICON("connector-avoid"));
79 avoid_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::path_set_avoid));
80 add(*avoid_item);
81 }
82
83 {
84 auto ignore_item = Gtk::manage(new Gtk::ToolButton(_("Ignore")));
85 ignore_item->set_tooltip_text(_("Make connectors ignore selected objects"));
86 ignore_item->set_icon_name(INKSCAPE_ICON("connector-ignore"));
87 ignore_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::path_set_ignore));
88 add(*ignore_item);
89 }
90
91 // Orthogonal connectors toggle button
92 {
93 _orthogonal = add_toggle_button(_("Orthogonal"),
94 _("Make connector orthogonal or polyline"));
95 _orthogonal->set_icon_name(INKSCAPE_ICON("connector-orthogonal"));
96
97 bool tbuttonstate = prefs->getBool("/tools/connector/orthogonal");
98 _orthogonal->set_active(( tbuttonstate ? TRUE : FALSE ));
99 _orthogonal->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::orthogonal_toggled));
100 }
101
102 add(* Gtk::manage(new Gtk::SeparatorToolItem()));
103
104 // Curvature spinbox
105 auto curvature_val = prefs->getDouble("/tools/connector/curvature", defaultConnCurvature);
106 _curvature_adj = Gtk::Adjustment::create(curvature_val, 0, 100, 1.0, 10.0);
107 auto curvature_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-curvature", _("Curvature:"), _curvature_adj, 1, 0));
108 curvature_item->set_tooltip_text(_("The amount of connectors curvature"));
109 curvature_item->set_focus_widget(desktop->canvas);
110 _curvature_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::curvature_changed));
111 add(*curvature_item);
112
113 // Spacing spinbox
114 auto spacing_val = prefs->getDouble("/tools/connector/spacing", defaultConnSpacing);
115 _spacing_adj = Gtk::Adjustment::create(spacing_val, 0, 100, 1.0, 10.0);
116 auto spacing_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-spacing", _("Spacing:"), _spacing_adj, 1, 0));
117 spacing_item->set_tooltip_text(_("The amount of space left around objects by auto-routing connectors"));
118 spacing_item->set_focus_widget(desktop->canvas);
119 _spacing_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::spacing_changed));
120 add(*spacing_item);
121
122 // Graph (connector network) layout
123 {
124 auto graph_item = Gtk::manage(new Gtk::ToolButton(_("Graph")));
125 graph_item->set_tooltip_text(_("Nicely arrange selected connector network"));
126 graph_item->set_icon_name(INKSCAPE_ICON("distribute-graph"));
127 graph_item->signal_clicked().connect(sigc::mem_fun(*this, &ConnectorToolbar::graph_layout));
128 add(*graph_item);
129 }
130
131 // Default connector length spinbox
132 auto length_val = prefs->getDouble("/tools/connector/length", 100);
133 _length_adj = Gtk::Adjustment::create(length_val, 10, 1000, 10.0, 100.0);
134 auto length_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("inkscape:connector-length", _("Length:"), _length_adj, 1, 0));
135 length_item->set_tooltip_text(_("Ideal length for connectors when layout is applied"));
136 length_item->set_focus_widget(desktop->canvas);
137 _length_adj->signal_value_changed().connect(sigc::mem_fun(*this, &ConnectorToolbar::length_changed));
138 add(*length_item);
139
140 // Directed edges toggle button
141 {
142 _directed_item = add_toggle_button(_("Downwards"),
143 _("Make connectors with end-markers (arrows) point downwards"));
144 _directed_item->set_icon_name(INKSCAPE_ICON("distribute-graph-directed"));
145
146 bool tbuttonstate = prefs->getBool("/tools/connector/directedlayout");
147 _directed_item->set_active(tbuttonstate ? TRUE : FALSE);
148
149 _directed_item->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::directed_graph_layout_toggled));
150 desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &ConnectorToolbar::selection_changed));
151 }
152
153 // Avoid overlaps toggle button
154 {
155 _overlap_item = add_toggle_button(_("Remove overlaps"),
156 _("Do not allow overlapping shapes"));
157 _overlap_item->set_icon_name(INKSCAPE_ICON("distribute-remove-overlaps"));
158
159 bool tbuttonstate = prefs->getBool("/tools/connector/avoidoverlaplayout");
160 _overlap_item->set_active(tbuttonstate ? TRUE : FALSE);
161
162 _overlap_item->signal_toggled().connect(sigc::mem_fun(*this, &ConnectorToolbar::nooverlaps_graph_layout_toggled));
163 }
164
165 // Code to watch for changes to the connector-spacing attribute in
166 // the XML.
167 Inkscape::XML::Node *repr = desktop->namedview->getRepr();
168 g_assert(repr != nullptr);
169
170 if(_repr) {
171 _repr->removeListenerByData(this);
172 Inkscape::GC::release(_repr);
173 _repr = nullptr;
174 }
175
176 if (repr) {
177 _repr = repr;
178 Inkscape::GC::anchor(_repr);
179 _repr->addListener(&connector_tb_repr_events, this);
180 _repr->synthesizeEvents(&connector_tb_repr_events, this);
181 }
182
183 show_all();
184 }
185
186 GtkWidget *
create(SPDesktop * desktop)187 ConnectorToolbar::create( SPDesktop *desktop)
188 {
189 auto toolbar = new ConnectorToolbar(desktop);
190 return GTK_WIDGET(toolbar->gobj());
191 } // end of ConnectorToolbar::prep()
192
193 void
path_set_avoid()194 ConnectorToolbar::path_set_avoid()
195 {
196 Inkscape::UI::Tools::cc_selection_set_avoid(_desktop, true);
197 }
198
199 void
path_set_ignore()200 ConnectorToolbar::path_set_ignore()
201 {
202 Inkscape::UI::Tools::cc_selection_set_avoid(_desktop, false);
203 }
204
205 void
orthogonal_toggled()206 ConnectorToolbar::orthogonal_toggled()
207 {
208 auto doc = _desktop->getDocument();
209
210 if (!DocumentUndo::getUndoSensitive(doc)) {
211 return;
212 }
213
214 // quit if run by the _changed callbacks
215 if (_freeze) {
216 return;
217 }
218
219 // in turn, prevent callbacks from responding
220 _freeze = true;
221
222 bool is_orthog = _orthogonal->get_active();
223 gchar orthog_str[] = "orthogonal";
224 gchar polyline_str[] = "polyline";
225 gchar *value = is_orthog ? orthog_str : polyline_str ;
226
227 bool modmade = false;
228 auto itemlist= _desktop->getSelection()->items();
229 for(auto i=itemlist.begin();i!=itemlist.end();++i){
230 SPItem *item = *i;
231
232 if (Inkscape::UI::Tools::cc_item_is_connector(item)) {
233 item->setAttribute( "inkscape:connector-type",
234 value, nullptr);
235 item->getAvoidRef().handleSettingChange();
236 modmade = true;
237 }
238 }
239
240 if (!modmade) {
241 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
242 prefs->setBool("/tools/connector/orthogonal", is_orthog);
243 } else {
244
245 DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR,
246 is_orthog ? _("Set connector type: orthogonal"): _("Set connector type: polyline"));
247 }
248
249 _freeze = false;
250 }
251
252 void
curvature_changed()253 ConnectorToolbar::curvature_changed()
254 {
255 SPDocument *doc = _desktop->getDocument();
256
257 if (!DocumentUndo::getUndoSensitive(doc)) {
258 return;
259 }
260
261
262 // quit if run by the _changed callbacks
263 if (_freeze) {
264 return;
265 }
266
267 // in turn, prevent callbacks from responding
268 _freeze = true;
269
270 auto newValue = _curvature_adj->get_value();
271 gchar value[G_ASCII_DTOSTR_BUF_SIZE];
272 g_ascii_dtostr(value, G_ASCII_DTOSTR_BUF_SIZE, newValue);
273
274 bool modmade = false;
275 auto itemlist= _desktop->getSelection()->items();
276 for(auto i=itemlist.begin();i!=itemlist.end();++i){
277 SPItem *item = *i;
278
279 if (Inkscape::UI::Tools::cc_item_is_connector(item)) {
280 item->setAttribute( "inkscape:connector-curvature",
281 value, nullptr);
282 item->getAvoidRef().handleSettingChange();
283 modmade = true;
284 }
285 }
286
287 if (!modmade) {
288 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
289 prefs->setDouble(Glib::ustring("/tools/connector/curvature"), newValue);
290 }
291 else {
292 DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR,
293 _("Change connector curvature"));
294 }
295
296 _freeze = false;
297 }
298
299 void
spacing_changed()300 ConnectorToolbar::spacing_changed()
301 {
302 SPDocument *doc = _desktop->getDocument();
303
304 if (!DocumentUndo::getUndoSensitive(doc)) {
305 return;
306 }
307
308 Inkscape::XML::Node *repr = _desktop->namedview->getRepr();
309
310 if ( !repr->attribute("inkscape:connector-spacing") &&
311 ( _spacing_adj->get_value() == defaultConnSpacing )) {
312 // Don't need to update the repr if the attribute doesn't
313 // exist and it is being set to the default value -- as will
314 // happen at startup.
315 return;
316 }
317
318 // quit if run by the attr_changed listener
319 if (_freeze) {
320 return;
321 }
322
323 // in turn, prevent listener from responding
324 _freeze = true;
325
326 sp_repr_set_css_double(repr, "inkscape:connector-spacing", _spacing_adj->get_value());
327 _desktop->namedview->updateRepr();
328 bool modmade = false;
329
330 std::vector<SPItem *> items;
331 items = get_avoided_items(items, _desktop->currentRoot(), _desktop);
332 for (auto item : items) {
333 Geom::Affine m = Geom::identity();
334 avoid_item_move(&m, item);
335 modmade = true;
336 }
337
338 if(modmade) {
339 DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR,
340 _("Change connector spacing"));
341 }
342 _freeze = false;
343 }
344
345 void
graph_layout()346 ConnectorToolbar::graph_layout()
347 {
348 assert(_desktop);
349 if (!_desktop) {
350 return;
351 }
352 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
353
354 // hack for clones, see comment in align-and-distribute.cpp
355 int saved_compensation = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
356 prefs->setInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_UNMOVED);
357
358 auto tmp = _desktop->getSelection()->items();
359 std::vector<SPItem *> vec(tmp.begin(), tmp.end());
360 graphlayout(vec);
361
362 prefs->setInt("/options/clonecompensation/value", saved_compensation);
363
364 DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, _("Arrange connector network"));
365 }
366
367 void
length_changed()368 ConnectorToolbar::length_changed()
369 {
370 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
371 prefs->setDouble("/tools/connector/length", _length_adj->get_value());
372 }
373
374 void
directed_graph_layout_toggled()375 ConnectorToolbar::directed_graph_layout_toggled()
376 {
377 auto prefs = Inkscape::Preferences::get();
378 prefs->setBool("/tools/connector/directedlayout", _directed_item->get_active());
379 }
380
381 void
selection_changed(Inkscape::Selection * selection)382 ConnectorToolbar::selection_changed(Inkscape::Selection *selection)
383 {
384 SPItem *item = selection->singleItem();
385 if (SP_IS_PATH(item))
386 {
387 gdouble curvature = SP_PATH(item)->connEndPair.getCurvature();
388 bool is_orthog = SP_PATH(item)->connEndPair.isOrthogonal();
389 _orthogonal->set_active(is_orthog);
390 _curvature_adj->set_value(curvature);
391 }
392
393 }
394
395 void
nooverlaps_graph_layout_toggled()396 ConnectorToolbar::nooverlaps_graph_layout_toggled()
397 {
398 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
399 prefs->setBool("/tools/connector/avoidoverlaplayout",
400 _overlap_item->get_active());
401 }
402
403 void
event_attr_changed(Inkscape::XML::Node * repr,gchar const * name,gchar const *,gchar const *,bool,gpointer data)404 ConnectorToolbar::event_attr_changed(Inkscape::XML::Node *repr,
405 gchar const *name,
406 gchar const * /*old_value*/,
407 gchar const * /*new_value*/,
408 bool /*is_interactive*/,
409 gpointer data)
410 {
411 auto toolbar = reinterpret_cast<ConnectorToolbar *>(data);
412
413 if ( !toolbar->_freeze
414 && (strcmp(name, "inkscape:connector-spacing") == 0) ) {
415 gdouble spacing = defaultConnSpacing;
416 sp_repr_get_double(repr, "inkscape:connector-spacing", &spacing);
417
418 toolbar->_spacing_adj->set_value(spacing);
419
420 if (toolbar->_desktop->canvas) {
421 toolbar->_desktop->canvas->grab_focus();
422 }
423 }
424 }
425
426 }
427 }
428 }
429
430 /*
431 Local Variables:
432 mode:c++
433 c-file-style:"stroustrup"
434 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
435 indent-tabs-mode:nil
436 fill-column:99
437 End:
438 */
439 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
440