ViewManager.cpp 40 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

    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.
*/

20
21
22
// Own
#include "ViewManager.h"

23
// Qt
24
#include <QtCore/QDateTime>
Dirk Mueller's avatar
Dirk Mueller committed
25
#include <QtCore/QSignalMapper>
Jekyll Wu's avatar
Jekyll Wu committed
26
#include <QtCore/QStringList>
27
#include <QtGui/QMenu>
28

29
// KDE
Robert Knight's avatar
Robert Knight committed
30
#include <KAcceleratorManager>
31
#include <KGlobal>
32
33
#include <KLocale>
#include <KToggleAction>
34
#include <KActionCollection>
35
#include <KXMLGUIFactory>
Laurent Montel's avatar
Laurent Montel committed
36
#include <KConfigGroup>
37
38

// Konsole
39
40
#include <konsoleadaptor.h>

41
#include "ColorScheme.h"
42
#include "ProfileList.h"
43
#include "Session.h"
44
#include "TerminalDisplay.h"
45
46
47
48
#include "SessionController.h"
#include "SessionManager.h"
#include "ViewContainer.h"
#include "ViewSplitter.h"
49
#include "Profile.h"
50

51
52
using namespace Konsole;

53
54
ViewManager::ViewManager(QObject* parent , KActionCollection* collection)
    : QObject(parent)
55
    , _viewSplitter(0)
56
57
    , _actionCollection(collection)
    , _containerSignalMapper(new QSignalMapper(this))
58
    , _navigationMethod(TabbedNavigation)
59
    , _newViewMenu(0)
60
{
61
    // create main view area
Robert Knight's avatar
Robert Knight committed
62
    _viewSplitter = new ViewSplitter(0);  
63
    KAcceleratorManager::setNoAccel(_viewSplitter);
Robert Knight's avatar
Robert Knight committed
64

65
66
67
68
69
70
71
    // the ViewSplitter class supports both recursive and non-recursive splitting,
    // in non-recursive mode, all containers are inserted into the same top-level splitter
    // widget, and all the divider lines between the containers have the same orientation
    //
    // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting
    // mode 
    _viewSplitter->setRecursiveSplitting(false);
72
    _viewSplitter->setFocusPolicy(Qt::NoFocus);
73

74
    // setup actions which are related to the views
75
76
77
78
79
    setupActions();

    // emit a signal when all of the views held by this view manager are destroyed
    connect( _viewSplitter , SIGNAL(allContainersEmpty()) , this , SIGNAL(empty()) );
    connect( _viewSplitter , SIGNAL(empty(ViewSplitter*)) , this , SIGNAL(empty()) );
80
81
82
83

    // listen for addition or removal of views from associated containers
    connect( _containerSignalMapper , SIGNAL(mapped(QObject*)) , this , 
            SLOT(containerViewsChanged(QObject*)) ); 
84
85

    // listen for profile changes
86
87
    connect( SessionManager::instance() , SIGNAL(profileChanged(Profile::Ptr)) , this,
            SLOT(profileChanged(Profile::Ptr)) );
88
89
    connect( SessionManager::instance() , SIGNAL(sessionUpdated(Session*)) , this,
            SLOT(updateViewsForSession(Session*)) );
90
91
92
93
94

    //prepare DBus communication
    new KonsoleAdaptor(this);
    QDBusConnection::sessionBus().registerObject(QLatin1String("/Konsole"), this);

95
96
97
98
}

ViewManager::~ViewManager()
{
99
100
101
102
103
104
105
106
107
108
109
110
111
112
    delete _newViewMenu;
}
QMenu* ViewManager::createNewViewMenu() 
{
    if (_newViewMenu)
        return _newViewMenu;

    _newViewMenu = new QMenu(0);
    ProfileList* newViewProfiles = new ProfileList(false,_newViewMenu);
    newViewProfiles->syncWidgetActions(_newViewMenu,true);
    connect(newViewProfiles,SIGNAL(profileSelected(Profile::Ptr)),this,
        SIGNAL(newViewRequest(Profile::Ptr)));

    return _newViewMenu;
113
}
114
115
116
117
118
119
120
121
122
123
124
125
QWidget* ViewManager::activeView() const
{
    ViewContainer* container = _viewSplitter->activeContainer();
    if ( container )
    {
        return container->activeView();
    }
    else
    {
        return 0;
    }
}
126

127
128
129
130
131
QWidget* ViewManager::widget() const
{
    return _viewSplitter;
}

132
133
void ViewManager::setupActions()
{
134
    KActionCollection* collection = _actionCollection;
135

Kurt Hindenburg's avatar
Kurt Hindenburg committed
136
137
138
139
    KAction* nextViewAction = new KAction( i18nc("@action Shortcut entry", "Next Tab") , this );
    KAction* previousViewAction = new KAction( i18nc("@action Shortcut entry", "Previous Tab") , this );
    KAction* lastViewAction = new KAction( i18nc("@action Shortcut entry", "Switch to Last Tab") , this);
    KAction* nextContainerAction = new KAction( i18nc("@action Shortcut entry", "Next View Container") , this);
140

Kurt Hindenburg's avatar
Kurt Hindenburg committed
141
142
    KAction* moveViewLeftAction = new KAction( i18nc("@action Shortcut entry", "Move Tab Left") , this );
    KAction* moveViewRightAction = new KAction( i18nc("@action Shortcut entry", "Move Tab Right") , this );
143

144
145
146
147
148
    // list of actions that should only be enabled when there are multiple view
    // containers open
    QList<QAction*> multiViewOnlyActions;
    multiViewOnlyActions << nextContainerAction;

149
150
    if ( collection )
    {
151
        KAction* splitLeftRightAction = new KAction( KIcon("view-split-left-right"),
Thomas Reitelbach's avatar
Thomas Reitelbach committed
152
                                                      i18nc("@action:inmenu", "Split View Left/Right"),
153
                                                      this );
154
        splitLeftRightAction->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_ParenLeft) );
155
156
157
        collection->addAction("split-view-left-right",splitLeftRightAction);
        connect( splitLeftRightAction , SIGNAL(triggered()) , this , SLOT(splitLeftRight()) );

158
        KAction* splitTopBottomAction = new KAction( KIcon("view-split-top-bottom") , 
Thomas Reitelbach's avatar
Thomas Reitelbach committed
159
                                             i18nc("@action:inmenu", "Split View Top/Bottom"),this);
160
        splitTopBottomAction->setShortcut( QKeySequence(Qt::CTRL+Qt::Key_ParenRight) );
161
162
163
        collection->addAction("split-view-top-bottom",splitTopBottomAction);
        connect( splitTopBottomAction , SIGNAL(triggered()) , this , SLOT(splitTopBottom()));

Thomas Reitelbach's avatar
Thomas Reitelbach committed
164
        KAction* closeActiveAction = new KAction( i18nc("@action:inmenu Close Active View", "Close Active") , this );
Pino Toscano's avatar
Pino Toscano committed
165
        closeActiveAction->setIcon(KIcon("view-close"));
166
        closeActiveAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_S) );
167
        closeActiveAction->setEnabled(false);
168
169
        collection->addAction("close-active-view",closeActiveAction);
        connect( closeActiveAction , SIGNAL(triggered()) , this , SLOT(closeActiveView()) );
170
171

        multiViewOnlyActions << closeActiveAction;
172

Thomas Reitelbach's avatar
Thomas Reitelbach committed
173
        KAction* closeOtherAction = new KAction( i18nc("@action:inmenu Close Other Views", "Close Others") , this );
174
        closeOtherAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_O) );
175
        closeOtherAction->setEnabled(false);
176
177
        collection->addAction("close-other-views",closeOtherAction);
        connect( closeOtherAction , SIGNAL(triggered()) , this , SLOT(closeOtherViews()) );
178
179

        multiViewOnlyActions << closeOtherAction;
180

181
        KAction* detachViewAction = collection->addAction("detach-view");
182
        detachViewAction->setIcon(KIcon("tab-detach"));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
183
        detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab"));
184
185
        // Ctrl+Shift+D is not used as a shortcut by default because it is too close
        // to Ctrl+D - which will terminate the session in many cases
186
187
188
        detachViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H));

        connect( this , SIGNAL(splitViewToggle(bool)) , this , SLOT(updateDetachViewState()) );
189
        connect( detachViewAction , SIGNAL(triggered()) , this , SLOT(detachActiveView()) );
190

191
        // Expand & Shrink Active View
Thomas Reitelbach's avatar
Thomas Reitelbach committed
192
        KAction* expandActiveAction = new KAction( i18nc("@action:inmenu", "Expand View") , this );
193
        expandActiveAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_BracketRight) );
194
        expandActiveAction->setEnabled(false);
195
196
197
        collection->addAction("expand-active-view",expandActiveAction);
        connect( expandActiveAction , SIGNAL(triggered()) , this , SLOT(expandActiveView()) );

198
199
        multiViewOnlyActions << expandActiveAction;

Thomas Reitelbach's avatar
Thomas Reitelbach committed
200
        KAction* shrinkActiveAction = new KAction( i18nc("@action:inmenu", "Shrink View") , this );
201
        shrinkActiveAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_BracketLeft) );
202
        shrinkActiveAction->setEnabled(false);
203
204
205
        collection->addAction("shrink-active-view",shrinkActiveAction);
        connect( shrinkActiveAction , SIGNAL(triggered()) , this , SLOT(shrinkActiveView()) );

