SessionController.cpp 52.1 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
    Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/

// Own
#include "SessionController.h"
23

24
// Qt
25
#include <QtGui/QApplication>
26 27
#include <QMenu>

28 29
// KDE
#include <KAction>
Thomas Zander's avatar
Thomas Zander committed
30
#include <KDebug>
Robert Knight's avatar
 
Robert Knight committed
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>
Robert Knight's avatar
 
Robert Knight committed
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 "ScreenWindow.h"
56
#include "Session.h"
57
#include "ProfileList.h"
58
#include "TerminalDisplay.h"
59
#include "SessionManager.h"
60

61 62
// for SaveHistoryTask
#include <KFileDialog>
63
#include <KIO/Job>
64 65 66 67
#include <KJob>
#include "TerminalCharacterDecoder.h"


68
using namespace Konsole;
69

Robert Knight's avatar
 
Robert Knight committed
70 71
KIcon SessionController::_activityIcon;
KIcon SessionController::_silenceIcon;
72
QSet<SessionController*> SessionController::_allControllers;
73
QPointer<SearchHistoryThread> SearchHistoryTask::_thread;
74
int SessionController::_lastControllerId;
Robert Knight's avatar
 
Robert Knight committed
75

76
SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent)
Robert Knight's avatar
 
Robert Knight committed
77
    : ViewProperties(parent)
78 79 80
    , KXMLGUIClient()
    , _session(session)
    , _view(view)
81
    , _copyToGroup(0)
82
    , _profileList(0)
Robert Knight's avatar
 
Robert Knight committed
83
    , _previousState(-1)
84
    , _viewUrlFilter(0)
85 86
    , _searchFilter(0)
    , _searchToggleAction(0)
87 88
    , _findNextAction(0)
    , _findPreviousAction(0)
89
    , _urlFilterUpdateRequired(false)
90
    , _codecAction(0)
91
    , _changeProfileMenu(0)
92 93
    , _listenForScreenWindowUpdates(false)
    , _preventClose(false)
94
{
95 96
    _allControllers.insert(this);

97 98 99
    Q_ASSERT( session );
    Q_ASSERT( view );

100
    // handle user interface related to session (menus etc.)
101 102 103 104
    if (isKonsolePart())
        setXMLFile("konsole/partui.rc");
    else
        setXMLFile("konsole/sessionui.rc");
105

106
    setupActions();
107 108 109
    actionCollection()->addAssociatedWidget(view);
    foreach (QAction* action, actionCollection()->actions())
        action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
110

111
    setIdentifier(++_lastControllerId);
Robert Knight's avatar
 
Robert Knight committed
112
    sessionTitleChanged();
113 114

    view->installEventFilter(this);
Robert Knight's avatar
 
Robert Knight committed
115

116 117 118 119
    // listen for session resize requests
    connect( _session , SIGNAL(resizeRequest(const QSize&)) , this ,
            SLOT(sessionResizeRequest(const QSize&)) );

120
    // listen for popup menu requests
121 122
    connect( _view , SIGNAL(configureRequest(QPoint)) , this,
            SLOT(showDisplayContextMenu(QPoint)) );
123

124
    // move view to newest output when keystrokes occur
125
    connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , this ,
126
            SLOT(trackOutput(QKeyEvent*)) );
127

Robert Knight's avatar
 
Robert Knight committed
128
    // listen to activity / silence notifications from session
129
    connect( _session , SIGNAL(stateChanged(int)) , this ,
130
            SLOT(sessionStateChanged(int) ));
131
    // listen to title and icon changes
132
    connect( _session , SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) );
133

134 135 136
    // listen for color changes
    connect( _session , SIGNAL(changeBackgroundColorRequest(QColor)) , _view , SLOT(setBackgroundColor(QColor)) );
    connect( _session , SIGNAL(changeForegroundColorRequest(QColor)) , _view , SLOT(setForegroundColor(QColor)) );
