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