ViewManager.cpp 37.4 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
25
// System
#include <assert.h>

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

31
32
// KDE
#include <kdebug.h>
Robert Knight's avatar
Robert Knight committed
33
#include <KAcceleratorManager>
34
#include <KGlobal>
35
36
37
#include <KLocale>
#include <KToggleAction>
#include <KXMLGUIFactory>
38
#include <QStringList>
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

137
138
    KAction* nextViewAction = new KAction( i18n("Next View") , this );
    KAction* previousViewAction = new KAction( i18n("Previous View") , this );
139
    KAction* nextContainerAction = new KAction( i18n("Next View Container") , this);
140
  
141
142
    KAction* moveViewLeftAction = new KAction( i18n("Move View Left") , this );
    KAction* moveViewRightAction = new KAction( i18n("Move View 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
183
        detachViewAction->setIcon(KIcon("tab-detach"));
        detachViewAction->setText(i18n("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
194
195
196
        expandActiveAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_BracketRight) );
        collection->addAction("expand-active-view",expandActiveAction);
        connect( expandActiveAction , SIGNAL(triggered()) , this , SLOT(expandActiveView()) );

197
198
        multiViewOnlyActions << expandActiveAction;

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

204
205
        multiViewOnlyActions << shrinkActiveAction;

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

        // 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++)
        {
            KAction* switchToTabAction = new KAction(i18n("Switch to Tab %1",i+1),this);
            switchToTabMapper->setMapping(switchToTabAction,i);
            connect(switchToTabAction,SIGNAL(triggered()),switchToTabMapper,
                    SLOT(map()));
            collection->addAction(QString("switch-to-tab-%1").arg(i),switchToTabAction);
        }
225
    }
Robert Knight's avatar
   
Robert Knight committed
226

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

233
    // keyboard shortcut only actions
234
    KShortcut nextViewShortcut = nextViewAction->shortcut();
235
    nextViewShortcut.setPrimary( QKeySequence(Qt::SHIFT+Qt::Key_Right) );
236
    nextViewShortcut.setAlternate( QKeySequence(Qt::CTRL+Qt::Key_PageDown) );
237
    nextViewAction->setShortcut(nextViewShortcut); 
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
242
    KShortcut previousViewShortcut = previousViewAction->shortcut();
    previousViewShortcut.setPrimary( QKeySequence(Qt::SHIFT+Qt::Key_Left) );
243
    previousViewShortcut.setAlternate( QKeySequence(Qt::CTRL+Qt::Key_PageUp) );
244
    previousViewAction->setShortcut(previousViewShortcut);
Robert Knight's avatar
   
Robert Knight committed
245
    connect( previousViewAction, SIGNAL(triggered()) , this , SLOT(previousView()) );
246
    _viewSplitter->addAction(previousViewAction);
Robert Knight's avatar
   
Robert Knight committed
247
248
249

    nextContainerAction->setShortcut( QKeySequence(Qt::SHIFT+Qt::Key_Tab) );
    connect( nextContainerAction , SIGNAL(triggered()) , this , SLOT(nextContainer()) );
250
    _viewSplitter->addAction(nextContainerAction);
251
252
253
254
255
256
257

    moveViewLeftAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Left) );
    connect( moveViewLeftAction , SIGNAL(triggered()) , this , SLOT(moveActiveViewLeft()) );
    _viewSplitter->addAction(moveViewLeftAction);
    moveViewRightAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Right) );
    connect( moveViewRightAction , SIGNAL(triggered()) , this , SLOT(moveActiveViewRight()) );
    _viewSplitter->addAction(moveViewRightAction);
Robert Knight's avatar
   
