TracksView.cpp 37.6 KB
Newer Older
1
/*****************************************************************************
2
 * TracksView.cpp: QGraphicsView that contains the TracksScene
3
 *****************************************************************************
Ludovic Fauvet's avatar
Ludovic Fauvet committed
4
 * Copyright (C) 2008-2010 VideoLAN
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * 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 "TracksView.h"
24

25
26
#include "Library.h"
#include "GraphicsMovieItem.h"
27
#include "GraphicsAudioItem.h"
28
#include "GraphicsCursorItem.h"
29
#include "Commands.h"
30
#include "GraphicsTrack.h"
Ludovic Fauvet's avatar
Ludovic Fauvet committed
31
#include "WorkflowRenderer.h"
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <QScrollBar>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsRectItem>
#include <QtDebug>

TracksView::TracksView( QGraphicsScene *scene, MainWorkflow *mainWorkflow,
                        WorkflowRenderer *renderer, QWidget *parent )
    : QGraphicsView( scene, parent ),
    m_scene( scene ),
    m_mainWorkflow( mainWorkflow ),
    m_renderer( renderer )
47
{
48
    //TODO should be defined by the settings
49
    m_tracksHeight = 25;
50

51
    m_tracksCount = mainWorkflow->getTrackCount( MainWorkflow::VideoTrack );
52

Ludovic Fauvet's avatar
Ludovic Fauvet committed
53
54
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
55
56
    m_dragVideoItem = NULL;
    m_dragAudioItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
57
    m_actionMove = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
58
    m_actionResize = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
59
60
    m_actionRelativeX = -1;
    m_actionItem = NULL;
61
    m_tool = TOOL_DEFAULT;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
62

63
64
65
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
66
    setFrameStyle( QFrame::NoFrame );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
67
    setAlignment( Qt::AlignLeft | Qt::AlignTop );
68
    setCacheMode( QGraphicsView::CacheBackground );
69

Ludovic Fauvet's avatar
Ludovic Fauvet committed
70
    m_cursorLine = new GraphicsCursorItem( QPen( QColor( 220, 30, 30 ) ) );
71

72
    m_scene->addItem( m_cursorLine );
73

74
    connect( m_cursorLine, SIGNAL( cursorPositionChanged(qint64) ),
75
             this, SLOT( ensureCursorVisible() ) );
76
77
    connect( Library::getInstance(), SIGNAL( mediaRemoved( QUuid ) ),
             this, SLOT( deleteMedia( QUuid ) ) );
78
79
}

80
81
void
TracksView::createLayout()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
82
83
{
    m_layout = new QGraphicsLinearLayout( Qt::Vertical );
84
    m_layout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
85
86
    m_layout->setContentsMargins( 0, 0, 0, 0 );
    m_layout->setSpacing( 0 );
87
    m_layout->setPreferredWidth( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
88

89
    QGraphicsWidget *container = new QGraphicsWidget();
90
    container->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
91
92
93
    container->setContentsMargins( 0, 0, 0, 0 );
    container->setLayout( m_layout );

94
95
96
97
    // Create the initial layout
    // - 1 video track
    // - a separator
    // - 1 audio track
Ludovic Fauvet's avatar
Ludovic Fauvet committed
98
    addVideoTrack();
99
100

    m_separator = new QGraphicsWidget();
101
    m_separator->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
102
103
104
    m_separator->setPreferredHeight( 20 );
    m_layout->insertItem( 1, m_separator );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
105
106
107
    addAudioTrack();

    m_scene->addItem( container );
108
109

    setSceneRect( m_layout->contentsRect() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
110
111
}

112
113
void
TracksView::addVideoTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
114
{
115
    GraphicsTrack *track = new GraphicsTrack( MainWorkflow::VideoTrack, m_numVideoTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
116
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
117
    m_layout->insertItem( 0, track );
118
    m_layout->activate();
119
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
120
121
    m_scene->invalidate(); // Redraw the background
    m_numVideoTrack++;
122
    emit videoTrackAdded( track );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
123
124
}

125
126
void
TracksView::addAudioTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
127
{
128
    GraphicsTrack *track = new GraphicsTrack( MainWorkflow::AudioTrack, m_numAudioTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
129
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
130
    m_layout->insertItem( 1000, track );
131
    m_layout->activate();
132
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
133
134
    m_scene->invalidate(); // Redraw the background
    m_numAudioTrack++;
135
    emit audioTrackAdded( track );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
136
137
}

138
139
void
TracksView::removeVideoTrack()
140
141
142
{
    Q_ASSERT( m_numVideoTrack > 0 );

143
    QGraphicsLayoutItem *item = m_layout->itemAt( 0 );
144
145
146
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
147
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
148
149
150
151
152
    m_numVideoTrack--;
    emit videoTrackRemoved();
    delete item;
}

153
154
void
TracksView::removeAudioTrack()
155
156
157
{
    Q_ASSERT( m_numAudioTrack > 0 );

158
    QGraphicsLayoutItem *item = m_layout->itemAt( m_layout->count() - 1 );
159
160
161
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
162
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
163
164
165
166
167
    m_numAudioTrack--;
    emit audioTrackRemoved();
    delete item;
}

168
169
void
TracksView::clear()
170
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
171
    m_layout->removeItem( m_separator );
172

Ludovic Fauvet's avatar
Ludovic Fauvet committed
173
174
    while ( m_layout->count() > 0 )
        delete m_layout->itemAt( 0 );
175

Ludovic Fauvet's avatar
Ludovic Fauvet committed
176
    m_layout->addItem( m_separator );
177
178
179
180
181
182
183
184
185
186

    m_numAudioTrack = 0;
    m_numVideoTrack = 0;

    addVideoTrack();
    addAudioTrack();

    updateDuration();
}

187
188
void
TracksView::deleteMedia( const QUuid &uuid  )
189
{
190
    AbstractGraphicsMediaItem *item;
191
192
193
194
195
196
197
198

    // Get the list of all items in the timeline
    QList<AbstractGraphicsMediaItem*> items = mediaItems();

    // Iterate over each item to check if their parent's uuid
    // is the one we would like to remove.
    foreach( item, items )
    {
199
        if ( item->clip()->getParent()->uuid() ==
200
201
202
203
204
205
206
207
208
             uuid )
        {
            // This item needs to be removed.
            // Saving its values
            QUuid itemUuid = item->uuid();
            quint32 itemTn = item->trackNumber();
            MainWorkflow::TrackType itemTt = item->mediaType();

            // Remove the item from the timeline
209
            removeMediaItem( itemUuid, itemTn, itemTt );
210
211

            // Removing the item from the backend.
212
            MainWorkflow::getInstance()->removeClip( itemUuid,
213
214
215
216
217
218
                                    itemTn,
                                    itemTt );
        }
    }
}

219
220
void
TracksView::addMediaItem( Clip *clip, unsigned int track, MainWorkflow::TrackType trackType, qint64 start )
221
222
223
{
    Q_ASSERT( clip );

224
225
    // If there is not enough tracks to insert
    // the clip do it now.
226
    if ( trackType == MainWorkflow::VideoTrack )
227
    {
228
        if ( track >= m_numVideoTrack )
229
        {
230
            unsigned int nbTrackToAdd = track - m_numVideoTrack + 1;
231
232
233
234
235
            for ( unsigned int i = 0; i < nbTrackToAdd; ++i )
                addVideoTrack();
        }
        // Add the empty upper track
        if ( track + 1 == m_numVideoTrack )
236
237
            addVideoTrack();
    }
238
239
    else if ( trackType == MainWorkflow::AudioTrack )
    {
240
        if ( track >= m_numAudioTrack )
241
        {
242
            unsigned int nbTrackToAdd = track - m_numAudioTrack + 1;
243
244
245
246
247
248
249
            for ( unsigned int i = 0; i < nbTrackToAdd; ++i )
                addAudioTrack();
        }
        // Add the empty upper track
        if ( track + 1 == m_numAudioTrack )
            addAudioTrack();
    }
250
    // Is the clip already existing in the timeline ?
251
252
    QList<QGraphicsItem*> trackItems = getTrack( trackType, track )->childItems();;
    for ( int i = 0; i < trackItems.size(); ++i )
253
    {
254
        AbstractGraphicsMediaItem *item =
255
                dynamic_cast<AbstractGraphicsMediaItem*>( trackItems.at( i ) );
256
        if ( !item || item->uuid() != clip->uuid() ) continue;
257
258
259
260
        // Item already exist: goodbye!
        return;
    }

261
    AbstractGraphicsMediaItem *item = 0;
262
    if ( trackType == MainWorkflow::VideoTrack )
263
    {
Clement CHAVANCE's avatar
Clement CHAVANCE committed
264
        item = new GraphicsMovieItem( clip );
265
266
267
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
268
    else if ( trackType == MainWorkflow::AudioTrack )
269
    {
Clement CHAVANCE's avatar
Clement CHAVANCE committed
270
        item = new GraphicsAudioItem( clip );
271
272
273
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
274

Clement CHAVANCE's avatar
Clement CHAVANCE committed
275
276
277
278
279
280
281
    item->m_tracksView = this;
    item->setHeight( tracksHeight() );
    item->setParentItem( getTrack( trackType, track ) );
    item->setStartPos( start );
    item->oldTrackNumber = track;
    item->oldPosition = start;
    moveMediaItem( item, track, start );
282
    updateDuration();
283
284
}

285
286
void
TracksView::dragEnterEvent( QDragEnterEvent *event )
287
288
289
{
    if ( event->mimeData()->hasFormat( "vlmc/uuid" ) )
        event->acceptProposedAction();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
290

Ludovic Fauvet's avatar
Ludovic Fauvet committed
291
    QUuid uuid = QUuid( QString( event->mimeData()->data( "vlmc/uuid" ) ) );
292
    Clip *clip = Library::getInstance()->clip( uuid );
293
    if ( !clip ) return;
294
295
296
    if ( clip->getParent()->hasAudioTrack() == false &&
         clip->getParent()->hasVideoTrack() == false )
        return ;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
297

298
299
    Clip *audioClip = NULL;
    Clip *videoClip = NULL;
300
    //FIXME: Creating a new clip leaks, but at least we have independant clips.
301

302
303
304
305
306
307
308
309
310
    if ( clip->getParent()->hasAudioTrack() == true )
    {
        audioClip = new Clip( clip );

        if ( m_dragAudioItem ) delete m_dragAudioItem;
        m_dragAudioItem = new GraphicsAudioItem( audioClip );
        m_dragAudioItem->m_tracksView = this;
        m_dragAudioItem->setHeight( tracksHeight() );
        m_dragAudioItem->setParentItem( getTrack( m_dragAudioItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
311
312
        connect( m_dragAudioItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
313
314
315
316
317
318
319
320
321
322
    }
    if ( clip->getParent()->hasVideoTrack() == true )
    {
        videoClip = new Clip( clip );

        if ( m_dragVideoItem ) delete m_dragVideoItem;
        m_dragVideoItem = new GraphicsMovieItem( videoClip );
        m_dragVideoItem->m_tracksView = this;
        m_dragVideoItem->setHeight( tracksHeight() );
        m_dragVideoItem->setParentItem( getTrack( m_dragVideoItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
323
324
        connect( m_dragVideoItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
325
    }
326
327

    // Group the items together
328
329
330
331
332
333
    if ( audioClip != NULL && videoClip != NULL )
        m_dragVideoItem->group( m_dragAudioItem );
    if ( videoClip == NULL )
        moveMediaItem( m_dragAudioItem, event->pos() );
    else
        moveMediaItem( m_dragVideoItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
334
335
}

336
337
void
TracksView::dragMoveEvent( QDragMoveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
338
{
339
340
341
342
343
344
345
346
347
    AbstractGraphicsMediaItem* target;

    if ( m_dragVideoItem != NULL )
        target = m_dragVideoItem;
    else if ( m_dragAudioItem != NULL)
        target = m_dragAudioItem;
    else
        return ;
    moveMediaItem( target, event->pos() );
348
}
Ludovic Fauvet's avatar
Ludovic Fauvet committed
349

350
351
bool
TracksView::setItemOldTrack( const QUuid &uuid, quint32 oldTrackNumber )
352
353
354
355
356
357
358
359
360
361
362
363
364
365
{
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    for ( int i = 0; i < sceneItems.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( sceneItems.at( i ) );
        if ( !item || item->uuid() != uuid ) continue;
        item->oldTrackNumber = oldTrackNumber;
        return true;
    }
    return false;
}

366
367
void
TracksView::moveMediaItem( const QUuid &uuid, unsigned int track, qint64 time )
368
369
370
371
372
373
374
375
376
377
378
379
{
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    for ( int i = 0; i < sceneItems.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( sceneItems.at( i ) );
        if ( !item || item->uuid() != uuid ) continue;
        moveMediaItem( item, track, time );
    }
}

380
381
void
TracksView::moveMediaItem( AbstractGraphicsMediaItem *item, QPoint position )
382
{
383
384
    static GraphicsTrack *lastKnownTrack = NULL;
    GraphicsTrack *track = NULL;
385

386
    QList<QGraphicsItem*> list = items( 0, position.y() );
387
388
389
390
391
392
    for ( int i = 0; i < list.size(); ++i )
    {
        track = qgraphicsitem_cast<GraphicsTrack*>( list.at(i) );
        if (track) break;
    }

393
394
395
396
397
398
399
400
401
402
    if ( !track )
    {
        // When the mouse pointer is not on a track,
        // use the last known track.
        // This avoids "breaks" when moving a rush
        if ( !lastKnownTrack ) return;
        track = lastKnownTrack;
    }

    lastKnownTrack = track;
403

404
    qreal time = ( mapToScene( position ).x() + 0.5 );
405
    moveMediaItem( item, track->trackNumber(), (qint64)time);
406
407
}

408
409
void
TracksView::moveMediaItem( AbstractGraphicsMediaItem *item, quint32 track, qint64 time )
410
{
411
412
413
414
    if ( item->mediaType() == MainWorkflow::VideoTrack )
        track = qMin( track, m_numVideoTrack - 1 );
    else if ( item->mediaType() == MainWorkflow::AudioTrack )
        track = qMin( track, m_numAudioTrack - 1 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
415

416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
    ItemPosition p = findPosition( item, track, time );

    if ( item->groupItem() )
    {
        bool validPosFound = false;

        // Add missing tracks for the target
        if ( item->groupItem()->mediaType() == MainWorkflow::AudioTrack )
        {
            while ( item->trackNumber() >= m_numAudioTrack )
                addAudioTrack();
        }
        else if ( item->groupItem()->mediaType() == MainWorkflow::VideoTrack )
        {
            while ( item->trackNumber() >= m_numVideoTrack )
                addVideoTrack();
        }

        // Search a position for the linked item
        ItemPosition p2 = findPosition( item->groupItem(), track, time );

        if ( p.time() == p2.time() &&  p.track() == p2.track() )
            validPosFound = true;
        else
        {
            // We did not find a valid position for the two items.
            if ( p.time() == time && p.track() == track )
            {
                // The primary item has found a position that match the request.
                // Ask it to try with the position of the linked item.
                p = findPosition( item, p2.track(), p2.time() );

                if ( p.time() == p2.time() && p.track() == p2.track() )
                    validPosFound = true;
            }
            else if ( p2.time() == time && p2.track() == track )
            {
                // The linked item has found a position that match the request.
                // Ask it to try with the position of the primary item.
                p2 = findPosition( item->groupItem(), p.track(), p.time() );

                if ( p.time() == p2.time() && p.track() == p2.track() )
                    validPosFound = true;
            }
        }

        if ( validPosFound )
        {
            // We've found a valid position that fit for the two items.
            // Move the primary item to the target destination.
            item->setStartPos( p.time() );
            item->setParentItem( getTrack( item->mediaType(), p.track() ) );

            // Move the linked item to the target destination.
            item->groupItem()->setStartPos( p2.time() );
            item->groupItem()->setParentItem( getTrack( item->groupItem()->mediaType(), p2.track() ) );
        }
    }
    else
    {
        if ( p.isValid() )
        {
            item->setStartPos( p.time() );
            item->setParentItem( getTrack( item->mediaType(), track ) );
        }
    }
}

484
485
ItemPosition
TracksView::findPosition( AbstractGraphicsMediaItem *item, quint32 track, qint64 time )
486
487
488
{

    // Create a fake item for computing collisions
489
    QGraphicsRectItem *chkItem = new QGraphicsRectItem( item->boundingRect() );
490
491
492
    chkItem->setParentItem( getTrack( item->mediaType(), track ) );
    chkItem->setPos( time, 0 );

493
    QGraphicsItem *oldParent = item->parentItem();
494
495
    qreal oldPos = item->startPos();

496
    // Check for vertical collisions
Ludovic Fauvet's avatar
Ludovic Fauvet committed
497
498
    bool continueSearch = true;
    while ( continueSearch )
499
    {
500
        QList<QGraphicsItem*> colliding = chkItem->collidingItems( Qt::IntersectsItemShape );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
501
502
        bool itemCollision = false;
        for ( int i = 0; i < colliding.size(); ++i )
503
        {
504
            AbstractGraphicsMediaItem *currentItem = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
505
            if ( currentItem && currentItem != item )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
506
507
508
            {
                // Collision with an item of the same type
                itemCollision = true;
509
                if ( currentItem->trackNumber() >= track )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
510
                {
511
                    if ( track < 1 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
512
                    {
513
                        chkItem->setParentItem( oldParent );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
514
515
516
517
518
                        continueSearch = false;
                        break;
                    }
                    track -= 1;
                }
519
                else if ( currentItem->trackNumber() < track )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
520
521
522
                {
                    if ( track >= m_numVideoTrack - 1 )
                    {
523
                        chkItem->setParentItem( oldParent );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
524
525
526
527
528
                        continueSearch = false;
                        break;
                    }
                    track += 1;
                }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
529
                Q_ASSERT( getTrack( item->mediaType(), track ) != NULL );
530
                chkItem->setParentItem( getTrack( item->mediaType(), track ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
531
            }
532
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
533
534
        if ( !itemCollision )
            continueSearch = false;
535
    }
536
537


538
    // Check for horizontal collisions
539
    chkItem->setPos( qMax( time, (qint64)0 ), 0 );
540

541
    AbstractGraphicsMediaItem *hItem = NULL;
542
    QList<QGraphicsItem*> collide = chkItem->collidingItems( Qt::IntersectsItemShape );
543
    for ( int i = 0; i < collide.count(); ++i )
544
    {
545
        hItem = dynamic_cast<AbstractGraphicsMediaItem*>( collide.at( i ) );
546
        if ( hItem && hItem != item ) break;
547
    }
548

549
    if ( hItem && hItem != item )
550
551
552
    {
        qreal newpos;
        // Evaluate a possible solution
553
        if ( chkItem->pos().x() > hItem->pos().x() )
554
555
            newpos = hItem->pos().x() + hItem->boundingRect().width();
        else
556
            newpos = hItem->pos().x() - chkItem->boundingRect().width();
557

558
        if ( newpos < 0 || newpos == hItem->pos().x() )
559
            chkItem->setPos( oldPos, 0 ); // Fail
560
        else
561
562
        {
            // A solution may be found
563
564
            chkItem->setPos( qRound64( newpos ), 0 );
            QList<QGraphicsItem*> collideAgain = chkItem->collidingItems( Qt::IntersectsItemShape );
565
566
            for ( int i = 0; i < collideAgain.count(); ++i )
            {
567
                AbstractGraphicsMediaItem *currentItem =
568
                        dynamic_cast<AbstractGraphicsMediaItem*>( collideAgain.at( i ) );
569
                if ( currentItem && currentItem != item )
570
                {
571
                    chkItem->setPos( oldPos, 0 ); // Fail
572
573
574
575
                    break;
                }
            }
        }
576
    }
577

578
    GraphicsTrack *t = static_cast<GraphicsTrack*>( chkItem->parentItem() );
579

580
581
582
583
584
585
586
587
    Q_ASSERT( t );

    ItemPosition p;
    p.setTrack( t->trackNumber() );
    p.setTime( chkItem->pos().x() );

    delete chkItem;
    return p;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
588
589
}

590
591
void
TracksView::removeMediaItem( const QUuid &uuid, unsigned int track, MainWorkflow::TrackType trackType )
592
{
593
    QList<QGraphicsItem*> trackItems = getTrack( trackType, track )->childItems();;
594

595
    for ( int i = 0; i < trackItems.size(); ++i )
596
    {
597
        AbstractGraphicsMediaItem *item =
598
                dynamic_cast<AbstractGraphicsMediaItem*>( trackItems.at( i ) );
599
        if ( !item || item->uuid() != uuid ) continue;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
600
        removeMediaItem( item );
601
602
603
    }
}

604
605
void
TracksView::removeMediaItem( AbstractGraphicsMediaItem *item )
606
{
607
608
    QList<AbstractGraphicsMediaItem*> items;
    items.append( item );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
609
    removeMediaItem( items );
610
611
}

612
613
void
TracksView::removeMediaItem( const QList<AbstractGraphicsMediaItem*> &items )
614
{
615
    bool needUpdate = false;
616
    for ( int i = 0; i < items.size(); ++i )
617
    {
618
        GraphicsMovieItem *movieItem = qgraphicsitem_cast<GraphicsMovieItem*>( items.at( i ) );
619
620
621
        if ( !movieItem )
        {
            //TODO add support for audio tracks
Ludovic Fauvet's avatar
Ludovic Fauvet committed
622
            qWarning() << tr( "Action not supported." );
623
624
625
626
            continue;
        }

        delete movieItem;
627
        needUpdate = true;
628
    }
629
630

    if ( needUpdate ) updateDuration();
631
632
}

633
634
void
TracksView::dragLeaveEvent( QDragLeaveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
635
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
636
    Q_UNUSED( event )
637
638
639
640
641
    bool updateDurationNeeded = false;
    if ( m_dragAudioItem || m_dragVideoItem )
        updateDurationNeeded = true;

    if ( m_dragAudioItem )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
642
    {
643
644
645
646
647
648
649
        delete m_dragAudioItem;
        m_dragAudioItem = NULL;
    }
    if ( m_dragVideoItem )
    {
        delete m_dragVideoItem;
        m_dragVideoItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
650
    }
651
652
653

    if ( updateDurationNeeded )
        updateDuration();
654
655
}

656
657
void
TracksView::dropEvent( QDropEvent *event )
658
{
659
660
    qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );;

661
662
    UndoStack::getInstance()->beginMacro( "Add clip" );

663
    if ( m_dragAudioItem )
664
    {
665
        updateDuration();
666
667
668
669
670
671
672
        if ( getTrack( MainWorkflow::AudioTrack, m_numAudioTrack - 1 )->childItems().count() > 0 )
            addAudioTrack();
        event->acceptProposedAction();

        m_dragAudioItem->oldTrackNumber = m_dragAudioItem->trackNumber();
        m_dragAudioItem->oldPosition = (qint64)mappedXPos;

673
        Commands::trigger( new Commands::MainWorkflow::AddClip( m_dragAudioItem->clip(),
674
675
676
677
678
679
680
681
682
683
684
                                                                m_dragAudioItem->trackNumber(),
                                                                (qint64)mappedXPos,
                                                                MainWorkflow::AudioTrack ) );

        m_dragAudioItem = NULL;
    }

    if ( m_dragVideoItem )
    {
        updateDuration();
        if ( getTrack( MainWorkflow::VideoTrack, m_numVideoTrack - 1 )->childItems().count() > 0 )
685
686
            addVideoTrack();
        event->acceptProposedAction();
687

688
689
        m_dragVideoItem->oldTrackNumber = m_dragVideoItem->trackNumber();
        m_dragVideoItem->oldPosition = (qint64)mappedXPos;
690

691
        Commands::trigger( new Commands::MainWorkflow::AddClip( m_dragVideoItem->clip(),
692
                                                                m_dragVideoItem->trackNumber(),
693
                                                                (qint64)mappedXPos,
694
                                                                MainWorkflow::VideoTrack ) );
695
        m_dragVideoItem = NULL;
696
    }
697
698

    UndoStack::getInstance()->endMacro();
699
700
}

701
702
void
TracksView::setDuration( int duration )
703
{
704
    int diff = ( int ) qAbs( ( qreal )duration - sceneRect().width() );
705
706
707
708
709
710
711
712
713
714
    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;
}

715
716
void
TracksView::setTool( ToolButtons button )
717
718
{
    m_tool = button;
719
720
    if ( m_tool == TOOL_CUT )
        scene()->clearSelection();
721
722
}

723
724
void
TracksView::resizeEvent( QResizeEvent *event )
725
726
727
728
{
    QGraphicsView::resizeEvent( event );
}

729
730
void
TracksView::drawBackground( QPainter *painter, const QRectF &rect )
731
{
732
733
    painter->setWorldMatrixEnabled( false );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
734
735
    // Draw the tracks separators
    painter->setPen( QPen( QColor( 72, 72, 72 ) ) );
736
    for ( int i = 0; i < m_layout->count(); ++i )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
737
    {
738
739
740
        QGraphicsItem* gi = m_layout->itemAt( i )->graphicsItem();
        if ( !gi ) continue;
        GraphicsTrack* track = qgraphicsitem_cast<GraphicsTrack*>( gi );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
741
742
743
        if ( !track ) continue;

        QRectF trackRect = track->mapRectToScene( track->boundingRect() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
744
        if ( track->mediaType() == MainWorkflow::VideoTrack )
745
746
747
            painter->drawLine( trackRect.left(), trackRect.top(), rect.right(), trackRect.top() );
        else
            painter->drawLine( trackRect.left(), trackRect.bottom(), rect.right(), trackRect.bottom() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
748
749
750
    }

    // Audio/Video separator
751
752
753
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
754
    painter->setWorldMatrixEnabled( false );
755
756
757
758
759
760
761
762
763
764

    QLinearGradient g( 0, m_separator->y(), 0, m_separator->y() + m_separator->boundingRect().height() );
    QColor base = palette().window().color();
    QColor end = palette().dark().color();
    g.setColorAt( 0, end );
    g.setColorAt( 0.1, base );
    g.setColorAt( 0.9, base );
    g.setColorAt( 1.0, end );

    painter->setBrush( QBrush( g ) );
765
    painter->setPen( Qt::transparent );
766
767
768
769
    painter->drawRect( 0,
                       (int) m_separator->y(),
                       (int) r.right(),
                       (int) m_separator->boundingRect().height() );
770
}
771

772
773
void
TracksView::mouseMoveEvent( QMouseEvent *event )
774
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
775
776
777
778
    if ( event->modifiers() == Qt::NoModifier &&
         event->buttons() == Qt::LeftButton &&
         m_actionMove == true )
    {
779
780
781
        // The move action is obviously executed
        m_actionMoveExecuted = true;

782
        m_actionItem->setOpacity( 0.6 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
783
784
785
786
        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() ) );
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
787
788
789
790
791
792
793
    else if ( event->modifiers() == Qt::NoModifier &&
              event->buttons() == Qt::LeftButton &&
              m_actionResize == true )
    {
        QPointF itemPos = m_actionItem->mapToScene( 0, 0 );
        QPointF itemNewSize = mapToScene( event->pos() ) - itemPos;

794
        //FIXME: BEGIN UGLY
795
        GraphicsTrack *track = getTrack( m_actionItem->mediaType(), m_actionItem->trackNumber() );
796
797
798
799
800
801
802
803
804
        Q_ASSERT( track );

        QPointF collidePos = track->sceneBoundingRect().topRight();
        collidePos.setX( itemPos.x() + itemNewSize.x() );

        QList<QGraphicsItem*> gi = scene()->items( collidePos );

        bool collide = false;
        for ( int i = 0; i < gi.count(); ++i )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
805
        {
806
807
808
809
810
811
            AbstractGraphicsMediaItem* mi = dynamic_cast<AbstractGraphicsMediaItem*>( gi.at( i ) );
            if ( mi && mi != m_actionItem )
            {
                collide = true;
                break;
            }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
812
        }
813
814
815
        // END UGLY

        if ( !collide )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
816
        {
817
818
819
820
821
822
823
824
825
826
            if ( m_actionResizeType == AbstractGraphicsMediaItem::END )
            {
                qint64 distance = mapToScene( event->pos() ).x() - m_actionResizeStart;
                qint64 newsize = qMax( m_actionResizeBase - distance, (qint64)0 );
                m_actionItem->resize( newsize , AbstractGraphicsMediaItem::END );
            }
            else
            {
                m_actionItem->resize( itemNewSize.x(), AbstractGraphicsMediaItem::BEGINNING );
            }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
827
828
        }
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
829

830
831
832
    QGraphicsView::mouseMoveEvent( event );
}

833
834
void
TracksView::mousePressEvent( QMouseEvent *event )
835
{
836
    QList<AbstractGraphicsMediaItem*> mediaCollisionList = mediaItems( event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
837

838
839
840
    // Reset the drag mode
    setDragMode( QGraphicsView::NoDrag );

841
    if ( event->modifiers() == Qt::ControlModifier && mediaCollisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
842
843
    {
        setDragMode( QGraphicsView::ScrollHandDrag );
844
        event->accept();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
845
    }
846
    else if ( event->modifiers() == Qt::NoModifier &&
Ludovic Fauvet's avatar
Ludovic Fauvet committed
847
         event->button() == Qt::LeftButton &&
848
         tool() == TOOL_DEFAULT &&
Ludovic Fauvet's avatar
Ludovic Fauvet committed
849
850
         mediaCollisionList.count() == 1 )
    {
851
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
852
853
854
855
856
857
858
859
860
861
862
863
864
865

        QPoint itemEndPos = mapFromScene( item->mapToScene( item->boundingRect().bottomRight() ) );
        QPoint itemPos = mapFromScene( item->mapToScene( 0, 0 ) );
        QPoint clickPos = event->pos() - itemPos;
        QPoint itemSize = itemEndPos - itemPos;

        if ( clickPos.x() < RESIZE_ZONE || clickPos.x() > ( itemSize.x() - RESIZE_ZONE ) )
        {
            if ( clickPos.x() < RESIZE_ZONE )
                m_actionResizeType = AbstractGraphicsMediaItem::END;
            else
                m_actionResizeType = AbstractGraphicsMediaItem::BEGINNING;
            m_actionResize = true;
            m_actionResizeStart = mapToScene( event->pos() ).x();
866
867
            m_actionResizeBase = item->clip()->length();
            m_actionResizeOldBegin = item->clip()->begin();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
868
869
870
            m_actionItem = item;
        }
        else if ( item->moveable() )
871
872
        {
            m_actionMove = true;
873
            m_actionMoveExecuted = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
874
            m_actionItem = item;
875
        }
876
877
        scene()->clearSelection();
        item->setSelected( true );
878
        event->accept();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
879
    }
880
881
882
883
884
    else if ( event->modifiers() == Qt::NoModifier &&
         event->button() == Qt::RightButton &&
         tool() == TOOL_DEFAULT &&
         mediaCollisionList.count() == 1 )
    {
885
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
886
887
888
889
890
891
892

        if ( !scene()->selectedItems().contains( item ) )
        {
            scene()->clearSelection();
            item->setSelected( true );
        }
    }
893
894
    else if ( event->modifiers() == Qt::ControlModifier &&
              event->button() == Qt::LeftButton &&
895
              tool() == TOOL_DEFAULT &&
896
897
              mediaCollisionList.count() == 1 )
    {
898
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
899
        item->setSelected( !item->isSelected() );
900
        event->accept();
901
902
    }
    else if ( event->modifiers() & Qt::ShiftModifier && mediaCollisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
903
904
905
906
    {
        setDragMode( QGraphicsView::RubberBandDrag );
        if ( !event->modifiers() & Qt::ControlModifier )
            scene()->clearSelection();
907
        event->accept();
908
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
909

910
    QGraphicsView::mousePressEvent( event );
911
912
}

913
914
void
TracksView::mouseReleaseEvent( QMouseEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
915
{
916
    if ( m_actionMove && m_actionMoveExecuted )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
917
    {
918
        Q_ASSERT( m_actionItem );
919
920
        m_actionItem->setOpacity( 1.0 );

921
922
923
924
925
926
927
        updateDuration();

        if ( getTrack( MainWorkflow::VideoTrack, m_numVideoTrack - 1 )->childItems().count() > 0 )
            addVideoTrack();
        if ( getTrack( MainWorkflow::AudioTrack, m_numAudioTrack - 1 )->childItems().count() > 0 )
            addAudioTrack();

928
929
        UndoStack::getInstance()->beginMacro( "Move clip" );

930
        Commands::trigger( new Commands::MainWorkflow::MoveClip( m_mainWorkflow,
931
                                                                 m_actionItem->clip()->uuid(),
932
933
                                                                 m_actionItem->oldTrackNumber,
                                                                 m_actionItem->trackNumber(),
934
                                                                 m_actionItem->startPos(),
935
                                                                 m_actionItem->mediaType() ) );
936
937
938
939
940

        // Update the linked item too
        if ( m_actionItem->groupItem() )
        {
            Commands::trigger( new Commands::MainWorkflow::MoveClip( m_mainWorkflow,
941
                                                                     m_actionItem->groupItem()->clip()->uuid(),
942
943
944
945
                                                                     m_actionItem->groupItem()->oldTrackNumber,
                                                                     m_actionItem->groupItem()->trackNumber(),
                                                                     m_actionItem->groupItem()->startPos(),
                                                                     m_actionItem->groupItem()->mediaType() ) );
946
947
948

            m_actionItem->groupItem()->oldTrackNumber = m_actionItem->groupItem()->trackNumber();
            m_actionItem->groupItem()->oldPosition = m_actionItem->groupItem()->startPos();
949
        }
950

951
952
        UndoStack::getInstance()->endMacro();

953
        m_actionItem->oldTrackNumber = m_actionItem->trackNumber();
954
        m_actionItem->oldPosition = m_actionItem->startPos();
955
956
        m_actionRelativeX = -1;
        m_actionItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
957
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
958
959
    else if ( m_actionResize )
    {
960
        Clip *clip = m_actionItem->clip();
961
962
        //This is a "pointless action". The resize already occured. However, by doing this
        //we can have an undo action.
963
964
        Commands::trigger( new Commands::MainWorkflow::ResizeClip( clip->uuid(), clip->begin(),
                                                                   clip->end(), m_actionResizeOldBegin, m_actionResizeOldBegin + m_actionResizeBase,
965
                                                                   m_actionItem->pos().x(), m_actionResizeStart, m_actionItem->trackNumber(), m_actionItem->mediaType() ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
966
967
        updateDuration();
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
968

969
    m_actionMove = false;
970
    m_actionMoveExecuted = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
971
    m_actionResize = false;
972

973
    //setDragMode( QGraphicsView::NoDrag );
974
    QGraphicsView::mouseReleaseEvent( event );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
975
976
}

977
978
void
TracksView::wheelEvent( QWheelEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
{
    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 );
    }
}

997
998
QList<AbstractGraphicsMediaItem*>
TracksView::mediaItems( const QPoint &pos )
Ludovic Fauvet's avatar