ViewManager.cpp 36.3 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
154
155
156
157
                                                      this );
        splitLeftRightAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_L) );
        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
161
162
163
        splitTopBottomAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_T) );
        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
172
      
        multiViewOnlyActions << closeActiveAction; 

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");
Pino Toscano's avatar
Pino Toscano committed
182
        detachViewAction->setIcon( KIcon("tab-detach") );
183
184
185
186
        detachViewAction->setText( i18n("&Detach View") );
        // 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
        detachViewAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_H) );
187
       
188
          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
}
void ViewManager::detachActiveView()
{
    // find the currently active view and remove it from its container 
    ViewContainer* container = _viewSplitter->activeContainer();
321
    TerminalDisplay* activeView = dynamic_cast<TerminalDisplay*>(container->activeView());
322
323
324
325
326
327
328
329
330
331

    if (!activeView)
        return;

    emit viewDetached(_sessionMap[activeView]);
    
    _sessionMap.remove(activeView);

    // remove the view from this window
    container->removeView(activeView);
332
    activeView->deleteLater();
333
334
335
336
337
338
339

    // 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 )
    {
340
        removeContainer(container);
341
342
343
344
    }

}

345
void ViewManager::sessionFinished()
346
{
347
348
349
350
    // if this slot is called after the view manager's main widget
    // has been destroyed, do nothing
    if (!_viewSplitter)
        return;
351

352
353
    Session* session = qobject_cast<Session*>(sender());

354
355
    if ( _sessionMap[qobject_cast<TerminalDisplay*>(activeView())] == session )
    {
356
        // switch to the next view before deleting the session views to prevent flicker 
357
        // occurring as a result of an interval between removing the active view and switching
358
359
        // to the next view
        nextView();
360
361
    }

362
363
    Q_ASSERT(session);

364
    // close attached views
365
    QList<TerminalDisplay*> children = _viewSplitter->findChildren<TerminalDisplay*>();
366

367
    foreach ( TerminalDisplay* view , children )
368
369
370
371
    {
        if ( _sessionMap[view] == session )
        {
            _sessionMap.remove(view);
372
            view->deleteLater();
373
        }
Robert Knight's avatar
   
Robert Knight committed
374
    }
375

Robert Knight's avatar
   
Robert Knight committed
376
377
378
379
}

void ViewManager::focusActiveView()
{
Robert Knight's avatar
Robert Knight committed
380
381
382
383
384
385
    // 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
386
387
388
389
390
391
392
393
394
    ViewContainer* container = _viewSplitter->activeContainer(); 
    if ( container )
    {
        QWidget* activeView = container->activeView();
        if ( activeView )
        {
            activeView->setFocus(Qt::MouseFocusReason);
        }
    }
395
396
}

397

398
399
void ViewManager::viewActivated( QWidget* view )
{
400
401
402
403
404
405
    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);
406
407
}

408
void ViewManager::splitLeftRight()
409
{
410
    splitView(Qt::Horizontal);
411
}
412
void ViewManager::splitTopBottom()
413
{
414
    splitView(Qt::Vertical);
415
416
}

417
void ViewManager::splitView(Qt::Orientation orientation)
418
{
419
420
421
422
    // 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());
    
423
    ViewContainer* container = 0; 
424
425

    while (existingViewIter.hasNext())
426
    {
427
        Session* session = _sessionMap[(TerminalDisplay*)existingViewIter.next()];
428
        TerminalDisplay* display = createTerminalDisplay(session);
429
        applyProfile(display,SessionManager::instance()->sessionProfile(session),false);
430
        ViewProperties* properties = createController(session,display);
431

432
        _sessionMap[display] = session;
433

434
435
436
        // create a container using settings from the first 
        // session in the previous container
        if ( !container )
437
            container = createContainer(SessionManager::instance()->sessionProfile(session));
438

439
440
441
        container->addView(display,properties);
        session->addView( display );
    }
442

443
444
    _viewSplitter->addContainer(container,orientation);
    emit splitViewToggle(_viewSplitter->containers().count() > 0);
445

446
447
    // focus the new container
    container->containerWidget()->setFocus();
448
449

    // ensure that the active view is focused after the split / unsplit
450
451
452
453
454
    ViewContainer* activeContainer = _viewSplitter->activeContainer();
    QWidget* activeView = activeContainer ? activeContainer->activeView() : 0;

    if ( activeView )
        activeView->setFocus(Qt::OtherFocusReason);
455
}
456
void ViewManager::removeContainer(ViewContainer* container)
457
{
458
459
460
461
462
463
464
465
    // 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);
    } 

