ViewManager.cpp 39.5 KB
Newer Older
1
/*
2
3
4
    SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
5
6
*/

7
8
9
// Own
#include "ViewManager.h"

10
#include "config-konsole.h"
11

12
// Qt
13
#include <QStringList>
patrick pereira's avatar
patrick pereira committed
14
#include <QTabBar>
15

16
// KDE
17
#include <KLocalizedString>
18
#include <KActionCollection>
Laurent Montel's avatar
Laurent Montel committed
19
#include <KConfigGroup>
20
21

// Konsole
22
#include <windowadaptor.h>
23

24
25
26
27
28
#include "colorscheme/ColorScheme.h"
#include "colorscheme/ColorSchemeManager.h"

#include "profile/ProfileManager.h"

29
30
31
#include "session/Session.h"
#include "session/SessionController.h"
#include "session/SessionManager.h"
32

33
#include "terminalDisplay/TerminalDisplay.h"
34
#include "widgets/ViewContainer.h"
35
#include "widgets/ViewSplitter.h"
36

37
38
using namespace Konsole;

39
40
int ViewManager::lastManagerId = 0;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
41
42
ViewManager::ViewManager(QObject *parent, KActionCollection *collection) :
    QObject(parent),
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
43
    _viewContainer(nullptr),
44
    _pluggedController(nullptr),
45
    _sessionMap(QHash<TerminalDisplay *, Session *>()),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
46
    _actionCollection(collection),
47
    _navigationMethod(NoNavigation),
48
    _navigationVisibility(NavigationNotSet),
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
49
50
    _managerId(0),
    _terminalDisplayHistoryIndex(-1)
51
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
52
    _viewContainer = createContainer();
53
    // setup actions which are related to the views
54
55
    setupActions();

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
56
    /* TODO: Reconnect
57
    // emit a signal when all of the views held by this view manager are destroyed
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
58
59
    */
    connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
60
            this, &Konsole::ViewManager::empty);
61

62
    // listen for profile changes
Kurt Hindenburg's avatar
Kurt Hindenburg committed
63
64
65
66
    connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged,
            this, &Konsole::ViewManager::profileChanged);
    connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated,
            this, &Konsole::ViewManager::updateViewsForSession);
67
68

    //prepare DBus communication
69
    new WindowAdaptor(this);
70

71
    _managerId = ++lastManagerId;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
72
73
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/")
                                                 + QString::number(_managerId), this);
74
75
}

76
ViewManager::~ViewManager() = default;
77
78
79
80
81
82

int ViewManager::managerId() const
{
    return _managerId;
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
83
QWidget *ViewManager::activeView() const
84
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
85
    return _viewContainer->currentWidget();
86
}
87

Kurt Hindenburg's avatar
Kurt Hindenburg committed
88
QWidget *ViewManager::widget() const
89
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
90
    return _viewContainer;
91
92
}

93
94
void ViewManager::setupActions()
{
95
    Q_ASSERT(_actionCollection);
96
    if (_actionCollection == nullptr) {
97
98
99
        return;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
100
    KActionCollection *collection = _actionCollection;
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
101
    // Let's reuse the pointer, no need not to.
David Hallas's avatar
David Hallas committed
102
    auto *action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
103
104
105
106
    action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
    action->setText(i18nc("@action:inmenu", "Split View Left/Right"));
    connect(action, &QAction::triggered, this, &ViewManager::splitLeftRight);
    collection->addAction(QStringLiteral("split-view-left-right"), action);
107
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_ParenLeft);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
108

David Hallas's avatar
David Hallas committed
109
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
110
111
112
    action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
    action->setText(i18nc("@action:inmenu", "Split View Top/Bottom"));
    connect(action, &QAction::triggered, this, &ViewManager::splitTopBottom);
113
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_ParenRight);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
114
115
    collection->addAction(QStringLiteral("split-view-top-bottom"), action);

David Hallas's avatar
David Hallas committed
116
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
117
118
119
    action->setText(i18nc("@action:inmenu", "Expand View"));
    action->setEnabled(false);
    connect(action, &QAction::triggered, this, &ViewManager::expandActiveContainer);
120
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_BracketRight);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
121
122
123
    collection->addAction(QStringLiteral("expand-active-view"), action);
    _multiSplitterOnlyActions << action;

