ViewManager.cpp 40.5 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 24
#include <config-konsole.h>

25
// Qt
26 27
#include <QSignalMapper>
#include <QStringList>
28
#include <QAction>
29

30
// KDE
Robert Knight's avatar
Robert Knight committed
31
#include <KAcceleratorManager>
32
#include <KLocalizedString>
33
#include <KActionCollection>
Laurent Montel's avatar
Laurent Montel committed
34
#include <KConfigGroup>
35 36

// Konsole
37
#include <windowadaptor.h>
38

39
#include "ColorScheme.h"
40
#include "ColorSchemeManager.h"
41
#include "Session.h"
42
#include "TerminalDisplay.h"
43 44
#include "SessionController.h"
#include "SessionManager.h"
45
#include "ProfileManager.h"
46
#include "ViewSplitter.h"
47
#include "Enumeration.h"
48

49 50
using namespace Konsole;

51 52
int ViewManager::lastManagerId = 0;

53 54
ViewManager::ViewManager(QObject* parent , KActionCollection* collection)
    : QObject(parent)
55
    , _viewSplitter(0)
56 57
    , _actionCollection(collection)
    , _containerSignalMapper(new QSignalMapper(this))
58 59 60 61 62
    , _navigationMethod(TabbedNavigation)
    , _navigationVisibility(ViewContainer::AlwaysShowNavigation)
    , _navigationPosition(ViewContainer::NavigationPositionTop)
    , _showQuickButtons(false)
    , _newTabBehavior(PutNewTabAtTheEnd)
63
    , _navigationStyleSheet(QString())
64
    , _managerId(0)
65
{
66
    // create main view area
Kurt Hindenburg's avatar
Kurt Hindenburg committed
67
    _viewSplitter = new ViewSplitter(0);
68
    KAcceleratorManager::setNoAccel(_viewSplitter);
Robert Knight's avatar
Robert Knight committed
69

70 71 72 73 74
    // the ViewSplitter class supports both recursive and non-recursive splitting,
    // in non-recursive mode, all containers are inserted into the same top-level splitter
    // widget, and all the divider lines between the containers have the same orientation
    //
    // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting
Kurt Hindenburg's avatar
Kurt Hindenburg committed
75
    // mode
76
    _viewSplitter->setRecursiveSplitting(false);
77
    _viewSplitter->setFocusPolicy(Qt::NoFocus);
78

79
    // setup actions which are related to the views
80 81 82
    setupActions();

    // emit a signal when all of the views held by this view manager are destroyed
83 84
    connect(_viewSplitter.data() , &Konsole::ViewSplitter::allContainersEmpty , this , &Konsole::ViewManager::empty);
    connect(_viewSplitter.data() , &Konsole::ViewSplitter::empty , this , &Konsole::ViewManager::empty);
85 86

    // listen for addition or removal of views from associated containers
Laurent Montel's avatar
Laurent Montel committed
87
    connect(_containerSignalMapper , static_cast<void(QSignalMapper::*)(QObject*)>(&QSignalMapper::mapped) , this , &Konsole::ViewManager::containerViewsChanged);
88 89

    // listen for profile changes
Laurent Montel's avatar
Laurent Montel committed
90 91
    connect(ProfileManager::instance() , &Konsole::ProfileManager::profileChanged , this, &Konsole::ViewManager::profileChanged);
    connect(SessionManager::instance() , &Konsole::SessionManager::sessionUpdated , this, &Konsole::ViewManager::updateViewsForSession);
92 93

    //prepare DBus communication
94
    new WindowAdaptor(this);
95

96 97
    _managerId = ++lastManagerId;
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this);
98 99
}

100
ViewManager::~ViewManager() = default;
101 102 103 104 105 106

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

107 108 109
QWidget* ViewManager::activeView() const
{
    ViewContainer* container = _viewSplitter->activeContainer();
110
    if (container != nullptr) {
111
        return container->activeView();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
112
    } else {
113 114 115
        return 0;
    }
}
116

