Continues migration with DDS, Common
This commit is contained in:
1 parent
9b15cee841
commit
fa48db111e
668 files changed
+111841
-2
No files matched your search
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#
|
||||
# CMakeLists.txt for LtsTimeline, the base for EventTimeline and ScriptTimeline aka. Script Editor
|
||||
#
|
||||
# $Id: CMakeLists.txt 39615 2018-03-16 13:36:41Z wille_m $
|
||||
# $HeadURL: svn://192.168.133.21/TDS/branches/product/features/hotline/Lts/Common/LtsEventTimeline/CMakeLists.txt $
|
||||
# $Author: wille_m $
|
||||
#
|
||||
# Created by Mike Wille, SLTE, 2021-05-18
|
||||
#
|
||||
|
||||
SET(src
|
||||
timeline_model.h
|
||||
timeline_model.cpp
|
||||
|
||||
item.h
|
||||
item.cpp
|
||||
item_delegate.h
|
||||
item_delegate.cpp
|
||||
|
||||
timeline_header_view.cpp
|
||||
timeline_header_view.h
|
||||
timeline_header_view_mouse_handler.cpp
|
||||
timeline_header_view_mouse_handler.h
|
||||
timeline_utils.cpp
|
||||
timeline_utils.h
|
||||
timeline_view.h
|
||||
timeline_view.cpp
|
||||
timeline_view_drag_handler.cpp
|
||||
timeline_view_drag_handler.h
|
||||
timeline_view_resize_handler.cpp
|
||||
timeline_view_resize_handler.h
|
||||
timeline_view_selection_handler.cpp
|
||||
timeline_view_selection_handler.h
|
||||
timeline_view_track_handler.cpp
|
||||
timeline_view_track_handler.h
|
||||
timeline_widget.h
|
||||
timeline_widget.cpp
|
||||
|
||||
track.h
|
||||
track.cpp
|
||||
)
|
||||
|
||||
SET(uisrc
|
||||
timeline_widget.ui
|
||||
)
|
||||
|
||||
NGL_WRAP_UI(src ${uisrc})
|
||||
NGL_ADD_LIBRARY2(WS LtsTimeline SHARED ${src})
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsTimeline
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsTimeline
|
||||
PRIVATE
|
||||
NGLLogger
|
||||
ngltools # DTG
|
||||
LtsWidgets # LtsMessageDialogNew for remove filter track
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
)
|
||||
NGL_INSTALL_TARGET(LtsTimeline)
|
||||
|
||||
|
||||
# LtsTimelineTest
|
||||
NGL_ADD_EXECUTABLE(WS LtsTimelineTest main.cpp)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsTimelineTest
|
||||
LtsTimeline
|
||||
nglapplication
|
||||
NGLLogger
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
)
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
|
||||
|
||||
|
||||
#include "item.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QPen>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
QString Item::mimetype = QStringLiteral("application/rheinmetall.timeline.item");
|
||||
|
||||
QColor Item::s_highlightColor = QRgb(0x43b0e0);
|
||||
|
||||
|
||||
Item::Item(qint64 startTime, int duration, const QColor &color, bool isEditable)
|
||||
: m_color(color)
|
||||
, m_startTime(startTime)
|
||||
, m_duration(duration)
|
||||
, m_isEditable(isEditable)
|
||||
{}
|
||||
|
||||
|
||||
|
||||
|
||||
void Item::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &, bool highlight) const
|
||||
{
|
||||
painter->save();
|
||||
if (highlight)
|
||||
painter->setPen(QPen(QBrush(s_highlightColor), 5));
|
||||
else
|
||||
painter->setPen(QPen(QBrush(Qt::black), 1));
|
||||
painter->setBrush(QBrush(m_color));
|
||||
painter->drawRect(option.rect);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
|
||||
class QPainter;
|
||||
class QStyleOptionViewItem;
|
||||
class QModelIndex;
|
||||
|
||||
namespace PresentationManagement
|
||||
{
|
||||
class ScriptModel;
|
||||
}
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class ItemDelegate;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT Item
|
||||
{
|
||||
friend class ItemDelegate;
|
||||
|
||||
public:
|
||||
static QString mimetype;
|
||||
static QColor s_highlightColor;
|
||||
|
||||
Item() = default;
|
||||
Item(qint64 startTime, int duration, const QColor &color, bool isEditable = true);
|
||||
Item(const Item &) = delete;
|
||||
Item &operator=(const Item &) = delete;
|
||||
Item(Item &&) noexcept = default;
|
||||
virtual ~Item() = default;
|
||||
|
||||
int id() const { return m_id; }
|
||||
int parentTrackId() const { return m_parentTrackId; }
|
||||
|
||||
QColor color() const { return m_color; }
|
||||
void setColor(const QColor &color) { m_color = color; }
|
||||
|
||||
qint64 startTime() const { return m_startTime; }
|
||||
[[deprecated("use model's setItemStartTime instead, needed to notify the model that it has to re-sort items")]]
|
||||
void setStartTime(qint64 startTime) { m_startTime = startTime; }
|
||||
|
||||
int duration() const { return m_duration; }
|
||||
void setDuration(int duration) { m_duration = duration; }
|
||||
|
||||
bool isEditable() const { return m_isEditable; }
|
||||
void setEditable(bool editable) { m_isEditable = editable; }
|
||||
|
||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, bool highlight = false) const;
|
||||
|
||||
void _setStartTime(qint64 startTime) { m_startTime = startTime; }
|
||||
void _setDuration(int duration) { m_duration = duration; }
|
||||
|
||||
protected:
|
||||
QColor m_color;
|
||||
qint64 m_startTime { -1 };
|
||||
int m_duration { -1 };
|
||||
bool m_isEditable { false };
|
||||
|
||||
private:
|
||||
friend class TimelineModel;
|
||||
friend class PresentationManagement::ScriptModel;
|
||||
|
||||
int m_id { -1 };
|
||||
int m_parentTrackId { -1 };
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<Item> ItemPtr;
|
||||
typedef std::vector<ItemPtr> Items;
|
||||
typedef std::reference_wrapper<const ItemPtr> ItemPtrRef;
|
||||
typedef std::reference_wrapper<ItemPtr> ItemPtrNCVRef;
|
||||
typedef std::vector<ItemPtrNCVRef> ItemPtrNCVRefs;
|
||||
typedef std::vector<ItemPtrRef> ItemPtrRefs;
|
||||
|
||||
}
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
|
||||
|
||||
|
||||
#include "item_delegate.h"
|
||||
#include "item.h"
|
||||
#include "track.h"
|
||||
#include "timeline_view.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
ItemDelegate::ItemDelegate(TimelineView *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
, m_view(parent)
|
||||
, m_pushButton(new QPushButton)
|
||||
, m_pushButtonPressed(new QPushButton)
|
||||
{
|
||||
m_pushButtonPressed->setDown(true);
|
||||
}
|
||||
|
||||
ItemDelegate::~ItemDelegate()
|
||||
{
|
||||
m_pushButton->deleteLater();
|
||||
m_pushButton = nullptr;
|
||||
m_pushButtonPressed->deleteLater();
|
||||
m_pushButtonPressed = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
Q_UNUSED(option);
|
||||
Q_UNUSED(index);
|
||||
return QSize(100, 50);
|
||||
}
|
||||
|
||||
void ItemDelegate::paintDragItems(QPainter *painter) const
|
||||
{
|
||||
painter->save();
|
||||
|
||||
const QRect rightSectionRect = QRect(QPoint(m_view->m_header->sectionPosition(Section::eItems), 0),
|
||||
QPoint(m_view->geometry().right(), m_view->geometry().bottom()));
|
||||
painter->setClipRect(rightSectionRect);
|
||||
|
||||
int dx = m_view->m_dragHandler->deltaX();
|
||||
int dy = m_view->m_dragHandler->deltaY();
|
||||
try
|
||||
{
|
||||
for (const int &itemId : m_view->m_dragHandler->m_dragItemIds)
|
||||
{
|
||||
Item &item = m_view->timelineModel()->item(itemId);
|
||||
if (item.startTime() + item.duration() < m_view->currentTime())
|
||||
continue;
|
||||
|
||||
if (item.startTime() > m_view->endTime())
|
||||
break;
|
||||
|
||||
|
||||
|
||||
const QModelIndex rowIndex(m_view->timelineModel()->indexOfTrack(item.parentTrackId()));
|
||||
const QModelIndex columnIndex(m_view->timelineModel()->index(rowIndex.row(), 1, rowIndex.parent()));
|
||||
const QRect visualRect(m_view->visualRect(columnIndex));
|
||||
int left = m_view->toPixel(item.startTime()) + dx;
|
||||
int right = m_view->toPixel(item.startTime() + item.duration()) + dx;
|
||||
int top = visualRect.top() + dy;
|
||||
int bottom = visualRect.bottom() + dy;
|
||||
QStyleOptionViewItem option;
|
||||
option.rect = QRect(left, top, right - left - 1, bottom - top);
|
||||
item.paint(painter, option, columnIndex, m_view->selectedItemIds().contains(itemId));
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
|
||||
void ItemDelegate::paintTrackButtons(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
const Track &track = m_view->timelineModel()->track(index.data(Qt::UserRole).toInt());
|
||||
|
||||
int buttonLength = option.rect.height() / 2;
|
||||
if (track.hasInsertButton())
|
||||
{
|
||||
QPushButton *button = m_view->m_trackHandler->isInsertButtonPressed(index) ? m_pushButtonPressed : m_pushButton;
|
||||
QRect rect = QRect(0, 0, buttonLength, buttonLength);
|
||||
button->setGeometry(rect);
|
||||
button->setText(QStringLiteral("+"));
|
||||
button->setEnabled(m_view->timelineModel()->isEditable());
|
||||
QPixmap map = button->grab();
|
||||
painter->drawPixmap(option.rect.right() - buttonLength, option.rect.top(), map);
|
||||
}
|
||||
if (track.hasRemoveButton())
|
||||
{
|
||||
QPushButton *button = m_view->m_trackHandler->isRemoveButtonPressed(index) ? m_pushButtonPressed : m_pushButton;
|
||||
QRect rect = QRect(0, 0, buttonLength, buttonLength);
|
||||
button->setGeometry(rect);
|
||||
button->setText(QStringLiteral("-"));
|
||||
button->setEnabled(m_view->timelineModel()->isEditable());
|
||||
QPixmap map = button->grab();
|
||||
painter->drawPixmap(option.rect.right() - buttonLength, option.rect.center().y(), map);
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
if (index.column() == Section::eActuator)
|
||||
{
|
||||
painter->save();
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
paintTrackButtons(painter, option, index);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
auto drawItem = [this, &option, &painter, &index](const Item &item)
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (m_view->timelineModel()->isEditable())
|
||||
if (m_view->m_dragHandler->m_dragItemIds.contains(item.id()))
|
||||
return;
|
||||
|
||||
QStyleOptionViewItem opt = option;
|
||||
opt.rect.setLeft(std::max(m_view->toPixel(item.startTime()), option.rect.left()));
|
||||
opt.rect.setRight(std::min(m_view->toPixel(item.startTime() + item.duration()), option.rect.right()) - 1);
|
||||
item.paint(painter, opt, index, m_view->selectedItemIds().contains(item.id()));
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
const Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
painter->save();
|
||||
|
||||
|
||||
painter->setClipping(true);
|
||||
painter->setClipRect(option.rect);
|
||||
|
||||
const Items &items = track.items();
|
||||
for (const ItemPtr &item : items)
|
||||
drawItem(static_cast<Item &>(*item.get()));
|
||||
const QSet<int> copyItemIds = track.copyItemIds();
|
||||
for (const int &itemId : copyItemIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
drawItem(item);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "ItemDelegate::paint: cannot find item copy of %d\n", itemId);
|
||||
}
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << "ItemDelegate::paint -> CATCH";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QPushButton>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineView;
|
||||
class TimelineModel;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT ItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemDelegate(TimelineView *parent = nullptr);
|
||||
~ItemDelegate();
|
||||
|
||||
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &option = {}, const QModelIndex &index = {}) const override;
|
||||
|
||||
|
||||
|
||||
virtual void paintDragItems(QPainter *painter) const;
|
||||
virtual void paintTrackButtons(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
protected:
|
||||
TimelineView *m_view { nullptr };
|
||||
|
||||
QPushButton *m_pushButton { nullptr };
|
||||
QPushButton *m_pushButtonPressed { nullptr };
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
log4j.reset=true
|
||||
# exclusivly for timelinetest build
|
||||
|
||||
|
||||
log4j.rootLogger=ERROR, file
|
||||
#log4j.rootLogger=WARN, stdout
|
||||
|
||||
#log4j.logger.Timeline.EventInfoWindow=DEBUG
|
||||
#log4j.logger.Timeline.EventTools=DEBUG
|
||||
#log4j.logger.Timeline=WARN
|
||||
|
||||
|
||||
log4j.appender.file=org.apache.log4j.RollingFileAppender
|
||||
log4j.appender.file.MaxFileSize=1MB
|
||||
log4j.appender.file.MaxBackupIndex=9
|
||||
log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_timeline.log
|
||||
log4j.appender.file.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n
|
||||
|
||||
# Appnd_Console
|
||||
###########################################################
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n
|
||||
|
||||
###########################################################
|
||||
###########################################################
|
||||
###########################################################
|
||||
# 1) Available Levels in LOG4CXX Version 0.10.0:
|
||||
# TRACE,
|
||||
# DEBUG,
|
||||
# INFO,
|
||||
# WARN,
|
||||
# ERROR
|
||||
# FATAL
|
||||
#
|
||||
# d = date/time
|
||||
# p = priority (Level)
|
||||
# c = category/channel
|
||||
# m = message
|
||||
# F = file
|
||||
# L = line
|
||||
# n = platform dependent line separator character or characters.
|
||||
# ....
|
||||
#
|
||||
# Examples for console:
|
||||
# a)
|
||||
# %d %p %c #%m# (%.50F:%L)%n
|
||||
# date/time
|
||||
# priority
|
||||
# category/channel
|
||||
# #message#
|
||||
# (Maximal 50Character des Filename von rechts:Line)
|
||||
# line separator
|
||||
#
|
||||
# b)
|
||||
# %d (%.15F:%L) %-5p %c |#| %m%n
|
||||
# date/time
|
||||
# (Maximal 15Character des Filename von rechts:Line)
|
||||
# priority, Right pad with spaces if the priority name is less than 5 characters long.
|
||||
# category/channel
|
||||
# message
|
||||
# line separator
|
||||
#
|
||||
# Examples for files:
|
||||
|
||||
Executable
+289
@@ -0,0 +1,289 @@
|
||||
#include <timeline_widget.h>
|
||||
#include <timeline_model.h>
|
||||
#include <item.h>
|
||||
#include <track.h>
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <nglapplication/nglapplication.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
using namespace LtsTimeline;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AbstractStuff
|
||||
{
|
||||
public:
|
||||
virtual void thing() = 0;
|
||||
};
|
||||
|
||||
|
||||
class TheStuff : public AbstractStuff
|
||||
{
|
||||
void thing() override { fprintf(stderr, "thing() !\n"); }
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<AbstractStuff> get()
|
||||
{
|
||||
return std::make_unique<TheStuff>(std::move(TheStuff()));
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::unique_ptr<AbstractStuff>> test;
|
||||
|
||||
|
||||
void move(std::unique_ptr<AbstractStuff> &&stuff)
|
||||
{
|
||||
test.push_back(std::move(stuff));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void populateModel(LtsTimeline::TimelineModel *model)
|
||||
{
|
||||
{
|
||||
std::unique_ptr<AbstractStuff> asp = get();
|
||||
move(std::move(asp));
|
||||
}
|
||||
|
||||
{
|
||||
AbstractStuff &as = *test.front();
|
||||
as.thing();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#define l() qCritical()
|
||||
qint64 rtlnau = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
class TestItem : public Item
|
||||
{
|
||||
public:
|
||||
TestItem(qint64 startTime, int duration, const QColor &color, bool isEditable = true)
|
||||
: Item(startTime, duration, color, isEditable)
|
||||
, m_1234(1234)
|
||||
{}
|
||||
|
||||
int get1234() const { return m_1234; }
|
||||
|
||||
private:
|
||||
int m_1234 { 9876 };
|
||||
};
|
||||
|
||||
|
||||
int trackId;
|
||||
int itemId;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model->addTrack<Track>(0, Track(QStringLiteral("1st Track")));
|
||||
model->addItem<Item>(1, Item(rtlnau + 120'000, 30'000, Qt::green));
|
||||
|
||||
int firstChildId =
|
||||
model->addTrack(1, Track(QStringLiteral("1st Track's 1st Child")));
|
||||
model->addItem<LtsTimeline::Item>(2, Item(rtlnau + 180'000, 30'000, Qt::magenta));
|
||||
l() << "firstChildId:" << firstChildId;
|
||||
|
||||
model->addTrack<Track>(0, Track(QStringLiteral("2nd Track")));
|
||||
model->addItem<Item>(3, Item(rtlnau + 120'000, 60'000, Qt::blue));
|
||||
model->addItem<Item>(3, Item(rtlnau + 60'000, 30'000, Qt::red));
|
||||
|
||||
model->addTrack<Track>(0, Track(QStringLiteral("3rd Track")));
|
||||
model->addItem<Item>(4, Item(rtlnau + 150'000, 60'000, Qt::yellow));
|
||||
model->addItem<TestItem>(4, TestItem(rtlnau + 90'000, 30'000, Qt::cyan));
|
||||
|
||||
|
||||
|
||||
model->print(true);
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
const TestItem &testItem = dynamic_cast<const TestItem &>(model->item(5));
|
||||
|
||||
}
|
||||
catch(const std::exception &ex)
|
||||
{
|
||||
l() << "dynamic_cast back to TestItem failed!" << ex.what();
|
||||
}
|
||||
|
||||
|
||||
(void)itemId;
|
||||
(void)trackId;
|
||||
|
||||
|
||||
|
||||
qint64 now = rtlnau;
|
||||
|
||||
{
|
||||
#define non_strict false
|
||||
#define strict true
|
||||
#define recursive true
|
||||
#define sorted true
|
||||
#define unsorted false
|
||||
QVector<int> x;
|
||||
bool y;
|
||||
|
||||
|
||||
|
||||
x = model->itemIdsBetween(now + 60'000 + 1, now + 150'000 - 1, non_strict, recursive);
|
||||
|
||||
|
||||
x = model->itemIdsBetween(now + 60'000 + 1, now + 150'000 - 1, strict, recursive);
|
||||
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, -1, non_strict, recursive);
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, -1, strict, recursive);
|
||||
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, 5, non_strict, recursive);
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000 + 1, now + 150'000 - 1, 5, strict, recursive);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
x = model->itemIdsBetween(now + 60'000, now + 150'000, non_strict, recursive);
|
||||
|
||||
|
||||
x = model->itemIdsBetween(now + 60'000, now + 150'000, strict, recursive);
|
||||
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, -1, non_strict, recursive);
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, -1, strict, recursive);
|
||||
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, 5, non_strict, recursive);
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, 5, strict, recursive);
|
||||
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, QVector<int>({ 0, 3, 5 }), strict, recursive);
|
||||
|
||||
|
||||
y = model->isItemsBetween(now + 60'000, now + 150'000, QVector<int>({ 0, 5 }), strict, recursive);
|
||||
|
||||
|
||||
|
||||
|
||||
x = model->itemIdsBetween(0, now + 999'000, non_strict, recursive, sorted);
|
||||
// l() << "itemIdsBetween(0, now + 999'000, non_strict, recursive, sorted)" << x << "- okay?" << (x == QVector<int>({ 3, 5, 0, 2, 4, 1 }));
|
||||
#undef non_strict
|
||||
#undef strict
|
||||
#undef recursive
|
||||
#undef unsorted
|
||||
#undef sorted
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// item 1 von track 2 auf track 3, zu startTime 210'000
|
||||
model->moveItemToTrack(1, 3, now + 210'000);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "That didn't work. %s:%d\n", __FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
model->print(true);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// item 3 von track 3 auf track 2, zu startTime 150'000
|
||||
model->moveItemToTrack(3, 2, now + 150'000);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "That didn't work. %s:%d\n", __FUNCTION__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
/* now
|
||||
/30 /60 /90 /120 /150 /180 /210 /240
|
||||
| ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' | ' ' ' |
|
||||
T1 000000000
|
||||
>T2 333333333
|
||||
T3 2222222222222222111111111
|
||||
T4 55555555 44444444444444444
|
||||
*/
|
||||
|
||||
|
||||
model->print(true);
|
||||
|
||||
|
||||
#undef l
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// qputenv("TDS_DEVEL", "yes, of course");
|
||||
|
||||
qWarning() << "LtsTimeline - Test";
|
||||
NGLApplication app(argc, argv);
|
||||
app.createLogger("Timeline", "log4cxx_timeline.props");
|
||||
|
||||
STARTUP_LOG(app.baseLogger());
|
||||
|
||||
int ret = 0;
|
||||
|
||||
LtsTimeline::TimelineWidget *timeline = new LtsTimeline::TimelineWidget();
|
||||
LtsTimeline::TimelineModel *model = new LtsTimeline::TimelineModel(timeline->view());
|
||||
model->setSnapEnabled(true);
|
||||
populateModel(model);
|
||||
timeline->setModel(model);
|
||||
timeline->showDebugWidget();
|
||||
timeline->setWindowState(Qt::WindowMaximized);
|
||||
timeline->show();
|
||||
|
||||
// timeline->setMaximumZoomFactor(LtsTimeline::ZoomFactor::ThirtyMinutes);
|
||||
timeline->setZoomFactor(LtsTimeline::ZoomFactor::SixtyMinutes);
|
||||
timeline->setStartTime(QDateTime::currentMSecsSinceEpoch());
|
||||
timeline->setDuration(12 * 60 * 60 * 1000);
|
||||
|
||||
ret = app.exec();
|
||||
|
||||
SHUTDOWN_LOG(app.baseLogger());
|
||||
|
||||
return ret;
|
||||
}
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
#include "timeline_header_view.h"
|
||||
#include "timeline_view.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPolygon>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QPen>
|
||||
#include <QBrush>
|
||||
#include <QFont>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
QPixmap getMinLimitIndicator()
|
||||
{
|
||||
QPixmap pixmap(11, 11);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPolygon triangle;
|
||||
triangle << QPoint(4, 5);
|
||||
triangle << QPoint(10, 0);
|
||||
triangle << QPoint(10, 10);
|
||||
triangle << QPoint(4, 5);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
||||
painter.setPen(QGuiApplication::palette().color(QPalette::Text));
|
||||
painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text)));
|
||||
painter.drawLine(1, 0, 1, 10);
|
||||
painter.drawConvexPolygon(triangle);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap getMaxLimitIndicator()
|
||||
{
|
||||
QPixmap pixmap(11, 11);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPolygon triangle;
|
||||
triangle << QPoint(6, 5);
|
||||
triangle << QPoint(0, 0);
|
||||
triangle << QPoint(0, 10);
|
||||
triangle << QPoint(6, 5);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
||||
painter.setPen(QGuiApplication::palette().color(QPalette::Text));
|
||||
painter.setBrush(QBrush(QGuiApplication::palette().color(QPalette::Text)));
|
||||
painter.drawLine(10, 0, 10, 10);
|
||||
painter.drawConvexPolygon(triangle);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
|
||||
const QColor TimelineHeaderView::backgroundColor()
|
||||
{
|
||||
return QGuiApplication::palette().color(QPalette::Base);
|
||||
}
|
||||
|
||||
const QColor TimelineHeaderView::highlightColor()
|
||||
{
|
||||
return QRgb(0x43b0e0);
|
||||
}
|
||||
|
||||
|
||||
const int TimelineHeaderView::s_limitHeight = 22;
|
||||
|
||||
|
||||
TimelineHeaderView::TimelineHeaderView(QWidget *parent)
|
||||
: QHeaderView(Qt::Horizontal, parent)
|
||||
, m_mouseHandler(new TimelineHeaderViewMouseHandler(this))
|
||||
, m_liveTimer(new QTimer(this))
|
||||
, m_minLimitIndicator(getMinLimitIndicator())
|
||||
, m_maxLimitIndicator(getMaxLimitIndicator())
|
||||
{
|
||||
const QColor color = QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight);
|
||||
setStyleSheet(QStringLiteral("QTreeView { background-color: %1 }").arg(color.name(QColor::HexRgb)));
|
||||
try
|
||||
{
|
||||
m_timelineView = qobject_cast<TimelineView *>(parent);
|
||||
updateSection(Section::eActuator);
|
||||
updateSection(Section::eItems);
|
||||
if (m_timelineView)
|
||||
{
|
||||
connect(m_timelineView, &TimelineView::currentTimeChanged, this, &TimelineHeaderView::onUpdateTicks);
|
||||
connect(m_timelineView, &TimelineView::rangeChanged, this, &TimelineHeaderView::onUpdateTicks);
|
||||
connect(m_timelineView, &TimelineView::zoomChanged, this, &TimelineHeaderView::onUpdateTicks);
|
||||
}
|
||||
else
|
||||
qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent.";
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
qWarning() << "WARNING: main functionalities are inoperable without a TimelineView as parent.";
|
||||
}
|
||||
setStretchLastSection(true);
|
||||
setMinimumSectionSize(200);
|
||||
setFixedHeight(50);
|
||||
connect(this, &TimelineHeaderView::sectionResized, this, &TimelineHeaderView::onUpdateTicks);
|
||||
connect(m_liveTimer, &QTimer::timeout, this, &TimelineHeaderView::onLiveTimeout);
|
||||
|
||||
m_liveIndicatorColor = QGuiApplication::palette().color(QPalette::Link);
|
||||
}
|
||||
|
||||
TimelineHeaderView::~TimelineHeaderView()
|
||||
{
|
||||
m_timelineView = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderView::setParent(QWidget *)
|
||||
{
|
||||
qWarning() << QStringLiteral("WARNING: setting parent after construction is not allowed.");
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setTrackLabel(const QString &trackLabel)
|
||||
{
|
||||
m_trackLabel = trackLabel;
|
||||
updateSection(Section::eActuator);
|
||||
}
|
||||
|
||||
|
||||
void TimelineHeaderView::setDateTimeFormat(DateTimeFormat dateTimeFormat)
|
||||
{
|
||||
if (!m_isDateTimeFormatChangable)
|
||||
return;
|
||||
|
||||
m_dateTimeFormat = dateTimeFormat;
|
||||
for (Tick &majorTick : m_majorTicks)
|
||||
majorTick.label = DateTime::toString(majorTick.time, m_dateTimeFormat);
|
||||
for (Tick &mediumTick : m_mediumTicks)
|
||||
mediumTick.label = DateTime::toString(mediumTick.time, m_dateTimeFormat);
|
||||
for (Tick &minorTick : m_minorTicks)
|
||||
minorTick.label = DateTime::toString(minorTick.time, m_dateTimeFormat);
|
||||
updateSection(Section::eItems);
|
||||
}
|
||||
|
||||
|
||||
void TimelineHeaderView::setIndicatorScalingFactor(double factor)
|
||||
{
|
||||
if (factor == m_indicatorScalingFactor || factor <= 0.)
|
||||
return;
|
||||
|
||||
m_indicatorScalingFactor = factor;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setIndicatorTime(qint64 time)
|
||||
{
|
||||
if (time != -1 && m_timelineView)
|
||||
if (m_timelineView->timelineModel())
|
||||
if (m_timelineView->timelineModel()->isSnapEnabled())
|
||||
time = m_timelineView->timelineModel()->snapTime(time);
|
||||
|
||||
|
||||
|
||||
|
||||
if (time == m_indicatorTime)
|
||||
return;
|
||||
|
||||
m_indicatorTime = time;
|
||||
viewport()->update();
|
||||
emit indicatorTimeChanged(m_indicatorTime);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setIndicatorPosition(int position)
|
||||
{
|
||||
setIndicatorTime(toTime(position));
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setIndicatorVisible(bool isIndicatorVisible)
|
||||
{
|
||||
if (isIndicatorVisible == m_isIndicatorVisible)
|
||||
return;
|
||||
|
||||
m_isIndicatorVisible = isIndicatorVisible;
|
||||
m_timelineView->viewport()->update();
|
||||
emit indicatorVisibilityChanged(m_isIndicatorVisible);
|
||||
}
|
||||
|
||||
|
||||
void TimelineHeaderView::setLiveIndicatorScalingFactor(double factor)
|
||||
{
|
||||
if (factor == m_liveIndicatorScalingFactor || factor <= 0.)
|
||||
return;
|
||||
|
||||
m_liveIndicatorScalingFactor = factor;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setLiveIndicatorColor(const QColor &color)
|
||||
{
|
||||
if (color == m_liveIndicatorColor)
|
||||
return;
|
||||
|
||||
m_liveIndicatorColor = color;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setLiveIndicatorTime(qint64 time)
|
||||
{
|
||||
if (time != -1 && m_timelineView)
|
||||
if (m_timelineView->timelineModel())
|
||||
if (m_timelineView->timelineModel()->isSnapEnabled())
|
||||
time = m_timelineView->timelineModel()->snapTime(time);
|
||||
|
||||
if (time == m_liveIndicatorTime)
|
||||
return;
|
||||
|
||||
m_liveIndicatorTime = time;
|
||||
viewport()->update();
|
||||
emit liveIndicatorTimeChanged(m_liveIndicatorTime);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setLiveIndicatorPosition(int position)
|
||||
{
|
||||
setLiveIndicatorTime(toTime(position));
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setLiveIndicatorVisible(bool isLiveIndicatorVisible)
|
||||
{
|
||||
if (isLiveIndicatorVisible == m_isLiveIndicatorVisible)
|
||||
return;
|
||||
|
||||
m_isLiveIndicatorVisible = isLiveIndicatorVisible;
|
||||
viewport()->update();
|
||||
m_timelineView->viewport()->update();
|
||||
emit liveIndicatorVisibilityChanged(m_isLiveIndicatorVisible);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::startLiveTimer(qint64 startTime, int interval)
|
||||
{
|
||||
if (interval <= 0)
|
||||
{
|
||||
qCritical() << LOGFUNCTION << "invalid time interval:" << interval;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_liveTimer->isActive())
|
||||
{
|
||||
qWarning() << LOGFUNCTION << "timer is already active - restarting";
|
||||
m_liveTimer->stop();
|
||||
}
|
||||
|
||||
if (startTime < 0)
|
||||
startTime = m_timelineView->currentTime();
|
||||
|
||||
m_liveTimerInterval = interval;
|
||||
setLiveIndicatorTime(startTime);
|
||||
setLiveIndicatorVisible(true);
|
||||
m_liveTimer->setSingleShot(false);
|
||||
m_liveTimer->start(m_liveTimerInterval);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::stopLiveTimer()
|
||||
{
|
||||
if (!m_liveTimer->isActive())
|
||||
return;
|
||||
|
||||
qDebug() << LOGFUNCTION;
|
||||
m_liveTimer->stop();
|
||||
m_liveIndicatorTime = -1;
|
||||
setLiveIndicatorVisible(false);
|
||||
}
|
||||
|
||||
|
||||
void TimelineHeaderView::setHoverTime(qint64 time)
|
||||
{
|
||||
if (!m_isHoverVisible)
|
||||
return;
|
||||
|
||||
if (time < m_timelineView->currentTime() || m_timelineView->currentTime() + m_timelineView->zoomFactor() < time)
|
||||
time = -1;
|
||||
|
||||
m_hoverTime = time;
|
||||
m_hoverPosition = toPixel(m_hoverTime);
|
||||
updateSection(Section::eItems);
|
||||
emit hoverPositionChanged(m_hoverPosition);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setHoverPosition(int position)
|
||||
{
|
||||
if (!m_isHoverVisible)
|
||||
return;
|
||||
|
||||
m_hoverPosition = position;
|
||||
m_hoverTime = toTime(m_hoverPosition);
|
||||
updateSection(Section::eItems);
|
||||
emit hoverPositionChanged(m_hoverPosition);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setHoverVisible(bool visible)
|
||||
{
|
||||
if (visible == m_isHoverVisible)
|
||||
return;
|
||||
|
||||
m_isHoverVisible = visible;
|
||||
updateSection(Section::eItems);
|
||||
m_timelineView->viewport()->update();
|
||||
emit hoverVisibilityChanged(m_isHoverVisible);
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setHoverTimeVisible(bool visible)
|
||||
{
|
||||
if (visible == m_isHoverTimeVisible)
|
||||
return;
|
||||
|
||||
m_isHoverTimeVisible = visible;
|
||||
updateSection(Section::eItems);
|
||||
m_timelineView->viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setHoverDateTimeFormat(DateTimeFormat format)
|
||||
{
|
||||
if (format == m_hoverDateTimeFormat)
|
||||
return;
|
||||
|
||||
m_hoverDateTimeFormat = format;
|
||||
updateSection(Section::eItems);
|
||||
m_timelineView->viewport()->update();
|
||||
}
|
||||
|
||||
|
||||
void TimelineHeaderView::setLimitsVisible(bool visible)
|
||||
{
|
||||
if (visible == m_isLimitsVisible)
|
||||
return;
|
||||
|
||||
m_isLimitsVisible = visible;
|
||||
if (m_isLimitsVisible)
|
||||
setFixedHeight(70);
|
||||
else
|
||||
setFixedHeight(50);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineHeaderView::setLimitsDateTimeFormat(DateTimeFormat format)
|
||||
{
|
||||
if (format == m_limitsDateTimeFormat)
|
||||
return;
|
||||
|
||||
m_limitsDateTimeFormat = format;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
|
||||
int TimelineHeaderView::toPixel(qint64 time) const
|
||||
{
|
||||
if (time == -1)
|
||||
return -1;
|
||||
|
||||
return static_cast<int>(double(time - m_timelineView->currentTime()) / m_timelineView->zoomFactor() *
|
||||
(geometry().width() - sectionPosition(Section::eItems)) + sectionPosition(Section::eItems));
|
||||
}
|
||||
|
||||
qint64 TimelineHeaderView::toTime(int pixel) const
|
||||
{
|
||||
return m_timelineView->currentTime() +
|
||||
(m_timelineView->zoomFactor() * (double(pixel - sectionPosition(Section::eItems)) /
|
||||
(geometry().width() - sectionPosition(Section::eItems))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
|
||||
{
|
||||
if (logicalIndex == Section::eActuator)
|
||||
{
|
||||
painter->save();
|
||||
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing);
|
||||
painter->setPen(backgroundColor());
|
||||
painter->setBrush(QBrush(backgroundColor()));
|
||||
painter->drawRect(rect);
|
||||
painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 2));
|
||||
painter->drawLine(rect.topRight(), QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2));
|
||||
painter->drawLine(QPoint(rect.right() - rect.height() / 2, rect.top() + rect.height() / 2), rect.bottomRight());
|
||||
painter->setFont(m_font);
|
||||
painter->drawText(rect, Qt::AlignCenter, m_trackLabel);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
painter->save();
|
||||
|
||||
painter->setClipping(true);
|
||||
painter->setClipRect(rect);
|
||||
painter->setFont(m_font);
|
||||
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing);
|
||||
painter->setPen(backgroundColor());
|
||||
painter->setBrush(backgroundColor());
|
||||
painter->drawRect(rect);
|
||||
|
||||
int bottom = m_isLimitsVisible ? rect.bottom() - s_limitHeight : rect.bottom();
|
||||
int height = (bottom - rect.top()) / 2;
|
||||
|
||||
|
||||
for (const Tick &tick : qAsConst(m_majorTicks))
|
||||
{
|
||||
painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1));
|
||||
painter->drawText(QRect(tick.pos - 160 / 2, rect.top(), 160, height), Qt::AlignCenter, tick.label);
|
||||
if (m_isLimitsVisible)
|
||||
painter->drawLine(tick.pos, bottom, tick.pos, rect.bottom());
|
||||
painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1));
|
||||
painter->drawLine(tick.pos, rect.top() + height, tick.pos, bottom);
|
||||
}
|
||||
|
||||
painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Text), 1));
|
||||
|
||||
|
||||
for (const Tick &tick : qAsConst(m_mediumTicks))
|
||||
painter->drawLine(tick.pos, bottom - static_cast<int>(double(height) * 2 / 3), tick.pos, bottom);
|
||||
|
||||
|
||||
for (const Tick &tick : qAsConst(m_minorTicks))
|
||||
painter->drawLine(tick.pos, bottom - static_cast<int>(double(height) / 3), tick.pos, bottom);
|
||||
|
||||
|
||||
if (m_isLimitsVisible)
|
||||
{
|
||||
QString minLimitString = DateTime::toString(m_timelineView->m_currentTime, m_limitsDateTimeFormat);
|
||||
QString maxLimitString = DateTime::toString(m_timelineView->m_currentTime + m_timelineView->m_zoomFactor, m_limitsDateTimeFormat);
|
||||
int minLimitWidth = painter->fontMetrics().horizontalAdvance(minLimitString);
|
||||
int maxLimitWidth = painter->fontMetrics().horizontalAdvance(maxLimitString);
|
||||
painter->setPen(backgroundColor());
|
||||
painter->setBrush(QBrush(backgroundColor()));
|
||||
painter->drawRect(rect.left() + 4, geometry().bottom() - s_limitHeight, minLimitWidth + 11, 19);
|
||||
painter->drawRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth + 11, 19);
|
||||
painter->setPen(QGuiApplication::palette().color(QPalette::Text));
|
||||
painter->drawPixmap(rect.left() + 4, geometry().bottom() - 15, 11, 11, m_minLimitIndicator);
|
||||
painter->drawPixmap(rect.right() - 15, geometry().bottom() - 15, 11, 11, m_maxLimitIndicator);
|
||||
painter->drawText(
|
||||
QRect(rect.left() + 18, geometry().bottom() - s_limitHeight, minLimitWidth, s_limitHeight - 1),
|
||||
Qt::AlignLeft, minLimitString
|
||||
);
|
||||
painter->drawText(
|
||||
QRect(rect.right() - maxLimitWidth - 18, geometry().bottom() - s_limitHeight, maxLimitWidth, s_limitHeight - 1),
|
||||
Qt::AlignRight, maxLimitString
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (m_isHoverVisible)
|
||||
{
|
||||
if (m_hoverPosition != -1)
|
||||
{
|
||||
painter->setPen(highlightColor());
|
||||
painter->drawLine(m_hoverPosition, rect.top(), m_hoverPosition, rect.bottom());
|
||||
if (m_isHoverTimeVisible)
|
||||
{
|
||||
QString dateTimeString = DateTime::toString(m_hoverTime, m_hoverDateTimeFormat);
|
||||
|
||||
|
||||
|
||||
const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString) + 2;
|
||||
const int textHeight = m_isLimitsVisible ? s_limitHeight - 1 : 19;
|
||||
QRect dateTimeRect(m_hoverPosition, geometry().bottom() - textHeight, textWidth, textHeight - 1);
|
||||
if (dateTimeRect.right() >= geometry().right())
|
||||
{
|
||||
dateTimeRect.setLeft(m_hoverPosition - textWidth);
|
||||
dateTimeRect.setWidth(textWidth);
|
||||
}
|
||||
painter->setBrush(QBrush(backgroundColor()));
|
||||
painter->drawRect(dateTimeRect);
|
||||
painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (m_isIndicatorVisible)
|
||||
{
|
||||
int pos = toPixel(m_indicatorTime);
|
||||
if (m_indicatorTime != -1 && (m_timelineView->currentTime() <= m_indicatorTime &&
|
||||
m_indicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor()))
|
||||
{
|
||||
painter->setPen(highlightColor());
|
||||
painter->setBrush(QBrush(highlightColor()));
|
||||
int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_indicatorScalingFactor;
|
||||
int top = bottom - static_cast<int>(0.866 * sideLength);
|
||||
QPolygon polygon;
|
||||
polygon << QPoint(pos, bottom);
|
||||
polygon << QPoint(pos + sideLength / 2, top);
|
||||
polygon << QPoint(pos - sideLength / 2, top);
|
||||
polygon << QPoint(pos, bottom);
|
||||
QString dateTimeString = DateTime::toString(m_indicatorTime, m_hoverDateTimeFormat);
|
||||
const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString);
|
||||
painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0));
|
||||
painter->drawPolygon(polygon);
|
||||
if (m_isLimitsVisible)
|
||||
painter->drawLine(pos, bottom, pos, rect.bottom());
|
||||
QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19);
|
||||
painter->setBrush(QBrush(backgroundColor()));
|
||||
painter->drawRect(dateTimeRect);
|
||||
painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString);
|
||||
painter->drawLine(pos, geometry().top() + 19, pos, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (m_isLiveIndicatorVisible)
|
||||
{
|
||||
int pos = toPixel(m_liveIndicatorTime);
|
||||
if (pos != -1 && (m_timelineView->currentTime() <= m_liveIndicatorTime &&
|
||||
m_liveIndicatorTime <= m_timelineView->currentTime() + m_timelineView->zoomFactor()))
|
||||
{
|
||||
painter->setPen(m_liveIndicatorColor);
|
||||
painter->setBrush(QBrush(m_liveIndicatorColor));
|
||||
int sideLength = double(m_isLimitsVisible ? rect.height() - height : rect.height()) * m_liveIndicatorScalingFactor;
|
||||
int top = bottom - static_cast<int>(0.866 * sideLength);
|
||||
QPolygon polygon;
|
||||
polygon << QPoint(pos, bottom);
|
||||
polygon << QPoint(pos + sideLength / 2, top);
|
||||
polygon << QPoint(pos - sideLength / 2, top);
|
||||
polygon << QPoint(pos, bottom);
|
||||
QString dateTimeString = DateTime::toString(m_liveIndicatorTime, m_hoverDateTimeFormat);
|
||||
const int textWidth = painter->fontMetrics().horizontalAdvance(dateTimeString);
|
||||
painter->setClipRect(rect.adjusted(-textWidth / 2, 0, 0, 0));
|
||||
painter->drawPolygon(polygon);
|
||||
QRect dateTimeRect(pos - textWidth / 2, geometry().top(), textWidth + 2, 19);
|
||||
if (dateTimeRect.right() > geometry().right() - 20)
|
||||
dateTimeRect.adjust(-22, 0, -22, 0);
|
||||
painter->setBrush(QBrush(backgroundColor()));
|
||||
painter->drawRect(dateTimeRect);
|
||||
painter->drawText(dateTimeRect, Qt::AlignCenter, dateTimeString);
|
||||
}
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderView::updateMajorTicks()
|
||||
{
|
||||
m_majorTicks.clear();
|
||||
|
||||
if (m_timelineView->duration() <= 0)
|
||||
return;
|
||||
|
||||
const int interval = Zoom::majorInterval(m_timelineView->zoomFactor());
|
||||
if (interval == -1)
|
||||
return;
|
||||
|
||||
qint64 time = m_timelineView->currentTime();
|
||||
const int mod = time % interval;
|
||||
if (mod)
|
||||
time += interval - mod;
|
||||
const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor();
|
||||
while (time <= end)
|
||||
{
|
||||
QString label = DateTime::toString(time, m_dateTimeFormat);
|
||||
Tick tick(toPixel(time), time, label);
|
||||
m_majorTicks.append(tick);
|
||||
time += interval;
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineHeaderView::updateMediumTicks()
|
||||
{
|
||||
m_mediumTicks.clear();
|
||||
|
||||
if (m_timelineView->duration() <= 0)
|
||||
return;
|
||||
|
||||
const int interval = Zoom::mediumInterval(m_timelineView->zoomFactor());
|
||||
if (interval == -1)
|
||||
return;
|
||||
|
||||
qint64 time = m_timelineView->currentTime();
|
||||
const int mod = time % interval;
|
||||
if (mod)
|
||||
time += interval - mod;
|
||||
const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor();
|
||||
while (time <= end)
|
||||
{
|
||||
Tick tick(time);
|
||||
|
||||
|
||||
auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other)
|
||||
{
|
||||
return tick.time == other.time;
|
||||
});
|
||||
if (it != m_majorTicks.end())
|
||||
{
|
||||
time += interval;
|
||||
continue;
|
||||
}
|
||||
|
||||
tick.pos = toPixel(tick.time);
|
||||
tick.label = DateTime::toString(tick.time, m_dateTimeFormat);
|
||||
m_mediumTicks.append(tick);
|
||||
time += interval;
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineHeaderView::updateMinorTicks()
|
||||
{
|
||||
m_minorTicks.clear();
|
||||
|
||||
if (m_timelineView->duration() <= 0)
|
||||
return;
|
||||
|
||||
const int interval = Zoom::minorInterval(m_timelineView->zoomFactor());
|
||||
if (interval == -1)
|
||||
return;
|
||||
|
||||
qint64 time = m_timelineView->currentTime();
|
||||
const int mod = time % interval;
|
||||
if (mod)
|
||||
time += interval - mod;
|
||||
const qint64 end = m_timelineView->currentTime() + m_timelineView->zoomFactor();
|
||||
while (time <= end)
|
||||
{
|
||||
Tick tick(time);
|
||||
|
||||
|
||||
auto it = std::find_if(m_majorTicks.begin(), m_majorTicks.end(), [tick](const Tick &other)
|
||||
{
|
||||
return tick.time == other.time;
|
||||
});
|
||||
if (it != m_majorTicks.end())
|
||||
{
|
||||
time += interval;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
it = std::find_if(m_mediumTicks.begin(), m_mediumTicks.end(), [tick](const Tick &other)
|
||||
{
|
||||
return tick.time == other.time;
|
||||
});
|
||||
if (it != m_mediumTicks.end())
|
||||
{
|
||||
time += interval;
|
||||
continue;
|
||||
}
|
||||
|
||||
tick.pos = toPixel(tick.time);
|
||||
tick.label = DateTime::toString(tick.time, m_dateTimeFormat);
|
||||
m_minorTicks.append(tick);
|
||||
time += interval;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderView::onUpdateTicks()
|
||||
{
|
||||
updateMajorTicks();
|
||||
updateMediumTicks();
|
||||
updateMinorTicks();
|
||||
|
||||
if (m_isIndicatorVisible)
|
||||
setIndicatorTime(m_indicatorTime);
|
||||
|
||||
if (m_isLiveIndicatorVisible)
|
||||
setLiveIndicatorTime(m_liveIndicatorTime);
|
||||
|
||||
updateSection(Section::eItems);
|
||||
emit ticksChanged(m_majorTicks);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderView::onLiveTimeout()
|
||||
{
|
||||
setLiveIndicatorTime(m_liveIndicatorTime + m_liveTimerInterval);
|
||||
}
|
||||
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include "timeline_header_view_mouse_handler.h"
|
||||
#include "timeline_utils.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QHeaderView>
|
||||
|
||||
|
||||
class QMouseEvent;
|
||||
class QPainter;
|
||||
class QRect;
|
||||
class QTimer;
|
||||
class QWheelEvent;
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineHeaderView : public QHeaderView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class TimelineHeaderViewMouseHandler;
|
||||
friend class TimelineViewMouseHandler;
|
||||
|
||||
public:
|
||||
static const QColor backgroundColor();
|
||||
static const QColor highlightColor();
|
||||
|
||||
static const int s_limitHeight;
|
||||
|
||||
enum Section { eActuator = 0, eItems = 1 };
|
||||
|
||||
TimelineHeaderView(QWidget *parent = nullptr);
|
||||
~TimelineHeaderView();
|
||||
|
||||
void setParent(QWidget *);
|
||||
void setTrackLabel(const QString &trackLabel);
|
||||
|
||||
const Ticks &majorTicks() const { return m_majorTicks; }
|
||||
const Ticks &mediumTicks() const { return m_mediumTicks; }
|
||||
const Ticks &minorTicks() const { return m_minorTicks; }
|
||||
|
||||
DateTimeFormat dateTimeFormat() const { return m_dateTimeFormat; }
|
||||
void setDateTimeFormat(DateTimeFormat dateTimeFormat);
|
||||
void setDateTimeFormatChangable(bool changable) { m_isDateTimeFormatChangable = changable; }
|
||||
bool isDateTimeFormatChangable() const { return m_isDateTimeFormatChangable; }
|
||||
|
||||
double indicatorScalingFactor() const { return m_indicatorScalingFactor; }
|
||||
void setIndicatorScalingFactor(double factor);
|
||||
qint64 indicatorTime() const { return m_indicatorTime; }
|
||||
void setIndicatorTime(qint64 time);
|
||||
int indicatorPosition() const { return toPixel(m_indicatorTime); }
|
||||
void setIndicatorPosition(int position);
|
||||
bool isIndicatorVisible() const { return m_isIndicatorVisible; }
|
||||
void setIndicatorVisible(bool isIndicatorVisible);
|
||||
bool indicatorLocked() const { return m_isIndicatorLocked; }
|
||||
void setIndicatorLocked(bool locked) { m_isIndicatorLocked = locked; }
|
||||
void lockIndicator() { setIndicatorLocked(true); }
|
||||
void unlockIndicator() { setIndicatorLocked(false); }
|
||||
|
||||
double liveIndicatorScalingFactor() const { return m_liveIndicatorScalingFactor; }
|
||||
void setLiveIndicatorScalingFactor(double factor);
|
||||
QColor liveIndicatorColor() const { return m_liveIndicatorColor; }
|
||||
void setLiveIndicatorColor(const QColor &color);
|
||||
qint64 liveIndicatorTime() const { return m_liveIndicatorTime; }
|
||||
void setLiveIndicatorTime(qint64 time);
|
||||
int liveIndicatorPosition() const { return toPixel(m_liveIndicatorTime); }
|
||||
void setLiveIndicatorPosition(int position);
|
||||
bool isLiveIndicatorVisible() const { return m_isLiveIndicatorVisible; }
|
||||
void setLiveIndicatorVisible(bool isLiveIndicatorVisible);
|
||||
void startLiveTimer(qint64 startTime = -1, int interval = 1000);
|
||||
void stopLiveTimer();
|
||||
|
||||
qint64 hoverTime() const { return m_hoverTime; }
|
||||
void setHoverTime(qint64 time);
|
||||
int hoverPosition() const { return m_hoverPosition; }
|
||||
void setHoverPosition(int position);
|
||||
bool isHoverVisible() const { return m_isHoverVisible; }
|
||||
void setHoverVisible(bool visible);
|
||||
bool isHoverTimeVisible() const { return m_isHoverTimeVisible; }
|
||||
void setHoverTimeVisible(bool visible);
|
||||
DateTimeFormat hoverDateTimeFormat() const { return m_hoverDateTimeFormat; }
|
||||
void setHoverDateTimeFormat(DateTimeFormat format);
|
||||
|
||||
bool isLimitsVisible() const { return m_isLimitsVisible; }
|
||||
void setLimitsVisible(bool visible);
|
||||
DateTimeFormat limitsDateTimeFormat() const { return m_limitsDateTimeFormat; }
|
||||
void setLimitsDateTimeFormat(DateTimeFormat format);
|
||||
|
||||
int toPixel(qint64 time) const;
|
||||
qint64 toTime(int pixel) const;
|
||||
|
||||
QFont &font() { return m_font; }
|
||||
|
||||
TimelineHeaderViewMouseHandler *mouseHandler() { return m_mouseHandler; }
|
||||
|
||||
void wheelEvent(QWheelEvent *e) override { m_mouseHandler->wheelEvent(e); }
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *e) override { m_mouseHandler->mouseMoveEvent(e); }
|
||||
void mousePressEvent(QMouseEvent *e) override { m_mouseHandler->mousePressEvent(e); }
|
||||
void mouseReleaseEvent(QMouseEvent *e) override { m_mouseHandler->mouseReleaseEvent(e); }
|
||||
|
||||
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
|
||||
|
||||
private:
|
||||
void updateMajorTicks();
|
||||
void updateMediumTicks();
|
||||
void updateMinorTicks();
|
||||
|
||||
public slots:
|
||||
void onUpdateTicks();
|
||||
|
||||
private slots:
|
||||
void onLiveTimeout();
|
||||
|
||||
signals:
|
||||
void ticksChanged(const Ticks &);
|
||||
void indicatorTimeChanged(qint64);
|
||||
void indicatorVisibilityChanged(bool);
|
||||
void liveIndicatorTimeChanged(qint64);
|
||||
void liveIndicatorVisibilityChanged(bool);
|
||||
void hoverPositionChanged(int);
|
||||
void hoverVisibilityChanged(bool);
|
||||
|
||||
private:
|
||||
TimelineHeaderViewMouseHandler *m_mouseHandler{nullptr};
|
||||
TimelineView *m_timelineView{nullptr};
|
||||
Ticks m_majorTicks;
|
||||
Ticks m_mediumTicks;
|
||||
Ticks m_minorTicks;
|
||||
DateTimeFormat m_dateTimeFormat{DateTimeFormat::DTG};
|
||||
bool m_isDateTimeFormatChangable{true};
|
||||
|
||||
double m_indicatorScalingFactor{0.5};
|
||||
qint64 m_indicatorTime{-1};
|
||||
bool m_isIndicatorVisible{true};
|
||||
bool m_isIndicatorLocked{false};
|
||||
|
||||
double m_liveIndicatorScalingFactor{0.5};
|
||||
QColor m_liveIndicatorColor;
|
||||
qint64 m_liveIndicatorTime{-1};
|
||||
bool m_isLiveIndicatorVisible{false};
|
||||
QTimer *m_liveTimer{nullptr};
|
||||
int m_liveTimerInterval{1000};
|
||||
|
||||
qint64 m_hoverTime{-1};
|
||||
int m_hoverPosition{-1};
|
||||
bool m_isHoverVisible{true};
|
||||
bool m_isHoverTimeVisible{true};
|
||||
DateTimeFormat m_hoverDateTimeFormat{DateTimeFormat::DTG};
|
||||
|
||||
bool m_isLimitsVisible{false};
|
||||
DateTimeFormat m_limitsDateTimeFormat{DateTimeFormat::DTG};
|
||||
const QPixmap m_minLimitIndicator;
|
||||
const QPixmap m_maxLimitIndicator;
|
||||
|
||||
QString m_trackLabel;
|
||||
|
||||
QFont m_font;
|
||||
|
||||
};
|
||||
|
||||
typedef TimelineHeaderView::Section Section;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "timeline_header_view_mouse_handler.h"
|
||||
#include "timeline_header_view.h"
|
||||
#include "timeline_view.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineHeaderViewMouseHandler::TimelineHeaderViewMouseHandler(TimelineHeaderView *header)
|
||||
: m_header(header)
|
||||
{}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineHeaderViewMouseHandler::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (m_isLeftButtonPressed)
|
||||
m_hasMouseMovedLeft = true;
|
||||
if (m_isRightButtonPressed)
|
||||
m_hasMouseMovedRight = true;
|
||||
|
||||
if (!m_isResizingSection)
|
||||
{
|
||||
if (m_header->sectionPosition(Section::eItems) - 10 < e->pos().x() && e->pos().x() < m_header->sectionPosition(Section::eItems))
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::SizeHorCursor));
|
||||
else if (e->pos().y() <= m_header->geometry().bottom())
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor));
|
||||
}
|
||||
else
|
||||
m_header->resizeSection(Section::eActuator, std::min(e->pos().x(), m_header->geometry().width() / 2));
|
||||
|
||||
const bool isWithinRangeX = isMouseWithinRangeX(e->pos().x());
|
||||
if (!isWithinRangeX && !m_isLeftButtonPressed)
|
||||
{
|
||||
m_header->setHoverPosition(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWithinRangeX)
|
||||
m_header->setHoverPosition(e->pos().x());
|
||||
if (m_isLeftButtonPressed)
|
||||
{
|
||||
if (m_isIndicatorSelected && !m_header->m_isIndicatorLocked)
|
||||
{
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::ClosedHandCursor));
|
||||
if (isWithinRangeX)
|
||||
m_header->setIndicatorPosition(e->pos().x());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isMouseOverIndicator(e->pos()) && m_header->m_isIndicatorVisible && !m_header->m_isIndicatorLocked)
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::OpenHandCursor));
|
||||
else if (e->pos().y() <= m_header->geometry().bottom())
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor));
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineHeaderViewMouseHandler::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() == Qt::LeftButton)
|
||||
{
|
||||
m_lastPos = e->pos();
|
||||
m_isLeftButtonPressed = true;
|
||||
if (isMouseOverIndicator(e->pos()) && m_header->m_isIndicatorVisible && !m_header->m_isIndicatorLocked)
|
||||
{
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::ClosedHandCursor));
|
||||
m_isIndicatorSelected = true;
|
||||
}
|
||||
else if (m_header->m_timelineView->cursor().shape() == Qt::SizeHorCursor)
|
||||
{
|
||||
m_isResizingSection = true;
|
||||
}
|
||||
}
|
||||
if (e->button() == Qt::RightButton)
|
||||
m_isRightButtonPressed = true;
|
||||
}
|
||||
|
||||
void TimelineHeaderViewMouseHandler::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() == Qt::LeftButton)
|
||||
{
|
||||
if (!m_hasMouseMovedLeft && isMouseWithinRangeX(e->pos().x()) && !m_header->m_isIndicatorLocked)
|
||||
m_header->setIndicatorPosition(e->pos().x());
|
||||
if (isMouseOverIndicator(e->pos()))
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::OpenHandCursor));
|
||||
else
|
||||
m_header->m_timelineView->setCursor(QCursor(Qt::ArrowCursor));
|
||||
m_lastPos = {};
|
||||
m_isIndicatorSelected = false;
|
||||
m_isResizingSection = false;
|
||||
m_isLeftButtonPressed = false;
|
||||
m_hasMouseMovedLeft = false;
|
||||
}
|
||||
if (e->button() == Qt::RightButton)
|
||||
{
|
||||
m_header->setDateTimeFormat(static_cast<DateTimeFormat>((m_header->dateTimeFormat() + 1) % DateTime::numberOfDateTimeFormats()));
|
||||
m_isRightButtonPressed = false;
|
||||
m_hasMouseMovedRight = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineHeaderViewMouseHandler::wheelEvent(QWheelEvent *e)
|
||||
{
|
||||
if (!m_header->m_timelineView)
|
||||
return;
|
||||
|
||||
if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier)
|
||||
{
|
||||
if (e->angleDelta().y() < 0)
|
||||
m_header->m_timelineView->setZoomFactor(Zoom::increment(m_header->m_timelineView->zoomFactor()));
|
||||
else
|
||||
m_header->m_timelineView->setZoomFactor(Zoom::decrement(m_header->m_timelineView->zoomFactor()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (e->angleDelta().y() > 0)
|
||||
m_header->m_timelineView->setCurrentTime(
|
||||
m_header->m_timelineView->currentTime() - Zoom::majorInterval(m_header->m_timelineView->zoomFactor())
|
||||
);
|
||||
else
|
||||
m_header->m_timelineView->setCurrentTime(
|
||||
m_header->m_timelineView->currentTime() + Zoom::majorInterval(m_header->m_timelineView->zoomFactor())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool TimelineHeaderViewMouseHandler::isMouseWithinRangeX(int x) const
|
||||
{
|
||||
return m_header->sectionPosition(Section::eItems) < x && x < m_header->geometry().right();
|
||||
}
|
||||
|
||||
bool TimelineHeaderViewMouseHandler::isMouseWithinRangeY(int y) const
|
||||
{
|
||||
return m_header->geometry().top() < y && y < m_header->geometry().bottom();
|
||||
}
|
||||
|
||||
bool TimelineHeaderViewMouseHandler::isMouseOverIndicator(const QPoint &pos) const
|
||||
{
|
||||
const int height = m_header->height();
|
||||
return m_header->indicatorPosition() - height / 4 <= pos.x() && pos.x() <= m_header->indicatorPosition() + height / 4 &&
|
||||
m_header->geometry().center().y() <= pos.y() && pos.y() < m_header->geometry().bottom();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
|
||||
class QMouseEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineHeaderView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineHeaderViewMouseHandler
|
||||
{
|
||||
|
||||
public:
|
||||
explicit TimelineHeaderViewMouseHandler(TimelineHeaderView *header);
|
||||
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
void wheelEvent(QWheelEvent *e);
|
||||
|
||||
bool isBusy() const { return m_isIndicatorSelected || m_isResizingSection; }
|
||||
bool isIndicatorSelected() const { return m_isIndicatorSelected; }
|
||||
|
||||
private:
|
||||
|
||||
bool isMouseWithinRangeX(int x) const;
|
||||
bool isMouseWithinRangeY(int y) const;
|
||||
bool isMouseOverIndicator(const QPoint &pos) const;
|
||||
|
||||
private:
|
||||
TimelineHeaderView *m_header{nullptr};
|
||||
QPoint m_lastPos;
|
||||
bool m_isLeftButtonPressed{false};
|
||||
bool m_isRightButtonPressed{false};
|
||||
bool m_hasMouseMovedLeft{false};
|
||||
bool m_hasMouseMovedRight{false};
|
||||
bool m_isIndicatorSelected{false};
|
||||
bool m_isResizingSection{false};
|
||||
};
|
||||
|
||||
}
|
||||
Executable
+761
@@ -0,0 +1,761 @@
|
||||
|
||||
|
||||
|
||||
#include "timeline_model.h"
|
||||
#include "timeline_view_drag_handler.h"
|
||||
#include "track.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineModel::TimelineModel(TimelineView *view, QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_view(view)
|
||||
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineModel"))
|
||||
{
|
||||
m_rootTrack.m_id = nextFreeTrackId();
|
||||
m_trackCache.insert(std::pair<int, Track *>(m_rootTrack.m_id, &m_rootTrack));
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::clear()
|
||||
{
|
||||
m_rootTrack.m_childrenTracks.clear();
|
||||
m_rootTrack.m_items.clear();
|
||||
|
||||
|
||||
m_rootTrack.m_copyItemIds.clear();
|
||||
m_trackCache.clear();
|
||||
|
||||
m_itemCache.clear();
|
||||
|
||||
m_currentTrackId = 1;
|
||||
m_currentItemId = 0;
|
||||
m_trackCache.insert(std::pair<int, Track *>(m_rootTrack.m_id, &m_rootTrack));
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::clearItems()
|
||||
{
|
||||
for (std::pair<int , Track *> p : m_trackCache)
|
||||
p.second->clearItems();
|
||||
|
||||
m_itemCache.clear();
|
||||
m_currentItemId = 0;
|
||||
}
|
||||
|
||||
|
||||
Track &TimelineModel::track(int id) noexcept(false)
|
||||
{
|
||||
if (!m_trackCaching)
|
||||
{
|
||||
if (id == m_rootTrack.id())
|
||||
return m_rootTrack;
|
||||
return m_rootTrack.track(id);
|
||||
}
|
||||
|
||||
auto it = m_trackCache.find(id);
|
||||
|
||||
if (it == m_trackCache.end())
|
||||
throw QStringLiteral("Model: track with id %1 not found in cache").arg(id);
|
||||
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
|
||||
const Track &TimelineModel::track(int id) const noexcept(false)
|
||||
{
|
||||
if (!m_trackCaching)
|
||||
{
|
||||
if (id == m_rootTrack.id())
|
||||
return m_rootTrack;
|
||||
return m_rootTrack.track(id);
|
||||
}
|
||||
|
||||
std::map<int , Track *>::const_iterator it = m_trackCache.find(id);
|
||||
|
||||
if (it == m_trackCache.cend())
|
||||
throw QStringLiteral("Model: const track with id %1 not found in cache").arg(id);
|
||||
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
|
||||
Item &TimelineModel::item(int id) noexcept(false)
|
||||
{
|
||||
if (!m_itemCaching)
|
||||
return m_rootTrack.item(id);
|
||||
|
||||
auto it = m_itemCache.find(id);
|
||||
|
||||
if (it == m_itemCache.end())
|
||||
throw QStringLiteral("Model: item with id %1 not found in cache").arg(id);
|
||||
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
|
||||
const Item &TimelineModel::item(int id) const noexcept(false)
|
||||
{
|
||||
if (!m_itemCaching)
|
||||
return m_rootTrack.item(id);
|
||||
|
||||
decltype(m_itemCache)::const_iterator it = m_itemCache.find(id);
|
||||
|
||||
if (it == m_itemCache.cend())
|
||||
throw QStringLiteral("Model: const item with id %1 not found in cache").arg(id);
|
||||
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
|
||||
const QVector<int> TimelineModel::itemIds() const
|
||||
{
|
||||
if (!m_itemCaching)
|
||||
return m_rootTrack.itemIds(true);
|
||||
|
||||
QVector<int> ids;
|
||||
std::transform(m_itemCache.cbegin(), m_itemCache.cend(), std::back_inserter(ids),
|
||||
[](const std::pair<int, Item *> &pair)
|
||||
{
|
||||
return pair.first;
|
||||
});
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const QVector<int> TimelineModel::itemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted) const
|
||||
{
|
||||
return m_rootTrack.itemIdsBetween(start, end, strict, complete, recursive, sorted);
|
||||
}
|
||||
|
||||
bool TimelineModel::isItemsBetween(qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive) const
|
||||
{
|
||||
return m_rootTrack.isItemsBetween(start, end, ignoreIds, strict, complete, recursive);
|
||||
}
|
||||
|
||||
const QVector<int> TimelineModel::copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool sorted) const
|
||||
{
|
||||
return m_rootTrack.copyItemIdsBetween(start, end, strict, complete, true, sorted);
|
||||
}
|
||||
|
||||
bool TimelineModel::isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive) const
|
||||
{
|
||||
return m_rootTrack.isItemsBetween(start, end, ignoreId, strict, complete, recursive);
|
||||
}
|
||||
|
||||
|
||||
const QVector<int> TimelineModel::_copyItemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
|
||||
{
|
||||
QVector<int> itemIds;
|
||||
itemIds.reserve(t.itemCount());
|
||||
|
||||
|
||||
for (const int itemId : qAsConst(t.copyItemIds()))
|
||||
{
|
||||
const Item &i = item(itemId);
|
||||
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
|
||||
itemIds += i.id();
|
||||
}
|
||||
|
||||
if (recursive)
|
||||
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
|
||||
itemIds += _copyItemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration);
|
||||
|
||||
|
||||
itemIds.erase(std::unique(itemIds.begin(), itemIds.end()), itemIds.end());
|
||||
|
||||
itemIds.squeeze();
|
||||
|
||||
if (!sorted)
|
||||
return itemIds;
|
||||
|
||||
|
||||
std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB)
|
||||
{
|
||||
try
|
||||
{
|
||||
return item(itemIdA).startTime() < item(itemIdB).startTime();
|
||||
}
|
||||
catch (...) { return false; }
|
||||
});
|
||||
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
|
||||
const QVector<int> TimelineModel::_itemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
|
||||
{
|
||||
QVector<int> itemIds;
|
||||
itemIds.reserve(t.itemCount());
|
||||
|
||||
for (const ItemPtr &itemPtr : qAsConst(t.items()))
|
||||
{
|
||||
const Item &i = *itemPtr;
|
||||
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
|
||||
itemIds += i.id();
|
||||
}
|
||||
|
||||
if (recursive)
|
||||
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
|
||||
itemIds += _itemIdsBetween(*trackPtr.get(), start, end, strict, complete, true, false, minimumDuration);
|
||||
|
||||
|
||||
|
||||
|
||||
itemIds.squeeze();
|
||||
|
||||
if (!sorted)
|
||||
return itemIds;
|
||||
|
||||
|
||||
std::sort(itemIds.begin(), itemIds.end(), [this](const int itemIdA, const int itemIdB)
|
||||
{
|
||||
try
|
||||
{
|
||||
return item(itemIdA).startTime() < item(itemIdB).startTime();
|
||||
}
|
||||
catch (...) { return false; }
|
||||
});
|
||||
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
|
||||
bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
|
||||
{
|
||||
for (const ItemPtr &itemPtr : qAsConst(t.items()))
|
||||
{
|
||||
const Item &i = *itemPtr;
|
||||
if (!ignoreIds.contains(i.id()))
|
||||
if (Track::isItemBetween(i, start, end, strict, complete, minimumDuration))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!recursive)
|
||||
return false;
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(t.tracks()))
|
||||
if (_isItemsBetween(*trackPtr.get(), start, end, ignoreIds, strict, complete, true, minimumDuration))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool TimelineModel::_isItemsBetween(const Track &t, qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
|
||||
{
|
||||
return _isItemsBetween(t, start, end, QVector<int>({ ignoreId }), strict, complete, recursive, minimumDuration);
|
||||
}
|
||||
|
||||
|
||||
QVariant TimelineModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole && role != Qt::UserRole) || !index.isValid())
|
||||
return {};
|
||||
|
||||
int trackId = index.internalId();
|
||||
int column = index.column();
|
||||
|
||||
if (column == 1 && role == Qt::DisplayRole)
|
||||
return trackId;
|
||||
|
||||
if (role == Qt::UserRole)
|
||||
{
|
||||
if (column == 0)
|
||||
return trackId;
|
||||
|
||||
try
|
||||
{
|
||||
const Track &t = track(trackId);
|
||||
return t.userData();
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (column != 0)
|
||||
return {};
|
||||
|
||||
|
||||
QString text;
|
||||
|
||||
try
|
||||
{
|
||||
const Track &t = track(trackId);
|
||||
|
||||
#if 0
|
||||
text = QStringLiteral("[%1] %3").arg(t.id()).arg(t.text());
|
||||
#else
|
||||
text = t.text();
|
||||
#endif
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
return QStringLiteral("[?????]");
|
||||
}
|
||||
|
||||
if (text.isEmpty())
|
||||
text = tr("[unknown]");
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
Qt::ItemFlags TimelineModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return {};
|
||||
|
||||
int childTrackId;
|
||||
try
|
||||
{
|
||||
const Track &parentTrack = parent.isValid() ? track(parent.internalId()) : m_rootTrack;
|
||||
const Track &childTrack = parentTrack.trackAt(row);
|
||||
childTrackId = childTrack.id();
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent track " << msg;
|
||||
return {};
|
||||
}
|
||||
catch (const std::out_of_range &oor)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get child track " << QString::fromUtf8(oor.what());
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(row, column, childTrackId);
|
||||
}
|
||||
|
||||
|
||||
int TimelineModel::rowOfTrackId(int id) const noexcept(false)
|
||||
{
|
||||
const Track &parent = track(track(id).parentId());
|
||||
|
||||
if (parent.items().empty())
|
||||
throw("parent doesn't have items");
|
||||
|
||||
int row = 0;
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(parent.tracks()))
|
||||
{
|
||||
if (trackPtr->id() == id)
|
||||
return row;
|
||||
row++;
|
||||
}
|
||||
|
||||
throw("not found");
|
||||
}
|
||||
|
||||
|
||||
const Track &TimelineModel::trackOfIndex(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
throw QStringLiteral("trackOfIndex: index is invalid!");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
return track(index.internalId());
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
throw QStringLiteral("trackOfIndex: cannot find track by internal id %1: %2").arg(index.internalId()).arg(msg);
|
||||
}
|
||||
|
||||
|
||||
return m_rootTrack;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const Track &TimelineModel::parentOfTrackId(int id) const noexcept(false)
|
||||
{
|
||||
if (!m_trackCaching)
|
||||
{
|
||||
if (m_rootTrack.hasTrack(id))
|
||||
return m_rootTrack;
|
||||
|
||||
|
||||
}
|
||||
|
||||
const Track &theTrack = track(id);
|
||||
|
||||
if (theTrack.parentId() == m_rootTrack.id())
|
||||
return m_rootTrack;
|
||||
|
||||
return track(theTrack.parentId());
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int trackId = index.internalId();
|
||||
int parentId;
|
||||
int superParentId;
|
||||
try
|
||||
{
|
||||
const Track &parent = parentOfTrackId(trackId);
|
||||
parentId = parent.id();
|
||||
superParentId = parent.parentId();
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "cannot get parent of track with id" << trackId << msg;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (parentId == m_rootTrack.id())
|
||||
return {};
|
||||
|
||||
int parentsRow;
|
||||
try
|
||||
{
|
||||
const Track &superParent = track(superParentId);
|
||||
|
||||
try
|
||||
{
|
||||
parentsRow = superParent.rowOfTrack(parentId);
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "row of track for parent not found for parent id" << parentId << "-" << msg;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
DEBUG_LOG(m_logger) << Q_FUNC_INFO << "super parent not found for super parent id" << superParentId << "-" << msg;
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(parentsRow, 0, parentId);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::setItemStartTime(Track &t, Item &i, qint64 newStart)
|
||||
{
|
||||
i._setStartTime(newStart);
|
||||
t.sort();
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::setItemStartTime(Item &i, qint64 newStart) noexcept(false)
|
||||
{
|
||||
setItemStartTime(track(i.parentTrackId()), i, newStart);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::setItemStartTime(int itemId, qint64 newStart) noexcept(false)
|
||||
{
|
||||
setItemStartTime(item(itemId), newStart);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::setItemDuration(Track &t, Item &i, int duration)
|
||||
{
|
||||
i._setDuration(std::max(duration, m_minItemDuration));
|
||||
t.sort();
|
||||
}
|
||||
|
||||
void TimelineModel::setItemDuration(Item &i, int duration) noexcept(false)
|
||||
{
|
||||
setItemDuration(track(i.parentTrackId()), i, duration);
|
||||
}
|
||||
|
||||
void TimelineModel::setItemDuration(int itemId, int duration) noexcept(false)
|
||||
{
|
||||
setItemDuration(item(itemId), duration);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::copyToClipboard(const QVector<int> &ids) noexcept(false)
|
||||
{
|
||||
m_clipboard.clear();
|
||||
m_clipboard.reserve(ids.size());
|
||||
qint64 tStartMin = LLONG_MAX;
|
||||
for (int itemId : qAsConst(ids))
|
||||
{
|
||||
const Item &i = item(itemId);
|
||||
ItemPtr iPtr = std::make_unique<Item>(Item(i.startTime(), i.duration(), i.color()));
|
||||
iPtr->m_parentTrackId = i.m_parentTrackId;
|
||||
m_clipboard.push_back(std::move(iPtr));
|
||||
tStartMin = std::min(tStartMin, i.startTime());
|
||||
}
|
||||
for (ItemPtr &iPtr : m_clipboard)
|
||||
iPtr->_setStartTime(iPtr->startTime() - tStartMin);
|
||||
}
|
||||
|
||||
void TimelineModel::cutToClipboard(const QVector<int> &ids) noexcept(false)
|
||||
{
|
||||
copyToClipboard(ids);
|
||||
for (int itemId : qAsConst(ids))
|
||||
removeItem(itemId);
|
||||
}
|
||||
|
||||
void TimelineModel::pasteFromClipboard(qint64 time) noexcept(false)
|
||||
{
|
||||
if (m_isSnapEnabled)
|
||||
time -= (time % m_snapStep);
|
||||
for (ItemPtr &iPtr : m_clipboard)
|
||||
{
|
||||
iPtr->_setStartTime(time + iPtr->startTime());
|
||||
addItem(iPtr->parentTrackId(), std::move(iPtr));
|
||||
}
|
||||
m_clipboard.clear();
|
||||
}
|
||||
|
||||
|
||||
int TimelineModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
return m_rootTrack.tracks().size();
|
||||
|
||||
try
|
||||
{
|
||||
return track(parent.internalId()).tracks().size();
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::indexOfTrack(int trackId) const
|
||||
{
|
||||
for (int row = 0; row < rowCount(); ++row)
|
||||
{
|
||||
QModelIndex idx = indexOfTrackFromIndex(index(row, 0, {}), trackId);
|
||||
if (idx.isValid())
|
||||
return idx;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::indexOfTrackFromIndex(const QModelIndex &idx, int trackId) const
|
||||
{
|
||||
if (static_cast<int>(idx.internalId()) == trackId)
|
||||
return index(idx.row(), 1, idx.parent());
|
||||
|
||||
for (int row = 0; row < rowCount(idx); ++row)
|
||||
{
|
||||
const QModelIndex i = indexOfTrackFromIndex(index(row, 0, idx), trackId);
|
||||
if (i.isValid())
|
||||
return i;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::indexOfTrackSafe(int trackId) const noexcept(false)
|
||||
{
|
||||
for (int row = 0; row < rowCount(); ++row)
|
||||
{
|
||||
const QModelIndex idx(indexOfTrackFromIndex(index(row, 0 , {}), trackId));
|
||||
if (idx.isValid())
|
||||
return idx;
|
||||
}
|
||||
|
||||
QString trackText;
|
||||
try
|
||||
{
|
||||
trackText = track(trackId).text();
|
||||
} catch(...){}
|
||||
|
||||
throw QStringLiteral("indexOfTrackSafe: index for trackId %1 ('%2') not found ").arg(trackId).arg(trackText.isEmpty() ? QStringLiteral("(unknown)") : trackText);
|
||||
}
|
||||
|
||||
|
||||
QModelIndex TimelineModel::indexOfTrackFromIndexSafe(const QModelIndex &idx, int trackId) const noexcept(false)
|
||||
{
|
||||
if (static_cast<int>(idx.internalId()) == trackId)
|
||||
return idx;
|
||||
|
||||
for (int row = 0; row < rowCount(idx); ++row)
|
||||
{
|
||||
const QModelIndex i = indexOfTrackFromIndex(index(row, 1, idx), trackId);
|
||||
if (i.isValid())
|
||||
return i;
|
||||
}
|
||||
|
||||
throw QStringLiteral("indexOfTrackFromIndexSafe: index for trackId %1 not found").arg(trackId);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::moveItemToTrack(int itemId, int trackId, qint64 time) noexcept(false)
|
||||
{
|
||||
|
||||
|
||||
|
||||
const Item &theItem = item(itemId);
|
||||
Track &oldTrack = track(theItem.parentTrackId());
|
||||
Track &newTrack = track(trackId);
|
||||
|
||||
ItemPtr itemPtr = oldTrack.takeItem(itemId);
|
||||
|
||||
Item *theItemPtr = newTrack.addItem(std::move(itemPtr));
|
||||
setItemStartTime(newTrack, *theItemPtr, time);
|
||||
theItemPtr->m_parentTrackId = trackId;
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::removeItem(int itemId) noexcept(false)
|
||||
{
|
||||
const Item &i = item(itemId);
|
||||
Track &t = track(i.parentTrackId());
|
||||
|
||||
t.removeItem(itemId);
|
||||
|
||||
auto it = m_itemCache.find(itemId);
|
||||
if (it == m_itemCache.end())
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
throw QStringLiteral("removeItem: item with id %1 not found").arg(itemId);
|
||||
}
|
||||
|
||||
m_itemCache.erase(it);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::removeTrack(int trackId) noexcept(false)
|
||||
{
|
||||
if (trackId == m_rootTrack.id())
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
throw QStringLiteral("removeTrack: the root track shouldn't and *can't* be deleted");
|
||||
}
|
||||
|
||||
|
||||
const Track &t = track(trackId);
|
||||
Track &parentTrack = track(t.parentId());
|
||||
|
||||
for (int itemId : t.itemIds(true))
|
||||
{
|
||||
auto it = m_itemCache.find(itemId);
|
||||
|
||||
if (it == m_itemCache.end())
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_itemCache.erase(it);
|
||||
}
|
||||
|
||||
|
||||
parentTrack.removeTrack(trackId);
|
||||
|
||||
auto it = m_trackCache.find(trackId);
|
||||
if (it == m_trackCache.end())
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_trackCache.erase(it);
|
||||
}
|
||||
|
||||
|
||||
void TimelineModel::setMinimumDuration(int duration)
|
||||
{
|
||||
if (duration < 0)
|
||||
{
|
||||
WARNING_LOG(m_logger) << "negative duration is not possible -> setting minimum duration to 0";
|
||||
duration = 0;
|
||||
}
|
||||
if (duration % 1000 && m_isSnapEnabled)
|
||||
WARNING_LOG(m_logger) << "Snapping to 1000ms enabled, yet minimum duration is not a multiple of 1000ms: " << duration;
|
||||
m_minItemDuration = duration;
|
||||
}
|
||||
|
||||
bool TimelineModel::canDropItem(const Item &item, int trackId, qint64 start) const
|
||||
{
|
||||
Q_UNUSED(item);
|
||||
Q_UNUSED(trackId);
|
||||
Q_UNUSED(start);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TimelineModel::canMoveItem(const Item &item, qint64 start) const
|
||||
{
|
||||
Q_UNUSED(item);
|
||||
Q_UNUSED(start);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TimelineModel::canResizeItem(const Item &item, qint64 start, int duration) const
|
||||
{
|
||||
Q_UNUSED(item);
|
||||
Q_UNUSED(start);
|
||||
Q_UNUSED(duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool TimelineModel::isItemDragged(int _itemId) const
|
||||
{
|
||||
return m_view->dragHandler() ? m_view->dragHandler()->dragItemIds().contains(_itemId) : false;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+247
@@ -0,0 +1,247 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "item.h"
|
||||
#include "track.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class TimelineViewSelectHandler;
|
||||
|
||||
public:
|
||||
explicit TimelineModel(TimelineView *view, QObject *parent = nullptr);
|
||||
virtual ~TimelineModel() = default;
|
||||
|
||||
void clear();
|
||||
Track &rootTrack() { return m_rootTrack; }
|
||||
Track &track(int id) noexcept(false);
|
||||
const Track &track(int id) const noexcept(false);
|
||||
const Item &item(int id) const noexcept(false);
|
||||
Item &item(int id) noexcept(false);
|
||||
const QVector<int> itemIds() const;
|
||||
const QVector<int> itemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false) const;
|
||||
const QVector<int> copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool sorted) const;
|
||||
|
||||
bool isItemsBetween(qint64 start, qint64 end, const QVector<int> &ignoreIds = {}, bool strict = false, bool complete = false, bool recursive = false) const;
|
||||
bool isItemsBetween(qint64 start, qint64 end, int ignoreId = -1 , bool strict = false, bool complete = false, bool recursive = false) const;
|
||||
const Track &parentOfTrackId(int id) const noexcept(false);
|
||||
|
||||
virtual void setItemStartTime(Track &t, Item &i, qint64 newStart);
|
||||
virtual void setItemStartTime(Item &i, qint64 newStart) noexcept(false);
|
||||
virtual void setItemStartTime(int itemId, qint64 newStart) noexcept(false);
|
||||
|
||||
virtual void setItemDuration(Track &t, Item &i, int duration);
|
||||
virtual void setItemDuration(Item &i, int duration) noexcept(false);
|
||||
virtual void setItemDuration(int itemId, int duration) noexcept(false);
|
||||
|
||||
|
||||
void print(bool withItems = false) const { m_rootTrack.printTrackIds(withItems); }
|
||||
|
||||
template <typename TrackClass, typename = typename std::enable_if<std::is_base_of<Track, TrackClass>::value, TrackClass>::type>
|
||||
int addTrack(int parentTrackId, TrackClass &&newTrack ) noexcept(false)
|
||||
{
|
||||
int id = nextFreeTrackId();
|
||||
|
||||
TrackClass _track(std::move(newTrack));
|
||||
_track.m_id = id;
|
||||
_track.m_parentId = parentTrackId;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
auto *ptr = track(parentTrackId).addTrack(std::move(_track));
|
||||
m_trackCache.insert(std::pair<int, Track *>(id, ptr));
|
||||
|
||||
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
template <typename ItemClass, typename = typename std::enable_if<std::is_base_of<Item, ItemClass>::value, ItemClass>::type>
|
||||
int addItem(int parentTrackId, ItemClass &&item) noexcept(false)
|
||||
{
|
||||
int id = nextFreeItemId();
|
||||
|
||||
ItemClass _item(std::move(item));
|
||||
_item.m_id = id;
|
||||
_item.m_parentTrackId = parentTrackId;
|
||||
|
||||
if (m_isSnapEnabled)
|
||||
{
|
||||
_item.m_startTime = _item.m_startTime - (_item.m_startTime % 1000);
|
||||
_item.m_duration = _item.m_duration - (_item.m_duration % 1000);
|
||||
}
|
||||
|
||||
Item *ptr = track(parentTrackId).addItem(std::move(_item));
|
||||
m_itemCache.insert(std::pair<int, Item *>(id, ptr));
|
||||
|
||||
QModelIndex idx = indexOfTrack(parentTrackId);
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
bool isEditable() const { return m_isEditable; }
|
||||
void setEditable(bool editable) { m_isEditable = editable; }
|
||||
bool isSnapEnabled() const { return m_isSnapEnabled; }
|
||||
void setSnapEnabled(bool enabled) { m_isSnapEnabled = enabled; }
|
||||
int snapStep() const { return m_snapStep; }
|
||||
void setSnapStep(int step) { m_snapStep = step; }
|
||||
int snapTolerance() const { return m_snapTol; }
|
||||
void setSnapTolerance(int tolerance) { m_snapTol = tolerance; }
|
||||
qint64 snapTime(qint64 time) const { return m_isSnapEnabled ? time / m_snapStep * m_snapStep : time; }
|
||||
int minimumDuration() const { return m_minItemDuration; }
|
||||
void setMinimumDuration(int duration);
|
||||
|
||||
QModelIndex indexOfTrack(int trackId) const;
|
||||
QModelIndex indexOfTrackSafe(int trackId) const noexcept(false);
|
||||
|
||||
virtual void moveItemToTrack(int itemId, int trackId, qint64 time) noexcept(false);
|
||||
virtual void removeItem(int itemId) noexcept(false);
|
||||
virtual void removeTrack(int trackId) noexcept(false);
|
||||
|
||||
virtual bool canDropItem(const Item &item, int trackId, qint64 start) const;
|
||||
virtual bool canMoveItem(const Item &item, qint64 start) const;
|
||||
virtual bool canResizeItem(const Item &item, qint64 start, int duration) const;
|
||||
|
||||
virtual bool isItemDragged(int _itemId) const;
|
||||
|
||||
virtual void copyToClipboard(const QVector<int> &ids) noexcept(false);
|
||||
virtual void cutToClipboard(const QVector<int> &ids) noexcept(false);
|
||||
virtual void pasteFromClipboard(qint64 time) noexcept(false);
|
||||
|
||||
bool isClipboardEmpty() const { return m_clipboard.empty(); }
|
||||
|
||||
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
int columnCount(const QModelIndex &) const override { return 2; }
|
||||
|
||||
protected:
|
||||
ItemPtrNCVRef _itemNCV(Track &track, int id) noexcept(false)
|
||||
{
|
||||
return track._itemNCV(id);
|
||||
}
|
||||
|
||||
ItemPtrNCVRef _itemNCV(const TrackPtr &trackPtr, int id) noexcept(false)
|
||||
{
|
||||
return trackPtr->_itemNCV(id);
|
||||
}
|
||||
|
||||
const ItemPtrRefs _items(const TrackPtr &trackPtr, bool recursive) const
|
||||
{
|
||||
return trackPtr->_items(recursive);
|
||||
}
|
||||
|
||||
ItemPtrNCVRefs _itemsNCV(const TrackPtr &trackPtr, bool recursive)
|
||||
{
|
||||
return trackPtr->_itemsNCV(recursive);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int addItem(int parentTrackId, ItemPtr &&itemPtr, bool forceTheCompilerToUseMe = false) noexcept(false)
|
||||
{
|
||||
(void)forceTheCompilerToUseMe;
|
||||
|
||||
int id = nextFreeItemId();
|
||||
|
||||
ItemPtr _itemPtr(std::move(itemPtr));
|
||||
|
||||
_itemPtr->m_id = id;
|
||||
|
||||
_itemPtr->m_parentTrackId = parentTrackId;
|
||||
|
||||
if (m_isSnapEnabled)
|
||||
{
|
||||
_itemPtr->m_startTime = _itemPtr->m_startTime - (_itemPtr->m_startTime % 1000);
|
||||
_itemPtr->m_duration = _itemPtr->m_duration - (_itemPtr->m_duration % 1000);
|
||||
}
|
||||
Item *ptr = track(parentTrackId).addItem(std::move(_itemPtr));
|
||||
|
||||
m_itemCache.insert(std::pair<int, Item *>(id, ptr));
|
||||
|
||||
QModelIndex idx = indexOfTrack(parentTrackId);
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void clearItems();
|
||||
int rowOfTrackId(int id) const noexcept(false);
|
||||
|
||||
|
||||
|
||||
const Track &trackOfIndex(const QModelIndex &index) const;
|
||||
int indexOf(int id) const;
|
||||
|
||||
const QVector<int> _itemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const;
|
||||
const QVector<int> _copyItemIdsBetween(Track &t, qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const;
|
||||
bool _isItemsBetween(const Track &t, qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const;
|
||||
bool _isItemsBetween(const Track &t, qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const;
|
||||
|
||||
private:
|
||||
int nextFreeTrackId() { return m_currentTrackId++; }
|
||||
int nextFreeItemId() { return m_currentItemId++; }
|
||||
|
||||
QModelIndex indexOfTrackFromIndex(const QModelIndex &idx, int trackId) const;
|
||||
QModelIndex indexOfTrackFromIndexSafe(const QModelIndex &idx, int trackId) const noexcept(false);
|
||||
|
||||
public slots:
|
||||
virtual void onInsertTrack(const QModelIndex &index) { Q_UNUSED(index); }
|
||||
virtual void onRemoveTrack(const QModelIndex &index) { Q_UNUSED(index); }
|
||||
|
||||
protected:
|
||||
TimelineView *m_view{nullptr};
|
||||
|
||||
Track m_rootTrack;
|
||||
int m_currentTrackId{0};
|
||||
int m_currentItemId{0};
|
||||
bool m_isEditable{true};
|
||||
bool m_isSnapEnabled{false};
|
||||
int m_snapStep{500};
|
||||
int m_snapTol{1000};
|
||||
int m_minItemDuration{0};
|
||||
|
||||
|
||||
bool m_itemCaching{true};
|
||||
bool m_trackCaching{true};
|
||||
std::map<int , Track *> m_trackCache;
|
||||
|
||||
std::map<int , Item *> m_itemCache;
|
||||
Items m_clipboard;
|
||||
|
||||
NGL::INGLLogger &m_logger;
|
||||
};
|
||||
|
||||
}
|
||||
Executable
+343
@@ -0,0 +1,343 @@
|
||||
#include "timeline_utils.h"
|
||||
|
||||
#include <ngltools/ngltools_dtg.h>
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
namespace DateTime
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
int numberOfDateTimeFormats()
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
|
||||
QString toString(qint64 time, Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format::DTG:
|
||||
return DTG::toStringMSec(time, DTG::DisplayFormat::Long);
|
||||
case Format::ISODate:
|
||||
return QDateTime::fromMSecsSinceEpoch(time).toString(Qt::ISODate);
|
||||
case Format::Standard:
|
||||
return QDateTime::fromMSecsSinceEpoch(time).toString(QStringLiteral("dd.MM.yyyy hh:mm:ss"));
|
||||
case Format::DTGShort:
|
||||
{
|
||||
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(time).toUTC().addSecs(DTG::timezone2Hours(DTG::timezone()) * 3600);
|
||||
return dateTime.toString(QStringLiteral("hhmmss ")) + QStringLiteral("%1").arg(DTG::timezone());
|
||||
}
|
||||
case Format::DTGVeryShort:
|
||||
{
|
||||
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(time).toUTC().addSecs(DTG::timezone2Hours(DTG::timezone()) * 3600);
|
||||
return dateTime.toString(QStringLiteral("hhmm"));
|
||||
}
|
||||
case Format::TimeOnly:
|
||||
return QTime::fromMSecsSinceStartOfDay(time).toString();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Zoom
|
||||
{
|
||||
|
||||
int numberOfZoomFactors()
|
||||
{
|
||||
return 15;
|
||||
}
|
||||
|
||||
Factor LTSTIMELINE_EXPORT increment(Factor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return ZoomFactor::ThirtySeconds;
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return ZoomFactor::SixtySeconds;
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return ZoomFactor::TwoMinutes;
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return ZoomFactor::ThreeMinutes;
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return ZoomFactor::FiveMinutes;
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return ZoomFactor::TenMinutes;
|
||||
case ZoomFactor::TenMinutes:
|
||||
return ZoomFactor::FifteenMinutes;
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return ZoomFactor::ThirtyMinutes;
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return ZoomFactor::SixtyMinutes;
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return ZoomFactor::TwoHours;
|
||||
case ZoomFactor::TwoHours:
|
||||
return ZoomFactor::FourHours;
|
||||
case ZoomFactor::FourHours:
|
||||
return ZoomFactor::EightHours;
|
||||
case ZoomFactor::EightHours:
|
||||
return ZoomFactor::TwelveHours;
|
||||
case ZoomFactor::TwelveHours:
|
||||
return ZoomFactor::OneDay;
|
||||
case ZoomFactor::OneDay:
|
||||
return ZoomFactor::OneDay;
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
|
||||
Factor LTSTIMELINE_EXPORT decrement(Factor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return ZoomFactor::FifteenSeconds;
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return ZoomFactor::FifteenSeconds;
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return ZoomFactor::ThirtySeconds;
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return ZoomFactor::SixtySeconds;
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return ZoomFactor::TwoMinutes;
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return ZoomFactor::ThreeMinutes;
|
||||
case ZoomFactor::TenMinutes:
|
||||
return ZoomFactor::FiveMinutes;
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return ZoomFactor::TenMinutes;
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return ZoomFactor::FifteenMinutes;
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return ZoomFactor::ThirtyMinutes;
|
||||
case ZoomFactor::TwoHours:
|
||||
return ZoomFactor::SixtyMinutes;
|
||||
case ZoomFactor::FourHours:
|
||||
return ZoomFactor::TwoHours;
|
||||
case ZoomFactor::EightHours:
|
||||
return ZoomFactor::FourHours;
|
||||
case ZoomFactor::TwelveHours:
|
||||
return ZoomFactor::EightHours;
|
||||
case ZoomFactor::OneDay:
|
||||
return ZoomFactor::TwelveHours;
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
|
||||
Factor ceil(int value)
|
||||
{
|
||||
if (value >= Factor::OneDay)
|
||||
return Factor::OneDay;
|
||||
|
||||
Factor ret = Factor::FifteenSeconds;
|
||||
while (static_cast<int>(ret) < value)
|
||||
{
|
||||
ret = increment(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Factor floor(int value)
|
||||
{
|
||||
if (value <= Factor::FifteenSeconds)
|
||||
return Factor::FifteenSeconds;
|
||||
|
||||
Factor ret = Factor::OneDay;
|
||||
while (static_cast<int>(ret) > value)
|
||||
{
|
||||
ret = decrement(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<Factor> availableZoomFactors(Factor min, Factor max)
|
||||
{
|
||||
if (min > max)
|
||||
return {};
|
||||
|
||||
ZoomFactors ret;
|
||||
ret.reserve(numberOfZoomFactors());
|
||||
Factor factor = min;
|
||||
do
|
||||
{
|
||||
ret.append(factor);
|
||||
if (factor == max)
|
||||
break;
|
||||
factor = increment(factor);
|
||||
} while (increment(factor) > factor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString toString(ZoomFactor factor, bool useShort)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return useShort ? QApplication::tr("15 sec") : QApplication::tr("15 Seconds");
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return useShort ? QApplication::tr("30 sec") : QApplication::tr("30 Seconds");
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return useShort ? QApplication::tr("1 min") : QApplication::tr("1 Minute");
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return useShort ? QApplication::tr("2 min") : QApplication::tr("2 Minutes");
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return useShort ? QApplication::tr("3 min") : QApplication::tr("3 Minutes");
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return useShort ? QApplication::tr("5 min") : QApplication::tr("5 Minutes");
|
||||
case ZoomFactor::TenMinutes:
|
||||
return useShort ? QApplication::tr("10 min") : QApplication::tr("10 Minutes");
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return useShort ? QApplication::tr("15 min") : QApplication::tr("15 Minutes");
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return useShort ? QApplication::tr("30 min") : QApplication::tr("30 Minutes");
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return useShort ? QApplication::tr("1 h") : QApplication::tr("1 Hour");
|
||||
case ZoomFactor::TwoHours:
|
||||
return useShort ? QApplication::tr("2 h") : QApplication::tr("2 Hours");
|
||||
case ZoomFactor::FourHours:
|
||||
return useShort ? QApplication::tr("4 h") : QApplication::tr("4 Hours");
|
||||
case ZoomFactor::EightHours:
|
||||
return useShort ? QApplication::tr("8 h") : QApplication::tr("8 Hours");
|
||||
case ZoomFactor::TwelveHours:
|
||||
return useShort ? QApplication::tr("12 h") : QApplication::tr("12 Hours");
|
||||
case ZoomFactor::OneDay:
|
||||
return useShort ? QApplication::tr("1 d") : QApplication::tr("1 Day");
|
||||
}
|
||||
return useShort ? QApplication::tr("N/A") : QApplication::tr("Unknown");
|
||||
}
|
||||
|
||||
QString toShortString(Factor factor)
|
||||
{
|
||||
return toString(factor, true);
|
||||
}
|
||||
|
||||
int majorInterval(ZoomFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return 3000;
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return 5000;
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return 10000;
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return 15000;
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return 30000;
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return 60000;
|
||||
case ZoomFactor::TenMinutes:
|
||||
return 60000;
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return 180000;
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return 300000;
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return 600000;
|
||||
case ZoomFactor::TwoHours:
|
||||
return 900000;
|
||||
case ZoomFactor::FourHours:
|
||||
return 1800000;
|
||||
case ZoomFactor::EightHours:
|
||||
return 3600000;
|
||||
case ZoomFactor::TwelveHours:
|
||||
return 5400000;
|
||||
case ZoomFactor::OneDay:
|
||||
return 7200000;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mediumInterval(ZoomFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return -1;
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return -1;
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return 5000;
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return 6000;
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return 10000;
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return 15000;
|
||||
case ZoomFactor::TenMinutes:
|
||||
return 20000;
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return 60000;
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return 100000;
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return 150000;
|
||||
case ZoomFactor::TwoHours:
|
||||
return 300000;
|
||||
case ZoomFactor::FourHours:
|
||||
return 600000;
|
||||
case ZoomFactor::EightHours:
|
||||
return 900000;
|
||||
case ZoomFactor::TwelveHours:
|
||||
return 1800000;
|
||||
case ZoomFactor::OneDay:
|
||||
return 2400000;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int minorInterval(ZoomFactor factor)
|
||||
{
|
||||
switch (factor)
|
||||
{
|
||||
case ZoomFactor::FifteenSeconds:
|
||||
return 1000;
|
||||
case ZoomFactor::ThirtySeconds:
|
||||
return 1000;
|
||||
case ZoomFactor::SixtySeconds:
|
||||
return 1000;
|
||||
case ZoomFactor::TwoMinutes:
|
||||
return 3000;
|
||||
case ZoomFactor::ThreeMinutes:
|
||||
return 5000;
|
||||
case ZoomFactor::FiveMinutes:
|
||||
return 5000;
|
||||
case ZoomFactor::TenMinutes:
|
||||
return 10000;
|
||||
case ZoomFactor::FifteenMinutes:
|
||||
return 20000;
|
||||
case ZoomFactor::ThirtyMinutes:
|
||||
return 25000;
|
||||
case ZoomFactor::SixtyMinutes:
|
||||
return 50000;
|
||||
case ZoomFactor::TwoHours:
|
||||
return 60000;
|
||||
case ZoomFactor::FourHours:
|
||||
return 150000;
|
||||
case ZoomFactor::EightHours:
|
||||
return 300000;
|
||||
case ZoomFactor::TwelveHours:
|
||||
return 600000;
|
||||
case ZoomFactor::OneDay:
|
||||
return 800000;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QString>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
struct LTSTIMELINE_EXPORT Tick
|
||||
{
|
||||
int pos{-1};
|
||||
qint64 time{0};
|
||||
QString label;
|
||||
Tick() {}
|
||||
explicit Tick(qint64 time) : time(time) {}
|
||||
explicit Tick(int pos, qint64 time, const QString &label) : pos(pos), time(time), label(label) {}
|
||||
};
|
||||
|
||||
typedef QVector<Tick> Ticks;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace DateTime
|
||||
{
|
||||
|
||||
enum Format { DTG = 0, ISODate = Qt::ISODate, Standard = 2, DTGShort = 3, DTGVeryShort, TimeOnly = 5 };
|
||||
|
||||
extern int numberOfDateTimeFormats();
|
||||
|
||||
extern LTSTIMELINE_EXPORT QString toString(qint64 time, Format format);
|
||||
|
||||
}
|
||||
|
||||
typedef DateTime::Format DateTimeFormat;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Zoom
|
||||
{
|
||||
|
||||
enum Factor : int
|
||||
{
|
||||
FifteenSeconds = 15 * 1000, ThirtySeconds = 30 * 1000, SixtySeconds = 60 * 1000,
|
||||
TwoMinutes = 2 * 60 * 1000, ThreeMinutes = 3 * 60 * 1000, FiveMinutes = 5 * 60 * 1000,
|
||||
TenMinutes = 10 * 60 * 1000, FifteenMinutes = 15 * 60 * 1000, ThirtyMinutes = 30 * 60 * 1000, SixtyMinutes = 60 * 60 * 1000,
|
||||
TwoHours = 2 * 60 * 60 * 1000, FourHours = 4 * 60 * 60 * 1000, EightHours = 8 * 60 * 60 * 1000, TwelveHours = 12 * 60 * 60 * 1000,
|
||||
OneDay = 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
extern LTSTIMELINE_EXPORT int numberOfZoomFactors();
|
||||
|
||||
extern LTSTIMELINE_EXPORT Factor increment(Factor factor);
|
||||
|
||||
extern LTSTIMELINE_EXPORT Factor decrement(Factor factor);
|
||||
|
||||
extern LTSTIMELINE_EXPORT Factor ceil(int value);
|
||||
|
||||
extern LTSTIMELINE_EXPORT Factor floor(int value);
|
||||
|
||||
extern LTSTIMELINE_EXPORT QVector<Factor> availableZoomFactors(Factor min, Factor max);
|
||||
|
||||
extern LTSTIMELINE_EXPORT QString toString(Factor factor, bool useShort = false);
|
||||
|
||||
extern LTSTIMELINE_EXPORT QString toShortString(Factor factor);
|
||||
|
||||
extern LTSTIMELINE_EXPORT int majorInterval(Factor factor);
|
||||
|
||||
extern LTSTIMELINE_EXPORT int mediumInterval(Factor factor);
|
||||
|
||||
extern LTSTIMELINE_EXPORT int minorInterval(Factor factor);
|
||||
|
||||
}
|
||||
|
||||
typedef Zoom::Factor ZoomFactor;
|
||||
typedef QVector<ZoomFactor> ZoomFactors;
|
||||
|
||||
}
|
||||
Executable
+672
@@ -0,0 +1,672 @@
|
||||
|
||||
|
||||
|
||||
#include "timeline_view.h"
|
||||
#include "item_delegate.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
|
||||
#include <QAbstractProxyModel>
|
||||
#include <QApplication>
|
||||
#include <QBrush>
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QHeaderView>
|
||||
#include <QHelpEvent>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QRect>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
const int TimelineView::s_textHeight = 20;
|
||||
|
||||
|
||||
TimelineView::TimelineView(QWidget *parent)
|
||||
: QTreeView(parent)
|
||||
, m_header(new TimelineHeaderView(this))
|
||||
, m_dragHandler(new TimelineViewDragHandler(this))
|
||||
, m_resizeHandler(new TimelineViewResizeHandler(this))
|
||||
, m_selectHandler(new TimelineViewSelectHandler(this))
|
||||
, m_trackHandler(new TimelineViewTrackHandler(this))
|
||||
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.TimelineView"))
|
||||
{
|
||||
setAlternatingRowColors(true);
|
||||
setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
setSelectionBehavior(QAbstractItemView::SelectItems);
|
||||
setSelectionMode(QAbstractItemView::NoSelection);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
setHeader(m_header);
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineView::setHeader(QHeaderView *header)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_header && m_header != header)
|
||||
delete m_header;
|
||||
m_header = qobject_cast<TimelineHeaderView *>(header);
|
||||
QTreeView::setHeader(m_header);
|
||||
connect(m_header, &TimelineHeaderView::indicatorTimeChanged, this, [this]()
|
||||
{
|
||||
viewport()->update();
|
||||
});
|
||||
connect(m_header, &TimelineHeaderView::indicatorVisibilityChanged, this, [this]()
|
||||
{
|
||||
viewport()->update();
|
||||
});
|
||||
connect(m_header, &TimelineHeaderView::hoverPositionChanged, this, [this]()
|
||||
{
|
||||
if (m_model != nullptr)
|
||||
emit m_model->layoutChanged();
|
||||
});
|
||||
connect(m_header, &TimelineHeaderView::hoverVisibilityChanged, this, [this]()
|
||||
{
|
||||
viewport()->update();
|
||||
});
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "header view should be of type TimelineHeaderView";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setModel(QAbstractItemModel *model)
|
||||
{
|
||||
m_model = model;
|
||||
QTreeView::setModel(model);
|
||||
setMouseTracking(true);
|
||||
|
||||
|
||||
m_timelineModel = qobject_cast<TimelineModel *>(model);
|
||||
|
||||
if (m_timelineModel != nullptr)
|
||||
return;
|
||||
|
||||
|
||||
m_proxyModel = qobject_cast<QAbstractProxyModel *>(model);
|
||||
if (m_proxyModel != nullptr)
|
||||
m_timelineModel = qobject_cast<TimelineModel *>(m_proxyModel->sourceModel());
|
||||
|
||||
if (m_timelineModel == nullptr)
|
||||
qWarning() << "TimelineSelectHandler:c_tor: model is not a QAbstractItemModel!";
|
||||
|
||||
Q_ASSERT(m_timelineModel != nullptr);
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setProxyModel(QAbstractProxyModel *proxy) noexcept(false)
|
||||
{
|
||||
if (!qobject_cast<TimelineModel *>(proxy->sourceModel()))
|
||||
throw QStringLiteral("source model cannot be cast to TimelineModel");
|
||||
|
||||
m_model = qobject_cast<TimelineModel *>(proxy->sourceModel());
|
||||
m_proxyModel = proxy;
|
||||
QTreeView::setModel(m_proxyModel);
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setItemDelegate(QAbstractItemDelegate *delegate)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_delegate = static_cast<ItemDelegate *>(delegate);
|
||||
QTreeView::setItemDelegate(m_delegate);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR_LOG(m_logger) << "item delegate for TimelineView must be derived from ItemDelegate";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setZoomFactor(ZoomFactor zoomFactor)
|
||||
{
|
||||
if (zoomFactor == m_zoomFactor)
|
||||
return;
|
||||
|
||||
if (m_minZoomFactor > zoomFactor)
|
||||
{
|
||||
setZoomFactor(m_minZoomFactor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_upperZoomFactor < zoomFactor)
|
||||
{
|
||||
setZoomFactor(m_upperZoomFactor);
|
||||
return;
|
||||
}
|
||||
|
||||
m_zoomFactor = zoomFactor;
|
||||
emit zoomChanged(m_zoomFactor);
|
||||
if (m_currentTime + m_zoomFactor > endTime())
|
||||
setCurrentTime(endTime() - m_zoomFactor);
|
||||
else
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::setMinimumZoomFactor(ZoomFactor zoomFactor)
|
||||
{
|
||||
if (zoomFactor == m_minZoomFactor)
|
||||
return;
|
||||
|
||||
m_minZoomFactor = zoomFactor;
|
||||
if (m_zoomFactor < m_minZoomFactor)
|
||||
setZoomFactor(m_minZoomFactor);
|
||||
}
|
||||
|
||||
void TimelineView::setMaximumZoomFactor(ZoomFactor zoomFactor)
|
||||
{
|
||||
if (zoomFactor == m_maxZoomFactor)
|
||||
return;
|
||||
|
||||
m_maxZoomFactor = zoomFactor;
|
||||
if (m_upperZoomFactor > m_maxZoomFactor)
|
||||
m_upperZoomFactor = m_maxZoomFactor;
|
||||
if (m_zoomFactor > m_maxZoomFactor)
|
||||
setZoomFactor(m_maxZoomFactor);
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setStartTime(qint64 time)
|
||||
{
|
||||
if (time == m_startTime || time < 0)
|
||||
return;
|
||||
|
||||
m_startTime = time;
|
||||
if (m_currentTime < m_startTime)
|
||||
setCurrentTime(m_startTime);
|
||||
if (m_duration > 0)
|
||||
if (m_currentTime > endTime() - m_zoomFactor)
|
||||
setCurrentTime(std::max(endTime() - m_zoomFactor, m_startTime));
|
||||
emit rangeChanged(m_startTime, endTime());
|
||||
}
|
||||
|
||||
void TimelineView::setCurrentTime(qint64 time)
|
||||
{
|
||||
if (time == m_currentTime)
|
||||
return;
|
||||
|
||||
if (time < m_startTime)
|
||||
time = m_startTime;
|
||||
if (m_duration > 0)
|
||||
if (time > endTime() - m_zoomFactor)
|
||||
time = std::max(endTime() - m_zoomFactor, m_startTime);
|
||||
|
||||
if (time == m_currentTime)
|
||||
return;
|
||||
|
||||
m_currentTime = time;
|
||||
emit currentTimeChanged(m_currentTime);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::setEndTime(qint64 time)
|
||||
{
|
||||
if (time == m_startTime + m_duration)
|
||||
return;
|
||||
|
||||
if (time - m_startTime < m_minZoomFactor)
|
||||
time = m_startTime + m_minZoomFactor;
|
||||
|
||||
setDuration(time - m_startTime);
|
||||
}
|
||||
|
||||
void TimelineView::setDuration(int duration)
|
||||
{
|
||||
if (duration == m_duration)
|
||||
return;
|
||||
|
||||
if (duration <= m_minZoomFactor)
|
||||
duration = m_minZoomFactor;
|
||||
m_duration = duration;
|
||||
emit rangeChanged(m_startTime, endTime());
|
||||
ZoomFactor upperZoomFactor = Zoom::floor(duration);
|
||||
m_upperZoomFactor = std::min(upperZoomFactor, m_maxZoomFactor);
|
||||
if (duration < m_zoomFactor)
|
||||
setZoomFactor(m_upperZoomFactor);
|
||||
if (m_currentTime > endTime())
|
||||
setCurrentTime(endTime() - m_zoomFactor);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::setRange(qint64 start, qint64 end)
|
||||
{
|
||||
if (start == m_startTime && end - start == m_duration)
|
||||
return;
|
||||
|
||||
if (start > end)
|
||||
{
|
||||
m_startTime = end;
|
||||
m_duration = std::max(start - m_startTime, static_cast<qint64>(m_minZoomFactor));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_startTime = start;
|
||||
m_duration = std::max(end - m_startTime, static_cast<qint64>(m_minZoomFactor));
|
||||
}
|
||||
if (m_currentTime < m_startTime)
|
||||
m_currentTime = m_startTime;
|
||||
if (m_duration > 0)
|
||||
if (m_currentTime > endTime() - m_zoomFactor)
|
||||
m_currentTime = std::max(endTime() - m_zoomFactor, m_startTime);
|
||||
ZoomFactor upperZoomFactor = Zoom::floor(m_duration);
|
||||
m_upperZoomFactor = upperZoomFactor > m_maxZoomFactor ? m_maxZoomFactor : upperZoomFactor;
|
||||
emit rangeChanged(m_startTime, endTime());
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::setCursorTextVisible(bool visible)
|
||||
{
|
||||
m_isCursorTextVisible = visible;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::setCursorText(const QString &text)
|
||||
{
|
||||
m_cursorText = text;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
|
||||
int TimelineView::toPixel(qint64 time) const
|
||||
{
|
||||
return m_header ? m_header->toPixel(time) : -1;
|
||||
}
|
||||
|
||||
qint64 TimelineView::toTime(int pixel) const
|
||||
{
|
||||
return m_header ? m_header->toTime(pixel) : -1;
|
||||
}
|
||||
|
||||
qint64 TimelineView::toDuration(int pixel) const
|
||||
{
|
||||
if (!m_header)
|
||||
return -1;
|
||||
|
||||
int pos = m_header->sectionPosition(Section::eItems);
|
||||
return double(pixel - pos) / (geometry().width() - pos) * m_zoomFactor;
|
||||
}
|
||||
|
||||
int TimelineView::fromDuration(qint64 time) const
|
||||
{
|
||||
if (!m_header)
|
||||
return -1;
|
||||
|
||||
return (time - m_startTime) / m_zoomFactor * (geometry().width() - m_header->sectionPosition(Section::eItems));
|
||||
}
|
||||
|
||||
|
||||
void TimelineView::clearSelectedItems()
|
||||
{
|
||||
m_selectedItemIds.clear();
|
||||
emit selectedItemsChanged(m_selectedItemIds);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::selectAllItems()
|
||||
{
|
||||
if (m_model == nullptr)
|
||||
return;
|
||||
|
||||
m_selectedItemIds.clear();
|
||||
m_selectedItemIds = m_timelineModel->itemIds();
|
||||
emit selectedItemsChanged(m_selectedItemIds);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::selectItem(int itemId, bool reset)
|
||||
{
|
||||
if (reset)
|
||||
m_selectedItemIds.clear();
|
||||
else if (m_selectedItemIds.contains(itemId))
|
||||
return;
|
||||
m_selectedItemIds.append(itemId);
|
||||
emit selectedItemsChanged(m_selectedItemIds);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::selectItems(const QVector<int> &itemIds, bool reset)
|
||||
{
|
||||
if (reset)
|
||||
m_selectedItemIds.clear();
|
||||
for (const int &itemId : qAsConst(itemIds))
|
||||
if (!m_selectedItemIds.contains(itemId))
|
||||
m_selectedItemIds.append(itemId);
|
||||
emit selectedItemsChanged(m_selectedItemIds);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::unselectItem(int itemId)
|
||||
{
|
||||
const int iItem = m_selectedItemIds.indexOf(itemId);
|
||||
if (iItem == -1)
|
||||
return;
|
||||
m_selectedItemIds.remove(iItem);
|
||||
emit selectedItemsChanged(m_selectedItemIds);
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
|
||||
bool TimelineView::isCursorWithinRangeX(int x) const
|
||||
{
|
||||
if (m_header)
|
||||
return m_header->sectionPosition(Section::eItems) < x && x < geometry().right();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TimelineView::isCursorWithinRangeY(int y) const
|
||||
{
|
||||
return 0 < y && y < geometry().bottom();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineView::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (m_dragHandler)
|
||||
m_dragHandler->dragEnterEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
if (m_dragHandler)
|
||||
m_dragHandler->dragMoveEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
if (m_dragHandler)
|
||||
m_dragHandler->dragLeaveEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::dropEvent(QDropEvent *event)
|
||||
{
|
||||
if (m_dragHandler)
|
||||
m_dragHandler->dropEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (m_trackHandler)
|
||||
m_trackHandler->mouseDoubleClickEvent(event);
|
||||
if (m_selectHandler)
|
||||
m_selectHandler->mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_cursorPosition = event->pos();
|
||||
|
||||
|
||||
const QModelIndex index(indexAt(event->pos()));
|
||||
|
||||
if (m_header)
|
||||
{
|
||||
m_header->setHoverPosition(isCursorWithinRange(event->pos()) ? event->pos().x() : -1);
|
||||
if (m_header->mouseHandler()->isBusy())
|
||||
{
|
||||
m_header->mouseHandler()->mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
if (cursor() == Qt::SizeHorCursor)
|
||||
{
|
||||
if (m_resizeHandler)
|
||||
{
|
||||
if (!m_resizeHandler->isActive())
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
else
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_model)
|
||||
return;
|
||||
|
||||
if (m_dragHandler && m_timelineModel->isEditable())
|
||||
if (cursor().shape() == Qt::ClosedHandCursor || cursor().shape() == Qt::ForbiddenCursor)
|
||||
if (!m_dragHandler->m_dragItemIds.isEmpty() || !m_selectedItemIds.isEmpty())
|
||||
{
|
||||
m_dragHandler->mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_resizeHandler && m_timelineModel->isEditable())
|
||||
if (cursor().shape() == Qt::SizeHorCursor)
|
||||
if (m_resizeHandler->isActive())
|
||||
{
|
||||
m_resizeHandler->mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_selectHandler)
|
||||
m_selectHandler->mouseMoveEvent(event);
|
||||
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
{
|
||||
if (m_resizeHandler && m_timelineModel->isEditable())
|
||||
if (!m_resizeHandler->isActive())
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_timelineModel->isEditable())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_timelineModel->track(index.data().toInt());
|
||||
Item &item = track.itemAt(toTime(event->pos().x()));
|
||||
int startPos = toPixel(item.startTime());
|
||||
int endPos = toPixel(item.startTime() + item.duration());
|
||||
if (endPos - startPos < 15 || (startPos + 5 < event->pos().x() && event->pos().x() < endPos - 5))
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
else
|
||||
setCursor(QCursor(Qt::SizeHorCursor));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_model)
|
||||
return;
|
||||
|
||||
if (m_header)
|
||||
if (m_header->mouseHandler()->isBusy())
|
||||
{
|
||||
m_header->mouseHandler()->mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const QModelIndex index = indexAt(event->pos());
|
||||
if (index.column() == Section::eActuator)
|
||||
{
|
||||
QTreeView::mousePressEvent(event);
|
||||
if (m_trackHandler)
|
||||
m_trackHandler->mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_timelineModel->isEditable() && cursor().shape() == Qt::SizeHorCursor)
|
||||
{
|
||||
if (m_resizeHandler)
|
||||
m_resizeHandler->mousePressEvent(event);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_selectHandler)
|
||||
m_selectHandler->mousePressEvent(event);
|
||||
|
||||
if (m_dragHandler && m_timelineModel->isEditable())
|
||||
{
|
||||
m_dragHandler->mousePressEvent(event);
|
||||
if (m_dragHandler->isActive())
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton)
|
||||
emit customContextMenuRequested(event->pos());
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_model)
|
||||
return;
|
||||
|
||||
if (m_header)
|
||||
if (m_header->mouseHandler()->isBusy())
|
||||
{
|
||||
m_header->mouseHandler()->mouseReleaseEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_trackHandler)
|
||||
m_trackHandler->mouseReleaseEvent(event);
|
||||
|
||||
if (m_dragHandler && m_timelineModel->isEditable())
|
||||
if (m_dragHandler->isActive())
|
||||
{
|
||||
m_dragHandler->mouseReleaseEvent(event);
|
||||
viewport()->update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_resizeHandler && m_timelineModel->isEditable())
|
||||
if (m_resizeHandler->isActive())
|
||||
{
|
||||
m_resizeHandler->mouseReleaseEvent(event);
|
||||
viewport()->update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_selectHandler)
|
||||
if (m_selectHandler->isActive() || !m_timelineModel->isEditable())
|
||||
m_selectHandler->mouseReleaseEvent(event);
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (m_model == nullptr)
|
||||
return;
|
||||
if (m_timelineModel->isEditable())
|
||||
{
|
||||
if (m_resizeHandler)
|
||||
if (m_resizeHandler->isActive())
|
||||
return;
|
||||
|
||||
if (m_dragHandler)
|
||||
m_dragHandler->keyPressEvent(event);
|
||||
}
|
||||
if (m_selectHandler)
|
||||
m_selectHandler->keyPressEvent(event);
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineView::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (event->pos().y() <= m_header->geometry().bottom())
|
||||
m_header->wheelEvent(event);
|
||||
else if (QGuiApplication::queryKeyboardModifiers() == Qt::ControlModifier)
|
||||
{
|
||||
if (event->angleDelta().y() < 0)
|
||||
setZoomFactor(Zoom::increment(m_zoomFactor));
|
||||
else
|
||||
setZoomFactor(Zoom::decrement(m_zoomFactor));
|
||||
}
|
||||
else
|
||||
QTreeView::wheelEvent(event);
|
||||
}
|
||||
|
||||
void TimelineView::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QWidget *viewPort = viewport();
|
||||
QPainter painter(viewPort);
|
||||
|
||||
painter.save();
|
||||
QTreeView::paintEvent(event);
|
||||
if (m_dragHandler)
|
||||
{
|
||||
ItemDelegate *delegate = qobject_cast<ItemDelegate *>(itemDelegate());
|
||||
if (delegate)
|
||||
delegate->paintDragItems(&painter);
|
||||
}
|
||||
painter.restore();
|
||||
|
||||
painter.save();
|
||||
|
||||
painter.setClipping(true);
|
||||
|
||||
|
||||
if (m_selectHandler)
|
||||
if (m_selectHandler->isRubberBandActive())
|
||||
{
|
||||
QColor color = TimelineHeaderView::highlightColor();
|
||||
painter.setPen(color);
|
||||
color.setAlpha(100);
|
||||
painter.setBrush(QBrush(color));
|
||||
painter.drawRect(m_selectHandler->m_rubberBand);
|
||||
}
|
||||
|
||||
|
||||
if (m_header->isIndicatorVisible() && m_header->indicatorPosition() != -1 &&
|
||||
m_header->indicatorPosition() >= m_header->sectionPosition(Section::eItems))
|
||||
{
|
||||
painter.setPen(TimelineHeaderView::highlightColor());
|
||||
painter.drawLine(m_header->indicatorPosition(), 0, m_header->indicatorPosition(), geometry().bottom());
|
||||
}
|
||||
|
||||
|
||||
if (m_header->isHoverVisible() && m_header->hoverPosition() != -1)
|
||||
{
|
||||
painter.setPen(TimelineHeaderView::highlightColor());
|
||||
painter.drawLine(m_header->hoverPosition(), 0, m_header->hoverPosition(), geometry().bottom());
|
||||
if (m_isCursorTextVisible && !m_cursorText.isEmpty())
|
||||
{
|
||||
painter.setBrush(QBrush(TimelineHeaderView::backgroundColor()));
|
||||
QRect rect = QRect(
|
||||
m_cursorPosition.x(), m_cursorPosition.y() - s_textHeight - 2,
|
||||
painter.fontMetrics().horizontalAdvance(m_cursorText) + 2, s_textHeight + 2
|
||||
);
|
||||
painter.drawRect(rect);
|
||||
painter.drawText(rect.adjusted(1, 1, -1, -1), Qt::AlignCenter, m_cursorText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (m_header->isLiveIndicatorVisible() && m_header->liveIndicatorTime() != -1 &&
|
||||
m_currentTime <= m_header->liveIndicatorTime() && m_header->liveIndicatorTime() <= m_currentTime + m_zoomFactor)
|
||||
{
|
||||
painter.setPen(m_header->liveIndicatorColor());
|
||||
painter.drawLine(m_header->liveIndicatorPosition(), 0, m_header->liveIndicatorPosition(), geometry().bottom());
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+175
@@ -0,0 +1,175 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "timeline_view_drag_handler.h"
|
||||
#include "timeline_view_resize_handler.h"
|
||||
#include "timeline_view_selection_handler.h"
|
||||
#include "timeline_view_track_handler.h"
|
||||
#include "timeline_header_view.h"
|
||||
#include "timeline_model.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QLabel>
|
||||
#include <QTreeView>
|
||||
|
||||
|
||||
class QAbstractProxyModel;
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineViewDragHandler;
|
||||
class TimelineViewResizeHandler;
|
||||
class TimelineViewSelectHandler;
|
||||
class TimelineViewTrackHandler;
|
||||
class TimelineWidget;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class ItemDelegate;
|
||||
friend class TimelineHeaderView;
|
||||
friend class TimelineViewDragHandler;
|
||||
friend class TimelineViewResizeHandler;
|
||||
friend class TimelineViewSelectHandler;
|
||||
friend class TimelineViewTrackHandler;
|
||||
friend class TimelineWidget;
|
||||
|
||||
public:
|
||||
static const int s_textHeight;
|
||||
|
||||
|
||||
|
||||
explicit TimelineView(QWidget *parent = nullptr);
|
||||
|
||||
TimelineHeaderView *header() { return m_header; }
|
||||
void setHeader(QHeaderView *header);
|
||||
|
||||
QAbstractItemModel *model() { return m_model; }
|
||||
TimelineModel *timelineModel() { return m_timelineModel; }
|
||||
void setModel(QAbstractItemModel *model) override;
|
||||
|
||||
QAbstractProxyModel *proxyModel() { return m_proxyModel; }
|
||||
void setProxyModel(QAbstractProxyModel *proxy) noexcept(false);
|
||||
|
||||
void setItemDelegate(QAbstractItemDelegate *delegate);
|
||||
ItemDelegate *itemDelegate() const { return m_delegate; }
|
||||
|
||||
ZoomFactor zoomFactor() const { return m_zoomFactor; }
|
||||
|
||||
|
||||
void setZoomFactor(ZoomFactor zoomFactor);
|
||||
|
||||
ZoomFactor minimumZoomFactor() const { return m_minZoomFactor; }
|
||||
void setMinimumZoomFactor(ZoomFactor zoomFactor);
|
||||
|
||||
ZoomFactor maximumZoomFactor() const { return m_maxZoomFactor; }
|
||||
void setMaximumZoomFactor(ZoomFactor zoomFactor);
|
||||
|
||||
ZoomFactor upperZoomFactor() const { return m_upperZoomFactor; }
|
||||
|
||||
qint64 startTime() const { return m_startTime; }
|
||||
void setStartTime(qint64 time);
|
||||
|
||||
qint64 currentTime() const { return m_currentTime; }
|
||||
void setCurrentTime(qint64 time);
|
||||
|
||||
qint64 endTime() const { return m_startTime + m_duration; }
|
||||
void setEndTime(qint64 time);
|
||||
|
||||
int duration() const { return m_duration; }
|
||||
void setDuration(int duration);
|
||||
|
||||
void setRange(qint64 start, qint64 end);
|
||||
|
||||
bool isCursorTextVisible() const { return m_isCursorTextVisible; }
|
||||
void setCursorTextVisible(bool visible);
|
||||
void setCursorText(const QString &text);
|
||||
|
||||
int toPixel(qint64 time) const;
|
||||
qint64 toTime(int pixel) const;
|
||||
qint64 toDuration(int pixel) const;
|
||||
int fromDuration(qint64 time) const;
|
||||
|
||||
const QVector<int> &selectedItemIds() const { return m_selectedItemIds; }
|
||||
bool hasSelectedItems() const { return !m_selectedItemIds.isEmpty(); }
|
||||
void clearSelectedItems();
|
||||
void selectAllItems();
|
||||
void selectItem(int itemId, bool reset = true);
|
||||
void selectItems(const QVector<int> &itemIds, bool reset = true);
|
||||
void unselectItem(int itemId);
|
||||
|
||||
TimelineViewDragHandler *dragHandler() { return m_dragHandler; }
|
||||
void setDragHandler(TimelineViewDragHandler *dragHandler) { m_dragHandler = dragHandler; }
|
||||
TimelineViewResizeHandler *resizeHandler() { return m_resizeHandler; }
|
||||
void setResizeHandler(TimelineViewResizeHandler *resizeHandler) { m_resizeHandler = resizeHandler; }
|
||||
TimelineViewSelectHandler *selectHandler() { return m_selectHandler; }
|
||||
void setSelectHandler(TimelineViewSelectHandler *selectHandler) { m_selectHandler = selectHandler; }
|
||||
TimelineViewTrackHandler *trackHandler() { return m_trackHandler; }
|
||||
void setTrackHandler(TimelineViewTrackHandler *trackHandler) { m_trackHandler = trackHandler; }
|
||||
|
||||
bool isCursorWithinRange(const QPoint &pos) const { return isCursorWithinRangeX(pos.x()) && isCursorWithinRangeY(pos.y()); }
|
||||
bool isCursorWithinRangeX(int x) const;
|
||||
bool isCursorWithinRangeY(int y) const;
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void rangeChanged(qint64, qint64);
|
||||
void currentTimeChanged(qint64);
|
||||
void zoomChanged(ZoomFactor);
|
||||
void trackActivated(int);
|
||||
void selectedItemsChanged(const QVector<int> &);
|
||||
void itemActivated(int);
|
||||
void currentItemTimeChanged();
|
||||
|
||||
private:
|
||||
TimelineHeaderView *m_header{nullptr};
|
||||
TimelineModel *m_timelineModel{nullptr};
|
||||
QAbstractItemModel *m_model{nullptr};
|
||||
QAbstractProxyModel *m_proxyModel{nullptr};
|
||||
ItemDelegate *m_delegate{nullptr};
|
||||
TimelineViewDragHandler *m_dragHandler{nullptr};
|
||||
TimelineViewResizeHandler *m_resizeHandler{nullptr};
|
||||
TimelineViewSelectHandler *m_selectHandler{nullptr};
|
||||
TimelineViewTrackHandler *m_trackHandler{nullptr};
|
||||
|
||||
ZoomFactor m_zoomFactor{ZoomFactor::ThirtyMinutes};
|
||||
ZoomFactor m_upperZoomFactor{ZoomFactor::OneDay};
|
||||
ZoomFactor m_minZoomFactor{ZoomFactor::FifteenSeconds};
|
||||
ZoomFactor m_maxZoomFactor{ZoomFactor::OneDay};
|
||||
|
||||
qint64 m_startTime{-1};
|
||||
qint64 m_currentTime{-1};
|
||||
int m_duration{-1};
|
||||
|
||||
bool m_isCursorTextVisible{false};
|
||||
QString m_cursorText;
|
||||
QPoint m_cursorPosition;
|
||||
|
||||
QVector<int> m_selectedItemIds;
|
||||
|
||||
NGL::INGLLogger &m_logger;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+505
@@ -0,0 +1,505 @@
|
||||
#include "timeline_view_drag_handler.h"
|
||||
#include "item.h"
|
||||
#include "track.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QDrag>
|
||||
#include <QMimeData>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineViewDragHandler::TimelineViewDragHandler(TimelineView *view)
|
||||
: m_view(view)
|
||||
{}
|
||||
|
||||
TimelineViewDragHandler::~TimelineViewDragHandler()
|
||||
{
|
||||
m_view = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineViewDragHandler::dragEnterEvent(QDragEnterEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
{
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
if (e->mimeData()->hasFormat(Item::mimetype))
|
||||
e->acceptProposedAction();
|
||||
else
|
||||
e->ignore();
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::dragMoveEvent(QDragMoveEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
{
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
if (!e->mimeData()->hasFormat(Item::mimetype))
|
||||
return;
|
||||
|
||||
m_dragPos = e->pos();
|
||||
m_view->m_header->setHoverPosition(m_dragPos.x());
|
||||
e->setAccepted(canDragItemsTo(e->pos()));
|
||||
m_view->viewport()->update();
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::dragLeaveEvent(QDragLeaveEvent *e)
|
||||
{
|
||||
Q_UNUSED(e);
|
||||
m_dragPos = {};
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::dropEvent(QDropEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable() || e->source() != m_view || m_dragItemIds.isEmpty())
|
||||
{
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
if (!e->mimeData()->hasFormat(Item::mimetype))
|
||||
return;
|
||||
|
||||
if (!canDragItemsTo(e->pos()))
|
||||
{
|
||||
clear();
|
||||
e->acceptProposedAction();
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 dt = deltaTime();
|
||||
try
|
||||
{
|
||||
for (const int &itemId : qAsConst(m_dragItemIds))
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
m_view->timelineModel()->moveItemToTrack(itemId, dragItemTrackId(item), item.startTime() + dt);
|
||||
}
|
||||
}
|
||||
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; }
|
||||
clear();
|
||||
e->acceptProposedAction();
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
if (e->buttons() != Qt::LeftButton)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
return;
|
||||
|
||||
if ((e->pos() - m_dragStartPos).manhattanLength() < QApplication::startDragDistance())
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
{
|
||||
m_view->setCursor(QCursor(Qt::ForbiddenCursor));
|
||||
return;
|
||||
}
|
||||
|
||||
m_dragPos = e->pos();
|
||||
m_view->setCursor(QCursor(canDragItemsTo(m_dragPos) ? Qt::ClosedHandCursor : Qt::ForbiddenCursor));
|
||||
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
||||
return;
|
||||
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setData(Item::mimetype, {});
|
||||
QDrag *drag = new QDrag(m_view);
|
||||
drag->setMimeData(mimeData);
|
||||
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
||||
m_dragItemIds = m_view->m_selectedItemIds;
|
||||
if (!m_dragItemIds.contains(item.id()))
|
||||
m_dragItemIds = { item.id() };
|
||||
|
||||
for (const int &itemId : m_dragItemIds)
|
||||
if (!m_view->timelineModel()->item(itemId).isEditable())
|
||||
return;
|
||||
|
||||
m_dragStartIndex = index;
|
||||
m_dragStartTime = m_view->toTime(e->pos().x());
|
||||
m_dragStartPos = e->pos();
|
||||
m_dragPos = m_dragStartPos;
|
||||
m_view->setCursor(QCursor(Qt::ClosedHandCursor));
|
||||
}
|
||||
catch (const QString &str) { DEBUG_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
return;
|
||||
|
||||
if (index.data().toInt() != m_dragStartIndex.data().toInt())
|
||||
return;
|
||||
|
||||
m_dragPos = e->pos();
|
||||
if (m_dragPos.x() == m_dragStartPos.x())
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
if (!canMoveItemsTo(m_dragPos))
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
emit m_view->currentItemTimeChanged();
|
||||
qint64 dt = deltaTime();
|
||||
for (const int &itemId : m_dragItemIds)
|
||||
{
|
||||
Item &item = m_view->timelineModel()->item(itemId);
|
||||
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
||||
}
|
||||
clear();
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
if (m_view->m_selectedItemIds.isEmpty())
|
||||
return;
|
||||
|
||||
if (e->key() == Qt::Key_Escape)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->key() != Qt::Key_Left && e->key() != Qt::Key_Right)
|
||||
return;
|
||||
|
||||
int dt = m_view->timelineModel()->snapStep();
|
||||
if (e->key() == Qt::Key_Left)
|
||||
moveItemsLeft(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? -dt : -10 * dt);
|
||||
else
|
||||
moveItemsRight(m_view->selectedItemIds(), QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) ? dt : 10 * dt);
|
||||
}
|
||||
|
||||
|
||||
bool TimelineViewDragHandler::isActive() const
|
||||
{
|
||||
return !m_dragItemIds.isEmpty() && (m_dragPos - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance();
|
||||
}
|
||||
|
||||
|
||||
int TimelineViewDragHandler::deltaX() const
|
||||
{
|
||||
if (!m_view->timelineModel()->isSnapEnabled())
|
||||
return m_dragPos.x() - m_dragStartPos.x();
|
||||
return m_view->toPixel(m_dragStartTime + deltaTime()) - m_view->toPixel(m_dragStartTime);
|
||||
}
|
||||
|
||||
int TimelineViewDragHandler::deltaY() const
|
||||
{
|
||||
|
||||
const QModelIndex dragIndex = m_view->indexAt(m_dragPos);
|
||||
if (!dragIndex.isValid())
|
||||
return m_dragPos.y() - m_dragStartPos.y();
|
||||
return m_view->visualRect(dragIndex).top() - m_view->visualRect(m_dragStartIndex).top();
|
||||
}
|
||||
|
||||
qint64 TimelineViewDragHandler::deltaTime() const
|
||||
{
|
||||
if (!m_view->timelineModel()->isSnapEnabled())
|
||||
return m_view->toTime(m_dragPos.x()) - m_view->toTime(m_dragStartPos.x());
|
||||
|
||||
qint64 dt = ((m_view->toTime(m_dragPos.x()) - m_dragStartTime) / m_view->timelineModel()->snapStep()) * m_view->timelineModel()->snapStep();
|
||||
int dtAbsMin = 2 * m_view->timelineModel()->snapTolerance();
|
||||
int eps = m_view->timelineModel()->snapTolerance();
|
||||
for (const int &dragItemId : m_dragItemIds)
|
||||
{
|
||||
const Item &dragItem = m_view->timelineModel()->item(dragItemId);
|
||||
qint64 start = dragItem.startTime() + dt;
|
||||
qint64 end = start + dragItem.duration();
|
||||
for (int itemId : m_view->timelineModel()->itemIds())
|
||||
{
|
||||
if (m_dragItemIds.contains(itemId))
|
||||
continue;
|
||||
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
if (start - eps < item.startTime() + item.duration() && item.startTime() + item.duration() < start + eps)
|
||||
{
|
||||
qint64 dtCurrent = item.startTime() + item.duration() - dragItem.startTime();
|
||||
qint64 dtAbsCurrent = std::abs(dtCurrent);
|
||||
if (dtAbsCurrent < dtAbsMin)
|
||||
{
|
||||
dtAbsMin = dtAbsCurrent;
|
||||
dt = dtCurrent;
|
||||
}
|
||||
}
|
||||
else if (end - eps < item.startTime() && item.startTime() < end + eps)
|
||||
{
|
||||
qint64 dtCurrent = item.startTime() - (dragItem.startTime() + dragItem.duration());
|
||||
qint64 dtAbsCurrent = std::abs(dtCurrent);
|
||||
if (dtAbsCurrent < dtAbsMin)
|
||||
{
|
||||
dtAbsMin = dtAbsCurrent;
|
||||
dt = dtCurrent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool TimelineViewDragHandler::canDragItemsTo(const QPoint &point) const
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return false;
|
||||
|
||||
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
return false;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(point);
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
return false;
|
||||
|
||||
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
||||
return canMoveItemsTo(point);
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
qint64 dt = deltaTime();
|
||||
for (const int &itemId : m_dragItemIds)
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
int trackId = dragItemTrackId(item);
|
||||
if (trackId == 0)
|
||||
return false;
|
||||
|
||||
const Track &track = m_view->timelineModel()->track(trackId);
|
||||
qint64 startNew = item.startTime() + dt;
|
||||
if (!m_view->timelineModel()->canDropItem(item, track.id(), startNew))
|
||||
return false;
|
||||
|
||||
if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TimelineViewDragHandler::canMoveItemsTo(const QPoint &point) const
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return false;
|
||||
if (m_dragItemIds.isEmpty() || !m_dragStartIndex.isValid())
|
||||
return false;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(point);
|
||||
if (!index.isValid() || index.column() == Section::eActuator || index.data().toInt() != m_dragStartIndex.data().toInt())
|
||||
return false;
|
||||
|
||||
qint64 dt = deltaTime();
|
||||
for (const int &itemId : m_dragItemIds)
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
qint64 startNew = item.startTime() + dt;
|
||||
if (!m_view->timelineModel()->canMoveItem(item, startNew))
|
||||
return false;
|
||||
|
||||
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
||||
if (track.isItemsBetween(startNew, startNew + item.duration(), m_dragItemIds, true))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void TimelineViewDragHandler::moveItemsLeft(const QVector<int> &itemIds, int dt)
|
||||
{
|
||||
Q_ASSERT(dt < 0);
|
||||
try
|
||||
{
|
||||
for (int itemId : itemIds)
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
||||
if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true))
|
||||
{
|
||||
if (dt < - m_view->timelineModel()->snapStep())
|
||||
moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep());
|
||||
return;
|
||||
}
|
||||
if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt))
|
||||
{
|
||||
if (dt < - m_view->timelineModel()->snapStep())
|
||||
moveItemsLeft(itemIds, dt + m_view->timelineModel()->snapStep());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const QString &errMsg)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg;
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (int itemId : itemIds)
|
||||
{
|
||||
Item &item = m_view->timelineModel()->item(itemId);
|
||||
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
||||
}
|
||||
}
|
||||
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::moveItemsRight(const QVector<int> &itemIds, int dt)
|
||||
{
|
||||
Q_ASSERT(dt > 0);
|
||||
try
|
||||
{
|
||||
for (int itemId : itemIds)
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
const Track &track = m_view->timelineModel()->track(item.parentTrackId());
|
||||
if (track.isItemsBetween(item.startTime() + dt, item.startTime() + item.duration() + dt, itemIds, true))
|
||||
{
|
||||
if (dt > m_view->timelineModel()->snapStep())
|
||||
moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep());
|
||||
return;
|
||||
}
|
||||
if (!m_view->timelineModel()->canMoveItem(item, item.startTime() + dt))
|
||||
{
|
||||
if (dt > m_view->timelineModel()->snapStep())
|
||||
moveItemsRight(itemIds, dt - m_view->timelineModel()->snapStep());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const QString &errMsg)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed: " << errMsg;
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (int itemId : itemIds)
|
||||
{
|
||||
Item &item = m_view->timelineModel()->item(itemId);
|
||||
m_view->timelineModel()->setItemStartTime(item, item.startTime() + dt);
|
||||
}
|
||||
}
|
||||
catch (const QString &errMsg) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << errMsg; }
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
|
||||
int TimelineViewDragHandler::dragItemTrackId(const Item &item) const
|
||||
{
|
||||
int dy = deltaY();
|
||||
|
||||
const QModelIndex index = m_view->indexAt(m_dragStartPos + QPoint(0, dy));
|
||||
if (index.data().toInt() == m_dragStartIndex.data().toInt())
|
||||
return item.parentTrackId();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
const QModelIndex originalIndex = m_view->timelineModel()->indexOfTrack(item.parentTrackId());
|
||||
|
||||
const QModelIndex newIndex = m_view->indexAt(m_view->visualRect(originalIndex).center() + QPoint(0, dy));
|
||||
return newIndex.data().toInt();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineViewDragHandler::clear()
|
||||
{
|
||||
m_dragStartIndex = {};
|
||||
m_dragStartTime = -1;
|
||||
m_dragStartPos = {};
|
||||
m_dragPos = {};
|
||||
m_dragItemIds.clear();
|
||||
m_view->setCursor(QCursor(Qt::ArrowCursor));
|
||||
m_view->viewport()->update();
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "timeline_view.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QVector>
|
||||
#include <QModelIndex>
|
||||
|
||||
|
||||
class QDragEnterEvent;
|
||||
class QDragMoveEvent;
|
||||
class QDragLeaveEvent;
|
||||
class QDropEvent;
|
||||
class QMouseEvent;
|
||||
class QKeyEvent;
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class Item;
|
||||
class Track;
|
||||
class TimelineView;
|
||||
class ItemDelegate;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineViewDragHandler
|
||||
{
|
||||
friend class TimelineView;
|
||||
friend class ItemDelegate;
|
||||
|
||||
public:
|
||||
explicit TimelineViewDragHandler(TimelineView *view);
|
||||
virtual ~TimelineViewDragHandler();
|
||||
|
||||
virtual void dragEnterEvent(QDragEnterEvent *e);
|
||||
virtual void dragMoveEvent(QDragMoveEvent *e);
|
||||
virtual void dragLeaveEvent(QDragLeaveEvent *e);
|
||||
virtual void dropEvent(QDropEvent *e);
|
||||
virtual void mouseMoveEvent(QMouseEvent *e);
|
||||
virtual void mousePressEvent(QMouseEvent *e);
|
||||
virtual void mouseReleaseEvent(QMouseEvent *e);
|
||||
virtual void keyPressEvent(QKeyEvent *e);
|
||||
|
||||
virtual bool isActive() const;
|
||||
|
||||
virtual int deltaX() const;
|
||||
virtual int deltaY() const;
|
||||
virtual qint64 deltaTime() const;
|
||||
|
||||
|
||||
const QVector<int> &dragItemIds() const { return m_dragItemIds; }
|
||||
const QModelIndex &dragStartIndex() const { return m_dragStartIndex; }
|
||||
qint64 dragStartTime() const { return m_dragStartTime; }
|
||||
const QPoint &dragStartPos() const { return m_dragStartPos; }
|
||||
const QPoint &dragPos() const { return m_dragPos; }
|
||||
|
||||
protected:
|
||||
virtual bool canDragItemsTo(const QPoint &point) const;
|
||||
virtual bool canMoveItemsTo(const QPoint &point) const;
|
||||
|
||||
virtual void moveItemsLeft(const QVector<int> &itemIds, int dt);
|
||||
virtual void moveItemsRight(const QVector<int> &itemIds, int dt);
|
||||
|
||||
int dragItemTrackId(const Item &item) const;
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
TimelineView *m_view{nullptr};
|
||||
QVector<int> m_dragItemIds;
|
||||
QModelIndex m_dragStartIndex;
|
||||
qint64 m_dragStartTime{-1};
|
||||
QPoint m_dragStartPos;
|
||||
QPoint m_dragPos;
|
||||
|
||||
int m_snapLinePos{-1};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
#include "timeline_view_resize_handler.h"
|
||||
#include "timeline_view.h"
|
||||
#include "timeline_header_view.h"
|
||||
#include "item.h"
|
||||
#include "track.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineViewResizeHandler::TimelineViewResizeHandler(TimelineView *view)
|
||||
: m_view(view)
|
||||
{}
|
||||
|
||||
TimelineViewResizeHandler::~TimelineViewResizeHandler()
|
||||
{
|
||||
m_view = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineViewResizeHandler::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
if (m_currentTrackId == -1 || m_currentItemId == -1 || m_resizePos == ResizePosition::eNone)
|
||||
return;
|
||||
if (!m_view->timelineModel()->item(m_currentItemId).isEditable())
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index(m_view->indexAt(e->pos()));
|
||||
if (index.column() == Section::eActuator || !index.isValid())
|
||||
return;
|
||||
|
||||
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
if (track.id() != m_currentTrackId)
|
||||
return;
|
||||
|
||||
qint64 time = m_view->timelineModel()->isSnapEnabled() ? globalSnapTime(e->pos().x()) : localSnapTime(e->pos().x());
|
||||
try
|
||||
{
|
||||
Item &item = m_view->timelineModel()->item(m_currentItemId);
|
||||
if (m_resizePos == ResizePosition::eStart)
|
||||
{
|
||||
if (item.startTime() == time)
|
||||
return;
|
||||
|
||||
if (!m_view->timelineModel()->canResizeItem(item, time, item.startTime() + item.duration() - time))
|
||||
return;
|
||||
|
||||
m_view->timelineModel()->setItemDuration(track, item, item.startTime() + item.duration() - time);
|
||||
m_view->timelineModel()->setItemStartTime(track, item, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.duration() == time - item.startTime())
|
||||
return;
|
||||
|
||||
if (!m_view->timelineModel()->canResizeItem(item, item.startTime(), time - item.startTime()))
|
||||
return;
|
||||
|
||||
m_view->timelineModel()->setItemDuration(track, item, time - item.startTime());
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
mouseReleaseEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineViewResizeHandler::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index(m_view->indexAt(e->pos()));
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
const Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
const Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
||||
if (!item.isEditable())
|
||||
return;
|
||||
|
||||
const int startPos = m_view->toPixel(item.startTime());
|
||||
const int endPos = m_view->toPixel(item.startTime() + item.duration());
|
||||
if (endPos - startPos < 15 || (startPos + 5 < e->pos().x() && e->pos().x() < endPos - 5))
|
||||
{
|
||||
m_resizePos = ResizePosition::eNone;
|
||||
return;
|
||||
}
|
||||
else if (startPos + 5 >= e->pos().x())
|
||||
m_resizePos = ResizePosition::eStart;
|
||||
else
|
||||
m_resizePos = ResizePosition::eEnd;
|
||||
|
||||
m_currentTrackId = track.id();
|
||||
m_currentItemId = item.id();
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void TimelineViewResizeHandler::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
emit m_view->currentItemTimeChanged();
|
||||
m_view->setCursor(QCursor(Qt::ArrowCursor));
|
||||
m_currentTrackId = -1;
|
||||
m_currentItemId = -1;
|
||||
m_resizePos = ResizePosition::eNone;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
qint64 TimelineViewResizeHandler::localSnapTime(int pos) const
|
||||
{
|
||||
qint64 time = m_view->toTime(pos);
|
||||
if (m_currentItemId == -1 || m_resizePos == ResizePosition::eNone)
|
||||
return time;
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_view->timelineModel()->track(m_currentTrackId);
|
||||
const Item ¤tItem = m_view->timelineModel()->item(m_currentItemId);
|
||||
if (m_resizePos == ResizePosition::eStart)
|
||||
{
|
||||
if (time >= currentItem.startTime() + currentItem.duration())
|
||||
time = currentItem.startTime() + currentItem.duration() - m_view->timelineModel()->minimumDuration();
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
const Item &previous = track.previous(m_currentItemId);
|
||||
const qint64 endTime = previous.startTime() + previous.duration();
|
||||
if (time <= endTime)
|
||||
time = endTime;
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (time <= currentItem.startTime())
|
||||
time = currentItem.startTime() + m_view->timelineModel()->minimumDuration();
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Item &next = track.next(m_currentItemId);
|
||||
if (time >= next.startTime())
|
||||
time = next.startTime();
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
return time;
|
||||
}
|
||||
|
||||
qint64 TimelineViewResizeHandler::globalSnapTime(int pos) const
|
||||
{
|
||||
qint64 tPos = (m_view->toTime(pos) / m_view->timelineModel()->snapStep()) * m_view->timelineModel()->snapStep();
|
||||
if (m_currentItemId == -1 || m_resizePos == ResizePosition::eNone)
|
||||
return tPos;
|
||||
|
||||
qint64 tMin = m_view->m_startTime;
|
||||
qint64 tMax = m_view->endTime();
|
||||
try
|
||||
{
|
||||
const Item &previous = m_view->timelineModel()->track(m_currentTrackId).previous(m_currentItemId);
|
||||
tMin = previous.startTime() + previous.duration();
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
DEBUG_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
try
|
||||
{
|
||||
const Item &next = m_view->timelineModel()->track(m_currentTrackId).next(m_currentItemId);
|
||||
tMax = next.startTime();
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
DEBUG_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
|
||||
qint64 time = tPos;
|
||||
try
|
||||
{
|
||||
const Item ¤tItem = m_view->timelineModel()->item(m_currentItemId);
|
||||
|
||||
int minDist = 2 * m_view->timelineModel()->snapTolerance();
|
||||
int eps = m_view->timelineModel()->snapTolerance();
|
||||
for (int itemId : m_view->timelineModel()->itemIds())
|
||||
{
|
||||
if (itemId == m_currentItemId)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
const Item &item = m_view->timelineModel()->item(itemId);
|
||||
qint64 t;
|
||||
if (time - eps < item.startTime() && item.startTime() < time + eps)
|
||||
t = item.startTime();
|
||||
else if (time - eps < item.startTime() + item.duration() && item.startTime() + item.duration() < time + eps)
|
||||
t = item.startTime() + item.duration();
|
||||
else
|
||||
continue;
|
||||
|
||||
if (std::abs(t - tPos) < minDist)
|
||||
{
|
||||
if ((m_resizePos == ResizePosition::eStart && t >= currentItem.startTime() + currentItem.duration()) ||
|
||||
(m_resizePos == ResizePosition::eEnd && t <= currentItem.startTime()))
|
||||
continue;
|
||||
time = t;
|
||||
minDist = time - tPos;
|
||||
}
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
if (m_resizePos == ResizePosition::eStart && time >= currentItem.startTime() + currentItem.duration())
|
||||
return currentItem.startTime() + currentItem.duration() - m_view->timelineModel()->minimumDuration();
|
||||
else if (m_resizePos == ResizePosition::eEnd && time <= currentItem.startTime())
|
||||
return currentItem.startTime() + m_view->timelineModel()->minimumDuration();
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
|
||||
time = std::max(tMin, time);
|
||||
time = std::min(tMax, time);
|
||||
return time;
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class Track;
|
||||
class TimelineView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineViewResizeHandler
|
||||
{
|
||||
|
||||
public:
|
||||
enum class ResizePosition { eNone, eStart, eEnd };
|
||||
|
||||
explicit TimelineViewResizeHandler(TimelineView *view);
|
||||
virtual ~TimelineViewResizeHandler();
|
||||
|
||||
virtual void mouseMoveEvent(QMouseEvent *e);
|
||||
virtual void mousePressEvent(QMouseEvent *e);
|
||||
virtual void mouseReleaseEvent(QMouseEvent *e);
|
||||
|
||||
virtual bool isActive() const { return m_currentTrackId != -1 && m_currentItemId != -1 && m_resizePos != ResizePosition::eNone; }
|
||||
|
||||
private:
|
||||
virtual qint64 localSnapTime(int pos) const;
|
||||
virtual qint64 globalSnapTime(int pos) const;
|
||||
|
||||
protected:
|
||||
TimelineView *m_view{nullptr};
|
||||
int m_currentTrackId{-1};
|
||||
int m_currentItemId{-1};
|
||||
ResizePosition m_resizePos{ResizePosition::eNone};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
#include "timeline_view_selection_handler.h"
|
||||
#include "item_delegate.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QSize>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineViewSelectHandler::TimelineViewSelectHandler(TimelineView *view)
|
||||
: m_view(view)
|
||||
{
|
||||
}
|
||||
|
||||
TimelineViewSelectHandler::~TimelineViewSelectHandler()
|
||||
{
|
||||
m_view = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineViewSelectHandler::mouseDoubleClickEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
m_view->clearSelectedItems();
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
||||
emit m_view->itemActivated(item.id());
|
||||
}
|
||||
catch (const QString &str) { DEBUG_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; }
|
||||
}
|
||||
|
||||
void TimelineViewSelectHandler::mouseMoveEvent(QMouseEvent *e)
|
||||
{
|
||||
if (m_isRubberBandActive)
|
||||
{
|
||||
m_view->setCursor(QCursor(Qt::CrossCursor));
|
||||
if (!m_view->isCursorWithinRangeX(e->pos().x()))
|
||||
m_rubberBand.setHeight(e->pos().y() - m_rubberBand.top());
|
||||
|
||||
|
||||
else
|
||||
m_rubberBand.setRect(
|
||||
m_rubberBand.left(), m_rubberBand.top(), e->pos().x() - m_rubberBand.left(), e->pos().y() - m_rubberBand.top()
|
||||
);
|
||||
}
|
||||
else
|
||||
m_selectedItemId = -1;
|
||||
}
|
||||
|
||||
void TimelineViewSelectHandler::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() != Qt::LeftButton && e->button() != Qt::RightButton)
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (index.isValid())
|
||||
{
|
||||
if (index.column() == Section::eActuator)
|
||||
{
|
||||
qWarning() << "clicked on row with actuator" << index.data().toString();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
||||
if (e->button() == Qt::LeftButton)
|
||||
m_selectedItemId = item.id();
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
return;
|
||||
|
||||
if (e->button() == Qt::RightButton)
|
||||
{
|
||||
emit m_view->customContextMenuRequested(e->pos());
|
||||
return;
|
||||
}
|
||||
|
||||
m_view->clearSelectedItems();
|
||||
m_rubberBand.setLeft(e->pos().x());
|
||||
m_rubberBand.setTop(e->pos().y());
|
||||
m_rubberBand.setSize(QSize(0, 0));
|
||||
m_isRubberBandActive = true;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "TimelineViewSelectHandler::mousePressEvent: index invalid";
|
||||
}
|
||||
|
||||
if (e->button() == Qt::RightButton)
|
||||
return;
|
||||
|
||||
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
return;
|
||||
|
||||
m_view->clearSelectedItems();
|
||||
|
||||
if (!m_view->isCursorWithinRangeX(e->pos().x()))
|
||||
return;
|
||||
|
||||
m_rubberBand.setLeft(e->pos().x());
|
||||
m_rubberBand.setTop(e->pos().y());
|
||||
m_rubberBand.setSize(QSize(0, 0));
|
||||
m_isRubberBandActive = true;
|
||||
}
|
||||
|
||||
void TimelineViewSelectHandler::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() == Qt::RightButton)
|
||||
{
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (m_isRubberBandActive)
|
||||
{
|
||||
m_view->selectItems(getItemsWithinRubberBand());
|
||||
m_isRubberBandActive = false;
|
||||
m_rubberBand.setSize(QSize(0, 0));
|
||||
m_view->setCursor(QCursor(Qt::ArrowCursor));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() == Section::eActuator)
|
||||
{
|
||||
m_selectedItemId = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
Item &item = track.itemAt(m_view->toTime(e->pos().x()));
|
||||
if (item.id() == m_selectedItemId)
|
||||
{
|
||||
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
{
|
||||
if (m_view->selectedItemIds().contains(m_selectedItemId))
|
||||
m_view->unselectItem(m_selectedItemId);
|
||||
else
|
||||
m_view->selectItem(m_selectedItemId, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_view->m_selectedItemIds.size() > 1 || !m_view->m_selectedItemIds.contains(m_selectedItemId))
|
||||
m_view->selectItem(m_selectedItemId);
|
||||
else
|
||||
m_view->unselectItem(m_selectedItemId);
|
||||
}
|
||||
}
|
||||
} catch (...) {}
|
||||
m_selectedItemId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineViewSelectHandler::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape)
|
||||
{
|
||||
m_selectedItemId = -1;
|
||||
m_view->clearSelectedItems();
|
||||
}
|
||||
else if (e->key() == Qt::Key_Delete)
|
||||
{
|
||||
if (!m_view->m_selectedItemIds.isEmpty())
|
||||
{
|
||||
for (const int &itemId : qAsConst(m_view->m_selectedItemIds))
|
||||
{
|
||||
try
|
||||
{
|
||||
m_view->timelineModel()->removeItem(itemId);
|
||||
}
|
||||
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; }
|
||||
}
|
||||
m_view->clearSelectedItems();
|
||||
m_selectedItemId = -1;
|
||||
}
|
||||
}
|
||||
else if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (e->key())
|
||||
{
|
||||
case Qt::Key_A:
|
||||
m_view->selectAllItems();
|
||||
m_selectedItemId = -1;
|
||||
break;
|
||||
|
||||
case Qt::Key_C:
|
||||
if (m_view->hasSelectedItems())
|
||||
m_view->timelineModel()->copyToClipboard(m_view->selectedItemIds());
|
||||
break;
|
||||
|
||||
case Qt::Key_X:
|
||||
if (m_view->hasSelectedItems())
|
||||
{
|
||||
m_view->timelineModel()->cutToClipboard(m_view->selectedItemIds());
|
||||
m_view->clearSelectedItems();
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_V:
|
||||
if (!m_view->timelineModel()->m_clipboard.empty())
|
||||
{
|
||||
qint64 time = m_view->currentTime();
|
||||
if (m_view->m_header->isIndicatorVisible() && m_view->m_header->indicatorTime() != -1)
|
||||
time = m_view->m_header->indicatorTime();
|
||||
m_view->timelineModel()->pasteFromClipboard(time);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const QString &str) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " " << str; }
|
||||
catch (...) { ERROR_LOG(m_view->m_logger) << LOGFUNCTION << " failed"; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool TimelineViewSelectHandler::isActive() const
|
||||
{
|
||||
if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier))
|
||||
return true;
|
||||
if (m_isRubberBandActive)
|
||||
return true;
|
||||
return m_selectedItemId != -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QVector<int> TimelineViewSelectHandler::getItemsWithinRubberBand() const
|
||||
{
|
||||
const QPoint topLeft = { std::min(m_rubberBand.left(), m_rubberBand.right()), std::min(m_rubberBand.top(), m_rubberBand.bottom()) };
|
||||
const QPoint bottomRight = { std::max(m_rubberBand.left(), m_rubberBand.right()), std::max(m_rubberBand.top(), m_rubberBand.bottom()) };
|
||||
const qint64 start = m_view->toTime(topLeft.x());
|
||||
const qint64 end = m_view->toTime(bottomRight.x());
|
||||
if (start == end)
|
||||
return {};
|
||||
|
||||
QVector<int> itemsWithinRubberBand;
|
||||
const int h = m_view->itemDelegate()->sizeHint({}, {}).height();
|
||||
for (int y = topLeft.y(); y <= bottomRight.y(); y += h)
|
||||
{
|
||||
const QPoint point = { topLeft.x(), y };
|
||||
|
||||
const QModelIndex index = m_view->indexAt(point);
|
||||
if (!index.isValid())
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
const Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
itemsWithinRubberBand += track.itemIdsBetween(start, end);
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
if (topLeft.y() + h > bottomRight.y())
|
||||
{
|
||||
|
||||
const QModelIndex index = m_view->indexAt(bottomRight);
|
||||
if (!index.isValid())
|
||||
return itemsWithinRubberBand;
|
||||
|
||||
try
|
||||
{
|
||||
const Track &track = m_view->timelineModel()->track(index.data().toInt());
|
||||
itemsWithinRubberBand += track.itemIdsBetween(start, end);
|
||||
}
|
||||
catch (const QString &str)
|
||||
{
|
||||
ERROR_LOG(m_view->m_logger) << str;
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
return itemsWithinRubberBand;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "timeline_view.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QRect>
|
||||
|
||||
|
||||
class QMouseEvent;
|
||||
class QKeyEvent;
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineViewSelectHandler
|
||||
{
|
||||
friend class TimelineView;
|
||||
|
||||
public:
|
||||
explicit TimelineViewSelectHandler(TimelineView *view);
|
||||
virtual ~TimelineViewSelectHandler();
|
||||
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *e);
|
||||
virtual void mouseMoveEvent(QMouseEvent *e);
|
||||
virtual void mousePressEvent(QMouseEvent *e);
|
||||
virtual void mouseReleaseEvent(QMouseEvent *e);
|
||||
virtual void keyPressEvent(QKeyEvent *e);
|
||||
|
||||
virtual bool isActive() const;
|
||||
virtual bool isRubberBandActive() const { return m_isRubberBandActive; }
|
||||
|
||||
protected:
|
||||
virtual QVector<int> getItemsWithinRubberBand() const;
|
||||
|
||||
protected:
|
||||
TimelineView *m_view{nullptr};
|
||||
int m_selectedItemId{-1};
|
||||
QRect m_rubberBand;
|
||||
bool m_isRubberBandActive{false};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#include "timeline_view_resize_handler.h"
|
||||
#include "timeline_view.h"
|
||||
#include "timeline_header_view.h"
|
||||
#include "item.h"
|
||||
#include "track.h"
|
||||
|
||||
#include <LtsWidgets/lts_message_dialog.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineViewTrackHandler::TimelineViewTrackHandler(TimelineView *view)
|
||||
: m_view(view)
|
||||
{
|
||||
}
|
||||
|
||||
TimelineViewTrackHandler::~TimelineViewTrackHandler()
|
||||
{
|
||||
m_view = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineViewTrackHandler::mouseDoubleClickEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() != Section::eActuator)
|
||||
return;
|
||||
|
||||
emit m_view->trackActivated(index.data().toInt());
|
||||
}
|
||||
|
||||
void TimelineViewTrackHandler::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
if (e->button() != Qt::LeftButton)
|
||||
{
|
||||
if (e->button() == Qt::RightButton)
|
||||
emit m_view->customContextMenuRequested(e->pos());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const QModelIndex index = m_view->indexAt(e->pos());
|
||||
if (!index.isValid() || index.column() != Section::eActuator)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if(!m_view->timelineModel()->isEditable())
|
||||
return;
|
||||
|
||||
const Track &track = m_view->timelineModel()->track(index.data(Qt::UserRole).toInt());
|
||||
QRect visRect = m_view->visualRect(index);
|
||||
int buttonLength = visRect.height() / 2;
|
||||
if (track.hasInsertButton())
|
||||
{
|
||||
QRect rect = QRect(visRect.right() - buttonLength, visRect.top(), buttonLength, buttonLength);
|
||||
if (rect.contains(e->pos()))
|
||||
{
|
||||
m_pressedInsertButtonIndex = index;
|
||||
|
||||
m_view->timelineModel()->onInsertTrack(index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (track.hasRemoveButton())
|
||||
{
|
||||
QRect rect = QRect(visRect.right() - buttonLength, visRect.center().y(), buttonLength, buttonLength);
|
||||
if (rect.contains(e->pos()))
|
||||
{
|
||||
m_pressedRemoveButtonIndex = index;
|
||||
|
||||
auto msg = new LtsWidgets::LtsMessageDialogNew(m_view, {}, QApplication::translate("LtsTimeline", "Do you really want to remove this filter track and all of its contents?"),
|
||||
LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes | LtsWidgets::Button::Cancel);
|
||||
|
||||
QObject::connect(msg, &LtsWidgets::LtsMessageDialogNew::yes,
|
||||
m_view, [this, index]()
|
||||
{
|
||||
|
||||
m_view->timelineModel()->onRemoveTrack(index);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
}
|
||||
|
||||
void TimelineViewTrackHandler::mouseReleaseEvent(QMouseEvent *e)
|
||||
{
|
||||
Q_UNUSED(e);
|
||||
m_pressedInsertButtonIndex = {};
|
||||
m_pressedRemoveButtonIndex = {};
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QModelIndex>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class Track;
|
||||
class TimelineView;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineViewTrackHandler
|
||||
{
|
||||
|
||||
public:
|
||||
explicit TimelineViewTrackHandler(TimelineView *view);
|
||||
virtual ~TimelineViewTrackHandler();
|
||||
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *e);
|
||||
virtual void mousePressEvent(QMouseEvent *e);
|
||||
virtual void mouseReleaseEvent(QMouseEvent *e);
|
||||
|
||||
bool isInsertButtonPressed(const QModelIndex &index) const { return index == m_pressedInsertButtonIndex; }
|
||||
bool isRemoveButtonPressed(const QModelIndex &index) const { return index == m_pressedRemoveButtonIndex; }
|
||||
|
||||
protected:
|
||||
TimelineView *m_view{nullptr};
|
||||
|
||||
QModelIndex m_pressedInsertButtonIndex;
|
||||
QModelIndex m_pressedRemoveButtonIndex;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Executable
+402
@@ -0,0 +1,402 @@
|
||||
|
||||
|
||||
|
||||
#include "timeline_widget.h"
|
||||
#include "ui_timeline_widget.h"
|
||||
#include "timeline_view.h"
|
||||
#include "timeline_header_view.h"
|
||||
#include "timeline_model.h"
|
||||
#include "item_delegate.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
|
||||
#include <QSize>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QStandardItemModel>
|
||||
#include <QDateTime>
|
||||
#include <QtGlobal>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
TimelineWidget::TimelineWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::Timeline)
|
||||
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Widget"))
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->debugWidget->hide();
|
||||
m_ui->wPlaceholder->hide();
|
||||
m_ui->pbZoomIn->setProperty("const", true);
|
||||
m_ui->pbZoomOut->setProperty("const", true);
|
||||
m_ui->cbZoom->setProperty("const", true);
|
||||
|
||||
connect(m_ui->cbZoom, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
this, &TimelineWidget::onZoomSelected);
|
||||
m_ui->pbZoomIn->setIcon(QIcon(QStringLiteral("icon:bgv_zoom_in.svg")));
|
||||
connect(m_ui->pbZoomIn, &QPushButton::clicked,
|
||||
this, &TimelineWidget::onZoomIn);
|
||||
m_ui->pbZoomOut->setIcon(QIcon(QStringLiteral("icon:bgv_zoom_out.svg")));
|
||||
connect(m_ui->pbZoomOut, &QPushButton::clicked,
|
||||
this, &TimelineWidget::onZoomOut);
|
||||
|
||||
connect(m_ui->view, &TimelineView::rangeChanged, this, [this](qint64 min, qint64 max)
|
||||
{
|
||||
m_ui->slider->setRange(min / 1000, (max - m_ui->view->zoomFactor()) / 1000);
|
||||
onUpdateZoomButtons();
|
||||
});
|
||||
connect(m_ui->view, &TimelineView::currentTimeChanged, this, [this](qint64 time)
|
||||
{
|
||||
m_ui->slider->setValue(time / 1000);
|
||||
});
|
||||
|
||||
connect(m_ui->view, &TimelineView::zoomChanged,
|
||||
this, &TimelineWidget::onZoomChanged);
|
||||
|
||||
connect(m_ui->view->header(), &TimelineHeaderView::sectionResized,
|
||||
this, &TimelineWidget::onSectionResized);
|
||||
|
||||
connect(m_ui->slider, &QScrollBar::valueChanged,
|
||||
this, &TimelineWidget::onSliderValueChanged);
|
||||
|
||||
onZoomChanged(m_ui->view->zoomFactor());
|
||||
}
|
||||
|
||||
TimelineWidget::~TimelineWidget()
|
||||
{
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TimelineView *TimelineWidget::view()
|
||||
{
|
||||
return m_ui->view;
|
||||
}
|
||||
|
||||
|
||||
TimelineHeaderView *TimelineWidget::header()
|
||||
{
|
||||
return m_ui->view->header();
|
||||
}
|
||||
|
||||
|
||||
TimelineModel *TimelineWidget::timelineModel()
|
||||
{
|
||||
return m_ui->view->timelineModel();
|
||||
}
|
||||
|
||||
|
||||
void TimelineWidget::setModel(QAbstractItemModel *model)
|
||||
{
|
||||
m_ui->view->setModel(model);
|
||||
}
|
||||
|
||||
|
||||
QScrollBar *TimelineWidget::slider()
|
||||
{
|
||||
return m_ui->slider;
|
||||
}
|
||||
|
||||
|
||||
ZoomFactor TimelineWidget::zoomFactor() const
|
||||
{
|
||||
return m_ui->view->zoomFactor();
|
||||
}
|
||||
|
||||
void TimelineWidget::setZoomFactor(ZoomFactor factor)
|
||||
{
|
||||
m_ui->view->setZoomFactor(factor);
|
||||
}
|
||||
|
||||
|
||||
ZoomFactor TimelineWidget::minimumZoomFactor() const
|
||||
{
|
||||
return m_ui->view->minimumZoomFactor();
|
||||
}
|
||||
|
||||
void TimelineWidget::setMinimumZoomFactor(ZoomFactor factor)
|
||||
{
|
||||
m_ui->view->setMinimumZoomFactor(factor);
|
||||
populateZoomFactorComboBox();
|
||||
}
|
||||
|
||||
|
||||
ZoomFactor TimelineWidget::maximumZoomFactor() const
|
||||
{
|
||||
return m_ui->view->maximumZoomFactor();
|
||||
}
|
||||
|
||||
void TimelineWidget::setMaximumZoomFactor(ZoomFactor factor)
|
||||
{
|
||||
m_ui->view->setMaximumZoomFactor(factor);
|
||||
populateZoomFactorComboBox();
|
||||
}
|
||||
|
||||
|
||||
qint64 TimelineWidget::startTime() const
|
||||
{
|
||||
return m_ui->view->startTime();
|
||||
}
|
||||
|
||||
void TimelineWidget::setStartTime(qint64 time)
|
||||
{
|
||||
m_ui->view->setStartTime(time);
|
||||
}
|
||||
|
||||
|
||||
qint64 TimelineWidget::currentTime() const
|
||||
{
|
||||
return m_ui->view->currentTime();
|
||||
}
|
||||
|
||||
void TimelineWidget::setCurrentTime(qint64 time)
|
||||
{
|
||||
m_ui->view->setCurrentTime(time);
|
||||
}
|
||||
|
||||
|
||||
qint64 TimelineWidget::endTime() const
|
||||
{
|
||||
return m_ui->view->endTime();
|
||||
}
|
||||
|
||||
void TimelineWidget::setEndTime(qint64 time)
|
||||
{
|
||||
m_ui->view->setEndTime(time);
|
||||
}
|
||||
|
||||
|
||||
int TimelineWidget::duration() const
|
||||
{
|
||||
return m_ui->view->duration();
|
||||
}
|
||||
|
||||
void TimelineWidget::setDuration(int duration)
|
||||
{
|
||||
m_ui->view->setDuration(duration);
|
||||
}
|
||||
|
||||
|
||||
bool TimelineWidget::isCursorTextVisible() const
|
||||
{
|
||||
return m_ui->view->isCursorTextVisible();
|
||||
}
|
||||
|
||||
void TimelineWidget::setCursorTextVisible(bool visible)
|
||||
{
|
||||
m_ui->view->setCursorTextVisible(visible);
|
||||
}
|
||||
|
||||
void TimelineWidget::setCursorText(const QString &text)
|
||||
{
|
||||
m_ui->view->setCursorText(text);
|
||||
}
|
||||
|
||||
|
||||
bool TimelineWidget::isSnapEnabled() const
|
||||
{
|
||||
return m_ui->view->timelineModel()->isSnapEnabled();
|
||||
}
|
||||
|
||||
void TimelineWidget::setSnapEnabled(bool enabled)
|
||||
{
|
||||
m_ui->view->timelineModel()->setSnapEnabled(enabled);
|
||||
}
|
||||
|
||||
void TimelineWidget::enableSnap()
|
||||
{
|
||||
m_ui->view->timelineModel()->setSnapEnabled(true);
|
||||
}
|
||||
|
||||
void TimelineWidget::disableSnap()
|
||||
{
|
||||
m_ui->view->timelineModel()->setSnapEnabled(false);
|
||||
}
|
||||
|
||||
void TimelineWidget::triggerSnap()
|
||||
{
|
||||
m_ui->view->timelineModel()->setSnapEnabled(!m_ui->view->timelineModel()->isSnapEnabled());
|
||||
if (m_ui->debugWidget->isVisible())
|
||||
m_ui->pbDebugTriggerSnap->setText(m_ui->view->timelineModel()->isSnapEnabled() ? QStringLiteral("Disable Snap") :
|
||||
QStringLiteral("Enable Snap"));
|
||||
}
|
||||
|
||||
|
||||
void TimelineWidget::setZoomInIcon(QIcon icon)
|
||||
{
|
||||
m_ui->pbZoomIn->setIcon(icon);
|
||||
}
|
||||
|
||||
void TimelineWidget::setZoomOutIcon(QIcon icon)
|
||||
{
|
||||
m_ui->pbZoomOut->setIcon(icon);
|
||||
}
|
||||
|
||||
|
||||
QWidget *TimelineWidget::placeholder()
|
||||
{
|
||||
if (m_ui->wPlaceholder->layout()->count() == 0)
|
||||
return nullptr;
|
||||
return m_ui->wPlaceholder->layout()->itemAt(0)->widget();
|
||||
}
|
||||
|
||||
void TimelineWidget::setPlaceholder(QWidget *widget)
|
||||
{
|
||||
QWidget *ptr = placeholder();
|
||||
if (ptr)
|
||||
{
|
||||
if (ptr == widget)
|
||||
return;
|
||||
m_ui->wPlaceholder->layout()->removeWidget(ptr);
|
||||
delete ptr;
|
||||
}
|
||||
m_ui->wPlaceholder->layout()->addWidget(widget);
|
||||
}
|
||||
|
||||
|
||||
void TimelineWidget::showDebugWidget()
|
||||
{
|
||||
m_ui->debugWidget->show();
|
||||
|
||||
m_ui->sbDebugSnapStep->setValue(m_ui->view->timelineModel()->snapStep());
|
||||
m_ui->sbDebugSnapTol->setValue(m_ui->view->timelineModel()->snapTolerance());
|
||||
m_ui->pbDebugTriggerSnap->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Snap") :
|
||||
QStringLiteral("Enable Snap"));
|
||||
m_ui->pbDebugTriggerIndicator->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Indicator") :
|
||||
QStringLiteral("Enable Indicator"));
|
||||
m_ui->pbDebugTriggerHover->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Hover") :
|
||||
QStringLiteral("Enable Hover"));
|
||||
|
||||
connect(m_ui->view, &TimelineView::currentTimeChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
connect(m_ui->view, &TimelineView::rangeChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
connect(m_ui->view, &TimelineView::selectedItemsChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
connect(m_ui->view, &TimelineView::zoomChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
connect(m_ui->pbDebugTriggerSnap, &QPushButton::clicked, this, &TimelineWidget::triggerSnap);
|
||||
connect(m_ui->sbDebugSnapStep, qOverload<int>(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapStep);
|
||||
connect(m_ui->sbDebugSnapTol, qOverload<int>(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapTolerance);
|
||||
connect(m_ui->pbDebugTriggerIndicator, &QPushButton::clicked, this, [this]()
|
||||
{
|
||||
m_ui->view->header()->setIndicatorVisible(!m_ui->view->header()->isIndicatorVisible());
|
||||
m_ui->pbDebugTriggerIndicator->setText(m_ui->view->header()->isIndicatorVisible() ? QStringLiteral("Disable Indicator") :
|
||||
QStringLiteral("Enable Indicator"));
|
||||
});
|
||||
connect(m_ui->pbDebugTriggerHover, &QPushButton::clicked, this, [this]()
|
||||
{
|
||||
m_ui->view->header()->setHoverVisible(!m_ui->view->header()->isHoverVisible());
|
||||
m_ui->pbDebugTriggerHover->setText(m_ui->view->header()->isHoverVisible() ? QStringLiteral("Disable Hover") :
|
||||
QStringLiteral("Enable Hover"));
|
||||
});
|
||||
|
||||
onUpdateDebugInfo();
|
||||
}
|
||||
|
||||
void TimelineWidget::hideDebugWidget()
|
||||
{
|
||||
m_ui->debugWidget->hide();
|
||||
disconnect(m_ui->view, &TimelineView::currentTimeChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
disconnect(m_ui->view, &TimelineView::rangeChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
disconnect(m_ui->view, &TimelineView::selectedItemsChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
disconnect(m_ui->view, &TimelineView::zoomChanged, this, &TimelineWidget::onUpdateDebugInfo);
|
||||
disconnect(m_ui->pbDebugTriggerSnap, &QPushButton::clicked, this, &TimelineWidget::triggerSnap);
|
||||
disconnect(m_ui->sbDebugSnapStep, qOverload<int>(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapStep);
|
||||
disconnect(m_ui->sbDebugSnapTol, qOverload<int>(&QSpinBox::valueChanged), m_ui->view->timelineModel(), &TimelineModel::setSnapTolerance);
|
||||
disconnect(m_ui->pbDebugTriggerIndicator);
|
||||
disconnect(m_ui->pbDebugTriggerHover);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineWidget::populateZoomFactorComboBox()
|
||||
{
|
||||
disconnect(m_ui->cbZoom, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
this, &TimelineWidget::onZoomSelected);
|
||||
m_ui->cbZoom->clear();
|
||||
QVector<ZoomFactor> availableZooms = Zoom::availableZoomFactors(m_ui->view->minimumZoomFactor(), m_ui->view->upperZoomFactor());
|
||||
for (ZoomFactor factor : qAsConst(availableZooms))
|
||||
{
|
||||
m_ui->cbZoom->addItem(Zoom::toString(factor), QVariant(factor));
|
||||
if (factor == zoomFactor())
|
||||
m_ui->cbZoom->setCurrentIndex(m_ui->cbZoom->findData(static_cast<int>(zoomFactor())));
|
||||
}
|
||||
connect(m_ui->cbZoom, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
this, &TimelineWidget::onZoomSelected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void TimelineWidget::onSectionResized(int logicalIndex, int oldSize, int newSize)
|
||||
{
|
||||
Q_UNUSED(oldSize);
|
||||
if (logicalIndex == Section::eItems)
|
||||
{
|
||||
int width = m_ui->view->verticalScrollBar()->width();
|
||||
m_ui->slider->setMinimumHeight(width);
|
||||
m_ui->slider->setMaximumHeight(width);
|
||||
m_ui->slider->setMaximumWidth(newSize);
|
||||
m_ui->slider->setMinimumWidth(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::onSliderValueChanged(int value)
|
||||
{
|
||||
setCurrentTime(qint64(value) * 1000);
|
||||
}
|
||||
|
||||
void TimelineWidget::onUpdateZoomButtons()
|
||||
{
|
||||
m_ui->pbZoomIn->setEnabled(zoomFactor() > minimumZoomFactor());
|
||||
m_ui->pbZoomOut->setEnabled(zoomFactor() < m_ui->view->upperZoomFactor());
|
||||
populateZoomFactorComboBox();
|
||||
}
|
||||
|
||||
void TimelineWidget::onZoomChanged(ZoomFactor factor)
|
||||
{
|
||||
m_ui->slider->setMaximum((m_ui->view->endTime() - m_ui->view->zoomFactor()) / 1000);
|
||||
m_ui->slider->setPageStep(factor / 1000);
|
||||
m_ui->slider->setSingleStep(Zoom::minorInterval(factor));
|
||||
onUpdateZoomButtons();
|
||||
if (static_cast<ZoomFactor>(m_ui->cbZoom->currentData().toInt()) != factor)
|
||||
m_ui->cbZoom->setCurrentIndex(m_ui->cbZoom->findData(QVariant(factor)));
|
||||
}
|
||||
|
||||
void TimelineWidget::onZoomSelected()
|
||||
{
|
||||
ZoomFactor factor = static_cast<ZoomFactor>(m_ui->cbZoom->currentData().toInt());
|
||||
if (factor != zoomFactor())
|
||||
setZoomFactor(factor);
|
||||
}
|
||||
|
||||
void TimelineWidget::onZoomIn()
|
||||
{
|
||||
setZoomFactor(Zoom::decrement(zoomFactor()));
|
||||
}
|
||||
|
||||
void TimelineWidget::onZoomOut()
|
||||
{
|
||||
setZoomFactor(Zoom::increment(zoomFactor()));
|
||||
}
|
||||
|
||||
|
||||
void TimelineWidget::onUpdateDebugInfo()
|
||||
{
|
||||
m_ui->lblDebugStart->setText(DateTime::toString(startTime(), DateTimeFormat::DTG));
|
||||
m_ui->lblDebugEnd->setText(DateTime::toString(endTime(), DateTimeFormat::DTG));
|
||||
m_ui->lblDebugDuration->setText(QString::number(duration() / 1000) + QStringLiteral("s"));
|
||||
m_ui->lblDebugCurrent->setText(DateTime::toString(currentTime(), DateTimeFormat::DTG));
|
||||
m_ui->lblDebugSlider->setText(DateTime::toString(qint64(m_ui->slider->value()) * 1000, DateTimeFormat::DTG));
|
||||
m_ui->lblDebugZoom->setText(Zoom::toString(m_ui->view->zoomFactor()));
|
||||
QString selection;
|
||||
for (int itemId : qAsConst(m_ui->view->selectedItemIds()))
|
||||
selection += QString::number(itemId) + QStringLiteral(", ");
|
||||
if (selection.size() > 1)
|
||||
selection.chop(2);
|
||||
m_ui->lblDebugSelection->setText(selection);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "timeline_utils.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
|
||||
class QScrollBar;
|
||||
class QAbstractItemModel;
|
||||
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class Timeline;
|
||||
}
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
class TimelineView;
|
||||
class TimelineHeaderView;
|
||||
class TimelineModel;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT TimelineWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TimelineWidget(QWidget *parent = nullptr);
|
||||
~TimelineWidget();
|
||||
|
||||
TimelineView *view();
|
||||
|
||||
TimelineHeaderView *header();
|
||||
|
||||
TimelineModel *timelineModel();
|
||||
void setModel(QAbstractItemModel *model);
|
||||
|
||||
QScrollBar *slider();
|
||||
|
||||
ZoomFactor zoomFactor() const;
|
||||
void setZoomFactor(ZoomFactor factor);
|
||||
|
||||
ZoomFactor minimumZoomFactor() const;
|
||||
void setMinimumZoomFactor(ZoomFactor factor);
|
||||
|
||||
ZoomFactor maximumZoomFactor() const;
|
||||
void setMaximumZoomFactor(ZoomFactor factor);
|
||||
|
||||
qint64 startTime() const;
|
||||
void setStartTime(qint64 time);
|
||||
|
||||
qint64 currentTime() const;
|
||||
void setCurrentTime(qint64 time);
|
||||
|
||||
qint64 endTime() const;
|
||||
void setEndTime(qint64 time);
|
||||
|
||||
int duration() const;
|
||||
void setDuration(int duration);
|
||||
|
||||
bool isCursorTextVisible() const;
|
||||
void setCursorTextVisible(bool visible);
|
||||
void setCursorText(const QString &text);
|
||||
void setCursorText(const QStringList &texts);
|
||||
|
||||
bool isSnapEnabled() const;
|
||||
void setSnapEnabled(bool enabled);
|
||||
void enableSnap();
|
||||
void disableSnap();
|
||||
void triggerSnap();
|
||||
|
||||
void setZoomInIcon(QIcon icon);
|
||||
void setZoomOutIcon(QIcon icon);
|
||||
|
||||
QWidget *placeholder();
|
||||
void setPlaceholder(QWidget *widget);
|
||||
|
||||
void showDebugWidget();
|
||||
void hideDebugWidget();
|
||||
|
||||
private:
|
||||
void populateZoomFactorComboBox();
|
||||
|
||||
public:
|
||||
void onUpdateZoomButtons();
|
||||
|
||||
private slots:
|
||||
void onSectionResized(int logicalIndex, int oldSize, int newSize);
|
||||
void onSliderValueChanged(int value);
|
||||
void onZoomChanged(ZoomFactor factor);
|
||||
void onZoomSelected();
|
||||
void onZoomIn();
|
||||
void onZoomOut();
|
||||
|
||||
void onUpdateDebugInfo();
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
Ui::Timeline *m_ui{nullptr};
|
||||
NGL::INGLLogger &m_logger;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Executable
+434
@@ -0,0 +1,434 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Timeline</class>
|
||||
<widget class="QWidget" name="Timeline">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1298</width>
|
||||
<height>409</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Timeline</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="4">
|
||||
<widget class="QWidget" name="wPlaceholder" native="true"/>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="7">
|
||||
<widget class="QWidget" name="debugWidget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugStart">
|
||||
<property name="text">
|
||||
<string>start:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugStart">
|
||||
<property name="text">
|
||||
<string>[START]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugEnd">
|
||||
<property name="text">
|
||||
<string>end:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugEnd">
|
||||
<property name="text">
|
||||
<string>[END]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugCurrent">
|
||||
<property name="text">
|
||||
<string>current:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugCurrent">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>[CURRENT]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugDuration">
|
||||
<property name="text">
|
||||
<string>duration:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugDuration">
|
||||
<property name="text">
|
||||
<string>[DURATION]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugSlider">
|
||||
<property name="text">
|
||||
<string>slider:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugSlider">
|
||||
<property name="text">
|
||||
<string>[SLIDER]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugZoom">
|
||||
<property name="text">
|
||||
<string>zoom:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugZoom">
|
||||
<property name="text">
|
||||
<string>[ZOOM]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugSelection">
|
||||
<property name="text">
|
||||
<string>selection:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblDebugSelection">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>[SELECTION]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pbDebugUpdate">
|
||||
<property name="text">
|
||||
<string notr="true">Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pbDebugTriggerSnap">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable Snap</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugSnapStep">
|
||||
<property name="text">
|
||||
<string>Snap Step:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sbDebugSnapStep">
|
||||
<property name="maximum">
|
||||
<number>60000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lbxDebugSnapTol">
|
||||
<property name="text">
|
||||
<string>Snap Tol:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sbDebugSnapTol">
|
||||
<property name="maximum">
|
||||
<number>60000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pbDebugTriggerIndicator">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable Indicator</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pbDebugTriggerHover">
|
||||
<property name="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable Hover</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="6">
|
||||
<widget class="QScrollBar" name="slider">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>ArrowCursor</cursorShape>
|
||||
</property>
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sliderPosition">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="tracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="invertedControls">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="pbZoomOut">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="7">
|
||||
<widget class="LtsTimeline::TimelineView" name="view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="mouseTracking">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pbZoomIn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QComboBox" name="cbZoom">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>125</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LtsTimeline::TimelineView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>timeline_view.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Executable
+853
@@ -0,0 +1,853 @@
|
||||
|
||||
|
||||
|
||||
#include "track.h"
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
|
||||
const Track &Track::track(int id) const noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_childrenTracks.cbegin(), m_childrenTracks.cend(),
|
||||
[id](const TrackPtr &trackPtr)
|
||||
{
|
||||
return (trackPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_childrenTracks.cend())
|
||||
return **it;
|
||||
|
||||
|
||||
for (const TrackPtr &track : qAsConst(m_childrenTracks))
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->track(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
bool Track::hasTrack(int id, bool recursive) const
|
||||
{
|
||||
if (std::find_if(m_childrenTracks.cbegin(), m_childrenTracks.cend(),
|
||||
[id](const TrackPtr &trackPtr)
|
||||
{
|
||||
return (trackPtr.get()->id() == id);
|
||||
}) != m_childrenTracks.cend())
|
||||
return true;
|
||||
|
||||
if (!recursive)
|
||||
return false;
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
{
|
||||
if (trackPtr->hasTrack(id, true))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Track::addCopyItemIds(const QSet<int> &ids)
|
||||
{
|
||||
m_copyItemIds += ids;
|
||||
}
|
||||
|
||||
|
||||
void Track::addCopyItemId(int id)
|
||||
{
|
||||
m_copyItemIds += id;
|
||||
}
|
||||
|
||||
|
||||
const Track &Track::parentOf(int id) const noexcept(false)
|
||||
{
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
{
|
||||
if (trackPtr->hasTrack(id))
|
||||
return *trackPtr;
|
||||
|
||||
if (trackPtr->hasTrack(id, true))
|
||||
return trackPtr->parentOf(id);
|
||||
}
|
||||
|
||||
throw("not found");
|
||||
}
|
||||
|
||||
|
||||
bool Track::isItemBetween(const Item &item, qint64 start, qint64 end, bool strict, bool complete, qint64 minimumDuration)
|
||||
{
|
||||
if (end - start == -1)
|
||||
{
|
||||
if (item.duration() == -1)
|
||||
{
|
||||
if (!complete)
|
||||
return true;
|
||||
|
||||
return strict ? item.startTime() > start : item.startTime() >= start;
|
||||
}
|
||||
|
||||
if (strict)
|
||||
{
|
||||
return (complete && item.startTime() > start) ||
|
||||
(!complete && item.startTime() + item.duration() > start);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (complete && item.startTime() >= start) ||
|
||||
(!complete && item.startTime() + item.duration() >= start);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.duration() == -1)
|
||||
{
|
||||
if (complete)
|
||||
return false;
|
||||
|
||||
return strict ? item.startTime() < end : item.startTime() <= end;
|
||||
}
|
||||
|
||||
Q_ASSERT(end+1 > start);
|
||||
qint64 itemStart = item.startTime(), itemEnd = item.startTime() + (std::max((qint64)item.duration(), minimumDuration));
|
||||
|
||||
if (strict)
|
||||
return ((complete && (itemStart > start && itemEnd < end)) ||
|
||||
(!complete && (itemEnd > start && itemStart < end)));
|
||||
else
|
||||
return ((complete && (itemStart >= start && itemEnd <= end)) ||
|
||||
(!complete && (itemEnd >= start && itemStart <= end)));
|
||||
}
|
||||
|
||||
|
||||
void Track::removeCopyItem(int itemId) noexcept(false)
|
||||
{
|
||||
m_copyItemIds.remove(itemId);
|
||||
|
||||
for (TrackPtr &t : m_childrenTracks)
|
||||
{
|
||||
t->removeCopyItem(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const QVector<int> Track::copyItemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
|
||||
{
|
||||
auto itemPtrRefs = _copyItems();
|
||||
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
{
|
||||
auto itemPtrRefs2 = trackPtr->_copyItems();
|
||||
itemPtrRefs.insert(itemPtrRefs.end(), itemPtrRefs2.begin(), itemPtrRefs2.end());
|
||||
}
|
||||
}
|
||||
|
||||
itemPtrRefs.erase(std::remove_if(itemPtrRefs.begin(), itemPtrRefs.end(),
|
||||
[this, start, end, strict, complete](const ItemPtr &itemPtr)
|
||||
{
|
||||
return !isItemBetween(*itemPtr, start, end, strict, complete);
|
||||
}), itemPtrRefs.end());
|
||||
|
||||
if (sorted)
|
||||
{
|
||||
sort(itemPtrRefs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return transformItemPtrsToIds(itemPtrRefs);
|
||||
}
|
||||
|
||||
|
||||
const QVector<int> Track::itemIdsBetween(qint64 start, qint64 end, bool strict, bool complete, bool recursive, bool sorted, qint64 minimumDuration) const
|
||||
{
|
||||
if (sorted)
|
||||
{
|
||||
auto itemPtrRefs = _items(recursive);
|
||||
itemPtrRefs.erase(std::remove_if(itemPtrRefs.begin(), itemPtrRefs.end(),
|
||||
[this, start, end, strict, complete, minimumDuration](const ItemPtr &itemPtr)
|
||||
{
|
||||
return !isItemBetween(*itemPtr, start, end, strict, complete, minimumDuration);
|
||||
}), itemPtrRefs.end());
|
||||
|
||||
sort(itemPtrRefs);
|
||||
|
||||
return transformItemPtrsToIds(itemPtrRefs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QVector<int> itemIds;
|
||||
itemIds.reserve(m_items.size());
|
||||
|
||||
for (const ItemPtr &itemPtr : qAsConst(m_items))
|
||||
{
|
||||
const Item &item = *itemPtr;
|
||||
if (isItemBetween(item, start, end, strict, complete, minimumDuration))
|
||||
itemIds += item.id();
|
||||
}
|
||||
|
||||
if (!recursive)
|
||||
{
|
||||
itemIds.squeeze();
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
itemIds += trackPtr->itemIdsBetween(start, end, strict, complete, true, minimumDuration);
|
||||
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
|
||||
bool Track::isItemsBetween(qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
|
||||
{
|
||||
for (const ItemPtr &itemPtr : qAsConst(m_items))
|
||||
{
|
||||
const Item &i = *itemPtr;
|
||||
if (!ignoreIds.contains(i.id()))
|
||||
if (isItemBetween(i, start, end, strict, complete, minimumDuration))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
for (const int itemId : qAsConst(m_copyItemIds))
|
||||
{
|
||||
const Item &i = item(itemId);
|
||||
if (!ignoreIds.contains(i.id()))
|
||||
if (isItemBetween(i, start, end, strict, complete, minimumDuration))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!recursive)
|
||||
return false;
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
if (trackPtr->isItemsBetween(start, end, ignoreIds, strict, complete, true, minimumDuration))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool Track::isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict, bool complete, bool recursive, qint64 minimumDuration) const
|
||||
{
|
||||
return isItemsBetween(start, end, QVector<int>({ ignoreId }), strict, complete, recursive, minimumDuration);
|
||||
}
|
||||
|
||||
|
||||
ItemPtrRefs Track::_items(bool recursive) const
|
||||
{
|
||||
|
||||
ItemPtrRefs x(m_items.cbegin(), m_items.cend());
|
||||
|
||||
if (!m_copyItemIds.isEmpty())
|
||||
x.reserve(x.size() + m_copyItemIds.count());
|
||||
|
||||
|
||||
for (const int itemId : qAsConst(m_copyItemIds))
|
||||
{
|
||||
try
|
||||
{
|
||||
x.push_back(std::cref(_item(itemId)));
|
||||
}
|
||||
catch (const QString &msg)
|
||||
{
|
||||
fprintf(stderr, "cannot add item ptr ref from copy item with copy item id %d: %s\n", itemId, msg.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!recursive)
|
||||
return x;
|
||||
|
||||
|
||||
for (const TrackPtr &t : qAsConst(m_childrenTracks))
|
||||
{
|
||||
const ItemPtrRefs x2(t->_items(true));
|
||||
x.insert(x.end(), x2.begin(), x2.end());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
ItemPtrRefs Track::_copyItems() const
|
||||
{
|
||||
|
||||
ItemPtrRefs x;
|
||||
|
||||
for (int id : qAsConst(m_copyItemIds))
|
||||
{
|
||||
try
|
||||
{
|
||||
x.push_back(_item(id));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
fprintf(stderr, "Track::_copyItems: cannot find item for item id %d\n", id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
ItemPtrNCVRefs Track::_itemsNCV(bool recursive)
|
||||
{
|
||||
|
||||
ItemPtrNCVRefs x(m_items.begin(), m_items.end());
|
||||
|
||||
if (!recursive)
|
||||
return x;
|
||||
|
||||
|
||||
for (TrackPtr &t : m_childrenTracks)
|
||||
{
|
||||
ItemPtrNCVRefs x2(t->_itemsNCV(true));
|
||||
x.insert(x.end(), x2.begin(), x2.end());
|
||||
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const QVector<int> Track::_itemIds(bool recursive) const
|
||||
{
|
||||
QVector<int> itemIds(transformItemPtrsToIds(m_items));
|
||||
|
||||
if (!recursive)
|
||||
return itemIds;
|
||||
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
itemIds += trackPtr->_itemIds(true);
|
||||
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
|
||||
ItemPtrRef Track::_item(int id) const noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_items.cbegin(), m_items.cend(),
|
||||
[id](const ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.cend())
|
||||
return *it;
|
||||
|
||||
|
||||
for (const TrackPtr &track : qAsConst(m_childrenTracks))
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->_item(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("_item: item with id %1 not found!").arg(id);
|
||||
}
|
||||
|
||||
|
||||
void Track::printTrackIds(bool withItems, int level) const
|
||||
{
|
||||
fprintf(stderr, "%*c Track %16.16s ", level * 2, '>', text().toStdString().c_str());
|
||||
|
||||
if (withItems)
|
||||
{
|
||||
fprintf(stderr, " ( ");
|
||||
for (const ItemPtr &itemPtr : qAsConst(m_items))
|
||||
{
|
||||
fprintf(stderr, "%d ", itemPtr->id());
|
||||
}
|
||||
fprintf(stderr, ")");
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
trackPtr->printTrackIds(level + 1);
|
||||
}
|
||||
|
||||
|
||||
QVector<int> Track::itemIds(bool recursive, bool sorted) const
|
||||
{
|
||||
if (sorted)
|
||||
{
|
||||
if (!recursive)
|
||||
return transformItemPtrsToIds(m_items);
|
||||
|
||||
|
||||
ItemPtrRefs is(_items(true));
|
||||
|
||||
sort(is);
|
||||
|
||||
return transformItemPtrsToIds(is);
|
||||
}
|
||||
|
||||
|
||||
|
||||
QVector<int> itemIds;
|
||||
itemIds.reserve(m_items.size());
|
||||
|
||||
itemIds += transformItemPtrsToIds(m_items);
|
||||
|
||||
if (!recursive)
|
||||
return itemIds;
|
||||
|
||||
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
itemIds += trackPtr->_itemIds(true);
|
||||
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
|
||||
void Track::addItemPtr(ItemPtr &&itemPtr)
|
||||
{
|
||||
m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), itemPtr->startTime(),
|
||||
[](qint64 startTime, const ItemPtr &other)
|
||||
{
|
||||
return (other->startTime() > startTime);
|
||||
}), std::move(itemPtr));
|
||||
}
|
||||
|
||||
|
||||
void Track::removeItem(int itemId) noexcept(false)
|
||||
{
|
||||
auto it = std::find_if(m_items.begin(), m_items.end(),
|
||||
[itemId](const ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr->id() == itemId);
|
||||
});
|
||||
|
||||
if (it == m_items.end())
|
||||
throw QStringLiteral("removeItem: item %1 not found").arg(itemId);
|
||||
|
||||
m_items.erase(it);
|
||||
}
|
||||
|
||||
|
||||
void Track::removeTrack(int trackId) noexcept(false)
|
||||
{
|
||||
auto it = std::find_if(m_childrenTracks.begin(), m_childrenTracks.end(),
|
||||
[trackId](const TrackPtr &trackPtr)
|
||||
{
|
||||
return (trackPtr->id() == trackId);
|
||||
});
|
||||
|
||||
if (it == m_childrenTracks.end())
|
||||
throw QStringLiteral("removeTrack: track %1 not found on track %2").arg(trackId, id());
|
||||
|
||||
m_childrenTracks.erase(it);
|
||||
}
|
||||
|
||||
|
||||
ItemPtr Track::takeItem(int itemId) noexcept(false)
|
||||
{
|
||||
auto it = std::find_if(m_items.begin(), m_items.end(),
|
||||
[itemId](const ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr->id() == itemId);
|
||||
});
|
||||
|
||||
if (it == m_items.end())
|
||||
throw QStringLiteral("Track::takeItem: item with id %1 not found").arg(itemId);
|
||||
|
||||
ItemPtr ptr = std::move(*it);
|
||||
|
||||
m_items.erase(it);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Track &Track::track(int id) noexcept(false)
|
||||
{
|
||||
|
||||
auto it = std::find_if(m_childrenTracks.begin(), m_childrenTracks.end(),
|
||||
[id](TrackPtr &trackPtr)
|
||||
{
|
||||
return (trackPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_childrenTracks.end())
|
||||
{
|
||||
Track &t = **it;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
for (TrackPtr &track : m_childrenTracks)
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->track(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("Track: track with id %1 not found").arg(id);
|
||||
}
|
||||
|
||||
|
||||
ItemPtrNCVRef Track::_itemNCV(int id) noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_items.begin(), m_items.end(),
|
||||
[id](ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.end())
|
||||
return *it;
|
||||
|
||||
|
||||
for (TrackPtr &track : m_childrenTracks)
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->_itemNCV(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
Item &Track::item(int id) noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_items.begin(), m_items.end(),
|
||||
[id](ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.end())
|
||||
return **it;
|
||||
|
||||
|
||||
for (TrackPtr &track : m_childrenTracks)
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->item(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
const Item &Track::item(int id) const noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_items.cbegin(), m_items.cend(),
|
||||
[id](const ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.cend())
|
||||
return **it;
|
||||
|
||||
|
||||
for (const TrackPtr &track : qAsConst(m_childrenTracks))
|
||||
{
|
||||
try
|
||||
{
|
||||
return track->item(id);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Item &Track::previous(int id) noexcept(false)
|
||||
{
|
||||
|
||||
auto it = std::find_if(m_items.begin(), m_items.end(), [id](ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.end() && (*it) != m_items.front())
|
||||
return **(--it);
|
||||
|
||||
throw QStringLiteral("Track::previous: previous item for id %1 not found").arg(id);
|
||||
}
|
||||
|
||||
|
||||
Item &Track::next(int id) noexcept(false)
|
||||
{
|
||||
|
||||
auto it = std::find_if(m_items.begin(), m_items.end(), [id](ItemPtr &itemPtr)
|
||||
{
|
||||
return (itemPtr.get()->id() == id);
|
||||
});
|
||||
|
||||
if (it != m_items.end() && (*it) != m_items.back())
|
||||
return **(++it);
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int Track::rowOfTrack(int id) const noexcept(false)
|
||||
{
|
||||
int row = 0;
|
||||
for (const TrackPtr &trackPtr : qAsConst(m_childrenTracks))
|
||||
{
|
||||
if (trackPtr->id() == id)
|
||||
return row;
|
||||
row++;
|
||||
}
|
||||
|
||||
throw("not found");
|
||||
}
|
||||
|
||||
|
||||
Track &Track::trackAt(int index) noexcept(false)
|
||||
{
|
||||
return *m_childrenTracks.at(index);
|
||||
}
|
||||
|
||||
|
||||
const Track &Track::trackAt(int index) const noexcept(false)
|
||||
{
|
||||
return *m_childrenTracks.at(index);
|
||||
}
|
||||
|
||||
|
||||
const Item &Track::copyItemAt(qint64 time) const noexcept(false)
|
||||
{
|
||||
auto const itemPtrRefs = _copyItems();
|
||||
const ItemPtrRefs::const_iterator it =
|
||||
std::find_if(itemPtrRefs.cbegin(), itemPtrRefs.cend(), [time](const ItemPtr &itemPtr)
|
||||
{
|
||||
return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration();
|
||||
});
|
||||
|
||||
if (it != itemPtrRefs.cend())
|
||||
return *it->get();
|
||||
|
||||
throw QStringLiteral("Track: can't find copy item at %1").arg(time);
|
||||
}
|
||||
|
||||
|
||||
Item &Track::itemAt(qint64 time) noexcept(false)
|
||||
{
|
||||
|
||||
auto it = std::find_if(m_items.cbegin(), m_items.cend(), [time](const ItemPtr &itemPtr)
|
||||
{
|
||||
return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration();
|
||||
});
|
||||
|
||||
if (it != m_items.cend())
|
||||
return **it;
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
|
||||
const Item &Track::itemAt(qint64 time) const noexcept(false)
|
||||
{
|
||||
|
||||
auto const it = std::find_if(m_items.cbegin(), m_items.cend(), [time](const ItemPtr &itemPtr)
|
||||
{
|
||||
return itemPtr.get()->startTime() <= time && time <= itemPtr.get()->startTime() + itemPtr.get()->duration();
|
||||
});
|
||||
|
||||
if (it != m_items.cend())
|
||||
return **it;
|
||||
|
||||
throw QStringLiteral("lookup failed!");;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Executable
+263
@@ -0,0 +1,263 @@
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "item.h"
|
||||
|
||||
#include <LtsTimeline/ltstimeline_export.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QVariant>
|
||||
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace LtsTimeline
|
||||
{
|
||||
class Track;
|
||||
|
||||
class TimelineModel;
|
||||
|
||||
typedef std::unique_ptr<Track> TrackPtr;
|
||||
typedef std::vector<TrackPtr> Tracks;
|
||||
typedef std::reference_wrapper<TrackPtr> TrackPtrRef;
|
||||
typedef std::vector<TrackPtrRef> TrackPtrRefs;
|
||||
|
||||
|
||||
class LTSTIMELINE_EXPORT Track
|
||||
{
|
||||
public:
|
||||
Track(const QString &text = {})
|
||||
: m_text(text)
|
||||
{}
|
||||
|
||||
Track(const QString &text, int specialTrackType)
|
||||
: m_text(text)
|
||||
, m_userData(specialTrackType)
|
||||
{}
|
||||
|
||||
Track(const Track &) = delete;
|
||||
Track &operator=(const Track &) = delete;
|
||||
Track(Track &&t) noexcept = default;
|
||||
virtual ~Track() = default;
|
||||
|
||||
[[deprecated("Use model's track(int) instead.")]] const Track &track(int id) const noexcept(false);
|
||||
[[deprecated("Use model's track(int) instead.")]] Track &track(int id) noexcept(false);
|
||||
[[deprecated("Use model's item(int) instead.")]] Item &item(int id) noexcept(false);
|
||||
[[deprecated("Use model's item(int) instead.")]] const Item &item(int id) const noexcept(false);
|
||||
Item &previous(int id) noexcept(false);
|
||||
Item &next(int id) noexcept(false);
|
||||
|
||||
const Item ©ItemAt(qint64 time) const noexcept(false);
|
||||
Item &itemAt(qint64 time) noexcept(false);
|
||||
const Item &itemAt(qint64 time) const noexcept(false);
|
||||
Track &trackAt(int index) noexcept(false);
|
||||
const Track &trackAt(int index) const noexcept(false);
|
||||
bool hasTrack(int id, bool recursive = false) const;
|
||||
bool hasTracks() const { return !m_childrenTracks.empty(); }
|
||||
int rowOfTrack(int id) const noexcept(false);
|
||||
const Track &parentOf(int id) const noexcept(false);
|
||||
|
||||
|
||||
const QSet<int> ©ItemIds() const { return m_copyItemIds; }
|
||||
void clearCopyItemIds() { m_copyItemIds.clear(); }
|
||||
void addCopyItemIds(const QSet<int> &ids);
|
||||
void addCopyItemId(int id);
|
||||
bool hasItems() const { return !m_items.empty(); }
|
||||
bool hasCopyItems() const { return !m_copyItemIds.empty(); }
|
||||
int itemCount() const { return m_items.size(); }
|
||||
Items &items() { return m_items; }
|
||||
const Items &items() const { return m_items; }
|
||||
int trackCount() const { return m_childrenTracks.size(); }
|
||||
Tracks &tracks() { return m_childrenTracks; }
|
||||
const Tracks &tracks() const { return m_childrenTracks; }
|
||||
QVector<int> itemIds(bool recursive = false, bool sorted = false) const;
|
||||
const QVector<int> itemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false, qint64 minimumDuration = 0) const;
|
||||
[[deprecated]] const QVector<int> copyItemIdsBetween(qint64 start, qint64 end, bool strict = false, bool complete = false, bool recursive = false, bool sorted = false, qint64 minimumDuration = 0) const;
|
||||
bool isItemsBetween(qint64 start, qint64 end, const QVector<int> &ignoreIds, bool strict = false, bool complete = false, bool recursive = false, qint64 minimumDuration = 0) const;
|
||||
bool isItemsBetween(qint64 start, qint64 end, int ignoreId, bool strict = false, bool complete = false, bool recursive = false, qint64 minimumDuration = 0) const;
|
||||
|
||||
|
||||
int id() const { return m_id; }
|
||||
virtual const QString text() const { return m_text; }
|
||||
int parent() const { return m_parentId; }
|
||||
int parentId() const { return m_parentId; }
|
||||
void printTrackIds(bool withItems = false, int level = 0) const;
|
||||
void removeCopyItem(int itemId) noexcept(false);
|
||||
void removeItem(int itemId) noexcept(false);
|
||||
ItemPtr takeItem(int itemId) noexcept(false);
|
||||
QVariant userData() const { return m_userData; }
|
||||
void setUserData(const QVariant &data) { m_userData = data; }
|
||||
|
||||
|
||||
inline static void sort(Items &items)
|
||||
{
|
||||
std::sort(items.begin(), items.end(), [](const ItemPtr &itemA, const ItemPtr &itemB)
|
||||
{
|
||||
return itemA->startTime() < itemB->startTime();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
inline static void sort(ItemPtrNCVRefs &items)
|
||||
{
|
||||
std::sort(items.begin(), items.end(), [](const ItemPtrNCVRef &itemA, const ItemPtrNCVRef &itemB)
|
||||
{
|
||||
return itemA.get()->startTime() < itemB.get()->startTime();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
inline static void sort(ItemPtrRefs &items)
|
||||
{
|
||||
std::sort(items.begin(), items.end(), [](const ItemPtrRef &itemA, const ItemPtrRef &itemB)
|
||||
{
|
||||
return itemA.get()->startTime() < itemB.get()->startTime();
|
||||
});
|
||||
}
|
||||
|
||||
bool hasInsertButton() const { return m_hasInsertButton; }
|
||||
void setInsertButton(bool hasButton) { m_hasInsertButton = hasButton; }
|
||||
bool hasRemoveButton() const { return m_hasRemoveButton; }
|
||||
void setRemoveButton(bool hasButton) { m_hasRemoveButton = hasButton; }
|
||||
|
||||
|
||||
protected:
|
||||
QString m_text;
|
||||
QVariant m_userData;
|
||||
|
||||
|
||||
private:
|
||||
void removeTrack(int trackId) noexcept(false);
|
||||
void addItemPtr(ItemPtr &&itemPtr);
|
||||
|
||||
static bool isItemBetween(const Item &item, qint64 start, qint64 end, bool strict, bool complete, qint64 minimumDuration = 0);
|
||||
ItemPtrRefs _copyItems() const;
|
||||
ItemPtrRefs _items(bool recursive) const;
|
||||
ItemPtrNCVRefs _itemsNCV(bool recursive);
|
||||
ItemPtrNCVRef _itemNCV(int id) noexcept(false);
|
||||
ItemPtrRef _item(int id) const noexcept(false);
|
||||
const QVector<int> _itemIds(bool recursive) const;
|
||||
|
||||
|
||||
public:
|
||||
void clearItems() { m_items.clear(); }
|
||||
private:
|
||||
|
||||
friend class TimelineModel;
|
||||
|
||||
|
||||
void sort()
|
||||
{
|
||||
sort(m_items);
|
||||
}
|
||||
|
||||
|
||||
template <typename TrackClass>
|
||||
Track *addTrack(TrackClass &&trackClass)
|
||||
{
|
||||
TrackClass _trackClass = std::move(trackClass);
|
||||
m_childrenTracks.push_back(std::move(std::move(std::make_unique<TrackClass>(std::move(_trackClass)))));
|
||||
return m_childrenTracks.back().get();
|
||||
}
|
||||
|
||||
|
||||
template <typename TrackClass>
|
||||
[[deprecated("Use model's track(...) and call addTrack(TrackClass) directly on that track.")]]
|
||||
Track *addTrack(int parentTrackId, TrackClass &&trackClass) noexcept(false)
|
||||
{
|
||||
if (m_id == parentTrackId)
|
||||
return addTrack<TrackClass>(std::move(trackClass));
|
||||
|
||||
return track(parentTrackId).addTrack(std::move(trackClass));
|
||||
}
|
||||
|
||||
|
||||
template <typename ItemClass>
|
||||
Item *addItem(ItemClass &&itemClass)
|
||||
{
|
||||
ItemClass _itemClass(std::move(itemClass));
|
||||
|
||||
auto it = m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), _itemClass.startTime(),
|
||||
[](qint64 startTime, const ItemPtr &other)
|
||||
{
|
||||
return (other->startTime() > startTime);
|
||||
}), std::move(std::make_unique<ItemClass>(std::move(_itemClass))));
|
||||
|
||||
return it->get();
|
||||
}
|
||||
|
||||
|
||||
Item *addItem(ItemPtr &&itemPtr)
|
||||
{
|
||||
auto it = m_items.insert(std::upper_bound(m_items.begin(), m_items.end(), itemPtr->startTime(),
|
||||
[](qint64 startTime, const ItemPtr &other)
|
||||
{
|
||||
return (other->startTime() > startTime);
|
||||
}), std::move(itemPtr));
|
||||
|
||||
return it->get();
|
||||
}
|
||||
|
||||
|
||||
template <typename ItemContainer>
|
||||
[[deprecated]]
|
||||
QVector<int> transformItemPtrsToIds(const ItemContainer &itemContainer) const
|
||||
{
|
||||
QVector<int> ids;
|
||||
std::transform(itemContainer.cbegin(), itemContainer.cend(), std::back_inserter(ids),
|
||||
[](const ItemPtr &itemPtr) -> int
|
||||
{
|
||||
return itemPtr->id();
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int m_id { -1 };
|
||||
Tracks m_childrenTracks;
|
||||
Items m_items;
|
||||
int m_parentId { -1 };
|
||||
bool m_hasInsertButton { false };
|
||||
bool m_hasRemoveButton { false };
|
||||
|
||||
|
||||
QSet<int> m_copyItemIds;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user