2 Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 #include "SessionController.h"
24 #include <QtGui/QApplication>
31 #include <KInputDialog>
36 #include <KToggleAction>
38 #include <KXMLGUIFactory>
39 #include <KXMLGUIBuilder>
41 #include <kcodecaction.h>
42 #include <kdeversion.h>
45 #include "EditProfileDialog.h"
46 #include "CopyInputDialog.h"
47 #include "Emulation.h"
50 #include "IncrementalSearchBar.h"
51 #include "ScreenWindow.h"
53 #include "ProfileList.h"
54 #include "TerminalDisplay.h"
55 #include "SessionManager.h"
57 // for SaveHistoryTask
58 #include <KFileDialog>
61 #include <KMessageBox>
62 #include "TerminalCharacterDecoder.h"
65 using namespace Konsole
;
67 KIcon
SessionController::_activityIcon
;
68 KIcon
SessionController::_silenceIcon
;
69 QSet
<SessionController
*> SessionController::_allControllers
;
70 QPointer
<SearchHistoryThread
> SearchHistoryTask::_thread
;
71 int SessionController::_lastControllerId
;
73 SessionController::SessionController(Session
* session
, TerminalDisplay
* view
, QObject
* parent
)
74 : ViewProperties(parent
)
83 , _searchToggleAction(0)
85 , _findPreviousAction(0)
86 , _urlFilterUpdateRequired(false)
88 , _changeProfileMenu(0)
89 , _listenForScreenWindowUpdates(false)
90 , _preventClose(false)
92 _allControllers
.insert(this);
97 // handle user interface related to session (menus etc.)
100 setXMLFile("konsole/partui.rc");
102 setXMLFile("konsole/sessionui.rc");
106 actionCollection()->addAssociatedWidget(view
);
107 foreach (QAction
* action
, actionCollection()->actions())
108 action
->setShortcutContext(Qt::WidgetWithChildrenShortcut
);
110 setIdentifier(++_lastControllerId
);
111 sessionTitleChanged();
113 view
->installEventFilter(this);
115 // listen for session resize requests
116 connect( _session
, SIGNAL(resizeRequest(const QSize
&)) , this ,
117 SLOT(sessionResizeRequest(const QSize
&)) );
119 // listen for popup menu requests
120 connect( _view
, SIGNAL(configureRequest(QPoint
)) , this,
121 SLOT(showDisplayContextMenu(QPoint
)) );
123 // move view to newest output when keystrokes occur
124 connect( _view
, SIGNAL(keyPressedSignal(QKeyEvent
*)) , this ,
125 SLOT(trackOutput(QKeyEvent
*)) );
127 // listen to activity / silence notifications from session
128 connect( _session
, SIGNAL(stateChanged(int)) , this ,
129 SLOT(sessionStateChanged(int) ));
130 // listen to title and icon changes
131 connect( _session
, SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) );
133 // listen for color changes
134 connect( _session
, SIGNAL(changeBackgroundColorRequest(QColor
)) , _view
, SLOT(setBackgroundColor(QColor
)) );
135 connect( _session
, SIGNAL(changeForegroundColorRequest(QColor
)) , _view
, SLOT(setForegroundColor(QColor
)) );
137 // update the title when the session starts
138 connect( _session
, SIGNAL(started()) , this , SLOT(snapshot()) );
140 // listen for output changes to set activity flag
141 connect( _session
->emulation() , SIGNAL(outputChanged()) , this ,
142 SLOT(fireActivity()) );
144 // listen for flow control status changes
145 connect( _session
, SIGNAL(flowControlEnabledChanged(bool)) , _view
,
146 SLOT(setFlowControlWarningEnabled(bool)) );
147 _view
->setFlowControlWarningEnabled(_session
->flowControlEnabled());
149 // take a snapshot of the session state every so often when
150 // user activity occurs
152 // the timer is owned by the session so that it will be destroyed along
154 QTimer
* activityTimer
= new QTimer(_session
);
155 activityTimer
->setSingleShot(true);
156 activityTimer
->setInterval(2000);
157 connect( _view
, SIGNAL(keyPressedSignal(QKeyEvent
*)) , activityTimer
, SLOT(start()) );
158 connect( activityTimer
, SIGNAL(timeout()) , this , SLOT(snapshot()) );
161 void SessionController::updateSearchFilter()
165 Q_ASSERT( searchBar() && searchBar()->isVisible() );
167 _view
->processFilters();
171 SessionController::~SessionController()
174 _view
->setScreenWindow(0);
176 _allControllers
.remove(this);
178 void SessionController::trackOutput(QKeyEvent
* event
)
180 Q_ASSERT( _view
->screenWindow() );
182 // jump to the end of the scrollback buffer unless the key pressed
183 // is one of the three main modifiers, as these are used to select
184 // the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection)
185 switch (event
->key())
188 case Qt::Key_Control
:
192 _view
->screenWindow()->setTrackOutput(true);
195 void SessionController::requireUrlFilterUpdate()
197 // this method is called every time the screen window's output changes, so do not
198 // do anything expensive here.
200 _urlFilterUpdateRequired
= true;
202 void SessionController::snapshot()
204 Q_ASSERT( _session
!= 0 );
206 QString title
= _session
->getDynamicTitle();
207 title
= title
.simplified();
209 // crude indicator when the session is broadcasting to others
210 if (_copyToGroup
&& _copyToGroup
->sessions().count() > 1)
214 if ( !title
.isEmpty() )
215 _session
->setTitle(Session::DisplayedTitleRole
,title
);
217 _session
->setTitle(Session::DisplayedTitleRole
,_session
->title(Session::NameRole
));
220 QString
SessionController::currentDir() const
222 return _session
->currentWorkingDirectory();
225 KUrl
SessionController::url() const
227 return _session
->getUrl();
230 void SessionController::rename()
235 void SessionController::openUrl( const KUrl
& url
)
237 // handle local paths
238 if ( url
.isLocalFile() )
240 QString path
= url
.toLocalFile();
241 _session
->emulation()->sendText("cd " + KShell::quoteArg(path
) + '\r');
243 else if ( url
.protocol() == "ssh" )
245 _session
->emulation()->sendText("ssh ");
248 _session
->emulation()->sendText(url
.user() + '@');
250 _session
->emulation()->sendText(url
.host() + '\r');
254 //TODO Implement handling for other Url types
255 kWarning(1211) << "Unable to open bookmark at url" << url
<< ", I do not know"
256 << " how to handle the protocol " << url
.protocol();
260 bool SessionController::eventFilter(QObject
* watched
, QEvent
* event
)
262 if ( watched
== _view
)
264 if ( event
->type() == QEvent::FocusIn
)
266 // notify the world that the view associated with this session has been focused
267 // used by the view manager to update the title of the MainWindow widget containing the view
270 // when the view is focused, set bell events from the associated session to be delivered
271 // by the focused view
273 // first, disconnect any other views which are listening for bell signals from the session
274 disconnect( _session
, SIGNAL(bellRequest(const QString
&)) , 0 , 0 );
275 // second, connect the newly focused view to listen for the session's bell signal
276 connect( _session
, SIGNAL(bellRequest(const QString
&)) ,
277 _view
, SLOT(bell(const QString
&)) );
279 // when a mouse move is received, create the URL filter and listen for output changes if
280 // it has not already been created. If it already exists, then update only if the output
281 // has changed since the last update ( _urlFilterUpdateRequired == true )
283 // also check that no mouse buttons are pressed since the URL filter only applies when
284 // the mouse is hovering over the view
285 if ( event
->type() == QEvent::MouseMove
&&
286 (!_viewUrlFilter
|| _urlFilterUpdateRequired
) &&
287 ((QMouseEvent
*)event
)->buttons() == Qt::NoButton
)
289 if ( _view
->screenWindow() && !_viewUrlFilter
)
291 connect( _view
->screenWindow() , SIGNAL(scrolled(int)) , this ,
292 SLOT(requireUrlFilterUpdate()) );
293 connect( _view
->screenWindow() , SIGNAL(outputChanged()) , this ,
294 SLOT(requireUrlFilterUpdate()) );
296 // install filter on the view to highlight URLs
297 _viewUrlFilter
= new UrlFilter();
298 _view
->filterChain()->addFilter( _viewUrlFilter
);
301 _view
->processFilters();
302 _urlFilterUpdateRequired
= false;
309 void SessionController::removeSearchFilter()
314 _view
->filterChain()->removeFilter(_searchFilter
);
315 delete _searchFilter
;
319 void SessionController::setSearchBar(IncrementalSearchBar
* searchBar
)
321 // disconnect the existing search bar
324 disconnect( this , 0 , _searchBar
, 0 );
325 disconnect( _searchBar
, 0 , this , 0 );
328 // remove any existing search filter
329 removeSearchFilter();
331 // connect new search bar
332 _searchBar
= searchBar
;
335 connect( _searchBar
, SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
336 connect( _searchBar
, SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
337 connect( _searchBar
, SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
338 connect( _searchBar
, SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) );
340 // if the search bar was previously active
341 // then re-enter search mode
342 searchHistory( _searchToggleAction
->isChecked() );
345 IncrementalSearchBar
* SessionController::searchBar() const
350 void SessionController::setShowMenuAction(QAction
* action
)
352 actionCollection()->addAction("show-menubar",action
);
355 void SessionController::setupActions()
358 KToggleAction
* toggleAction
= 0;
359 KActionCollection
* collection
= actionCollection();
362 action
= collection
->addAction("close-session");
363 action
->setIcon( KIcon("tab-close") );
364 action
->setText( i18n("&Close Tab") );
365 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_W
) );
366 connect( action
, SIGNAL(triggered()) , this , SLOT(closeSession()) );
369 action
= collection
->addAction("open-browser");
370 action
->setText( i18n("Open Browser Here") );
371 action
->setIcon( KIcon("system-file-manager") );
372 connect( action
, SIGNAL(triggered()), this, SLOT(openBrowser()) );
375 action
= collection
->addAction("copy");
376 action
->setIcon( KIcon("edit-copy") );
377 action
->setText( i18n("&Copy") );
378 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_C
) );
379 connect( action
, SIGNAL(triggered()) , this , SLOT(copy()) );
381 KAction
* pasteAction
= new KAction( i18n("&Paste") , this );
382 pasteAction
->setIcon( KIcon("edit-paste") );
384 KShortcut pasteShortcut
= pasteAction
->shortcut();
385 pasteShortcut
.setPrimary( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_V
) );
386 pasteShortcut
.setAlternate( QKeySequence(Qt::SHIFT
+Qt::Key_Insert
) );
387 pasteAction
->setShortcut(pasteShortcut
);
389 collection
->addAction("paste",pasteAction
);
391 connect( pasteAction
, SIGNAL(triggered()) , this , SLOT(paste()) );
393 action
= collection
->addAction("paste-selection");
394 action
->setText( i18n("Paste Selection") );
395 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_Insert
) );
396 connect( action
, SIGNAL(triggered()) , this , SLOT(pasteSelection()) );
399 action
= collection
->addAction("rename-session");
400 action
->setText( i18n("&Rename Tab...") );
401 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::ALT
+Qt::Key_S
) );
402 connect( action
, SIGNAL(triggered()) , this , SLOT(renameSession()) );
405 action
= collection
->addAction("copy-input-to");
406 action
->setText(i18n("Copy Input To..."));
407 connect( action
, SIGNAL(triggered()) , this , SLOT(copyInputTo()) );
409 // Clear and Clear+Reset
410 action
= collection
->addAction("clear");
411 action
->setText( i18n("C&lear Display") );
412 action
->setIcon( KIcon("edit-clear") );
413 connect( action
, SIGNAL(triggered()) , this , SLOT(clear()) );
415 action
= collection
->addAction("clear-and-reset");
416 action
->setText( i18n("Clear && Reset") );
417 action
->setIcon( KIcon("edit-clear-history") );
418 connect( action
, SIGNAL(triggered()) , this , SLOT(clearAndReset()) );
421 toggleAction
= new KToggleAction(i18n("Monitor for &Activity"),this);
422 toggleAction
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_A
) );
423 action
= collection
->addAction("monitor-activity",toggleAction
);
424 connect( action
, SIGNAL(toggled(bool)) , this , SLOT(monitorActivity(bool)) );
426 toggleAction
= new KToggleAction(i18n("Monitor for &Silence"),this);
427 toggleAction
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_I
) );
428 action
= collection
->addAction("monitor-silence",toggleAction
);
429 connect( action
, SIGNAL(toggled(bool)) , this , SLOT(monitorSilence(bool)) );
431 // Character Encoding
432 _codecAction
= new KCodecAction(i18n("Character Encoding"),this);
433 collection
->addAction("character-encoding",_codecAction
);
434 connect( _codecAction
->menu() , SIGNAL(aboutToShow()) , this , SLOT(updateCodecAction()) );
435 connect( _codecAction
, SIGNAL(triggered(QTextCodec
*)) , this , SLOT(changeCodec(QTextCodec
*)) );
438 action
= collection
->addAction("increase-text-size");
439 action
->setText( i18n("Increase Text Size") );
440 action
->setIcon( KIcon("zoom-in") );
441 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::Key_Plus
) );
442 connect( action
, SIGNAL(triggered()) , this , SLOT(increaseTextSize()) );
444 action
= collection
->addAction("decrease-text-size");
445 action
->setText( i18n("Decrease Text Size") );
446 action
->setIcon( KIcon("zoom-out") );
447 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::Key_Minus
) );
448 connect( action
, SIGNAL(triggered()) , this , SLOT(decreaseTextSize()) );
451 _searchToggleAction
= new KAction(i18n("Search Output..."),this);
452 _searchToggleAction
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_F
) );
453 _searchToggleAction
->setIcon( KIcon("edit-find") );
454 _searchToggleAction
->setCheckable(true);
455 action
= collection
->addAction("search-history" , _searchToggleAction
);
456 connect( action
, SIGNAL(toggled(bool)) , this , SLOT(searchHistory(bool)) );
458 _findNextAction
= collection
->addAction("find-next");
459 _findNextAction
->setIcon( KIcon("go-down-search") );
460 _findNextAction
->setText( i18n("Find Next") );
461 _findNextAction
->setShortcut( QKeySequence(Qt::Key_F3
) );
462 _findNextAction
->setEnabled(false);
463 connect( _findNextAction
, SIGNAL(triggered()) , this , SLOT(findNextInHistory()) );
465 _findPreviousAction
= collection
->addAction("find-previous");
466 _findPreviousAction
->setIcon( KIcon("go-up-search") );
467 _findPreviousAction
->setText( i18n("Find Previous") );
468 _findPreviousAction
->setShortcut( QKeySequence(Qt::SHIFT
+ Qt::Key_F3
) );
469 _findPreviousAction
->setEnabled(false);
470 connect( _findPreviousAction
, SIGNAL(triggered()) , this , SLOT(findPreviousInHistory()) );
472 action
= collection
->addAction("save-history");
473 action
->setText( i18n("Save Output...") );
474 action
->setIcon( KIcon("document-save-as") );
475 connect( action
, SIGNAL(triggered()) , this , SLOT(saveHistory()) );
477 action
= collection
->addAction("history-options");
478 action
->setText( i18n("Scrollback Options") );
479 action
->setIcon( KIcon("configure") );
480 connect( action
, SIGNAL(triggered()) , this , SLOT(showHistoryOptions()) );
482 action
= collection
->addAction("clear-history");
483 action
->setText( i18n("Clear Scrollback") );
484 connect( action
, SIGNAL(triggered()) , this , SLOT(clearHistory()) );
486 action
= collection
->addAction("clear-history-and-reset");
487 action
->setText( i18n("Clear Scrollback && Reset") );
488 action
->setShortcut( QKeySequence(Qt::CTRL
+Qt::SHIFT
+Qt::Key_X
) );
489 connect( action
, SIGNAL(triggered()) , this , SLOT(clearHistoryAndReset()) );
492 action
= collection
->addAction("edit-current-profile");
493 action
->setText( i18n("Edit Current Profile...") );
494 action
->setIcon( KIcon("document-properties") );
495 connect( action
, SIGNAL(triggered()) , this , SLOT(editCurrentProfile()) );
497 _changeProfileMenu
= new KMenu(i18n("Change Profile"),_view
);
498 collection
->addAction("change-profile",_changeProfileMenu
->menuAction());
499 connect( _changeProfileMenu
, SIGNAL(aboutToShow()) , this , SLOT(prepareChangeProfileMenu()) );
501 void SessionController::changeProfile(Profile::Ptr profile
)
503 SessionManager::instance()->setSessionProfile(_session
,profile
);
505 void SessionController::prepareChangeProfileMenu()
507 if ( _changeProfileMenu
->isEmpty() )
509 _profileList
= new ProfileList(false,this);
510 connect( _profileList
, SIGNAL(profileSelected(Profile::Ptr
)) ,
511 this , SLOT(changeProfile(Profile::Ptr
)) );
514 _changeProfileMenu
->clear();
515 _changeProfileMenu
->addActions(_profileList
->actions());
517 void SessionController::updateCodecAction()
519 _codecAction
->setCurrentCodec( QString(_session
->emulation()->codec()->name()) );
521 void SessionController::changeCodec(QTextCodec
* codec
)
523 _session
->setCodec(codec
);
526 void SessionController::editCurrentProfile()
528 EditProfileDialog
* dialog
= new EditProfileDialog( QApplication::activeWindow() );
530 dialog
->setProfile(SessionManager::instance()->sessionProfile(_session
));
533 void SessionController::renameSession()
535 QPointer
<Session
> guard(_session
);
537 const QString
& text
= KInputDialog::getText( i18n("Rename Tab") ,
538 i18n("Enter new tab text:") ,
539 _session
->tabTitleFormat(Session::LocalTabTitle
) ,
540 &ok
, QApplication::activeWindow() );
546 // renaming changes both the local and remote tab title formats, to save confusion over
547 // the tab title not changing if renaming the tab whilst the remote tab title format is
550 // The downside of this approach is that after renaming a tab manually, the ability to
551 // have separate formats for local and remote activities is lost
552 _session
->setTabTitleFormat(Session::LocalTabTitle
,text
);
553 _session
->setTabTitleFormat(Session::RemoteTabTitle
,text
);
555 // trigger an update of the tab text
559 void SessionController::saveSession()
561 Q_ASSERT(0); // not implemented yet
563 //SaveSessionDialog dialog(_view);
564 //int result = dialog.exec();
566 bool SessionController::confirmClose() const
568 if (_session
->isChildActive())
570 QString title
= _session
->childName();
572 // hard coded for now. In future make it possible for the user to specify which programs
573 // are ignored when considering whether to display a confirmation
574 QStringList ignoreList
;
575 ignoreList
<< QString(qgetenv("SHELL")).section('/',-1);
576 if (ignoreList
.contains(title
))
581 question
= i18n("A program is currently running in this session."
582 " Are you sure you want to close it?");
584 question
= i18n("The program '%1' is currently running in this session."
585 " Are you sure you want to close it?",title
);
587 int result
= KMessageBox::warningYesNo(_view
->window(),question
,i18n("Confirm Close"));
588 return (result
== KMessageBox::Yes
) ? true : false;
592 void SessionController::closeSession()
601 void SessionController::openBrowser()
603 new KRun(url(), QApplication::activeWindow());
606 void SessionController::copy()
608 _view
->copyClipboard();
611 void SessionController::paste()
613 _view
->pasteClipboard();
615 void SessionController::pasteSelection()
617 _view
->pasteSelection();
619 void SessionController::copyInputTo()
623 _copyToGroup
= new SessionGroup(this);
624 _copyToGroup
->addSession(_session
);
625 _copyToGroup
->setMasterStatus(_session
,true);
626 _copyToGroup
->setMasterMode(SessionGroup::CopyInputToAll
);
629 CopyInputDialog
* dialog
= new CopyInputDialog(_view
);
630 dialog
->setMasterSession(_session
);
632 QSet
<Session
*> currentGroup
= QSet
<Session
*>::fromList(_copyToGroup
->sessions());
633 currentGroup
.remove(_session
);
635 dialog
->setChosenSessions(currentGroup
);
637 QPointer
<Session
> guard(_session
);
638 int result
= dialog
->exec();
644 QSet
<Session
*> newGroup
= dialog
->chosenSessions();
645 newGroup
.remove(_session
);
647 QSet
<Session
*> completeGroup
= newGroup
| currentGroup
;
648 foreach(Session
* session
, completeGroup
)
650 if (newGroup
.contains(session
) && !currentGroup
.contains(session
))
651 _copyToGroup
->addSession(session
);
652 else if (!newGroup
.contains(session
) && currentGroup
.contains(session
))
653 _copyToGroup
->removeSession(session
);
661 void SessionController::clear()
663 Emulation
* emulation
= _session
->emulation();
665 emulation
->clearEntireScreen();
667 void SessionController::clearAndReset()
669 Emulation
* emulation
= _session
->emulation();
674 void SessionController::searchClosed()
676 _searchToggleAction
->toggle();
680 void SessionController::searchHistory()
686 void SessionController::listenForScreenWindowUpdates()
688 if (_listenForScreenWindowUpdates
)
691 connect( _view
->screenWindow() , SIGNAL(outputChanged()) , this ,
692 SLOT(updateSearchFilter()) );
693 connect( _view
->screenWindow() , SIGNAL(scrolled(int)) , this ,
694 SLOT(updateSearchFilter()) );
696 _listenForScreenWindowUpdates
= true;
699 // searchHistory() may be called either as a result of clicking a menu item or
700 // as a result of changing the search bar widget
701 void SessionController::searchHistory(bool showSearchBar
)
705 _searchBar
->setVisible(showSearchBar
);
709 removeSearchFilter();
711 listenForScreenWindowUpdates();
713 _searchFilter
= new RegExpFilter();
714 _view
->filterChain()->addFilter(_searchFilter
);
715 connect( _searchBar
, SIGNAL(searchChanged(const QString
&)) , this ,
716 SLOT(searchTextChanged(const QString
&)) );
718 // invoke search for matches for the current search text
719 const QString
& currentSearchText
= _searchBar
->searchText();
720 if (!currentSearchText
.isEmpty())
722 searchTextChanged(currentSearchText
);
725 setFindNextPrevEnabled(true);
729 setFindNextPrevEnabled(false);
731 disconnect( _searchBar
, SIGNAL(searchChanged(const QString
&)) , this ,
732 SLOT(searchTextChanged(const QString
&)) );
734 removeSearchFilter();
736 _view
->setFocus( Qt::ActiveWindowFocusReason
);
740 void SessionController::setFindNextPrevEnabled(bool enabled
)
742 _findNextAction
->setEnabled(enabled
);
743 _findPreviousAction
->setEnabled(enabled
);
745 void SessionController::searchTextChanged(const QString
& text
)
747 Q_ASSERT( _view
->screenWindow() );
749 if ( text
.isEmpty() )
750 _view
->screenWindow()->clearSelection();
752 // update search. this is called even when the text is
753 // empty to clear the view's filters
754 beginSearch(text
, SearchHistoryTask::ForwardsSearch
);
756 void SessionController::searchCompleted(bool success
)
759 _searchBar
->setFoundMatch(success
);
762 void SessionController::beginSearch(const QString
& text
, int direction
)
764 Q_ASSERT( _searchBar
);
765 Q_ASSERT( _searchFilter
);
767 Qt::CaseSensitivity caseHandling
= _searchBar
->matchCase() ? Qt::CaseSensitive
: Qt::CaseInsensitive
;
768 QRegExp::PatternSyntax syntax
= _searchBar
->matchRegExp() ? QRegExp::RegExp
: QRegExp::FixedString
;
770 QRegExp
regExp( text
.trimmed() , caseHandling
, syntax
);
771 _searchFilter
->setRegExp(regExp
);
773 if ( !regExp
.isEmpty() )
775 SearchHistoryTask
* task
= new SearchHistoryTask(this);
777 connect( task
, SIGNAL(completed(bool)) , this , SLOT(searchCompleted(bool)) );
779 task
->setRegExp(regExp
);
780 task
->setSearchDirection( (SearchHistoryTask::SearchDirection
)direction
);
781 task
->setAutoDelete(true);
782 task
->addScreenWindow( _session
, _view
->screenWindow() );
786 _view
->processFilters();
788 void SessionController::highlightMatches(bool highlight
)
792 _view
->filterChain()->addFilter(_searchFilter
);
793 _view
->processFilters();
797 _view
->filterChain()->removeFilter(_searchFilter
);
802 void SessionController::findNextInHistory()
804 Q_ASSERT( _searchBar
);
805 Q_ASSERT( _searchFilter
);
807 beginSearch(_searchBar
->searchText(),SearchHistoryTask::ForwardsSearch
);
809 void SessionController::findPreviousInHistory()
811 Q_ASSERT( _searchBar
);
812 Q_ASSERT( _searchFilter
);
814 beginSearch(_searchBar
->searchText(),SearchHistoryTask::BackwardsSearch
);
816 void SessionController::showHistoryOptions()
818 HistorySizeDialog
* dialog
= new HistorySizeDialog( QApplication::activeWindow() );
819 const HistoryType
& currentHistory
= _session
->historyType();
821 if ( currentHistory
.isEnabled() )
823 if ( currentHistory
.isUnlimited() )
824 dialog
->setMode( HistorySizeDialog::UnlimitedHistory
);
827 dialog
->setMode( HistorySizeDialog::FixedSizeHistory
);
828 dialog
->setLineCount( currentHistory
.maximumLineCount() );
832 dialog
->setMode( HistorySizeDialog::NoHistory
);
834 connect( dialog
, SIGNAL(optionsChanged(int,int)) ,
835 this , SLOT(scrollBackOptionsChanged(int,int)) );
839 void SessionController::sessionResizeRequest(const QSize
& size
)
841 kDebug(1211) << "View resize requested to " << size
;
842 _view
->setSize(size
.width(),size
.height());
844 void SessionController::scrollBackOptionsChanged( int mode
, int lines
)
848 case HistorySizeDialog::NoHistory
:
849 _session
->setHistoryType( HistoryTypeNone() );
851 case HistorySizeDialog::FixedSizeHistory
:
852 _session
->setHistoryType( HistoryTypeBuffer(lines
) );
854 case HistorySizeDialog::UnlimitedHistory
:
855 _session
->setHistoryType( HistoryTypeFile() );
860 void SessionController::saveHistory()
862 SessionTask
* task
= new SaveHistoryTask(this);
863 task
->setAutoDelete(true);
864 task
->addSession( _session
);
867 void SessionController::clearHistory()
869 _session
->clearHistory();
871 void SessionController::clearHistoryAndReset()
876 void SessionController::increaseTextSize()
878 QFont font
= _view
->getVTFont();
879 font
.setPointSize(font
.pointSize()+1);
880 _view
->setVTFont(font
);
882 //TODO - Save this setting as a session default
884 void SessionController::decreaseTextSize()
886 static const int MinimumFontSize
= 6;
888 QFont font
= _view
->getVTFont();
889 font
.setPointSize( qMax(font
.pointSize()-1,MinimumFontSize
) );
890 _view
->setVTFont(font
);
892 //TODO - Save this setting as a session default
894 void SessionController::monitorActivity(bool monitor
)
896 _session
->setMonitorActivity(monitor
);
898 void SessionController::monitorSilence(bool monitor
)
900 _session
->setMonitorSilence(monitor
);
902 void SessionController::sessionTitleChanged()
904 if ( _sessionIconName
!= _session
->iconName() )
906 _sessionIconName
= _session
->iconName();
907 _sessionIcon
= KIcon( _sessionIconName
);
908 setIcon( _sessionIcon
);
911 QString title
= _session
->title(Session::DisplayedTitleRole
);
913 // special handling for the "%w" marker which is replaced with the
914 // window title set by the shell
915 title
.replace("%w",_session
->userTitle());
916 // special handling for the "%#" marker which is replaced with the
917 // number of the shell
918 title
.replace("%#",QString::number(_session
->sessionId()));
920 if ( title
.isEmpty() )
921 title
= _session
->title(Session::NameRole
);
926 void SessionController::showDisplayContextMenu(const QPoint
& position
)
928 // needed to make sure the popup menu is available, even if a hosting
929 // application did not merge our GUI.
932 if (!clientBuilder())
933 setClientBuilder(new KXMLGUIBuilder(_view
));
935 KXMLGUIFactory
* factory
= new KXMLGUIFactory(clientBuilder(), this);
936 factory
->addClient(this);
939 QMenu
* popup
= qobject_cast
<QMenu
*>(factory()->container("session-popup-menu",this));
942 // prepend content-specific actions such as "Open Link", "Copy Email Address" etc.
943 QList
<QAction
*> contentActions
= _view
->filterActions(position
);
944 QAction
* contentSeparator
= new QAction(popup
);
945 contentSeparator
->setSeparator(true);
946 contentActions
<< contentSeparator
;
948 _preventClose
= true;
950 popup
->insertActions(popup
->actions().value(0,0),contentActions
);
951 QAction
* chosen
= popup
->exec( _view
->mapToGlobal(position
) );
953 // remove content-specific actions, unless the close action was chosen
954 // in which case the popup menu will be partially destroyed at this point
955 foreach(QAction
* action
,contentActions
)
956 popup
->removeAction(action
);
957 delete contentSeparator
;
959 _preventClose
= false;
961 if (chosen
&& chosen
->objectName() == "close-session")
966 kWarning() << "Unable to display popup menu for session"
967 << _session
->title(Session::NameRole
)
968 << ", no GUI factory available to build the popup.";
972 void SessionController::sessionStateChanged(int state
)
974 if ( state
== _previousState
)
977 _previousState
= state
;
979 // TODO - Replace the icon choices below when suitable icons for silence and activity
981 if ( state
== NOTIFYACTIVITY
)
983 if (_activityIcon
.isNull())
985 _activityIcon
= KIcon("dialog-information");
988 setIcon(_activityIcon
);
990 else if ( state
== NOTIFYSILENCE
)
992 if (_silenceIcon
.isNull())
994 _silenceIcon
= KIcon("dialog-information");
997 setIcon(_silenceIcon
);
999 else if ( state
== NOTIFYNORMAL
)
1001 if ( _sessionIconName
!= _session
->iconName() )
1003 _sessionIconName
= _session
->iconName();
1004 _sessionIcon
= KIcon( _sessionIconName
);
1007 setIcon( _sessionIcon
);
1011 SessionTask::SessionTask(QObject
* parent
)
1013 , _autoDelete(false)
1016 void SessionTask::setAutoDelete(bool enable
)
1018 _autoDelete
= enable
;
1020 bool SessionTask::autoDelete() const
1024 void SessionTask::addSession(Session
* session
)
1026 _sessions
<< session
;
1028 QList
<SessionPtr
> SessionTask::sessions() const
1033 SaveHistoryTask::SaveHistoryTask(QObject
* parent
)
1034 : SessionTask(parent
)
1037 SaveHistoryTask::~SaveHistoryTask()
1041 void SaveHistoryTask::execute()
1043 QListIterator
<SessionPtr
> iter(sessions());
1045 // TODO - think about the UI when saving multiple history sessions, if there are more than two or
1046 // three then providing a URL for each one will be tedious
1048 // TODO - show a warning ( preferably passive ) if saving the history output fails
1051 KFileDialog
* dialog
= new KFileDialog( QString(":konsole") /* check this */,
1052 QString(), QApplication::activeWindow() );
1053 dialog
->setOperationMode(KFileDialog::Saving
);
1054 dialog
->setConfirmOverwrite(true);
1056 QStringList mimeTypes
;
1057 mimeTypes
<< "text/plain";
1058 mimeTypes
<< "text/html";
1059 dialog
->setMimeFilter(mimeTypes
,"text/plain");
1061 // iterate over each session in the task and display a dialog to allow the user to choose where
1062 // to save that session's history.
1063 // then start a KIO job to transfer the data from the history to the chosen URL
1064 while ( iter
.hasNext() )
1066 SessionPtr session
= iter
.next();
1068 dialog
->setCaption( i18n("Save Output From %1",session
->title(Session::NameRole
)) );
1070 int result
= dialog
->exec();
1072 if ( result
!= QDialog::Accepted
)
1075 KUrl url
= dialog
->selectedUrl();
1077 if ( !url
.isValid() )
1078 { // UI: Can we make this friendlier?
1079 KMessageBox::sorry( 0 , i18n("%1 is an invalid URL, the output could not be saved.",url
.url()) );
1083 KIO::TransferJob
* job
= KIO::put( url
,
1084 -1, // no special permissions
1085 // overwrite existing files
1086 // do not resume an existing transfer
1087 // show progress information only for remote
1089 KIO::Overwrite
| (url
.isLocalFile() ? KIO::HideProgressInfo
: KIO::DefaultFlags
)
1090 // a better solution would be to show progress
1091 // information after a certain period of time
1092 // instead, since the overall speed of transfer
1093 // depends on factors other than just the protocol
1099 jobInfo
.session
= session
;
1100 jobInfo
.lastLineFetched
= -1; // when each request for data comes in from the KIO subsystem
1101 // lastLineFetched is used to keep track of how much of the history
1102 // has already been sent, and where the next request should continue
1104 // this is set to -1 to indicate the job has just been started
1106 if ( dialog
->currentMimeFilter() == "text/html" )
1107 jobInfo
.decoder
= new HTMLDecoder();
1109 jobInfo
.decoder
= new PlainTextDecoder();
1111 _jobSession
.insert(job
,jobInfo
);
1113 connect( job
, SIGNAL(dataReq(KIO::Job
*,QByteArray
&)),
1114 this, SLOT(jobDataRequested(KIO::Job
*,QByteArray
&)) );
1115 connect( job
, SIGNAL(result(KJob
*)),
1116 this, SLOT(jobResult(KJob
*)) );
1119 dialog
->deleteLater();
1121 void SaveHistoryTask::jobDataRequested(KIO::Job
* job
, QByteArray
& data
)
1123 // TODO - Report progress information for the job
1125 // PERFORMANCE: Do some tests and tweak this value to get faster saving
1126 const int LINES_PER_REQUEST
= 500;
1128 SaveJob
& info
= _jobSession
[job
];
1130 // transfer LINES_PER_REQUEST lines from the session's history
1131 // to the save location
1134 // note: when retrieving lines from the emulation,
1135 // the first line is at index 0.
1137 int sessionLines
= info
.session
->emulation()->lineCount();
1139 if ( sessionLines
-1 == info
.lastLineFetched
)
1140 return; // if there is no more data to transfer then stop the job
1142 int copyUpToLine
= qMin( info
.lastLineFetched
+ LINES_PER_REQUEST
,
1145 QTextStream
stream(&data
,QIODevice::ReadWrite
);
1146 info
.decoder
->begin(&stream
);
1147 info
.session
->emulation()->writeToStream( info
.decoder
, info
.lastLineFetched
+1 , copyUpToLine
);
1148 info
.decoder
->end();
1150 // if there are still more lines to process after this request
1151 // then insert a new line character
1152 // to ensure that the next block of lines begins on a new line
1154 // FIXME - There is still an extra new-line at the end of the save data.
1155 if ( copyUpToLine
<= sessionLines
-1 )
1161 info
.lastLineFetched
= copyUpToLine
;
1164 void SaveHistoryTask::jobResult(KJob
* job
)
1168 KMessageBox::sorry( 0 , i18n("A problem occurred when saving the output.\n%1",job
->errorString()) );
1171 SaveJob
& info
= _jobSession
[job
];
1173 _jobSession
.remove(job
);
1175 delete info
.decoder
;
1177 // notify the world that the task is done
1178 emit
completed(true);
1183 void SearchHistoryTask::addScreenWindow( Session
* session
, ScreenWindow
* searchWindow
)
1185 _windows
.insert(session
,searchWindow
);
1187 void SearchHistoryTask::execute()
1189 QMapIterator
< SessionPtr
, ScreenWindowPtr
> iter(_windows
);
1191 while ( iter
.hasNext() )
1194 executeOnScreenWindow( iter
.key() , iter
.value() );
1198 void SearchHistoryTask::executeOnScreenWindow( SessionPtr session
, ScreenWindowPtr window
)
1200 Q_ASSERT( session
);
1203 Emulation
* emulation
= session
->emulation();
1205 int selectionColumn
= 0;
1206 int selectionLine
= 0;
1208 window
->getSelectionEnd(selectionColumn
, selectionLine
);
1210 if ( !_regExp
.isEmpty() )
1213 const bool forwards
= ( _direction
== ForwardsSearch
);
1214 const int startLine
= selectionLine
+ window
->currentLine() + ( forwards
? 1 : -1 );
1215 const int lastLine
= window
->lineCount() - 1;
1218 //text stream to read history into string for pattern or regular expression searching
1219 QTextStream
searchStream(&string
);
1221 PlainTextDecoder decoder
;
1222 decoder
.setRecordLinePositions(true);
1224 //setup first and last lines depending on search direction
1225 int line
= startLine
;
1227 //read through and search history in blocks of 10K lines.
1228 //this balances the need to retrieve lots of data from the history each time
1229 //(for efficient searching)
1230 //without using silly amounts of memory if the history is very large.
1231 const int maxDelta
= qMin(window
->lineCount(),10000);
1232 int delta
= forwards
? maxDelta
: -maxDelta
;
1235 bool hasWrapped
= false; // set to true when we reach the top/bottom
1236 // of the output and continue from the other
1239 //loop through history in blocks of <delta> lines.
1242 // ensure that application does not appear to hang
1243 // if searching through a lengthy output
1244 QApplication::processEvents();
1246 // calculate lines to search in this iteration
1249 if ( endLine
== lastLine
)
1251 else if ( endLine
== 0 )
1257 endLine
= qMin( startLine
, endLine
);
1259 endLine
= qMax( startLine
, endLine
);
1265 if ( endLine
> lastLine
)
1269 } else if ( endLine
< 0 )
1276 decoder
.begin(&searchStream
);
1277 emulation
->writeToStream(&decoder
, qMin(endLine
,line
) , qMax(endLine
,line
) );
1280 // line number search below assumes that the buffer ends with a new-line
1281 string
.append('\n');
1285 pos
= string
.indexOf(_regExp
);
1287 pos
= string
.lastIndexOf(_regExp
);
1289 //if a match is found, position the cursor on that line and update the screen
1293 QList
<int> linePositions
= decoder
.linePositions();
1294 while (newLines
< linePositions
.count() && linePositions
[newLines
] <= pos
)
1297 // ignore the new line at the start of the buffer
1300 int findPos
= qMin(line
,endLine
) + newLines
;
1302 highlightResult(window
,findPos
);
1304 emit
completed(true);
1309 //clear the current block of text and move to the next one
1313 } while ( startLine
!= endLine
);
1315 // if no match was found, clear selection to indicate this
1316 window
->clearSelection();
1317 window
->notifyOutputChanged();
1320 emit
completed(false);
1322 void SearchHistoryTask::highlightResult(ScreenWindowPtr window
, int findPos
)
1324 //work out how many lines into the current block of text the search result was found
1325 //- looks a little painful, but it only has to be done once per search.
1327 kDebug(1211) << "Found result at line " << findPos
;
1329 //update display to show area of history containing selection
1330 window
->scrollTo(findPos
);
1331 window
->setSelectionStart( 0 , findPos
- window
->currentLine() , false );
1332 window
->setSelectionEnd( window
->columnCount() , findPos
- window
->currentLine() );
1333 window
->setTrackOutput(false);
1334 window
->notifyOutputChanged();
1337 SearchHistoryTask::SearchHistoryTask(QObject
* parent
)
1338 : SessionTask(parent
)
1339 , _direction(ForwardsSearch
)
1343 void SearchHistoryTask::setSearchDirection( SearchDirection direction
)
1345 _direction
= direction
;
1347 SearchHistoryTask::SearchDirection
SearchHistoryTask::searchDirection() const
1351 void SearchHistoryTask::setRegExp(const QRegExp
& expression
)
1353 _regExp
= expression
;
1355 QRegExp
SearchHistoryTask::regExp() const
1360 #include "SessionController.moc"
1365 c-file-style: "stroustrup"
1366 indent-tabs-mode: nil