C++ source, ASCII text
        
            1
            #include "gpanthera.hh" 
        
            2
            #include <cassert> 
        
            4
            #include <iostream> 
        
            5
            #include <utility> 
        
            6
            #include <libintl.h> 
        
            7
            #include <locale.h> 
        
            8
            #include <filesystem> 
        
            9
            #include <ranges> 
        
            10
            #include <nlohmann/json.hpp> 
        
            11
            #include <unordered_set> 
        
            12
            #define _(STRING) gettext(STRING) 
        
            14
            namespace gPanthera { 
        
            16
                sigc::signal<void(double, double)> add_context_menu(Gtk::Widget &widget) { 
        
            17
                    sigc::signal<void(double, double)> signal; 
        
            18
                    // Add a context menu to a widget 
        
            19
                    auto gesture_click = Gtk::GestureClick::create(); 
        
            20
                    gesture_click->set_button(3); 
        
            21
                    auto gesture_keyboard = Gtk::EventControllerKey::create(); 
        
            22
                    gesture_click->signal_pressed().connect([&widget, signal](int n_press, double x, double y) { 
        
            23
                        if(n_press == 1) { 
        
            24
                            signal.emit(x, y); 
        
            25
                        } 
        
            26
                    }, false); 
        
            27
                    gesture_keyboard->signal_key_pressed().connect([&widget, signal](int keyval, int keycode, Gdk::ModifierType state) { 
        
            28
                        if((keyval == GDK_KEY_F10) && (state == Gdk::ModifierType::SHIFT_MASK) || keyval == GDK_KEY_Menu) { 
        
            29
                            // auto popover = Gtk::make_managed<Gtk::PopoverMenu>(); 
        
            30
                            // popover->set_menu_model(menu); 
        
            31
                            // popover->set_has_arrow(false); 
        
            32
                            // popover->set_halign(Gtk::Align::START); 
        
            33
                            // popover->set_pointing_to(Gdk::Rectangle(0, 0, 1, 1)); 
        
            34
                            // popover->set_parent(widget); 
        
            35
                            // popover->popup(); 
        
            36
                            signal.emit(0, 0); 
        
            37
                        } 
        
            38
                        return true; 
        
            39
                    }, false); 
        
            40
                    widget.add_controller(gesture_click); 
        
            41
                    widget.add_controller(gesture_keyboard); 
        
            42
                    return signal; 
        
            43
                } 
        
            44
                std::vector<Gtk::Widget*> collect_children(Gtk::Widget &widget) { 
        
            46
                    // Get a vector of the children of a GTK widget, since the container API was removed in GTK 4 
        
            47
                    std::vector<Gtk::Widget*> children; 
        
            48
                    for(auto *child = widget.get_first_child(); child; child = child->get_next_sibling()) { 
        
            49
                        children.push_back(child); 
        
            50
                    } 
        
            51
                    return children; 
        
            52
                } 
        
            53
                Gtk::Image *copy_image(Gtk::Image *image) { 
        
            55
                    // Generate a new Gtk::Image with the same contents as an existing one 
        
            56
                    if(image->get_storage_type() == Gtk::Image::Type::PAINTABLE) { 
        
            57
                        return Gtk::make_managed<Gtk::Image>(image->get_paintable()); 
        
            58
                    } else if(image->get_storage_type() == Gtk::Image::Type::ICON_NAME) { 
        
            59
                        auto new_image = Gtk::make_managed<Gtk::Image>(); 
        
            60
                        new_image->set_from_icon_name(image->get_icon_name()); 
        
            61
                        return new_image; 
        
            62
                    } else { 
        
            63
                        return nullptr; 
        
            64
                    } 
        
            65
                } 
        
            66
                void init() { 
        
            68
                    g_log_set_always_fatal(G_LOG_LEVEL_CRITICAL); 
        
            69
                    // Set up gettext configurationIsn't 
        
            70
                    bindtextdomain("gpanthera", "./locales"); 
        
            71
                    textdomain("gpanthera"); 
        
            72
                } 
        
            73
                DockablePane::DockablePane(std::shared_ptr<LayoutManager> layout, Gtk::Widget &child, const Glib::ustring &name, const Glib::ustring &label, Gtk::Image *icon, DockStack *stack, Gtk::Widget *custom_header) 
        
            75
                    : Gtk::Box(Gtk::Orientation::VERTICAL, 0), name(name) { 
        
            76
                    if(icon) { 
        
            77
                        this->icon = icon; 
        
            78
                    } 
        
            79
                    if(stack) { 
        
            80
                        this->stack = stack; 
        
            81
                    } 
        
            82
                    this->layout = layout; 
        
            83
                    this->layout->add_pane(this); 
        
            84
                    this->label.set_text(label); 
        
            85
                    // This should be replaced with a custom class in the future 
        
            86
                    header = std::make_unique<Gtk::HeaderBar>(); 
        
            87
                    header->set_show_title_buttons(false); 
        
            88
                    if(custom_header) { 
        
            89
                        header->set_title_widget(*custom_header); 
        
            90
                    } else { 
        
            91
                        header->set_title_widget(this->label); 
        
            92
                    } 
        
            93
                    header->add_css_class("gpanthera-dock-titlebar"); 
        
            94
                    auto header_menu_button = Gtk::make_managed<Gtk::MenuButton>(); 
        
            95
                    header_menu_button->set_direction(Gtk::ArrowType::NONE); 
        
            96
                    // Pane menu 
        
            98
                    this->action_group = Gio::SimpleActionGroup::create(); 
        
            99
                    header_menu_button->insert_action_group("win", action_group); 
        
            100
                    // Close action 
        
            102
                    auto close_action = Gio::SimpleAction::create("close"); 
        
            103
                    close_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            104
                        if(this->stack) { 
        
            105
                            this->stack->set_visible_child(""); 
        
            106
                        } 
        
            107
                    }); 
        
            108
                    action_group->add_action(close_action); 
        
            109
                    header_menu->append(_("Close"), "win.close"); 
        
            110
                    // Pop out action 
        
            112
                    auto pop_out_action = Gio::SimpleAction::create("pop_out"); 
        
            113
                    pop_out_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            114
                        if(this->stack) { 
        
            115
                            this->pop_out(); 
        
            116
                        } 
        
            117
                    }); 
        
            118
                    action_group->add_action(pop_out_action); 
        
            119
                    header_menu->append(_("Pop out"), "win.pop_out"); 
        
            120
                    // Move menu 
        
            122
                    auto move_menu = Gio::Menu::create(); 
        
            123
                    for(auto &this_stack : this->layout->stacks) { 
        
            124
                        auto action_name = "move_" + this_stack->name; 
        
            125
                        auto move_action = Gio::SimpleAction::create(action_name); 
        
            126
                        move_action->signal_activate().connect([this, this_stack](const Glib::VariantBase&) { 
        
            127
                            this_stack->add_pane(*this); 
        
            128
                        }); 
        
            129
                        action_group->add_action(move_action); 
        
            130
                        move_menu->append(this_stack->name, "win." + action_name); 
        
            131
                    } 
        
            132
                    // Add move submenu 
        
            134
                    header_menu->append_submenu(_("Move"), move_menu); 
        
            135
                    // Switch to traditional (nested) submenus, not sliding 
        
            137
                    auto popover_menu = Gtk::make_managed<Gtk::PopoverMenu>(header_menu, Gtk::PopoverMenu::Flags::NESTED); 
        
            138
                    popover_menu->set_has_arrow(false); 
        
            139
                    header_menu_button->set_popover(*popover_menu); 
        
            140
                    // TODO: Add a context menu as well 
        
            142
                    header->pack_end(*header_menu_button); 
        
            144
                    this->prepend(*header); 
        
            146
                    this->child = &child; 
        
            147
                    this->append(child); 
        
            148
                } 
        
            149
                Gtk::Stack *DockablePane::get_stack() const { 
        
            151
                    return stack; 
        
            152
                } 
        
            153
                void DockablePane::redock(DockStack *stack) { 
        
            155
                    if(this->window != nullptr) { 
        
            156
                        this->window->hide(); 
        
            157
                        // Put the titlebar back 
        
            158
                        this->window->unset_titlebar(); 
        
            159
                        this->window->set_decorated(false); 
        
            160
                        this->header->get_style_context()->remove_class("titlebar"); 
        
            161
                        this->prepend(*this->header); 
        
            162
                        this->window->unset_child(); 
        
            163
                        this->window->close(); 
        
            164
                    } else if(this->get_parent() && this->stack == this->get_parent()) { 
        
            165
                        this->stack->remove(*this); 
        
            166
                    } 
        
            167
                    this->stack = stack; 
        
            168
                    this->last_stack = stack; 
        
            169
                    this->stack->add(*this, this->get_identifier()); 
        
            170
                    if(this->window != nullptr) { 
        
            171
                        this->window->destroy(); 
        
            172
                        delete this->window; 
        
            173
                        this->window = nullptr; 
        
            174
                        // Re-enable the pop out option 
        
            175
                        auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out")); 
        
            176
                        action->set_enabled(true); 
        
            177
                        this->header->get_style_context()->remove_class("gpanthera-dock-titlebar-popout"); 
        
            178
                    } 
        
            179
                    layout->signal_pane_moved.emit(this); 
        
            180
                } 
        
            181
                void DockablePane::pop_out() { 
        
            183
                    if(this->stack != nullptr) { 
        
            184
                        this->stack->remove(*this); 
        
            185
                        this->stack = nullptr; 
        
            186
                    } 
        
            187
                    if(this->window == nullptr) { 
        
            189
                        // Remove the header bar from the pane, so it can be used as the titlebar of the window 
        
            190
                        this->remove(*this->header); 
        
            191
                        this->window = new DockWindow(this); 
        
            192
                        this->window->set_titlebar(*this->header); 
        
            193
                        this->window->set_child(*this); 
        
            194
                        this->window->set_decorated(true); 
        
            195
                        // Grey out the pop-out option 
        
            196
                        auto action = std::dynamic_pointer_cast<Gio::SimpleAction>(this->action_group->lookup_action("pop_out")); 
        
            197
                        action->set_enabled(false); 
        
            198
                        this->header->get_style_context()->add_class("gpanthera-dock-titlebar-popout"); 
        
            199
                    } 
        
            200
                    this->window->present(); 
        
            201
                    layout->signal_pane_moved.emit(this); 
        
            202
                } 
        
            203
                Glib::ustring DockablePane::get_identifier() const { 
        
            205
                    return name; 
        
            206
                } 
        
            207
                Gtk::Image *DockablePane::get_icon() const { 
        
            209
                    return icon; 
        
            210
                } 
        
            211
                Gtk::Widget *DockablePane::get_child() const { 
        
            213
                    return child; 
        
            214
                } 
        
            215
                Gtk::Label *DockablePane::get_label() { 
        
            217
                    return &label; 
        
            218
                } 
        
            219
                LayoutManager::LayoutManager() : Glib::ObjectBase("LayoutManager") { 
        
            221
                } 
        
            222
                void LayoutManager::add_pane(DockablePane *pane) { 
        
            224
                    panes.push_back(pane); 
        
            225
                } 
        
            226
                void LayoutManager::add_stack(DockStack *stack) { 
        
            228
                    stacks.push_back(stack); 
        
            229
                } 
        
            230
                void LayoutManager::remove_pane(DockablePane *pane) { 
        
            232
                    panes.erase(std::ranges::remove(panes, pane).begin(), panes.end()); 
        
            233
                } 
        
            234
                void LayoutManager::remove_stack(DockStack *stack) { 
        
            236
                    stacks.erase(std::ranges::remove(stacks, stack).begin(), stacks.end()); 
        
            237
                } 
        
            238
                BaseStack::BaseStack() : Gtk::Stack() { 
        
            240
                } 
        
            241
                DockStack::DockStack(std::shared_ptr<LayoutManager> layout, const Glib::ustring &name, const std::string &id) : BaseStack(), layout(layout), name(name), id(id) { 
        
            243
                    auto empty_child = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 0); 
        
            244
                    this->add(*empty_child, ""); 
        
            245
                    // Add the stack to a layout manager 
        
            246
                    this->layout->add_stack(this); 
        
            247
                    // Hide the stack when no child is visible 
        
            249
                    this->property_visible_child_name().signal_changed().connect([this]() { 
        
            250
                        if(this->get_visible_child_name() == "") { 
        
            251
                            this->hide(); 
        
            252
                        } else { 
        
            253
                            this->show(); 
        
            254
                        } 
        
            255
                    }); 
        
            256
                    // Also hide when the visible child is removed 
        
            258
                    this->signal_child_removed.connect([this](Gtk::Widget* const &child) { 
        
            259
                        if(this->get_visible_child_name() == "") { 
        
            260
                            this->hide(); 
        
            261
                        } 
        
            262
                    }); 
        
            263
                    this->set_visible_child(""); 
        
            265
                    this->hide(); 
        
            266
                } 
        
            267
                DockWindow::DockWindow(DockablePane *pane) { 
        
            269
                    this->pane = pane; 
        
            270
                    this->set_child(*pane); 
        
            271
                    // Attempting to close the window should redock the pane so it doesn't vanish 
        
            272
                    this->signal_close_request().connect([this]() { 
        
            273
                        this->pane->redock(this->pane->last_stack); 
        
            274
                        return true; 
        
            275
                    }, false); 
        
            276
                } 
        
            277
                DockStackSwitcher::DockStackSwitcher(DockStack *stack, Gtk::Orientation orientation) : Gtk::Box(orientation), stack(stack) { 
        
            279
                    auto update_callback = [this](Gtk::Widget*) { 
        
            280
                        this->update_buttons(); 
        
            281
                    }; 
        
            282
                    this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB_LIST); 
        
            283
                    stack->signal_child_added.connect(update_callback); 
        
            284
                    stack->signal_child_removed.connect(update_callback); 
        
            285
                    this->get_style_context()->add_class("gpanthera-dock-switcher"); 
        
            286
                    drop_target = Gtk::DropTarget::create(DockablePane::get_type(), Gdk::DragAction::MOVE); 
        
            287
                    this->add_controller(drop_target); 
        
            288
                    // Process dropped buttons 
        
            289
                    drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) { 
        
            290
                        const auto &widget = static_cast<const Glib::Value<DockablePane*>&>(value).get(); 
        
            291
                        if(widget) { 
        
            293
                            if(auto pane = dynamic_cast<DockablePane*>(widget)) { 
        
            294
                                this->stack->add_pane(*pane); 
        
            295
                            } 
        
            296
                        } 
        
            297
                        return true;  // Drop OK 
        
            299
                    }, false); 
        
            300
                } 
        
            301
                DockStack *DockStackSwitcher::get_stack() const { 
        
            303
                    return stack; 
        
            304
                } 
        
            305
                DockStackSwitcher::~DockStackSwitcher() { 
        
            307
                    add_handler.disconnect(); 
        
            308
                    remove_handler.disconnect(); 
        
            309
                } 
        
            310
                DockButton::DockButton(DockablePane *pane) : Gtk::ToggleButton(), pane(pane) { 
        
            312
                    if(pane->get_icon()) { 
        
            313
                        this->set_child(*copy_image(pane->get_icon())); 
        
            314
                    } 
        
            315
                    this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB); 
        
            316
                    this->set_tooltip_text(pane->get_label()->get_text()); 
        
            317
                    this->set_halign(Gtk::Align::CENTER); 
        
            318
                    this->set_valign(Gtk::Align::CENTER); 
        
            319
                    this->get_style_context()->add_class("toggle"); 
        
            320
                    this->get_style_context()->add_class("gpanthera-dock-button"); 
        
            321
                    // Add/remove CSS classes when the pane is shown/hidden 
        
            322
                    active_style_handler = this->pane->get_stack()->property_visible_child_name().signal_changed().connect([this]() { 
        
            323
                        this->update_active_style(); 
        
            324
                    }); 
        
            325
                    drag_source = Gtk::DragSource::create(); 
        
            326
                    drag_source->set_exclusive(false); 
        
            327
                    // This is to prevent the click handler from taking over grabbing the button 
        
            328
                    drag_source->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); 
        
            329
                    value.init(DockablePane::get_type()); 
        
            330
                    value.set(pane); 
        
            331
                    // Add the drag source to the button 
        
            332
                    this->add_controller(drag_source); 
        
            333
                    this->signal_clicked().connect([this, pane]() { 
        
            334
                        if(pane->get_stack()->get_visible_child_name() == pane->get_identifier()) { 
        
            335
                            pane->get_stack()->set_visible_child(""); 
        
            336
                        } else { 
        
            337
                            pane->get_stack()->set_visible_child(pane->get_identifier()); 
        
            338
                        } 
        
            339
                    }); 
        
            340
                    // Provide the drag data 
        
            341
                    drag_source->signal_prepare().connect([this](double, double) { 
        
            342
                        drag_source->set_actions(Gdk::DragAction::MOVE); 
        
            343
                        auto const paintable = Gtk::WidgetPaintable::create(); 
        
            344
                        paintable->set_widget(*this); 
        
            345
                        drag_source->set_icon(paintable->get_current_image(), 0, 0); 
        
            346
                        return Gdk::ContentProvider::create(value); 
        
            347
                    }, false); 
        
            348
                    drag_source->signal_drag_begin().connect([this](const Glib::RefPtr<Gdk::Drag>&) { 
        
            349
                        this->set_opacity(0); 
        
            350
                    }, false); 
        
            351
                    // Pop out if dragged to an external location 
        
            352
                    drag_source->signal_drag_cancel().connect([this](const Glib::RefPtr<Gdk::Drag>&, Gdk::DragCancelReason reason) { 
        
            353
                        if(reason == Gdk::DragCancelReason::NO_TARGET) { 
        
            354
                            this->pane->pop_out(); 
        
            355
                            return true; 
        
            356
                        } 
        
            357
                        this->set_opacity(1); 
        
            358
                        return false; 
        
            359
                    }, false); 
        
            360
                    // Add a drop target to the button 
        
            361
                    auto drop_target = Gtk::DropTarget::create(DockablePane::get_type(), Gdk::DragAction::MOVE); 
        
            362
                    drop_target->set_actions(Gdk::DragAction::MOVE); 
        
            363
                    drop_target->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); 
        
            364
                    this->add_controller(drop_target); 
        
            365
                    // Process dropped buttons by inserting them after the current button 
        
            366
                    drop_target->signal_drop().connect([this](const Glib::ValueBase& value, double x, double y) { 
        
            367
                        const auto &widget = static_cast<const Glib::Value<DockablePane*>&>(value).get(); 
        
            368
                        if(widget) { 
        
            370
                            if(auto pane = dynamic_cast<DockablePane*>(widget)) { 
        
            371
                                if(pane->layout != this->pane->layout) { 
        
            372
                                    // If the pane is not in the same layout manager, reject 
        
            373
                                    return false; 
        
            374
                                } 
        
            375
                                auto switcher = dynamic_cast<DockStackSwitcher*>(this->get_parent()); 
        
            376
                                if(switcher) { 
        
            377
                                    auto *stack = switcher->get_stack(); 
        
            378
                                    // Move the button to the new position 
        
            379
                                    pane->redock(stack); 
        
            380
                                    if(switcher->get_orientation() == Gtk::Orientation::HORIZONTAL) { 
        
            381
                                        if(x < static_cast<double>(this->get_allocated_width()) / 2) { 
        
            382
                                            pane->insert_before(*stack, *this->pane); 
        
            383
                                        } else { 
        
            384
                                            pane->insert_after(*stack, *this->pane); 
        
            385
                                        } 
        
            386
                                    } else if(switcher->get_orientation() == Gtk::Orientation::VERTICAL) { 
        
            387
                                        if(y < static_cast<double>(this->get_allocated_height()) / 2) { 
        
            388
                                            pane->insert_before(*stack, *this->pane); 
        
            389
                                        } else { 
        
            390
                                            pane->insert_after(*stack, *this->pane); 
        
            391
                                        } 
        
            392
                                    } 
        
            393
                                    switcher->update_buttons(); 
        
            395
                                } 
        
            396
                            } 
        
            397
                        } 
        
            398
                        return true;  // Drop OK 
        
            400
                    }, false); 
        
            401
                    this->update_active_style(); 
        
            402
                } 
        
            403
                void DockButton::update_active_style() { 
        
            405
                    if(this->pane->get_stack()->get_visible_child_name() == this->pane->get_identifier()) { 
        
            406
                        this->add_css_class("checked"); 
        
            407
                        this->add_css_class("gpanthera-dock-button-active"); 
        
            408
                        this->set_active(true); 
        
            409
                    } else { 
        
            410
                        this->remove_css_class("checked"); 
        
            411
                        this->remove_css_class("gpanthera-dock-button-active"); 
        
            412
                        this->set_active(false); 
        
            413
                    } 
        
            414
                } 
        
            415
                DockButton::~DockButton() { 
        
            417
                    active_style_handler.disconnect(); 
        
            418
                } 
        
            419
                void DockStackSwitcher::update_buttons() { 
        
            421
                    // Clear the old buttons 
        
            422
                    auto old_buttons = collect_children(*this); 
        
            423
                    for(auto *button : old_buttons) { 
        
            424
                        remove(*button); 
        
            425
                    } 
        
            426
                    DockButton* first_child = nullptr; 
        
            427
                    for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) { 
        
            428
                        if(auto pane = dynamic_cast<DockablePane*>(widget)) { 
        
            429
                            auto *button = Gtk::make_managed<DockButton>(pane); 
        
            430
                            if(!first_child) { 
        
            431
                                first_child = button; 
        
            432
                            } else { 
        
            433
                                button->set_group(*first_child); 
        
            434
                            } 
        
            435
                            if(pane->get_identifier() != Glib::ustring("")) { 
        
            436
                                append(*button); 
        
            437
                            } 
        
            438
                        } 
        
            439
                    } 
        
            440
                } 
        
            441
                void DockStack::add_pane(DockablePane &child) { 
        
            443
                    child.redock(this); 
        
            444
                } 
        
            445
                void ContentManager::add_stack(ContentStack *stack) { 
        
            447
                    this->stacks.push_back(stack); 
        
            448
                } 
        
            449
                void ContentManager::remove_stack(ContentStack *stack) { 
        
            451
                    this->stacks.erase(std::ranges::remove(this->stacks, stack).begin(), this->stacks.end()); 
        
            452
                } 
        
            453
                void BaseStack::add(Gtk::Widget &child, const Glib::ustring &name) { 
        
            455
                    Gtk::Stack::add(child, name); 
        
            456
                    signal_child_added.emit(&child); 
        
            457
                } 
        
            458
                void BaseStack::add(Gtk::Widget &child) { 
        
            460
                    Gtk::Stack::add(child); 
        
            461
                    signal_child_added.emit(&child); 
        
            462
                } 
        
            463
                void BaseStack::remove(Gtk::Widget &child) { 
        
            465
                    Gtk::Stack::remove(child); 
        
            466
                    signal_child_removed.emit(&child); 
        
            467
                } 
        
            468
                void ContentStack::disable_children() { 
        
            470
                    auto children = collect_children(*this); 
        
            471
                    for(auto *child : children) { 
        
            472
                        child->set_can_target(false); 
        
            473
                    } 
        
            474
                } 
        
            475
                void ContentStack::enable_children() { 
        
            477
                    auto children = collect_children(*this); 
        
            478
                    for(auto *child : children) { 
        
            479
                        child->set_can_target(true); 
        
            480
                    } 
        
            481
                } 
        
            482
                ContentStack::ContentStack(std::shared_ptr<ContentManager> content_manager, std::function<bool(ContentPage*)> detach_handler) 
        
            484
                    : BaseStack(), content_manager(content_manager) { 
        
            485
                    this->set_name("gpanthera_content_stack"); 
        
            486
                    this->content_manager->add_stack(this); 
        
            487
                    if(detach_handler) { 
        
            488
                        this->detach_handler = detach_handler; 
        
            489
                        this->signal_detach.connect([this](ContentPage *widget) { 
        
            490
                            return this->detach_handler(widget); 
        
            491
                        }); 
        
            492
                    } 
        
            493
                    drop_target = Gtk::DropTarget::create(ContentPage::get_type(), Gdk::DragAction::MOVE); 
        
            495
                    this->add_controller(drop_target); 
        
            496
                    // Some widgets (such as webviews) will consume anything dropped on their ancestors. We have 
        
            497
                    // to disable all child widgets to ensure the drop can be caught by this widget. 
        
            498
                    drop_target->signal_enter().connect([this](double x, double y) { 
        
            499
                        disable_children(); 
        
            500
                        return Gdk::DragAction::MOVE; 
        
            501
                    }, false); 
        
            502
                    drop_target->signal_leave().connect([this]() { 
        
            503
                        enable_children(); 
        
            504
                    }, false); 
        
            505
                    // Process dropped buttons 
        
            506
                    drop_target->signal_drop().connect([this, content_manager](const Glib::ValueBase &value, double x, double y) { 
        
            507
                        enable_children(); 
        
            508
                        const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get(); 
        
            509
                        if(auto page = dynamic_cast<ContentPage*>(widget)) { 
        
            511
                            if(page->get_stack() == this && !this->get_first_child()->get_next_sibling()) { 
        
            512
                                // Don't allow splitting if there are no more pages 
        
            513
                                return false; 
        
            514
                            } 
        
            515
                            if(!(page->content_manager == this->content_manager)) { 
        
            516
                                // If the page is not in the same content manager, reject 
        
            517
                                return false; 
        
            518
                            } 
        
            519
                            double width = this->get_allocated_width(), height = this->get_allocated_height(); 
        
            520
                            // Split based on the drop position 
        
            521
                            if(!(x < width / 4 || x > width * 3 / 4 || y < height / 4 || y > height * 3 / 4)) { 
        
            522
                                if(page->get_stack() == this) { 
        
            523
                                    return false; 
        
            524
                                } 
        
            525
                                page->lose_visibility(); 
        
            526
                                // If the drop position is not at a quarter to the edges, move to the same stack 
        
            527
                                this->add_page(*page); 
        
            528
                                return true; 
        
            529
                            } 
        
            530
                            page->lose_visibility(); 
        
            531
                            auto new_stack = Gtk::make_managed<ContentStack>(this->content_manager, this->detach_handler); 
        
            532
                            auto this_notebook = dynamic_cast<ContentNotebook*>(this->get_parent()); 
        
            533
                            auto new_switcher = Gtk::make_managed<ContentTabBar>(new_stack, this_notebook ? this_notebook->get_switcher()->get_orientation() : Gtk::Orientation::HORIZONTAL, this_notebook->get_switcher()->get_extra_child_function()); 
        
            534
                            auto new_notebook = Gtk::make_managed<ContentNotebook>(new_stack, new_switcher, this_notebook ? this_notebook->get_tab_position() : Gtk::PositionType::TOP); 
        
            535
                            new_stack->add_page(*page); 
        
            536
                            new_stack->set_visible_child(*page); 
        
            537
                            content_manager->set_last_operated_page(page); 
        
            538
                            if(x < width / 4) { 
        
            539
                                this->make_paned(Gtk::Orientation::HORIZONTAL, Gtk::PackType::START); 
        
            540
                                if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) { 
        
            541
                                    paned->set_start_child(*new_notebook); 
        
            542
                                } 
        
            543
                            } else if(x > width * 3 / 4) { 
        
            544
                                this->make_paned(Gtk::Orientation::HORIZONTAL, Gtk::PackType::END); 
        
            545
                                if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) { 
        
            546
                                    paned->set_end_child(*new_notebook); 
        
            547
                                } 
        
            548
                            } else if(y < height / 4) { 
        
            549
                                this->make_paned(Gtk::Orientation::VERTICAL, Gtk::PackType::START); 
        
            550
                                if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) { 
        
            551
                                    paned->set_start_child(*new_notebook); 
        
            552
                                } 
        
            553
                            } else if(y > height * 3 / 4) { 
        
            554
                                this->make_paned(Gtk::Orientation::VERTICAL, Gtk::PackType::END); 
        
            555
                                if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) { 
        
            556
                                    paned->set_end_child(*new_notebook); 
        
            557
                                } 
        
            558
                            } 
        
            559
                        } 
        
            560
                        return true;  // Drop OK 
        
            562
                    }, false); 
        
            563
                } 
        
            564
                std::function<bool(ContentPage*)> ContentStack::get_detach_handler() const { 
        
            566
                    return this->detach_handler; 
        
            567
                } 
        
            568
                ContentNotebook::ContentNotebook(ContentStack *stack, ContentTabBar *switcher, Gtk::PositionType tab_position) : Gtk::Box(), stack(stack), switcher(switcher), tab_position(tab_position) { 
        
            570
                    this->append(*stack); 
        
            571
                    if(tab_position == Gtk::PositionType::TOP || tab_position == Gtk::PositionType::BOTTOM) { 
        
            572
                        this->set_orientation(Gtk::Orientation::VERTICAL); 
        
            573
                        if(tab_position == Gtk::PositionType::TOP) { 
        
            574
                            this->prepend(*switcher); 
        
            575
                        } else if(tab_position == Gtk::PositionType::BOTTOM) { 
        
            576
                            this->append(*switcher); 
        
            577
                        } 
        
            578
                    } else if(tab_position == Gtk::PositionType::LEFT || tab_position == Gtk::PositionType::RIGHT) { 
        
            579
                        this->set_orientation(Gtk::Orientation::HORIZONTAL); 
        
            580
                        if(tab_position == Gtk::PositionType::LEFT) { 
        
            581
                            this->prepend(*switcher); 
        
            582
                        } else if(tab_position == Gtk::PositionType::RIGHT) { 
        
            583
                            this->append(*switcher); 
        
            584
                        } 
        
            585
                    } 
        
            586
                } 
        
            587
                void ContentStack::make_paned(Gtk::Orientation orientation, Gtk::PackType pack_type) { 
        
            589
                    auto *parent = this->get_parent(); 
        
            590
                    if(auto notebook = dynamic_cast<ContentNotebook*>(parent)) { 
        
            591
                        auto *paned = Gtk::make_managed<Gtk::Paned>(orientation); 
        
            592
                        if(auto parent_paned = dynamic_cast<Gtk::Paned*>(notebook->get_parent())) { 
        
            593
                            if(parent_paned->get_start_child() == notebook) { 
        
            594
                                notebook->unparent(); 
        
            595
                                parent_paned->set_start_child(*paned); 
        
            596
                            } else if(parent_paned->get_end_child() == notebook) { 
        
            597
                                notebook->unparent(); 
        
            598
                                parent_paned->set_end_child(*paned); 
        
            599
                            } 
        
            600
                        } else if(auto box = dynamic_cast<Gtk::Box*>(notebook->get_parent())) { 
        
            601
                            auto previous_child = notebook->get_prev_sibling(); 
        
            602
                            box->remove(*notebook); 
        
            603
                            if(previous_child) { 
        
            604
                                paned->insert_after(*box, *previous_child); 
        
            605
                            } else { 
        
            606
                                box->prepend(*paned); 
        
            607
                            } 
        
            608
                        } 
        
            609
                        if(pack_type == Gtk::PackType::START) { 
        
            610
                            paned->set_end_child(*notebook); 
        
            611
                        } else if(pack_type == Gtk::PackType::END) { 
        
            612
                            paned->set_start_child(*notebook); 
        
            613
                        } 
        
            614
                    } 
        
            615
                } 
        
            616
                ContentTabBar::ContentTabBar(ContentStack *stack, Gtk::Orientation orientation, std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> extra_child_function) : Gtk::Box(orientation), stack(stack), extra_child_function(extra_child_function) { 
        
            618
                    this->get_style_context()->add_class("gpanthera-content-tab-bar"); 
        
            619
                    this->set_margin_top(0); 
        
            620
                    this->set_margin_bottom(0); 
        
            621
                    this->set_margin_start(0); 
        
            622
                    this->set_margin_end(0); 
        
            623
                    auto update_callback = [this](Gtk::Widget*) { 
        
            625
                        this->update_buttons(); 
        
            626
                    }; 
        
            627
                    this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB_LIST); 
        
            628
                    stack->signal_child_added.connect(update_callback); 
        
            629
                    stack->signal_child_removed.connect(update_callback); 
        
            630
                    drop_target = Gtk::DropTarget::create(ContentPage::get_type(), Gdk::DragAction::MOVE); 
        
            631
                    this->add_controller(drop_target); 
        
            632
                    // Process dropped buttons 
        
            633
                    drop_target->signal_drop().connect([this](const Glib::ValueBase &value, double x, double y) { 
        
            634
                        if(const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get()) { 
        
            635
                            if(auto page = dynamic_cast<ContentPage*>(widget)) { 
        
            636
                                this->stack->add_page(*page); 
        
            637
                            } 
        
            638
                        } 
        
            639
                        return true;  // Drop OK 
        
            641
                    }, false); 
        
            642
                    scrolled_window = Gtk::make_managed<Gtk::ScrolledWindow>(); 
        
            644
                    auto viewport = Gtk::make_managed<Gtk::Viewport>(nullptr, nullptr); 
        
            645
                    tab_box = Gtk::make_managed<Gtk::Box>(orientation); 
        
            646
                    this->prepend(*scrolled_window); 
        
            647
                    scrolled_window->set_child(*viewport); 
        
            648
                    viewport->set_child(*tab_box); 
        
            649
                    this->set_orientation(orientation); 
        
            651
                    if(this->extra_child_function) { 
        
            653
                        this->append(*this->extra_child_function(this)); 
        
            654
                    } 
        
            655
                } 
        
            656
                std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> ContentTabBar::get_extra_child_function() const { 
        
            658
                    return this->extra_child_function; 
        
            659
                } 
        
            660
                void ContentTabBar::set_extra_child_function(std::function<Gtk::Widget*(gPanthera::ContentTabBar*)> extra_child_function) { 
        
            662
                    // Note that this doesn't remove the existing extra child 
        
            663
                    this->extra_child_function = extra_child_function; 
        
            664
                } 
        
            665
                void ContentTabBar::set_orientation(Gtk::Orientation orientation) { 
        
            667
                    this->Gtk::Box::set_orientation(orientation); 
        
            668
                    if(orientation == Gtk::Orientation::HORIZONTAL) { 
        
            669
                        scrolled_window->set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::NEVER); 
        
            670
                        tab_box->set_orientation(Gtk::Orientation::HORIZONTAL); 
        
            671
                        scrolled_window->set_hexpand(true); 
        
            672
                        scrolled_window->set_vexpand(false); 
        
            673
                    } else if(orientation == Gtk::Orientation::VERTICAL) { 
        
            674
                        scrolled_window->set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); 
        
            675
                        tab_box->set_orientation(Gtk::Orientation::VERTICAL); 
        
            676
                        scrolled_window->set_vexpand(true); 
        
            677
                        scrolled_window->set_hexpand(false); 
        
            678
                    } 
        
            679
                } 
        
            680
                ContentTabBar::~ContentTabBar() { 
        
            682
                    add_handler.disconnect(); 
        
            683
                    remove_handler.disconnect(); 
        
            684
                } 
        
            685
                ContentStack *ContentTabBar::get_stack() const { 
        
            687
                    return stack; 
        
            688
                } 
        
            689
                void ContentTabBar::update_buttons() { 
        
            691
                    // Clear the old buttons 
        
            692
                    auto old_buttons = collect_children(*this->tab_box); 
        
            693
                    for(auto *button : old_buttons) { 
        
            694
                        if(auto *button_button = dynamic_cast<Gtk::Button*>(button)) { 
        
            695
                            button_button->unset_child(); 
        
            696
                            tab_box->remove(*button); 
        
            697
                        } 
        
            698
                    } 
        
            699
                    ContentTab* first_child = nullptr; 
        
            700
                    for(auto *widget = stack->get_first_child(); widget; widget = widget->get_next_sibling()) { 
        
            701
                        if(auto page = dynamic_cast<ContentPage*>(widget)) { 
        
            702
                            auto *button = Gtk::make_managed<ContentTab>(page); 
        
            703
                            if(!first_child) { 
        
            704
                                first_child = button; 
        
            705
                            } else { 
        
            706
                                button->set_group(*first_child); 
        
            707
                            } 
        
            708
                            this->tab_box->append(*button); 
        
            709
                        } 
        
            710
                    } 
        
            711
                } 
        
            712
                void ContentStack::add_page(ContentPage &child) { 
        
            714
                    child.redock(this); 
        
            715
                } 
        
            716
                void ContentStack::remove_with_paned() { 
        
            718
                    if(auto paned = dynamic_cast<Gtk::Paned*>(this->get_parent()->get_parent())) { 
        
            719
                        Gtk::Widget *child = nullptr; 
        
            720
                        if(this->get_parent() == paned->get_start_child()) { 
        
            721
                            child = paned->get_end_child(); 
        
            722
                        } else if(this->get_parent() == paned->get_end_child()) { 
        
            723
                            child = paned->get_start_child(); 
        
            724
                        } else { 
        
            725
                            return; 
        
            726
                        } 
        
            727
                        g_object_ref(child->gobj());    // Prevent the child from being automatically deleted 
        
            729
                        paned->property_start_child().reset_value();  // Some hacks because the Gtkmm API isn't complete; it doesn't have an unset_start_child() or unset_end_child() 
        
            730
                        paned->property_end_child().reset_value(); 
        
            731
                        if(child) { 
        
            733
                            if(auto parent_paned = dynamic_cast<Gtk::Paned*>(paned->get_parent())) { 
        
            734
                                if(parent_paned->get_start_child() == paned) { 
        
            735
                                    parent_paned->set_start_child(*child); 
        
            736
                                } else if(parent_paned->get_end_child() == paned) { 
        
            737
                                    parent_paned->set_end_child(*child); 
        
            738
                                } 
        
            739
                            } else if(auto box = dynamic_cast<Gtk::Box*>(paned->get_parent())) { 
        
            740
                                child->insert_after(*box, *paned); 
        
            741
                                paned->unparent(); 
        
            742
                                g_object_unref(child->gobj()); 
        
            743
                            } 
        
            744
                        } 
        
            745
                    } 
        
            746
                } 
        
            747
                ContentTab::ContentTab(ContentPage *page) : Gtk::ToggleButton(), page(page) { 
        
            749
                    this->set_child(*page->get_tab_widget()); 
        
            750
                    this->property_accessible_role().set_value(Gtk::Accessible::Role::TAB); 
        
            751
                    this->set_halign(Gtk::Align::CENTER); 
        
            752
                    this->set_valign(Gtk::Align::CENTER); 
        
            753
                    this->get_style_context()->add_class("toggle"); 
        
            754
                    this->get_style_context()->add_class("gpanthera-content-tab"); 
        
            755
                    // Add/remove CSS classes when the pane is shown/hidden 
        
            756
                    active_style_handler = this->page->get_stack()->property_visible_child().signal_changed().connect([this]() { 
        
            757
                        this->update_active_style(); 
        
            758
                    }); 
        
            759
                    drag_source = Gtk::DragSource::create(); 
        
            760
                    drag_source->set_exclusive(false); 
        
            761
                    // This is to prevent the click handler from taking over grabbing the button 
        
            762
                    drag_source->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); 
        
            763
                    value.init(ContentPage::get_type()); 
        
            764
                    value.set(page); 
        
            765
                    // Add the drag source to the button 
        
            766
                    this->add_controller(drag_source); 
        
            767
                    this->signal_clicked().connect([this, page]() { 
        
            768
                        page->get_stack()->set_visible_child(*page); 
        
            769
                        page->content_manager->set_last_operated_page(page); 
        
            770
                        update_active_style(); 
        
            771
                    }); 
        
            772
                    // Switch tabs on depress 
        
            773
                    auto gesture_click = Gtk::GestureClick::create(); 
        
            774
                    gesture_click->set_button(1); 
        
            775
                    this->add_controller(gesture_click); 
        
            776
                    gesture_click->signal_pressed().connect([this, page](int num_presses, double x, double y) { 
        
            777
                        page->get_stack()->set_visible_child(*page); 
        
            778
                        page->content_manager->set_last_operated_page(page); 
        
            779
                        update_active_style(); 
        
            780
                    }); 
        
            781
                    // Provide the drag data 
        
            782
                    drag_source->signal_prepare().connect([this](double, double) { 
        
            783
                        drag_source->set_actions(Gdk::DragAction::MOVE); 
        
            784
                        auto const paintable = Gtk::WidgetPaintable::create(); 
        
            785
                        paintable->set_widget(*this); 
        
            786
                        drag_source->set_icon(paintable->get_current_image(), 0, 0); 
        
            787
                        return Gdk::ContentProvider::create(value); 
        
            788
                    }, false); 
        
            789
                    update_active_style(); 
        
            790
                    drag_source->signal_drag_begin().connect([this](const Glib::RefPtr<Gdk::Drag>&) { 
        
            791
                        this->set_opacity(0); 
        
            792
                    }, false); 
        
            793
                    drop_target = Gtk::DropTarget::create(ContentPage::get_type(), Gdk::DragAction::MOVE); 
        
            794
                    // Process dropped buttons by inserting them after the current button 
        
            795
                    drop_target->signal_drop().connect([this](const Glib::ValueBase &value, double x, double y) { 
        
            796
                        const auto &widget = static_cast<const Glib::Value<ContentPage*>&>(value).get(); 
        
            797
                        if(widget) { 
        
            799
                            if(auto page = dynamic_cast<ContentPage*>(widget)) { 
        
            800
                                if(page->content_manager != this->page->content_manager) { 
        
            801
                                    // If the pane is not in the same layout manager, reject 
        
            802
                                    return false; 
        
            803
                                } 
        
            804
                                auto switcher = dynamic_cast<ContentTabBar*>(this->get_parent()->get_parent()->get_parent()->get_parent()); 
        
            805
                                if(switcher) { 
        
            806
                                    auto *stack = switcher->get_stack(); 
        
            807
                                    // Move the button to the new position 
        
            808
                                    page->redock(stack); 
        
            809
                                    if(switcher->get_orientation() == Gtk::Orientation::HORIZONTAL) { 
        
            810
                                        if(x < static_cast<double>(this->get_allocated_width()) / 2) { 
        
            811
                                            page->insert_before(*stack, *this->page); 
        
            812
                                        } else { 
        
            813
                                            page->insert_after(*stack, *this->page); 
        
            814
                                        } 
        
            815
                                    } else if(switcher->get_orientation() == Gtk::Orientation::VERTICAL) { 
        
            816
                                        if(y < static_cast<double>(this->get_allocated_height()) / 2) { 
        
            817
                                            page->insert_before(*stack, *this->page); 
        
            818
                                        } else { 
        
            819
                                            page->insert_after(*stack, *this->page); 
        
            820
                                        } 
        
            821
                                    } 
        
            822
                                    switcher->update_buttons(); 
        
            824
                                } 
        
            825
                            } 
        
            826
                        } 
        
            827
                        return true;  // Drop OK 
        
            829
                    }, false); 
        
            830
                    this->add_controller(drop_target); 
        
            831
                    // Pop out if dragged to an external location 
        
            832
                    drag_cancel_handler = drag_source->signal_drag_cancel().connect([this](const Glib::RefPtr<Gdk::Drag>&, Gdk::DragCancelReason reason) { 
        
            833
                        if(reason == Gdk::DragCancelReason::NO_TARGET) { 
        
            834
                            auto stack = dynamic_cast<ContentStack*>(this->page->get_stack()); 
        
            835
                            bool result = stack->signal_detach.emit(this->page); 
        
            836
                            if(!result) { 
        
            837
                                this->set_opacity(1); 
        
            838
                            } 
        
            839
                            return result; 
        
            840
                        } 
        
            841
                        this->set_opacity(1); 
        
            842
                        return false; 
        
            843
                    }, false); 
        
            844
                    drag_end_handler = drag_source->signal_drag_end().connect([this](const Glib::RefPtr<Gdk::Drag>&, bool drop_ok) { 
        
            845
                        if(drop_ok) { 
        
            846
                            this->set_opacity(1); 
        
            847
                        } 
        
            848
                    }, false); 
        
            849
                    // Provide a context menu 
        
            851
                    context_menu = Gio::Menu::create(); 
        
            852
                    auto action_group = Gio::SimpleActionGroup::create(); 
        
            853
                    this->insert_action_group("win", action_group); 
        
            854
                    auto close_action = Gio::SimpleAction::create("close"); 
        
            856
                    close_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            857
                        this->close(); 
        
            858
                    }); 
        
            859
                    action_group->add_action(close_action); 
        
            860
                    context_menu->append(_("Close"), "win.close"); 
        
            861
                    auto close_all_action = Gio::SimpleAction::create("close-all"); 
        
            863
                    close_all_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            864
                        for(auto page : collect_children(*this->page->get_stack())) { 
        
            865
                            if(auto content_page = dynamic_cast<ContentPage*>(page)) { 
        
            866
                                if(!content_page->signal_close.emit()) { 
        
            867
                                    content_page->redock(nullptr); 
        
            868
                                } 
        
            869
                            } 
        
            870
                        } 
        
            871
                    }); 
        
            872
                    action_group->add_action(close_all_action); 
        
            873
                    context_menu->append(_("Close all"), "win.close-all"); 
        
            874
                    auto close_others_action = Gio::SimpleAction::create("close-others"); 
        
            876
                    close_others_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            877
                        for(auto page : collect_children(*this->page->get_stack())) { 
        
            878
                            if(auto content_page = dynamic_cast<ContentPage*>(page)) { 
        
            879
                                if(content_page != this->page && !content_page->signal_close.emit()) { 
        
            880
                                    content_page->redock(nullptr); 
        
            881
                                } 
        
            882
                            } 
        
            883
                        } 
        
            884
                    }); 
        
            885
                    action_group->add_action(close_others_action); 
        
            886
                    context_menu->append(_("Close others"), "win.close-others"); 
        
            887
                    auto detach_action = Gio::SimpleAction::create("detach"); 
        
            889
                    detach_action->signal_activate().connect([this](const Glib::VariantBase&) { 
        
            890
                        auto stack = dynamic_cast<ContentStack*>(this->page->get_stack()); 
        
            891
                        bool result = stack->signal_detach.emit(this->page); 
        
            892
                    }); 
        
            893
                    action_group->add_action(detach_action); 
        
            894
                    context_menu->append(_("New window"), "win.detach"); 
        
            895
                    // TODO: Add more actions: "New window", "Split left", "Split right", "Split top", "Split bottom", "Close all", "Close others", "Close to the right", "Close to the left" 
        
            897
                    this->insert_action_group("win", action_group); 
        
            898
                    // Attach the context menu to the button 
        
            899
                    auto context_menu_signal = add_context_menu(*this); 
        
            900
                    context_menu_signal.connect([this, action_group](double x, double y) { 
        
            901
                        if(this->context_menu) { 
        
            902
                            auto popover = Gtk::make_managed<Gtk::PopoverMenu>(); 
        
            903
                            popover->set_menu_model(context_menu); 
        
            904
                            popover->set_parent(*this); 
        
            905
                            popover->set_has_arrow(false); 
        
            906
                            popover->set_halign(Gtk::Align::START); 
        
            907
                            popover->set_pointing_to(Gdk::Rectangle(x, y, 1, 1)); 
        
            908
                            popover->popup(); 
        
            909
                        } 
        
            910
                    }); 
        
            911
                    auto middle_click_controller = Gtk::GestureClick::create(); 
        
            912
                    middle_click_controller->set_button(2); 
        
            913
                    middle_click_controller->signal_released().connect([this](int num_presses, double x, double y) { 
        
            914
                        this->close(); 
        
            915
                    }); 
        
            916
                    this->add_controller(middle_click_controller); 
        
            917
                } 
        
            918
                void ContentTab::close() { 
        
            920
                    page->close(); 
        
            921
                } 
        
            922
                void ContentPage::close() { 
        
            924
                    if(!this->signal_close.emit()) { 
        
            925
                        if(this->get_next_sibling()) { 
        
            926
                            this->content_manager->set_last_operated_page(static_cast<ContentPage*>(this->get_next_sibling())); 
        
            927
                            this->get_stack()->set_visible_child(*this->get_next_sibling()); 
        
            928
                        } else if(this->get_prev_sibling()) { 
        
            929
                            this->content_manager->set_last_operated_page(static_cast<ContentPage*>(this->get_prev_sibling())); 
        
            930
                            this->get_stack()->set_visible_child(*this->get_prev_sibling()); 
        
            931
                        } 
        
            932
                        this->redock(nullptr); 
        
            933
                    } 
        
            934
                } 
        
            935
                void ContentTab::update_active_style() { 
        
            937
                    if(this->page->get_stack()->get_visible_child() == this->page) { 
        
            938
                        this->add_css_class("checked"); 
        
            939
                        this->add_css_class("gpanthera-dock-button-active"); 
        
            940
                        this->set_active(true); 
        
            941
                    } else { 
        
            942
                        this->remove_css_class("checked"); 
        
            943
                        this->remove_css_class("gpanthera-dock-button-active"); 
        
            944
                        this->set_active(false); 
        
            945
                    } 
        
            946
                } 
        
            947
                ContentTab::~ContentTab() { 
        
            949
                    active_style_handler.disconnect(); 
        
            950
                    drag_end_handler.disconnect(); 
        
            951
                    drag_cancel_handler.disconnect(); 
        
            952
                } 
        
            953
                Gtk::Widget *ContentPage::get_tab_widget() const { 
        
            955
                    return this->tab_widget; 
        
            956
                } 
        
            957
                ContentStack *ContentPage::get_stack() const { 
        
            959
                    return this->stack; 
        
            960
                } 
        
            961
                void ContentPage::lose_visibility() { 
        
            963
                    if(this->get_next_sibling()) { 
        
            964
                        this->content_manager->set_last_operated_page(static_cast<ContentPage*>(this->get_next_sibling())); 
        
            965
                        this->get_stack()->set_visible_child(*this->get_next_sibling()); 
        
            966
                    } else if(this->get_prev_sibling()) { 
        
            967
                        this->content_manager->set_last_operated_page(static_cast<ContentPage*>(this->get_prev_sibling())); 
        
            968
                        this->get_stack()->set_visible_child(*this->get_prev_sibling()); 
        
            969
                    } 
        
            970
                } 
        
            971
                void ContentPage::redock(ContentStack *stack) { 
        
            973
                    if(stack == nullptr) { 
        
            974
                        if(this->stack) { 
        
            975
                            this->stack->remove(*this); 
        
            976
                            if(dynamic_cast<ContentNotebook*>(this->stack->get_parent()) && !this->stack->get_first_child()) { 
        
            977
                                this->stack->remove_with_paned(); 
        
            978
                            } 
        
            979
                        } 
        
            980
                        auto old_stack = this->stack; 
        
            981
                        if(old_stack) { 
        
            982
                            if(!old_stack->get_first_child()) { 
        
            983
                                old_stack->signal_leave_empty.emit(); 
        
            984
                            } 
        
            985
                        } 
        
            986
                        this->stack = nullptr; 
        
            987
                        this->last_stack = nullptr; 
        
            988
                        return; 
        
            989
                    } 
        
            990
                    // Check if the stack is now empty, in which case we should remove it 
        
            991
                    if(this->stack == stack) { 
        
            992
                        return; 
        
            993
                    } 
        
            994
                    if(this->stack != nullptr) { 
        
            995
                        if(dynamic_cast<ContentNotebook*>(this->stack->get_parent()) && (!this->stack->get_first_child() || (this->stack->get_first_child() == this && !this->stack->get_first_child()->get_next_sibling()))) { 
        
            996
                            this->stack->remove(*this); 
        
            997
                            this->stack->remove_with_paned(); 
        
            998
                        } else if(this->get_parent() == this->stack) { 
        
            999
                            this->stack->remove(*this); 
        
            1000
                        } 
        
            1001
                    } 
        
            1002
                    auto old_stack = this->stack; 
        
            1003
                    this->stack = stack; 
        
            1004
                    this->last_stack = stack; 
        
            1005
                    this->stack->add(*this); 
        
            1006
                    if(old_stack) { 
        
            1007
                        if(!old_stack->get_first_child()) { 
        
            1008
                            old_stack->signal_leave_empty.emit(); 
        
            1009
                        } 
        
            1010
                    } 
        
            1011
                } 
        
            1012
                ContentPage::ContentPage(std::shared_ptr<ContentManager> content_manager, ContentStack *stack, Gtk::Widget *child, Gtk::Widget *tab_widget) : 
        
            1014
                    Gtk::Overlay(), content_manager(std::move(content_manager)), child(child), tab_widget(tab_widget) { 
        
            1015
                    this->set_name("gpanthera_content_page"); 
        
            1016
                    this->set_child(*child); 
        
            1017
                    this->set_tab_widget(tab_widget); 
        
            1018
                    this->set_margin_top(0); 
        
            1019
                    this->set_margin_bottom(0); 
        
            1020
                    this->set_margin_start(0); 
        
            1021
                    this->set_margin_end(0); 
        
            1022
                    if(stack) { 
        
            1023
                        stack->add_page(*this); 
        
            1024
                        this->content_manager->add_stack(this->stack); 
        
            1025
                    } 
        
            1026
                    auto click_controller = Gtk::GestureClick::create(); 
        
            1027
                    click_controller->set_button(0); 
        
            1028
                    click_controller->signal_pressed().connect([this](int num_presses, double x, double y) { 
        
            1029
                        this->content_manager->set_last_operated_page(this); 
        
            1030
                    }); 
        
            1031
                    this->property_has_focus().signal_changed().connect([this]() { 
        
            1032
                        if(this->property_has_focus()) { 
        
            1033
                            this->content_manager->set_last_operated_page(this); 
        
            1034
                        } 
        
            1035
                    }); 
        
            1036
                    click_controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE); 
        
            1037
                    this->add_controller(click_controller); 
        
            1038
                } 
        
            1039
                ContentManager::ContentManager() : Glib::ObjectBase("ContentManager") { 
        
            1041
                } 
        
            1042
                void ContentPage::set_tab_widget(Gtk::Widget *tab_widget) { 
        
            1044
                    this->tab_widget = tab_widget; 
        
            1045
                } 
        
            1046
                ContentWindow::ContentWindow(ContentNotebook *notebook) : notebook(notebook) { 
        
            1048
                    this->set_child(*notebook); 
        
            1049
                    this->set_decorated(true); 
        
            1050
                    this->set_resizable(true); 
        
            1051
                } 
        
            1052
                Gtk::PositionType ContentNotebook::get_tab_position() const { 
        
            1054
                    return this->tab_position; 
        
            1055
                } 
        
            1056
                ContentTabBar *ContentNotebook::get_switcher() const { 
        
            1058
                    return this->switcher; 
        
            1059
                } 
        
            1060
                ContentStack *ContentNotebook::get_stack() const { 
        
            1062
                    return this->stack; 
        
            1063
                } 
        
            1064
                ContentPage *ContentManager::get_last_operated_page() const { 
        
            1066
                    return this->last_operated_page; 
        
            1067
                } 
        
            1068
                void ContentManager::set_last_operated_page(ContentPage *page) { 
        
            1070
                    this->last_operated_page = page; 
        
            1071
                    this->signal_page_operated.emit(page); 
        
            1072
                } 
        
            1073
                DockWindow *DockablePane::get_window() const { 
        
            1075
                    return this->window; 
        
            1076
                } 
        
            1077
                std::string LayoutManager::get_layout_as_json() const { 
        
            1079
                    nlohmann::json json; 
        
            1080
                    std::unordered_set<DockablePane*> panes_set; 
        
            1081
                    for(auto pane: panes) { 
        
            1082
                        panes_set.insert(pane); 
        
            1083
                    } 
        
            1084
                    for(auto stack: stacks) { 
        
            1085
                        for(auto pane: collect_children(*stack)) { 
        
            1086
                            if(dynamic_cast<DockablePane*>(pane)) { 
        
            1087
                                panes_set.erase(dynamic_cast<DockablePane*>(pane)); 
        
            1088
                                json[stack->id].push_back(dynamic_cast<DockablePane*>(pane)->get_identifier()); 
        
            1089
                            } 
        
            1090
                        } 
        
            1091
                    } 
        
            1092
                    for(auto pane: panes_set) { 
        
            1093
                        if(pane->get_window()) { 
        
            1094
                            json[""].push_back(pane->get_identifier()); 
        
            1095
                        } 
        
            1096
                    } 
        
            1097
                    return json.dump(); 
        
            1098
                } 
        
            1099
                void LayoutManager::restore_json_layout(const std::string &json_string) { 
        
            1101
                    nlohmann::json json = nlohmann::json::parse(json_string); 
        
            1102
                    for(auto stack: stacks) { 
        
            1103
                        if(json.contains(stack->id)) { 
        
            1104
                            for(auto const &pane_id: json[stack->id]) { 
        
            1105
                                for(auto pane: panes) { 
        
            1106
                                    // TODO: could probably be done with a better complexity if there are 
        
            1107
                                    // many panes 
        
            1108
                                    if(pane->get_identifier() == static_cast<Glib::ustring>(pane_id.get<std::string>())) { 
        
            1109
                                        stack->add_pane(*pane); 
        
            1110
                                        break; 
        
            1111
                                    } 
        
            1112
                                } 
        
            1113
                            } 
        
            1114
                        } 
        
            1115
                    } 
        
            1116
                    // Process popped-out panes 
        
            1117
                    for(auto const &pane_id: json[""]) { 
        
            1118
                        for(auto pane: panes) { 
        
            1119
                            // TODO: could probably be done with a better complexity if there are 
        
            1120
                            // many panes 
        
            1121
                            if(pane->get_identifier() == static_cast<Glib::ustring>(pane_id.get<std::string>())) { 
        
            1122
                                pane->pop_out(); 
        
            1123
                                break; 
        
            1124
                            } 
        
            1125
                        } 
        
            1126
                    } 
        
            1127
                } 
        
            1128
            } // namespace gPanthera 
        
            1129