117 118 119 120 121
QWidget* ViewManager::widget() const
{
    return _viewSplitter;
}

122 123
void ViewManager::setupActions()
{
124
    Q_ASSERT(_actionCollection);
125
    if (_actionCollection == nullptr) {
126 127 128
        return;
    }

129
    KActionCollection* collection = _actionCollection;
130

131 132 133 134
    QAction* nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab") , this);
    QAction* previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab") , this);
    QAction* lastViewAction = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab") , this);
    QAction* nextContainerAction = new QAction(i18nc("@action Shortcut entry", "Next View Container") , this);
135

136 137
    QAction* moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left") , this);
    QAction* moveViewRightAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Right") , this);
138

139 140 141 142 143
    // list of actions that should only be enabled when there are multiple view
    // containers open
    QList<QAction*> multiViewOnlyActions;
    multiViewOnlyActions << nextContainerAction;

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
    QAction* splitLeftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")),
            i18nc("@action:inmenu", "Split View Left/Right"),
            this);
    collection->setDefaultShortcut(splitLeftRightAction, Konsole::ACCEL + Qt::Key_ParenLeft);
    collection->addAction("split-view-left-right", splitLeftRightAction);
    connect(splitLeftRightAction , &QAction::triggered , this , &Konsole::ViewManager::splitLeftRight);

    QAction* splitTopBottomAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")) ,
            i18nc("@action:inmenu", "Split View Top/Bottom"), this);
    collection->setDefaultShortcut(splitTopBottomAction, Konsole::ACCEL + Qt::Key_ParenRight);
    collection->addAction("split-view-top-bottom", splitTopBottomAction);
    connect(splitTopBottomAction , &QAction::triggered , this , &Konsole::ViewManager::splitTopBottom);

    QAction* closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active") , this);
    closeActiveAction->setIcon(QIcon::fromTheme(QStringLiteral("view-close")));
    collection->setDefaultShortcut(closeActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_X);
    closeActiveAction->setEnabled(false);
    collection->addAction("close-active-view", closeActiveAction);
    connect(closeActiveAction , &QAction::triggered , this , &Konsole::ViewManager::closeActiveContainer);

    multiViewOnlyActions << closeActiveAction;

    QAction* closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", "Close Others") , this);
    collection->setDefaultShortcut(closeOtherAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_O);
    closeOtherAction->setEnabled(false);
    collection->addAction("close-other-views", closeOtherAction);
    connect(closeOtherAction , &QAction::triggered , this , &Konsole::ViewManager::closeOtherContainers);

    multiViewOnlyActions << closeOtherAction;

    // Expand & Shrink Active View
    QAction* expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View") , this);
    collection->setDefaultShortcut(expandActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight);
    expandActiveAction->setEnabled(false);
    collection->addAction("expand-active-view", expandActiveAction);
    connect(expandActiveAction , &QAction::triggered , this , &Konsole::ViewManager::expandActiveContainer);

    multiViewOnlyActions << expandActiveAction;

    QAction* shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View") , this);
    collection->setDefaultShortcut(shrinkActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft);
    shrinkActiveAction->setEnabled(false);
    collection->addAction("shrink-active-view", shrinkActiveAction);
    connect(shrinkActiveAction , &QAction::triggered , this , &Konsole::ViewManager::shrinkActiveContainer);

    multiViewOnlyActions << shrinkActiveAction;
190

191
#if defined(ENABLE_DETACHING)
192 193 194 195 196 197 198 199 200
    QAction* detachViewAction = collection->addAction("detach-view");
    detachViewAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach")));
    detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab"));
    // 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
    collection->setDefaultShortcut(detachViewAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H);

    connect(this , &Konsole::ViewManager::splitViewToggle , this , &Konsole::ViewManager::updateDetachViewState);
    connect(detachViewAction , &QAction::triggered , this , &Konsole::ViewManager::detachActiveView);
