ViewManager.cpp 39.3 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/

20 21 22
// Own
#include "ViewManager.h"

23
#include "config-konsole.h"
24

25
// Qt
26
#include <QStringList>
patrick pereira's avatar
patrick pereira committed
27
#include <QTabBar>
28

29
// KDE
30
#include <KLocalizedString>
31
#include <KActionCollection>
Laurent Montel's avatar
Laurent Montel committed
32
#include <KConfigGroup>
33 34

// Konsole
35
#include <windowadaptor.h>
36

37
#include "ColorScheme.h"
38
#include "ColorSchemeManager.h"
39
#include "Session.h"
40
#include "TerminalDisplay.h"
41 42
#include "SessionController.h"
#include "SessionManager.h"
43
#include "ProfileManager.h"
44
#include "ViewSplitter.h"
45
#include "ViewContainer.h"
46

47 48
using namespace Konsole;

49 50
int ViewManager::lastManagerId = 0;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
51 52
ViewManager::ViewManager(QObject *parent, KActionCollection *collection) :
    QObject(parent),
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
53
    _viewContainer(nullptr),
54 55
    _pluggedController(nullptr),
    _sessionMap(QHash<TerminalDisplay *, Session *>()),
Kurt Hindenburg's avatar
Kurt Hindenburg committed
56
    _actionCollection(collection),
57
    _navigationMethod(NoNavigation),
58
    _navigationVisibility(NavigationNotSet),
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
59 60
    _managerId(0),
    _terminalDisplayHistoryIndex(-1)
61
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
62
    _viewContainer = createContainer();
63
    // setup actions which are related to the views
64 65
    setupActions();

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
66
    /* TODO: Reconnect
67
    // emit a signal when all of the views held by this view manager are destroyed
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
68 69
    */
    connect(_viewContainer.data(), &Konsole::TabbedViewContainer::empty,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
70
            this, &Konsole::ViewManager::empty);
71

72
    // listen for profile changes
Kurt Hindenburg's avatar
Kurt Hindenburg committed
73 74 75 76
    connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged,
            this, &Konsole::ViewManager::profileChanged);
    connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated,
            this, &Konsole::ViewManager::updateViewsForSession);
77 78

    //prepare DBus communication
79
    new WindowAdaptor(this);
80

81
    _managerId = ++lastManagerId;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
82 83
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/")
                                                 + QString::number(_managerId), this);
84 85
}

86
ViewManager::~ViewManager() = default;
87 88 89 90 91 92

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
93
QWidget *ViewManager::activeView() const
94
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
95
    return _viewContainer->currentWidget();
96
}
97

Kurt Hindenburg's avatar
Kurt Hindenburg committed
98
QWidget *ViewManager::widget() const
99
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
100
    return _viewContainer;
101 102
}

103 104
void ViewManager::setupActions()
{
105
    Q_ASSERT(_actionCollection);
106
    if (_actionCollection == nullptr) {
107 108 109
        return;
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
110
    KActionCollection *collection = _actionCollection;
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
111
    // Let's reuse the pointer, no need not to.
David Hallas's avatar
David Hallas committed
112
    auto *action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
113 114 115 116 117 118
    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);
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenLeft);

David Hallas's avatar
David Hallas committed
119
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
120 121 122 123 124 125
    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);
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_ParenRight);
    collection->addAction(QStringLiteral("split-view-top-bottom"), action);

David Hallas's avatar
David Hallas committed
126
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
127 128 129 130 131 132 133
    action->setText(i18nc("@action:inmenu", "Expand View"));
    action->setEnabled(false);
    connect(action, &QAction::triggered, this, &ViewManager::expandActiveContainer);
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight);
    collection->addAction(QStringLiteral("expand-active-view"), action);
    _multiSplitterOnlyActions << action;

David Hallas's avatar
David Hallas committed
134
    action = new QAction(this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
135 136 137 138 139 140
    action->setText(i18nc("@action:inmenu", "Shrink View"));
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft);
    action->setEnabled(false);
    collection->addAction(QStringLiteral("shrink-active-view"), action);
    connect(action, &QAction::triggered, this, &ViewManager::shrinkActiveContainer);
    _multiSplitterOnlyActions << action;
