SessionController.cpp 74.1 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
    Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

    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.
*/

// Own
#include "SessionController.h"
23

24
#include "ProfileManager.h"
25
#include "konsoledebug.h"
26

27
// Qt
28
#include <QApplication>
29
#include <QAction>
30
#include <QMenu>
31
#include <QKeyEvent>
32 33
#include <QPrinter>
#include <QPrintDialog>
34
#include <QFileDialog>
35
#include <QPainter>
36
#include <QStandardPaths>
37 38
#include <QUrl>
#include <QIcon>
39

40
// KDE
41 42
#include <KActionMenu>
#include <KActionCollection>
43
#include <KLocalizedString>
44
#include <KMessageBox>
45
#include <KRun>
46
#include <KShell>
Jekyll Wu's avatar
Jekyll Wu committed
47
#include <KToolInvocation>
Robert Knight's avatar
 
Robert Knight committed
48
#include <KToggleAction>
49
#include <KSelectAction>
50
#include <KXmlGuiWindow>
51
#include <KXMLGUIFactory>
52
#include <KXMLGUIBuilder>
Jekyll Wu's avatar
Jekyll Wu committed
53 54
#include <KUriFilter>
#include <KStringHandler>
Alex Richardson's avatar
Alex Richardson committed
55
#include <KSharedConfig>
56
#include <KConfigGroup>
57 58
#include <KCodecAction>

59
// Konsole
60
#include "EditProfileDialog.h"
61
#include "CopyInputDialog.h"
62
#include "Emulation.h"
63
#include "Filter.h"
64
#include "History.h"
65
#include "HistorySizeDialog.h"
66
#include "IncrementalSearchBar.h"
67
#include "RenameTabDialog.h"
68
#include "ScreenWindow.h"
69
#include "Session.h"
70
#include "ProfileList.h"
71
#include "TerminalDisplay.h"
72
#include "SessionManager.h"
73
#include "Enumeration.h"
74
#include "PrintOptions.h"
75

76
// for SaveHistoryTask
77
#include <KIO/Job>
78 79 80
#include <KJob>
#include "TerminalCharacterDecoder.h"

81 82 83
// For Unix signal names
#include <signal.h>

84
using namespace Konsole;
85

86 87
// TODO - Replace the icon choices below when suitable icons for silence and
// activity are available
88 89 90
Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _activityIcon, (QIcon::fromTheme(QLatin1String("dialog-information"))))
Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _silenceIcon, (QIcon::fromTheme(QLatin1String("dialog-information"))))
Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _broadcastIcon, (QIcon::fromTheme(QLatin1String("emblem-important"))))
91

92
QSet<SessionController*> SessionController::_allControllers;
93
int SessionController::_lastControllerId;
Robert Knight's avatar
 
Robert Knight committed
94

95
SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent)
Robert Knight's avatar
 
Robert Knight committed
96
    : ViewProperties(parent)
97 98 99
    , KXMLGUIClient()
    , _session(session)
    , _view(view)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
100 101
    , _copyToGroup(nullptr)
    , _profileList(nullptr)
Robert Knight's avatar
 
Robert Knight committed
102
    , _previousState(-1)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
103 104 105 106 107 108 109
    , _searchFilter(nullptr)
    , _urlFilter(nullptr)
    , _fileFilter(nullptr)
    , _copyInputToAllTabsAction(nullptr)
    , _findAction(nullptr)
    , _findNextAction(nullptr)
    , _findPreviousAction(nullptr)
