SessionController.cpp 68.7 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
#include "ProfileManager.h"
24

25
// Qt
26
#include <QApplication>
27
#include <QAction>
28
#include <QMenu>
29
#include <QtGui/QKeyEvent>
30 31
#include <QPrinter>
#include <QPrintDialog>
32
#include <QFileDialog>
33
#include <QPainter>
34
#include <QStandardPaths>
Kurt Hindenburg's avatar
Kurt Hindenburg committed
35
#include <QtCore/QUrl>
Michal Humpula's avatar
Michal Humpula committed
36
#include <QtGui/QIcon>
37
#include <QDebug>
38

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

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

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

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

83
using namespace Konsole;
84

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

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

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

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

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

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

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

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

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

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

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

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

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

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

168
    // listen for color changes
169 170
    connect(_session.data(), &Konsole::Session::changeBackgroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setBackgroundColor);
    connect(_session.data(), &Konsole::Session::changeForegroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setForegroundColor);
171

172
    // update the title when the session starts
173
    connect(_session.data(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot);
174

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

    // listen for detection of ZModem transfer
179
    connect(_session.data(), &Konsole::Session::zmodemDetected, this, &Konsole::SessionController::zmodemDownload);
180

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

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

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

203 204 205 206
    // xterm '11;?' request
    connect(_session.data(), &Konsole::Session::getBackgroundColor,
            this, &Konsole::SessionController::sendBackgroundColor);

207
    _allControllers.insert(this);
208 209 210 211 212

    // A list of programs that accept Ctrl+C to clear command line used
    // before outputting bookmark.
    _bookmarkValidProgramsToClear << "bash" << "fish" << "sh";
    _bookmarkValidProgramsToClear << "tcsh" << "zsh";
213 214 215
}

SessionController::~SessionController()
216
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
217 218
    if (_view)
        _view->setScreenWindow(0);
219

Kurt Hindenburg's avatar
Kurt Hindenburg committed
220
    _allControllers.remove(this);
221 222 223 224

    if (!_editProfileDialog.isNull()) {
        delete _editProfileDialog.data();
    }
225
}
226
void SessionController::trackOutput(QKeyEvent* event)
227
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
228
    Q_ASSERT(_view->screenWindow());
229

230 231
    // Only jump to the bottom if the user actually typed something in,
    // not if the user e. g. just pressed a modifier.
232
    if (event->text().isEmpty() && event->modifiers()) {
233
        return;
234
    }
235 236

    _view->screenWindow()->setTrackOutput(true);
237
}
238 239 240 241 242 243 244 245 246 247
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();
}

248 249
void SessionController::snapshot()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
250
    Q_ASSERT(_session != 0);
251

Kurt Hindenburg's avatar
Kurt Hindenburg committed
252
    QString title = _session->getDynamicTitle();
Robert Knight's avatar
 
Robert Knight committed
253
    title         = title.simplified();
254

255 256
    // Visualize that the session is broadcasting to others
    if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
257
        title.append('*');
258
    }
Jekyll Wu's avatar
Jekyll Wu committed
259 260 261 262 263

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

265
    // apply new title
Jekyll Wu's avatar
Jekyll Wu committed
266 267 268 269
    _session->setTitle(Session::DisplayedTitleRole, title);

    // do not forget icon
    updateSessionIcon();
270 271
}

