customtrackview.cpp 321 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/***************************************************************************
 *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   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          *
 ***************************************************************************/


#include "customtrackview.h"
22
#include "docclipbase.h"
23 24
#include "clipitem.h"
#include "definitions.h"
25 26 27 28 29 30 31 32 33 34 35 36 37 38
#include "commands/moveclipcommand.h"
#include "commands/movetransitioncommand.h"
#include "commands/resizeclipcommand.h"
#include "commands/editguidecommand.h"
#include "commands/addtimelineclipcommand.h"
#include "commands/addeffectcommand.h"
#include "commands/editeffectcommand.h"
#include "commands/moveeffectcommand.h"
#include "commands/addtransitioncommand.h"
#include "commands/edittransitioncommand.h"
#include "commands/editkeyframecommand.h"
#include "commands/changespeedcommand.h"
#include "commands/addmarkercommand.h"
#include "commands/razorclipcommand.h"
39
#include "kdenlivesettings.h"
40
#include "transition.h"
41
#include "clipmanager.h"
42
#include "renderer.h"
43
#include "markerdialog.h"
44
#include "mainwindow.h"
45
#include "ui_keyframedialog_ui.h"
46
#include "clipdurationdialog.h"
47
#include "abstractgroupitem.h"
48
#include "commands/insertspacecommand.h"
49
#include "spacerdialog.h"
50
#include "commands/addtrackcommand.h"
51
#include "commands/changeeffectstatecommand.h"
52
#include "commands/movegroupcommand.h"
53
#include "ui_addtrack_ui.h"
54
#include "initeffects.h"
55 56 57 58
#include "commands/locktrackcommand.h"
#include "commands/groupclipscommand.h"
#include "commands/splitaudiocommand.h"
#include "commands/changecliptypecommand.h"
59
#include "trackdialog.h"
60
#include "tracksconfigdialog.h"
61 62 63
#include "commands/configtrackscommand.h"
#include "commands/rebuildgroupcommand.h"
#include "commands/razorgroupcommand.h"
64
#include "profilesdialog.h"
65

Simon Eugster's avatar
Simon Eugster committed
66 67 68
#include "lib/audio/audioEnvelope.h"
#include "lib/audio/audioCorrelation.h"

69 70 71 72 73
#include <KDebug>
#include <KLocale>
#include <KUrl>
#include <KIcon>
#include <KCursor>
74 75
#include <KMessageBox>
#include <KIO/NetAccess>
76 77 78 79 80 81 82 83

#include <QMouseEvent>
#include <QStylePainter>
#include <QGraphicsItem>
#include <QDomDocument>
#include <QScrollBar>
#include <QApplication>
#include <QInputDialog>
84

85

86 87 88 89
#if QT_VERSION >= 0x040600
#include <QGraphicsDropShadowEffect>
#endif

90 91
//#define DEBUG

92 93 94 95 96 97
bool sortGuidesList(const Guide *g1 , const Guide *g2)
{
    return (*g1).position() < (*g2).position();
}


98 99 100 101 102 103 104 105
//TODO:
// disable animation if user asked it in KDE's global settings
// http://lists.kde.org/?l=kde-commits&m=120398724717624&w=2
// needs something like below (taken from dolphin)
// #include <kglobalsettings.h>
// const bool animate = KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects;
// const int duration = animate ? 1500 : 1;

106
CustomTrackView::CustomTrackView(KdenliveDoc *doc, CustomTrackScene* projectscene, QWidget *parent) :
Simon Eugster's avatar
Simon Eugster committed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
    QGraphicsView(projectscene, parent),
    m_tracksHeight(KdenliveSettings::trackheight()),
    m_projectDuration(0),
    m_cursorPos(0),
    m_document(doc),
    m_scene(projectscene),
    m_cursorLine(NULL),
    m_operationMode(NONE),
    m_moveOpMode(NONE),
    m_dragItem(NULL),
    m_dragGuide(NULL),
    m_visualTip(NULL),
    m_animation(NULL),
    m_clickPoint(),
    m_autoScroll(KdenliveSettings::autoscroll()),
    m_pasteEffectsAction(NULL),
    m_ungroupAction(NULL),
    m_scrollOffset(0),
    m_clipDrag(false),
    m_findIndex(0),
    m_tool(SELECTTOOL),
    m_copiedItems(),
    m_menuPosition(),
    m_blockRefresh(false),
    m_selectionGroup(NULL),
    m_selectedTrack(0),
    m_audioCorrelator(NULL),
    m_audioAlignmentReference(NULL),
    m_controlModifier(false)
{
    if (doc) {
138
        m_commandStack = doc->commandStack();
Simon Eugster's avatar
Simon Eugster committed
139
    } else {
140
        m_commandStack = NULL;
Simon Eugster's avatar
Simon Eugster committed
141
    }
142
    m_ct = 0;
143 144
    setMouseTracking(true);
    setAcceptDrops(true);
145 146
    setFrameShape(QFrame::NoFrame);
    setLineWidth(0);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
147
    //setCacheMode(QGraphicsView::CacheBackground);
148
    setAutoFillBackground(false);
149
    setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
150 151
    setContentsMargins(0, 0, 0, 0);

152
    m_activeTrackBrush = KStatefulBrush(KColorScheme::View, KColorScheme::ActiveBackground, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
153

154 155 156 157
    m_animationTimer = new QTimeLine(800);
    m_animationTimer->setFrameRange(0, 5);
    m_animationTimer->setUpdateInterval(100);
    m_animationTimer->setLoopCount(0);
158

159
    m_tipColor = QColor(0, 192, 0, 200);
160
    m_tipPen.setColor(QColor(255, 255, 255, 100));
161
    m_tipPen.setWidth(3);
162

163 164 165
    const int maxHeight = m_tracksHeight * m_document->tracksCount();
    setSceneRect(0, 0, sceneRect().width(), maxHeight);
    verticalScrollBar()->setMaximum(maxHeight);
166 167 168 169
    verticalScrollBar()->setTracking(true);
    // repaint guides when using vertical scroll
    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshGuides()));