Robert Knight committed
258
}
259
260
261
262
263
264
265
266
267
268
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));
}
269
270
void ViewManager::updateDetachViewState()
{
271
272
    if (!_actionCollection)
        return;
273

274

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

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

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

318
319
320
321
322
void ViewManager::detachActiveView()
{
    // find the currently active view and remove it from its container 
    ViewContainer* container = _viewSplitter->activeContainer();

323
324
325
326
327
328
329
330
331
    detachView(container, container->activeView());
}

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

    if (!viewToDetach)
332
333
        return;

334
    emit viewDetached(_sessionMap[viewToDetach]);
335
    
336
    _sessionMap.remove(viewToDetach);
337
338

    // remove the view from this window
339
340
    container->removeView(viewToDetach);
    viewToDetach->deleteLater();
341
342
343
344
345
346
347

    // 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 )
    {
348
        removeContainer(container);
349
350
351
352
    }

}

353
void ViewManager::sessionFinished()
354
{
355
356
357
358
    // if this slot is called after the view manager's main widget
    // has been destroyed, do nothing
    if (!_viewSplitter)
        return;
359

360
361
    Session* session = qobject_cast<Session*>(sender());

362
363
    if ( _sessionMap[qobject_cast<TerminalDisplay*>(activeView())] == session )
    {
364
        // switch to the next view before deleting the session views to prevent flicker 
365
        // occurring as a result of an interval between removing the active view and switching
366
367
        // to the next view
        nextView();
368
369
    }

370
371
    Q_ASSERT(session);

372
    // close attached views
373
    QList<TerminalDisplay*> children = _viewSplitter->findChildren<TerminalDisplay*>();
374

375
    foreach ( TerminalDisplay* view , children )
376
377
378
379
    {
        if ( _sessionMap[view] == session )
        {
            _sessionMap.remove(view);
380
            view->deleteLater();
381
        }
Robert Knight's avatar
   
Robert Knight committed
382
    }
383

Robert Knight's avatar
   
Robert Knight committed
384
385
386
387
}

void ViewManager::focusActiveView()
{
Robert Knight's avatar
Robert Knight committed
388
389
390
391
392
393
    // 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
394
395
396
397
398
399
400
401
402
    ViewContainer* container = _viewSplitter->activeContainer(); 
    if ( container )
    {
        QWidget* activeView = container->activeView();
        if ( activeView )
        {
            activeView->setFocus(Qt::MouseFocusReason);
        }
    }
403
404
}

405

406
407
void ViewManager::viewActivated( QWidget* view )
{
408
409
410
411
412
413
    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);
414
415
}

416
void ViewManager::splitLeftRight()
417
{
418
    splitView(Qt::Horizontal);
419
}
420
void ViewManager::splitTopBottom()
421
{
422
    splitView(Qt::Vertical);
423
424
}

425
void ViewManager::splitView(Qt::Orientation orientation)
426
{
427
428
429
430
    // 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());
    
431
    ViewContainer* container = 0; 
432
433

    while (existingViewIter.hasNext())
434
    {
435
        Session* session = _sessionMap[(TerminalDisplay*)existingViewIter.next()];
436
        TerminalDisplay* display = createTerminalDisplay(session);
437
438
        const Profile::Ptr info = SessionManager::instance()->sessionProfile(session);
        applyProfile(display, info, false);
439
        ViewProperties* properties = createController(session,display);
440

441
        _sessionMap[display] = session;
442

443
444
445
        // create a container using settings from the first 
        // session in the previous container
        if ( !container )
446
447
448
449
450
451
452
453
454
            container = createContainer(info);

        int tabBarMode = info->property<int>(Profile::TabBarMode);
        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);
455
456
457
        container->addView(display,properties);
        session->addView( display );
    }
458

459
460
    _viewSplitter->addContainer(container,orientation);
    emit splitViewToggle(_viewSplitter->containers().count() > 0);
461

462
463
    // focus the new container
    container->containerWidget()->setFocus();
464
465

    // ensure that the active view is focused after the split / unsplit
466
467
468
469
470
    ViewContainer* activeContainer = _viewSplitter->activeContainer();
    QWidget* activeView = activeContainer ? activeContainer->activeView() : 0;

    if ( activeView )
        activeView->setFocus(Qt::OtherFocusReason);
