283 lines
6.6 KiB
C++
283 lines
6.6 KiB
C++
#include "timelineview.h"
|
|
#include <QQmlContext>
|
|
#include <QQmlInfo>
|
|
|
|
struct TimelineView::Item
|
|
{
|
|
Item(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);
|
|
}
|
|
|
|
~Item()
|
|
{
|
|
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_toDate()
|
|
, m_model(nullptr)
|
|
, m_fromDate()
|
|
, m_items()
|
|
, m_reusableItems()
|
|
, 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();
|
|
}
|
|
|
|
TimelineModel *TimelineView::model() const
|
|
{
|
|
return m_model;
|
|
}
|
|
|
|
void TimelineView::setModel(TimelineModel *model)
|
|
{
|
|
if (model == m_model) {
|
|
return;
|
|
}
|
|
m_model = model;
|
|
emit modelChanged(m_model);
|
|
clear();
|
|
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();
|
|
}
|
|
|
|
TimelineView::Item *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 TimelineView::Item(day, len, *item, *this);
|
|
return viewItem;
|
|
}
|
|
|
|
void TimelineView::releaseItem(TimelineView::Item *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 || !m_model) {
|
|
return;
|
|
}
|
|
if (m_viewportX > m_prevViewportX) {
|
|
// Delete from the left
|
|
while (!m_items.isEmpty()) {
|
|
TimelineView::Item *item = m_items.first();
|
|
if (item->right() >= m_viewportX) {
|
|
break;
|
|
}
|
|
releaseItem(item);
|
|
}
|
|
} else if (m_viewportX < m_prevViewportX) {
|
|
// Insert from the left
|
|
auto [begin, end] = m_model->indexesOf(m_fromDate.addDays(qFloor(m_viewportX / m_dayWidth)),
|
|
m_fromDate.addDays(
|
|
qCeil(m_prevViewportX / m_dayWidth)));
|
|
for (qsizetype i = end - 1; i >= begin; --i) {
|
|
const TimelineModel::Item *item = m_model->at(i);
|
|
qint64 day = m_fromDate.daysTo(item->arrival);
|
|
if (!m_items.isEmpty() && m_items.first()->day <= day) {
|
|
continue;
|
|
}
|
|
TimelineView::Item *viewItem = createItem(day, item->nights);
|
|
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()) {
|
|
TimelineView::Item *item = m_items.last();
|
|
if (item->left() < currentRight) {
|
|
break;
|
|
}
|
|
releaseItem(item);
|
|
}
|
|
} else if (currentRight > prevRight) {
|
|
// Insert from the right
|
|
auto [begin, end] = m_model->indexesOf(m_fromDate.addDays(qFloor(prevRight / m_dayWidth)),
|
|
m_fromDate.addDays(qCeil(currentRight / m_dayWidth)));
|
|
for (qsizetype i = begin; i < end; ++i) {
|
|
const TimelineModel::Item *item = m_model->at(i);
|
|
qint64 day = m_fromDate.daysTo(item->arrival);
|
|
if (!m_items.isEmpty() && m_items.last()->day >= day) {
|
|
continue;
|
|
}
|
|
TimelineView::Item *viewItem = createItem(day, item->nights);
|
|
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"
|