272 273
QString SessionController::currentDir() const
{
Robert Knight's avatar
 
Robert Knight committed
274
    return _session->currentWorkingDirectory();
275 276
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
277
QUrl SessionController::url() const
278
{
Robert Knight's avatar
 
Robert Knight committed
279
    return _session->getUrl();
280 281
}

282 283
void SessionController::rename()
{
284
    renameSession();
285 286
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
287
void SessionController::openUrl(const QUrl& url)
288
{
289 290
    // Clear shell's command line
    if (!_session->isForegroundProcessActive()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
291
            && _bookmarkValidProgramsToClear.contains(_session->foregroundProcessName())) {
292
        _session->sendTextToTerminal(QChar(0x03), '\n'); // Ctrl+C
293 294
    }

295
    // handle local paths
Kurt Hindenburg's avatar
Kurt Hindenburg committed
296
    if (url.isLocalFile()) {
297
        QString path = url.toLocalFile();
298
        _session->sendTextToTerminal("cd " + KShell::quoteArg(path), '\r');
Kurt Hindenburg's avatar
Kurt Hindenburg committed
299 300
    } else if (url.scheme().isEmpty()) {
        // QUrl couldn't parse what the user entered into the URL field
301
        // so just dump it to the shell
Kurt Hindenburg's avatar
Kurt Hindenburg committed
302
        QString command = url.toDisplayString();
303
        if (!command.isEmpty())
304
            _session->sendTextToTerminal(command, '\r');
Kurt Hindenburg's avatar
Kurt Hindenburg committed
305
    } else if (url.scheme() == "ssh") {
306 307 308
        QString sshCommand = "ssh ";

        if (url.port() > -1) {
309
            sshCommand += QString("-p %1 ").arg(url.port());
310
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
311 312
        if (!url.userName().isEmpty()) {
            sshCommand += (url.userName() + '@');
313
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
314
        if (!url.host().isEmpty()) {
315 316
            sshCommand += url.host();
        }
317
        _session->sendTextToTerminal(sshCommand, '\r');
318

Kurt Hindenburg's avatar
Kurt Hindenburg committed
319
    } else if (url.scheme() == "telnet") {
320 321
        QString telnetCommand = "telnet ";

Kurt Hindenburg's avatar
Kurt Hindenburg committed
322 323
        if (!url.userName().isEmpty()) {
            telnetCommand += QString("-l %1 ").arg(url.userName());
324
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
325
        if (!url.host().isEmpty()) {
326 327 328
            telnetCommand += (url.host() + ' ');
        }
        if (url.port() > -1) {
329
            telnetCommand += QString::number(url.port());
330 331
        }

332
        _session->sendTextToTerminal(telnetCommand, '\r');
333

Kurt Hindenburg's avatar
Kurt Hindenburg committed
334
    } else {
335
        //TODO Implement handling for other Url types
336

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

Laurent Montel's avatar
Laurent Montel committed
341
        qWarning() << "Unable to open bookmark at url" << url << ", I do not know"
Kurt Hindenburg's avatar
Kurt Hindenburg committed
342
                   << " how to handle the protocol " << url.scheme();
343 344 345
    }
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
346
void SessionController::setupPrimaryScreenSpecificActions(bool use)
347
{
348
    KActionCollection* collection = actionCollection();
Jekyll Wu's avatar
Jekyll Wu committed
349 350 351
    QAction* clearAction = collection->action("clear-history");
    QAction* resetAction = collection->action("clear-history-and-reset");
    QAction* selectAllAction = collection->action("select-all");
352
    QAction* selectLineAction = collection->action("select-line");
353 354 355 356

    // these actions are meaningful only when primary screen is used.
    clearAction->setEnabled(use);
    resetAction->setEnabled(use);
357
    selectAllAction->setEnabled(use);
358
    selectLineAction->setEnabled(use);
359 360
}

Jekyll Wu's avatar
Jekyll Wu committed
361
void SessionController::selectionChanged(const QString& selectedText)
362
{
Jekyll Wu's avatar
Jekyll Wu committed
363 364 365
    _selectedText = selectedText;
    updateCopyAction(selectedText);
}
366

Jekyll Wu's avatar
Jekyll Wu committed
367 368 369 370 371 372 373 374 375 376 377 378 379 380
void SessionController::updateCopyAction(const QString& selectedText)
{
    QAction* copyAction = actionCollection()->action("edit_copy");

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

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

381
    if (_selectedText.isEmpty())
382
        return;
Jekyll Wu's avatar
Jekyll Wu committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397

    QString searchText = _selectedText;
    searchText = searchText.replace('\n', ' ').replace('\r', ' ').simplified();

    if (searchText.isEmpty())
        return;

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

398
            QAction* action = 0;
Jekyll Wu's avatar
Jekyll Wu committed
399

400
            foreach(const QString& searchProvider, searchProviders) {
401
                action = new QAction(searchProvider, _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
402
                action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
Jekyll Wu's avatar
Jekyll Wu committed
403
                action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
404
                connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction);
Jekyll Wu's avatar
Jekyll Wu committed
405 406 407 408 409
                _webSearchMenu->addAction(action);
            }

            _webSearchMenu->addSeparator();

410
            action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
411
            action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
412
            connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts);
Jekyll Wu's avatar
Jekyll Wu committed
413 414 415 416 417 418 419 420 421
            _webSearchMenu->addAction(action);

            _webSearchMenu->setVisible(true);
        }
    }
}

void SessionController::handleWebShortcutAction()
{
Laurent Montel's avatar
Laurent Montel committed
422
    QAction * action = qobject_cast<QAction*>(sender());
Jekyll Wu's avatar
Jekyll Wu committed
423 424 425 426 427 428
    if (!action)
        return;

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

    if (KUriFilter::self()->filterUri(filterData, QStringList() << "kurisearchfilter")) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
429
        const QUrl& url = filterData.uri();
430
        new KRun(url, QApplication::activeWindow());
Jekyll Wu's avatar
Jekyll Wu committed
431 432 433 434 435
    }
}

void SessionController::configureWebShortcuts()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
    KToolInvocation::kdeinitExec("kcmshell5", QStringList() << "webshortcuts");
437 438
}

439 440
void SessionController::sendSignal(QAction* action)
{
441 442 443 444
    const int signal = action->data().value<int>();
    _session->sendSignal(signal);
}

445 446 447 448 449 450
void SessionController::sendBackgroundColor()
{
    const QColor c = _view->getBackgroundColor();
    _session->reportBackgroundColor(c);
}

451
bool SessionController::eventFilter(QObject* watched , QEvent* event)
452
{
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
    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
        disconnect(_session.data(), &Konsole::Session::bellRequest, 0, 0);
        // 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);

        if (_copyInputToAllTabsAction && _copyInputToAllTabsAction->isChecked()) {
            // 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
470
        }
471
    }
472

473
    return false;
474 475
}