Harald Hvaal's avatar
Harald Hvaal committed
110 111
    , _searchStartLine(0)
    , _prevSearchResultLine(0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
112 113 114 115
    , _searchBar(nullptr)
    , _codecAction(nullptr)
    , _switchProfileMenu(nullptr)
    , _webSearchMenu(nullptr)
116 117
    , _listenForScreenWindowUpdates(false)
    , _preventClose(false)
118
    , _keepIconUntilInteraction(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
119
    , _showMenuAction(nullptr)
120
    , _isSearchBarEnabled(false)
121
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
122 123
    Q_ASSERT(session);
    Q_ASSERT(view);
124

125
    // handle user interface related to session (menus etc.)
126
    if (isKonsolePart()) {
127
        setComponentName(QStringLiteral("konsole"), i18n("Konsole"));
128
        setXMLFile(QStringLiteral("partui.rc"));
129 130
        setupCommonActions();
    } else {
131
        setXMLFile(QStringLiteral("sessionui.rc"));
132 133 134
        setupCommonActions();
        setupExtraActions();
    }
135

136
    actionCollection()->addAssociatedWidget(view);
Jekyll Wu's avatar
Jekyll Wu committed
137 138 139
    foreach(QAction * action, actionCollection()->actions()) {
        action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    }
140

141
    setIdentifier(++_lastControllerId);
Robert Knight's avatar
 
Robert Knight committed
142
    sessionTitleChanged();
143 144

    view->installEventFilter(this);
145
    view->setSessionController(this);
Robert Knight's avatar
 
Robert Knight committed
146

147 148
    // install filter on the view to highlight URLs and files
    updateFilterList(SessionManager::instance()->sessionProfile(_session));
149

150 151
    // listen for changes in session, we might need to change the enabled filters
    connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList);
152

153
    // listen for session resize requests
Laurent Montel's avatar
Laurent Montel committed
154
    connect(_session.data(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest);
155

156
    // listen for popup menu requests
Laurent Montel's avatar
Laurent Montel committed
157
    connect(_view.data(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu);
158

159
    // move view to newest output when keystrokes occur
Laurent Montel's avatar
Laurent Montel committed
160
    connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput);
161

Robert Knight's avatar
 
Robert Knight committed
162
    // listen to activity / silence notifications from session
Laurent Montel's avatar
Laurent Montel committed
163
    connect(_session.data(), &Konsole::Session::stateChanged, this, &Konsole::SessionController::sessionStateChanged);
164
    // listen to title and icon changes
165
    connect(_session.data(), &Konsole::Session::titleChanged, this, &Konsole::SessionController::sessionTitleChanged);
166
    connect(_session.data(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged);
167

168 169
    connect(this, &Konsole::SessionController::tabRenamedByUser,  _session,  &Konsole::Session::tabRenamedByUser);

Laurent Montel's avatar
Laurent Montel committed
170
    connect(_session.data() , &Konsole::Session::currentDirectoryChanged , this , &Konsole::SessionController::currentDirectoryChanged);
171

172
    // listen for color changes
173 174
    connect(_session.data(), &Konsole::Session::changeBackgroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setBackgroundColor);
    connect(_session.data(), &Konsole::Session::changeForegroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setForegroundColor);
175

176
    // update the title when the session starts
177
    connect(_session.data(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot);
178

179
    // listen for output changes to set activity flag
Laurent Montel's avatar
Laurent Montel committed
180
    connect(_session->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity);
181 182

    // listen for detection of ZModem transfer
183 184
    connect(_session.data(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload);
    connect(_session.data(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload);
185

Robert Knight's avatar
 
Robert Knight committed
186
    // listen for flow control status changes
Laurent Montel's avatar
Laurent Montel committed
187
    connect(_session.data(), &Konsole::Session::flowControlEnabledChanged, _view.data(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled);
188
    _view->setFlowControlWarningEnabled(_session->flowControlEnabled());
Robert Knight's avatar
 
Robert Knight committed
189

190 191
    // take a snapshot of the session state every so often when
    // user activity occurs
192 193 194
    //
    // the timer is owned by the session so that it will be destroyed along
    // with the session
Kurt Hindenburg's avatar
Kurt Hindenburg committed
195
    _interactionTimer = new QTimer(_session);
196
    _interactionTimer->setSingleShot(true);
197
    _interactionTimer->setInterval(500);
198 199
    connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
    connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::interactionHandler);
200

201
    // take a snapshot of the session state periodically in the background
Kurt Hindenburg's avatar
Kurt Hindenburg committed
202
    auto backgroundTimer = new QTimer(_session);
203 204
    backgroundTimer->setSingleShot(false);
    backgroundTimer->setInterval(2000);
205
    connect(backgroundTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
206 207
    backgroundTimer->start();

208 209 210 211
    // xterm '11;?' request
    connect(_session.data(), &Konsole::Session::getBackgroundColor,
            this, &Konsole::SessionController::sendBackgroundColor);

212 213
    connect(_session, &Konsole::Session::primaryScreenInUse, view, &Konsole::TerminalDisplay::usingPrimaryScreen);

214
    _allControllers.insert(this);
215 216 217

    // A list of programs that accept Ctrl+C to clear command line used
    // before outputting bookmark.
218 219
    _bookmarkValidProgramsToClear << QStringLiteral("bash") << QStringLiteral("fish") << QStringLiteral("sh");
    _bookmarkValidProgramsToClear << QStringLiteral("tcsh") << QStringLiteral("zsh");
220 221 222
}

SessionController::~SessionController()
223
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
224
    if (_view != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
225
        _view->setScreenWindow(nullptr);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
226
    }
227

Kurt Hindenburg's avatar
Kurt Hindenburg committed
228
    _allControllers.remove(this);
229 230 231 232

    if (!_editProfileDialog.isNull()) {
        delete _editProfileDialog.data();
    }
233
}
234
void SessionController::trackOutput(QKeyEvent* event)
235
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
236
    Q_ASSERT(_view->screenWindow());
237

238 239
    // Only jump to the bottom if the user actually typed something in,
    // not if the user e. g. just pressed a modifier.
240
    if (event->text().isEmpty() && (event->modifiers() != 0u)) {
241
        return;
242
    }
243 244

    _view->screenWindow()->setTrackOutput(true);
245
}
246 247 248 249 250 251 252 253 254 255
void SessionController::interactionHandler()
{
    // This flag is used to make sure those special icons indicating interest
    // events (activity/silence/bell?) remain in the tab until user interaction
    // happens. Otherwise, those special icons will quickly be replaced by
    // normal icon when ::snapshot() is triggered
    _keepIconUntilInteraction = false;
    _interactionTimer->start();
}

256 257
void SessionController::snapshot()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
258
    Q_ASSERT(_session != nullptr);
259

Kurt Hindenburg's avatar
Kurt Hindenburg committed
260
    QString title = _session->getDynamicTitle();
Robert Knight's avatar
 
Robert Knight committed
261
    title         = title.simplified();
262

263
    // Visualize that the session is broadcasting to others
264
    if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) {
265
        title.append(QLatin1Char('*'));
266
    }
Jekyll Wu's avatar
Jekyll Wu committed
267 268 269 270 271

    // use the fallback title if needed
    if (title.isEmpty()) {
        title = _session->title(Session::NameRole);
    }
272

273
    // apply new title
Jekyll Wu's avatar
Jekyll Wu committed
274 275 276 277
    _session->setTitle(Session::DisplayedTitleRole, title);

    // do not forget icon
    updateSessionIcon();
278 279
}

280 281
QString SessionController::currentDir() const
{
Robert Knight's avatar
 
Robert Knight committed
282
    return _session->currentWorkingDirectory();
283 284
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
285
QUrl SessionController::url() const
286
{
Robert Knight's avatar
 
Robert Knight committed
287
    return _session->getUrl();
288 289
}

290 291
void SessionController::rename()
{
292
    renameSession();
293 294
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
295
void SessionController::openUrl(const QUrl& url)
296
{
297 298
    // Clear shell's command line
    if (!_session->isForegroundProcessActive()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
299
            && _bookmarkValidProgramsToClear.contains(_session->foregroundProcessName())) {
300
        _session->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C
301 302
    }

303
    // handle local paths
Kurt Hindenburg's avatar
Kurt Hindenburg committed
304
    if (url.isLocalFile()) {
305
        QString path = url.toLocalFile();
306
        _session->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r'));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
307 308
    } else if (url.scheme().isEmpty()) {
        // QUrl couldn't parse what the user entered into the URL field
309
        // so just dump it to the shell
Kurt Hindenburg's avatar
Kurt Hindenburg committed
310
        QString command = url.toDisplayString();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
311
        if (!command.isEmpty()) {
312
            _session->sendTextToTerminal(command, QLatin1Char('\r'));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
313
        }
314 315
    } else if (url.scheme() == QLatin1String("ssh")) {
        QString sshCommand = QStringLiteral("ssh ");
316 317

        if (url.port() > -1) {
318
            sshCommand += QStringLiteral("-p %1 ").arg(url.port());
319
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
320
        if (!url.userName().isEmpty()) {
321
            sshCommand += (url.userName() + QLatin1Char('@'));
322
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
323
        if (!url.host().isEmpty()) {
324 325
            sshCommand += url.host();
        }
326
        _session->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
327

328 329
    } else if (url.scheme() == QLatin1String("telnet")) {
        QString telnetCommand = QStringLiteral("telnet ");
330

Kurt Hindenburg's avatar
Kurt Hindenburg committed
331
        if (!url.userName().isEmpty()) {
332
            telnetCommand += QStringLiteral("-l %1 ").arg(url.userName());
333
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
334
        if (!url.host().isEmpty()) {
335
            telnetCommand += (url.host() + QLatin1Char(' '));
336 337
        }
        if (url.port() > -1) {
338
            telnetCommand += QString::number(url.port());
339 340
        }

341
        _session->sendTextToTerminal(telnetCommand, QLatin1Char('\r'));
342

Kurt Hindenburg's avatar
Kurt Hindenburg committed
343
    } else {
344
        //TODO Implement handling for other Url types
345

346 347
        KMessageBox::sorry(_view->window(),
                           i18n("Konsole does not know how to open the bookmark: ") +
Kurt Hindenburg's avatar
Kurt Hindenburg committed
348
                           url.toDisplayString());
349

350
        qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know"
Kurt Hindenburg's avatar
Kurt Hindenburg committed
351
                   << " how to handle the protocol " << url.scheme();
352 353 354
    }
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
355
void SessionController::setupPrimaryScreenSpecificActions(bool use)
356
{
357
    KActionCollection* collection = actionCollection();
358 359 360 361
    QAction* clearAction = collection->action(QStringLiteral("clear-history"));
    QAction* resetAction = collection->action(QStringLiteral("clear-history-and-reset"));
    QAction* selectAllAction = collection->action(QStringLiteral("select-all"));
    QAction* selectLineAction = collection->action(QStringLiteral("select-line"));
362 363 364 365

    // these actions are meaningful only when primary screen is used.
    clearAction->setEnabled(use);
    resetAction->setEnabled(use);
366
    selectAllAction->setEnabled(use);
367
    selectLineAction->setEnabled(use);
368 369
}

Jekyll Wu's avatar
Jekyll Wu committed
370
void SessionController::selectionChanged(const QString& selectedText)
371
{
Jekyll Wu's avatar
Jekyll Wu committed
372 373 374
    _selectedText = selectedText;
    updateCopyAction(selectedText);
}
375

Jekyll Wu's avatar
Jekyll Wu committed
376 377
void SessionController::updateCopyAction(const QString& selectedText)
{
378
    QAction* copyAction = actionCollection()->action(QStringLiteral("edit_copy"));
Jekyll Wu's avatar
Jekyll Wu committed
379 380 381 382 383 384 385 386 387 388 389

    // copy action is meaningful only when some text is selected.
    copyAction->setEnabled(!selectedText.isEmpty());
}

void SessionController::updateWebSearchMenu()
{
    // reset
    _webSearchMenu->setVisible(false);
    _webSearchMenu->menu()->clear();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
390
    if (_selectedText.isEmpty()) {
391
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
392
    }
Jekyll Wu's avatar
Jekyll Wu committed
393 394

    QString searchText = _selectedText;
395
    searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
Jekyll Wu's avatar
Jekyll Wu committed
396

Kurt Hindenburg's avatar
Kurt Hindenburg committed
397
    if (searchText.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
398
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
399
    }
Jekyll Wu's avatar
Jekyll Wu committed
400 401 402 403 404 405 406 407 408

    KUriFilterData filterData(searchText);
    filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);

    if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) {
        const QStringList searchProviders = filterData.preferredSearchProviders();
        if (!searchProviders.isEmpty()) {
            _webSearchMenu->setText(i18n("Search for '%1' with",  KStringHandler::rsqueeze(searchText, 16)));

Kurt Hindenburg's avatar
Kurt Hindenburg committed
409
            QAction* action = nullptr;
Jekyll Wu's avatar
Jekyll Wu committed
410

411
            foreach(const QString& searchProvider, searchProviders) {
412
                action = new QAction(searchProvider, _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
413
                action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
Jekyll Wu's avatar
Jekyll Wu committed
414
                action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
415
                connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction);
Jekyll Wu's avatar
Jekyll Wu committed
416 417 418 419 420
                _webSearchMenu->addAction(action);
            }

            _webSearchMenu->addSeparator();

421
            action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
422
            action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
423
            connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts);
Jekyll Wu's avatar
Jekyll Wu committed
424 425 426 427 428 429 430 431 432
            _webSearchMenu->addAction(action);

            _webSearchMenu->setVisible(true);
        }
    }
}

void SessionController::handleWebShortcutAction()
{
Laurent Montel's avatar
Laurent Montel committed
433
    QAction * action = qobject_cast<QAction*>(sender());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
434
    if (action == nullptr) {
Jekyll Wu's avatar
Jekyll Wu committed
435
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
    }
Jekyll Wu's avatar
Jekyll Wu committed
437 438 439

    KUriFilterData filterData(action->data().toString());

440
    if (KUriFilter::self()->filterUri(filterData, QStringList() << QStringLiteral("kurisearchfilter"))) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
441
        const QUrl& url = filterData.uri();
442
        new KRun(url, QApplication::activeWindow());
Jekyll Wu's avatar
Jekyll Wu committed
443 444 445 446 447
    }
}

void SessionController::configureWebShortcuts()
{
448
    KToolInvocation::kdeinitExec(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"));
449 450
}

451 452
void SessionController::sendSignal(QAction* action)
{
453 454 455 456
    const int signal = action->data().value<int>();
    _session->sendSignal(signal);
}

457 458 459 460 461 462
void SessionController::sendBackgroundColor()
{
    const QColor c = _view->getBackgroundColor();
    _session->reportBackgroundColor(c);
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
463 464 465 466 467 468 469 470 471
void SessionController::toggleReadOnly()
{
    QAction *action = qobject_cast<QAction*>(sender());
    if (action != nullptr) {
        bool readonly = !isReadOnly();
        _session->setReadOnly(readonly);
    }
}

472
bool SessionController::eventFilter(QObject* watched , QEvent* event)
473
{
474 475 476 477 478 479 480 481 482
    if (event->type() == QEvent::FocusIn && watched == _view) {
        // notify the world that the view associated with this session has been focused
        // used by the view manager to update the title of the MainWindow widget containing the view
        emit focused(this);

        // when the view is focused, set bell events from the associated session to be delivered
        // by the focused view

        // first, disconnect any other views which are listening for bell signals from the session
Kurt Hindenburg's avatar
Kurt Hindenburg committed
483
        disconnect(_session.data(), &Konsole::Session::bellRequest, nullptr, nullptr);
484 485 486
        // second, connect the newly focused view to listen for the session's bell signal
        connect(_session.data(), &Konsole::Session::bellRequest, _view.data(), &Konsole::TerminalDisplay::bell);

487
        if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) {
488 489 490
            // A session with "Copy To All Tabs" has come into focus:
            // Ensure that newly created sessions are included in _copyToGroup!
            copyInputToAllTabs();
Robert Knight's avatar
 
Robert Knight committed
491
        }
492
    }
493
    return Konsole::ViewProperties::eventFilter(watched, event);
494 495
}

496 497
void SessionController::removeSearchFilter()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
498
    if (_searchFilter == nullptr) {
499
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
500
    }
501 502 503

    _view->filterChain()->removeFilter(_searchFilter);
    delete _searchFilter;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
504
    _searchFilter = nullptr;
505 506
}

507
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
508
{
509
    // disconnect the existing search bar
510
    if (_searchBar != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
511 512
        disconnect(this, nullptr, _searchBar, nullptr);
        disconnect(_searchBar, nullptr, this, nullptr);
513 514
    }

515 516
    // connect new search bar
    _searchBar = searchBar;
517
    if (_searchBar != nullptr) {
518 519 520 521 522 523 524
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::highlightMatchesToggled , this , &Konsole::SessionController::highlightMatches);
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch);
525
        connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch);
526

527 528
        // if the search bar was previously active
        // then re-enter search mode
529
        enableSearchBar(_isSearchBarEnabled);
530
    }
531 532 533 534 535 536
}
IncrementalSearchBar* SessionController::searchBar() const
{
    return _searchBar;
}

537 538
void SessionController::setShowMenuAction(QAction* action)
{
539
    _showMenuAction = action;
540 541
}

542
void SessionController::setupCommonActions()
543
{
544
    KActionCollection* collection = actionCollection();
545

546
    // Close Session
547
    QAction* action = collection->addAction(QStringLiteral("close-session"), this, SLOT(closeSession()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
548
    if (isKonsolePart()) {
549
        action->setText(i18n("&Close Session"));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
550
    } else {
551
        action->setText(i18n("&Close Tab"));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
552
    }
553

Michal Humpula's avatar
Michal Humpula committed
554
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
555
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W);
556 557

    // Open Browser
558
    action = collection->addAction(QStringLiteral("open-browser"), this, SLOT(openBrowser()));
559
    action->setText(i18n("Open File Manager"));
Michal Humpula's avatar
Michal Humpula committed
560
    action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
561

562
    // Copy and Paste
563
    action = KStandardAction::copy(this, SLOT(copy()), collection);
564
#ifdef Q_OS_MACOS
565 566 567 568 569 570
    // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META)
    // TODO: check what happens if we leave it to Qt to assign the default?
    collection->setDefaultShortcut(action, Qt::META + Qt::Key_C);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_C);
#endif
571 572
    // disabled at first, since nothing has been selected now
    action->setEnabled(false);
573

574
    action = KStandardAction::paste(this, SLOT(paste()), collection);
Alex Richardson's avatar
Alex Richardson committed
575
    QList<QKeySequence> pasteShortcut;
576
#ifdef Q_OS_MACOS
577 578 579 580
    pasteShortcut.append(QKeySequence(Qt::META + Qt::Key_V));
    // No Insert key on Mac keyboards
#else
    pasteShortcut.append(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_V));
Alex Richardson's avatar
Alex Richardson committed
581
    pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
582
#endif
583
    collection->setDefaultShortcuts(action, pasteShortcut);
584

585
    action = collection->addAction(QStringLiteral("paste-selection"), this, SLOT(pasteFromX11Selection()));
586
    action->setText(i18n("Paste Selection"));
587
#ifdef Q_OS_MACOS
588 589 590 591
    collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert);
