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
24
// System

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

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

// Konsole
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
50
#include "konsoleadaptor.h"
#include "Profile.h"
51

52
53
using namespace Konsole;

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

66
67
68
69
70
71
72
    // 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);
73
    _viewSplitter->setFocusPolicy(Qt::NoFocus);
74

75
76
77
78
79
80
    // setup actions which relating to the view
    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()) );
81
82
83
84

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

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

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

96
97
98
99
}

ViewManager::~ViewManager()
{
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    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;
114
}
115
116
117
118
119
120
121
122
123
124
125
126
QWidget* ViewManager::activeView() const
{
    ViewContainer* container = _viewSplitter->activeContainer();
    if ( container )
    {
        return container->activeView();
    }
    else
    {
        return 0;
    }
}
127

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

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
137
138
139
140
    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);
141
  
Kurt Hindenburg's avatar
Kurt Hindenburg committed
142
143
    KAction* moveViewLeftAction = new KAction( i18nc("@action Shortcut entry", "Move Tab Left") , this );
    KAction* moveViewRightAction = new KAction( i18nc("@action Shortcut entry", "Move Tab Right") , this );
144

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

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

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

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

        multiViewOnlyActions << closeActiveAction;
173

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

        multiViewOnlyActions << closeOtherAction;
181

182
        KAction* detachViewAction = collection->addAction("detach-view");
183
        detachViewAction->setIcon(KIcon("tab-detach"));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
184
        detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab"));
185
186
        // 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
187
188
189
        detachViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H));

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

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

199
200
        multiViewOnlyActions << expandActiveAction;

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

207
208
        multiViewOnlyActions << shrinkActiveAction;

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

        // 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
223
            KAction* switchToTabAction = new KAction(i18nc("@action Shortcut entry", "Switch to Tab %1",i+1),this);
224
225
226
227
228
            switchToTabMapper->setMapping(switchToTabAction,i);
            connect(switchToTabAction,SIGNAL(triggered()),switchToTabMapper,
                    SLOT(map()));
            collection->addAction(QString("switch-to-tab-%1").arg(i),switchToTabAction);
        }
229
    }
Robert Knight's avatar
   
Robert Knight committed
230

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

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

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

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

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

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

    connect( lastViewAction, SIGNAL(triggered()) , this , SLOT(lastView()));
    _viewSplitter->addAction(lastViewAction);
Robert Knight's avatar
   
Robert Knight committed
260
}
261
262
263
264
265
266
267
268
269
270
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));
}
271
272
void ViewManager::updateDetachViewState()
{
273
274
    if (!_actionCollection)
        return;
275

276

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

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

282
283
    if ( detachAction && shouldEnable != detachAction->isEnabled() )
        detachAction->setEnabled(shouldEnable);
284
}
285
286
287
288
289
290
291
292
293
294
295
296
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
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();
318
}
319

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

    Q_ASSERT( container );

    container->activateLastView();
}

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

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

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

    if (!viewToDetach)
343
344
        return;

345
    emit viewDetached(_sessionMap[viewToDetach]);
346
    
347
    _sessionMap.remove(viewToDetach);
348
349

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

    // 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 )
    {
359
        removeContainer(container);
360
361
362
363
    }

}

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

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

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

376
377
    Q_ASSERT(session);

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

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

390
391
392
393
    // 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
394
395
396
397
}

void ViewManager::focusActiveView()
{
Robert Knight's avatar
Robert Knight committed
398
399
400
401
402
403
    // 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
404
405
406
407
408
409
410
411
412
    ViewContainer* container = _viewSplitter->activeContainer(); 
    if ( container )
    {
        QWidget* activeView = container->activeView();
        if ( activeView )
        {
            activeView->setFocus(Qt::MouseFocusReason);
        }
    }
413
414
}

415

416
417
void ViewManager::viewActivated( QWidget* view )
{
418
419
420
421
422
423
    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);
424
425
}

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

435
void ViewManager::splitView(Qt::Orientation orientation)
436
{
437
438
439
440
    // 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());
    
441
    ViewContainer* container = 0; 
442
443

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

451
        _sessionMap[display] = session;
452

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

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

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

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

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

    if ( activeView )
        activeView->setFocus(Qt::OtherFocusReason);
477
}
478
void ViewManager::removeContainer(ViewContainer* container)
479
{
480
481
482
483
484
485
486
487
    // 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);
    } 

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

    emit splitViewToggle( _viewSplitter->containers().count() > 1 );
492
}
493
494
495
496
497
498
499
500
void ViewManager::expandActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),10);
}
void ViewManager::shrinkActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),-10);
}
501
502
503
504
505
506
507
508
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);
509

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

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

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

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

545
546
547
    return controller;
}

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

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

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

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

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

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

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

     bool isFirst = _sessionMap.isEmpty();
     if ( isFirst)
         applyProfileToContainer(container, profile);
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
     
     // 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 );
     }
603
604
    
     updateDetachViewState();
605
}
606

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

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

620
621
622
623
624
    // 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
