idealcontroller.cpp 17.1 KB
Newer Older
1
2
3
/*
  Copyright 2007 Roberto Raggi <roberto@kdevelop.org>
  Copyright 2007 Hamish Rodda <rodda@kde.org>
4
  Copyright 2011 Alexander Dymo <adymo@kdevelop.org>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

  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.
*/

23
#include "idealcontroller.h"
24

Roberto Raggi's avatar
Roberto Raggi committed
25
#include <QMainWindow>
26
#include <QToolBar>
27
#include <QStyle>
Kevin Funk's avatar
Kevin Funk committed
28

29
30
#include <KAcceleratorManager>
#include <KActionMenu>
31
#include <KLocalizedString>
32
#include <KSharedConfig>
33

34
#include "area.h"
35
36
#include "view.h"
#include "document.h"
37
#include "mainwindow.h"
38
#include "ideallayout.h"
39
40
#include "idealdockwidget.h"
#include "idealbuttonbarwidget.h"
41
42
43

using namespace Sublime;

44
45
IdealController::IdealController(Sublime::MainWindow* mainWindow):
    QObject(mainWindow), m_mainWindow(mainWindow)
46
{
47
    leftBarWidget = new IdealButtonBarWidget(Qt::LeftDockWidgetArea, this, m_mainWindow);
48
49
    connect(leftBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
            this, &IdealController::slotDockBarContextMenuRequested);
50

51
    rightBarWidget = new IdealButtonBarWidget(Qt::RightDockWidgetArea, this, m_mainWindow);
52
53
    connect(rightBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
            this, &IdealController::slotDockBarContextMenuRequested);
54

55
    bottomBarWidget = new IdealButtonBarWidget(Qt::BottomDockWidgetArea, this, m_mainWindow);
56
    bottomStatusBarLocation = bottomBarWidget->corner();
57
58
    connect(bottomBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
            this, &IdealController::slotDockBarContextMenuRequested);
59

60
    topBarWidget = new IdealButtonBarWidget(Qt::TopDockWidgetArea, this, m_mainWindow);
61
62
    connect(topBarWidget, &IdealButtonBarWidget::customContextMenuRequested,
            this, &IdealController::slotDockBarContextMenuRequested);
63

64
    m_docks = qobject_cast<KActionMenu*>(mainWindow->action("docks_submenu"));
65

66
67
68
    m_showLeftDock = m_mainWindow->action("show_left_dock");
    m_showRightDock = m_mainWindow->action("show_right_dock");
    m_showBottomDock = m_mainWindow->action("show_bottom_dock");
69
70
71

    // 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...
72
    m_showTopDock = m_mainWindow->action("show_top_dock");
73

74
    connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings);
75

76
77
}

78
void IdealController::addView(Qt::DockWidgetArea area, View* view)
79
{
80
    auto *dock = new IdealDockWidget(this, m_mainWindow);
81
    // dock object name is used to store tool view settings
82
83
84
    QString dockObjectName = view->document()->title();
    // support different configuration for same docks opened in different areas
    if (m_mainWindow->area())
85
        dockObjectName += QLatin1Char('_') + m_mainWindow->area()->objectName();
86
87

    dock->setObjectName(dockObjectName);
88
89

    KAcceleratorManager::setNoAccel(dock);
90
    QWidget *w = view->widget(dock);
91
    if (w->parent() == nullptr)
92
93
    {
        /* Could happen when we're moving the widget from
Roberto Raggi's avatar
Roberto Raggi committed
94
           one IdealDockWidget to another.  See moveView below.
95
96
97
           In this case, we need to reparent the widget. */
        w->setParent(dock);
    }
Roberto Raggi's avatar
Roberto Raggi committed
98
99

    QList<QAction *> toolBarActions = view->toolBarActions();
100
    if (toolBarActions.isEmpty()) {
Roberto Raggi's avatar
Roberto Raggi committed
101
      dock->setWidget(w);
102
    } else {
Roberto Raggi's avatar
Roberto Raggi committed
103
      QMainWindow *toolView = new QMainWindow();
104
      auto *toolBar = new QToolBar(toolView);
105
      int iconSize = m_mainWindow->style()->pixelMetric(QStyle::PM_SmallIconSize);
106
      toolBar->setIconSize(QSize(iconSize, iconSize));
Roberto Raggi's avatar
Roberto Raggi committed
107
      toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
108
      toolBar->setWindowTitle(i18n("%1 Toolbar", w->windowTitle()));
Roberto Raggi's avatar
Roberto Raggi committed
109
      toolBar->setFloatable(false);
110
      toolBar->setMovable(false);
111
      toolBar->addActions(toolBarActions);
Roberto Raggi's avatar
Roberto Raggi committed
112
      toolView->setCentralWidget(w);
113
      toolView->setFocusProxy(w);
Roberto Raggi's avatar
Roberto Raggi committed
114
115
      toolView->addToolBar(toolBar);
      dock->setWidget(toolView);
116
117

      KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled");
118
      toolBar->setVisible(cg.readEntry(dockObjectName, true));
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
119
120
121
122
123
      connect(toolBar->toggleViewAction(), &QAction::toggled,
            this, [toolBar, dockObjectName](){
                KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled");
                cg.writeEntry(dockObjectName, toolBar->toggleViewAction()->isChecked());
            });
Roberto Raggi's avatar
Roberto Raggi committed
124
125
    }

126
    dock->setWindowTitle(view->widget()->windowTitle());
127
    dock->setWindowIcon(view->widget()->windowIcon());
128
    dock->setFocusProxy(dock->widget());
129

130
    if (IdealButtonBarWidget* bar = barForDockArea(area)) {
131
        QAction* action = bar->addWidget(dock, static_cast<MainWindow*>(parent())->area(), view);
Vladimir Prus's avatar
Vladimir Prus committed
132
        m_dockwidget_to_action[dock] = m_view_to_action[view] = action;
133

134
        m_docks->addAction(action);
135
        connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle);
136
137
    }

