customtrackview.cpp 345 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
#include "commands/moveclipcommand.h"
#include "commands/movetransitioncommand.h"
#include "commands/resizeclipcommand.h"
#include "commands/editguidecommand.h"
29
#include "commands/addextradatacommand.h"
30 31 32 33 34 35 36 37 38 39
#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"
40
#include "kdenlivesettings.h"
41
#include "transition.h"
42
#include "clipmanager.h"
43
#include "renderer.h"
44
#include "markerdialog.h"
45
#include "mainwindow.h"
46
#include "ui_keyframedialog_ui.h"
47
#include "clipdurationdialog.h"
48
#include "abstractgroupitem.h"
49
#include "commands/insertspacecommand.h"
50
#include "spacerdialog.h"
51
#include "commands/addtrackcommand.h"
52
#include "commands/changeeffectstatecommand.h"
53
#include "commands/movegroupcommand.h"
54
#include "ui_addtrack_ui.h"
55
#include "ui_importkeyframesdialog_ui.h"
56
#include "initeffects.h"
57 58 59 60
#include "commands/locktrackcommand.h"
#include "commands/groupclipscommand.h"
#include "commands/splitaudiocommand.h"
#include "commands/changecliptypecommand.h"
61
#include "trackdialog.h"
62
#include "tracksconfigdialog.h"
63 64
#include "commands/configtrackscommand.h"
#include "commands/rebuildgroupcommand.h"
65
#include "commands/refreshmonitorcommand.h"
66
#include "profilesdialog.h"
67

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

71
#include <KDebug>
72
#include <KLocalizedString>
73 74 75
#include <KUrl>
#include <KIcon>
#include <KCursor>
76 77
#include <KMessageBox>
#include <KIO/NetAccess>
78
#include <KFileDialog>
79 80 81 82 83 84 85 86

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

88

89 90 91 92
#if QT_VERSION >= 0x040600
#include <QGraphicsDropShadowEffect>
#endif

93
#define SEEK_INACTIVE (-1)
94

95 96
//#define DEBUG

97 98 99 100 101 102
bool sortGuidesList(const Guide *g1 , const Guide *g2)
{
    return (*g1).position() < (*g2).position();
}


103 104 105 106 107 108 109 110
//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;

111
CustomTrackView::CustomTrackView(KdenliveDoc *doc, CustomTrackScene* projectscene, QWidget *parent) :
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
112
    QGraphicsView(projectscene, parent)
113 114 115 116 117 118
  , m_tracksHeight(KdenliveSettings::trackheight())
  , m_projectDuration(0)
  , m_cursorPos(0)
  , m_document(doc)
  , m_scene(projectscene)
  , m_cursorLine(NULL)
119 120
  , m_operationMode(None)
  , m_moveOpMode(None)
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
  , m_dragItem(NULL)
  , m_dragGuide(NULL)
  , m_visualTip(NULL)
  , m_animation(NULL)
  , m_clickPoint()
  , m_autoScroll(KdenliveSettings::autoscroll())
  , m_timelineContextMenu(NULL)
  , m_timelineContextClipMenu(NULL)
  , m_timelineContextTransitionMenu(NULL)
  , m_markerMenu(NULL)
  , m_autoTransition(NULL)
  , m_pasteEffectsAction(NULL)
  , m_ungroupAction(NULL)
  , m_editGuide(NULL)
  , m_deleteGuide(NULL)
  , m_clipTypeGroup(NULL)
  , m_scrollOffset(0)
  , m_clipDrag(false)
  , m_findIndex(0)
140
  , m_tool(SelectTool)
141 142 143 144 145 146 147 148 149
  , m_copiedItems()
  , m_menuPosition()
  , m_blockRefresh(false)
  , m_selectionGroup(NULL)
  , m_selectedTrack(0)
  , m_spacerOffset(0)
  , m_audioCorrelator(NULL)
  , m_audioAlignmentReference(NULL)
  , m_controlModifier(false)
Simon Eugster's avatar
Simon Eugster committed
150 151
{
    if (doc) {
152
        m_commandStack = doc->commandStack();
Simon Eugster's avatar
Simon Eugster committed
153
    } else {
154
        m_commandStack = NULL;
Simon Eugster's avatar
Simon Eugster committed
155
    }
156
    m_ct = 0;
157 158
    setMouseTracking(true);
    setAcceptDrops(true);
159 160
    setFrameShape(QFrame::NoFrame);
    setLineWidth(0);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
161
    //setCacheMode(QGraphicsView::CacheBackground);
162
    setAutoFillBackground(false);
163
    setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
164 165
    setContentsMargins(0, 0, 0, 0);

166
    m_activeTrackBrush = KStatefulBrush(KColorScheme::View, KColorScheme::ActiveBackground, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
167

168 169 170 171
    m_animationTimer = new QTimeLine(800);
    m_animationTimer->setFrameRange(0, 5);
    m_animationTimer->setUpdateInterval(100);
    m_animationTimer->setLoopCount(0);
172

173
    m_tipColor = QColor(0, 192, 0, 200);
174
    m_tipPen.setColor(QColor(255, 255, 255, 100));
175
    m_tipPen.setWidth(3);
176

177 178 179
    const int maxHeight = m_tracksHeight * m_document->tracksCount();
    setSceneRect(0, 0, sceneRect().width(), maxHeight);
    verticalScrollBar()->setMaximum(maxHeight);
180 181 182 183
    verticalScrollBar()->setTracking(true);
    // repaint guides when using vertical scroll
    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshGuides()));

184
    m_cursorLine = projectscene->addLine(0, 0, 0, maxHeight);
185
    m_cursorLine->setZValue(1000);
186 187
    QPen pen1 = QPen();
    pen1.setWidth(1);
188
    pen1.setColor(palette().text().color());
189 190
    m_cursorLine->setPen(pen1);
    m_cursorLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
191

192
    connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotCheckMouseScrolling()));
193
    m_scrollTimer.setInterval(100);
194
    m_scrollTimer.setSingleShot(true);
195 196 197 198

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

200 201 202 203 204
    KIcon razorIcon("edit-cut");
    m_razorCursor = QCursor(razorIcon.pixmap(22, 22));

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

207 208
CustomTrackView::~CustomTrackView()
{
209
    qDeleteAll(m_guides);
210
    m_guides.clear();
211
    m_waitingThumbs.clear();
Simon Eugster's avatar
Simon Eugster committed
212
    delete m_animationTimer;
213 214
}

215 216 217 218 219 220 221 222 223 224 225 226
//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);
}

227 228
void CustomTrackView::setDocumentModified()
{
229 230
    m_document->setModified(true);
}
231

232
void CustomTrackView::setContextMenu(QMenu *timeline, QMenu *clip, QMenu *transition, QActionGroup *clipTypeGroup, QMenu *markermenu)
233
{
234 235
    m_timelineContextMenu = timeline;
    m_timelineContextClipMenu = clip;
236
    m_clipTypeGroup = clipTypeGroup;
237
    connect(m_timelineContextMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition()));
238 239 240 241

    m_markerMenu = new QMenu(i18n("Go to marker..."), this);
    m_markerMenu->setEnabled(false);
    markermenu->addMenu(m_markerMenu);
Laurent Montel's avatar
Laurent Montel committed
242
    connect(m_markerMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotGoToMarker(QAction*)));
243
    QList <QAction *> list = m_timelineContextClipMenu->actions();
244
    for (int i = 0; i < list.count(); ++i) {
245
        if (list.at(i)->data().toString() == "paste_effects") m_pasteEffectsAction = list.at(i);
246
        else if (list.at(i)->data().toString() == "ungroup_clip") m_ungroupAction = list.at(i);
247 248
        else if (list.at(i)->data().toString() == "A") m_audioActions.append(list.at(i));
        else if (list.at(i)->data().toString() == "A+V") m_avActions.append(list.at(i));
249 250
    }

251
    m_timelineContextTransitionMenu = transition;
252
    list = m_timelineContextTransitionMenu->actions();
253
    for (int i = 0; i < list.count(); ++i) {
254 255 256 257 258
        if (list.at(i)->data().toString() == "auto") {
            m_autoTransition = list.at(i);
            break;
        }
    }
259 260 261 262 263 264 265 266 267

    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);
268 269
}

270 271 272 273 274 275 276 277 278 279 280
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()));
}