466
    _viewSplitter->removeContainer(container);
467
    container->deleteLater();
468
469

    emit splitViewToggle( _viewSplitter->containers().count() > 1 );
470
}
471
472
473
474
475
476
477
478
void ViewManager::expandActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),10);
}
void ViewManager::shrinkActiveView()
{
    _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(),-10);
}
479
480
481
482
483
484
485
486
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);
487

488
489
490
491
        // focus next container so that user can continue typing 
        // without having to manually focus it themselves
        nextContainer();
    }
492
493
494
495
496
497
498
499
500
501
}
void ViewManager::closeOtherViews()
{
    ViewContainer* active = _viewSplitter->activeContainer();

    QListIterator<ViewContainer*> iter(_viewSplitter->containers());
    while ( iter.hasNext() )
    {
        ViewContainer* next = iter.next();
        if ( next != active )
502
            removeContainer(next);
503
504
505
    }
}

506
SessionController* ViewManager::createController(Session* session , TerminalDisplay* view)
507
{
Robert Knight's avatar
Robert Knight committed
508
509
    // create a new controller for the session, and ensure that this view manager
    // is notified when the view gains the focus
510
    SessionController* controller = new SessionController(session,view,this);
511
    connect( controller , SIGNAL(focused(SessionController*)) , this , SLOT(controllerChanged(SessionController*)) );
512
513
    connect( session , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
    connect( view , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
514

515
516
517
    // if this is the first controller created then set it as the active controller
    if (!_pluggedController)
        controllerChanged(controller);
518

519
520
521
    return controller;
}

522
523
void ViewManager::controllerChanged(SessionController* controller)
{
524
525
    if ( controller == _pluggedController )
        return;
526

527
    _viewSplitter->setFocusProxy(controller->view());
528

529
530
    _pluggedController = controller;
    emit activeViewChanged(controller);
531
532
533
534
}

SessionController* ViewManager::activeViewController() const
{
535
    return _pluggedController;
536
}
537
538
539
540
541
542

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

543
544
void ViewManager::createView(Session* session, ViewContainer* container, int index)
{
545
    // notify this view manager when the session finishes so that its view
546
    // can be deleted
547
548
549
    //
    // TODO - Find a more efficient a way to avoid multiple connections
    disconnect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );
550
551
    connect( session , SIGNAL(finished()) , this , SLOT(sessionFinished()) );

552
     bool isFirst = _sessionMap.isEmpty();
553
     TerminalDisplay* display = createTerminalDisplay(session);
554
     applyProfile(display,SessionManager::instance()->sessionProfile(session),isFirst);
555
556
557
558
559
560
561
562
563
564
565
     
     // 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
566
     const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session);
567
568
569
570
571
572
573
     session->setDarkBackground( colorSchemeForProfile(profile)->hasDarkBackground() );

     if ( container == _viewSplitter->activeContainer() ) 
     {
         container->setActiveView(display);
         display->setFocus( Qt::OtherFocusReason );
     }
574
575
    
     updateDetachViewState();
576
}
577

