ViewManager.cpp 17.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
    Copyright (C) 2006-2007 by Robert Knight <robertknight@gmail.com>

    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
// System
#include <assert.h>

23
24
25
// Qt
#include <QSignalMapper>

26
27
28
29
30
31
32
// KDE
#include <kdebug.h>
#include <KLocale>
#include <KToggleAction>
#include <KXMLGUIFactory>

// Konsole
33
#include "BookmarkHandler.h"
34
#include "ColorScheme.h"
35
#include "Session.h"
36
#include "TerminalDisplay.h"
37
38
39
40
41
42
#include "SessionController.h"
#include "SessionManager.h"
#include "ViewContainer.h"
#include "ViewSplitter.h"
#include "ViewManager.h"

43
44
using namespace Konsole;

45
46
ViewManager::ViewManager(QObject* parent , KActionCollection* collection)
    : QObject(parent)
47
    , _viewSplitter(0)
48
49
    , _actionCollection(collection)
    , _containerSignalMapper(new QSignalMapper(this))
50
{
51
52
53
54
55
56
57
58
59
60
    // create main view area
    _viewSplitter = new ViewSplitter(0);   
    // 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);

61
62
63
64
65
66
    // 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()) );
67
68
69
70

    // listen for addition or removal of views from associated containers
    connect( _containerSignalMapper , SIGNAL(mapped(QObject*)) , this , 
            SLOT(containerViewsChanged(QObject*)) ); 
71
72
73
74
75
}

ViewManager::~ViewManager()
{
}
76
77
78
79
80
81
82
83
84
85
86
87
QWidget* ViewManager::activeView() const
{
    ViewContainer* container = _viewSplitter->activeContainer();
    if ( container )
    {
        return container->activeView();
    }
    else
    {
        return 0;
    }
}
88

89
90
91
92
93
QWidget* ViewManager::widget() const
{
    return _viewSplitter;
}

94
95
void ViewManager::setupActions()
{
96
    KActionCollection* collection = _actionCollection;
97

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
    KAction* nextViewAction = new KAction( i18n("Next View") , this );
    KAction* previousViewAction = new KAction( i18n("Previous View") , this );
    QAction* nextContainerAction = new QAction( i18n("Next View Container") , this);
    
    if ( collection )
    {
        KAction* splitLeftRightAction = new KAction( KIcon("view-left-right"),
                                                      i18n("Split View Left/Right"),
                                                      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()) );

        KAction* splitTopBottomAction = new KAction( KIcon("view-top-bottom") , 
                                             i18n("Split View Top/Bottom"),this);
        splitTopBottomAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_T) );
        collection->addAction("split-view-top-bottom",splitTopBottomAction);
        connect( splitTopBottomAction , SIGNAL(triggered()) , this , SLOT(splitTopBottom()));

        KAction* closeActiveAction = new KAction( i18n("Close Active") , this );
        closeActiveAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_S) );
        collection->addAction("close-active-view",closeActiveAction);
        connect( closeActiveAction , SIGNAL(triggered()) , this , SLOT(closeActiveView()) );

        KAction* closeOtherAction = new KAction( i18n("Close Others") , this );
        closeOtherAction->setShortcut( QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_O) );
        collection->addAction("close-other-views",closeOtherAction);
        connect( closeOtherAction , SIGNAL(triggered()) , this , SLOT(closeOtherViews()) );

        QAction* detachViewAction = collection->addAction("detach-view");
        detachViewAction->setIcon( KIcon("tab-breakoff") );
        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) );

        connect( detachViewAction , SIGNAL(triggered()) , this , SLOT(detachActiveView()) );
    
        collection->addAction("next-view",nextViewAction);
        collection->addAction("previous-view",previousViewAction);
        collection->addAction("next-container",nextContainerAction);
Robert Knight's avatar
   
Robert Knight committed
139

140
    }
Robert Knight's avatar
   
Robert Knight committed
141

142
143
144
145
    KShortcut nextViewShortcut = nextViewAction->shortcut();
    nextViewShortcut.setAlternate( QKeySequence(Qt::SHIFT+Qt::Key_Right) );
    nextViewShortcut.setPrimary( QKeySequence(Qt::CTRL+Qt::Key_PageUp) );
    nextViewAction->setShortcut(nextViewShortcut); 
