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

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

7
// Own
8
#include "widgets/ViewContainer.h"
9
#include "config-konsole.h"
10

11
// Qt
12
13
#include <QTabBar>
#include <QMenu>
14
#include <QFile>
Laurent Montel's avatar
Laurent Montel committed
15
#include <QKeyEvent>
16

Jekyll Wu's avatar
Jekyll Wu committed
17
// KDE
18
19
#include <KColorScheme>
#include <KColorUtils>
20
#include <KLocalizedString>
Kurt Hindenburg's avatar
Kurt Hindenburg committed
21
#include <KActionCollection>
22
23

// Konsole
24
#include "widgets/IncrementalSearchBar.h"
Robert Knight's avatar
   
Robert Knight committed
25
#include "ViewProperties.h"
26
#include "profile/ProfileList.h"
27
#include "KonsoleSettings.h"
28
#include "session/SessionController.h"
29
#include "session/SessionManager.h"
30
#include "DetachableTabBar.h"
31
#include "terminalDisplay/TerminalDisplay.h"
32
#include "widgets/ViewSplitter.h"
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
33
#include "MainWindow.h"
34

35
36
// TODO Perhaps move everything which is Konsole-specific into different files

37
38
using namespace Konsole;

39

40
41
TabbedViewContainer::TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent) :
    QTabWidget(parent),
42
    _connectedViewManager(connectedViewManager),
David Hallas's avatar
David Hallas committed
43
44
    _newTabButton(new QToolButton(this)),
    _closeTabButton(new QToolButton(this)),
45
    _contextMenuTabIndex(-1),
Thomas Surrel's avatar
Thomas Surrel committed
46
47
    _navigationVisibility(ViewManager::NavigationVisibility::NavigationNotSet),
    _newTabBehavior(PutNewTabAtTheEnd)
48
{
49
50
    setAcceptDrops(true);

51
52
    auto tabBarWidget = new DetachableTabBar();
    setTabBar(tabBarWidget);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
53
    setDocumentMode(true);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
54
    setMovable(true);
55
    connect(tabBarWidget, &DetachableTabBar::moveTabToWindow, this, &TabbedViewContainer::moveTabToWindow);
56
    tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
57
    _newTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-new")));
58
    _newTabButton->setAutoRaise(true);
59
    _newTabButton->setToolTip(i18nc("@info:tooltip", "Open a new tab"));
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
60
    connect(_newTabButton, &QToolButton::clicked, this, &TabbedViewContainer::newViewRequest);
61

62
63
    _closeTabButton->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
    _closeTabButton->setAutoRaise(true);
64
    _closeTabButton->setToolTip(i18nc("@info:tooltip", "Close this tab"));
65
66
67
68
    connect(_closeTabButton, &QToolButton::clicked, this, [this]{
       closeCurrentTab();
    });

69
    connect(tabBar(), &QTabBar::tabBarDoubleClicked, this,
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
70
        &Konsole::TabbedViewContainer::tabDoubleClicked);
71
    connect(tabBar(), &QTabBar::customContextMenuRequested, this,
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
72
        &Konsole::TabbedViewContainer::openTabContextMenu);
73
    connect(tabBarWidget, &DetachableTabBar::detachTab, this, [this](int idx) {
Ahmad Samir's avatar
Ahmad Samir committed
74
        Q_EMIT detachTab(idx);
75
    });
76
77
78
    connect(tabBarWidget, &DetachableTabBar::closeTab,
        this, &TabbedViewContainer::closeTerminalTab);
    connect(tabBarWidget, &DetachableTabBar::newTabRequest,
Carlos Alves's avatar
Carlos Alves committed
79
        this, [this]{ Q_EMIT newViewRequest(); });
80
    connect(this, &TabbedViewContainer::currentChanged, this, &TabbedViewContainer::currentTabChanged);
81

82
83
84
    connect(this, &TabbedViewContainer::setColor, tabBarWidget, &DetachableTabBar::setColor);
    connect(this, &TabbedViewContainer::removeColor, tabBarWidget, &DetachableTabBar::removeColor);

85
    // The context menu of tab bar
86
    _contextPopupMenu = new QMenu(tabBar());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
87
88
89
    connect(_contextPopupMenu, &QMenu::aboutToHide, this, [this]() {
        // Remove the read-only action when the popup closes
        for (auto &action : _contextPopupMenu->actions()) {
90
            if (action->objectName() == QStringLiteral("view-readonly")) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
91
92
93
94
95
                _contextPopupMenu->removeAction(action);
                break;
            }
        }
    });
96

97
98
    connect(tabBar(), &QTabBar::tabCloseRequested, this, &TabbedViewContainer::closeTerminalTab);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
99
100
101
    auto detachAction = _contextPopupMenu->addAction(
        QIcon::fromTheme(QStringLiteral("tab-detach")),
        i18nc("@action:inmenu", "&Detach Tab"), this,
Carlos Alves's avatar
Carlos Alves committed
102
        [this] { Q_EMIT detachTab(_contextMenuTabIndex); }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
103
    );
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
104
    detachAction->setObjectName(QStringLiteral("tab-detach"));
105

106
107
    auto editAction = _contextPopupMenu->addAction(
        QIcon::fromTheme(QStringLiteral("edit-rename")),
108
        i18nc("@action:inmenu", "&Configure or Rename Tab..."), this,
109
110
        [this]{ renameTab(_contextMenuTabIndex); }
    );
111
    editAction->setObjectName(QStringLiteral("edit-rename"));
112

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
113
114
115
116
117
118
119
    auto closeAction = _contextPopupMenu->addAction(
        QIcon::fromTheme(QStringLiteral("tab-close")),
        i18nc("@action:inmenu", "Close Tab"), this,
        [this] { closeTerminalTab(_contextMenuTabIndex); }
    );
    closeAction->setObjectName(QStringLiteral("tab-close"));

David Hallas's avatar
David Hallas committed
120
    auto profileMenu = new QMenu(this);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
121
    auto profileList = new ProfileList(false, profileMenu);
122
    profileList->syncWidgetActions(profileMenu, true);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
123
    connect(profileList, &Konsole::ProfileList::profileSelected, this, &TabbedViewContainer::newViewWithProfileRequest);
124
    _newTabButton->setMenu(profileMenu);
125
126
127

    konsoleConfigChanged();
    connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TabbedViewContainer::konsoleConfigChanged);
128
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
129

130
TabbedViewContainer::~TabbedViewContainer()
131
{
132
133
134
135
    for(int i = 0, end = count(); i < end; i++) {
        auto view = widget(i);
        disconnect(view, &QWidget::destroyed, this, &Konsole::TabbedViewContainer::viewDestroyed);
    }
136
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
137

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
138
139
140
141
142
143
ViewSplitter *TabbedViewContainer::activeViewSplitter()
{
    return viewSplitterAt(currentIndex());
}

ViewSplitter *TabbedViewContainer::viewSplitterAt(int index)
144
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
145
    return qobject_cast<ViewSplitter*>(widget(index));
146
147
}

148
149
void TabbedViewContainer::moveTabToWindow(int index, QWidget *window)
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
150
151
152
153
154
    auto splitter = viewSplitterAt(index);
    auto manager = window->findChild<ViewManager*>();

    QHash<TerminalDisplay*, Session*> sessionsMap = _connectedViewManager->forgetAll(splitter);

155
156
    const QList<TerminalDisplay *> displays = splitter->findChildren<TerminalDisplay*>();
    for (TerminalDisplay *terminal : displays) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
157
        manager->attachView(terminal, sessionsMap[terminal]);
158
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
159
160
161
162
163
164
    auto container = manager->activeContainer();
    container->addSplitter(splitter);

    auto controller = splitter->activeTerminalDisplay()->sessionController();
    container->currentSessionControllerChanged(controller);

165
    forgetView();
166
167
}

168
169
void TabbedViewContainer::konsoleConfigChanged()
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
170
171
172
173
    // don't show tabs if we are in KParts mode.
    // This is a hack, and this needs to be rewritten.
    // The container should not be part of the KParts, perhaps just the
    // TerminalDisplay should.
174
175
176
177

    // ASAN issue if using sessionController->isKonsolePart(), just
    // duplicate code for now
    if (qApp->applicationName() != QLatin1String("konsole")) {
178
        tabBar()->setVisible(false);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
179
180
181
182
183
184
185
186
    } else {
        // if we start with --show-tabbar or --hide-tabbar we ignore the preferences.
        setTabBarAutoHide(KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::ShowTabBarWhenNeeded);
        if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysShowTabBar) {
            tabBar()->setVisible(true);
        } else if (KonsoleSettings::tabBarVisibility() == KonsoleSettings::EnumTabBarVisibility::AlwaysHideTabBar) {
            tabBar()->setVisible(false);
        }
