TracksView.cpp 39.1 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
#include "ClipHelper.h"
26
#include "Commands.h"
27 28
#include "Library.h"
#include "GraphicsMovieItem.h"
29
#include "GraphicsAudioItem.h"
30
#include "GraphicsCursorItem.h"
31
#include "GraphicsTrack.h"
32
#include "Media.h"
33
#include "TrackWorkflow.h"
34
#include "UndoStack.h"
35
#include "WorkflowRenderer.h"
36

37 38 39
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsRectItem>
40 41 42 43
#include <QMouseEvent>
#include <QScrollBar>
#include <QWheelEvent>

44 45 46 47 48 49 50 51
#include <QtDebug>

TracksView::TracksView( QGraphicsScene *scene, MainWorkflow *mainWorkflow,
                        WorkflowRenderer *renderer, QWidget *parent )
    : QGraphicsView( scene, parent ),
    m_scene( scene ),
    m_mainWorkflow( mainWorkflow ),
    m_renderer( renderer )
52
{
53
    //TODO should be defined by the settings
54
    m_tracksHeight = 25;
55

Ludovic Fauvet's avatar
Ludovic Fauvet committed
56 57
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
58 59
    m_dragVideoItem = NULL;
    m_dragAudioItem = NULL;
60
    m_lastKnownTrack = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
61
    m_actionMove = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
62
    m_actionResize = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
63 64
    m_actionRelativeX = -1;
    m_actionItem = NULL;
65
    m_tool = TOOL_DEFAULT;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
66

67 68 69
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
70
    setFrameStyle( QFrame::NoFrame );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
71
    setAlignment( Qt::AlignLeft | Qt::AlignTop );
72
    setCacheMode( QGraphicsView::CacheBackground );
73

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

76
    m_scene->addItem( m_cursorLine );
77

78
    connect( m_cursorLine, SIGNAL( cursorMoved(qint64) ),
79
             this, SLOT( ensureCursorVisible() ) );
80 81
}

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

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

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

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

Ludovic Fauvet's avatar
Ludovic Fauvet committed
107 108 109
    addAudioTrack();

    m_scene->addItem( container );
110 111

    setSceneRect( m_layout->contentsRect() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
112 113
}