201
#endif
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
    // Next / Previous View , Next Container
    collection->addAction("next-view", nextViewAction);
    collection->addAction("previous-view", previousViewAction);
    collection->addAction("last-tab", lastViewAction);
    collection->addAction("next-container", nextContainerAction);
    collection->addAction("move-view-left", moveViewLeftAction);
    collection->addAction("move-view-right", moveViewRightAction);

    // Switch to tab N shortcuts
    const int SWITCH_TO_TAB_COUNT = 19;
    auto switchToTabMapper = new QSignalMapper(this);
    connect(switchToTabMapper, static_cast<void(QSignalMapper::*)(int)>(&QSignalMapper::mapped), this, &Konsole::ViewManager::switchToView);
    for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) {
        QAction* switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this);
        switchToTabMapper->setMapping(switchToTabAction, i);
        connect(switchToTabAction, &QAction::triggered, switchToTabMapper, static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map));
        collection->addAction(QString("switch-to-tab-%1").arg(i), switchToTabAction);
220
    }
Robert Knight's avatar
 
Robert Knight committed
221

Kurt Hindenburg's avatar
Kurt Hindenburg committed
222
    foreach(QAction* action, multiViewOnlyActions) {
223
        connect(this , &Konsole::ViewManager::splitViewToggle , action , &QAction::setEnabled);
224 225
    }

226
    // keyboard shortcut only actions
227
    collection->setDefaultShortcut(nextViewAction, Qt::SHIFT + Qt::Key_Right);
228
    connect(nextViewAction, &QAction::triggered , this , &Konsole::ViewManager::nextView);
229
    _viewSplitter->addAction(nextViewAction);
Robert Knight's avatar
 
Robert Knight committed
230

231
    collection->setDefaultShortcut(previousViewAction, Qt::SHIFT + Qt::Key_Left);
232
    connect(previousViewAction, &QAction::triggered , this , &Konsole::ViewManager::previousView);
233
    _viewSplitter->addAction(previousViewAction);
Robert Knight's avatar
 
Robert Knight committed
234

235
    collection->setDefaultShortcut(nextContainerAction, Qt::SHIFT + Qt::Key_Tab);
236
    connect(nextContainerAction , &QAction::triggered , this , &Konsole::ViewManager::nextContainer);
237
    _viewSplitter->addAction(nextContainerAction);
238

239 240 241 242 243
#ifdef Q_OS_OSX
    collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft);
#else
    collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Left);
#endif
244
    connect(moveViewLeftAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewLeft);
245
    _viewSplitter->addAction(moveViewLeftAction);
246

247 248 249 250 251
#ifdef Q_OS_OSX
    collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight);
#else
    collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Right);
#endif
252
    connect(moveViewRightAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewRight);
253
    _viewSplitter->addAction(moveViewRightAction);
254

255
    connect(lastViewAction, &QAction::triggered , this , &Konsole::ViewManager::lastView);
256
    _viewSplitter->addAction(lastViewAction);
Robert Knight's avatar
 
Robert Knight committed
257
}
258 259 260 261
void ViewManager::switchToView(int index)
{
    Q_ASSERT(index >= 0);
    ViewContainer* container = _viewSplitter->activeContainer();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
262
    Q_ASSERT(container);
263 264 265 266 267
    QList<QWidget*> containerViews = container->views();
    if (index >= containerViews.count())
        return;
    container->setActiveView(containerViews.at(index));
}
268 269
void ViewManager::updateDetachViewState()
{
270
    Q_ASSERT(_actionCollection);
271
    if (_actionCollection == nullptr)
272
        return;
273

Jekyll Wu's avatar
Jekyll Wu committed
274
    const bool splitView = _viewSplitter->containers().count() >= 2;
275
    auto activeContainer = _viewSplitter->activeContainer();
276
    const bool shouldEnable = splitView || ((activeContainer != nullptr) && activeContainer->views().count() >= 2);
277

278
    QAction* detachAction = _actionCollection->action("detach-view");
279

280
    if ((detachAction != nullptr) && shouldEnable != detachAction->isEnabled())
281
        detachAction->setEnabled(shouldEnable);
282
}
283 284 285
void ViewManager::moveActiveViewLeft()
{
    ViewContainer* container = _viewSplitter->activeContainer();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
286 287
    Q_ASSERT(container);
    container->moveActiveView(ViewContainer::MoveViewLeft);
288 289 290 291
}
void ViewManager::moveActiveViewRight()
{
    ViewContainer* container = _viewSplitter->activeContainer();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
292 293
    Q_ASSERT(container);
    container->moveActiveView(ViewContainer::MoveViewRight);
294
}
Robert Knight's avatar
 
