• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

Konsole

SessionController.cpp

Go to the documentation of this file.
00001 /*
00002     Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
00003 
00004     This program is free software; you can redistribute it and/or modify
00005     it under the terms of the GNU General Public License as published by
00006     the Free Software Foundation; either version 2 of the License, or
00007     (at your option) any later version.
00008 
00009     This program is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012     GNU General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017     02110-1301  USA.
00018 */
00019 
00020 // Own
00021 #include "SessionController.h"
00022 
00023 // Qt
00024 #include <QtGui/QApplication>
00025 #include <QMenu>
00026 
00027 // KDE
00028 #include <KAction>
00029 #include <KDebug>
00030 #include <KIcon>
00031 #include <KInputDialog>
00032 #include <KLocale>
00033 #include <KMenu>
00034 #include <KRun>
00035 #include <kshell.h>
00036 #include <KToggleAction>
00037 #include <KUrl>
00038 #include <KXMLGUIFactory>
00039 #include <KXMLGUIBuilder>
00040 #include <kdebug.h>
00041 #include <kcodecaction.h>
00042 #include <kdeversion.h>
00043 
00044 // Konsole
00045 #include "EditProfileDialog.h"
00046 #include "CopyInputDialog.h"
00047 #include "Emulation.h"
00048 #include "Filter.h"
00049 #include "History.h"
00050 #include "IncrementalSearchBar.h"
00051 #include "ScreenWindow.h"
00052 #include "Session.h"
00053 #include "ProcessInfo.h"
00054 #include "ProfileList.h"
00055 #include "TerminalDisplay.h"
00056 #include "SessionManager.h"
00057 
00058 // for SaveHistoryTask
00059 #include <KFileDialog>
00060 #include <KIO/Job>
00061 #include <KJob>
00062 #include <KMessageBox>
00063 #include "TerminalCharacterDecoder.h"
00064 
00065 
00066 using namespace Konsole;
00067 
00068 KIcon SessionController::_activityIcon;
00069 KIcon SessionController::_silenceIcon;
00070 QPointer<SearchHistoryThread> SearchHistoryTask::_thread;
00071 int SessionController::_lastControllerId;
00072 
00073 SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent)
00074     : ViewProperties(parent)
00075     , KXMLGUIClient()
00076     , _session(session)
00077     , _view(view)
00078     , _copyToGroup(0)
00079     , _profileList(0)
00080     , _previousState(-1)
00081     , _viewUrlFilter(0)
00082     , _searchFilter(0)
00083     , _searchToggleAction(0)
00084     , _findNextAction(0)
00085     , _findPreviousAction(0)
00086     , _urlFilterUpdateRequired(false)
00087     , _codecAction(0)
00088     , _changeProfileMenu(0)
00089     , _listenForScreenWindowUpdates(false)
00090     , _preventClose(false)
00091 {
00092     Q_ASSERT( session );
00093     Q_ASSERT( view );
00094 
00095     // handle user interface related to session (menus etc.)
00096 
00097 #ifdef KONSOLE_PART
00098     setXMLFile("konsole/partui.rc");
00099 #else
00100     setXMLFile("konsole/sessionui.rc");
00101 #endif
00102 
00103     setupActions();
00104     actionCollection()->addAssociatedWidget(view);
00105     foreach (QAction* action, actionCollection()->actions())
00106         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
00107 
00108     setIdentifier(++_lastControllerId);
00109     sessionTitleChanged();
00110 
00111     view->installEventFilter(this);
00112 
00113     // listen for session resize requests
00114     connect( _session , SIGNAL(resizeRequest(const QSize&)) , this ,
00115             SLOT(sessionResizeRequest(const QSize&)) );
00116 
00117     // listen for popup menu requests
00118     connect( _view , SIGNAL(configureRequest(TerminalDisplay*,int,const QPoint&)) , this,
00119             SLOT(showDisplayContextMenu(TerminalDisplay*,int,const QPoint&)) );
00120 
00121     // move view to newest output when keystrokes occur
00122     connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , this ,
00123             SLOT(trackOutput(QKeyEvent*)) );
00124 
00125     // listen to activity / silence notifications from session
00126     connect( _session , SIGNAL(stateChanged(int)) , this ,
00127             SLOT(sessionStateChanged(int) ));
00128     // listen to title and icon changes
00129     connect( _session , SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) );
00130 
00131     // listen for color changes
00132     connect( _session , SIGNAL(changeBackgroundColorRequest(QColor)) , _view , SLOT(setBackgroundColor(QColor)) );
00133     connect( _session , SIGNAL(changeForegroundColorRequest(QColor)) , _view , SLOT(setForegroundColor(QColor)) );
00134 
00135     // update the title when the session starts
00136     connect( _session , SIGNAL(started()) , this , SLOT(snapshot()) ); 
00137 
00138     // listen for output changes to set activity flag
00139     connect( _session->emulation() , SIGNAL(outputChanged()) , this ,
00140             SLOT(fireActivity()) );
00141     
00142     // listen for flow control status changes
00143     connect( _session , SIGNAL(flowControlEnabledChanged(bool)) , _view ,
00144         SLOT(setFlowControlWarningEnabled(bool)) );
00145     _view->setFlowControlWarningEnabled(_session->flowControlEnabled());
00146 
00147     // take a snapshot of the session state every so often when
00148     // user activity occurs
00149     //
00150     // the timer is owned by the session so that it will be destroyed along
00151     // with the session
00152     QTimer* activityTimer = new QTimer(_session);
00153     activityTimer->setSingleShot(true);
00154     activityTimer->setInterval(2000);
00155     connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , activityTimer , SLOT(start()) );
00156     connect( activityTimer , SIGNAL(timeout()) , this , SLOT(snapshot()) );
00157 }
00158 
00159 void SessionController::updateSearchFilter()
00160 {
00161     if ( _searchFilter ) 
00162     {
00163         Q_ASSERT( searchBar() && searchBar()->isVisible() );
00164 
00165         _view->processFilters();
00166     }
00167 }
00168 
00169 SessionController::~SessionController()
00170 {
00171    if ( _view )
00172       _view->setScreenWindow(0);
00173 }
00174 void SessionController::trackOutput(QKeyEvent* event)
00175 {
00176     Q_ASSERT( _view->screenWindow() );
00177 
00178     // jump to the end of the scrollback buffer unless the key pressed
00179     // is one of the three main modifiers, as these are used to select
00180     // the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection)
00181     switch (event->key())
00182     {
00183         case Qt::Key_Shift:
00184         case Qt::Key_Control:
00185         case Qt::Key_Alt:
00186             break;
00187         default:
00188             _view->screenWindow()->setTrackOutput(true);
00189     }
00190 }
00191 void SessionController::requireUrlFilterUpdate()
00192 {
00193     // this method is called every time the screen window's output changes, so do not
00194     // do anything expensive here.
00195 
00196     _urlFilterUpdateRequired = true;
00197 }
00198 void SessionController::snapshot()
00199 {
00200     Q_ASSERT( _session != 0 );
00201 
00202     ProcessInfo* process = 0;
00203     ProcessInfo* snapshot = ProcessInfo::newInstance(_session->processId());
00204     snapshot->update();
00205 
00206     // use foreground process information if available
00207     // fallback to session process otherwise
00208     int pid = _session->foregroundProcessId(); //snapshot->foregroundPid(&ok);
00209     if ( pid != 0 )
00210     {
00211        process = ProcessInfo::newInstance(pid);
00212        process->update();
00213     }
00214     else
00215        process = snapshot;
00216 
00217     bool ok = false;
00218 
00219     // format tab titles using process info
00220     QString title;
00221     if ( process->name(&ok) == "ssh" && ok )
00222     {
00223         SSHProcessInfo sshInfo(*process);
00224         title = sshInfo.format(_session->tabTitleFormat(Session::RemoteTabTitle));
00225     }
00226     else
00227         title = process->format(_session->tabTitleFormat(Session::LocalTabTitle) ) ;
00228 
00229 
00230     if ( snapshot != process )
00231     {
00232         delete snapshot;
00233         delete process;
00234     }
00235     else
00236         delete snapshot;
00237 
00238     title = title.simplified();
00239 
00240     // crude indicator when the session is broadcasting to others
00241     if (_copyToGroup && _copyToGroup->sessions().count() > 1)
00242         title.append('*');
00243 
00244     // apply new title
00245     if ( !title.isEmpty() )
00246         _session->setTitle(Session::DisplayedTitleRole,title);
00247     else
00248         _session->setTitle(Session::DisplayedTitleRole,_session->title(Session::NameRole));
00249 }
00250 
00251 QString SessionController::currentDir() const
00252 {
00253     ProcessInfo* info = ProcessInfo::newInstance(_session->processId());
00254     info->update();
00255 
00256     bool ok = false;
00257     QString path = info->currentDir(&ok);
00258 
00259     delete info;
00260 
00261     if ( ok )
00262         return path;
00263     else
00264         return QString();
00265 }
00266 
00267 KUrl SessionController::url() const
00268 {
00269     ProcessInfo* info = ProcessInfo::newInstance(_session->processId());
00270     info->update();
00271 
00272     QString path;
00273     if ( info->isValid() )
00274     {
00275         bool ok = false;
00276 
00277         // check if foreground process is bookmark-able
00278         int pid = _session->foregroundProcessId();
00279         if ( pid != 0 )
00280         {
00281             ProcessInfo* foregroundInfo = ProcessInfo::newInstance(pid);
00282             foregroundInfo->update();
00283 
00284             // for remote connections, save the user and host
00285             // bright ideas to get the directory at the other end are welcome :)
00286             if ( foregroundInfo->name(&ok) == "ssh" && ok )
00287             {
00288                 SSHProcessInfo sshInfo(*foregroundInfo);
00289                 path = "ssh://" + sshInfo.userName() + '@' + sshInfo.host();
00290             }
00291             else
00292             {
00293                 path = foregroundInfo->currentDir(&ok);
00294 
00295                 if (!ok)
00296                     path.clear();
00297             }
00298 
00299             delete foregroundInfo;
00300         }
00301         else // otherwise use the current working directory of the shell process
00302         {
00303             path = info->currentDir(&ok);
00304             if (!ok)
00305                 path.clear();
00306         }
00307     }
00308 
00309     delete info;
00310     return KUrl( path );
00311 }
00312 
00313 void SessionController::rename()
00314 {
00315     renameSession();
00316 }
00317 
00318 void SessionController::openUrl( const KUrl& url )
00319 {
00320     // handle local paths
00321     if ( url.isLocalFile() )
00322     {
00323         QString path = url.toLocalFile();
00324         _session->emulation()->sendText("cd " + KShell::quoteArg(path) + '\r');
00325     }
00326     else if ( url.protocol() == "ssh" )
00327     {
00328         _session->emulation()->sendText("ssh ");
00329 
00330         if ( url.hasUser() )
00331             _session->emulation()->sendText(url.user() + '@');
00332         if ( url.hasHost() )
00333             _session->emulation()->sendText(url.host() + '\r');
00334     }
00335     else
00336     {
00337         //TODO Implement handling for other Url types
00338         kWarning(1211) << "Unable to open bookmark at url" << url << ", I do not know"
00339            << " how to handle the protocol " << url.protocol();
00340     }
00341 }
00342 
00343 bool SessionController::eventFilter(QObject* watched , QEvent* event)
00344 {
00345     if ( watched == _view )
00346     {
00347         if ( event->type() == QEvent::FocusIn )
00348         {
00349             // notify the world that the view associated with this session has been focused
00350             // used by the view manager to update the title of the MainWindow widget containing the view
00351             emit focused(this);
00352 
00353             // when the view is focused, set bell events from the associated session to be delivered
00354             // by the focused view
00355 
00356             // first, disconnect any other views which are listening for bell signals from the session
00357             disconnect( _session , SIGNAL(bellRequest(const QString&)) , 0 , 0 );
00358             // second, connect the newly focused view to listen for the session's bell signal
00359             connect( _session , SIGNAL(bellRequest(const QString&)) ,
00360                     _view , SLOT(bell(const QString&)) );
00361         }
00362         // when a mouse move is received, create the URL filter and listen for output changes if
00363         // it has not already been created.  If it already exists, then update only if the output
00364         // has changed since the last update ( _urlFilterUpdateRequired == true )
00365         //
00366         // also check that no mouse buttons are pressed since the URL filter only applies when
00367         // the mouse is hovering over the view
00368         if ( event->type() == QEvent::MouseMove &&
00369             (!_viewUrlFilter || _urlFilterUpdateRequired) &&
00370             ((QMouseEvent*)event)->buttons() == Qt::NoButton )
00371         {
00372             if ( _view->screenWindow() && !_viewUrlFilter )
00373             {
00374                 connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this ,
00375                         SLOT(requireUrlFilterUpdate()) );
00376                 connect( _view->screenWindow() , SIGNAL(outputChanged()) , this ,
00377                          SLOT(requireUrlFilterUpdate()) );
00378 
00379                 // install filter on the view to highlight URLs
00380                 _viewUrlFilter = new UrlFilter();
00381                 _view->filterChain()->addFilter( _viewUrlFilter );
00382             }
00383 
00384             _view->processFilters();
00385             _urlFilterUpdateRequired = false;
00386         }
00387     }
00388 
00389     return false;
00390 }
00391 
00392 void SessionController::removeSearchFilter()
00393 {
00394     if (!_searchFilter)
00395         return;
00396 
00397     _view->filterChain()->removeFilter(_searchFilter);
00398     delete _searchFilter;
00399     _searchFilter = 0;
00400 }
00401 
00402 void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
00403 {
00404     // disconnect the existing search bar
00405     if ( _searchBar )
00406     {
00407         disconnect( this , 0 , _searchBar , 0 );
00408         disconnect( _searchBar , 0 , this , 0 );
00409     }
00410 
00411     // remove any existing search filter
00412     removeSearchFilter();
00413 
00414     // connect new search bar
00415     _searchBar = searchBar;
00416     if ( _searchBar )
00417     {
00418         connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
00419         connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
00420         connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
00421         connect( _searchBar , SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) );
00422 
00423         // if the search bar was previously active
00424         // then re-enter search mode
00425         searchHistory( _searchToggleAction->isChecked() );
00426     }
00427 }
00428 IncrementalSearchBar* SessionController::searchBar() const
00429 {
00430     return _searchBar;
00431 }
00432 
00433 void SessionController::setShowMenuAction(QAction* action)
00434 {
00435     actionCollection()->addAction("show-menubar",action);
00436 }
00437 
00438 void SessionController::setupActions()
00439 {
00440     KAction* action = 0;
00441     KToggleAction* toggleAction = 0;
00442     KActionCollection* collection = actionCollection();
00443 
00444     // Close Session
00445     action = collection->addAction("close-session");
00446     action->setIcon( KIcon("tab-close") );
00447     action->setText( i18n("&Close Tab") );
00448     action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_W) );
00449     connect( action , SIGNAL(triggered()) , this , SLOT(closeSession()) );
00450 
00451     // Open Browser
00452     action = collection->addAction("open-browser");
00453     action->setText( i18n("Open Browser Here") );
00454     action->setIcon( KIcon("system-file-manager") );
00455     connect( action, SIGNAL(triggered()), this, SLOT(openBrowser()) );
00456 
00457     // Copy and Paste
00458     action = collection->addAction("copy");
00459     action->setIcon( KIcon("edit-copy") );
00460     action->setText( i18n("&Copy") );
00461     action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_C) );
00462     connect( action , SIGNAL(triggered()) , this , SLOT(copy()) );
00463 
00464     KAction* pasteAction = new KAction( i18n("&Paste") , this );
00465     pasteAction->setIcon( KIcon("edit-paste") );
00466 
00467     KShortcut pasteShortcut = pasteAction->shortcut();
00468     pasteShortcut.setPrimary( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_V) );
00469     pasteShortcut.setAlternate( QKeySequence(Qt::SHIFT+Qt::Key_Insert) );
00470     pasteAction->setShortcut(pasteShortcut);
00471 
00472     collection->addAction("paste",pasteAction);
00473 
00474     connect( pasteAction , SIGNAL(triggered()) , this , SLOT(paste()) );
00475 
00476     action = collection->addAction("paste-selection");
00477     action->setText( i18n("Paste Selection") );
00478     action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Insert) );
00479     connect( action , SIGNAL(triggered()) , this , SLOT(pasteSelection()) );
00480 
00481     // Rename Session
00482     action = collection->addAction("rename-session");
00483     action->setText( i18n("&Rename Tab...") );
00484     action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) );
00485     connect( action , SIGNAL(triggered()) , this , SLOT(renameSession()) );
00486 
00487     // Copy Input To
00488     action = collection->addAction("copy-input-to");
00489     action->setText(i18n("Copy Input To..."));
00490     connect( action , SIGNAL(triggered()) , this , SLOT(copyInputTo()) );
00491 
00492     // Clear and Clear+Reset
00493     action = collection->addAction("clear");
00494     action->setText( i18n("C&lear Display") );
00495     action->setIcon( KIcon("edit-clear") );
00496     connect( action , SIGNAL(triggered()) , this , SLOT(clear()) );
00497 
00498     action = collection->addAction("clear-and-reset");
00499     action->setText( i18n("Clear && Reset") );
00500     action->setIcon( KIcon("edit-clear-history") );
00501     connect( action , SIGNAL(triggered()) , this , SLOT(clearAndReset()) );
00502 
00503     // Monitor
00504     toggleAction = new KToggleAction(i18n("Monitor for &Activity"),this);
00505     toggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A) );
00506     action = collection->addAction("monitor-activity",toggleAction);
00507     connect( action , SIGNAL(toggled(bool)) , this , SLOT(monitorActivity(bool)) );
00508 
00509     toggleAction = new KToggleAction(i18n("Monitor for &Silence"),this);
00510     toggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_I) );
00511     action = collection->addAction("monitor-silence",toggleAction);
00512     connect( action , SIGNAL(toggled(bool)) , this , SLOT(monitorSilence(bool)) );
00513 
00514     // Character Encoding
00515     _codecAction = new KCodecAction(i18n("Character Encoding"),this);
00516     collection->addAction("character-encoding",_codecAction);
00517     connect( _codecAction->menu() , SIGNAL(aboutToShow()) , this , SLOT(updateCodecAction()) );
00518     connect( _codecAction , SIGNAL(triggered(QTextCodec*)) , this , SLOT(changeCodec(QTextCodec*)) );
00519 
00520     // Text Size
00521     action = collection->addAction("increase-text-size");
00522     action->setText( i18n("Increase Text Size") );
00523     action->setIcon( KIcon("zoom-in") );
00524     action->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_Plus) );
00525     connect( action , SIGNAL(triggered()) , this , SLOT(increaseTextSize()) );
00526 
00527     action = collection->addAction("decrease-text-size");
00528     action->setText( i18n("Decrease Text Size") );
00529     action->setIcon( KIcon("zoom-out") );
00530     action->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_Minus) );
00531     connect( action , SIGNAL(triggered()) , this , SLOT(decreaseTextSize()) );
00532 
00533     // Scrollback
00534     _searchToggleAction = new KAction(i18n("Search Output..."),this);
00535     _searchToggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_F) );
00536     _searchToggleAction->setIcon( KIcon("edit-find") );
00537     _searchToggleAction->setCheckable(true);
00538     action = collection->addAction("search-history" , _searchToggleAction);
00539     connect( action , SIGNAL(toggled(bool)) , this , SLOT(searchHistory(bool)) );
00540 
00541     _findNextAction = collection->addAction("find-next");
00542     _findNextAction->setIcon( KIcon("go-down-search") );
00543     _findNextAction->setText( i18n("Find Next") );
00544     _findNextAction->setShortcut( QKeySequence(Qt::Key_F3) );
00545     _findNextAction->setEnabled(false);
00546     connect( _findNextAction , SIGNAL(triggered()) , this , SLOT(findNextInHistory()) );
00547 
00548     _findPreviousAction = collection->addAction("find-previous");
00549     _findPreviousAction->setIcon( KIcon("go-up-search") );
00550     _findPreviousAction->setText( i18n("Find Previous") );
00551     _findPreviousAction->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_F3) );
00552     _findPreviousAction->setEnabled(false);
00553     connect( _findPreviousAction , SIGNAL(triggered()) , this , SLOT(findPreviousInHistory()) );
00554 
00555     action = collection->addAction("save-history");
00556     action->setText( i18n("Save Output...") );
00557     action->setIcon( KIcon("document-save-as") );
00558     connect( action , SIGNAL(triggered()) , this , SLOT(saveHistory()) );
00559 
00560     action = collection->addAction("history-options");
00561     action->setText( i18n("Scrollback Options") );
00562     action->setIcon( KIcon("configure") );
00563     connect( action , SIGNAL(triggered()) , this , SLOT(showHistoryOptions()) );
00564 
00565     action = collection->addAction("clear-history");
00566     action->setText( i18n("Clear Scrollback") );
00567     connect( action , SIGNAL(triggered()) , this , SLOT(clearHistory()) );
00568 
00569     action = collection->addAction("clear-history-and-reset");
00570     action->setText( i18n("Clear Scrollback && Reset") );
00571     action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_X) );
00572     connect( action , SIGNAL(triggered()) , this , SLOT(clearHistoryAndReset()) );
00573 
00574     // Profile Options
00575     action = collection->addAction("edit-current-profile");
00576     action->setText( i18n("Edit Current Profile...") );
00577     action->setIcon( KIcon("document-properties") );
00578     connect( action , SIGNAL(triggered()) , this , SLOT(editCurrentProfile()) );
00579 
00580     _changeProfileMenu = new KMenu(i18n("Change Profile"),_view);
00581     collection->addAction("change-profile",_changeProfileMenu->menuAction());
00582     connect( _changeProfileMenu , SIGNAL(aboutToShow()) , this , SLOT(prepareChangeProfileMenu()) );
00583 
00584     // debugging tools
00585     //action = collection->addAction("debug-process");
00586     //action->setText( "Get Foreground Process" );
00587     //connect( action , SIGNAL(triggered()) , this , SLOT(debugProcess()) );
00588 }
00589 void SessionController::changeProfile(Profile::Ptr profile)
00590 {
00591     SessionManager::instance()->setSessionProfile(_session,profile);    
00592 }
00593 void SessionController::prepareChangeProfileMenu()
00594 {
00595     if ( _changeProfileMenu->isEmpty() )
00596     {
00597         _profileList = new ProfileList(false,this);
00598         connect( _profileList , SIGNAL(profileSelected(Profile::Ptr)) ,
00599                 this , SLOT(changeProfile(Profile::Ptr)) );
00600     }
00601 
00602     _changeProfileMenu->clear();
00603     _changeProfileMenu->addActions(_profileList->actions());
00604 }
00605 void SessionController::updateCodecAction()
00606 {
00607     _codecAction->setCurrentCodec( QString(_session->emulation()->codec()->name()) );
00608 }
00609 void SessionController::changeCodec(QTextCodec* codec)
00610 {
00611     _session->setCodec(codec);
00612 }
00613 void SessionController::debugProcess()
00614 {
00615     // testing facility to retrieve process information about
00616     // currently active process in the shell
00617     ProcessInfo* sessionProcess = ProcessInfo::newInstance(_session->processId());
00618     sessionProcess->update();
00619 
00620     bool ok = false;
00621     int fpid = sessionProcess->foregroundPid(&ok);
00622 
00623     if ( ok )
00624     {
00625         ProcessInfo* fp = ProcessInfo::newInstance(fpid);
00626         fp->update();
00627 
00628         QString name = fp->name(&ok);
00629 
00630         if ( ok )
00631         {
00632             _session->setTitle(Session::DisplayedTitleRole,name);
00633             sessionTitleChanged();
00634         }
00635 
00636         QString currentDir = fp->currentDir(&ok);
00637 
00638         if ( ok )
00639             kDebug(1211) << currentDir;
00640         else
00641             kDebug(1211) << "could not read current dir of foreground process";
00642 
00643         delete fp;
00644     }
00645     delete sessionProcess;
00646 }
00647 
00648 void SessionController::editCurrentProfile()
00649 {
00650     EditProfileDialog* dialog = new EditProfileDialog( QApplication::activeWindow() );
00651 
00652     dialog->setProfile(SessionManager::instance()->sessionProfile(_session));
00653     dialog->show();
00654 }
00655 void SessionController::renameSession()
00656 {
00657     QPointer<Session> guard(_session);
00658     bool ok = false;
00659     const QString& text = KInputDialog::getText( i18n("Rename Tab") ,
00660                                                  i18n("Enter new tab text:") ,
00661                                                  _session->tabTitleFormat(Session::LocalTabTitle) ,
00662                                                  &ok, QApplication::activeWindow() );
00663     if (!guard)
00664         return;
00665 
00666     if ( ok )
00667     {
00668         // renaming changes both the local and remote tab title formats, to save confusion over
00669         // the tab title not changing if renaming the tab whilst the remote tab title format is 
00670         // being displayed
00671         //
00672         // The downside of this approach is that after renaming a tab manually, the ability to 
00673         // have separate formats for local and remote activities is lost
00674         _session->setTabTitleFormat(Session::LocalTabTitle,text);
00675         _session->setTabTitleFormat(Session::RemoteTabTitle,text);
00676 
00677         // trigger an update of the tab text
00678         snapshot();
00679     }
00680 }
00681 void SessionController::saveSession()
00682 {
00683     Q_ASSERT(0); // not implemented yet
00684 
00685     //SaveSessionDialog dialog(_view);
00686     //int result = dialog.exec();
00687 }
00688 bool SessionController::confirmClose() const
00689 {
00690     if (_session->foregroundProcessId() != _session->processId())
00691     {
00692         ProcessInfo* foregroundInfo = ProcessInfo::newInstance(_session->foregroundProcessId());
00693         foregroundInfo->update();
00694         bool ok = false;
00695         QString title = foregroundInfo->name(&ok);
00696         delete foregroundInfo;
00697       
00698         // hard coded for now.  In future make it possible for the user to specify which programs
00699         // are ignored when considering whether to display a confirmation
00700         QStringList ignoreList; 
00701         ignoreList << QString(getenv("SHELL")).section('/',-1);
00702         if (ignoreList.contains(title))
00703             return true;
00704 
00705         QString question;
00706         if (ok)
00707             question = i18n("The program '%1' is currently running in this session."  
00708                             "  Are you sure you want to close it?",title);
00709         else
00710             question = i18n("A program is currently running in this session."
00711                             "  Are you sure you want to close it?");
00712 
00713         int result = KMessageBox::warningYesNo(_view->window(),question,i18n("Confirm Close"));
00714         return (result == KMessageBox::Yes) ? true : false; 
00715     }
00716     return true;
00717 }
00718 void SessionController::closeSession()
00719 {
00720     if (_preventClose)
00721         return;
00722 
00723     _session->close();
00724 }
00725 
00726 void SessionController::openBrowser()
00727 {
00728     new KRun(url(), QApplication::activeWindow());
00729 }
00730 
00731 void SessionController::copy()
00732 {
00733     _view->copyClipboard();
00734 }
00735 
00736 void SessionController::paste()
00737 {
00738     _view->pasteClipboard();
00739 }
00740 void SessionController::pasteSelection()
00741 {
00742     _view->pasteSelection();
00743 }
00744 void SessionController::copyInputTo()
00745 {
00746     if (!_copyToGroup)
00747     {
00748         _copyToGroup = new SessionGroup(this);
00749         _copyToGroup->addSession(_session);
00750         _copyToGroup->setMasterStatus(_session,true);
00751         _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
00752     }
00753 
00754     CopyInputDialog* dialog = new CopyInputDialog(_view);
00755     dialog->setMasterSession(_session);
00756     
00757     QSet<Session*> currentGroup = QSet<Session*>::fromList(_copyToGroup->sessions());
00758     currentGroup.remove(_session);
00759     
00760     dialog->setChosenSessions(currentGroup);
00761 
00762     QPointer<Session> guard(_session);
00763     int result = dialog->exec();
00764     if (!guard)
00765         return;
00766 
00767     if (result)
00768     {
00769         QSet<Session*> newGroup = dialog->chosenSessions();
00770         newGroup.remove(_session);
00771     
00772         QSet<Session*> completeGroup = newGroup | currentGroup;
00773         foreach(Session* session, completeGroup)
00774         {
00775             if (newGroup.contains(session) && !currentGroup.contains(session))
00776                 _copyToGroup->addSession(session);
00777             else if (!newGroup.contains(session) && currentGroup.contains(session))
00778                 _copyToGroup->removeSession(session);
00779         }
00780 
00781         snapshot();
00782     }
00783 
00784     delete dialog;
00785 }
00786 void SessionController::clear()
00787 {
00788     Emulation* emulation = _session->emulation();
00789 
00790     emulation->clearEntireScreen();
00791 }
00792 void SessionController::clearAndReset()
00793 {
00794     Emulation* emulation = _session->emulation();
00795 
00796     emulation->reset();
00797     _session->refresh();
00798 }
00799 void SessionController::searchClosed()
00800 {
00801     _searchToggleAction->toggle();
00802 }
00803 
00804 #if 0
00805 void SessionController::searchHistory()
00806 {
00807     searchHistory(true);
00808 }
00809 #endif
00810 
00811 void SessionController::listenForScreenWindowUpdates()
00812 {
00813     if (_listenForScreenWindowUpdates)
00814         return;
00815 
00816     connect( _view->screenWindow() , SIGNAL(outputChanged()) , this , 
00817             SLOT(updateSearchFilter()) );
00818     connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this , 
00819             SLOT(updateSearchFilter()) );
00820 
00821     _listenForScreenWindowUpdates = true;
00822 }
00823 
00824 // searchHistory() may be called either as a result of clicking a menu item or
00825 // as a result of changing the search bar widget
00826 void SessionController::searchHistory(bool showSearchBar)
00827 {
00828     if ( _searchBar )
00829     {
00830         _searchBar->setVisible(showSearchBar);
00831 
00832         if (showSearchBar)
00833         {
00834             removeSearchFilter();
00835 
00836             listenForScreenWindowUpdates();
00837             
00838             _searchFilter = new RegExpFilter();
00839             _view->filterChain()->addFilter(_searchFilter);
00840             connect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
00841                     SLOT(searchTextChanged(const QString&)) );
00842 
00843             // invoke search for matches for the current search text
00844             const QString& currentSearchText = _searchBar->searchText();
00845             if (!currentSearchText.isEmpty())
00846             {
00847                 searchTextChanged(currentSearchText);
00848             }
00849 
00850             setFindNextPrevEnabled(true);
00851         }
00852         else
00853         {
00854             setFindNextPrevEnabled(false);
00855 
00856             disconnect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
00857                     SLOT(searchTextChanged(const QString&)) );
00858 
00859             removeSearchFilter();
00860 
00861             _view->setFocus( Qt::ActiveWindowFocusReason );
00862         }
00863     }
00864 }
00865 void SessionController::setFindNextPrevEnabled(bool enabled)
00866 {
00867     _findNextAction->setEnabled(enabled);
00868     _findPreviousAction->setEnabled(enabled);
00869 }
00870 void SessionController::searchTextChanged(const QString& text)
00871 {
00872     Q_ASSERT( _view->screenWindow() );
00873 
00874     if ( text.isEmpty() )
00875         _view->screenWindow()->clearSelection();
00876 
00877     // update search.  this is called even when the text is
00878     // empty to clear the view's filters
00879     beginSearch(text , SearchHistoryTask::ForwardsSearch);
00880 }
00881 void SessionController::searchCompleted(bool success)
00882 {
00883     if ( _searchBar )
00884         _searchBar->setFoundMatch(success);
00885 }
00886 
00887 void SessionController::beginSearch(const QString& text , int direction)
00888 {
00889     Q_ASSERT( _searchBar );
00890     Q_ASSERT( _searchFilter );
00891 
00892     Qt::CaseSensitivity caseHandling = _searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive;
00893     QRegExp::PatternSyntax syntax = _searchBar->matchRegExp() ? QRegExp::RegExp : QRegExp::FixedString;
00894 
00895     QRegExp regExp( text.trimmed() ,  caseHandling , syntax );
00896     _searchFilter->setRegExp(regExp);
00897 
00898     if ( !regExp.isEmpty() )
00899     {
00900         SearchHistoryTask* task = new SearchHistoryTask(this);
00901 
00902         connect( task , SIGNAL(completed(bool)) , this , SLOT(searchCompleted(bool)) );
00903 
00904         task->setRegExp(regExp);
00905         task->setSearchDirection( (SearchHistoryTask::SearchDirection)direction );
00906         task->setAutoDelete(true);
00907         task->addScreenWindow( _session , _view->screenWindow() );
00908         task->execute();
00909     }
00910 
00911     _view->processFilters();
00912 }
00913 void SessionController::highlightMatches(bool highlight)
00914 {
00915     if ( highlight )
00916     {
00917         _view->filterChain()->addFilter(_searchFilter);
00918         _view->processFilters();
00919     }
00920     else
00921     {
00922         _view->filterChain()->removeFilter(_searchFilter);
00923     }
00924 
00925     _view->update();
00926 }
00927 void SessionController::findNextInHistory()
00928 {
00929     Q_ASSERT( _searchBar );
00930     Q_ASSERT( _searchFilter );
00931 
00932     beginSearch(_searchBar->searchText(),SearchHistoryTask::ForwardsSearch);
00933 }
00934 void SessionController::findPreviousInHistory()
00935 {
00936     Q_ASSERT( _searchBar );
00937     Q_ASSERT( _searchFilter );
00938 
00939     beginSearch(_searchBar->searchText(),SearchHistoryTask::BackwardsSearch);
00940 }
00941 void SessionController::showHistoryOptions()
00942 {
00943     HistorySizeDialog* dialog = new HistorySizeDialog( QApplication::activeWindow() );
00944     const HistoryType& currentHistory = _session->historyType();
00945 
00946     if ( currentHistory.isEnabled() )
00947     {
00948         if ( currentHistory.isUnlimited() )
00949             dialog->setMode( HistorySizeDialog::UnlimitedHistory );
00950         else
00951         {
00952             dialog->setMode( HistorySizeDialog::FixedSizeHistory );
00953             dialog->setLineCount( currentHistory.maximumLineCount() );
00954         }
00955     }
00956     else
00957         dialog->setMode( HistorySizeDialog::NoHistory );
00958 
00959     connect( dialog , SIGNAL(optionsChanged(int,int)) ,
00960              this , SLOT(scrollBackOptionsChanged(int,int)) );
00961 
00962     dialog->show();
00963 }
00964 void SessionController::sessionResizeRequest(const QSize& size)
00965 {
00966     kDebug(1211) << "View resize requested to " << size;
00967     _view->setSize(size.width(),size.height());
00968 }
00969 void SessionController::scrollBackOptionsChanged( int mode , int lines )
00970 {
00971     switch (mode)
00972     {
00973         case HistorySizeDialog::NoHistory:
00974             _session->setHistoryType( HistoryTypeNone() );
00975             break;
00976         case HistorySizeDialog::FixedSizeHistory:
00977             _session->setHistoryType( HistoryTypeBuffer(lines) );
00978             break;
00979         case HistorySizeDialog::UnlimitedHistory:
00980             _session->setHistoryType( HistoryTypeFile() );
00981             break;
00982     }
00983 }
00984 
00985 void SessionController::saveHistory()
00986 {
00987     SessionTask* task = new SaveHistoryTask(this);
00988     task->setAutoDelete(true);
00989     task->addSession( _session );
00990     task->execute();
00991 }
00992 void SessionController::clearHistory()
00993 {
00994     _session->clearHistory();
00995 }
00996 void SessionController::clearHistoryAndReset()
00997 {
00998     clearAndReset();
00999     clearHistory();
01000 }
01001 void SessionController::increaseTextSize()
01002 {
01003     QFont font = _view->getVTFont();
01004     font.setPointSize(font.pointSize()+1);
01005     _view->setVTFont(font);
01006 
01007     //TODO - Save this setting as a session default
01008 }
01009 void SessionController::decreaseTextSize()
01010 {
01011     static const int MinimumFontSize = 6;
01012 
01013     QFont font = _view->getVTFont();
01014     font.setPointSize( qMax(font.pointSize()-1,MinimumFontSize) );
01015     _view->setVTFont(font);
01016 
01017     //TODO - Save this setting as a session default
01018 }
01019 void SessionController::monitorActivity(bool monitor)
01020 {
01021     _session->setMonitorActivity(monitor);
01022 }
01023 void SessionController::monitorSilence(bool monitor)
01024 {
01025     _session->setMonitorSilence(monitor);
01026 }
01027 void SessionController::sessionTitleChanged()
01028 {
01029         if ( _sessionIconName != _session->iconName() )
01030         {
01031             _sessionIconName = _session->iconName();
01032             _sessionIcon = KIcon( _sessionIconName );
01033             setIcon( _sessionIcon );
01034         }
01035 
01036         QString title = _session->title(Session::DisplayedTitleRole);
01037 
01038         // special handling for the "%w" marker which is replaced with the
01039         // window title set by the shell
01040         title.replace("%w",_session->userTitle());
01041         // special handling for the "%#" marker which is replaced with the 
01042         // number of the shell
01043         title.replace("%#",QString::number(_session->sessionId()));
01044 
01045        if ( title.isEmpty() )
01046           title = _session->title(Session::NameRole);
01047 
01048        setTitle( title );
01049 }
01050 
01051 void SessionController::showDisplayContextMenu(TerminalDisplay* /*display*/ , int /*state*/, const QPoint& position)
01052 {
01053     QMenu* popup = 0;
01054 
01055     // needed to make sure the popup menu is available, even if a hosting
01056     // application did not merge our GUI.
01057     if (!factory()) {
01058         if (!clientBuilder()) {
01059             setClientBuilder(new KXMLGUIBuilder(_view));
01060         }
01061         KXMLGUIFactory* f = new KXMLGUIFactory(clientBuilder(), this);
01062         f->addClient(this);
01063     }
01064 
01065     if ( factory() )
01066         popup = qobject_cast<QMenu*>(factory()->container("session-popup-menu",this));
01067 
01068     if (popup)
01069     {
01070         // prepend content-specific actions such as "Open Link", "Copy Email Address" etc.
01071         QList<QAction*> contentActions = _view->filterActions(position);
01072         QAction* contentSeparator = new QAction(popup);
01073         contentSeparator->setSeparator(true);
01074         contentActions << contentSeparator;
01075 
01076         _preventClose = true;
01077 
01078         popup->insertActions(popup->actions().value(0,0),contentActions);
01079         QAction* chosen = popup->exec( _view->mapToGlobal(position) );
01080 
01081         // remove content-specific actions, unless the close action was chosen
01082         // in which case the popup menu will be partially destroyed at this point
01083         foreach(QAction* action,contentActions)
01084             popup->removeAction(action);
01085         delete contentSeparator;
01086 
01087         _preventClose = false;
01088 
01089         if (chosen && chosen->objectName() == "close-session")
01090             chosen->trigger();
01091     }
01092     else
01093     {
01094         kWarning(1211) << "Unable to display popup menu for session"
01095                    << _session->title(Session::NameRole)
01096                    << ", no GUI factory available to build the popup.";
01097     }
01098 }
01099 
01100 void SessionController::sessionStateChanged(int state)
01101 {
01102     if ( state == _previousState )
01103         return;
01104 
01105     _previousState = state;
01106 
01107     // TODO - Replace the icon choices below when suitable icons for silence and activity
01108     // are available
01109     if ( state == NOTIFYACTIVITY )
01110     {
01111         if (_activityIcon.isNull())
01112         {
01113             _activityIcon = KIcon("dialog-information");
01114         }
01115 
01116         setIcon(_activityIcon);
01117     }
01118     else if ( state == NOTIFYSILENCE )
01119     {
01120         if (_silenceIcon.isNull())
01121         {
01122             _silenceIcon = KIcon("dialog-information");
01123         }
01124 
01125         setIcon(_silenceIcon);
01126     }
01127     else if ( state == NOTIFYNORMAL )
01128     {
01129         if ( _sessionIconName != _session->iconName() )
01130         {
01131             _sessionIconName = _session->iconName();
01132             _sessionIcon = KIcon( _sessionIconName );
01133         }
01134 
01135         setIcon( _sessionIcon );
01136     }
01137 }
01138 
01139 SessionTask::SessionTask(QObject* parent)
01140     :  QObject(parent)
01141     ,  _autoDelete(false)
01142 {
01143 }
01144 void SessionTask::setAutoDelete(bool enable)
01145 {
01146     _autoDelete = enable;
01147 }
01148 bool SessionTask::autoDelete() const
01149 {
01150     return _autoDelete;
01151 }
01152 void SessionTask::addSession(Session* session)
01153 {
01154     _sessions << session;
01155 }
01156 QList<SessionPtr> SessionTask::sessions() const
01157 {
01158     return _sessions;
01159 }
01160 
01161 SaveHistoryTask::SaveHistoryTask(QObject* parent)
01162     : SessionTask(parent)
01163 {
01164 }
01165 SaveHistoryTask::~SaveHistoryTask()
01166 {
01167 }
01168 
01169 void SaveHistoryTask::execute()
01170 {
01171     QListIterator<SessionPtr> iter(sessions());
01172 
01173     // TODO - prompt the user if the file already exists, currently existing files
01174     //        are always overwritten
01175 
01176     // TODO - think about the UI when saving multiple history sessions, if there are more than two or
01177     //        three then providing a URL for each one will be tedious
01178 
01179     // TODO - show a warning ( preferably passive ) if saving the history output fails
01180     //
01181 
01182      KFileDialog* dialog = new KFileDialog( QString(":konsole") /* check this */,
01183                                                QString(), QApplication::activeWindow() );
01184      QStringList mimeTypes;
01185      mimeTypes << "text/plain";
01186      mimeTypes << "text/html";
01187      dialog->setMimeFilter(mimeTypes,"text/plain");
01188 
01189      // iterate over each session in the task and display a dialog to allow the user to choose where
01190      // to save that session's history.
01191      // then start a KIO job to transfer the data from the history to the chosen URL
01192     while ( iter.hasNext() )
01193     {
01194         SessionPtr session = iter.next();
01195 
01196         dialog->setCaption( i18n("Save Output From %1",session->title(Session::NameRole)) );
01197 
01198         int result = dialog->exec();
01199 
01200         if ( result != QDialog::Accepted )
01201             continue;
01202 
01203         KUrl url = dialog->selectedUrl();
01204 
01205         if ( !url.isValid() )
01206         { // UI:  Can we make this friendlier?
01207             KMessageBox::sorry( 0 , i18n("%1 is an invalid URL, the output could not be saved.",url.url()) );
01208             continue;
01209         }
01210 
01211         KIO::TransferJob* job = KIO::put( url,
01212                                           -1,   // no special permissions
01213                                           // overwrite existing files
01214                                           // do not resume an existing transfer
01215                                           // show progress information only for remote
01216                                           // URLs
01217                                           KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags)
01218                                                              // a better solution would be to show progress
01219                                                              // information after a certain period of time
01220                                                              // instead, since the overall speed of transfer
01221                                                              // depends on factors other than just the protocol
01222                                                              // used
01223                                         );
01224 
01225 
01226         SaveJob jobInfo;
01227         jobInfo.session = session;
01228         jobInfo.lastLineFetched = -1;  // when each request for data comes in from the KIO subsystem
01229                                        // lastLineFetched is used to keep track of how much of the history
01230                                        // has already been sent, and where the next request should continue
01231                                        // from.
01232                                        // this is set to -1 to indicate the job has just been started
01233 
01234         if ( dialog->currentMimeFilter() == "text/html" )
01235            jobInfo.decoder = new HTMLDecoder();
01236         else
01237            jobInfo.decoder = new PlainTextDecoder();
01238 
01239         _jobSession.insert(job,jobInfo);
01240 
01241         connect( job , SIGNAL(dataReq(KIO::Job*,QByteArray&)),
01242                  this, SLOT(jobDataRequested(KIO::Job*,QByteArray&)) );
01243         connect( job , SIGNAL(result(KJob*)),
01244                  this, SLOT(jobResult(KJob*)) );
01245     }
01246 
01247     dialog->deleteLater();
01248 }
01249 void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data)
01250 {
01251     // TODO - Report progress information for the job
01252 
01253     // PERFORMANCE:  Do some tests and tweak this value to get faster saving
01254     const int LINES_PER_REQUEST = 500;
01255 
01256     SaveJob& info = _jobSession[job];
01257 
01258     // transfer LINES_PER_REQUEST lines from the session's history
01259     // to the save location
01260     if ( info.session )
01261     {
01262         // note:  when retrieving lines from the emulation,
01263         // the first line is at index 0.
01264 
01265         int sessionLines = info.session->emulation()->lineCount();
01266 
01267         if ( sessionLines-1 == info.lastLineFetched )
01268             return; // if there is no more data to transfer then stop the job
01269 
01270         int copyUpToLine = qMin( info.lastLineFetched + LINES_PER_REQUEST ,
01271                                  sessionLines-1 );
01272 
01273         QTextStream stream(&data,QIODevice::ReadWrite);
01274         info.decoder->begin(&stream);
01275         info.session->emulation()->writeToStream( info.decoder , info.lastLineFetched+1 , copyUpToLine );
01276         info.decoder->end();
01277 
01278         // if there are still more lines to process after this request
01279         // then insert a new line character
01280         // to ensure that the next block of lines begins on a new line
01281         //
01282         // FIXME - There is still an extra new-line at the end of the save data.
01283         if ( copyUpToLine <= sessionLines-1 )
01284         {
01285             stream << '\n';
01286         }
01287 
01288 
01289         info.lastLineFetched = copyUpToLine;
01290     }
01291 }
01292 void SaveHistoryTask::jobResult(KJob* job)
01293 {
01294     if ( job->error() )
01295     {
01296         KMessageBox::sorry( 0 , i18n("A problem occurred when saving the output.\n%1",job->errorString()) );
01297     }
01298 
01299     SaveJob& info = _jobSession[job];
01300 
01301     _jobSession.remove(job);
01302 
01303     delete info.decoder;
01304 
01305     // notify the world that the task is done
01306     emit completed(true);
01307 
01308     if ( autoDelete() )
01309         deleteLater();
01310 }
01311 void SearchHistoryTask::addScreenWindow( Session* session , ScreenWindow* searchWindow )
01312 {
01313    _windows.insert(session,searchWindow);
01314 }
01315 void SearchHistoryTask::execute()
01316 {
01317     QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows);
01318 
01319     while ( iter.hasNext() )
01320     {
01321         iter.next();
01322         executeOnScreenWindow( iter.key() , iter.value() );
01323     }
01324 }
01325 
01326 void SearchHistoryTask::executeOnScreenWindow( SessionPtr session , ScreenWindowPtr window )
01327 {
01328     Q_ASSERT( session );
01329     Q_ASSERT( window );
01330 
01331     Emulation* emulation = session->emulation();
01332 
01333     int selectionColumn = 0;
01334     int selectionLine = 0;
01335 
01336     window->getSelectionEnd(selectionColumn , selectionLine);
01337 
01338     if ( !_regExp.isEmpty() )
01339     {
01340         int pos = -1;
01341         const bool forwards = ( _direction == ForwardsSearch );
01342         const int startLine = selectionLine + window->currentLine() + ( forwards ? 1 : -1 );
01343         const int lastLine = window->lineCount() - 1;
01344         QString string;
01345 
01346         //text stream to read history into string for pattern or regular expression searching
01347         QTextStream searchStream(&string);
01348 
01349         PlainTextDecoder decoder;
01350         decoder.setRecordLinePositions(true);
01351 
01352         //setup first and last lines depending on search direction
01353         int line = startLine;
01354 
01355         //read through and search history in blocks of 10K lines.
01356         //this balances the need to retrieve lots of data from the history each time
01357         //(for efficient searching)
01358         //without using silly amounts of memory if the history is very large.
01359         const int maxDelta = qMin(window->lineCount(),10000);
01360         int delta = forwards ? maxDelta : -maxDelta;
01361 
01362         int endLine = line;
01363         bool hasWrapped = false;  // set to true when we reach the top/bottom
01364                                   // of the output and continue from the other
01365                                   // end
01366 
01367         //loop through history in blocks of <delta> lines.
01368         do
01369         {
01370             // ensure that application does not appear to hang
01371             // if searching through a lengthy output
01372             QApplication::processEvents();
01373 
01374             // calculate lines to search in this iteration
01375             if ( hasWrapped )
01376             {
01377                 if ( endLine == lastLine )
01378                     line = 0;
01379                 else if ( endLine == 0 )
01380                     line = lastLine;
01381 
01382                 endLine += delta;
01383 
01384                 if ( forwards )
01385                    endLine = qMin( startLine , endLine );
01386                 else
01387                    endLine = qMax( startLine , endLine );
01388             }
01389             else
01390             {
01391                 endLine += delta;
01392 
01393                 if ( endLine > lastLine )
01394                 {
01395                     hasWrapped = true;
01396                     endLine = lastLine;
01397                 } else if ( endLine < 0 )
01398                 {
01399                     hasWrapped = true;
01400                     endLine = 0;
01401                 }
01402             }
01403 
01404             decoder.begin(&searchStream);
01405             emulation->writeToStream(&decoder, qMin(endLine,line) , qMax(endLine,line) );
01406             decoder.end();
01407 
01408             // line number search below assumes that the buffer ends with a new-line 
01409             string.append('\n');
01410 
01411             pos = -1;
01412             if (forwards)
01413                 pos = string.indexOf(_regExp);
01414             else
01415                 pos = string.lastIndexOf(_regExp);
01416 
01417             //if a match is found, position the cursor on that line and update the screen
01418             if ( pos != -1 )
01419             {
01420                 int newLines = 0;
01421                 QList<int> linePositions = decoder.linePositions();
01422                 while (newLines < linePositions.count() && linePositions[newLines] <= pos)
01423                     newLines++;
01424 
01425                 // ignore the new line at the start of the buffer
01426                 newLines--;
01427 
01428                 int findPos = qMin(line,endLine) + newLines;
01429 
01430                 highlightResult(window,findPos);
01431 
01432                 emit completed(true);
01433 
01434                 return;
01435             }
01436 
01437             //clear the current block of text and move to the next one
01438             string.clear();
01439             line = endLine;
01440 
01441         } while ( startLine != endLine );
01442 
01443         // if no match was found, clear selection to indicate this
01444         window->clearSelection();
01445         window->notifyOutputChanged();
01446     }
01447 
01448     emit completed(false);
01449 }
01450 void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos)
01451 {
01452      //work out how many lines into the current block of text the search result was found
01453      //- looks a little painful, but it only has to be done once per search.
01454 
01455      kDebug(1211) << "Found result at line " << findPos;
01456 
01457      //update display to show area of history containing selection
01458      window->scrollTo(findPos);
01459      window->setSelectionStart( 0 , findPos - window->currentLine() , false );
01460      window->setSelectionEnd( window->columnCount() , findPos - window->currentLine() );
01461      window->setTrackOutput(false);
01462      window->notifyOutputChanged();
01463 }
01464 
01465 SearchHistoryTask::SearchHistoryTask(QObject* parent)
01466     : SessionTask(parent)
01467     , _direction(ForwardsSearch)
01468 {
01469 
01470 }
01471 void SearchHistoryTask::setSearchDirection( SearchDirection direction )
01472 {
01473     _direction = direction;
01474 }
01475 SearchHistoryTask::SearchDirection SearchHistoryTask::searchDirection() const
01476 {
01477     return _direction;
01478 }
01479 void SearchHistoryTask::setRegExp(const QRegExp& expression)
01480 {
01481     _regExp = expression;
01482 }
01483 QRegExp SearchHistoryTask::regExp() const
01484 {
01485     return _regExp;
01486 }
01487 
01488 #include "SessionController.moc"

Konsole

Skip menu "Konsole"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • Konsole
  • Libraries
  •   libkonq
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal