main.qml 25.4 KB
Newer Older
luyikei's avatar
luyikei committed
1 2
import QtQuick 2.0
import QtQuick.Controls 1.4
3
import QtQuick.Dialogs 1.2
luyikei's avatar
luyikei committed
4 5 6 7 8 9

Rectangle {
    id: page
    anchors.fill: parent
    color: "#777777"
    border.width: 0
10
    focus: true
luyikei's avatar
luyikei committed
11

12
    property int length // in frames
13
    property int cursorPosition: 0 // in frames
14
    property int initPosOfCursor: 150
15 16
    property double ppu: 10 // Pixels Per minimum Unit
    property double unit: 3000 // In milliseconds therefore ppu / unit = Pixels Per milliseconds
luyikei's avatar
luyikei committed
17 18
    property double fps: 29.97
    property int maxZ: 100
19
    property int scale: 4
luyikei's avatar
luyikei committed
20
    property var allClips: [] // Actual clip item objects
21
    property var allClipsDict: ({}) // Actual clip item objects
22
    property var selectedClips: [] // Selected clip uuids
luyikei's avatar
luyikei committed
23
    property var groups: [] // list of lists of clip uuids
24
    property var linkedClipsDict: ({}) // Uuid
25
    property alias isMagneticMode: magneticModeButton.selected
26
    property bool isCutMode: false
27
    property bool dragging: false
luyikei's avatar
luyikei committed
28
    property int trackHeight: 60
luyikei's avatar
luyikei committed
29

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
    readonly property int magneticMargin: 25

    function findNewPosition( newPos, target, dragSource, useMagneticMode ) {
        if ( useMagneticMode === true ) {
            var leastDistance = ptof( magneticMargin );
            // Check two times
            for ( var k = 0; k < 2; ++k ) {
                for ( var j = 0; j < markers.count; ++j ) {
                    var mPos = markers.get( j ).position;
                    if ( Math.abs( newPos - mPos ) < leastDistance ) {
                        leastDistance = Math.abs( newPos - mPos );
                        newPos = mPos;
                    }
                    else if ( Math.abs( newPos + target.length - 1 - mPos ) < leastDistance ) {
                        leastDistance = Math.abs( newPos + target.length - 1 - mPos );
                        newPos = mPos - target.length + 1;
                    }
                }
            }
            // Magnet for the left edge of the timeline
            if ( newPos < ptof( magneticMargin ) )
                newPos = 0;
        }

        // Collision detection
        var isCollided = true;
        var currentTrack = trackContainer( target.type )["tracks"].get( target.newTrackId );
        if ( currentTrack )
            var clips = currentTrack["clips"];
        else
            return target.position;
        for ( j = 0; j < clips.count + 2 && isCollided; ++j ) {
            isCollided = false;
            for ( k = 0; k < clips.count; ++k ) {
                var clip = clips.get( k );
                if ( clip.uuid === target.uuid ||
                     ( clip.uuid === dragSource.uuid && target.newTrackId !== dragSource.newTrackId )
                   )
                    continue;
                var cPos = clip.uuid === dragSource.uuid ? ptof( dragSource.x ) : clip["position"];
                var cEndPos = clip["position"] + clip["length"] - 1;

                if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos )
                    isCollided = true;

                // HACK: If magnetic mode, consider clips bigger
                //       but not if "clip" is also selected because both of them will be moving
                //       and we want to keep the same distance between them as much as possible
                var clipMargin = useMagneticMode && findClipItem( clip.uuid ).selected === false ? ptof( magneticMargin ) : 0;
79 80
                cPos -= clipMargin;
                cEndPos += clipMargin;
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
                if ( cEndPos >= newPos && newPos + target.length - 1 >= cPos ) {
                    if ( cPos >= newPos ) {
                        if ( cPos - target.length + 1 > 0 )
                            newPos = cPos - target.length + clipMargin;
                        else
                            newPos = target.position;
                    } else {
                        newPos = cEndPos - clipMargin + 1;
                    }
                }

                if ( isCollided )
                    break;
            }
        }

        if ( isCollided ) {
            for ( k = 0; k < clips.count; ++k ) {
                clip = clips.get( k );
                if ( clip.uuid === target.uuid ||
                     ( clip.uuid === dragSource.uuid && target.newTrackId !== dragSource.newTrackId ) )
                    continue;
                cPos = clip.uuid === dragSource.uuid ? ptof( dragSource.x ) : clip["position"];
                cEndPos = clip["position"] + clip["length"] - 1;
                newPos = Math.max( newPos, cEndPos + 1 );
            }
        }

        return newPos;
    }