137

138 139
    // update the title when the session starts
    connect( _session , SIGNAL(started()) , this , SLOT(snapshot()) ); 
140

141
    // listen for output changes to set activity flag
142
    connect( _session->emulation() , SIGNAL(outputChanged()) , this ,
143
            SLOT(fireActivity()) );
144 145 146 147

    // listen for detection of ZModem transfer
    connect( _session , SIGNAL(zmodemDetected()) , this , SLOT(zmodemDownload()) ); 

Robert Knight's avatar
 
Robert Knight committed
148
    // listen for flow control status changes
Stephan Binner's avatar
Stephan Binner committed
149
    connect( _session , SIGNAL(flowControlEnabledChanged(bool)) , _view ,
150 151
        SLOT(setFlowControlWarningEnabled(bool)) );
    _view->setFlowControlWarningEnabled(_session->flowControlEnabled());
Robert Knight's avatar
 
Robert Knight committed
152

153 154
    // take a snapshot of the session state every so often when
    // user activity occurs
155 156 157 158
    //
    // the timer is owned by the session so that it will be destroyed along
    // with the session
    QTimer* activityTimer = new QTimer(_session);
159 160 161 162
    activityTimer->setSingleShot(true);
    activityTimer->setInterval(2000);
    connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , activityTimer , SLOT(start()) );
    connect( activityTimer , SIGNAL(timeout()) , this , SLOT(snapshot()) );
163 164
}

165 166
void SessionController::updateSearchFilter()
{
167 168 169
    if ( _searchFilter ) 
    {
        Q_ASSERT( searchBar() && searchBar()->isVisible() );
170

171 172
        _view->processFilters();
    }
173 174
}

175
SessionController::~SessionController()
176 177
{
   if ( _view )
178
      _view->setScreenWindow(0);
179 180

   _allControllers.remove(this);
181
}
182
void SessionController::trackOutput(QKeyEvent* event)
183 184 185
{
    Q_ASSERT( _view->screenWindow() );

186 187 188 189 190 191 192 193 194 195 196 197
    // jump to the end of the scrollback buffer unless the key pressed
    // is one of the three main modifiers, as these are used to select
    // the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection)
    switch (event->key())
    {
        case Qt::Key_Shift:
        case Qt::Key_Control:
        case Qt::Key_Alt:
            break;
        default:
            _view->screenWindow()->setTrackOutput(true);
    }
198
}
199 200 201 202
void SessionController::requireUrlFilterUpdate()
{
    // this method is called every time the screen window's output changes, so do not
    // do anything expensive here.
203

204 205
    _urlFilterUpdateRequired = true;
}
206 207
void SessionController::snapshot()
{
208
    Q_ASSERT( _session != 0 );
209

Robert Knight's avatar
 
Robert Knight committed
210 211
    QString title = _session->getDynamicTitle();    
    title         = title.simplified();
212

213 214
    // Visualize that the session is broadcasting to others
    if (_copyToGroup && _copyToGroup->sessions().count() > 1) {
215
        title.append('*');
216 217
    }
    updateSessionIcon();
218

219
    // apply new title
220
    if ( !title.isEmpty() )
221
        _session->setTitle(Session::DisplayedTitleRole,title);
222
    else
223
        _session->setTitle(Session::DisplayedTitleRole,_session->title(Session::NameRole));
224 225
}

226 227
QString SessionController::currentDir() const
{
Robert Knight's avatar
 
Robert Knight committed
228
    return _session->currentWorkingDirectory();
229 230
}

231 232
KUrl SessionController::url() const
{
Robert Knight's avatar
 
Robert Knight committed
233
    return _session->getUrl();
234 235
}

236 237
void SessionController::rename()
{
238
    renameSession();
239 240
}

