idealcontroller.cpp 16.9 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

Kevin Funk's avatar
Kevin Funk committed
66
67
68
    m_showLeftDock = qobject_cast<QAction*>(m_mainWindow->action("show_left_dock"));
    m_showRightDock = qobject_cast<QAction*>(m_mainWindow->action("show_right_dock"));
    m_showBottomDock = qobject_cast<QAction*>(m_mainWindow->action("show_bottom_dock"));
69
70
71
72
73

    // 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) {
74
        m_showTopDock = qobject_cast<QAction*>(action);
75
    }
76

77
    connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings);
78

79
80
}

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

    dock->setObjectName(dockObjectName);
91
92

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

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

      KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings/Docks/ToolbarEnabled");
121
      toolBar->setVisible(cg.readEntry(dockObjectName, true));
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
122
123
124
125
126
      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
127
128
    }

129
    dock->setWindowTitle(view->widget()->windowTitle());
130
    dock->setWindowIcon(view->widget()->windowIcon());
131
    dock->setFocusProxy(dock->widget());
132

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

137
        m_docks->addAction(action);
138
        connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle);
139
140
    }

141
    connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged);
142

143
144
    dock->hide();

145
    docks.insert(dock);
146
147
}

148
void IdealController::dockLocationChanged(Qt::DockWidgetArea area)
149
{
150
151
152
    IdealDockWidget *dock = qobject_cast<IdealDockWidget*>(sender());
    View *view = dock->view();
    QAction* action = m_view_to_action.value(view);
153

154
155
156
157
    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
158
        // 2) user rearranges tool views inside the same area
159
160
161
162
163
164
        // 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();
165
166
        }

167
        return;
168
169
    }

170
    if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
171
        bar->removeAction(action);
172

173
    docks.insert(dock);
174

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

179
180
        // at this point the dockwidget is visible (user dragged it)
        // properly set up UI state
181
        bar->showWidget(action, true);
182

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

190
        // after drag, the tool view loses focus, so focus it again
191
        dock->setFocus(Qt::ShortcutFocusReason);
192

193
194
        m_docks->addAction(action);
    }
195

196
    if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea)
197
        dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | IdealDockWidget::DockWidgetVerticalTitleBar);
198
    else
199
        dock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
200
201
}

202
IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const
203
{
204
205
206
    switch (area) {
        case Qt::LeftDockWidgetArea:
            return leftBarWidget;
207

208
209
        case Qt::TopDockWidgetArea:
            return topBarWidget;
210

211
212
        case Qt::RightDockWidgetArea:
            return rightBarWidget;
Hamish Rodda's avatar
Hamish Rodda committed
213

214
215
        case Qt::BottomDockWidgetArea:
            return bottomBarWidget;
216

217
218
        default:
            Q_ASSERT(false);
219
            return nullptr;
220
221
222
    }
}

223
void IdealController::slotDockBarContextMenuRequested(const QPoint& position)
224
{
225
226
227
228
    IdealButtonBarWidget* bar = qobject_cast<IdealButtonBarWidget*>(sender());
    Q_ASSERT(bar);

    emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position));
229
230
}

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

242
243
    QAction* action = m_view_to_action.value(view);
    Q_ASSERT(action);
244

245
    QWidget *focusWidget = m_mainWindow->focusWidget();
246

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

255
QList< IdealDockWidget* > IdealController::allDockWidgets() const
256
{
257
    return docks.toList();
Hamish Rodda's avatar
Hamish Rodda committed
258
259
}

260
void IdealController::showDockWidget(IdealDockWidget* dock, bool show)
Hamish Rodda's avatar
Hamish Rodda committed
261
262
263
{
    Q_ASSERT(docks.contains(dock));

264
    Qt::DockWidgetArea area = dock->dockWidgetArea();
265

266
    if (show) {
267
268
        m_mainWindow->addDockWidget(area, dock);
        dock->show();
269
    } else {
270
        m_mainWindow->removeDockWidget(dock);
271
    }
272

273
274
    setShowDockStatus(area, show);
    emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show);
275
276

    if (!show)
277
        // Put the focus back on the editor if a dock was hidden
278
        focusEditor();
279
280
281
282
    else {
        // focus the dock
        dock->setFocus(Qt::ShortcutFocusReason);
    }
283
284
}

285
void IdealController::focusEditor()
286
{
287
288
289
    if (View* view = m_mainWindow->activeView())
        if (view->hasWidget())
            view->widget()->setFocus(Qt::ShortcutFocusReason);
290
291
}

292
QWidget* IdealController::statusBarLocation() const
293
{
294
    return bottomStatusBarLocation;
295
296
}

297
QAction* IdealController::actionForView(View* view) const
298
{
299
    return m_view_to_action.value(view);
300
301
}

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

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

