1
mirror of https://invent.kde.org/network/falkon.git synced 2024-12-20 02:36:34 +01:00

VerticalTabs: Show pinned tabs in horizontal list above normal tabs

This commit is contained in:
David Rosca 2018-02-02 14:02:59 +01:00
parent 801c1845b5
commit 072d3c28ee
No known key found for this signature in database
GPG Key ID: EBC3FC294452C6D8
10 changed files with 455 additions and 7 deletions

View File

@ -6,6 +6,9 @@ set( VerticalTabs_SRCS
tabtreeview.cpp
tabtreedelegate.cpp
loadinganimator.cpp
tabfiltermodel.cpp
tablistview.cpp
tablistdelegate.cpp
)
set( VerticalTabs_UIS

View File

@ -0,0 +1,49 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "tabfiltermodel.h"
#include "tabmodel.h"
TabFilterModel::TabFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
void TabFilterModel::resetFilter()
{
m_mode = NoFilter;
invalidateFilter();
}
void TabFilterModel::setFilterPinnedTabs(bool filter)
{
m_mode = FilterPinnedTabs;
m_filterPinnedTabs = filter;
invalidateFilter();
}
bool TabFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (m_mode == NoFilter) {
return true;
}
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
return index.data(TabModel::PinnedRole).toBool() != m_filterPinnedTabs;
}

View File

@ -0,0 +1,43 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#pragma once
#include <QSortFilterProxyModel>
class TabFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit TabFilterModel(QObject *parent = nullptr);
void resetFilter();
void setFilterPinnedTabs(bool pinned);
private:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
enum Mode {
NoFilter,
FilterPinnedTabs
};
Mode m_mode = NoFilter;
bool m_filterPinnedTabs = false;
};

View File

@ -0,0 +1,104 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "tablistdelegate.h"
#include "tablistview.h"
#include "loadinganimator.h"
#include "tabmodel.h"
#include "tabicon.h"
#include <QPainter>
TabListDelegate::TabListDelegate(TabListView *view)
: QStyledItemDelegate()
, m_view(view)
{
m_padding = qMax(5, m_view->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1);
m_loadingAnimator = new LoadingAnimator(this);
connect(m_loadingAnimator, &LoadingAnimator::updateIndex, this, [this](const QModelIndex &index) {
m_view->update(index);
});
}
QRect TabListDelegate::audioButtonRect(const QModelIndex &index) const
{
if (!index.data(TabModel::AudioPlayingRole).toBool() && !index.data(TabModel::AudioMutedRole).toBool()) {
return QRect();
}
const QRect rect = m_view->visualRect(index);
const int center = rect.height() / 2 + rect.top();
return QRect(rect.right() - 16, center - 16 / 2, 16, 16);
}
void TabListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
const QWidget *w = option.widget;
const QStyle *style = w ? w->style() : m_view->style();
QStyleOptionViewItem opt = option;
opt.state.setFlag(QStyle::State_Active, true);
opt.state.setFlag(QStyle::State_HasFocus, false);
opt.state.setFlag(QStyle::State_Selected, index.data(TabModel::CurrentTabRole).toBool());
const int height = opt.rect.height();
const int center = height / 2 + opt.rect.top();
const QIcon::Mode iconMode = opt.state & QStyle::State_Selected ? QIcon::Selected : QIcon::Normal;
// Draw background
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w);
// Draw icon
const int iconSize = 16;
const int iconYPos = center - (iconSize / 2);
QRect iconRect(opt.rect.left() + (opt.rect.width() - iconSize) / 2, iconYPos, iconSize, iconSize);
QPixmap pixmap;
if (index.data(TabModel::LoadingRole).toBool()) {
pixmap = m_loadingAnimator->pixmap(index);
} else {
pixmap = index.data(Qt::DecorationRole).value<QIcon>().pixmap(iconSize, iconMode);
}
painter->drawPixmap(iconRect, pixmap);
// Draw audio icon
const bool audioMuted = index.data(TabModel::AudioMutedRole).toBool();
const bool audioPlaying = index.data(TabModel::AudioPlayingRole).toBool();
if (audioMuted || audioPlaying) {
QSize audioSize(16, 16);
QPoint pos(opt.rect.right() - audioSize.width(), center - audioSize.height() / 2);
QRect audioRect(pos, audioSize);
QColor c = opt.palette.color(QPalette::Window);
c.setAlpha(180);
painter->setPen(c);
painter->setBrush(c);
painter->drawEllipse(audioRect);
painter->drawPixmap(audioRect, audioMuted ? TabIcon::data()->audioMutedPixmap : TabIcon::data()->audioPlayingPixmap);
}
}
QSize TabListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt(option);
initStyleOption(&opt, index);
return QSize(m_padding * 4 + 16, m_padding * 2 + opt.fontMetrics.height());
}