141

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
142 143 144 145 146 147 148 149
    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;

150 151
    // 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
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
152 153 154 155 156 157 158 159
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H);

    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;
160

161
    // keyboard shortcut only actions
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
162
    action = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this);
163
    const QList<QKeySequence> nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown};
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
164 165 166 167 168
    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
169

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
170
    action = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this);
171
    const QList<QKeySequence> previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp};
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
172 173 174 175 176 177
    collection->setDefaultShortcuts(action, previousViewActionKeys);
    collection->addAction(QStringLiteral("previous-tab"), action);
    connect(action, &QAction::triggered, this, &ViewManager::previousView);
    _multiTabOnlyActions << action;
    // _viewSplitter->addAction(previousViewAction);

178
    action = new QAction(i18nc("@action Shortcut entry", "Focus Above Terminal"), this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
179
    connect(action, &QAction::triggered, this, &ViewManager::focusUp);
180
    collection->addAction(QStringLiteral("focus-view-above"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
181 182 183 184
    collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Up);
    _viewContainer->addAction(action);
    _multiSplitterOnlyActions << action;

185
    action = new QAction(i18nc("@action Shortcut entry", "Focus Below Terminal"), this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
186
    collection->setDefaultShortcut(action, Qt::SHIFT + Qt::CTRL + Qt::Key_Down);
187
    collection->addAction(QStringLiteral("focus-view-below"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
188 189 190 191
    connect(action, &QAction::triggered, this, &ViewManager::focusDown);
    _multiSplitterOnlyActions << action;
    _viewContainer->addAction(action);

192
    action = new QAction(i18nc("@action Shortcut entry", "Focus Left Terminal"), this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
193 194
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::LEFT);
    connect(action, &QAction::triggered, this, &ViewManager::focusLeft);
195
    collection->addAction(QStringLiteral("focus-view-left"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
196 197
    _multiSplitterOnlyActions << action;

198
    action = new QAction(i18nc("@action Shortcut entry", "Focus Right Terminal"), this);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
199 200
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Konsole::RIGHT);
    connect(action, &QAction::triggered, this, &ViewManager::focusRight);
201
    collection->addAction(QStringLiteral("focus-view-right"), action);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    _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);
    collection->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Tab);
    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);
    collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_Tab);
    connect(action, &QAction::triggered, this, &ViewManager::lastUsedViewReverse);

224 225 226 227
    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);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
228
    collection->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_E);
229
    connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
230 231 232
    _multiSplitterOnlyActions << action;
    _viewContainer->addAction(action);

233 234 235 236 237 238 239 240 241 242 243 244
    action = new QAction(i18nc("@action Shortcut entry", "Move tab to the right"), this);
    collection->addAction(QStringLiteral("move-tab-to-right"), action);
    collection->setDefaultShortcut(action, Qt::CTRL + Qt::ALT + Qt::Key_Right);
    connect(action, &QAction::triggered, _viewContainer, &TabbedViewContainer::moveTabRight);
    _viewContainer->addAction(action);

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

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
245 246
    // _viewSplitter->addAction(lastUsedViewReverseAction);
    const int SWITCH_TO_TAB_COUNT = 19;
247
    for (int i = 0; i < SWITCH_TO_TAB_COUNT; ++i) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
248 249 250
        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);
251 252 253 254 255

        // only add default shortcut bindings for the first 9 tabs, regardless of SWITCH_TO_TAB_COUNT
        if (i < 9) {
            collection->setDefaultShortcut(action, QStringLiteral("Alt+%1").arg(i + 1));
        }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
256
    }
257

258 259 260
    connect(_viewContainer, &TabbedViewContainer::viewAdded, this, &ViewManager::toggleActionsBasedOnState);
    connect(_viewContainer, &TabbedViewContainer::viewRemoved, this, &ViewManager::toggleActionsBasedOnState);
    connect(_viewContainer, &QTabWidget::currentChanged, this, &ViewManager::toggleActionsBasedOnState);
261

262 263 264 265 266
    toggleActionsBasedOnState();
}

