34 , m_expandedSessionKey(
QSL(
"VerticalTabs-expanded"))
38 setHeaderHidden(
true);
39 setUniformRowHeights(
true);
40 setDropIndicatorShown(
true);
41 setAllColumnsShowFocus(
true);
42 setMouseTracking(
true);
43 setFocusPolicy(Qt::NoFocus);
44 setFrameShape(QFrame::NoFrame);
45 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
46 setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
50 setItemDelegate(m_delegate);
53 setLayoutDirection(isRightToLeft() ? Qt::LeftToRight : Qt::RightToLeft);
56 viewport()->setAttribute(Qt::WA_Hover);
58 auto saveExpandedState = [
this](
const QModelIndex &index,
bool expanded) {
67 connect(
this, &TabTreeView::expanded,
this, std::bind(saveExpandedState, std::placeholders::_1,
true));
68 connect(
this, &TabTreeView::collapsed,
this, std::bind(saveExpandedState, std::placeholders::_1,
false));
73 return m_backgroundIndentation;
78 m_backgroundIndentation = indentation;
88 m_tabsInOrder = enable;
93 return m_haveTreeModel;
98 m_haveTreeModel = enable;
103 QTreeView::setModel(model);
105 m_initializing =
true;
106 QTimer::singleShot(0,
this, &TabTreeView::initView);
111 QRect rect = visualRect(index);
112 if (!rect.isValid()) {
116 rect.moveTop(rect.y() - rect.height() / 2);
117 rect.setHeight(rect.height() * 2);
118 viewport()->update(rect);
123 const QModelIndex index = option->index;
125 option->state.setFlag(QStyle::State_Active,
true);
126 option->state.setFlag(QStyle::State_HasFocus,
false);
129 if (!index.isValid()) {
130 option->viewItemPosition = QStyleOptionViewItem::Invalid;
131 }
else if (model()->rowCount() == 1) {
132 option->viewItemPosition = QStyleOptionViewItem::OnlyOne;
134 if (!indexAbove(index).isValid()) {
135 option->viewItemPosition = QStyleOptionViewItem::Beginning;
136 }
else if (!indexBelow(index).isValid()) {
137 option->viewItemPosition = QStyleOptionViewItem::End;
139 option->viewItemPosition = QStyleOptionViewItem::Middle;
144void TabTreeView::drawBranches(QPainter *,
const QRect &,
const QModelIndex &)
const
149void TabTreeView::currentChanged(
const QModelIndex ¤t,
const QModelIndex &previous)
152 QTreeView::currentChanged(current, previous);
154 setCurrentIndex(previous);
158void TabTreeView::dataChanged(
const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles)
160 QTreeView::dataChanged(topLeft, bottomRight, roles);
163 setCurrentIndex(topLeft);
167void TabTreeView::rowsInserted(
const QModelIndex &parent,
int start,
int end)
169 QTreeView::rowsInserted(parent, start, end);
171 if (m_initializing) {
176 const QPersistentModelIndex index = model()->index(start, 0, parent);
177 QTimer::singleShot(0,
this, [=]() {
178 if (!index.isValid()) {
181 QModelIndex idx = index;
182 QVector<QModelIndex> stack;
186 } while (idx.isValid());
187 for (
const QModelIndex &index : std::as_const(stack)) {
191 setCurrentIndex(index);
196bool TabTreeView::viewportEvent(QEvent *event)
198 switch (event->type()) {
199 case QEvent::MouseButtonPress: {
200 auto *me =
static_cast<QMouseEvent*
>(event);
201 const QModelIndex index = indexAt(me->pos());
204 if (me->buttons() == Qt::MiddleButton) {
206 if (isExpanded(index)) {
215 if (me->buttons() != Qt::LeftButton) {
216 m_pressedIndex = QModelIndex();
217 m_pressedButton = NoButton;
220 m_pressedIndex = index;
221 m_pressedButton = buttonAt(me->pos(), m_pressedIndex);
222 if (m_pressedIndex.isValid()) {
223 if (m_pressedButton == ExpandButton) {
224 if (isExpanded(m_pressedIndex)) {
225 collapse(m_pressedIndex);
227 expand(m_pressedIndex);
231 }
else if (m_pressedButton == NoButton && tab) {
232 tab->makeCurrentTab();
242 case QEvent::MouseMove: {
243 auto *me =
static_cast<QMouseEvent*
>(event);
251 case QEvent::MouseButtonRelease: {
252 auto *me =
static_cast<QMouseEvent*
>(event);
253 if (me->buttons() != Qt::NoButton) {
256 const QModelIndex index = indexAt(me->pos());
258 if (m_pressedIndex != index) {
261 DelegateButton button = buttonAt(me->pos(), index);
262 if (m_pressedButton == button) {
263 if (m_pressedButton == ExpandButton) {
271 }
else if (m_pressedButton == AudioButton) {
283 case QEvent::MouseButtonDblClick: {
284 auto *me =
static_cast<QMouseEvent*
>(event);
285 const QModelIndex index = indexAt(me->pos());
286 if (me->button() == Qt::LeftButton && !index.isValid()) {
292 case QEvent::HoverEnter:
293 case QEvent::HoverLeave:
294 case QEvent::HoverMove: {
295 auto *he =
static_cast<QHoverEvent*
>(event);
297 m_hoveredIndex = indexAt(he->position().toPoint());
302 case QEvent::ToolTip: {
303 auto *he =
static_cast<QHelpEvent*
>(event);
304 const QModelIndex index = indexAt(he->pos());
305 DelegateButton button = buttonAt(he->pos(), index);
306 if (button == AudioButton) {
308 QToolTip::showText(he->globalPos(), muted ? tr(
"Unmute Tab") : tr(
"Mute Tab"),
this, visualRect(index));
312 QToolTip::showText(he->globalPos(), tr(
"Close Tab"),
this, visualRect(index));
315 }
else if (button == NoButton) {
316 QToolTip::showText(he->globalPos(), index.data().toString(),
this, visualRect(index));
323 case QEvent::ContextMenu: {
324 auto *ce =
static_cast<QContextMenuEvent*
>(event);
325 const QModelIndex index = indexAt(ce->pos());
327 const int tabIndex = tab ? tab->
tabIndex() : -1;
333 addMenuActions(&menu, index);
334 menu.exec(ce->globalPos());
341 return QTreeView::viewportEvent(event);
344void TabTreeView::initView()
347 for (
int i = 0;
i < model()->rowCount(); ++
i) {
348 const QModelIndex index = model()->index(
i, 0);
349 reverseTraverse(index, [
this](
const QModelIndex &index) {
352 setExpanded(index, tab->sessionData().value(m_expandedSessionKey,
true).toBool());
357 m_initializing =
false;
360TabTreeView::DelegateButton TabTreeView::buttonAt(
const QPoint &pos,
const QModelIndex &index)
const
363 if (model()->rowCount(index) > 0) {
374void TabTreeView::addMenuActions(QMenu *menu,
const QModelIndex &index)
376 if (!m_haveTreeModel) {
380 menu->addSeparator();
381 QMenu *m = menu->addMenu(tr(
"Tab Tree"));
383 if (index.isValid() && model()->rowCount(index) > 0) {
384 QPersistentModelIndex pindex = index;
385 m->addAction(tr(
"Close Tree"),
this, [=]() {
388 m->addAction(tr(
"Unload Tree"),
this, [=]() {
394 m->addAction(tr(
"Expand All"),
this, &TabTreeView::expandAll);
395 m->addAction(tr(
"Collapse All"),
this, &TabTreeView::collapseAll);
398void TabTreeView::reverseTraverse(
const QModelIndex &root,
const std::function<
void(
const QModelIndex&)> &callback)
const
400 if (!root.isValid()) {
403 for (
int i = 0;
i < model()->rowCount(root); ++
i) {
404 reverseTraverse(model()->index(
i, 0, root), callback);
409void TabTreeView::closeTree(
const QModelIndex &root)
411 QVector<WebTab*> tabs;
412 reverseTraverse(root, [&](
const QModelIndex &index) {
418 for (
WebTab *tab : std::as_const(tabs)) {
423void TabTreeView::unloadTree(
const QModelIndex &root)
425 reverseTraverse(root, [&](
const QModelIndex &index) {
427 if (tab && tab->isRestored()) {
QRect audioButtonRect(const QModelIndex &index) const
QRect closeButtonRect(const QModelIndex &index) const
QRect expandButtonRect(const QModelIndex &index) const
int backgroundIndentation
void setModel(QAbstractItemModel *model) override
void setHaveTreeModel(bool enable)
bool areTabsInOrder() const
TabTreeView(BrowserWindow *window, QWidget *parent=nullptr)
bool haveTreeModel() const
void updateIndex(const QModelIndex &index)
void setBackgroundIndentation(int indentation)
void setTabsInOrder(bool enable)
void adjustStyleOption(QStyleOptionViewItem *option)
void setSessionData(const QString &key, const QVariant &value)