~ubuntu-branches/ubuntu/utopic/kdebase/utopic

« back to all changes in this revision

Viewing changes to konsole/src/SessionController.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Debian Qt/KDE Maintainers, José Manuel Santamaría Lema, Modestas Vainius
  • Date: 2011-05-26 02:53:50 UTC
  • mfrom: (0.7.7 upstream) (0.4.5 experimental)
  • mto: This revision was merged to the branch mainline in revision 296.
  • Revision ID: james.westby@ubuntu.com-20110526025350-7o10g65yegec2rnq
Tags: 4:4.6.3-1
* New upstream release.

[ José Manuel Santamaría Lema ]
* Bump kde-sc-dev-latest build dependency to 4:4.6.3.
* Bump Standards-Version to 3.9.2; no changes needed.

[ Modestas Vainius ]
* Enable DLRestrictions for libraries in this package. Requires
  libdlrestrictions-dev 0.14 and kdelibs5-dev 4:4.6.3.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
 
3
    Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
 
4
 
 
5
    This program is free software; you can redistribute it and/or modify
 
6
    it under the terms of the GNU General Public License as published by
 
7
    the Free Software Foundation; either version 2 of the License, or
 
8
    (at your option) any later version.
 
9
 
 
10
    This program is distributed in the hope that it will be useful,
 
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
    GNU General Public License for more details.
 
14
 
 
15
    You should have received a copy of the GNU General Public License
 
16
    along with this program; if not, write to the Free Software
 
17
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
18
    02110-1301  USA.
 
19
*/
 
20
 
 
21
// Own
 
22
#include "SessionController.h"
 
23
 
 
24
// Qt
 
25
#include <QtGui/QApplication>
 
26
#include <QMenu>
 
27
 
 
28
// KDE
 
29
#include <KAction>
 
30
#include <KDebug>
 
31
#include <KIcon>
 
32
#include <KInputDialog>
 
33
#include <KLocale>
 
34
#include <KMenu>
 
35
#include <KMessageBox>
 
36
#include <KRun>
 
37
#include <kshell.h>
 
38
#include <KStandardDirs>
 
39
#include <KToggleAction>
 
40
#include <KUrl>
 
41
#include <KXmlGuiWindow>
 
42
#include <KXMLGUIFactory>
 
43
#include <KXMLGUIBuilder>
 
44
#include <kdebug.h>
 
45
#include <kcodecaction.h>
 
46
#include <kdeversion.h>
 
47
 
 
48
// Konsole
 
49
#include "EditProfileDialog.h"
 
50
#include "CopyInputDialog.h"
 
51
#include "Emulation.h"
 
52
#include "Filter.h"
 
53
#include "History.h"
 
54
#include "IncrementalSearchBar.h"
 
55
#include "RenameTabsDialog.h"
 
56
#include "ScreenWindow.h"
 
57
#include "Session.h"
 
58
#include "ProfileList.h"
 
59
#include "TerminalDisplay.h"
 
60
#include "SessionManager.h"
 
61
 
 
62
// for SaveHistoryTask
 
63
#include <KFileDialog>
 
64
#include <KIO/Job>
 
65
#include <KJob>
 
66
#include "TerminalCharacterDecoder.h"
 
67
 
 
68
 
 
69
using namespace Konsole;
 
70
 
 
71
KIcon SessionController::_activityIcon;
 
72
KIcon SessionController::_silenceIcon;
 
73
QSet<SessionController*> SessionController::_allControllers;
 
74
QPointer<SearchHistoryThread> SearchHistoryTask::_thread;
 
75
int SessionController::_lastControllerId;
 
76
 
 
77
SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent)
 
78
    : ViewProperties(parent)
 
79
    , KXMLGUIClient()
 
80
    , _session(session)
 
81
    , _view(view)
 
82
    , _copyToGroup(0)
 
83
    , _profileList(0)
 
84
    , _previousState(-1)
 
85
    , _viewUrlFilter(0)
 
86
    , _searchFilter(0)
 
87
    , _searchToggleAction(0)
 
88
    , _findNextAction(0)
 
89
    , _findPreviousAction(0)
 
90
    , _urlFilterUpdateRequired(false)
 
91
    , _codecAction(0)
 
92
    , _changeProfileMenu(0)
 
93
    , _listenForScreenWindowUpdates(false)
 
94
    , _preventClose(false)
 
95
{
 
96
    _allControllers.insert(this);
 
97
 
 
98
    Q_ASSERT( session );
 
99
    Q_ASSERT( view );
 
100
 
 
101
    // handle user interface related to session (menus etc.)
 
102
    if (isKonsolePart())
 
103
        setXMLFile("konsole/partui.rc");
 
104
    else
 
105
        setXMLFile("konsole/sessionui.rc");
 
106
 
 
107
    setupActions();
 
108
    actionCollection()->addAssociatedWidget(view);
 
109
    foreach (QAction* action, actionCollection()->actions())
 
110
        action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
 
111
 
 
112
    setIdentifier(++_lastControllerId);
 
113
    sessionTitleChanged();
 
114
 
 
115
    view->installEventFilter(this);
 
116
 
 
117
    // listen for session resize requests
 
118
    connect( _session , SIGNAL(resizeRequest(const QSize&)) , this ,
 
119
            SLOT(sessionResizeRequest(const QSize&)) );
 
120
 
 
121
    // listen for popup menu requests
 
122
    connect( _view , SIGNAL(configureRequest(QPoint)) , this,
 
123
            SLOT(showDisplayContextMenu(QPoint)) );
 
124
 
 
125
    // move view to newest output when keystrokes occur
 
126
    connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , this ,
 
127
            SLOT(trackOutput(QKeyEvent*)) );
 
128
 
 
129
    // listen to activity / silence notifications from session
 
130
    connect( _session , SIGNAL(stateChanged(int)) , this ,
 
131
            SLOT(sessionStateChanged(int) ));
 
132
    // listen to title and icon changes
 
133
    connect( _session , SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) );
 
134
 
 
135
    // listen for color changes
 
136
    connect( _session , SIGNAL(changeBackgroundColorRequest(QColor)) , _view , SLOT(setBackgroundColor(QColor)) );
 
137
    connect( _session , SIGNAL(changeForegroundColorRequest(QColor)) , _view , SLOT(setForegroundColor(QColor)) );
 
138
 
 
139
    // update the title when the session starts
 
140
    connect( _session , SIGNAL(started()) , this , SLOT(snapshot()) ); 
 
141
 
 
142
    // listen for output changes to set activity flag
 
143
    connect( _session->emulation() , SIGNAL(outputChanged()) , this ,
 
144
            SLOT(fireActivity()) );
 
145
 
 
146
    // listen for detection of ZModem transfer
 
147
    connect( _session , SIGNAL(zmodemDetected()) , this , SLOT(zmodemDownload()) ); 
 
148
 
 
149
    // listen for flow control status changes
 
150
    connect( _session , SIGNAL(flowControlEnabledChanged(bool)) , _view ,
 
151
        SLOT(setFlowControlWarningEnabled(bool)) );
 
152
    _view->setFlowControlWarningEnabled(_session->flowControlEnabled());
 
153
 
 
154
    // take a snapshot of the session state every so often when
 
155
    // user activity occurs
 
156
    //
 
157
    // the timer is owned by the session so that it will be destroyed along
 
158
    // with the session
 
159
    QTimer* activityTimer = new QTimer(_session);
 
160
    activityTimer->setSingleShot(true);
 
161
    activityTimer->setInterval(2000);
 
162
    connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , activityTimer , SLOT(start()) );
 
163
    connect( activityTimer , SIGNAL(timeout()) , this , SLOT(snapshot()) );
 
164
}
 
165
 
 
166
void SessionController::updateSearchFilter()
 
167
{
 
168
    if ( _searchFilter ) 
 
169
    {
 
170
        Q_ASSERT( searchBar() && searchBar()->isVisible() );
 
171
 
 
172
        _view->processFilters();
 
173
    }
 
174
}
 
175
 
 
176
SessionController::~SessionController()
 