241
void SessionController::openUrl( const KUrl& url )
242 243
{
    // handle local paths
244
    if ( url.isLocalFile() )
245
    {
246
        QString path = url.toLocalFile();
Dirk Mueller's avatar
Dirk Mueller committed
247
        _session->emulation()->sendText("cd " + KShell::quoteArg(path) + '\r');
248
    }
249 250 251 252
    else if ( url.protocol() == "ssh" )
    {
        _session->emulation()->sendText("ssh ");

253 254
        if ( url.port() > -1 )
            _session->emulation()->sendText("-p " + QString::number(url.port()) + ' ' );
255 256 257 258 259
        if ( url.hasUser() )
            _session->emulation()->sendText(url.user() + '@');
        if ( url.hasHost() )
            _session->emulation()->sendText(url.host() + '\r');
    }
260 261 262 263 264 265 266 267 268 269 270 271
    else if ( url.protocol() == "telnet" )
    {
        _session->emulation()->sendText("telnet ");

        if ( url.hasUser() )
            _session->emulation()->sendText("-l " + url.user() + ' ');
        if ( url.hasHost() )
            _session->emulation()->sendText(url.host() + ' ');
        if ( url.port() > -1 )
            _session->emulation()->sendText(QString::number(url.port()));
         _session->emulation()->sendText("\r");
    }
272 273 274
    else
    {
        //TODO Implement handling for other Url types
275

276 277 278
        KMessageBox::sorry(_view->window(),
                           i18n("Konsole does not know how to open the bookmark: ") +
                           url.prettyUrl());
279

Thomas Zander's avatar
Thomas Zander committed
280
        kWarning(1211) << "Unable to open bookmark at url" << url << ", I do not know"
281
           << " how to handle the protocol " << url.protocol();
282 283 284
    }
}

285
bool SessionController::eventFilter(QObject* watched , QEvent* event)
286
{
287 288 289 290
    if ( watched == _view )
    {
        if ( event->type() == QEvent::FocusIn )
        {
291 292
            // notify the world that the view associated with this session has been focused
            // used by the view manager to update the title of the MainWindow widget containing the view
293
            emit focused(this);
Robert Knight's avatar
 
Robert Knight committed
294

295 296
            // when the view is focused, set bell events from the associated session to be delivered
            // by the focused view
297 298

            // first, disconnect any other views which are listening for bell signals from the session
299 300
            disconnect( _session , SIGNAL(bellRequest(const QString&)) , 0 , 0 );
            // second, connect the newly focused view to listen for the session's bell signal
Robert Knight's avatar
 
Robert Knight committed
301 302
            connect( _session , SIGNAL(bellRequest(const QString&)) ,
                    _view , SLOT(bell(const QString&)) );
303 304 305 306 307 308
                    
            if(_copyToAllTabsAction->isChecked()) {
                // A session with "Copy To All Tabs" has come into focus:
                // Ensure that newly created sessions are included in _copyToGroup!
                copyInputToAllTabs();
            }
Robert Knight's avatar
 
Robert Knight committed
309
        }
310 311 312
        // when a mouse move is received, create the URL filter and listen for output changes if
        // it has not already been created.  If it already exists, then update only if the output
        // has changed since the last update ( _urlFilterUpdateRequired == true )
313 314 315
        //
        // also check that no mouse buttons are pressed since the URL filter only applies when
        // the mouse is hovering over the view
316
        if ( event->type() == QEvent::MouseMove &&
317 318
            (!_viewUrlFilter || _urlFilterUpdateRequired) &&
            ((QMouseEvent*)event)->buttons() == Qt::NoButton )
319 320 321
        {
            if ( _view->screenWindow() && !_viewUrlFilter )
            {
322 323
                connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this ,
                        SLOT(requireUrlFilterUpdate()) );
324 325 326
                connect( _view->screenWindow() , SIGNAL(outputChanged()) , this ,
                         SLOT(requireUrlFilterUpdate()) );

327 328 329 330 331 332 333 334
                // install filter on the view to highlight URLs
                _viewUrlFilter = new UrlFilter();
                _view->filterChain()->addFilter( _viewUrlFilter );
            }

            _view->processFilters();
            _urlFilterUpdateRequired = false;
        }
335
    }
