File indexing completed on 2024-04-28 04:37:31

0001 /*
0002     SPDX-FileCopyrightText: 2006-2009 Alexander Dymo <adymo@kdevelop.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "container.h"
0008 
0009 #include <QApplication>
0010 #include <QBoxLayout>
0011 #include <QClipboard>
0012 #include <QDir>
0013 #include <QEvent>
0014 #include <QSpacerItem>
0015 #include <QLabel>
0016 #include <QMenu>
0017 #include <QMouseEvent>
0018 #include <QPointer>
0019 #include <QStackedWidget>
0020 #include <QStyleFactory>
0021 #include <QStyleOptionTabBarBase>
0022 #include <QStylePainter>
0023 #include <QTabBar>
0024 #include <QToolButton>
0025 #include <QWindow>
0026 
0027 #include <KAcceleratorManager>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 #include <KSharedConfig>
0031 
0032 #include "view.h"
0033 #include "urldocument.h"
0034 
0035 #include <KSqueezedTextLabel>
0036 
0037 #include <algorithm>
0038 #include <utility>
0039 
0040 namespace {
0041 /**
0042  * @return the last two segments of @p document's path or an empty string in case of failure.
0043  */
0044 QString documentDirAndFilename(const Sublime::Document* document)
0045 {
0046     auto* const urlDocument = qobject_cast<const Sublime::UrlDocument*>(document);
0047     if (!urlDocument) {
0048         return QString{};
0049     }
0050     const auto path = urlDocument->url().path();
0051 
0052     const auto lastSlashIndex = path.lastIndexOf(QLatin1Char{'/'});
0053     if (lastSlashIndex <= 0) {
0054         return path; // either no slash in path or a single slash is the first symbol of path
0055     }
0056 
0057     const auto penultimateSlashIndex = path.lastIndexOf(QLatin1Char{'/'}, lastSlashIndex - 1);
0058     // If there is only one slash in the path, penultimateSlashIndex equals -1 and we correctly return the whole path.
0059     return path.mid(penultimateSlashIndex + 1);
0060 }
0061 
0062 struct ActionInsertionPoint
0063 {
0064     QAction* previous;
0065     QAction* next;
0066 };
0067 
0068 /**
0069  * Find a suitable insertion point in a list of actions.
0070  *
0071  * @param actions a list of actions ordered by title of their associated document case-insensitively.
0072  * @param actionDocumentTitle the title of the document associated with the action to be inserted.
0073  */
0074 ActionInsertionPoint findActionInsertionPoint(const QList<QAction*>& actions, const QString& actionDocumentTitle)
0075 {
0076     const auto it = std::lower_bound(
0077         actions.cbegin(), actions.cend(), actionDocumentTitle, [](const QAction* lhs, const QString& rhs) {
0078             return lhs->data().value<Sublime::View*>()->document()->title().compare(rhs, Qt::CaseInsensitive) < 0;
0079         });
0080     ActionInsertionPoint ret;
0081     ret.next = it == actions.cend() ? nullptr : *it;
0082     ret.previous = it == actions.cbegin() ? nullptr : *(it - 1);
0083     return ret;
0084 }
0085 
0086 } // namespace
0087 
0088 namespace Sublime {
0089 
0090 // class ContainerTabBar
0091 
0092 class ContainerTabBar : public QTabBar
0093 {
0094     Q_OBJECT
0095 
0096 public:
0097     explicit ContainerTabBar(Container* container)
0098         : QTabBar(container), m_container(container)
0099     {
0100         if (QApplication::style()->objectName() == QLatin1String("macintosh")) {
0101             static QPointer<QStyle> qTabBarStyle = QStyleFactory::create(QStringLiteral("fusion"));
0102             if (qTabBarStyle) {
0103                 setStyle(qTabBarStyle);
0104             }
0105         }
0106         // configure the QTabBar style so it behaves as appropriately as possible,
0107         // even if we end up using the native Macintosh style because the user's
0108         // Qt doesn't have the Fusion style installed.
0109         setDocumentMode(true);
0110         setUsesScrollButtons(true);
0111         setElideMode(Qt::ElideNone);
0112 
0113         installEventFilter(this);
0114     }
0115 
0116     bool event(QEvent* ev) override {
0117         if(ev->type() == QEvent::ToolTip)
0118         {
0119             ev->accept();
0120 
0121             auto* helpEvent = static_cast<QHelpEvent*>(ev);
0122             int tab = tabAt(helpEvent->pos());
0123 
0124             if(tab != -1)
0125             {
0126                 m_container->showTooltipForTab(tab);
0127             }
0128 
0129             return true;
0130         }
0131 
0132         return QTabBar::event(ev);
0133     }
0134     void mousePressEvent(QMouseEvent* event) override {
0135         if (event->button() == Qt::MiddleButton) {
0136             // just close on midbutton, drag can still be done with left mouse button
0137 
0138             int tab = tabAt(event->pos());
0139             if (tab != -1) {
0140                 emit tabCloseRequested(tab);
0141             }
0142             return;
0143         }
0144         QTabBar::mousePressEvent(event);
0145     }
0146 
0147     bool eventFilter(QObject* obj, QEvent* event) override
0148     {
0149         if (obj != this) {
0150             return QObject::eventFilter(obj, event);
0151         }
0152 
0153         // TODO Qt6: Move to mouseDoubleClickEvent when fixme in qttabbar.cpp is resolved
0154         // see "fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons." in qtabbar.cpp
0155         if (event->type() == QEvent::MouseButtonDblClick) {
0156             // block tabBarDoubleClicked signals with RMB, see https://bugs.kde.org/show_bug.cgi?id=356016
0157             auto mouseEvent = static_cast<const QMouseEvent*>(event);
0158             if (mouseEvent->button() == Qt::MiddleButton) {
0159                 return true;
0160             }
0161         }
0162 
0163         return QObject::eventFilter(obj, event);
0164     }
0165 
0166 Q_SIGNALS:
0167     void newTabRequested();
0168 
0169 private:
0170     Container* const m_container;
0171 };
0172 
0173 #ifdef Q_OS_MACOS
0174 // only one of these per process:
0175 static QMenu* currentDockMenu = nullptr;
0176 #endif
0177 
0178 class ContainerPrivate
0179 {
0180 public:
0181     QBoxLayout* layout;
0182     QHash<QWidget*, View*> viewForWidget;
0183 
0184     ContainerTabBar *tabBar;
0185     QStackedWidget *stack;
0186     KSqueezedTextLabel *fileNameCorner;
0187     QSpacerItem* shortCutHelpLeftSpacerItem;
0188     QSpacerItem* shortCutHelpRightSpacerItem;
0189     QLabel *shortcutHelpLabel;
0190     QLabel *fileStatus;
0191     KSqueezedTextLabel *statusCorner;
0192     QPointer<QWidget> leftCornerWidget;
0193     QToolButton* documentListButton;
0194     QMenu* documentListMenu; ///< a popup menu that contains an activating action for each view in this Container
0195     QHash<View*, QAction*> documentListActionForView;
0196 
0197     /**
0198      * Set up @p viewAction's text and insert it into @a documentListMenu.
0199      */
0200     void insertIntoDocumentListMenu(View* view, QAction* viewAction)
0201     {
0202         Q_ASSERT(view);
0203         Q_ASSERT(viewAction);
0204         Q_ASSERT(viewAction->data().value<Sublime::View*>() == view);
0205 
0206         const auto viewDocumentTitle = view->document()->title();
0207         const auto insertionPoint = findActionInsertionPoint(documentListMenu->actions(), viewDocumentTitle);
0208 
0209         // Check if the document titles of the actions that neighbor viewAction equal viewDocumentTitle.
0210         // If so, include the containing directory name in the equivalent actions' texts for disambiguation.
0211         bool includeDirInViewActionText = false;
0212         for (auto* neighborAction : {insertionPoint.previous, insertionPoint.next}) {
0213             if (!neighborAction) {
0214                 continue; // no neighboring action on this side
0215             }
0216             const auto* const neighborView = neighborAction->data().value<View*>();
0217             const auto neighborDocumentTitle = neighborView->document()->title();
0218 
0219             if (neighborDocumentTitle.compare(viewDocumentTitle, Qt::CaseInsensitive) != 0) {
0220                 continue; // the titles are not equal, nothing to do
0221             }
0222             includeDirInViewActionText = true;
0223 
0224             if (neighborAction->text() != neighborDocumentTitle) {
0225                 Q_ASSERT(neighborAction->text() == documentDirAndFilename(neighborView->document()));
0226                 continue; // neighborAction's text is already disambiguated
0227             }
0228             auto newText = documentDirAndFilename(neighborView->document());
0229             if (!newText.isEmpty()) {
0230                 neighborAction->setText(std::move(newText));
0231             }
0232         }
0233 
0234         auto viewActionText = viewDocumentTitle;
0235         if (includeDirInViewActionText) {
0236             auto text = documentDirAndFilename(view->document());
0237             if (!text.isEmpty()) {
0238                 viewActionText = std::move(text);
0239             }
0240         }
0241         viewAction->setText(std::move(viewActionText));
0242 
0243         documentListMenu->insertAction(insertionPoint.next, viewAction);
0244 
0245         setAsDockMenu();
0246     }
0247 
0248     void insertIntoDocumentListMenu(View* view)
0249     {
0250         Q_ASSERT(view);
0251         Q_ASSERT(!documentListActionForView.contains(view));
0252 
0253         auto* const action = new QAction(documentListMenu);
0254         action->setData(QVariant::fromValue(view));
0255         action->setIcon(view->document()->icon());
0256         ///FIXME: push this code somehow into shell, such that we can access the project model for
0257         ///       icons and also get a neat, short path like the document switcher.
0258 
0259         documentListActionForView.insert(view, action);
0260         insertIntoDocumentListMenu(view, action);
0261     }
0262 
0263     void renameInDocumentListMenu(View* view)
0264     {
0265         Q_ASSERT(view);
0266 
0267         auto* const action = documentListActionForView.value(view);
0268         Q_ASSERT(action);
0269         // Here and in Container::removeWidget() we don't consider removing the containing directory name
0270         // from the text of former neighbor actions of the [re]moved action, because if
0271         // multiple recently open documents shared a file name, it still deserves disambiguation.
0272         documentListMenu->removeAction(action);
0273         insertIntoDocumentListMenu(view, action);
0274     }
0275 
0276     void setAsDockMenu()
0277     {
0278 #ifdef Q_OS_MACOS
0279         if (documentListMenu != currentDockMenu) {
0280             documentListMenu->setAsDockMenu();
0281             currentDockMenu = documentListMenu;
0282         }
0283 #endif
0284     }
0285 
0286     ~ContainerPrivate()
0287     {
0288 #ifdef Q_OS_MACOS
0289         if (documentListMenu == currentDockMenu) {
0290             QMenu().setAsDockMenu();
0291             currentDockMenu = nullptr;
0292         }
0293 #endif
0294     }
0295 };
0296 
0297 class UnderlinedLabel: public KSqueezedTextLabel {
0298 Q_OBJECT
0299 public:
0300     explicit UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr)
0301         :KSqueezedTextLabel(parent), m_tabBar(tabBar)
0302     {
0303     }
0304 
0305 protected:
0306     void paintEvent(QPaintEvent *ev) override
0307     {
0308 #ifndef Q_OS_OSX
0309         // getting the underlining right on OS X is tricky; omitting
0310         // the underlining attracts the eye less than not getting it
0311         // exactly right.
0312         if (m_tabBar->isVisible() && m_tabBar->count() > 0)
0313         {
0314             QStylePainter p(this);
0315             QStyleOptionTabBarBase optTabBase;
0316             optTabBase.init(m_tabBar);
0317             optTabBase.shape = m_tabBar->shape();
0318             optTabBase.tabBarRect = m_tabBar->rect();
0319             optTabBase.tabBarRect.moveRight(0);
0320 
0321             QStyleOptionTab tabOverlap;
0322             tabOverlap.shape = m_tabBar->shape();
0323             int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar);
0324             if( overlap > 0 )
0325             {
0326                 QRect rect;
0327                 rect.setRect(0, height()-overlap, width(), overlap);
0328                 optTabBase.rect = rect;
0329             }
0330             if( m_tabBar->drawBase() )
0331             {
0332                 p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
0333             }
0334         }
0335 #endif
0336 
0337         KSqueezedTextLabel::paintEvent(ev);
0338     }
0339 
0340     QTabBar *m_tabBar;
0341 };
0342 
0343 
0344 class StatusLabel: public UnderlinedLabel {
0345 Q_OBJECT
0346 public:
0347     explicit StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr):
0348         UnderlinedLabel(tabBar, parent)
0349     {
0350         setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0351         setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
0352     }
0353 
0354     QSize minimumSizeHint() const override
0355     {
0356         QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000"));
0357         rect.setHeight(m_tabBar->height());
0358         return rect.size();
0359     }
0360 };
0361 
0362 // class Container
0363 
0364 Container::Container(QWidget *parent)
0365     : QWidget(parent)
0366     , d_ptr(new ContainerPrivate())
0367 {
0368     Q_D(Container);
0369 
0370     KAcceleratorManager::setNoAccel(this);
0371 
0372     auto *l = new QBoxLayout(QBoxLayout::TopToBottom, this);
0373     l->setContentsMargins(0, 0, 0, 0);
0374     l->setSpacing(0);
0375 
0376     d->layout = new QBoxLayout(QBoxLayout::LeftToRight);
0377     d->layout->setContentsMargins(0, 0, 0, 0);
0378     d->layout->setSpacing(0);
0379 
0380     d->documentListMenu = new QMenu(this);
0381     d->documentListButton = new QToolButton(this);
0382     d->documentListButton->setIcon(QIcon::fromTheme(QStringLiteral("format-list-unordered")));
0383     d->documentListButton->setMenu(d->documentListMenu);
0384 #ifdef Q_OS_MACOS
0385     // for maintaining the Dock menu:
0386     setFocusPolicy(Qt::StrongFocus);
0387 #endif
0388     d->documentListButton->setPopupMode(QToolButton::InstantPopup);
0389     d->documentListButton->setAutoRaise(true);
0390     d->documentListButton->setToolTip(i18nc("@info:tooltip", "Show sorted list of opened documents"));
0391     d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
0392     d->layout->addWidget(d->documentListButton);
0393     d->tabBar = new ContainerTabBar(this);
0394     d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
0395     d->layout->addWidget(d->tabBar);
0396     d->fileStatus = new QLabel( this );
0397     d->fileStatus->setFixedSize( QSize( 16, 16 ) );
0398     d->layout->addWidget(d->fileStatus);
0399     d->fileNameCorner = new UnderlinedLabel(d->tabBar, this);
0400     d->fileNameCorner->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
0401     d->layout->addWidget(d->fileNameCorner);
0402     d->shortcutHelpLabel = new QLabel(i18nc("@info", "(Press Ctrl+Tab to switch)"), this);
0403     auto font = d->shortcutHelpLabel->font();
0404     font.setPointSize(font.pointSize() - 2);
0405     font.setItalic(true);
0406     d->shortcutHelpLabel->setFont(font);
0407     d->shortCutHelpLeftSpacerItem = new QSpacerItem(0, 0); // fully set in setTabBarHidden()
0408     d->layout->addSpacerItem(d->shortCutHelpLeftSpacerItem);
0409     d->shortcutHelpLabel->setAlignment(Qt::AlignCenter);
0410     d->layout->addWidget(d->shortcutHelpLabel);
0411     d->shortCutHelpRightSpacerItem = new QSpacerItem(0, 0); // fully set in setTabBarHidden()
0412     d->layout->addSpacerItem(d->shortCutHelpRightSpacerItem);
0413     d->statusCorner = new StatusLabel(d->tabBar, this);
0414     d->layout->addWidget(d->statusCorner);
0415     l->addLayout(d->layout);
0416 
0417     d->stack = new QStackedWidget(this);
0418     l->addWidget(d->stack);
0419 
0420     connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated);
0421     connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, QOverload<int>::of(&Container::requestClose));
0422     connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested);
0423     connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved);
0424     connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu);
0425     connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered);
0426     connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered);
0427 
0428 
0429     setTabBarHidden(!configTabBarVisible());
0430     d->tabBar->setTabsClosable(configCloseButtonsOnTabs());
0431     d->tabBar->setMovable(true);
0432     d->tabBar->setExpanding(false);
0433     d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
0434 }
0435 
0436 Container::~Container() = default;
0437 
0438 bool Container::configTabBarVisible()
0439 {
0440     KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings");
0441     return group.readEntry("TabBarVisibility", 1);
0442 }
0443 
0444 bool Container::configCloseButtonsOnTabs()
0445 {
0446     KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings");
0447     return group.readEntry("CloseButtonsOnTabs", 1);
0448 }
0449 
0450 void Container::setLeftCornerWidget(QWidget* widget)
0451 {
0452     Q_D(Container);
0453 
0454     if(d->leftCornerWidget.data() == widget) {
0455         if(d->leftCornerWidget)
0456             d->leftCornerWidget.data()->setParent(nullptr);
0457     }else{
0458         delete d->leftCornerWidget.data();
0459         d->leftCornerWidget.clear();
0460     }
0461     d->leftCornerWidget = widget;
0462     if(!widget)
0463         return;
0464     widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
0465     d->layout->insertWidget(0, widget);
0466     widget->show();
0467 }
0468 
0469 QList<View*> Container::views() const
0470 {
0471     Q_D(const Container);
0472 
0473     return d->viewForWidget.values();
0474 }
0475 
0476 void Container::requestClose(int idx)
0477 {
0478     emit requestClose(widget(idx));
0479 }
0480 
0481 void Container::widgetActivated(int idx)
0482 {
0483     Q_D(Container);
0484 
0485     if (idx < 0)
0486         return;
0487     if (QWidget* w = d->stack->widget(idx)) {
0488         Sublime::View* view = d->viewForWidget.value(w);
0489         if(view)
0490             emit activateView(view);
0491     }
0492 }
0493 
0494 void Container::addWidget(View *view, int position)
0495 {
0496     Q_D(Container);
0497 
0498     QWidget *w = view->widget(this);
0499     int idx = 0;
0500     if (position != -1)
0501     {
0502         idx = d->stack->insertWidget(position, w);
0503     }
0504     else
0505         idx = d->stack->addWidget(w);
0506     d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title());
0507     Q_ASSERT(view);
0508     d->viewForWidget[w] = view;
0509 
0510     // Update document list context menu. This has to be called before
0511     // setCurrentWidget, because we call the status icon and title update slots
0512     // from there, which in turn require view to be present in the document list menu.
0513     d->insertIntoDocumentListMenu(view);
0514 
0515     setCurrentWidget(d->stack->currentWidget());
0516 
0517     // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab.
0518     // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone.
0519     // The problem could only be fixed by closing/opening another view.
0520     d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height());
0521 
0522     connect(view, &View::statusChanged, this, &Container::statusChanged);
0523     connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged);
0524     connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged);
0525 }
0526 
0527 void Container::statusChanged(Sublime::View* view)
0528 {
0529     Q_D(Container);
0530 
0531     const auto statusText = view->viewStatus();
0532     d->statusCorner->setText(statusText);
0533     d->statusCorner->setVisible(!statusText.isEmpty());
0534 }
0535 
0536 void Container::statusIconChanged(Document* doc)
0537 {
0538     Q_D(Container);
0539 
0540     QHashIterator<QWidget*, View*> it = d->viewForWidget;
0541     while (it.hasNext()) {
0542         if (it.next().value()->document() == doc) {
0543             d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) );
0544             int tabIndex = d->stack->indexOf(it.key());
0545             if (tabIndex != -1) {
0546                 d->tabBar->setTabIcon(tabIndex, doc->statusIcon());
0547             }
0548 
0549             // Update the document title's menu associated action
0550             // using the View* index map
0551             auto* const action = d->documentListActionForView.value(it.value());
0552             Q_ASSERT(action);
0553             action->setIcon(doc->icon());
0554             break;
0555         }
0556     }
0557 }
0558 
0559 void Container::documentTitleChanged(Sublime::Document* doc)
0560 {
0561     Q_D(Container);
0562 
0563     QHashIterator<QWidget*, View*> it = d->viewForWidget;
0564     while (it.hasNext()) {
0565         Sublime::View* view = it.next().value();
0566         if (view->document() == doc) {
0567             if (currentView() == view) {
0568                 d->fileNameCorner->setText( doc->title(Document::Extended) );
0569                 // TODO KF6: remove this as soon as it is included upstream and we reqire
0570                 // that version
0571                 // see https://phabricator.kde.org/D7010
0572                 d->fileNameCorner->updateGeometry();
0573             }
0574             int tabIndex = d->stack->indexOf(it.key());
0575             if (tabIndex != -1) {
0576                 d->tabBar->setTabText(tabIndex, doc->title());
0577             }
0578 
0579             // Update document list popup title
0580             d->renameInDocumentListMenu(view);
0581             break;
0582         }
0583     }
0584 }
0585 
0586 int Container::count() const
0587 {
0588     Q_D(const Container);
0589 
0590     return d->stack->count();
0591 }
0592 
0593 QWidget* Container::currentWidget() const
0594 {
0595     Q_D(const Container);
0596 
0597     return d->stack->currentWidget();
0598 }
0599 
0600 void Container::setCurrentWidget(QWidget* w)
0601 {
0602     Q_D(Container);
0603 
0604     if (d->stack->currentWidget() == w) {
0605         return;
0606     }
0607     d->stack->setCurrentWidget(w);
0608     d->tabBar->setCurrentIndex(d->stack->indexOf(w));
0609     if (View* view = viewForWidget(w))
0610     {
0611         statusChanged(view);
0612         if (!d->tabBar->isVisible())
0613         {
0614             // repaint icon and document title only in tabbar-less mode
0615             // tabbar will do repainting for us
0616             statusIconChanged( view->document() );
0617             documentTitleChanged( view->document() );
0618         }
0619     }
0620 }
0621 
0622 QWidget* Container::widget(int i) const
0623 {
0624     Q_D(const Container);
0625 
0626     return d->stack->widget(i);
0627 }
0628 
0629 int Container::indexOf(QWidget* w) const
0630 {
0631     Q_D(const Container);
0632 
0633     return d->stack->indexOf(w);
0634 }
0635 
0636 void Container::removeWidget(QWidget *w)
0637 {
0638     Q_D(Container);
0639 
0640     if (w) {
0641         int widgetIdx = d->stack->indexOf(w);
0642         d->stack->removeWidget(w);
0643         d->tabBar->removeTab(widgetIdx);
0644         if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) {
0645             // repaint icon and document title only in tabbar-less mode
0646             // tabbar will do repainting for us
0647             View* view = currentView();
0648             if( view ) {
0649                 statusIconChanged( view->document() );
0650                 documentTitleChanged( view->document() );
0651             }
0652         }
0653         View* view = d->viewForWidget.take(w);
0654         if (view)
0655         {
0656             disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged);
0657             disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged);
0658             disconnect(view, &View::statusChanged, this, &Container::statusChanged);
0659 
0660             // Update document list context menu
0661             Q_ASSERT(d->documentListActionForView.contains(view));
0662             delete d->documentListActionForView.take(view);
0663         }
0664     }
0665 }
0666 
0667 bool Container::hasWidget(QWidget* w) const
0668 {
0669     Q_D(const Container);
0670 
0671     return d->stack->indexOf(w) != -1;
0672 }
0673 
0674 View *Container::viewForWidget(QWidget *w) const
0675 {
0676     Q_D(const Container);
0677 
0678     return d->viewForWidget.value(w);
0679 }
0680 
0681 void Container::setTabBarHidden(bool hide)
0682 {
0683     Q_D(Container);
0684 
0685     if (hide)
0686     {
0687         d->tabBar->hide();
0688         d->fileStatus->show();
0689         d->shortCutHelpLeftSpacerItem->changeSize(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0,
0690                                                   QSizePolicy::Fixed, QSizePolicy::Fixed);
0691         d->shortcutHelpLabel->show();
0692         d->shortCutHelpRightSpacerItem->changeSize(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
0693         d->fileNameCorner->show();
0694     }
0695     else
0696     {
0697         d->fileNameCorner->hide();
0698         d->fileStatus->hide();
0699         d->tabBar->show();
0700         d->shortCutHelpLeftSpacerItem->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
0701         d->shortcutHelpLabel->hide();
0702         d->shortCutHelpRightSpacerItem->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
0703     }
0704     // have spacer item changes taken into account
0705     d->layout->invalidate();
0706 
0707     View* v = currentView();
0708     if (v) {
0709         documentTitleChanged(v->document());
0710     }
0711 }
0712 
0713 void Container::setCloseButtonsOnTabs(bool show)
0714 {
0715     Q_D(Container);
0716 
0717     d->tabBar->setTabsClosable(show);
0718 }
0719 
0720 void Container::resetTabColors(const QColor& color)
0721 {
0722     Q_D(Container);
0723 
0724     for (int i = 0; i < count(); i++){
0725         d->tabBar->setTabTextColor(i, color);
0726     }
0727 }
0728 
0729 void Container::setTabColor(const View* view, const QColor& color)
0730 {
0731     Q_D(Container);
0732 
0733     for (int i = 0; i < count(); i++){
0734         if (view == viewForWidget(widget(i))) {
0735             d->tabBar->setTabTextColor(i, color);
0736         }
0737     }
0738 }
0739 
0740 void Container::setTabColors(const QHash<const View*, QColor>& colors)
0741 {
0742     Q_D(Container);
0743 
0744     for (int i = 0; i < count(); i++) {
0745         auto view = viewForWidget(widget(i));
0746         auto color = colors[view];
0747         if (color.isValid()) {
0748             d->tabBar->setTabTextColor(i, color);
0749         }
0750     }
0751 }
0752 
0753 void Container::tabMoved(int from, int to)
0754 {
0755     Q_D(Container);
0756 
0757     QWidget *w = d->stack->widget(from);
0758     d->stack->removeWidget(w);
0759     d->stack->insertWidget(to, w);
0760     viewForWidget(w)->notifyPositionChanged(to);
0761 }
0762 
0763 void Container::contextMenu( const QPoint& pos )
0764 {
0765     Q_D(Container);
0766 
0767     QWidget* senderWidget = qobject_cast<QWidget*>(sender());
0768     Q_ASSERT(senderWidget);
0769 
0770     int currentTab = d->tabBar->tabAt(pos);
0771 
0772     QMenu menu;
0773     // Polish before creating a native window below. The style could want change the surface format
0774     // of the window which will have no effect when the native window has already been created.
0775     menu.ensurePolished();
0776     // At least for positioning on Wayland the window the menu belongs to
0777     // needs to be set. We cannot set senderWidget as parent because some actions (e.g. split view)
0778     // result in sync destruction of the senderWidget, which then would also prematurely
0779     // destruct the menu object
0780     // Workaround (best known currently, check again API of Qt >5.9):
0781     menu.winId(); // trigger being a native widget already, to ensure windowHandle created
0782     auto parentWindowHandle = senderWidget->windowHandle();
0783     if (!parentWindowHandle) {
0784         parentWindowHandle = senderWidget->nativeParentWidget()->windowHandle();
0785     }
0786     menu.windowHandle()->setTransientParent(parentWindowHandle);
0787 
0788     Sublime::View* view = viewForWidget(widget(currentTab));
0789     emit tabContextMenuRequested(view, &menu);
0790 
0791     menu.addSeparator();
0792     QAction* copyPathAction = nullptr;
0793     QAction* closeTabAction = nullptr;
0794     QAction* closeOtherTabsAction = nullptr;
0795     if (view) {
0796         copyPathAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")),
0797                                         i18nc("@action:inmenu", "Copy Filename"));
0798         menu.addSeparator();
0799         closeTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")),
0800                                         i18nc("@action:inmenu", "Close"));
0801         closeOtherTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")),
0802                                               i18nc("@action:inmenu", "Close All Other"));
0803     }
0804     QAction* closeAllTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close All"));
0805 
0806     QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos));
0807 
0808     if (triggered) {
0809         if ( triggered == closeTabAction ) {
0810             requestClose(currentTab);
0811         } else if ( triggered == closeOtherTabsAction ) {
0812             // activate the remaining tab
0813             widgetActivated(currentTab);
0814             // first get the widgets to be closed since otherwise the indices will be wrong
0815             QList<QWidget*> otherTabs;
0816             for ( int i = 0; i < count(); ++i ) {
0817                 if ( i != currentTab ) {
0818                     otherTabs << widget(i);
0819                 }
0820             }
0821             // finally close other tabs
0822             for (QWidget* tab : qAsConst(otherTabs)) {
0823                 emit requestClose(tab);
0824             }
0825         } else if ( triggered == closeAllTabsAction ) {
0826             // activate last tab
0827             widgetActivated(count() - 1);
0828 
0829             // close all
0830             for ( int i = 0; i < count(); ++i ) {
0831                 emit requestClose(widget(i));
0832             }
0833         } else if( triggered == copyPathAction ) {
0834             auto view = viewForWidget( widget( currentTab ) );
0835             auto urlDocument = qobject_cast<UrlDocument*>( view->document() );
0836             if( urlDocument ) {
0837                 QString toCopy = urlDocument->url().toDisplayString(QUrl::PreferLocalFile);
0838                 if (urlDocument->url().isLocalFile()) {
0839                     toCopy = QDir::toNativeSeparators(toCopy);
0840                 }
0841                 QApplication::clipboard()->setText(toCopy);
0842             }
0843         } // else the action was handled by someone else
0844     }
0845 }
0846 
0847 void Container::showTooltipForTab(int tab)
0848 {
0849     emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab);
0850 }
0851 
0852 bool Container::isCurrentTab(int tab) const
0853 {
0854     Q_D(const Container);
0855 
0856     return d->tabBar->currentIndex() == tab;
0857 }
0858 
0859 QRect Container::tabRect(int tab) const
0860 {
0861     Q_D(const Container);
0862 
0863     return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0)));
0864 }
0865 
0866 void Container::doubleClickTriggered(int tab)
0867 {
0868     if (tab == -1) {
0869         emit newTabRequested();
0870     } else {
0871         emit tabDoubleClicked(viewForWidget(widget(tab)));
0872     }
0873 }
0874 
0875 void Container::documentListActionTriggered(QAction* action)
0876 {
0877     Q_D(Container);
0878 
0879     auto* view = action->data().value< Sublime::View* >();
0880     Q_ASSERT(view);
0881     QWidget* widget = d->viewForWidget.key(view);
0882     Q_ASSERT(widget);
0883     setCurrentWidget(widget);
0884 }
0885 Sublime::View* Container::currentView() const
0886 {
0887     Q_D(const Container);
0888 
0889     return d->viewForWidget.value(widget( d->tabBar->currentIndex() ));
0890 }
0891 
0892 void Container::focusInEvent(QFocusEvent* event)
0893 {
0894     Q_D(Container);
0895 
0896     d->setAsDockMenu();
0897     QWidget::focusInEvent(event);
0898 }
0899 
0900 }
0901 
0902 #include "container.moc"
0903 #include "moc_container.cpp"