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

19
#include "timeline_frames_view.h"
20

21
#include "timeline_frames_model.h"
22 23 24

#include "timeline_ruler_header.h"
#include "timeline_layers_header.h"
25
#include "timeline_insert_keyframe_dialog.h"
26

27
#include <cmath>
28 29 30
#include <limits>

#include <QPainter>
31

32
#include <QFileInfo>
33
#include <QApplication>
34 35 36 37
#include <QHeaderView>
#include <QDropEvent>
#include <QToolButton>
#include <QMenu>
38
#include <QScrollBar>
Dmitry Kazakov's avatar
Dmitry Kazakov committed
39
#include <QIcon>
40
#include <QDrag>
41 42
#include <QWidgetAction>
#include <kis_signals_blocker.h>
43
#include <kis_image_config.h>
44

45 46
#include "KSharedConfig"

47
#include "kis_debug.h"
48
#include "timeline_frames_item_delegate.h"
49

50
#include "kis_zoom_button.h"
51 52

#include "kis_icon_utils.h"
53

54
#include "kis_animation_utils.h"
55
#include "kis_custom_modifiers_catcher.h"
56
#include "kis_action.h"
57 58
#include "kis_signal_compressor.h"
#include "kis_time_range.h"
59
#include "kis_color_label_selector_widget.h"
60
#include "kis_slider_spin_box.h"
61
#include <KisImportExportManager.h>
62

63
#include <KoFileDialog.h>
64
#include <KoIconToolTip.h>
65
#include <QStandardPaths>
66
#include <QWidgetAction>
67
#include <QInputDialog>
68

69 70 71
#include <QClipboard>
#include <QMimeData>

72 73
#include "config-qtmultimedia.h"

74 75
typedef QPair<QRect, QModelIndex> QItemViewPaintPair;
typedef QList<QItemViewPaintPair> QItemViewPaintPairs;
76

77
struct TimelineFramesView::Private
78
{
79 80 81
    Private(TimelineFramesView *_q)
        : q(_q),
          fps(1),
82
          zoomStillPointIndex(-1),
83
          zoomStillPointOriginalOffset(0),
84
          dragInProgress(false),
85
          dragWasSuccessful(false),
86 87
          modifiersCatcher(0),
          selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE)
88 89 90
    {}

    TimelineFramesView *q;
91

92
    TimelineFramesModel *model;
93 94 95
    TimelineRulerHeader *horizontalRuler;
    TimelineLayersHeader *layersHeader;
    int fps;
96 97
    int zoomStillPointIndex;
    int zoomStillPointOriginalOffset;
98
    QPoint initialDragPanValue;
99
    QPoint initialDragPanPos;
100 101

    QToolButton *addLayersButton;
102
    KisAction *showHideLayerAction;
103

104 105
    QToolButton *audioOptionsButton;

106 107 108 109 110
    KisColorLabelSelectorWidget *colorSelector;
    QWidgetAction *colorSelectorAction;
    KisColorLabelSelectorWidget *multiframeColorSelector;
    QWidgetAction *multiframeColorSelectorAction;

111 112 113
    QMenu *audioOptionsMenu;
    QAction *openAudioAction;
    QAction *audioMuteAction;
114 115
    KisSliderSpinBox *volumeSlider;

116 117 118
    QMenu *layerEditingMenu;
    QMenu *existingLayersMenu;

119 120
    TimelineInsertKeyframeDialog *insertKeyframeDialog;

121
    KisZoomButton *zoomDragButton;
122

123
    bool dragInProgress;
124
    bool dragWasSuccessful;
125 126 127

    KisCustomModifiersCatcher *modifiersCatcher;
    QPoint lastPressedPosition;
128
    Qt::KeyboardModifiers lastPressedModifier;
129
    KisSignalCompressor selectionChangedCompressor;
130

131
    QStyleOptionViewItem viewOptionsV4() const;
132 133 134
    QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const;
    QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const;

135
    KoIconToolTip tip;
136 137

    KisActionManager * actionMan = 0;
138 139
};

140
TimelineFramesView::TimelineFramesView(QWidget *parent)
141
    : QTableView(parent),
142
      m_d(new Private(this))
143
{
144 145 146 147
    m_d->modifiersCatcher = new KisCustomModifiersCatcher(this);
    m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space);
    m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt);

148 149
    setCornerButtonEnabled(false);
    setSelectionBehavior(QAbstractItemView::SelectItems);
150
    setSelectionMode(QAbstractItemView::ExtendedSelection);
151

152
    setItemDelegate(new TimelineFramesItemDelegate(this));
153 154

    setDragEnabled(true);
155
    setDragDropMode(QAbstractItemView::DragDrop);
156 157
    setAcceptDrops(true);
    setDropIndicatorShown(true);
158
    setDefaultDropAction(Qt::MoveAction);
159 160 161 162

    m_d->horizontalRuler = new TimelineRulerHeader(this);
    this->setHorizontalHeader(m_d->horizontalRuler);

163 164 165 166 167 168 169 170 171
    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeft()), SLOT(slotInsertColumnsLeft()));
    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRight()), SLOT(slotInsertColumnsRight()));

    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsLeftCustom()), SLOT(slotInsertColumnsLeftCustom()));
    connect(m_d->horizontalRuler, SIGNAL(sigInsertColumnsRightCustom()), SLOT(slotInsertColumnsRightCustom()));

    connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumns()), SLOT(slotRemoveColumns()));
    connect(m_d->horizontalRuler, SIGNAL(sigRemoveColumnsAndShift()), SLOT(slotRemoveColumnsAndShift()));