578
void ViewManager::createView(Session* session)
579
{
580
581
582
    // create the default container
    if (_viewSplitter->containers().count() == 0)
    {
583
        _viewSplitter->addContainer( createContainer(SessionManager::instance()->sessionProfile(session)) , 
584
                                     Qt::Vertical );
585
        emit splitViewToggle(false);
586
587
    }

588
       
Robert Knight's avatar
Robert Knight committed
589
590
591
    // 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 
592
593
594
595
596
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());

    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
597
        createView(session,container,-1);
598
    }
599

600
601
}

602
ViewContainer* ViewManager::createContainer(const Profile::Ptr info)
603
{
604
605
    Q_ASSERT( info );

606
    const int tabPosition = info->property<int>(Profile::TabBarPosition);
607
608
609
610
611

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

612
613
614
615
616
    ViewContainer* container = 0;

    switch ( _navigationMethod )
    {
        case TabbedNavigation:    
617
            container = new TabbedViewContainer(position,_viewSplitter);
618
619
620
621
622
            break;
        case NoNavigation:
        default:
            container = new StackedViewContainer(_viewSplitter);
    }
623

624
    // connect signals and slots
625
626
627
628
629
630
    connect( container , SIGNAL(viewAdded(QWidget*,ViewProperties*)) , _containerSignalMapper ,
           SLOT(map()) );
    connect( container , SIGNAL(viewRemoved(QWidget*)) , _containerSignalMapper ,
           SLOT(map()) ); 
    _containerSignalMapper->setMapping(container,container);

631
    connect( container, SIGNAL(newViewRequest()), this, SIGNAL(newViewRequest()) );
632
    connect( container, SIGNAL(moveViewRequest(int,int,bool&)), 
633
    this , SLOT(containerMoveViewRequest(int,int,bool&)) );
634
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
635
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
636
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
637
    
638
    return container;
639
}
640
641
void ViewManager::containerMoveViewRequest(int index, int id, bool& moved)
{
642
643
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
644

645
646
    if (!controller)
        return;
647

648
649
    createView(controller->session(),container,index);
    moved = true;
650
}
651
652
653
654
655
656
657
658
void ViewManager::setNavigationMethod(NavigationMethod method)
{
    _navigationMethod = method;

    KActionCollection* collection = _actionCollection;

    if ( collection )
    {
659
660
661
662
663
664
665
666
667
        // 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.

668
669
670
671
672
673
674
        QAction* action;

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

        action = collection->action( "previous-view" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
675
676
677
678
679
680

        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 );
681
682
683

        action = collection->action( "rename-session" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
684
685
686
687
688
689

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

        action = collection->action( "move-view-right" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
690
691
692
    }
}

693
694
ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; }

695
696
void ViewManager::containerViewsChanged(QObject* container)
{
697
    if (_viewSplitter && container == _viewSplitter->activeContainer() )
698
699
700
701
702
    {
        emit viewPropertiesChanged( viewProperties() );
    } 
}

703
704
void ViewManager::viewCloseRequest(QWidget* view)
{
705
706
707
708
709
    //FIXME Check that this cast is actually legal
    TerminalDisplay* display = (TerminalDisplay*)view;
  
    Q_ASSERT(display);

710
711
    // 1. detach view from session
    // 2. if the session has no views left, close it
712
    Session* session = _sessionMap[ display ];
713
    _sessionMap.remove(display);
714
715
    if ( session )
    {
716
        display->deleteLater();
717

718
        if ( session->views().count() == 0 )
719
            session->close();
720
    }
721
722
723
724
725
    //we only update the focus if the splitter is still alive
    if (_viewSplitter) {
        focusActiveView();
        updateDetachViewState();
    }
726
727
}

728
TerminalDisplay* ViewManager::createTerminalDisplay(Session* session)
729
{
730
   TerminalDisplay* display = new TerminalDisplay(0);
731
732

   //TODO Temporary settings used here
733
734
   display->setBellMode(TerminalDisplay::NotifyBell);
   display->setTerminalSizeHint(true);
735
   display->setTripleClickMode(TerminalDisplay::SelectWholeLine);
736
   display->setTerminalSizeStartup(true);
737
   display->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
738
739
   display->setRandomSeed(session->sessionId() * 31);

740
741
742
   return display;
}

743
const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr info) const
744
{
745
746
    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
                                            findColorScheme(info->colorScheme());
747
748
    if ( !colorScheme )
       colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); 