281 282
void CustomTrackView::checkAutoScroll()
{
283
    m_autoScroll = KdenliveSettings::autoscroll();
284 285
}

286
/*sQList <TrackInfo> CustomTrackView::tracksList() const {
287
    return m_scene->m_tracksList;
288
}*/
289

290

291
int CustomTrackView::getFrameWidth() const
292
{
293 294 295 296 297 298 299 300
    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;
301
    for (int i = 0; i < itemList.count(); ++i) {
302
        if (itemList.at(i)->type() == AVWidget) {
Vincent PINON's avatar
Vincent PINON committed
303
            item = static_cast<ClipItem*>(itemList.at(i));
304 305 306 307 308 309 310 311 312
            item->resetFrameWidth(frameWidth);
            item->resetThumbs(true);
        }
    }
}

bool CustomTrackView::checkTrackHeight()
{
    if (m_tracksHeight == KdenliveSettings::trackheight()) return false;
313
    m_tracksHeight = KdenliveSettings::trackheight();
314
    emit trackHeightChanged();
315 316 317
    QList<QGraphicsItem *> itemList = items();
    ClipItem *item;
    Transition *transitionitem;
318
    int frameWidth = getFrameWidth();
319 320
    bool snap = KdenliveSettings::snaptopoints();
    KdenliveSettings::setSnaptopoints(false);
321
    for (int i = 0; i < itemList.count(); ++i) {
322
        if (itemList.at(i)->type() == AVWidget) {
Vincent PINON's avatar
Vincent PINON committed
323
            item = static_cast<ClipItem*>(itemList.at(i));
324
            item->setRect(0, 0, item->rect().width(), m_tracksHeight - 1);
325
            item->setPos((qreal) item->startPos().frames(m_document->fps()), (qreal) item->track() * m_tracksHeight + 1);
326
            item->resetFrameWidth(frameWidth);
327
            item->resetThumbs(true);
328
        } else if (itemList.at(i)->type() == TransitionWidget) {
Vincent PINON's avatar
Vincent PINON committed
329
            transitionitem = static_cast<Transition*>(itemList.at(i));
330
            transitionitem->setRect(0, 0, transitionitem->rect().width(), m_tracksHeight / 3 * 2 - 1);
331
            transitionitem->setPos((qreal) transitionitem->startPos().frames(m_document->fps()), (qreal) transitionitem->track() * m_tracksHeight + m_tracksHeight / 3 * 2);
332 333
        }
    }
334
    double newHeight = m_tracksHeight * m_document->tracksCount() * matrix().m22();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
335
    m_cursorLine->setLine(0, 0, 0, newHeight - 1);
336
    for (int i = 0; i < m_guides.count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
337
        m_guides.at(i)->setLine(0, 0, 0, newHeight - 1);
338 339
    }

340
    setSceneRect(0, 0, sceneRect().width(), m_tracksHeight * m_document->tracksCount());
341
    //     verticalScrollBar()->setMaximum(m_tracksHeight * m_document->tracksCount());
342
    KdenliveSettings::setSnaptopoints(snap);
343
    viewport()->update();
344
    return true;
345 346
}

347 348 349 350 351 352 353 354
/** 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 */
355 356
void CustomTrackView::wheelEvent(QWheelEvent * e)
{
357 358 359 360
    if (e->modifiers() == Qt::ControlModifier) {
        if (e->delta() > 0) emit zoomIn();
        else emit zoomOut();
    } else {
361
        if (e->delta() <= 0) horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep());
362 363
        else  horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep());
    }
364 365
}

366 367
int CustomTrackView::getPreviousVideoTrack(int track)
{
368
    track = m_document->tracksCount() - track - 1;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
369
    track --;
Laurent Montel's avatar
use --i  
Laurent Montel committed
370
    for (int i = track; i > -1; --i) {
371
        if (m_document->trackInfoAt(i).type == VideoTrack) return i + 1;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
372 373 374
    }
    return 0;
}
375

376

377 378 379 380
void CustomTrackView::slotFetchNextThumbs()
{
    if (!m_waitingThumbs.isEmpty()) {
        ClipItem *item = m_waitingThumbs.takeFirst();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
381
        while (item == NULL && !m_waitingThumbs.isEmpty()) {
382 383 384 385 386 387 388
            item = m_waitingThumbs.takeFirst();
        }
        if (item) item->slotFetchThumbs();
        if (!m_waitingThumbs.isEmpty()) m_thumbsTimer.start();
    }
}

