Falkon Develop
Cross-platform Qt-based web browser
tabmanagerwidget.cpp
Go to the documentation of this file.
1/* ============================================================
2* TabManager plugin for Falkon
3* Copyright (C) 2013-2017 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
4* Copyright (C) 2018 David Rosca <nowrep@gmail.com>
5*
6* This program is free software: you can redistribute it and/or modify
7* it under the terms of the GNU General Public License as published by
8* the Free Software Foundation, either version 3 of the License, or
9* (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <http://www.gnu.org/licenses/>.
18* ============================================================ */
19#include "tabmanagerwidget.h"
20#include "ui_tabmanagerwidget.h"
21#include "mainapplication.h"
22#include "browserwindow.h"
23#include "webtab.h"
24#include "webpage.h"
25#include "tabbedwebview.h"
26#include "tabwidget.h"
27#include "locationbar.h"
28#include "bookmarkstools.h"
29#include "bookmarkitem.h"
30#include "bookmarks.h"
31#include "tabmanagerplugin.h"
33#include "tabmanagerdelegate.h"
34#include "tabcontextmenu.h"
35#include "tabbar.h"
36
37#include <QDialogButtonBox>
38#include <QStackedWidget>
39#include <QDialog>
40#include <QTimer>
41#include <QLabel>
42#include <QMimeData>
43#include <QRegExp>
44
45
46TLDExtractor* TabManagerWidget::s_tldExtractor = nullptr;
47
48TabManagerWidget::TabManagerWidget(BrowserWindow* mainClass, QWidget* parent, bool defaultWidget)
49 : QWidget(parent)
50 , ui(new Ui::TabManagerWidget)
51 , m_window(mainClass)
52 , m_webPage(nullptr)
53 , m_isRefreshing(false)
54 , m_refreshBlocked(false)
55 , m_waitForRefresh(false)
56 , m_isDefaultWidget(defaultWidget)
57{
58 if(s_tldExtractor == nullptr)
59 {
60 s_tldExtractor = TLDExtractor::instance();
61 s_tldExtractor->setDataSearchPaths(QStringList() << TabManagerPlugin::settingsPath());
62 }
63
64 ui->setupUi(this);
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);
73
74 ui->treeWidget->setExpandsOnDoubleClick(false);
75 ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
76
77 ui->treeWidget->installEventFilter(this);
78 ui->filterBar->installEventFilter(this);
79
80 auto* closeButton = new QPushButton(ui->filterBar);
81 closeButton->setFlat(true);
82 closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
83 ui->filterBar->addWidget(closeButton, LineEdit::RightSide);
84 ui->filterBar->hide();
85
86 ui->treeWidget->setItemDelegate(new TabManagerDelegate(ui->treeWidget));
87
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()));
93}
94
96{
97 delete ui;
98}
99
101{
102 m_groupType = type;
103}
104
105QString TabManagerWidget::domainFromUrl(const QUrl &url, bool useHostName)
106{
107 QString appendString = QL1S(":");
108 QString urlString = url.toString();
109
110 if (url.scheme() == QSL("file")) {
111 return tr("Local File System:");
112 }
113 else if (url.scheme() == QSL("falkon") || urlString.isEmpty()) {
114 return tr("Falkon:");
115 }
116 else if (url.scheme() == QSL("ftp")) {
117 appendString.prepend(tr(" [FTP]"));
118 }
119
120 QString host = url.host();
121 if (host.isEmpty()) {
122 return urlString.append(appendString);
123 }
124
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)) {
127 host.remove(0, 4);
128 }
129
130 return host.append(appendString);
131 }
132 else {
133 const QString registeredDomain = s_tldExtractor->registrableDomain(host);
134
135 if (!registeredDomain.isEmpty()) {
136 host = registeredDomain;
137 }
138
139 return host.append(appendString);
140 }
141}
142
144{
145 if (m_refreshBlocked || m_waitForRefresh) {
146 return;
147 }
148
149 if (m_isRefreshing && !p) {
150 return;
151 }
152
153 m_webPage = p;
154 m_waitForRefresh = true;
155 QTimer::singleShot(50, this, &TabManagerWidget::refreshTree);
156}
157
158void TabManagerWidget::refreshTree()
159{
160 if (m_refreshBlocked) {
161 return;
162 }
163
164 if (m_isRefreshing && !m_webPage) {
165 return;
166 }
167
168 // store selected items
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) {
173 continue;
174 }
175
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) {
179 continue;
180 }
181 selectedTabs << tabItem->webTab();
182 }
183 }
184
185 ui->treeWidget->clear();
186 ui->treeWidget->setEnableDragTabs(m_groupType == GroupByWindow);
187
188 QTreeWidgetItem* currentTabItem = nullptr;
189
190 if (m_groupType == GroupByHost) {
191 currentTabItem = groupByDomainName(true);
192 }
193 else if (m_groupType == GroupByDomain) {
194 currentTabItem = groupByDomainName();
195 }
196 else { // fallback to GroupByWindow
197 m_groupType = GroupByWindow;
198 currentTabItem = groupByWindow();
199 }
200
201 // restore selected items
202 for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
203 QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(i);
204
205 for (int j = 0; j < winItem->childCount(); ++j) {
206 auto* tabItem = static_cast<TabItem*>(winItem->child(j));
207
208 if (tabItem && selectedTabs.contains(tabItem->webTab())) {
209 tabItem->setCheckState(0, Qt::Checked);
210 }
211 }
212 }
213
214 filterChanged(m_filterText, true);
215 ui->treeWidget->expandAll();
216
217 if (currentTabItem)
218 ui->treeWidget->scrollToItem(currentTabItem, QAbstractItemView::EnsureVisible);
219
220 m_isRefreshing = false;
221 m_waitForRefresh = false;
222}
223
224void TabManagerWidget::onItemActivated(QTreeWidgetItem* item, int column)
225{
226 auto* tabItem = static_cast<TabItem*>(item);
227 if (!tabItem) {
228 return;
229 }
230
231 BrowserWindow* mainWindow = tabItem->window();
232 QWidget* tabWidget = tabItem->webTab();
233
234 if (column == 1) {
235 if (item->childCount() > 0)
236 QMetaObject::invokeMethod(mainWindow ? mainWindow : mApp->getWindow(), "addTab");
237 else if (tabWidget && mainWindow)
238 mainWindow->tabWidget()->requestCloseTab(mainWindow->tabWidget()->indexOf(tabWidget));
239 return;
240 }
241
242 if (!mainWindow) {
243 return;
244 }
245
246 if (mainWindow->isMinimized()) {
247 mainWindow->showNormal();
248 }
249 else {
250 mainWindow->show();
251 }
252 mainWindow->activateWindow();
253 mainWindow->raise();
254 mainWindow->weView()->setFocus();
255
256 if (tabWidget && tabWidget != mainWindow->tabWidget()->currentWidget()) {
257 mainWindow->tabWidget()->setCurrentIndex(mainWindow->tabWidget()->indexOf(tabWidget));
258 }
259}
260
261bool TabManagerWidget::isTabSelected()
262{
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) {
267 selected = true;
268 break;
269 }
270 }
271
272 return selected;
273}
274
275void TabManagerWidget::customContextMenuRequested(const QPoint &pos)
276{
277 QMenu* menu = nullptr;
278
279 auto* item = static_cast<TabItem*>(ui->treeWidget->itemAt(pos));
280
281 if (item) {
282 BrowserWindow* mainWindow = item->window();
283 QWidget* tabWidget = item->webTab();
284
285 if (mainWindow && tabWidget) {
286 int index = mainWindow->tabWidget()->indexOf(tabWidget);
287
288 // if items are not grouped by Window then actions "Close Other Tabs",
289 // "Close Tabs To The Bottom" and "Close Tabs To The Top"
290 // are ambiguous and should be hidden.
291 TabContextMenu::Options options = TabContextMenu::VerticalTabs;
292 if (m_groupType == GroupByWindow) {
294 }
295 menu = new TabContextMenu(index, mainWindow, options);
296 menu->addSeparator();
297 }
298 }
299
300 if (!menu)
301 menu = new QMenu;
302
303 menu->setAttribute(Qt::WA_DeleteOnClose);
304
305 QAction* action;
306 QMenu groupTypeSubmenu(tr("Group by"));
307 action = groupTypeSubmenu.addAction(tr("&Window"), this, &TabManagerWidget::changeGroupType);
308 action->setData(GroupByWindow);
309 action->setCheckable(true);
310 action->setChecked(m_groupType == GroupByWindow);
311
312 action = groupTypeSubmenu.addAction(tr("&Domain"), this, &TabManagerWidget::changeGroupType);
313 action->setData(GroupByDomain);
314 action->setCheckable(true);
315 action->setChecked(m_groupType == GroupByDomain);
316
317 action = groupTypeSubmenu.addAction(tr("&Host"), this, &TabManagerWidget::changeGroupType);
318 action->setData(GroupByHost);
319 action->setCheckable(true);
320 action->setChecked(m_groupType == GroupByHost);
321
322 menu->addMenu(&groupTypeSubmenu);
323
324 if (m_isDefaultWidget) {
325 menu->addAction(QIcon(QSL(":/tabmanager/data/side-by-side.png")), tr("&Show side by side"), this, &TabManagerWidget::showSideBySide)->setObjectName("sideBySide");
326 }
327
328 menu->addSeparator();
329
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");
335 }
336
337 menu->exec(ui->treeWidget->viewport()->mapToGlobal(pos));
338}
339
340void TabManagerWidget::filterChanged(const QString &filter, bool force)
341{
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);
351 }
352 parentItem->setHidden(false);
353 parentItem->setExpanded(true);
354 }
355
356 return;
357 }
358
359 const QRegularExpression filterRegExp(filter.simplified().replace(QL1C(' '), QLatin1String(".*"))
360 .append(QLatin1String(".*")).prepend(QLatin1String(".*")),
361 QRegularExpression::CaseInsensitiveOption);
362
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));
368 if (!childItem) {
369 continue;
370 }
371
372 if (childItem->text(0).contains(filterRegExp) || childItem->webTab()->url().toString().simplified().contains(filterRegExp)) {
373 ++visibleChildCount;
374 childItem->setHidden(false);
375 }
376 else {
377 childItem->setHidden(true);
378 }
379 }
380
381 if (visibleChildCount == 0) {
382 parentItem->setHidden(true);
383 }
384 else {
385 parentItem->setHidden(false);
386 parentItem->setExpanded(true);
387 }
388 }
389 }
390}
391
392void TabManagerWidget::filterBarClosed()
393{
394 ui->filterBar->clear();
395 ui->filterBar->hide();
396 ui->treeWidget->setFocusProxy(nullptr);
397 ui->treeWidget->setFocus();
398}
399
400bool TabManagerWidget::eventFilter(QObject* obj, QEvent* event)
401{
402 if (event->type() == QEvent::KeyPress) {
403 auto *keyEvent = static_cast<QKeyEvent *>(event);
404 const QString text = keyEvent->text().simplified();
405
406 if (obj == ui->treeWidget) {
407 // switch to tab/window on enter
408 if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
409 onItemActivated(ui->treeWidget->currentItem(), 0);
410 return QObject::eventFilter(obj, event);
411 }
412
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);
419 }
420
421 return true;
422 }
423 }
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;
431
432 // send scroll or action press key to treeWidget
433 if (isNavigationOrActionKey) {
434 QKeyEvent ev(QKeyEvent::KeyPress, keyEvent->key(), keyEvent->modifiers());
435 QApplication::sendEvent(ui->treeWidget, &ev);
436 return false;
437 }
438 }
439 }
440
441 if (obj == ui->treeWidget && (event->type() == QEvent::Resize || event->type() == QEvent::Show))
442 ui->treeWidget->setColumnHidden(1, ui->treeWidget->viewport()->width() < 150);
443
444 return QObject::eventFilter(obj, event);
445}
446
447void TabManagerWidget::processActions()
448{
449 if (!sender()) {
450 return;
451 }
452
453 m_refreshBlocked = true;
454
455 QMultiHash<BrowserWindow*, WebTab*> selectedTabs;
456
457 const QString &command = sender()->objectName();
458
459 for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
460 QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(i);
461 if (winItem->checkState(0) == Qt::Unchecked) {
462 continue;
463 }
464
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) {
468 continue;
469 }
470
471 BrowserWindow* mainWindow = tabItem->window();
472 WebTab* webTab = tabItem->webTab();
473
474 // current supported actions are not applied to pinned tabs
475 if (webTab->isPinned()) {
476 tabItem->setCheckState(0, Qt::Unchecked);
477 continue;
478 }
479
480 selectedTabs.insert(mainWindow, webTab);
481 }
482 winItem->setCheckState(0, Qt::Unchecked);
483 }
484
485 if (!selectedTabs.isEmpty()) {
486 if (command == QSL("closeSelection")) {
487 closeSelectedTabs(selectedTabs);
488 }
489 else if (command == QSL("detachSelection")) {
490 detachSelectedTabs(selectedTabs);
491 }
492 else if (command == QSL("bookmarkSelection")) {
493 bookmarkSelectedTabs(selectedTabs);
494 }
495 else if (command == QSL("unloadSelection")) {
496 unloadSelectedTabs(selectedTabs);
497 }
498 }
499
500 m_refreshBlocked = false;
502}
503
505{
506 auto* action = qobject_cast<QAction*>(sender());
507
508 if (action) {
509 int type = action->data().toInt();
510
511 if (m_groupType != GroupType(type)) {
512 m_groupType = GroupType(type);
513
515
516 Q_EMIT groupTypeChanged(m_groupType);
517 }
518 }
519}
520
521void TabManagerWidget::closeSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
522{
523 if (tabsHash.isEmpty()) {
524 return;
525 }
526
527 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
528 for (BrowserWindow* mainWindow : windows) {
529 const QList<WebTab*> tabs = tabsHash.values(mainWindow);
530
531 for (WebTab* webTab : tabs) {
532 mainWindow->tabWidget()->requestCloseTab(webTab->tabIndex());
533 }
534 }
535}
536
537static void detachTabsTo(BrowserWindow* targetWindow, const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
538{
539 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
540 for (BrowserWindow* mainWindow : windows) {
541 const QList<WebTab*> &tabs = tabsHash.values(mainWindow);
542 for (WebTab* webTab : tabs) {
543 mainWindow->tabWidget()->detachTab(webTab);
544
545 if (mainWindow && mainWindow->tabCount() == 0) {
546 mainWindow->close();
547 mainWindow = nullptr;
548 }
549
550 targetWindow->tabWidget()->addView(webTab, Qz::NT_NotSelectedTab);
551 }
552 }
553}
554
555void TabManagerWidget::detachSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
556{
557 if (tabsHash.isEmpty() ||
558 (tabsHash.uniqueKeys().size() == 1 &&
559 tabsHash.size() == tabsHash.keys().at(0)->tabCount())) {
560 return;
561 }
562
563 BrowserWindow* newWindow = mApp->createWindow(Qz::BW_OtherRestoredWindow);
564 const QRect &availableGeometryForScreen = screen()->availableGeometry();
565 newWindow->move(availableGeometryForScreen.topLeft() + QPoint(30, 30));
566
567 detachTabsTo(newWindow, tabsHash);
568}
569
570bool TabManagerWidget::bookmarkSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
571{
572 auto* dialog = new QDialog(getWindow(), Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint);
573 auto* layout = new QBoxLayout(QBoxLayout::TopToBottom, dialog);
574 auto* label = new QLabel(dialog);
575 auto* folderButton = new BookmarksFoldersButton(dialog);
576
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);
582
583 layout->addWidget(label);
584 layout->addWidget(folderButton);
585 layout->addWidget(box);
586
587 label->setText(tr("Choose folder for bookmarks:"));
588 dialog->setWindowTitle(tr("Bookmark Selected Tabs"));
589
590 QSize size = dialog->size();
591 size.setWidth(350);
592 dialog->resize(size);
593 dialog->exec();
594
595 if (dialog->result() == QDialog::Rejected) {
596 return false;
597 }
598
599 for (WebTab* tab : tabsHash) {
600 if (!tab->url().isEmpty()) {
601 auto* bookmark = new BookmarkItem(BookmarkItem::Url);
602 bookmark->setTitle(tab->title());
603 bookmark->setUrl(tab->url());
604 mApp->bookmarks()->addBookmark(folderButton->selectedFolder(), bookmark);
605 }
606 }
607
608 delete dialog;
609 return true;
610}
611
612void TabManagerWidget::unloadSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
613{
614 if (tabsHash.isEmpty()) {
615 return;
616 }
617
618 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
619 for (BrowserWindow* mainWindow : windows) {
620 const QList<WebTab*> tabs = tabsHash.values(mainWindow);
621
622 for (WebTab* webTab : tabs) {
623 mainWindow->tabWidget()->unloadTab(webTab->tabIndex());
624 }
625 }
626}
627
628QTreeWidgetItem* TabManagerWidget::groupByDomainName(bool useHostName)
629{
630 QTreeWidgetItem* currentTabItem = nullptr;
631
632 QList<BrowserWindow*> windows = mApp->windows();
633 int currentWindowIdx = windows.indexOf(getWindow());
634 if (currentWindowIdx == -1) {
635 // getWindow() instance is closing
636 return nullptr;
637 }
638
639 QMap<QString, QTreeWidgetItem*> tabsGroupedByDomain;
640
641 for (int win = 0; win < windows.count(); ++win) {
642 BrowserWindow* mainWin = windows.at(win);
643
644 QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
645
646 for (int tab = 0; tab < tabs.count(); ++tab) {
647 WebTab* webTab = tabs.at(tab);
648 if (webTab->webView() && m_webPage == webTab->webView()->page()) {
649 m_webPage = nullptr;
650 continue;
651 }
652 QString domain = domainFromUrl(webTab->url(), useHostName);
653
654 if (!tabsGroupedByDomain.contains(domain)) {
655 auto* groupItem = new TabItem(ui->treeWidget, false, false, nullptr, false);
656 groupItem->setTitle(domain);
657 groupItem->setIsActiveOrCaption(true);
658
659 tabsGroupedByDomain.insert(domain, groupItem);
660 }
661
662 QTreeWidgetItem* groupItem = tabsGroupedByDomain.value(domain);
663
664 auto* tabItem = new TabItem(ui->treeWidget, false, true, groupItem);
665 tabItem->setBrowserWindow(mainWin);
666 tabItem->setWebTab(webTab);
667
668 if (webTab == mainWin->weView()->webTab()) {
669 tabItem->setIsActiveOrCaption(true);
670
671 if (mainWin == getWindow())
672 currentTabItem = tabItem;
673 }
674
675
676 tabItem->updateIcon();
677 tabItem->setTitle(webTab->title());
678 }
679 }
680
681 ui->treeWidget->insertTopLevelItems(0, tabsGroupedByDomain.values());
682
683 return currentTabItem;
684}
685
686QTreeWidgetItem* TabManagerWidget::groupByWindow()
687{
688 QTreeWidgetItem* currentTabItem = nullptr;
689
690 QList<BrowserWindow*> windows = mApp->windows();
691 int currentWindowIdx = windows.indexOf(getWindow());
692 if (currentWindowIdx == -1) {
693 return nullptr;
694 }
695 m_isRefreshing = true;
696
697 if (!m_isDefaultWidget) {
698 windows.move(currentWindowIdx, 0);
699 currentWindowIdx = 0;
700 }
701
702 for (int win = 0; win < windows.count(); ++win) {
703 BrowserWindow* mainWin = windows.at(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);
709
710 QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
711
712 for (int tab = 0; tab < tabs.count(); ++tab) {
713 WebTab* webTab = tabs.at(tab);
714 if (webTab->webView() && m_webPage == webTab->webView()->page()) {
715 m_webPage = nullptr;
716 continue;
717 }
718 auto* tabItem = new TabItem(ui->treeWidget, true, true, winItem);
719 tabItem->setBrowserWindow(mainWin);
720 tabItem->setWebTab(webTab);
721
722 if (webTab == mainWin->weView()->webTab()) {
723 tabItem->setIsActiveOrCaption(true);
724
725 if (mainWin == getWindow())
726 currentTabItem = tabItem;
727 }
728
729 tabItem->updateIcon();
730 tabItem->setTitle(webTab->title());
731 }
732 }
733
734 return currentTabItem;
735}
736
737BrowserWindow* TabManagerWidget::getWindow()
738{
739 if (m_isDefaultWidget || !m_window) {
740 return mApp->getWindow();
741 }
742 else {
743 return m_window.data();
744 }
745}
746
747TabItem::TabItem(QTreeWidget* treeWidget, bool supportDrag, bool isTab, QTreeWidgetItem* parent, bool addToTree)
748 : QObject()
749 , QTreeWidgetItem(addToTree ? (parent ? parent : treeWidget->invisibleRootItem()) : nullptr, 1)
750 , m_treeWidget(treeWidget)
751 , m_window(nullptr)
752 , m_webTab(nullptr)
753 , m_isTab(isTab)
754{
755 Qt::ItemFlags flgs = flags() | (parent ? Qt::ItemIsUserCheckable : Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
756
757 if (supportDrag) {
758 if (isTab) {
759 flgs |= Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
760 flgs &= ~Qt::ItemIsDropEnabled;
761 }
762 else {
763 flgs |= Qt::ItemIsDropEnabled;
764 flgs &= ~Qt::ItemIsDragEnabled;
765 }
766 }
767
768 setFlags(flgs);
769
770 setCheckState(0, Qt::Unchecked);
771}
772
774{
775 return m_window;
776}
777
779{
780 m_window = window;
781}
782
784{
785 return m_webTab;
786}
787
789{
790 m_webTab = webTab;
791
792 if (m_webTab->isRestored())
794 else
795 setIsSavedTab(true);
796
797 connect(m_webTab->webView(), &QWebEngineView::titleChanged, this, &TabItem::setTitle);
798 connect(m_webTab->webView(), &QWebEngineView::iconChanged, this, &TabItem::updateIcon);
799
800 auto pageChanged = [this](WebPage *page) {
801 connect(page, &WebPage::audioMutedChanged, this, &TabItem::updateIcon);
802 connect(page, &WebPage::loadFinished, this, &TabItem::updateIcon);
803 connect(page, &WebPage::loadStarted, this, &TabItem::updateIcon);
804 };
805 pageChanged(m_webTab->webView()->page());
806 connect(m_webTab->webView(), &WebView::pageChanged, this, pageChanged);
807}
808
810{
811 if (!m_webTab)
812 return;
813
814 if (!m_webTab->isLoading()) {
815 if (!m_webTab->isPinned()) {
816 if (m_webTab->isMuted()) {
817 setIcon(0, QIcon::fromTheme(QSL("audio-volume-muted"), QIcon(QSL(":icons/other/audiomuted.svg"))));
818 }
819 else if (!m_webTab->isMuted() && m_webTab->webView()->page()->recentlyAudible()) {
820 setIcon(0, QIcon::fromTheme(QSL("audio-volume-high"), QIcon(QSL(":icons/other/audioplaying.svg"))));
821 }
822 else {
823 setIcon(0, m_webTab->icon());
824 }
825 }
826 else {
827 setIcon(0, QIcon(QSL(":tabmanager/data/tab-pinned.png")));
828 }
829
830 if (m_webTab->isRestored())
832 else
833 setIsSavedTab(true);
834 }
835 else {
836 setIcon(0, QIcon(QSL(":tabmanager/data/tab-loading.png")));
838 }
839}
840
841void TabItem::setTitle(const QString &title)
842{
843 setText(0, title);
844 setToolTip(0, title);
845}
846
848{
849 setData(0, ActiveOrCaptionRole, yes ? QVariant(true) : QVariant());
850
851 setIsSavedTab(false);
852}
853
855{
856 setData(0, SavedRole, yes ? QVariant(true) : QVariant());
857}
858
859bool TabItem::isTab() const
860{
861 return m_isTab;
862}
863
865 : QTreeWidget(parent)
866{
867 invisibleRootItem()->setFlags(invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled);
868}
869
871{
872 return Qt::MoveAction | Qt::CopyAction;
873}
874
875#define MIMETYPE QLatin1String("application/falkon.tabs")
876
877QStringList TabTreeWidget::mimeTypes() const
878{
879 QStringList types;
880 types.append(MIMETYPE);
881 return types;
882}
883
884QMimeData *TabTreeWidget::mimeData(const QList<QTreeWidgetItem*> &items) const
885{
886 auto* mimeData = new QMimeData();
887 QByteArray encodedData;
888
889 QDataStream stream(&encodedData, QIODevice::WriteOnly);
890
891 if (items.size() > 0) {
892 auto* tabItem = static_cast<TabItem*>(items.at(0));
893 if (!tabItem || !tabItem->isTab())
894 return nullptr;
895
896 stream << (quintptr) tabItem->window() << (quintptr) tabItem->webTab();
897
898 mimeData->setData(MIMETYPE, encodedData);
899
900 return mimeData;
901 }
902
903 return nullptr;
904}
905
906bool TabTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
907{
908 if (action == Qt::IgnoreAction) {
909 return true;
910 }
911
912 auto* parentItem = static_cast<TabItem*>(parent);
913
914 if (!data->hasFormat(MIMETYPE) || !parentItem) {
915 return false;
916 }
917
918 Q_ASSERT(!parentItem->isTab());
919
920 BrowserWindow* targetWindow = parentItem->window();
921
922 QByteArray encodedData = data->data(MIMETYPE);
923 QDataStream stream(&encodedData, QIODevice::ReadOnly);
924
925 if (!stream.atEnd()) {
926 quintptr webTabPtr;
927 quintptr windowPtr;
928
929 stream >> windowPtr >> webTabPtr;
930
931 auto* webTab = (WebTab*) webTabPtr;
932 auto* window = (BrowserWindow*) windowPtr;
933
934 if (window == targetWindow) {
935 if (index > 0 && webTab->tabIndex() < index)
936 --index;
937
938 if (webTab->isPinned() && index >= targetWindow->tabWidget()->pinnedTabsCount())
939 index = targetWindow->tabWidget()->pinnedTabsCount() - 1;
940
941 if (!webTab->isPinned() && index < targetWindow->tabWidget()->pinnedTabsCount())
942 index = targetWindow->tabWidget()->pinnedTabsCount();
943
944 if (index != webTab->tabIndex()) {
945 targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
946
947 if (!webTab->isCurrentTab())
948 Q_EMIT requestRefreshTree();
949 }
950 else {
951 return false;
952 }
953 }
954 else if (!webTab->isPinned()) {
955 QMultiHash<BrowserWindow*, WebTab*> tabsHash;
956 tabsHash.insert(window, webTab);
957
958 detachTabsTo(targetWindow, tabsHash);
959
960 if (index < targetWindow->tabWidget()->pinnedTabsCount())
961 index = targetWindow->tabWidget()->pinnedTabsCount();
962
963 targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
964 }
965 }
966
967 return true;
968}
969
971{
972 setDragEnabled(enable);
973 setAcceptDrops(enable);
974 viewport()->setAcceptDrops(enable);
975 setDropIndicatorShown(enable);
976}
TabWidget * tabWidget() const
int tabCount() const
TabbedWebView * weView() const
void moveTab(int from, int to)
@ RightSide
Definition: lineedit.h:80
static TLDExtractor * instance()
QString registrableDomain(const QString &host)
void setDataSearchPaths(const QStringList &searchPaths=TLDExtractor::defaultDataSearchPaths())
bool isTab() const
void setIsActiveOrCaption(bool yes)
void setTitle(const QString &title)
void updateIcon()
void setIsSavedTab(bool yes)
void setBrowserWindow(BrowserWindow *window)
WebTab * webTab() const
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()
~TabManagerWidget() override
bool eventFilter(QObject *obj, QEvent *event) override
void delayedRefreshTree(WebPage *p=nullptr)
TabManagerWidget(BrowserWindow *mainClass, QWidget *parent=nullptr, bool defaultWidget=false)
bool bookmarkSelectedTabs(const QMultiHash< BrowserWindow *, WebTab * > &tabsHash)
void closeSelectedTabs(const QMultiHash< BrowserWindow *, WebTab * > &tabsHash)
void detachSelectedTabs(const QMultiHash< BrowserWindow *, WebTab * > &tabsHash)
void setGroupType(GroupType type)
static QString domainFromUrl(const QUrl &url, bool useHostName=false)
void groupTypeChanged(TabManagerWidget::GroupType)
void unloadSelectedTabs(const QMultiHash< BrowserWindow *, WebTab * > &tabsHash)
QWidget * currentWidget() const
int indexOf(QWidget *widget) const
void setEnableDragTabs(bool enable)
Qt::DropActions supportedDropActions() const override
TabTreeWidget(QWidget *parent=nullptr)
bool dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action) override
void requestRefreshTree()
QMimeData * mimeData(const QList< QTreeWidgetItem * > &items) const override
QStringList mimeTypes() const override
void requestCloseTab(int index=-1)
Definition: tabwidget.cpp:471
void setCurrentIndex(int index)
Definition: tabwidget.cpp:536
void unloadTab(int index)
Definition: tabwidget.cpp:749
TabBar * tabBar() const
Definition: tabwidget.cpp:580
int addView(const LoadRequest &req, const Qz::NewTabPositionFlags &openFlags, bool selectLine=false, bool pinned=false)
Definition: tabwidget.cpp:314
int pinnedTabsCount() const
Definition: tabwidget.cpp:556
QList< WebTab * > allTabs(bool withPinned=true)
Definition: tabwidget.cpp:833
void detachTab(WebTab *tab)
Definition: tabwidget.cpp:685
WebTab * webTab() const
Definition: webtab.h:40
bool isMuted() const
Definition: webtab.cpp:433
bool isCurrentTab() const
Definition: webtab.cpp:684
int tabIndex() const
Definition: webtab.cpp:710
bool isRestored() const
Definition: webtab.cpp:548
QUrl url() const
Definition: webtab.cpp:263
QString title(bool allowEmpty=false) const
Definition: webtab.cpp:276
bool isPinned() const
Definition: webtab.cpp:414
bool isLoading() const
Definition: webtab.cpp:409
TabbedWebView * webView() const
Definition: webtab.cpp:209
QIcon icon(bool allowNull=false) const
Definition: webtab.cpp:286
WebPage * page() const
Definition: webview.cpp:132
void pageChanged(WebPage *page)
#define mApp
@ BW_OtherRestoredWindow
Definition: qzcommon.h:66
@ NT_NotSelectedTab
Definition: qzcommon.h:98
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QL1C(x)
Definition: qzcommon.h:48
#define QSL(x)
Definition: qzcommon.h:40
#define MIMETYPE