diff --git a/kdevplatform/shell/settings/uiconfig.kcfg b/kdevplatform/shell/settings/uiconfig.kcfg index 6c9691bb02..25c8737c12 100644 --- a/kdevplatform/shell/settings/uiconfig.kcfg +++ b/kdevplatform/shell/settings/uiconfig.kcfg @@ -1,25 +1,28 @@ 1 + + 1 + 1 1 true diff --git a/kdevplatform/shell/settings/uiconfig.ui b/kdevplatform/shell/settings/uiconfig.ui index 81b41ed7a8..ed02382af1 100644 --- a/kdevplatform/shell/settings/uiconfig.ui +++ b/kdevplatform/shell/settings/uiconfig.ui @@ -1,195 +1,224 @@ UiConfig 0 0 521 399 User Interface 0 Dock Window Behavior <p>Controls whether the bottom left corner is occupied by the dock at the left, or by the dock at the bottom.</p> Bottom left corner occupied by: <p>Controls whether the bottom right corner is occupied by the dock at the right, or by the dock at the bottom.</p> Bottom right corner occupied by: <p>Controls whether the bottom left corner is occupied by the dock at the left, or by the dock at the bottom.</p> Left Dock Bottom Dock <p>Controls whether the bottom right corner is occupied by the dock at the right, or by the dock at the bottom.</p> Right Dock Bottom Dock Tabbed Browsing - + Controls whether to open new tabs next to the active one. Open new tab after current true - + When enabled, plugins can group related files side by side. For example, a header file will be opened next to the implementation file. Arrange related documents side by side Enables or disables the display of the tab bar at the top of the editor window. Show tabs true + + + + Enables or disables the display of a close button in each tab in the tab bar at the top of the editor window. + + + Show close buttons on tabs + + + true + + + Coloring When enabled, tabs and other widgets are colored based on the project affiliation Color widgets based on the project affiliation true Qt::Vertical kcfg_TabBarVisibility toggled(bool) kcfg_TabBarOpenAfterCurrent setEnabled(bool) 260 133 260 158 kcfg_TabBarVisibility toggled(bool) kcfg_TabBarArrangeBuddies setEnabled(bool) 260 133 260 183 + + kcfg_TabBarVisibility + toggled(bool) + kcfg_CloseButtonsOnTabs + setEnabled(bool) + + + 260 + 133 + + + 260 + 158 + + + diff --git a/kdevplatform/sublime/container.cpp b/kdevplatform/sublime/container.cpp index 003e0a0f7e..ba16c5dc58 100644 --- a/kdevplatform/sublime/container.cpp +++ b/kdevplatform/sublime/container.cpp @@ -1,814 +1,827 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "container.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "view.h" #include "urldocument.h" #include namespace Sublime { // class ContainerTabBar class ContainerTabBar : public QTabBar { Q_OBJECT public: explicit ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { if (QApplication::style()->objectName() == QLatin1String("macintosh")) { static QPointer qTabBarStyle = QStyleFactory::create(QStringLiteral("fusion")); if (qTabBarStyle) { setStyle(qTabBarStyle); } } // configure the QTabBar style so it behaves as appropriately as possible, // even if we end up using the native Macintosh style because the user's // Qt doesn't have the Fusion style installed. setDocumentMode(true); setUsesScrollButtons(true); setElideMode(Qt::ElideNone); installEventFilter(this); } bool event(QEvent* ev) override { if(ev->type() == QEvent::ToolTip) { ev->accept(); auto* helpEvent = static_cast(ev); int tab = tabAt(helpEvent->pos()); if(tab != -1) { m_container->showTooltipForTab(tab); } return true; } return QTabBar::event(ev); } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) { // just close on midbutton, drag can still be done with left mouse button int tab = tabAt(event->pos()); if (tab != -1) { emit tabCloseRequested(tab); } return; } QTabBar::mousePressEvent(event); } bool eventFilter(QObject* obj, QEvent* event) override { if (obj != this) { return QObject::eventFilter(obj, event); } // TODO Qt6: Move to mouseDoubleClickEvent when fixme in qttabbar.cpp is resolved // see "fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons." in qtabbar.cpp if (event->type() == QEvent::MouseButtonDblClick) { // block tabBarDoubleClicked signals with RMB, see https://bugs.kde.org/show_bug.cgi?id=356016 auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::MidButton) { return true; } } return QObject::eventFilter(obj, event); } Q_SIGNALS: void newTabRequested(); private: Container* const m_container; }; bool sortViews(const View* const lhs, const View* const rhs) { return lhs->document()->title().compare(rhs->document()->title(), Qt::CaseInsensitive) < 0; } #ifdef Q_OS_MACOS // only one of these per process: static QMenu* currentDockMenu = nullptr; #endif class ContainerPrivate { public: QBoxLayout* layout; QHash viewForWidget; ContainerTabBar *tabBar; QStackedWidget *stack; KSqueezedTextLabel *fileNameCorner; QLabel *shortcutHelpLabel; QLabel *fileStatus; KSqueezedTextLabel *statusCorner; QPointer leftCornerWidget; QToolButton* documentListButton; QMenu* documentListMenu; QHash documentListActionForView; /** * Updates the context menu which is shown when * the document list button in the tab bar is clicked. * * It shall build a popup menu which contains all currently * enabled views using the title their document provides. */ void updateDocumentListPopupMenu() { qDeleteAll(documentListActionForView); documentListActionForView.clear(); documentListMenu->clear(); // create a lexicographically sorted list QVector views; views.reserve(viewForWidget.size()); for (View* view : qAsConst(viewForWidget)) { views << view; } std::sort(views.begin(), views.end(), sortViews); for (int i = 0; i < views.size(); ++i) { View *view = views.at(i); QString visibleEntryTitle; // if filename is not unique, prepend containing directory if ((i < views.size() - 1 && view->document()->title() == views.at(i + 1)->document()->title()) || (i > 0 && view->document()->title() == views.at(i - 1)->document()->title()) ) { auto urlDoc = qobject_cast(view->document()); if (!urlDoc) { visibleEntryTitle = view->document()->title(); } else { auto url = urlDoc->url().toString(); int secondOffset = url.lastIndexOf(QLatin1Char('/')); secondOffset = url.lastIndexOf(QLatin1Char('/'), secondOffset - 1); visibleEntryTitle = url.right(url.length() - url.lastIndexOf(QLatin1Char('/'), secondOffset) - 1); } } else { visibleEntryTitle = view->document()->title(); } QAction* action = documentListMenu->addAction(visibleEntryTitle); action->setData(QVariant::fromValue(view)); documentListActionForView[view] = action; action->setIcon(view->document()->icon()); ///FIXME: push this code somehow into shell, such that we can access the project model for /// icons and also get a neat, short path like the document switcher. } setAsDockMenu(); } void setAsDockMenu() { #ifdef Q_OS_MACOS if (documentListMenu != currentDockMenu) { documentListMenu->setAsDockMenu(); currentDockMenu = documentListMenu; } #endif } ~ContainerPrivate() { #ifdef Q_OS_MACOS if (documentListMenu == currentDockMenu) { QMenu().setAsDockMenu(); currentDockMenu = nullptr; } #endif } }; class UnderlinedLabel: public KSqueezedTextLabel { Q_OBJECT public: explicit UnderlinedLabel(QTabBar *tabBar, QWidget* parent = nullptr) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: void paintEvent(QPaintEvent *ev) override { #ifndef Q_OS_OSX // getting the underlining right on OS X is tricky; omitting // the underlining attracts the eye less than not getting it // exactly right. if (m_tabBar->isVisible() && m_tabBar->count() > 0) { QStylePainter p(this); QStyleOptionTabBarBase optTabBase; optTabBase.init(m_tabBar); optTabBase.shape = m_tabBar->shape(); optTabBase.tabBarRect = m_tabBar->rect(); optTabBase.tabBarRect.moveRight(0); QStyleOptionTab tabOverlap; tabOverlap.shape = m_tabBar->shape(); int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar); if( overlap > 0 ) { QRect rect; rect.setRect(0, height()-overlap, width(), overlap); optTabBase.rect = rect; } if( m_tabBar->drawBase() ) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } } #endif KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { Q_OBJECT public: explicit StatusLabel(QTabBar *tabBar, QWidget* parent = nullptr): UnderlinedLabel(tabBar, parent) { setAlignment(Qt::AlignRight | Qt::AlignVCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); } QSize minimumSizeHint() const override { QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000")); rect.setHeight(m_tabBar->height()); return rect.size(); } }; // class Container Container::Container(QWidget *parent) : QWidget(parent) , d_ptr(new ContainerPrivate()) { Q_D(Container); KAcceleratorManager::setNoAccel(this); auto *l = new QBoxLayout(QBoxLayout::TopToBottom, this); l->setMargin(0); l->setSpacing(0); d->layout = new QBoxLayout(QBoxLayout::LeftToRight); d->layout->setMargin(0); d->layout->setSpacing(0); d->documentListMenu = new QMenu(this); d->documentListButton = new QToolButton(this); d->documentListButton->setIcon(QIcon::fromTheme(QStringLiteral("format-list-unordered"))); d->documentListButton->setMenu(d->documentListMenu); #ifdef Q_OS_MACOS // for maintaining the Dock menu: setFocusPolicy(Qt::StrongFocus); #endif d->documentListButton->setPopupMode(QToolButton::InstantPopup); d->documentListButton->setAutoRaise(true); d->documentListButton->setToolTip(i18n("Show sorted list of opened documents")); d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->layout->addWidget(d->documentListButton); d->tabBar = new ContainerTabBar(this); d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu); d->layout->addWidget(d->tabBar); d->fileStatus = new QLabel( this ); d->fileStatus->setFixedSize( QSize( 16, 16 ) ); d->layout->addWidget(d->fileStatus); d->fileNameCorner = new UnderlinedLabel(d->tabBar, this); d->fileNameCorner->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); d->layout->addWidget(d->fileNameCorner); d->shortcutHelpLabel = new QLabel(i18n("(Press Ctrl+Tab to switch)"), this); auto font = d->shortcutHelpLabel->font(); font.setPointSize(font.pointSize() - 2); font.setItalic(true); d->shortcutHelpLabel->setFont(font); d->layout->addSpacerItem(new QSpacerItem(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0, QSizePolicy::Fixed, QSizePolicy::Fixed)); d->shortcutHelpLabel->setAlignment(Qt::AlignCenter); d->layout->addWidget(d->shortcutHelpLabel); d->layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); d->statusCorner = new StatusLabel(d->tabBar, this); d->layout->addWidget(d->statusCorner); l->addLayout(d->layout); d->stack = new QStackedWidget(this); l->addWidget(d->stack); connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated); connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, QOverload::of(&Container::requestClose)); connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested); connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved); connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu); connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered); connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered); setTabBarHidden(!configTabBarVisible()); - d->tabBar->setTabsClosable(true); + d->tabBar->setTabsClosable(configCloseButtonsOnTabs()); d->tabBar->setMovable(true); d->tabBar->setExpanding(false); d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); } Container::~Container() = default; bool Container::configTabBarVisible() { KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); return group.readEntry("TabBarVisibility", 1); } +bool Container::configCloseButtonsOnTabs() +{ + KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); + return group.readEntry("CloseButtonsOnTabs", 1); +} + void Container::setLeftCornerWidget(QWidget* widget) { Q_D(Container); if(d->leftCornerWidget.data() == widget) { if(d->leftCornerWidget) d->leftCornerWidget.data()->setParent(nullptr); }else{ delete d->leftCornerWidget.data(); d->leftCornerWidget.clear(); } d->leftCornerWidget = widget; if(!widget) return; widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->layout->insertWidget(0, widget); widget->show(); } QList Container::views() const { Q_D(const Container); return d->viewForWidget.values(); } void Container::requestClose(int idx) { emit requestClose(widget(idx)); } void Container::widgetActivated(int idx) { Q_D(Container); if (idx < 0) return; if (QWidget* w = d->stack->widget(idx)) { Sublime::View* view = d->viewForWidget.value(w); if(view) emit activateView(view); } } void Container::addWidget(View *view, int position) { Q_D(Container); QWidget *w = view->widget(this); int idx = 0; if (position != -1) { idx = d->stack->insertWidget(position, w); } else idx = d->stack->addWidget(w); d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title()); Q_ASSERT(view); d->viewForWidget[w] = view; // Update document list context menu. This has to be called before // setCurrentWidget, because we call the status icon and title update slots // already, which in turn need the document list menu to be setup. d->updateDocumentListPopupMenu(); setCurrentWidget(d->stack->currentWidget()); // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab. // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone. // The problem could only be fixed by closing/opening another view. d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height()); connect(view, &View::statusChanged, this, &Container::statusChanged); connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); } void Container::statusChanged(Sublime::View* view) { Q_D(Container); const auto statusText = view->viewStatus(); d->statusCorner->setText(statusText); d->statusCorner->setVisible(!statusText.isEmpty()); } void Container::statusIconChanged(Document* doc) { Q_D(Container); QHashIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabIcon(tabIndex, doc->statusIcon()); } // Update the document title's menu associated action // using the View* index map Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setIcon(doc->icon()); break; } } } void Container::documentTitleChanged(Sublime::Document* doc) { Q_D(Container); QHashIterator it = d->viewForWidget; while (it.hasNext()) { Sublime::View* view = it.next().value(); if (view->document() == doc) { if (currentView() == view) { d->fileNameCorner->setText( doc->title(Document::Extended) ); // TODO KF6: remove this as soon as it is included upstream and we reqire // that version // see https://phabricator.kde.org/D7010 d->fileNameCorner->updateGeometry(); } int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabText(tabIndex, doc->title()); } break; } } // Update document list popup title d->updateDocumentListPopupMenu(); } int Container::count() const { Q_D(const Container); return d->stack->count(); } QWidget* Container::currentWidget() const { Q_D(const Container); return d->stack->currentWidget(); } void Container::setCurrentWidget(QWidget* w) { Q_D(Container); if (d->stack->currentWidget() == w) { return; } d->stack->setCurrentWidget(w); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); if (View* view = viewForWidget(w)) { statusChanged(view); if (!d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } } QWidget* Container::widget(int i) const { Q_D(const Container); return d->stack->widget(i); } int Container::indexOf(QWidget* w) const { Q_D(const Container); return d->stack->indexOf(w); } void Container::removeWidget(QWidget *w) { Q_D(Container); if (w) { int widgetIdx = d->stack->indexOf(w); d->stack->removeWidget(w); d->tabBar->removeTab(widgetIdx); if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us View* view = currentView(); if( view ) { statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } View* view = d->viewForWidget.take(w); if (view) { disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); disconnect(view, &View::statusChanged, this, &Container::statusChanged); // Update document list context menu Q_ASSERT(d->documentListActionForView.contains(view)); delete d->documentListActionForView.take(view); } } } bool Container::hasWidget(QWidget* w) const { Q_D(const Container); return d->stack->indexOf(w) != -1; } View *Container::viewForWidget(QWidget *w) const { Q_D(const Container); return d->viewForWidget.value(w); } void Container::setTabBarHidden(bool hide) { Q_D(Container); if (hide) { d->tabBar->hide(); d->fileStatus->show(); d->shortcutHelpLabel->show(); d->fileNameCorner->show(); } else { d->fileNameCorner->hide(); d->fileStatus->hide(); d->tabBar->show(); d->shortcutHelpLabel->hide(); } View* v = currentView(); if (v) { documentTitleChanged(v->document()); } } +void Container::setCloseButtonsOnTabs(bool show) +{ + Q_D(Container); + + d->tabBar->setTabsClosable(show); +} + void Container::resetTabColors(const QColor& color) { Q_D(Container); for (int i = 0; i < count(); i++){ d->tabBar->setTabTextColor(i, color); } } void Container::setTabColor(const View* view, const QColor& color) { Q_D(Container); for (int i = 0; i < count(); i++){ if (view == viewForWidget(widget(i))) { d->tabBar->setTabTextColor(i, color); } } } void Container::setTabColors(const QHash& colors) { Q_D(Container); for (int i = 0; i < count(); i++) { auto view = viewForWidget(widget(i)); auto color = colors[view]; if (color.isValid()) { d->tabBar->setTabTextColor(i, color); } } } void Container::tabMoved(int from, int to) { Q_D(Container); QWidget *w = d->stack->widget(from); d->stack->removeWidget(w); d->stack->insertWidget(to, w); d->viewForWidget[w]->notifyPositionChanged(to); } void Container::contextMenu( const QPoint& pos ) { Q_D(Container); QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); int currentTab = d->tabBar->tabAt(pos); QMenu menu; // At least for positioning on Wayland the window the menu belongs to // needs to be set. We cannot set senderWidget as parent because some actions (e.g. split view) // result in sync destruction of the senderWidget, which then would also prematurely // destruct the menu object // Workaround (best known currently, check again API of Qt >5.9): menu.winId(); // trigger being a native widget already, to ensure windowHandle created auto parentWindowHandle = senderWidget->windowHandle(); if (!parentWindowHandle) { parentWindowHandle = senderWidget->nativeParentWidget()->windowHandle(); } menu.windowHandle()->setTransientParent(parentWindowHandle); Sublime::View* view = viewForWidget(widget(currentTab)); emit tabContextMenuRequested(view, &menu); menu.addSeparator(); QAction* copyPathAction = nullptr; QAction* closeTabAction = nullptr; QAction* closeOtherTabsAction = nullptr; if (view) { copyPathAction = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Filename")); menu.addSeparator(); closeTabAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close")); closeOtherTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close All Other")); } QAction* closeAllTabsAction = menu.addAction(QIcon::fromTheme(QStringLiteral("document-close")), i18n("Close All")); QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos)); if (triggered) { if ( triggered == closeTabAction ) { requestClose(currentTab); } else if ( triggered == closeOtherTabsAction ) { // activate the remaining tab widgetActivated(currentTab); // first get the widgets to be closed since otherwise the indices will be wrong QList otherTabs; for ( int i = 0; i < count(); ++i ) { if ( i != currentTab ) { otherTabs << widget(i); } } // finally close other tabs for (QWidget* tab : qAsConst(otherTabs)) { emit requestClose(tab); } } else if ( triggered == closeAllTabsAction ) { // activate last tab widgetActivated(count() - 1); // close all for ( int i = 0; i < count(); ++i ) { emit requestClose(widget(i)); } } else if( triggered == copyPathAction ) { auto view = viewForWidget( widget( currentTab ) ); auto urlDocument = qobject_cast( view->document() ); if( urlDocument ) { QString toCopy = urlDocument->url().toDisplayString(QUrl::PreferLocalFile); if (urlDocument->url().isLocalFile()) { toCopy = QDir::toNativeSeparators(toCopy); } QApplication::clipboard()->setText(toCopy); } } // else the action was handled by someone else } } void Container::showTooltipForTab(int tab) { emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab); } bool Container::isCurrentTab(int tab) const { Q_D(const Container); return d->tabBar->currentIndex() == tab; } QRect Container::tabRect(int tab) const { Q_D(const Container); return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0))); } void Container::doubleClickTriggered(int tab) { if (tab == -1) { emit newTabRequested(); } else { emit tabDoubleClicked(viewForWidget(widget(tab))); } } void Container::documentListActionTriggered(QAction* action) { Q_D(Container); auto* view = action->data().value< Sublime::View* >(); Q_ASSERT(view); QWidget* widget = d->viewForWidget.key(view); Q_ASSERT(widget); setCurrentWidget(widget); } Sublime::View* Container::currentView() const { Q_D(const Container); return d->viewForWidget.value(widget( d->tabBar->currentIndex() )); } void Container::focusInEvent(QFocusEvent* event) { Q_D(Container); d->setAsDockMenu(); QWidget::focusInEvent(event); } } #include "container.moc" diff --git a/kdevplatform/sublime/container.h b/kdevplatform/sublime/container.h index 3cb613e6a2..046fb35f0d 100644 --- a/kdevplatform/sublime/container.h +++ b/kdevplatform/sublime/container.h @@ -1,123 +1,125 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMECONTAINER_H #define KDEVPLATFORM_SUBLIMECONTAINER_H #include #include "sublimeexport.h" class QMenu; class QPaintEvent; namespace Sublime { class View; class Document; class ContainerPrivate; /** @short Container for the widgets. This container is placed inside mainwindow splitters to show widgets for views in the area. */ class KDEVPLATFORMSUBLIME_EXPORT Container: public QWidget { Q_OBJECT public: explicit Container(QWidget *parent = nullptr); ~Container() override; /**Adds the widget for given @p view to the container.*/ void addWidget(Sublime::View* view, int position = -1); /**Removes the widget from the container.*/ void removeWidget(QWidget *w); /** @return true if widget is placed inside this container.*/ bool hasWidget(QWidget* w) const; QList views() const; int count() const; QWidget *currentWidget() const; void setCurrentWidget(QWidget *w); QWidget *widget(int i) const; int indexOf(QWidget *w) const; View *viewForWidget(QWidget *w) const; void setTabBarHidden(bool hide); + void setCloseButtonsOnTabs(bool show); void setTabColor(const View* view, const QColor& color); void setTabColors(const QHash& colors); void resetTabColors(const QColor& color); /** Adds a corner widget to the left of this containers tab-bar. To remove it again, just delete it. * The ownership otherwise goes to the container. */ void setLeftCornerWidget(QWidget* widget); void showTooltipForTab(int tab); bool isCurrentTab(int tab) const; /// @return Rect in global position of the tab identified by index @p tab QRect tabRect(int tab) const; static bool configTabBarVisible(); + static bool configCloseButtonsOnTabs(); Q_SIGNALS: void activateView(Sublime::View* view); void requestClose(QWidget *w); /** * This signal is emitted whenever the users double clicks on the free * space next to the tab bar. Typically, a new document should be * created. */ void newTabRequested(); void tabContextMenuRequested(Sublime::View* view, QMenu* menu); /** * @p view The view represented by the tab that was hovered * @p Container The tab container that triggered the event * @p idx The index of the tab that was hovered */ void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int idx); void tabDoubleClicked(Sublime::View* view); private Q_SLOTS: void widgetActivated(int idx); void documentTitleChanged(Sublime::Document* doc); void statusIconChanged(Sublime::Document*); void statusChanged(Sublime::View *view); void requestClose(int idx); void tabMoved(int from, int to); void contextMenu(const QPoint&); void doubleClickTriggered(int tab); void documentListActionTriggered(QAction*); private: Sublime::View* currentView() const; protected: void focusInEvent(QFocusEvent*) override; private: const QScopedPointer d_ptr; Q_DECLARE_PRIVATE(Container) }; } #endif diff --git a/kdevplatform/sublime/mainwindow.cpp b/kdevplatform/sublime/mainwindow.cpp index 507b6feb18..0b36845894 100644 --- a/kdevplatform/sublime/mainwindow.cpp +++ b/kdevplatform/sublime/mainwindow.cpp @@ -1,490 +1,492 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "idealbuttonbarwidget.h" #include "idealcontroller.h" #include "holdupdates.h" #include namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) : KParts::MainWindow(nullptr, flags) , d_ptr(new MainWindowPrivate(this, controller)) { connect(this, &MainWindow::destroyed, controller, QOverload<>::of(&Controller::areaReleased)); loadGeometry(KSharedConfig::openConfig()->group("Main Window")); // don't allow AllowTabbedDocks - that doesn't make sense for "ideal" UI setDockOptions(QMainWindow::AnimatedDocks); } bool MainWindow::containsView(View* view) const { const auto areas = this->areas(); return std::any_of(areas.begin(), areas.end(), [view](Area* area) { return area->views().contains(view); }); } QList< Area* > MainWindow::areas() const { QList< Area* > areas = controller()->areas(const_cast(this)); if(areas.isEmpty()) areas = controller()->defaultAreas(); return areas; } MainWindow::~MainWindow() { qCDebug(SUBLIME) << "destroying mainwindow"; } void MainWindow::reconstructViews(const QList& topViews) { Q_D(MainWindow); d->reconstructViews(topViews); } QList MainWindow::topViews() const { Q_D(const MainWindow); QList topViews; const auto views = d->area->views(); for (View* view : views) { if(view->hasWidget()) { QWidget* widget = view->widget(); if(widget->parent() && widget->parent()->parent()) { auto* container = qobject_cast(widget->parent()->parent()); if(container->currentWidget() == widget) topViews << view; } } } return topViews; } QList MainWindow::containers() const { Q_D(const MainWindow); return d->viewContainers.values(); } void MainWindow::setArea(Area *area) { Q_D(MainWindow); if (d->area) disconnect(d->area, nullptr, d, nullptr); bool differentArea = (area != d->area); /* All views will be removed from dock area now. However, this does not mean those are removed from area, so prevent slotDockShown from recording those views as no longer shown in the area. */ d->ignoreDockShown = true; if (d->autoAreaSettingsSave && differentArea) saveSettings(); HoldUpdates hu(this); if (d->area) clearArea(); d->area = area; d->reconstruct(); if(d->area->activeView()) activateView(d->area->activeView()); else d->activateFirstVisibleView(); initializeStatusBar(); emit areaChanged(area); d->ignoreDockShown = false; hu.stop(); loadSettings(); connect(area, &Area::viewAdded, d, &MainWindowPrivate::viewAdded); connect(area, &Area::viewRemoved, d, &MainWindowPrivate::viewRemovedInternal); connect(area, &Area::requestToolViewRaise, d, &MainWindowPrivate::raiseToolView); connect(area, &Area::aboutToRemoveView, d, &MainWindowPrivate::aboutToRemoveView); connect(area, &Area::toolViewAdded, d, &MainWindowPrivate::toolViewAdded); connect(area, &Area::aboutToRemoveToolView, d, &MainWindowPrivate::aboutToRemoveToolView); connect(area, &Area::toolViewMoved, d, &MainWindowPrivate::toolViewMoved); } void MainWindow::initializeStatusBar() { //nothing here, reimplement in the subclasses if you want to have status bar //inside the bottom tool view buttons row } void MainWindow::clearArea() { Q_D(MainWindow); emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { Q_D(const MainWindow); return d->docks; } Area *Sublime::MainWindow::area() const { Q_D(const MainWindow); return d->area; } Controller *MainWindow::controller() const { Q_D(const MainWindow); return d->controller; } View *MainWindow::activeView() const { Q_D(const MainWindow); return d->activeView; } View *MainWindow::activeToolView() const { Q_D(const MainWindow); return d->activeToolView; } void MainWindow::activateView(Sublime::View* view, bool focus) { Q_D(MainWindow); const auto containerIt = d->viewContainers.constFind(view); if (containerIt == d->viewContainers.constEnd()) return; if (d->activeView == view) { if (focus && view && !view->widget()->hasFocus()) view->widget()->setFocus(); return; } (*containerIt)->setCurrentWidget(view->widget()); setActiveView(view, focus); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view, bool focus) { Q_D(MainWindow); View* oldActiveView = d->activeView; d->activeView = view; if (focus && view && !view->widget()->hasFocus()) view->widget()->setFocus(); if(d->activeView != oldActiveView) emit activeViewChanged(view); } void Sublime::MainWindow::setActiveToolView(View *view) { Q_D(MainWindow); d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { Q_D(MainWindow); d->disableConcentrationMode(); QString group = QStringLiteral("MainWindow"); if (area()) group += QLatin1Char('_') + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); // This will try to save also the window size and the enabled state of the statusbar. // But it's OK, since we won't use this information when loading. saveMainWindowSettings(cg); //debugToolBar visibility is stored separately to allow a area dependent default value const auto toolBars = this->toolBars(); for (KToolBar* toolbar : toolBars) { if (toolbar->objectName() == QLatin1String("debugToolBar")) { cg.writeEntry("debugToolBarVisibility", toolbar->isVisibleTo(this)); } } d->idealController->leftBarWidget->saveOrderSettings(cg); d->idealController->bottomBarWidget->saveOrderSettings(cg); d->idealController->rightBarWidget->saveOrderSettings(cg); cg.sync(); } void MainWindow::loadSettings() { Q_D(MainWindow); HoldUpdates hu(this); qCDebug(SUBLIME) << "loading settings for " << (area() ? area()->objectName() : QString()); QString group = QStringLiteral("MainWindow"); if (area()) group += QLatin1Char('_') + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); // What follows is copy-paste from applyMainWindowSettings. Unfortunately, // we don't really want that one to try restoring window size, and we also // cannot stop it from doing that in any clean way. // We also do not want that one do it for the enabled state of the statusbar: // KMainWindow scans the widget tree for a QStatusBar-inheriting instance and // set enabled state by the config value stored by the key "StatusBar", // while the QStatusBar subclass used in sublime should always be enabled. auto* mb = findChild(); if (mb) { QString entry = cg.readEntry ("MenuBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) mb->hide(); else mb->show(); } if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { QString entry = cg.readEntry ("ToolBarsMovable", "Enabled"); if ( entry == QLatin1String("Disabled") ) KToolBar::setToolBarsLocked(true); else KToolBar::setToolBarsLocked(false); } // Utilise the QMainWindow::restoreState() functionality // Note that we're fixing KMainWindow bug here -- the original // code has this fragment above restoring toolbar properties. // As result, each save/restore would move the toolbar a bit to // the left. if (cg.hasKey("State")) { QByteArray state; state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } else { // If there's no state we use a default size of 870x650 // Resize only when showing "code" area. If we do that for other areas, // then we'll hit bug https://bugs.kde.org/show_bug.cgi?id=207990 // TODO: adymo: this is more like a hack, we need a proper first-start initialization if (area() && area()->objectName() == QLatin1String("code")) resize(870,650); } int n = 1; // Toolbar counter. toolbars are counted from 1, const auto toolBars = this->toolBars(); for (KToolBar* toolbar : toolBars) { QString group(QStringLiteral("Toolbar")); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars group += (toolbar->objectName().isEmpty() ? QString::number(n) : QLatin1Char(' ')+toolbar->objectName()); KConfigGroup toolbarGroup(&cg, group); toolbar->applySettings(toolbarGroup); if (toolbar->objectName() == QLatin1String("debugToolBar")) { //debugToolBar visibility is stored separately to allow a area dependent default value bool visibility = cg.readEntry("debugToolBarVisibility", area()->objectName() == QLatin1String("debug")); toolbar->setVisible(visibility); } n++; } const bool tabBarHidden = !Container::configTabBarVisible(); + const bool closeButtonsOnTabs = Container::configCloseButtonsOnTabs(); for (Container *container : qAsConst(d->viewContainers)) { container->setTabBarHidden(tabBarHidden); + container->setCloseButtonsOnTabs(closeButtonsOnTabs); } hu.stop(); d->idealController->leftBarWidget->loadOrderSettings(cg); d->idealController->bottomBarWidget->loadOrderSettings(cg); d->idealController->rightBarWidget->loadOrderSettings(cg); emit settingsLoaded(); d->disableConcentrationMode(); } bool MainWindow::queryClose() { // saveSettings(); KConfigGroup config(KSharedConfig::openConfig(), "Main Window"); saveGeometry(config); config.sync(); return KParts::MainWindow::queryClose(); } QString MainWindow::screenKey() const { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QList screens = QApplication::screens(); QRect desk = screens[scnum]->geometry(); // if the desktop is virtual then use virtual screen size if (QGuiApplication::primaryScreen()->virtualSiblings().size() > 1) desk = QGuiApplication::primaryScreen()->virtualGeometry(); return QStringLiteral("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); } void MainWindow::saveGeometry(KConfigGroup &config) const { config.writeEntry(screenKey(), geometry()); } void MainWindow::loadGeometry(const KConfigGroup &config) { // The below code, essentially, is copy-paste from // KMainWindow::restoreWindowSize. Right now, that code is buggy, // as per http://permalink.gmane.org/gmane.comp.kde.devel.core/52423 // so we implement a less theoretically correct, but working, version // below QRect g = config.readEntry(screenKey(), QRect()); if (!g.isEmpty()) setGeometry(g); } void MainWindow::enableAreaSettingsSave() { Q_D(MainWindow); d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() const { Q_D(const MainWindow); return d->idealController->statusBarLocation(); } ViewBarContainer *MainWindow::viewBarContainer() const { Q_D(const MainWindow); return d->viewBarContainer; } void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { Q_D(MainWindow); d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabDoubleClicked(View* view) { Q_UNUSED(view); Q_D(MainWindow); d->toggleDocksShown(); } void MainWindow::tabContextMenuRequested(View* , QMenu* ) { // do nothing } void MainWindow::tabToolTipRequested(View*, Container*, int) { // do nothing } void MainWindow::newTabRequested() { } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea , const QPoint& ) { // do nothing } View* MainWindow::viewForPosition(const QPoint& globalPos) const { Q_D(const MainWindow); for (Container* container : qAsConst(d->viewContainers)) { QRect globalGeom = QRect(container->mapToGlobal(QPoint(0,0)), container->mapToGlobal(QPoint(container->width(), container->height()))); if(globalGeom.contains(globalPos)) { return d->widgetToView[container->currentWidget()]; } } return nullptr; } void MainWindow::setBackgroundCentralWidget(QWidget* w) { Q_D(MainWindow); d->setBackgroundCentralWidget(w); } } #include "moc_mainwindow.cpp"