471
}
472
void ViewManager::removeContainer(ViewContainer* container)
473
{
474
475
476
477
478
479
480
481
    // 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);
    } 

482
    _viewSplitter->removeContainer(container);
483
    container->deleteLater();
484
485

    emit splitViewToggle( _viewSplitter->containers().count() > 1 );
486
}
487
488
489
490
491
492
493
494
void ViewManager::expandActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),10);
}
void ViewManager::shrinkActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),-10);
}
495
496
497
498
499
500
501
502
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);
503

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

    QListIterator<ViewContainer*> iter(_viewSplitter->containers());
    while ( iter.hasNext() )
    {
        ViewContainer* next = iter.next();
        if ( next != active )
518
            removeContainer(next);
519
520
521
    }
}

522
SessionController* ViewManager::createController(Session* session , TerminalDisplay* view)
523
{
Robert Knight's avatar
Robert Knight committed
524
525
    // create a new controller for the session, and ensure that this view manager
    // is notified when the view gains the focus
526
    SessionController* controller = new SessionController(session,view,this);
527
    connect( controller , SIGNAL(focused(SessionController*)) , this , SLOT(controllerChanged(SessionController*)) );
528
529
    connect( session , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
    connect( view , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
530

531
532
533
    // if this is the first controller created then set it as the active controller
    if (!_pluggedController)
        controllerChanged(controller);
534

535
536
537
    return controller;
}

538
539
void ViewManager::controllerChanged(SessionController* controller)
{
540
541
    if ( controller == _pluggedController )
        return;
542

543
    _viewSplitter->setFocusProxy(controller->view());
544

545
546
    _pluggedController = controller;
    emit activeViewChanged(controller);
547
548
549
550
}

SessionController* ViewManager::activeViewController() const
{
551
    return _pluggedController;
552
}
553
554
555
556
557
558

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

559
560
void ViewManager::createView(Session* session, ViewContainer* container, int index)
{
561
    // notify this view manager when the session finishes so that its view
562
    // can be deleted
563
564
565
    //
    // TODO - Find a more efficient a way to avoid multiple connections
    disconnect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );
566
567
    connect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );

568
     bool isFirst = _sessionMap.isEmpty();
569
     TerminalDisplay* display = createTerminalDisplay(session);
570
     applyProfile(display,SessionManager::instance()->sessionProfile(session),isFirst);
571
572
573
574
575
576
577
578
579
580
581
     
     // 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
582
     const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
583
584
585
586
587
588
589
     session->setDarkBackground( colorSchemeForProfile(profile)->hasDarkBackground() );

     if ( container == _viewSplitter->activeContainer() ) 
     {
         container->setActiveView(display);
         display->setFocus( Qt::OtherFocusReason );
     }
590
591
    
     updateDetachViewState();
592
}
593

594
void ViewManager::createView(Session* session)
595
{
596
597
598
    // create the default container
    if (_viewSplitter->containers().count() == 0)
    {
599
        _viewSplitter->addContainer( createContainer(SessionManager::instance()->sessionProfile(session)) , 
600
                                     Qt::Vertical );
601
        emit splitViewToggle(false);
602
603
    }

604
       
Robert Knight's avatar
Robert Knight committed
605
606
607
    // 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 
608
609
610
611
612
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());

    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
613
        createView(session,container,-1);
614
    }
615

616
617
}

618
ViewContainer* ViewManager::createContainer(const Profile::Ptr info)
619
{
620
621
    Q_ASSERT( info );

622
    const int tabPosition = info->property<int>(Profile::TabBarPosition);
623
624
625
626
627

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

628
629
630
631
632
    ViewContainer* container = 0;

    switch ( _navigationMethod )
    {
        case TabbedNavigation:    
633
634
635
636
637
638
639
640
641
642
            {
                container =
                    new TabbedViewContainer(position,_viewSplitter);

                connect(container,
                    SIGNAL(detachTab(ViewContainer*, QWidget*)),
                    this,
                    SLOT(detachView(ViewContainer*, QWidget*))
                    );
            }
643
644
645
646
647
            break;
        case NoNavigation:
        default:
            container = new StackedViewContainer(_viewSplitter);
    }
648

649
    // connect signals and slots
650
651
652
653
654
655
    connect( container , SIGNAL(viewAdded(QWidget*,ViewProperties*)) , _containerSignalMapper ,
           SLOT(map()) );
    connect( container , SIGNAL(viewRemoved(QWidget*)) , _containerSignalMapper ,
           SLOT(map()) ); 
    _containerSignalMapper->setMapping(container,container);

656
    connect( container, SIGNAL(newViewRequest()), this, SIGNAL(newViewRequest()) );
657
    connect( container, SIGNAL(moveViewRequest(int,int,bool&)), 
658
    this , SLOT(containerMoveViewRequest(int,int,bool&)) );
659
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
660
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
661
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
662
    
663
    return container;
664
}
665
666
void ViewManager::containerMoveViewRequest(int index, int id, bool& moved)
{
667
668
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
669

670
671
    if (!controller)
        return;
672

673
    createView(controller->session(),container,index);
674
    controller->session()->refresh();
675
    moved = true;
676
}
677
678
679
680
681
682
683
684
void ViewManager::setNavigationMethod(NavigationMethod method)
{
    _navigationMethod = method;

    KActionCollection* collection = _actionCollection;

    if ( collection )
    {
685
686
687
688
689
690
691
692
693
        // 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.

694
695
696
697
698
699
700
        QAction* action;

        action = collection->action( "next-view" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );

        action = collection->action( "previous-view" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
701
702
703
704
705
706

        action = collection->action( "split-view-left-right" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );

        action = collection->action( "split-view-top-bottom" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
707
708
709

        action = collection->action( "rename-session" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
710
711
712
713
714
715

        action = collection->action( "move-view-left" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );

        action = collection->action( "move-view-right" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
716
717
718
    }
}

719
720
ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; }

721
722
void ViewManager::containerViewsChanged(QObject* container)
{
723
    if (_viewSplitter && container == _viewSplitter->activeContainer() )
724
725
726
727
728
    {
        emit viewPropertiesChanged( viewProperties() );
    } 
}

729
730
void ViewManager::viewCloseRequest(QWidget* view)
{
731
732
733
734
735
    //FIXME Check that this cast is actually legal
    TerminalDisplay* display = (TerminalDisplay*)view;
  
    Q_ASSERT(display);

736
737
    // 1. detach view from session
    // 2. if the session has no views left, close it
738
    Session* session = _sessionMap[ display ];
739
    _sessionMap.remove(display);
740
741
    if ( session )
    {
742
        display->deleteLater();
743

744
        if ( session->views().count() == 0 )
745
            session->close();
746
    }
747
748
749
750
751
    //we only update the focus if the splitter is still alive
    if (_viewSplitter) {
        focusActiveView();
        updateDetachViewState();
    }
752
753
}

754
TerminalDisplay* ViewManager::createTerminalDisplay(Session* session)
755
{
756
   TerminalDisplay* display = new TerminalDisplay(0);
757
758

   //TODO Temporary settings used here
759
760
   display->setBellMode(TerminalDisplay::NotifyBell);
   display->setTerminalSizeHint(true);
761
   display->setTripleClickMode(TerminalDisplay::SelectWholeLine);
762
   display->setTerminalSizeStartup(true);
763
   display->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
764
765
   display->setRandomSeed(session->sessionId() * 31);

766
767
768
   return display;
}

769
const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr info) const
770
{
771
772
    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
                                            findColorScheme(info->colorScheme());
773
774
    if ( !colorScheme )
       colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); 
775
776
    Q_ASSERT( colorScheme );

777
778
779
    return colorScheme;
}

780
781
void ViewManager::applyProfile(TerminalDisplay* view , const Profile::Ptr info, 
                               bool applyContainerSettings) 
782
783
784
{
    Q_ASSERT( info );
    
785
    const ColorScheme* colorScheme = colorSchemeForProfile(info);
786

787
    // menu bar visibility
788
    emit setMenuBarVisibleRequest( info->property<bool>(Profile::ShowMenuBar) );
789

790
791
    emit setSaveGeometryOnExitRequest( info->property<bool>(Profile::SaveGeometryOnExit) );

792
    // tab bar visibility
793
794
795
796
797
    if (applyContainerSettings)
    {
        ViewContainer* container = _viewSplitter->activeContainer();
        int tabBarMode = info->property<int>(Profile::TabBarMode);
        int tabBarPosition = info->property<int>(Profile::TabBarPosition);
Robert Knight's avatar
   
Robert Knight committed
798
        bool showNewCloseButtons = info->property<bool>(Profile::ShowNewAndCloseTabButtons);
799

800
801
802
803
804
805
        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);
806

807
        ViewContainer::NavigationPosition position = container->navigationPosition();
808

809
810
811
812
        if ( tabBarPosition == Profile::TabBarTop )
            position = ViewContainer::NavigationPositionTop;
        else if ( tabBarPosition == Profile::TabBarBottom )
            position = ViewContainer::NavigationPositionBottom; 
813

814
815
        if ( container->supportedNavigationPositions().contains(position) )
            container->setNavigationPosition(position);
816
       
Robert Knight's avatar
   
Robert Knight committed
817
        if (showNewCloseButtons)
818
        {
Robert Knight's avatar
   
Robert Knight committed
819
820
            container->setFeatures(container->features() 
                               | ViewContainer::QuickNewView | ViewContainer::QuickCloseView);
821
822
823
            container->setNewViewMenu(createNewViewMenu());
        }
        else
Robert Knight's avatar
   
Robert Knight committed
824
825
            container->setFeatures(container->features() 
                            & ~ViewContainer::QuickNewView & ~ViewContainer::QuickCloseView);
826
    }
827

828
    // load colour scheme
829
830
831
832
    ColorEntry table[TABLE_COLORS];
    
    colorScheme->getColorTable(table , view->randomSeed() );
    view->setColorTable(table);
833
    view->setOpacity(colorScheme->opacity());
834
  
835
    // load font 
836
    view->setAntialias(info->property<bool>(Profile::AntiAliasFonts));
837
    view->setBoldIntense(info->property<bool>(Profile::BoldIntense));
838
    view->setVTFont(info->font());
839
840

    // set scroll-bar position
841
    int scrollBarPosition = info->property<int>(Profile::ScrollBarPosition);
842
843

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

    // terminal features
851
    bool blinkingCursor = info->property<bool>(Profile::BlinkingCursorEnabled);
852
853
    view->setBlinkingCursor(blinkingCursor);  

854
855
856
    bool blinkingText = info->property<bool>(Profile::BlinkingTextEnabled);
    view->setBlinkingTextEnabled(blinkingText);

857
858
859
    bool tripleClickMode = info->property<bool>(Profile::TripleClickMode);
    view->setTripleClickMode(tripleClickMode ? TerminalDisplay::SelectForwardsFromCursor : TerminalDisplay::SelectWholeLine);

860
861
862
    bool bidiEnabled = info->property<bool>(Profile::BidiRenderingEnabled);
    view->setBidiEnabled(bidiEnabled);

863
    // cursor shape
864
    int cursorShape = info->property<int>(Profile::CursorShape);
865
866
867
868
869
870
871
872
873

    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
874
875
    bool useCustomColor = info->property<bool>(Profile::UseCustomCursorColor);
    const QColor& cursorColor = info->property<QColor>(Profile::CustomCursorColor);
876
877
878
879
        
    view->setKeyboardCursorColor(!useCustomColor,cursorColor);

    // word characters
880
    view->setWordCharacters( info->property<QString>(Profile::WordCharacters) );
881
882
}