749
750
    Q_ASSERT( colorScheme );

751
752
753
    return colorScheme;
}

754
755
void ViewManager::applyProfile(TerminalDisplay* view , const Profile::Ptr info, 
                               bool applyContainerSettings) 
756
757
758
{
    Q_ASSERT( info );
    
759
    const ColorScheme* colorScheme = colorSchemeForProfile(info);
760

761
    // menu bar visibility
762
    emit setMenuBarVisibleRequest( info->property<bool>(Profile::ShowMenuBar) );
763
764

    // tab bar visibility
765
766
767
768
769
    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
770
        bool showNewCloseButtons = info->property<bool>(Profile::ShowNewAndCloseTabButtons);
771

772
773
774
775
776
777
        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);
778

779
        ViewContainer::NavigationPosition position = container->navigationPosition();
780

781
782
783
784
        if ( tabBarPosition == Profile::TabBarTop )
            position = ViewContainer::NavigationPositionTop;
        else if ( tabBarPosition == Profile::TabBarBottom )
            position = ViewContainer::NavigationPositionBottom; 
785

786
787
        if ( container->supportedNavigationPositions().contains(position) )
            container->setNavigationPosition(position);
788
       
Robert Knight's avatar
   
Robert Knight committed
789
        if (showNewCloseButtons)
790
        {
Robert Knight's avatar
   
Robert Knight committed
791
792
            container->setFeatures(container->features() 
                               | ViewContainer::QuickNewView | ViewContainer::QuickCloseView);
793
794
795
            container->setNewViewMenu(createNewViewMenu());
        }
        else
Robert Knight's avatar
   
Robert Knight committed
796
797
            container->setFeatures(container->features() 
                            & ~ViewContainer::QuickNewView & ~ViewContainer::QuickCloseView);
798
    }
799

800
    // load colour scheme
801
802
803
804
    ColorEntry table[TABLE_COLORS];
    
    colorScheme->getColorTable(table , view->randomSeed() );
    view->setColorTable(table);
805
    view->setOpacity(colorScheme->opacity());
806
  
807
    // load font 
808
    view->setAntialias(info->property<bool>(Profile::AntiAliasFonts));
809
    view->setVTFont(info->font());
810
811

    // set scroll-bar position
812
    int scrollBarPosition = info->property<int>(Profile::ScrollBarPosition);
813
814

    if ( scrollBarPosition == Profile::ScrollBarHidden )
815
       view->setScrollBarPosition(TerminalDisplay::NoScrollBar);
816
    else if ( scrollBarPosition == Profile::ScrollBarLeft )
817
       view->setScrollBarPosition(TerminalDisplay::ScrollBarLeft);
818
    else if ( scrollBarPosition == Profile::ScrollBarRight )
819
       view->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
820
821

    // terminal features
822
    bool blinkingCursor = info->property<bool>(Profile::BlinkingCursorEnabled);
823
824
    view->setBlinkingCursor(blinkingCursor);  

825
826
827
    bool blinkingText = info->property<bool>(Profile::BlinkingTextEnabled);
    view->setBlinkingTextEnabled(blinkingText);

828
829
830
    bool tripleClickMode = info->property<bool>(Profile::TripleClickMode);
    view->setTripleClickMode(tripleClickMode ? TerminalDisplay::SelectForwardsFromCursor : TerminalDisplay::SelectWholeLine);

831
832
833
    bool bidiEnabled = info->property<bool>(Profile::BidiRenderingEnabled);
    view->setBidiEnabled(bidiEnabled);

834
    // cursor shape