476 477 478 479 480 481 482 483 484 485
void SessionController::removeSearchFilter()
{
    if (!_searchFilter)
        return;

    _view->filterChain()->removeFilter(_searchFilter);
    delete _searchFilter;
    _searchFilter = 0;
}

486
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
487
{
488
    // disconnect the existing search bar
Kurt Hindenburg's avatar
Kurt Hindenburg committed
489
    if (_searchBar) {
490 491
        disconnect(this, 0, _searchBar, 0);
        disconnect(_searchBar, 0, this, 0);
492 493
    }

494 495
    // connect new search bar
    _searchBar = searchBar;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
496
    if (_searchBar) {
497 498 499 500 501 502 503
        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);
504

505 506
        // if the search bar was previously active
        // then re-enter search mode
507
        enableSearchBar(_isSearchBarEnabled);
508
    }
509 510 511 512 513 514
}
IncrementalSearchBar* SessionController::searchBar() const
{
    return _searchBar;
}

515 516
void SessionController::setShowMenuAction(QAction* action)
{
517
    _showMenuAction = action;
518 519
}

520
void SessionController::setupCommonActions()
521
{
Alex Richardson's avatar
Alex Richardson committed
522 523
    QAction* action = 0;

524
    KActionCollection* collection = actionCollection();
525

526
    // Close Session
527
    action = collection->addAction("close-session", this, SLOT(closeSession()));
528 529 530 531 532
    if (isKonsolePart())
        action->setText(i18n("&Close Session"));
    else
        action->setText(i18n("&Close Tab"));

Michal Humpula's avatar
Michal Humpula committed
533
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
534
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W);
535 536

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

541
    // Copy and Paste
542
    action = KStandardAction::copy(this, SLOT(copy()), collection);
543 544 545 546 547 548 549
#ifdef Q_OS_OSX
    // 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
550 551
    // disabled at first, since nothing has been selected now
    action->setEnabled(false);
552

553
    action = KStandardAction::paste(this, SLOT(paste()), collection);
Alex Richardson's avatar
Alex Richardson committed
554
    QList<QKeySequence> pasteShortcut;
555 556 557 558 559
#ifdef Q_OS_OSX
    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
560
    pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
561
#endif
562
    collection->setDefaultShortcuts(action, pasteShortcut);
563

564
    action = collection->addAction("paste-selection", this, SLOT(pasteFromX11Selection()));
565
    action->setText(i18n("Paste Selection"));
566 567 568 569 570
#ifdef Q_OS_OSX
    collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert);
#endif
571

572
    _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
Michal Humpula's avatar
Michal Humpula committed
573
    _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
Jekyll Wu's avatar
Jekyll Wu committed
574 575 576 577
    _webSearchMenu->setVisible(false);
    collection->addAction("web-search", _webSearchMenu);


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

582 583 584
    action = collection->addAction("select-line", this, SLOT(selectLine()));
    action->setText(i18n("Select &Line"));

585 586
    action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection);
    action->setText(i18n("Save Output &As..."));
587 588 589
#ifdef Q_OS_OSX
    action->setShortcut(QKeySequence(Qt::META + Qt::Key_S));
#endif
590

591 592
    action = KStandardAction::print(this, SLOT(print_screen()), collection);
    action->setText(i18n("&Print Screen..."));
593
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P);
594

595 596
    action = collection->addAction("adjust-history", this, SLOT(showHistoryOptions()));
    action->setText(i18n("Adjust Scrollback..."));
Michal Humpula's avatar
Michal Humpula committed
597
    action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
598 599 600

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

    action = collection->addAction("clear-history-and-reset", this, SLOT(clearHistoryAndReset()));
    action->setText(i18n("Clear Scrollback and Reset"));
Michal Humpula's avatar
Michal Humpula committed
605
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
606
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K);
607 608 609

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

613
    _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this);
614
    collection->addAction("switch-profile", _switchProfileMenu);
615
    connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu);
616

617
    // History
618
    _findAction = KStandardAction::find(this, SLOT(searchBarEvent()), collection);