206
207
        multiViewOnlyActions << shrinkActiveAction;

208
        // Next / Previous View , Next Container
209
210
        collection->addAction("next-view",nextViewAction);
        collection->addAction("previous-view",previousViewAction);
211
        collection->addAction("last-tab",lastViewAction);
212
        collection->addAction("next-container",nextContainerAction);
213
214
        collection->addAction("move-view-left",moveViewLeftAction);
        collection->addAction("move-view-right",moveViewRightAction);
215
216
217
218
219
220
221

        // Switch to tab N shortcuts
        const int SWITCH_TO_TAB_COUNT = 10;
        QSignalMapper* switchToTabMapper = new QSignalMapper(this);
        connect(switchToTabMapper,SIGNAL(mapped(int)),this,SLOT(switchToView(int)));
        for (int i=0;i < SWITCH_TO_TAB_COUNT;i++)
        {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
222
            KAction* switchToTabAction = new KAction(i18nc("@action Shortcut entry", "Switch to Tab %1",i+1),this);
223
224
225
226
227
            switchToTabMapper->setMapping(switchToTabAction,i);
            connect(switchToTabAction,SIGNAL(triggered()),switchToTabMapper,
                    SLOT(map()));
            collection->addAction(QString("switch-to-tab-%1").arg(i),switchToTabAction);
        }
228
    }
Robert Knight's avatar
   
Robert Knight committed
229

230
231
232
233
234
235
    QListIterator<QAction*> iter(multiViewOnlyActions);
    while ( iter.hasNext() )
    {
        connect( this , SIGNAL(splitViewToggle(bool)) , iter.next() , SLOT(setEnabled(bool)) );
    }

236
    // keyboard shortcut only actions
237
    nextViewAction->setShortcut( QKeySequence(Qt::SHIFT+Qt::Key_Right) );
Robert Knight's avatar
   
Robert Knight committed
238
    connect( nextViewAction, SIGNAL(triggered()) , this , SLOT(nextView()) );
239
    _viewSplitter->addAction(nextViewAction);
Robert Knight's avatar
   
Robert Knight committed
240

241
    previousViewAction->setShortcut( QKeySequence(Qt::SHIFT+Qt::Key_Left) );
Robert Knight's avatar
   
Robert Knight committed
242
    connect( previousViewAction, SIGNAL(triggered()) , this , SLOT(previousView()) );
243
    _viewSplitter->addAction(previousViewAction);
Robert Knight's avatar
   
Robert Knight committed
244
245
246

    nextContainerAction->setShortcut( QKeySequence(Qt::SHIFT+Qt::Key_Tab) );
    connect( nextContainerAction , SIGNAL(triggered()) , this , SLOT(nextContainer()) );
247
    _viewSplitter->addAction(nextContainerAction);
248
249
250
251

    moveViewLeftAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Left) );
    connect( moveViewLeftAction , SIGNAL(triggered()) , this , SLOT(moveActiveViewLeft()) );
    _viewSplitter->addAction(moveViewLeftAction);
252

253
254
255
    moveViewRightAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Right) );
    connect( moveViewRightAction , SIGNAL(triggered()) , this , SLOT(moveActiveViewRight()) );
    _viewSplitter->addAction(moveViewRightAction);
256
257
258

    connect( lastViewAction, SIGNAL(triggered()) , this , SLOT(lastView()));
    _viewSplitter->addAction(lastViewAction);
Robert Knight's avatar
   
Robert Knight committed
259
}
260
261
262
263
264
265
266
267
268
269
void ViewManager::switchToView(int index)
{
    Q_ASSERT(index >= 0);
    ViewContainer* container = _viewSplitter->activeContainer();
    Q_ASSERT( container );
    QList<QWidget*> containerViews = container->views();
    if (index >= containerViews.count())
        return;
    container->setActiveView(containerViews.at(index));
}
270
271
void ViewManager::updateDetachViewState()
{
272
273
    if (!_actionCollection)
        return;
274

275

276
277
    bool splitView = _viewSplitter->containers().count() >= 2;
    bool shouldEnable = splitView || _viewSplitter->activeContainer()->views().count() >= 2;
278

279
    QAction* detachAction = _actionCollection->action("detach-view");
280

281
282
    if ( detachAction && shouldEnable != detachAction->isEnabled() )
        detachAction->setEnabled(shouldEnable);
283
}
284
285
286
287
288
289
290
291
292
293
294
295
void ViewManager::moveActiveViewLeft()
{
    ViewContainer* container = _viewSplitter->activeContainer();
    Q_ASSERT( container );
    container->moveActiveView( ViewContainer::MoveViewLeft );
}
void ViewManager::moveActiveViewRight()
{
    ViewContainer* container = _viewSplitter->activeContainer();
    Q_ASSERT( container );
    container->moveActiveView( ViewContainer::MoveViewRight );
}
Robert Knight's avatar
   
Robert Knight committed
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
void ViewManager::nextContainer()
{
    _viewSplitter->activateNextContainer();
}

void ViewManager::nextView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

    Q_ASSERT( container );

    container->activateNextView();
}

void ViewManager::previousView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

    Q_ASSERT( container );

    container->activatePreviousView();
317
}
318

319
320
321
322
323
324
325
326
327
void ViewManager::lastView()
{
    ViewContainer* container = _viewSplitter->activeContainer();

    Q_ASSERT( container );

    container->activateLastView();
}

328
329
330
331
332
void ViewManager::detachActiveView()
{
    // find the currently active view and remove it from its container 
    ViewContainer* container = _viewSplitter->activeContainer();

333
334
335
336
337
338
339
340
341
    detachView(container, container->activeView());
}

void ViewManager::detachView(ViewContainer* container, QWidget* widgetView)
{
    TerminalDisplay * viewToDetach = 
        dynamic_cast<TerminalDisplay*>(widgetView);

    if (!viewToDetach)
342
343
        return;

344
    emit viewDetached(_sessionMap[viewToDetach]);
345

346
    _sessionMap.remove(viewToDetach);
347
348

    // remove the view from this window
349
350
    container->removeView(viewToDetach);
    viewToDetach->deleteLater();
351
352
353
354
355
356
357

    // if the container from which the view was removed is now empty then it can be deleted,
    // unless it is the only container in the window, in which case it is left empty
    // so that there is always an active container
    if ( _viewSplitter->containers().count() > 1 && 
         container->views().count() == 0 )
    {
358
        removeContainer(container);
359
360
361
362
    }

}

363
void ViewManager::sessionFinished()
364
{
365
366
367
368
    // if this slot is called after the view manager's main widget
    // has been destroyed, do nothing
    if (!_viewSplitter)
        return;
369

370
371
    Session* session = qobject_cast<Session*>(sender());

372
373
    // We're using setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab)
    // so no need to manually select next tab.
374

375
376
    Q_ASSERT(session);

377
    // close attached views
378
    QList<TerminalDisplay*> children = _viewSplitter->findChildren<TerminalDisplay*>();
379

380
    foreach ( TerminalDisplay* view , children )
381
382
383
384
    {
        if ( _sessionMap[view] == session )
        {
            _sessionMap.remove(view);
385
            view->deleteLater();
386
        }
Robert Knight's avatar
   
Robert Knight committed
387
    }
388

389
390
391
392
    // This is needed to remove this controller from factory() in 
    // order to prevent BUG: 185466 - disappearing menu popup
    if (_pluggedController)
        emit unplugController(_pluggedController);
Robert Knight's avatar
   
Robert Knight committed
393
394
395
396
}

void ViewManager::focusActiveView()
{
Robert Knight's avatar
Robert Knight committed
397
398
399
400
401
402
    // give the active view in a container the focus.  this ensures 
    // that controller associated with that view is activated and the session-specific
    // menu items are replaced with the ones for the newly focused view

    // see the viewFocused() method

Robert Knight's avatar
   
Robert Knight committed
403
404
405
406
407
408
409
410
411
    ViewContainer* container = _viewSplitter->activeContainer(); 
    if ( container )
    {
        QWidget* activeView = container->activeView();
        if ( activeView )
        {
            activeView->setFocus(Qt::MouseFocusReason);
        }
    }
412
413
}

414

415
416
void ViewManager::viewActivated( QWidget* view )
{
417
418
419
420
421
422
    Q_ASSERT( view != 0 );

    // focus the activated view, this will cause the SessionController
    // to notify the world that the view has been focused and the appropriate UI
    // actions will be plugged in.
    view->setFocus(Qt::OtherFocusReason);
423
424
}

425
void ViewManager::splitLeftRight()
426
{
427
    splitView(Qt::Horizontal);
428
}
429
void ViewManager::splitTopBottom()
430
{
431
    splitView(Qt::Vertical);
432
433
}