#endif
592

593
    _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
Michal Humpula's avatar
Michal Humpula committed
594
    _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
Jekyll Wu's avatar
Jekyll Wu committed
595
    _webSearchMenu->setVisible(false);
596
    collection->addAction(QStringLiteral("web-search"), _webSearchMenu);
Jekyll Wu's avatar
Jekyll Wu committed
597 598


599
    action = collection->addAction(QStringLiteral("select-all"), this, SLOT(selectAll()));
600
    action->setText(i18n("&Select All"));
Michal Humpula's avatar
Michal Humpula committed
601
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
602

603
    action = collection->addAction(QStringLiteral("select-line"), this, SLOT(selectLine()));
604 605
    action->setText(i18n("Select &Line"));

606 607
    action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection);
    action->setText(i18n("Save Output &As..."));
608
#ifdef Q_OS_MACOS
609 610
    action->setShortcut(QKeySequence(Qt::META + Qt::Key_S));
#endif
611

612 613
    action = KStandardAction::print(this, SLOT(print_screen()), collection);
    action->setText(i18n("&Print Screen..."));
614
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P);
615

616
    action = collection->addAction(QStringLiteral("adjust-history"), this, SLOT(showHistoryOptions()));
617
    action->setText(i18n("Adjust Scrollback..."));