336

337
    return false;
338 339
}

340 341 342 343 344 345 346 347 348 349
void SessionController::removeSearchFilter()
{
    if (!_searchFilter)
        return;

    _view->filterChain()->removeFilter(_searchFilter);
    delete _searchFilter;
    _searchFilter = 0;
}

350
void SessionController::setSearchBar(IncrementalSearchBar* searchBar)
351
{
352
    // disconnect the existing search bar
353
    if ( _searchBar )
354 355
    {
        disconnect( this , 0 , _searchBar , 0 );
356
        disconnect( _searchBar , 0 , this , 0 );
357 358
    }

359
    // remove any existing search filter
360
    removeSearchFilter();
361

362 363 364 365 366 367
    // connect new search bar
    _searchBar = searchBar;
    if ( _searchBar )
    {
        connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) );
        connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) );
368 369
        connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) );
        connect( _searchBar , SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) );
370

371 372 373 374
        // if the search bar was previously active
        // then re-enter search mode
        searchHistory( _searchToggleAction->isChecked() );
    }
375 376 377 378 379 380
}
IncrementalSearchBar* SessionController::searchBar() const
{
    return _searchBar;
}

381 382 383 384 385
void SessionController::setShowMenuAction(QAction* action)
{
    actionCollection()->addAction("show-menubar",action);
}

386
void SessionController::setupActions()
387
{
388
    KAction* action = 0;
389
    KToggleAction* toggleAction = 0;
390
    KActionCollection* collection = actionCollection();
391

392
    // Close Session
393
    action = collection->addAction("close-session");
394
    action->setIcon( KIcon("tab-close") );
395
    action->setText( i18n("&Close Tab") );
396
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_W) );
397
    connect( action , SIGNAL(triggered()) , this , SLOT(closeSession()) );
398 399 400

    // Open Browser
    action = collection->addAction("open-browser");
401
    action->setText( i18n("Open File Manager") );
402
    action->setIcon( KIcon("system-file-manager") );
403
    connect( action, SIGNAL(triggered()), this, SLOT(openBrowser()) );
404

405
    // Copy and Paste
406
    action = collection->addAction("copy");
Robert Knight's avatar
Robert Knight committed
407
    action->setIcon( KIcon("edit-copy") );
408
    action->setText( i18n("&Copy") );
409
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_C) );
410
    connect( action , SIGNAL(triggered()) , this , SLOT(copy()) );
411

412
    KAction* pasteAction = new KAction( i18n("&Paste") , this );
413 414 415 416 417 418 419 420 421 422
    pasteAction->setIcon( KIcon("edit-paste") );

    KShortcut pasteShortcut = pasteAction->shortcut();
    pasteShortcut.setPrimary( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_V) );
    pasteShortcut.setAlternate( QKeySequence(Qt::SHIFT+Qt::Key_Insert) );
    pasteAction->setShortcut(pasteShortcut);

    collection->addAction("paste",pasteAction);

    connect( pasteAction , SIGNAL(triggered()) , this , SLOT(paste()) );
423

424 425 426 427
    action = collection->addAction("paste-selection");
    action->setText( i18n("Paste Selection") );
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Insert) );
    connect( action , SIGNAL(triggered()) , this , SLOT(pasteSelection()) );
428

429 430
    // Rename Session
    action = collection->addAction("rename-session");
Stephan Binner's avatar
Stephan Binner committed
431
    action->setText( i18n("&Rename Tab...") );
432 433 434
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) );
    connect( action , SIGNAL(triggered()) , this , SLOT(renameSession()) );

