add more spacing
[personal-kdebase.git] / apps / konsole / src / SessionController.cpp
blob5b2ae7fedd7211098456bf38b4d797a83452bf51
1 /*
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
17 02110-1301 USA.
20 // Own
21 #include "SessionController.h"
23 // Qt
24 #include <QtGui/QApplication>
25 #include <QMenu>
27 // KDE
28 #include <KAction>
29 #include <KDebug>
30 #include <KIcon>
31 #include <KInputDialog>
32 #include <KLocale>
33 #include <KMenu>
34 #include <KRun>
35 #include <kshell.h>
36 #include <KToggleAction>
37 #include <KUrl>
38 #include <KXMLGUIFactory>
39 #include <KXMLGUIBuilder>
40 #include <kdebug.h>
41 #include <kcodecaction.h>
42 #include <kdeversion.h>
44 // Konsole
45 #include "EditProfileDialog.h"
46 #include "CopyInputDialog.h"
47 #include "Emulation.h"
48 #include "Filter.h"
49 #include "History.h"
50 #include "IncrementalSearchBar.h"
51 #include "ScreenWindow.h"
52 #include "Session.h"
53 #include "ProfileList.h"
54 #include "TerminalDisplay.h"
55 #include "SessionManager.h"
57 // for SaveHistoryTask
58 #include <KFileDialog>
59 #include <KIO/Job>
60 #include <KJob>
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)
75 , KXMLGUIClient()
76 , _session(session)
77 , _view(view)
78 , _copyToGroup(0)
79 , _profileList(0)
80 , _previousState(-1)
81 , _viewUrlFilter(0)
82 , _searchFilter(0)
83 , _searchToggleAction(0)
84 , _findNextAction(0)
85 , _findPreviousAction(0)
86 , _urlFilterUpdateRequired(false)
87 , _codecAction(0)
88 , _changeProfileMenu(0)
89 , _listenForScreenWindowUpdates(false)
90 , _preventClose(false)
92 _allControllers.insert(this);
94 Q_ASSERT( session );
95 Q_ASSERT( view );
97 // handle user interface related to session (menus etc.)
99 #ifdef KONSOLE_PART
100 setXMLFile("konsole/partui.rc");
101 #else
102 setXMLFile("konsole/sessionui.rc");
103 #endif
105 setupActions();
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
153 // with the session
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()
163 if ( _searchFilter )
165 Q_ASSERT( searchBar() && searchBar()->isVisible() );
167 _view->processFilters();
171 SessionController::~SessionController()
173 if ( _view )
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())
187 case Qt::Key_Shift:
188 case Qt::Key_Control:
189 case Qt::Key_Alt:
190 break;
191 default:
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)
211 title.append('*');
213 // apply new title
214 if ( !title.isEmpty() )
215 _session->setTitle(Session::DisplayedTitleRole,title);
216 else
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()
232 renameSession();
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 ");
247 if ( url.hasUser() )
248 _session->emulation()->sendText(url.user() + '@');
249 if ( url.hasHost() )
250 _session->emulation()->sendText(url.host() + '\r');
252 else
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
268 emit focused(this);
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;
306 return false;
309 void SessionController::removeSearchFilter()
311 if (!_searchFilter)
312 return;
314 _view->filterChain()->removeFilter(_searchFilter);
315 delete _searchFilter;
316 _searchFilter = 0;
319 void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
321 // disconnect the existing search bar
322 if ( _searchBar )
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;
333 if ( _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
347 return _searchBar;
350 void SessionController::setShowMenuAction(QAction* action)
352 actionCollection()->addAction("show-menubar",action);
355 void SessionController::setupActions()
357 KAction* action = 0;
358 KToggleAction* toggleAction = 0;
359 KActionCollection* collection = actionCollection();
361 // Close Session
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()) );
368 // Open Browser
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()) );
374 // Copy and Paste
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()) );
398 // Rename Session
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()) );
404 // Copy Input To
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()) );
420 // Monitor
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*)) );
437 // Text Size
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()) );
450 // Scrollback
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()) );
491 // Profile Options
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));
531 dialog->show();
533 void SessionController::renameSession()
535 QPointer<Session> guard(_session);
536 bool ok = false;
537 const QString& text = KInputDialog::getText( i18n("Rename Tab") ,
538 i18n("Enter new tab text:") ,
539 _session->tabTitleFormat(Session::LocalTabTitle) ,
540 &ok, QApplication::activeWindow() );
541 if (!guard)
542 return;
544 if ( ok )
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
548 // being displayed
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
556 snapshot();
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))
577 return true;
579 QString question;
580 if (title.isEmpty())
581 question = i18n("A program is currently running in this session."
582 " Are you sure you want to close it?");
583 else
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;
590 return true;
592 void SessionController::closeSession()
594 if (_preventClose)
595 return;
597 if (confirmClose())
598 _session->close();
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()
621 if (!_copyToGroup)
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();
639 if (!guard)
640 return;
642 if (result)
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);
656 snapshot();
659 delete dialog;
661 void SessionController::clear()
663 Emulation* emulation = _session->emulation();
665 emulation->clearEntireScreen();
667 void SessionController::clearAndReset()
669 Emulation* emulation = _session->emulation();
671 emulation->reset();
672 _session->refresh();
674 void SessionController::searchClosed()
676 _searchToggleAction->toggle();
679 #if 0
680 void SessionController::searchHistory()
682 searchHistory(true);
684 #endif
686 void SessionController::listenForScreenWindowUpdates()
688 if (_listenForScreenWindowUpdates)
689 return;
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)
703 if ( _searchBar )
705 _searchBar->setVisible(showSearchBar);
707 if (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);
727 else
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)
758 if ( _searchBar )
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() );
783 task->execute();
786 _view->processFilters();
788 void SessionController::highlightMatches(bool highlight)
790 if ( highlight )
792 _view->filterChain()->addFilter(_searchFilter);
793 _view->processFilters();
795 else
797 _view->filterChain()->removeFilter(_searchFilter);
800 _view->update();
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 );
825 else
827 dialog->setMode( HistorySizeDialog::FixedSizeHistory );
828 dialog->setLineCount( currentHistory.maximumLineCount() );
831 else
832 dialog->setMode( HistorySizeDialog::NoHistory );
834 connect( dialog , SIGNAL(optionsChanged(int,int)) ,
835 this , SLOT(scrollBackOptionsChanged(int,int)) );
837 dialog->show();
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 )
846 switch (mode)
848 case HistorySizeDialog::NoHistory:
849 _session->setHistoryType( HistoryTypeNone() );
850 break;
851 case HistorySizeDialog::FixedSizeHistory:
852 _session->setHistoryType( HistoryTypeBuffer(lines) );
853 break;
854 case HistorySizeDialog::UnlimitedHistory:
855 _session->setHistoryType( HistoryTypeFile() );
856 break;
860 void SessionController::saveHistory()
862 SessionTask* task = new SaveHistoryTask(this);
863 task->setAutoDelete(true);
864 task->addSession( _session );
865 task->execute();
867 void SessionController::clearHistory()
869 _session->clearHistory();
871 void SessionController::clearHistoryAndReset()
873 clearAndReset();
874 clearHistory();
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);
923 setTitle( title );
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.
930 if (!factory())
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));
940 if (popup)
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")
962 chosen->trigger();
964 else
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 )
975 return;
977 _previousState = state;
979 // TODO - Replace the icon choices below when suitable icons for silence and activity
980 // are available
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)
1012 : QObject(parent)
1013 , _autoDelete(false)
1016 void SessionTask::setAutoDelete(bool enable)
1018 _autoDelete = enable;
1020 bool SessionTask::autoDelete() const
1022 return _autoDelete;
1024 void SessionTask::addSession(Session* session)
1026 _sessions << session;
1028 QList<SessionPtr> SessionTask::sessions() const
1030 return _sessions;
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 )
1073 continue;
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()) );
1080 continue;
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
1088 // URLs
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
1094 // used
1098 SaveJob jobInfo;
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
1103 // from.
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();
1108 else
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
1132 if ( info.session )
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 ,
1143 sessionLines-1 );
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 )
1157 stream << '\n';
1161 info.lastLineFetched = copyUpToLine;
1164 void SaveHistoryTask::jobResult(KJob* job)
1166 if ( job->error() )
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);
1180 if ( autoDelete() )
1181 deleteLater();
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() )
1193 iter.next();
1194 executeOnScreenWindow( iter.key() , iter.value() );
1198 void SearchHistoryTask::executeOnScreenWindow( SessionPtr session , ScreenWindowPtr window )
1200 Q_ASSERT( session );
1201 Q_ASSERT( window );
1203 Emulation* emulation = session->emulation();
1205 int selectionColumn = 0;
1206 int selectionLine = 0;
1208 window->getSelectionEnd(selectionColumn , selectionLine);
1210 if ( !_regExp.isEmpty() )
1212 int pos = -1;
1213 const bool forwards = ( _direction == ForwardsSearch );
1214 const int startLine = selectionLine + window->currentLine() + ( forwards ? 1 : -1 );
1215 const int lastLine = window->lineCount() - 1;
1216 QString string;
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;
1234 int endLine = line;
1235 bool hasWrapped = false; // set to true when we reach the top/bottom
1236 // of the output and continue from the other
1237 // end
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
1247 if ( hasWrapped )
1249 if ( endLine == lastLine )
1250 line = 0;
1251 else if ( endLine == 0 )
1252 line = lastLine;
1254 endLine += delta;
1256 if ( forwards )
1257 endLine = qMin( startLine , endLine );
1258 else
1259 endLine = qMax( startLine , endLine );
1261 else
1263 endLine += delta;
1265 if ( endLine > lastLine )
1267 hasWrapped = true;
1268 endLine = lastLine;
1269 } else if ( endLine < 0 )
1271 hasWrapped = true;
1272 endLine = 0;
1276 decoder.begin(&searchStream);
1277 emulation->writeToStream(&decoder, qMin(endLine,line) , qMax(endLine,line) );
1278 decoder.end();
1280 // line number search below assumes that the buffer ends with a new-line
1281 string.append('\n');
1283 pos = -1;
1284 if (forwards)
1285 pos = string.indexOf(_regExp);
1286 else
1287 pos = string.lastIndexOf(_regExp);
1289 //if a match is found, position the cursor on that line and update the screen
1290 if ( pos != -1 )
1292 int newLines = 0;
1293 QList<int> linePositions = decoder.linePositions();
1294 while (newLines < linePositions.count() && linePositions[newLines] <= pos)
1295 newLines++;
1297 // ignore the new line at the start of the buffer
1298 newLines--;
1300 int findPos = qMin(line,endLine) + newLines;
1302 highlightResult(window,findPos);
1304 emit completed(true);
1306 return;
1309 //clear the current block of text and move to the next one
1310 string.clear();
1311 line = endLine;
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
1349 return _direction;
1351 void SearchHistoryTask::setRegExp(const QRegExp& expression)
1353 _regExp = expression;
1355 QRegExp SearchHistoryTask::regExp() const
1357 return _regExp;
1360 #include "SessionController.moc"
1363 Local Variables:
1364 mode: c++
1365 c-file-style: "stroustrup"
1366 indent-tabs-mode: nil
1367 tab-width: 4
1368 End: