diff --git a/shell/ktexteditorpluginintegration.cpp b/shell/ktexteditorpluginintegration.cpp index 3ff3cd2fd2..7164a48616 100644 --- a/shell/ktexteditorpluginintegration.cpp +++ b/shell/ktexteditorpluginintegration.cpp @@ -1,359 +1,421 @@ /* Copyright 2015 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "ktexteditorpluginintegration.h" #include #include +#include #include #include #include #include #include #include #include "core.h" #include "partcontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "textdocument.h" #include using namespace KDevelop; namespace { KTextEditor::MainWindow *toKteWrapper(KParts::MainWindow *window) { if (auto mainWindow = dynamic_cast(window)) { return mainWindow->kateWrapper() ? mainWindow->kateWrapper()->interface() : nullptr; } else { return nullptr; } } KTextEditor::View *toKteView(Sublime::View *view) { if (auto textView = dynamic_cast(view)) { return textView->textView(); } else { return nullptr; } } class ToolViewFactory; /** * This HACK is required to massage the KTextEditor plugin API into the * GUI concepts we apply in KDevelop. Kate does not allow the user to * delete toolviews and then readd them. We do. To support our use case * we prevent the widget we return to KTextEditor plugins from * MainWindow::createToolView from getting destroyed. This widget class * unsets the parent of the so called container in its dtor. The * ToolViewFactory handles the ownership and destroys the kate widget * as needed. */ class KeepAliveWidget : public QWidget { Q_OBJECT public: explicit KeepAliveWidget(ToolViewFactory *factory, QWidget *parent = nullptr) : QWidget(parent) , m_factory(factory) { } ~KeepAliveWidget() override; private: ToolViewFactory *m_factory; }; class ToolViewFactory : public QObject, public KDevelop::IToolViewFactory { Q_OBJECT public: ToolViewFactory(const QString &text, const QIcon &icon, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos) : m_text(text) , m_icon(icon) , m_identifier(identifier) , m_container(new QWidget) , m_pos(pos) { m_container->setLayout(new QVBoxLayout); } ~ToolViewFactory() override { delete m_container; } QWidget *create(QWidget *parent = nullptr) override { auto widget = new KeepAliveWidget(this, parent); widget->setWindowTitle(m_text); widget->setWindowIcon(m_icon); widget->setLayout(new QVBoxLayout); widget->layout()->addWidget(m_container); widget->addActions(m_container->actions()); return widget; } Qt::DockWidgetArea defaultPosition() override { switch (m_pos) { case KTextEditor::MainWindow::Left: return Qt::LeftDockWidgetArea; case KTextEditor::MainWindow::Right: return Qt::RightDockWidgetArea; case KTextEditor::MainWindow::Top: return Qt::TopDockWidgetArea; case KTextEditor::MainWindow::Bottom: return Qt::BottomDockWidgetArea; } Q_UNREACHABLE(); } QString id() const override { return m_identifier; } QWidget *container() const { return m_container; } private: QString m_text; QIcon m_icon; QString m_identifier; QPointer m_container; KTextEditor::MainWindow::ToolViewPosition m_pos; friend class KeepAliveWidget; }; KeepAliveWidget::~KeepAliveWidget() { // if the container is still valid, unparent it to prevent it from getting deleted // this happens when the user removes a toolview // on shutdown, the container does get deleted, thus we must guard against that. if (m_factory->container()) { Q_ASSERT(m_factory->container()->parentWidget() == this); m_factory->container()->setParent(nullptr); } } +class ViewBarStackedLayout : public QStackedLayout +{ + Q_OBJECT +public: + QSize sizeHint() const override + { + if (currentWidget()) { + return currentWidget()->sizeHint(); + } + return QStackedLayout::sizeHint(); + } + + QSize minimumSize() const override + { + if (currentWidget()) { + return currentWidget()->minimumSize(); + } + return QStackedLayout::minimumSize(); + } +}; + } namespace KTextEditorIntegration { Application::Application(QObject *parent) : QObject(parent) { } Application::~Application() { KTextEditor::Editor::instance()->setApplication(nullptr); } KTextEditor::MainWindow *Application::activeMainWindow() const { return toKteWrapper(Core::self()->uiController()->activeMainWindow()); } QList Application::mainWindows() const { return {activeMainWindow()}; } bool Application::closeDocument(KTextEditor::Document *document) const { auto documentController = Core::self()->documentControllerInternal(); return documentController->closeDocument(document->url()); } KTextEditor::Plugin *Application::plugin(const QString &id) const { auto kdevPlugin = Core::self()->pluginController()->loadPlugin(id); const auto plugin = dynamic_cast(kdevPlugin); return plugin ? plugin->interface() : nullptr; } MainWindow::MainWindow(KDevelop::MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_interface(new KTextEditor::MainWindow(this)) + , m_viewBarContainerLayout(new ViewBarStackedLayout) { + auto viewBarContainer = m_mainWindow->viewBarContainer(); + viewBarContainer->setLayout(m_viewBarContainerLayout); + connect(mainWindow, &Sublime::MainWindow::viewAdded, this, [this] (Sublime::View *view) { if (auto kteView = toKteView(view)) { emit m_interface->viewCreated(kteView); } }); connect(mainWindow, &Sublime::MainWindow::activeViewChanged, this, [this] (Sublime::View *view) { - emit m_interface->viewChanged(toKteView(view)); + auto kteView = toKteView(view); + emit m_interface->viewChanged(kteView); + + if (auto viewBar = m_viewBars.value(kteView)) { + m_viewBarContainerLayout->setCurrentWidget(viewBar); + } }); } MainWindow::~MainWindow() = default; QWidget *MainWindow::createToolView(KTextEditor::Plugin* plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { auto factory = new ToolViewFactory(text, icon, identifier, pos); Core::self()->uiController()->addToolView(text, factory); connect(plugin, &QObject::destroyed, this, [=] { Core::self()->uiController()->removeToolView(factory); }); return factory->container(); } KXMLGUIFactory *MainWindow::guiFactory() const { return m_mainWindow->guiFactory(); } QWidget *MainWindow::window() const { return m_mainWindow; } QList MainWindow::views() const { QList views; foreach (auto area, m_mainWindow->areas()) { foreach (auto view, area->views()) { if (auto kteView = toKteView(view)) { views << kteView; } } } return views; } KTextEditor::View *MainWindow::activeView() const { return toKteView(m_mainWindow->activeView()); } QObject *MainWindow::pluginView(const QString &id) const { return m_pluginViews.value(id); } -QWidget *MainWindow::createViewBar(KTextEditor::View* view) +QWidget *MainWindow::createViewBar(KTextEditor::View *view) { Q_UNUSED(view); - // TODO - return nullptr; + // we reuse the central view bar for every view + return m_mainWindow->viewBarContainer(); } -void MainWindow::deleteViewBar(KTextEditor::View* view) +void MainWindow::deleteViewBar(KTextEditor::View *view) { - Q_UNUSED(view); - // TODO + auto viewBar = m_viewBars.take(view); + if (viewBar == m_viewBarContainerLayout->currentWidget()) { + m_mainWindow->viewBarContainer()->hide(); + } + delete viewBar; +} + +void MainWindow::showViewBar(KTextEditor::View *view) +{ + auto viewBar = m_viewBars.value(view); + Q_ASSERT(viewBar); + m_viewBarContainerLayout->setCurrentWidget(viewBar); + viewBar->show(); + + m_mainWindow->viewBarContainer()->show(); +} + +void MainWindow::hideViewBar(KTextEditor::View *view) +{ + auto viewBar = m_viewBars.value(view); + Q_ASSERT(viewBar); + m_viewBarContainerLayout->setCurrentWidget(viewBar); + viewBar->hide(); + + m_mainWindow->viewBarContainer()->hide(); +} + +void MainWindow::addWidgetToViewBar(KTextEditor::View *view, QWidget *widget) +{ + Q_ASSERT(widget); + m_viewBars[view] = widget; + + m_viewBarContainerLayout->addWidget(widget); } KTextEditor::MainWindow *MainWindow::interface() const { return m_interface; } void MainWindow::addPluginView(const QString &id, QObject *view) { m_pluginViews.insert(id, view); emit m_interface->pluginViewCreated(id, view); } void MainWindow::removePluginView(const QString &id) { auto view = m_pluginViews.take(id).data(); delete view; emit m_interface->pluginViewDeleted(id, view); } Plugin::Plugin(KTextEditor::Plugin *plugin, QObject *parent) : IPlugin({}, parent) , m_plugin(plugin) , m_tracker(new ObjectListTracker(ObjectListTracker::CleanupWhenDone, this)) { } Plugin::~Plugin() = default; void Plugin::unload() { if (auto mainWindow = KTextEditor::Editor::instance()->application()->activeMainWindow()) { auto integration = dynamic_cast(mainWindow->parent()); if (integration) { integration->removePluginView(pluginId()); } } m_tracker->deleteAll(); delete m_plugin; } KXMLGUIClient *Plugin::createGUIForMainWindow(Sublime::MainWindow* window) { auto ret = IPlugin::createGUIForMainWindow(window); auto mainWindow = dynamic_cast(window); Q_ASSERT(mainWindow); auto wrapper = mainWindow->kateWrapper(); auto view = m_plugin->createView(wrapper->interface()); wrapper->addPluginView(pluginId(), view); // ensure that unloading the plugin kills all views m_tracker->append(view); return ret; } KTextEditor::Plugin *Plugin::interface() const { return m_plugin.data(); } QString Plugin::pluginId() const { return Core::self()->pluginController()->pluginInfo(this).pluginId(); } void initialize() { auto app = new KTextEditor::Application(new Application(Core::self())); KTextEditor::Editor::instance()->setApplication(app); } void MainWindow::splitView(Qt::Orientation orientation) { m_mainWindow->split(orientation); } } #include "ktexteditorpluginintegration.moc" diff --git a/shell/ktexteditorpluginintegration.h b/shell/ktexteditorpluginintegration.h index 7152fdfa0f..b0f65af6f5 100644 --- a/shell/ktexteditorpluginintegration.h +++ b/shell/ktexteditorpluginintegration.h @@ -1,122 +1,128 @@ /* Copyright 2015 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #define KDEVPLATFORM_KTEXTEDITOR_PLUGIN_INTEGRATION_H #include #include #include #include #include #include +class QStackedLayout; + namespace KDevelop { class ObjectListTracker; class MainWindow; } namespace KTextEditorIntegration { /** * Class mimicking the KTextEditor::Application interface */ class Application : public QObject { Q_OBJECT public: explicit Application(QObject *parent = nullptr); ~Application() override; public slots: KTextEditor::MainWindow *activeMainWindow() const; QList mainWindows() const; KTextEditor::Plugin *plugin(const QString &id) const; bool closeDocument(KTextEditor::Document *document) const; }; class MainWindow : public QObject { Q_OBJECT public: explicit MainWindow(KDevelop::MainWindow *mainWindow); ~MainWindow() override; public slots: QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); KXMLGUIFactory *guiFactory() const; QWidget *window() const; QList views() const; KTextEditor::View *activeView() const; QObject *pluginView(const QString &id) const; void splitView(Qt::Orientation orientation); QWidget *createViewBar(KTextEditor::View *view); - void deleteViewBar(KTextEditor::View *view); + void showViewBar(KTextEditor::View *view); + void hideViewBar(KTextEditor::View *view); + void addWidgetToViewBar(KTextEditor::View *view, QWidget *widget); public: KTextEditor::MainWindow *interface() const; void addPluginView(const QString &id, QObject *pluginView); void removePluginView(const QString &id); private: KDevelop::MainWindow *m_mainWindow; KTextEditor::MainWindow *m_interface; QHash> m_pluginViews; + QStackedLayout *m_viewBarContainerLayout; + QHash m_viewBars; }; class Plugin : public KDevelop::IPlugin { Q_OBJECT public: explicit Plugin(KTextEditor::Plugin *plugin, QObject *parent = nullptr); ~Plugin() override; KXMLGUIClient* createGUIForMainWindow(Sublime::MainWindow *window) override; void unload() override; KTextEditor::Plugin *interface() const; QString pluginId() const; private: QPointer m_plugin; // view objects and toolviews that should get deleted when the plugin gets unloaded KDevelop::ObjectListTracker *m_tracker; }; void initialize(); } #endif diff --git a/sublime/mainwindow.cpp b/sublime/mainwindow.cpp index 57ef1c297a..8c4e8d11e1 100644 --- a/sublime/mainwindow.cpp +++ b/sublime/mainwindow.cpp @@ -1,441 +1,446 @@ /*************************************************************************** * 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 "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "idealbuttonbarwidget.h" #include "idealcontroller.h" #include "holdupdates.h" #include "sublimedebug.h" Q_LOGGING_CATEGORY(SUBLIME, "kdevplatform.sublime") namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) : KParts::MainWindow(nullptr, flags), d(new MainWindowPrivate(this, controller)) { connect(this, &MainWindow::destroyed, controller, static_cast(&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 { foreach(Area* area, areas()) if(area->views().contains(view)) return true; return false; } 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"; delete d; } void MainWindow::reconstructViews(QList topViews) { d->reconstructViews(topViews); } QList MainWindow::getTopViews() const { QList topViews; foreach(View* view, d->area->views()) { if(view->hasWidget()) { QWidget* widget = view->widget(); if(widget->parent() && widget->parent()->parent()) { Container* container = qobject_cast(widget->parent()->parent()); if(container->currentWidget() == widget) topViews << view; } } } return topViews; } QList MainWindow::containers() const { return d->viewContainers.values(); } void MainWindow::setArea(Area *area) { 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 toolview buttons row } void MainWindow::resizeEvent(QResizeEvent* event) { return KParts::MainWindow::resizeEvent(event); } void MainWindow::clearArea() { emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { return d->docks; } Area *Sublime::MainWindow::area() const { return d->area; } Controller *MainWindow::controller() const { return d->controller; } View *MainWindow::activeView() const { return d->activeView; } View *MainWindow::activeToolView() const { return d->activeToolView; } void MainWindow::activateView(Sublime::View* view, bool focus) { if (!d->viewContainers.contains(view)) return; d->viewContainers[view]->setCurrentWidget(view->widget()); setActiveView(view, focus); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view, bool focus) { 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) { d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { d->disableConcentrationMode(); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); /* This will try to save window size, too. 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 foreach (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() { HoldUpdates hu(this); qCDebug(SUBLIME) << "loading settings for " << (area() ? area()->objectName() : QLatin1String("")); QString group = QStringLiteral("MainWindow"); if (area()) group += '_' + 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. QStatusBar* sb = findChild(); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); if ( entry == QLatin1String("Disabled") ) sb->hide(); else sb->show(); } QMenuBar* 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, foreach (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) : QStringLiteral(" ")+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(); foreach (Container *container, d->viewContainers) { container->setTabBarHidden(tabBarHidden); } 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()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); return QStringLiteral("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); } void MainWindow::saveGeometry(KConfigGroup &config) { 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() { d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() const { return d->idealController->statusBarLocation(); } +QWidget *MainWindow::viewBarContainer() const +{ + return d->viewBarContainer; +} + void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabDoubleClicked(View* view) { Q_UNUSED(view); 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(QPoint globalPos) const { foreach(Container* container, 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) { d->setBackgroundCentralWidget(w); } } #include "moc_mainwindow.cpp" diff --git a/sublime/mainwindow.h b/sublime/mainwindow.h index cbad74d51c..3072b1ee41 100644 --- a/sublime/mainwindow.h +++ b/sublime/mainwindow.h @@ -1,181 +1,184 @@ /*************************************************************************** * 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_SUBLIMEMAINWINDOW_H #define KDEVPLATFORM_SUBLIMEMAINWINDOW_H #include #include #include #include "sublimeexport.h" class QDockWidget; namespace Sublime { class Container; class Area; class View; class Controller; class MainWindowOperator; /** @short Sublime Main Window The area-enabled mainwindow to show Sublime views and toolviews. To use, a controller and constructed areas are necessary: @code MainWindow w(controller); controller->showArea(area, &w); @endcode */ class KDEVPLATFORMSUBLIME_EXPORT MainWindow: public KParts::MainWindow { Q_OBJECT public: /**Creates a mainwindow and adds it to the controller.*/ explicit MainWindow(Controller *controller, Qt::WindowFlags flags = KDE_DEFAULT_WINDOWFLAGS); ~MainWindow() override; /**@return the list of dockwidgets that contain area's toolviews.*/ QList toolDocks() const; /**@return area which mainwindow currently shows or 0 if no area has been set.*/ Area *area() const; /**@return controller for this mainwindow.*/ Controller *controller() const; /**@return active view inside this mainwindow.*/ View *activeView() const; /**@return active toolview inside this mainwindow.*/ View *activeToolView() const; /**Enable saving of per-area UI settings (like toolbar properties and position) whenever area is changed. This should be called after all areas are restored, and main window area is set, to prevent saving a half-broken state. */ void enableAreaSettingsSave(); /** Allows setting an additional widget that will be inserted left to the document tab-bar. * The ownership goes to the target. */ void setTabBarLeftCornerWidget(QWidget* widget); /**Sets the area of main window and fills it with views. *The contents is reconstructed, even if the area equals the currently set area. */ void setArea(Area *area); /** * Reconstruct the view structure. This is required after significant untracked changes to the * area-index structure. * Views listed in topViews will be on top of their view stacks. * */ void reconstructViews(QList topViews = QList()); /**Returns a list of all views which are on top of their corresponding view stacks*/ QList getTopViews() const; QList containers() const; /**Returns the view that is closest to the given global position, or zero.*/ View* viewForPosition(QPoint globalPos) const; /**Returns true if this main-window contains this view*/ bool containsView(View* view) const; /**Returns all areas that belong to this main-window*/ QList areas() const; /** Sets a @p w widget that will be shown when there are no opened documents. * This method takes the ownership of @p w. */ void setBackgroundCentralWidget(QWidget* w); + /**Returns a widget that can hold a centralized view bar*/ + QWidget *viewBarContainer() const; + public Q_SLOTS: /**Shows the @p view and makes it active, focusing it by default).*/ void activateView(Sublime::View *view, bool focus = true); /**Loads size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to load more and don't forget to call inherited method.*/ virtual void loadSettings(); Q_SIGNALS: /**Emitted before the area is cleared from this mainwindow.*/ void areaCleared(Sublime::Area*); /**Emitted after the new area has been shown in this mainwindow.*/ void areaChanged(Sublime::Area*); /**Emitted when the active view is changed.*/ void activeViewChanged(Sublime::View*); /**Emitted when the active toolview is changed.*/ void activeToolViewChanged(Sublime::View*); /**Emitted when the user interface settings have changed.*/ void settingsLoaded(); /**Emitted when a new view is added to the mainwindow.*/ void viewAdded(Sublime::View*); /**Emitted when a view is going to be removed from the mainwindow.*/ void aboutToRemoveView(Sublime::View*); protected: QWidget *statusBarLocation() const; virtual void initializeStatusBar(); protected Q_SLOTS: virtual void tabDoubleClicked(Sublime::View* view); virtual void tabContextMenuRequested(Sublime::View*, QMenu*); virtual void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab); virtual void newTabRequested(); /**Called whenever the user requests a context menu on a dockwidget bar. You can then e.g. add actions to add dockwidgets. Default implementation does nothing.**/ virtual void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&); public: // FIXME? /**Saves size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to save more and don't forget to call inherited method.*/ virtual void saveSettings(); /**Reimplemented to save settings.*/ bool queryClose() override; /** Allow connecting to activateView without the need for a lambda for the default parameter */ void activateViewAndFocus(Sublime::View *view) { activateView(view, true); } private: QString screenKey() const; //Inherit MainWindowOperator to access four methods below /**Unsets the area clearing main window.*/ void clearArea(); /**Sets the active view.*/ void setActiveView(Sublime::View* view, bool focus = true); /**Sets the active toolview and focuses it.*/ void setActiveToolView(View *view); void resizeEvent(QResizeEvent* event) override; void saveGeometry(KConfigGroup &config); void loadGeometry(const KConfigGroup &config); class MainWindowPrivate *const d; friend class MainWindowOperator; friend class MainWindowPrivate; }; } #endif diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index c4361d2397..a12d7115c9 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,799 +1,814 @@ /*************************************************************************** * 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 "mainwindow_p.h" #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" #include "sublimedebug.h" class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, bool hideWhenEmpty, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent) , m_buttons(buttons) , m_hideWhenEmpty(hideWhenEmpty) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); if (m_hideWhenEmpty) { connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, this, &IdealToolBar::updateVisibilty); } } private slots: void updateVisibilty() { setVisible(!m_buttons->isEmpty()); } private: Sublime::IdealButtonBarWidget* m_buttons; const bool m_hideWhenEmpty; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(nullptr), activeView(nullptr), activeToolView(nullptr), bgCentralWidget(nullptr), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); m_concentrationModeAction = new QAction(i18n("Concentration Mode"), this); m_concentrationModeAction->setIcon(QIcon::fromTheme(QStringLiteral("page-zoom"))); m_concentrationModeAction->setToolTip(i18n("Removes most of the controls so you can focus on what matters.")); m_concentrationModeAction->setCheckable(true); m_concentrationModeAction->setChecked(false); ac->setDefaultShortcut(m_concentrationModeAction, Qt::META | Qt::Key_C); connect(m_concentrationModeAction, &QAction::toggled, this, &MainWindowPrivate::restoreConcentrationMode); ac->addAction(QStringLiteral("toggle_concentration_mode"), m_concentrationModeAction); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Left); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction(QStringLiteral("show_left_dock"), action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Right); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction(QStringLiteral("show_right_dock"), action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Down); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction(QStringLiteral("show_bottom_dock"), action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_E); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction(QStringLiteral("focus_editor"), action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_Up); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction(QStringLiteral("hide_all_docks"), action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction(QStringLiteral("select_next_dock"), action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction(QStringLiteral("select_previous_dock"), action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction(QStringLiteral("docks_submenu"), action); idealController = new IdealController(m_mainWindow); m_leftToolBar = new IdealToolBar(i18n("Left Button Bar"), true, idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, m_leftToolBar); m_rightToolBar = new IdealToolBar(i18n("Right Button Bar"), true, idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, m_rightToolBar); m_bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), false, idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, m_bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; + centralWidget->setObjectName(QStringLiteral("centralWidget")); QVBoxLayout* layout = new QVBoxLayout(centralWidget); - centralWidget->setLayout(layout); layout->setMargin(0); + centralWidget->setLayout(layout); + splitterCentralWidget = new QSplitter(centralWidget); - layout->addWidget(splitterCentralWidget); + // take as much space as possible + splitterCentralWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + layout->addWidget(splitterCentralWidget, 2); + + // this view bar container is used for the ktexteditor integration to show + // all view bars at a central place, esp. for split view configurations + viewBarContainer = new QWidget; + viewBarContainer->setObjectName(QStringLiteral("viewBarContainer")); + // hide by default + viewBarContainer->setVisible(false); + // only take as much as needed + viewBarContainer->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + layout->addWidget(viewBarContainer); + m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::disableConcentrationMode() { m_concentrationModeAction->setChecked(false); restoreConcentrationMode(); } void MainWindowPrivate::restoreConcentrationMode() { const bool concentrationModeOn = m_concentrationModeAction->isChecked(); QWidget* cornerWidget = nullptr; if (m_concentrateToolBar) { QLayout* l = m_concentrateToolBar->layout(); QLayoutItem* li = l->takeAt(1); //ensure the cornerWidget isn't destroyed with the toolbar if (li) { cornerWidget = li->widget(); delete li; } m_concentrateToolBar->deleteLater(); } m_mainWindow->menuBar()->setVisible(!concentrationModeOn); m_bottomToolBar->setVisible(!concentrationModeOn); m_leftToolBar->setVisible(!concentrationModeOn); m_rightToolBar->setVisible(!concentrationModeOn); if (concentrationModeOn) { m_concentrateToolBar = new QToolBar(m_mainWindow); m_concentrateToolBar->setObjectName(QStringLiteral("concentrateToolBar")); m_concentrateToolBar->addAction(m_concentrationModeAction); QWidgetAction *action = new QWidgetAction(this); action->setDefaultWidget(m_mainWindow->menuBar()->cornerWidget(Qt::TopRightCorner)); m_concentrateToolBar->addAction(action); m_concentrateToolBar->setMovable(false); m_mainWindow->addToolBar(Qt::TopToolBarArea, m_concentrateToolBar); m_mainWindow->menuBar()->setCornerWidget(nullptr, Qt::TopRightCorner); } else if (cornerWidget) { m_mainWindow->menuBar()->setCornerWidget(cornerWidget, Qt::TopRightCorner); cornerWidget->show(); } if (concentrationModeOn) { m_mainWindow->installEventFilter(this); } else { m_mainWindow->removeEventFilter(this); } } bool MainWindowPrivate::eventFilter(QObject* obj, QEvent* event) { Q_ASSERT(m_mainWindow == obj); if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { const auto ev = static_cast(event); Qt::KeyboardModifiers modifiers = ev->modifiers(); //QLineEdit banned mostly so that alt navigation can be used from QuickOpen const bool visible = modifiers == Qt::AltModifier && ev->type() == QEvent::KeyPress && !qApp->focusWidget()->inherits("QLineEdit"); m_mainWindow->menuBar()->setVisible(visible); } return false; } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = nullptr; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; widget->setParent(nullptr); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); { QSignalBlocker blocker(m_mainWindow); qCDebug(SUBLIME) << "RECONSTRUCT" << area << area->shownToolViews(Sublime::Left); foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget->setParent(nullptr); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(nullptr); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(nullptr); } cleanCentralWidget(); m_mainWindow->setActiveView(nullptr); m_indexSplitters.clear(); area = nullptr; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(nullptr); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qWarning() << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(nullptr); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(nullptr); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(nullptr); else qWarning() << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(nullptr); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ container->setParent(nullptr); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(nullptr); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = nullptr; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = *viewContainers.constBegin(); } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/sublime/mainwindow_p.h b/sublime/mainwindow_p.h index abef1a759e..4650f63ab1 100644 --- a/sublime/mainwindow_p.h +++ b/sublime/mainwindow_p.h @@ -1,166 +1,167 @@ /*************************************************************************** * 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_SUBLIMEMAINWINDOW_P_H #define KDEVPLATFORM_SUBLIMEMAINWINDOW_P_H #include #include #include #include #include #include #include #include "area.h" #include "sublimedefs.h" #include "mainwindow.h" #include #include #include #include #include #include class QMenu; class QAction; class QSplitter; class QDockWidget; class QComboBox; class IdealToolBar; namespace Sublime { class View; class Container; class Controller; class AreaIndex; class IdealMainWidget; class IdealController; class MainWindowPrivate: public QObject { Q_OBJECT public: MainWindowPrivate(MainWindow *w, Controller* controller); ~MainWindowPrivate() override; /**Use this to create tool views for an area.*/ class IdealToolViewCreator { public: explicit IdealToolViewCreator(MainWindowPrivate *_d): d(_d) {} Area::WalkerMode operator() (View *view, Sublime::Position position); private: MainWindowPrivate *d; }; /**Use this to create views for an area.*/ class ViewCreator { public: explicit ViewCreator(MainWindowPrivate *_d, QList _topViews = QList()): d(_d), topViews(_topViews.toSet()) {} Area::WalkerMode operator() (AreaIndex *index); private: MainWindowPrivate *d; QSet topViews; }; /**Reconstructs the mainwindow according to the current area.*/ void reconstruct(); /**Reconstructs the views according to the current area index.*/ void reconstructViews(QList topViews = QList()); /**Clears the area leaving mainwindow empty.*/ void clearArea(); /** Sets a @p w widget that will be shown when there are no documents on the area */ void setBackgroundCentralWidget(QWidget* w); void activateFirstVisibleView(); Controller *controller; Area *area; QList docks; QMap viewContainers; QMap widgetToView; View *activeView; View *activeToolView; QWidget *centralWidget; QWidget* bgCentralWidget; + QWidget* viewBarContainer; QSplitter* splitterCentralWidget; IdealController *idealController; int ignoreDockShown; bool autoAreaSettingsSave; bool eventFilter(QObject* obj, QEvent* event) override; void disableConcentrationMode(); public slots: void toggleDocksShown(); void viewAdded(Sublime::AreaIndex *index, Sublime::View *view); void viewRemovedInternal(Sublime::AreaIndex *index, Sublime::View *view); void raiseToolView(Sublime::View* view); void aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view); void toolViewAdded(Sublime::View *toolView, Sublime::Position position); void aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position position); void toolViewMoved(Sublime::View *toolView, Sublime::Position position); void setTabBarLeftCornerWidget(QWidget* widget); private slots: void switchToArea(QAction *action); void updateAreaSwitcher(Sublime::Area *area); void slotDockShown(Sublime::View*, Sublime::Position, bool); void widgetResized(Qt::DockWidgetArea dockArea, int thickness); void widgetCloseRequest(QWidget* widget); void showLeftDock(bool b); void showRightDock(bool b); void showBottomDock(bool b); void focusEditor(); void selectNextDock(); void selectPreviousDock(); private: void restoreConcentrationMode(); void setBackgroundVisible(bool v); Qt::DockWidgetArea positionToDockArea(Position position); void cleanCentralWidget(); MainWindow *m_mainWindow; // uses QPointer to make already-deleted splitters detectable QMap > m_indexSplitters; QMap m_areaActions; QMap m_actionAreas; QPointer m_leftTabbarCornerWidget; QPointer m_concentrateToolBar; IdealToolBar* m_bottomToolBar; IdealToolBar* m_rightToolBar; IdealToolBar* m_leftToolBar; QAction* m_concentrationModeAction; }; } #endif