172 173 174 175 176 177
    connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumns()), SLOT(slotInsertHoldColumns()));
    connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumns()), SLOT(slotRemoveHoldColumns()));

    connect(m_d->horizontalRuler, SIGNAL(sigInsertHoldColumnsCustom()), SLOT(slotInsertHoldColumnsCustom()));
    connect(m_d->horizontalRuler, SIGNAL(sigRemoveHoldColumnsCustom()), SLOT(slotRemoveHoldColumnsCustom()));

178 179
    connect(m_d->horizontalRuler, SIGNAL(sigMirrorColumns()), SLOT(slotMirrorColumns()));

180 181 182 183
    connect(m_d->horizontalRuler, SIGNAL(sigCopyColumns()), SLOT(slotCopyColumns()));
    connect(m_d->horizontalRuler, SIGNAL(sigCutColumns()), SLOT(slotCutColumns()));
    connect(m_d->horizontalRuler, SIGNAL(sigPasteColumns()), SLOT(slotPasteColumns()));

184 185
    m_d->layersHeader = new TimelineLayersHeader(this);

Dmitry Kazakov's avatar
Dmitry Kazakov committed
186
    m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed);
187 188

    m_d->layersHeader->setDefaultSectionSize(24);
189
    m_d->layersHeader->setMinimumWidth(60);
190 191 192 193
    m_d->layersHeader->setHighlightSections(true);

    this->setVerticalHeader(m_d->layersHeader);

194 195 196
    connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount()));
    connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount()));

197 198
    /********** New Layer Menu ***********************************************************/

199 200
    m_d->addLayersButton = new QToolButton(this);
    m_d->addLayersButton->setAutoRaise(true);
201
    m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer"));
202
    m_d->addLayersButton->setIconSize(QSize(20, 20));
203 204 205
    m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup);

    m_d->layerEditingMenu = new QMenu(this);
206 207
    m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer()));
    m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::addExistingLayerActionName);
208
    m_d->layerEditingMenu->addSeparator();
209

210
    m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer()));
211 212 213 214 215 216 217 218

    connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu()));
    connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*)));

    connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(const QPoint&)), SLOT(slotLayerContextMenuRequested(const QPoint&)));

    m_d->addLayersButton->setMenu(m_d->layerEditingMenu);

219 220 221 222
    /********** Audio Channel Menu *******************************************************/

    m_d->audioOptionsButton = new QToolButton(this);
    m_d->audioOptionsButton->setAutoRaise(true);
223
    m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none"));
224
    m_d->audioOptionsButton->setIconSize(QSize(20, 20)); // very small on windows if not explicity set
225 226 227 228 229
    m_d->audioOptionsButton->setPopupMode(QToolButton::InstantPopup);

    m_d->audioOptionsMenu = new QMenu(this);

#ifndef HAVE_QT_MULTIMEDIA
Yuri Chornoivan's avatar
Yuri Chornoivan committed
230
    m_d->audioOptionsMenu->addSection(i18nc("@item:inmenu", "Audio playback is not supported in this build!"));
231 232 233 234 235 236 237 238 239 240 241 242 243
#endif

    m_d->openAudioAction= new QAction("XXX", this);
    connect(m_d->openAudioAction, SIGNAL(triggered()), this, SLOT(slotSelectAudioChannelFile()));
    m_d->audioOptionsMenu->addAction(m_d->openAudioAction);

    m_d->audioMuteAction = new QAction(i18nc("@item:inmenu", "Mute"), this);
    m_d->audioMuteAction->setCheckable(true);
    connect(m_d->audioMuteAction, SIGNAL(triggered(bool)), SLOT(slotAudioChannelMute(bool)));

    m_d->audioOptionsMenu->addAction(m_d->audioMuteAction);
    m_d->audioOptionsMenu->addAction(i18nc("@item:inmenu", "Remove audio"), this, SLOT(slotAudioChannelRemove()));

244 245 246 247 248 249 250 251 252
    m_d->audioOptionsMenu->addSeparator();

    m_d->volumeSlider = new KisSliderSpinBox(this);
    m_d->volumeSlider->setRange(0, 100);
    m_d->volumeSlider->setSuffix("%");
    m_d->volumeSlider->setPrefix(i18nc("@item:inmenu, slider", "Volume:"));
    m_d->volumeSlider->setSingleStep(1);
    m_d->volumeSlider->setPageStep(10);
    m_d->volumeSlider->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
253

254 255 256 257 258 259
    connect(m_d->volumeSlider, SIGNAL(valueChanged(int)), SLOT(slotAudioVolumeChanged(int)));

    QWidgetAction *volumeAction = new QWidgetAction(m_d->audioOptionsMenu);
    volumeAction->setDefaultWidget(m_d->volumeSlider);
    m_d->audioOptionsMenu->addAction(volumeAction);

260 261 262 263
    m_d->audioOptionsButton->setMenu(m_d->audioOptionsMenu);

    /********** Frame Editing Context Menu ***********************************************/

264 265 266 267 268 269 270 271 272
    m_d->colorSelector = new KisColorLabelSelectorWidget(this);
    m_d->colorSelectorAction = new QWidgetAction(this);
    m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector);
    connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged);

    m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this);
    m_d->multiframeColorSelectorAction = new QWidgetAction(this);
    m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector);
    connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged);
273

274 275 276 277
    /********** Insert Keyframes Dialog **************************************************/

    m_d->insertKeyframeDialog = new TimelineInsertKeyframeDialog(this);

