Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
canvas-grid.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Author:
7 * Tavmjong Bah
8 *
9 * Copyright (C) 2020 Tavmjong Bah
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14// The scrollbars, and canvas are tightly coupled so it makes sense to have a dedicated
15// widget to handle their interactions. The buttons are along for the ride. I don't see
16// how to add the buttons easily via a .ui file (which would allow the user to put any
17// buttons they want in their place).
18
19#include <utility>
20#include <glibmm/i18n.h>
21#include <glibmm/main.h>
22#include <gtkmm/adjustment.h>
23#include <gtkmm/builder.h>
24#include <gtkmm/checkbutton.h>
25#include <gtkmm/enums.h>
26#include <gtkmm/eventcontrollermotion.h>
27#include <gtkmm/gestureclick.h>
28#include <gtkmm/image.h>
29#include <gtkmm/label.h>
30#include <gtkmm/popover.h>
31#include <sigc++/adaptors/bind.h>
32#include <sigc++/functors/mem_fun.h>
33
34#include "canvas-grid.h"
35#include "desktop.h" // Hopefully temp.
36#include "desktop-events.h" // Hopefully temp.
37#include "document-undo.h"
38#include "message-context.h"
39#include "selection.h"
41#include "page-manager.h"
42
43#include "io/resource.h"
44#include "object/sp-grid.h"
45#include "object/sp-root.h"
46#include "ui/builder-utils.h"
47#include "ui/controller.h"
49#include "ui/drag-and-drop.h"
50#include "ui/tools/tool-base.h"
51#include "ui/util.h"
52#include "ui/widget/canvas.h"
53#include "ui/widget/desktop-widget.h" // Hopefully temp.
56#include "ui/widget/ink-ruler.h"
57#include "ui/widget/stack.h"
59#include "util/units.h"
60
61namespace Inkscape::UI::Widget {
62
64{
65 _dtw = dtw;
66 set_name("CanvasGrid");
67
68 // Tabs widget
69 _tabs_widget = std::make_unique<Inkscape::UI::Widget::TabsWidget>(dtw);
70
71 // Command palette
72 _command_palette = std::make_unique<Inkscape::UI::Dialog::CommandPalette>();
73
74 // Notice overlay, note using unique_ptr will cause destruction race conditions
76
77 // Canvas overlay
79 _canvas_overlay.add_overlay(_command_palette->get_base_widget());
80 _canvas_overlay.add_overlay(*_notice);
81 _canvas_overlay.set_expand();
82
83 _canvas_stack = Gtk::make_managed<Inkscape::UI::Widget::Stack>();
86
87 // Horizontal Ruler
88 _hruler = std::make_unique<Inkscape::UI::Widget::Ruler>(Gtk::Orientation::HORIZONTAL);
89 _hruler->set_hexpand(true);
90 // Tooltip/Unit set elsewhere
91
92 // Vertical Ruler
93 _vruler = std::make_unique<Inkscape::UI::Widget::Ruler>(Gtk::Orientation::VERTICAL);
94 _vruler->set_vexpand(true);
95 // Tooltip/Unit set elsewhere.
96
97 // Guide Lock
98 _guide_lock.set_name("LockGuides");
99 _guide_lock.set_action_name("doc.lock-all-guides");
100 auto set_lock_icon = [this](){
101 _guide_lock.set_image_from_icon_name(_guide_lock.get_active() ? "object-locked" : "object-unlocked");
102 };
103 // To be replaced by Gio::Action:
104 _guide_lock.signal_toggled().connect([=,this](){
105 set_lock_icon();
106 });
107 _guide_lock.signal_clicked().connect([this] {
108 bool down = !_guide_lock.get_active(); // Hack: Reversed since button state only changes after click.
109 _dtw->get_desktop()->guidesMessageContext()->flash(Inkscape::NORMAL_MESSAGE, down ? _("Locked all guides") : _("Unlocked all guides"));
110 });
111 set_lock_icon();
112 _guide_lock.set_tooltip_text(_("Toggle lock of all guides in the document"));
113
114 // Subgrid
115 _subgrid.attach(_guide_lock, 0, 0, 1, 1);
116 _subgrid.attach(*_vruler, 0, 1, 1, 1);
117 _subgrid.attach(*_hruler, 1, 0, 1, 1);
118 _subgrid.attach(_canvas_overlay, 1, 1, 1, 1);
119 _subgrid.set_expand();
120
121 // Horizontal Scrollbar
122 _hadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0);
123 _hadj->signal_value_changed().connect(sigc::mem_fun(*this, &CanvasGrid::_adjustmentChanged));
124 _hscrollbar = Gtk::Scrollbar(_hadj, Gtk::Orientation::HORIZONTAL);
125 _hscrollbar.set_name("CanvasScrollbar");
126 _hscrollbar.set_hexpand(true);
127
128 // Vertical Scrollbar
129 _vadj = Gtk::Adjustment::create(0.0, -4000.0, 4000.0, 10.0, 100.0, 4.0);
130 _vadj->signal_value_changed().connect(sigc::mem_fun(*this, &CanvasGrid::_adjustmentChanged));
131 _vscrollbar = Gtk::Scrollbar(_vadj, Gtk::Orientation::VERTICAL);
132 _vscrollbar.set_name("CanvasScrollbar");
133 _vscrollbar.set_vexpand(true);
134
135 // CMS Adjust (To be replaced by Gio::Action)
136 _cms_adjust.set_name("CMS_Adjust");
137 _cms_adjust.set_action_name("win.canvas-color-manage");
138 _cms_adjust.set_tooltip_text(_("Toggle color-managed display for this document window"));
139 auto set_cms_icon = [this](){
140 _cms_adjust.set_image_from_icon_name(_cms_adjust.get_active() ? "color-management" : "color-management-off");
141 };
142 set_cms_icon();
143 _cms_adjust.signal_toggled().connect([=,this](){ set_cms_icon(); });
144
145 // popover with some common display mode related options
146 _builder_display_popup = create_builder("display-popup.glade");
147 auto popover = &get_widget<Gtk::Popover> (_builder_display_popup, "popover");
148 auto sticky_zoom = &get_widget<Gtk::CheckButton>(_builder_display_popup, "zoom-resize");
149
150 // To be replaced by Gio::Action:
151 sticky_zoom->signal_toggled().connect([this](){ _dtw->sticky_zoom_toggled(); });
152
153 _quick_actions.set_name("QuickActions");
154 _quick_actions.set_popover(*popover);
155 _quick_actions.set_icon_name("display-symbolic");
156 _quick_actions.set_direction(Gtk::ArrowType::LEFT);
157 _quick_actions.set_tooltip_text(_("Display options"));
158
159 _quick_preview_label = &get_widget<Gtk::Label>(_builder_display_popup, "quick_preview_label");
160 _quick_zoom_label = &get_widget<Gtk::Label>(_builder_display_popup, "quick_zoom_label");
161
162 auto quick_preview_shortcut = _preview_accel.getShortcutText();
163 auto quick_zoom_shortcut = _zoom_accel.getShortcutText();
164
165 if (!quick_preview_shortcut.empty()) {
166 _quick_preview_label->set_label("<b>" + quick_preview_shortcut[0] + "</b>");
167 } else {
168 _quick_preview_label->set_label("");
169 }
170
171 if (!quick_zoom_shortcut.empty()) {
172 _quick_zoom_label->set_label("<b>" + quick_zoom_shortcut[0] + "</b>");
173 } else {
174 _quick_zoom_label->set_label("");
175 }
176
178 if(_preview_accel.getShortcutText().empty()) {
179 _quick_preview_label->set_label("");
180 return;
181 }
182 _quick_preview_label->set_label("<b>" + _preview_accel.getShortcutText()[0] + "</b>");
183 });
184
186 if(_zoom_accel.getShortcutText().empty()) {
187 _quick_zoom_label->set_label("");
188 return;
189 }
190 _quick_zoom_label->set_label("<b>" + _zoom_accel.getShortcutText()[0] + "</b>");
191 });
192
193 // Main grid
194 attach(*_tabs_widget, 0, 0);
195 attach(_subgrid, 0, 1, 1, 2);
196 attach(_hscrollbar, 0, 3, 1, 1);
197 attach(_cms_adjust, 1, 3, 1, 1);
198 attach(_quick_actions, 1, 1, 1, 1);
199 attach(_vscrollbar, 1, 2, 1, 1);
200
201 // For creating guides, etc.
202 auto const bind_controllers = [&](auto& ruler, RulerOrientation orientation) {
203 auto const click = Gtk::GestureClick::create();
204 click->set_button(1); // left
205 click->signal_pressed().connect(Controller::use_state([this](auto &&...args) { return _rulerButtonPress(args...); }, *click));
206 click->signal_released().connect(Controller::use_state([this, orientation](auto &&...args) { return _rulerButtonRelease(args..., orientation); }, *click));
207 ruler->add_controller(click);
208
209 auto const motion = Gtk::EventControllerMotion::create();
210 motion->signal_motion().connect([this, orientation, &motion = *motion](auto &&...args) { _rulerMotion(motion, args..., orientation); });
211 ruler->add_controller(motion);
212 };
213
214 bind_controllers(_hruler, RulerOrientation::horizontal);
215 bind_controllers(_vruler, RulerOrientation::vertical);
216
217 auto prefs = Inkscape::Preferences::get();
218 _box_observer = prefs->createObserver("/tools/bounding_box", [this](const Preferences::Entry& entry) {
219 updateRulers();
220 });
221}
222
223CanvasGrid::~CanvasGrid() = default;
224
226{
227 canvas->set_hexpand(true);
228 canvas->set_vexpand(true);
229 canvas->set_focusable(true);
230 _canvas_stack->add(*canvas);
231}
232
234{
235 _canvas_stack->remove(*canvas);
236}
237
239{
240 if (_canvas) {
241 _hruler->clear_track_widget();
242 _vruler->clear_track_widget();
243 }
244
245 _canvas = canvas;
246
248
249 if (_canvas) {
250 _hruler->set_track_widget(*_canvas);
251 _vruler->set_track_widget(*_canvas);
252 }
253}
254
256{
257 // actions should be available now
258 parent_type::on_realize();
259
260 auto const map = _dtw->get_action_map();
261 if (!map) {
262 g_warning("No action map available to canvas-grid");
263 return;
264 }
265
266 auto const cms_action = std::dynamic_pointer_cast<Gio::SimpleAction>(map->lookup_action("canvas-color-manage"));
267 auto const disp_action = std::dynamic_pointer_cast<Gio::SimpleAction>(map->lookup_action("canvas-display-mode"));
268 if (!cms_action || !disp_action) {
269 g_warning("No canvas-display-mode and/or canvas-color-manage action available to canvas-grid");
270 return;
271 }
272
273 auto set_display_icon = [=, this] {
274 int display_mode;
275 disp_action->get_state<int>(display_mode);
276
277 Glib::ustring id;
278 switch (static_cast<Inkscape::RenderMode>(display_mode)) {
280 id = "display";
281 break;
283 id = "display-outline";
284 break;
286 id = "display-outline-overlay";
287 break;
289 id = "display-enhance-stroke";
290 break;
292 id = "display-no-filter";
293 break;
294 default:
295 g_warning("Unknown display mode in canvas-grid");
296 return;
297 }
298
299 bool cms_mode;
300 cms_action->get_state<bool>(cms_mode);
301
302 // if CMS is ON show alternative icons
303 if (cms_mode) {
304 id += "-alt";
305 }
306
307 _quick_actions.set_icon_name(id + "-symbolic");
308 };
309
310 // when display mode state changes, update icon
311 disp_action->property_state().signal_changed().connect([=] { set_display_icon(); });
312 cms_action-> property_state().signal_changed().connect([=] { set_display_icon(); });
313 set_display_icon();
314}
315
316// TODO: remove when sticky zoom gets replaced by Gio::Action:
317Gtk::CheckButton *CanvasGrid::GetStickyZoom() {
318 return &get_widget<Gtk::CheckButton>(_builder_display_popup, "zoom-resize");
319}
320
321// _dt2r should be a member of _canvas.
322// get_display_area should be a member of _canvas.
324{
325 auto const desktop = _dtw->get_desktop();
326 auto document = desktop->getDocument();
327 auto &pm = document->getPageManager();
328 auto sel = desktop->getSelection();
329
330 // Our connections to the document are handled with a lazy pattern to avoid
331 // having to refactor the SPDesktopWidget class. We know UpdateRulers is
332 // called in all situations when documents are loaded and replaced.
333 if (document != _document) {
334 _document = document;
335
336 _page_selected_connection = pm.connectPageSelected([this](SPPage const *) { updateRulers(); });
337 _page_modified_connection = pm.connectPageModified([this](SPPage const *) { updateRulers(); });
338
339 _sel_modified_connection.disconnect();
340 _sel_changed_connection .disconnect();
341 if (sel) {
343 _sel_changed_connection = sel->connectChanged ([this](Inkscape::Selection const * ) { updateRulers(); });
344 }
345 }
346
347 Geom::Rect viewbox = _canvas->get_area_world();
348 Geom::Rect startbox = viewbox;
349 if (document->get_origin_follows_page()) {
350 // Move viewbox according to the selected page's position (if any)
351 auto page_transform = pm.getSelectedPageAffine().inverse() * desktop->d2w();
352 startbox += page_transform.translation();
353 }
354
355 // Scale coordinates to current display units
356 auto d2c_scalerot = _canvas->get_affine();
357 // w2r and c2r scale should be the same
358 // c2r = c2d * d2r = (1/d2c)*d2r
359 double w2r_scale = _dtw->get_dt2r() / d2c_scalerot.expansionX();
360 auto const rulerbox = startbox * Geom::Scale{w2r_scale};
361 _hruler->set_range(rulerbox.left(), rulerbox.right());
362 if (desktop->is_yaxisdown()) {
363 _vruler->set_range(rulerbox.top(), rulerbox.bottom());
364 } else {
365 _vruler->set_range(-rulerbox.top(), -rulerbox.bottom());
366 }
367
369 auto d2c = d2c_scalerot * Geom::Translate(-pos);
370 auto pagebox = (pm.getSelectedPageRect() * d2c).roundOutwards();
371 _hruler->set_page(pagebox.left(), pagebox.right());
372 _vruler->set_page(pagebox.top(), pagebox.bottom());
373
374 Geom::Rect selbox = Geom::IntRect(0, 0, 0, 0);
375 if (sel) {
376 if (auto const bbox = sel->preferredBounds()) {
377 selbox = (*bbox * d2c).roundOutwards();
378 }
379 }
380 _hruler->set_selection(selbox.left(), selbox.right());
381 _vruler->set_selection(selbox.top(), selbox.bottom());
382}
383
384void
386{
387 if (_show_scrollbars == state) return;
388
389 _show_scrollbars = state;
390 _hscrollbar .set_visible(_show_scrollbars);
391 _vscrollbar .set_visible(_show_scrollbars);
392 _cms_adjust .set_visible(_show_scrollbars);
393 _quick_actions.set_visible(_show_scrollbars);
394}
395
396void
398{
401
402 // Will be replaced by actions
403 auto prefs = Inkscape::Preferences::get();
404 prefs->setBool("/fullscreen/scrollbars/state", _show_scrollbars);
405 prefs->setBool("/window/scrollbars/state", _show_scrollbars);
406}
407
408void
410{
411 if (_show_rulers == state) return;
412
413 _show_rulers = state;
414
415 _hruler ->set_visible(_show_rulers);
416 _vruler ->set_visible(_show_rulers);
417 _guide_lock.set_visible(_show_rulers);
418}
419
421{
424
425 // Will be replaced by actions
426 auto prefs = Inkscape::Preferences::get();
427 prefs->setBool("/fullscreen/rulers/state", _show_rulers);
428 prefs->setBool("/window/rulers/state", _show_rulers);
429}
430
435
436void CanvasGrid::showNotice(Glib::ustring const &msg, int timeout)
437{
439}
440
441void
443{
444 if (state) {
445 _command_palette->open();
446 } else {
447 _command_palette->close();
448 }
449}
450
451// Update rulers on change of widget size, but only if allocation really changed.
452void
453CanvasGrid::size_allocate_vfunc(int const width, int const height, int const baseline)
454{
455 Gtk::Grid::size_allocate_vfunc(width, height, baseline);
456
457 if (std::exchange(_width , width ) != width ||
458 std::exchange(_height, height) != height)
459 {
460 updateRulers();
461 }
462}
463
465{
467 (horiz ? _hruler : _vruler)->translate_coordinates(*_canvas, 0, 0, result.x(), result.y());
468 return result.round();
469}
470
471// Start guide creation by dragging from ruler.
472Gtk::EventSequenceState CanvasGrid::_rulerButtonPress(Gtk::GestureClick const &gesture,
473 int /*n_press*/, double x, double y)
474{
475 if (_ruler_clicked) {
476 return Gtk::EventSequenceState::NONE;
477 }
478
479 auto const state = gesture.get_current_event_state();
480
481 _ruler_clicked = true;
482 _ruler_dragged = false;
483 _ruler_ctrl_clicked = Controller::has_flag(state, Gdk::ModifierType::CONTROL_MASK);
485
486 return Gtk::EventSequenceState::CLAIMED;
487}
488
489void CanvasGrid::_createGuideItem(Geom::Point const &pos, bool horiz)
490{
491 auto const desktop = _dtw->get_desktop();
492
493 // Ensure new guide is visible
495
496 // Calculate the normal of the guidelines when dragged from the edges of rulers.
497 auto const y_dir = desktop->yaxisdir();
498 auto normal_bl_to_tr = Geom::Point( 1, y_dir).normalized(); // Bottom-left to top-right
499 auto normal_tr_to_bl = Geom::Point(-1, y_dir).normalized(); // Top-right to bottom-left
500
501 if (auto const grid = desktop->getNamedView()->getFirstEnabledGrid();
502 grid && grid->getType() == GridType::AXONOMETRIC)
503 {
504 auto const angle_x = Geom::rad_from_deg(grid->getAngleX());
505 auto const angle_z = Geom::rad_from_deg(grid->getAngleZ());
507 // guidelines normal to gridlines
508 normal_bl_to_tr = Geom::Point::polar(angle_x * y_dir, 1.0);
509 normal_tr_to_bl = Geom::Point::polar(-angle_z * y_dir, 1.0);
510 } else {
511 normal_bl_to_tr = Geom::Point::polar(-angle_z * y_dir, 1.0).cw();
512 normal_tr_to_bl = Geom::Point::polar(angle_x * y_dir, 1.0).cw();
513 }
514 }
515
516 if (horiz) {
517 if (pos.x() < 50) {
518 _normal = normal_bl_to_tr;
519 } else if (pos.x() > _canvas->get_width() - 50) {
520 _normal = normal_tr_to_bl;
521 } else {
522 _normal = Geom::Point(0, 1);
523 }
524 } else {
525 if (pos.y() < 50) {
526 _normal = normal_bl_to_tr;
527 } else if (pos.y() > _canvas->get_height() - 50) {
528 _normal = normal_tr_to_bl;
529 } else {
530 _normal = Geom::Point(1, 0);
531 }
532 }
533
534 _active_guide = make_canvasitem<CanvasItemGuideLine>(desktop->getCanvasGuides(), Glib::ustring(), Geom::Point(), Geom::Point());
535 _active_guide->set_stroke(desktop->getNamedView()->getGuideHiColor().toRGBA());
536}
537
538void CanvasGrid::_rulerMotion(Gtk::EventControllerMotion const &controller, double x, double y, RulerOrientation orientation)
539{
540 if (!_ruler_clicked) {
541 return;
542 }
543
544 // Get the position in canvas coordinates.
545 auto const horiz = orientation == RulerOrientation::horizontal;
546 auto const pos = Geom::Point(x, y) + _rulerToCanvas(horiz);
547
548 if (!_ruler_dragged) {
549 // Discard small movements without starting a drag.
550 auto prefs = Preferences::get();
551 int tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
552 if (Geom::LInfty(Geom::Point(x, y).floor() - _ruler_drag_origin) < tolerance) {
553 return;
554 }
555
556 _createGuideItem(pos, horiz);
557
558 _ruler_dragged = true;
559 }
560
561 // Synthesize the CanvasEvent.
562 auto event = MotionEvent();
563 event.modifiers = (unsigned)controller.get_current_event_state();
564 event.device = controller.get_current_event_device();
565 event.pos = pos;
566 event.time = controller.get_current_event_time();
567 event.extinput = extinput_from_gdkevent(*controller.get_current_event());
568
569 rulerMotion(event, horiz);
570}
571
573{
574 desktop->getCanvas()->grab_focus();
575
576 auto &m = desktop->getNamedView()->snap_manager;
577 m.setup(desktop);
578
579 // We're dragging a brand new guide, just pulled out of the rulers seconds ago. When snapping to a
580 // path this guide will change it slope to become either tangential or perpendicular to that path. It's
581 // therefore not useful to try tangential or perpendicular snapping, so this will be disabled temporarily
582 bool pref_perp = m.snapprefs.isTargetSnappable(SNAPTARGET_PATH_PERPENDICULAR);
583 bool pref_tang = m.snapprefs.isTargetSnappable(SNAPTARGET_PATH_TANGENTIAL);
584 m.snapprefs.setTargetSnappable(SNAPTARGET_PATH_PERPENDICULAR, false);
585 m.snapprefs.setTargetSnappable(SNAPTARGET_PATH_TANGENTIAL, false);
586
587 // We only have a temporary guide which is not stored in our document yet.
588 // Because the guide snapper only looks in the document for guides to snap to,
589 // we don't have to worry about a guide snapping to itself here
590 auto const normal_orig = normal;
591 m.guideFreeSnap(event_dt, normal, false, false);
592
593 // After snapping, both event_dt and normal have been modified accordingly; we'll take the normal (of the
594 // curve we snapped to) to set the normal the guide. And rotate it by 90 deg. if needed
595 if (pref_perp) { // Perpendicular snapping to paths is requested by the user, so let's do that
596 if (normal != normal_orig) {
597 normal = Geom::rot90(normal);
598 }
599 }
600
601 if (!(pref_tang || pref_perp)) { // if we don't want to snap either perpendicularly or tangentially, then
602 normal = normal_orig; // we must restore the normal to its original state
603 }
604
605 // Restore the preferences
606 m.snapprefs.setTargetSnappable(SNAPTARGET_PATH_PERPENDICULAR, pref_perp);
607 m.snapprefs.setTargetSnappable(SNAPTARGET_PATH_TANGENTIAL, pref_tang);
608 m.unSetup();
609}
610
611void CanvasGrid::rulerMotion(MotionEvent const &event, bool horiz)
612{
613 auto const desktop = _dtw->get_desktop();
614
617
618 desktop->getTool()->snap_delay_handler(this, nullptr, event, origin);
619 auto const event_w = _canvas->canvas_to_world(event.pos);
620 auto event_dt = _dtw->get_desktop()->w2d(event_w);
621
622 // Update the displayed coordinates.
624
625 if (_active_guide) {
626 // Get the snapped position and normal.
627 auto normal = _normal;
628 if (!(event.modifiers & GDK_SHIFT_MASK)) {
629 ruler_snap_new_guide(desktop, event_dt, normal);
630 }
631
632 // Apply the position and normal to the guide.
633 _active_guide->set_normal(normal);
634 _active_guide->set_origin(event_dt);
635 }
636}
637
639{
640 auto const desktop = _dtw->get_desktop();
641 auto const xml_doc = desktop->doc()->getReprDoc();
642 auto const repr = xml_doc->createElement("sodipodi:guide");
643
645 // This condition occurs when guides are locked
648 }
649
650 // <sodipodi:guide> stores inverted y-axis coordinates
651 if (desktop->is_yaxisdown()) {
652 origin.y() = desktop->doc()->getHeight().value("px") - origin.y();
653 normal.y() *= -1.0;
654 }
655
656 // If root viewBox set, interpret guides in terms of viewBox (90/96)
657 auto root = desktop->doc()->getRoot();
658 if (root->viewBox_set) {
659 origin.x() *= root->viewBox.width() / root->width.computed;
660 origin.y() *= root->viewBox.height() / root->height.computed;
661 }
662
663 repr->setAttributePoint("position", origin);
664 repr->setAttributePoint("orientation", normal);
666 GC::release(repr);
667 DocumentUndo::done(desktop->getDocument(), _("Create guide"), "");
668}
669
670// End guide creation or toggle guides on/off.
671Gtk::EventSequenceState CanvasGrid::_rulerButtonRelease(Gtk::GestureClick const &gesture,
672 int /*n_press*/, double x, double y, RulerOrientation orientation)
673{
674 if (!_ruler_clicked) {
675 return Gtk::EventSequenceState::NONE;
676 }
677
678 auto const horiz = orientation == RulerOrientation::horizontal;
679 auto const desktop = _dtw->get_desktop();
680
681 if (_active_guide) {
683
684 auto const pos = Geom::Point(x, y) + _rulerToCanvas(horiz);
685 auto const state = gesture.get_current_event_state();
686
687 // Get the snapped position and normal.
688 auto const event_w = _canvas->canvas_to_world(pos);
689 auto event_dt = desktop->w2d(event_w);
690 auto normal = _normal;
691 if (!(bool)(state & Gdk::ModifierType::SHIFT_MASK)) {
692 ruler_snap_new_guide(desktop, event_dt, normal);
693 }
694
695 // Clear the guide on-canvas.
696 _active_guide.reset();
697
698 // FIXME: If possible, clear the snap indicator here too.
699
700 // If the guide is on-screen, create the actual guide in the document.
701 if (pos[horiz ? Geom::Y : Geom::X] >= 0) {
702 _createGuide(event_dt, normal);
703 }
704
705 // Update the coordinate display.
707 } else {
708 // Ruler click (without drag) toggles the guide visibility on and off.
710 }
711
712 _ruler_clicked = false;
713 _ruler_dragged = false;
714
715 return Gtk::EventSequenceState::CLAIMED;
716}
717
719{
720 _guide_lock.get_style_context()->add_class("blink");
721 _blink_lock_button_timeout = Glib::signal_timeout().connect([this] {
722 _guide_lock.get_style_context()->remove_class("blink");
723 return false;
724 }, 500);
725}
726
727static void set_adjustment(Gtk::Adjustment *adj, double l, double u, double ps, double si, double pi)
728{
729 if (l != adj->get_lower() ||
730 u != adj->get_upper() ||
731 ps != adj->get_page_size() ||
732 si != adj->get_step_increment() ||
733 pi != adj->get_page_increment())
734 {
735 adj->set_lower(l);
736 adj->set_upper(u);
737 adj->set_page_size(ps);
738 adj->set_step_increment(si);
739 adj->set_page_increment(pi);
740 }
741}
742
744{
745 if (_updating) {
746 return;
747 }
748 _updating = true;
749
750 // The desktop region we always show unconditionally.
751 auto const desktop = _dtw->get_desktop();
752 auto const doc = desktop->doc();
753
754 auto deskarea = *doc->preferredBounds();
755 deskarea.expandBy(doc->getDimensions()); // Double size
756
757 // The total size of pages should be added unconditionally.
758 deskarea |= doc->getPageManager().getDesktopRect();
759
760 if (Preferences::get()->getInt("/tools/bounding_box") == 0) {
761 deskarea |= doc->getRoot()->desktopVisualBounds();
762 } else {
763 deskarea |= doc->getRoot()->desktopGeometricBounds();
764 }
765
766 // Canvas region we always show unconditionally.
767 double const y_dir = desktop->yaxisdir();
768 auto carea = deskarea * Geom::Scale(scale, scale * y_dir);
769 carea.expandBy(64);
770
771 auto const viewbox = Geom::Rect(_canvas->get_area_world());
772
773 // Viewbox is always included into scrollable region.
774 carea |= viewbox;
775
776 set_adjustment(_hadj.get(), carea.left(), carea.right(),
777 viewbox.width(),
778 0.1 * viewbox.width(),
779 viewbox.width());
780 _hadj->set_value(viewbox.left());
781
782 set_adjustment(_vadj.get(), carea.top(), carea.bottom(),
783 viewbox.height(),
784 0.1 * viewbox.height(),
785 viewbox.height());
786 _vadj->set_value(viewbox.top());
787
788 _updating = false;
789}
790
792{
793 if (_updating) {
794 return;
795 }
796 _updating = true;
797
798 // Do not call canvas->scrollTo directly... messes up 'offset'.
799 _dtw->get_desktop()->scroll_absolute({_hadj->get_value(), _vadj->get_value()});
800
801 _updating = false;
802}
803
804// TODO Add actions so we can set shortcuts.
805// * Sticky Zoom
806// * CMS Adjust
807// * Guide Lock
808
809} // namespace Inkscape::UI::Widget
810
811/*
812 Local Variables:
813 mode:c++
814 c-file-style:"stroustrup"
815 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
816 indent-tabs-mode:nil
817 fill-column:99
818 End:
819*/
820// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Gtk builder utilities.
Rewrite of code originally in desktop-widget.cpp.
Inkscape canvas widget.
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
Two-dimensional point with integer coordinates.
Definition int-point.h:57
Two-dimensional point that doubles as a vector.
Definition point.h:66
IntPoint floor() const
Round coordinates downwards.
Definition point.h:206
Point normalized() const
Definition point.h:121
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
IntPoint round() const
Round to nearest integer coordinates.
Definition point.h:202
constexpr Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void flash(MessageType type, char const *message)
pushes a message onto the stack for a brief period of time without disturbing our "current" message
Data type representing a typeless value of a preference.
static Preferences * get()
Access the singleton Preferences object.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void snap_delay_handler(gpointer item, gpointer item2, MotionEvent const &event, DelayedSnapEvent::Origin origin)
Analyses the current event, calculates the mouse speed, turns snapping off (temporarily) if the mouse...
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
CanvasItemPtr< CanvasItemGuideLine > _active_guide
The guide being handled during a ruler event.
sigc::scoped_connection _sel_modified_connection
Inkscape::UI::Widget::Canvas * _canvas
void updateScrollbars(double scale)
void _rulerMotion(Gtk::EventControllerMotion const &controller, double x, double y, RulerOrientation orientation)
std::unique_ptr< Inkscape::UI::Widget::Ruler > _vruler
std::unique_ptr< Inkscape::UI::Widget::TabsWidget > _tabs_widget
sigc::scoped_connection _page_modified_connection
sigc::scoped_connection _update_zoom_connection
Glib::RefPtr< Gtk::Builder > _builder_display_popup
bool _ruler_ctrl_clicked
Whether ctrl was held when the ruler was clicked.
Inkscape::UI::Widget::Stack * _canvas_stack
Inkscape::UI::Widget::PopoverBin _popoverbin
void ShowRulers(bool state=true)
bool _ruler_dragged
True if a drag on the ruler is occurring.
Gtk::EventSequenceState _rulerButtonPress(Gtk::GestureClick const &gesture, int n_press, double x, double y)
sigc::scoped_connection _update_preview_connection
std::unique_ptr< Dialog::CommandPalette > _command_palette
CanvasGrid(SPDesktopWidget *dtw)
Gtk::CheckButton * GetStickyZoom()
Gtk::EventSequenceState _rulerButtonRelease(Gtk::GestureClick const &gesture, int n_press, double x, double y, RulerOrientation orientation)
sigc::scoped_connection _blink_lock_button_timeout
void size_allocate_vfunc(int width, int height, int baseline) override
void ShowScrollbars(bool state=true)
bool _ruler_clicked
True if the ruler has been clicked.
Geom::IntPoint _rulerToCanvas(bool horiz) const
sigc::scoped_connection _sel_changed_connection
std::unique_ptr< Inkscape::UI::Widget::Ruler > _hruler
void _createGuideItem(Geom::Point const &pos, bool horiz)
Glib::RefPtr< Gtk::Adjustment > _vadj
void showNotice(Glib::ustring const &msg, int timeout=0)
void _createGuide(Geom::Point origin, Geom::Point normal)
Inkscape::Util::ActionAccel _preview_accel
sigc::scoped_connection _page_selected_connection
void rulerMotion(MotionEvent const &event, bool horiz)
Glib::RefPtr< Gtk::Adjustment > _hadj
Geom::Point _normal
Normal to the guide currently being handled during ruler event.
void ShowCommandPalette(bool state=true)
Geom::IntPoint _ruler_drag_origin
Position of start of drag.
Inkscape::Util::ActionAccel _zoom_accel
void show(Glib::ustring const &msg, int timeout=0)
A widget for Inkscape's canvas.
Definition canvas.h:62
const Geom::Affine & get_affine() const
Definition canvas.h:86
Geom::IntRect get_area_world() const
Return the area shown in the canvas in world coordinates.
Definition canvas.cpp:1560
const Geom::IntPoint & get_pos() const
Definition canvas.h:85
Geom::Point canvas_to_world(Geom::Point const &window) const
Translate point in canvas to world coordinates.
Definition canvas.cpp:1552
void setChild(Gtk::Widget *child)
Definition popover-bin.h:23
void remove(Gtk::Widget &widget)
Definition stack.cpp:22
void setActive(Gtk::Widget *widget)
Definition stack.cpp:30
void add(Gtk::Widget &widget)
Definition stack.cpp:16
sigc::connection connectModified(sigc::slot< void()> const &slot)
Connects a void callback which will run whenever the keybindings for the action change.
std::vector< Glib::ustring > getShortcutText() const
Returns all keyboard shortcuts for the action in the form of text.
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:565
A GtkBox on an SPDesktop.
double get_dt2r() const
Gio::ActionMap * get_action_map()
SPDesktop * get_desktop()
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
Inkscape::CanvasItemGroup * getCanvasGuides() const
Definition desktop.h:200
SPDocument * getDocument() const
Definition desktop.h:189
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
double yaxisdir() const
Definition desktop.h:426
void set_coordinate_status(Geom::Point const &p)
Sets the coordinate status to a given point.
Definition desktop.cpp:331
Inkscape::MessageContext * guidesMessageContext() const
Definition desktop.h:290
SPDocument * doc() const
Definition desktop.h:159
bool is_yaxisdown() const
Definition desktop.h:427
void scroll_absolute(Geom::Point const &point)
Scroll canvas by to a particular point (window coordinates).
Definition desktop.cpp:854
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
SVGLength x
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
Geom::OptRect preferredBounds() const
Definition document.cpp:963
sigc::connection connectModified(ModifiedSignal::slot_type slot)
Inkscape::PageManager & getPageManager()
Definition document.h:162
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
Inkscape::Util::Quantity getHeight() const
Definition document.cpp:881
SnapManager snap_manager
SPGrid * getFirstEnabledGrid()
Returns the first grid it could find that isEnabled().
void toggleShowGuides()
void setLockGuides(bool v)
Colors::Color getGuideHiColor() const
bool getLockGuides()
void setShowGuides(bool v)
void appendChild(Inkscape::XML::Node *child)
float computed
Definition svg-length.h:50
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
CommandPalette: Class providing Command Palette feature.
RootCluster root
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
sigc::scoped_connection timeout
Css & result
Glib::ustring msg
A class to hold:
Editable view implementation.
TODO: insert short description here.
void ink_drag_setup(SPDesktopWidget *dtw, Gtk::Widget *widget)
Drag and drop of drawings onto canvas.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
auto floor(Geom::Rect const &rect)
Definition geom.h:131
Interface for locally managing a current status message.
GenericRect< IntCoord > IntRect
Definition forward.h:57
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Coord LInfty(Point const &p)
static R & release(R &r)
Decrements the reference count of a anchored object.
auto use_state(Slot &&slot)
Definition controller.h:43
bool has_flag(Gdk::ModifierType const state, Gdk::ModifierType const flags)
Helper to query if ModifierType state contains one or more of given flag(s).
Definition controller.h:25
Custom widgets.
Definition desktop.h:126
static void set_adjustment(Gtk::Adjustment *adj, double l, double u, double ps, double si, double pi)
static constexpr int height
static void ruler_snap_new_guide(SPDesktop *desktop, Geom::Point &event_dt, Geom::Point &normal)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
@ SNAPTARGET_PATH_TANGENTIAL
Definition snap-enums.h:87
@ SNAPTARGET_PATH_PERPENDICULAR
Definition snap-enums.h:86
ExtendedInput extinput_from_gdkevent(Gdk::Event const &event)
Read the extended input data from a Gdk::Event.
@ NORMAL_MESSAGE
Definition message.h:26
Inkscape::IO::Resource - simple resource API.
SPRoot: SVG <svg> implementation.
unsigned modifiers
The modifiers mask immediately before the event.
Movement of the mouse pointer.
Geom::Point pos
Location of the cursor.
virtual Node * createElement(char const *name)=0
SPDesktop * desktop
Inkscape document tabs bar.
double width