Robert Knight committed
295 296 297 298 299 300 301 302 303
void ViewManager::nextContainer()
{
    _viewSplitter->activateNextContainer();
}

void ViewManager::nextView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
304
    Q_ASSERT(container);
Robert Knight's avatar
 
Robert Knight committed
305 306 307 308 309 310 311 312

    container->activateNextView();
}

void ViewManager::previousView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
313
    Q_ASSERT(container);
Robert Knight's avatar
 
Robert Knight committed
314 315

    container->activatePreviousView();
316
}
317

318 319 320 321
void ViewManager::lastView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
322
    Q_ASSERT(container);
323 324 325 326

    container->activateLastView();
}

327 328
void ViewManager::detachActiveView()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
329
    // find the currently active view and remove it from its container
330 331
    ViewContainer* container = _viewSplitter->activeContainer();

332 333 334 335 336
    detachView(container, container->activeView());
}

void ViewManager::detachView(ViewContainer* container, QWidget* widgetView)
{
337 338 339 340
#if !defined(ENABLE_DETACHING)
    return;
#endif

341
    TerminalDisplay * viewToDetach = qobject_cast<TerminalDisplay*>(widgetView);
342

343
    if (viewToDetach == nullptr)
344 345
        return;

346
    emit viewDetached(_sessionMap[viewToDetach]);
347

348
    _sessionMap.remove(viewToDetach);
349 350

    // remove the view from this window
351 352
    container->removeView(viewToDetach);
    viewToDetach->deleteLater();
353 354 355 356

    // if the container from which the view was removed is now empty then it can be deleted,
    // unless it is the only container in the window, in which case it is left empty
    // so that there is always an active container
Kurt Hindenburg's avatar
Kurt Hindenburg committed
357 358
    if (_viewSplitter->containers().count() > 1 &&
            container->views().count() == 0) {
359
        removeContainer(container);
360 361 362
    }
}

363
void ViewManager::sessionFinished()
364
{
365 366
    // if this slot is called after the view manager's main widget
    // has been destroyed, do nothing
367
    if (_viewSplitter == nullptr)
368
        return;
369

370 371 372
    Session* session = qobject_cast<Session*>(sender());
    Q_ASSERT(session);

373
    // close attached views
374
    QList<TerminalDisplay*> children = _viewSplitter->findChildren<TerminalDisplay*>();
375

Kurt Hindenburg's avatar
Kurt Hindenburg committed
376
    foreach(TerminalDisplay* view , children) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
377
        if (_sessionMap[view] == session) {
378
            _sessionMap.remove(view);
379
            view->deleteLater();
380
        }
Robert Knight's avatar
 
Robert Knight committed
381
    }
382

Kurt Hindenburg's avatar
Kurt Hindenburg committed
383
    // This is needed to remove this controller from factory() in
384
    // order to prevent BUG: 185466 - disappearing menu popup
385
    if (_pluggedController != nullptr)
386
        emit unplugController(_pluggedController);
Robert Knight's avatar
 
Robert Knight committed
387 388
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
389
void ViewManager::viewActivated(QWidget* view)
390
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
391
    Q_ASSERT(view != 0);
392 393 394 395 396

    // 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);