177
{
 
178
   if ( _view )
 
179
      _view->setScreenWindow(0);
 
180
 
 
181
   _allControllers.remove(this);
 
182
}
 
183
void SessionController::trackOutput(QKeyEvent* event)
 
184
{
 
185
    Q_ASSERT( _view->screenWindow() );
 
186
 
 
187
    // jump to the end of the history buffer unless the key pressed
 
188
    // is one of the three main modifiers, as these are used to select
 
189
    // the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection)
 
190
    switch (event->key())
 
191
    {
 
192
        case Qt::Key_Shift:
 
193
        case Qt::Key_Control:
 
194
        case Qt::Key_Alt:
 
195
            break;
 
196
        default:
 
197
            _view->screenWindow()->setTrackOutput(true);
 
198
    }
 
199
}
 
200
void SessionController::requireUrlFilterUpdate()
 
201
{
 
202
    // this method is called every time the screen window's output changes, so do not
 
203
    // do anything expensive here.
 
204
 
 
205
    _urlFilterUpdateRequired = true;
 
206
}
 
207
void SessionController::snapshot()
 
208
{
 
209
    Q_ASSERT( _session != 0 );
 
210
 
 
211
    QString title = _session->getDynamicTitle();    
 
212
    title         = title.simplified();
 
213
 
 
214
    // Visualize that the session is broadcasting to others
 
215
    if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
 
216
        title.append('*');
 
217
    }
 
218
    updateSessionIcon();
 
219
 
 
220
    // apply new title
 
221
    if ( !title.isEmpty() )
 
222
        _session->setTitle(Session::DisplayedTitleRole,title);
 
223
    else
 
224
        _session->setTitle(Session::DisplayedTitleRole,_session->title(Session::NameRole));
 
225
}
 
226
 
 
227
QString SessionController::currentDir() const
 
228
{
 
229
    return _session->currentWorkingDirectory();
 
230
}
 
231
 
 
232
KUrl SessionController::url() const
 
233
{
 
234
    return _session->getUrl();
 
235
}
 
236
 
 
237
void SessionController::rename()
 
238
{
 
239
    renameSession();
 
240
}
 
241
 
 
242
void SessionController::openUrl( const KUrl& url )
 
243
{
 
244
    // handle local paths
 
245
    if ( url.isLocalFile() )
 
246
    {
 
247
        QString path = url.toLocalFile();
 
248
        _session->emulation()->sendText("cd " + KShell::quoteArg(path) + '\r');
 
249
    }
 
250
    else if ( url.protocol() == "ssh" )
 
251
    {
 
252
        _session->emulation()->sendText("ssh ");
 
253
 
 
254
        if ( url.port() > -1 )
 
255
            _session->emulation()->sendText("-p " + QString::number(url.port()) + ' ' );
 
256
        if ( url.hasUser() )
 
257
            _session->emulation()->sendText(url.user() + '@');
 
258
        if ( url.hasHost() )
 
259
            _session->emulation()->sendText(url.host() + '\r');
 
260
    }
 
261
    else if ( url.protocol() == "telnet" )
 
262
    {
 
263
        _session->emulation()->sendText("telnet ");
 
264
 
 
265
        if ( url.hasUser() )
 
266
            _session->emulation()->sendText("-l " + url.user() + ' ');
 
267
        if ( url.hasHost() )
 
268
            _session->emulation()->sendText(url.host() + ' ');
 
269
        if ( url.port() > -1 )
 
270
            _session->emulation()->sendText(QString::number(url.port()));
 
271
         _session->emulation()->sendText("\r");
 
272
    }
 
273
    else
 
274
    {
 
275
        //TODO Implement handling for other Url types
 
276
 
 
277
        KMessageBox::sorry(_view->window(),
 
278
                           i18n("Konsole does not know how to open the bookmark: ") +
 
279
                           url.prettyUrl());
 
280
 
 
281
        kWarning(1211) << "Unable to open bookmark at url" << url << ", I do not know"
 
282
           << " how to handle the protocol " << url.protocol();
 
283
    }
 
284
}
 
285
 
 
286
bool SessionController::eventFilter(QObject* watched , QEvent* event)
 
287
{
 
288
    if ( watched == _view )
 
289
    {
 
290
        if ( event->type() == QEvent::FocusIn )
 
291
        {
 
292
            // notify the world that the view associated with this session has been focused
 
293
            // used by the view manager to update the title of the MainWindow widget containing the view
 
294
            emit focused(this);
 
295
 
 
296
            // when the view is focused, set bell events from the associated session to be delivered
 
297
            // by the focused view
 
298
 
 
299
            // first, disconnect any other views which are listening for bell signals from the session
 
300
            disconnect( _session , SIGNAL(bellRequest(const QString&)) , 0 , 0 );
 
301
            // second, connect the newly focused view to listen for the session's bell signal
 
302
            connect( _session , SIGNAL(bellRequest(const QString&)) ,
 
303
                    _view , SLOT(bell(const QString&)) );
 
304
                    
 
305
            if(_copyToAllTabsAction->isChecked()) {
 
306
                // A session with "Copy To All Tabs" has come into focus:
 
307
                // Ensure that newly created sessions are included in _copyToGroup!
 
308
                copyInputToAllTabs();
 
309
            }
 
310
        }
 
311
        // when a mouse move is received, create the URL filter and listen for output changes if
 
312
        // it has not already been created.  If it already exists, then update only if the output
 
313
        // has changed since the last update ( _urlFilterUpdateRequired == true )
 
314
        //
 
315
        // also check that no mouse buttons are pressed since the URL filter only applies when
 
316
        // the mouse is hovering over the view
 
317
        if ( event->type() == QEvent::MouseMove &&
 
318
            (!_viewUrlFilter || _urlFilterUpdateRequired) &&
 
319
            ((QMouseEvent*)event)->buttons() == Qt::NoButton )
 
320
        {
 
321
            if ( _view->screenWindow() && !_viewUrlFilter )
 
322
            {
 
323
                connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this ,
 
324
                        SLOT(requireUrlFilterUpdate()) );
 
325
                connect( _view->screenWindow() , SIGNAL(outputChanged()) , this ,
 
326
                         SLOT(requireUrlFilterUpdate()) );
 
327
 
 
328
                // install filter on the view to highlight URLs
 
329
                _viewUrlFilter = new UrlFilter();
 
330
                _view->filterChain()->addFilter( _viewUrlFilter );
 
331
            }
 
332
 
 
333
            _view->processFilters();
 
334
            _urlFilterUpdateRequired = false;
 
335
        }
 
336
    }
 
337
 
 
338
    return false;
 
339
}
 
340
 
 
341
void SessionController::removeSearchFilter()
 
342
{
 
343
    if (!_searchFilter)
 
344
        return;
 
345
 
 
346
    _view->filterChain()->removeFilter(_searchFilter);
 
347
    delete _searchFilter;
 
348
    _searchFilter = 0;
 
349
}
 
350
 
 
351
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
 
352
{
 
353
    // disconnect the existing search bar
 
354
    if ( _searchBar )
 
355
    {
 
356
        disconnect( this , 0 , _searchBar , 0 );
 
357
        disconnect( _searchBar , 0 , this , 0 );
 
358
    }
 
359
 
 
360
    // remove any existing search filter
 
361
    removeSearchFilter();
 
362
 
 
363
    // connect new search bar
 
364
    _searchBar = searchBar;
 
365
    if ( _searchBar )
 
366
    {
 
367
        connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
 
368
        connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
 
369
        connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
 
370
        connect( _searchBar , SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) );
 
371
 
 
372
        // if the search bar was previously active
 
373
        // then re-enter search mode
 
374
        searchHistory( _searchToggleAction->isChecked() );
 
375
    }
 
376
}
 
377
IncrementalSearchBar* SessionController::searchBar() const
 
378
{
 
379
    return _searchBar;
 
380
}
 
381
 
 
382
void SessionController::setShowMenuAction(QAction* action)
 
383
{
 
384
    actionCollection()->addAction("show-menubar",action);
 
385
}
 
386
 
 
387
void SessionController::setupActions()
 
388
{
 
389
    KAction* action = 0;
 
390
    KToggleAction* toggleAction = 0;
 
391
    KActionCollection* collection = actionCollection();
 
392
 
 
393
    // Close Session
 
394
    action = collection->addAction("close-session", this, SLOT(closeSession()));
 
395
    action->setIcon(KIcon("tab-close"));
 
396
    action->setText(i18n("&Close Tab"));
 
397
    action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W));
 
398
 
 
399
    // Open Browser
 
400
    action = collection->addAction("open-browser", this, SLOT(openBrowser()));
 
401
    action->setText(i18n("Open File Manager"));
 
402
    action->setIcon(KIcon("system-file-manager"));
 
403
 
 
404
    // Copy and Paste
 
405
    action = KStandardAction::copy(this, SLOT(copy()), collection);
 
406
    action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C));
 
407
 
 
408
    action = KStandardAction::paste(this, SLOT(paste()), collection);
 
409
    KShortcut pasteShortcut = action->shortcut();
 
410
    pasteShortcut.setPrimary(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V));
 
411
    pasteShortcut.setAlternate(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
 
412
    action->setShortcut(pasteShortcut);
 
413
 
 
414
    action = collection->addAction("paste-selection", this, SLOT(pasteSelection()));
 
415
    action->setText(i18n("Paste Selection"));
 
416
    action->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Insert));
 
417
 
 
418
    // Rename Session
 
419
    action = collection->addAction("rename-session", this, SLOT(renameSession()));
 
420
    action->setText( i18n("&Rename Tab...") );
 
421
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) );
 
422
 
 
423
    // Copy Input To -> All Tabs in Current Window
 
424
    _copyToAllTabsAction = collection->addAction("copy-input-to-all-tabs", this, SLOT(copyInputToAllTabs()));
 
425
    _copyToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
 
426
    _copyToAllTabsAction->setCheckable(true);
 
427
 
 
428
    // Copy Input To -> Select Tabs
 
429
    _copyToSelectedAction = collection->addAction("copy-input-to-selected-tabs", this, SLOT(copyInputToSelectedTabs()));
 
430
    _copyToSelectedAction->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Period) );
 
431
    _copyToSelectedAction->setText(i18n("&Select Tabs..."));
 
432
    _copyToSelectedAction->setCheckable(true);
 
433
 
 
434
    // Copy Input To -> None
 
435
    _copyToNoneAction = collection->addAction("copy-input-to-none", this, SLOT(copyInputToNone()));
 
436
    _copyToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
 
437
    _copyToNoneAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Slash));
 
438
    _copyToNoneAction->setCheckable(true);
 
439
    _copyToNoneAction->setChecked(true);
 
440
 
 
441
    action = collection->addAction("zmodem-upload", this, SLOT(zmodemUpload()));
 
442
    action->setText(i18n("&ZModem Upload..."));
 
443
    action->setIcon(KIcon("document-open"));
 
444
    action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U));
 
445
 
 
446
    // Monitor
 
447
    toggleAction = new KToggleAction(i18n("Monitor for &Activity"),this);
 
448
    toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A));
 
449
    action = collection->addAction("monitor-activity", toggleAction);
 
450
    connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorActivity(bool)));
 
451
 
 
452
    toggleAction = new KToggleAction(i18n("Monitor for &Silence"),this);
 
453
    toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_I));
 
454
    action = collection->addAction("monitor-silence", toggleAction);
 
455
    connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorSilence(bool)));
 
456
 
 
457
    // Character Encoding
 
458
    _codecAction = new KCodecAction(i18n("Set &Encoding"),this);
 
459
    _codecAction->setIcon(KIcon("character-set"));
 
460
    collection->addAction("set-encoding", _codecAction);
 
461
    connect(_codecAction->menu(), SIGNAL(aboutToShow()), this, SLOT(updateCodecAction()));
 
462
    connect(_codecAction, SIGNAL(triggered(QTextCodec*)), this, SLOT(changeCodec(QTextCodec*)));
 
463
 
 
464
    // Text Size
 
465
    action = collection->addAction("enlarge-font", this, SLOT(increaseTextSize()));
 
466
    action->setText(i18n("Enlarge Font"));
 
467
    action->setIcon(KIcon("format-font-size-more"));
 
468
    action->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Plus));
 
469
 
 
470
    action = collection->addAction("shrink-font", this, SLOT(decreaseTextSize()));
 
471
    action->setText(i18n("Shrink Font"));
 
472
    action->setIcon(KIcon("format-font-size-less"));
 
473
    action->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_Minus));
 
474
 
 
475
    // History
 
476
    _searchToggleAction = KStandardAction::find(this, NULL, collection);
 
477
    _searchToggleAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F));
 
478
    _searchToggleAction->setCheckable(true);
 
479
    connect(_searchToggleAction, SIGNAL(toggled(bool)), this, SLOT(searchHistory(bool)));
 
480
 
 
481
    _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection);
 
482
    _findNextAction->setEnabled(false);
 
483
 
 
484
    _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection);
 
485
    _findPreviousAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_F3));
 
486
    _findPreviousAction->setEnabled(false);
 
487
 
 
488
    action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection);
 
489
    action->setText(i18n("Save Output &As..."));
 
490
 
 
491
    action = collection->addAction("configure-history", this, SLOT(showHistoryOptions()));
 
492
    action->setText(i18n("Configure Scrollback..."));
 
493
    action->setIcon(KIcon("configure"));
 
494
 
 
495
    action = collection->addAction("clear-history", this, SLOT(clearHistory()));
 
496
    action->setText(i18n("Clear Scrollback"));
 
497
    action->setIcon(KIcon("edit-clear-history"));
 
498
 
 
499
    action = collection->addAction("clear-history-and-reset", this, SLOT(clearHistoryAndReset()));
 
500
    action->setText(i18n("Clear Scrollback and Reset"));
 
501
    action->setIcon(KIcon("edit-clear-history"));
 
502
    action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_X));
 
503
 
 
504
    // Profile Options
 
505
    action = collection->addAction("edit-current-profile", this, SLOT(editCurrentProfile()));
 
506
    action->setText(i18n("Configure Current Profile..."));
 
507
    action->setIcon(KIcon("document-properties") );
 
508
 
 
509
    _changeProfileMenu = new KActionMenu(i18n("Change Profile"), _view);
 
510
    collection->addAction("change-profile", _changeProfileMenu);
 
511
    connect(_changeProfileMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(prepareChangeProfileMenu()));
 
512
}
 
513
 
 
514
void SessionController::changeProfile(Profile::Ptr profile)
 
515
{
 
516
    SessionManager::instance()->setSessionProfile(_session,profile);
 
517
}
 
518
 
 
519
void SessionController::prepareChangeProfileMenu()
 
520
{
 
521
    if (_changeProfileMenu->menu()->isEmpty()) {
 
522
        _profileList = new ProfileList(false,this);
 
523
        connect(_profileList, SIGNAL(profileSelected(Profile::Ptr)), this, SLOT(changeProfile(Profile::Ptr)));
 
524
    }
 
525
 
 
526
    _changeProfileMenu->menu()->clear();
 
527
    _changeProfileMenu->menu()->addActions(_profileList->actions());
 
528
}
 
529
void SessionController::updateCodecAction()
 
530
{
 
531
    _codecAction->setCurrentCodec(QString(_session->emulation()->codec()->name()));
 
532
}
 
533
 
 
534
void SessionController::changeCodec(QTextCodec* codec)
 
535
{
 
536
    _session->setCodec(codec);
 
537
}
 
538
 
 
539
void SessionController::editCurrentProfile()
 
540
{
 
541
    EditProfileDialog* dialog = new EditProfileDialog( QApplication::activeWindow() );
 
542
 
 
543
    dialog->setProfile(SessionManager::instance()->sessionProfile(_session));
 
544
    dialog->show();
 
545
}
 
546
 
 
547
void SessionController::renameSession()
 
548
{
 
549
    QScopedPointer<RenameTabsDialog> dialog(new RenameTabsDialog(QApplication::activeWindow()));
 
550
    dialog->setTabTitleText(_session->tabTitleFormat(Session::LocalTabTitle));
 
551
    dialog->setRemoteTabTitleText(_session->tabTitleFormat(Session::RemoteTabTitle));
 
552
 
 
553
    if (!_session->isRemote()) {
 
554
        dialog->focusTabTitleText();
 
555
    } else {
 
556
        dialog->focusRemoteTabTitleText();
 
557
    }
 
558
 
 
559
    QPointer<Session> guard(_session);
 
560
    int result = dialog->exec();
 
561
    if (!guard)
 
562
        return;
 
563
 
 
564
    if (result)
 
565
    {
 
566
        QString tabTitle = dialog->tabTitleText();
 
567
        QString remoteTabTitle = dialog->remoteTabTitleText();
 
568
 
 
569
        _session->setTabTitleFormat(Session::LocalTabTitle, tabTitle);
 
570
        _session->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle);
 
571
 
 
572
        // trigger an update of the tab text
 
573
        snapshot();
 
574
    }
 
575
}
 
576
void SessionController::saveSession()
 
577
{
 
578
    Q_ASSERT(0); // not implemented yet
 
579
 
 
580
    //SaveSessionDialog dialog(_view);
 
581
    //int result = dialog.exec();
 
582
}
 
583
bool SessionController::confirmClose() const
 
584
{
 
585
    if (_session->isForegroundProcessActive())
 
586
    {
 
587
        QString title = _session->foregroundProcessName();
 
588
      
 
589
        // hard coded for now.  In future make it possible for the user to specify which programs
 
590
        // are ignored when considering whether to display a confirmation
 
591
        QStringList ignoreList; 
 
592
        ignoreList << QString(qgetenv("SHELL")).section('/',-1);
 
593
        if (ignoreList.contains(title))
 
594
            return true;
 
595
 
 
596
        QString question;
 
597
        if (title.isEmpty())
 
598
            question = i18n("A program is currently running in this session."
 
599
                            "  Are you sure you want to close it?");
 
600
        else
 
601
            question = i18n("The program '%1' is currently running in this session."  
 
602
                            "  Are you sure you want to close it?",title);
 
603
 
 
604
        int result = KMessageBox::warningYesNo(_view->window(),question,i18n("Confirm Close"));
 
605
        return (result == KMessageBox::Yes) ? true : false; 
 
606
    }
 
607
    return true;
 
608
}
 
609
void SessionController::closeSession()
 
610
{
 
611
    if (_preventClose)
 
612
        return;
 
613
 
 
614
    if (confirmClose())
 
615
        _session->close();
 
616
}
 
617
 
 
618
void SessionController::openBrowser()
 
619
{
 
620
    new KRun(url(), QApplication::activeWindow());
 
621
}
 
622
 
 
623
void SessionController::copy()
 
624
{
 
625
    _view->copyClipboard();
 
626
}
 
627
 
 
628
void SessionController::paste()
 
629
{
 
630
    _view->pasteClipboard();
 
631
}
 
632
void SessionController::pasteSelection()
 
633
{
 
634
    _view->pasteSelection();
 
635
}
 
636
static const KXmlGuiWindow* findWindow(const QObject* object)
 
637
{
 
638
    // Walk up the QObject hierarchy to find a KXmlGuiWindow.
 
639
    while(object != NULL) {
 
640
        const KXmlGuiWindow* window = dynamic_cast<const KXmlGuiWindow*>(object);
 
641
        if(window != NULL) {
 
642
            return(window);
 
643
        }
 
644
        object = object->parent();
 
645
    }
 
646
    return(NULL);
 
647
}
 
648
 
 
649
static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window)
 
650
{
 
651
    // Iterate all TerminalDisplays of this Session ...
 
652
    QListIterator<TerminalDisplay*> terminalDisplayIterator(session->views());
 
653
    while(terminalDisplayIterator.hasNext()) {
 
654
        const TerminalDisplay* terminalDisplay = terminalDisplayIterator.next();
 
655
        // ... and check whether a TerminalDisplay has the same
 
656
        // window as given in the parameter
 
657
        if(window == findWindow(terminalDisplay)) {
 
658
            return(true);    
 
659
        }
 
660
    }
 
661
    return(false);
 
662
}
 
663
 
 
664
void SessionController::copyInputToAllTabs()
 
665
{
 
666
    if(!_copyToGroup) {
 
667
        _copyToGroup = new SessionGroup(this);
 
668
    }
 
669
 
 
670
    // Find our window ...
 
671
    const KXmlGuiWindow* myWindow = findWindow(_view);
 
672
 
 
673
    QSet<Session*> group =
 
674
       QSet<Session*>::fromList(SessionManager::instance()->sessions());
 
675
    for(QSet<Session*>::iterator iterator = group.begin();
 
676
        iterator != group.end(); ++iterator) {
 
677
        Session* session = *iterator;
 
678
 
 
679
        // First, ensure that the session is removed
 
680
        // (necessary to avoid duplicates on addSession()!)
 
681
        _copyToGroup->removeSession(session);
 
682
 
 
683
        // Add current session if it is displayed our window
 
684
        if(hasTerminalDisplayInSameWindow(session, myWindow)) {
 
685
            _copyToGroup->addSession(session);
 
686
        }
 
687
    }
 
688
    _copyToGroup->setMasterStatus(_session, true);
 
689
    _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
 
690
    
 
691
    snapshot();
 
692
    _copyToAllTabsAction->setChecked(true);
 
693
    _copyToSelectedAction->setChecked(false);
 
694
    _copyToNoneAction->setChecked(false);
 
695
}
 
696
 
 
697
void SessionController::copyInputToSelectedTabs()
 
698
{
 
699
    if (!_copyToGroup)
 
700
    {
 
701
        _copyToGroup = new SessionGroup(this);
 
702
        _copyToGroup->addSession(_session);
 
703
        _copyToGroup->setMasterStatus(_session,true);
 
704
        _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
 
705
    }
 
706
 
 
707
    CopyInputDialog* dialog = new CopyInputDialog(_view);
 
708
    dialog->setMasterSession(_session);
 
709
    
 
710
    QSet<Session*> currentGroup = QSet<Session*>::fromList(_copyToGroup->sessions());
 
711
    currentGroup.remove(_session);
 
712
    
 
713
    dialog->setChosenSessions(currentGroup);
 
714
 
 
715
    QPointer<Session> guard(_session);
 
716
    int result = dialog->exec();
 
717
    if (!guard)
 
718
        return;
 
719
 
 
720
    if (result)
 
721
    {
 
722
        QSet<Session*> newGroup = dialog->chosenSessions();
 
723
        newGroup.remove(_session);
 
724
    
 
725
        QSet<Session*> completeGroup = newGroup | currentGroup;
 
726
        foreach(Session* session, completeGroup)
 
727
        {
 
728
            if (newGroup.contains(session) && !currentGroup.contains(session))
 
729
                _copyToGroup->addSession(session);
 
730
            else if (!newGroup.contains(session) && currentGroup.contains(session))
 
731
                _copyToGroup->removeSession(session);
 
732
        }
 
733
 
 
734
        _copyToGroup->setMasterStatus(_session, true);
 
735
        _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
 
736
        snapshot();        
 
737
    }
 
738
 
 
739
    delete dialog;
 
740
    _copyToAllTabsAction->setChecked(false);
 
741
    _copyToSelectedAction->setChecked(true);
 
742
    _copyToNoneAction->setChecked(false);
 
743
}
 
744
 
 
745
void SessionController::copyInputToNone()
 
746
{
 
747
    if (!_copyToGroup)      // No 'Copy To' is active
 
748
        return;
 
749
 
 
750
    QSet<Session*> group =
 
751
       QSet<Session*>::fromList(SessionManager::instance()->sessions());
 
752
    for(QSet<Session*>::iterator iterator = group.begin();
 
753
        iterator != group.end(); ++iterator) {
 
754
        Session* session = *iterator;
 
755
 
 
756
        if(session != _session) {
 
757
            _copyToGroup->removeSession(*iterator);
 
758
        }
 
759
    }
 
760
    delete _copyToGroup;
 
761
    _copyToGroup = NULL;
 
762
    snapshot();
 
763
    
 
764
    _copyToAllTabsAction->setChecked(false);
 
765
    _copyToSelectedAction->setChecked(false);
 
766
    _copyToNoneAction->setChecked(true);
 
767
}
 
768
 
 
769
void SessionController::searchClosed()
 
770
{
 
771
    _searchToggleAction->toggle();
 
772
}
 
773
 
 
774
#if 0
 
775
void SessionController::searchHistory()
 
776
{
 
777
    searchHistory(true);
 
778
}
 
779
#endif
 
780
 
 
781
void SessionController::listenForScreenWindowUpdates()
 
782
{
 
783
    if (_listenForScreenWindowUpdates)
 
784
        return;
 
785
 
 
786
    connect( _view->screenWindow() , SIGNAL(outputChanged()) , this , 
 
787
            SLOT(updateSearchFilter()) );
 
788
    connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this , 
 
789
            SLOT(updateSearchFilter()) );
 
790
 
 
791
    _listenForScreenWindowUpdates = true;
 
792
}
 
793
 
 
794
// searchHistory() may be called either as a result of clicking a menu item or
 
795
// as a result of changing the search bar widget
 
796
void SessionController::searchHistory(bool showSearchBar)
 
797
{
 
798
    if ( _searchBar )
 
799
    {
 
800
        _searchBar->setVisible(showSearchBar);
 
801
 
 
802
        if (showSearchBar)
 
803
        {
 
804
            removeSearchFilter();
 
805
 
 
806
            listenForScreenWindowUpdates();
 
807
            
 
808
            _searchFilter = new RegExpFilter();
 
809
            _view->filterChain()->addFilter(_searchFilter);
 
810
            connect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
 
811
                    SLOT(searchTextChanged(const QString&)) );
 
812
 
 
813
            // invoke search for matches for the current search text
 
814
            const QString& currentSearchText = _searchBar->searchText();
 
815
            if (!currentSearchText.isEmpty())
 
816
            {
 
817
                searchTextChanged(currentSearchText);
 
818
            }
 
819
 
 
820
            setFindNextPrevEnabled(true);
 
821
        }
 
822
        else
 
823
        {
 
824
            setFindNextPrevEnabled(false);
 
825
 
 
826
            disconnect( _searchBar , SIGNAL(searchChanged(const QString&)) , this ,
 
827
                    SLOT(searchTextChanged(const QString&)) );
 
828
 
 
829
            removeSearchFilter();
 
830
 
 
831
            _view->setFocus( Qt::ActiveWindowFocusReason );
 
832
        }
 
833
    }
 
834
}
 
835
void SessionController::setFindNextPrevEnabled(bool enabled)
 
836
{
 
837
    _findNextAction->setEnabled(enabled);
 
838
    _findPreviousAction->setEnabled(enabled);
 
839
}
 
840
void SessionController::searchTextChanged(const QString& text)
 
841
{
 
842
    Q_ASSERT( _view->screenWindow() );
 
843
 
 
844
    if ( text.isEmpty() )
 
845
        _view->screenWindow()->clearSelection();
 
846
 
 
847
    // update search.  this is called even when the text is
 
848
    // empty to clear the view's filters
 
849
    beginSearch(text , SearchHistoryTask::ForwardsSearch);
 
850
}
 
851
void SessionController::searchCompleted(bool success)
 
852
{
 
853
    if ( _searchBar )
 
854
        _searchBar->setFoundMatch(success);
 
855
}
 
856
 
 
857
void SessionController::beginSearch(const QString& text , int direction)
 
858
{
 
859
    Q_ASSERT( _searchBar );
 
860
    Q_ASSERT( _searchFilter );
 
861
 
 
862
    Qt::CaseSensitivity caseHandling = _searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive;
 
863
    QRegExp::PatternSyntax syntax = _searchBar->matchRegExp() ? QRegExp::RegExp : QRegExp::FixedString;
 
864
 
 
865
    QRegExp regExp( text.trimmed() ,  caseHandling , syntax );
 
866
    _searchFilter->setRegExp(regExp);
 
867
 
 
868
    if ( !regExp.isEmpty() )
 
869
    {
 
870
        SearchHistoryTask* task = new SearchHistoryTask(this);
 
871
 
 
872
        connect( task , SIGNAL(completed(bool)) , this , SLOT(searchCompleted(bool)) );
 
873
 
 
874
        task->setRegExp(regExp);
 
875
        task->setSearchDirection( (SearchHistoryTask::SearchDirection)direction );
 
876
        task->setAutoDelete(true);
 
877
        task->addScreenWindow( _session , _view->screenWindow() );
 
878
        task->execute();
 
879
    }
 
880
 
 
881
    _view->processFilters();
 
882
}
 
883
void SessionController::highlightMatches(bool highlight)
 
884
{
 
885
    if ( highlight )
 
886
    {
 
887
        _view->filterChain()->addFilter(_searchFilter);
 
888
        _view->processFilters();
 
889
    }
 
890
    else
 
891
    {
 
892
        _view->filterChain()->removeFilter(_searchFilter);
 
893
    }
 
894
 
 
895
    _view->update();
 
896
}
 
897
void SessionController::findNextInHistory()
 
898
{
 
899
    Q_ASSERT( _searchBar );
 
900
    Q_ASSERT( _searchFilter );
 
901
 
 
902
    beginSearch(_searchBar->searchText(),SearchHistoryTask::ForwardsSearch);
 
903
}
 
904
void SessionController::findPreviousInHistory()
 
905
{
 
906
    Q_ASSERT( _searchBar );
 
907
    Q_ASSERT( _searchFilter );
 
908
 
 
909
    beginSearch(_searchBar->searchText(),SearchHistoryTask::BackwardsSearch);
 
910
}
 
911
void SessionController::showHistoryOptions()
 
912
{
 
913
    HistorySizeDialog* dialog = new HistorySizeDialog( QApplication::activeWindow() );
 
914
    const HistoryType& currentHistory = _session->historyType();
 
915
 
 
916
    if ( currentHistory.isEnabled() )
 
917
    {
 
918
        if ( currentHistory.isUnlimited() )
 
919
            dialog->setMode( HistorySizeDialog::UnlimitedHistory );
 
920
        else
 
921
        {
 
922
            dialog->setMode( HistorySizeDialog::FixedSizeHistory );
 
923
            dialog->setLineCount( currentHistory.maximumLineCount() );
 
924
        }
 
925
    }
 
926
    else
 
927
        dialog->setMode( HistorySizeDialog::NoHistory );
 
928
 
 
929
    connect( dialog , SIGNAL(optionsChanged(int,int,bool)) ,
 
930
             this , SLOT(scrollBackOptionsChanged(int,int,bool)) );
 
931
 
 
932
    dialog->show();
 
933
}
 
934
void SessionController::sessionResizeRequest(const QSize& size)
 
935
{
 
936
    //kDebug(1211) << "View resize requested to " << size;
 
937
    _view->setSize(size.width(),size.height());
 
938
}
 
939
void SessionController::scrollBackOptionsChanged(int mode, int lines, bool saveToCurrentProfile )
 
940
{
 
941
    switch (mode)
 
942
    {
 
943
        case HistorySizeDialog::NoHistory:
 
944
            _session->setHistoryType( HistoryTypeNone() );
 
945
            break;
 
946
         case HistorySizeDialog::FixedSizeHistory:
 
947
            _session->setHistoryType( CompactHistoryType(lines) );
 
948
            break;
 
949
         case HistorySizeDialog::UnlimitedHistory:
 
950
             _session->setHistoryType( HistoryTypeFile() );
 
951
            break;
 
952
    }
 
953
    if (saveToCurrentProfile)
 
954
    {
 
955
        Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session);
 
956
 
 
957
        switch (mode)
 
958
        {
 
959
            case HistorySizeDialog::NoHistory:
 
960
                profile->setProperty(Profile::HistoryMode , Profile::DisableHistory);
 
961
                break;
 
962
            case HistorySizeDialog::FixedSizeHistory:
 
963
                profile->setProperty(Profile::HistoryMode , Profile::FixedSizeHistory);
 
964
                profile->setProperty(Profile::HistorySize , lines);
 
965
                break;
 
966
            case HistorySizeDialog::UnlimitedHistory:
 
967
                profile->setProperty(Profile::HistoryMode , Profile::UnlimitedHistory);
 
968
                break;
 
969
        }
 
970
        SessionManager::instance()->changeProfile(profile, profile->setProperties());
 
971
    }
 
972
}
 
973
 
 
974
void SessionController::saveHistory()
 
975
{
 
976
    SessionTask* task = new SaveHistoryTask(this);
 
977
    task->setAutoDelete(true);
 
978
    task->addSession( _session );
 
979
    task->execute();
 
980
}
 
981
 
 
982
void SessionController::clearHistory()
 
983
{
 
984
    _session->clearHistory();
 
985
    _view->updateImage();   // To reset view scrollbar
 
986
}
 
987
 
 
988
void SessionController::clearHistoryAndReset()
 
989
{
 
990
    Emulation* emulation = _session->emulation();
 
991
    emulation->reset();
 
992
    _session->refresh();
 
993
    clearHistory();
 
994
}
 
995
 
 
996
void SessionController::increaseTextSize()
 
997
{
 
998
    QFont font = _view->getVTFont();
 
999
    font.setPointSizeF(font.pointSizeF()+1);
 
1000
    _view->setVTFont(font);
 
1001
 
 
1002
    //TODO - Save this setting as a session default
 
1003
}
 
1004
 
 
1005
void SessionController::decreaseTextSize()
 
1006
{
 
1007
    static const qreal MinimumFontSize = 6;
 
1008
 
 
1009
    QFont font = _view->getVTFont();
 
1010
    font.setPointSizeF( qMax(font.pointSizeF()-1,MinimumFontSize) );
 
1011
    _view->setVTFont(font);
 
1012
 
 
1013
    //TODO - Save this setting as a session default
 
1014
}
 
1015
 
 
1016
void SessionController::monitorActivity(bool monitor)
 
1017
{
 
1018
    _session->setMonitorActivity(monitor);
 
1019
}
 
1020
void SessionController::monitorSilence(bool monitor)
 
1021
{
 
1022
    _session->setMonitorSilence(monitor);
 
1023
}
 
1024
void SessionController::updateSessionIcon()
 
1025
{
 
1026
    // Visualize that the session is broadcasting to others
 
1027
    if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
 
1028
        // Master Mode: set different icon, to warn the user to be careful
 
1029
        setIcon(KIcon("emblem-important"));
 
1030
    }
 
1031
    else {
 
1032
        // Not in Master Mode: use normal icon
 
1033
        setIcon( _sessionIcon );
 
1034
    }
 
1035
}
 
1036
void SessionController::sessionTitleChanged()
 
1037
{
 
1038
        if ( _sessionIconName != _session->iconName() )
 
1039
        {
 
1040
            _sessionIconName = _session->iconName();
 
1041
            _sessionIcon = KIcon( _sessionIconName );
 
1042
            updateSessionIcon();
 
1043
        }
 
1044
 
 
1045
        QString title = _session->title(Session::DisplayedTitleRole);
 
1046
 
 
1047
        // special handling for the "%w" marker which is replaced with the
 
1048
        // window title set by the shell
 
1049
        title.replace("%w",_session->userTitle());
 
1050
        // special handling for the "%#" marker which is replaced with the 
 
1051
        // number of the shell
 
1052
        title.replace("%#",QString::number(_session->sessionId()));
 
1053
 
 
1054
       if ( title.isEmpty() )
 
1055
          title = _session->title(Session::NameRole);
 
1056
 
 
1057
       setTitle( title );
 
1058
}
 
1059
 
 
1060
void SessionController::showDisplayContextMenu(const QPoint& position)
 
1061
{
 
1062
    // needed to make sure the popup menu is available, even if a hosting
 
1063
    // application did not merge our GUI.
 
1064
    if (!factory())
 
1065
    {
 
1066
        if (!clientBuilder())
 
1067
        {
 
1068
            setClientBuilder(new KXMLGUIBuilder(_view));
 
1069
        }
 
1070
 
 
1071
        KXMLGUIFactory* factory = new KXMLGUIFactory(clientBuilder(), this);
 
1072
        factory->addClient(this);
 
1073
        //kDebug(1211) << "Created xmlgui factory" << factory;
 
1074
    }
 
1075
 
 
1076
    QMenu* popup = qobject_cast<QMenu*>(factory()->container("session-popup-menu",this));
 
1077
    if (popup)
 
1078
    {
 
1079
        // prepend content-specific actions such as "Open Link", "Copy Email Address" etc.
 
1080
        QList<QAction*> contentActions = _view->filterActions(position);
 
1081
        QAction* contentSeparator = new QAction(popup);
 
1082
        contentSeparator->setSeparator(true);
 
1083
        contentActions << contentSeparator;
 
1084
 
 
1085
        _preventClose = true;
 
1086
 
 
1087
        popup->insertActions(popup->actions().value(0,0),contentActions);
 
1088
        QAction* chosen = popup->exec( _view->mapToGlobal(position) );
 
1089
 
 
1090
        // remove content-specific actions, unless the close action was chosen
 
1091
        // in which case the popup menu will be partially destroyed at this point
 
1092
           foreach(QAction* action,contentActions)
 
1093
            popup->removeAction(action);
 
1094
        delete contentSeparator;
 
1095
 
 
1096
        _preventClose = false;
 
1097
 
 
1098
        if (chosen && chosen->objectName() == "close-session")
 
1099
            chosen->trigger();
 
1100
    }
 
1101
    else
 
1102
    {
 
1103
        kWarning() << "Unable to display popup menu for session"
 
1104
                   << _session->title(Session::NameRole)
 
1105
                   << ", no GUI factory available to build the popup.";
 
1106
    }
 
1107
}
 
1108
 
 
1109
void SessionController::sessionStateChanged(int state)
 
1110
{
 
1111
    if ( state == _previousState )
 
1112
        return;
 
1113
 
 
1114
    _previousState = state;
 
1115
 
 
1116
    // TODO - Replace the icon choices below when suitable icons for silence and activity
 
1117
    // are available
 
1118
    if ( state == NOTIFYACTIVITY )
 
1119
    {
 
1120
        if (_activityIcon.isNull())
 
1121
        {
 
1122
            _activityIcon = KIcon("dialog-information");
 
1123
        }
 
1124
 
 
1125
        setIcon(_activityIcon);
 
1126
    }
 
1127
    else if ( state == NOTIFYSILENCE )
 
1128
    {
 
1129
        if (_silenceIcon.isNull())
 
1130
        {
 
1131
            _silenceIcon = KIcon("dialog-information");
 
1132
        }
 
1133
 
 
1134
        setIcon(_silenceIcon);
 
1135
    }
 
1136
    else if ( state == NOTIFYNORMAL )
 
1137
    {
 
1138
        if ( _sessionIconName != _session->iconName() )
 
1139
        {
 
1140
            _sessionIconName = _session->iconName();
 
1141
            _sessionIcon = KIcon( _sessionIconName );
 
1142
        }
 
1143
 
 
1144
        updateSessionIcon();
 
1145
    }
 
1146
}
 
1147
 
 
1148
void SessionController::zmodemDownload()
 