389 390
void CustomTrackView::slotCheckMouseScrolling()
{
391 392 393 394 395 396 397 398
    if (m_scrollOffset == 0) {
        m_scrollTimer.stop();
        return;
    }
    horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_scrollOffset);
    m_scrollTimer.start();
}

399 400
void CustomTrackView::slotCheckPositionScrolling()
{
401
    // If mouse is at a border of the view, scroll
402
    if (m_moveOpMode != Seek) return;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
403
    if (mapFromScene(m_cursorPos, 0).x() < 3) {
404
        if (horizontalScrollBar()->value() == 0) return;
405 406
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 2);
        QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
407
        seekCursorPos(mapToScene(QPoint(-2, 0)).x());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
408
    } else if (viewport()->width() - 3 < mapFromScene(m_cursorPos + 1, 0).x()) {
409
        horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 2);
410
        seekCursorPos(mapToScene(QPoint(viewport()->width(), 0)).x() + 1);
411 412 413 414
        QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling()));
    }
}

415
void CustomTrackView::slotAlignPlayheadToMousePos()
416
{
417 418 419 420 421 422
    /* get curser point ref in screen coord */
    QPoint ps = QCursor::pos();
    /* get xPos in scene coord */
    int mappedXPos = qMax((int)(mapToScene(mapFromGlobal(ps)).x() + 0.5), 0);
    /* move playhead to new xPos*/
    seekCursorPos(mappedXPos);
423
}
424

425 426 427 428 429
int CustomTrackView::getMousePos() const
{
    return qMax((int)(mapToScene(mapFromGlobal(QCursor::pos())).x() + 0.5), 0);
}

430
// virtual
431 432
void CustomTrackView::mouseMoveEvent(QMouseEvent * event)
{
433
    int pos = event->x();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
434
    int mappedXPos = qMax((int)(mapToScene(event->pos()).x() + 0.5), 0);
435
    double snappedPos = getSnapPointForPos(mappedXPos);
436
    emit mousePosition(mappedXPos);
437

438
    if (m_operationMode == ScrollTimeline) {
439 440
        QGraphicsView::mouseMoveEvent(event);
        return;
441 442
    }

443
    if (event->buttons() & Qt::MidButton) return;
444
    if (dragMode() == QGraphicsView::RubberBandDrag || (event->modifiers() == Qt::ControlModifier && m_tool != SpacerTool && m_operationMode != ResizeStart && m_operationMode != ResizeEnd)) {
445
        event->setAccepted(true);
446
        m_moveOpMode = None;
447
        if (event->modifiers() != Qt::ControlModifier || dragMode() == QGraphicsView::RubberBandDrag) QGraphicsView::mouseMoveEvent(event);
448 449 450
        return;
    }

451
    if (event->buttons() != Qt::NoButton) {
452
        bool move = (event->pos() - m_clickEvent).manhattanLength() >= QApplication::startDragDistance();
453
        if (m_dragItem && move) m_clipDrag = true;
454 455
        if (m_dragItem && m_tool == SelectTool) {
            if (m_operationMode == MoveOperation && m_clipDrag) {
456
                QGraphicsView::mouseMoveEvent(event);
457 458
                // If mouse is at a border of the view, scroll
                if (pos < 5) {
459
                    m_scrollOffset = -30;
460 461
                    m_scrollTimer.start();
                } else if (viewport()->width() - pos < 10) {
462
                    m_scrollOffset = 30;
463
                    m_scrollTimer.start();
464 465 466
                } else if (m_scrollTimer.isActive()) {
                    m_scrollTimer.stop();
                }
467
            } else if (m_operationMode == ResizeStart && move) {
468
                m_document->renderer()->pause();
469
                if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
470 471
                    AbstractGroupItem *parent = static_cast <AbstractGroupItem *>(m_dragItem->parentItem());
                    if (parent)
472
                        parent->resizeStart((int)(snappedPos - m_dragItemInfo.startPos.frames(m_document->fps())));
473
                } else {
474
                    m_dragItem->resizeStart((int)(snappedPos), true, false);
475
                }
476 477 478
                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());
479
                emit displayMessage(i18n("Crop from start:") + ' ' + crop + ' ' + i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
480
            } else if (m_operationMode == ResizeEnd && move) {
481
                m_document->renderer()->pause();
482
                if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) {
483 484
                    AbstractGroupItem *parent = static_cast <AbstractGroupItem *>(m_dragItem->parentItem());
                    if (parent)
485
                        parent->resizeEnd((int)(snappedPos - m_dragItemInfo.endPos.frames(m_document->fps())));
486
                } else {
487
                    m_dragItem->resizeEnd((int)(snappedPos), false);
488
                }
489 490
                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());
491
                emit displayMessage(i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage);
492
            } else if (m_operationMode == FadeIn && move) {
Vincent PINON's avatar
Vincent PINON committed
493
                ((ClipItem*) m_dragItem)->setFadeIn(static_cast<int>(mappedXPos - m_dragItem->startPos().frames(m_document->fps())));
494
            } else if (m_operationMode == FadeOut && move) {
Vincent PINON's avatar
Vincent PINON committed
495
                ((ClipItem*) m_dragItem)->setFadeOut(static_cast<int>(m_dragItem->endPos().frames(m_document->fps()) - mappedXPos));
496
            } else if (m_operationMode == KeyFrame && move) {
497
                GenTime keyFramePos = GenTime(mappedXPos, m_document->fps()) - m_dragItem->startPos() + m_dragItem->cropStart();
498
                double pos = mapToScene(event->pos()).toPoint().y();
499
                QRectF br = m_dragItem->sceneBoundingRect();
500 501 502
                double maxh = 100.0 / br.height();
                pos = (br.bottom() - pos) * maxh;
                m_dragItem->updateKeyFramePos(keyFramePos, pos);
503 504
                QString position = m_document->timecode().getDisplayTimecodeFromFrames(m_dragItem->editedKeyFramePos(), KdenliveSettings::frametimecode());
                emit displayMessage(position + " : " + QString::number(m_dragItem->editedKeyFrameValue()), InformationMessage);
505
            }
