20#include <glibmm/i18n.h>
21#include <glibmm/value.h>
22#include <gdkmm/contentprovider.h>
23#include <gtkmm/button.h>
24#include <gtkmm/menubutton.h>
25#include <gtkmm/gestureclick.h>
26#include <gtkmm/separator.h>
27#include <gtkmm/eventcontrollerscroll.h>
28#include <gtkmm/grid.h>
56 if (!tabs)
return nullptr;
71 set_name(
"DialogNotebook");
72 set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::NEVER);
80 _label_pref = prefs->createObserver(
"/options/notebooklabels/value", [=,
this](
const auto& entry) {
81 auto status = entry.getInt();
93 _tabclose_pref = prefs->createObserver(
"/options/notebooktabs/show-closebutton", [
this](
const auto& entry) {
99 _notebook.set_name(
"DockedDialogNotebook");
101 _notebook.set_group_name(
"InkscapeDialogGroup");
105 auto box =
dynamic_cast<Gtk::Box*
>(
_notebook.get_first_child());
107 auto scroll_controller = Gtk::EventControllerScroll::create();
108 scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL | Gtk::EventControllerScroll::Flags::DISCRETE);
109 box->add_controller(scroll_controller);
119 auto const menubtn = Gtk::make_managed<Gtk::MenuButton>();
120 menubtn->set_icon_name(
"go-down-symbolic");
121 menubtn->set_has_frame(
false);
123 menubtn->set_visible(
true);
124 menubtn->set_valign(Gtk::Align::CENTER);
125 menubtn->set_halign(Gtk::Align::CENTER);
126 menubtn->set_focusable(
false);
127 menubtn->set_can_focus(
false);
128 menubtn->set_focus_on_click(
false);
129 menubtn->set_name(
"DialogMenuButton");
142 auto curr_page =
_notebook.get_nth_page(page_pos);
143 if (
auto dialog =
dynamic_cast<DialogBase*
>(curr_page)) {
144 dialog->focus_dialog();
160 _tabs.
signal_move_tab().connect([
this](
auto& tab,
int src_position,
auto& source,
int dest_position) {
163 if (auto page = notebook->get_page(src_position)) {
164 move_tab_from(*notebook, *page, dest_position);
168 _tabs.signal_tab_rearranged().connect([
this](
int from,
int to) {
169 if (
auto page = _notebook.get_nth_page(from)) {
170 _notebook.reorder_child(*page, to);
173 _tabs.signal_dnd_begin().connect([
this] {
175 for (
auto instance : _instances) {
176 instance->add_highlight_header();
179 _tabs.signal_dnd_end().connect([
this](
bool) {
182 for (
auto instance : _instances) {
183 instance->remove_highlight_header();
189 auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
191 hbox->append(*menubtn);
192 _content.append(*hbox);
193 auto sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
194 sep->set_size_request(-1, 1);
195 _content.append(*sep);
196 _content.append(_notebook);
199 _instances.push_back(
this);
203 const auto icon_size = Gtk::IconSize::NORMAL;
206 auto grid = Gtk::make_managed<Gtk::Grid>();
207 grid->set_name(
"MenuDockingRect");
208 auto make_item = [icon_size](
const char* icon,
const char* tooltip) {
209 auto item = Gtk::make_managed<UI::Widget::PopoverMenuItem>(
"",
true, icon, icon_size);
210 item->set_tooltip_text(_(tooltip));
213 auto dock_lt = make_item(
"dock-left-top",
"Dock current tab at the top left");
214 _conn.emplace_back(dock_lt->signal_activate().connect([
this]{ dock_current_tab(DialogContainer::TopLeft); }));
215 auto dock_rt = make_item(
"dock-right-top",
"Dock current tab at the top right");
216 _conn.emplace_back(dock_rt->signal_activate().connect([
this]{ dock_current_tab(DialogContainer::TopRight); }));
217 auto dock_lb = make_item(
"dock-left-bottom",
"Dock current tab at the bottom left");
218 _conn.emplace_back(dock_lb->signal_activate().connect([
this]{ dock_current_tab(DialogContainer::BottomLeft); }));
219 auto dock_rb = make_item(
"dock-right-bottom",
"Dock current tab at the bottom right");
220 _conn.emplace_back(dock_rb->signal_activate().connect([
this]{ dock_current_tab(DialogContainer::BottomRight); }));
222 auto floating = make_item(
"floating-dialog",
"Move current tab to new window");
223 floating->set_valign(Gtk::Align::CENTER);
224 _conn.emplace_back(floating->signal_activate().connect([
this]{ pop_tab(nullptr); }));
225 grid->attach(*dock_lt, 0, 0);
226 grid->attach(*dock_lb, 0, 1);
227 grid->attach(*floating, 1, 0, 1, 2);
228 grid->attach(*dock_rt, 2, 0);
229 grid->attach(*dock_rb, 2, 1);
231 menu.
attach(*grid, 0, 1, row, row + 1);
233 auto sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
234 sep->set_size_request(-1, 1);
235 menu.
attach(*sep, 0, 1, row, row + 1);
238 auto new_menu_item = Gtk::make_managed<UI::Widget::PopoverMenuItem>(_(
"Close Tab"));
239 _conn.emplace_back(new_menu_item->signal_activate().connect([
this]{ close_tab(nullptr); }));
240 menu.
attach(*new_menu_item, 0, 1, row, row + 1);
243 new_menu_item = Gtk::make_managed<UI::Widget::PopoverMenuItem>(_(
"Close Panel"));
244 _conn.emplace_back(new_menu_item->signal_activate().connect([
this]{ close_notebook(); }));
245 menu.
attach(*new_menu_item, 0, 1, row, row + 1);
248 menu.add_css_class(
"symbolic");
255 const auto icon_size = Gtk::IconSize::NORMAL;
258 for (
auto const &
data : dialog_data) {
262 auto callback = [
this,
key =
data.key]{
264 if (
auto desktop = SP_ACTIVE_DESKTOP) {
268 bool floating = DialogManager::singleton().should_open_floating(
key);
269 container->new_dialog(
key, container == _container && !floating ?
this :
nullptr);
273 builder.add_item(
data.label,
data.category, {},
data.icon_name,
true,
false, std::move(callback));
280 menu.add_css_class(
"symbolic");
284DialogNotebook::~DialogNotebook()
291 for (
int i = _notebook.get_n_pages(); i >= 0; --i) {
293 _container->unlink_dialog(dialog);
294 _notebook.remove_page(i);
297 _instances.remove(
this);
300void DialogNotebook::add_highlight_header()
302 _notebook.add_css_class(
"nb-highlight");
305void DialogNotebook::remove_highlight_header()
307 _notebook.remove_css_class(
"nb-highlight");
313bool DialogNotebook::provide_scroll(Gtk::Widget &
page) {
317 auto data = dialog_data.find(dialogbase->get_type());
325Gtk::ScrolledWindow* DialogNotebook::get_scrolledwindow(Gtk::Widget &
page)
328 if (
auto const scrolledwindow =
dynamic_cast<Gtk::ScrolledWindow *
>(children[0])) {
329 return scrolledwindow;
338Gtk::ScrolledWindow* DialogNotebook::get_current_scrolledwindow(
bool const skip_scroll_provider)
340 auto const pagenum = _notebook.get_current_page();
341 if (
auto const page = _notebook.get_nth_page(pagenum)) {
342 if (skip_scroll_provider && provide_scroll(*
page)) {
345 return get_scrolledwindow(*
page);
353void DialogNotebook::add_page(Gtk::Widget &
page) {
358 if (
auto const box =
dynamic_cast<Gtk::Box *
>(&
page)) {
359 auto const wrapper = Gtk::make_managed<Gtk::ScrolledWindow>();
360 wrapper->set_vexpand(
true);
361 wrapper->set_propagate_natural_height(
true);
362 wrapper->set_overlay_scrolling(
false);
363 wrapper->get_style_context()->add_class(
"noborder");
365 auto const wrapperbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL,0);
366 wrapperbox->set_vexpand(
true);
372 wrapperbox->append(
child);
377 wrapper->set_child(*wrapperbox);
378 box ->append(*wrapper);
380 if (provide_scroll(
page)) {
381 wrapper->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::EXTERNAL);
383 wrapper->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
387 add_notebook_page(
page, -1);
390void DialogNotebook::add_notebook_page(Gtk::Widget&
page,
int position) {
391 int page_number = _notebook.insert_page(
page, position);
392 _notebook.set_tab_reorderable(
page);
393 _notebook.set_tab_detachable(
page);
394 _notebook.set_current_page(page_number);
400void DialogNotebook::move_page(Gtk::Widget &
page)
403 auto old_notebook = get_page_notebook(
page);
405 std::cerr <<
"DialogNotebook::move_page: page not in notebook!" << std::endl;
408 if (old_notebook == &_notebook) {
415 old_notebook->detach_tab(
page);
416 add_notebook_page(
page, -1);
421 _notebook.set_tab_reorderable(
page);
422 _notebook.set_tab_detachable(
page);
425void DialogNotebook::select_page(Gtk::Widget&
page) {
426 auto pos = _notebook.page_num(
page);
427 _notebook.set_current_page(pos);
435void DialogNotebook::close_tab(Gtk::Widget*
page) {
436 int page_number = _notebook.get_current_page();
439 page_number = _notebook.page_num(*
page);
442 if (
dynamic_cast<DialogBase*
>(_notebook.get_nth_page(page_number))) {
444 if (
auto window =
dynamic_cast<DialogWindow*
>(_container->get_root())) {
446 DialogManager::singleton().store_state(*window);
451 _notebook.remove_page(page_number);
453 if (_notebook.get_n_pages() == 0) {
459 on_size_allocate_scroll(get_width());
465void DialogNotebook::close_notebook()
470 multipaned->
remove(*
this);
471 }
else if (get_parent()) {
472 std::cerr <<
"DialogNotebook::close_notebook: Unexpected parent!" << std::endl;
482 old_notebook.detach_tab(
page);
483 add_notebook_page(
page, position);
488 _notebook.set_tab_reorderable(
page);
489 _notebook.set_tab_detachable(
page);
491 if (old_notebook.get_n_pages() == 0) {
496Gtk::Widget* DialogNotebook::get_page(
int position) {
497 return _notebook.get_nth_page(position);
500Gtk::Notebook* DialogNotebook::get_page_notebook(Gtk::Widget&
page) {
502 auto notebook =
dynamic_cast<Gtk::Notebook*
>(
parent);
503 if (!notebook &&
parent) {
505 notebook =
dynamic_cast<Gtk::Notebook*
>(
parent->get_parent());
514 window->set_visible(
true);
516 if (_notebook.get_n_pages() == 0) {
522 on_size_allocate_scroll(get_width());
533 page = _notebook.get_nth_page(_notebook.get_current_page());
537 std::cerr <<
"DialogNotebook::pop_tab: page not found!" << std::endl;
541 return float_tab(*
page);
545 auto page = _notebook.get_nth_page(_notebook.get_current_page());
550 auto wnd = _container->get_inkscape_window();
552 auto container = wnd->get_desktop()->getContainer();
553 if (!container)
return;
555 container->dock_dialog(*
page, *
this, location,
nullptr,
nullptr);
563void DialogNotebook::on_page_added(Gtk::Widget *
page,
int page_num)
568 if (dialog && _container->has_dialog_of_type(dialog)) {
572 auto other_dialog = _container->get_dialog(dialog->get_type());
573 other_dialog->
blink();
576 _detaching_duplicate =
true;
577 _notebook.detach_tab(*
page);
583 _container->link_dialog(dialog);
589 auto tab = _tabs.add_tab(dialog->get_name(), dialog->get_icon(), page_num);
590 _tabs.select_tab(*tab);
593 on_size_allocate_scroll(get_width());
599void DialogNotebook::on_page_removed(Gtk::Widget *
page,
int page_num)
606 if (_detaching_duplicate) {
607 _detaching_duplicate =
false;
614 _container->unlink_dialog(dialog);
617 _tabs.remove_tab_at(page_num);
618 _tabs.select_tab_at(_notebook.get_current_page());
621void DialogNotebook::size_allocate_vfunc(
int const width,
int const height,
int const baseline)
623 Gtk::ScrolledWindow::size_allocate_vfunc(
width,
height, baseline);
625 on_size_allocate_scroll(
width);
632void DialogNotebook::on_size_allocate_scroll(
int const width)
635 static constexpr int MIN_HEIGHT = 60;
640 if (!provide_scroll(
page)) {
641 auto const scrolledwindow = get_scrolledwindow(
page);
642 if (scrolledwindow) {
643 double height = scrolledwindow->get_allocation().get_height();
645 auto property = scrolledwindow->property_vscrollbar_policy();
646 auto const policy =
property.get_value();
647 if (
height >= MIN_HEIGHT && policy != Gtk::PolicyType::AUTOMATIC) {
648 property.set_value(Gtk::PolicyType::AUTOMATIC);
649 }
else if (
height < MIN_HEIGHT && policy != Gtk::PolicyType::EXTERNAL) {
650 property.set_value(Gtk::PolicyType::EXTERNAL);
669void DialogNotebook::on_page_switch(Gtk::Widget *curr_page, guint
page) {
670 _tabs.select_tab_at(
page);
671 if (
auto dialog =
dynamic_cast<DialogBase*
>(curr_page)) {
672 dialog->focus_dialog();
676bool DialogNotebook::on_scroll_event(
double dx,
double dy)
678 if (_notebook.get_n_pages() <= 1) {
683 auto current_page = _notebook.get_current_page();
684 if (current_page > 0) {
685 _notebook.set_current_page(current_page - 1);
688 auto current_page = _notebook.get_current_page();
689 if (current_page < _notebook.get_n_pages() - 1) {
690 _notebook.set_current_page(current_page + 1);
699void DialogNotebook::change_page(
size_t pagenum)
701 _notebook.set_current_page(pagenum);
704void DialogNotebook::measure_vfunc(Gtk::Orientation orientation,
int for_size,
int &min,
int &nat,
int &min_baseline,
int &nat_baseline)
const
706 Gtk::ScrolledWindow::measure_vfunc(orientation, for_size, min, nat, min_baseline, nat_baseline);
707 if (orientation == Gtk::Orientation::VERTICAL && _natural_height > 0) {
708 nat = _natural_height;
709 min = std::min(min, _natural_height);
713void DialogNotebook::set_requested_height(
int height) {
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
DialogBase is the base class for the dialog system.
void blink()
Highlight notebook where dialog already exists.
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned contain...
void remove(Gtk::Widget &child)
Removes a widget from DialogMultipaned.
static void add_drop_zone_highlight_instances()
static void remove_drop_zone_highlight_instances()
A widget that wraps a Gtk::Notebook with dialogs as pages.
void on_page_added(Gtk::Widget *page, int page_num)
Signal handler to update dialog list when adding a page.
UI::Widget::PopoverMenu _menu_dialogs
std::vector< sigc::scoped_connection > _conn
static std::list< DialogNotebook * > _instances
PrefObserver _tabclose_pref
void on_page_removed(Gtk::Widget *page, int page_num)
Signal handler to update dialog list when removing a page.
void build_docking_menu(UI::Widget::PopoverMenu &menu)
void close_notebook()
Shutdown callback - delete the parent DialogMultipaned before destructing.
UI::Widget::PopoverMenu _menu_dock
UI::Widget::PopoverMenu _menu_tab_ctx
Gtk::Widget * get_page(int position)
void build_dialog_menu(UI::Widget::PopoverMenu &menu)
UI::Widget::TabStrip _tabs
DialogNotebook(DialogContainer *container)
DialogNotebook constructor.
bool on_scroll_event(double dx, double dy)
void on_page_switch(Gtk::Widget *page, guint page_number)
DialogWindow holds DialogContainer instances for undocked dialogs.
InkscapeWindow * get_inkscape_window()
Inkscape::UI::Dialog::DialogContainer * getContainer()
A base class for all dialogs.
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned.
std::span< const DialogData > get_dialog_data_list()
const std::map< std::string, DialogData > & get_dialog_data()
Get the data about all existing dialogs.
char const *const dialog_categories[DialogData::_num_categories]
A widget with multiple panes.
A wrapper for Gtk::Notebook.
A window for floating docks.
static char const *const parent
@ PREFS_NOTEBOOK_LABELS_AUTO
@ PREFS_NOTEBOOK_LABELS_ACTIVE
Inkscape - An SVG editor.
static const Glib::Quark dialog_notebook_id("dialog-notebook-id")
DialogNotebook * find_dialog_notebook(Widget::TabStrip *tabs)
Gtk::Widget * find_dialog_page(Widget::TabStrip *tabs, int position)
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
Gtk::Widget * for_each_page(Gtk::Notebook ¬ebook, Func &&func)
Similar to for_each_child, but only iterates over pages in a notebook.
static cairo_user_data_key_t key
Singleton class to access the preferences file in a convenient way.
Glib::RefPtr< Gtk::Builder > builder