1149
{
 
1150
    QString zmodem = KGlobal::dirs()->findExe("rz");
 
1151
    if(zmodem.isEmpty()) {
 
1152
       zmodem = KGlobal::dirs()->findExe("lrz");
 
1153
    }
 
1154
    if(!zmodem.isEmpty()) {
 
1155
        const QString path = KFileDialog::getExistingDirectory(
 
1156
                                QString(), _view,
 
1157
                                i18n("Save ZModem Download to..."));
 
1158
 
 
1159
        if(!path.isEmpty()) {
 
1160
            _session->startZModem(zmodem, path, QStringList());
 
1161
            return;
 
1162
        }
 
1163
    }
 
1164
    else {
 
1165
        KMessageBox::error(_view,
 
1166
          i18n("<p>A ZModem file transfer attempt has been detected, "
 
1167
               "but no suitable ZModem software was found on this system.</p>"
 
1168
               "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
 
1169
    }
 
1170
    _session->cancelZModem();
 
1171
    return;
 
1172
}
 
1173
 
 
1174
void SessionController::zmodemUpload()
 
1175
{
 
1176
    if(_session->isZModemBusy()) {
 
1177
      KMessageBox::sorry(_view,
 
1178
         i18n("<p>The current session already has a ZModem file transfer in progress.</p>"));
 
1179
      return;
 
1180
    }
 
1181
    QString zmodem = KGlobal::dirs()->findExe("sz");
 
1182
    if(zmodem.isEmpty()) {
 
1183
       zmodem = KGlobal::dirs()->findExe("lsz");
 
1184
    }
 
1185
    if(zmodem.isEmpty()) {
 
1186
        KMessageBox::sorry(_view,
 
1187
           i18n("<p>No suitable ZModem software was found on this system.</p>"
 
1188
                "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
 
1189
        return;
 
1190
    }
 
1191
 
 
1192
    QStringList files = KFileDialog::getOpenFileNames(KUrl(), QString(), _view,
 
1193
                           i18n("Select Files for ZModem Upload"));
 
1194
    if(!files.isEmpty()) {
 
1195
        _session->startZModem(zmodem, QString(), files);
 
1196
    }
 
1197
}
 
1198
 
 
1199
bool SessionController::isKonsolePart() const
 
1200
{
 
1201
    // Check to see if we are being called from Konsole or a KPart
 
1202
    if (QString(qApp->metaObject()->className()) == "Konsole::Application")
 
1203
        return false;
 
1204
    else
 
1205
        return true;
 
1206
}
 
1207
 
 
1208
SessionTask::SessionTask(QObject* parent)
 
1209
    :  QObject(parent)
 
1210
    ,  _autoDelete(false)
 
1211
{
 
1212
}
 
1213
void SessionTask::setAutoDelete(bool enable)
 
1214
{
 
1215
    _autoDelete = enable;
 
1216
}
 
1217
bool SessionTask::autoDelete() const
 
1218
{
 
1219
    return _autoDelete;
 
1220
}
 
1221
void SessionTask::addSession(Session* session)
 
1222
{
 
1223
    _sessions << session;
 
1224
}
 
1225
QList<SessionPtr> SessionTask::sessions() const
 
1226
{
 
1227
    return _sessions;
 
1228
}
 
1229
 
 
1230
SaveHistoryTask::SaveHistoryTask(QObject* parent)
 
1231
    : SessionTask(parent)
 
1232
{
 
1233
}
 
1234
SaveHistoryTask::~SaveHistoryTask()
 
1235
{
 
1236
}
 
1237
 
 
1238
void SaveHistoryTask::execute()
 
1239
{
 
1240
    QListIterator<SessionPtr> iter(sessions());
 
1241
 
 
1242
    // TODO - think about the UI when saving multiple history sessions, if there are more than two or
 
1243
    //        three then providing a URL for each one will be tedious
 
1244
 
 
1245
    // TODO - show a warning ( preferably passive ) if saving the history output fails
 
1246
    //
 
1247
 
 
1248
     KFileDialog* dialog = new KFileDialog( QString(":konsole") /* check this */,
 
1249
                                               QString(), QApplication::activeWindow() );
 
1250
     dialog->setOperationMode(KFileDialog::Saving);
 
1251
     dialog->setConfirmOverwrite(true);
 
1252
 
 
1253
     QStringList mimeTypes;
 
1254
     mimeTypes << "text/plain";
 
1255
     mimeTypes << "text/html";
 
1256
     dialog->setMimeFilter(mimeTypes,"text/plain");
 
1257
 
 
1258
     // iterate over each session in the task and display a dialog to allow the user to choose where
 
1259
     // to save that session's history.
 
1260
     // then start a KIO job to transfer the data from the history to the chosen URL
 
1261
    while ( iter.hasNext() )
 
1262
    {
 
1263
        SessionPtr session = iter.next();
 
1264
 
 
1265
        dialog->setCaption( i18n("Save Output From %1",session->title(Session::NameRole)) );
 
1266
 
 
1267
        int result = dialog->exec();
 
1268
 
 
1269
        if ( result != QDialog::Accepted )
 
1270
            continue;
 
1271
 
 
1272
        KUrl url = dialog->selectedUrl();
 
1273
 
 
1274
        if ( !url.isValid() )
 
1275
        { // UI:  Can we make this friendlier?
 
1276
            KMessageBox::sorry( 0 , i18n("%1 is an invalid URL, the output could not be saved.",url.url()) );
 
1277
            continue;
 
1278
        }
 
1279
 
 
1280
        KIO::TransferJob* job = KIO::put( url,
 
1281
                                          -1,   // no special permissions
 
1282
                                          // overwrite existing files
 
1283
                                          // do not resume an existing transfer
 
1284
                                          // show progress information only for remote
 
1285
                                          // URLs
 
1286
                                          KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags)
 
1287
                                                             // a better solution would be to show progress
 
1288
                                                             // information after a certain period of time
 
1289
                                                             // instead, since the overall speed of transfer
 
1290
                                                             // depends on factors other than just the protocol
 
1291
                                                             // used
 
1292
                                        );
 
1293
 
 
1294
 
 
1295
        SaveJob jobInfo;
 
1296
        jobInfo.session = session;
 
1297
        jobInfo.lastLineFetched = -1;  // when each request for data comes in from the KIO subsystem
 
1298
                                       // lastLineFetched is used to keep track of how much of the history
 
1299
                                       // has already been sent, and where the next request should continue
 
1300
                                       // from.
 
1301
                                       // this is set to -1 to indicate the job has just been started
 
1302
 
 
1303
        if ( dialog->currentMimeFilter() == "text/html" )
 
1304
           jobInfo.decoder = new HTMLDecoder();
 
1305
        else
 
1306
           jobInfo.decoder = new PlainTextDecoder();
 
1307
 
 
1308
        _jobSession.insert(job,jobInfo);
 
1309
 
 
1310
        connect( job , SIGNAL(dataReq(KIO::Job*,QByteArray&)),
 
1311
                 this, SLOT(jobDataRequested(KIO::Job*,QByteArray&)) );
 
1312
        connect( job , SIGNAL(result(KJob*)),
 
1313
                 this, SLOT(jobResult(KJob*)) );
 
1314
    }
 
1315
 
 
1316
    dialog->deleteLater();
 
1317
}
 
1318
void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data)
 
1319
{
 
1320
    // TODO - Report progress information for the job
 
1321
 
 
1322
    // PERFORMANCE:  Do some tests and tweak this value to get faster saving
 
1323
    const int LINES_PER_REQUEST = 500;
 
1324
 
 
1325
    SaveJob& info = _jobSession[job];
 
1326
 
 
1327
    // transfer LINES_PER_REQUEST lines from the session's history
 
1328
    // to the save location
 
1329
    if ( info.session )
 
1330
    {
 
1331
        // note:  when retrieving lines from the emulation,
 
1332
        // the first line is at index 0.
 
1333
 
 
1334
        int sessionLines = info.session->emulation()->lineCount();
 
1335
 
 
1336
        if ( sessionLines-1 == info.lastLineFetched )
 
1337
            return; // if there is no more data to transfer then stop the job
 
1338
 
 
1339
        int copyUpToLine = qMin( info.lastLineFetched + LINES_PER_REQUEST ,
 
1340
                                 sessionLines-1 );
 
1341
 
 
1342
        QTextStream stream(&data,QIODevice::ReadWrite);
 
1343
        info.decoder->begin(&stream);
 
1344
        info.session->emulation()->writeToStream( info.decoder , info.lastLineFetched+1 , copyUpToLine );
 
1345
        info.decoder->end();
 
1346
 
 
1347
        // if there are still more lines to process after this request
 
1348
        // then insert a new line character
 
1349
        // to ensure that the next block of lines begins on a new line
 
1350
        //
 
1351
        // FIXME - There is still an extra new-line at the end of the save data.
 
1352
        if ( copyUpToLine <= sessionLines-1 )
 
1353
        {
 
1354
            stream << '\n';
 
1355
        }
 
1356
 
 
1357
 
 
1358
        info.lastLineFetched = copyUpToLine;
 
1359
    }
 
1360
}
 
1361
void SaveHistoryTask::jobResult(KJob* job)
 
1362
{
 
1363
    if ( job->error() )
 
1364
    {
 
1365
        KMessageBox::sorry( 0 , i18n("A problem occurred when saving the output.\n%1",job->errorString()) );
 
1366
    }
 
1367
 
 
1368
    TerminalCharacterDecoder * decoder = _jobSession[job].decoder;
 
1369
 
 
1370
    _jobSession.remove(job);
 
1371
 
 
1372
    delete decoder;
 
1373
 
 
1374
    // notify the world that the task is done
 
1375
    emit completed(true);
 
1376
 
 
1377
    if ( autoDelete() )
 
1378
        deleteLater();
 
1379
}
 
1380
void SearchHistoryTask::addScreenWindow( Session* session , ScreenWindow* searchWindow )
 
1381
{
 
1382
   _windows.insert(session,searchWindow);
 
1383
}
 
1384
void SearchHistoryTask::execute()
 
1385
{
 
1386
    QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows);
 
1387
 
 
1388
    while ( iter.hasNext() )
 
1389
    {
 
1390
        iter.next();
 
1391
        executeOnScreenWindow( iter.key() , iter.value() );
 
1392
    }
 
1393
}
 
1394
 
 
1395
void SearchHistoryTask::executeOnScreenWindow( SessionPtr session , ScreenWindowPtr window )
 
1396
{
 
1397
    Q_ASSERT( session );
 
1398
    Q_ASSERT( window );
 
1399
 
 
1400
    Emulation* emulation = session->emulation();
 
1401
 
 
1402
    int selectionColumn = 0;
 
1403
    int selectionLine = 0;
 
1404
 
 
1405
    window->getSelectionEnd(selectionColumn , selectionLine);
 
1406
 
 
1407
    if ( !_regExp.isEmpty() )
 
1408
    {
 
1409
        int pos = -1;
 
1410
        const bool forwards = ( _direction == ForwardsSearch );
 
1411
        int startLine = selectionLine + window->currentLine() + ( forwards ? 1 : -1 );
 
1412
        // Temporary fix for #205495
 
1413
        if (startLine < 0) startLine = 0;
 
1414
        const int lastLine = window->lineCount() - 1;
 
1415
        QString string;
 
1416
 
 
1417
        //text stream to read history into string for pattern or regular expression searching
 
1418
        QTextStream searchStream(&string);
 
1419
 
 
1420
        PlainTextDecoder decoder;
 
1421
        decoder.setRecordLinePositions(true);
 
1422
 
 
1423
        //setup first and last lines depending on search direction
 
1424
        int line = startLine;
 
1425
 
 
1426
        //read through and search history in blocks of 10K lines.
 
1427
        //this balances the need to retrieve lots of data from the history each time
 
1428
        //(for efficient searching)
 
1429
        //without using silly amounts of memory if the history is very large.
 
1430
        const int maxDelta = qMin(window->lineCount(),10000);
 
1431
        int delta = forwards ? maxDelta : -maxDelta;
 
1432
 
 
1433
        int endLine = line;
 
1434
        bool hasWrapped = false;  // set to true when we reach the top/bottom
 
1435
                                  // of the output and continue from the other
 
1436
                                  // end
 
1437
 
 
1438
        //loop through history in blocks of <delta> lines.
 
1439
        do
 
1440
        {
 
1441
            // ensure that application does not appear to hang
 
1442
            // if searching through a lengthy output
 
1443
            QApplication::processEvents();
 
1444
 
 
1445
            // calculate lines to search in this iteration
 
1446
            if ( hasWrapped )
 
1447
            {
 
1448
                if ( endLine == lastLine )
 
1449
                    line = 0;
 
1450
                else if ( endLine == 0 )
 
1451
                    line = lastLine;
 
1452
 
 
1453
                endLine += delta;
 
1454
 
 
1455
                if ( forwards )
 
1456
                   endLine = qMin( startLine , endLine );
 
1457
                else
 
1458
                   endLine = qMax( startLine , endLine );
 
1459
            }
 
1460
            else
 
1461
            {
 
1462
                endLine += delta;
 
1463
 
 
1464
                if ( endLine > lastLine )
 
1465
                {
 
1466
                    hasWrapped = true;
 
1467
                    endLine = lastLine;
 
1468
                } else if ( endLine < 0 )
 
1469
                {
 
1470
                    hasWrapped = true;
 
1471
                    endLine = 0;
 
1472
                }
 
1473
            }
 
1474
 
 
1475
            decoder.begin(&searchStream);
 
1476
            emulation->writeToStream(&decoder, qMin(endLine,line) , qMax(endLine,line) );
 
1477
            decoder.end();
 
1478
 
 
1479
            // line number search below assumes that the buffer ends with a new-line 
 
1480
            string.append('\n');
 
1481
 
 
1482
            pos = -1;
 
1483
            if (forwards)
 
1484
                pos = string.indexOf(_regExp);
 
1485
            else
 
1486
                pos = string.lastIndexOf(_regExp);
 
1487
 
 
1488
            //if a match is found, position the cursor on that line and update the screen
 
1489
            if ( pos != -1 )
 
1490
            {
 
1491
                int newLines = 0;
 
1492
                QList<int> linePositions = decoder.linePositions();
 
1493
                while (newLines < linePositions.count() && linePositions[newLines] <= pos)
 
1494
                    newLines++;
 
1495
 
 
1496
                // ignore the new line at the start of the buffer
 
1497
                newLines--;
 
1498
 
 
1499
                int findPos = qMin(line,endLine) + newLines;
 
1500
 
 
1501
                highlightResult(window,findPos);
 
1502
 
 
1503
                emit completed(true);
 
1504
 
 
1505
                return;
 
1506
            }
 
1507
 
 
1508
            //clear the current block of text and move to the next one
 
1509
            string.clear();
 
1510
            line = endLine;
 
1511
 
 
1512
        } while ( startLine != endLine );
 
1513
 
 
1514
        // if no match was found, clear selection to indicate this
 
1515
        window->clearSelection();
 
1516
        window->notifyOutputChanged();
 
1517
    }
 
1518
 
 
1519
    emit completed(false);
 
1520
}
 
1521
void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos)
 