170
    m_cursorLine = projectscene->addLine(0, 0, 0, maxHeight);
171
    m_cursorLine->setZValue(1000);
172 173
    QPen pen1 = QPen();
    pen1.setWidth(1);
174
    pen1.setColor(palette().text().color());
175 176
    m_cursorLine->setPen(pen1);
    m_cursorLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
177

178
    connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotCheckMouseScrolling()));
179
    m_scrollTimer.setInterval(100);
180
    m_scrollTimer.setSingleShot(true);
181 182 183 184

    connect(&m_thumbsTimer, SIGNAL(timeout()), this, SLOT(slotFetchNextThumbs()));
    m_thumbsTimer.setInterval(500);
    m_thumbsTimer.setSingleShot(true);
185

186 187 188 189 190
    KIcon razorIcon("edit-cut");
    m_razorCursor = QCursor(razorIcon.pixmap(22, 22));

    KIcon spacerIcon("kdenlive-spacer-tool");
    m_spacerCursor = QCursor(spacerIcon.pixmap(22, 22));
191 192
}

193 194
CustomTrackView::~CustomTrackView()
{
195
    qDeleteAll(m_guides);
196
    m_guides.clear();
197
    m_waitingThumbs.clear();
Simon Eugster's avatar
Simon Eugster committed
198
    delete m_animationTimer;
199 200
}

201 202 203 204 205 206 207 208 209 210 211 212
//virtual
void CustomTrackView::keyPressEvent(QKeyEvent * event)
{
    if (event->key() == Qt::Key_Up) {
        slotTrackUp();
        event->accept();
    } else if (event->key() == Qt::Key_Down) {
        slotTrackDown();
        event->accept();
    } else QWidget::keyPressEvent(event);
}

213 214
void CustomTrackView::setDocumentModified()
{
215 216
    m_document->setModified(true);
}
217

218
void CustomTrackView::setContextMenu(QMenu *timeline, QMenu *clip, QMenu *transition, QActionGroup *clipTypeGroup, QMenu *markermenu)
219
{
220 221
    m_timelineContextMenu = timeline;
    m_timelineContextClipMenu = clip;
222
    m_clipTypeGroup = clipTypeGroup;
223
    connect(m_timelineContextMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition()));
224 225 226 227 228

    m_markerMenu = new QMenu(i18n("Go to marker..."), this);
    m_markerMenu->setEnabled(false);
    markermenu->addMenu(m_markerMenu);
    connect(m_markerMenu, SIGNAL(triggered(QAction *)), this, SLOT(slotGoToMarker(QAction *)));
229 230
    QList <QAction *> list = m_timelineContextClipMenu->actions();
    for (int i = 0; i < list.count(); i++) {
231
        if (list.at(i)->data().toString() == "paste_effects") m_pasteEffectsAction = list.at(i);
232
        else if (list.at(i)->data().toString() == "ungroup_clip") m_ungroupAction = list.at(i);
233 234
    }

235
    m_timelineContextTransitionMenu = transition;
236 237 238 239 240 241 242
    list = m_timelineContextTransitionMenu->actions();
    for (int i = 0; i < list.count(); i++) {
        if (list.at(i)->data().toString() == "auto") {
            m_autoTransition = list.at(i);
            break;
        }
    }
243 244 245 246 247 248 249 250 251

    m_timelineContextMenu->addSeparator();
    m_deleteGuide = new KAction(KIcon("edit-delete"), i18n("Delete Guide"), this);
    connect(m_deleteGuide, SIGNAL(triggered()), this, SLOT(slotDeleteTimeLineGuide()));
    m_timelineContextMenu->addAction(m_deleteGuide);

    m_editGuide = new KAction(KIcon("document-properties"), i18n("Edit Guide"), this);
    connect(m_editGuide, SIGNAL(triggered()), this, SLOT(slotEditTimeLineGuide()));
    m_timelineContextMenu->addAction(m_editGuide);
252 253
}

254 255 256 257 258 259 260 261 262 263 264
void CustomTrackView::slotDoResetMenuPosition()
{
    m_menuPosition = QPoint();
}

void CustomTrackView::slotResetMenuPosition()
{
    // after a short time (so that the action is triggered / or menu is closed, we reset the menu pos
    QTimer::singleShot(300, this, SLOT(slotDoResetMenuPosition()));
}

265 266
void CustomTrackView::checkAutoScroll()
{
267
    m_autoScroll = KdenliveSettings::autoscroll();
268 269
}

270
/*sQList <TrackInfo> CustomTrackView::tracksList() const {
271
    return m_scene->m_tracksList;
272
}*/
273

274 275

int CustomTrackView::getFrameWidth()
276
{
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
    return (int) (m_tracksHeight * m_document->mltProfile().display_aspect_num / m_document->mltProfile().display_aspect_den + 0.5);
}

void CustomTrackView::updateSceneFrameWidth()
{
    int frameWidth = getFrameWidth();
    QList<QGraphicsItem *> itemList = items();
    ClipItem *item;
    for (int i = 0; i < itemList.count(); i++) {
        if (itemList.at(i)->type() == AVWIDGET) {
            item = (ClipItem*) itemList.at(i);
            item->resetFrameWidth(frameWidth);
            item->resetThumbs(true);
        }
    }
}

bool CustomTrackView::checkTrackHeight()
{
    if (m_tracksHeight == KdenliveSettings::trackheight()) return false;
297
    m_tracksHeight = KdenliveSettings::trackheight();
298
    emit trackHeightChanged();
299 300 301
    QList<QGraphicsItem *> itemList = items();
    ClipItem *item;
    Transition *transitionitem;
302
    int frameWidth = getFrameWidth();
303 304
    bool snap = KdenliveSettings::snaptopoints();
    KdenliveSettings::setSnaptopoints(false);
305 306 307
    for (int i = 0; i < itemList.count(); i++) {
        if (itemList.at(i)->type() == AVWIDGET) {
            item = (ClipItem*) itemList.at(i);
308
            item->setRect(0, 0, item->rect().width(), m_tracksHeight - 1);
309
            item->setPos((qreal) item->startPos().frames(m_document->fps()), (qreal) item->track() * m_tracksHeight + 1);
310
            item->resetFrameWidth(frameWidth);
311
            item->resetThumbs(true);
312 313
        } else if (itemList.at(i)->type() == TRANSITIONWIDGET) {
            transitionitem = (Transition*) itemList.at(i);
314
            transitionitem->setRect(0, 0, transitionitem->rect().width(), m_tracksHeight / 3 * 2 - 1);
315
            transitionitem->setPos((qreal) transitionitem->startPos().frames(m_document->fps()), (qreal) transitionitem->track() * m_tracksHeight + m_tracksHeight / 3 * 2);
316 317
        }
    }
318
    double newHeight = m_tracksHeight * m_document->tracksCount() * matrix().m22();
319
    m_cursorLine->setLine(m_cursorLine->line().x1(), 0, m_cursorLine->line().x1(), newHeight - 1);
320 321

    for (int i = 0; i < m_guides.count(); i++) {
322
        QLineF l = m_guides.at(i)->line();
323
        l.setP2(QPointF(l.x2(), newHeight));
324
        m_guides.at(i)->setLine(l);
325 326
    }

327
    setSceneRect(0, 0, sceneRect().width(), m_tracksHeight * m_document->tracksCount());
328
//     verticalScrollBar()->setMaximum(m_tracksHeight * m_document->tracksCount());
329
    KdenliveSettings::setSnaptopoints(snap);
330
    viewport()->update();
331
    return true;
332 333
}

334 335 336 337 338 339 340 341
/** Zoom or move viewport on mousewheel
 *
 * If mousewheel+Ctrl, zooms in/out on the timeline.
 *
 * With Ctrl, moves viewport towards end of timeline if down/back,
 * opposite on up/forward.
 *
 * See also http://www.kdenlive.org/mantis/view.php?id=265 */
342 343
void CustomTrackView::wheelEvent(QWheelEvent * e)
{
344 345 346 347
    if (e->modifiers() == Qt::ControlModifier) {
        if (e->delta() > 0) emit zoomIn();
        else emit zoomOut();
    } else {
348
        if (e->delta() <= 0) horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep());
349 350
        else  horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep());
    }
351 352
}

353 354
int CustomTrackView::getPreviousVideoTrack(int track)
{
355
    track = m_document->tracksCount() - track - 1;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
356 357
    track --;
    for (int i = track; i > -1; i--) {
358
        if (m_document->trackInfoAt(i).type == VIDEOTRACK) return i + 1;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
359 360 361
    }
    return 0;
}
362

363

364 365 366 367
void CustomTrackView::slotFetchNextThumbs()
{
    if (!m_waitingThumbs.isEmpty()) {
        ClipItem *item = m_waitingThumbs.takeFirst();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
368
        while (item == NULL && !m_waitingThumbs.isEmpty()) {
369 370 371 372 373 374 375
            item = m_waitingThumbs.takeFirst();
        }
        if (item) item->slotFetchThumbs();
        if (!m_waitingThumbs.isEmpty()) m_thumbsTimer.start();
    }
}

376 377
void CustomTrackView::slotCheckMouseScrolling()
{
378 379 380 381 382 383 384 385
    if (m_scrollOffset == 0) {
        m_scrollTimer.stop();
        return;
    }
    horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_scrollOffset);
    m_scrollTimer.start();
}

386 387
void CustomTrackView::slotCheckPositionScrolling()
{
388 389
    // If mouse is at a border of the view, scroll
    if (m_moveOpMode != SEEK) return;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
390
    if (mapFromScene(m_cursorPos, 0).x() < 3) {
391 392
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 2);
        QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
393 394
        setCursorPos(mapToScene(QPoint(-2, 0)).x());
    } else if (viewport()->width() - 3 < mapFromScene(m_cursorPos + 1, 0).x()) {
395
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 2);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
396
        setCursorPos(mapToScene(QPoint(viewport()->width(), 0)).x() + 1);
397 398 399 400 401
        QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
    }
}


402
// virtual
403 404
void CustomTrackView::mouseMoveEvent(QMouseEvent * event)
{
405
    int pos = event->x();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
406
    int mappedXPos = qMax((int)(mapToScene(event->pos()).x() + 0.5), 0);
407
   
408
    double snappedPos = getSnapPointForPos(mappedXPos);
409
    emit mousePosition(mappedXPos);
410

411
    if (event->buttons() & Qt::MidButton) return;
412
    if (dragMode() == QGraphicsView::RubberBandDrag || (event->modifiers() == Qt::ControlModifier && m_tool != SPACERTOOL && m_operationMode != RESIZESTART && m_operationMode != RESIZEEND)) {
413
        event->setAccepted(true);
414
        m_moveOpMode = NONE;
415
        QGraphicsView::mouseMoveEvent(event);
416 417 418
        return;
    }

419
    if (event->buttons() != Qt::NoButton) {
420
        bool move = (event->pos() - m_clickEvent).manhattanLength() >= QApplication::startDragDistance();
421
        if (m_dragItem && m_tool == SELECTTOOL) {
422 423
            if (m_operationMode == MOVE && move) {
                QGraphicsView::mouseMoveEvent(event);
424 425
                // If mouse is at a border of the view, scroll
                if (pos < 5) {
426
                    m_scrollOffset = -30;
427 428
                    m_scrollTimer.start();
                } else if (viewport()->width() - pos < 10) {
429
                    m_scrollOffset = 30;
430
                    m_scrollTimer.start();
431 432 433
                } else if (m_scrollTimer.isActive()) {
                    m_scrollTimer.stop();
                }
434
            } else if (m_operationMode == RESIZESTART && move) {
435
                m_document->renderer()->pause();
436
                if (!m_controlModifier && m_dragItem->type() == AVWIDGET && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
437 438
                    AbstractGroupItem *parent = static_cast <AbstractGroupItem *>(m_dragItem->parentItem());
                    if (parent)
439
                        parent->resizeStart((int)(snappedPos - m_dragItemInfo.startPos.frames(m_document->fps())));
440 441 442
                } else {
                    m_dragItem->resizeStart((int)(snappedPos));
                }
443 444 445
                QString crop = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart(), KdenliveSettings::frametimecode());
                QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode());
                QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart() - m_dragItemInfo.cropStart, KdenliveSettings::frametimecode());
446
                emit displayMessage(i18n("Crop from start:") + ' ' + crop + ' ' + i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
447
            } else if (m_operationMode == RESIZEEND && move) {
448
                m_document->renderer()->pause();
449
                if (!m_controlModifier && m_dragItem->type() == AVWIDGET && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
450 451
                    AbstractGroupItem *parent = static_cast <AbstractGroupItem *>(m_dragItem->parentItem());
                    if (parent)
452
                        parent->resizeEnd((int)(snappedPos - m_dragItemInfo.endPos.frames(m_document->fps())));
453 454 455
                } else {
                    m_dragItem->resizeEnd((int)(snappedPos));
                }
456 457
                QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode());
                QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration() - m_dragItemInfo.cropDuration, KdenliveSettings::frametimecode());
458
                emit displayMessage(i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
459
            } else if (m_operationMode == FADEIN && move) {
460
                ((ClipItem*) m_dragItem)->setFadeIn((int)(mappedXPos - m_dragItem->startPos().frames(m_document->fps())));
461
            } else if (m_operationMode == FADEOUT && move) {
462
                ((ClipItem*) m_dragItem)->setFadeOut((int)(m_dragItem->endPos().frames(m_document->fps()) - mappedXPos));
463
            } else if (m_operationMode == KEYFRAME && move) {
464
                GenTime keyFramePos = GenTime(mappedXPos, m_document->fps()) - m_dragItem->startPos() + m_dragItem->cropStart();
465
                double pos = mapToScene(event->pos()).toPoint().y();
466
                QRectF br = m_dragItem->sceneBoundingRect();
467 468 469
                double maxh = 100.0 / br.height();
                pos = (br.bottom() - pos) * maxh;
                m_dragItem->updateKeyFramePos(keyFramePos, pos);
470 471
                QString position = m_document->timecode().getDisplayTimecodeFromFrames(m_dragItem->editedKeyFramePos(), KdenliveSettings::frametimecode());
                emit displayMessage(position + " : " + QString::number(m_dragItem->editedKeyFrameValue()), InformationMessage);
472
            }
473
            removeTipAnimation();
474 475
            return;
        } else if (m_operationMode == MOVEGUIDE) {
476
            removeTipAnimation();
477 478
            QGraphicsView::mouseMoveEvent(event);
            return;
479
        } else if (m_operationMode == SPACER && move && m_selectionGroup) {
480
            // spacer tool
481
            snappedPos = getSnapPointForPos(mappedXPos + m_spacerOffset);
482
            if (snappedPos < 0) snappedPos = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
483

484 485 486 487 488 489
            // Make sure there is no collision
            QList<QGraphicsItem *> children = m_selectionGroup->childItems();
            QPainterPath shape = m_selectionGroup->clipGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
            QList<QGraphicsItem*> collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
            collidingItems.removeAll(m_selectionGroup);
            for (int i = 0; i < children.count(); i++) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
490 491
                if (children.at(i)->type() == GROUPWIDGET) {
                    QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
492
                    for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
493 494
                        collidingItems.removeAll(subchildren.at(j));
                }
495 496 497
                collidingItems.removeAll(children.at(i));
            }
            bool collision = false;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
            int offset = 0;
            for (int i = 0; i < collidingItems.count(); i++) {
                if (!collidingItems.at(i)->isEnabled()) continue;
                if (collidingItems.at(i)->type() == AVWIDGET && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
                    AbstractClipItem *item = static_cast <AbstractClipItem *>(collidingItems.at(i));
                    // Moving backward, determine best pos
                    QPainterPath clipPath;
                    clipPath.addRect(item->sceneBoundingRect());
                    QPainterPath res = shape.intersected(clipPath);
                    offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
                }
            }
            snappedPos += offset;
            // make sure we have no collision
            shape = m_selectionGroup->clipGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
            collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
            collidingItems.removeAll(m_selectionGroup);
            for (int i = 0; i < children.count(); i++) {
                if (children.at(i)->type() == GROUPWIDGET) {
                    QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
518
                    for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
519 520 521 522 523
                        collidingItems.removeAll(subchildren.at(j));
                }
                collidingItems.removeAll(children.at(i));
            }

524
            for (int i = 0; i < collidingItems.count(); i++) {
525
                if (!collidingItems.at(i)->isEnabled()) continue;
526 527 528 529 530
                if (collidingItems.at(i)->type() == AVWIDGET) {
                    collision = true;
                    break;
                }
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
531 532


533 534 535 536 537 538
            if (!collision) {
                // Check transitions
                shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
                collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
                collidingItems.removeAll(m_selectionGroup);
                for (int i = 0; i < children.count(); i++) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
539 540
                    if (children.at(i)->type() == GROUPWIDGET) {
                        QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
541
                        for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
                            collidingItems.removeAll(subchildren.at(j));
                    }
                    collidingItems.removeAll(children.at(i));
                }
                offset = 0;

                for (int i = 0; i < collidingItems.count(); i++) {
                    if (collidingItems.at(i)->type() == TRANSITIONWIDGET && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
                        AbstractClipItem *item = static_cast <AbstractClipItem *>(collidingItems.at(i));
                        // Moving backward, determine best pos
                        QPainterPath clipPath;
                        clipPath.addRect(item->sceneBoundingRect());
                        QPainterPath res = shape.intersected(clipPath);
                        offset = qMax(offset, (int)(res.boundingRect().width() + 0.5));
                    }
                }

                snappedPos += offset;
                // make sure we have no collision
                shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
                collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
                collidingItems.removeAll(m_selectionGroup);
                for (int i = 0; i < children.count(); i++) {
                    if (children.at(i)->type() == GROUPWIDGET) {
                        QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
567
                        for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
568 569
                            collidingItems.removeAll(subchildren.at(j));
                    }
570 571 572 573 574 575 576 577 578
                    collidingItems.removeAll(children.at(i));
                }
                for (int i = 0; i < collidingItems.count(); i++) {
                    if (collidingItems.at(i)->type() == TRANSITIONWIDGET) {
                        collision = true;
                        break;
                    }
                }
            }
579

580 581
            if (!collision)
                m_selectionGroup->translate(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0);
582
            //m_selectionGroup->setPos(mappedXPos + (((int) m_selectionGroup->boundingRect().topLeft().x() + 0.5) - mappedClick) , m_selectionGroup->pos().y());
583
        }
584
    }
585

586 587
    if (m_tool == RAZORTOOL) {
        setCursor(m_razorCursor);
588 589
        //QGraphicsView::mouseMoveEvent(event);
        //return;
590 591 592
    } else if (m_tool == SPACERTOOL) {
        setCursor(m_spacerCursor);
        return;
593
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
594

595 596 597
    QList<QGraphicsItem *> itemList = items(event->pos());
    QGraphicsRectItem *item = NULL;
    OPERATIONTYPE opMode = NONE;
598

599 600 601 602 603 604
    if (itemList.count() == 1 && itemList.at(0)->type() == GUIDEITEM) {
        opMode = MOVEGUIDE;
    } else for (int i = 0; i < itemList.count(); i++) {
            if (itemList.at(i)->type() == AVWIDGET || itemList.at(i)->type() == TRANSITIONWIDGET) {
                item = (QGraphicsRectItem*) itemList.at(i);
                break;
605
            }
606
        }
607

608
    if (item && event->buttons() == Qt::NoButton) {
609
        AbstractClipItem *clip = static_cast <AbstractClipItem*>(item);
610 611
        if (m_tool == RAZORTOOL) {
            // razor tool over a clip, display current frame in monitor
612
            if (false && !m_blockRefresh && item->type() == AVWIDGET) {
613
                //TODO: solve crash when showing frame when moving razor over clip
614
                emit showClipFrame(((ClipItem *) item)->baseClip(), QPoint(), false, mappedXPos - (clip->startPos() - clip->cropStart()).frames(m_document->fps()));
615
            }
616
            event->accept();
617 618
            return;
        }
619 620 621 622 623

        if (m_selectionGroup && clip->parentItem() == m_selectionGroup) {
            // all other modes break the selection, so the user probably wants to move it
            opMode = MOVE;
        } else {
624 625 626 627 628
	    if (clip->rect().width() * transform().m11() < 15) {
		// If the item is very small, only allow move
		opMode = MOVE;
	    }
            else opMode = clip->operationMode(mapToScene(event->pos()));
629 630
        }

631
        const double size = 5;
632 633 634
        if (opMode == m_moveOpMode) {
            QGraphicsView::mouseMoveEvent(event);
            return;
635
        } else {
636
            removeTipAnimation();
637
        }
638
        m_moveOpMode = opMode;
639
        setTipAnimation(clip, opMode, size);
640
        ClipItem *ci = NULL;
641 642 643
        if (item->type() == AVWIDGET)
            ci = static_cast <ClipItem *>(item);
        QString message;
644
        if (opMode == MOVE) {
645
            setCursor(Qt::OpenHandCursor);
646 647
            if (ci) {
                message = ci->clipName() + i18n(":");
648 649 650 651 652 653 654 655 656 657 658 659 660
                message.append(i18n(" Position:") + m_document->timecode().getDisplayTimecode(ci->info().startPos, KdenliveSettings::frametimecode()));
                message.append(i18n(" Duration:") + m_document->timecode().getDisplayTimecode(ci->cropDuration(),  KdenliveSettings::frametimecode()));
                if (clip->parentItem() && clip->parentItem()->type() == GROUPWIDGET) {
                    AbstractGroupItem *parent = static_cast <AbstractGroupItem *>(clip->parentItem());
                    if (clip->parentItem() == m_selectionGroup)
                        message.append(i18n(" Selection duration:"));
                    else
                        message.append(i18n(" Group duration:"));
                    message.append(m_document->timecode().getDisplayTimecode(parent->duration(), KdenliveSettings::frametimecode()));
                    if (parent->parentItem() && parent->parentItem()->type() == GROUPWIDGET) {
                        AbstractGroupItem *parent2 = static_cast <AbstractGroupItem *>(parent->parentItem());
                        message.append(i18n(" Selection duration:") + m_document->timecode().getDisplayTimecode(parent2->duration(), KdenliveSettings::frametimecode()));
                    }
661 662
                }
            }
663
        } else if (opMode == RESIZESTART) {
664
            setCursor(KCursor("left_side", Qt::SizeHorCursor));
665 666
            if (ci)
                message = i18n("Crop from start: ") + m_document->timecode().getDisplayTimecode(ci->cropStart(), KdenliveSettings::frametimecode());
667
            if (item->type() == AVWIDGET && item->parentItem() && item->parentItem() != m_selectionGroup)
668
                message.append(i18n("Use Ctrl to resize only current item, otherwise all items in this group will be resized at once."));
669
        } else if (opMode == RESIZEEND) {
670
            setCursor(KCursor("right_side", Qt::SizeHorCursor));
671 672
            if (ci)
                message = i18n("Duration: ") + m_document->timecode().getDisplayTimecode(ci->cropDuration(), KdenliveSettings::frametimecode());
673
            if (item->type() == AVWIDGET && item->parentItem() && item->parentItem() != m_selectionGroup)
674
                message.append(i18n("Use Ctrl to resize only current item, otherwise all items in this group will be resized at once."));
675
        } else if (opMode == FADEIN || opMode == FADEOUT) {
676
            setCursor(Qt::PointingHandCursor);
677 678 679 680 681 682 683 684 685
            if (ci && opMode == FADEIN && ci->fadeIn()) {
                message = i18n("Fade in duration: ");
                message.append(m_document->timecode().getDisplayTimecodeFromFrames(ci->fadeIn(), KdenliveSettings::frametimecode()));
            } else if (ci && opMode == FADEOUT && ci->fadeOut()) {
                message = i18n("Fade out duration: ");
                message.append(m_document->timecode().getDisplayTimecodeFromFrames(ci->fadeOut(), KdenliveSettings::frametimecode()));
            } else {
                message = i18n("Drag to add or resize a fade effect.");
            }
686 687
        } else if (opMode == TRANSITIONSTART || opMode == TRANSITIONEND) {
            setCursor(Qt::PointingHandCursor);
688
            message = i18n("Click to add a transition.");
689 690 691 692
        } else if (opMode == KEYFRAME) {
            setCursor(Qt::PointingHandCursor);
            emit displayMessage(i18n("Move keyframe above or below clip to remove it, double click to add a new one."), InformationMessage);
        }
693 694 695

        if (!message.isEmpty())
            emit displayMessage(message, InformationMessage);
696
    } // no clip under mouse
697
    else if (m_tool == RAZORTOOL) {
698
        event->accept();
699 700
        return;
    } else if (opMode == MOVEGUIDE) {
701 702 703
        m_moveOpMode = opMode;
        setCursor(Qt::SplitHCursor);
    } else {
704
        removeTipAnimation();
705
        setCursor(Qt::ArrowCursor);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
706 707 708 709 710 711 712
        if (event->buttons() != Qt::NoButton && event->modifiers() == Qt::NoModifier) {
            QGraphicsView::mouseMoveEvent(event);
            m_moveOpMode = SEEK;
            setCursorPos(mappedXPos);
            slotCheckPositionScrolling();
            return;
        } else m_moveOpMode = NONE;
713
    }
714
    QGraphicsView::mouseMoveEvent(event);
715 716
}

