TracksView.cpp 13 KB
Newer Older
1
/*****************************************************************************
2
 * TracksView.cpp: QGraphicsView that contains the TracksScene
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *****************************************************************************
 * Copyright (C) 2008-2009 the VLMC team
 *
 * Authors: Ludovic Fauvet <etix@l0cal.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

23
#include <QScrollBar>
24
#include <QMouseEvent>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
25
#include <QWheelEvent>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
26 27
#include <QGraphicsLinearLayout>
#include <QGraphicsWidget>
Ludovic Fauvet's avatar
Ludovic Fauvet committed
28
#include <QGraphicsSceneDragDropEvent>
29 30
#include <QtDebug>
#include <cmath>
31
#include "TracksView.h"
32
#include "Media.h"
33 34
#include "Library.h"
#include "GraphicsMovieItem.h"
35
#include "GraphicsCursorItem.h"
36

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

43
    m_tracksCount = mainWorkflow->getTrackCount();
44
    m_fps = 30;
45

Ludovic Fauvet's avatar
Ludovic Fauvet committed
46 47
    m_numAudioTrack = 0;
    m_numVideoTrack = 0;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
48
    m_dragItem = NULL;
Ludovic Fauvet's avatar
Ludovic Fauvet committed
49

50 51 52
    setMouseTracking( true );
    setAcceptDrops( true );
    setContentsMargins( 0, 0, 0, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
53
    setFrameStyle( QFrame::NoFrame );
54

55 56 57 58 59
    //// Adjust the height using the number of tracks
    //const int maxHeight = m_tracksHeight * m_tracksCount;
    //setSceneRect( 0, 0, sceneRect().width(), maxHeight );

    const int maxHeight = m_tracksHeight * 4;
60

61 62
    m_cursorLine = new GraphicsCursorItem( maxHeight, QPen( QColor( 220, 30, 30 ) ) );
    m_scene->addItem( m_cursorLine );
63

Ludovic Fauvet's avatar
Ludovic Fauvet committed
64 65
    createLayout();

66 67
    connect( m_cursorLine, SIGNAL( cursorPositionChanged(int) ),
             this, SLOT( ensureCursorVisible() ) );
68 69
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
70 71 72
void TracksView::createLayout()
{
    m_layout = new QGraphicsLinearLayout( Qt::Vertical );
73
    m_layout->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
74 75
    m_layout->setContentsMargins( 0, 0, 0, 0 );
    m_layout->setSpacing( 0 );
76
    m_layout->setPreferredWidth( 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
77 78

    QGraphicsWidget* container = new QGraphicsWidget();
79
    container->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
80 81 82
    container->setContentsMargins( 0, 0, 0, 0 );
    container->setLayout( m_layout );

83 84 85 86
    // Create the initial layout
    // - 1 video track
    // - a separator
    // - 1 audio track
Ludovic Fauvet's avatar
Ludovic Fauvet committed
87
    addVideoTrack();
88 89

    m_separator = new QGraphicsWidget();
90
    m_separator->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
91 92 93
    m_separator->setPreferredHeight( 20 );
    m_layout->insertItem( 1, m_separator );

Ludovic Fauvet's avatar
Ludovic Fauvet committed
94 95 96 97 98 99 100
    addAudioTrack();

    m_scene->addItem( container );
}

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

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

125 126 127 128
void TracksView::dragEnterEvent( QDragEnterEvent* event )
{
    if ( event->mimeData()->hasFormat( "vlmc/uuid" ) )
        event->acceptProposedAction();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
129 130 131 132 133 134 135 136 137 138 139

    QUuid uuid = QUuid( (const QString& )event->mimeData()->data( "vlmc/uuid" ) );
    Media* media = Library::getInstance()->getMedia( uuid );
    if ( !media ) return;

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

    if ( m_dragItem ) delete m_dragItem;
    m_dragItem = new GraphicsMovieItem( media );
    m_dragItem->setWidth( ( (double)media->getLength() / 1000 ) * m_fps );
    m_dragItem->setHeight( tracksHeight() );
140 141 142
    m_dragItem->setPos( mappedXPos, 0 );
    m_dragItem->setParentItem( m_layout->itemAt( 0 )->graphicsItem() );
    moveMediaItem( m_dragItem, event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
143 144 145 146 147
}

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

151 152 153
void TracksView::moveMediaItem( AbstractGraphicsMediaItem* item, QPoint position )
{
    int track = (unsigned int)( mapToScene( position ).y() / m_tracksHeight );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
154 155 156
    if ( track > m_numVideoTrack - 1)
        track = m_numVideoTrack - 1;

Ludovic Fauvet's avatar
Ludovic Fauvet committed
157
    qreal mappedXPos = ( mapToScene( position ).x() + 0.5 );
158 159 160 161 162

    QPointF oldPos = m_dragItem->pos();
    QGraphicsItem* oldParent = m_dragItem->parentItem();

    // Check for vertical collisions
Ludovic Fauvet's avatar
Ludovic Fauvet committed
163
    m_dragItem->setParentItem( m_layout->itemAt( track )->graphicsItem() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
164 165
    bool continueSearch = true;
    while ( continueSearch )
166
    {
Ludovic Fauvet's avatar
Ludovic Fauvet committed
167 168 169
        QList<QGraphicsItem*> colliding = m_dragItem->collidingItems( Qt::IntersectsItemShape );
        bool itemCollision = false;
        for ( int i = 0; i < colliding.size(); ++i )
170
        {
Ludovic Fauvet's avatar
Ludovic Fauvet committed
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
            AbstractGraphicsMediaItem* item = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
            if ( item )
            {
                // Collision with an item of the same type
                itemCollision = true;
                if ( item->pos().y() < position.y() )
                {
                    if ( track < 0 )
                    {
                        m_dragItem->setParentItem( oldParent );
                        continueSearch = false;
                        break;
                    }
                    track -= 1;
                    m_dragItem->setParentItem( m_layout->itemAt( track )->graphicsItem() );
                }
                else if ( item->pos().y() > position.y() )
                {
                    if ( track >= m_numVideoTrack - 1 )
                    {
                        m_dragItem->setParentItem( oldParent );
                        continueSearch = false;
                        break;
                    }
                    track += 1;
                    m_dragItem->setParentItem( m_layout->itemAt( track )->graphicsItem() );
                }
            }
199
        }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
200 201
        if ( !itemCollision )
            continueSearch = false;
202 203 204 205
    }

    // Check for horizontal collisions
    m_dragItem->setPos( mappedXPos, 0 );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
206
    QList<QGraphicsItem*> colliding = m_dragItem->collidingItems( Qt::IntersectsItemShape );
207 208 209 210 211 212 213 214
    for ( int i = 0; i < colliding.size(); ++i )
    {
        AbstractGraphicsMediaItem* item = dynamic_cast<AbstractGraphicsMediaItem*>( colliding.at( i ) );
        if ( item )
        {
            // Collision with an item of the same type
            // Restoring original position (horizontal)
            m_dragItem->setPos( oldPos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
215
            break;
216 217
        }
    }
Ludovic Fauvet's avatar
Ludovic Fauvet committed
218 219 220 221 222 223 224 225
}

void TracksView::dragLeaveEvent( QDragLeaveEvent* event )
{
    if ( m_dragItem )
    {
        delete m_dragItem;
        m_dragItem = NULL;
226
        updateDuration();
Ludovic Fauvet's avatar
Ludovic Fauvet committed
227
    }
228 229 230 231
}

void TracksView::dropEvent( QDropEvent* event )
{
232
    if ( m_dragItem )
233
    {
234
        updateDuration();
235 236 237
        if ( m_layout->itemAt( 0 )->graphicsItem()->childItems().count() > 0 )
            addVideoTrack();
        event->acceptProposedAction();
238 239 240 241 242 243 244 245 246

        int track = (unsigned int)( mapToScene( event->pos() ).y() / m_tracksHeight );
        if ( track > m_numVideoTrack - 1)
            track = m_numVideoTrack - 1;
        qreal mappedXPos = ( mapToScene( event->pos() ).x() + 0.5 );
        //FIXME this leaks, but it will be corrected once we really use Clip instead
        // of Media
        m_mainWorkflow->addClip( new Clip( m_dragItem->media() ), track, (qint64)mappedXPos );

247 248
        m_dragItem = NULL; // Temporary action
    }
249 250
}

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
void TracksView::setDuration( int duration )
{
    int diff = qAbs( duration - sceneRect().width() );
    if ( diff * matrix().m11() > -50 )
    {
        if ( matrix().m11() < 0.4 )
            setSceneRect( 0, 0, ( duration + 100 / matrix().m11() ), sceneRect().height() );
        else
            setSceneRect( 0, 0, ( duration + 300 ), sceneRect().height() );
    }
    m_projectDuration = duration;
}

void TracksView::resizeEvent( QResizeEvent* event )
{
    QGraphicsView::resizeEvent( event );
}

void TracksView::drawBackground( QPainter* painter, const QRectF& rect )
{
271 272 273
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

274
    painter->setBrush( QBrush( palette().dark().color(), Qt::Dense3Pattern ) );
275 276 277 278 279
    painter->setPen( Qt::transparent );
    painter->drawRect( r.left(), m_separator->y(),
                       r.right(),
                       m_separator->boundingRect().height() );

280
    /*QColor base = palette().button().color();
281 282 283 284 285 286 287 288 289 290 291 292
    QRectF r = rect;
    r.setWidth( r.width() + 1 );

    painter->setClipRect( r );
    painter->drawLine( r.left(), 0, r.right(), 0 );

    uint tracks = m_tracksCount;
    for ( uint i = 0; i < tracks; ++i )
        painter->drawLine( r.left(), m_tracksHeight * ( i + 1 ), r.right(), m_tracksHeight * ( i + 1 ) );

    int lowerLimit = m_tracksHeight * m_tracksCount + 1;
    if ( height() > lowerLimit )
293 294
        painter->fillRect( QRectF ( r.left(), lowerLimit, r.width(),
        height() - lowerLimit ), QBrush( base ) );*/