David Hallas's avatar
David Hallas committed
124
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
125
    action->setText(i18nc("@action:inmenu", "Shrink View"));
126
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_BracketLeft);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
127
128
129
130
    action->setEnabled(false);
    collection->addAction(QStringLiteral("shrink-active-view"), action);
    connect(action, &QAction::triggered, this, &ViewManager::shrinkActiveContainer);
    _multiSplitterOnlyActions << action;
131

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
132
133
134
135
136
137
138
139
    action = collection->addAction(QStringLiteral("detach-view"));
    action->setEnabled(true);
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach")));
    action->setText(i18nc("@action:inmenu", "Detach Current &View"));

    connect(action, &QAction::triggered, this, &ViewManager::detachActiveView);
    _multiSplitterOnlyActions << action;

140
141
    // Ctrl+Shift+D is not used as a shortcut by default because it is too close
    // to Ctrl+D - which will terminate the session in many cases
142
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_H);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
143
144
145
146
147
148
149

    action = collection->addAction(QStringLiteral("detach-tab"));
    action->setEnabled(true);
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach")));
    action->setText(i18nc("@action:inmenu", "Detach Current &Tab"));
    connect(action, &QAction::triggered, this, &ViewManager::detachActiveTab);
    _multiTabOnlyActions << action;
150

151
    // keyboard shortcut only actions
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
152
    action = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this);
153
    const QList<QKeySequence> nextViewActionKeys{Qt::SHIFT | Qt::Key_Right, Qt::CTRL | Qt::Key_PageDown};
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
154
155
156
157
158
    collection->setDefaultShortcuts(action, nextViewActionKeys);
    collection->addAction(QStringLiteral("next-tab"), action);
    connect(action, &QAction::triggered, this, &ViewManager::nextView);
    _multiTabOnlyActions << action;
    // _viewSplitter->addAction(nextViewAction);
Robert Knight's avatar
   
Robert Knight committed
159

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
160
    action = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this);
161
    const QList<QKeySequence> previousViewActionKeys{Qt::SHIFT | Qt::Key_Left, Qt::CTRL | Qt::Key_PageUp};
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
162
163
164
165
166
167
    collection->setDefaultShortcuts(action, previousViewActionKeys);
    collection->addAction(QStringLiteral("previous-tab"), action);
    connect(action, &QAction::triggered, this, &ViewManager::previousView);
    _multiTabOnlyActions << action;
    // _viewSplitter->addAction(previousViewAction);

168
    action = new QAction(i18nc("@action Shortcut entry", "Focus Above Terminal"), this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
169
    connect(action, &QAction::triggered, this, &ViewManager::focusUp);
170
    collection->addAction(QStringLiteral("focus-view-above"), action);
171
    collection->setDefaultShortcut(action, Qt::SHIFT | Qt::CTRL | Qt::Key_Up);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
172
173
174
    _viewContainer->addAction(action);
    _multiSplitterOnlyActions << action;

175
    action = new QAction(i18nc("@action Shortcut entry", "Focus Below Terminal"), this);
176
    collection->setDefaultShortcut(action, Qt::SHIFT | Qt::CTRL | Qt::Key_Down);
177
    collection->addAction(QStringLiteral("focus-view-below"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
178
179
180
181
    connect(action, &QAction::triggered, this, &ViewManager::focusDown);
    _multiSplitterOnlyActions << action;
    _viewContainer->addAction(action);

182
    action = new QAction(i18nc("@action Shortcut entry", "Focus Left Terminal"), this);
183
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Konsole::LEFT);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
184
    connect(action, &QAction::triggered, this, &ViewManager::focusLeft);
185
    collection->addAction(QStringLiteral("focus-view-left"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
186
187
    _multiSplitterOnlyActions << action;

188
    action = new QAction(i18nc("@action Shortcut entry", "Focus Right Terminal"), this);
189
    collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Konsole::RIGHT);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
190
    connect(action, &QAction::triggered, this, &ViewManager::focusRight);
191
    collection->addAction(QStringLiteral("focus-view-right"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
192
193
194
195
196
197
198
199
200
    _multiSplitterOnlyActions << action;

    action = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this);
    connect(action, &QAction::triggered, this, &ViewManager::lastView);
    collection->addAction(QStringLiteral("last-tab"), action);
    _multiTabOnlyActions << action;

    action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this);
    connect(action, &QAction::triggered, this, &ViewManager::lastUsedView);
201
    collection->setDefaultShortcut(action, Qt::CTRL | Qt::Key_Tab);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
202
203
204
205
206
207
208
209
210
    collection->addAction(QStringLiteral("last-used-tab"), action);

    action = new QAction(i18nc("@action Shortcut entry", "Toggle Between Two Tabs"), this);
    connect(action, &QAction::triggered, this, &Konsole::ViewManager::toggleTwoViews);
    collection->addAction(QStringLiteral("toggle-two-tabs"), action);
    _multiTabOnlyActions << action;

    action = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs (Reverse)"), this);
    collection->addAction(QStringLiteral("last-used-tab-reverse"), action);
211
    collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
212
213
    connect(action, &QAction::triggered, this, &ViewManager::lastUsedViewReverse);

214
215
216
217
    action = new QAction(i18nc("@action Shortcut entry", "Toggle maximize current view"), this);
    action->setText(i18nc("@action:inmenu", "Toggle maximize current view"));
    action->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
    collection->addAction(QStringLiteral("toggle-maximize-current-view"), action);
218
    collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_E);
219
    connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
220
221
222
    _multiSplitterOnlyActions << action;
    _viewContainer->addAction(action);

223
224
    action = new QAction(i18nc("@action Shortcut entry", "Move tab to the right"), this);
    collection->addAction(QStringLiteral("move-tab-to-right"), action);
225
    collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_Right);
226
    connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabRight);
