ViewManager.cpp 39.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>
Jekyll Wu's avatar
Jekyll Wu committed
29
#include <QtCore/QStringList>
30
#include <QtGui/QMenu>
31

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

// Konsole
42
#include "ColorScheme.h"
43
#include "ProfileList.h"
44
#include "Session.h"
45
#include "TerminalDisplay.h"
46 47 48 49
#include "SessionController.h"
#include "SessionManager.h"
#include "ViewContainer.h"
#include "ViewSplitter.h"
50 51
#include "konsoleadaptor.h"
#include "Profile.h"
52

53 54
using namespace Konsole;

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

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

76 77 78 79 80 81
    // 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()) );
82 83 84 85

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

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

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

97 98 99 100
}

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

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

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

138 139
    KAction* nextViewAction = new KAction( i18n("Next Tab") , this );
    KAction* previousViewAction = new KAction( i18n("Previous Tab") , this );
140
    KAction* lastViewAction = new KAction( i18n("Switch to Last Tab") , this);
141
    KAction* nextContainerAction = new KAction( i18n("Next View Container") , this);
142
  
143 144
    KAction* moveViewLeftAction = new KAction( i18n("Move Tab Left") , this );
    KAction* moveViewRightAction = new KAction( i18n("Move Tab Right") , this );
145

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

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

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

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

        multiViewOnlyActions << closeActiveAction;
174

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

        multiViewOnlyActions << closeOtherAction;
182

183
        KAction* detachViewAction = collection->addAction("detach-view");
184 185
        detachViewAction->setIcon(KIcon("tab-detach"));
        detachViewAction->setText(i18n("D&etach Current Tab"));
186 187
        // 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
188 189 190
        detachViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H));

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

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

199 200
        multiViewOnlyActions << expandActiveAction;

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

206 207
        multiViewOnlyActions << shrinkActiveAction;

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

        // 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);
        }
228
    }
Robert Knight's avatar
 
Robert Knight committed
229

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

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

243 244 245
    KShortcut previousViewShortcut = previousViewAction->shortcut();
    previousViewShortcut.setPrimary( QKeySequence(Qt::SHIFT+Qt::Key_Left) );
    previousViewAction->setShortcut(previousViewShortcut);
Robert Knight's avatar
 
Robert Knight committed
246
    connect( previousViewAction, SIGNAL(triggered()) , this , SLOT(previousView()) );
247
    _viewSplitter->addAction(previousViewAction);
Robert Knight's avatar
 
Robert Knight committed
248 249 250

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

    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);
259 260 261

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

278

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

282
    QAction* detachAction = _actionCollection->action("detach-view");
283

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

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

    Q_ASSERT( container );

    container->activateLastView();
}

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

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

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

    if (!viewToDetach)
345 346
        return;

347
    emit viewDetached(_sessionMap[viewToDetach]);
348
    
349
    _sessionMap.remove(viewToDetach);
350 351

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

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

}

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

373 374
    Session* session = qobject_cast<Session*>(sender());

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

378 379
    Q_ASSERT(session);

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

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

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

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

417

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

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

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

    while (existingViewIter.hasNext())
446
    {
447
        Session* session = _sessionMap[(TerminalDisplay*)existingViewIter.next()];
448
        TerminalDisplay* display = createTerminalDisplay(session);
449
        const Profile::Ptr info = SessionManager::instance()->sessionProfile(session);
450
        applyProfileToView(display, info);
451
        ViewProperties* properties = createController(session,display);
452

453
        _sessionMap[display] = session;
454

455 456 457
        // create a container using settings from the first 
        // session in the previous container
        if ( !container )
458
        {
459
            container = createContainer(info);
460 461
            applyProfileToContainer(container, info);
        }
462

463 464 465
        container->addView(display,properties);
        session->addView( display );
    }
466

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

470 471
    // focus the new container
    container->containerWidget()->setFocus();
472 473

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

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

490
    _viewSplitter->removeContainer(container);
491
    container->deleteLater();
492 493

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

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

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

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

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

547 548 549
    return controller;
}

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

555
    _viewSplitter->setFocusProxy(controller->view());
556

557 558
    _pluggedController = controller;
    emit activeViewChanged(controller);