397 398
}

399
void ViewManager::splitLeftRight()
400
{
401
    splitView(Qt::Horizontal);
402
}
403
void ViewManager::splitTopBottom()
404
{
405
    splitView(Qt::Vertical);
406 407
}

408
void ViewManager::splitView(Qt::Orientation orientation)
409
{
410 411
    ViewContainer* container = createContainer();

412
    // iterate over each session which has a view in the current active
Kurt Hindenburg's avatar
Kurt Hindenburg committed
413
    // container and create a new view for that session in a new container
Kurt Hindenburg's avatar
Kurt Hindenburg committed
414
    foreach(QWidget* view,  _viewSplitter->activeContainer()->views()) {
415
        Session* session = _sessionMap[qobject_cast<TerminalDisplay*>(view)];
416
        TerminalDisplay* display = createTerminalDisplay(session);
Jekyll Wu's avatar
Jekyll Wu committed
417 418
        const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
        applyProfileToView(display, profile);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
419
        ViewProperties* properties = createController(session, display);
420

421
        _sessionMap[display] = session;
422

Kurt Hindenburg's avatar
Kurt Hindenburg committed
423 424
        container->addView(display, properties);
        session->addView(display);
425
    }
426

Kurt Hindenburg's avatar
Kurt Hindenburg committed
427
    _viewSplitter->addContainer(container, orientation);
428
    emit splitViewToggle(_viewSplitter->containers().count() > 0);
429

430 431
    // focus the new container
    container->containerWidget()->setFocus();
432 433

    // ensure that the active view is focused after the split / unsplit
434
    ViewContainer* activeContainer = _viewSplitter->activeContainer();
435
    QWidget* activeView = activeContainer != nullptr ? activeContainer->activeView() : 0;
436

437
    if (activeView != nullptr)
438
        activeView->setFocus(Qt::OtherFocusReason);
439
}
440
void ViewManager::removeContainer(ViewContainer* container)
441
{
442
    // remove session map entries for views in this container
Kurt Hindenburg's avatar
Kurt Hindenburg committed
443
    foreach(QWidget* view , container->views()) {
444 445 446
        TerminalDisplay* display = qobject_cast<TerminalDisplay*>(view);
        Q_ASSERT(display);
        _sessionMap.remove(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
447
    }
448

449
    _viewSplitter->removeContainer(container);
450
    container->deleteLater();
451

Kurt Hindenburg's avatar
Kurt Hindenburg committed
452
    emit splitViewToggle(_viewSplitter->containers().count() > 1);
453
}
454
void ViewManager::expandActiveContainer()
455
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
456
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10);
457
}
458
void ViewManager::shrinkActiveContainer()
459
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
460
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10);
461
}
462
void ViewManager::closeActiveContainer()
463 464
{
    // only do something if there is more than one container active
Kurt Hindenburg's avatar
Kurt Hindenburg committed
465
    if (_viewSplitter->containers().count() > 1) {
466 467 468
        ViewContainer* container = _viewSplitter->activeContainer();

        removeContainer(container);
469

Kurt Hindenburg's avatar
Kurt Hindenburg committed
470
        // focus next container so that user can continue typing
471 472 473
        // without having to manually focus it themselves
        nextContainer();
    }
474
}
475
void ViewManager::closeOtherContainers()
476 477 478
{
    ViewContainer* active = _viewSplitter->activeContainer();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
479
    foreach(ViewContainer* container, _viewSplitter->containers()) {
480 481
        if (container != active)
            removeContainer(container);
482 483 484
    }
}