278 279
    /********** Zoom Button **************************************************************/

280
    m_d->zoomDragButton = new KisZoomButton(this);
281
    m_d->zoomDragButton->setAutoRaise(true);
282
    m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
283 284
    m_d->zoomDragButton->setIconSize(QSize(20, 20)); // this icon is very small on windows if no explicity set

Boudewijn Rempt's avatar
Boudewijn Rempt committed
285
    m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right."));
286
    m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup);
287 288
    connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal)));
    connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal)));
289

290
    setFramesPerSecond(12);
291
    setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
292 293 294

    connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()),
            SLOT(slotSelectionChanged()));
295 296 297 298 299 300 301
    connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()),
            SLOT(slotUpdateFrameActions()));

    {
        QClipboard *cb = QApplication::clipboard();
        connect(cb, SIGNAL(dataChanged()), SLOT(slotUpdateFrameActions()));
    }
302 303
}

304
TimelineFramesView::~TimelineFramesView()
305 306 307
{
}

308 309 310 311 312 313
void TimelineFramesView::setShowInTimeline(KisAction* action)
{
    m_d->showHideLayerAction = action;
    m_d->layerEditingMenu->addAction(m_d->showHideLayerAction);
}

314 315 316 317
void TimelineFramesView::setActionManager( KisActionManager * actionManager)
{
    m_d->actionMan = actionManager;
    m_d->horizontalRuler->setActionManager(actionManager);
318 319 320 321 322


    if (actionManager) {
        KisAction *action = 0;

323 324 325 326
        action = m_d->actionMan->createAction("add_blank_frame");
        connect(action, SIGNAL(triggered()), SLOT(slotNewFrame()));

        action = m_d->actionMan->createAction("add_duplicate_frame");
Dmitry Kazakov's avatar
Dmitry Kazakov committed
327
        connect(action, SIGNAL(triggered()), SLOT(slotCopyFrame()));
328 329 330 331 332 333 334 335 336 337 338 339 340

        action = m_d->actionMan->createAction("insert_keyframes_right");
        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRight()));

        action = m_d->actionMan->createAction("insert_n_keyframes_right");
        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesRightCustom()));

        action = m_d->actionMan->createAction("insert_keyframes_left");
        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeft()));

        action = m_d->actionMan->createAction("insert_n_keyframes_left");
        connect(action, SIGNAL(triggered()), SLOT(slotInsertKeyframesLeftCustom()));

341
        action = m_d->actionMan->createAction("remove_frames_and_pull");
342 343 344 345
        connect(action, SIGNAL(triggered()), SLOT(slotRemoveFramesAndShift()));

        action = m_d->actionMan->createAction("remove_frames");
        connect(action, SIGNAL(triggered()), SLOT(slotRemoveFrame()));
346

347
        action = m_d->actionMan->createAction("insert_hold_frame");
348 349
        connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFrames()));

350
        action = m_d->actionMan->createAction("insert_n_hold_frames");
351 352 353 354 355 356 357
        connect(action, SIGNAL(triggered()), SLOT(slotInsertHoldFramesCustom()));

        action = m_d->actionMan->createAction("remove_hold_frame");
        connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFrames()));

        action = m_d->actionMan->createAction("remove_n_hold_frames");
        connect(action, SIGNAL(triggered()), SLOT(slotRemoveHoldFramesCustom()));
358 359 360

        action = m_d->actionMan->createAction("mirror_frames");
        connect(action, SIGNAL(triggered()), SLOT(slotMirrorFrames()));
361 362 363 364 365 366 367 368 369 370

        action = m_d->actionMan->createAction("copy_frames_to_clipboard");
        connect(action, SIGNAL(triggered()), SLOT(slotCopyFrames()));

        action = m_d->actionMan->createAction("cut_frames_to_clipboard");
        connect(action, SIGNAL(triggered()), SLOT(slotCutFrames()));

        action = m_d->actionMan->createAction("paste_frames_from_clipboard");
        connect(action, SIGNAL(triggered()), SLOT(slotPasteFrames()));

371 372 373 374 375 376 377 378
        action = m_d->actionMan->createAction("set_start_time");
        connect(action, SIGNAL(triggered()), SLOT(slotSetStartTimeToCurrentPosition()));

        action = m_d->actionMan->createAction("set_end_time");
        connect(action, SIGNAL(triggered()), SLOT(slotSetEndTimeToCurrentPosition()));

        action = m_d->actionMan->createAction("update_playback_range");
        connect(action, SIGNAL(triggered()), SLOT(slotUpdatePlackbackRange()));
379
    }
380 381
}

382 383 384 385 386 387 388 389
void resizeToMinimalSize(QAbstractButton *w, int minimalSize) {
    QSize buttonSize = w->sizeHint();
    if (buttonSize.height() > minimalSize) {
        buttonSize = QSize(minimalSize, minimalSize);
    }
    w->resize(buttonSize);
}

390
void TimelineFramesView::updateGeometries()
391 392 393 394 395 396 397
{
    QTableView::updateGeometries();

    const int availableHeight = m_d->horizontalRuler->height();
    const int margin = 2;
    const int minimalSize = availableHeight - 2 * margin;

398
    resizeToMinimalSize(m_d->addLayersButton, minimalSize);
399
    resizeToMinimalSize(m_d->audioOptionsButton, minimalSize);
400
    resizeToMinimalSize(m_d->zoomDragButton, minimalSize);
401 402

    int x = 2 * margin;
403 404
    int y = (availableHeight - minimalSize) / 2;
    m_d->addLayersButton->move(x, 2 * y);
405
    m_d->audioOptionsButton->move(x + minimalSize + 2 * margin, 2 * y);
406 407

    const int availableWidth = m_d->layersHeader->width();
408

409 410
    x = availableWidth - margin - minimalSize;
    m_d->zoomDragButton->move(x, 2 * y);
411 412
}