434
void ViewManager::splitView(Qt::Orientation orientation)
435
{
436
437
438
    // iterate over each session which has a view in the current active
    // container and create a new view for that session in a new container 
    QListIterator<QWidget*> existingViewIter(_viewSplitter->activeContainer()->views());
439

440
    ViewContainer* container = 0; 
441
442

    while (existingViewIter.hasNext())
443
    {
444
        Session* session = _sessionMap[(TerminalDisplay*)existingViewIter.next()];
445
        TerminalDisplay* display = createTerminalDisplay(session);
Jekyll Wu's avatar
Jekyll Wu committed
446
447
        const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
        applyProfileToView(display, profile);
448
        ViewProperties* properties = createController(session,display);
449

450
        _sessionMap[display] = session;
451

452
453
454
        // create a container using settings from the first 
        // session in the previous container
        if ( !container )
455
        {
Jekyll Wu's avatar
Jekyll Wu committed
456
457
            container = createContainer(profile);
            applyProfileToContainer(container, profile);
458
        }
459

460
461
462
        container->addView(display,properties);
        session->addView( display );
    }
463

464
465
    _viewSplitter->addContainer(container,orientation);
    emit splitViewToggle(_viewSplitter->containers().count() > 0);
466

467
468
    // focus the new container
    container->containerWidget()->setFocus();
469
470

    // ensure that the active view is focused after the split / unsplit
471
472
473
474
475
    ViewContainer* activeContainer = _viewSplitter->activeContainer();
    QWidget* activeView = activeContainer ? activeContainer->activeView() : 0;

    if ( activeView )
        activeView->setFocus(Qt::OtherFocusReason);
476
}
477
void ViewManager::removeContainer(ViewContainer* container)
478
{
479
480
481
482
483
484
485
486
    // remove session map entries for views in this container
    foreach( QWidget* view , container->views() )
    {
        TerminalDisplay* display = qobject_cast<TerminalDisplay*>(view);
        Q_ASSERT(display);
        _sessionMap.remove(display);
    } 

487
    _viewSplitter->removeContainer(container);
488
    container->deleteLater();
489
490

    emit splitViewToggle( _viewSplitter->containers().count() > 1 );
491
}
492
493
494
495
496
497
498
499
void ViewManager::expandActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),10);
}
void ViewManager::shrinkActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),-10);
}
500
501
502
503
504
505
506
507
void ViewManager::closeActiveView()
{
    // only do something if there is more than one container active
    if ( _viewSplitter->containers().count() > 1 )
    {
        ViewContainer* container = _viewSplitter->activeContainer();

        removeContainer(container);
508

509
510
511
512
        // focus next container so that user can continue typing 
        // without having to manually focus it themselves
        nextContainer();
    }
513
514
515
516
517
518
519
520
521
522
}
void ViewManager::closeOtherViews()
{
    ViewContainer* active = _viewSplitter->activeContainer();

    QListIterator<ViewContainer*> iter(_viewSplitter->containers());
    while ( iter.hasNext() )
    {
        ViewContainer* next = iter.next();
        if ( next != active )
523
            removeContainer(next);
524
525
526
    }
}

527
SessionController* ViewManager::createController(Session* session , TerminalDisplay* view)
528
{
Robert Knight's avatar
Robert Knight committed
529
530
    // create a new controller for the session, and ensure that this view manager
    // is notified when the view gains the focus
531
    SessionController* controller = new SessionController(session,view,this);
532
    connect( controller , SIGNAL(focused(SessionController*)) , this , SLOT(controllerChanged(SessionController*)) );
533
    connect( session , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
534
535
    connect( session , SIGNAL(primaryScreenInUse(bool)) ,
             controller , SLOT(setupPrimaryScreenSpecificActions(bool)) );
536
537
    connect( session , SIGNAL(selectedText(QString)) ,
             controller , SLOT(updateCopyAction(QString)) );
538
    connect( view , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
539

540
541
542
    // if this is the first controller created then set it as the active controller
    if (!_pluggedController)
        controllerChanged(controller);
543

544
545
546
    return controller;
}

547
548
void ViewManager::controllerChanged(SessionController* controller)
{
549
550
    if ( controller == _pluggedController )
        return;
551

552
    _viewSplitter->setFocusProxy(controller->view());
553

554
555
    _pluggedController = controller;
    emit activeViewChanged(controller);
556
557
558
559
}

SessionController* ViewManager::activeViewController() const
{
560
    return _pluggedController;
561
}
562
563
564
565
566
567

IncrementalSearchBar* ViewManager::searchBar() const
{
    return _viewSplitter->activeSplitter()->activeContainer()->searchBar();
}

568
569
void ViewManager::createView(Session* session, ViewContainer* container, int index)
{
570
    // notify this view manager when the session finishes so that its view
571
    // can be deleted
572
573
574
    //
    // TODO - Find a more efficient a way to avoid multiple connections
    disconnect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );
575
576
577
    connect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );

     TerminalDisplay* display = createTerminalDisplay(session);
578
579
580
581
582
583
     const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
     applyProfileToView(display, profile);

     bool isFirst = _sessionMap.isEmpty();
     if ( isFirst)
         applyProfileToContainer(container, profile);
584

585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
     // set initial size
     display->setSize(80,40);

     ViewProperties* properties = createController(session,display);

     _sessionMap[display] = session; 
     container->addView(display,properties,index);
     session->addView(display);

     // tell the session whether it has a light or dark background
     session->setDarkBackground( colorSchemeForProfile(profile)->hasDarkBackground() );

     if ( container == _viewSplitter->activeContainer() ) 
     {
         container->setActiveView(display);
         display->setFocus( Qt::OtherFocusReason );
     }
602

603
     updateDetachViewState();
604
}
605