View File

@ -0,0 +1,39 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#pragma once
#include <QStyledItemDelegate>
class TabListView;
class LoadingAnimator;
class TabListDelegate : public QStyledItemDelegate
{
public:
explicit TabListDelegate(TabListView *view);
QRect audioButtonRect(const QModelIndex &index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
TabListView *m_view;
LoadingAnimator *m_loadingAnimator;
int m_padding;
};

View File

@ -0,0 +1,143 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "tablistview.h"
#include "tablistdelegate.h"
#include "loadinganimator.h"
#include "tabmodel.h"
#include "webtab.h"
#include "tabcontextmenu.h"
#include <QToolTip>
#include <QHoverEvent>
TabListView::TabListView(QWidget *parent)
: QListView(parent)
{
setDragEnabled(true);
setAcceptDrops(true);
setUniformItemSizes(true);
setDropIndicatorShown(true);
setMouseTracking(true);
setFlow(QListView::LeftToRight);
setFrameShape(QFrame::NoFrame);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
m_delegate = new TabListDelegate(this);
setItemDelegate(m_delegate);
setFixedHeight(m_delegate->sizeHint(viewOptions(), QModelIndex()).height());
}
void TabListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
if (current.data(TabModel::CurrentTabRole).toBool()) {
QListView::currentChanged(current, previous);
} else if (previous.data(TabModel::CurrentTabRole).toBool()) {
setCurrentIndex(previous);
}
}
void TabListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
QListView::dataChanged(topLeft, bottomRight, roles);
if (roles.size() == 1 && roles.at(0) == TabModel::CurrentTabRole && topLeft.data(TabModel::CurrentTabRole).toBool()) {
setCurrentIndex(topLeft);
}
}
bool TabListView::viewportEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress: {
QMouseEvent *me = static_cast<QMouseEvent*>(event);
const QModelIndex index = indexAt(me->pos());
WebTab *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
if (me->buttons() == Qt::MiddleButton && tab) {
tab->closeTab();
}
if (me->buttons() != Qt::LeftButton) {
m_pressedIndex = QModelIndex();
m_pressedButton = NoButton;
break;
}
m_pressedIndex = index;
m_pressedButton = buttonAt(me->pos(), m_pressedIndex);
if (m_pressedButton == NoButton && tab) {
tab->makeCurrentTab();
}
break;
}
case QEvent::MouseButtonRelease: {
QMouseEvent *me = static_cast<QMouseEvent*>(event);
if (me->buttons() != Qt::NoButton) {
break;
}
const QModelIndex index = indexAt(me->pos());
if (m_pressedIndex != index) {
break;
}
DelegateButton button = buttonAt(me->pos(), index);
if (m_pressedButton == button) {
WebTab *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
if (tab && m_pressedButton == AudioButton) {
tab->toggleMuted();
}
}
break;
}
case QEvent::ToolTip: {
QHelpEvent *he = static_cast<QHelpEvent*>(event);
const QModelIndex index = indexAt(he->pos());
DelegateButton button = buttonAt(he->pos(), index);
if (button == AudioButton) {
const bool muted = index.data(TabModel::AudioMutedRole).toBool();
QToolTip::showText(he->globalPos(), muted ? tr("Unmute Tab") : tr("Mute Tab"), this, visualRect(index));
he->accept();
return true;
}
break;
}
case QEvent::ContextMenu: {
QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
const QModelIndex index = indexAt(ce->pos());
WebTab *tab = index.data(TabModel::WebTabRole).value<WebTab*>();
if (tab) {
TabContextMenu menu(tab, Qt::Horizontal, false);
menu.exec(ce->globalPos());
}
break;
}
default:
break;
}
return QListView::viewportEvent(event);
}
TabListView::DelegateButton TabListView::buttonAt(const QPoint &pos, const QModelIndex &index) const
{
if (m_delegate->audioButtonRect(index).contains(pos)) {
return AudioButton;
}
return NoButton;
}

