TracksView.cpp 15.8 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
#include "Commands.hpp"
37

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

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

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

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

60
61
62
63
64
    //// 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;
65

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

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

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

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

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

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

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

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

    m_scene->addItem( container );
}

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

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

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

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

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

    if ( m_dragItem ) delete m_dragItem;
143
    m_dragItem = new GraphicsMovieItem( new Clip( media ) );
144
145
    m_dragItem->setWidth( ( int ) ( ( (double)media->getLength() / 1000 ) *
                                    ( (media->getFps() > 0) ? media->getFps() : m_fps) ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
146
    m_dragItem->setHeight( tracksHeight() );
147
148
149
    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
150
151
152
153
154
}

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

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

Ludovic Fauvet's avatar
Ludovic Fauvet committed
166
    qreal mappedXPos = ( mapToScene( position ).x() + 0.5 );
167

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

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

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

        qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );
252
        m_dragItem->oldTrackNumber = m_dragItem->trackNumber();
253
254
255
256
        Commands::trigger( new Commands::MainWorkflow::AddClip( m_mainWorkflow,
                                                                m_dragItem->clip(),
                                                                m_dragItem->trackNumber(),
                                                                (qint64)mappedXPos ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
257
        m_dragItem = NULL;
258
    }
259
260
}

261
262
void TracksView::setDuration( int duration )
{
263
    int diff = ( int ) qAbs( ( qreal )duration - sceneRect().width() );
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    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 )
{
281
282
283
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

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

290
    /*QColor base = palette().button().color();
291
292
293
294
295
296
297
298
299
300
301
302
    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 )
303
304
        painter->fillRect( QRectF ( r.left(), lowerLimit, r.width(),
        height() - lowerLimit ), QBrush( base ) );*/
305
}
306
307
308

void TracksView::mouseMoveEvent( QMouseEvent* event )
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
309
310
311
312
313
314
315
316
317
    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() ) );
    }

318
319
320
321
322
    QGraphicsView::mouseMoveEvent( event );
}

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

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

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

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

354
    QGraphicsView::mousePressEvent( event );
355
356
}

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

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

Ludovic Fauvet's avatar
Ludovic Fauvet committed
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
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 );
    }
}

400
401
402
403
404
405
406
407
408
409
410
411
412
413
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
414
void TracksView::setCursorPos( int pos )
415
{
416
417
    if ( pos < 0 ) pos = 0;
    m_cursorLine->setCursorPos( pos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
418
419
420
421
}

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

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

432
    int diff = ( int ) ( sceneRect().width() - ( qreal ) m_projectDuration );
433
434
435
436
437
438
439
    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() );
    }
440
    centerOn( m_cursorLine );
441
}
442
443
444
445

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

void TracksView::updateDuration()
{
456
    //TODO this should use a variant of mediaItems( const QPoint& )
457
458
459
460
461
462
463
464
465
    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 )
466
            projectDuration = ( int ) ( item->pos().x() + item->boundingRect().width() );
467
468
469
470
471
472
473
474
475
476
    }

    m_projectDuration = projectDuration;

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

    emit durationChanged( m_projectDuration );
}