717
// virtual
718 719
void CustomTrackView::mousePressEvent(QMouseEvent * event)
{
Jean-Baptiste Mardelle's avatar
Fix :  
Jean-Baptiste Mardelle committed
720
    setFocus(Qt::MouseFocusReason);
721
    m_menuPosition = QPoint();
722 723 724

    // special cases (middle click button or ctrl / shift click
    if (event->button() == Qt::MidButton) {
725
        emit playMonitor();
726 727 728 729 730
        m_blockRefresh = false;
        m_operationMode = NONE;
        return;
    }

731
    if (event->modifiers() & Qt::ShiftModifier) {
732
        // Rectangle selection
733
        setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
734 735 736 737 738 739 740
        setDragMode(QGraphicsView::RubberBandDrag);
        if (!(event->modifiers() & Qt::ControlModifier)) {
            resetSelectionGroup();
            scene()->clearSelection();
        }
        m_blockRefresh = false;
        m_operationMode = RUBBERSELECTION;
741
        QGraphicsView::mousePressEvent(event);
742 743 744
        return;
    }

745
    m_blockRefresh = true;
746
    m_dragGuide = NULL;
747

748
    if (m_tool != RAZORTOOL) activateMonitor();
749 750 751 752
    else if (m_document->renderer()->playSpeed() != 0.0) {
        m_document->renderer()->pause();
        return;
    }
753
    m_clickEvent = event->pos();
754 755

    // check item under mouse
756
    QList<QGraphicsItem *> collisionList = items(m_clickEvent);
757
    if (event->modifiers() == Qt::ControlModifier && m_tool != SPACERTOOL && collisionList.count() == 0) {
758
        // Pressing Ctrl + left mouse button in an empty area scrolls the timeline
759 760
        setDragMode(QGraphicsView::ScrollHandDrag);
        QGraphicsView::mousePressEvent(event);
761
        m_blockRefresh = false;
762
        m_operationMode = NONE;
763
        return;
764 765
    }

766 767
    // if a guide and a clip were pressed, just select the guide
    for (int i = 0; i < collisionList.count(); ++i) {
Alberto Villa's avatar
Alberto Villa committed
768 769 770
        if (collisionList.at(i)->type() == GUIDEITEM) {
            // a guide item was pressed
            m_dragGuide = (Guide *) collisionList.at(i);
771 772 773 774 775 776 777 778 779 780 781
            if (event->button() == Qt::LeftButton) { // move it
                m_dragGuide->setFlag(QGraphicsItem::ItemIsMovable, true);
                m_operationMode = MOVEGUIDE;
                // deselect all clips so that only the guide will move
                m_scene->clearSelection();
                resetSelectionGroup(false);
                updateSnapPoints(NULL);
                QGraphicsView::mousePressEvent(event);
                return;
            } else // show context menu
                break;
Alberto Villa's avatar
Alberto Villa committed
782
        }
783 784
    }

785
    // Find first clip, transition or group under mouse (when no guides selected)
786 787
    int ct = 0;
    AbstractGroupItem *dragGroup = NULL;
788 789
    AbstractClipItem *collisionClip = NULL;
    bool found = false;
790
    while (!m_dragGuide && ct < collisionList.count()) {
791
        if (collisionList.at(ct)->type() == AVWIDGET || collisionList.at(ct)->type() == TRANSITIONWIDGET) {
792
            collisionClip = static_cast <AbstractClipItem *>(collisionList.at(ct));
793 794 795
            if (collisionClip->isItemLocked())
                break;
            if (collisionClip == m_dragItem)
796
                collisionClip = NULL;
797 798
            else
                m_dragItem = collisionClip;
799
            found = true;
800
            m_dragItemInfo = m_dragItem->info();
801
            if (m_dragItem->parentItem() && m_dragItem->parentItem()->type() == GROUPWIDGET && m_dragItem->parentItem() != m_selectionGroup) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
802
                // kDebug()<<"// KLIK FOUND GRP: "<<m_dragItem->sceneBoundingRect();
803 804
                dragGroup = static_cast <AbstractGroupItem *>(m_dragItem->parentItem());
            }
805 806
            break;
        }
807
        ct++;
808
    }
