20#include "ui_tabmanagerwidget.h"
37#include <QDialogButtonBox>
38#include <QStackedWidget>
53 , m_isRefreshing(false)
54 , m_refreshBlocked(false)
55 , m_waitForRefresh(false)
56 , m_isDefaultWidget(defaultWidget)
58 if(s_tldExtractor ==
nullptr)
65 ui->treeWidget->setSelectionMode(QTreeWidget::SingleSelection);
66 ui->treeWidget->setUniformRowHeights(
true);
67 ui->treeWidget->setColumnCount(2);
68 ui->treeWidget->header()->hide();
69 ui->treeWidget->header()->setStretchLastSection(
false);
70 ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
71 ui->treeWidget->header()->setSectionResizeMode(1, QHeaderView::Fixed);
72 ui->treeWidget->header()->resizeSection(1, 16);
74 ui->treeWidget->setExpandsOnDoubleClick(
false);
75 ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
77 ui->treeWidget->installEventFilter(
this);
78 ui->filterBar->installEventFilter(
this);
80 auto* closeButton =
new QPushButton(ui->filterBar);
81 closeButton->setFlat(
true);
82 closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
84 ui->filterBar->hide();
88 connect(closeButton, &QAbstractButton::clicked,
this, &TabManagerWidget::filterBarClosed);
89 connect(ui->filterBar, SIGNAL(textChanged(QString)),
this, SLOT(filterChanged(QString)));
90 connect(ui->treeWidget, &QTreeWidget::itemClicked,
this, &TabManagerWidget::onItemActivated);
91 connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(customContextMenuRequested(QPoint)));
92 connect(ui->treeWidget, SIGNAL(requestRefreshTree()),
this, SLOT(
delayedRefreshTree()));
107 QString appendString =
QL1S(
":");
108 QString urlString = url.toString();
110 if (url.scheme() ==
QSL(
"file")) {
111 return tr(
"Local File System:");
113 else if (url.scheme() ==
QSL(
"falkon") || urlString.isEmpty()) {
114 return tr(
"Falkon:");
116 else if (url.scheme() ==
QSL(
"ftp")) {
117 appendString.prepend(tr(
" [FTP]"));
120 QString host = url.host();
121 if (host.isEmpty()) {
122 return urlString.append(appendString);
125 if (useHostName || QRegExp(
QSL(R
"(^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)")).indexIn(host) >= 0) {
126 if (host.startsWith(
QSL(
"www."), Qt::CaseInsensitive)) {
130 return host.append(appendString);
135 if (!registeredDomain.isEmpty()) {
136 host = registeredDomain;
139 return host.append(appendString);
145 if (m_refreshBlocked || m_waitForRefresh) {
149 if (m_isRefreshing && !p) {
154 m_waitForRefresh =
true;
155 QTimer::singleShot(50,
this, &TabManagerWidget::refreshTree);
158void TabManagerWidget::refreshTree()
160 if (m_refreshBlocked) {
164 if (m_isRefreshing && !m_webPage) {
169 QList<QWidget*> selectedTabs;
170 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
171 QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(
i);
172 if (winItem->checkState(0) == Qt::Unchecked) {
176 for (
int j = 0; j < winItem->childCount(); ++j) {
177 auto* tabItem =
static_cast<TabItem*
>(winItem->child(j));
178 if (!tabItem || tabItem->checkState(0) == Qt::Unchecked) {
181 selectedTabs << tabItem->
webTab();
185 ui->treeWidget->clear();
186 ui->treeWidget->setEnableDragTabs(m_groupType ==
GroupByWindow);
188 QTreeWidgetItem* currentTabItem =
nullptr;
191 currentTabItem = groupByDomainName(
true);
194 currentTabItem = groupByDomainName();
198 currentTabItem = groupByWindow();
202 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
203 QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(
i);
205 for (
int j = 0; j < winItem->childCount(); ++j) {
206 auto* tabItem =
static_cast<TabItem*
>(winItem->child(j));
208 if (tabItem && selectedTabs.contains(tabItem->webTab())) {
209 tabItem->setCheckState(0, Qt::Checked);
214 filterChanged(m_filterText,
true);
215 ui->treeWidget->expandAll();
218 ui->treeWidget->scrollToItem(currentTabItem, QAbstractItemView::EnsureVisible);
220 m_isRefreshing =
false;
221 m_waitForRefresh =
false;
224void TabManagerWidget::onItemActivated(QTreeWidgetItem* item,
int column)
226 auto* tabItem =
static_cast<TabItem*
>(item);
232 QWidget* tabWidget = tabItem->webTab();
235 if (item->childCount() > 0)
236 QMetaObject::invokeMethod(mainWindow ? mainWindow :
mApp->getWindow(),
"addTab");
237 else if (tabWidget && mainWindow)
246 if (mainWindow->isMinimized()) {
247 mainWindow->showNormal();
252 mainWindow->activateWindow();
254 mainWindow->
weView()->setFocus();
261bool TabManagerWidget::isTabSelected()
263 bool selected =
false;
264 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
265 QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(
i);
266 if (parentItem->checkState(0) != Qt::Unchecked) {
275void TabManagerWidget::customContextMenuRequested(
const QPoint &pos)
277 QMenu* menu =
nullptr;
279 auto* item =
static_cast<TabItem*
>(ui->treeWidget->itemAt(pos));
283 QWidget* tabWidget = item->webTab();
285 if (mainWindow && tabWidget) {
296 menu->addSeparator();
303 menu->setAttribute(Qt::WA_DeleteOnClose);
306 QMenu groupTypeSubmenu(tr(
"Group by"));
309 action->setCheckable(
true);
314 action->setCheckable(
true);
319 action->setCheckable(
true);
322 menu->addMenu(&groupTypeSubmenu);
324 if (m_isDefaultWidget) {
328 menu->addSeparator();
330 if (isTabSelected()) {
331 menu->addAction(QIcon(
QSL(
":/tabmanager/data/tab-detach.png")), tr(
"&Detach checked tabs"),
this, &TabManagerWidget::processActions)->setObjectName(
"detachSelection");
332 menu->addAction(QIcon(
QSL(
":/tabmanager/data/tab-bookmark.png")), tr(
"Book&mark checked tabs"),
this, &TabManagerWidget::processActions)->setObjectName(
"bookmarkSelection");
333 menu->addAction(QIcon(
QSL(
":/tabmanager/data/tab-close.png")), tr(
"&Close checked tabs"),
this, &TabManagerWidget::processActions)->setObjectName(
"closeSelection");
334 menu->addAction(tr(
"&Unload checked tabs"),
this, &TabManagerWidget::processActions)->setObjectName(
"unloadSelection");
337 menu->exec(ui->treeWidget->viewport()->mapToGlobal(pos));
340void TabManagerWidget::filterChanged(
const QString &filter,
bool force)
342 if (force || filter != m_filterText) {
343 m_filterText = filter.simplified();
344 ui->treeWidget->itemDelegate()->setProperty(
"filterText", m_filterText);
345 if (m_filterText.isEmpty()) {
346 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
347 QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(
i);
348 for (
int j = 0; j < parentItem->childCount(); ++j) {
349 QTreeWidgetItem* childItem = parentItem->child(j);
350 childItem->setHidden(
false);
352 parentItem->setHidden(
false);
353 parentItem->setExpanded(
true);
359 const QRegularExpression filterRegExp(filter.simplified().replace(
QL1C(
' '), QLatin1String(
".*"))
360 .append(QLatin1String(
".*")).prepend(QLatin1String(
".*")),
361 QRegularExpression::CaseInsensitiveOption);
363 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
364 QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(
i);
365 int visibleChildCount = 0;
366 for (
int j = 0; j < parentItem->childCount(); ++j) {
367 auto* childItem =
static_cast<TabItem*
>(parentItem->child(j));
372 if (childItem->text(0).contains(filterRegExp) || childItem->webTab()->url().toString().simplified().contains(filterRegExp)) {
374 childItem->setHidden(
false);
377 childItem->setHidden(
true);
381 if (visibleChildCount == 0) {
382 parentItem->setHidden(
true);
385 parentItem->setHidden(
false);
386 parentItem->setExpanded(
true);
392void TabManagerWidget::filterBarClosed()
394 ui->filterBar->clear();
395 ui->filterBar->hide();
396 ui->treeWidget->setFocusProxy(
nullptr);
397 ui->treeWidget->setFocus();
402 if (event->type() == QEvent::KeyPress) {
403 auto *keyEvent =
static_cast<QKeyEvent *
>(event);
404 const QString text = keyEvent->text().simplified();
406 if (obj == ui->treeWidget) {
408 if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
409 onItemActivated(ui->treeWidget->currentItem(), 0);
410 return QObject::eventFilter(obj, event);
413 if (!text.isEmpty() || ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_F)) {
414 ui->filterBar->show();
415 ui->treeWidget->setFocusProxy(ui->filterBar);
416 ui->filterBar->setFocus();
417 if (!text.isEmpty() && text.at(0).isPrint()) {
418 ui->filterBar->setText(ui->filterBar->text() + text);
424 else if (obj == ui->filterBar) {
425 bool isNavigationOrActionKey = keyEvent->key() == Qt::Key_Up ||
426 keyEvent->key() == Qt::Key_Down ||
427 keyEvent->key() == Qt::Key_PageDown ||
428 keyEvent->key() == Qt::Key_PageUp ||
429 keyEvent->key() == Qt::Key_Enter ||
430 keyEvent->key() == Qt::Key_Return;
433 if (isNavigationOrActionKey) {
434 QKeyEvent ev(QKeyEvent::KeyPress, keyEvent->key(), keyEvent->modifiers());
435 QApplication::sendEvent(ui->treeWidget, &ev);
441 if (obj == ui->treeWidget && (event->type() == QEvent::Resize || event->type() == QEvent::Show))
442 ui->treeWidget->setColumnHidden(1, ui->treeWidget->viewport()->width() < 150);
444 return QObject::eventFilter(obj, event);
447void TabManagerWidget::processActions()
453 m_refreshBlocked =
true;
455 QMultiHash<BrowserWindow*, WebTab*> selectedTabs;
457 const QString &command = sender()->objectName();
459 for (
int i = 0;
i < ui->treeWidget->topLevelItemCount(); ++
i) {
460 QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(
i);
461 if (winItem->checkState(0) == Qt::Unchecked) {
465 for (
int j = 0; j < winItem->childCount(); ++j) {
466 auto* tabItem =
static_cast<TabItem*
>(winItem->child(j));
467 if (!tabItem || tabItem->checkState(0) == Qt::Unchecked) {
472 WebTab* webTab = tabItem->webTab();
476 tabItem->setCheckState(0, Qt::Unchecked);
480 selectedTabs.insert(mainWindow, webTab);
482 winItem->setCheckState(0, Qt::Unchecked);
485 if (!selectedTabs.isEmpty()) {
486 if (command ==
QSL(
"closeSelection")) {
489 else if (command ==
QSL(
"detachSelection")) {
492 else if (command ==
QSL(
"bookmarkSelection")) {
495 else if (command ==
QSL(
"unloadSelection")) {
500 m_refreshBlocked =
false;
506 auto* action = qobject_cast<QAction*>(sender());
509 int type = action->data().toInt();
523 if (tabsHash.isEmpty()) {
527 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
529 const QList<WebTab*> tabs = tabsHash.values(mainWindow);
531 for (
WebTab* webTab : tabs) {
537static void detachTabsTo(
BrowserWindow* targetWindow,
const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
539 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
541 const QList<WebTab*> &tabs = tabsHash.values(mainWindow);
542 for (
WebTab* webTab : tabs) {
545 if (mainWindow && mainWindow->
tabCount() == 0) {
547 mainWindow =
nullptr;
557 if (tabsHash.isEmpty() ||
558 (tabsHash.uniqueKeys().size() == 1 &&
559 tabsHash.size() == tabsHash.keys().at(0)->tabCount())) {
564 const QRect &availableGeometryForScreen = screen()->availableGeometry();
565 newWindow->move(availableGeometryForScreen.topLeft() + QPoint(30, 30));
567 detachTabsTo(newWindow, tabsHash);
572 auto* dialog =
new QDialog(getWindow(), Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint);
573 auto* layout =
new QBoxLayout(QBoxLayout::TopToBottom, dialog);
574 auto* label =
new QLabel(dialog);
577 auto* box =
new QDialogButtonBox(dialog);
578 box->addButton(QDialogButtonBox::Ok);
579 box->addButton(QDialogButtonBox::Cancel);
580 QObject::connect(box, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
581 QObject::connect(box, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
583 layout->addWidget(label);
584 layout->addWidget(folderButton);
585 layout->addWidget(box);
587 label->setText(tr(
"Choose folder for bookmarks:"));
588 dialog->setWindowTitle(tr(
"Bookmark Selected Tabs"));
590 QSize size = dialog->size();
592 dialog->resize(size);
595 if (dialog->result() == QDialog::Rejected) {
599 for (
WebTab* tab : tabsHash) {
600 if (!tab->url().isEmpty()) {
602 bookmark->setTitle(tab->title());
603 bookmark->setUrl(tab->url());
604 mApp->bookmarks()->addBookmark(folderButton->selectedFolder(), bookmark);
614 if (tabsHash.isEmpty()) {
618 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
620 const QList<WebTab*> tabs = tabsHash.values(mainWindow);
622 for (
WebTab* webTab : tabs) {
628QTreeWidgetItem* TabManagerWidget::groupByDomainName(
bool useHostName)
630 QTreeWidgetItem* currentTabItem =
nullptr;
632 QList<BrowserWindow*> windows =
mApp->windows();
633 int currentWindowIdx = windows.indexOf(getWindow());
634 if (currentWindowIdx == -1) {
639 QMap<QString, QTreeWidgetItem*> tabsGroupedByDomain;
641 for (
int win = 0; win < windows.count(); ++win) {
646 for (
int tab = 0; tab < tabs.count(); ++tab) {
647 WebTab* webTab = tabs.at(tab);
654 if (!tabsGroupedByDomain.contains(domain)) {
655 auto* groupItem =
new TabItem(ui->treeWidget,
false,
false,
nullptr,
false);
656 groupItem->setTitle(domain);
657 groupItem->setIsActiveOrCaption(
true);
659 tabsGroupedByDomain.insert(domain, groupItem);
662 QTreeWidgetItem* groupItem = tabsGroupedByDomain.value(domain);
664 auto* tabItem =
new TabItem(ui->treeWidget,
false,
true, groupItem);
665 tabItem->setBrowserWindow(mainWin);
666 tabItem->setWebTab(webTab);
669 tabItem->setIsActiveOrCaption(
true);
671 if (mainWin == getWindow())
672 currentTabItem = tabItem;
676 tabItem->updateIcon();
677 tabItem->setTitle(webTab->
title());
681 ui->treeWidget->insertTopLevelItems(0, tabsGroupedByDomain.values());
683 return currentTabItem;
686QTreeWidgetItem* TabManagerWidget::groupByWindow()
688 QTreeWidgetItem* currentTabItem =
nullptr;
690 QList<BrowserWindow*> windows =
mApp->windows();
691 int currentWindowIdx = windows.indexOf(getWindow());
692 if (currentWindowIdx == -1) {
695 m_isRefreshing =
true;
697 if (!m_isDefaultWidget) {
698 windows.move(currentWindowIdx, 0);
699 currentWindowIdx = 0;
702 for (
int win = 0; win < windows.count(); ++win) {
704 auto* winItem =
new TabItem(ui->treeWidget,
true,
false);
705 winItem->setBrowserWindow(mainWin);
706 winItem->setText(0, tr(
"Window %1").arg(QString::number(win + 1)));
707 winItem->setToolTip(0, tr(
"Double click to switch"));
708 winItem->setIsActiveOrCaption(win == currentWindowIdx);
712 for (
int tab = 0; tab < tabs.count(); ++tab) {
713 WebTab* webTab = tabs.at(tab);
718 auto* tabItem =
new TabItem(ui->treeWidget,
true,
true, winItem);
719 tabItem->setBrowserWindow(mainWin);
720 tabItem->setWebTab(webTab);
723 tabItem->setIsActiveOrCaption(
true);
725 if (mainWin == getWindow())
726 currentTabItem = tabItem;
729 tabItem->updateIcon();
730 tabItem->setTitle(webTab->
title());
734 return currentTabItem;
739 if (m_isDefaultWidget || !m_window) {
740 return mApp->getWindow();
743 return m_window.data();
747TabItem::TabItem(QTreeWidget* treeWidget,
bool supportDrag,
bool isTab, QTreeWidgetItem* parent,
bool addToTree)
749 , QTreeWidgetItem(addToTree ? (parent ? parent : treeWidget->invisibleRootItem()) : nullptr, 1)
750 , m_treeWidget(treeWidget)
755 Qt::ItemFlags flgs = flags() | (parent ? Qt::ItemIsUserCheckable : Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
759 flgs |= Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
760 flgs &= ~Qt::ItemIsDropEnabled;
763 flgs |= Qt::ItemIsDropEnabled;
764 flgs &= ~Qt::ItemIsDragEnabled;
770 setCheckState(0, Qt::Unchecked);
800 auto pageChanged = [
this](
WebPage *page) {
817 setIcon(0, QIcon::fromTheme(
QSL(
"audio-volume-muted"), QIcon(
QSL(
":icons/other/audiomuted.svg"))));
820 setIcon(0, QIcon::fromTheme(
QSL(
"audio-volume-high"), QIcon(
QSL(
":icons/other/audioplaying.svg"))));
823 setIcon(0, m_webTab->
icon());
827 setIcon(0, QIcon(
QSL(
":tabmanager/data/tab-pinned.png")));
836 setIcon(0, QIcon(
QSL(
":tabmanager/data/tab-loading.png")));
844 setToolTip(0, title);
856 setData(0,
SavedRole, yes ? QVariant(
true) : QVariant());
865 : QTreeWidget(parent)
867 invisibleRootItem()->setFlags(invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled);
872 return Qt::MoveAction | Qt::CopyAction;
875#define MIMETYPE QLatin1String("application/falkon.tabs")
887 QByteArray encodedData;
889 QDataStream stream(&encodedData, QIODevice::WriteOnly);
891 if (items.size() > 0) {
892 auto* tabItem =
static_cast<TabItem*
>(items.at(0));
893 if (!tabItem || !tabItem->isTab())
896 stream << (quintptr) tabItem->
window() << (quintptr) tabItem->webTab();
908 if (action == Qt::IgnoreAction) {
912 auto* parentItem =
static_cast<TabItem*
>(parent);
914 if (!data->hasFormat(
MIMETYPE) || !parentItem) {
918 Q_ASSERT(!parentItem->isTab());
922 QByteArray encodedData = data->data(
MIMETYPE);
923 QDataStream stream(&encodedData, QIODevice::ReadOnly);
925 if (!stream.atEnd()) {
929 stream >> windowPtr >> webTabPtr;
931 auto* webTab = (
WebTab*) webTabPtr;
934 if (window == targetWindow) {
935 if (index > 0 && webTab->
tabIndex() < index)
955 QMultiHash<BrowserWindow*, WebTab*> tabsHash;
956 tabsHash.insert(window, webTab);
958 detachTabsTo(targetWindow, tabsHash);
960 if (index < targetWindow->tabWidget()->pinnedTabsCount())
972 setDragEnabled(enable);
973 setAcceptDrops(enable);
974 viewport()->setAcceptDrops(enable);
975 setDropIndicatorShown(enable);
TabWidget * tabWidget() const
TabbedWebView * weView() const
void moveTab(int from, int to)
void setIsActiveOrCaption(bool yes)
void setTitle(const QString &title)
void setIsSavedTab(bool yes)
void setBrowserWindow(BrowserWindow *window)
BrowserWindow * window() const
TabItem(QTreeWidget *treeWidget, bool supportDrag=true, bool isTab=true, QTreeWidgetItem *parent=nullptr, bool addToTree=true)
void setWebTab(WebTab *webTab)
static QString settingsPath()
bool isCurrentTab() const
QString title(bool allowEmpty=false) const
TabbedWebView * webView() const
QIcon icon(bool allowNull=false) const
void pageChanged(WebPage *page)