606
void ViewManager::createView(Session* session)
607
{
608
609
610
    // create the default container
    if (_viewSplitter->containers().count() == 0)
    {
611
        _viewSplitter->addContainer( createContainer(SessionManager::instance()->sessionProfile(session)) , 
612
                                     Qt::Vertical );
613
        emit splitViewToggle(false);
614
615
    }

616
    // new tab will be put at the end by default.
617
618
    int index = -1;

619
620
621
622
623
    // TODO: currently, whether new tab should be put after current tab is a per
    // profile setting, while in concept it should be per ViewManager or global
    // setting. The current implementation is limited by the design of Profile.h
    // It should be re-implmented at some appropriate time in the future.
    // comment by jekyllwu
Jekyll Wu's avatar
Jekyll Wu committed
624
625
    Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
    int newTabBehavior = profile->property<int>(Profile::NewTabBehavior);
626
    if ( newTabBehavior == Profile::PutNewTabAfterCurrentTab )
627
    {
628
629
630
631
632
633
        QWidget* view = activeView();
        if (view)
        {
            QList<QWidget*> views =  _viewSplitter->activeContainer()->views();
            index = views.indexOf(view) + 1;
        }
634
635
    }

Robert Knight's avatar
Robert Knight committed
636
637
638
    // iterate over the view containers owned by this view manager
    // and create a new terminal display for the session in each of them, along with
    // a controller for the session/display pair 
639
640
641
642
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());
    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
643
        createView(session, container, index);
644
645
646
    }
}

Jekyll Wu's avatar
Jekyll Wu committed
647
ViewContainer* ViewManager::createContainer(const Profile::Ptr profile)
648
{
Jekyll Wu's avatar
Jekyll Wu committed
649
    Q_ASSERT( profile );
650

Jekyll Wu's avatar
Jekyll Wu committed
651
    const int tabPosition = profile->property<int>(Profile::TabBarPosition);
652
653
654
655
656

    ViewContainer::NavigationPosition position = ( tabPosition == Profile::TabBarTop ) ?
                                                   ViewContainer::NavigationPositionTop :
                                                   ViewContainer::NavigationPositionBottom;

657
658
659
660
661
    ViewContainer* container = 0;

    switch ( _navigationMethod )
    {
        case TabbedNavigation:    
662
663
664
665
666
            {
                container =
                    new TabbedViewContainer(position,_viewSplitter);

                connect(container,
Laurent Montel's avatar
Laurent Montel committed
667
                    SIGNAL(detachTab(ViewContainer*,QWidget*)),
668
                    this,
Laurent Montel's avatar
Laurent Montel committed
669
                    SLOT(detachView(ViewContainer*,QWidget*))
670
                    );
671
                connect(container,
Laurent Montel's avatar
Laurent Montel committed
672
                    SIGNAL(closeTab(ViewContainer*,QWidget*)),
673
                    this,
Laurent Montel's avatar
Laurent Montel committed
674
                    SLOT(closeTabFromContainer(ViewContainer*,QWidget*)));
675

676
            }
677
678
679
680
681
            break;
        case NoNavigation:
        default:
            container = new StackedViewContainer(_viewSplitter);
    }
682

Jekyll Wu's avatar
Jekyll Wu committed
683
    applyProfileToContainer(container, profile);
684

685
    // connect signals and slots
686
687
688
689
690
691
    connect( container , SIGNAL(viewAdded(QWidget*,ViewProperties*)) , _containerSignalMapper ,
           SLOT(map()) );
    connect( container , SIGNAL(viewRemoved(QWidget*)) , _containerSignalMapper ,
           SLOT(map()) ); 
    _containerSignalMapper->setMapping(container,container);

692
    connect( container, SIGNAL(newViewRequest()), this, SIGNAL(newViewRequest()) );
693
    connect( container, SIGNAL(moveViewRequest(int,int,bool&)), 
694
    this , SLOT(containerMoveViewRequest(int,int,bool&)) );
695
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
696
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
697
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
698

699
    return container;
700
}
701
702
void ViewManager::containerMoveViewRequest(int index, int id, bool& moved)
{
703
704
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
705

706
707
    if (!controller)
        return;
708

709
    createView(controller->session(),container,index);
710
    controller->session()->refresh();
711
    moved = true;
712
}
713
714
715
716
717
718
719
720
void ViewManager::setNavigationMethod(NavigationMethod method)
{
    _navigationMethod = method;

    KActionCollection* collection = _actionCollection;

    if ( collection )
    {
721
722
723
724
725
726
727
728
729
        // FIXME: The following disables certain actions for the KPart that it
        // doesn't actually have a use for, to avoid polluting the action/shortcut
        // namespace of an application using the KPart (otherwise, a shortcut may
        // be in use twice, and the user gets to see an "ambiguous shortcut over-
        // load" error dialog). However, this approach sucks - it's the inverse of
        // what it should be. Rather than disabling actions not used by the KPart,
        // a method should be devised to only enable those that are used, perhaps
        // by using a separate action collection.

730
        const bool enable = (_navigationMethod != NoNavigation) ;
731
732
733
        QAction* action;

        action = collection->action( "next-view" );
734
        if ( action ) action->setEnabled( enable );
735
736

        action = collection->action( "previous-view" );
737
        if ( action ) action->setEnabled( enable );
738

739
        action = collection->action( "last-tab" );
740
        if ( action ) action->setEnabled( enable );
741

742
        action = collection->action( "split-view-left-right" );
743
        if ( action ) action->setEnabled( enable );
744
745

        action = collection->action( "split-view-top-bottom" );
746
        if ( action ) action->setEnabled( enable );
747
748

        action = collection->action( "rename-session" );
749
        if ( action ) action->setEnabled( enable );
750
751

        action = collection->action( "move-view-left" );
752
        if ( action ) action->setEnabled( enable );
753
754

        action = collection->action( "move-view-right" );
755
        if ( action ) action->setEnabled( enable );
756
757
758
    }
}