187
188
    }

189
    setTabPosition((QTabWidget::TabPosition) KonsoleSettings::tabBarPosition());
190

191
192
193
194
195
196
197
    setCornerWidget(KonsoleSettings::newTabButton() ? _newTabButton : nullptr, Qt::TopLeftCorner);
    _newTabButton->setVisible(KonsoleSettings::newTabButton());

    setCornerWidget(KonsoleSettings::closeTabButton() == 1 ? _closeTabButton : nullptr, Qt::TopRightCorner);
    _closeTabButton->setVisible(KonsoleSettings::closeTabButton() == 1);

    tabBar()->setTabsClosable(KonsoleSettings::closeTabButton() == 0);
198

199
200
    tabBar()->setExpanding(KonsoleSettings::expandTabWidth());
    tabBar()->update();
201

202
203
    if (KonsoleSettings::tabBarUseUserStyleSheet()) {
        setCssFromFile(KonsoleSettings::tabBarUserStyleSheetFile());
204
    } else {
205
        setCss();
206
207
208
    }
}

209
210
211
212
213
214
void TabbedViewContainer::setCss(const QString& styleSheet)
{
    static const QString defaultCss = QStringLiteral("QTabWidget::tab-bar, QTabWidget::pane { margin: 0; }\n");
    setStyleSheet(defaultCss + styleSheet);
}

215
216
217
218
219
220
221
222
223
224
225
226
227
void TabbedViewContainer::setCssFromFile(const QUrl &url)
{
    // Let's only deal w/ local files for now
    if (!url.isLocalFile()) {
        setStyleSheet(KonsoleSettings::tabBarStyleSheet());
    }

    QFile file(url.toLocalFile());
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        setStyleSheet(KonsoleSettings::tabBarStyleSheet());
    }

    QTextStream in(&file);
228
    setCss(in.readAll());
229
230
}

231
void TabbedViewContainer::moveActiveView(MoveDirection direction)
232
{
233
234
235
    if (count() < 2) { // return if only one view
        return;
    }
236
    const int currentIndex = indexOf(currentWidget());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
237
    int newIndex = direction  == MoveViewLeft ? qMax(currentIndex - 1, 0) : qMin(currentIndex + 1, count() - 1);
238

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
239
240
241
242
243
244
245
    auto swappedWidget = viewSplitterAt(newIndex);
    auto swappedTitle = tabBar()->tabText(newIndex);
    auto swappedIcon = tabBar()->tabIcon(newIndex);

    auto currentWidget = viewSplitterAt(currentIndex);
    auto currentTitle = tabBar()->tabText(currentIndex);
    auto currentIcon = tabBar()->tabIcon(currentIndex);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
246
247

    if (newIndex < currentIndex) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
248
249
        insertTab(newIndex, currentWidget, currentIcon, currentTitle);
        insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
250
    } else {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
251
252
        insertTab(currentIndex, swappedWidget, swappedIcon, swappedTitle);
        insertTab(newIndex, currentWidget, currentIcon, currentTitle);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
253
254
    }
    setCurrentIndex(newIndex);
255
}
256

257
void TabbedViewContainer::terminalDisplayDropped(TerminalDisplay *terminalDisplay) {
258
259
260
261
262
263
264
265
    if (terminalDisplay->sessionController()->parent() != connectedViewManager()) {
        // Terminal from another window - recreate SessionController for current ViewManager
        disconnectTerminalDisplay(terminalDisplay);
        Session* terminalSession = terminalDisplay->sessionController()->session();
        terminalDisplay->sessionController()->deleteLater();
        connectedViewManager()->attachView(terminalDisplay, terminalSession);
        connectTerminalDisplay(terminalDisplay);
    }
266
267
}

