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"