485
SessionController* ViewManager::createController(Session* session , TerminalDisplay* view)
486
{
Robert Knight's avatar
Robert Knight committed
487 488
    // 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
489
    auto controller = new SessionController(session, view, this);
490 491
    connect(controller , &Konsole::SessionController::focused , this , &Konsole::ViewManager::controllerChanged);
    connect(session , &Konsole::Session::destroyed , controller , &Konsole::SessionController::deleteLater);
Laurent Montel's avatar
Laurent Montel committed
492 493
    connect(session , &Konsole::Session::primaryScreenInUse , controller , &Konsole::SessionController::setupPrimaryScreenSpecificActions);
    connect(session , &Konsole::Session::selectionChanged , controller , &Konsole::SessionController::selectionChanged);
494
    connect(view , &Konsole::TerminalDisplay::destroyed , controller , &Konsole::SessionController::deleteLater);
495

496
    // if this is the first controller created then set it as the active controller
497
    if (_pluggedController == nullptr)
498
        controllerChanged(controller);
499

500 501 502
    return controller;
}

503 504
void ViewManager::controllerChanged(SessionController* controller)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
505
    if (controller == _pluggedController)
506
        return;
507

508
    _viewSplitter->setFocusProxy(controller->view());
509

510 511
    _pluggedController = controller;
    emit activeViewChanged(controller);
512 513 514 515
}

SessionController* ViewManager::activeViewController() const
{
516
    return _pluggedController;
517
}
518 519 520 521 522 523

IncrementalSearchBar* ViewManager::searchBar() const
{
    return _viewSplitter->activeSplitter()->activeContainer()->searchBar();
}

524 525
void ViewManager::createView(Session* session, ViewContainer* container, int index)
{
526
    // notify this view manager when the session finishes so that its view
527
    // can be deleted
528
    //
529
    // Use Qt::UniqueConnection to avoid duplicate connection
530
    connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection);
531

Kurt Hindenburg's avatar
Kurt Hindenburg committed
532 533 534
    TerminalDisplay* display = createTerminalDisplay(session);
    const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
    applyProfileToView(display, profile);
535

Kurt Hindenburg's avatar
Kurt Hindenburg committed
536
    // set initial size
537 538
    const QSize& preferredSize = session->preferredSize();
    // FIXME: +1 is needed here for getting the expected rows
539 540 541 542
    // Note that the display shouldn't need to take into account the tabbar.
    // However, it appears that taking into account the tabbar is needed.
    // If tabbar is not visible, no +1 is needed here; however, depending on
    // settings/tabbar style, +2 might be needed.
543 544 545 546 547 548
    // 1st attempt at fixing the above:
    // Guess if tabbar will NOT be visible; ignore ShowNavigationAsNeeded
    int heightAdjustment = 0;
    if (_navigationVisibility != ViewContainer::AlwaysHideNavigation) {
        heightAdjustment = 2;
    }
549

550
    display->setSize(preferredSize.width(), preferredSize.height() + heightAdjustment);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
551
    ViewProperties* properties = createController(session, display);
552

Kurt Hindenburg's avatar
Kurt Hindenburg committed
553 554 555
    _sessionMap[display] = session;
    container->addView(display, properties, index);
    session->addView(display);
556

Kurt Hindenburg's avatar
Kurt Hindenburg committed
557 558
    // tell the session whether it has a light or dark background
    session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground());
559

Kurt Hindenburg's avatar
Kurt Hindenburg committed
560 561 562 563
    if (container == _viewSplitter->activeContainer()) {
        container->setActiveView(display);
        display->setFocus(Qt::OtherFocusReason);
    }
564

Kurt Hindenburg's avatar
Kurt Hindenburg committed
565
    updateDetachViewState();
566
}
567