559 560 561 562
}

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

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

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

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

     bool isFirst = _sessionMap.isEmpty();
     if ( isFirst)
         applyProfileToContainer(container, profile);
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
     
     // set initial size
     display->setSize(80,40);

     ViewProperties* properties = createController(session,display);

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

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

     if ( container == _viewSplitter->activeContainer() ) 
     {
         container->setActiveView(display);
         display->setFocus( Qt::OtherFocusReason );
     }
605 606
    
     updateDetachViewState();
607
}
608

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

619 620 621 622
    int index = -1;

    // In all containers the new view will be put at the same position, i.e.,
    // the position right after the active view in the active container.
623 624
    QWidget* view = activeView();
    if (view)
625
    {
626 627
        QList<QWidget*> views =  _viewSplitter->activeContainer()->views();
        index = views.indexOf(view) + 1;
628 629
    }

Robert Knight's avatar
Robert Knight committed
630 631 632
    // 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 
633 634 635 636
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());
    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
637
        createView(session, container, index);
638 639 640
    }
}

641
ViewContainer* ViewManager::createContainer(const Profile::Ptr info)
642
{
643 644
    Q_ASSERT( info );

645
    const int tabPosition = info->property<int>(Profile::TabBarPosition);
646 647 648 649 650

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

651 652 653 654 655
    ViewContainer* container = 0;

    switch ( _navigationMethod )
    {
        case TabbedNavigation:    
656 657 658 659 660
            {
                container =
                    new TabbedViewContainer(position,_viewSplitter);

                connect(container,
Laurent Montel's avatar
Laurent Montel committed
661
                    SIGNAL(detachTab(ViewContainer*,QWidget*)),
662
                    this,
Laurent Montel's avatar
Laurent Montel committed
663
                    SLOT(detachView(ViewContainer*,QWidget*))
664
                    );
665
                connect(container,
Laurent Montel's avatar
Laurent Montel committed
666
                    SIGNAL(closeTab(ViewContainer*,QWidget*)),
667
                    this,
Laurent Montel's avatar
Laurent Montel committed
668
                    SLOT(closeTabFromContainer(ViewContainer*,QWidget*)));
669

670
            }
671 672 673 674 675
            break;
        case NoNavigation:
        default:
            container = new StackedViewContainer(_viewSplitter);
    }
676

677
    applyProfileToContainer(container, info);
678

679
    // connect signals and slots
680 681 682 683 684 685
    connect( container , SIGNAL(viewAdded(QWidget*,ViewProperties*)) , _containerSignalMapper ,
           SLOT(map()) );
    connect( container , SIGNAL(viewRemoved(QWidget*)) , _containerSignalMapper ,
           SLOT(map()) ); 
    _containerSignalMapper->setMapping(container,container);

686
    connect( container, SIGNAL(newViewRequest()), this, SIGNAL(newViewRequest()) );
687
    connect( container, SIGNAL(moveViewRequest(int,int,bool&)), 
688
    this , SLOT(containerMoveViewRequest(int,int,bool&)) );
689
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
690
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
691
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
692
    
693
    return container;
694
}
695 696
void ViewManager::containerMoveViewRequest(int index, int id, bool& moved)
{
697 698
    ViewContainer* container = qobject_cast<ViewContainer*>(sender());
    SessionController* controller = qobject_cast<SessionController*>(ViewProperties::propertiesById(id));
699

700 701
    if (!controller)
        return;
702

703
    createView(controller->session(),container,index);
704
    controller->session()->refresh();
705
    moved = true;
706
}
707 708 709 710 711 712 713 714
void ViewManager::setNavigationMethod(NavigationMethod method)
{
    _navigationMethod = method;

    KActionCollection* collection = _actionCollection;

    if ( collection )
    {
715 716 717 718 719 720 721 722 723
        // 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.

724 725 726 727 728 729 730
        QAction* action;

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

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

732 733 734
        action = collection->action( "last-tab" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );

735 736 737 738 739
        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 );
740 741 742

        action = collection->action( "rename-session" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
743 744 745 746 747 748

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

        action = collection->action( "move-view-right" );
        if ( action ) action->setEnabled( _navigationMethod != NoNavigation );
749 750 751
    }
}