326
void IdealController::removeView(View* view, bool nondestructive)
327
{
328
329
    Q_ASSERT(m_view_to_action.contains(view));
    QAction* action = m_view_to_action.value(view);
330

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

340
341
342
343
344
    /* 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);
345

346
    if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea()))
347
        bar->removeAction(action);
348

349
350
    m_view_to_action.remove(view);
    m_dockwidget_to_action.remove(dock);
351

352
    if (nondestructive)
353
        view->widget()->setParent(nullptr);
354

355
    delete dock;
356
357
}

358
void IdealController::moveView(View *view, Qt::DockWidgetArea area)
Hamish Rodda's avatar
Hamish Rodda committed
359
{
360
361
    removeView(view);
    addView(area, view);
Hamish Rodda's avatar
Hamish Rodda committed
362
363
}

364
void IdealController::showBottomDock(bool show)
Hamish Rodda's avatar
Hamish Rodda committed
365
{
366
    showDock(Qt::BottomDockWidgetArea, show);
367
368
}

369
void IdealController::showLeftDock(bool show)
370
{
371
    showDock(Qt::LeftDockWidgetArea, show);
Hamish Rodda's avatar
Hamish Rodda committed
372
373
}

374
void IdealController::showRightDock(bool show)
Hamish Rodda's avatar
Hamish Rodda committed
375
{
376
    showDock(Qt::RightDockWidgetArea, show);
Hamish Rodda's avatar
Hamish Rodda committed
377
378
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
379
380
381
382
383
384
385
386
387
void IdealController::hideDocks(IdealButtonBarWidget *bar)
{
    foreach (QAction *action, bar->actions()) {
        if (action->isChecked())
            action->setChecked(false);
    }
    focusEditor();
}

388
void IdealController::showDock(Qt::DockWidgetArea area, bool show)
Hamish Rodda's avatar
Hamish Rodda committed
389
{
390
391
    IdealButtonBarWidget *bar = barForDockArea(area);
    if (!bar) return;
392
    IdealDockWidget *lastDock = lastDockWidget[area].data();
Hamish Rodda's avatar
Hamish Rodda committed
393

394
395
396
    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
397
        QAction* action = actionForArea(area);
398
        if (!action->isChecked()) {
399
            QSignalBlocker blocker(action);
400
401
402
403
            action->setChecked(true);
        }
        return;
    }
Hamish Rodda's avatar
Hamish Rodda committed
404

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

413
414
415
            lastDock->setFocus(Qt::ShortcutFocusReason);
            return;
        }
Hamish Rodda's avatar
Hamish Rodda committed
416

417
        if (!barForDockArea(area)->actions().isEmpty())
418
            barForDockArea(area)->actions().first()->setChecked(show);
Hamish Rodda's avatar
Hamish Rodda committed
419
420
421
    }
}

422
// returns currently focused dock widget (if any)
423
IdealDockWidget* IdealController::currentDockWidget() const
424
{
425
426
427
428
429
430
    QWidget *w = m_mainWindow->focusWidget();
    while (true) {
        if (!w) break;
        IdealDockWidget *dockCandidate = qobject_cast<IdealDockWidget*>(w);
        if (dockCandidate)
            return dockCandidate;
431

432
433
        w = w->parentWidget();
    }
434
    return nullptr;
435
436
}

437
void IdealController::goPrevNextDock(IdealController::Direction direction)
438
{
439
440
441
442
    IdealDockWidget *currentDock = currentDockWidget();
    if (!currentDock)
        return;
    IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea());
443

444
    int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock));
445
    int step = (direction == NextDock) ? 1 : -1;
446

447
448
    if (bar->area() == Qt::BottomDockWidgetArea)
        step = -step;
449

450
451
452
453
454
455
456
457
458
    index += step;

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

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

    bar->actions().at(index)->setChecked(true);
459
460
}

461
void IdealController::toggleDocksShown()
462
{
463
464
465
466
    bool anyBarShown =
        (leftBarWidget->isShown() && !leftBarWidget->isLocked()) ||
        (bottomBarWidget->isShown() && !bottomBarWidget->isLocked()) ||
        (rightBarWidget->isShown() && !rightBarWidget->isLocked());
Anton Anikin's avatar
Anton Anikin committed
467
468
469
470
471

    if (anyBarShown) {
        leftBarWidget->saveShowState();
        bottomBarWidget->saveShowState();
        rightBarWidget->saveShowState();
472
473
    }

474
475
476
477
478
479
480
481
    if (!leftBarWidget->isLocked())
        toggleDocksShown(leftBarWidget, !anyBarShown && leftBarWidget->lastShowState());

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

    if (!rightBarWidget->isLocked())
        toggleDocksShown(rightBarWidget, !anyBarShown && rightBarWidget->lastShowState());
482
483
}

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

495
void IdealController::loadSettings()
496
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
497
    KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings");
498
499
500
501
502
503
504
505
506
507
508
509

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