void ViewManager::toggleActionsBasedOnState() {
    const int count = _viewContainer->count();
267
    for (QAction *tabOnlyAction : qAsConst(_multiTabOnlyActions)) {
268 269 270
        tabOnlyAction->setEnabled(count > 1);
    }

271
    if ((_viewContainer != nullptr) && (_viewContainer->activeViewSplitter() != nullptr)) {
272 273 274 275 276 277
        const int splitCount = _viewContainer
                ->activeViewSplitter()
                ->getToplevelSplitter()
                ->findChildren<TerminalDisplay*>()
                    .count();

278
        for (QAction *action : qAsConst(_multiSplitterOnlyActions)) {
279 280 281
            action->setEnabled(splitCount > 1);
        }
    }
Robert Knight's avatar
 
Robert Knight committed
282
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
283

284 285
void ViewManager::switchToView(int index)
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
    _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));
    }
301
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
302

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

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

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

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

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

328 329
void ViewManager::moveActiveViewRight()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
330
    _viewContainer->moveActiveView(TabbedViewContainer::MoveViewRight);
331
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
332

Robert Knight's avatar
 
Robert Knight committed
333 334
void ViewManager::nextContainer()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
335
//    _viewSplitter->activateNextContainer();
Robert Knight's avatar
 
Robert Knight committed
336 337 338 339
}

void ViewManager::nextView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
340
    _viewContainer->activateNextView();
Robert Knight's avatar
 
Robert Knight committed
341 342 343 344
}

void ViewManager::previousView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
345
    _viewContainer->activatePreviousView();
346
}
347

348 349
void ViewManager::lastView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
    _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]);
376 377
}

378 379
void ViewManager::lastUsedView()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
380
    activateLastUsedView(false);
381 382 383 384
}

void ViewManager::lastUsedViewReverse()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
385
    activateLastUsedView(true);
386 387
}

388 389
void ViewManager::toggleTwoViews()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
390 391 392 393 394
    if (_terminalDisplayHistory.count() <= 1) {
        return;
    }

    switchToTerminalDisplay(_terminalDisplayHistory.at(1));
395 396
}

397 398
void ViewManager::detachActiveView()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
399
    // find the currently active view and remove it from its container
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
400 401 402 403 404 405 406 407
    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);
        emit terminalsDetached(newSplitter, detachedSessions);
        focusAnotherTerminal(activeSplitter->getToplevelSplitter());
408
        toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
409
    }
410 411
}

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
412 413 414 415 416 417 418
void ViewManager::detachActiveTab()
{
    const int currentIdx = _viewContainer->currentIndex();
    detachTab(currentIdx);
}

void ViewManager::detachTab(int tabIdx)
419
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
420 421 422 423
    ViewSplitter* splitter = _viewContainer->viewSplitterAt(tabIdx);
    QHash<TerminalDisplay*, Session*> detachedSessions = forgetAll(_viewContainer->viewSplitterAt(tabIdx));
    emit terminalsDetached(splitter, detachedSessions);
}
424

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

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
436 437
Session* ViewManager::forgetTerminal(TerminalDisplay* terminal)
{
438 439
    disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr);

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

450 451 452 453 454 455 456 457 458 459 460
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;
}

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
469
    auto *session = qobject_cast<Session *>(sender());
470 471
    Q_ASSERT(session);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
472 473
    auto view = _sessionMap.key(session);
    _sessionMap.remove(view);
474

475 476 477 478
    if (SessionManager::instance()->isClosingAllSessions()){
        return;
    }

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
479
    // Before deleting the view, let's unmaximize if it's maximized.
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
480
    auto *splitter = qobject_cast<ViewSplitter*>(view->parentWidget());
481
    if (splitter == nullptr) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
482 483 484 485
        return;
    }

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

487
    toplevelSplitter->handleMinimizeMaximize(false);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
488
    view->deleteLater();
489

490

491 492 493
    // 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