1522
{
 
1523
     //work out how many lines into the current block of text the search result was found
 
1524
     //- looks a little painful, but it only has to be done once per search.
 
1525
 
 
1526
     //kDebug(1211) << "Found result at line " << findPos;
 
1527
 
 
1528
     //update display to show area of history containing selection
 
1529
     window->scrollTo(findPos);
 
1530
     window->setSelectionStart( 0 , findPos - window->currentLine() , false );
 
1531
     window->setSelectionEnd( window->columnCount() , findPos - window->currentLine() );
 
1532
     window->setTrackOutput(false);
 
1533
     window->notifyOutputChanged();
 
1534
}
 
1535
 
 
1536
SearchHistoryTask::SearchHistoryTask(QObject* parent)
 
1537
    : SessionTask(parent)
 
1538
    , _direction(ForwardsSearch)
 
1539
{
 
1540
 
 
1541
}
 
1542
void SearchHistoryTask::setSearchDirection( SearchDirection direction )
 
1543
{
 
1544
    _direction = direction;
 
1545
}
 
1546
SearchHistoryTask::SearchDirection SearchHistoryTask::searchDirection() const
 
1547
{
 
1548
    return _direction;
 
1549
}
 
1550
void SearchHistoryTask::setRegExp(const QRegExp& expression)
 
1551
{
 
1552
    _regExp = expression;
 
1553
}
 
1554
QRegExp SearchHistoryTask::regExp() const
 
1555
{
 
1556
    return _regExp;
 
1557
}
 
1558
 
 
1559
#include "SessionController.moc"
 
1560