luyikei's avatar
luyikei committed
112 113
    function clearSelectedClips() {
        while ( selectedClips.length ) {
114
            var clip = findClipItem( selectedClips.pop() );
luyikei's avatar
luyikei committed
115 116
            if ( clip )
                clip.selected = false;
luyikei's avatar
luyikei committed
117 118 119
        }
    }

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
    function zerofill( number, width ) {
        var str = "" + number;
        while ( str.length < width ) {
            str = "0" + str;
        }
        return str;
    }

    function timecodeFromFrames( frames ) {
        var seconds = Math.floor( frames / Math.round( fps )  );
        var minutes = Math.floor( seconds / 60 );
        var hours = Math.floor( minutes / 60 );

        return zerofill( hours, 3 ) + ':' + // hours
                zerofill( minutes % 60, 2 ) + ':' + // minutes
                zerofill( seconds % 60, 2 ) + ':' + // seconds
                // The second Math.round prevents the first value from exceeding fps.
                // e.g. 30 % Math.round( 29.97 ) = 0
                zerofill( Math.floor( frames % Math.round( fps ) ), 2 ); // frames in a minute
    }

luyikei's avatar
luyikei committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    // Convert length in frames to pixels
    function ftop( frames )
    {
        return frames / fps * 1000 * ppu / unit;
    }

    // Convert length in pixels to frames
    function ptof( pixels )
    {
        return Math.round( pixels * fps / 1000 / ppu * unit );
    }

    function trackContainer( trackType )
    {
        if ( trackType === "Video" )
            return trackContainers.get( 0 );
        return trackContainers.get( 1 );
    }

    function addTrack( trackType )
    {
        trackContainer( trackType )["tracks"].append( { "clips": [] } );
    }

    function removeTrack( trackType )
    {
        var tracks = trackContainer( trackType )["tracks"];
        tracks.remove( tracks.count - 1 );
    }

    function addClip( trackType, trackId, clipDict )
    {
        var newDict = {};
        newDict["begin"] = clipDict["begin"];
        newDict["end"] = clipDict["end"];
        newDict["position"] = clipDict["position"];
        newDict["length"] = clipDict["length"];
178
        newDict["libraryUuid"] = clipDict["libraryUuid"];
luyikei's avatar
luyikei committed
179 180 181
        newDict["uuid"] = clipDict["uuid"];
        newDict["trackId"] = trackId;
        newDict["name"] = clipDict["name"];
182
        newDict["selected"] = clipDict["selected"] === false ? false : true ;
luyikei's avatar
luyikei committed
183
        var tracks = trackContainer( trackType )["tracks"];
184 185
        while ( trackId > tracks.count - 1 )
            addTrack( trackType );
luyikei's avatar
luyikei committed
186 187 188 189 190 191 192 193 194
        tracks.get( trackId )["clips"].append( newDict );
        return newDict;
    }

    function removeClipFromTrack( trackType, trackId, uuid )
    {
        var ret = false;
        var tracks = trackContainer( trackType )["tracks"];
        var clips = tracks.get( trackId )["clips"];
luyikei's avatar
luyikei committed
195

luyikei's avatar
luyikei committed
196 197 198 199 200 201 202 203 204 205 206 207 208
        for ( var j = 0; j < clips.count; j++ ) {
            var clip = clips.get( j );
            if ( clip.uuid === uuid ) {
                clips.remove( j );
                ret = true;
                j--;
            }
        }
        return ret;
    }

    function removeClipFromTrackContainer( trackType, uuid )
    {
209
        for ( var i = 0; i < trackContainer( trackType )["tracks"].count; i++  )
luyikei's avatar
luyikei committed
210 211 212
            removeClipFromTrack( trackType, i, uuid );
    }

