Falkon Develop
Cross-platform Qt-based web browser
tablistview.cpp
Go to the documentation of this file.
1/* ============================================================
2* VerticalTabs plugin for Falkon
3* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "tablistview.h"
19#include "tablistdelegate.h"
20#include "loadinganimator.h"
21
22#include "tabmodel.h"
23#include "webtab.h"
24#include "tabcontextmenu.h"
25
26#include <QTimer>
27#include <QToolTip>
28#include <QHoverEvent>
29
30TabListView::TabListView(BrowserWindow *window, QWidget *parent)
31 : QListView(parent)
32 , m_window(window)
33{
34 setDragEnabled(true);
35 setAcceptDrops(true);
36 setUniformItemSizes(true);
37 setDropIndicatorShown(true);
38 setMouseTracking(true);
39 setFlow(QListView::LeftToRight);
40 setFocusPolicy(Qt::NoFocus);
41 setFrameShape(QFrame::NoFrame);
42 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
43 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
44 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
45
46 m_delegate = new TabListDelegate(this);
47 setItemDelegate(m_delegate);
48
49 updateHeight();
50}
51
53{
54 return m_hideWhenEmpty;
55}
56
58{
59 m_hideWhenEmpty = enable;
60 updateVisibility();
61}
62
63void TabListView::updateIndex(const QModelIndex &index)
64{
65 QRect rect = visualRect(index);
66 if (!rect.isValid()) {
67 return;
68 }
69 // Need to update a little above/under to account for negative margins
70 rect.moveTop(rect.y() - rect.height() / 2);
71 rect.setHeight(rect.height() * 2);
72 viewport()->update(rect);
73}
74
75void TabListView::adjustStyleOption(QStyleOptionViewItem *option)
76{
77 const QModelIndex index = option->index;
78
79 option->state.setFlag(QStyle::State_Active, true);
80 option->state.setFlag(QStyle::State_HasFocus, false);
81 option->state.setFlag(QStyle::State_Selected, index.data(TabModel::CurrentTabRole).toBool());
82
83 if (!index.isValid()) {
84 option->viewItemPosition = QStyleOptionViewItem::Invalid;
85 } else if (model()->rowCount() == 1) {
86 option->viewItemPosition = QStyleOptionViewItem::OnlyOne;
87 } else {
88 if (!indexBefore(index).isValid()) {
89 option->viewItemPosition = QStyleOptionViewItem::Beginning;
90 } else if (!indexAfter(index).isValid()) {
91 option->viewItemPosition = QStyleOptionViewItem::End;
92 } else {
93 option->viewItemPosition = QStyleOptionViewItem::Middle;
94 }
95 }
96}
97
98QModelIndex TabListView::indexAfter(const QModelIndex &index) const
99{
100 if (!index.isValid()) {
101 return {};
102 }
103 const QRect rect = visualRect(index);
104 return indexAt(QPoint(rect.right() + rect.width() / 2, rect.y()));
105}
106
107QModelIndex TabListView::indexBefore(const QModelIndex &index) const
108{
109 if (!index.isValid()) {
110 return {};
111 }
112 const QRect rect = visualRect(index);
113 return indexAt(QPoint(rect.left() - rect.width() / 2, rect.y()));
114}
115
116void TabListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
117{
118 if (current.data(TabModel::CurrentTabRole).toBool()) {
119 QListView::currentChanged(current, previous);
120 } else if (previous.data(TabModel::CurrentTabRole).toBool()) {
121 setCurrentIndex(previous);
122 }
123}
124
125void TabListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
126{
127 QListView::dataChanged(topLeft, bottomRight, roles);
128
129 if (roles.size() == 1 && roles.at(0) == TabModel::CurrentTabRole && topLeft.data(TabModel::CurrentTabRole).toBool()) {
130 setCurrentIndex(topLeft);
131 }
132}
133
134void TabListView::rowsInserted(const QModelIndex &parent, int start, int end)
135{
136 QListView::rowsInserted(parent, start, end);
137
138 updateVisibility();
139}
140
141void TabListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
142{
143 QListView::rowsAboutToBeRemoved(parent, start, end);
144
145 QTimer::singleShot(0, this, &TabListView::updateVisibility);
146}
147
148bool TabListView::viewportEvent(QEvent *event)
149{
150 switch (event->type()) {
151 case QEvent::MouseButtonPress: {
152 auto *me = static_cast<QMouseEvent*>(event);
153 const QModelIndex index = indexAt(me->pos());
154 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
155 if (me->buttons() == Qt::MiddleButton && tab) {
156 tab->closeTab();
157 }
158 if (me->buttons() != Qt::LeftButton) {
159 m_pressedIndex = QModelIndex();
160 m_pressedButton = NoButton;
161 break;
162 }
163 m_pressedIndex = index;
164 m_pressedButton = buttonAt(me->pos(), m_pressedIndex);
165 if (m_pressedButton == NoButton && tab) {
166 tab->makeCurrentTab();
167 }
168 break;
169 }
170
171 case QEvent::MouseButtonRelease: {
172 auto *me = static_cast<QMouseEvent*>(event);
173 if (me->buttons() != Qt::NoButton) {
174 break;
175 }
176 const QModelIndex index = indexAt(me->pos());
177 if (m_pressedIndex != index) {
178 break;
179 }
180 DelegateButton button = buttonAt(me->pos(), index);
181 if (m_pressedButton == button) {
182 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
183 if (tab && m_pressedButton == AudioButton) {
184 tab->toggleMuted();
185 }
186 }
187 break;
188 }
189
190 case QEvent::ToolTip: {
191 auto *he = static_cast<QHelpEvent*>(event);
192 const QModelIndex index = indexAt(he->pos());
193 DelegateButton button = buttonAt(he->pos(), index);
194 if (button == AudioButton) {
195 const bool muted = index.data(TabModel::AudioMutedRole).toBool();
196 QToolTip::showText(he->globalPos(), muted ? tr("Unmute Tab") : tr("Mute Tab"), this, visualRect(index));
197 he->accept();
198 return true;
199 } else if (button == NoButton) {
200 QToolTip::showText(he->globalPos(), index.data().toString(), this, visualRect(index));
201 he->accept();
202 return true;
203 }
204 break;
205 }
206
207 case QEvent::ContextMenu: {
208 auto *ce = static_cast<QContextMenuEvent*>(event);
209 const QModelIndex index = indexAt(ce->pos());
210 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
211 const int tabIndex = tab ? tab->tabIndex() : -1;
213 TabContextMenu menu(tabIndex, m_window, options);
214 menu.exec(ce->globalPos());
215 break;
216 }
217
218 case QEvent::StyleChange:
219 updateHeight();
220 break;
221
222 default:
223 break;
224 }
225 return QListView::viewportEvent(event);
226}
227
228TabListView::DelegateButton TabListView::buttonAt(const QPoint &pos, const QModelIndex &index) const
229{
230 if (m_delegate->audioButtonRect(index).contains(pos)) {
231 return AudioButton;
232 }
233 return NoButton;
234}
235
236void TabListView::updateVisibility()
237{
238 setVisible(!m_hideWhenEmpty || model()->rowCount() > 0);
239}
240
241void TabListView::updateHeight()
242{
243 QStyleOptionViewItem option;
244 initViewItemOption(&option);
245 setFixedHeight(m_delegate->sizeHint(option, QModelIndex()).height());
246}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
QRect audioButtonRect(const QModelIndex &index) const
void adjustStyleOption(QStyleOptionViewItem *option)
Definition: tablistview.cpp:75
TabListView(BrowserWindow *window, QWidget *parent=nullptr)
Definition: tablistview.cpp:30
QModelIndex indexBefore(const QModelIndex &index) const
void updateIndex(const QModelIndex &index)
Definition: tablistview.cpp:63
bool isHidingWhenEmpty() const
Definition: tablistview.cpp:52
QModelIndex indexAfter(const QModelIndex &index) const
Definition: tablistview.cpp:98
void setHideWhenEmpty(bool enable)
Definition: tablistview.cpp:57
@ CurrentTabRole
Definition: tabmodel.h:59
@ WebTabRole
Definition: tabmodel.h:54
@ AudioMutedRole
Definition: tabmodel.h:62
Definition: webtab.h:40
int tabIndex() const
Definition: webtab.cpp:710
void toggleMuted()
Definition: webtab.cpp:448
void closeTab()
Definition: webtab.cpp:696