Mariusz Glebocki's avatar
Mariusz Glebocki committed
268
269
270
271
272
273
274
QSize TabbedViewContainer::sizeHint() const
{
    // QTabWidget::sizeHint() contains some margins added by widgets
    // style, which were making the initial window size too big.
    const auto tabsSize = tabBar()->sizeHint();
    const auto *leftWidget = cornerWidget(Qt::TopLeftCorner);
    const auto *rightWidget = cornerWidget(Qt::TopRightCorner);
275
276
    const auto leftSize = leftWidget != nullptr ? leftWidget->sizeHint() : QSize(0, 0);
    const auto rightSize = rightWidget != nullptr ? rightWidget->sizeHint() : QSize(0, 0);
Mariusz Glebocki's avatar
Mariusz Glebocki committed
277
278
279
280
281
282
283
284

    auto tabBarSize = QSize(0, 0);
    // isVisible() won't work; this is called when the window is not yet visible
    if (tabBar()->isVisibleTo(this)) {
        tabBarSize.setWidth(leftSize.width() + tabsSize.width() + rightSize.width());
        tabBarSize.setHeight(qMax(tabsSize.height(), qMax(leftSize.height(), rightSize.height())));
    }

285
    const auto terminalSize = currentWidget() != nullptr ? currentWidget()->sizeHint() : QSize(0, 0);
Mariusz Glebocki's avatar
Mariusz Glebocki committed
286
287
288
289
290
291
292
293
294
295
296
297
298
299

    //        width
    // ├──────────────────┤
    //
    // ┌──────────────────┐  ┬
    // │                  │  │
    // │     Terminal     │  │
    // │                  │  │ height
    // ├───┬──────────┬───┤  │  ┬
    // │ L │   Tabs   │ R │  │  │ tab bar height
    // └───┴──────────┴───┘  ┴  ┴
    //
    // L/R = left/right widget

300
301
    return {qMax(terminalSize.width(), tabBarSize.width()),
                 tabBarSize.height() + terminalSize.height()};
Mariusz Glebocki's avatar
Mariusz Glebocki committed
302
303
}

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
304
void TabbedViewContainer::addSplitter(ViewSplitter *viewSplitter, int index) {
305
    index = insertTab(index, viewSplitter, QString());
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
306
    connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
307
308
309
310

    disconnect(viewSplitter, &ViewSplitter::terminalDisplayDropped, nullptr, nullptr);
    connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);

311
312
    const auto terminalDisplays = viewSplitter->findChildren<TerminalDisplay*>();
    for (TerminalDisplay *terminal : terminalDisplays) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
313
314
        connectTerminalDisplay(terminal);
    }
315
316
    if (terminalDisplays.count() > 0) {
        updateTitle(qobject_cast<ViewProperties*>(terminalDisplays.at(0)->sessionController()));
317
        updateColor(qobject_cast<ViewProperties*>(terminalDisplays.at(0)->sessionController()));
318
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
319
320
321
    setCurrentIndex(index);
}

Thomas Surrel's avatar
Thomas Surrel committed
322
void TabbedViewContainer::addView(TerminalDisplay *view)
323
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
324
325
    auto viewSplitter = new ViewSplitter();
    viewSplitter->addTerminalDisplay(view, Qt::Horizontal);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
326
    auto item = view->sessionController();
Thomas Surrel's avatar
Thomas Surrel committed
327
    int index = _newTabBehavior == PutNewTabAfterCurrentTab ? currentIndex() + 1 : -1;
328
    index = insertTab(index, viewSplitter, item->icon(), item->title());
329

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
330
331
    connectTerminalDisplay(view);
    connect(viewSplitter, &ViewSplitter::destroyed, this, &TabbedViewContainer::viewDestroyed);
332
333
    connect(viewSplitter, &ViewSplitter::terminalDisplayDropped, this, &TabbedViewContainer::terminalDisplayDropped);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
334
    setCurrentIndex(index);
Ahmad Samir's avatar
Ahmad Samir committed
335
    Q_EMIT viewAdded(view);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
336
337
338
339
340
341
342
343
344
345
346
347
}

void TabbedViewContainer::splitView(TerminalDisplay *view, Qt::Orientation orientation)
{
    auto viewSplitter = qobject_cast<ViewSplitter*>(currentWidget());
    viewSplitter->addTerminalDisplay(view, orientation);
    connectTerminalDisplay(view);
}