435 436 437 438
    // Copy Input To -> All Tabs in Current Window
    _copyToAllTabsAction = collection->addAction("copy-input-to-all-tabs");
    _copyToAllTabsAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Comma) );
    _copyToAllTabsAction->setText(i18n("&All Tabs in Current Window") );
439
    _copyToAllTabsAction->setCheckable(true);
440 441 442 443 444 445
    connect( _copyToAllTabsAction , SIGNAL(triggered()) , this , SLOT(copyInputToAllTabs()) );

    // Copy Input To -> Select Tabs
    _copyToSelectedAction = collection->addAction("copy-input-to-selected-tabs");
    _copyToSelectedAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Period) );
    _copyToSelectedAction->setText( i18n("&Select Tabs...") );
446
    _copyToSelectedAction->setCheckable(true);
447 448 449 450 451 452
    connect( _copyToSelectedAction , SIGNAL(triggered()) , this , SLOT(copyInputToSelectedTabs()) );

    // Copy Input To -> None
    _copyToNoneAction = collection->addAction("copy-input-to-none");
    _copyToNoneAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Slash) );
    _copyToNoneAction->setText( i18n("&None") );
453 454
    _copyToNoneAction->setCheckable(true);
    _copyToNoneAction->setChecked(true);
455
    connect( _copyToNoneAction , SIGNAL(triggered()) , this , SLOT(copyInputToNone()) );
456

457
    // Clear+Reset
458
    action = collection->addAction("clear-and-reset");
Stephan Binner's avatar
Stephan Binner committed
459
    action->setText( i18n("Clear && Reset") );
Pino Toscano's avatar
Pino Toscano committed
460
    action->setIcon( KIcon("edit-clear-history") );
461 462
    connect( action , SIGNAL(triggered()) , this , SLOT(clearAndReset()) );

463 464 465 466 467 468
    action = collection->addAction("zmodem-upload");
    action->setText( i18n( "&ZModem Upload..." ) );
    action->setIcon( KIcon("document-open") );
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_U) );
    connect( action , SIGNAL(triggered()) , this , SLOT(zmodemUpload()) );
    
Robert Knight's avatar
 
Robert Knight committed
469
    // Monitor
470
    toggleAction = new KToggleAction(i18n("Monitor for &Activity"),this);
471
    toggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A) );
472
    action = collection->addAction("monitor-activity",toggleAction);
Robert Knight's avatar
 
Robert Knight committed
473 474
    connect( action , SIGNAL(toggled(bool)) , this , SLOT(monitorActivity(bool)) );

475
    toggleAction = new KToggleAction(i18n("Monitor for &Silence"),this);
476
    toggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_I) );
477
    action = collection->addAction("monitor-silence",toggleAction);
Robert Knight's avatar
 
Robert Knight committed
478
    connect( action , SIGNAL(toggled(bool)) , this , SLOT(monitorSilence(bool)) );
479

480
    // Character Encoding
481
    _codecAction = new KCodecAction(i18n("Character Encoding"),this);
482 483 484 485
    collection->addAction("character-encoding",_codecAction);
    connect( _codecAction->menu() , SIGNAL(aboutToShow()) , this , SLOT(updateCodecAction()) );
    connect( _codecAction , SIGNAL(triggered(QTextCodec*)) , this , SLOT(changeCodec(QTextCodec*)) );

486 487 488 489 490 491 492 493 494 495
    // Text Size
    action = collection->addAction("increase-text-size");
    action->setText( i18n("Increase Text Size") );
    action->setIcon( KIcon("zoom-in") );
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_Plus) );
    connect( action , SIGNAL(triggered()) , this , SLOT(increaseTextSize()) );

    action = collection->addAction("decrease-text-size");
    action->setText( i18n("Decrease Text Size") );
    action->setIcon( KIcon("zoom-out") );
496
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_Minus) );
497 498
    connect( action , SIGNAL(triggered()) , this , SLOT(decreaseTextSize()) );