138
    connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged);
139

140
141
    dock->hide();

142
    docks.insert(dock);
143
144
}

145
void IdealController::dockLocationChanged(Qt::DockWidgetArea area)
146
{
147
148
149
150
151
152
153
    // Seems since Qt 5.13 the signal QDockWidget::dockLocationChanged is emitted also when the dock changes
    // to be floating, with area = Qt::NoDockWidgetArea. The current code is not designed for this,
    // so just ignore the signal in that case for now
    if (area == Qt::NoDockWidgetArea) {
        return;
    }

154
    auto *dock = qobject_cast<IdealDockWidget*>(sender());
155
156
    View *view = dock->view();
    QAction* action = m_view_to_action.value(view);
157

158
159
160
161
    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
162
        // 2) user rearranges tool views inside the same area
163
164
165
166
167
168
        // 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();
169
170
        }

171
        return;
172
173
    }

174
    if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
175
        bar->removeAction(action);
176

177
    docks.insert(dock);
178

179
    if (IdealButtonBarWidget* bar = barForDockArea(area)) {
180
        QAction* action = bar->addWidget(dock, static_cast<MainWindow*>(parent())->area(), view);
181
        m_dockwidget_to_action[dock] = m_view_to_action[view] = action;
Hamish Rodda's avatar
Hamish Rodda committed
182

183
184
        // at this point the dockwidget is visible (user dragged it)
        // properly set up UI state
185
        bar->showWidget(action, true);
186

187
        // the dock should now be the "last" opened in a new area, not in the old area
188
        for (auto it = lastDockWidget.begin(); it != lastDockWidget.end(); ++it) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
189
190
            if (it->data() == dock)
                it->clear();
191
192
        }
        lastDockWidget[area] = dock;
193

194
        // after drag, the tool view loses focus, so focus it again
195
        dock->setFocus(Qt::ShortcutFocusReason);
196

197
198
        m_docks->addAction(action);
    }
199

200
    if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea)
201
        dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | IdealDockWidget::DockWidgetVerticalTitleBar);
202
    else
203
        dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
204
205
}

206
IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const
207
{
208
209
210
    switch (area) {
        case Qt::LeftDockWidgetArea:
            return leftBarWidget;
211

212
213
        case Qt::TopDockWidgetArea:
            return topBarWidget;
214

215
216
        case Qt::RightDockWidgetArea:
            return rightBarWidget;
Hamish Rodda's avatar
Hamish Rodda committed
217

218
219
        case Qt::BottomDockWidgetArea:
            return bottomBarWidget;
220

221
222
        default:
            Q_ASSERT(false);
223
            return nullptr;
224
225
226
    }
}

227
void IdealController::slotDockBarContextMenuRequested(const QPoint& position)
228
{
229
    auto* bar = qobject_cast<IdealButtonBarWidget*>(sender());
230
231
232
    Q_ASSERT(bar);

    emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position));
233
234
}