413
void TimelineFramesView::setModel(QAbstractItemModel *model)
414
{
415
    TimelineFramesModel *framesModel = qobject_cast<TimelineFramesModel*>(model);
416 417 418 419
    m_d->model = framesModel;

    QTableView::setModel(model);

420 421 422
    connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
            this, SLOT(slotHeaderDataChanged(Qt::Orientation, int, int)));

423 424
    connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(slotDataChanged(QModelIndex,QModelIndex)));
425 426 427

    connect(m_d->model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
            this, SLOT(slotReselectCurrentIndex()));
428 429 430

    connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()),
            this, SLOT(slotUpdateInfiniteFramesCount()));
431

432 433 434
    connect(m_d->model, SIGNAL(sigAudioChannelChanged()),
            this, SLOT(slotUpdateAudioActions()));

435 436
    connect(selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
            &m_d->selectionChangedCompressor, SLOT(start()));
437

438
    connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int)));
439
    slotUpdateAudioActions();
440 441
}

442
void TimelineFramesView::setFramesPerSecond(int fps)
443 444 445 446
{
    m_d->fps = fps;
    m_d->horizontalRuler->setFramePerSecond(fps);

447 448 449 450
    // For some reason simple update sometimes doesn't work here, so
    // reset the whole header
    //
    // m_d->horizontalRuler->reset();
451 452
}

453
void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint)
454
{
455
    m_d->zoomStillPointIndex =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
456
            qIsNaN(staticPoint) ? currentIndex().column() : staticPoint;
457

458 459 460
    const int w = m_d->horizontalRuler->defaultSectionSize();

    m_d->zoomStillPointOriginalOffset =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
461 462
            w * m_d->zoomStillPointIndex -
            horizontalScrollBar()->value();
463 464
}

465
void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel)
466
{
467 468
    if (m_d->horizontalRuler->setZoom(zoomLevel)) {
        slotUpdateInfiniteFramesCount();
469

470 471 472 473 474
        const int w = m_d->horizontalRuler->defaultSectionSize();
        horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset);

        viewport()->update();
    }
475 476
}

477 478 479
void TimelineFramesView::slotColorLabelChanged(int label)
{
    Q_FOREACH(QModelIndex index, selectedIndexes()) {
480
        m_d->model->setData(index, label, TimelineFramesModel::FrameColorLabelIndexRole);
481
    }
482 483 484

    KisImageConfig config;
    config.setDefaultFrameColorLabel(label);
485 486
}

487 488 489 490
void TimelineFramesView::slotSelectAudioChannelFile()
{
    if (!m_d->model) return;

491
    QString defaultDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
492 493 494 495 496 497 498

    const QString currentFile = m_d->model->audioChannelFileName();
    QDir baseDir = QFileInfo(currentFile).absoluteDir();
    if (baseDir.exists()) {
        defaultDir = baseDir.absolutePath();
    }

499
    const QString result = KisImportExportManager::askForAudioFileName(defaultDir, this);
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    const QFileInfo info(result);

    if (info.exists()) {
        m_d->model->setAudioChannelFileName(info.absoluteFilePath());
    }
}

void TimelineFramesView::slotAudioChannelMute(bool value)
{
    if (!m_d->model) return;

    if (value != m_d->model->isAudioMuted()) {
        m_d->model->setAudioMuted(value);
    }
}

516 517 518 519 520 521 522
void TimelineFramesView::slotUpdateIcons()
{
    m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer"));
    m_d->audioOptionsButton->setIcon(KisIconUtils::loadIcon("audio-none"));
    m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal"));
}

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
void TimelineFramesView::slotAudioChannelRemove()
{
    if (!m_d->model) return;
    m_d->model->setAudioChannelFileName(QString());
}

void TimelineFramesView::slotUpdateAudioActions()
{
    if (!m_d->model) return;

    const QString currentFile = m_d->model->audioChannelFileName();

    if (currentFile.isEmpty()) {
        m_d->openAudioAction->setText(i18nc("@item:inmenu", "Open audio..."));
    } else {
        QFileInfo info(currentFile);
        m_d->openAudioAction->setText(i18nc("@item:inmenu", "Change audio (%1)...", info.fileName()));
    }

    m_d->audioMuteAction->setChecked(m_d->model->isAudioMuted());
543 544 545 546 547 548 549 550 551 552 553 554 555

    QIcon audioIcon;
    if (currentFile.isEmpty()) {
        audioIcon = KisIconUtils::loadIcon("audio-none");
    } else {
        if (m_d->model->isAudioMuted()) {
            audioIcon = KisIconUtils::loadIcon("audio-volume-mute");
        } else {
            audioIcon = KisIconUtils::loadIcon("audio-volume-high");
        }
    }

    m_d->audioOptionsButton->setIcon(audioIcon);
556 557 558 559 560 561 562 563 564 565

    m_d->volumeSlider->setEnabled(!m_d->model->isAudioMuted());

    KisSignalsBlocker b(m_d->volumeSlider);
    m_d->volumeSlider->setValue(qRound(m_d->model->audioVolume() * 100.0));
}

void TimelineFramesView::slotAudioVolumeChanged(int value)
{
    m_d->model->setAudioVolume(qreal(value) / 100.0);
566 567
}