227
    _multiTabOnlyActions << action;
228
229
230
231
    _viewContainer->addAction(action);

    action = new QAction(i18nc("@action Shortcut entry", "Move tab to the left"), this);
    collection->addAction(QStringLiteral("move-tab-to-left"), action);
232
    collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_Left);
233
    connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabLeft);
234
    _multiTabOnlyActions << action;
235
236
    _viewContainer->addAction(action);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
237
238
    // _viewSplitter->addAction(lastUsedViewReverseAction);
    const int SWITCH_TO_TAB_COUNT = 19;
239
    for (int i = 0; i < SWITCH_TO_TAB_COUNT; ++i) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
240
241
242
        action = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this);
        connect(action, &QAction::triggered, this, [this, i]() { switchToView(i); });
        collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), action);
243
        _multiTabOnlyActions << action;
244

245
        // only add default shortcut bindings for the first 10 tabs, regardless of SWITCH_TO_TAB_COUNT
246
247
        if (i < 9) {
            collection->setDefaultShortcut(action, QStringLiteral("Alt+%1").arg(i + 1));
248
249
        } else if (i == 9) {
            // add shortcut for 10th tab
250
            collection->setDefaultShortcut(action, Qt::ALT | Qt::Key_0);
251
        }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
252
    }
253

254
255
    connect(_viewContainer, &TabbedViewContainer::viewAdded, this, &ViewManager::toggleActionsBasedOnState);
    connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::toggleActionsBasedOnState);
256

257
258
259
260
261
    toggleActionsBasedOnState();
}

void ViewManager::toggleActionsBasedOnState() {
    const int count = _viewContainer->count();
262
    for (QAction *tabOnlyAction : qAsConst(_multiTabOnlyActions)) {
263
264
265
        tabOnlyAction->setEnabled(count > 1);
    }

266
    if ((_viewContainer != nullptr) && (_viewContainer->activeViewSplitter() != nullptr)) {
267
268
269
270
271
272
        const int splitCount = _viewContainer
                ->activeViewSplitter()
                ->getToplevelSplitter()
                ->findChildren<TerminalDisplay*>()
                    .count();

273
        for (QAction *action : qAsConst(_multiSplitterOnlyActions)) {
274
275
276
            action->setEnabled(splitCount > 1);
        }
    }
Robert Knight's avatar
   
Robert Knight committed
277
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
278

279
280
void ViewManager::switchToView(int index)
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    _viewContainer->setCurrentIndex(index);
}

