camper/src/timelineview.cpp

283 lines
6.6 KiB
C++
Raw Normal View History

Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
#include "timelineview.h"
#include <QQmlContext>
#include <QQmlInfo>
struct TimelineView::Item
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
{
Item(qint64 day, qint64 len, QQuickItem &qitem, TimelineView &view)
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
: 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()
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
{
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)
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
, 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();
}
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
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)
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
{
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);
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
return viewItem;
}
void TimelineView::releaseItem(TimelineView::Item *item)
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
{
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) {
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
return;
}
if (m_viewportX > m_prevViewportX) {
// Delete from the left
while (!m_items.isEmpty()) {
TimelineView::Item *item = m_items.first();
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
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);
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
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();
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
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);
Begin TimelineView control This is supposed to be like a kind of horizontal ListView, however the elements need to be placed according to its starting date, and must be as long as the number of days of the stay. As far as i know, i can not do that with a Qt-provided ListView because it has a strict one-to-one mapping between QModelIndex’s row and visual element’s row (i.e., the first item is always the first row, the second item the second row, etc.), while i to have “holes” in the rows for item that are not continous in time. Unfortunately, Qt Quick does not provide a C++ API, meaning that i can not derive from QQuickListView or, rather, QQuickItemView, without using the private API. And that API is private for a reason; i do not want to see myself redoing the control because they have changed who knows what. Thus, had to base this control on QQuickItem. I am also pretty sure i will not be able to use QAbstractItemModel for this control, as, again, that model assumes that everything is continuous: lists have rows next to each other, tables columns next to each other, and trees are “just” nested tables. But i am not certain yet. Meanwhile, what i really need to do is to show a delegate for each “filled in” day, and that’s is what this controls does for now. Since i can not inherit from QQuickFlickable—not that it would be a great idea here, since i need a list of these views anyway—, i have to know the viewport’s position and width in order to have only the minimum number of items visible. I do like QQmlDelgateModel (that i can not reuse without private API), and reuse the delegates when possible.
2025-01-07 11:53:27 +00:00
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"