235
void IdealController::raiseView(View* view, RaiseMode mode)
236
{
237
238
239
    /// @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.
240
    ///       Example: Open two equal tool views (for example 2x konsole),
241
242
    ///                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
243
    ///       for tool views of the same type.
244
    mode = HideOtherViews;
245

246
247
    QAction* action = m_view_to_action.value(view);
    Q_ASSERT(action);
248

249
    QWidget *focusWidget = m_mainWindow->focusWidget();
250

251
    action->setProperty("raise", mode);
252
253
    action->setChecked(true);
    // TODO: adymo: hack: focus needs to stay inside the previously
254
    // focused widget (setChecked will focus the tool view)
255
256
    if (focusWidget)
        focusWidget->setFocus(Qt::ShortcutFocusReason);
257
}
258

259
QList< IdealDockWidget* > IdealController::allDockWidgets() const
260
{
261
    return docks.toList();
Hamish Rodda's avatar
Hamish Rodda committed
262
263
}

264
void IdealController::showDockWidget(IdealDockWidget* dock, bool show)
Hamish Rodda's avatar
Hamish Rodda committed
265
266
267
{
    Q_ASSERT(docks.contains(dock));

268
    Qt::DockWidgetArea area = dock->dockWidgetArea();
269

270
    if (show) {
271
272
        m_mainWindow->addDockWidget(area, dock);
        dock->show();
273
    } else {
274
        m_mainWindow->removeDockWidget(dock);
275
    }
276

277
278
    setShowDockStatus(area, show);
    emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show);
279
280

    if (!show)
281
        // Put the focus back on the editor if a dock was hidden
282
        focusEditor();
283
284
285
286
    else {
        // focus the dock
        dock->setFocus(Qt::ShortcutFocusReason);
    }
287
288
}

289
void IdealController::focusEditor()
290
{
291
292
293
    if (View* view = m_mainWindow->activeView())
        if (view->hasWidget())
            view->widget()->setFocus(Qt::ShortcutFocusReason);
294
295
}

296
QWidget* IdealController::statusBarLocation() const
297
{
298
    return bottomStatusBarLocation;
299
300
}

301
QAction* IdealController::actionForView(View* view) const
302
{
303
    return m_view_to_action.value(view);
304
305
}

306
void IdealController::setShowDockStatus(Qt::DockWidgetArea area, bool checked)
307
{
Kevin Funk's avatar
Kevin Funk committed
308
    QAction* action = actionForArea(area);
309
    if (action->isChecked() != checked) {
310
        QSignalBlocker blocker(action);
311
        action->setChecked(checked);
312
313
314
    }
}

Kevin Funk's avatar
Kevin Funk committed
315
QAction* IdealController::actionForArea(Qt::DockWidgetArea area) const
316
{
317
318
319
320
321
322
323
324
325
326
    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;
327
328
329
    }
}