void TabbedViewContainer::connectTerminalDisplay(TerminalDisplay *display)
{
    auto item = display->sessionController();
348
    connect(item, &Konsole::SessionController::viewFocused, this,
Mariusz Glebocki's avatar
Mariusz Glebocki committed
349
            &Konsole::TabbedViewContainer::currentSessionControllerChanged);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
350

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
351
352
    connect(item, &Konsole::ViewProperties::titleChanged, this,
            &Konsole::TabbedViewContainer::updateTitle);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
353

354
355
356
    connect(item, &Konsole::ViewProperties::colorChanged, this,
            &Konsole::TabbedViewContainer::updateColor);

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
357
358
    connect(item, &Konsole::ViewProperties::iconChanged, this,
            &Konsole::TabbedViewContainer::updateIcon);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
359

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
360
361
    connect(item, &Konsole::ViewProperties::activity, this,
            &Konsole::TabbedViewContainer::updateActivity);
362
363
364
365
366
367
368
369
370

    connect(item, &Konsole::ViewProperties::notificationChanged, this,
            &Konsole::TabbedViewContainer::updateNotification);

    connect(item, &Konsole::ViewProperties::readOnlyChanged, this,
            &Konsole::TabbedViewContainer::updateSpecialState);

    connect(item, &Konsole::ViewProperties::copyInputChanged, this,
            &Konsole::TabbedViewContainer::updateSpecialState);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
371
372
373
374
375
}

void TabbedViewContainer::disconnectTerminalDisplay(TerminalDisplay *display)
{
    auto item = display->sessionController();
376
    item->disconnect(this);
377
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
378

379
void TabbedViewContainer::viewDestroyed(QObject *view)
380
{
381
382
383
    QWidget *widget = qobject_cast<QWidget*>(view);
    Q_ASSERT(widget);
    const int idx = indexOf(widget);
384
385

    removeTab(idx);
386
    forgetView();
387
    _tabIconState.remove(widget);
388
389

    Q_EMIT viewRemoved();
390
391
}

392
void TabbedViewContainer::forgetView()
393
{
394
    if (count() == 0) {
Ahmad Samir's avatar
Ahmad Samir committed
395
        Q_EMIT empty(this);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
396
    }
397
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
398

399
void TabbedViewContainer::activateNextView()
400
{
401
402
403
    QWidget *active = currentWidget();
    int index = indexOf(active);
    setCurrentIndex(index == count() - 1 ? 0 : index + 1);
404
405
}

406
void TabbedViewContainer::activateLastView()
407
{
408
    setCurrentIndex(count() - 1);
409
410
}

411
void TabbedViewContainer::activatePreviousView()
412
{
413
414
415
    QWidget *active = currentWidget();
    int index = indexOf(active);
    setCurrentIndex(index == 0 ? count() - 1 : index - 1);
416
}
417

418
419
void TabbedViewContainer::keyReleaseEvent(QKeyEvent* event)
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
420
421
    if (event->modifiers() == Qt::NoModifier) {
        _connectedViewManager->updateTerminalDisplayHistory();
422
423
424
    }
}

425
void TabbedViewContainer::closeCurrentTab()
426
{
427
    if (currentIndex() != -1) {
428
        closeTerminalTab(currentIndex());
429
    }
430
431
}

432
433
void TabbedViewContainer::tabDoubleClicked(int index)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
434
    if (index >= 0) {
435
        renameTab(index);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
    } else {
Ahmad Samir's avatar
Ahmad Samir committed
437
        Q_EMIT newViewRequest();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
438
    }
439
440
441
442
}

void TabbedViewContainer::renameTab(int index)
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
443
    if (index != -1) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
444
445
446
447
448
        setCurrentIndex(index);
        viewSplitterAt(index)
            -> activeTerminalDisplay()
            -> sessionController()
            -> rename();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
449
    }
450
451
}

452
void TabbedViewContainer::openTabContextMenu(const QPoint &point)
453
{
454
    if (point.isNull()) {
455
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
456
    }
457

458
459
    _contextMenuTabIndex = tabBar()->tabAt(point);
    if (_contextMenuTabIndex < 0) {
460
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
461
    }
462

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
463
464
465
    //TODO: add a countChanged signal so we can remove this for.
    // Detaching in mac causes crashes.
    for(auto action : _contextPopupMenu->actions()) {
466
        if (action->objectName() == QStringLiteral("tab-detach")) {
467
            action->setEnabled(count() > 1);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
468
469
        }
    }
470

471
    _contextPopupMenu->exec(tabBar()->mapToGlobal(point));
472
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
473

474
void TabbedViewContainer::currentTabChanged(int index)
475
{
476
    if (index != -1) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
477
478
        auto splitview = qobject_cast<ViewSplitter*>(widget(index));
        auto view = splitview->activeTerminalDisplay();
479
        setTabActivity(index, false);
480
        _tabIconState[splitview].notification = Session::NoNotification;
481
        if (view != nullptr) {
Ahmad Samir's avatar
Ahmad Samir committed
482
            Q_EMIT activeViewChanged(view);
483
484
            updateIcon(view->sessionController());
        }
485
486
    } else {
        deleteLater();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
487
    }
488
}
489

