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"
32 #include "tldextractor/tldextractor.h"
33 #include "tabmanagerdelegate.h"
34 #include "tabcontextmenu.h"
35 #include "tabbar.h"
36
37 #include <QDesktopWidget>
38 #include <QDialogButtonBox>
39 #include <QStackedWidget>
40 #include <QDialog>
41 #include <QTimer>
42 #include <QLabel>
43 #include <QMimeData>
44
45
46 TLDExtractor* TabManagerWidget::s_tldExtractor = 0;
47
TabManagerWidget(BrowserWindow * mainClass,QWidget * parent,bool defaultWidget)48 TabManagerWidget::TabManagerWidget(BrowserWindow* mainClass, QWidget* parent, bool defaultWidget)
49 : QWidget(parent)
50 , ui(new Ui::TabManagerWidget)
51 , m_window(mainClass)
52 , m_webPage(0)
53 , m_isRefreshing(false)
54 , m_refreshBlocked(false)
55 , m_waitForRefresh(false)
56 , m_isDefaultWidget(defaultWidget)
57 {
58 if(s_tldExtractor == 0)
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 QPushButton* 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
~TabManagerWidget()95 TabManagerWidget::~TabManagerWidget()
96 {
97 delete ui;
98 }
99
setGroupType(GroupType type)100 void TabManagerWidget::setGroupType(GroupType type)
101 {
102 m_groupType = type;
103 }
104
domainFromUrl(const QUrl & url,bool useHostName)105 QString TabManagerWidget::domainFromUrl(const QUrl &url, bool useHostName)
106 {
107 QString appendString = QL1S(":");
108 QString urlString = url.toString();
109
110 if (url.scheme() == "file") {
111 return tr("Local File System:");
112 }
113 else if (url.scheme() == "falkon" || urlString.isEmpty()) {
114 return tr("Falkon:");
115 }
116 else if (url.scheme() == "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 || host.contains(QRegExp("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$"))) {
126 if (host.startsWith("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
delayedRefreshTree(WebPage * p)143 void TabManagerWidget::delayedRefreshTree(WebPage* p)
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
refreshTree()158 void 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 TabItem* 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 TabItem* 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
onItemActivated(QTreeWidgetItem * item,int column)224 void TabManagerWidget::onItemActivated(QTreeWidgetItem* item, int column)
225 {
226 TabItem* 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
isTabSelected()261 bool 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
customContextMenuRequested(const QPoint & pos)275 void TabManagerWidget::customContextMenuRequested(const QPoint &pos)
276 {
277 QMenu* menu = nullptr;
278
279 TabItem* 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) {
293 options |= TabContextMenu::ShowCloseOtherTabsActions;
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(":/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(":/tabmanager/data/tab-detach.png"), tr("&Detach checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("detachSelection");
332 menu->addAction(QIcon(":/tabmanager/data/tab-bookmark.png"), tr("Book&mark checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("bookmarkSelection");
333 menu->addAction(QIcon(":/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
filterChanged(const QString & filter,bool force)340 void 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(QChar(' '), 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 TabItem* 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
filterBarClosed()392 void TabManagerWidget::filterBarClosed()
393 {
394 ui->filterBar->clear();
395 ui->filterBar->hide();
396 ui->treeWidget->setFocusProxy(0);
397 ui->treeWidget->setFocus();
398 }
399
eventFilter(QObject * obj,QEvent * event)400 bool TabManagerWidget::eventFilter(QObject* obj, QEvent* event)
401 {
402 if (event->type() == QEvent::KeyPress) {
403 QKeyEvent *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
processActions()447 void TabManagerWidget::processActions()
448 {
449 if (!sender()) {
450 return;
451 }
452
453 m_refreshBlocked = true;
454
455 QHash<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 TabItem* 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.insertMulti(mainWindow, webTab);
481 }
482 winItem->setCheckState(0, Qt::Unchecked);
483 }
484
485 if (!selectedTabs.isEmpty()) {
486 if (command == "closeSelection") {
487 closeSelectedTabs(selectedTabs);
488 }
489 else if (command == "detachSelection") {
490 detachSelectedTabs(selectedTabs);
491 }
492 else if (command == "bookmarkSelection") {
493 bookmarkSelectedTabs(selectedTabs);
494 }
495 else if (command == "unloadSelection") {
496 unloadSelectedTabs(selectedTabs);
497 }
498 }
499
500 m_refreshBlocked = false;
501 delayedRefreshTree();
502 }
503
changeGroupType()504 void TabManagerWidget::changeGroupType()
505 {
506 QAction* 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
514 delayedRefreshTree();
515
516 emit groupTypeChanged(m_groupType);
517 }
518 }
519 }
520
closeSelectedTabs(const QHash<BrowserWindow *,WebTab * > & tabsHash)521 void TabManagerWidget::closeSelectedTabs(const QHash<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
detachTabsTo(BrowserWindow * targetWindow,const QHash<BrowserWindow *,WebTab * > & tabsHash)537 static void detachTabsTo(BrowserWindow* targetWindow, const QHash<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 = 0;
548 }
549
550 targetWindow->tabWidget()->addView(webTab, Qz::NT_NotSelectedTab);
551 }
552 }
553 }
554
detachSelectedTabs(const QHash<BrowserWindow *,WebTab * > & tabsHash)555 void TabManagerWidget::detachSelectedTabs(const QHash<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 newWindow->move(mApp->desktop()->availableGeometry(this).topLeft() + QPoint(30, 30));
565
566 detachTabsTo(newWindow, tabsHash);
567 }
568
bookmarkSelectedTabs(const QHash<BrowserWindow *,WebTab * > & tabsHash)569 bool TabManagerWidget::bookmarkSelectedTabs(const QHash<BrowserWindow*, WebTab*> &tabsHash)
570 {
571 QDialog* dialog = new QDialog(getWindow(), Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint);
572 QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, dialog);
573 QLabel* label = new QLabel(dialog);
574 BookmarksFoldersButton* folderButton = new BookmarksFoldersButton(dialog);
575
576 QDialogButtonBox* box = new QDialogButtonBox(dialog);
577 box->addButton(QDialogButtonBox::Ok);
578 box->addButton(QDialogButtonBox::Cancel);
579 QObject::connect(box, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
580 QObject::connect(box, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
581
582 layout->addWidget(label);
583 layout->addWidget(folderButton);
584 layout->addWidget(box);
585
586 label->setText(tr("Choose folder for bookmarks:"));
587 dialog->setWindowTitle(tr("Bookmark Selected Tabs"));
588
589 QSize size = dialog->size();
590 size.setWidth(350);
591 dialog->resize(size);
592 dialog->exec();
593
594 if (dialog->result() == QDialog::Rejected) {
595 return false;
596 }
597
598 for (WebTab* tab : tabsHash) {
599 if (!tab->url().isEmpty()) {
600 BookmarkItem* bookmark = new BookmarkItem(BookmarkItem::Url);
601 bookmark->setTitle(tab->title());
602 bookmark->setUrl(tab->url());
603 mApp->bookmarks()->addBookmark(folderButton->selectedFolder(), bookmark);
604 }
605 }
606
607 delete dialog;
608 return true;
609 }
610
unloadSelectedTabs(const QHash<BrowserWindow *,WebTab * > & tabsHash)611 void TabManagerWidget::unloadSelectedTabs(const QHash<BrowserWindow*, WebTab*> &tabsHash)
612 {
613 if (tabsHash.isEmpty()) {
614 return;
615 }
616
617 const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
618 for (BrowserWindow* mainWindow : windows) {
619 const QList<WebTab*> tabs = tabsHash.values(mainWindow);
620
621 for (WebTab* webTab : tabs) {
622 mainWindow->tabWidget()->unloadTab(webTab->tabIndex());
623 }
624 }
625 }
626
groupByDomainName(bool useHostName)627 QTreeWidgetItem* TabManagerWidget::groupByDomainName(bool useHostName)
628 {
629 QTreeWidgetItem* currentTabItem = nullptr;
630
631 QList<BrowserWindow*> windows = mApp->windows();
632 int currentWindowIdx = windows.indexOf(getWindow());
633 if (currentWindowIdx == -1) {
634 // getWindow() instance is closing
635 return nullptr;
636 }
637
638 QMap<QString, QTreeWidgetItem*> tabsGroupedByDomain;
639
640 for (int win = 0; win < windows.count(); ++win) {
641 BrowserWindow* mainWin = windows.at(win);
642
643 QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
644
645 for (int tab = 0; tab < tabs.count(); ++tab) {
646 WebTab* webTab = tabs.at(tab);
647 if (webTab->webView() && m_webPage == webTab->webView()->page()) {
648 m_webPage = 0;
649 continue;
650 }
651 QString domain = domainFromUrl(webTab->url(), useHostName);
652
653 if (!tabsGroupedByDomain.contains(domain)) {
654 TabItem* groupItem = new TabItem(ui->treeWidget, false, false, 0, false);
655 groupItem->setTitle(domain);
656 groupItem->setIsActiveOrCaption(true);
657
658 tabsGroupedByDomain.insert(domain, groupItem);
659 }
660
661 QTreeWidgetItem* groupItem = tabsGroupedByDomain.value(domain);
662
663 TabItem* tabItem = new TabItem(ui->treeWidget, false, true, groupItem);
664 tabItem->setBrowserWindow(mainWin);
665 tabItem->setWebTab(webTab);
666
667 if (webTab == mainWin->weView()->webTab()) {
668 tabItem->setIsActiveOrCaption(true);
669
670 if (mainWin == getWindow())
671 currentTabItem = tabItem;
672 }
673
674
675 tabItem->updateIcon();
676 tabItem->setTitle(webTab->title());
677 }
678 }
679
680 ui->treeWidget->insertTopLevelItems(0, tabsGroupedByDomain.values());
681
682 return currentTabItem;
683 }
684
groupByWindow()685 QTreeWidgetItem* TabManagerWidget::groupByWindow()
686 {
687 QTreeWidgetItem* currentTabItem = nullptr;
688
689 QList<BrowserWindow*> windows = mApp->windows();
690 int currentWindowIdx = windows.indexOf(getWindow());
691 if (currentWindowIdx == -1) {
692 return nullptr;
693 }
694 m_isRefreshing = true;
695
696 if (!m_isDefaultWidget) {
697 windows.move(currentWindowIdx, 0);
698 currentWindowIdx = 0;
699 }
700
701 for (int win = 0; win < windows.count(); ++win) {
702 BrowserWindow* mainWin = windows.at(win);
703 TabItem* winItem = new TabItem(ui->treeWidget, true, false);
704 winItem->setBrowserWindow(mainWin);
705 winItem->setText(0, tr("Window %1").arg(QString::number(win + 1)));
706 winItem->setToolTip(0, tr("Double click to switch"));
707 winItem->setIsActiveOrCaption(win == currentWindowIdx);
708
709 QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
710
711 for (int tab = 0; tab < tabs.count(); ++tab) {
712 WebTab* webTab = tabs.at(tab);
713 if (webTab->webView() && m_webPage == webTab->webView()->page()) {
714 m_webPage = 0;
715 continue;
716 }
717 TabItem* tabItem = new TabItem(ui->treeWidget, true, true, winItem);
718 tabItem->setBrowserWindow(mainWin);
719 tabItem->setWebTab(webTab);
720
721 if (webTab == mainWin->weView()->webTab()) {
722 tabItem->setIsActiveOrCaption(true);
723
724 if (mainWin == getWindow())
725 currentTabItem = tabItem;
726 }
727
728 tabItem->updateIcon();
729 tabItem->setTitle(webTab->title());
730 }
731 }
732
733 return currentTabItem;
734 }
735
getWindow()736 BrowserWindow* TabManagerWidget::getWindow()
737 {
738 if (m_isDefaultWidget || !m_window) {
739 return mApp->getWindow();
740 }
741 else {
742 return m_window.data();
743 }
744 }
745
TabItem(QTreeWidget * treeWidget,bool supportDrag,bool isTab,QTreeWidgetItem * parent,bool addToTree)746 TabItem::TabItem(QTreeWidget* treeWidget, bool supportDrag, bool isTab, QTreeWidgetItem* parent, bool addToTree)
747 : QObject()
748 , QTreeWidgetItem(addToTree ? (parent ? parent : treeWidget->invisibleRootItem()) : 0, 1)
749 , m_treeWidget(treeWidget)
750 , m_window(0)
751 , m_webTab(0)
752 , m_isTab(isTab)
753 {
754 Qt::ItemFlags flgs = flags() | (parent ? Qt::ItemIsUserCheckable : Qt::ItemIsUserCheckable | Qt::ItemIsTristate);
755
756 if (supportDrag) {
757 if (isTab) {
758 flgs |= Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
759 flgs &= ~Qt::ItemIsDropEnabled;
760 }
761 else {
762 flgs |= Qt::ItemIsDropEnabled;
763 flgs &= ~Qt::ItemIsDragEnabled;
764 }
765 }
766
767 setFlags(flgs);
768
769 setCheckState(0, Qt::Unchecked);
770 }
771
window() const772 BrowserWindow* TabItem::window() const
773 {
774 return m_window;
775 }
776
setBrowserWindow(BrowserWindow * window)777 void TabItem::setBrowserWindow(BrowserWindow* window)
778 {
779 m_window = window;
780 }
781
webTab() const782 WebTab* TabItem::webTab() const
783 {
784 return m_webTab;
785 }
786
setWebTab(WebTab * webTab)787 void TabItem::setWebTab(WebTab* webTab)
788 {
789 m_webTab = webTab;
790
791 if (m_webTab->isRestored())
792 setIsActiveOrCaption(m_webTab->isCurrentTab());
793 else
794 setIsSavedTab(true);
795
796 connect(m_webTab->webView(), &QWebEngineView::titleChanged, this, &TabItem::setTitle);
797 connect(m_webTab->webView(), &QWebEngineView::iconChanged, this, &TabItem::updateIcon);
798
799 auto pageChanged = [this](WebPage *page) {
800 connect(page, &WebPage::audioMutedChanged, this, &TabItem::updateIcon);
801 connect(page, &WebPage::loadFinished, this, &TabItem::updateIcon);
802 connect(page, &WebPage::loadStarted, this, &TabItem::updateIcon);
803 };
804 pageChanged(m_webTab->webView()->page());
805 connect(m_webTab->webView(), &WebView::pageChanged, this, pageChanged);
806 }
807
updateIcon()808 void TabItem::updateIcon()
809 {
810 if (!m_webTab)
811 return;
812
813 if (!m_webTab->isLoading()) {
814 if (!m_webTab->isPinned()) {
815 if (m_webTab->isMuted()) {
816 setIcon(0, QIcon::fromTheme(QSL("audio-volume-muted"), QIcon(QSL(":icons/other/audiomuted.svg"))));
817 }
818 else if (!m_webTab->isMuted() && m_webTab->webView()->page()->recentlyAudible()) {
819 setIcon(0, QIcon::fromTheme(QSL("audio-volume-high"), QIcon(QSL(":icons/other/audioplaying.svg"))));
820 }
821 else {
822 setIcon(0, m_webTab->icon());
823 }
824 }
825 else {
826 setIcon(0, QIcon(":tabmanager/data/tab-pinned.png"));
827 }
828
829 if (m_webTab->isRestored())
830 setIsActiveOrCaption(m_webTab->isCurrentTab());
831 else
832 setIsSavedTab(true);
833 }
834 else {
835 setIcon(0, QIcon(":tabmanager/data/tab-loading.png"));
836 setIsActiveOrCaption(m_webTab->isCurrentTab());
837 }
838 }
839
setTitle(const QString & title)840 void TabItem::setTitle(const QString &title)
841 {
842 setText(0, title);
843 setToolTip(0, title);
844 }
845
setIsActiveOrCaption(bool yes)846 void TabItem::setIsActiveOrCaption(bool yes)
847 {
848 setData(0, ActiveOrCaptionRole, yes ? QVariant(true) : QVariant());
849
850 setIsSavedTab(false);
851 }
852
setIsSavedTab(bool yes)853 void TabItem::setIsSavedTab(bool yes)
854 {
855 setData(0, SavedRole, yes ? QVariant(true) : QVariant());
856 }
857
isTab() const858 bool TabItem::isTab() const
859 {
860 return m_isTab;
861 }
862
TabTreeWidget(QWidget * parent)863 TabTreeWidget::TabTreeWidget(QWidget *parent)
864 : QTreeWidget(parent)
865 {
866 invisibleRootItem()->setFlags(invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled);
867 }
868
supportedDropActions() const869 Qt::DropActions TabTreeWidget::supportedDropActions() const
870 {
871 return Qt::MoveAction | Qt::CopyAction;
872 }
873
874 #define MIMETYPE QLatin1String("application/falkon.tabs")
875
mimeTypes() const876 QStringList TabTreeWidget::mimeTypes() const
877 {
878 QStringList types;
879 types.append(MIMETYPE);
880 return types;
881 }
882
mimeData(const QList<QTreeWidgetItem * > items) const883 QMimeData *TabTreeWidget::mimeData(const QList<QTreeWidgetItem*> items) const
884 {
885 QMimeData* mimeData = new QMimeData();
886 QByteArray encodedData;
887
888 QDataStream stream(&encodedData, QIODevice::WriteOnly);
889
890 if (items.size() > 0) {
891 TabItem* tabItem = static_cast<TabItem*>(items.at(0));
892 if (!tabItem || !tabItem->isTab())
893 return 0;
894
895 stream << (quintptr) tabItem->window() << (quintptr) tabItem->webTab();
896
897 mimeData->setData(MIMETYPE, encodedData);
898
899 return mimeData;
900 }
901
902 return 0;
903 }
904
dropMimeData(QTreeWidgetItem * parent,int index,const QMimeData * data,Qt::DropAction action)905 bool TabTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
906 {
907 if (action == Qt::IgnoreAction) {
908 return true;
909 }
910
911 TabItem* parentItem = static_cast<TabItem*>(parent);
912
913 if (!data->hasFormat(MIMETYPE) || !parentItem) {
914 return false;
915 }
916
917 Q_ASSERT(!parentItem->isTab());
918
919 BrowserWindow* targetWindow = parentItem->window();
920
921 QByteArray encodedData = data->data(MIMETYPE);
922 QDataStream stream(&encodedData, QIODevice::ReadOnly);
923
924 if (!stream.atEnd()) {
925 quintptr webTabPtr;
926 quintptr windowPtr;
927
928 stream >> windowPtr >> webTabPtr;
929
930 WebTab* webTab = (WebTab*) webTabPtr;
931 BrowserWindow* window = (BrowserWindow*) windowPtr;
932
933 if (window == targetWindow) {
934 if (index > 0 && webTab->tabIndex() < index)
935 --index;
936
937 if (webTab->isPinned() && index >= targetWindow->tabWidget()->pinnedTabsCount())
938 index = targetWindow->tabWidget()->pinnedTabsCount() - 1;
939
940 if (!webTab->isPinned() && index < targetWindow->tabWidget()->pinnedTabsCount())
941 index = targetWindow->tabWidget()->pinnedTabsCount();
942
943 if (index != webTab->tabIndex()) {
944 targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
945
946 if (!webTab->isCurrentTab())
947 emit requestRefreshTree();
948 }
949 else {
950 return false;
951 }
952 }
953 else if (!webTab->isPinned()) {
954 QHash<BrowserWindow*, WebTab*> tabsHash;
955 tabsHash.insert(window, webTab);
956
957 detachTabsTo(targetWindow, tabsHash);
958
959 if (index < targetWindow->tabWidget()->pinnedTabsCount())
960 index = targetWindow->tabWidget()->pinnedTabsCount();
961
962 targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
963 }
964 }
965
966 return true;
967 }
968
setEnableDragTabs(bool enable)969 void TabTreeWidget::setEnableDragTabs(bool enable)
970 {
971 setDragEnabled(enable);
972 setAcceptDrops(enable);
973 viewport()->setAcceptDrops(enable);
974 setDropIndicatorShown(enable);
975 }
976