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