506
            removeTipAnimation();
507
            return;
508
        } else if (m_operationMode == MoveGuide) {
509
            removeTipAnimation();
510 511
            QGraphicsView::mouseMoveEvent(event);
            return;
512
        } else if (m_operationMode == Spacer && move && m_selectionGroup) {
513
            // spacer tool
514
            snappedPos = getSnapPointForPos(mappedXPos + m_spacerOffset);
515
            if (snappedPos < 0) snappedPos = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
516

517 518
            // Make sure there is no collision
            QList<QGraphicsItem *> children = m_selectionGroup->childItems();
519
            QPainterPath shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
520 521
            QList<QGraphicsItem*> collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
            collidingItems.removeAll(m_selectionGroup);
522
            for (int i = 0; i < children.count(); ++i) {
523
                if (children.at(i)->type() == GroupWidget) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
524
                    QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
525
                    for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
526 527
                        collidingItems.removeAll(subchildren.at(j));
                }
528 529 530
                collidingItems.removeAll(children.at(i));
            }
            bool collision = false;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
531
            int offset = 0;
532
            for (int i = 0; i < collidingItems.count(); ++i) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
533
                if (!collidingItems.at(i)->isEnabled()) continue;
534
                if (collidingItems.at(i)->type() == AVWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
535 536 537 538 539 540 541 542 543 544
                    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
545
            shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
546 547
            collidingItems = scene()->items(shape, Qt::IntersectsItemShape);
            collidingItems.removeAll(m_selectionGroup);
548
            for (int i = 0; i < children.count(); ++i) {
549
                if (children.at(i)->type() == GroupWidget) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
550
                    QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
551
                    for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
552 553 554 555 556
                        collidingItems.removeAll(subchildren.at(j));
                }
                collidingItems.removeAll(children.at(i));
            }

557
            for (int i = 0; i < collidingItems.count(); ++i) {
558
                if (!collidingItems.at(i)->isEnabled()) continue;
559
                if (collidingItems.at(i)->type() == AVWidget) {
560 561 562 563
                    collision = true;
                    break;
                }
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
564 565


566 567 568 569 570
            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);
