ViewManager.cpp 40.2 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
Dirk Mueller's avatar
Dirk Mueller committed
26
#include <QtCore/QSignalMapper>
Jekyll Wu's avatar
Jekyll Wu committed
27
#include <QtCore/QStringList>
28
#include <QAction>
Jekyll Wu's avatar
Jekyll Wu committed
29
#include <QtDBus/QtDBus>
30

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

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

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

50
51
using namespace Konsole;

52
53
int ViewManager::lastManagerId = 0;

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

71
72
73
74
75
    // 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
76
    // mode
77
    _viewSplitter->setRecursiveSplitting(false);
78
    _viewSplitter->setFocusPolicy(Qt::NoFocus);
79

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

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

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

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

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

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

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

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

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

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

123
124
void ViewManager::setupActions()
{
125
126
127
128
129
    Q_ASSERT(_actionCollection);
    if (!_actionCollection) {
        return;
    }

130
    KActionCollection* collection = _actionCollection;
131

132
133
134
135
    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);
136

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

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

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
190
    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;
191

192
#if defined(ENABLE_DETACHING)
193
194
195
196
197
198
199
200
201
    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);
202
#endif
203

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
    // 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);
221
    }
Robert Knight's avatar
   
Robert Knight committed
222

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

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

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

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

240
241
242
243
244
#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
245
    connect(moveViewLeftAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewLeft);
246
    _viewSplitter->addAction(moveViewLeftAction);
247

248
249
250
251
252
#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
253
    connect(moveViewRightAction , &QAction::triggered , this , &Konsole::ViewManager::moveActiveViewRight);
254
    _viewSplitter->addAction(moveViewRightAction);
255

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

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

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

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

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

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

    container->activateNextView();
}

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

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

    container->activatePreviousView();
317
}
318

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

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

    container->activateLastView();
}

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

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

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

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

    if (!viewToDetach)
345
346
        return;

347
    emit viewDetached(_sessionMap[viewToDetach]);
348

349
    _sessionMap.remove(viewToDetach);
350
351

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

    // 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
358
359
    if (_viewSplitter->containers().count() > 1 &&
            container->views().count() == 0) {
360
        removeContainer(container);
361
362
363
    }
}

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

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

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

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

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

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

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

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

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

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

422
        _sessionMap[display] = session;
423

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

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

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

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

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

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

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

        removeContainer(container);
470

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

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

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

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

501
502
503
    return controller;
}

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

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

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

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

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

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

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
537
    // set initial size
538
539
    const QSize& preferredSize = session->preferredSize();
    // FIXME: +1 is needed here for getting the expected rows
540
541
542
543
    // 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.
544
545
546
547
548
549
    // 1st attempt at fixing the above:
    // Guess if tabbar will NOT be visible; ignore ShowNavigationAsNeeded
    int heightAdjustment = 0;
    if (_navigationVisibility != ViewContainer::AlwaysHideNavigation) {
        heightAdjustment = 2;
    }
550

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

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

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

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

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

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

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

581
    if (_newTabBehavior == PutNewTabAfterCurrentTab) {
582
        QWidget* view = activeView();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
583
        if (view) {
584
585
586
            QList<QWidget*> views =  _viewSplitter->activeContainer()->views();
            index = views.indexOf(view) + 1;
        }
587
588
    }

Robert Knight's avatar
Robert Knight committed
589
590
    // 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
591
    // a controller for the session/display pair
Kurt Hindenburg's avatar
Kurt Hindenburg committed
592
    foreach(ViewContainer* container,  _viewSplitter->containers()) {
593
        createView(session, container, index);
594
595
596
    }
}

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

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

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

615
616
617
618
619
620
    // 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
621
622
                               | ViewContainer::QuickNewView
                               | ViewContainer::QuickCloseView);
623
624
    } else {
        container->setFeatures(container->features()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
625
626
                               & ~ViewContainer::QuickNewView
                               & ~ViewContainer::QuickCloseView);
627
    }
628

629
    // connect signals and slots
Laurent Montel's avatar
Laurent Montel committed
630
631
    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
632
633
    _containerSignalMapper->setMapping(container, container);

634
635
    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
636
    connect(container, &Konsole::ViewContainer::moveViewRequest, this , &Konsole::ViewManager::containerMoveViewRequest);
637
638
    connect(container , &Konsole::ViewContainer::viewRemoved , this , &Konsole::ViewManager::viewDestroyed);
    connect(container , &Konsole::ViewContainer::activeViewChanged , this , &Konsole::ViewManager::viewActivated);