494
    if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) {
495 496
        // This is needed to remove this controller from factory() in
        // order to prevent BUG: 185466 - disappearing menu popup
497
        emit unplugController(_pluggedController);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
498
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
499

500
    if (!_sessionMap.empty()) {
501 502
        updateTerminalDisplayHistory(view, true);
        focusAnotherTerminal(toplevelSplitter);
503
        toggleActionsBasedOnState();
504
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
505 506 507 508 509
}

void ViewManager::focusAnotherTerminal(ViewSplitter *toplevelSplitter)
{
    auto tabTterminalDisplays = toplevelSplitter->findChildren<TerminalDisplay*>();
510 511 512 513
    if (tabTterminalDisplays.count() == 0) {
        return;
    }

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
514 515 516 517 518 519 520 521 522 523 524 525 526 527
    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
528 529
}

530
void ViewManager::activateView(TerminalDisplay *view)
531
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
532
    Q_ASSERT(view != nullptr);
533 534 535 536 537

    // 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);
538 539
}

540
void ViewManager::splitLeftRight()
541
{
542
    splitView(Qt::Horizontal);
543
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
544

545
void ViewManager::splitTopBottom()
546
{
547
    splitView(Qt::Vertical);
548 549
}

550
void ViewManager::splitView(Qt::Orientation orientation)
551
{
552 553 554 555 556 557 558 559
    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);
560

561 562 563
    const QString directory = profile->startInCurrentSessionDir()
                              ? activeSession->currentWorkingDirectory()
                              : QString();
564
    auto *session = createSession(profile, directory);
565

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
566
    auto terminalDisplay = createView(session);
567

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

570
    toggleActionsBasedOnState();
571

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
572 573
    // focus the new container
    terminalDisplay->setFocus();
574
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
575

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

581
void ViewManager::shrinkActiveContainer()
582
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
583
    _viewContainer->activeViewSplitter()->adjustActiveTerminalDisplaySize(-10);
584 585
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
SessionController *ViewManager::createController(Session *session, TerminalDisplay *view)
587
{
Robert Knight's avatar
Robert Knight committed
588 589
    // 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
590
    auto controller = new SessionController(session, view, this);
591
    connect(controller, &Konsole::SessionController::viewFocused, this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
592 593 594 595 596 597 598 599 600
            &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);
601

602
    // if this is the first controller created then set it as the active controller
603
    if (_pluggedController.isNull()) {
604
        controllerChanged(controller);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
605
    }
606

607 608 609
    return controller;
}

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
610 611 612 613
// should this be handed by ViewManager::unplugController signal
void ViewManager::removeController(SessionController* controller)
{
    if (_pluggedController == controller) {
614
        _pluggedController.clear();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
615 616 617 618
    }
    controller->deleteLater();
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
619
void ViewManager::controllerChanged(SessionController *controller)
620
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
621
    if (controller == _pluggedController) {
622
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
623
    }
624

625
    _viewContainer->setFocusProxy(controller->view());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
626
    updateTerminalDisplayHistory(controller->view());
627

628 629
    _pluggedController = controller;
    emit activeViewChanged(controller);
630 631
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
632
SessionController *ViewManager::activeViewController() const
633
{
634
    return _pluggedController;
635
}
636

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
637 638 639 640
void ViewManager::attachView(TerminalDisplay *terminal, Session *session)
{
    connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished,
            Qt::UniqueConnection);
641 642 643 644 645 646 647 648

    // Disconnect from the other viewcontainer.
    disconnect(terminal, &TerminalDisplay::requestToggleExpansion, nullptr, nullptr);

    // reconnect on this container.
    connect(terminal, &TerminalDisplay::requestToggleExpansion,
            _viewContainer, &TabbedViewContainer::toggleMaximizeCurrentTerminal, Qt::UniqueConnection);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
649 650
    _sessionMap[terminal] = session;
    createController(session, terminal);
651
    toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
652 653 654 655
    _terminalDisplayHistory.append(terminal);
}

TerminalDisplay *ViewManager::createView(Session *session)
656
{
657
    // notify this view manager when the session finishes so that its view
658
    // can be deleted
659
    //
660
    // Use Qt::UniqueConnection to avoid duplicate connection
Kurt Hindenburg's avatar
Kurt Hindenburg committed
661 662 663
    connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished,
            Qt::UniqueConnection);
    TerminalDisplay *display = createTerminalDisplay(session);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
664

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
668
    // set initial size
Kurt Hindenburg's avatar
Kurt Hindenburg committed
669
    const QSize &preferredSize = session->preferredSize();
670

671
    display->setSize(preferredSize.width(), preferredSize.height());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
672
    createController(session, display);
673

Kurt Hindenburg's avatar
Kurt Hindenburg committed
674 675
    _sessionMap[display] = session;
    session->addView(display);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
676
    _terminalDisplayHistory.append(display);
677

Kurt Hindenburg's avatar
Kurt Hindenburg committed
678
    // tell the session whether it has a light or dark background
679
    session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground());