571
                for (int i = 0; i < children.count(); ++i) {
572
                    if (children.at(i)->type() == GroupWidget) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
573
                        QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
574
                        for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
575 576 577 578 579 580
                            collidingItems.removeAll(subchildren.at(j));
                    }
                    collidingItems.removeAll(children.at(i));
                }
                offset = 0;

581
                for (int i = 0; i < collidingItems.count(); ++i) {
582
                    if (collidingItems.at(i)->type() == TransitionWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
583 584 585 586 587 588 589 590 591 592 593 594 595 596
                        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);
597
                for (int i = 0; i < children.count(); ++i) {
598
                    if (children.at(i)->type() == GroupWidget) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
599
                        QList<QGraphicsItem *> subchildren = children.at(i)->childItems();
Till Theato's avatar
Till Theato committed
600
                        for (int j = 0; j < subchildren.count(); j++)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
601 602
                            collidingItems.removeAll(subchildren.at(j));
                    }
603 604
                    collidingItems.removeAll(children.at(i));
                }
605
                for (int i = 0; i < collidingItems.count(); ++i) {
606
                    if (collidingItems.at(i)->type() == TransitionWidget) {
607 608 609 610 611
                        collision = true;
                        break;
                    }
                }
            }
612

613 614
            if (!collision)
                m_selectionGroup->translate(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0);
615
            //m_selectionGroup->setPos(mappedXPos + (((int) m_selectionGroup->boundingRect().topLeft().x() + 0.5) - mappedClick) , m_selectionGroup->pos().y());
616
        }
617
    }
618

619
    if (m_tool == RazorTool) {
620
        setCursor(m_razorCursor);
621
    } else if (m_tool == SpacerTool) {
622
        setCursor(m_spacerCursor);
623
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
624

625 626
    QList<QGraphicsItem *> itemList = items(event->pos());
    QGraphicsRectItem *item = NULL;
627
    OperationType opMode = None;
628

629
    if (itemList.count() == 1 && itemList.at(0)->type() == GUIDEITEM) {
630
        opMode = MoveGuide;
631
        setCursor(Qt::SplitHCursor);
632
    } else for (int i = 0; i < itemList.count(); ++i) {
633
        if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) {
634
            item = (QGraphicsRectItem*) itemList.at(i);
635 636
            break;
        }
637 638
    }

639
    if (m_tool == SpacerTool) {
640 641 642
        event->accept();
        return;
    }
643

644
    if (item && event->buttons() == Qt::NoButton) {
645
        AbstractClipItem *clip = static_cast <AbstractClipItem*>(item);
646
        if (m_tool == RazorTool) {
647
            // razor tool over a clip, display current frame in monitor
648
            if (false && !m_blockRefresh && item->type() == AVWidget) {
649
                //TODO: solve crash when showing frame when moving razor over clip
Vincent PINON's avatar
Vincent PINON committed
650
                emit showClipFrame(static_cast<ClipItem*>(item)->baseClip(), QPoint(), false, mappedXPos - (clip->startPos() - clip->cropStart()).frames(m_document->fps()));
651
            }
652
            event->accept();
653 654
            return;
        }
655 656 657

        if (m_selectionGroup && clip->parentItem() == m_selectionGroup) {
            // all other modes break the selection, so the user probably wants to move it
658
            opMode = MoveOperation;
659
        } else {
660 661
            if (clip->rect().width() * transform().m11() < 15) {
                // If the item is very small, only allow move
662
                opMode = MoveOperation;
663
            }
664
            else opMode = clip->operationMode(mapToScene(event->pos()));
665 666
        }

667
        const double size = 5;
668 669 670
        if (opMode == m_moveOpMode) {
            QGraphicsView::mouseMoveEvent(event);
            return;
671
        } else {
672
            removeTipAnimation();
673
        }
674
        m_moveOpMode = opMode;
675
        setTipAnimation(clip, opMode, size);
676
        ClipItem *ci = NULL;
677
        if (item->type() == AVWidget)
678 679
            ci = static_cast <ClipItem *>(item);
        QString message;
680
        if (opMode == MoveOperation) {
681
            setCursor(Qt::OpenHandCursor);
682 683
            if (ci) {
                message = ci->clipName() + i18n(":");
684 685
                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()));
686
                if (clip->parentItem() && clip->parentItem()->type() == GroupWidget) {
687 688 689 690 691 692
                    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()));