Michal Humpula's avatar
Michal Humpula committed
618
    action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
619

620
    action = collection->addAction(QStringLiteral("clear-history"), this, SLOT(clearHistory()));
621
    action->setText(i18n("Clear Scrollback"));
Michal Humpula's avatar
Michal Humpula committed
622
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
623

624
    action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, SLOT(clearHistoryAndReset()));
625
    action->setText(i18n("Clear Scrollback and Reset"));
Michal Humpula's avatar
Michal Humpula committed
626
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
627
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K);
628 629

    // Profile Options
630
    action = collection->addAction(QStringLiteral("edit-current-profile"), this, SLOT(editCurrentProfile()));
631
    action->setText(i18n("Edit Current Profile..."));
Michal Humpula's avatar
Michal Humpula committed
632
    action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
633

634
    _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this);
635
    collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu);
636
    connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu);
637

638
    // History
639
    _findAction = KStandardAction::find(this, SLOT(searchBarEvent()), collection);
640
    collection->setDefaultShortcut(_findAction, QKeySequence());
641 642

    _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection);
643
    collection->setDefaultShortcut(_findNextAction, QKeySequence());
644 645 646
    _findNextAction->setEnabled(false);

    _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection);
647
    collection->setDefaultShortcut(_findPreviousAction, QKeySequence());