568
void TimelineFramesView::slotUpdateInfiniteFramesCount()
569 570 571 572
{
    if (horizontalScrollBar()->isSliderDown()) return;

    const int sectionWidth = m_d->horizontalRuler->defaultSectionSize();
573
    const int calculatedIndex =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
574 575
            (horizontalScrollBar()->value() +
             m_d->horizontalRuler->width() - 1) / sectionWidth;
576 577 578 579

    m_d->model->setLastVisibleFrame(calculatedIndex);
}

580
void TimelineFramesView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
581 582 583
{
    QTableView::currentChanged(current, previous);

584 585 586 587
    if (previous.column() != current.column()) {
        m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole);
        m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole);
    }
588 589
}

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index,
                                                                         const QEvent *event) const
{
    // WARNING: Copy-pasted from KisNodeView! Please keep in sync!

    /**
     * Qt has a bug: when we Ctrl+click on an item, the item's
     * selections gets toggled on mouse *press*, whereas usually it is
     * done on mouse *release*.  Therefore the user cannot do a
     * Ctrl+D&D with the default configuration. This code fixes the
     * problem by manually returning QItemSelectionModel::NoUpdate
     * flag when the user clicks on an item and returning
     * QItemSelectionModel::Toggle on release.
     */

    if (event &&
Boudewijn Rempt's avatar
Boudewijn Rempt committed
606 607 608
            (event->type() == QEvent::MouseButtonPress ||
             event->type() == QEvent::MouseButtonRelease) &&
            index.isValid()) {
609 610 611 612

        const QMouseEvent *mevent = static_cast<const QMouseEvent*>(event);

        if (mevent->button() == Qt::RightButton &&
Boudewijn Rempt's avatar
Boudewijn Rempt committed
613
                selectionModel()->selectedIndexes().contains(index)) {
614 615 616 617 618 619

            // Allow calling context menu for multiple layers
            return QItemSelectionModel::NoUpdate;
        }

        if (event->type() == QEvent::MouseButtonPress &&
Boudewijn Rempt's avatar
Boudewijn Rempt committed
620
                (mevent->modifiers() & Qt::ControlModifier)) {
621 622 623 624 625

            return QItemSelectionModel::NoUpdate;
        }

        if (event->type() == QEvent::MouseButtonRelease &&
Boudewijn Rempt's avatar
Boudewijn Rempt committed
626
                (mevent->modifiers() & Qt::ControlModifier)) {
627 628 629 630 631 632 633 634

            return QItemSelectionModel::Toggle;
        }
    }

    return QAbstractItemView::selectionCommand(index, event);
}

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
void TimelineFramesView::slotSelectionChanged()
{
    int minColumn = std::numeric_limits<int>::max();
    int maxColumn = std::numeric_limits<int>::min();

    foreach (const QModelIndex &idx, selectedIndexes()) {
        if (idx.column() > maxColumn) {
            maxColumn = idx.column();
        }

        if (idx.column() < minColumn) {
            minColumn = idx.column();
        }
    }

    KisTimeRange range;
    if (maxColumn > minColumn) {
        range = KisTimeRange(minColumn, maxColumn - minColumn + 1);
    }
    m_d->model->setPlaybackRange(range);
}

657
void TimelineFramesView::slotReselectCurrentIndex()
658 659 660 661 662
{
    QModelIndex index = currentIndex();
    currentChanged(index, index);
}

663 664 665 666 667 668 669 670 671
void TimelineFramesView::slotEnsureRowVisible(int row)
{
    QModelIndex index = currentIndex();
    if (!index.isValid() || row < 0) return;

    index = m_d->model->index(row, index.column());
    scrollTo(index);
}

672
void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
673
{
674 675
    if (m_d->model->isPlaybackActive()) return;

676 677 678 679
    int selectedColumn = -1;

    for (int j = topLeft.column(); j <= bottomRight.column(); j++) {
        QVariant value = m_d->model->data(
Boudewijn Rempt's avatar
Boudewijn Rempt committed
680 681
                    m_d->model->index(topLeft.row(), j),
                    TimelineFramesModel::ActiveFrameRole);
682 683 684 685 686 687 688 689 690

        if (value.isValid() && value.toBool()) {
            selectedColumn = j;
            break;
        }
    }

    QModelIndex index = currentIndex();

691
    if (!index.isValid() && selectedColumn < 0) {
692 693
        return;
    }
694 695 696 697 698 699


    if (selectedColumn == -1) {
        selectedColumn = index.column();
    }

700
    if (selectedColumn != index.column() && !m_d->dragInProgress) {
701
        int row= index.isValid() ? index.row() : 0;
702
        selectionModel()->setCurrentIndex(m_d->model->index(row, selectedColumn), QItemSelectionModel::ClearAndSelect);
703 704 705
    }
}

706
void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last)
707
{
708 709 710
    Q_UNUSED(first);
    Q_UNUSED(last);

711 712 713 714 715 716 717 718 719
    if (orientation == Qt::Horizontal) {
        const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt();

        if (newFps != m_d->fps) {
            setFramesPerSecond(newFps);
        }
    }
}

720 721 722 723 724 725
void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end)
{
    QTableView::rowsInserted(parent, start, end);
}


726 727 728 729
inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) {
    return (model->flags(index) & Qt::ItemIsDragEnabled);
}

730
QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const
731
{
732
    QStyleOptionViewItem option = q->viewOptions();
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
    option.locale = q->locale();
    option.locale.setNumberOptions(QLocale::OmitGroupSeparator);
    option.widget = q;
    return option;
}

QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
{
    Q_ASSERT(r);
    QRect &rect = *r;
    const QRect viewportRect = q->viewport()->rect();
    QItemViewPaintPairs ret;
    for (int i = 0; i < indexes.count(); ++i) {
        const QModelIndex &index = indexes.at(i);
        const QRect current = q->visualRect(index);
        if (current.intersects(viewportRect)) {
            ret += qMakePair(current, index);
            rect |= current;
        }
    }
    rect &= viewportRect;
    return ret;
}

QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const
{
    Q_ASSERT(r);
    QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r);
    if (paintPairs.isEmpty())
        return QPixmap();
    QPixmap pixmap(r->size());
    pixmap.fill(Qt::transparent);
    QPainter painter(&pixmap);
766
    QStyleOptionViewItem option = viewOptionsV4();
767 768 769 770 771
    option.state |= QStyle::State_Selected;
    for (int j = 0; j < paintPairs.count(); ++j) {
        option.rect = paintPairs.at(j).first.translated(-r->topLeft());
        const QModelIndex &current = paintPairs.at(j).second;
        //adjustViewOptionsForIndex(&option, current);
772

773 774 775 776 777 778 779 780 781 782 783 784 785
        q->itemDelegate(current)->paint(&painter, option, current);
    }
    return pixmap;
}

void TimelineFramesView::startDrag(Qt::DropActions supportedActions)
{
    QModelIndexList indexes = selectionModel()->selectedIndexes();

    if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) {
        QVector<int> rows;
        int leftmostColumn = std::numeric_limits<int>::max();

786
        Q_FOREACH (const QModelIndex &index, indexes) {
787 788 789 790 791 792 793 794 795
            leftmostColumn = qMin(leftmostColumn, index.column());
            if (!rows.contains(index.row())) {
                rows.append(index.row());
            }
        }

        const int lastColumn = m_d->model->columnCount() - 1;

        selectionModel()->clear();
796
        Q_FOREACH (const int row, rows) {
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
            QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn));
            selectionModel()->select(sel, QItemSelectionModel::Select);
        }

        supportedActions = Qt::MoveAction;

        {
            QModelIndexList indexes = selectedIndexes();
            for(int i = indexes.count() - 1 ; i >= 0; --i) {
                if (!isIndexDragEnabled(m_d->model, indexes.at(i)))
                    indexes.removeAt(i);
            }

            selectionModel()->clear();

            if (indexes.count() > 0) {
                QMimeData *data = m_d->model->mimeData(indexes);
                if (!data)
                    return;
                QRect rect;
                QPixmap pixmap = m_d->renderToPixmap(indexes, &rect);
                rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
                QDrag *drag = new QDrag(this);
                drag->setPixmap(pixmap);
                drag->setMimeData(data);
                drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft());
                drag->exec(supportedActions, Qt::MoveAction);
824
                setCurrentIndex(currentIndex());
825 826 827
            }
        }
    } else {
828 829 830 831 832 833 834 835 836 837 838 839 840 841

        /**
         * Workaround for Qt5's bug: if we start a dragging action right during
         * Shift-selection, Qt will get crazy. We cannot workaround it easily,
         * because we would need to fork mouseMoveEvent() for that (where the
         * decision about drag state is done). So we just abort dragging in that
         * case.
         *
         * BUG:373067
         */
        if (m_d->lastPressedModifier & Qt::ShiftModifier) {
            return;
        }

842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
        /**
         * Workaround for Qt5's bugs:
         *
         * 1) Qt doesn't treat selection the selection on D&D
         *    correctly, so we save it in advance and restore
         *    afterwards.
         *
         * 2) There is a private variable in QAbstractItemView:
         *    QAbstractItemView::Private::currentSelectionStartIndex.
         *    It is initialized *only* when the setCurrentIndex() is called
         *    explicitly on the view object, not on the selection model.
         *    Therefore we should explicitly call setCurrentIndex() after
         *    D&D, even if it already has *correct* value!
         *
         * 2) We should also call selectionModel()->select()
         *    explicitly.  There are two reasons for it: 1) Qt doesn't
         *    maintain selection over D&D; 2) when reselecting single
         *    element after D&D, Qt goes crazy, because it tries to
         *    read *global* keyboard modifiers. Therefore if we are
         *    dragging with Shift or Ctrl pressed it'll get crazy. So
         *    just reset it explicitly.
         */

        QModelIndexList selectionBefore = selectionModel()->selectedIndexes();
        QModelIndex currentBefore = selectionModel()->currentIndex();

        // initialize a global status variable
        m_d->dragWasSuccessful = false;
870
        QAbstractItemView::startDrag(supportedActions);
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887

        QModelIndex newCurrent;
        QPoint selectionOffset;

        if (m_d->dragWasSuccessful) {
            newCurrent = currentIndex();
            selectionOffset = QPoint(newCurrent.column() - currentBefore.column(),
                                     newCurrent.row() - currentBefore.row());
        } else {
            newCurrent = currentBefore;
            selectionOffset = QPoint();
        }

        setCurrentIndex(newCurrent);
        selectionModel()->clearSelection();
        Q_FOREACH (const QModelIndex &idx, selectionBefore) {
            QModelIndex newIndex =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
888 889
                    model()->index(idx.row() + selectionOffset.y(),
                                   idx.column() + selectionOffset.x());
890 891
            selectionModel()->select(newIndex, QItemSelectionModel::Select);
        }
892 893 894
    }
}

