mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-13 18:36:38 +02:00
The parent owner of the TabBar changes as it is added to layout objects within other widgets. So the parent we set at the beginning is no longer the parent when we right-click on a tab. We now just store the TabWidget directly.
248 lines
6.8 KiB
C++
248 lines
6.8 KiB
C++
/*
|
|
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
|
|
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
|
|
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StdLibExtras.h>
|
|
#include <UI/Qt/Icon.h>
|
|
#include <UI/Qt/Tab.h>
|
|
#include <UI/Qt/TabBar.h>
|
|
|
|
#include <QContextMenuEvent>
|
|
#include <QEvent>
|
|
#include <QHBoxLayout>
|
|
#include <QMouseEvent>
|
|
#include <QToolButton>
|
|
#include <QVBoxLayout>
|
|
|
|
namespace Ladybird {
|
|
|
|
TabBar::TabBar(TabWidget* tab_widget)
|
|
: QTabBar(tab_widget)
|
|
, m_tab_widget(tab_widget)
|
|
{
|
|
}
|
|
|
|
void TabBar::set_available_width(int width)
|
|
{
|
|
if (m_available_width != width) {
|
|
m_available_width = width;
|
|
updateGeometry();
|
|
}
|
|
}
|
|
|
|
QSize TabBar::tabSizeHint(int index) const
|
|
{
|
|
auto hint = QTabBar::tabSizeHint(index);
|
|
|
|
if (auto count = this->count(); count > 0) {
|
|
auto width = (m_available_width > 0 ? m_available_width : this->width()) / count;
|
|
width = min(225, width);
|
|
width = max(128, width);
|
|
|
|
hint.setWidth(width);
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
void TabBar::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
if (!m_tab_widget)
|
|
return;
|
|
|
|
if (auto* tab = m_tab_widget->tab(tabAt(event->pos())))
|
|
tab->context_menu()->exec(event->globalPos());
|
|
}
|
|
|
|
void TabBar::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
event->ignore();
|
|
|
|
auto rect_of_current_tab = tabRect(tabAt(event->pos()));
|
|
m_x_position_in_selected_tab_while_dragging = event->pos().x() - rect_of_current_tab.x();
|
|
|
|
QTabBar::mousePressEvent(event);
|
|
}
|
|
|
|
void TabBar::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
event->ignore();
|
|
|
|
auto rect_of_first_tab = tabRect(0);
|
|
auto rect_of_last_tab = tabRect(count() - 1);
|
|
|
|
auto boundary_limit_for_dragging_tab = QRect(rect_of_first_tab.x() + m_x_position_in_selected_tab_while_dragging, 0,
|
|
rect_of_last_tab.x() + m_x_position_in_selected_tab_while_dragging, 0);
|
|
|
|
if (event->pos().x() >= boundary_limit_for_dragging_tab.x() && event->pos().x() <= boundary_limit_for_dragging_tab.width()) {
|
|
QTabBar::mouseMoveEvent(event);
|
|
} else {
|
|
auto pos = event->pos();
|
|
if (event->pos().x() > boundary_limit_for_dragging_tab.width())
|
|
pos.setX(boundary_limit_for_dragging_tab.width());
|
|
else if (event->pos().x() < boundary_limit_for_dragging_tab.x())
|
|
pos.setX(boundary_limit_for_dragging_tab.x());
|
|
QMouseEvent ev(event->type(), pos, event->globalPosition(), event->button(), event->buttons(), event->modifiers());
|
|
QTabBar::mouseMoveEvent(&ev);
|
|
}
|
|
}
|
|
|
|
TabWidget::TabWidget(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
m_tab_bar = new TabBar(this);
|
|
m_tab_bar->setDocumentMode(true);
|
|
m_tab_bar->setElideMode(Qt::TextElideMode::ElideRight);
|
|
m_tab_bar->setMovable(true);
|
|
m_tab_bar->setTabsClosable(true);
|
|
m_tab_bar->setExpanding(false);
|
|
m_tab_bar->setUsesScrollButtons(true);
|
|
m_tab_bar->setDrawBase(false);
|
|
|
|
m_stacked_widget = new QStackedWidget(this);
|
|
|
|
m_new_tab_button = new QToolButton(this);
|
|
m_new_tab_button->setIconSize(QSize(16, 16));
|
|
m_new_tab_button->setAutoRaise(true);
|
|
m_new_tab_button->setToolTip("New Tab");
|
|
|
|
recreate_icons();
|
|
|
|
auto* tab_bar_row_layout = new QHBoxLayout();
|
|
tab_bar_row_layout->setSpacing(0);
|
|
tab_bar_row_layout->setContentsMargins(0, 0, 0, 0);
|
|
tab_bar_row_layout->addWidget(m_tab_bar);
|
|
tab_bar_row_layout->addWidget(m_new_tab_button);
|
|
tab_bar_row_layout->addStretch(1);
|
|
|
|
m_tab_bar_row = new QWidget(this);
|
|
m_tab_bar_row->setLayout(tab_bar_row_layout);
|
|
|
|
auto* main_layout = new QVBoxLayout(this);
|
|
main_layout->setSpacing(0);
|
|
main_layout->setContentsMargins(0, 0, 0, 0);
|
|
main_layout->addWidget(m_tab_bar_row);
|
|
main_layout->addWidget(m_stacked_widget, 1);
|
|
|
|
connect(m_tab_bar, &QTabBar::currentChanged, this, [this](int index) {
|
|
if (index >= 0 && index < m_stacked_widget->count())
|
|
m_stacked_widget->setCurrentIndex(index);
|
|
|
|
emit current_tab_changed(index);
|
|
});
|
|
|
|
connect(m_tab_bar, &QTabBar::tabCloseRequested, this, &TabWidget::tab_close_requested);
|
|
|
|
connect(m_tab_bar, &QTabBar::tabMoved, this, [this](int from, int to) {
|
|
ScopeGuard guard { [&]() { m_stacked_widget->blockSignals(false); } };
|
|
m_stacked_widget->blockSignals(true);
|
|
|
|
auto* widget = m_stacked_widget->widget(from);
|
|
m_stacked_widget->removeWidget(widget);
|
|
m_stacked_widget->insertWidget(to, widget);
|
|
m_stacked_widget->setCurrentIndex(m_tab_bar->currentIndex());
|
|
});
|
|
}
|
|
|
|
void TabWidget::add_tab(Tab* widget, QString const& label)
|
|
{
|
|
m_stacked_widget->addWidget(widget);
|
|
m_tab_bar->addTab(label);
|
|
|
|
update_tab_layout();
|
|
}
|
|
|
|
void TabWidget::remove_tab(int index)
|
|
{
|
|
auto* widget = m_stacked_widget->widget(index);
|
|
m_stacked_widget->removeWidget(widget);
|
|
|
|
m_tab_bar->removeTab(index);
|
|
|
|
if (m_tab_bar->count() > 0 && m_tab_bar->currentIndex() >= 0)
|
|
m_stacked_widget->setCurrentIndex(m_tab_bar->currentIndex());
|
|
|
|
update_tab_layout();
|
|
}
|
|
|
|
void TabWidget::set_current_tab(Tab* widget)
|
|
{
|
|
if (auto index = m_stacked_widget->indexOf(widget); index >= 0)
|
|
set_current_index(index);
|
|
}
|
|
|
|
int TabWidget::index_of(Tab* widget) const
|
|
{
|
|
return m_stacked_widget->indexOf(widget);
|
|
}
|
|
|
|
void TabWidget::set_new_tab_action(QAction* action)
|
|
{
|
|
disconnect(m_new_tab_button, &QToolButton::clicked, nullptr, nullptr);
|
|
|
|
if (!action)
|
|
return;
|
|
|
|
connect(m_new_tab_button, &QToolButton::clicked, action, &QAction::trigger);
|
|
}
|
|
|
|
bool TabWidget::event(QEvent* event)
|
|
{
|
|
if (auto type = event->type(); type == QEvent::MouseButtonRelease) {
|
|
auto const* mouse_event = static_cast<QMouseEvent const*>(event);
|
|
|
|
if (mouse_event->button() == Qt::MiddleButton) {
|
|
if (auto index = m_tab_bar->tabAt(mouse_event->pos()); index != -1) {
|
|
emit tab_close_requested(index);
|
|
return true;
|
|
}
|
|
}
|
|
} else if (type == QEvent::PaletteChange) {
|
|
recreate_icons();
|
|
}
|
|
|
|
return QWidget::event(event);
|
|
}
|
|
|
|
void TabWidget::resizeEvent(QResizeEvent* event)
|
|
{
|
|
QWidget::resizeEvent(event);
|
|
update_tab_layout();
|
|
}
|
|
|
|
void TabWidget::update_tab_layout()
|
|
{
|
|
auto button_width = m_new_tab_button->sizeHint().width();
|
|
auto available_for_tabs = width() - button_width;
|
|
|
|
m_tab_bar->set_available_width(available_for_tabs);
|
|
}
|
|
|
|
void TabWidget::recreate_icons()
|
|
{
|
|
m_new_tab_button->setIcon(create_tvg_icon_with_theme_colors("new_tab", palette()));
|
|
}
|
|
|
|
TabBarButton::TabBarButton(QIcon const& icon, QWidget* parent)
|
|
: QPushButton(icon, {}, parent)
|
|
{
|
|
resize({ 20, 20 });
|
|
setFlat(true);
|
|
}
|
|
|
|
bool TabBarButton::event(QEvent* event)
|
|
{
|
|
if (event->type() == QEvent::Enter)
|
|
setFlat(false);
|
|
if (event->type() == QEvent::Leave)
|
|
setFlat(true);
|
|
|
|
return QPushButton::event(event);
|
|
}
|
|
|
|
}
|