499
    // Scrollback
Stephan Binner's avatar
Stephan Binner committed
500
    _searchToggleAction = new KAction(i18n("Search Output..."),this);
501
    _searchToggleAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_F) );
Robert Knight's avatar
Robert Knight committed
502
    _searchToggleAction->setIcon( KIcon("edit-find") );
503
    _searchToggleAction->setCheckable(true);
504
    action = collection->addAction("search-history" , _searchToggleAction);
505 506
    connect( action , SIGNAL(toggled(bool)) , this , SLOT(searchHistory(bool)) );

507
    _findNextAction = collection->addAction("find-next");
508
    _findNextAction->setIcon( KIcon("go-down-search") );
509 510 511 512 513 514
    _findNextAction->setText( i18n("Find Next") );
    _findNextAction->setShortcut( QKeySequence(Qt::Key_F3) );
    _findNextAction->setEnabled(false);
    connect( _findNextAction , SIGNAL(triggered()) , this , SLOT(findNextInHistory()) );

    _findPreviousAction = collection->addAction("find-previous");
515
    _findPreviousAction->setIcon( KIcon("go-up-search") );
516 517 518 519
    _findPreviousAction->setText( i18n("Find Previous") );
    _findPreviousAction->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_F3) );
    _findPreviousAction->setEnabled(false);
    connect( _findPreviousAction , SIGNAL(triggered()) , this , SLOT(findPreviousInHistory()) );
520

521
    action = collection->addAction("save-history");
522 523
    action->setText( i18n("Save Output...") );
    action->setIcon( KIcon("document-save-as") );
524
    connect( action , SIGNAL(triggered()) , this , SLOT(saveHistory()) );
525

526
    action = collection->addAction("history-options");
Stephan Binner's avatar
Stephan Binner committed
527
    action->setText( i18n("Scrollback Options") );
Robert Knight's avatar
Robert Knight committed
528
    action->setIcon( KIcon("configure") );
529
    connect( action , SIGNAL(triggered()) , this , SLOT(showHistoryOptions()) );
530

531
    action = collection->addAction("clear-history-and-reset");
532
    action->setText( i18n("Clear Scrollback && Reset") );
533
    action->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_X) );
534 535
    connect( action , SIGNAL(triggered()) , this , SLOT(clearHistoryAndReset()) );

536
    // Profile Options
537 538
    action = collection->addAction("edit-current-profile");
    action->setText( i18n("Edit Current Profile...") );
539
    action->setIcon( KIcon("document-properties") );
540
    connect( action , SIGNAL(triggered()) , this , SLOT(editCurrentProfile()) );
541

542 543 544 545
    _changeProfileMenu = new KActionMenu(i18n("Change Profile"), _view);
    collection->addAction("change-profile", _changeProfileMenu);
    connect( _changeProfileMenu->menu() , SIGNAL(aboutToShow()) , this , SLOT(prepareChangeProfileMenu()) );

546
}
547
void SessionController::changeProfile(Profile::Ptr profile)
548
{
549
    SessionManager::instance()->setSessionProfile(_session,profile);    
550 551 552
}
void SessionController::prepareChangeProfileMenu()
{
553
    if ( _changeProfileMenu->menu()->isEmpty() )
554
    {
555
        _profileList = new ProfileList(false,this);
556 557
        connect( _profileList , SIGNAL(profileSelected(Profile::Ptr)) ,
                this , SLOT(changeProfile(Profile::Ptr)) );
558
    }
559

560 561
    _changeProfileMenu->menu()->clear();
    _changeProfileMenu->menu()->addActions(_profileList->actions());
562
}
563 564 565 566 567 568
void SessionController::updateCodecAction()
{
    _codecAction->setCurrentCodec( QString(_session->emulation()->codec()->name()) );
}
void SessionController::changeCodec(QTextCodec* codec)
{
569
    _session->setCodec(codec);
570
}
571