330
void IdealController::removeView(View* view, bool nondestructive)
331
{
332
333
    Q_ASSERT(m_view_to_action.contains(view));
    QAction* action = m_view_to_action.value(view);
334

335
    QWidget *viewParent = view->widget()->parentWidget();
336
    auto *dock = qobject_cast<IdealDockWidget *>(viewParent);
337
    if (!dock) { // tool views with a toolbar live in a QMainWindow which lives in a Dock
338
339
340
        Q_ASSERT(qobject_cast<QMainWindow*>(viewParent));
        viewParent = viewParent->parentWidget();
        dock = qobject_cast<IdealDockWidget*>(viewParent);
341
    }
342
    Q_ASSERT(dock);
343

344
345
346
347
348
    /* 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);
349

350
    if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
351
        bar->removeAction(action);
352

353
354
    m_view_to_action.remove(view);
    m_dockwidget_to_action.remove(dock);
355

356
    if (nondestructive)
357
        view->widget()->setParent(nullptr);
358

359
    delete dock;
360
361
}

362
void IdealController::moveView(View *view, Qt::DockWidgetArea area)
Hamish Rodda's avatar
Hamish Rodda committed
363
{
364
365
    removeView(view);
    addView(area, view);
Hamish Rodda's avatar
Hamish Rodda committed
366
367
}

368
void IdealController::showBottomDock(bool show)
Hamish Rodda's avatar
Hamish Rodda committed
369
{
370
    showDock(Qt::BottomDockWidgetArea, show);
371
372
}

373
void IdealController::showLeftDock(bool show)
374
{
375
    showDock(Qt::LeftDockWidgetArea, show);
Hamish Rodda's avatar
Hamish Rodda committed
376
377
}

378
void IdealController::showRightDock(bool show)
Hamish Rodda's avatar
Hamish Rodda committed
379
{
380
    showDock(Qt::RightDockWidgetArea, show);
Hamish Rodda's avatar
Hamish Rodda committed
381
382
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
383
384
void IdealController::hideDocks(IdealButtonBarWidget *bar)
{
385
386
    const auto barActions = bar->actions();
    for (QAction* action : barActions) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
387
388
389
390
391
392
        if (action->isChecked())
            action->setChecked(false);
    }
    focusEditor();
}

393
void IdealController::showDock(Qt::DockWidgetArea area, bool show)
Hamish Rodda's avatar
Hamish Rodda committed
394
{
395
396
    IdealButtonBarWidget *bar = barForDockArea(area);
    if (!bar) return;
397
    IdealDockWidget *lastDock = lastDockWidget[area].data();
Hamish Rodda's avatar
Hamish Rodda committed
398

399
400
401
    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
Kevin Funk's avatar
Kevin Funk committed
402
        QAction* action = actionForArea(area);
403
        if (!action->isChecked()) {
404
            QSignalBlocker blocker(action);
405
406
407
408
            action->setChecked(true);
        }
        return;
    }
Hamish Rodda's avatar
Hamish Rodda committed
409

410
    if (!show) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
411
        hideDocks(bar);
412
    } else {
413
        // open the last opened tool view (or the first one) and focus it
414
415
416
        if (lastDock) {
            if (QAction *action = m_dockwidget_to_action.value(lastDock))
                action->setChecked(show);
Hamish Rodda's avatar
Hamish Rodda committed
417

418
419
420
            lastDock->setFocus(Qt::ShortcutFocusReason);
            return;
        }
Hamish Rodda's avatar
Hamish Rodda committed
421

422
        if (!barForDockArea(area)->actions().isEmpty())
423
            barForDockArea(area)->actions().first()->setChecked(show);
Hamish Rodda's avatar
Hamish Rodda committed
424
425
426
    }
}

427
// returns currently focused dock widget (if any)
428
IdealDockWidget* IdealController::currentDockWidget() const
429
{
430
431
432
    QWidget *w = m_mainWindow->focusWidget();
    while (true) {
        if (!w) break;
433
        auto *dockCandidate = qobject_cast<IdealDockWidget*>(w);
434
435
        if (dockCandidate)
            return dockCandidate;
436

437
438
        w = w->parentWidget();
    }
439
    return nullptr;
440
441
}

442
void IdealController::goPrevNextDock(IdealController::Direction direction)
443
{
444
445
446
447
    IdealDockWidget *currentDock = currentDockWidget();
    if (!currentDock)
        return;
    IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea());
448

449
    int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock));
450
    int step = (direction == NextDock) ? 1 : -1;
451

452
453
    if (bar->area() == Qt::BottomDockWidgetArea)
        step = -step;
454

455
456
457
458
459
460
461
462
463
    index += step;

    if (index < 0)
        index = bar->actions().count() - 1;

    if (index > bar->actions().count() - 1)
        index = 0;

    bar->actions().at(index)->setChecked(true);
464
465
}

466
void IdealController::toggleDocksShown()
467
{
468
469
470
471
    bool anyBarShown =
        (leftBarWidget->isShown() && !leftBarWidget->isLocked()) ||
        (bottomBarWidget->isShown() && !bottomBarWidget->isLocked()) ||
        (rightBarWidget->isShown() && !rightBarWidget->isLocked());
Anton Anikin's avatar
Anton Anikin committed
472
473
474
475
476

    if (anyBarShown) {
        leftBarWidget->saveShowState();
        bottomBarWidget->saveShowState();
        rightBarWidget->saveShowState();
477
478
    }

479
480
481
482
483
484
485
486
    if (!leftBarWidget->isLocked())
        toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState());

    if (!bottomBarWidget->isLocked())
        toggleDocksShown(bottomBarWidget, !anyBarShown && bottomBarWidget->lastShowState());

    if (!rightBarWidget->isLocked())
        toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState());
487
488
}

489
void IdealController::toggleDocksShown(IdealButtonBarWidget* bar, bool show)
490
{
491
    if (!show) {
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
492
        hideDocks(bar);
493
    } else {
494
        IdealDockWidget *lastDock = lastDockWidget[bar->area()].data();
495
496
497
        if (lastDock)
            m_dockwidget_to_action[lastDock]->setChecked(true);
    }
498
499
}

500
void IdealController::loadSettings()
501
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
502
    KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings");
503
504
505
506
507
508
509
510
511
512
513
514

    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);
515
}