213 214 215 216 217 218
    function removeClip( uuid )
    {
        removeClipFromTrackContainer( "Audio", uuid );
        removeClipFromTrackContainer( "Video", uuid );
    }

luyikei's avatar
luyikei committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    function findClipFromTrackContainer( trackType, uuid )
    {
        var tracks = trackContainer( trackType )["tracks"];
        for ( var i = 0; i < tracks.count; i++  ) {
            var clip = findClipFromTrack( trackType, i, uuid );
            if( clip )
                return clip;
        }

        return null;
    }

    function findClipFromTrack( trackType, trackId, uuid )
    {
        var clips = trackContainer( trackType )["tracks"].get( trackId )["clips"];
        for ( var j = 0; j < clips.count; j++ ) {
            var clip = clips.get( j );
            if ( clip.uuid === uuid )
                return clip;
        }
        return null;
    }

    function findClip( uuid )
    {
        var v = findClipFromTrackContainer( "Video", uuid );
        if ( !v )
            return findClipFromTrackContainer( "Audio", uuid );
        return v;
    }

250
    function findClipItem( uuid ) {
251
        return allClipsDict[uuid];
252 253
    }

luyikei's avatar
luyikei committed
254 255 256 257 258 259 260 261 262
    function adjustTracks( trackType ) {
        var tracks = trackContainer( trackType )["tracks"];

        while ( tracks.count > 1 && tracks.get( tracks.count - 1 )["clips"].count === 0 &&
               tracks.get( tracks.count - 2 )["clips"].count === 0 )
            removeTrack( trackType );

        if ( tracks.get( tracks.count - 1 )["clips"].count > 0 )
            addTrack( trackType );
luyikei's avatar
luyikei committed
263
    }
luyikei's avatar
luyikei committed
264

luyikei's avatar
luyikei committed
265
    function addMarker( pos ) {
luyikei's avatar
luyikei committed
266 267 268
        markers.append( {
                           "position": pos
                       } );
luyikei's avatar
luyikei committed
269 270
    }

luyikei's avatar
luyikei committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    function findMarker( pos ) {
        for ( var i = 0; i < markers.count; ++i ) {
            if ( markers.get( i )["position"] === pos ) {
                return markers.get( i );
            }
        }
        return null;
    }

    function removeMarker( pos ) {
        for ( var i = 0; i < markers.count; ++i ) {
            if ( markers.get( i )["position"] === pos ) {
                markers.remove( i );
                return;
            }
        }
    }

luyikei's avatar
luyikei committed
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
    function addGroup( clips ) {
        groups.push( clips );
    }

    function findGroup( uuid ) {
        for ( var i = 0; i < groups.length; ++i ) {
            var group = groups[i];
            for ( var j = 0; j < group.length; ++j ) {
                if ( group[j] === uuid )
                    return group;
            }
        }
        return null;
    }

    function removeGroup( uuid ) {
        for ( var i = 0; i < groups.length; ++i ) {
            var group = groups[i];
            for ( var j = 0; j < group.length; ++j ) {
                if ( group[j] === uuid ) {
                    groups.splice( i, 1 );
                    return;
                }
            }
        }
    }

316 317 318 319 320 321
    function updateLinkedClips( uuid ) {
        var item = findClipItem( uuid );
        if ( item )
            item.linkedClips = linkedClipsDict[uuid];
    }

322
    function zoomIn( ratio, scrollToCuror ) {
323 324 325
        var newPpu = ppu;
        var newUnit = unit;
        newPpu *= ratio;
326
        var contentXPos = ptof( sView.flickableItem.contentX );
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344

        // Don't be too narrow.
        while ( newPpu < 10 ) {
            newPpu *= 2;
            newUnit *= 2;
        }

        // Don't be too distant.
        while ( newPpu > 20 ) {
            newPpu /= 2;
            newUnit /= 2;
        }

        // Can't be more precise than 1000msec / fps.
        var mUnit = 1000 / fps;

        if ( newUnit < mUnit ) {
            newPpu /= ratio; // Restore the original scale.
345
            newPpu *= mUnit / newUnit;
346 347 348
            newUnit = mUnit;
        }

349
        // Make unit a multiple of 1 / fps.
350
        newPpu *= ( newUnit - ( newUnit % mUnit ) ) / newUnit;
351 352 353 354 355 356 357 358 359
        newUnit -= newUnit % mUnit;

        // If "almost" the same value, don't bother redrawing the ruler.
        if ( Math.abs( unit - newUnit ) > 0.01 )
            unit = newUnit;

        if ( Math.abs( ppu - newPpu ) > 0.0001 )
            ppu = newPpu;

360 361 362 363 364 365 366 367 368 369 370
        if ( scrollToCuror === true ) {
            // Let's scroll to the cursor position!
            var newContentX = cursor.x - sView.width / 2;
            // Never show the background behind the timeline
            if ( newContentX >= 0 && sView.flickableItem.contentWidth - newContentX > sView.width  )
                sView.flickableItem.contentX = newContentX;
        }
        else {
            sView.flickableItem.contentX = ftop( contentXPos );
        }

371

372 373 374
        scale = Math.floor( Math.log( newUnit / mUnit ) / Math.log( 2 ) - 1 );
        scale = Math.min( 9, scale );
        scale = Math.max( 0, scale );
375
        mainwindow.setScale( scale );
376 377
    }

378 379 380
    // Sort clips in a manner that clips won't overlap each other while they are being moved
    function sortSelectedClips() {
        // Workaround: We cannot sort selectedClips directly maybe because of a Qt bug
381
        var sorted = selectedClips.concat();
382
        sorted.sort(
383
                    function( clipAUuid, clipBUuid )
384
                    {
385 386
                        var clipA = findClipItem( clipAUuid );
                        var clipB = findClipItem( clipBUuid );
387 388 389 390 391
                        if ( clipA.newTrackId > clipA.trackId )
                        {
                            return - ( clipA.newTrackId - clipB.newTrackId );
                        }
                        else if ( clipA.newTrackId < clipA.trackId )
392 393 394 395 396 397 398
                        {
                            return clipA.newTrackId - clipB.newTrackId;
                        }
                        else if ( clipA.position > clipA.lastPosition )
                        {
                            return - ( clipA.position - clipB.position );
                        }
399
                        else if ( clipA.position < clipA.lastPosition )
400 401
                        {
                            return clipA.position - clipB.position;
402 403
                        };
                        return 0;
404 405
                    }
                    );
406
        selectedClips = sorted;
407
    }
408

409
    function dragFinished() {
410
        dragging = false;
411
        sortSelectedClips();
412 413 414 415

        // We don't want to rely on selectedClips while moving since it "will" be changed
        // I'm aware that it's not the best solution but it's the safest solution for sure
        var toMove = [];
416
        for ( var i = 0; i < selectedClips.length; ++i )
417 418 419 420
        {
            var clip = findClipItem( selectedClips[i] );
            toMove.push( [clip.uuid, clip.newTrackId, clip.position] );
        }
421
        for ( i = 0; i < toMove.length; ++i )
luyikei's avatar
luyikei committed
422
            workflow.moveClip( toMove[i][0], toMove[i][1], toMove[i][2] );
423

luyikei's avatar
luyikei committed
424 425 426 427
        adjustTracks( "Audio" );
        adjustTracks( "Video" );
    }

luyikei's avatar
luyikei committed
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
    ListModel {
        id: trackContainers

        ListElement {
            name: "Video"
            tracks: []
        }

        ListElement {
            name: "Audio"
            tracks: []
        }

        Component.onCompleted: {
            addTrack( "Video" );
            addTrack( "Audio" );
        }
    }

luyikei's avatar
luyikei committed
447 448 449 450
    ListModel {
        id: markers
    }

luyikei's avatar
luyikei committed
451 452
    MouseArea {
        id: selectionArea
453 454 455 456
        width: parent.width - initPosOfCursor
        height: audioTrackContainer.y + audioTrackContainer.height - videoTrackContainer.y
        y: videoTrackContainer.y
        x: initPosOfCursor
luyikei's avatar
luyikei committed
457 458 459 460

        onPressed: {
            clearSelectedClips();
            selectionRect.visible = true;
461 462
            selectionRect.x = mouseX + x;
            selectionRect.y = mouseY + y;
luyikei's avatar
luyikei committed
463 464
            selectionRect.width = 0;
            selectionRect.height = 0;
465
            selectionRect.initPos = Qt.point( mouseX + x, mouseY + y );
luyikei's avatar
luyikei committed
466 467 468 469
        }

        onPositionChanged: {
            if ( selectionRect.visible === true ) {
470 471 472 473
                selectionRect.x = Math.min( mouseX + x, selectionRect.initPos.x );
                selectionRect.y = Math.min( mouseY + y, selectionRect.initPos.y );
                selectionRect.width = Math.abs( mouseX + x - selectionRect.initPos.x );
                selectionRect.height = Math.abs( mouseY + y - selectionRect.initPos.y );
474
                selectionRect.selectClips();
luyikei's avatar
luyikei committed
475 476 477 478 479 480 481 482
            }
        }

        onReleased: {
            selectionRect.visible = false;
        }
    }

483 484
    ScrollView {
        id: sView
luyikei's avatar
luyikei committed
485 486
        height: page.height
        width: page.width
487 488 489 490

        readonly property int sViewPadding: 50

        flickableItem.contentWidth: Math.max( page.width, ftop( length ) + initPosOfCursor + sViewPadding )
491 492 493
        flickableItem.contentHeight: Math.max( sView.height,
                                              topArea.height + videoTrackContainer.height +
                                              containerMarginItem.height + audioTrackContainer.height )
luyikei's avatar
luyikei committed
494

495
        Flickable {
luyikei's avatar
luyikei committed
496

luyikei's avatar
luyikei committed
497 498
            interactive: false

499 500 501 502 503 504 505 506 507 508 509 510
            TrackContainer {
                y: topArea.height
                id: videoTrackContainer
                type: "Video"
                isUpward: true
                tracks: trackContainers.get( 0 )["tracks"]
            }

            Rectangle {
                id: containerMarginItem
                anchors.top: videoTrackContainer.bottom
                height: 20
511
                width: parent.width
512 513 514 515
                gradient: Gradient {
                    GradientStop {
                        position: 0.00;
                        color: "#797979"
516 517
                    }

518 519 520 521
                    GradientStop {
                        position: 0.748
                        color: "#959697"
                    }
522

523 524 525
                    GradientStop {
                        position: 0.986
                        color: "#858f99"
526
                    }
527
                }
528
            }
529

530 531 532 533 534 535 536 537 538 539 540 541 542 543
            TrackContainer {
                anchors.top: containerMarginItem.bottom
                id: audioTrackContainer
                type: "Audio"
                isUpward: false
                tracks: trackContainers.get( 1 )["tracks"]
            }

            Item {
                id: topArea
                width: parent.width
                height: 52
                x: topLeftArea.width
                y: sView.flickableItem.contentY
luyikei's avatar
luyikei committed
544

545 546 547 548 549 550 551 552
                Ruler {
                    id: ruler

                    Rectangle {
                        id: borderBottomOfRuler
                        width: parent.width
                        height: 1
                        color: "#111111"
luyikei's avatar
luyikei committed
553 554
                    }
                }
555

556 557 558 559 560
                Cursor {
                    id: cursor
                    anchors.top: ruler.bottom
                    z: 2000
                    height: page.height
561
                }
luyikei's avatar
luyikei committed
562

563 564 565 566 567 568 569 570
                Repeater {
                    model: markers
                    anchors.top: topArea.top
                    delegate: Marker {
                        position: model.position
                        markerModel: model
                    }
                }
luyikei's avatar
luyikei committed
571
            }
luyikei's avatar
luyikei committed
572

573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
            Rectangle {
                id: topLeftArea
                x: sView.flickableItem.contentX
                y: sView.flickableItem.contentY
                width: initPosOfCursor
                height: topArea.height
                color: "#333333"
                border.width: 1
                border.color: "#111111"

                Text {
                    id: cursorTimecode
                    x: 5
                    y: 2

                    text: timecodeFromFrames( cursorPosition )
                    color: "#EEEEEE"
                    font.pixelSize: parent.height / 4
                }

                Item {
                    id: properties
                    x: 5
                    y: parent.height / 2
                    width: parent.width - x * 2
                    height: parent.height / 2

luyikei's avatar
luyikei committed
600 601 602 603 604 605 606 607 608
                    Row {
                        spacing: 2

                        PropertyButton {
                            id: magneticModeButton
                            text: "M"
                            selected: true
                        }

609 610 611 612 613 614
                        PropertyButton {
                            id: zoomInButton
                            text: "+"
                            selected: false

                            onPressed: {
615
                                zoomIn( 2.0, true );
616 617 618 619 620 621 622 623 624 625
                                selected = false;
                            }
                        }

                        PropertyButton {
                            id: zoomOutButton
                            text: "-"
                            selected: false

                            onPressed: {
626
                                zoomIn( 0.5, true );
627 628 629
                                selected = false;
                            }
                        }
630 631 632 633 634 635 636 637 638 639 640

                        PropertyButton {
                            id: fxButton
                            text: "Fx"
                            selected: false

                            onPressed: {
                                workflow.showEffectStack();
                                selected = false;
                            }
                        }
641
                    }
luyikei's avatar
luyikei committed
642 643
                }
            }
luyikei's avatar
luyikei committed
644 645 646 647 648 649 650 651
        }
    }

    Rectangle {
        id: selectionRect
        visible: false
        color: "#999999cc"
        property point initPos
652 653 654 655 656 657 658 659 660 661

        function selectClips() {
            for ( var i = 0; i < allClips.length; ++i ) {
                var clip = allClips[i];
                var clipPos = clip.mapToItem( page, 0, 0 );
                if ( ( x - clip.width < clipPos.x && clipPos.x < x + width ) &&
                     ( y - clip.height < clipPos.y && clipPos.y < y + height ) )
                    clip.selected = true;
            }
        }
luyikei's avatar
luyikei committed
662 663
    }

664
    MessageDialog {
luyikei's avatar
luyikei committed
665
        id: removeSelectedClipsDialog
666 667 668 669 670
        title: "VLMC"
        text: qsTr( "Do you really want to remove selected clips?" )
        icon: StandardIcon.Question
        standardButtons: StandardButton.Yes | StandardButton.No
        onYes: {
671 672
            while ( selectedClips.length > 0 )
                workflow.removeClip( selectedClips[0] );
673 674 675 676 677
        }
    }

    Keys.onPressed: {
        if ( event.key === Qt.Key_Delete ) {
luyikei's avatar
luyikei committed
678
            removeSelectedClipsDialog.visible = true;
679
        }
680 681 682
        else if ( event.key === Qt.Key_Plus &&
                 event.modifiers & Qt.ControlModifier
                 && scale > 0 ) {
683
            zoomIn( 2, true );
684
        }
685 686 687
        else if ( event.key === Qt.Key_Minus &&
                 event.modifiers & Qt.ControlModifier &&
                 scale < 9 ) {
688
            zoomIn( 0.5, true );
689 690
        }
        event.accepted = true;
691 692
    }

