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

0001 /*
0002     SPDX-FileCopyrightText: 2007 Roberto Raggi <roberto@kdevelop.org>
0003     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
0004     SPDX-FileCopyrightText: 2011 Alexander Dymo <adymo@kdevelop.org>
0005 
0006     SPDX-License-Identifier: LicenseRef-MIT-KDevelop-Ideal
0007 */
0008 
0009 #include "idealcontroller.h"
0010 
0011 #include <QMainWindow>
0012 #include <QToolBar>
0013 #include <QStyle>
0014 
0015 #include <KAcceleratorManager>
0016 #include <KActionMenu>
0017 #include <KLocalizedString>
0018 #include <KSharedConfig>
0019 
0020 #include "area.h"
0021 #include "view.h"
0022 #include "document.h"
0023 #include "mainwindow.h"
0024 #include "ideallayout.h"
0025 #include "idealdockwidget.h"
0026 #include "idealbuttonbarwidget.h"
0027 
0028 using namespace Sublime;
0029 
0030 IdealController::IdealController(Sublime::MainWindow* mainWindow):
0031     QObject(mainWindow), m_mainWindow(mainWindow)
0032 {
0033     leftBarWidget = new IdealButtonBarWidget(Qt::LeftDockWidgetArea, this, m_mainWindow);
0034     connect(leftBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
0035             this, &IdealController::slotDockBarContextMenuRequested);
0036 
0037     rightBarWidget = new IdealButtonBarWidget(Qt::RightDockWidgetArea, this, m_mainWindow);
0038     connect(rightBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
0039             this, &IdealController::slotDockBarContextMenuRequested);
0040 
0041     bottomBarWidget = new IdealButtonBarWidget(Qt::BottomDockWidgetArea, this, m_mainWindow);
0042     bottomStatusBarLocation = bottomBarWidget->corner();
0043     connect(bottomBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
0044             this, &IdealController::slotDockBarContextMenuRequested);
0045 
0046     topBarWidget = new IdealButtonBarWidget(Qt::TopDockWidgetArea, this, m_mainWindow);
0047     connect(topBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
0048             this, &IdealController::slotDockBarContextMenuRequested);
0049 
0050     m_docks = qobject_cast<KActionMenu*>(mainWindow->action("docks_submenu"));
0051 
0052     m_showLeftDock = m_mainWindow->action("show_left_dock");
0053     m_showRightDock = m_mainWindow->action("show_right_dock");
0054     m_showBottomDock = m_mainWindow->action("show_bottom_dock");
0055 
0056     // the 'show top dock' action got removed (IOW, it's never created)
0057     // (let's keep this code around if we ever want to reintroduce the feature...
0058     m_showTopDock = m_mainWindow->action("show_top_dock");
0059 
0060     connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings);
0061 
0062 }
0063 
0064 void IdealController::addView(Qt::DockWidgetArea area, View* view)
0065 {
0066     auto *dock = new IdealDockWidget(this, m_mainWindow);
0067     // dock object name is used to store tool view settings
0068     QString dockObjectName = view->document()->title();
0069     // support different configuration for same docks opened in different areas
0070     if (m_mainWindow->area())
0071         dockObjectName += QLatin1Char('_') + m_mainWindow->area()->objectName();
0072 
0073     dock->setObjectName(dockObjectName);
0074 
0075     KAcceleratorManager::setNoAccel(dock);
0076     QWidget *w = view->widget(dock);
0077     if (w->parent() == nullptr)
0078     {
0079         /* Could happen when we're moving the widget from
0080            one IdealDockWidget to another.  See moveView below.
0081            In this case, we need to reparent the widget. */
0082         w->setParent(dock);
0083     }
0084 
0085     QList<QAction *> toolBarActions = view->toolBarActions();
0086     if (toolBarActions.isEmpty()) {
0087       dock->setWidget(w);
0088     } else {
0089       auto* toolView = new QMainWindow();
0090       auto *toolBar = new QToolBar(toolView);
0091       int iconSize = m_mainWindow->style()->pixelMetric(QStyle::PM_SmallIconSize);
0092       toolBar->setIconSize(QSize(iconSize, iconSize));
0093       toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0094       toolBar->setWindowTitle(i18nc("@title:window", "%1 Toolbar", w->windowTitle()));
0095       toolBar->setFloatable(false);
0096       toolBar->setMovable(false);
0097       toolBar->addActions(toolBarActions);
0098       toolView->setCentralWidget(w);
0099       toolView->setFocusProxy(w);
0100       toolView->addToolBar(toolBar);
0101       dock->setWidget(toolView);
0102 
0103       KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled");
0104       toolBar->setVisible(cg.readEntry(dockObjectName, true));
0105       connect(toolBar->toggleViewAction(), &QAction::toggled,
0106             this, [toolBar, dockObjectName](){
0107                 KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled");
0108                 cg.writeEntry(dockObjectName, toolBar->toggleViewAction()->isChecked());
0109             });
0110     }
0111 
0112     dock->setWindowTitle(view->widget()->windowTitle());
0113     dock->setWindowIcon(view->widget()->windowIcon());
0114     dock->setFocusProxy(dock->widget());
0115 
0116     if (IdealButtonBarWidget* bar = barForDockArea(area)) {
0117         QAction* action = bar->addWidget(dock, m_mainWindow->area(), view);
0118         m_dockwidget_to_action[dock] = m_view_to_action[view] = action;
0119 
0120         m_docks->addAction(action);
0121         connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle);
0122     }
0123 
0124     connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged);
0125 
0126     dock->hide();
0127 
0128     docks.insert(dock);
0129 }
0130 
0131 void IdealController::dockLocationChanged(Qt::DockWidgetArea area)
0132 {
0133     // Seems since Qt 5.13 the signal QDockWidget::dockLocationChanged is emitted also when the dock changes
0134     // to be floating, with area = Qt::NoDockWidgetArea. The current code is not designed for this,
0135     // so just ignore the signal in that case for now
0136     if (area == Qt::NoDockWidgetArea) {
0137         return;
0138     }
0139 
0140     auto *dock = qobject_cast<IdealDockWidget*>(sender());
0141     View *view = dock->view();
0142     QAction* action = m_view_to_action.value(view);
0143 
0144     if (dock->dockWidgetArea() == area) {
0145         // this event can happen even when dock changes its location within the same area
0146         // usecases:
0147         // 1) user drags to the same area
0148         // 2) user rearranges tool views inside the same area
0149         // 3) state restoration shows the dock widget
0150 
0151         // in 3rd case we need to show dock if we don't want it to be shown
0152         // TODO: adymo: invent a better solution for the restoration problem
0153         if (!action->isChecked() && dock->isVisible()) {
0154             dock->hide();
0155         }
0156 
0157         return;
0158     }
0159 
0160     if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
0161         bar->removeAction(action);
0162 
0163     docks.insert(dock);
0164 
0165     if (IdealButtonBarWidget* bar = barForDockArea(area)) {
0166         QAction* action = bar->addWidget(dock, m_mainWindow->area(), view);
0167         m_dockwidget_to_action[dock] = m_view_to_action[view] = action;
0168 
0169         // at this point the dockwidget is visible (user dragged it)
0170         // properly set up UI state
0171         bar->showWidget(action, true);
0172 
0173         // the dock should now be the "last" opened in a new area, not in the old area
0174         for (auto& dockWidgetPtr : lastDockWidget) {
0175             if (dockWidgetPtr.data() == dock) {
0176                 dockWidgetPtr.clear();
0177             }
0178         }
0179         lastDockWidget[area] = dock;
0180 
0181         // after drag, the tool view loses focus, so focus it again
0182         dock->setFocus(Qt::ShortcutFocusReason);
0183 
0184         m_docks->addAction(action);
0185     }
0186 
0187     if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea)
0188         dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetVerticalTitleBar);
0189     else
0190         dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
0191 }
0192 
0193 IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const
0194 {
0195     switch (area) {
0196         case Qt::LeftDockWidgetArea:
0197             return leftBarWidget;
0198 
0199         case Qt::TopDockWidgetArea:
0200             return topBarWidget;
0201 
0202         case Qt::RightDockWidgetArea:
0203             return rightBarWidget;
0204 
0205         case Qt::BottomDockWidgetArea:
0206             return bottomBarWidget;
0207 
0208         default:
0209             Q_ASSERT(false);
0210             return nullptr;
0211     }
0212 }
0213 
0214 void IdealController::slotDockBarContextMenuRequested(const QPoint& position)
0215 {
0216     auto* bar = qobject_cast<IdealButtonBarWidget*>(sender());
0217     Q_ASSERT(bar);
0218 
0219     emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position));
0220 }
0221 
0222 void IdealController::raiseView(View* view, RaiseMode mode)
0223 {
0224     /// @todo GroupWithOtherViews is disabled for now by forcing "mode = HideOtherViews".
0225     ///       for the release of KDevelop 4.3.
0226     ///       Reason: Inherent bugs which need significant changes to be fixed.
0227     ///       Example: Open two equal tool views (for example 2x konsole),
0228     ///                activate one, switch area, switch back, -> Both are active instead of one.
0229     ///       The problem is that views are identified purely by their factory-id, which is equal
0230     ///       for tool views of the same type.
0231     mode = HideOtherViews;
0232 
0233     QAction* action = m_view_to_action.value(view);
0234     Q_ASSERT(action);
0235 
0236     QWidget *focusWidget = m_mainWindow->focusWidget();
0237 
0238     action->setProperty("raise", mode);
0239     action->setChecked(true);
0240     // TODO: adymo: hack: focus needs to stay inside the previously
0241     // focused widget (setChecked will focus the tool view)
0242     if (focusWidget)
0243         focusWidget->setFocus(Qt::ShortcutFocusReason);
0244 }
0245 
0246 QList< IdealDockWidget* > IdealController::allDockWidgets() const
0247 {
0248     return docks.values();
0249 }
0250 
0251 void IdealController::showDockWidget(IdealDockWidget* dock, bool show)
0252 {
0253     Q_ASSERT(docks.contains(dock));
0254 
0255     Qt::DockWidgetArea area = dock->dockWidgetArea();
0256 
0257     if (show) {
0258         m_mainWindow->addDockWidget(area, dock);
0259         dock->show();
0260     } else {
0261         m_mainWindow->removeDockWidget(dock);
0262     }
0263 
0264     setShowDockStatus(area, show);
0265     emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show);
0266 
0267     if (!show)
0268         // Put the focus back on the editor if a dock was hidden
0269         focusEditor();
0270     else {
0271         // focus the dock
0272         dock->setFocus(Qt::ShortcutFocusReason);
0273     }
0274 }
0275 
0276 void IdealController::focusEditor()
0277 {
0278     if (View* view = m_mainWindow->activeView())
0279         if (view->hasWidget())
0280             view->widget()->setFocus(Qt::ShortcutFocusReason);
0281 }
0282 
0283 QWidget* IdealController::statusBarLocation() const
0284 {
0285     return bottomStatusBarLocation;
0286 }
0287 
0288 QAction* IdealController::actionForView(View* view) const
0289 {
0290     return m_view_to_action.value(view);
0291 }
0292 
0293 void IdealController::setShowDockStatus(Qt::DockWidgetArea area, bool checked)
0294 {
0295     QAction* action = actionForArea(area);
0296     if (action->isChecked() != checked) {
0297         QSignalBlocker blocker(action);
0298         action->setChecked(checked);
0299     }
0300 }
0301 
0302 QAction* IdealController::actionForArea(Qt::DockWidgetArea area) const
0303 {
0304     switch (area) {
0305         case Qt::LeftDockWidgetArea:
0306         default:
0307             return m_showLeftDock;
0308         case Qt::RightDockWidgetArea:
0309             return m_showRightDock;
0310         case Qt::TopDockWidgetArea:
0311             return m_showTopDock;
0312         case Qt::BottomDockWidgetArea:
0313             return m_showBottomDock;
0314     }
0315 }
0316 
0317 void IdealController::removeView(View* view, bool nondestructive)
0318 {
0319     Q_ASSERT(m_view_to_action.contains(view));
0320     QAction* action = m_view_to_action.value(view);
0321 
0322     QWidget *viewParent = view->widget()->parentWidget();
0323     auto *dock = qobject_cast<IdealDockWidget *>(viewParent);
0324     if (!dock) { // tool views with a toolbar live in a QMainWindow which lives in a Dock
0325         Q_ASSERT(qobject_cast<QMainWindow*>(viewParent));
0326         viewParent = viewParent->parentWidget();
0327         dock = qobject_cast<IdealDockWidget*>(viewParent);
0328     }
0329     Q_ASSERT(dock);
0330 
0331     /* Hide the view, first.  This is a workaround -- if we
0332        try to remove IdealDockWidget without this, then eventually
0333        a call to IdealMainLayout::takeAt will be made, which
0334        method asserts immediately.  */
0335     action->setChecked(false);
0336 
0337     if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
0338         bar->removeAction(action);
0339 
0340     m_view_to_action.remove(view);
0341     m_dockwidget_to_action.remove(dock);
0342 
0343     if (nondestructive)
0344         view->widget()->setParent(nullptr);
0345 
0346     delete dock;
0347 }
0348 
0349 void IdealController::moveView(View *view, Qt::DockWidgetArea area)
0350 {
0351     removeView(view);
0352     addView(area, view);
0353 }
0354 
0355 void IdealController::showBottomDock(bool show)
0356 {
0357     showDock(Qt::BottomDockWidgetArea, show);
0358 }
0359 
0360 void IdealController::showLeftDock(bool show)
0361 {
0362     showDock(Qt::LeftDockWidgetArea, show);
0363 }
0364 
0365 void IdealController::showRightDock(bool show)
0366 {
0367     showDock(Qt::RightDockWidgetArea, show);
0368 }
0369 
0370 void IdealController::hideDocks(IdealButtonBarWidget *bar)
0371 {
0372     const auto barActions = bar->actions();
0373     for (QAction* action : barActions) {
0374         if (action->isChecked())
0375             action->setChecked(false);
0376     }
0377     focusEditor();
0378 }
0379 
0380 void IdealController::showDock(Qt::DockWidgetArea area, bool show)
0381 {
0382     IdealButtonBarWidget *bar = barForDockArea(area);
0383     if (!bar) return;
0384     IdealDockWidget *lastDock = lastDockWidget[area].data();
0385 
0386     if (lastDock && lastDock->isVisible() && !lastDock->hasFocus()) {
0387         lastDock->setFocus(Qt::ShortcutFocusReason);
0388         // re-sync action state given we may have asked for the dock to be hidden
0389         QAction* action = actionForArea(area);
0390         if (!action->isChecked()) {
0391             QSignalBlocker blocker(action);
0392             action->setChecked(true);
0393         }
0394         return;
0395     }
0396 
0397     if (!show) {
0398         hideDocks(bar);
0399     } else {
0400         // open the last opened tool view (or the first one) and focus it
0401         if (lastDock) {
0402             if (QAction *action = m_dockwidget_to_action.value(lastDock))
0403                 action->setChecked(true);
0404 
0405             lastDock->setFocus(Qt::ShortcutFocusReason);
0406             return;
0407         }
0408 
0409         const auto barActions = bar->actions();
0410         if (!barActions.isEmpty())
0411             barActions.first()->setChecked(true);
0412     }
0413 }
0414 
0415 // returns currently focused dock widget (if any)
0416 IdealDockWidget* IdealController::currentDockWidget() const
0417 {
0418     QWidget *w = m_mainWindow->focusWidget();
0419     while (true) {
0420         if (!w) break;
0421         auto *dockCandidate = qobject_cast<IdealDockWidget*>(w);
0422         if (dockCandidate)
0423             return dockCandidate;
0424 
0425         w = w->parentWidget();
0426     }
0427     return nullptr;
0428 }
0429 
0430 void IdealController::goPrevNextDock(IdealController::Direction direction)
0431 {
0432     IdealDockWidget *currentDock = currentDockWidget();
0433     if (!currentDock)
0434         return;
0435     IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea());
0436 
0437     int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock));
0438     int step = (direction == NextDock) ? 1 : -1;
0439 
0440     if (bar->area() == Qt::BottomDockWidgetArea)
0441         step = -step;
0442 
0443     index += step;
0444 
0445     if (index < 0)
0446         index = bar->actions().count() - 1;
0447 
0448     if (index > bar->actions().count() - 1)
0449         index = 0;
0450 
0451     bar->actions().at(index)->setChecked(true);
0452 }
0453 
0454 void IdealController::toggleDocksShown()
0455 {
0456     bool anyBarShown =
0457         (leftBarWidget->isShown() && !leftBarWidget->isLocked()) ||
0458         (bottomBarWidget->isShown() && !bottomBarWidget->isLocked()) ||
0459         (rightBarWidget->isShown() && !rightBarWidget->isLocked());
0460 
0461     if (anyBarShown) {
0462         leftBarWidget->saveShowState();
0463         bottomBarWidget->saveShowState();
0464         rightBarWidget->saveShowState();
0465     }
0466 
0467     if (!leftBarWidget->isLocked())
0468         toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState());
0469 
0470     if (!bottomBarWidget->isLocked())
0471         toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState());
0472 
0473     if (!rightBarWidget->isLocked())
0474         toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState());
0475 }
0476 
0477 void IdealController::toggleDocksShown(IdealButtonBarWidget* bar, bool show)
0478 {
0479     if (!show) {
0480         hideDocks(bar);
0481     } else {
0482         IdealDockWidget *lastDock = lastDockWidget[bar->area()].data();
0483         if (lastDock)
0484             m_dockwidget_to_action[lastDock]->setChecked(true);
0485     }
0486 }
0487 
0488 void IdealController::loadSettings()
0489 {
0490     KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings");
0491 
0492     int bottomOwnsBottomLeft = cg.readEntry("BottomLeftCornerOwner", 0);
0493     if (bottomOwnsBottomLeft)
0494         m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
0495     else
0496         m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
0497 
0498     int bottomOwnsBottomRight = cg.readEntry("BottomRightCornerOwner", 0);
0499     if (bottomOwnsBottomRight)
0500         m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
0501     else
0502         m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
0503 }
0504 
0505 #include "moc_idealcontroller.cpp"