File indexing completed on 2024-05-19 04:59:20

0001 /* ============================================================
0002 * TabManager plugin for Falkon
0003 * Copyright (C) 2013-2017  S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
0004 * Copyright (C)      2018 David Rosca <nowrep@gmail.com>
0005 *
0006 * This program is free software: you can redistribute it and/or modify
0007 * it under the terms of the GNU General Public License as published by
0008 * the Free Software Foundation, either version 3 of the License, or
0009 * (at your option) any later version.
0010 *
0011 * This program is distributed in the hope that it will be useful,
0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 * GNU General Public License for more details.
0015 *
0016 * You should have received a copy of the GNU General Public License
0017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018 * ============================================================ */
0019 #include "tabmanagerwidget.h"
0020 #include "ui_tabmanagerwidget.h"
0021 #include "mainapplication.h"
0022 #include "browserwindow.h"
0023 #include "webtab.h"
0024 #include "webpage.h"
0025 #include "tabbedwebview.h"
0026 #include "tabwidget.h"
0027 #include "locationbar.h"
0028 #include "bookmarkstools.h"
0029 #include "bookmarkitem.h"
0030 #include "bookmarks.h"
0031 #include "tabmanagerplugin.h"
0032 #include "tldextractor/tldextractor.h"
0033 #include "tabmanagerdelegate.h"
0034 #include "tabcontextmenu.h"
0035 #include "tabbar.h"
0036 
0037 #include <QDialogButtonBox>
0038 #include <QStackedWidget>
0039 #include <QDialog>
0040 #include <QTimer>
0041 #include <QLabel>
0042 #include <QMimeData>
0043 #include <QRegExp>
0044 
0045 
0046 TLDExtractor* TabManagerWidget::s_tldExtractor = nullptr;
0047 
0048 TabManagerWidget::TabManagerWidget(BrowserWindow* mainClass, QWidget* parent, bool defaultWidget)
0049     : QWidget(parent)
0050     , ui(new Ui::TabManagerWidget)
0051     , m_window(mainClass)
0052     , m_webPage(nullptr)
0053     , m_isRefreshing(false)
0054     , m_refreshBlocked(false)
0055     , m_waitForRefresh(false)
0056     , m_isDefaultWidget(defaultWidget)
0057 {
0058     if(s_tldExtractor == nullptr)
0059     {
0060         s_tldExtractor = TLDExtractor::instance();
0061         s_tldExtractor->setDataSearchPaths(QStringList() << TabManagerPlugin::settingsPath());
0062     }
0063 
0064     ui->setupUi(this);
0065     ui->treeWidget->setSelectionMode(QTreeWidget::SingleSelection);
0066     ui->treeWidget->setUniformRowHeights(true);
0067     ui->treeWidget->setColumnCount(2);
0068     ui->treeWidget->header()->hide();
0069     ui->treeWidget->header()->setStretchLastSection(false);
0070     ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
0071     ui->treeWidget->header()->setSectionResizeMode(1, QHeaderView::Fixed);
0072     ui->treeWidget->header()->resizeSection(1, 16);
0073 
0074     ui->treeWidget->setExpandsOnDoubleClick(false);
0075     ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
0076 
0077     ui->treeWidget->installEventFilter(this);
0078     ui->filterBar->installEventFilter(this);
0079 
0080     auto* closeButton = new QPushButton(ui->filterBar);
0081     closeButton->setFlat(true);
0082     closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
0083     ui->filterBar->addWidget(closeButton, LineEdit::RightSide);
0084     ui->filterBar->hide();
0085 
0086     ui->treeWidget->setItemDelegate(new TabManagerDelegate(ui->treeWidget));
0087 
0088     connect(closeButton, &QAbstractButton::clicked, this, &TabManagerWidget::filterBarClosed);
0089     connect(ui->filterBar, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
0090     connect(ui->treeWidget, &QTreeWidget::itemClicked, this, &TabManagerWidget::onItemActivated);
0091     connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint)));
0092     connect(ui->treeWidget, SIGNAL(requestRefreshTree()), this, SLOT(delayedRefreshTree()));
0093 }
0094 
0095 TabManagerWidget::~TabManagerWidget()
0096 {
0097     delete ui;
0098 }
0099 
0100 void TabManagerWidget::setGroupType(GroupType type)
0101 {
0102     m_groupType = type;
0103 }
0104 
0105 QString TabManagerWidget::domainFromUrl(const QUrl &url, bool useHostName)
0106 {
0107     QString appendString = QL1S(":");
0108     QString urlString = url.toString();
0109 
0110     if (url.scheme() == QSL("file")) {
0111         return tr("Local File System:");
0112     }
0113     else if (url.scheme() == QSL("falkon") || urlString.isEmpty()) {
0114         return tr("Falkon:");
0115     }
0116     else if (url.scheme() == QSL("ftp")) {
0117         appendString.prepend(tr(" [FTP]"));
0118     }
0119 
0120     QString host = url.host();
0121     if (host.isEmpty()) {
0122         return urlString.append(appendString);
0123     }
0124 
0125     if (useHostName || QRegExp(QSL(R"(^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)")).indexIn(host) >= 0) {
0126         if (host.startsWith(QSL("www."), Qt::CaseInsensitive)) {
0127             host.remove(0, 4);
0128         }
0129 
0130         return host.append(appendString);
0131     }
0132     else {
0133         const QString registeredDomain = s_tldExtractor->registrableDomain(host);
0134 
0135         if (!registeredDomain.isEmpty()) {
0136             host = registeredDomain;
0137         }
0138 
0139         return host.append(appendString);
0140     }
0141 }
0142 
0143 void TabManagerWidget::delayedRefreshTree(WebPage* p)
0144 {
0145     if (m_refreshBlocked || m_waitForRefresh) {
0146         return;
0147     }
0148 
0149     if (m_isRefreshing && !p) {
0150         return;
0151     }
0152 
0153     m_webPage = p;
0154     m_waitForRefresh = true;
0155     QTimer::singleShot(50, this, &TabManagerWidget::refreshTree);
0156 }
0157 
0158 void TabManagerWidget::refreshTree()
0159 {
0160     if (m_refreshBlocked) {
0161         return;
0162     }
0163 
0164     if (m_isRefreshing && !m_webPage) {
0165         return;
0166     }
0167 
0168     // store selected items
0169     QList<QWidget*> selectedTabs;
0170     for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0171         QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(i);
0172         if (winItem->checkState(0) == Qt::Unchecked) {
0173             continue;
0174         }
0175 
0176         for (int j = 0; j < winItem->childCount(); ++j) {
0177             auto* tabItem = static_cast<TabItem*>(winItem->child(j));
0178             if (!tabItem || tabItem->checkState(0) == Qt::Unchecked) {
0179                 continue;
0180             }
0181             selectedTabs << tabItem->webTab();
0182         }
0183     }
0184 
0185     ui->treeWidget->clear();
0186     ui->treeWidget->setEnableDragTabs(m_groupType == GroupByWindow);
0187 
0188     QTreeWidgetItem* currentTabItem = nullptr;
0189 
0190     if (m_groupType == GroupByHost) {
0191         currentTabItem = groupByDomainName(true);
0192     }
0193     else if (m_groupType == GroupByDomain) {
0194         currentTabItem = groupByDomainName();
0195     }
0196     else { // fallback to GroupByWindow
0197         m_groupType = GroupByWindow;
0198         currentTabItem = groupByWindow();
0199     }
0200 
0201     // restore selected items
0202     for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0203         QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(i);
0204 
0205         for (int j = 0; j < winItem->childCount(); ++j) {
0206             auto* tabItem = static_cast<TabItem*>(winItem->child(j));
0207 
0208             if (tabItem && selectedTabs.contains(tabItem->webTab())) {
0209                 tabItem->setCheckState(0, Qt::Checked);
0210             }
0211         }
0212     }
0213 
0214     filterChanged(m_filterText, true);
0215     ui->treeWidget->expandAll();
0216 
0217     if (currentTabItem)
0218         ui->treeWidget->scrollToItem(currentTabItem, QAbstractItemView::EnsureVisible);
0219 
0220     m_isRefreshing = false;
0221     m_waitForRefresh = false;
0222 }
0223 
0224 void TabManagerWidget::onItemActivated(QTreeWidgetItem* item, int column)
0225 {
0226     auto* tabItem = static_cast<TabItem*>(item);
0227     if (!tabItem) {
0228         return;
0229     }
0230 
0231     BrowserWindow* mainWindow = tabItem->window();
0232     QWidget* tabWidget = tabItem->webTab();
0233 
0234     if (column == 1) {
0235         if (item->childCount() > 0)
0236             QMetaObject::invokeMethod(mainWindow ? mainWindow : mApp->getWindow(), "addTab");
0237         else if (tabWidget && mainWindow)
0238             mainWindow->tabWidget()->requestCloseTab(mainWindow->tabWidget()->indexOf(tabWidget));
0239         return;
0240     }
0241 
0242     if (!mainWindow) {
0243         return;
0244     }
0245 
0246     if (mainWindow->isMinimized()) {
0247         mainWindow->showNormal();
0248     }
0249     else {
0250         mainWindow->show();
0251     }
0252     mainWindow->activateWindow();
0253     mainWindow->raise();
0254     mainWindow->weView()->setFocus();
0255 
0256     if (tabWidget && tabWidget != mainWindow->tabWidget()->currentWidget()) {
0257         mainWindow->tabWidget()->setCurrentIndex(mainWindow->tabWidget()->indexOf(tabWidget));
0258     }
0259 }
0260 
0261 bool TabManagerWidget::isTabSelected()
0262 {
0263     bool selected = false;
0264     for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0265         QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(i);
0266         if (parentItem->checkState(0) != Qt::Unchecked) {
0267             selected = true;
0268             break;
0269         }
0270     }
0271 
0272     return selected;
0273 }
0274 
0275 void TabManagerWidget::customContextMenuRequested(const QPoint &pos)
0276 {
0277     QMenu* menu = nullptr;
0278 
0279     auto* item = static_cast<TabItem*>(ui->treeWidget->itemAt(pos));
0280 
0281     if (item) {
0282         BrowserWindow* mainWindow = item->window();
0283         QWidget* tabWidget = item->webTab();
0284 
0285         if (mainWindow && tabWidget) {
0286             int index = mainWindow->tabWidget()->indexOf(tabWidget);
0287 
0288             // if items are not grouped by Window then actions "Close Other Tabs",
0289             // "Close Tabs To The Bottom" and "Close Tabs To The Top"
0290             // are ambiguous and should be hidden.
0291             TabContextMenu::Options options = TabContextMenu::VerticalTabs;
0292             if (m_groupType == GroupByWindow) {
0293                 options |= TabContextMenu::ShowCloseOtherTabsActions;
0294             }
0295             menu = new TabContextMenu(index, mainWindow, options);
0296             menu->addSeparator();
0297         }
0298     }
0299 
0300     if (!menu)
0301         menu = new QMenu;
0302 
0303     menu->setAttribute(Qt::WA_DeleteOnClose);
0304 
0305     QAction* action;
0306     QMenu groupTypeSubmenu(tr("Group by"));
0307     action = groupTypeSubmenu.addAction(tr("&Window"), this, &TabManagerWidget::changeGroupType);
0308     action->setData(GroupByWindow);
0309     action->setCheckable(true);
0310     action->setChecked(m_groupType == GroupByWindow);
0311 
0312     action = groupTypeSubmenu.addAction(tr("&Domain"), this, &TabManagerWidget::changeGroupType);
0313     action->setData(GroupByDomain);
0314     action->setCheckable(true);
0315     action->setChecked(m_groupType == GroupByDomain);
0316 
0317     action = groupTypeSubmenu.addAction(tr("&Host"), this, &TabManagerWidget::changeGroupType);
0318     action->setData(GroupByHost);
0319     action->setCheckable(true);
0320     action->setChecked(m_groupType == GroupByHost);
0321 
0322     menu->addMenu(&groupTypeSubmenu);
0323 
0324     if (m_isDefaultWidget) {
0325         menu->addAction(QIcon(QSL(":/tabmanager/data/side-by-side.png")), tr("&Show side by side"), this, &TabManagerWidget::showSideBySide)->setObjectName("sideBySide");
0326     }
0327 
0328     menu->addSeparator();
0329 
0330     if (isTabSelected()) {
0331         menu->addAction(QIcon(QSL(":/tabmanager/data/tab-detach.png")), tr("&Detach checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("detachSelection");
0332         menu->addAction(QIcon(QSL(":/tabmanager/data/tab-bookmark.png")), tr("Book&mark checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("bookmarkSelection");
0333         menu->addAction(QIcon(QSL(":/tabmanager/data/tab-close.png")), tr("&Close checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("closeSelection");
0334         menu->addAction(tr("&Unload checked tabs"), this, &TabManagerWidget::processActions)->setObjectName("unloadSelection");
0335     }
0336 
0337     menu->exec(ui->treeWidget->viewport()->mapToGlobal(pos));
0338 }
0339 
0340 void TabManagerWidget::filterChanged(const QString &filter, bool force)
0341 {
0342     if (force || filter != m_filterText) {
0343         m_filterText = filter.simplified();
0344         ui->treeWidget->itemDelegate()->setProperty("filterText", m_filterText);
0345         if (m_filterText.isEmpty()) {
0346             for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0347                 QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(i);
0348                 for (int j = 0; j < parentItem->childCount(); ++j) {
0349                     QTreeWidgetItem* childItem = parentItem->child(j);
0350                     childItem->setHidden(false);
0351                 }
0352                 parentItem->setHidden(false);
0353                 parentItem->setExpanded(true);
0354             }
0355 
0356             return;
0357         }
0358 
0359         const QRegularExpression filterRegExp(filter.simplified().replace(QL1C(' '), QLatin1String(".*"))
0360                                               .append(QLatin1String(".*")).prepend(QLatin1String(".*")),
0361                                               QRegularExpression::CaseInsensitiveOption);
0362 
0363         for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0364             QTreeWidgetItem* parentItem = ui->treeWidget->topLevelItem(i);
0365             int visibleChildCount = 0;
0366             for (int j = 0; j < parentItem->childCount(); ++j) {
0367                 auto* childItem = static_cast<TabItem*>(parentItem->child(j));
0368                 if (!childItem) {
0369                     continue;
0370                 }
0371 
0372                 if (childItem->text(0).contains(filterRegExp) || childItem->webTab()->url().toString().simplified().contains(filterRegExp)) {
0373                     ++visibleChildCount;
0374                     childItem->setHidden(false);
0375                 }
0376                 else {
0377                     childItem->setHidden(true);
0378                 }
0379             }
0380 
0381             if (visibleChildCount == 0) {
0382                 parentItem->setHidden(true);
0383             }
0384             else {
0385                 parentItem->setHidden(false);
0386                 parentItem->setExpanded(true);
0387             }
0388         }
0389     }
0390 }
0391 
0392 void TabManagerWidget::filterBarClosed()
0393 {
0394     ui->filterBar->clear();
0395     ui->filterBar->hide();
0396     ui->treeWidget->setFocusProxy(nullptr);
0397     ui->treeWidget->setFocus();
0398 }
0399 
0400 bool TabManagerWidget::eventFilter(QObject* obj, QEvent* event)
0401 {
0402     if (event->type() == QEvent::KeyPress) {
0403         auto *keyEvent = static_cast<QKeyEvent *>(event);
0404         const QString text = keyEvent->text().simplified();
0405 
0406         if (obj == ui->treeWidget) {
0407             // switch to tab/window on enter
0408             if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
0409                 onItemActivated(ui->treeWidget->currentItem(), 0);
0410                 return QObject::eventFilter(obj, event);
0411             }
0412 
0413             if (!text.isEmpty() || ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_F)) {
0414                 ui->filterBar->show();
0415                 ui->treeWidget->setFocusProxy(ui->filterBar);
0416                 ui->filterBar->setFocus();
0417                 if (!text.isEmpty() && text.at(0).isPrint()) {
0418                     ui->filterBar->setText(ui->filterBar->text() + text);
0419                 }
0420 
0421                 return true;
0422             }
0423         }
0424         else if (obj == ui->filterBar) {
0425             bool isNavigationOrActionKey = keyEvent->key() == Qt::Key_Up ||
0426                     keyEvent->key() == Qt::Key_Down ||
0427                     keyEvent->key() == Qt::Key_PageDown ||
0428                     keyEvent->key() == Qt::Key_PageUp ||
0429                     keyEvent->key() == Qt::Key_Enter ||
0430                     keyEvent->key() == Qt::Key_Return;
0431 
0432             // send scroll or action press key to treeWidget
0433             if (isNavigationOrActionKey) {
0434                 QKeyEvent ev(QKeyEvent::KeyPress, keyEvent->key(), keyEvent->modifiers());
0435                 QApplication::sendEvent(ui->treeWidget, &ev);
0436                 return false;
0437             }
0438         }
0439     }
0440 
0441     if (obj == ui->treeWidget && (event->type() == QEvent::Resize || event->type() == QEvent::Show))
0442         ui->treeWidget->setColumnHidden(1, ui->treeWidget->viewport()->width() < 150);
0443 
0444     return QObject::eventFilter(obj, event);
0445 }
0446 
0447 void TabManagerWidget::processActions()
0448 {
0449     if (!sender()) {
0450         return;
0451     }
0452 
0453     m_refreshBlocked = true;
0454 
0455     QMultiHash<BrowserWindow*, WebTab*> selectedTabs;
0456 
0457     const QString &command = sender()->objectName();
0458 
0459     for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
0460         QTreeWidgetItem* winItem = ui->treeWidget->topLevelItem(i);
0461         if (winItem->checkState(0) == Qt::Unchecked) {
0462             continue;
0463         }
0464 
0465         for (int j = 0; j < winItem->childCount(); ++j) {
0466             auto* tabItem = static_cast<TabItem*>(winItem->child(j));
0467             if (!tabItem || tabItem->checkState(0) == Qt::Unchecked) {
0468                 continue;
0469             }
0470 
0471             BrowserWindow* mainWindow = tabItem->window();
0472             WebTab* webTab = tabItem->webTab();
0473 
0474             // current supported actions are not applied to pinned tabs
0475             if (webTab->isPinned()) {
0476                 tabItem->setCheckState(0, Qt::Unchecked);
0477                 continue;
0478             }
0479 
0480             selectedTabs.insert(mainWindow, webTab);
0481         }
0482         winItem->setCheckState(0, Qt::Unchecked);
0483     }
0484 
0485     if (!selectedTabs.isEmpty()) {
0486         if (command == QSL("closeSelection")) {
0487             closeSelectedTabs(selectedTabs);
0488         }
0489         else if (command == QSL("detachSelection")) {
0490             detachSelectedTabs(selectedTabs);
0491         }
0492         else if (command == QSL("bookmarkSelection")) {
0493             bookmarkSelectedTabs(selectedTabs);
0494         }
0495         else if (command == QSL("unloadSelection")) {
0496             unloadSelectedTabs(selectedTabs);
0497         }
0498     }
0499 
0500     m_refreshBlocked = false;
0501     delayedRefreshTree();
0502 }
0503 
0504 void TabManagerWidget::changeGroupType()
0505 {
0506     auto* action = qobject_cast<QAction*>(sender());
0507 
0508     if (action) {
0509         int type = action->data().toInt();
0510 
0511         if (m_groupType != GroupType(type)) {
0512             m_groupType = GroupType(type);
0513 
0514             delayedRefreshTree();
0515 
0516             Q_EMIT groupTypeChanged(m_groupType);
0517         }
0518     }
0519 }
0520 
0521 void TabManagerWidget::closeSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
0522 {
0523     if (tabsHash.isEmpty()) {
0524         return;
0525     }
0526 
0527     const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
0528     for (BrowserWindow* mainWindow : windows) {
0529         const QList<WebTab*> tabs = tabsHash.values(mainWindow);
0530 
0531         for (WebTab* webTab : tabs) {
0532             mainWindow->tabWidget()->requestCloseTab(webTab->tabIndex());
0533         }
0534     }
0535 }
0536 
0537 static void detachTabsTo(BrowserWindow* targetWindow, const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
0538 {
0539     const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
0540     for (BrowserWindow* mainWindow : windows) {
0541         const QList<WebTab*> &tabs = tabsHash.values(mainWindow);
0542         for (WebTab* webTab : tabs) {
0543             mainWindow->tabWidget()->detachTab(webTab);
0544 
0545             if (mainWindow && mainWindow->tabCount() == 0) {
0546                 mainWindow->close();
0547                 mainWindow = nullptr;
0548             }
0549 
0550             targetWindow->tabWidget()->addView(webTab, Qz::NT_NotSelectedTab);
0551         }
0552     }
0553 }
0554 
0555 void TabManagerWidget::detachSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
0556 {
0557     if (tabsHash.isEmpty() ||
0558             (tabsHash.uniqueKeys().size() == 1 &&
0559              tabsHash.size() == tabsHash.keys().at(0)->tabCount())) {
0560         return;
0561     }
0562 
0563     BrowserWindow* newWindow = mApp->createWindow(Qz::BW_OtherRestoredWindow);
0564     const QRect &availableGeometryForScreen = screen()->availableGeometry();
0565     newWindow->move(availableGeometryForScreen.topLeft() + QPoint(30, 30));
0566 
0567     detachTabsTo(newWindow, tabsHash);
0568 }
0569 
0570 bool TabManagerWidget::bookmarkSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
0571 {
0572     auto* dialog = new QDialog(getWindow(), Qt::WindowStaysOnTopHint | Qt::MSWindowsFixedSizeDialogHint);
0573     auto* layout = new QBoxLayout(QBoxLayout::TopToBottom, dialog);
0574     auto* label = new QLabel(dialog);
0575     auto* folderButton = new BookmarksFoldersButton(dialog);
0576 
0577     auto* box = new QDialogButtonBox(dialog);
0578     box->addButton(QDialogButtonBox::Ok);
0579     box->addButton(QDialogButtonBox::Cancel);
0580     QObject::connect(box, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
0581     QObject::connect(box, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
0582 
0583     layout->addWidget(label);
0584     layout->addWidget(folderButton);
0585     layout->addWidget(box);
0586 
0587     label->setText(tr("Choose folder for bookmarks:"));
0588     dialog->setWindowTitle(tr("Bookmark Selected Tabs"));
0589 
0590     QSize size = dialog->size();
0591     size.setWidth(350);
0592     dialog->resize(size);
0593     dialog->exec();
0594 
0595     if (dialog->result() == QDialog::Rejected) {
0596         return false;
0597     }
0598 
0599     for (WebTab* tab : tabsHash) {
0600         if (!tab->url().isEmpty()) {
0601             auto* bookmark = new BookmarkItem(BookmarkItem::Url);
0602             bookmark->setTitle(tab->title());
0603             bookmark->setUrl(tab->url());
0604             mApp->bookmarks()->addBookmark(folderButton->selectedFolder(), bookmark);
0605         }
0606     }
0607 
0608     delete dialog;
0609     return true;
0610 }
0611 
0612 void TabManagerWidget::unloadSelectedTabs(const QMultiHash<BrowserWindow*, WebTab*> &tabsHash)
0613 {
0614     if (tabsHash.isEmpty()) {
0615         return;
0616     }
0617 
0618     const QList<BrowserWindow*> &windows = tabsHash.uniqueKeys();
0619     for (BrowserWindow* mainWindow : windows) {
0620         const QList<WebTab*> tabs = tabsHash.values(mainWindow);
0621 
0622         for (WebTab* webTab : tabs) {
0623             mainWindow->tabWidget()->unloadTab(webTab->tabIndex());
0624         }
0625     }
0626 }
0627 
0628 QTreeWidgetItem* TabManagerWidget::groupByDomainName(bool useHostName)
0629 {
0630     QTreeWidgetItem* currentTabItem = nullptr;
0631 
0632     QList<BrowserWindow*> windows = mApp->windows();
0633     int currentWindowIdx = windows.indexOf(getWindow());
0634     if (currentWindowIdx == -1) {
0635         // getWindow() instance is closing
0636         return nullptr;
0637     }
0638 
0639     QMap<QString, QTreeWidgetItem*> tabsGroupedByDomain;
0640 
0641     for (int win = 0; win < windows.count(); ++win) {
0642         BrowserWindow* mainWin = windows.at(win);
0643 
0644         QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
0645 
0646         for (int tab = 0; tab < tabs.count(); ++tab) {
0647             WebTab* webTab = tabs.at(tab);
0648             if (webTab->webView() && m_webPage == webTab->webView()->page()) {
0649                 m_webPage = nullptr;
0650                 continue;
0651             }
0652             QString domain = domainFromUrl(webTab->url(), useHostName);
0653 
0654             if (!tabsGroupedByDomain.contains(domain)) {
0655                 auto* groupItem = new TabItem(ui->treeWidget, false, false, nullptr, false);
0656                 groupItem->setTitle(domain);
0657                 groupItem->setIsActiveOrCaption(true);
0658 
0659                 tabsGroupedByDomain.insert(domain, groupItem);
0660             }
0661 
0662             QTreeWidgetItem* groupItem = tabsGroupedByDomain.value(domain);
0663 
0664             auto* tabItem = new TabItem(ui->treeWidget, false, true, groupItem);
0665             tabItem->setBrowserWindow(mainWin);
0666             tabItem->setWebTab(webTab);
0667 
0668             if (webTab == mainWin->weView()->webTab()) {
0669                 tabItem->setIsActiveOrCaption(true);
0670 
0671                 if (mainWin == getWindow())
0672                     currentTabItem = tabItem;
0673             }
0674 
0675 
0676             tabItem->updateIcon();
0677             tabItem->setTitle(webTab->title());
0678         }
0679     }
0680 
0681     ui->treeWidget->insertTopLevelItems(0, tabsGroupedByDomain.values());
0682 
0683     return currentTabItem;
0684 }
0685 
0686 QTreeWidgetItem* TabManagerWidget::groupByWindow()
0687 {
0688     QTreeWidgetItem* currentTabItem = nullptr;
0689 
0690     QList<BrowserWindow*> windows = mApp->windows();
0691     int currentWindowIdx = windows.indexOf(getWindow());
0692     if (currentWindowIdx == -1) {
0693         return nullptr;
0694     }
0695     m_isRefreshing = true;
0696 
0697     if (!m_isDefaultWidget) {
0698         windows.move(currentWindowIdx, 0);
0699         currentWindowIdx = 0;
0700     }
0701 
0702     for (int win = 0; win < windows.count(); ++win) {
0703         BrowserWindow* mainWin = windows.at(win);
0704         auto* winItem = new TabItem(ui->treeWidget, true, false);
0705         winItem->setBrowserWindow(mainWin);
0706         winItem->setText(0, tr("Window %1").arg(QString::number(win + 1)));
0707         winItem->setToolTip(0, tr("Double click to switch"));
0708         winItem->setIsActiveOrCaption(win == currentWindowIdx);
0709 
0710         QList<WebTab*> tabs = mainWin->tabWidget()->allTabs();
0711 
0712         for (int tab = 0; tab < tabs.count(); ++tab) {
0713             WebTab* webTab = tabs.at(tab);
0714             if (webTab->webView() && m_webPage == webTab->webView()->page()) {
0715                 m_webPage = nullptr;
0716                 continue;
0717             }
0718             auto* tabItem = new TabItem(ui->treeWidget, true, true, winItem);
0719             tabItem->setBrowserWindow(mainWin);
0720             tabItem->setWebTab(webTab);
0721 
0722             if (webTab == mainWin->weView()->webTab()) {
0723                 tabItem->setIsActiveOrCaption(true);
0724 
0725                 if (mainWin == getWindow())
0726                     currentTabItem = tabItem;
0727             }
0728 
0729             tabItem->updateIcon();
0730             tabItem->setTitle(webTab->title());
0731         }
0732     }
0733 
0734     return currentTabItem;
0735 }
0736 
0737 BrowserWindow* TabManagerWidget::getWindow()
0738 {
0739     if (m_isDefaultWidget || !m_window) {
0740         return mApp->getWindow();
0741     }
0742     else {
0743         return m_window.data();
0744     }
0745 }
0746 
0747 TabItem::TabItem(QTreeWidget* treeWidget, bool supportDrag, bool isTab, QTreeWidgetItem* parent, bool addToTree)
0748     : QObject()
0749     , QTreeWidgetItem(addToTree ? (parent ? parent : treeWidget->invisibleRootItem()) : nullptr, 1)
0750     , m_treeWidget(treeWidget)
0751     , m_window(nullptr)
0752     , m_webTab(nullptr)
0753     , m_isTab(isTab)
0754 {
0755     Qt::ItemFlags flgs = flags() | (parent ? Qt::ItemIsUserCheckable : Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
0756 
0757     if (supportDrag) {
0758         if (isTab) {
0759             flgs |= Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren;
0760             flgs &= ~Qt::ItemIsDropEnabled;
0761         }
0762         else {
0763             flgs |= Qt::ItemIsDropEnabled;
0764             flgs &= ~Qt::ItemIsDragEnabled;
0765         }
0766     }
0767 
0768     setFlags(flgs);
0769 
0770     setCheckState(0, Qt::Unchecked);
0771 }
0772 
0773 BrowserWindow* TabItem::window() const
0774 {
0775     return m_window;
0776 }
0777 
0778 void TabItem::setBrowserWindow(BrowserWindow* window)
0779 {
0780     m_window = window;
0781 }
0782 
0783 WebTab* TabItem::webTab() const
0784 {
0785     return m_webTab;
0786 }
0787 
0788 void TabItem::setWebTab(WebTab* webTab)
0789 {
0790     m_webTab = webTab;
0791 
0792     if (m_webTab->isRestored())
0793         setIsActiveOrCaption(m_webTab->isCurrentTab());
0794     else
0795         setIsSavedTab(true);
0796 
0797     connect(m_webTab->webView(), &QWebEngineView::titleChanged, this, &TabItem::setTitle);
0798     connect(m_webTab->webView(), &QWebEngineView::iconChanged, this, &TabItem::updateIcon);
0799 
0800     auto pageChanged = [this](WebPage *page) {
0801         connect(page, &WebPage::audioMutedChanged, this, &TabItem::updateIcon);
0802         connect(page, &WebPage::loadFinished, this, &TabItem::updateIcon);
0803         connect(page, &WebPage::loadStarted, this, &TabItem::updateIcon);
0804     };
0805     pageChanged(m_webTab->webView()->page());
0806     connect(m_webTab->webView(), &WebView::pageChanged, this, pageChanged);
0807 }
0808 
0809 void TabItem::updateIcon()
0810 {
0811     if (!m_webTab)
0812         return;
0813 
0814     if (!m_webTab->isLoading()) {
0815         if (!m_webTab->isPinned()) {
0816             if (m_webTab->isMuted()) {
0817                 setIcon(0, QIcon::fromTheme(QSL("audio-volume-muted"), QIcon(QSL(":icons/other/audiomuted.svg"))));
0818             }
0819             else if (!m_webTab->isMuted() && m_webTab->webView()->page()->recentlyAudible()) {
0820                 setIcon(0, QIcon::fromTheme(QSL("audio-volume-high"), QIcon(QSL(":icons/other/audioplaying.svg"))));
0821             }
0822             else {
0823                 setIcon(0, m_webTab->icon());
0824             }
0825         }
0826         else {
0827             setIcon(0, QIcon(QSL(":tabmanager/data/tab-pinned.png")));
0828         }
0829 
0830         if (m_webTab->isRestored())
0831             setIsActiveOrCaption(m_webTab->isCurrentTab());
0832         else
0833             setIsSavedTab(true);
0834     }
0835     else {
0836         setIcon(0, QIcon(QSL(":tabmanager/data/tab-loading.png")));
0837         setIsActiveOrCaption(m_webTab->isCurrentTab());
0838     }
0839 }
0840 
0841 void TabItem::setTitle(const QString &title)
0842 {
0843     setText(0, title);
0844     setToolTip(0, title);
0845 }
0846 
0847 void TabItem::setIsActiveOrCaption(bool yes)
0848 {
0849     setData(0, ActiveOrCaptionRole, yes ? QVariant(true) : QVariant());
0850 
0851     setIsSavedTab(false);
0852 }
0853 
0854 void TabItem::setIsSavedTab(bool yes)
0855 {
0856     setData(0, SavedRole, yes ? QVariant(true) : QVariant());
0857 }
0858 
0859 bool TabItem::isTab() const
0860 {
0861     return m_isTab;
0862 }
0863 
0864 TabTreeWidget::TabTreeWidget(QWidget *parent)
0865     : QTreeWidget(parent)
0866 {
0867     invisibleRootItem()->setFlags(invisibleRootItem()->flags() & ~Qt::ItemIsDropEnabled);
0868 }
0869 
0870 Qt::DropActions TabTreeWidget::supportedDropActions() const
0871 {
0872     return Qt::MoveAction | Qt::CopyAction;
0873 }
0874 
0875 #define MIMETYPE QLatin1String("application/falkon.tabs")
0876 
0877 QStringList TabTreeWidget::mimeTypes() const
0878 {
0879     QStringList types;
0880     types.append(MIMETYPE);
0881     return types;
0882 }
0883 
0884 QMimeData *TabTreeWidget::mimeData(const QList<QTreeWidgetItem*> &items) const
0885 {
0886     auto* mimeData = new QMimeData();
0887     QByteArray encodedData;
0888 
0889     QDataStream stream(&encodedData, QIODevice::WriteOnly);
0890 
0891     if (items.size() > 0) {
0892         auto* tabItem = static_cast<TabItem*>(items.at(0));
0893         if (!tabItem || !tabItem->isTab())
0894             return nullptr;
0895 
0896         stream << (quintptr) tabItem->window() << (quintptr) tabItem->webTab();
0897 
0898         mimeData->setData(MIMETYPE, encodedData);
0899 
0900         return mimeData;
0901     }
0902 
0903     return nullptr;
0904 }
0905 
0906 bool TabTreeWidget::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
0907 {
0908     if (action == Qt::IgnoreAction) {
0909         return true;
0910     }
0911 
0912     auto* parentItem = static_cast<TabItem*>(parent);
0913 
0914     if (!data->hasFormat(MIMETYPE) || !parentItem) {
0915         return false;
0916     }
0917 
0918     Q_ASSERT(!parentItem->isTab());
0919 
0920     BrowserWindow* targetWindow = parentItem->window();
0921 
0922     QByteArray encodedData = data->data(MIMETYPE);
0923     QDataStream stream(&encodedData, QIODevice::ReadOnly);
0924 
0925     if (!stream.atEnd()) {
0926         quintptr webTabPtr;
0927         quintptr windowPtr;
0928 
0929         stream >> windowPtr >> webTabPtr;
0930 
0931         auto* webTab = (WebTab*) webTabPtr;
0932         auto* window = (BrowserWindow*) windowPtr;
0933 
0934         if (window == targetWindow) {
0935             if (index > 0 && webTab->tabIndex() < index)
0936                 --index;
0937 
0938             if (webTab->isPinned() && index >= targetWindow->tabWidget()->pinnedTabsCount())
0939                 index = targetWindow->tabWidget()->pinnedTabsCount() - 1;
0940 
0941             if (!webTab->isPinned() && index < targetWindow->tabWidget()->pinnedTabsCount())
0942                 index = targetWindow->tabWidget()->pinnedTabsCount();
0943 
0944             if (index != webTab->tabIndex()) {
0945                 targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
0946 
0947                 if (!webTab->isCurrentTab())
0948                     Q_EMIT requestRefreshTree();
0949             }
0950             else {
0951                 return false;
0952             }
0953         }
0954         else if (!webTab->isPinned()) {
0955             QMultiHash<BrowserWindow*, WebTab*> tabsHash;
0956             tabsHash.insert(window, webTab);
0957 
0958             detachTabsTo(targetWindow, tabsHash);
0959 
0960             if (index < targetWindow->tabWidget()->pinnedTabsCount())
0961                 index = targetWindow->tabWidget()->pinnedTabsCount();
0962 
0963             targetWindow->tabWidget()->tabBar()->moveTab(webTab->tabIndex(), index);
0964         }
0965     }
0966 
0967     return true;
0968 }
0969 
0970 void TabTreeWidget::setEnableDragTabs(bool enable)
0971 {
0972     setDragEnabled(enable);
0973     setAcceptDrops(enable);
0974     viewport()->setAcceptDrops(enable);
0975     setDropIndicatorShown(enable);
0976 }