Robert Knight's avatar
   
Robert Knight committed
146
    connect( nextViewAction, SIGNAL(triggered()) , this , SLOT(nextView()) );
147
    _viewSplitter->addAction(nextViewAction);
Robert Knight's avatar
   
Robert Knight committed
148

149
150
151
152
    KShortcut previousViewShortcut = previousViewAction->shortcut();
    previousViewShortcut.setPrimary( QKeySequence(Qt::SHIFT+Qt::Key_Left) );
    previousViewShortcut.setAlternate( QKeySequence(Qt::CTRL+Qt::Key_PageDown) );
    previousViewAction->setShortcut(previousViewShortcut);
Robert Knight's avatar
   
Robert Knight committed
153
    connect( previousViewAction, SIGNAL(triggered()) , this , SLOT(previousView()) );
154
    _viewSplitter->addAction(previousViewAction);
Robert Knight's avatar
   
Robert Knight committed
155
156
157

    nextContainerAction->setShortcut( QKeySequence(Qt::SHIFT+Qt::Key_Tab) );
    connect( nextContainerAction , SIGNAL(triggered()) , this , SLOT(nextContainer()) );
158
    _viewSplitter->addAction(nextContainerAction);
Robert Knight's avatar
   
Robert Knight committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
}

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();
182
183
184
185
186
187
}

void ViewManager::detachActiveView()
{
    // find the currently active view and remove it from its container 
    ViewContainer* container = _viewSplitter->activeContainer();
188
    TerminalDisplay* activeView = dynamic_cast<TerminalDisplay*>(container->activeView());
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

    if (!activeView)
        return;

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

    // remove the view from this window
    container->removeView(activeView);
    delete activeView;


    // 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 )
    {
        delete container;
209
210
211

        // this will need to be removed if Konsole is modified so the menu item to
        // split the view is no longer one toggle-able item
212
        //_splitViewAction->setChecked(false);
213
214
215
216
    }

}

217
void ViewManager::sessionFinished( Session* session )
218
{
219
    QList<TerminalDisplay*> children = _viewSplitter->findChildren<TerminalDisplay*>();
220

221
    foreach ( TerminalDisplay* view , children )
222
223
224
225
226
227
    {
        if ( _sessionMap[view] == session )
        {
            _sessionMap.remove(view);
            delete view;
        }
Robert Knight's avatar
   
Robert Knight committed
228
229
230
231
232
233
234
    }

    focusActiveView(); 
}

void ViewManager::focusActiveView()
{
Robert Knight's avatar
Robert Knight committed
235
236
237
238
239
240
    // 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
241
242
243
244
245
246
247
248
249
    ViewContainer* container = _viewSplitter->activeContainer(); 
    if ( container )
    {
        QWidget* activeView = container->activeView();
        if ( activeView )
        {
            activeView->setFocus(Qt::MouseFocusReason);
        }
    }
250
251
}

252

253
254
void ViewManager::viewActivated( QWidget* view )
{
255
256
257
258
259
260
    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);
261
262
}

263
void ViewManager::splitLeftRight()
264
{
265
    splitView(Qt::Horizontal);
266
}
267
void ViewManager::splitTopBottom()
268
{
269
    splitView(Qt::Vertical);
270
271
}

272
void ViewManager::splitView(Qt::Orientation orientation)
273
{
274
    if ( true )
275
276
277
278
279
280
281
282
283
    {
        // 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());
        
        ViewContainer* container = createContainer(); 

        while (existingViewIter.hasNext())
        {
284
            Session* session = _sessionMap[(TerminalDisplay*)existingViewIter.next()];
285
            TerminalDisplay* display = createTerminalDisplay();
286
            loadViewSettings(display,session); 
Robert Knight's avatar
   
Robert Knight committed
287
            ViewProperties* properties = createController(session,display);
288
289
290

            _sessionMap[display] = session;

Robert Knight's avatar
   
Robert Knight committed
291
            container->addView(display,properties);
292
293
294
            session->addView( display );
        }

295
        _viewSplitter->addContainer(container,orientation);
296
297
298
299
300
301
302
303
304
305
306
307
    }
    else
    {
        // delete the active container when unsplitting the view unless it is the last
        // one
        if ( _viewSplitter->containers().count() > 1 )
        {
            ViewContainer* container = _viewSplitter->activeContainer();
        
            delete container;
        }
    }
308
309
310

    // ensure that the active view is focused after the split / unsplit
    _viewSplitter->activeContainer()->activeView()->setFocus(Qt::OtherFocusReason);
311
312
}