625
626
    Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
    int newTabBehavior = profile->property<int>(Profile::NewTabBehavior);
627
    if ( newTabBehavior == Profile::PutNewTabAfterCurrentTab )
628
    {
629
630
631
632
633
634
        QWidget* view = activeView();
        if (view)
        {
            QList<QWidget*> views =  _viewSplitter->activeContainer()->views();
            index = views.indexOf(view) + 1;
        }
635
636
    }

Robert Knight's avatar
Robert Knight committed
637
638
639
    // 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 
640
641
642
643
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());
    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
644
        createView(session, container, index);
645
646
647
    }
}

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

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

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

658
659
660
661
662
    ViewContainer* container = 0;

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

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

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

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

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

693
    connect( container, SIGNAL(newViewRequest()), this, SIGNAL(newViewRequest()) );
694
    connect( container, SIGNAL(moveViewRequest(int,int,bool&)), 
695
    this , SLOT(containerMoveViewRequest(int,int,bool&)) );
696
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
697
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
698
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
699
    
700
    return container;
701
}
702
703
void ViewManager::containerMoveViewRequest(int index, int id, bool& moved)
{
704
705
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
706

707
708
    if (!controller)
        return;
709

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

    KActionCollection* collection = _actionCollection;

    if ( collection )
    {
722
723
724
725
726
727
728
729
730
        // 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

804
    return display;
805
806
}

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

815
816
817
    return colorScheme;
}

Jekyll Wu's avatar
Jekyll Wu committed
818
void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr profile)
819
{
Jekyll Wu's avatar
Jekyll Wu committed
820
    Q_ASSERT( profile );
821
    
Jekyll Wu's avatar
Jekyll Wu committed
822
    const ColorScheme* colorScheme = colorSchemeForProfile(profile);
823

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

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

829
830
    emit updateWindowIcon();

831
    // load colour scheme
832
833
834
835
    ColorEntry table[TABLE_COLORS];
    
    colorScheme->getColorTable(table , view->randomSeed() );
    view->setColorTable(table);
836
    view->setOpacity(colorScheme->opacity());
837
    view->setWallpaper(colorScheme->wallpaper());
838
  
839
    // load font 
Jekyll Wu's avatar
Jekyll Wu committed
840
841
842
    view->setAntialias(profile->property<bool>(Profile::AntiAliasFonts));
    view->setBoldIntense(profile->property<bool>(Profile::BoldIntense));
    view->setVTFont(profile->font());
843
844

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

847
    if ( scrollBarPosition == Profile::ScrollBarLeft )
848
       view->setScrollBarPosition(TerminalDisplay::ScrollBarLeft);
849
    else if ( scrollBarPosition == Profile::ScrollBarRight )
850
       view->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
851
852
    else if ( scrollBarPosition == Profile::ScrollBarHidden )
       view->setScrollBarPosition(TerminalDisplay::ScrollBarHidden);
853

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

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

Jekyll Wu's avatar
Jekyll Wu committed
861
    bool blinkingText = profile->property<bool>(Profile::BlinkingTextEnabled);
862
863
    view->setBlinkingTextEnabled(blinkingText);

Jekyll Wu's avatar
Jekyll Wu committed
864
    int tripleClickMode = profile->property<int>(Profile::TripleClickMode);
865
866
    view->setTripleClickMode( TerminalDisplay::TripleClickMode(tripleClickMode) );

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

Jekyll Wu's avatar
Jekyll Wu committed
869
    bool bidiEnabled = profile->property<bool>(Profile::BidiRenderingEnabled);
870
871
    view->setBidiEnabled(bidiEnabled);

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

    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
883
884
    bool useCustomColor = profile->property<bool>(Profile::UseCustomCursorColor);
    const QColor& cursorColor = profile->property<QColor>(Profile::CustomCursorColor);
885
886
887
888
        
    view->setKeyboardCursorColor(!useCustomColor,cursorColor);

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

Jekyll Wu's avatar
Jekyll Wu committed
892
void ViewManager::applyProfileToContainer(ViewContainer* container , const Profile::Ptr profile)
893
{
Jekyll Wu's avatar
Jekyll Wu committed
894
895
896
    int tabBarMode = profile->property<int>(Profile::TabBarMode);
    int tabBarPosition = profile->property<int>(Profile::TabBarPosition);
    bool showNewCloseButtons = profile->property<bool>(Profile::ShowNewAndCloseTabButtons);
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
924
925
926

    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());
    }
    else
    {
        container->setFeatures(container->features()
                & ~ViewContainer::QuickNewView & ~ViewContainer::QuickCloseView);
    }
}

927
928
void ViewManager::updateViewsForSession(Session* session)
{
929
    QListIterator<TerminalDisplay*> iter(_sessionMap.keys(session));
930
931
    while ( iter.hasNext() )
    {
932
        applyProfileToView(iter.next(),SessionManager::instance()->sessionProfile(session));
933
934
935
    }
}

936
void ViewManager::profileChanged(Profile::Ptr profile)
Robert Knight's avatar