639

640
    return container;
641
}
642
643

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

648
649
    if (!controller)
        return;
650

Kurt Hindenburg's avatar
Kurt Hindenburg committed
651
    // do not move the last tab in a split view.
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
    if (sourceTabbedContainer) {
        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
667
    createView(controller->session(), container, index);
668
    controller->session()->refresh();
669
    moved = true;
670
}
671

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

    KActionCollection* collection = _actionCollection;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

761
    return display;
762
763
}

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

772
773
774
    return colorScheme;
}

Jekyll Wu's avatar
Jekyll Wu committed
775
void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile)
776
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
777
    Q_ASSERT(profile);
778

779
780
    emit updateWindowIcon();

781
    // load color scheme
782
    ColorEntry table[TABLE_COLORS];
783
    const ColorScheme* colorScheme = colorSchemeForProfile(profile);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
784
    colorScheme->getColorTable(table , view->randomSeed());
785
    view->setColorTable(table);
786
    view->setOpacity(colorScheme->opacity());
787
    view->setWallpaper(colorScheme->wallpaper());
788

Kurt Hindenburg's avatar
Kurt Hindenburg committed
789
    // load font
790
791
    view->setAntialias(profile->antiAliasFonts());
    view->setBoldIntense(profile->boldIntense());
792
    view->setUseFontLineCharacters(profile->useFontLineCharacters());
Jekyll Wu's avatar
Jekyll Wu committed
793
    view->setVTFont(profile->font());
794
795

    // set scroll-bar position
Jekyll Wu's avatar
Jekyll Wu committed
796
    int scrollBarPosition = profile->property<int>(Profile::ScrollBarPosition);
797

798
799
800
801
802
803
    if (scrollBarPosition == Enum::ScrollBarLeft)
        view->setScrollBarPosition(Enum::ScrollBarLeft);
    else if (scrollBarPosition == Enum::ScrollBarRight)
        view->setScrollBarPosition(Enum::ScrollBarRight);
    else if (scrollBarPosition == Enum::ScrollBarHidden)
        view->setScrollBarPosition(Enum::ScrollBarHidden);
804

805
806
    bool scrollFullPage = profile->property<bool>(Profile::ScrollFullPage);
    view->setScrollFullPage(scrollFullPage);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
807

808
    // show hint about terminal size after resizing
809
    view->setShowTerminalSizeHint(profile->showTerminalSizeHint());
810

811
    // terminal features
Jekyll Wu's avatar
Jekyll Wu committed
812
813
    view->setBlinkingCursorEnabled(profile->blinkingCursorEnabled());
    view->setBlinkingTextEnabled(profile->blinkingTextEnabled());
814

Jekyll Wu's avatar
Jekyll Wu committed
815
    int tripleClickMode = profile->property<int>(Profile::TripleClickMode);
816
    view->setTripleClickMode(Enum::TripleClickModeEnum(tripleClickMode));
817

818
    view->setAutoCopySelectedText(profile->autoCopySelectedText());
819
    view->setControlDrag(profile->property<bool>(Profile::CtrlRequiredForDrag));
820
    view->setDropUrlsAsText(profile->property<bool>(Profile::DropUrlsAsText));
Jekyll Wu's avatar
Jekyll Wu committed
821
    view->setBidiEnabled(profile->bidiRenderingEnabled());
822
    view->setLineSpacing(profile->lineSpacing());
823
    view->setTrimTrailingSpaces(profile->property<bool>(Profile::TrimTrailingSpacesInSelectedText));
824

825
826
    view->setOpenLinksByDirectClick(profile->property<bool>(Profile::OpenLinksByDirectClickEnabled));

827
    view->setUrlHintsModifiers(profile->property<int>(Profile::UrlHintsModifiers));
828

829
830
831
832
833
834
    int middleClickPasteMode = profile->property<int>(Profile::MiddleClickPasteMode);
    if (middleClickPasteMode == Enum::PasteFromX11Selection)
        view->setMiddleClickPasteMode(Enum::PasteFromX11Selection);
    else if (middleClickPasteMode == Enum::PasteFromClipboard)
        view->setMiddleClickPasteMode(Enum::PasteFromClipboard);

835
836
837
    // margin/center
    view->setMargin(profile->property<int>(Profile::TerminalMargin));
    view->setCenterContents(profile->property<bool>(Profile</