568
void ViewManager::createView(Session* session)
569
{
570
    // create the default container
Kurt Hindenburg's avatar
Kurt Hindenburg committed
571
    if (_viewSplitter->containers().count() == 0) {
572 573
        ViewContainer* container = createContainer();
        _viewSplitter->addContainer(container, Qt::Vertical);
574
        emit splitViewToggle(false);
575 576
    }

577
    // new tab will be put at the end by default.
578 579
    int index = -1;

580
    if (_newTabBehavior == PutNewTabAfterCurrentTab) {
581
        QWidget* view = activeView();
582
        if (view != nullptr) {
583 584 585
            QList<QWidget*> views =  _viewSplitter->activeContainer()->views();
            index = views.indexOf(view) + 1;
        }
586 587
    }

Robert Knight's avatar
Robert Knight committed
588 589
    // iterate over the view containers owned by this view manager
    // and create a new terminal display for the session in each of them, along with
Kurt Hindenburg's avatar
Kurt Hindenburg committed
590
    // a controller for the session/display pair
Kurt Hindenburg's avatar
Kurt Hindenburg committed
591
    foreach(ViewContainer* container,  _viewSplitter->containers()) {
592
        createView(session, container, index);
593 594 595
    }
}

596
ViewContainer* ViewManager::createContainer()
597
{
598 599
    ViewContainer* container = 0;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
600 601
    switch (_navigationMethod) {
    case TabbedNavigation: {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
602
        auto tabbedContainer = new TabbedViewContainer(_navigationPosition, this, _viewSplitter);
603
        container = tabbedContainer;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
604

Laurent Montel's avatar
Laurent Montel committed
605 606
        connect(tabbedContainer, &TabbedViewContainer::detachTab, this, &ViewManager::detachView);
        connect(tabbedContainer, &TabbedViewContainer::closeTab, this, &ViewManager::closeTabFromContainer);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
607 608 609 610 611
    }
    break;
    case NoNavigation:
    default:
        container = new StackedViewContainer(_viewSplitter);
612
    }
613

614 615 616 617 618 619
    // FIXME: these code feels duplicated
    container->setNavigationVisibility(_navigationVisibility);
    container->setNavigationPosition(_navigationPosition);
    container->setStyleSheet(_navigationStyleSheet);
    if (_showQuickButtons) {
        container->setFeatures(container->features()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
620 621
                               | ViewContainer::QuickNewView
                               | ViewContainer::QuickCloseView);
622 623
    } else {
        container->setFeatures(container->features()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
624 625
                               & ~ViewContainer::QuickNewView
                               & ~ViewContainer::QuickCloseView);
626
    }
627

628
    // connect signals and slots
Laurent Montel's avatar
Laurent Montel committed
629 630
    connect(container , &Konsole::ViewContainer::viewAdded , _containerSignalMapper , static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map));
    connect(container , &Konsole::ViewContainer::viewRemoved , _containerSignalMapper , static_cast<void(QSignalMapper::*)()>(&QSignalMapper::map));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
631 632
    _containerSignalMapper->setMapping(container, container);

633 634
    connect(container, static_cast<void(ViewContainer::*)()>(&Konsole::ViewContainer::newViewRequest), this, static_cast<void(ViewManager::*)()>(&Konsole::ViewManager::newViewRequest));
    connect(container, static_cast<void(ViewContainer::*)(Profile::Ptr)>(&Konsole::ViewContainer::newViewRequest), this, static_cast<void(ViewManager::*)(Profile::Ptr)>(&Konsole::ViewManager::newViewRequest));
Laurent Montel's avatar
Laurent Montel committed
635
    connect(container, &Konsole::ViewContainer::moveViewRequest, this , &Konsole::ViewManager::containerMoveViewRequest);
636 637
    connect(container , &Konsole::ViewContainer::viewRemoved , this , &Konsole::ViewManager::viewDestroyed);
    connect(container , &Konsole::ViewContainer::activeViewChanged , this , &Konsole::ViewManager::viewActivated);
638

639
    return container;
640
}
641 642

