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 }