313
314
315
316
void ViewManager::closeActiveView()
{
    ViewContainer* container = _viewSplitter->activeContainer();
    delete container;
317
318
319
320

    // focus next container so that user can continue typing 
    // without having to manually focus it themselves
    nextContainer();
321
322
323
324
325
326
327
328
329
330
331
332
333
334
}
void ViewManager::closeOtherViews()
{
    ViewContainer* active = _viewSplitter->activeContainer();

    QListIterator<ViewContainer*> iter(_viewSplitter->containers());
    while ( iter.hasNext() )
    {
        ViewContainer* next = iter.next();
        if ( next != active )
            delete next;
    }
}

335
SessionController* ViewManager::createController(Session* session , TerminalDisplay* view)
336
{
Robert Knight's avatar
Robert Knight committed
337
338
    // create a new controller for the session, and ensure that this view manager
    // is notified when the view gains the focus
339
    SessionController* controller = new SessionController(session,view,this);
340
    connect( controller , SIGNAL(focused(SessionController*)) , this , SIGNAL(activeViewChanged(SessionController*)) );
341
342
343
    connect( session , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
    connect( view , SIGNAL(destroyed()) , controller , SLOT(deleteLater()) );
    
344
345
346
    return controller;
}

347
void ViewManager::createView(Session* session)
348
{
349
350
351
352
353
354
    // create the default container
    if (_viewSplitter->containers().count() == 0)
    {
        _viewSplitter->addContainer( createContainer() , Qt::Vertical );
    }

Robert Knight's avatar
Robert Knight committed
355
356
    // notify this view manager when the session finishes so that its view
    // can be deleted
357
    connect( session , SIGNAL(done(Session*)) , this , SLOT(sessionFinished(Session*)) );
Robert Knight's avatar
Robert Knight committed
358
359
360
361
   
    // 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 
362
363
364
365
366
367
    ViewContainer* const activeContainer = _viewSplitter->activeContainer();
    QListIterator<ViewContainer*> containerIter(_viewSplitter->containers());

    while ( containerIter.hasNext() )
    {
        ViewContainer* container = containerIter.next();
368
        TerminalDisplay* display = createTerminalDisplay();
369
        loadViewSettings(display,session);
Robert Knight's avatar
   
Robert Knight committed
370
        ViewProperties* properties = createController(session,display);
371
372

        _sessionMap[display] = session; 
Robert Knight's avatar
   
Robert Knight committed
373
        container->addView(display,properties);
374
375
        session->addView(display);

376
377
        if ( container == activeContainer ) 
        {
378
            container->setActiveView(display);
379
380
            display->setFocus( Qt::OtherFocusReason );
        }
381
382
383
384
385
    }
}

ViewContainer* ViewManager::createContainer()
{
386
    ViewContainer* container = new TabbedViewContainerV2(_viewSplitter);
387

388
    // connect signals and slots
389
390
391
392
393
394
    connect( container , SIGNAL(viewAdded(QWidget*,ViewProperties*)) , _containerSignalMapper ,
           SLOT(map()) );
    connect( container , SIGNAL(viewRemoved(QWidget*)) , _containerSignalMapper ,
           SLOT(map()) ); 
    _containerSignalMapper->setMapping(container,container);

395
    connect( container , SIGNAL(viewRemoved(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
396
    connect( container , SIGNAL(closeRequest(QWidget*)) , this , SLOT(viewCloseRequest(QWidget*)) );
397
    connect( container , SIGNAL(activeViewChanged(QWidget*)) , this , SLOT(viewActivated(QWidget*)));
398
    
399
    return container;
400
401
}

402
403
404
405
406
407
408
409
410
411
void ViewManager::containerViewsChanged(QObject* container)
{
    qDebug() << "Container views changed";

    if ( container == _viewSplitter->activeContainer() )
    {
        emit viewPropertiesChanged( viewProperties() );
    } 
}

412
413
414
415
void ViewManager::viewCloseRequest(QWidget* view)
{
    // 1. detach view from session
    // 2. if the session has no views left, close it
416
    
417
    TerminalDisplay* display = (TerminalDisplay*)view;
418
    Session* session = _sessionMap[ display ];
419
420
421
422
423
424
425
426
427
428
429
    if ( session )
    {
        delete display;
        
        if ( session->views().count() == 0 )
            session->closeSession();
    }
    else
    {
        kDebug() << __FILE__ << __LINE__ << ": received close request from unknown view." << endl;
    }
430
431
432

    qDebug() << "Closing view";
    focusActiveView();
433
434
}

435
436
void ViewManager::merge(ViewManager* otherManager)
{
Robert Knight's avatar
Robert Knight committed
437
438
439
440
441
442
    // iterate over the views in otherManager's active container and take them from that
    // manager and put them in the active container in this manager
    //
    // TODO - This currently does not consider views in containers other than
    //        the active one in the other manager
    //
443
444
445
446
447
448
449
450
451
    ViewSplitter* otherSplitter = otherManager->_viewSplitter;
    ViewContainer* otherContainer = otherSplitter->activeContainer();

    QListIterator<QWidget*> otherViewIter(otherContainer->views());

    ViewContainer* activeContainer = _viewSplitter->activeContainer();

    while ( otherViewIter.hasNext() )
    {
452
        TerminalDisplay* view = dynamic_cast<TerminalDisplay*>(otherViewIter.next());
453
454
455
        
        assert(view);

456
        takeView(otherManager,otherContainer,activeContainer,view);
457
458
459
    } 
}

460
461

void ViewManager::takeView(ViewManager* otherManager , ViewContainer* otherContainer, 
462
                           ViewContainer* newContainer, TerminalDisplay* view)
463
{
Robert Knight's avatar
Robert Knight committed
464
465
466
    // FIXME - the controller associated with the display which is being moved
    //         may have signals which are connected to otherManager.  they need
    //         to be redirected to slots in this view manager
467
468
469
470
471
472
473
474
475
476
    ViewProperties* properties = otherContainer->viewProperties(view);
    otherContainer->removeView(view);

    newContainer->addView(view,properties);

    // transfer the session map entries
    _sessionMap.insert(view,otherManager->_sessionMap[view]);
    otherManager->_sessionMap.remove(view);
}

477
TerminalDisplay* ViewManager::createTerminalDisplay()
478
{
479
   TerminalDisplay* display = new TerminalDisplay(0);
480
481
482
483
484
485

   //TODO Temporary settings used here
   display->setBellMode(0);
   display->setTerminalSizeHint(false);
   display->setCutToBeginningOfLine(true);
   display->setTerminalSizeStartup(false);
486
   display->setScrollBarLocation(TerminalDisplay::SCROLLBAR_RIGHT);
487
488
489
490

   return display;
}

491
void ViewManager::loadViewSettings(TerminalDisplay* view , Session* session)
492
{
493
494
495
496
497
498
499
500
    SessionInfo* info = SessionManager::instance()->sessionType(session->type());
    Q_ASSERT( info );

    const ColorScheme* colorScheme = ColorSchemeManager::instance()->
                                            findColorScheme(info->colorScheme());
    
    Q_ASSERT( colorScheme );

501
    // load colour scheme
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
    view->setColorTable(colorScheme->colorTable());
    
    // load font, fall back to system monospace font if not specified
    view->setVTFont(info->defaultFont());
   
    // set initial size
    // temporary default used for now
    view->setSize(80,40);
}

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

    ViewContainer* container = _viewSplitter->activeContainer();

    Q_ASSERT( container );

    QListIterator<QWidget*> viewIter(container->views());
    while ( viewIter.hasNext() )
    {
        ViewProperties* properties = container->viewProperties(viewIter.next());        Q_ASSERT( properties );
        list << properties; 
    } 
526

527
    return list;
528
529
}

530
#include "ViewManager.moc"