TracksView.cpp 38.5 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 34 35 36
//Ugly part {
#include "Timeline.h"
#include "TracksRuler.h"
//} this should be fixed, it brokes the design
37
#include "TrackWorkflow.h"
38
#include "UndoStack.h"
39
#include "WorkflowRenderer.h"
40

41 42 43
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsRectItem>
44 45 46 47
#include <QMouseEvent>
#include <QScrollBar>
#include <QWheelEvent>

48 49 50 51 52 53 54 55
#include <QtDebug>

TracksView::TracksView( QGraphicsScene *scene, MainWorkflow *mainWorkflow,
                        WorkflowRenderer *renderer, QWidget *parent )
    : QGraphicsView( scene, parent ),
    m_scene( scene ),
    m_mainWorkflow( mainWorkflow ),
    m_renderer( renderer )
56
{
57
    //TODO should be defined by the settings
58
    m_tracksHeight = 25;
59

Ludovic Fauvet's avatar
Ludovic Fauvet committed
60 61
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
62 63
    m_dragVideoItem = NULL;
    m_dragAudioItem = NULL;
64
    m_lastKnownTrack = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
65
    m_actionMove = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
66
    m_actionResize = false;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
67 68
    m_actionRelativeX = -1;
    m_actionItem = NULL;
69
    m_tool = TOOL_DEFAULT;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
70

71 72 73
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
74
    setFrameStyle( QFrame::NoFrame );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
75
    setAlignment( Qt::AlignLeft | Qt::AlignTop );
76
    setCacheMode( QGraphicsView::CacheBackground );
77

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

80
    m_scene->addItem( m_cursorLine );
81

82
    connect( m_cursorLine, SIGNAL( cursorMoved(qint64) ),
83
             this, SLOT( ensureCursorVisible() ) );
84 85
}

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

95
    QGraphicsWidget *container = new QGraphicsWidget();
96
    container->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
97 98 99
    container->setContentsMargins( 0, 0, 0, 0 );
    container->setLayout( m_layout );

100 101 102 103
    // Create the initial layout
    // - 1 video track
    // - a separator
    // - 1 audio track
Ludovic Fauvet's avatar
Ludovic Fauvet committed
104
    addVideoTrack();
105 106

    m_separator = new QGraphicsWidget();
107
    m_separator->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
108 109 110
    m_separator->setPreferredHeight( 20 );
    m_layout->insertItem( 1, m_separator );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
111 112 113
    addAudioTrack();

    m_scene->addItem( container );
114 115

    setSceneRect( m_layout->contentsRect() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
116 117
}

