TracksView.cpp 48.4 KB
Newer Older
1
/*****************************************************************************
2
 * TracksView.cpp: QGraphicsView that contains the TracksScene
3
 *****************************************************************************
4
 * Copyright (C) 2008-2014 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 "Main/Core.h"
26
#include "Project/Project.h"
27 28 29 30
#include "Workflow/ClipHelper.h"
#include "Workflow/ClipWorkflow.h"
#include "Commands/Commands.h"
#include "EffectsEngine/EffectHelper.h"
31
#include "GraphicsMovieItem.h"
32
#include "GraphicsAudioItem.h"
33
#include "GraphicsEffectItem.h"
34
#include "GraphicsCursorItem.h"
35
#include "GraphicsTrack.h"
36 37 38 39
#include "Workflow/Helper.h"
#include "Backend/ISource.h"
#include "Library/Library.h"
#include "Media/Media.h"
40 41 42
//Ugly part {
#include "Timeline.h"
#include "TracksRuler.h"
43
//} this should be fixed, it breaks the design
44 45 46
#include "Workflow/TrackWorkflow.h"
#include "Tools/VlmcDebug.h"
#include "Renderer/WorkflowRenderer.h"
47

48 49 50
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
#include <QGraphicsRectItem>
51 52 53
#include <QMouseEvent>
#include <QScrollBar>
#include <QWheelEvent>
54
#include <QMimeData>
55

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

Ludovic Fauvet's avatar
Ludovic Fauvet committed
66 67
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
68 69
    m_dragVideoItem = NULL;
    m_dragAudioItem = NULL;
70
    m_dragEffectItem = NULL;
71
    m_lastKnownTrack = NULL;
72
    m_effectTarget = NULL;
73
    m_action = None;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
74 75
    m_actionRelativeX = -1;
    m_actionItem = NULL;
76
    m_tool = TOOL_DEFAULT;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
77

78 79 80
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
81
    setFrameStyle( QFrame::NoFrame );
82
    setAlignment( Qt::AlignLeft | Qt::AlignTop );
83
    setCacheMode( QGraphicsView::CacheBackground );
84

85
    m_cursorLine = new GraphicsCursorItem( QPen( QColor( 255, 0, 0, 125 ), 2 ) );
86

87
    m_scene->addItem( m_cursorLine );
88

89
    connect( m_cursorLine, SIGNAL( cursorMoved(qint64) ),
90
             this, SLOT( ensureCursorVisible() ) );
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

    for ( quint32 type = Workflow::VideoTrack; type < Workflow::NbTrackType; ++type )
    {
        for ( quint32 i = 0; i < m_mainWorkflow->trackCount(); ++i )
        {
            TrackWorkflow   *tw = m_mainWorkflow->track( static_cast<Workflow::TrackType>( type ), i );
            //Clips part:
            connect( tw, SIGNAL( clipAdded( TrackWorkflow*, Workflow::Helper*, qint64 ) ),
                     this, SLOT( addItem( TrackWorkflow*, Workflow::Helper*, qint64 ) ) );
            connect( tw, SIGNAL( clipRemoved( TrackWorkflow*, const QUuid& ) ),
                     this, SLOT( removeItem( TrackWorkflow*, const QUuid& ) ) );
            connect( tw, SIGNAL( clipMoved( TrackWorkflow*, const QUuid&, qint64 ) ),
                     this, SLOT( moveItem( TrackWorkflow*, const QUuid&, qint64 ) ) );
            //Effect part:
            connect( tw, SIGNAL( effectAdded( TrackWorkflow*, Workflow::Helper*, qint64 ) ),
                     this, SLOT(addItem( TrackWorkflow*, Workflow::Helper*, qint64 ) ), Qt::QueuedConnection );
            connect( tw, SIGNAL( effectRemoved( TrackWorkflow*, QUuid ) ),
                     this, SLOT( removeItem( TrackWorkflow*, QUuid ) ), Qt::QueuedConnection );
            connect( tw, SIGNAL( effectMoved( TrackWorkflow*, QUuid, qint64 ) ),
                     this, SLOT( moveItem( TrackWorkflow*, QUuid, qint64 ) ), Qt::QueuedConnection );

        }
    }
114 115
}

116 117
void
TracksView::createLayout()
Ludovic Fauvet's avatar
Ludovic Fauvet committed
118 119
{
    m_layout = new QGraphicsLinearLayout( Qt::Vertical );
120
    m_layout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
121 122
    m_layout->setContentsMargins( 0, 0, 0, 0 );
    m_layout->setSpacing( 0 );
123
    m_layout->setPreferredWidth( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
124

125
    QGraphicsWidget *container = new QGraphicsWidget();
126
    container->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
127 128 129
    container->setContentsMargins( 0, 0, 0, 0 );
    container->setLayout( m_layout );

130 131 132 133
    // Create the initial layout
    // - 1 video track
    // - a separator
    // - 1 audio track
134
    addTrack( Workflow::VideoTrack );
135 136

    m_separator = new QGraphicsWidget();
137
    m_separator->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
138 139 140
    m_separator->setPreferredHeight( 20 );
    m_layout->insertItem( 1, m_separator );

141
    addTrack( Workflow::AudioTrack );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
142 143

    m_scene->addItem( container );
144 145

    setSceneRect( m_layout->contentsRect() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
146 147
}

148
void
149
TracksView::addTrack( Workflow::TrackType type )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
150
{
151 152
    GraphicsTrack *track = new GraphicsTrack( type,
                                              type == Workflow::VideoTrack ? m_numVideoTrack : m_numAudioTrack );
153
    track->setHeight( tracksHeight() );
154
    m_layout->insertItem( type == Workflow::VideoTrack ? 0 : 1000, track );
155
    m_layout->activate();
156
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
157
    m_scene->invalidate(); // Redraw the background
158

159 160 161 162 163 164 165 166 167 168
    if ( type == Workflow::VideoTrack )
    {
        m_numVideoTrack++;
        emit videoTrackAdded( track );
    }
    else
    {
        m_numAudioTrack++;
        emit audioTrackAdded( track );
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
169 170 171

}

172 173
void
TracksView::removeVideoTrack()
174 175 176
{
    Q_ASSERT( m_numVideoTrack > 0 );

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

187 188
void
TracksView::removeAudioTrack()
189 190 191
{
    Q_ASSERT( m_numAudioTrack > 0 );

192
    QGraphicsLayoutItem *item = m_layout->itemAt( m_layout->count() - 1 );
193 194 195
    m_layout->removeItem( item );
    m_layout->activate();
    m_scene->invalidate(); // Redraw the background
196
    m_cursorLine->setHeight( m_layout->contentsRect().height() );
197 198 199 200 201
    m_numAudioTrack--;
    emit audioTrackRemoved();
    delete item;
}

202 203
void
TracksView::clear()
204
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
205
    m_layout->removeItem( m_separator );
206

Ludovic Fauvet's avatar
Ludovic Fauvet committed
207 208
    while ( m_layout->count() > 0 )
        delete m_layout->itemAt( 0 );
209

Ludovic Fauvet's avatar
Ludovic Fauvet committed
210
    m_layout->addItem( m_separator );
211 212 213 214

    m_numAudioTrack = 0;
    m_numVideoTrack = 0;

215 216
    addTrack( Workflow::VideoTrack );
    addTrack( Workflow::AudioTrack );
217
    m_itemsLoaded.clear();
218 219 220 221

    updateDuration();
}

222
void
223
TracksView::removeClip( const QUuid& uuid  )
224 225
{
    // Get the list of all items in the timeline
226
    QList<AbstractGraphicsItem*> items = timelineItems();
227 228 229

    // Iterate over each item to check if their parent's uuid
    // is the one we would like to remove.
230
    foreach( AbstractGraphicsItem *item, items )
231
    {
232
        if ( item->uuid() == uuid )
233 234
        {
            // Remove the item from the timeline
235
            removeItem( item->track()->trackWorkflow(), item->uuid() );
236 237

            // Removing the item from the backend.
238
            item->track()->trackWorkflow()->removeClip( item->uuid() );
239 240 241 242
        }
    }
}

243
void
244
TracksView::addItem( TrackWorkflow *tw, Workflow::Helper *helper, qint64 start )
245
{
246
    Q_ASSERT( helper );
247

248 249 250
    //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)
251
    if ( m_itemsLoaded.contains( helper->uuid() ) )
252
        return ;
253
    qint32                  track = tw->trackId();
254 255
    Workflow::TrackType     trackType = tw->type();

256 257
    // If there is not enough tracks to insert
    // the clip do it now.
258
    if ( trackType == Workflow::VideoTrack )
259
    {
260
        if ( track + 1 >= m_numVideoTrack )
261
        {
262 263
            int nbTrackToAdd = ( track + 2 ) - m_numVideoTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
264
                addTrack( Workflow::VideoTrack );
265
        }
266
    }
267
    else if ( trackType == Workflow::AudioTrack )
268
    {
269
        if ( track + 1 >= m_numAudioTrack )
270
        {
271 272
            int nbTrackToAdd = ( track + 2 ) - m_numAudioTrack;
            for ( int i = 0; i < nbTrackToAdd; ++i )
273
                addTrack( Workflow::AudioTrack );
274
        }
275 276
    }

277 278 279
    AbstractGraphicsItem        *item = NULL;
    ClipHelper                  *clipHelper = qobject_cast<ClipHelper*>( helper );
    if ( clipHelper != NULL )
280
    {
281 282 283 284 285 286 287 288 289 290 291 292 293 294
        AbstractGraphicsMediaItem   *mediaItem = NULL;
        if ( trackType == Workflow::VideoTrack )
        {
            mediaItem = new GraphicsMovieItem( clipHelper );
            connect( mediaItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                     this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
        }
        else if ( trackType == Workflow::AudioTrack )
        {
            mediaItem = new GraphicsAudioItem( clipHelper );
            connect( mediaItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                     this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
        }
        item = mediaItem;
295 296 297 298 299 300 301
        m_itemsLoaded.insert( helper->uuid() );
        item->m_tracksView = this;
        item->setHeight( item->itemHeight() );
        item->setTrack( getTrack( trackType, track ) );
        item->setStartPos( start );
        item->m_oldTrack = tw;
        moveItem( item, track, start );
302 303 304 305 306
        //If the item has some effects:
        foreach ( EffectHelper *effectHelper, clipHelper->clipWorkflow()->effects( Effect::Filter ) )
        {
            addEffectItem( effectHelper, trackType, track, start );
        }
307
    }
308
    else
309
    {
310
        EffectHelper    *effectHelper = qobject_cast<EffectHelper*>( helper );
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
        addEffectItem( effectHelper, trackType, track, start );
    }
    updateDuration();
}

void
TracksView::addEffectItem( EffectHelper *effectHelper, Workflow::TrackType trackType,
                           qint32 trackId, qint64 start )
{
    Q_ASSERT( effectHelper != NULL );
    GraphicsEffectItem *item = new GraphicsEffectItem( effectHelper );
    m_itemsLoaded.insert( effectHelper->uuid() );
    item->m_tracksView = this;
    item->setHeight( item->itemHeight() );
    GraphicsTrack   *track = getTrack( trackType, trackId );
    item->setTrack( track );
    item->setStartPos( start );
    item->m_oldTrack = track->trackWorkflow();
    moveItem( item, trackId, start );
    QList<QGraphicsItem*>     collidingItems = item->collidingItems();
    item->setContainer( NULL );
    foreach ( QGraphicsItem *collider, collidingItems )
    {
        AbstractGraphicsMediaItem   *mediaItem = dynamic_cast<AbstractGraphicsMediaItem*>( collider );
        if ( mediaItem != NULL )
336
        {
337 338
            item->setContainer( mediaItem );
            break ;
339
        }
340
    }
341 342
}

343 344
void
TracksView::dragEnterEvent( QDragEnterEvent *event )
345 346
{
    if ( event->mimeData()->hasFormat( "vlmc/uuid" ) )
347
    {
348
        event->acceptProposedAction();
349 350 351
        clipDragEnterEvent( event );
    }
    else if ( event->mimeData()->hasFormat( "vlmc/effect_name" ) )
352
    {
353 354
        event->acceptProposedAction();
        effectDragEnterEvent( event );
355
    }
356 357 358 359 360 361 362
    else
        event->ignore();
}

void
TracksView::effectDragEnterEvent( QDragEnterEvent *event )
{
363
    Effect* effect = Core::getInstance()->effectsEngine()->effect( event->mimeData()->data( "vlmc/effect_name") );
364
    if ( effect != NULL )
365 366
    {
        m_dragEffectItem = new GraphicsEffectItem( effect );
367 368
        m_dragEffectItem->setHeight( m_dragEffectItem->itemHeight() );
        m_dragEffectItem->m_tracksView = this;
369
    }
370
    else
371
        vlmcWarning() << "Can't find effect name" << event->mimeData()->data( "vlmc/effect_name");
372
}
Ludovic Fauvet's avatar
Ludovic Fauvet committed
373

374 375 376 377
void
TracksView::clipDragEnterEvent( QDragEnterEvent *event )
{
    const QString fullId = QString( event->mimeData()->data( "vlmc/uuid" ) );
378
    Clip *clip = Project::getInstance()->library()->clip( fullId );
379 380 381 382 383
    if ( clip == NULL )
        return;
    bool hasVideo = clip->getMedia()->source()->hasVideo();
    bool hasAudio = clip->getMedia()->source()->hasAudio();
    if ( hasAudio == false && hasVideo == false )
384
        return ;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
385

386
    if ( hasAudio == true )
387
    {
388 389
        if ( m_dragAudioItem )
            delete m_dragAudioItem;
390
        m_dragAudioItem = new GraphicsAudioItem( clip );
391
        m_dragAudioItem->m_tracksView = this;
392
        m_dragAudioItem->setHeight( m_dragAudioItem->itemHeight() );
393
        m_dragAudioItem->setTrack( getTrack( m_dragAudioItem->trackType(), 0 ) );
394 395
        connect( m_dragAudioItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
396
    }
397
    if ( hasVideo == true )
398
    {
399 400
        if ( m_dragVideoItem )
            delete m_dragVideoItem;
401
        m_dragVideoItem = new GraphicsMovieItem( clip );
402
        m_dragVideoItem->m_tracksView = this;
403
        m_dragVideoItem->setHeight( m_dragVideoItem->itemHeight() );
404
        m_dragVideoItem->setTrack( getTrack( m_dragVideoItem->trackType(), 0 ) );
405 406
        connect( m_dragVideoItem, SIGNAL( split(AbstractGraphicsMediaItem*,qint64) ),
                 this, SLOT( split(AbstractGraphicsMediaItem*,qint64) ) );
407
    }
408
    // Group the items together
409
    if ( hasVideo == true && hasAudio == true )
410
        m_dragVideoItem->group( m_dragAudioItem );
411
    if ( hasVideo == false )
412
        moveItem( m_dragAudioItem, event->pos() );
413
    else
414
        moveItem( m_dragVideoItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
415 416
}

417 418
void
TracksView::dragMoveEvent( QDragMoveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
419
{
420
    if ( m_dragVideoItem != NULL )
421
        moveItem( m_dragVideoItem, event->pos() );
422
    else if ( m_dragAudioItem != NULL)
423
        moveItem( m_dragAudioItem, event->pos() );
424
    else if ( m_dragEffectItem != NULL )
425
    {
426 427 428
        //Only get medias from here, as we much drag an effect to a media or a track
        QList<AbstractGraphicsMediaItem*>   itemList = mediaItems<AbstractGraphicsMediaItem>( event->pos() );
        if ( itemList.size() > 0 )
429
        {
430
            AbstractGraphicsMediaItem   *item = itemList.first();
431 432 433
            ClipHelper                  *clipHelper = qobject_cast<ClipHelper*>( item->helper() );
            Q_ASSERT( clipHelper != NULL );

434 435
            m_dragEffectItem->setWidth( item->clipHelper()->length() );
            m_dragEffectItem->setStartPos( item->startPos() );
436
            m_dragEffectItem->setTrack( item->track() );
437
            m_dragEffectItem->effectHelper()->setTarget( clipHelper->clipWorkflow() );
438
        }
439 440 441 442 443 444
        else
        {
            QList<QGraphicsItem*> tracks = items( 0, event->pos().y() );
            foreach ( QGraphicsItem* item, tracks )
            {
                GraphicsTrack   *track = qgraphicsitem_cast<GraphicsTrack*>( item );
445
                if ( track != NULL && track->mediaType() == Workflow::VideoTrack )
446
                {
447
                    m_dragEffectItem->setWidth( m_dragEffectItem->helper()->length() );
448 449
                    m_dragEffectItem->setStartPos( 0 );
                    m_dragEffectItem->setTrack( track );
450
                    m_dragEffectItem->effectHelper()->setTarget( track->trackWorkflow() );
451
                    break ;
452 453 454
                }
            }
        }
455
    }
456
}
Ludovic Fauvet's avatar
Ludovic Fauvet committed
457

458
void
459
TracksView::moveItem( TrackWorkflow *tw, const QUuid& uuid, qint64 time )
460 461 462 463 464
{
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    for ( int i = 0; i < sceneItems.size(); ++i )
    {
465 466 467
        AbstractGraphicsItem* item =
                dynamic_cast<AbstractGraphicsItem*>( sceneItems.at( i ) );
        if ( !item || item->uuid() != uuid )
468
            continue;
469
        moveItem( item, tw->trackId(), time );
470
        break ;
471
    }
472 473
    updateDuration();
    Timeline::getInstance()->tracksRuler()->update();
474 475
}

476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
QPoint
TracksView::boundEffectInClip( GraphicsEffectItem *effectItem, QPoint position )
{
    if ( effectItem->effectHelper()->target()->effectType() == EffectUser::ClipEffectUser )
    {
        QList<QGraphicsItem*>   list = effectItem->collidingItems( Qt::IntersectsItemShape );
        foreach ( QGraphicsItem *graphicsItem, list )
        {
            AbstractGraphicsMediaItem   *mediaItem = dynamic_cast<AbstractGraphicsMediaItem*>( graphicsItem );
            if ( mediaItem == NULL )
                continue ;
            if ( position.x() < mediaItem->pos().x() )
                position.setX( mediaItem->pos().x() );
            if ( position.x() + effectItem->width() > mediaItem->pos().x() + mediaItem->width() )
                position.setX( mediaItem->pos().x() + mediaItem->width() - effectItem->width() );
        }
    }
    return position;
}

496
void
497
TracksView::moveItem( AbstractGraphicsItem *item, QPoint position )
498
{
499
    GraphicsTrack *track = NULL;
500 501

    if ( !m_lastKnownTrack )
502
        m_lastKnownTrack = getTrack( Workflow::VideoTrack, 0 );
503

504 505 506 507 508
    QPoint  mappedPos = mapToScene( position ).toPoint();

    GraphicsEffectItem  *effectItem = qgraphicsitem_cast<GraphicsEffectItem*>( item );
    if ( effectItem != NULL )
        mappedPos = boundEffectInClip( effectItem, mappedPos );
509
    QList<QGraphicsItem*> list = items( 0, position.y() );
510 511 512
    for ( int i = 0; i < list.size(); ++i )
    {
        track = qgraphicsitem_cast<GraphicsTrack*>( list.at(i) );
513 514
        if ( track )
            break;
515 516
    }

517 518 519 520 521
    if ( !track )
    {
        // When the mouse pointer is not on a track,
        // use the last known track.
        // This avoids "breaks" when moving a rush
522
        if ( !m_lastKnownTrack )
523
            return;
524
        track = m_lastKnownTrack;
525 526
    }

527
    m_lastKnownTrack = track;
528

529
    qreal time = mappedPos.x() + 0.5;
530
    moveItem( item, track->trackNumber(), (qint64)time);
531 532
}

533
void
534
TracksView::moveItem( AbstractGraphicsItem *item, qint32 track, qint64 time )
535
{
536
    // Add missing tracks
537
    if ( item->trackType() == Workflow::AudioTrack )
538 539
    {
        while ( track >= m_numAudioTrack )
540
            addTrack( Workflow::AudioTrack );
541
    }
542
    else if ( item->trackType() == Workflow::VideoTrack )
543 544
    {
        while ( track >= m_numVideoTrack )
545
            addTrack( Workflow::VideoTrack );
546
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
547

548 549
    ItemPosition p = findPosition( item, track, time );

550
    if ( p.isValid() && item->groupItem() )
551 552 553 554
    {
        bool validPosFound = false;

        // Add missing tracks for the target
555
        if ( item->groupItem()->trackType() == Workflow::AudioTrack )
556
        {
557
            while ( p.track() >= m_numAudioTrack )
558
                addTrack( Workflow::AudioTrack );
559
        }
560
        else if ( item->groupItem()->trackType() == Workflow::VideoTrack )
561
        {
562
            while ( p.track() >= m_numVideoTrack )
563
                addTrack( Workflow::VideoTrack );
564 565 566 567 568
        }

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

569
        // Add missing tracks for the source
570
        if ( item->trackType() == Workflow::AudioTrack )
571 572
        {
            while ( p2.track() >= m_numAudioTrack )
573
                addTrack( Workflow::AudioTrack );
574
        }
575
        else if ( item->trackType() == Workflow::VideoTrack )
576 577
        {
            while ( p2.track() >= m_numVideoTrack )
578
                addTrack( Workflow::VideoTrack );
579 580
        }

581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
        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() );
611
            item->setTrack( getTrack( item->trackType(), p.track() ) );
612 613 614

            // Move the linked item to the target destination.
            item->groupItem()->setStartPos( p2.time() );
615
            item->groupItem()->setTrack( getTrack( item->groupItem()->trackType(), p2.track() ) );
616 617 618 619 620 621 622
        }
    }
    else
    {
        if ( p.isValid() )
        {
            item->setStartPos( p.time() );
623
            item->setTrack( getTrack( item->trackType(), p.track() ) );
624 625 626 627
        }
    }
}

628
ItemPosition
629
TracksView::findPosition( AbstractGraphicsItem *item, qint32 track, qint64 time )
630
{
631 632
    if ( qgraphicsitem_cast<GraphicsEffectItem*>( item ) != NULL )
        return ItemPosition( track, time );
633
    // Create a fake item for computing collisions
634
    QGraphicsRectItem *chkItem = new QGraphicsRectItem( item->boundingRect().adjusted( 0, 1, 0, -1 ) );
635
    chkItem->setParentItem( getTrack( item->trackType(), track ) );
636 637
    chkItem->setPos( time, 0 );

638
    QGraphicsItem *oldParent = item->parentItem();
639 640
    qreal oldPos = item->startPos();

641
    // Check for vertical collisions
642 643
    bool continueSearch = true;
    while ( continueSearch )
644
    {
645
        QList<QGraphicsItem*> colliding = chkItem->collidingItems( Qt::IntersectsItemShape );
646 647
        bool itemCollision = false;
        for ( int i = 0; i < colliding.size(); ++i )
648
        {
649
            AbstractGraphicsMediaItem *currentItem = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
650
            if ( currentItem && currentItem != item )
651
            {
652 653 654
                qint32  trackId = currentItem->trackNumber();
                Q_ASSERT( trackId >= 0 );

655 656
                // Collision with an item of the same type
                itemCollision = true;
657
                if ( trackId > track )
658
                {
659
                    if ( track < 1 )
660
                    {
661
                        chkItem->setParentItem( oldParent );
662 663 664 665 666
                        continueSearch = false;
                        break;
                    }
                    track -= 1;
                }
667
                else if ( trackId <= track )
668
                {
669
                    int higherTrack = 0;
670
                    if ( item->trackType() == Workflow::VideoTrack )
671
                        higherTrack = m_numVideoTrack;
672
                    else if ( item->trackType() == Workflow::AudioTrack )
673 674 675
                        higherTrack = m_numAudioTrack;

                    if ( track >= higherTrack - 1 )
676
                    {
677
                        chkItem->setParentItem( oldParent );
678 679 680 681 682
                        continueSearch = false;
                        break;
                    }
                    track += 1;
                }
683 684
                Q_ASSERT( getTrack( item->trackType(), track ) != NULL );
                chkItem->setParentItem( getTrack( item->trackType(), track ) );
685
            }
686
        }
687 688
        if ( !itemCollision )
            continueSearch = false;
689
    }
690 691


692
    // Check for horizontal collisions
693
    chkItem->setPos( qMax( time, (qint64)0 ), 0 );
694

695
    AbstractGraphicsMediaItem *hItem = NULL;
696
    QList<QGraphicsItem*> collide = chkItem->collidingItems( Qt::IntersectsItemShape );
697
    for ( int i = 0; i < collide.count(); ++i )
698
    {
699
        hItem = dynamic_cast<AbstractGraphicsMediaItem*>( collide.at( i ) );
700
        if ( hItem && hItem != item ) break;
701
    }
702

703
    if ( hItem && hItem != item )
704 705 706
    {
        qreal newpos;
        // Evaluate a possible solution
707
        if ( chkItem->pos().x() > hItem->pos().x() )
708 709
            newpos = hItem->pos().x() + hItem->boundingRect().width();
        else
710
            newpos = hItem->pos().x() - chkItem->boundingRect().width();
711

712
        if ( newpos < 0 || newpos == hItem->pos().x() )
713
            chkItem->setPos( oldPos, 0 ); // Fail
714
        else
715 716
        {
            // A solution may be found
717 718
            chkItem->setPos( qRound64( newpos ), 0 );
            QList<QGraphicsItem*> collideAgain = chkItem->collidingItems( Qt::IntersectsItemShape );
719 720
            for ( int i = 0; i < collideAgain.count(); ++i )
            {
721
                AbstractGraphicsMediaItem *currentItem =
722
                        dynamic_cast<AbstractGraphicsMediaItem*>( collideAgain.at( i ) );
723
                if ( currentItem && currentItem != item )
724
                {
725
                    chkItem->setPos( oldPos, 0 ); // Fail
726 727 728 729
                    break;
                }
            }
        }
730
    }
731

732
    GraphicsTrack *t = static_cast<GraphicsTrack*>( chkItem->parentItem() );
733

734 735 736
    ItemPosition p;
    p.setTime( chkItem->pos().x() );

737 738 739 740 741
    if ( t )
        p.setTrack( t->trackNumber() );
    else
        p.setTrack( -1 ); // Return in valid position

742 743
    delete chkItem;
    return p;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
744 745
}

746
void
747
TracksView::removeItem( TrackWorkflow *tw, const QUuid &uuid )
748
{
749
    GraphicsTrack           *track = getTrack( tw->type(), tw->trackId() );
750 751 752 753

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

755
    for ( int i = 0; i < trackItems.size(); ++i )
756
    {
757
        AbstractGraphicsItem    *item = dynamic_cast<AbstractGraphicsItem*>( trackItems.at( i ) );
758 759
        if ( !item || item->uuid() != uuid )
            continue;
760
        removeItem( item );
761 762 763
    }
}

764
void
765
TracksView::removeItem( AbstractGraphicsItem *item )
766
{
767 768 769 770
    // Is it the same item captured by mouse events
    if( item == m_actionItem )
        m_actionItem = NULL;

771
    m_itemsLoaded.remove( item->uuid() );
772 773
    delete item;
    updateDuration();
774 775
}

776 777
void
TracksView::dragLeaveEvent( QDragLeaveEvent *event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
778
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
779
    Q_UNUSED( event )
780 781 782 783
    bool updateDurationNeeded = false;
    if ( m_dragAudioItem || m_dragVideoItem )
        updateDurationNeeded = true;

Ludovic Fauvet's avatar
Ludovic Fauvet committed
784 785
    delete m_dragAudioItem;
    delete m_dragVideoItem;
786
    delete m_dragEffectItem;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
787 788
    m_dragAudioItem = NULL;
    m_dragVideoItem = NULL;
789
    m_dragEffectItem = NULL;
790 791 792

    if ( updateDurationNeeded )
        updateDuration();
793 794
}

795 796
void
TracksView::dropEvent( QDropEvent *event )
797
{
798 799
    qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );;

800
    if ( m_dragAudioItem != NULL || m_dragVideoItem != NULL )
801
    {
802
        Project::getInstance()->undoStack()->beginMacro( "Add clip" );
803

804 805
        if ( m_dragAudioItem )
        {
806
            m_itemsLoaded.insert( m_dragAudioItem->clipHelper()->uuid() );
807

808 809 810 811
            updateDuration();
            if ( getTrack( Workflow::AudioTrack, m_numAudioTrack - 1 )->childItems().count() > 0 )
                addTrack( Workflow::AudioTrack );
            event->acceptProposedAction();
812

813
            m_dragAudioItem->m_oldTrack = m_dragAudioItem->track()->trackWorkflow();
814

815
            Commands::trigger( new Commands::Clip::Add( m_dragAudioItem->clipHelper(),
816 817 818 819
                                                                    m_dragAudioItem->track()->trackWorkflow(),
                                                                    (qint64)mappedXPos ) );
            m_dragAudioItem = NULL;
        }
820

821 822
        if ( m_dragVideoItem )
        {
823
            m_itemsLoaded.insert( m_dragVideoItem->clipHelper()->uuid() );
824

825 826 827 828
            updateDuration();
            if ( getTrack( Workflow::VideoTrack, m_numVideoTrack - 1 )->childItems().count() > 0 )
                addTrack( Workflow::VideoTrack );
            event->acceptProposedAction();
829

830
            m_dragVideoItem->m_oldTrack = m_dragVideoItem->track()->trackWorkflow();
831

832
            Commands::trigger( new Commands::Clip::Add( m_dragVideoItem->clipHelper(),
833 834 835 836
                                                                    m_dragVideoItem->track()->trackWorkflow(),
                                                                    (qint64)mappedXPos ) );
            m_dragVideoItem = NULL;
        }
837

838
        Project::getInstance()->undoStack()->endMacro();
839 840 841

        m_lastKnownTrack = NULL;
    }
842
    else if ( m_dragEffectItem != NULL )
843
    {
844
        QList<AbstractGraphicsMediaItem*>   clips = mediaItems<AbstractGraphicsMediaItem>( event->pos() );
845
        if ( clips.size() > 0 )
846
        {
847
            m_itemsLoaded.insert( m_dragEffectItem->helper()->uuid() );
848
            AbstractGraphicsMediaItem   *item = clips.first();
849 850
            Commands::trigger( new Commands::Effect::Add( m_dragEffectItem->effectHelper(),
                                                          item->clipHelper()->clipWorkflow() ) );
851 852
            m_dragEffectItem->m_oldTrack = item->track()->trackWorkflow();
            event->acceptProposedAction();
853
            m_dragEffectItem->setContainer( item );
854
        }
855 856 857 858 859 860 861
        else
        {
            QList<QGraphicsItem*> tracks = items( 0, event->pos().y() );
            foreach ( QGraphicsItem* item, tracks )
            {
                GraphicsTrack   *track = qgraphicsitem_cast<GraphicsTrack*>( item );
                if ( track != NULL )
862
                {
863
                    m_itemsLoaded.insert( m_dragEffectItem->helper()->uuid() );
864 865 866 867
                    updateDuration();
                    if ( getTrack( Workflow::VideoTrack, m_numVideoTrack - 1 )->childItems().count() > 0 )
                        addTrack( Workflow::VideoTrack );
                    m_dragEffectItem->m_oldTrack = track->trackWorkflow();
868 869
                    Commands::trigger( new Commands::Effect::Add( m_dragEffectItem->effectHelper(),
                                                                  track->trackWorkflow() ) );
870

871
                    event->acceptProposedAction();
872
                    m_dragEffectItem->setContainer( NULL );
873 874
                    break ;
                }
875 876
            }
        }
877
    }
878 879
}

880 881
void
TracksView::setDuration( int duration )
882
{
883
    int diff = ( int ) qAbs( ( qreal )duration - sceneRect().width() );
884 885 886 887 888 889 890 891 892 893
    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;
}

894 895
void
TracksView::setTool( ToolButtons button )
896 897
{
    m_tool = button;
898 899
    if ( m_tool == TOOL_CUT )
        scene()->clearSelection();
900 901
}

902 903
void
TracksView::resizeEvent( QResizeEvent *event )
904 905 906 907
{
    QGraphicsView::resizeEvent( event );
}

908 909
void
TracksView::drawBackground( QPainter *painter, const QRectF &rect )
910
{
911 912
    // Fill the background
    painter->fillRect( rect, QBrush( palette().base() ) );
913

914 915
    // Draw the tracks separators
    painter->setPen( QPen( QColor( 72, 72, 72 ) ) );
916
    for ( int i = 0; i < m_layout->count(); ++i )
917
    {
918 919 920
        QGraphicsItem* gi = m_layout->itemAt( i )->graphicsItem();
        if ( !gi ) continue;
        GraphicsTrack* track = qgraphicsitem_cast<GraphicsTrack*>( gi );
921 922 923
        if ( !track ) continue;

        QRectF trackRect = track->mapRectToScene( track->boundingRect() );
924
        if ( track->mediaType() == Workflow::VideoTrack )
925
            painter->drawLine( rect.left(), trackRect.top(), rect.right() + 1, trackRect.top() );
926
        else
927
            painter->drawLine( rect.left(), trackRect.bottom(), rect.right() + 1, trackRect.bottom() );
928 929 930
    }

    // Audio/Video separator
931 932 933 934 935 936 937 938 939
    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 ) );
940
    painter->setPen( Qt::transparent );
941
    painter->drawRect( rect.left(),
942
                       (int) m_separator->y(),
943
                       (int) rect.right() + 1,
944
                       (int) m_separator->boundingRect().height() );
945

946
}
947

948 949
void
TracksView::mouseMoveEvent( QMouseEvent *event )
950
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
951 952
    if ( event->modifiers() == Qt::NoModifier &&
         event->buttons() == Qt::LeftButton &&
953
         m_action == TracksView::Move )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
954
    {
955 956 957 958
        // Check if the item exists or has been removed
        if ( m_actionItem == NULL )
            return;

959
        //Moving item.
960
        m_actionItem->setOpacity( 0.6F );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
961 962
        if ( m_actionRelativeX < 0 )
            m_actionRelativeX = event->pos().x() - mapFromScene( m_actionItem->pos() ).x();
963 964 965 966 967
        QPoint  pos( event->pos().x() - m_actionRelativeX, event->pos().y() );
        moveItem( m_actionItem, pos );
        GraphicsEffectItem  *effectItem = qgraphicsitem_cast<GraphicsEffectItem*>( m_actionItem );
        if ( effectItem != NULL )
        {
968
            QList<QGraphicsItem*>   list = m_actionItem->collidingItems();
969
            m_effectTarget = NULL;
970 971 972 973 974
            foreach ( QGraphicsItem *collidingItem, list )
            {
                AbstractGraphicsMediaItem   *mediaItem = dynamic_cast<AbstractGraphicsMediaItem*>( collidingItem );
                if ( mediaItem != NULL )
                {
975 976
                    ClipHelper  *clipHelper = qobject_cast<ClipHelper*>( mediaItem->helper() );
                    Q_ASSERT( clipHelper != NULL );
977
                    m_effectTarget = mediaItem;
978
//                    effectItem->effectHelper()->setTarget( clipHelper->clipWorkflow() );
979 980 981
                    break ;
                }
            }
982 983 984 985 986
//            if ( m_effectTarget == NULL ) //Avoid doing this all the time.
//            {
//                GraphicsTrack *track = getTrack( m_actionItem->trackType(), m_actionItem->trackNumber() );
//                effectItem->effectHelper()->setTarget( track->trackWorkflow() );
//            }
987
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
988
    }
989 990
    else if ( event->modifiers() == Qt::NoModifier &&
              event->buttons() == Qt::LeftButton &&
991
              m_action == TracksView::Resize )
992
    {
993 994 995 996 997 998
        qint64  itemPos = m_actionItem->mapToScene( 0, 0 ).x();
        qint64  itemNewSize;
        if ( m_actionResizeType == AbstractGraphicsItem::BEGINNING )
            itemNewSize = mapToScene( event->pos() ).x() - itemPos;
        else
            itemNewSize = itemPos + m_actionItem->width() - mapToScene( event->pos() ).x();
999

1000 1001 1002
        qint32  trackId = m_actionItem->trackNumber();
        Q_ASSERT( trackId >= 0 );

1003
        //FIXME: BEGIN UGLY
1004
        GraphicsTrack *track = getTrack( m_actionItem->trackType(), trackId );
1005 1006 1007
        Q_ASSERT( track );

        QPointF collidePos = track->sceneBoundingRect().topRight();
1008
        collidePos.setX( itemPos + itemNewSize );
1009 1010 1011 1012

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

        bool collide = false;
1013
        if ( m_actionItem->type() != GraphicsEffectItem::Type )
1014
        {
1015
            for ( int i = 0; i < gi.count(); ++i )
1016
            {
1017 1018 1019 1020 1021 1022
                AbstractGraphicsMediaItem* mi = dynamic_cast<AbstractGraphicsMediaItem*>( gi.at( i ) );
                if ( mi && mi != m_actionItem )
                {
                    collide = true;
                    break;
                }
1023
            }
1024
        }
1025 1026
        // END UGLY
        if ( !collide )
1027
        {
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
            GraphicsEffectItem  *effectItem = qgraphicsitem_cast<GraphicsEffectItem*>( m_actionItem );
            if ( effectItem != NULL )
            {
                QList<QGraphicsItem*>   list = m_actionItem->collidingItems();
                foreach ( QGraphicsItem *collidingItem, list )
                {
                    AbstractGraphicsMediaItem   *mediaItem = dynamic_cast<AbstractGraphicsMediaItem*>( collidingItem );
                    if ( mediaItem != NULL )
                    {
                        m_effectTarget = mediaItem;
                        break ;
                    }
                }
            }
1042
            if ( m_actionResizeType == AbstractGraphicsItem::END )
1043
            {
1044 1045
                m_actionItem->resize( itemNewSize, mapToScene( event->pos() ).x(),
                                      itemPos + itemNewSize, AbstractGraphicsItem::END );
1046 1047 1048
            }
            else
            {
1049 1050
                m_actionItem->resize( itemNewSize, itemPos + itemNewSize,
                                      itemPos, AbstractGraphicsItem::BEGINNING );
1051
            }
1052 1053
        }
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
1054

1055 1056 1057
    QGraphicsView::mouseMoveEvent( event );
}

1058 1059
void
TracksView::mousePressEvent( QMouseEvent *event )
1060
{
1061
    QList<AbstractGraphicsItem*> mediaCollisionList = mediaItems<AbstractGraphicsItem>( event->pos() );
1062