883
884
void ViewManager::updateViewsForSession(Session* session)
{
885
    QListIterator<TerminalDisplay*> iter(_sessionMap.keys(session));
886
887
    while ( iter.hasNext() )
    {
888
        applyProfile(iter.next(),SessionManager::instance()->sessionProfile(session),false);
889
890
891
    }
}

892
void ViewManager::profileChanged(Profile::Ptr profile)
893
{
894
    QHashIterator<TerminalDisplay*,Session*> iter(_sessionMap);
895
896
897
898
899
900

    while ( iter.hasNext() )
    {
        iter.next();

        // if session uses this profile, update the display
901
        if ( iter.key() != 0 && 
902
903
             iter.value() != 0 && 
             SessionManager::instance()->sessionProfile(iter.value()) == profile ) 
904
        {
905
            applyProfile(iter.key(),profile,true);
906
907
        }
    }
908
909
910
911
912
913
914
915
916
917
918
919
920
}

QList<ViewProperties*> ViewManager::viewProperties() const
{
    QList<ViewProperties*> list;

    ViewContainer* container = _viewSplitter->activeContainer();

    Q_ASSERT( container );

    QListIterator<QWidget*> viewIter(container->views());
    while ( viewIter.hasNext() )
    {
Robert Knight's avatar
Robert Knight committed
921
922
        ViewProperties* properties = container->viewProperties(viewIter.next()); 
        Q_ASSERT( properties );
923
924
        list << properties; 
    } 
925

926
    return list;
927
928
}