void ViewManager::containerMoveViewRequest(int index, int id, bool& moved, TabbedViewContainer* sourceTabbedContainer)
643
{
644 645
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
646

647
    if (controller == nullptr)
648
        return;
649

Kurt Hindenburg's avatar
Kurt Hindenburg committed
650
    // do not move the last tab in a split view.
651
    if (sourceTabbedContainer != nullptr) {
652 653 654 655 656 657 658 659 660 661 662 663 664 665
        QPointer<ViewContainer> sourceContainer = qobject_cast<ViewContainer*>(sourceTabbedContainer);

        if (_viewSplitter->containers().contains(sourceContainer)) {
            return;
        } else {
            ViewManager* sourceViewManager = sourceTabbedContainer->connectedViewManager();

            // do not remove the last tab on the window
            if (qobject_cast<ViewSplitter*>(sourceViewManager->widget())->containers().size() > 1) {
                return;
            }
        }
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
666
    createView(controller->session(), container, index);
667
    controller->session()->refresh();
668
    moved = true;
669
}
670

671 672
void ViewManager::setNavigationMethod(NavigationMethod method)
{
673
    Q_ASSERT(_actionCollection);
674
    if (_actionCollection == nullptr) {
675 676
        return;
    }
677 678 679 680
    _navigationMethod = method;

    KActionCollection* collection = _actionCollection;

681 682 683 684 685 686 687 688
    // 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.
689

690 691
    const bool enable = (_navigationMethod != NoNavigation);
    QAction* action;
692

693
    action = collection->action("next-view");
694
    if (action != nullptr) action->setEnabled(enable);
695

696
    action = collection->action("previous-view");
697
    if (action != nullptr) action->setEnabled(enable);
698

699
    action = collection->action("last-tab");
700
    if (action != nullptr) action->setEnabled(enable);
701

702
    action = collection->action("split-view-left-right");
703
    if (action != nullptr) action->setEnabled(enable);
704

705
    action = collection->action("split-view-top-bottom");
706
    if (action != nullptr) action->setEnabled(enable);
707

708
    action = collection->action("rename-session");
709
    if (action != nullptr) action->setEnabled(enable);
710

711
    action = collection->action("move-view-left");
712
    if (action != nullptr) action->setEnabled(enable);
713

714
    action = collection->action("move-view-right");
715
    if (action != nullptr) action->setEnabled(enable);
716 717
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
718 719 720 721
ViewManager::NavigationMethod ViewManager::navigationMethod() const
{
    return _navigationMethod;
}
722

723 724
void ViewManager::containerViewsChanged(QObject* container)
{
725
    if ((_viewSplitter != nullptr) && container == _viewSplitter->activeContainer()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
726 727
        emit viewPropertiesChanged(viewProperties());
    }
728 729
}

730
void ViewManager::viewDestroyed(QWidget* view)
731
{
732 733
    // Note: the received QWidget has already been destroyed, so
    // using dynamic_cast<> or qobject_cast<> does not work here
734 735
    // We only need the pointer address to look it up below
    TerminalDisplay* display = reinterpret_cast<TerminalDisplay*>(view);
736

737 738
    // 1. detach view from session
    // 2. if the session has no views left, close it
739
    Session* session = _sessionMap[ display ];
740
    _sessionMap.remove(display);
741
    if (session != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
742
        if (session->views().count() == 0)
743
            session->close();
744
    }
745
    //we only update the focus if the splitter is still alive
746
    if (_viewSplitter != nullptr) {
747 748
        updateDetachViewState();
    }
749
    // The below causes the menus  to be messed up
750
    // Only happens when using the tab bar close button
751 752
//    if (_pluggedController)
//        emit unplugController(_pluggedController);
753 754
}

755
TerminalDisplay* ViewManager::createTerminalDisplay(Session* session)
756
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
757
    auto display = new TerminalDisplay(0);
758
    display->setRandomSeed(session->sessionId() * 31);
759

760
    return display;
761 762
}

763
const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr profile)
764
{
765
    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
Kurt Hindenburg's avatar
Kurt Hindenburg committed
766
                                     findColorScheme(profile->colorScheme());
767
    if (colorScheme == nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
768 769
        colorScheme = ColorSchemeManager::instance()->defaultColorScheme();
    Q_ASSERT(colorScheme);
770

771 772 773
    return colorScheme;
}

Jekyll Wu's avatar
Jekyll Wu committed
774
void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile)
775
{