648
    _findPreviousAction->setEnabled(false);
649 650 651

    // Character Encoding
    _codecAction = new KCodecAction(i18n("Set &Encoding"), this);
Michal Humpula's avatar
Michal Humpula committed
652
    _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set")));
653
    collection->addAction(QStringLiteral("set-encoding"), _codecAction);
654 655
    connect(_codecAction->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::updateCodecAction);
    connect(_codecAction, static_cast<void(KCodecAction::*)(QTextCodec*)>(&KCodecAction::triggered), this, &Konsole::SessionController::changeCodec);
656 657 658 659 660 661

    // Read-only
    action = collection->addAction(QStringLiteral("view-readonly"), this, SLOT(toggleReadOnly()));
    action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only"));
    action->setCheckable(true);
    updateReadOnlyActionStates();
662 663 664 665 666 667
}

void SessionController::setupExtraActions()
{
    KActionCollection* collection = actionCollection();

668
    // Rename Session
669
    QAction* action = collection->addAction(QStringLiteral("rename-session"), this, SLOT(renameSession()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
670
    action->setText(i18n("&Rename Tab..."));
Michal Humpula's avatar
Michal Humpula committed
671
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
672
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S);
673

674
    // Copy input to ==> all tabs
675
    KToggleAction* copyInputToAllTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-all-tabs"));
676 677 678 679 680 681
    copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
    copyInputToAllTabsAction->setData(CopyInputToAllTabsMode);
    // this action is also used in other place, so remember it
    _copyInputToAllTabsAction = copyInputToAllTabsAction;

    // Copy input to ==> selected tabs
682
    KToggleAction* copyInputToSelectedTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-selected-tabs"));
683
    copyInputToSelectedTabsAction->setText(i18n("&Select Tabs..."));
684
    collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Period);
685 686 687
    copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode);

    // Copy input to ==> none
