2
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
5
This program is free software; you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation; either version 2 of the License, or
8
(at your option) any later version.
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
#include "SessionController.h"
25
#include <QtGui/QApplication>
32
#include <KInputDialog>
35
#include <KMessageBox>
38
#include <KStandardDirs>
39
#include <KToggleAction>
41
#include <KXmlGuiWindow>
42
#include <KXMLGUIFactory>
43
#include <KXMLGUIBuilder>
45
#include <kcodecaction.h>
46
#include <kdeversion.h>
49
#include "EditProfileDialog.h"
50
#include "CopyInputDialog.h"
51
#include "Emulation.h"
54
#include "IncrementalSearchBar.h"
55
#include "RenameTabsDialog.h"
56
#include "ScreenWindow.h"
58
#include "ProfileList.h"
59
#include "TerminalDisplay.h"
60
#include "SessionManager.h"
62
// for SaveHistoryTask
63
#include <KFileDialog>
66
#include "TerminalCharacterDecoder.h"
69
using namespace Konsole;
71
KIcon SessionController::_activityIcon;
72
KIcon SessionController::_silenceIcon;
73
QSet<SessionController*> SessionController::_allControllers;
74
QPointer<SearchHistoryThread> SearchHistoryTask::_thread;
75
int SessionController::_lastControllerId;
77
SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent)
78
: ViewProperties(parent)
87
, _searchToggleAction(0)
89
, _findPreviousAction(0)
90
, _urlFilterUpdateRequired(false)
92
, _changeProfileMenu(0)
93
, _listenForScreenWindowUpdates(false)
94
, _preventClose(false)
96
_allControllers.insert(this);
101
// handle user interface related to session (menus etc.)
103
setXMLFile("konsole/partui.rc");
105
setXMLFile("konsole/sessionui.rc");
108
actionCollection()->addAssociatedWidget(view);
109
foreach (QAction* action, actionCollection()->actions())
110
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
112
setIdentifier(++_lastControllerId);
113
sessionTitleChanged();
115
view->installEventFilter(this);
117
// listen for session resize requests
118
connect( _session , SIGNAL(resizeRequest(const QSize&)) , this ,
119
SLOT(sessionResizeRequest(const QSize&)) );
121
// listen for popup menu requests
122
connect( _view , SIGNAL(configureRequest(QPoint)) , this,
123
SLOT(showDisplayContextMenu(QPoint)) );
125
// move view to newest output when keystrokes occur
126
connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , this ,
127
SLOT(trackOutput(QKeyEvent*)) );
129
// listen to activity / silence notifications from session
130
connect( _session , SIGNAL(stateChanged(int)) , this ,
131
SLOT(sessionStateChanged(int) ));
132
// listen to title and icon changes
133
connect( _session , SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) );
135
// listen for color changes
136
connect( _session , SIGNAL(changeBackgroundColorRequest(QColor)) , _view , SLOT(setBackgroundColor(QColor)) );
137
connect( _session , SIGNAL(changeForegroundColorRequest(QColor)) , _view , SLOT(setForegroundColor(QColor)) );
139
// update the title when the session starts
140
connect( _session , SIGNAL(started()) , this , SLOT(snapshot()) );
142
// listen for output changes to set activity flag
143
connect( _session->emulation() , SIGNAL(outputChanged()) , this ,
144
SLOT(fireActivity()) );
146
// listen for detection of ZModem transfer
147
connect( _session , SIGNAL(zmodemDetected()) , this , SLOT(zmodemDownload()) );
149
// listen for flow control status changes
150
connect( _session , SIGNAL(flowControlEnabledChanged(bool)) , _view ,
151
SLOT(setFlowControlWarningEnabled(bool)) );
152
_view->setFlowControlWarningEnabled(_session->flowControlEnabled());
154
// take a snapshot of the session state every so often when
155
// user activity occurs
157
// the timer is owned by the session so that it will be destroyed along
159
QTimer* activityTimer = new QTimer(_session);
160
activityTimer->setSingleShot(true);
161
activityTimer->setInterval(2000);
162
connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , activityTimer , SLOT(start()) );
163
connect( activityTimer , SIGNAL(timeout()) , this , SLOT(snapshot()) );
166
void SessionController::updateSearchFilter()
170
Q_ASSERT( searchBar() && searchBar()->isVisible() );
172
_view->processFilters();
176
SessionController::~SessionController()
179
_view->setScreenWindow(0);
181
_allControllers.remove(this);
183
void SessionController::trackOutput(QKeyEvent* event)
185
Q_ASSERT( _view->screenWindow() );
187
// jump to the end of the history buffer unless the key pressed
188
// is one of the three main modifiers, as these are used to select
189
// the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection)
190
switch (event->key())
193
case Qt::Key_Control:
197
_view->screenWindow()->setTrackOutput(true);
200
void SessionController::requireUrlFilterUpdate()
202
// this method is called every time the screen window's output changes, so do not
203
// do anything expensive here.
205
_urlFilterUpdateRequired = true;
207
void SessionController::snapshot()
209
Q_ASSERT( _session != 0 );
211
QString title = _session->getDynamicTitle();
212
title = title.simplified();
214
// Visualize that the session is broadcasting to others
215
if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
221
if ( !title.isEmpty() )
222
_session->setTitle(Session::DisplayedTitleRole,title);
224
_session->setTitle(Session::DisplayedTitleRole,_session->title(Session::NameRole));
227
QString SessionController::currentDir() const
229
return _session->currentWorkingDirectory();
232
KUrl SessionController::url() const
234
return _session->getUrl();
237
void SessionController::rename()
242
void SessionController::openUrl( const KUrl& url )
244
// handle local paths
245
if ( url.isLocalFile() )
247
QString path = url.toLocalFile();
248
_session->emulation()->sendText("cd " + KShell::quoteArg(path) + '\r');
250
else if ( url.protocol() == "ssh" )
252
_session->emulation()->sendText("ssh ");
254
if ( url.port() > -1 )
255
_session->emulation()->sendText("-p " + QString::number(url.port()) + ' ' );
257
_session->emulation()->sendText(url.user() + '@');
259
_session->emulation()->sendText(url.host() + '\r');
261
else if ( url.protocol() == "telnet" )
263
_session->emulation()->sendText("telnet ");
266
_session->emulation()->sendText("-l " + url.user() + ' ');
268
_session->emulation()->sendText(url.host() + ' ');
269
if ( url.port() > -1 )
270
_session->emulation()->sendText(QString::number(url.port()));
271
_session->emulation()->sendText("\r");
275
//TODO Implement handling for other Url types
277
KMessageBox::sorry(_view->window(),
278
i18n("Konsole does not know how to open the bookmark: ") +
281
kWarning(1211) << "Unable to open bookmark at url" << url << ", I do not know"
282
<< " how to handle the protocol " << url.protocol();
286
bool SessionController::eventFilter(QObject* watched , QEvent* event)
288
if ( watched == _view )
290
if ( event->type() == QEvent::FocusIn )
292
// notify the world that the view associated with this session has been focused
293
// used by the view manager to update the title of the MainWindow widget containing the view
296
// when the view is focused, set bell events from the associated session to be delivered
297
// by the focused view
299
// first, disconnect any other views which are listening for bell signals from the session
300
disconnect( _session , SIGNAL(bellRequest(const QString&)) , 0 , 0 );
301
// second, connect the newly focused view to listen for the session's bell signal
302
connect( _session , SIGNAL(bellRequest(const QString&)) ,
303
_view , SLOT(bell(const QString&)) );
305
if(_copyToAllTabsAction->isChecked()) {
306
// A session with "Copy To All Tabs" has come into focus:
307
// Ensure that newly created sessions are included in _copyToGroup!
308
copyInputToAllTabs();
311
// when a mouse move is received, create the URL filter and listen for output changes if
312
// it has not already been created. If it already exists, then update only if the output
313
// has changed since the last update ( _urlFilterUpdateRequired == true )
315
// also check that no mouse buttons are pressed since the URL filter only applies when
316
// the mouse is hovering over the view
317
if ( event->type() == QEvent::MouseMove &&
318
(!_viewUrlFilter || _urlFilterUpdateRequired) &&
319
((QMouseEvent*)event)->buttons() == Qt::NoButton )
321
if ( _view->screenWindow() && !_viewUrlFilter )
323
connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this ,
324
SLOT(requireUrlFilterUpdate()) );
325
connect( _view->screenWindow() , SIGNAL(outputChanged()) , this ,
326
SLOT(requireUrlFilterUpdate()) );
328
// install filter on the view to highlight URLs
329
_viewUrlFilter = new UrlFilter();
330
_view->filterChain()->addFilter( _viewUrlFilter );
333
_view->processFilters();
334
_urlFilterUpdateRequired = false;
341
void SessionController::removeSearchFilter()
346
_view->filterChain()->removeFilter(_searchFilter);
347
delete _searchFilter;
351
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
353
// disconnect the existing search bar
356
disconnect( this , 0 , _searchBar , 0 );
357
disconnect( _searchBar , 0 , this , 0 );
360
// remove any existing search filter
361
removeSearchFilter();
363
// connect new search bar
364
_searchBar = searchBar;
367
connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
368
connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
369
connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
370
connect( _searchBar , SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) );
372
// if the search bar was previously active
373
// then re-enter search mode
374
searchHistory( _searchToggleAction->isChecked() );
377
IncrementalSearchBar* SessionController::searchBar() const
382
void SessionController::setShowMenuAction(QAction* action)
384
actionCollection()->addAction("show-menubar",action);
387
void SessionController::setupActions()
390
KToggleAction* toggleAction = 0;
391
KActionCollection* collection = actionCollection();
394
action = collection->addAction("close-session", this, SLOT(closeSession()));
395
action->setIcon(KIcon("tab-close"));
396
action->setText(i18n("&Close Tab"));
397
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W));
400
action = collection->addAction("open-browser", this, SLOT(openBrowser()));
401
action->setText(i18n("Open File Manager"));
402
action->setIcon(KIcon("system-file-manager"));
405
action = KStandardAction::copy(this, SLOT(copy()), collection);
406
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C));
408
action = KStandardAction::paste(this, SLOT(paste()), collection);
409
KShortcut pasteShortcut = action->shortcut();
410
pasteShortcut.setPrimary(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V));
411
pasteShortcut.setAlternate(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
412
action->setShortcut(pasteShortcut);
414
action = collection->addAction("paste-selection", this, SLOT(pasteSelection()));
415
action->setText(i18n("Paste Selection"));
416
action->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Insert));
419
action = collection->addAction("rename-session", this, SLOT(renameSession()));
420
action->setText( i18n("&Rename Tab...") );
421
action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) );
423
// Copy Input To -> All Tabs in Current Window
424
_copyToAllTabsAction = collection->addAction("copy-input-to-all-tabs", this, SLOT(copyInputToAllTabs()));
425
_copyToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
426
_copyToAllTabsAction->setCheckable(true);
428
// Copy Input To -> Select Tabs
429
_copyToSelectedAction = collection->addAction("copy-input-to-selected-tabs", this, SLOT(copyInputToSelectedTabs()));
430
_copyToSelectedAction->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Period) );
431
_copyToSelectedAction->setText(i18n("&Select Tabs..."));
432
_copyToSelectedAction->setCheckable(true);
434
// Copy Input To -> None
435
_copyToNoneAction = collection->addAction("copy-input-to-none", this, SLOT(copyInputToNone()));
436
_copyToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
437
_copyToNoneAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Slash));
438
_copyToNoneAction->setCheckable(true);
439
_copyToNoneAction->setChecked(true);
441
action = collection->addAction("zmodem-upload", this, SLOT(zmodemUpload()));
442
action->setText(i18n("&ZModem Upload..."));
443
action->setIcon(KIcon("document-open"));
444
action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U));
447
toggleAction = new KToggleAction(i18n("Monitor for &Activity"),this);
448
toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A));
449
action = collection->addAction("monitor-activity", toggleAction);
450
connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorActivity(bool)));
452
toggleAction = new KToggleAction(i18n("Monitor for &Silence"),this);
453
toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_I));
454
action = collection->addAction("monitor-silence", toggleAction);
455
connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorSilence(bool)));
457
// Character Encoding
458
_codecAction = new KCodecAction(i18n("Set &Encoding"),this);
459
_codecAction->setIcon(KIcon("character-set"));
460
collection->addAction("set-encoding", _codecAction);
461
connect(_codecAction->menu(), SIGNAL(aboutToShow()), this, SLOT(updateCodecAction()));
462
connect(_codecAction, SIGNAL(triggered(QTextCodec*)), this, SLOT(changeCodec(QTextCodec*)));
465
action = collection->addAction("enlarge-font", this, SLOT(increaseTextSize()));
466
action->setText(i18n("Enlarge Font"));
467
action->setIcon(KIcon("format-font-size-more"));
468
action->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Plus));
470
action = collection->addAction("shrink-font", this, SLOT(decreaseTextSize()));
471
action->setText(i18n("Shrink Font"));
472
action->setIcon(KIcon("format-font-size-less"));
473
action->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Minus));
476
_searchToggleAction = KStandardAction::find(this, NULL, collection);
477
_searchToggleAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F));
478
_searchToggleAction->setCheckable(true);
479
connect(_searchToggleAction, SIGNAL(toggled(bool)), this, SLOT(searchHistory(bool)));
481
_findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection);
482
_findNextAction->setEnabled(false);
484
_findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection);
485
_findPreviousAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_F3));
486
_findPreviousAction->setEnabled(false);
488
action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection);
489
action->setText(i18n("Save Output &As..."));
491
action = collection->addAction("configure-history", this, SLOT(showHistoryOptions()));
492
action->setText(i18n("Configure Scrollback..."));
493
action->setIcon(KIcon("configure"));
495
action = collection->addAction("clear-history", this, SLOT(clearHistory()));
496
action->setText(i18n("Clear Scrollback"));
497
action->setIcon(KIcon("edit-clear-history"));
499
action = collection->addAction("clear-history-and-reset", this, SLOT(clearHistoryAndReset()));
500
action->setText(i18n("Clear Scrollback and Reset"));
501
action->setIcon(KIcon("edit-clear-history"));
502
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_X));
505
action = collection->addAction("edit-current-profile", this, SLOT(editCurrentProfile()));
506
action->setText(i18n("Configure Current Profile..."));
507
action->setIcon(KIcon("document-properties") );
509
_changeProfileMenu = new KActionMenu(i18n("Change Profile"), _view);
510
collection->addAction("change-profile", _changeProfileMenu);
511
connect(_changeProfileMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(prepareChangeProfileMenu()));
514
void SessionController::changeProfile(Profile::Ptr profile)
516
SessionManager::instance()->setSessionProfile(_session,profile);
519
void SessionController::prepareChangeProfileMenu()
521
if (_changeProfileMenu->menu()->isEmpty()) {
522
_profileList = new ProfileList(false,this);
523
connect(_profileList, SIGNAL(profileSelected(Profile::Ptr)), this, SLOT(changeProfile(Profile::Ptr)));
526
_changeProfileMenu->menu()->clear();
527
_changeProfileMenu->menu()->addActions(_profileList->actions());
529
void SessionController::updateCodecAction()
531
_codecAction->setCurrentCodec(QString(_session->emulation()->codec()->name()));
534
void SessionController::changeCodec(QTextCodec* codec)
536
_session->setCodec(codec);
539
void SessionController::editCurrentProfile()
541
EditProfileDialog* dialog = new EditProfileDialog( QApplication::activeWindow() );
543
dialog->setProfile(SessionManager::instance()->sessionProfile(_session));
547
void SessionController::renameSession()
549
QScopedPointer<RenameTabsDialog> dialog(new RenameTabsDialog(QApplication::activeWindow()));
550
dialog->setTabTitleText(_session->tabTitleFormat(Session::LocalTabTitle));
551
dialog->setRemoteTabTitleText(_session->tabTitleFormat(Session::RemoteTabTitle));
553
if (!_session->isRemote()) {
554
dialog->focusTabTitleText();
556
dialog->focusRemoteTabTitleText();
559
QPointer<Session> guard(_session);
560
int result = dialog->exec();
566
QString tabTitle = dialog->tabTitleText();
567
QString remoteTabTitle = dialog->remoteTabTitleText();
569
_session->setTabTitleFormat(Session::LocalTabTitle, tabTitle);
570
_session->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle);
572
// trigger an update of the tab text
576
void SessionController::saveSession()
578
Q_ASSERT(0); // not implemented yet
580
//SaveSessionDialog dialog(_view);
581
//int result = dialog.exec();
583
bool SessionController::confirmClose() const
585
if (_session->isForegroundProcessActive())
587
QString title = _session->foregroundProcessName();
589
// hard coded for now. In future make it possible for the user to specify which programs
590
// are ignored when considering whether to display a confirmation
591
QStringList ignoreList;
592
ignoreList << QString(qgetenv("SHELL")).section('/',-1);
593
if (ignoreList.contains(title))
598
question = i18n("A program is currently running in this session."
599
" Are you sure you want to close it?");
601
question = i18n("The program '%1' is currently running in this session."
602
" Are you sure you want to close it?",title);
604
int result = KMessageBox::warningYesNo(_view->window(),question,i18n("Confirm Close"));
605
return (result == KMessageBox::Yes) ? true : false;
609
void SessionController::closeSession()
618
void SessionController::openBrowser()
620
new KRun(url(), QApplication::activeWindow());
623
void SessionController::copy()
625
_view->copyClipboard();
628
void SessionController::paste()
630
_view->pasteClipboard();
632
void SessionController::pasteSelection()
634
_view->pasteSelection();
636
static const KXmlGuiWindow* findWindow(const QObject* object)
638
// Walk up the QObject hierarchy to find a KXmlGuiWindow.
639
while(object != NULL) {
640
const KXmlGuiWindow* window = dynamic_cast<const KXmlGuiWindow*>(object);
644
object = object->parent();
649
static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window)
651
// Iterate all TerminalDisplays of this Session ...
652
QListIterator<TerminalDisplay*> terminalDisplayIterator(session->views());
653
while(terminalDisplayIterator.hasNext()) {
654
const TerminalDisplay* terminalDisplay = terminalDisplayIterator.next();
655
// ... and check whether a TerminalDisplay has the same
656
// window as given in the parameter
657
if(window == findWindow(terminalDisplay)) {
664
void SessionController::copyInputToAllTabs()
667
_copyToGroup = new SessionGroup(this);
670
// Find our window ...
671
const KXmlGuiWindow* myWindow = findWindow(_view);
673
QSet<Session*> group =
674
QSet<Session*>::fromList(SessionManager::instance()->sessions());
675
for(QSet<Session*>::iterator iterator = group.begin();
676
iterator != group.end(); ++iterator) {
677
Session* session = *iterator;
679
// First, ensure that the session is removed
680
// (necessary to avoid duplicates on addSession()!)
681
_copyToGroup->removeSession(session);
683
// Add current session if it is displayed our window
684
if(hasTerminalDisplayInSameWindow(session, myWindow)) {
685
_copyToGroup->addSession(session);
688
_copyToGroup->setMasterStatus(_session, true);
689
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
692
_copyToAllTabsAction->setChecked(true);
693
_copyToSelectedAction->setChecked(false);
694
_copyToNoneAction->setChecked(false);
697
void SessionController::copyInputToSelectedTabs()
701
_copyToGroup = new SessionGroup(this);
702
_copyToGroup->addSession(_session);
703
_copyToGroup->setMasterStatus(_session,true);
704
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
707
CopyInputDialog* dialog = new CopyInputDialog(_view);
708
dialog->setMasterSession(_session);
710
QSet<Session*> currentGroup = QSet<Session*>::fromList(_copyToGroup->sessions());
711
currentGroup.remove(_session);
713
dialog->setChosenSessions(currentGroup);
715
QPointer<Session> guard(_session);
716
int result = dialog->exec();
722
QSet<Session*> newGroup = dialog->chosenSessions();
723
newGroup.remove(_session);
725
QSet<Session*> completeGroup = newGroup | currentGroup;
726
foreach(Session* session, completeGroup)
728
if (newGroup.contains(session) && !currentGroup.contains(session))
729
_copyToGroup->addSession(session);
730
else if (!newGroup.contains(session) && currentGroup.contains(session))
731
_copyToGroup->removeSession(session);
734
_copyToGroup->setMasterStatus(_session, true);
735
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
740
_copyToAllTabsAction->setChecked(false);
741
_copyToSelectedAction->setChecked(true);
742
_copyToNoneAction->setChecked(false);
745
void SessionController::copyInputToNone()
747
if (!_copyToGroup) // No 'Copy To' is active
750
QSet<Session*> group =
751
QSet<Session*>::fromList(SessionManager::instance()->sessions());
752
for(QSet<Session*>::iterator iterator = group.begin();
753
iterator != group.end(); ++iterator) {
754
Session* session = *iterator;
756
if(session != _session) {
757
_copyToGroup->removeSession(*iterator);
764
_copyToAllTabsAction->setChecked(false);
765
_copyToSelectedAction->setChecked(false);
766
_copyToNoneAction->setChecked(true);
769
void SessionController::searchClosed()
771
_searchToggleAction->toggle();
775
void SessionController::searchHistory()
781
void SessionController::listenForScreenWindowUpdates()
783
if (_listenForScreenWindowUpdates)
786
connect( _view->screenWindow() , SIGNAL(outputChanged()) , this ,
787
SLOT(updateSearchFilter()) );
788
connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this ,
789
SLOT(updateSearchFilter()) );
791
_listenForScreenWindowUpdates = true;
794
// searchHistory() may be called either as a result of clicking a menu item or
795
// as a result of changing the search bar widget
796
void SessionController::searchHistory(bool showSearchBar)
800
_searchBar->setVisible(showSearchBar);
804
removeSearchFilter();
806
listenForScreenWindowUpdates();
808
_searchFilter = new RegExpFilter();
809
_view->filterChain()->addFilter(_searchFilter);
810
connect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
811
SLOT(searchTextChanged(const QString&)) );
813
// invoke search for matches for the current search text
814
const QString& currentSearchText = _searchBar->searchText();
815
if (!currentSearchText.isEmpty())
817
searchTextChanged(currentSearchText);
820
setFindNextPrevEnabled(true);
824
setFindNextPrevEnabled(false);
826
disconnect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
827
SLOT(searchTextChanged(const QString&)) );
829
removeSearchFilter();
831
_view->setFocus( Qt::ActiveWindowFocusReason );
835
void SessionController::setFindNextPrevEnabled(bool enabled)
837
_findNextAction->setEnabled(enabled);
838
_findPreviousAction->setEnabled(enabled);
840
void SessionController::searchTextChanged(const QString& text)
842
Q_ASSERT( _view->screenWindow() );
844
if ( text.isEmpty() )
845
_view->screenWindow()->clearSelection();
847
// update search. this is called even when the text is
848
// empty to clear the view's filters
849
beginSearch(text , SearchHistoryTask::ForwardsSearch);
851
void SessionController::searchCompleted(bool success)
854
_searchBar->setFoundMatch(success);
857
void SessionController::beginSearch(const QString& text , int direction)
859
Q_ASSERT( _searchBar );
860
Q_ASSERT( _searchFilter );
862
Qt::CaseSensitivity caseHandling = _searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive;
863
QRegExp::PatternSyntax syntax = _searchBar->matchRegExp() ? QRegExp::RegExp : QRegExp::FixedString;
865
QRegExp regExp( text.trimmed() , caseHandling , syntax );
866
_searchFilter->setRegExp(regExp);
868
if ( !regExp.isEmpty() )
870
SearchHistoryTask* task = new SearchHistoryTask(this);
872
connect( task , SIGNAL(completed(bool)) , this , SLOT(searchCompleted(bool)) );
874
task->setRegExp(regExp);
875
task->setSearchDirection( (SearchHistoryTask::SearchDirection)direction );
876
task->setAutoDelete(true);
877
task->addScreenWindow( _session , _view->screenWindow() );
881
_view->processFilters();
883
void SessionController::highlightMatches(bool highlight)
887
_view->filterChain()->addFilter(_searchFilter);
888
_view->processFilters();
892
_view->filterChain()->removeFilter(_searchFilter);
897
void SessionController::findNextInHistory()
899
Q_ASSERT( _searchBar );
900
Q_ASSERT( _searchFilter );
902
beginSearch(_searchBar->searchText(),SearchHistoryTask::ForwardsSearch);
904
void SessionController::findPreviousInHistory()
906
Q_ASSERT( _searchBar );
907
Q_ASSERT( _searchFilter );
909
beginSearch(_searchBar->searchText(),SearchHistoryTask::BackwardsSearch);
911
void SessionController::showHistoryOptions()
913
HistorySizeDialog* dialog = new HistorySizeDialog( QApplication::activeWindow() );
914
const HistoryType& currentHistory = _session->historyType();
916
if ( currentHistory.isEnabled() )
918
if ( currentHistory.isUnlimited() )
919
dialog->setMode( HistorySizeDialog::UnlimitedHistory );
922
dialog->setMode( HistorySizeDialog::FixedSizeHistory );
923
dialog->setLineCount( currentHistory.maximumLineCount() );
927
dialog->setMode( HistorySizeDialog::NoHistory );
929
connect( dialog , SIGNAL(optionsChanged(int,int,bool)) ,
930
this , SLOT(scrollBackOptionsChanged(int,int,bool)) );
934
void SessionController::sessionResizeRequest(const QSize& size)
936
//kDebug(1211) << "View resize requested to " << size;
937
_view->setSize(size.width(),size.height());
939
void SessionController::scrollBackOptionsChanged(int mode, int lines, bool saveToCurrentProfile )
943
case HistorySizeDialog::NoHistory:
944
_session->setHistoryType( HistoryTypeNone() );
946
case HistorySizeDialog::FixedSizeHistory:
947
_session->setHistoryType( CompactHistoryType(lines) );
949
case HistorySizeDialog::UnlimitedHistory:
950
_session->setHistoryType( HistoryTypeFile() );
953
if (saveToCurrentProfile)
955
Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session);
959
case HistorySizeDialog::NoHistory:
960
profile->setProperty(Profile::HistoryMode , Profile::DisableHistory);
962
case HistorySizeDialog::FixedSizeHistory:
963
profile->setProperty(Profile::HistoryMode , Profile::FixedSizeHistory);
964
profile->setProperty(Profile::HistorySize , lines);
966
case HistorySizeDialog::UnlimitedHistory:
967
profile->setProperty(Profile::HistoryMode , Profile::UnlimitedHistory);
970
SessionManager::instance()->changeProfile(profile, profile->setProperties());
974
void SessionController::saveHistory()
976
SessionTask* task = new SaveHistoryTask(this);
977
task->setAutoDelete(true);
978
task->addSession( _session );
982
void SessionController::clearHistory()
984
_session->clearHistory();
985
_view->updateImage(); // To reset view scrollbar
988
void SessionController::clearHistoryAndReset()
990
Emulation* emulation = _session->emulation();
996
void SessionController::increaseTextSize()
998
QFont font = _view->getVTFont();
999
font.setPointSizeF(font.pointSizeF()+1);
1000
_view->setVTFont(font);
1002
//TODO - Save this setting as a session default
1005
void SessionController::decreaseTextSize()
1007
static const qreal MinimumFontSize = 6;
1009
QFont font = _view->getVTFont();
1010
font.setPointSizeF( qMax(font.pointSizeF()-1,MinimumFontSize) );
1011
_view->setVTFont(font);
1013
//TODO - Save this setting as a session default
1016
void SessionController::monitorActivity(bool monitor)
1018
_session->setMonitorActivity(monitor);
1020
void SessionController::monitorSilence(bool monitor)
1022
_session->setMonitorSilence(monitor);
1024
void SessionController::updateSessionIcon()
1026
// Visualize that the session is broadcasting to others
1027
if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
1028
// Master Mode: set different icon, to warn the user to be careful
1029
setIcon(KIcon("emblem-important"));
1032
// Not in Master Mode: use normal icon
1033
setIcon( _sessionIcon );
1036
void SessionController::sessionTitleChanged()
1038
if ( _sessionIconName != _session->iconName() )
1040
_sessionIconName = _session->iconName();
1041
_sessionIcon = KIcon( _sessionIconName );
1042
updateSessionIcon();
1045
QString title = _session->title(Session::DisplayedTitleRole);
1047
// special handling for the "%w" marker which is replaced with the
1048
// window title set by the shell
1049
title.replace("%w",_session->userTitle());
1050
// special handling for the "%#" marker which is replaced with the
1051
// number of the shell
1052
title.replace("%#",QString::number(_session->sessionId()));
1054
if ( title.isEmpty() )
1055
title = _session->title(Session::NameRole);
1060
void SessionController::showDisplayContextMenu(const QPoint& position)
1062
// needed to make sure the popup menu is available, even if a hosting
1063
// application did not merge our GUI.
1066
if (!clientBuilder())
1068
setClientBuilder(new KXMLGUIBuilder(_view));
1071
KXMLGUIFactory* factory = new KXMLGUIFactory(clientBuilder(), this);
1072
factory->addClient(this);
1073
//kDebug(1211) << "Created xmlgui factory" << factory;
1076
QMenu* popup = qobject_cast<QMenu*>(factory()->container("session-popup-menu",this));
1079
// prepend content-specific actions such as "Open Link", "Copy Email Address" etc.
1080
QList<QAction*> contentActions = _view->filterActions(position);
1081
QAction* contentSeparator = new QAction(popup);
1082
contentSeparator->setSeparator(true);
1083
contentActions << contentSeparator;
1085
_preventClose = true;
1087
popup->insertActions(popup->actions().value(0,0),contentActions);
1088
QAction* chosen = popup->exec( _view->mapToGlobal(position) );
1090
// remove content-specific actions, unless the close action was chosen
1091
// in which case the popup menu will be partially destroyed at this point
1092
foreach(QAction* action,contentActions)
1093
popup->removeAction(action);
1094
delete contentSeparator;
1096
_preventClose = false;
1098
if (chosen && chosen->objectName() == "close-session")
1103
kWarning() << "Unable to display popup menu for session"
1104
<< _session->title(Session::NameRole)
1105
<< ", no GUI factory available to build the popup.";
1109
void SessionController::sessionStateChanged(int state)
1111
if ( state == _previousState )
1114
_previousState = state;
1116
// TODO - Replace the icon choices below when suitable icons for silence and activity
1118
if ( state == NOTIFYACTIVITY )
1120
if (_activityIcon.isNull())
1122
_activityIcon = KIcon("dialog-information");
1125
setIcon(_activityIcon);
1127
else if ( state == NOTIFYSILENCE )
1129
if (_silenceIcon.isNull())
1131
_silenceIcon = KIcon("dialog-information");
1134
setIcon(_silenceIcon);
1136
else if ( state == NOTIFYNORMAL )
1138
if ( _sessionIconName != _session->iconName() )
1140
_sessionIconName = _session->iconName();
1141
_sessionIcon = KIcon( _sessionIconName );
1144
updateSessionIcon();
1148
void SessionController::zmodemDownload()
1150
QString zmodem = KGlobal::dirs()->findExe("rz");
1151
if(zmodem.isEmpty()) {
1152
zmodem = KGlobal::dirs()->findExe("lrz");
1154
if(!zmodem.isEmpty()) {
1155
const QString path = KFileDialog::getExistingDirectory(
1157
i18n("Save ZModem Download to..."));
1159
if(!path.isEmpty()) {
1160
_session->startZModem(zmodem, path, QStringList());
1165
KMessageBox::error(_view,
1166
i18n("<p>A ZModem file transfer attempt has been detected, "
1167
"but no suitable ZModem software was found on this system.</p>"
1168
"<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
1170
_session->cancelZModem();
1174
void SessionController::zmodemUpload()
1176
if(_session->isZModemBusy()) {
1177
KMessageBox::sorry(_view,
1178
i18n("<p>The current session already has a ZModem file transfer in progress.</p>"));
1181
QString zmodem = KGlobal::dirs()->findExe("sz");
1182
if(zmodem.isEmpty()) {
1183
zmodem = KGlobal::dirs()->findExe("lsz");
1185
if(zmodem.isEmpty()) {
1186
KMessageBox::sorry(_view,
1187
i18n("<p>No suitable ZModem software was found on this system.</p>"
1188
"<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
1192
QStringList files = KFileDialog::getOpenFileNames(KUrl(), QString(), _view,
1193
i18n("Select Files for ZModem Upload"));
1194
if(!files.isEmpty()) {
1195
_session->startZModem(zmodem, QString(), files);
1199
bool SessionController::isKonsolePart() const
1201
// Check to see if we are being called from Konsole or a KPart
1202
if (QString(qApp->metaObject()->className()) == "Konsole::Application")
1208
SessionTask::SessionTask(QObject* parent)
1210
, _autoDelete(false)
1213
void SessionTask::setAutoDelete(bool enable)
1215
_autoDelete = enable;
1217
bool SessionTask::autoDelete() const
1221
void SessionTask::addSession(Session* session)
1223
_sessions << session;
1225
QList<SessionPtr> SessionTask::sessions() const
1230
SaveHistoryTask::SaveHistoryTask(QObject* parent)
1231
: SessionTask(parent)
1234
SaveHistoryTask::~SaveHistoryTask()
1238
void SaveHistoryTask::execute()
1240
QListIterator<SessionPtr> iter(sessions());
1242
// TODO - think about the UI when saving multiple history sessions, if there are more than two or
1243
// three then providing a URL for each one will be tedious
1245
// TODO - show a warning ( preferably passive ) if saving the history output fails
1248
KFileDialog* dialog = new KFileDialog( QString(":konsole") /* check this */,
1249
QString(), QApplication::activeWindow() );
1250
dialog->setOperationMode(KFileDialog::Saving);
1251
dialog->setConfirmOverwrite(true);
1253
QStringList mimeTypes;
1254
mimeTypes << "text/plain";
1255
mimeTypes << "text/html";
1256
dialog->setMimeFilter(mimeTypes,"text/plain");
1258
// iterate over each session in the task and display a dialog to allow the user to choose where
1259
// to save that session's history.
1260
// then start a KIO job to transfer the data from the history to the chosen URL
1261
while ( iter.hasNext() )
1263
SessionPtr session = iter.next();
1265
dialog->setCaption( i18n("Save Output From %1",session->title(Session::NameRole)) );
1267
int result = dialog->exec();
1269
if ( result != QDialog::Accepted )
1272
KUrl url = dialog->selectedUrl();
1274
if ( !url.isValid() )
1275
{ // UI: Can we make this friendlier?
1276
KMessageBox::sorry( 0 , i18n("%1 is an invalid URL, the output could not be saved.",url.url()) );
1280
KIO::TransferJob* job = KIO::put( url,
1281
-1, // no special permissions
1282
// overwrite existing files
1283
// do not resume an existing transfer
1284
// show progress information only for remote
1286
KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags)
1287
// a better solution would be to show progress
1288
// information after a certain period of time
1289
// instead, since the overall speed of transfer
1290
// depends on factors other than just the protocol
1296
jobInfo.session = session;
1297
jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem
1298
// lastLineFetched is used to keep track of how much of the history
1299
// has already been sent, and where the next request should continue
1301
// this is set to -1 to indicate the job has just been started
1303
if ( dialog->currentMimeFilter() == "text/html" )
1304
jobInfo.decoder = new HTMLDecoder();
1306
jobInfo.decoder = new PlainTextDecoder();
1308
_jobSession.insert(job,jobInfo);
1310
connect( job , SIGNAL(dataReq(KIO::Job*,QByteArray&)),
1311
this, SLOT(jobDataRequested(KIO::Job*,QByteArray&)) );
1312
connect( job , SIGNAL(result(KJob*)),
1313
this, SLOT(jobResult(KJob*)) );
1316
dialog->deleteLater();
1318
void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data)
1320
// TODO - Report progress information for the job
1322
// PERFORMANCE: Do some tests and tweak this value to get faster saving
1323
const int LINES_PER_REQUEST = 500;
1325
SaveJob& info = _jobSession[job];
1327
// transfer LINES_PER_REQUEST lines from the session's history
1328
// to the save location
1331
// note: when retrieving lines from the emulation,
1332
// the first line is at index 0.
1334
int sessionLines = info.session->emulation()->lineCount();
1336
if ( sessionLines-1 == info.lastLineFetched )
1337
return; // if there is no more data to transfer then stop the job
1339
int copyUpToLine = qMin( info.lastLineFetched + LINES_PER_REQUEST ,
1342
QTextStream stream(&data,QIODevice::ReadWrite);
1343
info.decoder->begin(&stream);
1344
info.session->emulation()->writeToStream( info.decoder , info.lastLineFetched+1 , copyUpToLine );
1345
info.decoder->end();
1347
// if there are still more lines to process after this request
1348
// then insert a new line character
1349
// to ensure that the next block of lines begins on a new line
1351
// FIXME - There is still an extra new-line at the end of the save data.
1352
if ( copyUpToLine <= sessionLines-1 )
1358
info.lastLineFetched = copyUpToLine;
1361
void SaveHistoryTask::jobResult(KJob* job)
1365
KMessageBox::sorry( 0 , i18n("A problem occurred when saving the output.\n%1",job->errorString()) );
1368
TerminalCharacterDecoder * decoder = _jobSession[job].decoder;
1370
_jobSession.remove(job);
1374
// notify the world that the task is done
1375
emit completed(true);
1380
void SearchHistoryTask::addScreenWindow( Session* session , ScreenWindow* searchWindow )
1382
_windows.insert(session,searchWindow);
1384
void SearchHistoryTask::execute()
1386
QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows);
1388
while ( iter.hasNext() )
1391
executeOnScreenWindow( iter.key() , iter.value() );
1395
void SearchHistoryTask::executeOnScreenWindow( SessionPtr session , ScreenWindowPtr window )
1397
Q_ASSERT( session );
1400
Emulation* emulation = session->emulation();
1402
int selectionColumn = 0;
1403
int selectionLine = 0;
1405
window->getSelectionEnd(selectionColumn , selectionLine);
1407
if ( !_regExp.isEmpty() )
1410
const bool forwards = ( _direction == ForwardsSearch );
1411
int startLine = selectionLine + window->currentLine() + ( forwards ? 1 : -1 );
1412
// Temporary fix for #205495
1413
if (startLine < 0) startLine = 0;
1414
const int lastLine = window->lineCount() - 1;
1417
//text stream to read history into string for pattern or regular expression searching
1418
QTextStream searchStream(&string);
1420
PlainTextDecoder decoder;
1421
decoder.setRecordLinePositions(true);
1423
//setup first and last lines depending on search direction
1424
int line = startLine;
1426
//read through and search history in blocks of 10K lines.
1427
//this balances the need to retrieve lots of data from the history each time
1428
//(for efficient searching)
1429
//without using silly amounts of memory if the history is very large.
1430
const int maxDelta = qMin(window->lineCount(),10000);
1431
int delta = forwards ? maxDelta : -maxDelta;
1434
bool hasWrapped = false; // set to true when we reach the top/bottom
1435
// of the output and continue from the other
1438
//loop through history in blocks of <delta> lines.
1441
// ensure that application does not appear to hang
1442
// if searching through a lengthy output
1443
QApplication::processEvents();
1445
// calculate lines to search in this iteration
1448
if ( endLine == lastLine )
1450
else if ( endLine == 0 )
1456
endLine = qMin( startLine , endLine );
1458
endLine = qMax( startLine , endLine );
1464
if ( endLine > lastLine )
1468
} else if ( endLine < 0 )
1475
decoder.begin(&searchStream);
1476
emulation->writeToStream(&decoder, qMin(endLine,line) , qMax(endLine,line) );
1479
// line number search below assumes that the buffer ends with a new-line
1480
string.append('\n');
1484
pos = string.indexOf(_regExp);
1486
pos = string.lastIndexOf(_regExp);
1488
//if a match is found, position the cursor on that line and update the screen
1492
QList<int> linePositions = decoder.linePositions();
1493
while (newLines < linePositions.count() && linePositions[newLines] <= pos)
1496
// ignore the new line at the start of the buffer
1499
int findPos = qMin(line,endLine) + newLines;
1501
highlightResult(window,findPos);
1503
emit completed(true);
1508
//clear the current block of text and move to the next one
1512
} while ( startLine != endLine );
1514
// if no match was found, clear selection to indicate this
1515
window->clearSelection();
1516
window->notifyOutputChanged();
1519
emit completed(false);
1521
void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos)
1523
//work out how many lines into the current block of text the search result was found
1524
//- looks a little painful, but it only has to be done once per search.
1526
//kDebug(1211) << "Found result at line " << findPos;
1528
//update display to show area of history containing selection
1529
window->scrollTo(findPos);
1530
window->setSelectionStart( 0 , findPos - window->currentLine() , false );
1531
window->setSelectionEnd( window->columnCount() , findPos - window->currentLine() );
1532
window->setTrackOutput(false);
1533
window->notifyOutputChanged();
1536
SearchHistoryTask::SearchHistoryTask(QObject* parent)
1537
: SessionTask(parent)
1538
, _direction(ForwardsSearch)
1542
void SearchHistoryTask::setSearchDirection( SearchDirection direction )
1544
_direction = direction;
1546
SearchHistoryTask::SearchDirection SearchHistoryTask::searchDirection() const
1550
void SearchHistoryTask::setRegExp(const QRegExp& expression)
1552
_regExp = expression;
1554
QRegExp SearchHistoryTask::regExp() const
1559
#include "SessionController.moc"