Robert Knight's avatar
   
Robert Knight committed
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
void ViewManager::saveSessions(KConfigGroup& group)
{
    // find all unique session restore IDs
    QList<int> ids;
    QHash<Session*,int> unique;

    // first: sessions in the active container, preserving the order
    ViewContainer* container = _viewSplitter->activeContainer();
    Q_ASSERT(container);
    TerminalDisplay* activeview = dynamic_cast<TerminalDisplay*>(container->activeView());

    QListIterator<QWidget*> viewIter(container->views());
    int tab = 1;
    while (viewIter.hasNext())
    {
        TerminalDisplay *view = dynamic_cast<TerminalDisplay*>(viewIter.next());
        Q_ASSERT(view);
        Session *session = _sessionMap[view];
        ids << SessionManager::instance()->getRestoreId(session);
        if (view == activeview) group.writeEntry("Active", tab);
        unique.insert(session, 1);
        tab++;
    }

    // second: all other sessions, in random order
    // we don't want to have sessions restored that are not connected
    foreach(Session* session, _sessionMap)
        if (!unique.contains(session))
        {
            ids << SessionManager::instance()->getRestoreId(session);
            unique.insert(session, 1);
        }

    group.writeEntry("Sessions", ids);
}

void ViewManager::restoreSessions(const KConfigGroup& group)
{
    QList<int> ids = group.readEntry("Sessions", QList<int>());
    int activeTab  = group.readEntry("Active", 0);
    TerminalDisplay *display = 0;

    int tab = 1;
    foreach(int id, ids)
    {
        Session *session = SessionManager::instance()->idToSession(id);
        createView(session);
        if (!session->isRunning())
            session->run();
        if (tab++ == activeTab)
            display = dynamic_cast<TerminalDisplay*>(activeView());
    }

    if (display)
    {
        _viewSplitter->activeContainer()->setActiveView(display);
        display->setFocus(Qt::OtherFocusReason);
    }
987
988
989
990
991
992
993
994
995

    if (ids.isEmpty()) // Session file is unusable, start default Profile
    {
        Profile::Ptr profile = SessionManager::instance()->defaultProfile();
        Session* session = SessionManager::instance()->createSession(profile);
        createView(session);
        if (!session->isRunning())
            session->run();
    }
Robert Knight's avatar
   
Robert Knight committed
996
997
}

998
999
1000
uint qHash(QPoin