diff --git a/shell/mainwindow_p.cpp b/shell/mainwindow_p.cpp index 8c85833ed8..14ca7c1686 100644 --- a/shell/mainwindow_p.cpp +++ b/shell/mainwindow_p.cpp @@ -1,457 +1,474 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library 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 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. 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 #include #include #include #include #include #include #include #include #include "core.h" #include "partdocument.h" #include "partcontroller.h" #include "uicontroller.h" #include "statusbar.h" #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" #include "debug.h" #include "ktexteditorpluginintegration.h" #include "colorschemechooser.h" #include #include #include #include #include namespace KDevelop { MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_statusBar(nullptr) , lastXMLGUIClientView(nullptr) , m_changingActiveView(false) , m_kateWrapper(new KTextEditorIntegration::MainWindow(mainWindow)) { } void MainWindowPrivate::setupGui() { m_statusBar = new KDevelop::StatusBar(m_mainWindow); setupStatusBar(); } void MainWindowPrivate::setupStatusBar() { QWidget *location = m_mainWindow->statusBarLocation(); if (m_statusBar) location->layout()->addWidget(m_statusBar); } void MainWindowPrivate::addPlugin( IPlugin *plugin ) { qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName(); Q_ASSERT( plugin ); //The direct plugin client can only be added to the first mainwindow if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0]) m_mainWindow->guiFactory()->addClient( plugin ); Q_ASSERT(!m_pluginCustomClients.contains(plugin)); KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow); if(ownClient) { m_pluginCustomClients[plugin] = ownClient; connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->addClient(ownClient); } } void MainWindowPrivate::pluginDestroyed(QObject* pluginObj) { IPlugin* plugin = static_cast(pluginObj); KXMLGUIClient* p = m_pluginCustomClients.take(plugin); m_mainWindow->guiFactory()->removeClient( p ); delete p; } MainWindowPrivate::~MainWindowPrivate() { qDeleteAll(m_pluginCustomClients); } void MainWindowPrivate::removePlugin( IPlugin *plugin ) { Q_ASSERT( plugin ); pluginDestroyed(plugin); disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->removeClient( plugin ); } void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) m_mainWindow->createGUI(part); } void MainWindowPrivate::changeActiveView(Sublime::View *view) { //disable updates on a window to avoid toolbar flickering on xmlgui client change Sublime::HoldUpdates s(m_mainWindow); mergeView(view); if(!view) return; IDocument *doc = dynamic_cast(view->document()); if (doc) { doc->activate(view, m_mainWindow); } else { //activated view is not a part document so we need to remove active part gui ///@todo adymo: only this window needs to remove GUI // KParts::Part *activePart = Core::self()->partController()->activePart(); // if (activePart) // guiFactory()->removeClient(activePart); } } void MainWindowPrivate::mergeView(Sublime::View* view) { PushPositiveValue block(m_changingActiveView, true); // If the previous view was KXMLGUIClient, remove its actions // In the case that that view was removed, lastActiveView // will auto-reset, and xmlguifactory will disconnect that // client, I think. if (lastXMLGUIClientView) { qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView; m_mainWindow->guiFactory()->removeClient(dynamic_cast(lastXMLGUIClientView)); disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, nullptr); lastXMLGUIClientView = nullptr; } if (!view) return; QWidget* viewWidget = view->widget(); Q_ASSERT(viewWidget); qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow; // If the new view is KXMLGUIClient, add it. if (KXMLGUIClient* c = dynamic_cast(viewWidget)) { qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget; lastXMLGUIClientView = viewWidget; m_mainWindow->guiFactory()->addClient(c); connect(viewWidget, &QWidget::destroyed, this, &MainWindowPrivate::xmlguiclientDestroyed); } } void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj) { /* We're informed the QWidget for the active view that is also KXMLGUIclient is dying. KXMLGUIFactory will not like deleted clients, really. Unfortunately, there's nothing we can do at this point. For example, KateView derives from QWidget and KXMLGUIClient. The destroyed() signal is emitted by ~QWidget. At this point, event attempt to cross-cast to KXMLGUIClient is undefined behaviour. We hope to catch view deletion a bit later, but if we fail, we better report it now, rather than get a weird crash a bit later. */ Q_ASSERT(obj == lastXMLGUIClientView); Q_ASSERT(false && "xmlgui clients management is messed up"); Q_UNUSED(obj); } void MainWindowPrivate::setupActions() { connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll); QAction* action; const QString app = qApp->applicationName(); action = KStandardAction::preferences( this, SLOT(settingsDialog()), actionCollection()); action->setToolTip( i18nc( "%1 = application name", "Configure %1", app ) ); action->setWhatsThis( i18n( "Lets you customize %1.", app ) ); action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); action->setText( i18n("Configure Notifications...") ); action->setToolTip( i18nc("@info:tooltip", "Configure notifications") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) ); action = actionCollection()->addAction( QStringLiteral("about_platform"), this, SLOT(showAboutPlatform()) ); action->setText( i18n("About KDevelop Platform") ); action->setMenuRole( QAction::NoRole ); // OSX: prevent QT from hiding this due to conflict with 'About KDevelop' action->setStatusTip( i18n("Show Information about KDevelop Platform") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about KDevelop Platform." ) ); action = actionCollection()->addAction( QStringLiteral("loaded_plugins"), this, SLOT(showLoadedPlugins()) ); action->setText( i18n("Loaded Plugins") ); action->setStatusTip( i18n("Show a list of all loaded plugins") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_window") ); action->setText( i18n( "&Next Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setToolTip( i18nc( "@info:tooltip", "Next window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_window") ); action->setText( i18n( "&Previous Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action = actionCollection()->addAction(QStringLiteral("next_error")); action->setText(i18n("Jump to Next Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem); action = actionCollection()->addAction(QStringLiteral("prev_error")); action->setText(i18n("Jump to Previous Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) ); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem); action = actionCollection()->addAction( QStringLiteral("split_horizontal") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-top-bottom") )); action->setText( i18n( "Split View &Top/Bottom" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_T ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal ); action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) ); action = actionCollection()->addAction( QStringLiteral("split_vertical") ); action->setIcon(QIcon::fromTheme( QStringLiteral("view-split-left-right") )); action->setText( i18n( "Split View &Left/Right" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_L ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical ); action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) ); action = actionCollection()->addAction( QStringLiteral("view_next_split") ); action->setText( i18n( "&Next Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_N ); action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); action = actionCollection()->addAction( QStringLiteral("view_previous_split") ); action->setText( i18n( "&Previous Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_P ); action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) ); action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); action = KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() ); action = actionCollection()->addAction( QStringLiteral("file_new") ); action->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_N ); action->setText( i18n( "&New" ) ); action->setIconText( i18nc( "Shorter Text for 'New File' shown in the toolbar", "New") ); connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew ); action->setToolTip( i18nc( "@info:tooltip", "New file" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) ); action = actionCollection()->addAction( QStringLiteral("add_toolview") ); action->setIcon(QIcon::fromTheme(QStringLiteral("window-new"))); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_V ); action->setText( i18n( "&Add Tool View..." ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView ); action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) ); //Load themes actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new ColorSchemeChooser(actionCollection())); } void MainWindowPrivate::toggleArea(bool b) { if (!b) return; QAction* action = qobject_cast(sender()); if (!action) return; m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow); } KActionCollection * MainWindowPrivate::actionCollection() { return m_mainWindow->actionCollection(); } void MainWindowPrivate::registerStatus(QObject* status) { m_statusBar->registerStatus(status); } void MainWindowPrivate::showErrorMessage(QString message, int timeout) { m_statusBar->showErrorMessage(message, timeout); } void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { m_tabView = view; QAction* action; action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split View Top/Bottom")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal); action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18n("Split View Left/Right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("New File")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew); if (view) { if (TextDocument* doc = dynamic_cast(view->document())) { action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload")); connect(action, &QAction::triggered, doc, &TextDocument::reload); action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reload All")); connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll); } } } void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { if (m_tabTooltip.second) { if (m_tabTooltip.first == view) { // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab return; } else { m_tabTooltip.second.data()->close(); } } Sublime::UrlDocument* urlDoc = dynamic_cast(view->document()); if (urlDoc) { DUChainReadLocker lock; TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url()); if (top) { if ( QWidget* navigationWidget = top->createNavigationWidget() ) { NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); tooltip->setHandleRect(container->tabRect(tab)); m_tabTooltip.first = view; m_tabTooltip.second = tooltip; ActiveToolTip::showToolTip(m_tabTooltip.second.data()); } } } } void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { QMenu menu; menu.addSection(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Add Tool View")); QHash factories = Core::self()->uiControllerInternal()->factoryDocuments(); QHash actionToFactory; if ( !factories.isEmpty() ) { // sorted actions QMap actionMap; for (QHash::const_iterator it = factories.constBegin(); it != factories.constEnd(); ++it) { QAction* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu); action->setIcon(it.value()->statusIcon()); if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) { action->setDisabled(true); } actionToFactory.insert(action, it.key()); actionMap[action->text()] = action; } menu.addActions(actionMap.values()); } + QAction* lockAction = new QAction(this); + lockAction->setCheckable(true); + lockAction->setText(i18n("Lock the Panel from Hiding")); + + KConfigGroup config = KSharedConfig::openConfig()->group("UI"); + lockAction->setChecked(config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), false)); + + menu.addSeparator(); + menu.addAction(lockAction); + QAction* triggered = menu.exec(position); if ( !triggered ) { return; } + + if (triggered == lockAction) { + KConfigGroup config = KSharedConfig::openConfig()->group("UI"); + config.writeEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(area), lockAction->isChecked()); + return; + } + Core::self()->uiControllerInternal()->addToolViewToDockArea( actionToFactory[triggered], area ); } bool MainWindowPrivate::changingActiveView() const { return m_changingActiveView; } KTextEditorIntegration::MainWindow *MainWindowPrivate::kateWrapper() const { return m_kateWrapper; } } #include "mainwindow_actions.cpp" diff --git a/sublime/idealbuttonbarwidget.cpp b/sublime/idealbuttonbarwidget.cpp index 39e839577b..d9f02f773d 100644 --- a/sublime/idealbuttonbarwidget.cpp +++ b/sublime/idealbuttonbarwidget.cpp @@ -1,354 +1,360 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealbuttonbarwidget.h" #include "mainwindow.h" #include "idealdockwidget.h" #include "ideallayout.h" #include "idealtoolbutton.h" #include "document.h" #include "view.h" #include #include #include #include #include using namespace Sublime; class ToolViewAction : public QAction { public: ToolViewAction(IdealDockWidget *dock, QObject* parent) : QAction(parent), m_dock(dock) { setCheckable(true); const QString title = dock->view()->document()->title(); setIcon(dock->windowIcon()); setToolTip(i18n("Toggle '%1' tool view.", title)); setText(title); //restore toolview shortcut config KConfigGroup config = KSharedConfig::openConfig()->group("UI"); QStringList shortcutStrings = config.readEntry(QStringLiteral("Shortcut for %1").arg(title), QStringList()); setShortcuts({ QKeySequence::fromString(shortcutStrings.value(0)), QKeySequence::fromString(shortcutStrings.value(1)) }); dock->setWindowTitle(title); dock->view()->widget()->installEventFilter(this); refreshText(); } IdealDockWidget *dockWidget() const { Q_ASSERT(m_dock); return m_dock; } IdealToolButton* button() { return m_button; } void setButton(IdealToolButton* button) { m_button = button; refreshText(); } QString id() { return m_dock->view()->document()->documentSpecifier(); } private: bool eventFilter(QObject * watched, QEvent * event) override { if (watched == m_dock->view()->widget() && event->type() == QEvent::EnabledChange) { refreshText(); } return QObject::eventFilter(watched, event); } void refreshText() { const auto widget = m_dock->view()->widget(); const QString title = m_dock->view()->document()->title(); setText(widget->isEnabled() ? title : QStringLiteral("(%1)").arg(title)); } QPointer m_dock; QPointer m_button; }; IdealButtonBarWidget::IdealButtonBarWidget(Qt::DockWidgetArea area, IdealController *controller, Sublime::MainWindow *parent) : QWidget(parent) , _area(area) , _controller(controller) , _corner(nullptr) , _showState(false) { setContextMenuPolicy(Qt::CustomContextMenu); setToolTip(i18nc("@info:tooltip", "Right click to add new tool views.")); if (area == Qt::BottomDockWidgetArea) { QBoxLayout *statusLayout = new QBoxLayout(QBoxLayout::RightToLeft, this); statusLayout->setMargin(0); statusLayout->setSpacing(IDEAL_LAYOUT_SPACING); statusLayout->setContentsMargins(0, IDEAL_LAYOUT_MARGIN, 0, IDEAL_LAYOUT_MARGIN); IdealButtonBarLayout *l = new IdealButtonBarLayout(orientation()); statusLayout->addLayout(l); _corner = new QWidget(this); QBoxLayout *cornerLayout = new QBoxLayout(QBoxLayout::LeftToRight, _corner); cornerLayout->setMargin(0); cornerLayout->setSpacing(0); statusLayout->addWidget(_corner); statusLayout->addStretch(1); } else (void) new IdealButtonBarLayout(orientation(), this); } QAction* IdealButtonBarWidget::addWidget(IdealDockWidget *dock, Area *area, View *view) { if (_area == Qt::BottomDockWidgetArea || _area == Qt::TopDockWidgetArea) dock->setFeatures( dock->features() | IdealDockWidget::DockWidgetVerticalTitleBar ); dock->setArea(area); dock->setView(view); dock->setDockWidgetArea(_area); auto action = new ToolViewAction(dock, this); addAction(action); return action; } QWidget* IdealButtonBarWidget::corner() { return _corner; } void IdealButtonBarWidget::addAction(QAction* qaction) { QWidget::addAction(qaction); auto action = dynamic_cast(qaction); if (!action || action->button()) { return; } bool wasEmpty = isEmpty(); IdealToolButton *button = new IdealToolButton(_area); //apol: here we set the usual width of a button for the vertical toolbars as the minimumWidth //this is done because otherwise when we remove all the buttons and re-add new ones we get all //the screen flickering. This is solved by not defaulting to a smaller width when it's empty int w = button->sizeHint().width(); if (orientation() == Qt::Vertical && w > minimumWidth()) { setMinimumWidth(w); } action->setButton(button); button->setDefaultAction(action); Q_ASSERT(action->dockWidget()); connect(action, &QAction::toggled, this, static_cast(&IdealButtonBarWidget::showWidget)); connect(button, &IdealToolButton::customContextMenuRequested, action->dockWidget(), &IdealDockWidget::contextMenuRequested); addButtonToOrder(button); applyOrderToLayout(); if (wasEmpty) { emit emptyChanged(); } } void IdealButtonBarWidget::removeAction(QAction* widgetAction) { QWidget::removeAction(widgetAction); auto action = dynamic_cast(widgetAction); delete action->button(); delete action; if (layout()->isEmpty()) { emit emptyChanged(); } } bool IdealButtonBarWidget::isEmpty() { return actions().isEmpty(); } bool IdealButtonBarWidget::isShown() { return std::any_of(actions().cbegin(), actions().cend(), [](const QAction* action){ return action->isChecked(); }); } void IdealButtonBarWidget::saveShowState() { _showState = isShown(); } bool IdealButtonBarWidget::lastShowState() { return _showState; } QString IdealButtonBarWidget::id(const IdealToolButton* button) const { foreach (QAction* a, actions()) { auto tva = dynamic_cast(a); if (tva && tva->button() == button) { return tva->id(); } } return QString(); } IdealToolButton* IdealButtonBarWidget::button(const QString& id) const { foreach (QAction* a, actions()) { auto tva = dynamic_cast(a); if (tva && tva->id() == id) { return tva->button(); } } return nullptr; } void IdealButtonBarWidget::addButtonToOrder(const IdealToolButton* button) { QString buttonId = id(button); if (!_buttonsOrder.contains(buttonId)) { if (_area == Qt::BottomDockWidgetArea) { _buttonsOrder.push_front(buttonId); } else { _buttonsOrder.push_back(buttonId); } } } void IdealButtonBarWidget::loadOrderSettings(const KConfigGroup& configGroup) { _buttonsOrder = configGroup.readEntry(QStringLiteral("(%1) Tool Views Order").arg(_area), QStringList()); applyOrderToLayout(); } void IdealButtonBarWidget::saveOrderSettings(KConfigGroup& configGroup) { takeOrderFromLayout(); configGroup.writeEntry(QStringLiteral("(%1) Tool Views Order").arg(_area), _buttonsOrder); } +bool IdealButtonBarWidget::isLocked() +{ + KConfigGroup config = KSharedConfig::openConfig()->group("UI"); + return config.readEntry(QStringLiteral("Toolview Bar (%1) Is Locked").arg(_area), false); +} + void IdealButtonBarWidget::applyOrderToLayout() { // If widget already have some buttons in the layout then calling loadOrderSettings() may leads // to situations when loaded order does not contains all existing buttons. Therefore we should // fix this with using addToOrder() method. for (int i = 0; i < layout()->count(); ++i) { if (auto button = dynamic_cast(layout()->itemAt(i)->widget())) { addButtonToOrder(button); layout()->removeWidget(button); } } foreach(const QString& id, _buttonsOrder) { if (auto b = button(id)) { layout()->addWidget(b); } } } void IdealButtonBarWidget::takeOrderFromLayout() { _buttonsOrder.clear(); for (int i = 0; i < layout()->count(); ++i) { if (auto button = dynamic_cast(layout()->itemAt(i)->widget())) { _buttonsOrder += id(button); } } } Qt::Orientation IdealButtonBarWidget::orientation() const { if (_area == Qt::LeftDockWidgetArea || _area == Qt::RightDockWidgetArea) return Qt::Vertical; return Qt::Horizontal; } Qt::DockWidgetArea IdealButtonBarWidget::area() const { return _area; } void IdealButtonBarWidget::showWidget(bool checked) { Q_ASSERT(parentWidget() != nullptr); QAction *action = qobject_cast(sender()); Q_ASSERT(action); showWidget(action, checked); } void IdealButtonBarWidget::showWidget(QAction *action, bool checked) { auto widgetAction = dynamic_cast(action); IdealToolButton* button = widgetAction->button(); Q_ASSERT(button); if (checked) { if ( !QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ) { // Make sure only one widget is visible at any time. // The alternative to use a QActionCollection and setting that to "exclusive" // has a big drawback: QActions in a collection that is exclusive cannot // be un-checked by the user, e.g. in the View -> Tool Views menu. foreach(QAction *otherAction, actions()) { if ( otherAction != widgetAction && otherAction->isChecked() ) otherAction->setChecked(false); } } _controller->lastDockWidget[_area] = widgetAction->dockWidget(); } _controller->showDockWidget(widgetAction->dockWidget(), checked); widgetAction->setChecked(checked); button->setChecked(checked); } IdealDockWidget * IdealButtonBarWidget::widgetForAction(QAction *_action) const { return dynamic_cast(_action)->dockWidget(); } diff --git a/sublime/idealbuttonbarwidget.h b/sublime/idealbuttonbarwidget.h index 22a5ee2abe..6fba18d1b0 100644 --- a/sublime/idealbuttonbarwidget.h +++ b/sublime/idealbuttonbarwidget.h @@ -1,99 +1,101 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef IDEALBUTTONBARWIDGET_H #define IDEALBUTTONBARWIDGET_H #include #include class IdealToolButton; class ToolViewAction; class QAction; class KConfigGroup; namespace Sublime { class MainWindow; class IdealController; class IdealDockWidget; class View; class Area; class IdealButtonBarWidget: public QWidget { Q_OBJECT public: IdealButtonBarWidget(Qt::DockWidgetArea area, IdealController *controller, Sublime::MainWindow *parent); QAction* addWidget(IdealDockWidget *widget, Area* area, View *view); void addAction(QAction *action); void removeAction(QAction* action); Qt::Orientation orientation() const; Qt::DockWidgetArea area() const; IdealDockWidget* widgetForAction(QAction* action) const; QWidget* corner(); void showWidget(QAction *widgetAction, bool checked); bool isEmpty(); bool isShown(); void saveShowState(); bool lastShowState(); void loadOrderSettings(const KConfigGroup& configGroup); void saveOrderSettings(KConfigGroup& configGroup); + bool isLocked(); + signals: void emptyChanged(); private: void showWidget(bool checked); void applyOrderToLayout(); void takeOrderFromLayout(); IdealToolButton* button(const QString& id) const; QString id(const IdealToolButton* button) const; void addButtonToOrder(const IdealToolButton* button); Qt::DockWidgetArea _area; IdealController *_controller; QWidget *_corner; bool _showState; QStringList _buttonsOrder; }; } #endif // IDEALBUTTONBARWIDGET_H diff --git a/sublime/idealcontroller.cpp b/sublime/idealcontroller.cpp index 03715233bb..3461e452a7 100644 --- a/sublime/idealcontroller.cpp +++ b/sublime/idealcontroller.cpp @@ -1,499 +1,507 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealcontroller.h" #include #include #include #include #include #include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" #include "ideallayout.h" #include "idealtoolbutton.h" #include "idealdockwidget.h" #include "idealbuttonbarwidget.h" using namespace Sublime; IdealController::IdealController(Sublime::MainWindow* mainWindow): QObject(mainWindow), m_mainWindow(mainWindow) { leftBarWidget = new IdealButtonBarWidget(Qt::LeftDockWidgetArea, this, m_mainWindow); connect(leftBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); rightBarWidget = new IdealButtonBarWidget(Qt::RightDockWidgetArea, this, m_mainWindow); connect(rightBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); bottomBarWidget = new IdealButtonBarWidget(Qt::BottomDockWidgetArea, this, m_mainWindow); bottomStatusBarLocation = bottomBarWidget->corner(); connect(bottomBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); topBarWidget = new IdealButtonBarWidget(Qt::TopDockWidgetArea, this, m_mainWindow); connect(topBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); m_docks = qobject_cast(mainWindow->action("docks_submenu")); m_showLeftDock = qobject_cast(m_mainWindow->action("show_left_dock")); m_showRightDock = qobject_cast(m_mainWindow->action("show_right_dock")); m_showBottomDock = qobject_cast(m_mainWindow->action("show_bottom_dock")); // the 'show top dock' action got removed (IOW, it's never created) // (let's keep this code around if we ever want to reintroduce the feature... auto action = m_mainWindow->action("show_top_dock"); if (action) { m_showTopDock = qobject_cast(action); } connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings); } void IdealController::addView(Qt::DockWidgetArea area, View* view) { IdealDockWidget *dock = new IdealDockWidget(this, m_mainWindow); // dock object name is used to store toolview settings QString dockObjectName = view->document()->title(); // support different configuration for same docks opened in different areas if (m_mainWindow->area()) dockObjectName += '_' + m_mainWindow->area()->objectName(); dock->setObjectName(dockObjectName); KAcceleratorManager::setNoAccel(dock); QWidget *w = view->widget(dock); if (w->parent() == nullptr) { /* Could happen when we're moving the widget from one IdealDockWidget to another. See moveView below. In this case, we need to reparent the widget. */ w->setParent(dock); } QList toolBarActions = view->toolBarActions(); if (toolBarActions.isEmpty()) { dock->setWidget(w); } else { QMainWindow *toolView = new QMainWindow(); QToolBar *toolBar = new QToolBar(toolView); int iconSize = m_mainWindow->style()->pixelMetric(QStyle::PM_SmallIconSize); toolBar->setIconSize(QSize(iconSize, iconSize)); toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar->setWindowTitle(i18n("%1 Tool Bar", w->windowTitle())); toolBar->setFloatable(false); toolBar->setMovable(false); toolBar->addActions(toolBarActions); toolView->setCentralWidget(w); toolView->setFocusProxy(w); toolView->addToolBar(toolBar); dock->setWidget(toolView); } dock->setWindowTitle(view->widget()->windowTitle()); dock->setWindowIcon(view->widget()->windowIcon()); dock->setFocusProxy(dock->widget()); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget(dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; m_docks->addAction(action); connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle); } connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged); dock->hide(); docks.insert(dock); } void IdealController::dockLocationChanged(Qt::DockWidgetArea area) { IdealDockWidget *dock = qobject_cast(sender()); View *view = dock->view(); QAction* action = m_view_to_action.value(view); if (dock->dockWidgetArea() == area) { // this event can happen even when dock changes its location within the same area // usecases: // 1) user drags to the same area // 2) user rearranges toolviews inside the same area // 3) state restoration shows the dock widget // in 3rd case we need to show dock if we don't want it to be shown // TODO: adymo: invent a better solution for the restoration problem if (!action->isChecked() && dock->isVisible()) { dock->hide(); } return; } if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); docks.insert(dock); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget(dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; // at this point the dockwidget is visible (user dragged it) // properly set up UI state bar->showWidget(action, true); // the dock should now be the "last" opened in a new area, not in the old area for (auto it = lastDockWidget.begin(); it != lastDockWidget.end(); ++it) { if (it->data() == dock) it->clear(); } lastDockWidget[area] = dock; // after drag, the toolview loses focus, so focus it again dock->setFocus(Qt::ShortcutFocusReason); m_docks->addAction(action); } if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea) dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | IdealDockWidget::DockWidgetVerticalTitleBar | QDockWidget::DockWidgetMovable); else dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetMovable ); } IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: return leftBarWidget; case Qt::TopDockWidgetArea: return topBarWidget; case Qt::RightDockWidgetArea: return rightBarWidget; case Qt::BottomDockWidgetArea: return bottomBarWidget; default: Q_ASSERT(false); return nullptr; } } void IdealController::slotDockBarContextMenuRequested(QPoint position) { IdealButtonBarWidget* bar = qobject_cast(sender()); Q_ASSERT(bar); emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position)); } void IdealController::raiseView(View* view, RaiseMode mode) { /// @todo GroupWithOtherViews is disabled for now by forcing "mode = HideOtherViews". /// for the release of KDevelop 4.3. /// Reason: Inherent bugs which need significant changes to be fixed. /// Example: Open two equal toolviews (for example 2x konsole), /// activate one, switch area, switch back, -> Both are active instead of one. /// The problem is that views are identified purely by their factory-id, which is equal /// for toolviews of the same type. mode = HideOtherViews; QAction* action = m_view_to_action.value(view); Q_ASSERT(action); QWidget *focusWidget = m_mainWindow->focusWidget(); action->setProperty("raise", mode); action->setChecked(true); // TODO: adymo: hack: focus needs to stay inside the previously // focused widget (setChecked will focus the toolview) if (focusWidget) focusWidget->setFocus(Qt::ShortcutFocusReason); } QList< IdealDockWidget* > IdealController::allDockWidgets() { return docks.toList(); } void IdealController::showDockWidget(IdealDockWidget* dock, bool show) { Q_ASSERT(docks.contains(dock)); Qt::DockWidgetArea area = dock->dockWidgetArea(); if (show) { m_mainWindow->addDockWidget(area, dock); dock->show(); } else { m_mainWindow->removeDockWidget(dock); } setShowDockStatus(area, show); emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show); if (!show) // Put the focus back on the editor if a dock was hidden focusEditor(); else { // focus the dock dock->setFocus(Qt::ShortcutFocusReason); } } void IdealController::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } QWidget* IdealController::statusBarLocation() const { return bottomStatusBarLocation; } QAction* IdealController::actionForView(View* view) const { return m_view_to_action.value(view); } void IdealController::setShowDockStatus(Qt::DockWidgetArea area, bool checked) { QAction* action = actionForArea(area); if (action->isChecked() != checked) { QSignalBlocker blocker(action); action->setChecked(checked); } } QAction* IdealController::actionForArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: default: return m_showLeftDock; case Qt::RightDockWidgetArea: return m_showRightDock; case Qt::TopDockWidgetArea: return m_showTopDock; case Qt::BottomDockWidgetArea: return m_showBottomDock; } } void IdealController::removeView(View* view, bool nondestructive) { Q_ASSERT(m_view_to_action.contains(view)); QAction* action = m_view_to_action.value(view); QWidget *viewParent = view->widget()->parentWidget(); IdealDockWidget *dock = qobject_cast(viewParent); if (!dock) { // toolviews with a toolbar live in a QMainWindow which lives in a Dock Q_ASSERT(qobject_cast(viewParent)); viewParent = viewParent->parentWidget(); dock = qobject_cast(viewParent); } Q_ASSERT(dock); /* Hide the view, first. This is a workaround -- if we try to remove IdealDockWidget without this, then eventually a call to IdealMainLayout::takeAt will be made, which method asserts immediately. */ action->setChecked(false); if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); m_view_to_action.remove(view); m_dockwidget_to_action.remove(dock); if (nondestructive) view->widget()->setParent(nullptr); delete dock; } void IdealController::moveView(View *view, Qt::DockWidgetArea area) { removeView(view); addView(area, view); } void IdealController::showBottomDock(bool show) { showDock(Qt::BottomDockWidgetArea, show); } void IdealController::showLeftDock(bool show) { showDock(Qt::LeftDockWidgetArea, show); } void IdealController::showRightDock(bool show) { showDock(Qt::RightDockWidgetArea, show); } void IdealController::hideDocks(IdealButtonBarWidget *bar) { foreach (QAction *action, bar->actions()) { if (action->isChecked()) action->setChecked(false); } focusEditor(); } void IdealController::showDock(Qt::DockWidgetArea area, bool show) { IdealButtonBarWidget *bar = barForDockArea(area); if (!bar) return; IdealDockWidget *lastDock = lastDockWidget[area].data(); if (lastDock && lastDock->isVisible() && !lastDock->hasFocus()) { lastDock->setFocus(Qt::ShortcutFocusReason); // re-sync action state given we may have asked for the dock to be hidden QAction* action = actionForArea(area); if (!action->isChecked()) { QSignalBlocker blocker(action); action->setChecked(true); } return; } if (!show) { hideDocks(bar); } else { // open the last opened toolview (or the first one) and focus it if (lastDock) { if (QAction *action = m_dockwidget_to_action.value(lastDock)) action->setChecked(show); lastDock->setFocus(Qt::ShortcutFocusReason); return; } if (!barForDockArea(area)->actions().isEmpty()) barForDockArea(area)->actions().first()->setChecked(show); } } // returns currently focused dock widget (if any) IdealDockWidget* IdealController::currentDockWidget() { QWidget *w = m_mainWindow->focusWidget(); while (true) { if (!w) break; IdealDockWidget *dockCandidate = qobject_cast(w); if (dockCandidate) return dockCandidate; w = w->parentWidget(); } return nullptr; } void IdealController::goPrevNextDock(IdealController::Direction direction) { IdealDockWidget *currentDock = currentDockWidget(); if (!currentDock) return; IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea()); int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock)); int step = (direction == NextDock) ? 1 : -1; if (bar->area() == Qt::BottomDockWidgetArea) step = -step; index += step; if (index < 0) index = bar->actions().count() - 1; if (index > bar->actions().count() - 1) index = 0; bar->actions().at(index)->setChecked(true); } void IdealController::toggleDocksShown() { - bool anyBarShown = leftBarWidget->isShown() || bottomBarWidget->isShown() || rightBarWidget->isShown(); + bool anyBarShown = + (leftBarWidget->isShown() && !leftBarWidget->isLocked()) || + (bottomBarWidget->isShown() && !bottomBarWidget->isLocked()) || + (rightBarWidget->isShown() && !rightBarWidget->isLocked()); if (anyBarShown) { leftBarWidget->saveShowState(); bottomBarWidget->saveShowState(); rightBarWidget->saveShowState(); } - toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState()); - toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState()); - toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState()); + if (!leftBarWidget->isLocked()) + toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState()); + + if (!bottomBarWidget->isLocked()) + toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState()); + + if (!rightBarWidget->isLocked()) + toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState()); } void IdealController::toggleDocksShown(IdealButtonBarWidget* bar, bool show) { if (!show) { hideDocks(bar); } else { IdealDockWidget *lastDock = lastDockWidget[bar->area()].data(); if (lastDock) m_dockwidget_to_action[lastDock]->setChecked(true); } } void IdealController::loadSettings() { KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); int bottomOwnsBottomLeft = cg.readEntry("BottomLeftCornerOwner", 0); if (bottomOwnsBottomLeft) m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); int bottomOwnsBottomRight = cg.readEntry("BottomRightCornerOwner", 0); if (bottomOwnsBottomRight) m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); } void IdealController::emitWidgetResized(Qt::DockWidgetArea dockArea, int thickness) { emit widgetResized(dockArea, thickness); }