688
    KToggleAction* copyInputToNoneAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-none"));
689
    copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
690
    collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Slash);
691 692 693 694 695
    copyInputToNoneAction->setData(CopyInputToNoneMode);
    copyInputToNoneAction->setChecked(true); // the default state

    // The "Copy Input To" submenu
    // The above three choices are represented as combo boxes
696
    KSelectAction* copyInputActions = collection->add<KSelectAction>(QStringLiteral("copy-input-to"));
697 698 699 700
    copyInputActions->setText(i18n("Copy Input To"));
    copyInputActions->addAction(copyInputToAllTabsAction);
    copyInputActions->addAction(copyInputToSelectedTabsAction);
    copyInputActions->addAction(copyInputToNoneAction);
701
    connect(copyInputActions, static_cast<void(KSelectAction::*)(QAction*)>(&KSelectAction::triggered), this, &Konsole::SessionController::copyInputActionsTriggered);
702

703
    action = collection->addAction(QStringLiteral("zmodem-upload"), this, SLOT(zmodemUpload()));
704
    action->setText(i18n("&ZModem Upload..."));
Michal Humpula's avatar
Michal Humpula committed
705
    action->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
706
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_U);
707

Robert Knight's avatar
 
Robert Knight committed
708
    // Monitor
709
    KToggleAction* toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this);
710
    collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_A);
711
    action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction);
712
    connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity);
Robert Knight's avatar
 
Robert Knight committed
713

Kurt Hindenburg's avatar
Kurt Hindenburg committed