693
                    if (parent->parentItem() && parent->parentItem()->type() == GroupWidget) {
694 695 696
                        AbstractGroupItem *parent2 = static_cast <AbstractGroupItem *>(parent->parentItem());
                        message.append(i18n(" Selection duration:") + m_document->timecode().getDisplayTimecode(parent2->duration(), KdenliveSettings::frametimecode()));
                    }
697 698
                }
            }
699
        } else if (opMode == ResizeStart) {
700
            setCursor(KCursor("left_side", Qt::SizeHorCursor));
701 702
            if (ci)
                message = i18n("Crop from start: ") + m_document->timecode().getDisplayTimecode(ci->cropStart(), KdenliveSettings::frametimecode());
703
            if (item->type() == AVWidget && item->parentItem() && item->parentItem() != m_selectionGroup)
704
                message.append(i18n("Use Ctrl to resize only current item, otherwise all items in this group will be resized at once."));
705
        } else if (opMode == ResizeEnd) {
706
            setCursor(KCursor("right_side", Qt::SizeHorCursor));
707 708
            if (ci)
                message = i18n("Duration: ") + m_document->timecode().getDisplayTimecode(ci->cropDuration(), KdenliveSettings::frametimecode());
709
            if (item->type() == AVWidget && item->parentItem() && item->parentItem() != m_selectionGroup)
710
                message.append(i18n("Use Ctrl to resize only current item, otherwise all items in this group will be resized at once."));
711
        } else if (opMode == FadeIn || opMode == FadeOut) {
712
            setCursor(Qt::PointingHandCursor);
713
            if (ci && opMode == FadeIn && ci->fadeIn()) {
714 715
                message = i18n("Fade in duration: ");
                message.append(m_document->timecode().getDisplayTimecodeFromFrames(ci->fadeIn(), KdenliveSettings::frametimecode()));
716
            } else if (ci && opMode == FadeOut && ci->fadeOut()) {
717 718 719 720 721
                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.");
            }
722
        } else if (opMode == TransitionStart || opMode == TransitionEnd) {
723
            setCursor(Qt::PointingHandCursor);
724
            message = i18n("Click to add a transition.");
725
        } else if (opMode == KeyFrame) {
726 727 728
            setCursor(Qt::PointingHandCursor);
            emit displayMessage(i18n("Move keyframe above or below clip to remove it, double click to add a new one."), InformationMessage);
        }
729 730 731

        if (!message.isEmpty())
            emit displayMessage(message, InformationMessage);
732
    } // no clip under mouse
733
    else if (m_tool == RazorTool) {
734
        event->accept();
735
        return;
736
    } else if (opMode == MoveGuide) {
737 738
        m_moveOpMode = opMode;
    } else {
739
        removeTipAnimation();
740
        setCursor(Qt::ArrowCursor);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
741 742
        if (event->buttons() != Qt::NoButton && event->modifiers() == Qt::NoModifier) {
            QGraphicsView::mouseMoveEvent(event);
743
            m_moveOpMode = Seek;
744 745 746 747
            if (mappedXPos != m_document->renderer()->getCurrentSeekPosition() && mappedXPos != cursorPos()) {
                seekCursorPos(mappedXPos);
                slotCheckPositionScrolling();
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
748
            return;
749
        } else m_moveOpMode = None;
750
    }
751
    QGraphicsView::mouseMoveEvent(event);
752 753
}

754
// virtual
755 756
void CustomTrackView::mousePressEvent(QMouseEvent * event)
{
Jean-Baptiste Mardelle's avatar
Fix :  
Jean-Baptiste Mardelle committed
757
    setFocus(Qt::MouseFocusReason);
758
    m_menuPosition = QPoint();
759
    m_clipDrag = false;
760 761 762

    // special cases (middle click button or ctrl / shift click
    if (event->button() == Qt::MidButton) {
763
        emit playMonitor();
764
        m_blockRefresh = false;
765
        m_operationMode = None;
766 767 768
        return;
    }

769
    if (event->modifiers() & Qt::ShiftModifier) {
770
        // Rectangle selection