572
void SessionController::editCurrentProfile()
573
{
574
    EditProfileDialog* dialog = new EditProfileDialog( QApplication::activeWindow() );
575

576
    dialog->setProfile(SessionManager::instance()->sessionProfile(_session));
577
    dialog->show();
578 579 580
}
void SessionController::renameSession()
{
581
    QPointer<Session> guard(_session);
582 583 584
    bool ok = false;
    const QString& text = KInputDialog::getText( i18n("Rename Tab") ,
                                                 i18n("Enter new tab text:") ,
585
                                                 _session->tabTitleFormat(Session::LocalTabTitle) ,
Craig Drummond's avatar
Craig Drummond committed
586
                                                 &ok, QApplication::activeWindow() );
587
    if (!guard)
588
        return;
589

590
    if ( ok )
591
    {
592 593 594 595 596 597
        // renaming changes both the local and remote tab title formats, to save confusion over
        // the tab title not changing if renaming the tab whilst the remote tab title format is 
        // being displayed
        //
        // The downside of this approach is that after renaming a tab manually, the ability to 
        // have separate formats for local and remote activities is lost
598
        _session->setTabTitleFormat(Session::LocalTabTitle,text);
599
        _session->setTabTitleFormat(Session::RemoteTabTitle,text);
600

601 602 603
        // trigger an update of the tab text
        snapshot();
    }
604
}
605 606
void SessionController::saveSession()
{
607 608
    Q_ASSERT(0); // not implemented yet

609 610 611
    //SaveSessionDialog dialog(_view);
    //int result = dialog.exec();
}
612 613
bool SessionController::confirmClose() const
{
614
    if (_session->isForegroundProcessActive())
615
    {
616
        QString title = _session->foregroundProcessName();
617 618 619 620
      
        // hard coded for now.  In future make it possible for the user to specify which programs
        // are ignored when considering whether to display a confirmation
        QStringList ignoreList; 
621
        ignoreList << QString(qgetenv("SHELL")).section('/',-1);
622 623 624 625
        if (ignoreList.contains(title))
            return true;

        QString question;
Robert Knight's avatar
 
Robert Knight committed
626
        if (title.isEmpty())
627 628
            question = i18n("A program is currently running in this session."
                            "  Are you sure you want to close it?");
Robert Knight's avatar
 
Robert Knight committed
629 630 631
        else
            question = i18n("The program '%1' is currently running in this session."  
                            "  Are you sure you want to close it?",title);
632 633 634 635 636 637

        int result = KMessageBox::warningYesNo(_view->window(),question,i18n("Confirm Close"));
        return (result == KMessageBox::Yes) ? true : false; 
    }
    return true;
}
638
void SessionController::closeSession()
639
{
640 641
    if (_preventClose)
        return;
642

643 644
    if (confirmClose())
        _session->close();
645
}
646

647 648 649 650 651
void SessionController::openBrowser()
{
    new KRun(url(), QApplication::activeWindow());
}

652
void SessionController::copy()
653
{
654
    _view->copyClipboard();
655
}
656 657

void SessionController::paste()
658
{
659
    _view->pasteClipboard();
660
}
661 662
void SessionController::pasteSelection()
{
663
    _view->pasteSelection();
664
}
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
static const KXmlGuiWindow* findWindow(const QObject* object)
{
    // Walk up the QObject hierarchy to find a KXmlGuiWindow.
    while(object != NULL) {
        const KXmlGuiWindow* window = dynamic_cast<const KXmlGuiWindow*>(object);
        if(window != NULL) {
            return(window);
        }
        object = object->parent();
    }
    return(NULL);
}

