Continues migration with DDS, Common

This commit is contained in:
torsten committed 2026-07-17 12:41:42 +02:00
1 parent 9b15cee841
commit fa48db111e
668 files changed
+111841 -2

No files matched your search

+74
View File
@@ -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
)
+45
View File
@@ -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();
}
}
+87
View File
@@ -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;
}
+185
View File
@@ -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";
}
}
}
+47
View File
@@ -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
View File
@@ -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:
+289
View File
@@ -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
View File
@@ -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
View File
@@ -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};
};
}
+761
View File
@@ -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;
}
}
+247
View File
@@ -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;
};
}
+343
View File
@@ -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;
}
}
}
+83
View File
@@ -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;
}
+672
View File
@@ -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();
}
}
+175
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 &currentItem = 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 &currentItem = 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
View File
@@ -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
View File
@@ -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;
}
}
+48
View File
@@ -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
View File
@@ -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
View File
@@ -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;
};
}
+402
View File
@@ -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);
}
}
+114
View File
@@ -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;
};
}
+434
View File
@@ -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>
+853
View File
@@ -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!");;
}
}
+263
View File
@@ -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 &copyItemAt(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> &copyItemIds() 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;
};
}