Files
ladybird/UI/Qt/TabBar.cpp
Timothy Flynn 0a3e2196c9 UI/Qt: Store the TabWidget parent directly on TabBar
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.
2026-03-22 18:36:36 -04:00

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);
}
}