Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
tabs-widget.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2#include "tabs-widget.h"
3
4#include <cassert>
5#include <glibmm/i18n.h>
6#include <giomm/menu.h>
7#include <glibmm/main.h>
8#include <gtkmm/button.h>
9#include <gtkmm/dragicon.h>
10#include <gtkmm/dragsource.h>
11#include <gtkmm/droptarget.h>
12#include <gtkmm/eventcontrollermotion.h>
13#include <gtkmm/gestureclick.h>
14#include <gtkmm/label.h>
15#include <gtkmm/picture.h>
16#include <gtkmm/popovermenu.h>
17#include <gtkmm/snapshot.h>
18#include <gtkmm/tooltip.h>
19
20#include "desktop.h"
21#include "document.h"
23#include "inkscape-window.h"
24#include "ui/builder-utils.h"
25#include "ui/containerize.h"
26#include "ui/popup-menu.h"
27#include "ui/util.h"
28#include "ui/widget/canvas.h"
30#include "util/value-utils.h"
31
32#define BUILD(name) name{UI::get_widget<std::remove_reference_t<decltype(name)>>(builder, #name)}
33
34using namespace Inkscape::Util;
35
36namespace Inkscape::UI::Widget {
37namespace {
38
39struct TooltipUI
40{
41 TooltipUI()
42 : TooltipUI{create_builder("document-tab-preview.ui")}
43 {}
44
45 TooltipUI(Glib::RefPtr<Gtk::Builder> const &builder)
46 : BUILD(root)
47 , BUILD(name)
48 , BUILD(preview)
49 {
50 root.reference();
51
52 // Clear preview on dismissal to save memory.
53 root.signal_unmap().connect([this] {
54 preview.set_paintable({});
56 });
57 }
58
59 ~TooltipUI()
60 {
61 root.unreference();
62 }
63
64 Gtk::Box &root;
65 Gtk::Label &name;
66 Gtk::Picture &preview;
67
68 struct CurrentDisplayInfo
69 {
72 auto operator<=>(CurrentDisplayInfo const &) const = default;
73 };
74 std::optional<CurrentDisplayInfo> current_display_info;
75};
76
77struct PointerTransparentWidget : Gtk::Widget
78{
79 bool contains_vfunc(double, double) const override { return false; }
80};
81
82Gtk::PopoverMenu to_popovermenu(Glib::RefPtr<Gio::Menu> const &menu)
83{
84 auto popovermenu = Gtk::PopoverMenu{menu};
85 popovermenu.set_has_arrow(false);
86 popovermenu.set_position(Gtk::PositionType::BOTTOM);
87 popovermenu.set_halign(Gtk::Align::START);
88 return popovermenu;
89}
90
91Gtk::PopoverMenu create_context_menu()
92{
93 auto menu = Gio::Menu::create();
94 menu->append_item(Gio::MenuItem::create(_("Detach to New Window"), "tabs.detach"));
95 menu->append_item(Gio::MenuItem::create(_("Close File"), "tabs.close"));
96 return to_popovermenu(menu);
97}
98
99Gtk::PopoverMenu &get_context_menu()
100{
101 static auto menu = create_context_menu();
102 return menu;
103}
104
105Gtk::PopoverMenu create_context_menu_background()
106{
107 auto menu = Gio::Menu::create();
108 auto sec1 = Gio::Menu::create();
109 sec1->append_item(Gio::MenuItem::create(_("Close"), "win.document-close"));
110 menu->append_section(sec1);
111 auto sec2 = Gio::Menu::create();
112 sec2->append_item(Gio::MenuItem::create(_("New Document..."), "win.document-new"));
113 sec2->append_item(Gio::MenuItem::create(_("Open Document..."), "win.document-open"));
114 menu->append_section(sec2);
115 return to_popovermenu(menu);
116}
117
118Gtk::PopoverMenu &get_context_menu_background()
119{
120 static auto menu = create_context_menu_background();
121 return menu;
122}
123
124std::optional<Geom::Point> get_current_pointer_pos(Glib::RefPtr<Gdk::Device> const &pointer, Gtk::Widget &widget)
125{
126 double x, y;
127 Gdk::ModifierType mask;
128 auto root = widget.get_root();
129 dynamic_cast<Gtk::Native &>(*root).get_surface()->get_device_position(pointer, x, y, mask);
130 dynamic_cast<Gtk::Widget &>(*root).translate_coordinates(widget, x, y, x, y);
131 return Geom::Point{x, y};
132}
133
135Glib::ustring get_title(SPDesktop *desktop)
136{
137 auto result = Glib::ustring{};
138
139 auto const doc = desktop->doc();
140
141 if (doc->isModifiedSinceSave()) {
142 result += "*";
143 }
144
145 result += doc->getDocumentName();
146
147 if (auto const v = desktop->viewNumber(); v > 1) {
148 result += ": ";
149 result += Glib::ustring::format(v);
150 }
151
152 return result;
153}
154
155} // namespace
156
159class TabsWidget::Instances
160{
161public:
162 static Instances &get()
163 {
164 static Instances instance;
165 return instance;
166 }
167
168 void add(TabsWidget *w)
169 {
170 _instances.push_back(w);
171 if (_instances.size() > 1) {
172 _updateVisibilityAll();
173 }
174 }
175
176 void remove(TabsWidget *w)
177 {
178 _instances.erase(std::find(_instances.begin(), _instances.end(), w));
179 if (_instances.size() <= 1) {
180 _updateVisibilityAll();
181 }
182 }
183
184 bool forceVisible() const { return _instances.size() > 1; }
185
186 void addHighlight()
187 {
188 for (auto w : _instances) {
189 w->add_css_class("drop-highlight");
190 }
191 }
192
193 void removeHighlight()
194 {
195 for (auto w : _instances) {
196 w->remove_css_class("drop-highlight");
197 }
198 }
199
200private:
201 Instances() = default;
202
203 void _updateVisibilityAll()
204 {
205 for (auto w : _instances) {
206 w->_updateVisibility();
207 }
208 }
209
210 std::vector<TabsWidget *> _instances;
211};
212
214struct DumbTab : Gtk::Box
215{
216 Gtk::Label &name;
217 Gtk::Button &close;
218
219 DumbTab()
220 : DumbTab{create_builder("document-tab.ui")}
221 {}
222
223 DumbTab(Glib::RefPtr<Gtk::Builder> const &builder)
224 : BUILD(name)
225 , BUILD(close)
226 {
227 set_name("DocumentTab");
228 append(get_widget<Gtk::Box>(builder, "root"));
229 }
230
231 void set_active()
232 {
233 get_style_context()->add_class("tab_active");
234 }
235
236 void set_inactive()
237 {
238 get_style_context()->remove_class("tab_active");
239 }
240};
241
243struct Tab : DumbTab
244{
245 SPDesktop *const desktop;
246 TabsWidget *const parent;
247
248 Tab(SPDesktop *desktop, TabsWidget *parent)
250 , parent{parent}
251 {
252 set_has_tooltip(true);
253 }
254};
255
256class TabDrag
257{
258public:
262 TabDrag(Tab *src, Geom::Point const &offset, Glib::RefPtr<Gdk::Device> device)
263 : _src{src}
264 , _offset{offset}
265 , _device{std::move(device)}
266 , _dst{src->parent}
267 {}
268
271 void motion(std::optional<Geom::Point> pos)
272 {
273 constexpr int detach_dist = 50;
274 if (pos && Geom::Rect(0, 0, _dst->get_width(), _dst->get_height()).distanceSq(*pos) < Geom::sqr(detach_dist)) {
275 // Pointer is still sufficiently near dst - update drop position.
276 _drop_x = pos->x() - static_cast<int>(std::round(_offset.x()));
277 _dst->queue_allocate();
278 } else {
279 // Pointer is too far away from dst - detach from it.
280 cancelTick();
281 _ensureDrag();
282 setDst(nullptr);
283 }
284 }
285
289 void addTick()
290 {
291 if (!_tick_callback) {
292 _tick_callback = _dst->add_tick_callback([this] (auto&&) {
293 motion(get_current_pointer_pos(_device, *_dst));
294 return true;
295 });
296 }
297 }
298
299 void cancelTick()
300 {
301 if (_tick_callback) {
302 _dst->remove_tick_callback(_tick_callback);
303 _tick_callback = 0;
304 }
305 }
306
308 void setDst(TabsWidget *new_dst)
309 {
310 if (new_dst == _dst) {
311 return;
312 }
313
314 if (_dst) {
315 _dst->_drag_dst = {};
316 _dst->queue_resize();
317 }
318
319 _dst = new_dst;
320
321 if (_dst) {
322 _dst->_drag_dst = _src->parent->_drag_src;
323 _drop_x = {};
324 _drop_i = {};
325 }
326
327 _queueReparent();
328 }
329
333 void finish(bool cancel = false)
334 {
335 // Cancel the tick callback if one is being used for motion polling.
336 cancelTick();
337
338 // Detach from source and destination, keeping self alive until end of function.
339 auto const self_ref = std::move(_src->parent->_drag_src);
340 assert(self_ref.get() == this);
341 if (_dst) {
342 _dst->_drag_dst = {};
343 }
344
345 // Undo widget modifications to source and destination.
346 _src->set_visible(true);
347 _src->parent->queue_resize();
348 if (_dst) {
349 if (_widget && _widget->get_parent() == _dst) {
350 _widget->unparent();
351 }
352 _dst->queue_resize();
353 }
354 TabsWidget::Instances::get().removeHighlight();
355
356 if (!_dst && _src->parent->_tabs.size() == 1) {
357 cancel = true; // cancel if detaching lone tab
358 }
359
360 if (cancel) {
361 _src->parent->_desktop_widget->get_window()->present();
362 return;
363 }
364
365 if (_drag) {
366 _drag->drag_drop_done(true); // suppress drag-failed animation
367 }
368
369 if (!_dst) {
370 // Detach
372 } else if (_dst == _src->parent) {
373 // Reorder
374 if (_drop_i) {
375 int const from = _src->parent->positionOfTab(_src->desktop);
376 _src->parent->_reorderTab(from, *_drop_i);
377 }
378 } else {
379 // Migrate
380 if (_drop_i) {
381 auto const desktop = _src->desktop;
382 _src->desktop->getDesktopWidget()->removeDesktop(desktop); // deletes src
383 _dst->_desktop_widget->addDesktop(desktop, *_drop_i);
384 }
385 }
386 }
387
388 Tab *src() const { return _src; }
389 DumbTab *widget() const { return _widget.get(); }
390 std::optional<int> const &dropX() const { return _drop_x; }
391 void setDropI(int i) { _drop_i = i; }
392
393private:
394 Tab *const _src; // The source tab.
395 Geom::Point const _offset; // The point within the tab that the drag started from.
396 Glib::RefPtr<Gdk::Device> const _device; // The pointing device that started the drag.
397
398 TabsWidget *_dst; // The destination tabs widget, possibly null.
399 std::optional<int> _drop_x; // Position within dst where tab is dropped.
400 std::optional<int> _drop_i; // The index within dst where the tab is dropped.
401
402 sigc::scoped_connection _reparent_conn; // Used to defer reparenting of widget.
403 sigc::scoped_connection _cancel_conn; // Connection to the Gdk::Drag's cancel signal.
404 sigc::scoped_connection _drop_conn; // Connection to the Gdk::Drag's drop-perfomed signal.
405 Glib::RefPtr<Gdk::Drag> _drag; // The Gdk::Drag, lazily created at the same time as widget.
406 std::unique_ptr<DumbTab> _widget; // The dummy tab that is placed inside the drag icon or dst.
407 guint _tick_callback = 0; // If nonzero, the tick callback used for motion polling.
408
409 void _ensureDrag()
410 {
411 if (_drag) {
412 return;
413 }
414
415 // Create the Gdk drag.
416 assert(_src->parent->_drag_src.get() == this);
417 auto content = Gdk::ContentProvider::create(GlibValue::create<std::weak_ptr<TabDrag>>(_src->parent->_drag_src));
418 _drag = _src->parent->get_native()->get_surface()->drag_begin_from_point(_device, content, Gdk::DragAction::MOVE, _offset.x(), _offset.y());
419
420 // Handle drag cancellation.
421 _cancel_conn = _drag->signal_cancel().connect([this] (auto reason) {
422 finish(reason == Gdk::DragCancelReason::USER_CANCELLED);
423 }, false);
424
425 // Some buggy clients accept the drop when they shouldn't. We interpret it as a drop on nothing.
426 _drop_conn = _drag->signal_drop_performed().connect([this] {
427 finish();
428 }, false);
429
430 // Hide the real tab.
431 _src->set_visible(false);
432
433 // Create a visual replica of the tab.
434 _widget = std::make_unique<DumbTab>();
435 _widget->name.set_text(get_title(_src->desktop));
436 _widget->set_active();
437 }
438
439 // Schedule widget to be reparented. GTK is picky about when this can be done, hence the need for an idle callback.
440 // In particular, widget hierarchy changes aren't allowed in a tick callback or an enter/leave handler.
441 void _queueReparent()
442 {
443 if (!_reparent_conn) {
444 _reparent_conn = Glib::signal_idle().connect([this] { _reparentWidget(); return false; }, Glib::PRIORITY_HIGH);
445 }
446 }
447
448 void _reparentWidget()
449 {
450 auto drag_icon = Gtk::DragIcon::get_for_drag(_drag);
451
452 if (_widget.get() == drag_icon->get_child()) {
453 drag_icon->unset_child();
454 // Fixme: Shouldn't be needed, but works around https://gitlab.gnome.org/GNOME/gtk/-/issues/7185
455 Gtk::DragIcon::set_from_paintable(_drag, to_texture(Cairo::ImageSurface::create(Cairo::ImageSurface::Format::ARGB32, 1, 1)), 0, 0);
456 } else if (_widget->get_parent()) {
457 assert(dynamic_cast<TabsWidget *>(_widget->get_parent()));
458 _widget->unparent();
459 }
460
461 // Put the replica tab inside dst or the drag icon.
462 if (_dst) {
463 _widget->insert_before(*_dst, *_dst->_overlay);
464 _dst->queue_resize();
465 TabsWidget::Instances::get().removeHighlight();
466 } else {
467 drag_icon->set_child(*_widget);
468 _drag->set_hotspot(_offset.x(), _offset.y());
469 TabsWidget::Instances::get().addHighlight();
470 }
471 }
472};
473
474static std::shared_ptr<TabDrag> get_tab_drag(Gtk::DropTarget &droptarget)
475{
476 // Determine whether an in-app tab is being dragged and get information about it.
477 auto const drag = droptarget.get_drop()->get_drag();
478 if (!drag) {
479 return {}; // not in-app
480 }
481 auto const content = GlibValue::from_content_provider<std::weak_ptr<TabDrag>>(*drag->get_content());
482 if (!content) {
483 return {}; // not a tab
484 }
485 return content->lock();
486}
487
488// This is used to ensure that a Tab never outlives its parent TabsWidget,
489// which would result in Tab::parent dangling.
490static SPDesktop *consume_tab_return_desktop(std::shared_ptr<Tab> tab)
491{
492 return tab ? tab->desktop : nullptr;
493}
494
496 : _desktop_widget{desktop_widget}
497 , _overlay{Gtk::make_managed<PointerTransparentWidget>()}
498{
499 set_name("DocumentTabsWidget");
500 set_overflow(Gtk::Overflow::HIDDEN);
501 containerize(*this);
502
503 _overlay->insert_at_end(*this); // always kept topmost
504 _overlay->set_name("Overlay");
505
506 auto click = Gtk::GestureClick::create();
507 click->set_button(0);
508 click->signal_pressed().connect([this, click = click.get()] (int, double x, double y) {
509 // Find clicked tab.
510 auto const [tab_weak, tab_pos] = _tabAtPoint({x, y});
511 auto tab = tab_weak.lock();
512
513 // Handle button actions.
514 switch (click->get_current_button()) {
515 case GDK_BUTTON_PRIMARY:
516 if (tab) {
517 double xc, yc;
518 translate_coordinates(tab->close, x, y, xc, yc);
519 if (!tab->close.contains(xc, yc)) {
520 _desktop_widget->switchDesktop(tab->desktop);
521 _left_clicked = tab_weak;
522 _left_click_pos = {x, y};
523 }
524 }
525 break;
526 case GDK_BUTTON_SECONDARY: {
527 auto &menu = tab ? get_context_menu() : get_context_menu_background();
528 auto old_parent = dynamic_cast<TabsWidget *>(menu.get_parent());
529 if (old_parent != this) {
530 if (old_parent) {
531 menu.unparent();
532 old_parent->_popover = nullptr;
533 }
534 menu.set_parent(*this);
535 _popover = &menu;
536 }
537 UI::popup_at(menu, *this, x, y);
538 _right_clicked = tab_weak;
539 break;
540 }
541 case GDK_BUTTON_MIDDLE:
542 if (tab) {
544 }
545 break;
546 default:
547 break;
548 }
549 });
550 click->signal_released().connect([this] (auto&&...) {
551 _left_clicked = {};
552 if (_drag_src) {
553 _drag_src->finish();
554 }
555 });
556 add_controller(click);
557
558 auto motion = Gtk::EventControllerMotion::create();
559 motion->signal_motion().connect([this, &motion = *motion] (double x, double y) {
560 if (!_drag_src) {
561 auto const tab = _left_clicked.lock();
562 if (!tab) {
563 return;
564 }
565
566 constexpr int drag_initiate_dist = 5;
567 if ((Geom::Point{x, y} - _left_click_pos).lengthSq() < Geom::sqr(drag_initiate_dist)) {
568 return;
569 }
570
571 _left_clicked = {};
572
574 translate_coordinates(*tab, _left_click_pos.x(), _left_click_pos.y(), offset.x(), offset.y());
575
576 // Start dragging.
577 _drag_src = _drag_dst = std::make_shared<TabDrag>(
578 tab.get(),
579 offset,
580 motion.get_current_event_device()
581 );
582
583 // Raise dragged tab to top.
584 tab->insert_before(*this, *_overlay);
585 }
586
587 if (!_drag_src->widget()) {
588 _drag_src->motion(Geom::Point{x, y});
589 }
590 });
591 add_controller(motion);
592
593 auto droptarget = Gtk::DropTarget::create(GlibValue::type<std::weak_ptr<TabDrag>>(), Gdk::DragAction::MOVE);
594 auto handler = [this, &droptarget = *droptarget] (double x, double y) -> Gdk::DragAction {
595 if (auto tabdrag = get_tab_drag(droptarget)) {
596 tabdrag->cancelTick();
597 tabdrag->setDst(this);
598 tabdrag->motion(Geom::Point{x, y});
599 }
600 return {};
601 };
602 droptarget->signal_enter().connect(handler, false);
603 droptarget->signal_motion().connect(handler, false);
604 droptarget->signal_leave().connect([this] {
605 if (_drag_dst) {
606 _drag_dst->addTick();
607 }
608 });
609 add_controller(droptarget);
610
611 auto actiongroup = Gio::SimpleActionGroup::create();
612 actiongroup->add_action("detach", [this] {
613 if (auto desktop = consume_tab_return_desktop(_right_clicked.lock())) {
614 InkscapeApplication::instance()->detachDesktopToNewWindow(desktop);
615 }
616 });
617 actiongroup->add_action("close", [this] {
618 if (auto desktop = consume_tab_return_desktop(_right_clicked.lock())) {
619 InkscapeApplication::instance()->destroyDesktop(desktop);
620 }
621 });
622 insert_action_group("tabs", actiongroup);
623
624 Instances::get().add(this);
625 _updateVisibility();
626}
627
628TabsWidget::~TabsWidget()
629{
630 Instances::get().remove(this);
631
632 // Note: This code will fail if TabsWidget becomes a managed widget, in which
633 // case it must be done on signal_destroy() instead.
634 if (_drag_dst) {
635 _drag_dst->setDst(nullptr);
636 }
637 if (_drag_src) {
638 _drag_src->finish(true);
639 }
640}
641
642void TabsWidget::addTab(SPDesktop *desktop, int pos)
643{
644 auto tab = std::make_shared<Tab>(desktop, this);
645 tab->name.set_text(get_title(desktop));
646
647 tab->close.signal_clicked().connect([this, desktop] { InkscapeApplication::instance()->destroyDesktop(desktop); });
648
649 tab->signal_query_tooltip().connect([this, desktop] (int, int, bool, Glib::RefPtr<Gtk::Tooltip> const &tooltip) {
650 _setTooltip(desktop, tooltip);
651 return true; // show the tooltip
652 }, true);
653
654 assert(positionOfTab(desktop) == -1);
655
656 if (pos == -1) {
657 pos = _tabs.size();
658 }
659 assert(0 <= pos && pos <= _tabs.size());
660
661 tab->insert_before(*this, *_overlay);
662 _tabs.insert(_tabs.begin() + pos, std::move(tab));
663
664 _updateVisibility();
665}
666
667void TabsWidget::removeTab(SPDesktop *desktop)
668{
669 int const i = positionOfTab(desktop);
670 assert(i != -1);
671
672 if (_drag_src && _drag_src->src() == _tabs[i].get()) {
673 _drag_src->finish(true);
674 }
675
676 _tabs[i]->unparent();
677 _tabs.erase(_tabs.begin() + i);
678
679 _updateVisibility();
680}
681
682void TabsWidget::switchTab(SPDesktop *desktop)
683{
684 auto const active = _active.lock();
685
686 if (active && active->desktop == desktop) {
687 return;
688 }
689
690 if (active) {
691 active->set_inactive();
692 _active = {};
693 }
694
695 int const i = positionOfTab(desktop);
696 if (i != -1) {
697 _tabs[i]->set_active();
698 _active = _tabs[i];
699 }
700}
701
702void TabsWidget::refreshTitle(SPDesktop *desktop)
703{
704 int const i = positionOfTab(desktop);
705 assert(i != -1);
706 _tabs[i]->name.set_text(get_title(_tabs[i]->desktop));
707}
708
709int TabsWidget::positionOfTab(SPDesktop *desktop) const
710{
711 for (int i = 0; i < _tabs.size(); i++) {
712 if (_tabs[i]->desktop == desktop) {
713 return i;
714 }
715 }
716 return -1;
717}
718
719SPDesktop *TabsWidget::tabAtPosition(int i) const
720{
721 return _tabs[i]->desktop;
722}
723
724void TabsWidget::_updateVisibility()
725{
726 set_visible(_tabs.size() > 1 || Instances::get().forceVisible());
727}
728
729Gtk::SizeRequestMode TabsWidget::get_request_mode_vfunc() const
730{
731 return Gtk::SizeRequestMode::CONSTANT_SIZE;
732}
733
734void TabsWidget::measure_vfunc(Gtk::Orientation orientation, int, int &min, int &nat, int &, int &) const
735{
736 if (orientation == Gtk::Orientation::VERTICAL) {
737 min = 0;
738 auto consider = [&] (Gtk::Widget const &w) {
739 auto const m = w.measure(Gtk::Orientation::VERTICAL, -1);
740 min = std::max(min, m.sizes.minimum);
741 };
742 for (auto const &tab : _tabs) {
743 consider(*tab);
744 }
745 if (_drag_src) {
746 if (auto widget = _drag_src->widget()) {
747 consider(*widget);
748 }
749 }
750 if (_drag_dst) {
751 if (auto widget = _drag_dst->widget()) {
752 consider(*widget);
753 }
754 }
755 } else {
756 min = 0;
757 }
758 nat = min;
759}
760
761void TabsWidget::size_allocate_vfunc(int width, int height, int)
762{
763 struct Drop
764 {
765 int x;
766 int w;
767 DumbTab *widget;
768 bool done = false;
769 };
770
771 std::optional<Drop> drop;
772 if (_drag_dst && _drag_dst->dropX()) {
773 auto widget = !_drag_dst->widget()
774 ? _drag_dst->src()
775 : _drag_dst->widget();
776 if (widget->get_parent() == this) {
777 drop = Drop{
778 .x = *_drag_dst->dropX(),
779 .w = widget->measure(Gtk::Orientation::HORIZONTAL, -1).sizes.natural,
780 .widget = widget,
781 };
782 }
783 }
784
785 int x = 0;
786 for (int i = 0; i < _tabs.size(); i++) {
787 auto const tab = _tabs[i].get();
788 if (_drag_src && tab == _drag_src->src()) {
789 continue;
790 }
791 auto const w = tab->measure(Gtk::Orientation::HORIZONTAL, -1).sizes.natural;
792 if (drop && !drop->done && x + w / 2 > drop->x) {
793 x += drop->w;
794 _drag_dst->setDropI(i);
795 drop->done = true;
796 }
797 tab->size_allocate(Gtk::Allocation(x, 0, w, height), -1);
798 x += w;
799 }
800
801 _overlay->size_allocate(Gtk::Allocation(0, 0, width, height), -1);
802
803 // GTK burdens custom widgets with having to implement this manually.
804 if (_popover) {
805 _popover->present();
806 }
807
808 if (drop) {
809 if (!drop->done) {
810 _drag_dst->setDropI(_tabs.size());
811 }
812 drop->widget->size_allocate(Gtk::Allocation(drop->x, 0, drop->w, height), -1);
813 }
814}
815
816void TabsWidget::_setTooltip(SPDesktop *desktop, Glib::RefPtr<Gtk::Tooltip> const &tooltip)
817{
818 // Lazy-load tooltip ui file, shared among all instances.
819 static auto const tooltip_ui = std::make_unique<TooltipUI>();
820
821 auto const active = _active.lock();
822 bool const is_active_tab = active && desktop == active->desktop;
823 auto const display_info = TooltipUI::CurrentDisplayInfo{
824 .desktop = desktop,
825 .is_active_tab = is_active_tab
826 };
827
828 if (tooltip_ui->current_display_info != display_info) { // avoid pointless updates
829 tooltip_ui->current_display_info = display_info;
830
831 // Set name.
832 tooltip_ui->name.set_label(desktop->doc()->getDocumentName());
833
834 // Set preview.
835 if (is_active_tab) {
836 tooltip_ui->preview.set_paintable({}); // no preview for active tab
837 } else {
838 constexpr double scale = 0.2;
839 auto snapshot = Gtk::Snapshot::create();
840 snapshot->scale(scale, scale);
841 desktop->getCanvas()->snapshot_vfunc(snapshot);
842 tooltip_ui->preview.set_paintable(snapshot->to_paintable());
843 }
844 }
845
846 // Embed custom widget into tooltip.
847 tooltip->set_custom(tooltip_ui->root);
848}
849
850std::pair<std::weak_ptr<Tab>, Geom::Point> TabsWidget::_tabAtPoint(Geom::Point const &pos)
851{
852 double xt, yt;
853 auto const it = std::find_if(_tabs.begin(), _tabs.end(), [&] (auto const &tab) {
854 translate_coordinates(*tab, pos.x(), pos.y(), xt, yt);
855 return tab->contains(xt, yt);
856 });
857 if (it == _tabs.end()) {
858 return {};
859 }
860 return {*it, {xt, yt}};
861}
862
863void TabsWidget::_reorderTab(int from, int to)
864{
865 assert(0 <= from && from < _tabs.size());
866 assert(0 <= to && to <= _tabs.size());
867
868 if (from == to) {
869 return;
870 }
871
872 auto tab = std::move(_tabs[from]);
873 _tabs.erase(_tabs.begin() + from);
874 _tabs.insert(_tabs.begin() + to - (to > from), std::move(tab));
875}
876
877} // namespace Inkscape::UI::Widget
double scale
Definition aa.cpp:228
Gtk builder utilities.
Inkscape canvas widget.
C distanceSq(CPoint const &p) const
Get rectangle's distance SQUARED away from the given point.
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
bool destroyDesktop(SPDesktop *desktop, bool keep_alive=false)
Destroy a window and close the document it contains.
static InkscapeApplication * instance()
Singleton instance.
void detachDesktopToNewWindow(SPDesktop *desktop)
void snapshot_vfunc(Glib::RefPtr< Gtk::Snapshot > const &snapshot) override
Widget that implements the document tab bar.
Definition tabs-widget.h:36
TabsWidget(SPDesktopWidget *desktop_widget)
std::weak_ptr< Tab > _right_clicked
Definition tabs-widget.h:56
std::weak_ptr< Tab > _left_clicked
Definition tabs-widget.h:57
SPDesktopWidget *const _desktop_widget
Definition tabs-widget.h:50
A GtkBox on an SPDesktop.
void removeDesktop(SPDesktop *desktop)
void switchDesktop(SPDesktop *desktop)
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
SPDesktopWidget * getDesktopWidget() const
Definition desktop.h:192
SPDocument * doc() const
Definition desktop.h:159
int viewNumber() const
Definition desktop.h:194
char const * getDocumentName() const
basename or other human-readable label for the document.
Definition document.h:238
const double w
Definition conic-4.cpp:19
RootCluster root
Css & result
A class to hold:
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
Inkscape - An SVG editor.
double offset
size_t v
T sqr(const T &x)
Definition math-utils.h:57
Definition desktop.h:50
Glib::ustring get_title(const SPObject &object)
Custom widgets.
Definition desktop.h:126
static SPDesktop * consume_tab_return_desktop(std::shared_ptr< Tab > tab)
static std::shared_ptr< TabWidgetDrag > get_tab_drag(Gtk::DropTarget &droptarget)
void containerize(Gtk::Widget &widget)
Make a custom widget implement sensible memory management for its children.
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
GType type()
Returns the type used for storing an object of type T inside a value.
Definition value-utils.h:29
Glib::ValueBase create(Args &&... args)
Return a value containing and owning a newly-created T instance.
T * get(GValue *value)
Returns a borrowed pointer to the T held by a value if it holds one, else nullptr.
Definition value-utils.h:64
Miscellaneous supporting code.
Definition document.h:95
static void append(std::vector< T > &target, std::vector< T > &&source)
STL namespace.
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
Ocnode * parent
Definition quantize.cpp:31
void remove(std::vector< T > &vec, T const &val)
Definition sanitize.cpp:94
bool is_active_tab
std::optional< CurrentDisplayInfo > current_display_info
Gtk::Picture & preview
SPDesktop * desktop
Inkscape document tabs bar.
double height
double width
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
Glib::RefPtr< Gdk::Texture > to_texture(Cairo::RefPtr< Cairo::Surface > const &surface)
Convert an image surface in ARGB32 format to a texture.
Definition util.cpp:495
Wrapper for the GLib value API.