490
void TabbedViewContainer::wheelScrolled(int delta)
491
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
492
    if (delta < 0) {
Jekyll Wu's avatar
Jekyll Wu committed
493
        activateNextView();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
494
    } else {
Jekyll Wu's avatar
Jekyll Wu committed
495
        activatePreviousView();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
496
    }
497
498
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
499
void TabbedViewContainer::setTabActivity(int index, bool activity)
500
{
501
    const QPalette &palette = tabBar()->palette();
502
    KColorScheme colorScheme(palette.currentColorGroup());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
503
    const QColor colorSchemeActive = colorScheme.foreground(KColorScheme::ActiveText).color();
504

505
    const QColor normalColor = palette.text().color();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
506
    const QColor activityColor = KColorUtils::mix(normalColor, colorSchemeActive);
507

508
    QColor color = activity ? activityColor : QColor();
509

510
511
    if (color != tabBar()->tabTextColor(index)) {
        tabBar()->setTabTextColor(index, color);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
512
    }
513
514
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
515
void TabbedViewContainer::updateTitle(ViewProperties *item)
516
{
517
    auto controller = qobject_cast<SessionController*>(item);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
518
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();
519
520
521
    if (controller->view() != topLevelSplitter->activeTerminalDisplay()) {
        return;
    }
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
522
    const int index = indexOf(topLevelSplitter);
523
    QString tabText = item->title();
524

525
526
527
528
529
    setTabToolTip(index, tabText);

    // To avoid having & replaced with _ (shortcut indicator)
    tabText.replace(QLatin1Char('&'), QLatin1String("&&"));
    setTabText(index, tabText);
530
}
Kurt Hindenburg's avatar
Kurt Hindenburg committed
531

532
533
534
535
536
537
void TabbedViewContainer::updateColor(ViewProperties *item)
{
    auto controller = qobject_cast<SessionController *>(item);
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();
    const int index = indexOf(topLevelSplitter);

Ahmad Samir's avatar
Ahmad Samir committed
538
    Q_EMIT setColor(index, item->color());
539
540
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
541
void TabbedViewContainer::updateIcon(ViewProperties *item)
542
{
543
544
545
    auto controller = qobject_cast<SessionController *>(item);
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();
    const int index = indexOf(topLevelSplitter);
546
    const auto &state = _tabIconState[topLevelSplitter];
547

548
549
550
551
552
553
554
555
556
557
558
    // Tab icon priority (from highest to lowest):
    //
    // 1. Latest Notification
    //    - Inactive tab: Latest notification from any view in a tab. Removed
    //      when tab is activated.
    //    - Active tab: Latest notification from focused view. Removed when
    //      focus changes or when the Session clears its notifications
    // 2. Copy input or read-only indicator when all views in the tab have
    //    the status
    // 3. Active view icon

559
    QIcon icon = item->icon();
560
561
    if (state.notification != Session::NoNotification) {
        switch(state.notification) {
562
563
564
565
566
567
568
569
        case Session::Bell: {
            auto session = controller->session();
            auto profilePtr = SessionManager::instance()->sessionProfile(session);
            if (profilePtr->property<int>(Profile::BellMode) != Enum::NoBell) {
                icon = QIcon::fromTheme(QLatin1String("notifications"));
            }
        }
        break;
570
571
572
573
        case Session::Activity:
            icon = QIcon::fromTheme(QLatin1String("dialog-information"));
            break;
        case Session::Silence:
574
            icon = QIcon::fromTheme(QLatin1String("system-suspend"));
575
576
577
578
579
            break;
        default:
            break;
        }
    } else if (state.broadcast) {
580
        icon = QIcon::fromTheme(QLatin1String("irc-voice"));
581
582
583
584
585
586
587
    } else if (state.readOnly) {
        icon = QIcon::fromTheme(QLatin1String("object-locked"));
    }

    if (tabIcon(index).name() != icon.name()) {
        setTabIcon(index, icon);
    }
588
589
}

590
591
592
593
594
595
596
597
598
599
600
void TabbedViewContainer::updateActivity(ViewProperties *item)
{
    auto controller = qobject_cast<SessionController*>(item);
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();

    const int index = indexOf(topLevelSplitter);
    if (index != currentIndex()) {
        setTabActivity(index, true);
    }
}

601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
void TabbedViewContainer::updateNotification(ViewProperties *item, Session::Notification notification, bool enabled)
{
    auto controller = qobject_cast<SessionController*>(item);
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();
    const int index = indexOf(topLevelSplitter);
    auto &state = _tabIconState[topLevelSplitter];

    if (enabled && (index != currentIndex() || controller->view()->hasCompositeFocus())) {
        state.notification = notification;
        updateIcon(item);
    } else if (!enabled && controller->view()->hasCompositeFocus()) {
        state.notification = Session::NoNotification;
        updateIcon(item);
    }
}

void TabbedViewContainer::updateSpecialState(ViewProperties *item)
{
    auto controller = qobject_cast<SessionController*>(item);
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();

    auto &state = _tabIconState[topLevelSplitter];
    state.readOnly = true;
    state.broadcast = true;
    const auto displays = topLevelSplitter->findChildren<TerminalDisplay*>();
    for (const auto display : displays) {
        if (!display->sessionController()->isReadOnly()) {
            state.readOnly = false;
        }
        if (!display->sessionController()->isCopyInputActive()) {
            state.broadcast = false;
        }
    }
    updateIcon(item);
}

637
638
void TabbedViewContainer::currentSessionControllerChanged(SessionController *controller)
{
639
640
641
642
643
644
645
646
647
    auto topLevelSplitter = qobject_cast<ViewSplitter*>(controller->view()->parentWidget())->getToplevelSplitter();
    const int index = indexOf(topLevelSplitter);

    if (index == currentIndex()) {
        // Active view changed in current tab - clear notifications
        auto &state = _tabIconState[topLevelSplitter];
        state.notification = Session::NoNotification;
    }

648
    updateTitle(qobject_cast<ViewProperties*>(controller));
649
    updateColor(qobject_cast<ViewProperties*>(controller));
650
    updateActivity(qobject_cast<ViewProperties*>(controller));
651
    updateSpecialState(qobject_cast<ViewProperties*>(controller));
652
653
}

654
void TabbedViewContainer::closeTerminalTab(int idx) {
Ahmad Samir's avatar
Ahmad Samir committed
655
    Q_EMIT removeColor(idx);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
656
657
658
659
    //TODO: This for should probably go to the ViewSplitter
    for (auto terminal : viewSplitterAt(idx)->findChildren<TerminalDisplay*>()) {
        terminal->sessionController()->closeSession();
    }
660
661
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
662
ViewManager *TabbedViewContainer::connectedViewManager()
663
664
665
{
    return _connectedViewManager;
}
666
667
668
669
670
671
672
673
674
675
676
677
678

void TabbedViewContainer::setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility) {
    if (navigationVisibility == ViewManager::NavigationNotSet) {
        return;
    }

    setTabBarAutoHide(navigationVisibility == ViewManager::ShowNavigationAsNeeded);
    if (navigationVisibility == ViewManager::AlwaysShowNavigation) {
        tabBar()->setVisible(true);
    } else if (navigationVisibility == ViewManager::AlwaysHideNavigation) {
        tabBar()->setVisible(false);
    }
}
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
679

680
void TabbedViewContainer::toggleMaximizeCurrentTerminal()
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
681
{
682
683
684
685
    if (auto *terminal = qobject_cast<TerminalDisplay*>(sender())) {
        terminal->setFocus(Qt::FocusReason::OtherFocusReason);
    }

686
    activeViewSplitter()->toggleMaximizeCurrentTerminal();
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
687
}
688

Thomas Surrel's avatar
Thomas Surrel committed
689
void TabbedViewContainer::moveTabLeft()
690
691
692
693
694
695
696
697
698
699
700
701
702
{
    if (currentIndex() == 0) {
        return;
    }
    tabBar()->moveTab(currentIndex(), currentIndex() -1);
}

void TabbedViewContainer::moveTabRight()
{
    if (currentIndex() == count() -1) {
        return;
    }
    tabBar()->moveTab(currentIndex(), currentIndex() + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
703
}
Thomas Surrel's avatar
Thomas Surrel committed
704
705
706
707
708

void TabbedViewContainer::setNavigationBehavior(int behavior)
{
    _newTabBehavior = static_cast<NewTabBehavior>(behavior);
}
709
710
711
712

void TabbedViewContainer::moveToNewTab(TerminalDisplay* display) {
    addView(display);
}