void ViewManager::switchToTerminalDisplay(Konsole::TerminalDisplay* terminalDisplay)
{
    auto splitter = qobject_cast<ViewSplitter*>(terminalDisplay->parentWidget());
    auto toplevelSplitter = splitter->getToplevelSplitter();

    // Focus the TermialDisplay
    terminalDisplay->setFocus();

    if (_viewContainer->currentWidget() != toplevelSplitter) {
        // Focus the tab
        switchToView(_viewContainer->indexOf(toplevelSplitter));
    }
296
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
297

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
298
299
300
301
void ViewManager::focusUp()
{
    _viewContainer->activeViewSplitter()->focusUp();
}
302

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
303
304
305
306
void ViewManager::focusDown()
{
    _viewContainer->activeViewSplitter()->focusDown();
}
307

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
308
309
310
311
void ViewManager::focusLeft()
{
    _viewContainer->activeViewSplitter()->focusLeft();
}
312

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
313
314
315
void ViewManager::focusRight()
{
    _viewContainer->activeViewSplitter()->focusRight();
316
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
317

318
319
void ViewManager::moveActiveViewLeft()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
320
    _viewContainer->moveActiveView(TabbedViewContainer::MoveViewLeft);
321
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
322

323
324
void ViewManager::moveActiveViewRight()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
325
    _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight);
326
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
327

Robert Knight's avatar
   
Robert Knight committed
328
329
void ViewManager::nextContainer()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
330
//    _viewSplitter->activateNextContainer();
Robert Knight's avatar
   
Robert Knight committed
331
332
333
334
}

void ViewManager::nextView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
335
    _viewContainer->activateNextView();
Robert Knight's avatar
   
Robert Knight committed
336
337
338
339
}

void ViewManager::previousView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
340
    _viewContainer->activatePreviousView();
341
}
342

343
344
void ViewManager::lastView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    _viewContainer->activateLastView();
}

void ViewManager::activateLastUsedView(bool reverse)
{
    if (_terminalDisplayHistory.count() <= 1) {
        return;
    }

    if (_terminalDisplayHistoryIndex == -1) {
        _terminalDisplayHistoryIndex = reverse ? _terminalDisplayHistory.count() - 1 : 1;
    } else if (reverse) {
        if (_terminalDisplayHistoryIndex == 0) {
            _terminalDisplayHistoryIndex = _terminalDisplayHistory.count() - 1;
        } else {
            _terminalDisplayHistoryIndex--;
        }
    } else {
        if (_terminalDisplayHistoryIndex >= _terminalDisplayHistory.count() - 1) {
            _terminalDisplayHistoryIndex = 0;
        } else {
            _terminalDisplayHistoryIndex++;
        }
    }

    switchToTerminalDisplay(_terminalDisplayHistory[_terminalDisplayHistoryIndex]);
371
372
}

373
374
void ViewManager::lastUsedView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
375
    activateLastUsedView(false);
376
377
378
379
}

void ViewManager::lastUsedViewReverse()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
380
    activateLastUsedView(true);
381
382
}

383
384
void ViewManager::toggleTwoViews()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
385
386
387
388
389
    if (_terminalDisplayHistory.count() <= 1) {
        return;
    }

    switchToTerminalDisplay(_terminalDisplayHistory.at(1));
390
391
}

392
393
void ViewManager::detachActiveView()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
394
    // find the currently active view and remove it from its container
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
395
396
397
398
399
400
    if ((_viewContainer->findChildren<TerminalDisplay*>()).count() > 1) {
        auto activeSplitter = _viewContainer->activeViewSplitter();
        auto terminal = activeSplitter->activeTerminalDisplay();
        auto newSplitter = new ViewSplitter();
        newSplitter->addTerminalDisplay(terminal, Qt::Horizontal);
        QHash<TerminalDisplay*, Session*> detachedSessions = forgetAll(newSplitter);
Ahmad Samir's avatar
Ahmad Samir committed
401
        Q_EMIT terminalsDetached(newSplitter, detachedSessions);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
402
        focusAnotherTerminal(activeSplitter->getToplevelSplitter());
403
        toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
404
    }
405
406
}

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
407
408
409
410
411
412
413
void ViewManager::detachActiveTab()
{
    const int currentIdx = _viewContainer->currentIndex();
    detachTab(currentIdx);
}