759
760
ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; }

761
762
void ViewManager::containerViewsChanged(QObject* container)
{
763
    if (_viewSplitter && container == _viewSplitter->activeContainer() )
764
765
766
767
768
    {
        emit viewPropertiesChanged( viewProperties() );
    } 
}

769
770
void ViewManager::viewCloseRequest(QWidget* view)
{
771
772
    //FIXME Check that this cast is actually legal
    TerminalDisplay* display = (TerminalDisplay*)view;
773

774
775
    Q_ASSERT(display);

776
777
    // 1. detach view from session
    // 2. if the session has no views left, close it
778
    Session* session = _sessionMap[ display ];
779
    _sessionMap.remove(display);
780
781
    if ( session )
    {
782
        display->deleteLater();
783

784
        if ( session->views().count() == 0 )
785
            session->close();
786
    }
787
788
789
790
791
    //we only update the focus if the splitter is still alive
    if (_viewSplitter) {
        focusActiveView();
        updateDetachViewState();
    }
792
793
794
795
    // The below causes the menus  to be messed up
    // Only happenss when using the tab bar close button
//    if (_pluggedController)
//        emit unplugController(_pluggedController);
796
797
}

798
TerminalDisplay* ViewManager::createTerminalDisplay(Session* session)
799
{
800
801
    TerminalDisplay* display = new TerminalDisplay(0);
    display->setRandomSeed(session->sessionId() * 31);
802

803
    return display;
804
805
}

Jekyll Wu's avatar
Jekyll Wu committed
806
const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr profile) const
807
{
808
    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
Jekyll Wu's avatar
Jekyll Wu committed
809
                                            findColorScheme(profile->colorScheme());
810
811
    if ( !colorScheme )
       colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); 
812
813
    Q_ASSERT( colorScheme );

814
815
816
    return colorScheme;
}