752 753
ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; }

754 755
void ViewManager::containerViewsChanged(QObject* container)
{
756
    if (_viewSplitter && container == _viewSplitter->activeContainer() )
757 758 759 760 761
    {
        emit viewPropertiesChanged( viewProperties() );
    } 
}

762 763
void ViewManager::viewCloseRequest(QWidget* view)
{
764 765 766 767 768
    //FIXME Check that this cast is actually legal
    TerminalDisplay* display = (TerminalDisplay*)view;
  
    Q_ASSERT(display);

769 770
    // 1. detach view from session
    // 2. if the session has no views left, close it
771
    Session* session = _sessionMap[ display ];
772
    _sessionMap.remove(display);
773 774
    if ( session )
    {
775
        display->deleteLater();
776

777
        if ( session->views().count() == 0 )
778
            session->close();
779
    }
780 781 782 783 784
    //we only update the focus if the splitter is still alive
    if (_viewSplitter) {
        focusActiveView();
        updateDetachViewState();
    }
785 786 787 788
    // The below causes the menus  to be messed up
    // Only happenss when using the tab bar close button
//    if (_pluggedController)
//        emit unplugController(_pluggedController);
789 790
}

791
TerminalDisplay* ViewManager::createTerminalDisplay(Session* session)
792
{
793
   TerminalDisplay* display = new TerminalDisplay(0);
794 795

   //TODO Temporary settings used here
796 797
   display->setBellMode(TerminalDisplay::NotifyBell);
   display->setTerminalSizeHint(true);
798
   display->setTripleClickMode(TerminalDisplay::SelectWholeLine);
799
   display->setTerminalSizeStartup(true);
800
   display->setScrollBarPosition(TerminalDisplay::ScrollBarRight);
801 802
   display->setRandomSeed(session->sessionId() * 31);

803 804 805
   return display;
}

806
const ColorScheme* ViewManager::colorSchemeForProfile(const Profile::Ptr info) const
807
{
808 809
    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
                                            findColorScheme(info->colorScheme());
810 811
    if ( !colorScheme )
       colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); 
812 813
    Q_ASSERT( colorScheme );

814 815 816
    return colorScheme;
}

817
void ViewManager::applyProfileToView(TerminalDisplay* view , const Profile::Ptr info)
818 819 820
{
    Q_ASSERT( info );
    
821
    const ColorScheme* colorScheme = colorSchemeForProfile(info);
822

823
    // menu bar visibility
824
    emit setMenuBarVisibleRequest( info->property<bool>(Profile::ShowMenuBar) );
825

826 827
    emit setSaveGeometryOnExitRequest( info->property<bool>(Profile::SaveGeometryOnExit) );

828 829
    emit updateWindowIcon();

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

    // set scroll-bar position
844
    int scrollBarPosition = info->property<int>(Profile::ScrollBarPosition);
845 846

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

853 854 855
    // set visibility of the size widget
    view->setSizeWidgetVisibility(info->property<bool>(Profile::ShowSizeWidget));

856
    // terminal features
857
    bool blinkingCursor = info->property<bool>(Profile::BlinkingCursorEnabled);
858 859
    view->setBlinkingCursor(blinkingCursor);  

860 861 862
    bool blinkingText = info->property<bool>(Profile::BlinkingTextEnabled);
    view->setBlinkingTextEnabled(blinkingText);

863 864
    bool tripleClickMode = info->property<bool>(Profile::TripleClickMode);
    view->setTripleClickMode(tripleClickMode ? TerminalDisplay::SelectForwardsFromCursor : TerminalDisplay::SelectWholeLine);
865 866
    
    view->setUnderlineLinks(info->property<bool>(Profile::UnderlineLinksEnabled));
867

868 869 870
    bool bidiEnabled = info->property<bool>(Profile::BidiRenderingEnabled);
    view->setBidiEnabled(bidiEnabled);

871
    // cursor shape
872
    int cursorShape = info->property<int>(Profile::CursorShape);
873 874 875 876 877 878 879 880 881

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

    // word characters
888
    view->setWordCharacters( info->property<QString>(Profile::WordCharacters) );
889 890
}

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

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

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

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

    if (showNewCloseButtons)
    {
        container->s