View File

@ -0,0 +1,47 @@
/* ============================================================
* VerticalTabs plugin for Falkon
* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#pragma once
#include <QListView>
class TabListDelegate;
class TabListView : public QListView
{
Q_OBJECT
public:
explicit TabListView(QWidget *parent = nullptr);
private:
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
bool viewportEvent(QEvent *event) override;
TabListDelegate *m_delegate;
enum DelegateButton {
NoButton,
AudioButton
};
DelegateButton buttonAt(const QPoint &pos, const QModelIndex &index) const;
DelegateButton m_pressedButton = NoButton;
QModelIndex m_pressedIndex;
};

View File

@ -36,6 +36,7 @@ TabTreeView::TabTreeView(QWidget *parent)
setDropIndicatorShown(true);
setAllColumnsShowFocus(true);
setMouseTracking(true);
setFrameShape(QFrame::NoFrame);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setIndentation(0);

View File

@ -17,12 +17,15 @@
* ============================================================ */
#include "verticaltabswidget.h"
#include "tabtreeview.h"
#include "tablistview.h"
#include "tabfiltermodel.h"
#include "tabmodel.h"
#include "tabtreemodel.h"
#include "browserwindow.h"
#include <QVBoxLayout>
#include <QListView>
VerticalTabsWidget::VerticalTabsWidget(BrowserWindow *window)
: QWidget()
@ -32,24 +35,38 @@ VerticalTabsWidget::VerticalTabsWidget(BrowserWindow *window)
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
m_view = new TabTreeView(this);
layout->addWidget(m_view);
TabListView *m_pinnedView = new TabListView(this);
m_normalView = new TabTreeView(this);
layout->addWidget(m_pinnedView);
layout->addWidget(m_normalView);
TabFilterModel *model = new TabFilterModel(m_pinnedView);
model->setFilterPinnedTabs(false);
model->setSourceModel(m_window->tabModel());
m_pinnedView->setModel(model);
m_pinnedView->setFocusProxy(m_normalView);
}
void VerticalTabsWidget::setViewType(VerticalTabsPlugin::ViewType type)
{
TabFilterModel *model = new TabFilterModel(m_normalView);
model->setFilterPinnedTabs(true);
switch (type) {
case VerticalTabsPlugin::TabListView:
m_view->setModel(m_window->tabModel());
m_view->setTabsInOrder(true);
model->setSourceModel(m_window->tabModel());
m_normalView->setModel(model);
m_normalView->setTabsInOrder(true);
break;
case VerticalTabsPlugin::TabTreeView:
delete m_treeModel;
m_treeModel = new TabTreeModel(this);
m_treeModel->setSourceModel(m_window->tabModel());
m_view->setModel(m_treeModel);
m_view->setTabsInOrder(false);
model->setSourceModel(m_treeModel);
m_normalView->setModel(model);
m_normalView->setTabsInOrder(false);
break;
default:

View File

@ -24,6 +24,7 @@
class BrowserWindow;
class TabTreeModel;
class TabListView;
class TabTreeView;
class VerticalTabsWidget : public QWidget
@ -36,6 +37,7 @@ public:
private:
BrowserWindow *m_window;
TabTreeView *m_view;
TabListView *m_pinnedView;
TabTreeView *m_normalView;
TabTreeModel *m_treeModel = nullptr;
};