895
void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event)
896 897
{
    m_d->dragInProgress = true;
898 899
    m_d->model->setScrubState(true);

900 901 902
    QTableView::dragEnterEvent(event);
}

903
void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event)
904 905
{
    m_d->dragInProgress = true;
906
    m_d->model->setScrubState(true);
907 908 909 910 911 912 913 914 915 916 917 918 919

    QTableView::dragMoveEvent(event);

    if (event->isAccepted()) {
        QModelIndex index = indexAt(event->pos());
        if (!m_d->model->canDropFrameData(event->mimeData(), index)) {
            event->ignore();
        } else {
            selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
        }
    }
}

920
void TimelineFramesView::dropEvent(QDropEvent *event)
921 922
{
    m_d->dragInProgress = false;
923
    m_d->model->setScrubState(false);
924 925

    QAbstractItemView::dropEvent(event);
926
    m_d->dragWasSuccessful = event->isAccepted();
927 928
}

929
void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event)
930 931
{
    m_d->dragInProgress = false;
932
    m_d->model->setScrubState(false);
933 934 935 936

    QAbstractItemView::dragLeaveEvent(event);
}

937 938 939 940
void TimelineFramesView::createFrameEditingMenuActions(QMenu *menu, bool addFrameCreationActions)
{
    slotUpdateFrameActions();

941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
    // calculate if selection range is set. This will determine if the update playback range is available
    QSet<int> rows;
    int minColumn = 0;
    int maxColumn = 0;
    calculateSelectionMetrics(minColumn, maxColumn, rows);

    bool selectionExists = minColumn != maxColumn;


    if (selectionExists) {
            KisActionManager::safePopulateMenu(menu, "update_playback_range", m_d->actionMan);
    } else {
        KisActionManager::safePopulateMenu(menu, "set_start_time", m_d->actionMan);
        KisActionManager::safePopulateMenu(menu, "set_end_time", m_d->actionMan);
    }
    menu->addSeparator();


959 960 961
    KisActionManager::safePopulateMenu(menu, "cut_frames_to_clipboard", m_d->actionMan);
    KisActionManager::safePopulateMenu(menu, "copy_frames_to_clipboard", m_d->actionMan);
    KisActionManager::safePopulateMenu(menu, "paste_frames_from_clipboard", m_d->actionMan);
962

963
    menu->addSeparator();
964
    QMenu *frames = menu->addMenu(i18nc("@item:inmenu", "Keyframes"));
965 966
    KisActionManager::safePopulateMenu(frames, "insert_keyframes_right", m_d->actionMan);
    KisActionManager::safePopulateMenu(frames, "insert_keyframes_left", m_d->actionMan);
967

968
    frames->addSeparator();
969 970
    KisActionManager::safePopulateMenu(frames, "insert_n_keyframes_right", m_d->actionMan);
    KisActionManager::safePopulateMenu(frames, "insert_n_keyframes_left", m_d->actionMan);
971 972

    QMenu *hold = menu->addMenu(i18nc("@item:inmenu", "Hold Frames"));
973 974
    KisActionManager::safePopulateMenu(hold, "insert_hold_frame", m_d->actionMan);
    KisActionManager::safePopulateMenu(hold, "remove_hold_frame", m_d->actionMan);
975

976
    hold->addSeparator();
977 978
    KisActionManager::safePopulateMenu(hold, "insert_n_hold_frames", m_d->actionMan);
    KisActionManager::safePopulateMenu(hold, "remove_n_hold_frames", m_d->actionMan);
979 980

    menu->addSeparator();
981 982
    KisActionManager::safePopulateMenu(menu, "remove_frames", m_d->actionMan);
    KisActionManager::safePopulateMenu(menu, "remove_frames_and_pull", m_d->actionMan);
983 984 985 986 987 988 989

    menu->addSeparator();
    if (addFrameCreationActions) {
        KisActionManager::safePopulateMenu(menu, "add_blank_frame", m_d->actionMan);
        KisActionManager::safePopulateMenu(menu, "add_duplicate_frame", m_d->actionMan);
        menu->addSeparator();
    }
990 991
}