luyikei's avatar
luyikei committed
693 694
    Connections {
        target: workflow
695 696 697 698 699

        onFpsChanged: {
            page.fps = fps;
        }

luyikei's avatar
luyikei committed
700
        onLengthChanged: {
luyikei's avatar
luyikei committed
701
            page.length = length;
702
        }
703 704 705 706

        onClipAdded: {
            var clipInfo = workflow.clipInfo( uuid );
            var type = clipInfo["audio"] ? "Audio" : "Video";
707
            clipInfo["selected"] = false;
708
            linkedClipsDict[uuid] = clipInfo["linkedClips"];
709
            addClip( type, clipInfo["trackId"], clipInfo );
luyikei's avatar
luyikei committed
710
            adjustTracks( type );
711 712 713 714 715 716
        }

        onClipMoved: {
            var clipInfo = workflow.clipInfo( uuid );
            var type = clipInfo["audio"] ? "Audio" : "Video";
            var oldClip = findClipFromTrackContainer( type, uuid );
717 718
            linkedClipsDict[uuid] = clipInfo["linkedClips"];
            updateLinkedClips( uuid );
719 720 721 722 723

            if ( clipInfo["trackId"] !== oldClip["trackId"] ) {
                addClip( type, clipInfo["trackId"], clipInfo );
                removeClipFromTrack( type, oldClip["trackId"], uuid );
            }
724 725
            findClipItem( uuid ).position = clipInfo["position"];
            findClipItem( uuid ).lastPosition = clipInfo["position"];
luyikei's avatar
luyikei committed
726
            adjustTracks( type );
727 728 729 730
        }

        onClipRemoved: {
            removeClip( uuid );
luyikei's avatar
luyikei committed
731 732
            adjustTracks( "Audio" );
            adjustTracks( "Video" );
733 734 735 736 737 738
        }

        onClipResized: {
            var clipInfo = workflow.clipInfo( uuid );
            var clip = findClipItem( uuid );
            clip.position = clipInfo["position"];
739
            clip.lastPosition = clipInfo["position"];
740 741
            clip.end = clipInfo["end"];
            clip.begin = clipInfo["begin"];
742
            clip.length = clipInfo["length"];
743
            clip.updateEffects( clipInfo );
744
        }
745 746

        onClipLinked: {
747 748 749 750
            linkedClipsDict[uuidA].push( uuidB );
            linkedClipsDict[uuidB].push( uuidA );
            updateLinkedClips( uuidA );
            updateLinkedClips( uuidB );
751
        }
752

753
        onClipUnlinked: {
754 755 756 757 758 759 760 761 762 763 764 765 766 767
            for ( var i = 0; i < linkedClipsDict[uuidA].length; ++i )
                if ( linkedClipsDict[uuidA][i] === uuidB )
                {
                    linkedClipsDict[uuidA].splice( i, 1 );
                    break;
                }
            for ( i = 0; i < linkedClipsDict[uuidB].length; ++i )
                if ( linkedClipsDict[uuidB][i] === uuidA )
                {
                    linkedClipsDict[uuidB].splice( i, 1 );
                    break;
                }
            updateLinkedClips( uuidA );
            updateLinkedClips( uuidB );
768 769
        }

770 771 772 773 774
        onEffectsUpdated: {
            var item = findClipItem( clipUuid );
            if ( item )
                item.updateEffects( workflow.clipInfo( clipUuid ) );
        }
775 776 777 778 779 780
    }

    Connections {
        target: mainwindow
        onScaleChanged: {
            // 10 levels
781
            if ( scale < scaleLevel )
782
                zoomIn( 0.5, false );
783
            else if ( scale > scaleLevel )
784
                zoomIn( 2, false );
785
            scale = scaleLevel;
luyikei's avatar
luyikei committed
786
        }
787 788 789 790 791 792
        onCutToolSelected: {
            isCutMode = true;
        }
        onSelectionToolSelected: {
            isCutMode = false;
        }
luyikei's avatar
luyikei committed
793
    }
794 795 796 797 798 799 800 801 802 803

    Connections {
        target: timeline
        onMarkerAdded: {
            addMarker( pos );
        }
        onMarkerRemoved: {
            removeMarker( pos );
        }
    }
luyikei's avatar
luyikei committed
804 805
}