File indexing completed on 2024-05-19 04:59:21
0001 /* ============================================================ 0002 * VerticalTabs plugin for Falkon 0003 * Copyright (C) 2018 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "tablistview.h" 0019 #include "tablistdelegate.h" 0020 #include "loadinganimator.h" 0021 0022 #include "tabmodel.h" 0023 #include "webtab.h" 0024 #include "tabcontextmenu.h" 0025 0026 #include <QTimer> 0027 #include <QToolTip> 0028 #include <QHoverEvent> 0029 0030 TabListView::TabListView(BrowserWindow *window, QWidget *parent) 0031 : QListView(parent) 0032 , m_window(window) 0033 { 0034 setDragEnabled(true); 0035 setAcceptDrops(true); 0036 setUniformItemSizes(true); 0037 setDropIndicatorShown(true); 0038 setMouseTracking(true); 0039 setFlow(QListView::LeftToRight); 0040 setFocusPolicy(Qt::NoFocus); 0041 setFrameShape(QFrame::NoFrame); 0042 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0043 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0044 setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); 0045 0046 m_delegate = new TabListDelegate(this); 0047 setItemDelegate(m_delegate); 0048 0049 updateHeight(); 0050 } 0051 0052 bool TabListView::isHidingWhenEmpty() const 0053 { 0054 return m_hideWhenEmpty; 0055 } 0056 0057 void TabListView::setHideWhenEmpty(bool enable) 0058 { 0059 m_hideWhenEmpty = enable; 0060 updateVisibility(); 0061 } 0062 0063 void TabListView::updateIndex(const QModelIndex &index) 0064 { 0065 QRect rect = visualRect(index); 0066 if (!rect.isValid()) { 0067 return; 0068 } 0069 // Need to update a little above/under to account for negative margins 0070 rect.moveTop(rect.y() - rect.height() / 2); 0071 rect.setHeight(rect.height() * 2); 0072 viewport()->update(rect); 0073 } 0074 0075 void TabListView::adjustStyleOption(QStyleOptionViewItem *option) 0076 { 0077 const QModelIndex index = option->index; 0078 0079 option->state.setFlag(QStyle::State_Active, true); 0080 option->state.setFlag(QStyle::State_HasFocus, false); 0081 option->state.setFlag(QStyle::State_Selected, index.data(TabModel::CurrentTabRole).toBool()); 0082 0083 if (!index.isValid()) { 0084 option->viewItemPosition = QStyleOptionViewItem::Invalid; 0085 } else if (model()->rowCount() == 1) { 0086 option->viewItemPosition = QStyleOptionViewItem::OnlyOne; 0087 } else { 0088 if (!indexBefore(index).isValid()) { 0089 option->viewItemPosition = QStyleOptionViewItem::Beginning; 0090 } else if (!indexAfter(index).isValid()) { 0091 option->viewItemPosition = QStyleOptionViewItem::End; 0092 } else { 0093 option->viewItemPosition = QStyleOptionViewItem::Middle; 0094 } 0095 } 0096 } 0097 0098 QModelIndex TabListView::indexAfter(const QModelIndex &index) const 0099 { 0100 if (!index.isValid()) { 0101 return {}; 0102 } 0103 const QRect rect = visualRect(index); 0104 return indexAt(QPoint(rect.right() + rect.width() / 2, rect.y())); 0105 } 0106 0107 QModelIndex TabListView::indexBefore(const QModelIndex &index) const 0108 { 0109 if (!index.isValid()) { 0110 return {}; 0111 } 0112 const QRect rect = visualRect(index); 0113 return indexAt(QPoint(rect.left() - rect.width() / 2, rect.y())); 0114 } 0115 0116 void TabListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) 0117 { 0118 if (current.data(TabModel::CurrentTabRole).toBool()) { 0119 QListView::currentChanged(current, previous); 0120 } else if (previous.data(TabModel::CurrentTabRole).toBool()) { 0121 setCurrentIndex(previous); 0122 } 0123 } 0124 0125 void TabListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) 0126 { 0127 QListView::dataChanged(topLeft, bottomRight, roles); 0128 0129 if (roles.size() == 1 && roles.at(0) == TabModel::CurrentTabRole && topLeft.data(TabModel::CurrentTabRole).toBool()) { 0130 setCurrentIndex(topLeft); 0131 } 0132 } 0133 0134 void TabListView::rowsInserted(const QModelIndex &parent, int start, int end) 0135 { 0136 QListView::rowsInserted(parent, start, end); 0137 0138 updateVisibility(); 0139 } 0140 0141 void TabListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) 0142 { 0143 QListView::rowsAboutToBeRemoved(parent, start, end); 0144 0145 QTimer::singleShot(0, this, &TabListView::updateVisibility); 0146 } 0147 0148 bool TabListView::viewportEvent(QEvent *event) 0149 { 0150 switch (event->type()) { 0151 case QEvent::MouseButtonPress: { 0152 auto *me = static_cast<QMouseEvent*>(event); 0153 const QModelIndex index = indexAt(me->pos()); 0154 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>(); 0155 if (me->buttons() == Qt::MiddleButton && tab) { 0156 tab->closeTab(); 0157 } 0158 if (me->buttons() != Qt::LeftButton) { 0159 m_pressedIndex = QModelIndex(); 0160 m_pressedButton = NoButton; 0161 break; 0162 } 0163 m_pressedIndex = index; 0164 m_pressedButton = buttonAt(me->pos(), m_pressedIndex); 0165 if (m_pressedButton == NoButton && tab) { 0166 tab->makeCurrentTab(); 0167 } 0168 break; 0169 } 0170 0171 case QEvent::MouseButtonRelease: { 0172 auto *me = static_cast<QMouseEvent*>(event); 0173 if (me->buttons() != Qt::NoButton) { 0174 break; 0175 } 0176 const QModelIndex index = indexAt(me->pos()); 0177 if (m_pressedIndex != index) { 0178 break; 0179 } 0180 DelegateButton button = buttonAt(me->pos(), index); 0181 if (m_pressedButton == button) { 0182 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>(); 0183 if (tab && m_pressedButton == AudioButton) { 0184 tab->toggleMuted(); 0185 } 0186 } 0187 break; 0188 } 0189 0190 case QEvent::ToolTip: { 0191 auto *he = static_cast<QHelpEvent*>(event); 0192 const QModelIndex index = indexAt(he->pos()); 0193 DelegateButton button = buttonAt(he->pos(), index); 0194 if (button == AudioButton) { 0195 const bool muted = index.data(TabModel::AudioMutedRole).toBool(); 0196 QToolTip::showText(he->globalPos(), muted ? tr("Unmute Tab") : tr("Mute Tab"), this, visualRect(index)); 0197 he->accept(); 0198 return true; 0199 } else if (button == NoButton) { 0200 QToolTip::showText(he->globalPos(), index.data().toString(), this, visualRect(index)); 0201 he->accept(); 0202 return true; 0203 } 0204 break; 0205 } 0206 0207 case QEvent::ContextMenu: { 0208 auto *ce = static_cast<QContextMenuEvent*>(event); 0209 const QModelIndex index = indexAt(ce->pos()); 0210 auto *tab = index.data(TabModel::WebTabRole).value<WebTab*>(); 0211 const int tabIndex = tab ? tab->tabIndex() : -1; 0212 TabContextMenu::Options options = TabContextMenu::HorizontalTabs | TabContextMenu::ShowDetachTabAction; 0213 TabContextMenu menu(tabIndex, m_window, options); 0214 menu.exec(ce->globalPos()); 0215 break; 0216 } 0217 0218 case QEvent::StyleChange: 0219 updateHeight(); 0220 break; 0221 0222 default: 0223 break; 0224 } 0225 return QListView::viewportEvent(event); 0226 } 0227 0228 TabListView::DelegateButton TabListView::buttonAt(const QPoint &pos, const QModelIndex &index) const 0229 { 0230 if (m_delegate->audioButtonRect(index).contains(pos)) { 0231 return AudioButton; 0232 } 0233 return NoButton; 0234 } 0235 0236 void TabListView::updateVisibility() 0237 { 0238 setVisible(!m_hideWhenEmpty || model()->rowCount() > 0); 0239 } 0240 0241 void TabListView::updateHeight() 0242 { 0243 QStyleOptionViewItem option; 0244 initViewItemOption(&option); 0245 setFixedHeight(m_delegate->sizeHint(option, QModelIndex()).height()); 0246 }