809 810 811 812
    if (!found) {
        if (m_dragItem) emit clipItemSelected(NULL);
        m_dragItem = NULL;
    }
813 814
#if QT_VERSION >= 0x040600
    // Add shadow to dragged item, currently disabled because of painting artifacts
Simon Eugster's avatar
Simon Eugster committed
815
    //TODO: re-enable when fixed
816 817 818 819 820
    /*QGraphicsDropShadowEffect *eff = new QGraphicsDropShadowEffect();
    eff->setBlurRadius(5);
    eff->setOffset(3, 3);
    m_dragItem->setGraphicsEffect(eff);*/
#endif
821 822 823 824 825 826
    if (m_dragItem && m_dragItem->type() == TRANSITIONWIDGET) {
        // update transition menu action
        m_autoTransition->setChecked(static_cast<Transition *>(m_dragItem)->isAutomatic());
        m_autoTransition->setEnabled(true);
    } else m_autoTransition->setEnabled(false);

827 828 829
    // context menu requested
    if (event->button() == Qt::RightButton) {
        if (m_dragItem) {
830 831
            if (dragGroup) dragGroup->setSelected(true);
            else if (!m_dragItem->isSelected()) {
832
                resetSelectionGroup(false);
833 834 835
                m_scene->clearSelection();
                m_dragItem->setSelected(true);
            }
836 837 838 839 840 841 842 843 844 845
        } else if (!m_dragGuide) {
            // check if there is a guide close to mouse click
            QList<QGraphicsItem *> guidesCollisionList = items(event->pos().x() - 5, event->pos().y(), 10, 2); // a rect of height < 2 does not always collide with the guide
            for (int i = 0; i < guidesCollisionList.count(); i++) {
                if (guidesCollisionList.at(i)->type() == GUIDEITEM) {
                    m_dragGuide = static_cast <Guide *>(guidesCollisionList.at(i));
                    break;
                }
            }
            // keep this to support multiple guides context menu in the future (?)
846 847
            /*if (guidesCollisionList.at(0)->type() != GUIDEITEM)
                guidesCollisionList.removeAt(0);
848 849
            }
            if (!guidesCollisionList.isEmpty())
850
            m_dragGuide = static_cast <Guide *>(guidesCollisionList.at(0));*/
851
        }
852