Jekyll Wu's avatar
Jekyll Wu committed
817
void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile)
818
{
Jekyll Wu's avatar
Jekyll Wu committed
819
    Q_ASSERT( profile );
820

821
    // menu bar visibility
Jekyll Wu's avatar
Jekyll Wu committed
822
    emit setMenuBarVisibleRequest( profile->property<bool>(Profile::ShowMenuBar) );
823

Jekyll Wu's avatar
Jekyll Wu committed
824
    emit setSaveGeometryOnExitRequest( profile->property<bool>(Profile::SaveGeometryOnExit) );
825

826
827
    emit updateWindowIcon();

828
    // load color scheme
829
    ColorEntry table[TABLE_COLORS];
830
    const ColorScheme* colorScheme = colorSchemeForProfile(profile);
831
832
    colorScheme->getColorTable(table , view->randomSeed() );
    view->setColorTable(table);
833
    view->setOpacity(colorScheme->opacity());
834
    view->setWallpaper(colorScheme->wallpaper());
835

836
    // load font 
Jekyll Wu's avatar
Jekyll Wu committed
837
838
839
    view->setAntialias(profile->property<bool>(Profile::AntiAliasFonts));
    view->setBoldIntense(profile->property<bool>(Profile::BoldIntense));
    view->setVTFont(profile->font());
840
841

    // set scroll-bar position
Jekyll Wu's avatar
Jekyll Wu committed
842
    int scrollBarPosition = profile->property<int>(Profile::ScrollBarPosition);
843

844
    if ( scrollBarPosition == Profile::ScrollBarLeft )
845
       view->setScrollBarPosition(TerminalDisplay::ScrollBarLeft);
846
    else if ( scrollBarPosition == Profile::ScrollBarRight )
847
       view->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
848
849
    else if ( scrollBarPosition == Profile::ScrollBarHidden )
       view->setScrollBarPosition(TerminalDisplay::ScrollBarHidden);
850

851
    // show hint about termianl size after resizing
Jekyll Wu's avatar
Jekyll Wu committed
852
    view->setShowTerminalSizeHint(profile->property<bool>(Profile::ShowTerminalSizeHint));
853

854
    // terminal features
Jekyll Wu's avatar
Jekyll Wu committed
855
    bool blinkingCursor = profile->property<bool>(Profile::BlinkingCursorEnabled);
Jekyll Wu's avatar
Jekyll Wu committed
856
    view->setBlinkingCursorEnabled(blinkingCursor);
857

Jekyll Wu's avatar
Jekyll Wu committed
858
    bool blinkingText = profile->property<bool>(Profile::BlinkingTextEnabled);
859
860
    view->setBlinkingTextEnabled(blinkingText);

Jekyll Wu's avatar
Jekyll Wu committed
861
    int tripleClickMode = profile->property<int>(Profile::TripleClickMode);
862
863
    view->setTripleClickMode( TerminalDisplay::TripleClickMode(tripleClickMode) );

Jekyll Wu's avatar
Jekyll Wu committed
864
    view->setUnderlineLinks(profile->property<bool>(Profile::UnderlineLinksEnabled));
865

Jekyll Wu's avatar
Jekyll Wu committed
866
    bool bidiEnabled = profile->property<bool>(Profile::BidiRenderingEnabled);
867
868
    view->setBidiEnabled(bidiEnabled);

869
    // cursor shape
Jekyll Wu's avatar
Jekyll Wu committed
870
    int cursorShape = profile->property<int>(Profile::CursorShape);
871
872
873
874
875
876
877
878
879

    if ( cursorShape == Profile::BlockCursor )
        view->setKeyboardCursorShape(TerminalDisplay::BlockCursor);  
    else if ( cursorShape == Profile::IBeamCursor )
        view->setKeyboardCursorShape(TerminalDisplay::IBeamCursor);
    else if ( cursorShape == Profile::UnderlineCursor )
        view->setKeyboardCursorShape(TerminalDisplay::UnderlineCursor);

    // cursor color
Jekyll Wu's avatar
Jekyll Wu committed
880
881
    bool useCustomColor = profile->property<bool>(Profile::UseCustomCursorColor);
    const QColor& cursorColor = profile->property<QColor>(Profile::CustomCursorColor);
882

883
884
885
    view->setKeyboardCursorColor(!useCustomColor,cursorColor);

    // word characters
Jekyll Wu's avatar
Jekyll Wu committed
886
    view->setWordCharacters( profile->property<QString>(Profile::WordCharacters) );
887
888
}

Jekyll Wu's avatar
Jekyll Wu committed
889
void ViewManager::applyProfileToContainer(ViewContainer* container , const Profile::Ptr profile)
890
{
Jekyll Wu's avatar
Jekyll Wu committed
891
892
893
    int tabBarMode = profile->property<int>(Profile::TabBarMode);
    int tabBarPosition = profile->property<int>(Profile::TabBarPosition);
    bool showNewCloseButtons = profile->property<bool>(Profile::ShowNewAndCloseTabButtons);
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923

    if ( tabBarMode == Profile::AlwaysHideTabBar )
        container->setNavigationDisplayMode(ViewContainer::AlwaysHideNavigation);
    else if ( tabBarMode == Profile::AlwaysShowTabBar )
        container->setNavigationDisplayMode(ViewContainer::AlwaysShowNavigation);
    else if ( tabBarMode == Profile::ShowTabBarAsNeeded )
        container->setNavigationDisplayMode(ViewContainer::ShowNavigationAsNeeded);

    ViewContainer::NavigationPosition position = container->navigationPosition();
    if ( tabBarPosition == Profile::TabBarTop )
        position = ViewContainer::NavigationPositionTop;
    else if ( tabBarPosition == Profile::TabBarBottom )
        position = ViewContainer::NavigationPositionBottom;

    if ( container->supportedNavigationPositions().contains(position) )
        container->setNavigationPosition(position);

    if (showNewCloseButtons)
    {
        container->setFeatures(container->features()
                | ViewContainer::QuickNewView | ViewContainer::QuickCloseView);
        container->setNewViewMenu(createNewViewMenu());