992
void TimelineFramesView::mousePressEvent(QMouseEvent *event)
993 994 995
{
    QPersistentModelIndex index = indexAt(event->pos());

996
    if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
997

998
        if (event->button() == Qt::RightButton) {
999 1000
            // TODO: try calculate index under mouse cursor even when
            //       it is outside any visible row
1001 1002
            qreal staticPoint = index.isValid() ? index.column() : currentIndex().column();
            m_d->zoomDragButton->beginZoom(event->pos(), staticPoint);
1003
        } else if (event->button() == Qt::LeftButton) {
1004
            m_d->initialDragPanPos = event->pos();
1005
            m_d->initialDragPanValue =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1006 1007
                    QPoint(horizontalScrollBar()->value(),
                           verticalScrollBar()->value());
1008 1009 1010
        }
        event->accept();

1011
    } else if (event->button() == Qt::RightButton) {
1012

1013
        int numSelectedItems = selectionModel()->selectedIndexes().size();
1014

1015
        if (index.isValid() &&
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1016 1017
                numSelectedItems <= 1 &&
                m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) {
1018 1019 1020 1021 1022

            model()->setData(index, true, TimelineFramesModel::ActiveLayerRole);
            model()->setData(index, true, TimelineFramesModel::ActiveFrameRole);
            setCurrentIndex(index);

1023
            if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() ||
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1024
                    model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) {
1025 1026

                {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1027
                    KisSignalsBlocker b(m_d->colorSelector);
1028
                    QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1029 1030
                    int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0;
                    m_d->colorSelector->setCurrentIndex(labelIndex);
1031 1032
                }

1033
                QMenu menu;
1034
                createFrameEditingMenuActions(&menu, false);
1035
                menu.addSeparator();
1036 1037 1038
                menu.addAction(m_d->colorSelectorAction);
                menu.exec(event->globalPos());

1039
            } else {
1040 1041 1042 1043 1044 1045 1046 1047
                {
                    KisSignalsBlocker b(m_d->colorSelector);
                    KisImageConfig cfg;
                    const int labelIndex = cfg.defaultFrameColorLabel();
                    m_d->colorSelector->setCurrentIndex(labelIndex);
                }

                QMenu menu;
1048 1049
                createFrameEditingMenuActions(&menu, true);

1050
                menu.addSeparator();
1051 1052
                menu.addAction(m_d->colorSelectorAction);
                menu.exec(event->globalPos());
1053 1054
            }
        } else if (numSelectedItems > 1) {
1055
            int labelIndex = -1;
1056 1057 1058
            bool haveFrames = false;
            Q_FOREACH(QModelIndex index, selectedIndexes()) {
                haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool();
1059
                QVariant colorLabel = index.data(TimelineFramesModel::FrameColorLabelIndexRole);
1060
                if (colorLabel.isValid()) {
1061 1062
                    if (labelIndex == -1) {
                        // First label
1063
                        labelIndex = colorLabel.toInt();
1064 1065 1066
                    } else if (labelIndex != colorLabel.toInt()) {
                        // Mixed colors in selection
                        labelIndex = -1;
1067 1068 1069 1070 1071
                        break;
                    }
                }
            }

1072
            if (haveFrames) {
1073 1074 1075 1076
                KisSignalsBlocker b(m_d->multiframeColorSelector);
                m_d->multiframeColorSelector->setCurrentIndex(labelIndex);
            }

1077
            QMenu menu;
1078
            createFrameEditingMenuActions(&menu, false);
1079
            menu.addSeparator();
1080
            KisActionManager::safePopulateMenu(&menu, "mirror_frames", m_d->actionMan);
1081 1082
            menu.addSeparator();
            menu.addAction(m_d->multiframeColorSelectorAction);
1083
            menu.exec(event->globalPos());
1084
        }
1085 1086 1087 1088 1089 1090 1091 1092 1093
    } else if (event->button() == Qt::MidButton) {
        QModelIndex index = model()->buddy(indexAt(event->pos()));
        if (index.isValid()) {
            QStyleOptionViewItem option = viewOptions();
            option.rect = visualRect(index);
            // The offset of the headers is needed to get the correct position inside the view.
            m_d->tip.showTip(this, event->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
        }
        event->accept();
1094
    } else {
1095 1096 1097 1098
        if (index.isValid()) {
            m_d->model->setLastClickedIndex(index);
        }

1099
        m_d->lastPressedPosition =
Boudewijn Rempt's avatar
Boudewijn Rempt committed
1100
                QPoint(horizontalOffset(), verticalOffset()) + event->pos();
1101
        m_d->lastPressedModifier = event->modifiers();
1102

1103 1104 1105 1106
        QAbstractItemView::mousePressEvent(event);
    }
}

1107
void TimelineFramesView::mouseMoveEvent(QMouseEvent *e)
1108
{
1109
    if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
1110 1111

        if (e->buttons() & Qt::RightButton) {
1112
            m_d->zoomDragButton->continueZoom(e->pos());
1113
        } else if (e->buttons() & Qt::LeftButton) {
1114
            QPoint diff = e->pos() - m_d->initialDragPanPos;
1115 1116
            QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(),
                                   m_d->initialDragPanValue.y() - diff.y());
1117

1118 1119
            const int height = m_d->layersHeader->defaultSectionSize();

1120
            horizontalScrollBar()->setValue(offset.x());
1121
            verticalScrollBar()->setValue(offset.y() / height);
1122 1123
        }
        e->accept();
1124 1125 1126 1127 1128 1129 1130 1131 1132
    } else if (e->buttons() == Qt::MidButton) {
        QModelIndex index = model()->buddy(indexAt(e->pos()));
        if (index.isValid()) {
            QStyleOptionViewItem option = viewOptions();
            option.rect = visualRect(index);
            // The offset of the headers is needed to get the correct position inside the view.
            m_d->tip.showTip(this, e->pos() + QPoint(verticalHeader()->width(), horizontalHeader()->height()), option, index);
        }
        e->accept();
1133
    } else {
1134
        m_d->model->setScrubState(true);
1135 1136 1137 1138
        QTableView::mouseMoveEvent(e);
    }
}

1139
void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e)
1140
{
1141
    if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) {
1142 1143
        e->accept();
    } else {
1144
        m_d->model->setScrubState(false);
1145 1146 1147 1148
        QTableView::mouseReleaseEvent(e);
    }
}

1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161
void TimelineFramesView::wheelEvent(QWheelEvent *e)
{
    QModelIndex index = currentIndex();
    int column= -1;

    if (index.isValid()) {
        column= index.column() + ((e->delta() > 0) ? 1 : -1);
    }

    if (column >= 0 && !m_d->dragInProgress) {
        setCurrentIndex(m_d->model->index(index.row(), column));
    }
}
1162

1163
void TimelineFramesView::slotUpdateLayersMenu()
1164 1165 1166 1167 1168
{
    QAction *action = 0;

    m_d->existingLayersMenu->clear();

1169
    QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole);
1170
    if (