camper/src/timelineview.cpp

261 lines
6.0 KiB
C++

#include "timelineview.h"
#include <QQmlContext>
#include <QQmlInfo>
struct TimelineViewItem
{
TimelineViewItem(qint64 day, qint64 len, QQuickItem &qitem, TimelineView &view)
: item(&qitem)
, day(day)
{
item->setParentItem(&view);
item->setPosition(QPointF(day * view.dayWidth(), 0.0));
item->setSize(QSizeF(len * view.dayWidth(), view.height()));
item->setVisible(true);
}
~TimelineViewItem()
{
item->setVisible(false);
item->setParent(nullptr);
}
qreal left() const { return item->x(); }
qreal right() const { return item->x() + item->width(); }
QQuickItem *item;
const qint64 day;
};
TimelineView::TimelineView(QQuickItem *parent)
: QQuickItem(parent)
, m_dayWidth(24.0)
, m_delegate(nullptr)
, m_fromDate()
, m_items()
, m_reusableItems()
, m_toDate()
, m_viewportX(0)
, m_prevViewportX(0)
, m_viewportWidth(0)
, m_prevViewportWidth(0)
{}
TimelineView::~TimelineView()
{
clear();
drainItems();
}
qreal TimelineView::dayWidth() const
{
return m_dayWidth;
}
void TimelineView::setDayWidth(qreal width)
{
if (width == m_dayWidth) {
return;
}
m_dayWidth = width;
emit dayWidthChanged(m_dayWidth);
updateImplicitWidth();
clear();
populate();
}
QQmlComponent *TimelineView::delegate() const
{
return m_delegate;
}
void TimelineView::setDelegate(QQmlComponent *delegate)
{
if (delegate == m_delegate) {
return;
}
m_delegate = delegate;
emit delegateChanged(m_delegate);
clear();
drainItems();
populate();
update();
}
QDate TimelineView::fromDate() const
{
return m_fromDate;
}
void TimelineView::setFromDate(QDate date)
{
if (date == m_fromDate) {
return;
}
m_fromDate = date;
emit fromDateChanged(m_fromDate);
updateImplicitWidth();
populate();
}
QDate TimelineView::toDate() const
{
return m_toDate;
}
void TimelineView::setToDate(QDate date)
{
if (date == m_toDate) {
return;
}
m_toDate = date;
emit toDateChanged(m_toDate);
updateImplicitWidth();
populate();
}
qreal TimelineView::viewportX() const
{
return m_viewportX;
}
void TimelineView::setViewportX(qreal x)
{
if (x == m_viewportX) {
return;
}
m_viewportX = x;
emit viewportXChanged(m_viewportX);
populate();
}
qreal TimelineView::viewportWidth() const
{
return m_viewportWidth;
}
void TimelineView::setViewportWidth(qreal width)
{
if (width == m_viewportWidth) {
return;
}
m_viewportWidth = width;
emit viewportWidthChanged(m_viewportWidth);
populate();
}
void TimelineView::componentComplete()
{
QQuickItem::componentComplete();
populate();
}
TimelineViewItem *TimelineView::createItem(qint64 day, qint64 len)
{
QQuickItem *item = m_reusableItems.isEmpty() ? qobject_cast<QQuickItem *>(m_delegate->create(
m_delegate->creationContext()))
: m_reusableItems.takeLast();
if (!item) {
qmlWarning(m_delegate) << TimelineView::tr("Delegate must be of Item type");
return nullptr;
}
auto *viewItem = new TimelineViewItem(day, len, *item, *this);
return viewItem;
}
void TimelineView::releaseItem(TimelineViewItem *item)
{
if (!item) {
return;
}
qsizetype index = m_items.indexOf(item);
if (index < 0) {
return;
}
QQuickItem *qitem = item->item;
if (!m_reusableItems.contains(qitem)) {
m_reusableItems.append(qitem);
}
m_items.removeAt(index);
delete item;
}
void TimelineView::drainItems()
{
qDeleteAll(m_reusableItems);
m_reusableItems.clear();
}
void TimelineView::clear()
{
while (!m_items.isEmpty()) {
releaseItem(m_items.last());
}
}
void TimelineView::populate()
{
if (!isComponentComplete() || !m_delegate) {
return;
}
if (m_viewportX > m_prevViewportX) {
// Delete from the left
while (!m_items.isEmpty()) {
TimelineViewItem *item = m_items.first();
if (item->right() >= m_viewportX) {
break;
}
releaseItem(item);
}
} else if (m_viewportX < m_prevViewportX) {
// Insert from the left
for (qint64 day = m_items.isEmpty() ? qCeil(m_prevViewportX / m_dayWidth) - 1
: m_items.first()->day - 1,
len = 1,
lastDay = qMax(-1, qFloor((m_viewportX - (len * m_dayWidth)) / m_dayWidth));
day > lastDay;
day -= len) {
TimelineViewItem *viewItem = createItem(day, len);
if (!viewItem) {
break;
}
m_items.prepend(viewItem);
}
}
int currentRight = m_viewportX + m_viewportWidth;
int prevRight = m_prevViewportX + m_prevViewportWidth;
if (currentRight < prevRight) {
// Delete from the right
while (!m_items.isEmpty()) {
TimelineViewItem *item = m_items.last();
if (item->left() < currentRight) {
break;
}
releaseItem(item);
}
} else if (currentRight > prevRight) {
// Insert from the right
for (qint64 day = m_items.isEmpty() ? qCeil(prevRight / m_dayWidth) - 1
: m_items.last()->day + 1,
len = 1,
lastDay = qFloor((currentRight + (len * m_dayWidth)) / m_dayWidth);
day < lastDay;
day += len) {
TimelineViewItem *viewItem = createItem(day, len);
if (!viewItem) {
break;
}
m_items.append(viewItem);
}
}
m_prevViewportX = m_viewportX;
m_prevViewportWidth = m_viewportWidth;
}
void TimelineView::updateImplicitWidth()
{
setImplicitWidth(m_dayWidth * m_fromDate.daysTo(m_toDate));
}
#include "moc_timelineview.cpp"