295
}
296 297 298 299 300 301 302 303

void TracksView::mouseMoveEvent( QMouseEvent* event )
{
    QGraphicsView::mouseMoveEvent( event );
}

void TracksView::mousePressEvent( QMouseEvent* event )
{
Ludovic Fauvet's avatar
Ludovic Fauvet committed
304

305
    /*QList<QGraphicsItem*> collisionList = items( event->pos() );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

    if ( event->modifiers() == Qt::ControlModifier && collisionList.count() == 0 )
    {
        setDragMode( QGraphicsView::ScrollHandDrag );
        QGraphicsView::mousePressEvent( event );
        return;
    }

    if ( event->modifiers() & Qt::ShiftModifier && collisionList.count() == 0 )
    {
        setDragMode( QGraphicsView::RubberBandDrag );
        if ( !event->modifiers() & Qt::ControlModifier )
            scene()->clearSelection();
        QGraphicsView::mousePressEvent( event );
        return;
    }

323
    QGraphicsView::mousePressEvent( event );*/
324 325
}

326
void TracksView::mouseReleaseEvent( QMouseEvent* event )
Ludovic Fauvet's avatar
Ludovic Fauvet committed
327
{
328 329
    /*setDragMode( QGraphicsView::NoDrag );
    QGraphicsView::mouseReleaseEvent( event );*/
Ludovic Fauvet's avatar
Ludovic Fauvet committed
330 331
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
void TracksView::wheelEvent( QWheelEvent* event )
{
    if ( event->modifiers() == Qt::ControlModifier )
    {
        // CTRL + WHEEL = Zoom
        if ( event->delta() > 0 )
            emit zoomIn();
        else
            emit zoomOut();
        event->accept();
    }
    else
    {
        //TODO should scroll the timeline
        event->ignore();
        QGraphicsView::wheelEvent( event );
    }
}

Ludovic Fauvet's avatar
Ludovic Fauvet committed
351
void TracksView::setCursorPos( int pos )
352
{
353 354
    if ( pos < 0 ) pos = 0;
    m_cursorLine->setCursorPos( pos );
Ludovic Fauvet's avatar
Ludovic Fauvet committed
355 356 357 358
}

int TracksView::cursorPos()
{
359
    return m_cursorLine->cursorPos();
360
}
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

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

    int diff = sceneRect().width() - m_projectDuration;
    if ( diff * matrix.m11() < 50 )
    {
        if ( matrix.m11() < 0.4 )
            setSceneRect( 0, 0, ( m_projectDuration + 100 / matrix.m11() ), sceneRect().height() );
        else
            setSceneRect( 0, 0, ( m_projectDuration + 300 ), sceneRect().height() );
    }
377
    centerOn( m_cursorLine );
378
}
379 380 381 382

void TracksView::ensureCursorVisible()
{
    if ( horizontalScrollBar()->isVisible() )
383 384 385 386 387 388
    {
        QRectF r( m_cursorLine->boundingRect().width() / 2,
                  m_cursorLine->boundingRect().height() / 2,
                  1, 1 );
        m_cursorLine->ensureVisible( r, 150, 50 );
    }
389
}
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

void TracksView::updateDuration()
{
    QList<QGraphicsItem*> sceneItems = m_scene->items();

    int projectDuration = 0;
    for ( int i = 0; i < sceneItems.size(); ++i )
    {
        AbstractGraphicsMediaItem* item =
                dynamic_cast<AbstractGraphicsMediaItem*>( sceneItems.at( i ) );
        if ( !item ) continue;
        if ( item->pos().x() + item->boundingRect().width() > projectDuration )
            projectDuration = item->pos().x() + item->boundingRect().width();
    }

    m_projectDuration = projectDuration;

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

    emit durationChanged( m_projectDuration );
}