619
    collection->setDefaultShortcut(_findAction, QKeySequence());
620 621

    _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection);
622
    collection->setDefaultShortcut(_findNextAction, QKeySequence());
623 624 625
    _findNextAction->setEnabled(false);

    _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection);
626
    collection->setDefaultShortcut(_findPreviousAction, QKeySequence());
627
    _findPreviousAction->setEnabled(false);
628 629 630

    // Character Encoding
    _codecAction = new KCodecAction(i18n("Set &Encoding"), this);
Michal Humpula's avatar
Michal Humpula committed
631
    _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set")));
632
    collection->addAction("set-encoding", _codecAction);
633 634
    connect(_codecAction->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::updateCodecAction);
    connect(_codecAction, static_cast<void(KCodecAction::*)(QTextCodec*)>(&KCodecAction::triggered), this, &Konsole::SessionController::changeCodec);
635 636 637 638
}

void SessionController::setupExtraActions()
{
Alex Richardson's avatar
Alex Richardson committed
639
    QAction* action = 0;
640 641 642
    KToggleAction* toggleAction = 0;
    KActionCollection* collection = actionCollection();

643
    // Rename Session
644
    action = collection->addAction("rename-session", this, SLOT(renameSession()));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
645
    action->setText(i18n("&Rename Tab..."));
Michal Humpula's avatar
Michal Humpula committed
646
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
647
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S);
648

649 650 651 652 653 654 655 656 657 658
    // Copy input to ==> all tabs
    KToggleAction* copyInputToAllTabsAction = collection->add<KToggleAction>("copy-input-to-all-tabs");
    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
    KToggleAction* copyInputToSelectedTabsAction = collection->add<KToggleAction>("copy-input-to-selected-tabs");
    copyInputToSelectedTabsAction->setText(i18n("&Select Tabs..."));
659
    collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Period);
660 661 662 663 664
    copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode);

    // Copy input to ==> none
    KToggleAction* copyInputToNoneAction = collection->add<KToggleAction>("copy-input-to-none");
    copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
665
    collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Slash);
666 667 668 669 670 671 672 673 674 675
    copyInputToNoneAction->setData(CopyInputToNoneMode);
    copyInputToNoneAction->setChecked(true); // the default state

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

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

Robert Knight's avatar
 
Robert Knight committed
683
    // Monitor
Kurt Hindenburg's avatar
Kurt Hindenburg committed
684
    toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this);
685
    collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_A);
686
    action = collection->addAction("monitor-activity", toggleAction);
687
    connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity);
Robert Knight's avatar
 
Robert Knight committed
688

Kurt Hindenburg's avatar
Kurt Hindenburg committed
689
    toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this);
690
    collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_I);
691
    action = collection->addAction("monitor-silence", toggleAction);
692
    connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence);
693

694
    // Text Size
695
    action = collection->addAction("enlarge-font", this, SLOT(increaseFontSize()));
696
    action->setText(i18n("Enlarge Font"));
Michal Humpula's avatar
Michal Humpula committed
697
    action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more")));
Alex Richardson's avatar
Alex Richardson committed
698
    QList<QKeySequence> enlargeFontShortcut;
699 700
    enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Plus));
    enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Equal));
701
    collection->setDefaultShortcuts(action, enlargeFontShortcut);
702

703
    action = collection->addAction("shrink-font", this, SLOT(decreaseFontSize()));
704
    action->setText(i18n("Shrink Font"));
Michal Humpula's avatar
Michal Humpula committed
705
    action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less")));
706
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_Minus);
Alex Richardson's avatar
Alex Richardson committed
707

708

709
    // Send signal
710
    KSelectAction* sendSignalActions = collection->add<KSelectAction>("send-signal");
711
    sendSignalActions->setText(i18n("Send Signal"));
712
    connect(sendSignalActions, static_cast<void(KSelectAction::*)(QAction*)>(&KSelectAction::triggered), this, &Konsole::SessionController::sendSignal);
713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753

    action = collection->addAction("sigstop-signal");
    action->setText(i18n("&Suspend Task")   + " (STOP)");
    action->setData(SIGSTOP);
    sendSignalActions->addAction(action);

    action = collection->addAction("sigcont-signal");
    action->setText(i18n("&Continue Task")  + " (CONT)");
    action->setData(SIGCONT);
    sendSignalActions->addAction(action);

    action = collection->addAction("sighup-signal");
    action->setText(i18n("&Hangup")         + " (HUP)");
    action->setData(SIGHUP);
    sendSignalActions->addAction(action);

    action = collection->addAction("sigint-signal");
    action->setText(i18n("&Interrupt Task") + " (INT)");
    action->setData(SIGINT);
    sendSignalActions->addAction(action);

    action = collection->addAction("sigterm-signal");