114 115
void
TracksView::addVideoTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
116
{
117
    GraphicsTrack *track = new GraphicsTrack( Workflow::VideoTrack, m_numVideoTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
118
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
119
    m_layout->insertItem( 0, track );
120
    m_layout->activate();
121
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
122 123
    m_scene->invalidate(); // Redraw the background
    m_numVideoTrack++;
124 125
    connect( track->trackWorkflow(), SIGNAL( clipAdded( TrackWorkflow*, ClipHelper*, qint64 ) ),
             this, SLOT( addMediaItem( TrackWorkflow*, ClipHelper*, qint64 ) ) );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
126 127
    connect( track->trackWorkflow(), SIGNAL( clipRemoved( TrackWorkflow*, ClipHelper* ) ),
             this, SLOT( removeMediaItem( TrackWorkflow*, ClipHelper* ) ) );
128
    emit videoTrackAdded( track );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
129 130
}

131 132
void
TracksView::addAudioTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
133
{
134
    GraphicsTrack *track = new GraphicsTrack( Workflow::AudioTrack, m_numAudioTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
135
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
136
    m_layout->insertItem( 1000, track );
137
    m_layout->activate();
138
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
139 140
    m_scene->invalidate(); // Redraw the background
    m_numAudioTrack++;
141 142
    connect( track->trackWorkflow(), SIGNAL( clipAdded( TrackWorkflow*, ClipHelper*, qint64 ) ),
             this, SLOT( addMediaItem( TrackWorkflow*, ClipHelper*, qint64 ) ) );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
143 144
    connect( track->trackWorkflow(), SIGNAL( clipRemoved( TrackWorkflow*, ClipHelper* ) ),
             this, SLOT( removeMediaItem( TrackWorkflow*, ClipHelper* ) ) );
145
    emit audioTrackAdded( track );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
146 147
}

148 149
void
TracksView::removeVideoTrack()
150 151 152
{
    Q_ASSERT( m_numVideoTrack > 0 );

153
    QGraphicsLayoutItem *item = m_layout->itemAt( 0 );
154 155 156
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
157
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
158 159 160 161 162
    m_numVideoTrack--;
    emit videoTrackRemoved();
    delete item;
}

163 164
void
TracksView::removeAudioTrack()
165 166 167
{
    Q_ASSERT( m_numAudioTrack > 0 );

168
    QGraphicsLayoutItem *item = m_layout->itemAt( m_layout->count() - 1 );
169 170 171
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
172
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
173 174 175 176 177
    m_numAudioTrack--;
    emit audioTrackRemoved();
    delete item;
}

178 179
void
TracksView::clear()
180
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
181
    m_layout->removeItem( m_separator );
182

Ludovic Fauvet's avatar
Ludovic Fauvet committed
183 184
    while ( m_layout->count() > 0 )
        delete m_layout->itemAt( 0 );
185

Ludovic Fauvet's avatar
Ludovic Fauvet committed
186
    m_layout->addItem( m_separator );
187 188 189 190 191 192 193 194 195 196

    m_numAudioTrack = 0;
    m_numVideoTrack = 0;

    addVideoTrack();
    addAudioTrack();

    updateDuration();
}

197
void
198
TracksView::removeClip( const QUuid& uuid  )
199
{
200
    AbstractGraphicsMediaItem*      item;
201 202 203 204 205 206 207 208

    // 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 )
    {
209
        if ( item->clipHelper()->clip()->uuid() == uuid ||
210
             item->clipHelper()->clip()->isChild( uuid ) == true ) //This is probably useless now
211 212 213 214 215 216
        {
            // This item needs to be removed.
            // Saving its values
            QUuid itemUuid = item->uuid();

            // Remove the item from the timeline
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
217
            removeMediaItem( item->track()->trackWorkflow(), item->clipHelper() );
218 219

            // Removing the item from the backend.
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
220
            item->track()->trackWorkflow()->removeClip( itemUuid );
221 222 223 224
        }
    }
}

225
void
226
TracksView::addMediaItem( TrackWorkflow *tw, ClipHelper *ch, qint64 start )
227
{
228
    Q_ASSERT( ch );
229

230 231 232 233 234 235 236 237
    //If for some reasons the clip was already loaded, don't add it twice.
    //This would likely happen when adding a clip from the timeline, as an element will
    //already be created (by the drag and drop operation)
    if ( m_clipsLoaded.contains( ch->uuid() ) )
        return ;
    quint32                 track = tw->trackId();
    Workflow::TrackType     trackType = tw->type();

238 239
    // If there is not enough tracks to insert
    // the clip do it now.
240
    if ( trackType == Workflow::VideoTrack )
241
    {
242
        if ( track + 1 >= m_numVideoTrack )
243
        {
244 245
            int nbTrackToAdd = ( track + 2 ) - m_numVideoTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
246 247
                addVideoTrack();
        }
248
    }
249
    else if ( trackType == Workflow::AudioTrack )
250
    {
251
        if ( track + 1 >= m_numAudioTrack )
252
        {
253 254
            int nbTrackToAdd = ( track + 2 ) - m_numAudioTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
255 256
                addAudioTrack();
        }
257 258
    }

259
    AbstractGraphicsMediaItem *item = 0;
260
    if ( trackType == Workflow::VideoTrack )
261
    {
262
        item = new GraphicsMovieItem( ch );
263 264 265
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
266
    else if ( trackType == Workflow::AudioTrack )
267
    {
268
        item = new GraphicsAudioItem( ch );
269 270 271
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
272
    m_clipsLoaded.insert( ch->uuid() );
Clement CHAVANCE's avatar
Clement CHAVANCE committed
273 274
    item->m_tracksView = this;
    item->setHeight( tracksHeight() );
275
    item->setTrack( getTrack( trackType, track ) );
Clement CHAVANCE's avatar
Clement CHAVANCE committed
276 277 278 279
    item->setStartPos( start );
    item->oldTrackNumber = track;
    item->oldPosition = start;
    moveMediaItem( item, track, start );
280
    updateDuration();
281 282
}

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

289 290
    QString fullId = QString( event->mimeData()->data( "vlmc/uuid" ) );
    Clip *clip = Library::getInstance()->clip( fullId );
291
    if ( !clip ) return;
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
292 293
    if ( clip->getMedia()->hasAudioTrack() == false &&
         clip->getMedia()->hasVideoTrack() == false )
294
        return ;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
295

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
296
    if ( clip->getMedia()->hasAudioTrack() == true )
297 298
    {
        if ( m_dragAudioItem ) delete m_dragAudioItem;
299
        m_dragAudioItem = new GraphicsAudioItem( clip );
300 301
        m_dragAudioItem->m_tracksView = this;
        m_dragAudioItem->setHeight( tracksHeight() );
302
        m_dragAudioItem->setTrack( getTrack( m_dragAudioItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
303 304
        connect( m_dragAudioItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
305
    }
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
306
    if ( clip->getMedia()->hasVideoTrack() == true )
307 308
    {
        if ( m_dragVideoItem ) delete m_dragVideoItem;
309
        m_dragVideoItem = new GraphicsMovieItem( clip );
310 311
        m_dragVideoItem->m_tracksView = this;
        m_dragVideoItem->setHeight( tracksHeight() );
312
        m_dragVideoItem->setTrack( getTrack( m_dragVideoItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
313 314
        connect( m_dragVideoItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
315
    }
316
    // Group the items together
317 318
    if ( clip->getMedia()->hasAudioTrack() == true &&
         clip->getMedia()->hasVideoTrack() == true  )
319
        m_dragVideoItem->group( m_dragAudioItem );
320
    if ( clip->getMedia()->hasVideoTrack() == false )
321 322 323
        moveMediaItem( m_dragAudioItem, event->pos() );
    else
        moveMediaItem( m_dragVideoItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
324 325
}

326 327
void
TracksView::dragMoveEvent( QDragMoveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
328
{
329 330 331 332 333 334 335 336 337
    AbstractGraphicsMediaItem* target;

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

340 341
bool
TracksView::setItemOldTrack( const QUuid &uuid, quint32 oldTrackNumber )
342 343 344 345 346 347 348 349 350 351 352 353 354 355
{
    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;
}

356 357
void
TracksView::moveMediaItem( const QUuid &uuid, unsigned int track, qint64 time )
358 359 360 361 362 363 364 365 366 367 368 369
{
    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 );
    }
}

370 371
void
TracksView::moveMediaItem( AbstractGraphicsMediaItem *item, QPoint position )
372
{
373
    GraphicsTrack *track = NULL;
374 375

    if ( !m_lastKnownTrack )
376
        m_lastKnownTrack = getTrack( Workflow::VideoTrack, 0 );
377

378
    QList<QGraphicsItem*> list = items( 0, position.y() );
379 380 381 382 383 384
    for ( int i = 0; i < list.size(); ++i )
    {
        track = qgraphicsitem_cast<GraphicsTrack*>( list.at(i) );
        if (track) break;
    }

385 386 387 388 389
    if ( !track )
    {
        // When the mouse pointer is not on a track,
        // use the last known track.
        // This avoids "breaks" when moving a rush
390
        if ( !m_lastKnownTrack )
391
            return;
392
        track = m_lastKnownTrack;
393 394
    }

395
    m_lastKnownTrack = track;
396

397
    qreal time = ( mapToScene( position ).x() + 0.5 );
398
    moveMediaItem( item, track->trackNumber(), (qint64)time);
399 400
}

401 402
void
TracksView::moveMediaItem( AbstractGraphicsMediaItem *item, quint32 track, qint64 time )
403
{
404
    // Add missing tracks
405
    if ( item->mediaType() == Workflow::AudioTrack )
406 407 408 409
    {
        while ( track >= m_numAudioTrack )
            addAudioTrack();
    }
410
    else if ( item->mediaType() == Workflow::VideoTrack )
411 412 413 414
    {
        while ( track >= m_numVideoTrack )
            addVideoTrack();
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
415

416 417
    ItemPosition p = findPosition( item, track, time );

418
    if ( p.isValid() && item->groupItem() )
419 420 421 422
    {
        bool validPosFound = false;

        // Add missing tracks for the target
423
        if ( item->groupItem()->mediaType() == Workflow::AudioTrack )
424
        {
425
            while ( p.track() >= m_numAudioTrack )
426 427
                addAudioTrack();
        }
428
        else if ( item->groupItem()->mediaType() == Workflow::VideoTrack )
429
        {
430
            while ( p.track() >= m_numVideoTrack )
431 432 433 434 435 436
                addVideoTrack();
        }

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

437
        // Add missing tracks for the source
438
        if ( item->mediaType() == Workflow::AudioTrack )
439 440 441 442
        {
            while ( p2.track() >= m_numAudioTrack )
                addAudioTrack();
        }
443
        else if ( item->mediaType() == Workflow::VideoTrack )
444 445 446 447 448
        {
            while ( p2.track() >= m_numVideoTrack )
                addVideoTrack();
        }

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
        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() );
479
            item->setTrack( getTrack( item->mediaType(), p.track() ) );
480 481 482

            // Move the linked item to the target destination.
            item->groupItem()->setStartPos( p2.time() );
483
            item->groupItem()->setTrack( getTrack( item->groupItem()->mediaType(), p2.track() ) );
484 485 486 487 488 489 490
        }
    }
    else
    {
        if ( p.isValid() )
        {
            item->setStartPos( p.time() );
491
            item->setTrack( getTrack( item->mediaType(), p.track() ) );
492 493 494 495
        }
    }
}

496 497
ItemPosition
TracksView::findPosition( AbstractGraphicsMediaItem *item, quint32 track, qint64 time )
498 499 500
{

    // Create a fake item for computing collisions
501
    QGraphicsRectItem *chkItem = new QGraphicsRectItem( item->boundingRect() );
502 503 504
    chkItem->setParentItem( getTrack( item->mediaType(), track ) );
    chkItem->setPos( time, 0 );

505
    QGraphicsItem *oldParent = item->parentItem();
506 507
    qreal oldPos = item->startPos();

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

                    if ( track >= higherTrack - 1 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
540
                    {
541
                        chkItem->setParentItem( oldParent );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
542 543 544 545 546
                        continueSearch = false;
                        break;
                    }
                    track += 1;
                }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
547
                Q_ASSERT( getTrack( item->mediaType(), track ) != NULL );
548
                chkItem->setParentItem( getTrack( item->mediaType(), track ) );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
549
            }
550
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
551 552
        if ( !itemCollision )
            continueSearch = false;
553
    }
554 555


556
    // Check for horizontal collisions
557
    chkItem->setPos( qMax( time, (qint64)0 ), 0 );
558

559
    AbstractGraphicsMediaItem *hItem = NULL;
560
    QList<QGraphicsItem*> collide = chkItem->collidingItems( Qt::IntersectsItemShape );
561
    for ( int i = 0; i < collide.count(); ++i )
562
    {
563
        hItem = dynamic_cast<AbstractGraphicsMediaItem*>( collide.at( i ) );
564
        if ( hItem && hItem != item ) break;
565
    }
566

567
    if ( hItem && hItem != item )
568 569 570
    {
        qreal newpos;
        // Evaluate a possible solution
571
        if ( chkItem->pos().x() > hItem->pos().x() )
572 573
            newpos = hItem->pos().x() + hItem->boundingRect().width();
        else
574
            newpos = hItem->pos().x() - chkItem->boundingRect().width();
575

576
        if ( newpos < 0 || newpos == hItem->pos().x() )
577
            chkItem->setPos( oldPos, 0 ); // Fail
578
        else
579 580
        {
            // A solution may be found
581 582
            chkItem->setPos( qRound64( newpos ), 0 );
            QList<QGraphicsItem*> collideAgain = chkItem->collidingItems( Qt::IntersectsItemShape );
583 584
            for ( int i = 0; i < collideAgain.count(); ++i )
            {
585
                AbstractGraphicsMediaItem *currentItem =
586
                        dynamic_cast<AbstractGraphicsMediaItem*>( collideAgain.at( i ) );
587
                if ( currentItem && currentItem != item )
588
                {
589
                    chkItem->setPos( oldPos, 0 ); // Fail
590 591 592 593
                    break;
                }
            }
        }
594
    }
595

596
    GraphicsTrack *t = static_cast<GraphicsTrack*>( chkItem->parentItem() );
597

598 599 600
    ItemPosition p;
    p.setTime( chkItem->pos().x() );

601 602 603 604 605
    if ( t )
        p.setTrack( t->trackNumber() );
    else
        p.setTrack( -1 ); // Return in valid position

606 607
    delete chkItem;
    return p;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
608 609
}

610
void
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
611
TracksView::removeMediaItem( TrackWorkflow *tw, ClipHelper *ch )
612
{
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
613
    GraphicsTrack           *track = getTrack( tw->type(), tw->trackId() );
614 615 616 617

    if ( track == NULL )
        return ;
    QList<QGraphicsItem*> trackItems = track->childItems();;
618

619
    for ( int i = 0; i < trackItems.size(); ++i )
620
    {
621
        AbstractGraphicsMediaItem *item =
622
                dynamic_cast<AbstractGraphicsMediaItem*>( trackItems.at( i ) );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
623
        if ( !item || item->uuid() != ch->uuid() ) continue;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
624
        removeMediaItem( item );
625 626 627
    }
}

628 629
void
TracksView::removeMediaItem( AbstractGraphicsMediaItem *item )
630
{
631 632
    QList<AbstractGraphicsMediaItem*> items;
    items.append( item );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
633
    removeMediaItem( items );
634 635
}

636 637
void
TracksView::removeMediaItem( const QList<AbstractGraphicsMediaItem*> &items )
638
{
639
    bool needUpdate = false;
640
    for ( int i = 0; i < items.size(); ++i )
641
    {
642
        GraphicsMovieItem *movieItem = qgraphicsitem_cast<GraphicsMovieItem*>( items.at( i ) );
643 644 645
        if ( !movieItem )
        {
            //TODO add support for audio tracks
Ludovic Fauvet's avatar
Ludovic Fauvet committed
646
            qWarning() << tr( "Action not supported." );
647 648 649 650
            continue;
        }

        delete movieItem;
651
        needUpdate = true;
652
    }
653 654

    if ( needUpdate ) updateDuration();
655 656
}

657 658
void
TracksView::dragLeaveEvent( QDragLeaveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
659
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
660
    Q_UNUSED( event )
661 662 663 664
    bool updateDurationNeeded = false;
    if ( m_dragAudioItem || m_dragVideoItem )
        updateDurationNeeded = true;

Ludovic Fauvet's avatar
Ludovic Fauvet committed
665 666 667 668
    delete m_dragAudioItem;
    delete m_dragVideoItem;
    m_dragAudioItem = NULL;
    m_dragVideoItem = NULL;
669 670 671

    if ( updateDurationNeeded )
        updateDuration();
672 673
}

674 675
void
TracksView::dropEvent( QDropEvent *event )
676
{
677 678
    qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );;

679 680
    UndoStack::getInstance()->beginMacro( "Add clip" );

681
    if ( m_dragAudioItem )
682
    {
683
        updateDuration();
684
        if ( getTrack( Workflow::AudioTrack, m_numAudioTrack - 1 )->childItems().count() > 0 )
685 686 687 688 689 690
            addAudioTrack();
        event->acceptProposedAction();

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

691
        Commands::trigger( new Commands::MainWorkflow::AddClip( m_dragAudioItem->clipHelper(),
692 693
                                                                m_dragAudioItem->track()->trackWorkflow(),
                                                                (qint64)mappedXPos ) );
694 695 696 697 698 699
        m_dragAudioItem = NULL;
    }

    if ( m_dragVideoItem )
    {
        updateDuration();
700
        if ( getTrack( Workflow::VideoTrack, m_numVideoTrack - 1 )->childItems().count() > 0 )
701 702
            addVideoTrack();
        event->acceptProposedAction();
703

704 705
        m_dragVideoItem->oldTrackNumber = m_dragVideoItem->trackNumber();
        m_dragVideoItem->oldPosition = (qint64)mappedXPos;
706

707
        Commands::trigger( new Commands::MainWorkflow::AddClip( m_dragVideoItem->clipHelper(),
708 709
                                                                m_dragVideoItem->track()->trackWorkflow(),
                                                                (qint64)mappedXPos ) );
710
        m_dragVideoItem = NULL;
711
    }
712 713

    UndoStack::getInstance()->endMacro();
714 715

    m_lastKnownTrack = NULL;
716 717
}

718 719
void
TracksView::setDuration( int duration )
720
{
721
    int diff = ( int ) qAbs( ( qreal )duration - sceneRect().width() );
722 723 724 725 726 727 728 729 730 731
    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;
}

732 733
void
TracksView::setTool( ToolButtons button )
734 735
{
    m_tool = button;
736 737
    if ( m_tool == TOOL_CUT )
        scene()->clearSelection();
738 739
}

740 741
void
TracksView::resizeEvent( QResizeEvent *event )
742 743 744 745
{
    QGraphicsView::resizeEvent( event );
}

746 747
void
TracksView::drawBackground( QPainter *painter, const QRectF &rect )
748
{
749 750
    // Fill the background
    painter->fillRect( rect, QBrush( palette().base() ) );
751

Ludovic Fauvet's avatar
Ludovic Fauvet committed
752 753
    // Draw the tracks separators
    painter->setPen( QPen( QColor( 72, 72, 72 ) ) );
754
    for ( int i = 0; i < m_layout->count(); ++i )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
755
    {
756 757 758
        QGraphicsItem* gi = m_layout->itemAt( i )->graphicsItem();
        if ( !gi ) continue;
        GraphicsTrack* track = qgraphicsitem_cast<GraphicsTrack*>( gi );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
759 760 761
        if ( !track ) continue;

        QRectF trackRect = track->mapRectToScene( track->boundingRect() );
762
        if ( track->mediaType() == Workflow::VideoTrack )
763
            painter->drawLine( rect.left(), trackRect.top(), rect.right() + 1, trackRect.top() );
764
        else
765
            painter->drawLine( rect.left(), trackRect.bottom(), rect.right() + 1, trackRect.bottom() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
766 767 768
    }

    // Audio/Video separator
769 770 771 772 773 774 775 776 777
    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 ) );
778
    painter->setPen( Qt::transparent );
779
    painter->drawRect( rect.left(),
780
                       (int) m_separator->y(),
781
                       (int) rect.right() + 1,
782
                       (int) m_separator->boundingRect().height() );
783

784
}
785

786 787
void
TracksView::mouseMoveEvent( QMouseEvent *event )
788
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
789 790 791 792
    if ( event->modifiers() == Qt::NoModifier &&
         event->buttons() == Qt::LeftButton &&
         m_actionMove == true )
    {
793 794 795
        // The move action is obviously executed
        m_actionMoveExecuted = true;

796
        m_actionItem->setOpacity( 0.6 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
797 798 799 800
        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
801 802 803 804 805 806 807
    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;

808
        //FIXME: BEGIN UGLY
809
        GraphicsTrack *track = getTrack( m_actionItem->mediaType(), m_actionItem->trackNumber() );
810 811 812 813 814 815 816 817 818
        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
819
        {
820 821 822 823 824 825
            AbstractGraphicsMediaItem* mi = dynamic_cast<AbstractGraphicsMediaItem*>( gi.at( i ) );
            if ( mi && mi != m_actionItem )
            {
                collide = true;
                break;
            }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
826
        }
827 828 829
        // END UGLY

        if ( !collide )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
830
        {
831 832 833 834 835 836 837 838 839 840
            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
841 842
        }
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
843

844 845 846
    QGraphicsView::mouseMoveEvent( event );
}

847 848
void
TracksView::mousePressEvent( QMouseEvent *event )
849
{
850
    QList<AbstractGraphicsMediaItem*> mediaCollisionList = mediaItems( event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
851

852 853 854
    // Reset the drag mode
    setDragMode( QGraphicsView::NoDrag );

855
    if ( event->modifiers() == Qt::ControlModifier && mediaCollisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
856 857
    {
        setDragMode( QGraphicsView::ScrollHandDrag );
858
        event->accept();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
859
    }
860
    else if ( event->modifiers() == Qt::NoModifier &&
Ludovic Fauvet's avatar
Ludovic Fauvet committed
861
         event->button() == Qt::LeftButton &&
862
         tool() == TOOL_DEFAULT &&
Ludovic Fauvet's avatar
Ludovic Fauvet committed
863 864
         mediaCollisionList.count() == 1 )
    {
865
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
866 867 868 869 870 871 872 873 874 875 876 877 878 879

        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();
880 881
            m_actionResizeBase = item->clipHelper()->length();
            m_actionResizeOldBegin = item->clipHelper()->begin();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
882 883 884
            m_actionItem = item;
        }
        else if ( item->moveable() )
885 886
        {
            m_actionMove = true;
887
            m_actionMoveExecuted = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
888
            m_actionItem = item;
889
        }
890 891
        scene()->clearSelection();
        item->setSelected( true );
892
        event->accept();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
893
    }
894 895 896 897 898
    else if ( event->modifiers() == Qt::NoModifier &&
         event->button() == Qt::RightButton &&
         tool() == TOOL_DEFAULT &&
         mediaCollisionList.count() == 1 )
    {
899
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
900 901 902 903 904 905 906

        if ( !scene()->selectedItems().contains( item ) )
        {
            scene()->clearSelection();
            item->setSelected( true );
        }
    }
907 908
    else if ( event->modifiers() == Qt::ControlModifier &&
              event->button() == Qt::LeftButton &&
909
              tool() == TOOL_DEFAULT &&
910 911
              mediaCollisionList.count() == 1 )
    {
912
        AbstractGraphicsMediaItem *item = mediaCollisionList.at( 0 );
913
        item->setSelected( !item->isSelected() );
914
        event->accept();
915 916
    }
    else if ( event->modifiers() & Qt::ShiftModifier && mediaCollisionList.count() == 0 )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
917 918 919 920
    {
        setDragMode( QGraphicsView::RubberBandDrag );
        if ( !event->modifiers() & Qt::ControlModifier )
            scene()->clearSelection();
921
        event->accept();
922
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
923

924
    QGraphicsView::mousePressEvent( event );
925 926
}

927 928
void
TracksView::mouseReleaseEvent( QMouseEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
929
{
930
    if ( m_actionMove && m_actionMoveExecuted )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
931
    {
932
        Q_ASSERT( m_actionItem );
933 934
        m_actionItem->setOpacity( 1.0 );

935 936
        updateDuration();