void ViewManager::detachTab(int tabIdx)
414
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
415
416
    ViewSplitter* splitter = _viewContainer->viewSplitterAt(tabIdx);
    QHash<TerminalDisplay*, Session*> detachedSessions = forgetAll(_viewContainer->viewSplitterAt(tabIdx));
Ahmad Samir's avatar
Ahmad Samir committed
417
    Q_EMIT terminalsDetached(splitter, detachedSessions);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
418
}
419

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
420
421
422
QHash<TerminalDisplay*, Session*> ViewManager::forgetAll(ViewSplitter* splitter) {
    splitter->setParent(nullptr);
    QHash<TerminalDisplay*, Session*> detachedSessions;
423
424
    const QList<TerminalDisplay *> displays = splitter->findChildren<TerminalDisplay*>();
    for (TerminalDisplay *terminal : displays) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
425
426
        Session* session = forgetTerminal(terminal);
        detachedSessions[terminal] = session;
427
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
428
429
    return detachedSessions;
}
430

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
431
432
Session* ViewManager::forgetTerminal(TerminalDisplay* terminal)
{
433
    unregisterTerminal(terminal);
434

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
435
    removeController(terminal->sessionController());
436
437
438
    auto session = _sessionMap.take(terminal);
    if (session != nullptr) {
        disconnect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished);
439
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
440
441
442
    _viewContainer->disconnectTerminalDisplay(terminal);
    updateTerminalDisplayHistory(terminal, true);
    return session;
443
444
}

445
446
447
448
449
450
451
452
453
454
455
Session* ViewManager::createSession(const Profile::Ptr &profile, const QString &directory)
{
    Session *session = SessionManager::instance()->createSession(profile);
    Q_ASSERT(session);
    if (!directory.isEmpty()) {
        session->setInitialWorkingDirectory(directory);
    }
    session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId()));
    return session;
}

456
void ViewManager::sessionFinished()
457
{
458
459
    // if this slot is called after the view manager's main widget
    // has been destroyed, do nothing
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
460
    if (_viewContainer.isNull()) {
461
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
462
    }
463

Kurt Hindenburg's avatar
Kurt Hindenburg committed
464
    auto *session = qobject_cast<Session *>(sender());
465
466
    Q_ASSERT(session);

467
468
    auto view = _sessionMap.key(session);
    _sessionMap.remove(view);
469

470
471
472
473
    if (SessionManager::instance()->isClosingAllSessions()){
        return;
    }

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
474
    // Before deleting the view, let's unmaximize if it's maximized.
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
475
    auto *splitter = qobject_cast<ViewSplitter*>(view->parentWidget());
476
    if (splitter == nullptr) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
477
478
479
480
        return;
    }

    auto *toplevelSplitter = splitter->getToplevelSplitter();
481

482
    toplevelSplitter->handleMinimizeMaximize(false);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
483
    view->deleteLater();
484
    connect(view, &QObject::destroyed, this, [this]() { toggleActionsBasedOnState(); });
485

486
487
488
    // Only remove the controller from factory() if it's actually controlling
    // the session from the sender.
    // This fixes BUG: 348478 - messed up menus after a detached tab is closed
489
    if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) {
490
491
        // This is needed to remove this controller from factory() in
        // order to prevent BUG: 185466 - disappearing menu popup
Ahmad Samir's avatar
Ahmad Samir committed
492
        Q_EMIT unplugController(_pluggedController);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
493
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
494

495
    if (!_sessionMap.empty()) {
496
497
498
        updateTerminalDisplayHistory(view, true);
        focusAnotherTerminal(toplevelSplitter);
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
499
500
501
502
503
}

void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter)
{
    auto tabTterminalDisplays = toplevelSplitter->findChildren<TerminalDisplay*>();
504
505
506
507
    if (tabTterminalDisplays.count() == 0) {
        return;
    }

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
508
509
510
511
512
513
514
515
516
517
518
519
520
521
    if (tabTterminalDisplays.count() > 1) {
        // Give focus to the last used terminal in this tab
        for (auto *historyItem : _terminalDisplayHistory) {
            for (auto *terminalDisplay : tabTterminalDisplays) {
                if (terminalDisplay == historyItem) {
                    terminalDisplay->setFocus(Qt::OtherFocusReason);
                    return;
                }
            }
        }
    } else if (_terminalDisplayHistory.count() >= 1) {
        // Give focus to the last used terminal tab
        switchToTerminalDisplay(_terminalDisplayHistory[0]);
    }
Robert Knight's avatar
   
Robert Knight committed
522
523
}