118 119
void
TracksView::addVideoTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
120
{
121
    GraphicsTrack *track = new GraphicsTrack( Workflow::VideoTrack, m_numVideoTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
122
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
123
    m_layout->insertItem( 0, track );
124
    m_layout->activate();
125
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
126 127
    m_scene->invalidate(); // Redraw the background
    m_numVideoTrack++;
128 129
    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
130 131
    connect( track->trackWorkflow(), SIGNAL( clipRemoved( TrackWorkflow*, ClipHelper* ) ),
             this, SLOT( removeMediaItem( TrackWorkflow*, ClipHelper* ) ) );
132 133 134
    connect( track->trackWorkflow(), SIGNAL( clipMoved( TrackWorkflow*, ClipHelper*, qint64 ) ),
             this, SLOT( moveMediaItem( TrackWorkflow*, ClipHelper*, qint64 ) ) );

135
    emit videoTrackAdded( track );
136

Ludovic Fauvet's avatar
Ludovic Fauvet committed
137 138
}

139 140
void
TracksView::addAudioTrack()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
141
{
142
    GraphicsTrack *track = new GraphicsTrack( Workflow::AudioTrack, m_numAudioTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
143
    track->setHeight( m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
144
    m_layout->insertItem( 1000, track );
145
    m_layout->activate();
146
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
147 148
    m_scene->invalidate(); // Redraw the background
    m_numAudioTrack++;
149 150
    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
151 152
    connect( track->trackWorkflow(), SIGNAL( clipRemoved( TrackWorkflow*, ClipHelper* ) ),
             this, SLOT( removeMediaItem( TrackWorkflow*, ClipHelper* ) ) );
153 154
    connect( track->trackWorkflow(), SIGNAL( clipMoved( TrackWorkflow*, ClipHelper*, qint64 ) ),
             this, SLOT( moveMediaItem( TrackWorkflow*, ClipHelper*, qint64 ) ) );
155
    emit audioTrackAdded( track );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
156 157
}

158 159
void
TracksView::removeVideoTrack()
160 161 162
{
    Q_ASSERT( m_numVideoTrack > 0 );

163
    QGraphicsLayoutItem *item = m_layout->itemAt( 0 );
164 165 166
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
167
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
168 169 170 171 172
    m_numVideoTrack--;
    emit videoTrackRemoved();
    delete item;
}

173 174
void
TracksView::removeAudioTrack()
175 176 177
{
    Q_ASSERT( m_numAudioTrack > 0 );

178
    QGraphicsLayoutItem *item = m_layout->itemAt( m_layout->count() - 1 );
179 180 181
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
182
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
183 184 185 186 187
    m_numAudioTrack--;
    emit audioTrackRemoved();
    delete item;
}

188 189
void
TracksView::clear()
190
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
191
    m_layout->removeItem( m_separator );
192

Ludovic Fauvet's avatar
Ludovic Fauvet committed
193 194
    while ( m_layout->count() > 0 )
        delete m_layout->itemAt( 0 );
195

Ludovic Fauvet's avatar
Ludovic Fauvet committed
196
    m_layout->addItem( m_separator );
197 198 199 200 201 202 203 204 205 206

    m_numAudioTrack = 0;
    m_numVideoTrack = 0;

    addVideoTrack();
    addAudioTrack();

    updateDuration();
}

207
void
208
TracksView::removeClip( const QUuid& uuid  )
209
{
210
    AbstractGraphicsMediaItem*      item;
211 212 213 214 215 216 217 218

    // 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 )
    {
219
        if ( item->clipHelper()->clip()->uuid() == uuid ||
220
             item->clipHelper()->clip()->isChild( uuid ) == true ) //This is probably useless now
221 222 223 224 225 226
        {
            // 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
227
            removeMediaItem( item->track()->trackWorkflow(), item->clipHelper() );
228 229

            // Removing the item from the backend.
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
230
            item->track()->trackWorkflow()->removeClip( itemUuid );
231 232

            m_clipsLoaded.remove( item->clipHelper()->uuid() );
233 234 235 236
        }
    }
}

237
void
238
TracksView::addMediaItem( TrackWorkflow *tw, ClipHelper *ch, qint64 start )
239
{
240
    Q_ASSERT( ch );
241

242 243 244 245 246 247 248 249
    //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();

250 251
    // If there is not enough tracks to insert
    // the clip do it now.
252
    if ( trackType == Workflow::VideoTrack )
253
    {
254
        if ( track + 1 >= m_numVideoTrack )
255
        {
256 257
            int nbTrackToAdd = ( track + 2 ) - m_numVideoTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
258 259
                addVideoTrack();
        }
260
    }
261
    else if ( trackType == Workflow::AudioTrack )
262
    {
263
        if ( track + 1 >= m_numAudioTrack )
264
        {
265 266
            int nbTrackToAdd = ( track + 2 ) - m_numAudioTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
267 268
                addAudioTrack();
        }
269 270
    }

271
    AbstractGraphicsMediaItem *item = 0;
272
    if ( trackType == Workflow::VideoTrack )
273
    {
274
        item = new GraphicsMovieItem( ch );
275 276 277
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
278
    else if ( trackType == Workflow::AudioTrack )
279
    {
280
        item = new GraphicsAudioItem( ch );
281 282 283
        connect( item, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
    }
284
    m_clipsLoaded.insert( ch->uuid() );
Clement CHAVANCE's avatar
Clement CHAVANCE committed
285 286
    item->m_tracksView = this;
    item->setHeight( tracksHeight() );
287
    item->setTrack( getTrack( trackType, track ) );
Clement CHAVANCE's avatar
Clement CHAVANCE committed
288
    item->setStartPos( start );
289
    item->m_oldTrack = tw;
Clement CHAVANCE's avatar
Clement CHAVANCE committed
290 291
    item->oldPosition = start;
    moveMediaItem( item, track, start );
292
    updateDuration();
293 294
}

295 296
void
TracksView::dragEnterEvent( QDragEnterEvent *event )
297 298 299
{
    if ( event->mimeData()->hasFormat( "vlmc/uuid" ) )
        event->acceptProposedAction();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
300

301 302
    QString fullId = QString( event->mimeData()->data( "vlmc/uuid" ) );
    Clip *clip = Library::getInstance()->clip( fullId );
303
    if ( !clip ) return;
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
304 305
    if ( clip->getMedia()->hasAudioTrack() == false &&
         clip->getMedia()->hasVideoTrack() == false )
306
        return ;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
307

Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
308
    if ( clip->getMedia()->hasAudioTrack() == true )
309 310
    {
        if ( m_dragAudioItem ) delete m_dragAudioItem;
311
        m_dragAudioItem = new GraphicsAudioItem( clip );
312 313
        m_dragAudioItem->m_tracksView = this;
        m_dragAudioItem->setHeight( tracksHeight() );
314
        m_dragAudioItem->setTrack( getTrack( m_dragAudioItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
315 316
        connect( m_dragAudioItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
317
    }
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
318
    if ( clip->getMedia()->hasVideoTrack() == true )
319 320
    {
        if ( m_dragVideoItem ) delete m_dragVideoItem;
321
        m_dragVideoItem = new GraphicsMovieItem( clip );
322 323
        m_dragVideoItem->m_tracksView = this;
        m_dragVideoItem->setHeight( tracksHeight() );
324
        m_dragVideoItem->setTrack( getTrack( m_dragVideoItem->mediaType(), 0 ) );
Hugo Beauzee-Luyssen's avatar
Hugo Beauzee-Luyssen committed
325 326
        connect( m_dragVideoItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
327
    }
328
    // Group the items together
329 330
    if ( clip->getMedia()->hasAudioTrack() == true &&
         clip->getMedia()->hasVideoTrack() == true  )
331
        m_dragVideoItem->group( m_dragAudioItem );
332
    if ( clip->getMedia()->hasVideoTrack() == false )
333 334 335
        moveMediaItem( m_dragAudioItem, event->pos() );
    else
        moveMediaItem( m_dragVideoItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
336 337
}

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

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

352
void
353
TracksView::moveMediaItem( TrackWorkflow *tw, ClipHelper *ch, qint64 time )
354 355 356 357 358 359 360
{
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    for ( int i = 0; i < sceneItems.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( sceneItems.at( i ) );
361 362
        if ( !item || item->uuid() != ch->uuid() ) continue;
        moveMediaItem( item, tw->trackId(), time );
363
    }
364 365
    updateDuration();
    Timeline::getInstance()->tracksRuler()->update();
366 367
}

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

    if ( !m_lastKnownTrack )
374
        m_lastKnownTrack = getTrack( Workflow::VideoTrack, 0 );
375

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

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

393
    m_lastKnownTrack = track;
394

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

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

414 415
    ItemPosition p = findPosition( item, track, time );

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

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

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

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

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

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

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

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

503
    QGraphicsItem *oldParent = item->parentItem();
504 505
    qreal oldPos = item->startPos();

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

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


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

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

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

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

594
    GraphicsTrack *t = static_cast<GraphicsTrack*>( chkItem->parentItem() );
595

596 597 598
    ItemPosition p;
    p.setTime( chkItem->pos().x() );

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

604 605
    delete chkItem;
    return p;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
606 607
}

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

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

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

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

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

        delete movieItem;
649
        needUpdate = true;
650
    }
651 652

    if ( needUpdate ) updateDuration();
653 654
}

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

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

    if ( updateDurationNeeded )
        updateDuration();
670 671
}

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

677 678
    UndoStack::getInstance()->beginMacro( "Add clip" );

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

686
        m_dragAudioItem->m_oldTrack = m_dragAudioItem->track()->trackWorkflow();
687 688
        m_dragAudioItem->oldPosition = (qint64)mappedXPos;

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

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

702
        m_dragVideoItem->m_oldTrack = m_dragVideoItem->track()->trackWorkflow();
703
        m_dragVideoItem->oldPosition = (qint64)mappedXPos;
704

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

    UndoStack::getInstance()->endMacro();
712 713

    m_lastKnownTrack = NULL;
714 715
}

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

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

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

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

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

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

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

782
}
783

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

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

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

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

842 843 844
    QGraphicsView::mouseMoveEvent( event );
}

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

850 851 852
    // Reset the drag mode
    setDragMode( QGraphicsView::NoDrag );

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

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

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