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"