524
void ViewManager::activateView(TerminalDisplay *view)
525
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
526
    Q_ASSERT(view != nullptr);
527
528
529
530
531

    // focus the activated view, this will cause the SessionController
    // to notify the world that the view has been focused and the appropriate UI
    // actions will be plugged in.
    view->setFocus(Qt::OtherFocusReason);
532
533
}

534
void ViewManager::splitLeftRight()
535
{
536
    splitView(Qt::Horizontal);
537
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
538

539
void ViewManager::splitTopBottom()
540
{
541
    splitView(Qt::Vertical);
542
543
}

544
void ViewManager::splitView(Qt::Orientation orientation)
545
{
546
547
548
549
550
551
552
553
    int currentSessionId = currentSession();
    // At least one display/session exists if we are splitting
    Q_ASSERT(currentSessionId >= 0);

    Session *activeSession = SessionManager::instance()->idToSession(currentSessionId);
    Q_ASSERT(activeSession);

    auto profile = SessionManager::instance()->sessionProfile(activeSession);
554

555
556
557
    const QString directory = profile->startInCurrentSessionDir()
                              ? activeSession->currentWorkingDirectory()
                              : QString();
558
    auto *session = createSession(profile, directory);
559

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
560
    auto terminalDisplay = createView(session);
561

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
562
    _viewContainer->splitView(terminalDisplay, orientation);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
563

564
    toggleActionsBasedOnState();
565

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
566
567
    // focus the new container
    terminalDisplay->setFocus();
568
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
569

570
void ViewManager::expandActiveContainer()
571
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
572
    _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(10);
573
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
574

575
void ViewManager::shrinkActiveContainer()
576
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
577
    _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10);
578
579
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
580
SessionController *ViewManager::createController(Session *session, TerminalDisplay *view)
581
{
Robert Knight's avatar
Robert Knight committed
582
583
    // create a new controller for the session, and ensure that this view manager
    // is notified when the view gains the focus
Kurt Hindenburg's avatar
Kurt Hindenburg committed
584
    auto controller = new SessionController(session, view, this);
585
    connect(controller, &Konsole::SessionController::viewFocused, this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
587
588
589
590
591
592
593
594
            &Konsole::ViewManager::controllerChanged);
    connect(session, &Konsole::Session::destroyed, controller,
            &Konsole::SessionController::deleteLater);
    connect(session, &Konsole::Session::primaryScreenInUse, controller,
            &Konsole::SessionController::setupPrimaryScreenSpecificActions);
    connect(session, &Konsole::Session::selectionChanged, controller,
            &Konsole::SessionController::selectionChanged);
    connect(view, &Konsole::TerminalDisplay::destroyed, controller,
            &Konsole::SessionController::deleteLater);
595

596
    // if this is the first controller created then set it as the active controller
597
    if (_pluggedController.isNull()) {
598
        controllerChanged(controller);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
599
    }
600

601
602
603
    return controller;
}

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
604
605
606
607
// should this be handed by ViewManager::unplugController signal
void ViewManager::removeController(SessionController* controller)
{
    if (_pluggedController == controller) {
608
        _pluggedController.clear();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
609
610
611
612
    }
    controller->deleteLater();
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
613
void ViewManager::controllerChanged(SessionController *controller)
614
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
615
    if (controller == _pluggedController) {
616
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
617
    }
618

619
    _viewContainer->setFocusProxy(controller->view());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
620
    updateTerminalDisplayHistory(controller->view());
621

622
    _pluggedController = controller;
Ahmad Samir's avatar
Ahmad Samir committed
623
    Q_EMIT activeViewChanged(controller);
624
625
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
626
SessionController *ViewManager::activeViewController() const
627
{
628
    return _pluggedController;
629
}
630

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
631
632
633
634
void ViewManager::attachView(TerminalDisplay *terminal, Session *session)
{
    connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished,
            Qt::UniqueConnection);
635
636

    // Disconnect from the other viewcontainer.
637
    unregisterTerminal(terminal);
638
639

    // reconnect on this container.
640
    registerTerminal(terminal);
641

642
    _sessionMap[terminal] = session;
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
643
    createController(session, terminal);
644
    toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
645
646
647
648
    _terminalDisplayHistory.append(terminal);
}

TerminalDisplay *ViewManager::createView(Session *session)
649
{
650
    // notify this view manager when the session finishes so that its view
651
    // can be deleted
652
    //
653
    // Use Qt::UniqueConnection to avoid duplicate connection
Kurt Hindenburg's avatar
Kurt Hindenburg committed
654
655
    connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished,
            Qt::UniqueConnection);
656
    TerminalDisplay *display = createTerminalDisplay(session);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
657

Kurt Hindenburg's avatar
Kurt Hindenburg committed
658
659
    const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
    applyProfileToView(display, profile);
660

Kurt Hindenburg's avatar
Kurt Hindenburg committed
661
    // set initial size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
662
    const QSize &preferredSize = session->preferredSize();
663

664
    display->setSize(preferredSize.width(), preferredSize.height());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
665
    createController(session, display);
666

667
    _sessionMap[display] = session;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
668
    session->addView(display);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
669
    _terminalDisplayHistory.append(display);
670

Kurt Hindenburg's avatar
Kurt Hindenburg committed
671
    // tell the session whether it has a light or dark background
672
    session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground());
