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