835
    int cursorShape = info->property<int>(Profile::CursorShape);
836
837
838
839
840
841
842
843
844

    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
845
846
    bool useCustomColor = info->property<bool>(Profile::UseCustomCursorColor);
    const QColor& cursorColor = info->property<QColor>(Profile::CustomCursorColor);
847
848
849
850
        
    view->setKeyboardCursorColor(!useCustomColor,cursorColor);

    // word characters
851
    view->setWordCharacters( info->property<QString>(Profile::WordCharacters) );
852
853
}

854
855
void ViewManager::updateViewsForSession(Session* session)
{
856
    QListIterator<TerminalDisplay*> iter(_sessionMap.keys(session));
857
858
    while ( iter.hasNext() )
    {
859
        applyProfile(iter.next(),SessionManager::instance()->sessionProfile(session),false);
860
861
862
    }
}

863
void ViewManager::profileChanged(Profile::Ptr profile)
864
{
865
    QHashIterator<TerminalDisplay*,Session*> iter(_sessionMap);
866
867
868
869
870
871

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

        // if session uses this profile, update the display
872
        if ( iter.key() != 0 && 
873
874
             iter.value() != 0 && 
             SessionManager::instance()->sessionProfile(iter.value()) == profile ) 
875
        {
876
            applyProfile(iter.key(),profile,true);
877
878
        }
    }
879
880
881
882
883
884
885
886
887
888
889
890
891
}

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
892
893
        ViewProperties* properties = container->viewProperties(viewIter.next()); 
        Q_ASSERT( properties );
894
895
        list << properties; 
    } 
896

897
    return list;
898
899
}

Robert Knight's avatar
   
Robert Knight committed
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
927
928
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
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);
    }
958
959
960
961
962
963
964
965
966

    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
967
968
}

969
970
971
972
973
uint qHash(QPointer<TerminalDisplay> display)
{
    return qHash((TerminalDisplay*)display);
}

974
975
976
977
978
int ViewManager::sessionCount()
{
    return this->_sessionMap.size();
}

979
980
981
982
983
984
985
986
987
988
989
int ViewManager::currentSession()
{
    QHash<TerminalDisplay*,Session*>::iterator i;
    for (i = this->_sessionMap.begin(); i != this->_sessionMap.end(); ++i)
        if (i.key()->isVisible())
            return i.value()->sessionId();
    return -1;
}

int ViewManager::newSession()
{
990
    Profile::Ptr profile = SessionManager::instance()->defaultProfile();
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
    Session* session = SessionManager::instance()->createSession(profile);

    this->createView(session);
    session->run();

    return session->sessionId();
}

int ViewManager::newSession(QString profile, QString directory)
{
    QList<Profile::Ptr> profilelist = SessionManager::instance()->loadedProfiles();
    QList<Profile::Ptr>::iterator i = profilelist.begin();

    Profile::Ptr profileptr = SessionManager::instance()->defaultProfile();

    while (i != profilelist.end() )
    {
        Profile::Ptr ptr = *i;
        if ( ptr->name().compare(profile) == 0)
            profileptr = ptr;
        i++;
    }

    Session* session = SessionManager::instance()->createSession(profileptr);
    session->setInitialWorkingDirectory(directory);

    this->createView(session);
    session->run();

    return session->sessionId();
}

QStringList ViewManager::profileList()
{
    QList<Profile::Ptr> profilelist = SessionManager::instance()->loadedProfiles();
    QList<Profile::Ptr>::iterator i = profilelist.begin();
    QStringList list;
    while (i != profilelist.end() )
    {
        Profile::Ptr ptr = *i;
        list.push_back(ptr->name());
        i++;
    }

    return list;
}

void ViewManager::nextSession()
{
    this->nextView();
}

void ViewManager::prevSession()
{
    this->previousView();
}

void ViewManager::moveSessionLeft()
{
    this->moveActiveViewLeft();
}