673
    display->setFocus(Qt::OtherFocusReason);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
674
//     updateDetachViewState();
675

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
676
    return display;
677
678
}

679
TabbedViewContainer *ViewManager::createContainer()
680
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
681
    auto *container = new TabbedViewContainer(this, nullptr);
682
    container->setNavigationVisibility(_navigationVisibility);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
683
    connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab);
684

685
    // connect signals and slots
686
    connect(container, &Konsole::TabbedViewContainer::viewAdded, this,
687
688
689
           [this, container]() {
               containerViewsChanged(container);
           });
690
691
692
693
    connect(container, &Konsole::TabbedViewContainer::viewRemoved, this,
           [this, container]() {
               containerViewsChanged(container);
           });
694

695
696
697
698
    connect(container, &TabbedViewContainer::newViewRequest,
            this, &ViewManager::newViewRequest);
    connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest,
            this, &Konsole::ViewManager::newViewWithProfileRequest);
699
    connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this,
700
            &Konsole::ViewManager::activateView);
701

702
    return container;
703
}
704

705
706
void ViewManager::setNavigationMethod(NavigationMethod method)
{
707
    Q_ASSERT(_actionCollection);
708
    if (_actionCollection == nullptr) {
709
710
        return;
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
711
    KActionCollection *collection = _actionCollection;
712

713
714
    _navigationMethod = method;

715
716
717
718
719
720
721
722
    // FIXME: The following disables certain actions for the KPart that it
    // doesn't actually have a use for, to avoid polluting the action/shortcut
    // namespace of an application using the KPart (otherwise, a shortcut may
    // be in use twice, and the user gets to see an "ambiguous shortcut over-
    // load" error dialog). However, this approach sucks - it's the inverse of
    // what it should be. Rather than disabling actions not used by the KPart,
    // a method should be devised to only enable those that are used, perhaps
    // by using a separate action collection.
723

724
    const bool enable = (method != NoNavigation);
725

726
727
728
729
730
731
732
733
734
735
    auto enableAction = [&enable, &collection](const QString& actionName) {
        auto *action = collection->action(actionName);
        if (action != nullptr) {
            action->setEnabled(enable);
        }
    };

    enableAction(QStringLiteral("next-view"));
    enableAction(QStringLiteral("previous-view"));
    enableAction(QStringLiteral("last-tab"));
736
737
    enableAction(QStringLiteral("last-used-tab"));
    enableAction(QStringLiteral("last-used-tab-reverse"));
738
739
740
741
742
    enableAction(QStringLiteral("split-view-left-right"));
    enableAction(QStringLiteral("split-view-top-bottom"));
    enableAction(QStringLiteral("rename-session"));
    enableAction(QStringLiteral("move-view-left"));
    enableAction(QStringLiteral("move-view-right"));
743
744
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
745
746
747
748
ViewManager::NavigationMethod ViewManager::navigationMethod() const
{
    return _navigationMethod;
}
749

750
void ViewManager::containerViewsChanged(TabbedViewContainer *container)
751
{
752
    Q_UNUSED(container)
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
753
    // TODO: Verify that this is right.
Ahmad Samir's avatar
Ahmad Samir committed
754
    Q_EMIT viewPropertiesChanged(viewProperties());
755
756
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
757
void ViewManager::viewDestroyed(QWidget *view)
758
{
759
760
    // Note: the received QWidget has already been destroyed, so
    // using dynamic_cast<> or qobject_cast<> does not work here
761
    // We only need the pointer address to look it up below
Kurt Hindenburg's avatar
Kurt Hindenburg committed
762
    auto *display = reinterpret_cast<TerminalDisplay *>(view);
763

764
765
    // 1. detach view from session
    // 2. if the session has no views left, close it
766
767
768
769
770
    Session *session = _sessionMap[ display ];
    _sessionMap.remove(display);
    if (session != nullptr) {
        if (session->views().count() == 0) {
            session->close();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
771
        }
772
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
773

774
    //we only update the focus if the splitter is still alive
775
    toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
776

777
    // The below causes the menus  to be messed up
778
    // Only happens when using the tab bar close button
779
//    if (_pluggedController)
Ahmad Samir's avatar
Ahmad Samir committed
780
//        Q_EMIT unplugController(_pluggedController);
781
782
}

783
TerminalDisplay *ViewManager::createTerminalDisplay(Session *session)
784
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
785
    auto display = new TerminalDisplay(nullptr);
786
    display->setRandomSeed(session->sessionId() | (qApp->applicationPid() << 10));
787
    registerTerminal(display);
788

789
    return display;
790
791
}

792
const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr &profile)
793
794
795
796
797
798
799
800
801
802
803
{
    const ColorScheme *colorScheme = ColorSchemeManager::instance()->
                                     findColorScheme(profile->colorScheme());
    if (colorScheme == nullptr) {
        colorScheme = ColorSchemeManager::instance()->defaultColorScheme();
    }
    Q_ASSERT(colorScheme);

    return colorScheme;
}

804
bool ViewManager::profileHasBlurEnabled(const Profile::Ptr &profile)
805
806
807
{
    return colorSchemeForProfile(profile)->blur();
}
808

809
void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr &profile)
810
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
811
    Q_ASSERT(profile);
812
    view->applyProfile(profile);
Ahmad Samir's avatar
Ahmad Samir committed
813
814
    Q_EMIT updateWindowIcon();
    Q_EMIT blurSettingChanged(view->colorScheme()->blur());
815
816
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
817
void ViewManager::updateViewsForSession(Session *session)
818
{
819
820
    const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);

821
822
    const QList<TerminalDisplay *> sessionMapKeys = _sessionMap.keys(session);
    for (TerminalDisplay *view : sessionMapKeys) {
823
        applyProfileToView(view, profile);
824
825
826
    }
}

827
void ViewManager::profileChanged(const Profile::Ptr &profile)
828
{
829
    // update all views associated with this profile
830
    QHashIterator<TerminalDisplay *, Session *> iter(_sessionMap);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
831
    while (iter.hasNext()) {
832
        iter.next();
833
834

        // if session uses this profile, update the display
835
836
837
838
        if (iter.key() != nullptr
            && iter.value() != nullptr
            && SessionManager::instance()->sessionProfile(iter.value()) == profile) {
            applyProfileToView(iter.key(), profile);
839
840
        }
    }
841
842
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
843
QList<ViewProperties *> ViewManager::viewProperties() const
844
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
845
    QList<ViewProperties *> list;
846

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
847
    TabbedViewContainer *container = _viewContainer;
848
849
850
    if (container == nullptr) {
        return {};
    }
851

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
852
853
    auto terminalContainers = _viewContainer->findChildren<TerminalDisplay*>();
    list.reserve(terminalContainers.size());
854

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
855
856
    for(auto terminalDisplay : _viewContainer->findChildren<TerminalDisplay*>()) {
        list.append(terminalDisplay->sessionController());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
857
    }
858

859
    return <