static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window)
{
    // Iterate all TerminalDisplays of this Session ...
    QListIterator<TerminalDisplay*> terminalDisplayIterator(session->views());
    while(terminalDisplayIterator.hasNext()) {
        const TerminalDisplay* terminalDisplay = terminalDisplayIterator.next();
        // ... and check whether a TerminalDisplay has the same
        // window as given in the parameter
        if(window == findWindow(terminalDisplay)) {
            return(true);    
        }
    }
    return(false);
}

void SessionController::copyInputToAllTabs()
{
    if(!_copyToGroup) {
        _copyToGroup = new SessionGroup(this);
    }

    // Find our window ...
    const KXmlGuiWindow* myWindow = findWindow(_view);

    QSet<Session*> group =
       QSet<Session*>::fromList(SessionManager::instance()->sessions());
    for(QSet<Session*>::iterator iterator = group.begin();
        iterator != group.end(); ++iterator) {
        Session* session = *iterator;
 
708 709 710
        // First, ensure that the session is removed
        // (necessary to avoid duplicates on addSession()!)
        _copyToGroup->removeSession(session);
711

712 713 714
        // Add current session if it is displayed our window
        if(hasTerminalDisplayInSameWindow(session, myWindow)) {
            _copyToGroup->addSession(session);
715 716
        }
    }
717 718 719
    _copyToGroup->setMasterStatus(_session, true);
    _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
    
720
    snapshot();
721 722 723
    _copyToAllTabsAction->setChecked(true);
    _copyToSelectedAction->setChecked(false);
    _copyToNoneAction->setChecked(false);
724 725 726
}

void SessionController::copyInputToSelectedTabs()
727
{
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
    if (!_copyToGroup)
    {
        _copyToGroup = new SessionGroup(this);
        _copyToGroup->addSession(_session);
        _copyToGroup->setMasterStatus(_session,true);
        _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
    }

    CopyInputDialog* dialog = new CopyInputDialog(_view);
    dialog->setMasterSession(_session);
    
    QSet<Session*> currentGroup = QSet<Session*>::fromList(_copyToGroup->sessions());
    currentGroup.remove(_session);
    
    dialog->setChosenSessions(currentGroup);

    QPointer<Session> guard(_session);
    int result = dialog->exec();
    if (!guard)
        return;

    if (result)
    {
        QSet<Session*> newGroup = dialog->chosenSessions();
        newGroup.remove(_session);
    
        QSet<Session*> completeGroup = newGroup | currentGroup;
        foreach(Session* session, completeGroup)
        {
            if (newGroup.contains(session) && !currentGroup.contains(session))
                _copyToGroup->addSession(session);
            else if (!newGroup.contains(session) && currentGroup.contains(session))
                _copyToGroup->removeSession(session);
        }

763 764
        _copyToGroup->setMasterStatus(_session, true);
        _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
765
        snapshot();        
766 767 768
    }

    delete dialog;
769 770 771
    _copyToAllTabsAction->setChecked(false);
    _copyToSelectedAction->setChecked(true);
    _copyToNoneAction->setChecked(false);
772
}
773 774 775 776 777 778 779 780 781 782 783 784 785

void SessionController::copyInputToNone()
{
    QSet<Session*> group =
       QSet<Session*>::fromList(SessionManager::instance()->sessions());
    for(QSet<Session*>::iterator iterator = group.begin();
        iterator != group.end(); ++iterator) {
        Session* session = *iterator;
 
        if(session != _session) {
            _copyToGroup->removeSession(*iterator);
        }
    }
786 787
    delete _copyToGroup;
    _copyToGroup = NULL;
788
    snapshot();
789
    
790 791 792
    _copyToAllTabsAction->setChecked(false);
    _copyToSelectedAction->setChecked(false);
    _copyToNoneAction->setChecked(true);
793 794
}

795
void SessionController::clear()
796
{
797
    Emulation* emulation = _session->emulation();
798

799
    emulation->clearEntireScreen();
800
}
801
void SessionController::clearAndReset()
802
{
803
    Emulation* emulation = _session->emulation();
804

805
    emulation->reset();