680
    display->setFocus(Qt::OtherFocusReason);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
681
//     updateDetachViewState();
682

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
683
    return display;
684 685
}

686
TabbedViewContainer *ViewManager::createContainer()
687
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
688
    auto *container = new TabbedViewContainer(this, nullptr);
689
    container->setNavigationVisibility(_navigationVisibility);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
690
    connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachTab);
691

692
    // connect signals and slots
693
    connect(container, &Konsole::TabbedViewContainer::viewAdded, this,
694 695 696 697
           [this, container]() {
               containerViewsChanged(container);
           });

698
    connect(container, &Konsole::TabbedViewContainer::viewRemoved, this,
699 700 701
           [this, container]() {
               containerViewsChanged(container);
           });
Kurt Hindenburg's avatar
Kurt Hindenburg committed
702

703 704 705 706
    connect(container, &TabbedViewContainer::newViewRequest,
            this, &ViewManager::newViewRequest);
    connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest,
            this, &Konsole::ViewManager::newViewWithProfileRequest);
707
    connect(container, &Konsole::TabbedViewContainer::viewRemoved, this,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
708
            &Konsole::ViewManager::viewDestroyed);
709
    connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this,
710
            &Konsole::ViewManager::activateView);
711

712
    return container;
713
}
714

715 716
void ViewManager::setNavigationMethod(NavigationMethod method)
{
717
    Q_ASSERT(_actionCollection);
718
    if (_actionCollection == nullptr) {
719 720
        return;
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
721
    KActionCollection *collection = _actionCollection;
722

723 724
    _navigationMethod = method;

725 726 727 728 729 730 731 732
    // 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.
733

734
    const bool enable = (method != NoNavigation);
735

736 737 738 739 740 741 742 743 744 745
    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"));
746 747
    enableAction(QStringLiteral("last-used-tab"));
    enableAction(QStringLiteral("last-used-tab-reverse"));
748 749 750 751 752
    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"));
753 754
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
755 756 757 758
ViewManager::NavigationMethod ViewManager::navigationMethod() const
{
    return _navigationMethod;
}
759

760
void ViewManager::containerViewsChanged(TabbedViewContainer *container)
761
{
762
    Q_UNUSED(container)
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
763 764
    // TODO: Verify that this is right.
    emit viewPropertiesChanged(viewProperties());
765 766
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
767
void ViewManager::viewDestroyed(QWidget *view)
768
{
769 770
    // Note: the received QWidget has already been destroyed, so
    // using dynamic_cast<> or qobject_cast<> does not work here
771
    // We only need the pointer address to look it up below
Kurt Hindenburg's avatar
Kurt Hindenburg committed
772
    auto *display = reinterpret_cast<TerminalDisplay *>(view);
773

774 775
    // 1. detach view from session
    // 2. if the session has no views left, close it
Kurt Hindenburg's avatar
Kurt Hindenburg committed
776
    Session *session = _sessionMap[ display ];
777
    _sessionMap.remove(display);
778
    if (session != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
779
        if (session->views().count() == 0) {
780
            session->close();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
781
        }
782
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
783

784
    //we only update the focus if the splitter is still alive
785
    toggleActionsBasedOnState();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
786

787
    // The below causes the menus  to be messed up
788
    // Only happens when using the tab bar close button
789 790
//    if (_pluggedController)
//        emit unplugController(_pluggedController);
791 792
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
793
TerminalDisplay *ViewManager::createTerminalDisplay(Session *session)</