File indexing completed on 2024-04-28 05:11:03
0001 /* 0002 This file is part of Akregator. 0003 0004 SPDX-FileCopyrightText: 2004 Sashmit Bhaduri <smt@vfemail.net> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0007 */ 0008 0009 #include "tabwidget.h" 0010 0011 #include <QHash> 0012 #include <QIcon> 0013 #include <QString> 0014 #include <QStyle> 0015 #include <QToolButton> 0016 0017 #include <QMenu> 0018 #include <QStyleOption> 0019 0020 #include <KIO/Global> 0021 #include <KLocalizedString> 0022 #include <QTabBar> 0023 0024 #include "akregatorconfig.h" 0025 #include "frame.h" 0026 #include "openurlrequest.h" 0027 0028 using namespace Akregator; 0029 0030 class Akregator::TabWidgetPrivate 0031 { 0032 private: 0033 TabWidget *const q; 0034 0035 public: 0036 explicit TabWidgetPrivate(TabWidget *qq) 0037 : q(qq) 0038 { 0039 } 0040 0041 QHash<QWidget *, Frame *> frames; 0042 QHash<int, Frame *> framesById; 0043 int currentMaxLength = 30; 0044 QWidget *currentItem = nullptr; 0045 QToolButton *tabsClose = nullptr; 0046 0047 [[nodiscard]] QWidget *selectedWidget() const 0048 { 0049 return (currentItem && q->indexOf(currentItem) != -1) ? currentItem : q->currentWidget(); 0050 } 0051 0052 int tabBarWidthForMaxChars(int maxLength); 0053 void setTitle(const QString &title, QWidget *sender); 0054 void updateTabBarVisibility(); 0055 Frame *currentFrame(); 0056 }; 0057 0058 void TabWidgetPrivate::updateTabBarVisibility() 0059 { 0060 const bool tabBarIsHidden = ((q->count() <= 1) && !Settings::alwaysShowTabBar()); 0061 if (tabBarIsHidden) { 0062 q->tabBar()->hide(); 0063 } else { 0064 q->tabBar()->show(); 0065 } 0066 if (q->count() >= 1 && Settings::closeButtonOnTabs()) { 0067 q->tabBar()->tabButton(0, QTabBar::RightSide)->hide(); 0068 } 0069 } 0070 0071 TabWidget::TabWidget(QWidget *parent) 0072 : QTabWidget(parent) 0073 , d(new TabWidgetPrivate(this)) 0074 { 0075 setMinimumSize(250, 150); 0076 setMovable(false); 0077 setDocumentMode(true); 0078 setContextMenuPolicy(Qt::CustomContextMenu); 0079 connect(this, &TabWidget::customContextMenuRequested, this, &TabWidget::slotTabContextMenuRequest); 0080 0081 connect(this, &TabWidget::currentChanged, this, &TabWidget::slotTabChanged); 0082 connect(this, &QTabWidget::tabCloseRequested, this, &TabWidget::slotCloseRequest); 0083 0084 setTabsClosable(Settings::closeButtonOnTabs()); 0085 0086 d->tabsClose = new QToolButton(this); 0087 connect(d->tabsClose, &QToolButton::clicked, this, &TabWidget::slotRemoveCurrentFrame); 0088 0089 d->tabsClose->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); 0090 d->tabsClose->setEnabled(false); 0091 d->tabsClose->adjustSize(); 0092 d->tabsClose->setToolTip(i18n("Close the current tab")); 0093 0094 #ifndef QT_NO_ACCESSIBILITY 0095 d->tabsClose->setAccessibleName(i18n("Close tab")); 0096 #endif 0097 0098 setCornerWidget(d->tabsClose, Qt::TopRightCorner); 0099 d->updateTabBarVisibility(); 0100 } 0101 0102 TabWidget::~TabWidget() = default; 0103 0104 void TabWidget::slotTabContextMenuRequest(const QPoint &pos) 0105 { 0106 QTabBar *bar = tabBar(); 0107 if (count() <= 1) { 0108 return; 0109 } 0110 0111 const int indexBar = bar->tabAt(bar->mapFrom(this, pos)); 0112 if (indexBar == -1) { 0113 return; 0114 } 0115 QMenu menu(this); 0116 0117 const int countTab = (count() > 1); 0118 QAction *detachTab = menu.addAction(i18nc("@action:inmenu", "Detach Tab")); 0119 detachTab->setEnabled((indexBar != 0) && countTab); 0120 detachTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); 0121 menu.addSeparator(); 0122 0123 QAction *closeTab = menu.addAction(i18nc("@action:inmenu", "Close Tab")); 0124 closeTab->setEnabled((indexBar != 0) && countTab); 0125 closeTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); 0126 0127 QAction *allOther = menu.addAction(i18nc("@action:inmenu", "Close All Other Tabs")); 0128 allOther->setEnabled(countTab); 0129 allOther->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); 0130 0131 QAction *allTab = menu.addAction(i18nc("@action:inmenu", "Close All Tabs")); 0132 allTab->setEnabled(countTab); 0133 allTab->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); 0134 0135 QAction *action = menu.exec(mapToGlobal(pos)); 0136 0137 if (action == allOther) { // Close all other tabs 0138 slotCloseAllTabExcept(indexBar); 0139 } else if (action == closeTab) { 0140 slotCloseRequest(indexBar); 0141 } else if (action == allTab) { 0142 slotCloseAllTab(); 0143 } else if (action == detachTab) { 0144 slotDetachTab(indexBar); 0145 } 0146 } 0147 0148 void TabWidget::closeAllTabExcept(int index) 0149 { 0150 // Don't close first tab 0151 for (int i = count() - 1; i > 0; --i) { 0152 if (i == index) { 0153 continue; 0154 } 0155 slotCloseRequest(i); 0156 } 0157 } 0158 0159 void TabWidget::slotCloseAllTabExcept(int index) 0160 { 0161 closeAllTabExcept(index); 0162 } 0163 0164 void TabWidget::slotCloseAllTab() 0165 { 0166 closeAllTabExcept(); 0167 } 0168 0169 void TabWidget::slotSettingsChanged() 0170 { 0171 if (tabsClosable() != Settings::closeButtonOnTabs()) { 0172 setTabsClosable(Settings::closeButtonOnTabs()); 0173 } 0174 d->updateTabBarVisibility(); 0175 } 0176 0177 void TabWidget::slotNextTab() 0178 { 0179 setCurrentIndex((currentIndex() + 1) % count()); 0180 } 0181 0182 void TabWidget::slotPreviousTab() 0183 { 0184 if (currentIndex() == 0) { 0185 setCurrentIndex(count() - 1); 0186 } else { 0187 setCurrentIndex(currentIndex() - 1); 0188 } 0189 } 0190 0191 void TabWidget::slotSelectFrame(int frameId) 0192 { 0193 Frame *frame = d->framesById.value(frameId); 0194 if (frame && frame != d->currentFrame()) { 0195 setCurrentWidget(frame); 0196 frame->setFocus(); 0197 } 0198 } 0199 0200 void TabWidget::slotAddFrame(Frame *frame) 0201 { 0202 if (!frame) { 0203 return; 0204 } 0205 d->frames.insert(frame, frame); 0206 d->framesById.insert(frame->id(), frame); 0207 addTab(frame, frame->title()); 0208 connect(frame, &Frame::signalTitleChanged, this, &TabWidget::slotSetTitle); 0209 0210 slotSetTitle(frame, frame->title()); 0211 } 0212 0213 Frame *TabWidgetPrivate::currentFrame() 0214 { 0215 QWidget *w = q->currentWidget(); 0216 Q_ASSERT(frames.value(w)); 0217 return w ? frames.value(w) : nullptr; 0218 } 0219 0220 void TabWidget::slotZoomChanged(qreal value) 0221 { 0222 if (!d->currentFrame()) { 0223 return; 0224 } 0225 Q_EMIT signalZoomChangedInFrame(d->currentFrame()->id(), value); 0226 } 0227 0228 void TabWidget::slotTabChanged(int index) 0229 { 0230 Frame *frame = d->frames.value(widget(index)); 0231 d->tabsClose->setEnabled(frame && frame->isRemovable()); 0232 Q_EMIT signalCurrentFrameChanged(frame ? frame->id() : -1); 0233 } 0234 0235 void TabWidget::tabInserted(int) 0236 { 0237 d->updateTabBarVisibility(); 0238 } 0239 0240 void TabWidget::tabRemoved(int) 0241 { 0242 d->updateTabBarVisibility(); 0243 } 0244 0245 void TabWidget::slotRemoveCurrentFrame() 0246 { 0247 Frame *const frame = d->currentFrame(); 0248 if (frame) { 0249 Q_EMIT signalRemoveFrameRequest(frame->id()); 0250 } 0251 } 0252 0253 void TabWidget::slotRemoveFrame(int frameId) 0254 { 0255 if (!d->framesById.contains(frameId)) { 0256 return; 0257 } 0258 Frame *f = d->framesById.value(frameId); 0259 d->frames.remove(f); 0260 d->framesById.remove(frameId); 0261 f->disconnect(this); 0262 removeTab(indexOf(f)); 0263 Q_EMIT signalRemoveFrameRequest(f->id()); 0264 if (d->currentFrame()) { 0265 d->setTitle(d->currentFrame()->title(), currentWidget()); 0266 } 0267 } 0268 0269 // copied wholesale from KonqFrameTabs 0270 int TabWidgetPrivate::tabBarWidthForMaxChars(int maxLength) 0271 { 0272 int hframe; 0273 QStyleOption o; 0274 hframe = q->tabBar()->style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &o, q); 0275 0276 QFontMetrics fm = q->tabBar()->fontMetrics(); 0277 int x = 0; 0278 for (int i = 0; i < q->count(); ++i) { 0279 Frame *f = frames.value(q->widget(i)); 0280 if (!f) { 0281 continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 0282 } 0283 QString newTitle = f->title(); 0284 if (newTitle.length() > maxLength) { 0285 newTitle = newTitle.left(maxLength - 3) + QLatin1StringView("..."); 0286 } 0287 0288 int lw = fm.boundingRect(newTitle).width(); 0289 int iw = q->tabBar()->tabIcon(i).pixmap(q->tabBar()->style()->pixelMetric(QStyle::PM_SmallIconSize), QIcon::Normal).width() + 4; 0290 0291 x += (q->tabBar()->style()->sizeFromContents(QStyle::CT_TabBarTab, &o, QSize(lw + hframe + iw, 0), q)).width(); 0292 } 0293 return x; 0294 } 0295 0296 void TabWidget::slotSetTitle(Frame *frame, const QString &title) 0297 { 0298 d->setTitle(title, frame); 0299 } 0300 0301 void TabWidget::slotWebPageMutedOrAudibleChanged(Akregator::Frame *frame, bool isAudioMuted, bool wasRecentlyAudible) 0302 { 0303 Q_UNUSED(wasRecentlyAudible) 0304 const int idx = indexOf(frame); 0305 if (idx < 0) { 0306 return; 0307 } 0308 if (isAudioMuted) { 0309 setTabIcon(idx, QIcon::fromTheme(QStringLiteral("audio-volume-muted"))); 0310 } else { 0311 setTabIcon(idx, frame->icon()); 0312 } 0313 } 0314 0315 void TabWidget::slotSetIcon(Akregator::Frame *frame, const QIcon &icon) 0316 { 0317 const int idx = indexOf(frame); 0318 if (idx < 0) { 0319 return; 0320 } 0321 frame->setIcon(icon); 0322 setTabIcon(idx, icon); 0323 } 0324 0325 void TabWidgetPrivate::setTitle(const QString &title, QWidget *sender) 0326 { 0327 int senderIndex = q->indexOf(sender); 0328 0329 q->setTabToolTip(senderIndex, QString()); 0330 0331 int lcw = 0; 0332 int rcw = 0; 0333 int tabBarHeight = q->tabBar()->sizeHint().height(); 0334 0335 QWidget *leftCorner = q->cornerWidget(Qt::TopLeftCorner); 0336 0337 if (leftCorner && leftCorner->isVisible()) { 0338 lcw = qMax(leftCorner->width(), tabBarHeight); 0339 } 0340 0341 QWidget *rightCorner = q->cornerWidget(Qt::TopRightCorner); 0342 0343 if (rightCorner && rightCorner->isVisible()) { 0344 rcw = qMax(rightCorner->width(), tabBarHeight); 0345 } 0346 int maxTabBarWidth = q->width() - lcw - rcw; 0347 0348 int newMaxLength = 30; 0349 0350 for (; newMaxLength > 3; newMaxLength--) { 0351 if (tabBarWidthForMaxChars(newMaxLength) < maxTabBarWidth) { 0352 break; 0353 } 0354 } 0355 0356 QString newTitle = title; 0357 if (newTitle.length() > newMaxLength) { 0358 q->setTabToolTip(senderIndex, newTitle); 0359 newTitle = newTitle.left(newMaxLength - 3) + QLatin1StringView("..."); 0360 } 0361 0362 newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); 0363 0364 if (q->tabText(senderIndex) != newTitle) { 0365 q->setTabText(senderIndex, newTitle); 0366 } 0367 0368 if (newMaxLength != currentMaxLength) { 0369 for (int i = 0; i < q->count(); ++i) { 0370 Frame *f = frames.value(q->widget(i)); 0371 if (!f) { 0372 continue; // frames is out of sync, e.g. because tabInserted wasn't called yet - #185597 0373 } 0374 newTitle = f->title(); 0375 int index = q->indexOf(q->widget(i)); 0376 q->setTabToolTip(index, QString()); 0377 0378 if (newTitle.length() > newMaxLength) { 0379 q->setTabToolTip(index, newTitle); 0380 newTitle = newTitle.left(newMaxLength - 3) + QLatin1StringView("..."); 0381 } 0382 0383 newTitle.replace(QLatin1Char('&'), QStringLiteral("&&")); 0384 if (newTitle != q->tabText(index)) { 0385 q->setTabText(index, newTitle); 0386 } 0387 } 0388 currentMaxLength = newMaxLength; 0389 } 0390 } 0391 0392 void TabWidget::slotDetachTab(int index) 0393 { 0394 QWidget *w = widget(index); 0395 Frame *frame = d->frames.value(w); 0396 if (frame && frame->url().isValid() && frame->isRemovable()) { 0397 OpenUrlRequest request; 0398 request.setUrl(frame->url()); 0399 request.setOptions(OpenUrlRequest::ExternalBrowser); 0400 Q_EMIT signalOpenUrlRequest(request); 0401 slotCloseRequest(index); 0402 } 0403 } 0404 0405 void TabWidget::slotTextToSpeech() 0406 { 0407 Q_EMIT signalTextToSpeechInFrame(d->currentFrame()->id()); 0408 } 0409 0410 void TabWidget::slotFindTextInHtml() 0411 { 0412 Q_EMIT signalFindTextInFrame(d->currentFrame()->id()); 0413 } 0414 0415 void TabWidget::slotCopyLinkAddress() 0416 { 0417 Q_EMIT signalCopyLinkAsInFrame(d->currentFrame()->id()); 0418 } 0419 0420 void TabWidget::slotSaveLinkAs() 0421 { 0422 Q_EMIT signalSaveLinkAsInFrame(d->currentFrame()->id()); 0423 } 0424 0425 void TabWidget::slotPrintPreview() 0426 { 0427 Q_EMIT signalPrintPreviewInFrame(d->currentFrame()->id()); 0428 } 0429 0430 void TabWidget::slotPrint() 0431 { 0432 Q_EMIT signalPrintInFrame(d->currentFrame()->id()); 0433 } 0434 0435 void TabWidget::slotCopy() 0436 { 0437 Q_EMIT signalCopyInFrame(d->currentFrame()->id()); 0438 } 0439 0440 void TabWidget::slotSaveImageOnDisk() 0441 { 0442 Q_EMIT signalSaveImageOnDisk(d->currentFrame()->id()); 0443 } 0444 0445 void TabWidget::slotUnMute() 0446 { 0447 Q_EMIT signalMute(d->currentFrame()->id(), false); 0448 } 0449 0450 void TabWidget::slotMute() 0451 { 0452 Q_EMIT signalMute(d->currentFrame()->id(), true); 0453 } 0454 0455 void TabWidget::slotCopyImageLocation() 0456 { 0457 Q_EMIT signalCopyImageLocation(d->currentFrame()->id()); 0458 } 0459 0460 void TabWidget::slotCloseTab() 0461 { 0462 QWidget *widget = d->selectedWidget(); 0463 Frame *frame = d->frames.value(widget); 0464 0465 if (frame == nullptr || !frame->isRemovable()) { 0466 return; 0467 } 0468 0469 Q_EMIT signalRemoveFrameRequest(frame->id()); 0470 } 0471 0472 void TabWidget::slotReloadAllTabs() 0473 { 0474 for (Frame *frame : std::as_const(d->frames)) { 0475 frame->slotReload(); 0476 } 0477 } 0478 0479 void TabWidget::slotCloseRequest(int index) 0480 { 0481 QWidget *w = widget(index); 0482 if (d->frames.value(w)) { 0483 Q_EMIT signalRemoveFrameRequest(d->frames.value(w)->id()); 0484 } 0485 } 0486 0487 void TabWidget::slotActivateTab() 0488 { 0489 setCurrentIndex(QStringView(sender()->objectName()).right(2).toInt() - 1); 0490 } 0491 0492 #include "moc_tabwidget.cpp"