/* * Copyright (c) 2026, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace Ladybird { static constexpr int BOOKMARK_BUTTON_MAX_WIDTH = 150; static constexpr int BOOKMARK_BUTTON_ICON_SIZE = 16; static void install_menu_event_filter(QObject* filter, QMenu* menu) { menu->installEventFilter(filter); for (auto* action : menu->actions()) { if (auto* submenu = action->menu()) install_menu_event_filter(filter, submenu); } } BookmarksBar::BookmarksBar(QWidget* parent) : QToolBar(parent) { setIconSize({ BOOKMARK_BUTTON_ICON_SIZE, BOOKMARK_BUTTON_ICON_SIZE }); setVisible(WebView::Application::settings().show_bookmarks_bar()); setMovable(false); installEventFilter(this); rebuild(); } void BookmarksBar::rebuild() { for (auto* action : actions()) { if (auto* menu = action->menu()) menu->close(); } clear(); auto set_button_properties = [&](QToolButton* button, QString const& title) { button->setText(button->fontMetrics().elidedText(title, Qt::ElideRight, BOOKMARK_BUTTON_MAX_WIDTH - 28)); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->setMaximumWidth(BOOKMARK_BUTTON_MAX_WIDTH); button->installEventFilter(this); }; for (auto const& item : WebView::Application::the().bookmarks_menu().items()) { item.visit( [&](NonnullRefPtr const& bookmark) { if (bookmark->id() != WebView::ActionID::BookmarkItem) return; auto* action = create_application_action(*this, *bookmark); addAction(action); if (auto* button = as_if(widgetForAction(action))) set_button_properties(button, qstring_from_ak_string(bookmark->text())); }, [&](NonnullRefPtr const& folder) { auto title = qstring_from_ak_string(folder->title()); auto* submenu = create_application_menu(*this, *folder); install_menu_event_filter(this, submenu); auto* action = new QAction(title, this); action->setIcon(create_tvg_icon_with_theme_colors("folder", palette())); action->setProperty("id", submenu->property("id")); action->setProperty("type", submenu->property("type")); action->setProperty("target_folder_id", submenu->property("target_folder_id")); action->setMenu(submenu); addAction(action); if (auto* button = as_if(widgetForAction(action))) { button->setPopupMode(QToolButton::InstantPopup); set_button_properties(button, title); } }, [](WebView::Separator) { }); } } void BookmarksBar::show_context_menu(QPoint position, Optional item, Optional target_folder_id) { if (item.has_value()) { m_selected_bookmark_menu_item_id = item->id; m_selected_bookmark_menu_target_folder_id = target_folder_id.copy(); if (item->is_bookmark()) bookmark_context_menu().exec(position); else if (item->is_folder()) bookmark_folder_context_menu().exec(position); } else { m_selected_bookmark_menu_item_id = {}; m_selected_bookmark_menu_target_folder_id = {}; bookmarks_bar_context_menu().exec(position); } } bool BookmarksBar::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::MouseButtonPress) { auto& mouse_event = as(*event); if (mouse_event.button() == Qt::LeftButton) return handle_left_mouse_click(&mouse_event, object); if (mouse_event.button() == Qt::MiddleButton) return handle_middle_mouse_click(&mouse_event, object); if (mouse_event.button() == Qt::RightButton) return handle_right_mouse_click(&mouse_event, object); } return QToolBar::eventFilter(object, event); } bool BookmarksBar::handle_left_mouse_click(QMouseEvent* event, QObject* item) { if (event->modifiers().testFlag(Qt::ControlModifier)) return handle_middle_mouse_click(event, item); return false; } bool BookmarksBar::handle_middle_mouse_click(QMouseEvent* event, QObject* item) { auto activate_tab = event->modifiers().testFlag(Qt::ShiftModifier) ? Web::HTML::ActivateTab::No : Web::HTML::ActivateTab::Yes; if (auto* button = as_if(item)) { auto* action = button->defaultAction(); extract_item_properties(action); if (m_selected_bookmark_menu_item_type == "bookmark") WebView::Application::the().open_bookmark_in_new_tab(m_selected_bookmark_menu_item_id, activate_tab); } else if (auto* menu = as_if(item)) { if (auto* action = menu->actionAt(event->pos())) { extract_item_properties(action); if (m_selected_bookmark_menu_item_type == "bookmark") WebView::Application::the().open_bookmark_in_new_tab(m_selected_bookmark_menu_item_id, activate_tab); } } return true; } bool BookmarksBar::handle_right_mouse_click(QMouseEvent* event, QObject* item) { if (is(item)) { m_selected_bookmark_menu_item_id = {}; m_selected_bookmark_menu_target_folder_id = {}; bookmarks_bar_context_menu().exec(event->globalPosition().toPoint()); } else if (auto* button = as_if(item)) { auto* action = button->defaultAction(); extract_item_properties(action); if (m_selected_bookmark_menu_item_type == "bookmark") bookmark_context_menu().exec(event->globalPosition().toPoint()); else if (m_selected_bookmark_menu_item_type == "folder") bookmark_folder_context_menu().exec(event->globalPosition().toPoint()); } else if (auto* menu = as_if(item)) { if (auto* action = menu->actionAt(event->pos())) { QObject* submenu = action->menu(); extract_item_properties(submenu ?: action); } if (m_selected_bookmark_menu_item_type.isEmpty()) extract_item_properties(menu); // FIXME: We create a temporary context menu parented to the dropdown. Otherwise, Qt complains that the context // menu's parent does not match the current topmost popup. It would be nice if we could figure out a way // to avoid this duplicated menu. QMenu context_menu(menu); if (m_selected_bookmark_menu_item_type == "bookmark") repopulate_application_menu(context_menu, context_menu, WebView::Application::the().bookmark_context_menu()); else if (m_selected_bookmark_menu_item_type == "folder") repopulate_application_menu(context_menu, context_menu, WebView::Application::the().bookmark_folder_context_menu()); if (!context_menu.isEmpty() && context_menu.exec(event->globalPosition().toPoint())) menu->close(); } return true; } void BookmarksBar::extract_item_properties(QObject* item) { m_selected_bookmark_menu_item_id = ak_string_from_qstring(item->property("id").toString()); m_selected_bookmark_menu_item_type = item->property("type").toString(); if (auto value = ak_string_from_qstring(item->property("target_folder_id").toString()); !value.is_empty()) m_selected_bookmark_menu_target_folder_id = AK::move(value); } QMenu& BookmarksBar::bookmarks_bar_context_menu() { if (!m_bookmarks_bar_context_menu) m_bookmarks_bar_context_menu = create_application_menu(*this, WebView::Application::the().bookmarks_bar_context_menu()); return *m_bookmarks_bar_context_menu; } QMenu& BookmarksBar::bookmark_context_menu() { if (!m_bookmark_context_menu) m_bookmark_context_menu = create_application_menu(*this, WebView::Application::the().bookmark_context_menu()); return *m_bookmark_context_menu; } QMenu& BookmarksBar::bookmark_folder_context_menu() { if (!m_bookmark_folder_context_menu) m_bookmark_folder_context_menu = create_application_menu(*this, WebView::Application::the().bookmark_folder_context_menu()); return *m_bookmark_folder_context_menu; } }