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"