TracksView.cpp 15.5 KB
Newer Older
1
/*****************************************************************************
2
 * TracksView.cpp: QGraphicsView that contains the TracksScene
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 *****************************************************************************
 * Copyright (C) 2008-2009 the VLMC team
 *
 * Authors: Ludovic Fauvet <etix@l0cal.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.
 *****************************************************************************/

23
#include <QScrollBar>
24
#include <QMouseEvent>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
25
#include <QWheelEvent>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
26
27
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
28
#include <QGraphicsSceneDragDropEvent>
29
30
#include <QtDebug>
#include <cmath>
31
#include "TracksView.h"
32
#include "Media.h"
33
34
#include "Library.h"
#include "GraphicsMovieItem.h"
35
#include "GraphicsCursorItem.h"
36

37
38
TracksView::TracksView( QGraphicsScene* scene, MainWorkflow* mainWorkflow, QWidget* parent )
        : QGraphicsView( scene, parent ), m_scene( scene ), m_mainWorkflow( mainWorkflow )
39
{
40
    //TODO should be defined by the settings
41
    m_tracksHeight = 25;
42

43
    m_tracksCount = mainWorkflow->getTrackCount();
44
    m_fps = FPS;
45

Ludovic Fauvet's avatar
Ludovic Fauvet committed
46
47
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
48
    m_videoTracksCounter = MAX_TRACKS - 1;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
49
    m_dragItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
50
51
52
    m_actionMove = false;
    m_actionRelativeX = -1;
    m_actionItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
53

54
55
56
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
57
    setFrameStyle( QFrame::NoFrame );
58

59
60
61
62
63
    //// Adjust the height using the number of tracks
    //const int maxHeight = m_tracksHeight * m_tracksCount;
    //setSceneRect( 0, 0, sceneRect().width(), maxHeight );

    const int maxHeight = m_tracksHeight * 4;
64

65
66
    m_cursorLine = new GraphicsCursorItem( maxHeight, QPen( QColor( 220, 30, 30 ) ) );
    m_scene->addItem( m_cursorLine );
67

Ludovic Fauvet's avatar
Ludovic Fauvet committed
68
69
    createLayout();

70
71
    connect( m_cursorLine, SIGNAL( cursorPositionChanged(int) ),
             this, SLOT( ensureCursorVisible() ) );
72
73
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
74
75
76
void TracksView::createLayout()
{
    m_layout = new QGraphicsLinearLayout( Qt::Vertical );
77
    m_layout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
78
79
    m_layout->setContentsMargins( 0, 0, 0, 0 );
    m_layout->setSpacing( 0 );
80
    m_layout->setPreferredWidth( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
81
82

    QGraphicsWidget* container = new QGraphicsWidget();
83
    container->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
84
85
86
    container->setContentsMargins( 0, 0, 0, 0 );
    container->setLayout( m_layout );

87
88
89
90
    // Create the initial layout
    // - 1 video track
    // - a separator
    // - 1 audio track
Ludovic Fauvet's avatar
Ludovic Fauvet committed
91
    addVideoTrack();
92
93

    m_separator = new QGraphicsWidget();
94
    m_separator->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
95
96
97
    m_separator->setPreferredHeight( 20 );
    m_layout->insertItem( 1, m_separator );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
98
99
100
101
102
103
104
    addAudioTrack();

    m_scene->addItem( container );
}

void TracksView::addVideoTrack()
{
105
    GraphicsTrack* track = new GraphicsTrack( GraphicsTrack::Video, m_videoTracksCounter );
106
    track->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
107
108
109
110
    track->setPreferredHeight( m_tracksHeight );
    track->setContentsMargins( 0, 0, 0, 0 );
    m_layout->insertItem( 0, track );
    m_numVideoTrack++;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
111
    m_videoTracksCounter--;
112
    m_scene->invalidate();
113
114
    //FIXME this should maybe go elsewhere
    setSceneRect( m_layout->contentsRect().adjusted( 0, 0, 100, 100 ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
115
116
117
118
}

void TracksView::addAudioTrack()
{
119
    GraphicsTrack* track = new GraphicsTrack( GraphicsTrack::Audio, 0 );
120
    track->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
121
122
123
124
    track->setPreferredHeight( m_tracksHeight );
    track->setContentsMargins( 0, 0, 0, 0 );
    m_layout->insertItem( 1000, track );
    m_numAudioTrack++;
125
    m_scene->invalidate();
126
127
    //FIXME this should maybe go elsewhere
    setSceneRect( m_layout->contentsRect().adjusted( 0, 0, 100, 100 ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
128
129
}

130
131
132
133
void TracksView::dragEnterEvent( QDragEnterEvent* event )
{
    if ( event->mimeData()->hasFormat( "vlmc/uuid" ) )
        event->acceptProposedAction();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
134

Ludovic Fauvet's avatar
Ludovic Fauvet committed
135
    QUuid uuid = QUuid( QString( event->mimeData()->data( "vlmc/uuid" ) ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
136
137
138
139
140
141
    Media* media = Library::getInstance()->getMedia( uuid );
    if ( !media ) return;

    qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );

    if ( m_dragItem ) delete m_dragItem;
142
    m_dragItem = new GraphicsMovieItem( new Clip( media ) );
143
144
    m_dragItem->setWidth( ( int ) ( ( (double)media->getLength() / 1000 ) *
                                    ( (media->getFps() > 0) ? media->getFps() : m_fps) ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
145
    m_dragItem->setHeight( tracksHeight() );
146
147
148
    m_dragItem->setPos( mappedXPos, 0 );
    m_dragItem->setParentItem( m_layout->itemAt( 0 )->graphicsItem() );
    moveMediaItem( m_dragItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
149
150
151
152
153
}

void TracksView::dragMoveEvent( QDragMoveEvent* event )
{
    if ( !m_dragItem ) return;
154
155
    moveMediaItem( m_dragItem, event->pos() );
}
Ludovic Fauvet's avatar
Ludovic Fauvet committed
156

157
158
159
void TracksView::moveMediaItem( AbstractGraphicsMediaItem* item, QPoint position )
{
    int track = (unsigned int)( mapToScene( position ).y() / m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
160
161
162
    if ( track < 0 )
        track = 0;
    else if ( track > m_numVideoTrack - 1)
Ludovic Fauvet's avatar
Ludovic Fauvet committed
163
164
        track = m_numVideoTrack - 1;

165
166
    qreal mappedXPos = ( mapToScene( position ).x() + 0.5 );

167
168
    QPointF oldPos = item->pos();
    QGraphicsItem* oldParent = item->parentItem();
169
    // Check for vertical collisions
170
    item->setParentItem( m_layout->itemAt( track )->graphicsItem() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
171
172
    bool continueSearch = true;
    while ( continueSearch )
173
    {
174
        QList<QGraphicsItem*> colliding = item->collidingItems( Qt::IntersectsItemShape );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
175
176
        bool itemCollision = false;
        for ( int i = 0; i < colliding.size(); ++i )
177
        {
178
179
            AbstractGraphicsMediaItem* currentItem = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
            if ( currentItem )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
180
181
182
            {
                // Collision with an item of the same type
                itemCollision = true;
183
                if ( currentItem->pos().y() < position.y() )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
184
                {
185
                    if ( track < 1 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
186
                    {
187
                        item->setParentItem( oldParent );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
188
189
190
191
                        continueSearch = false;
                        break;
                    }
                    track -= 1;
192
                    Q_ASSERT( m_layout->itemAt( track )->graphicsItem() != NULL );
193
                    item->setParentItem( m_layout->itemAt( track )->graphicsItem() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
194
                }
195
                else if ( currentItem->pos().y() > position.y() )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
196
197
198
                {
                    if ( track >= m_numVideoTrack - 1 )
                    {
199
                        item->setParentItem( oldParent );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
200
201
202
203
                        continueSearch = false;
                        break;
                    }
                    track += 1;
204
                    Q_ASSERT( m_layout->itemAt( track )->graphicsItem() != NULL );
205
                    item->setParentItem( m_layout->itemAt( track )->graphicsItem() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
206
207
                }
            }
208
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
209
210
        if ( !itemCollision )
            continueSearch = false;
211
212
    }
    // Check for horizontal collisions
213
    mappedXPos = qMax( mappedXPos, (qreal)0 );
214
215
    item->setPos( mappedXPos, 0 );
    QList<QGraphicsItem*> colliding = item->collidingItems( Qt::IntersectsItemShape );
216
217
    for ( int i = 0; i < colliding.size(); ++i )
    {
218
219
        AbstractGraphicsMediaItem* currentItem = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
        if ( currentItem )
220
221
222
        {
            // Collision with an item of the same type
            // Restoring original position (horizontal)
223
224
            if ( oldPos.isNull() == false )
                item->setPos( oldPos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
225
            break;
226
227
        }
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
228
229
230
231
}

void TracksView::dragLeaveEvent( QDragLeaveEvent* event )
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
232
    Q_UNUSED( event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
233
234
235
236
    if ( m_dragItem )
    {
        delete m_dragItem;
        m_dragItem = NULL;
237
        updateDuration();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
238
    }
239
240
241
242
}

void TracksView::dropEvent( QDropEvent* event )
{
243
    if ( m_dragItem )
244
    {
245
        updateDuration();
246
247
248
        if ( m_layout->itemAt( 0 )->graphicsItem()->childItems().count() > 0 )
            addVideoTrack();
        event->acceptProposedAction();
249
250

        qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );
251
        m_dragItem->oldTrackNumber = m_dragItem->trackNumber();
252
        m_mainWorkflow->addClip( m_dragItem->clip(),
253
                                 m_dragItem->trackNumber(),
254
                                 (qint64)mappedXPos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
255
        m_dragItem = NULL;
256
    }
257
258
}

259
260
void TracksView::setDuration( int duration )
{
261
    int diff = ( int ) qAbs( ( qreal )duration - sceneRect().width() );
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    if ( diff * matrix().m11() > -50 )
    {
        if ( matrix().m11() < 0.4 )
            setSceneRect( 0, 0, ( duration + 100 / matrix().m11() ), sceneRect().height() );
        else
            setSceneRect( 0, 0, ( duration + 300 ), sceneRect().height() );
    }
    m_projectDuration = duration;
}

void TracksView::resizeEvent( QResizeEvent* event )
{
    QGraphicsView::resizeEvent( event );
}

void TracksView::drawBackground( QPainter* painter, const QRectF& rect )
{
279
280
281
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

282
    painter->setBrush( QBrush( palette().dark().color(), Qt::Dense3Pattern ) );
283
    painter->setPen( Qt::transparent );
284
285
286
    painter->drawRect( ( int ) r.left(), ( int ) m_separator->y(),
                       ( int ) r.right(),
                       ( int ) m_separator->boundingRect().height() );
287

288
    /*QColor base = palette().button().color();
289
290
291
292
293
294
295
296
297
298
299
300
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

    painter->setClipRect( r );
    painter->drawLine( r.left(), 0, r.right(), 0 );

    uint tracks = m_tracksCount;
    for ( uint i = 0; i < tracks; ++i )
        painter->drawLine( r.left(), m_tracksHeight * ( i + 1 ), r.right(), m_tracksHeight * ( i + 1 ) );

    int lowerLimit = m_tracksHeight * m_tracksCount + 1;
    if ( height() > lowerLimit )
301
302
        painter->fillRect( QRectF ( r.left(), lowerLimit, r.width(),
        height() - lowerLimit ), QBrush( base ) );*/
303
}
304
305
306

void TracksView::mouseMoveEvent( QMouseEvent* event )
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
307
308
309
310
311
312
313
314
315
    if ( event->modifiers() == Qt::NoModifier &&
         event->buttons() == Qt::LeftButton &&
         m_actionMove == true )
    {
        if ( m_actionRelativeX < 0 )
            m_actionRelativeX = event->pos().x() - mapFromScene( m_actionItem->pos() ).x();
        moveMediaItem( m_actionItem, QPoint( event->pos().x() - m_actionRelativeX, event->pos().y() ) );
    }

316
317
318
319
320
    QGraphicsView::mouseMoveEvent( event );
}

void TracksView::mousePressEvent( QMouseEvent* event )
{
321
    QList<AbstractGraphicsMediaItem*> mediaCollisionList = mediaItems( event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
322

323
    if ( event->modifiers() == Qt::ControlModifier && mediaCollisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
324
325
326
327
328
329
    {
        setDragMode( QGraphicsView::ScrollHandDrag );
        QGraphicsView::mousePressEvent( event );
        return;
    }

Ludovic Fauvet's avatar
Ludovic Fauvet committed
330
331
332
333
    if ( event->modifiers() == Qt::NoModifier &&
         event->button() == Qt::LeftButton &&
         mediaCollisionList.count() == 1 )
    {
334
335
336
337
338
339
        AbstractGraphicsMediaItem* item = mediaCollisionList.at( 0 );
        if ( item->moveable() )
        {
            m_actionMove = true;
            m_actionItem = mediaCollisionList.at( 0 );
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
340
341
342
        return;
    }

343
    /*if ( event->modifiers() & Qt::ShiftModifier && collisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
344
345
346
347
348
349
    {
        setDragMode( QGraphicsView::RubberBandDrag );
        if ( !event->modifiers() & Qt::ControlModifier )
            scene()->clearSelection();
        QGraphicsView::mousePressEvent( event );
        return;
350
    }*/
Ludovic Fauvet's avatar
Ludovic Fauvet committed
351

352
    QGraphicsView::mousePressEvent( event );
353
354
}

355
void TracksView::mouseReleaseEvent( QMouseEvent* event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
356
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
357
358
    if ( m_actionMove )
    {
359
360
361
362
363
364
365
        GraphicsMovieItem* movieItem = qgraphicsitem_cast<GraphicsMovieItem*>( m_actionItem );
        if ( movieItem )
        {
            updateDuration();
            if ( m_layout->itemAt( 0 )->graphicsItem()->childItems().count() > 0 )
                addVideoTrack();
            emit clipMoved( movieItem->clip()->getUuid(),
366
                            movieItem->oldTrackNumber,
367
                            movieItem->trackNumber(),
368
                            (qint64)movieItem->pos().x() );
369
            movieItem->oldTrackNumber = movieItem->trackNumber();
370
371
372
373
            m_actionMove = false;
            m_actionRelativeX = -1;
            m_actionItem = NULL;
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
374
375
    }

376
377
    setDragMode( QGraphicsView::NoDrag );
    QGraphicsView::mouseReleaseEvent( event );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
378
379
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
void TracksView::wheelEvent( QWheelEvent* event )
{
    if ( event->modifiers() == Qt::ControlModifier )
    {
        // CTRL + WHEEL = Zoom
        if ( event->delta() > 0 )
            emit zoomIn();
        else
            emit zoomOut();
        event->accept();
    }
    else
    {
        //TODO should scroll the timeline
        event->ignore();
        QGraphicsView::wheelEvent( event );
    }
}

399
400
401
402
403
404
405
406
407
408
409
410
411
412
QList<AbstractGraphicsMediaItem*> TracksView::mediaItems( const QPoint& pos )
{
    QList<QGraphicsItem*> collisionList = items( pos );
    QList<AbstractGraphicsMediaItem*> mediaCollisionList;
    for ( int i = 0; i < collisionList.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( collisionList.at( i ) );
        if ( item )
            mediaCollisionList.append( item );
    }
    return mediaCollisionList;
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
413
void TracksView::setCursorPos( int pos )
414
{
415
416
    if ( pos < 0 ) pos = 0;
    m_cursorLine->setCursorPos( pos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
417
418
419
420
}

int TracksView::cursorPos()
{
421
    return m_cursorLine->cursorPos();
422
}
423
424
425
426
427
428
429
430

void TracksView::setScale( double scaleFactor )
{
    QMatrix matrix;
    matrix.scale( scaleFactor, 1 );
    //TODO update the scene scale ?
    setMatrix( matrix );

431
    int diff = ( int ) ( sceneRect().width() - ( qreal ) m_projectDuration );
432
433
434
435
436
437
438
    if ( diff * matrix.m11() < 50 )
    {
        if ( matrix.m11() < 0.4 )
            setSceneRect( 0, 0, ( m_projectDuration + 100 / matrix.m11() ), sceneRect().height() );
        else
            setSceneRect( 0, 0, ( m_projectDuration + 300 ), sceneRect().height() );
    }
439
    centerOn( m_cursorLine );
440
}
441
442
443
444

void TracksView::ensureCursorVisible()
{
    if ( horizontalScrollBar()->isVisible() )
445
446
447
448
449
450
    {
        QRectF r( m_cursorLine->boundingRect().width() / 2,
                  m_cursorLine->boundingRect().height() / 2,
                  1, 1 );
        m_cursorLine->ensureVisible( r, 150, 50 );
    }
451
}
452
453
454

void TracksView::updateDuration()
{
455
    //TODO this should use a variant of mediaItems( const QPoint& )
456
457
458
459
460
461
462
463
464
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    int projectDuration = 0;
    for ( int i = 0; i < sceneItems.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( sceneItems.at( i ) );
        if ( !item ) continue;
        if ( item->pos().x() + item->boundingRect().width() > projectDuration )
465
            projectDuration = ( int ) ( item->pos().x() + item->boundingRect().width() );
466
467
468
469
470
471
472
473
474
475
    }

    m_projectDuration = projectDuration;

    // PreferredWidth not working ?
    m_layout->setMinimumWidth( m_projectDuration );
    m_layout->setMaximumWidth( m_projectDuration );

    emit durationChanged( m_projectDuration );
}