Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+292
View File
@@ -0,0 +1,292 @@
/**
* @RDE_HEADER@
* MVC model
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "AbstractTimelineModel.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace LtsTimelineClassic
{
AbstractTimelineModel::AbstractTimelineModel(QObject *parent)
: QAbstractItemModel(parent)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.AbstractTimelineModel"))
, m_highestPositionedItem(0)
, m_lowestPositionedItem(0)
, m_getEventListsRecursive(true) // get events from actuators and all their children
, m_rootItem(new Item(QLatin1String("#mwille/r00t/")))
, m_markersItem(new Item(tr("Markers"), QStringLiteral("Markers")))
{
/*/
*
* Filter Text syntax see zwmstl_exconFilter.cpp
*
/*/
m_rootItem->addItem(m_markersItem); // reparented
}
AbstractTimelineModel::~AbstractTimelineModel()
{
delete m_rootItem;
}
void AbstractTimelineModel::retranslate()
{
m_markersItem->setName(tr("Markers"));
}
void AbstractTimelineModel::invalidateTimeline()
{
const LtsTimelineClassic::ItemList il = m_rootItem->getFullItemList(true);
for (auto i = il.constBegin(); i != il.constEnd(); ++i)
{
LtsTimelineClassic::Item *item = *i;
if (item != nullptr)
item->setEventListChanged();
}
}
//void AbstractTimelineModel::invalidateItemForEvent(int uniqueId)
//{
// const LtsTimelineClassic::ItemList il = m_rootItem->getFullItemList(true);
// for (auto i = il.constBegin(); i != il.constEnd(); ++i)
// {
// LtsTimelineClassic::Item *item = *i;
// if (item != nullptr && item->getIdentifier() != QStringLiteral("Markers"))
// {
// const LtsTimelineClassic::EventList el = item->getFullEventList(true, true);
// for (LtsTimelineClassic::Event *event : el)
// {
// if (event->uniqueId() == uniqueId)
// {
// item->setEventListChanged();
// return;
// }
// }
// }
// }
// ERROR_LOG(m_logger) << "unable to invalidate an item - event not found!";
//}
Event *AbstractTimelineModel::getEvent(int uniqueId) // NOTE: ignores markers
{
const LtsTimelineClassic::ItemList il = m_rootItem->getFullItemList(true);
for (auto i = il.constBegin(); i != il.constEnd(); ++i)
{
LtsTimelineClassic::Item *item = *i;
if (item != nullptr && item->getIdentifier() != QStringLiteral("Markers"))
{
const LtsTimelineClassic::EventList el = item->getFullEventList(true, true);
for (LtsTimelineClassic::Event *event : el)
{
if (event->uniqueId() == uniqueId)
return event;
}
}
}
return nullptr;
}
void AbstractTimelineModel::addEvent(Event *event) // FIXME: to const ref ! give ownership...
{
QStringList actuatorPath = event->getActuator();
// actuatorPath => path/to/event ==> event
Item *currentItem = m_rootItem; // set first item to the root item
QModelIndex currentIndex = QModelIndex();
for (const QString &curPath : qAsConst(actuatorPath)) // look for paths in actuatorPath
{
Item *oldItem = currentItem; // to see wether an item has been found or has to be created!!!
const ItemList *itemList = currentItem->getChildren();
if (itemList != nullptr)
{
for (int row = 0; row < itemList->count(); row++) // look for items with the name equal to the path name
{
Item *childItem = itemList->at(row);
if (childItem->getName() == curPath)
{
currentIndex = index(row, 0, currentIndex);
currentItem = childItem;
break;
}
}
}
if (oldItem == currentItem) // path not found, it (item) has to be created - DO NOT FILL WITH EVENT YET, it is possible that there are paths left!
{
beginInsertRows(currentIndex, currentItem->childCount(), currentItem->childCount());
Item *newItem = new Item(curPath);
currentItem->addItem(newItem);
endInsertRows();
currentIndex = index(currentItem->childCount() - 1, 0, currentIndex); // TODO: not needed?
currentItem = newItem; // we are now on the new item to work with it
}
}
currentItem->addEvent(event);
}
Item *AbstractTimelineModel::getRootItem()
{
return m_rootItem;
}
Item *AbstractTimelineModel::itemFromIndex(const QModelIndex &idx) const
{
if (!idx.isValid())
return m_rootItem;
return static_cast<Item *>(idx.internalPointer());
}
QVariant AbstractTimelineModel::data(const QModelIndex &index, int role) const
{
if (role != Qt::DisplayRole || !index.isValid())
return QVariant();
Item *item = itemFromIndex(index);
if (!item)
return QVariant();
// if (m_listMode)
// getFullEventList
// ....
// else
if (index.column() == 1) // event row = delegate row
{
PassiveEventContainer *pevcon = item->getPevcon(); // set new event list for pevcon in case that new events were appended
return QVariant::fromValue(pevcon);
}
// actuators column (0)
QString name(item->getName());
if (name.isEmpty())
name = tr("[unknown]");
return QVariant(name);
}
Qt::ItemFlags AbstractTimelineModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return {};
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
QVariant AbstractTimelineModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
// if (m_listMode)
switch (section)
{
case 0: return QString(tr("Actuator") + QStringLiteral("\r\n"));
case 1: return QString(tr("Events") + QStringLiteral("\r\n"));
}
// else switch (section)
// {
// case 1: return QString(tr("Event Type") + QStringLiteral("\r\n"));
// case 2: return QString(tr("Start time") + QStringLiteral("\r\n"));
// case 3: return QString(tr("End time") + QStringLiteral("\r\n"));
// }
}
return QVariant();
}
QModelIndex AbstractTimelineModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
Item *item = itemFromIndex(parent);
Item *child = item ? item->child(row) : nullptr;
if (child)
{
//qWarning() << "new Index: row = " << row << "column = " << column << "parent = " << &parent;
return createIndex(row, column, child);
}
return QModelIndex();
}
QModelIndex AbstractTimelineModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
Item *item = itemFromIndex(index);
Item *parent = item ? item->getParent() : nullptr;
if (parent == m_rootItem || parent == nullptr)
return QModelIndex();
int idx = parent->indexOfParent();
if (idx == -1)
return QModelIndex();
return createIndex(idx, 0, parent);
}
int AbstractTimelineModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
{
int r = m_rootItem->visibleChildCount();
//qWarning() << m_rootItem->getName() << "rowCount" << r;
return r;
}
Item *item = itemFromIndex(parent);
if (item)
{
int r = item->visibleChildCount();
//qWarning() << item->getName() << "rowCount" << r;
return r;
}
return 0;
}
int AbstractTimelineModel::columnCount(const QModelIndex &) const
{
return 2; // Actuator + Events
}
} // namespace LtsTimelineClassic
+158
View File
@@ -0,0 +1,158 @@
/**
* @RDE_HEADER@
* MVC model
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "Event.h"
#include "Item.h"
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
#include <QAbstractItemModel>
#include <NGLLogger/INGLLogger.h>
namespace LtsTimelineClassic
{
/**
* @brief MVC model
*/
class LTSTIMELINECLASSIC_EXPORT AbstractTimelineModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit AbstractTimelineModel(/*bool getEventListsRecursive = true,*/ QObject *parent = nullptr);
~AbstractTimelineModel();
void retranslate();
/**
* @brief returns the root item
* @return root item
*/
Item *getRootItem();
Event *getEvent(int uniqueId); // WARNING: unique ids are not unique between events and markers.... grrr
//void invalidateItemForEvent(int uniqueId);
void invalidateTimeline();
/**
* @brief refreshes the model data, which triggers all views to refresh also
*/
void refreshData();
qint64 highestPositionedItem() const { return m_highestPositionedItem; }
qint64 lowestPositionedItem() const { return m_lowestPositionedItem; }
protected:
NGL::INGLLogger &m_logger;
protected:
/**
* @brief adds an event to the model
* @param event event to add to item
*/
void addEvent(Event *event);
/**
* @brief returns coherant data of row & column for the view
* @param index row & column
* @param role what kind of data is needed by the view
* @return variable data
*/
QVariant data(const QModelIndex &index, int role) const;
/**
* @brief flags of the coherant data of row & column has (what is allowed, what is not)
* @param index row & column
* @return flags
*/
Qt::ItemFlags flags(const QModelIndex &index) const;
/**
* @brief returns the header text for the coherant column
* @param section column
* @param orientation use Qt::Horizontal
* @param role what kind of data is needed by the view
* @return variable data
*/
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
/**
* @brief
* @param index
* @return
*/
Item *itemFromIndex(const QModelIndex &idx) const;
/**
* @brief model internal method
* @param row
* @param column
* @param parent
* @return
*/
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
/**
* @brief model internal method
* @param index
* @return
*/
QModelIndex parent(const QModelIndex &index) const;
/**
* @brief model internal method
* @param parent
* @return
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const;
/**
* @brief model internal method
* @param parent
* @return
*/
int columnCount(const QModelIndex &parent = QModelIndex()) const;
virtual qint64 currentTime() const = 0;
//ItemList m_itemList; NO: m_rootItem has children [a item list huh] // to keep scope to all items - especially for the EventFactory which can now add to all events
qint64 m_highestPositionedItem;
qint64 m_lowestPositionedItem;
bool m_getEventListsRecursive;
Item *m_rootItem;
Item *m_markersItem; // NOTE: autodeleted by m_rootItem on its deletion
signals:
/**
* @brief signals when event is right-clicked (was triggered by ActiveEvent)
* @param position position of the mouse when right-clicked
* @param affectedEvent event that was right-clicked
*/
void signalContextMenu(const QPoint &position, Event *affectedEvent);
/**
* @brief signals that an event has been clicked (for use with standard-action) (was triggered by ActiveEvent)
* @param affectedEvent event that was clicked
*/
void signalClicked(Event *affectedEvent);
void signalToolTip(const QPoint &p, Event *e);
};
} // namespace LtsTimelineClassic
+82
View File
@@ -0,0 +1,82 @@
#
# CMakeLists.txt for Timeline Library
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, SLSE, 2017-02-10
#
SET(timeline_SRCS
EventDelegate.cpp
EventTools.cpp
Item.cpp
PassiveEventContainer.cpp
TimelineWidget.cpp
TimelineHeaderView.cpp
TimelineIndicator.cpp
TestTimelineModel.cpp
AbstractTimelineModel.cpp
TimelineView.cpp
Event.cpp
EventGraphic.cpp
EventDelegate.h
EventList.h
EventTools.h
Item.h
PassiveEventContainer.h
TimelineWidget.h
TimelineHeaderView.h
TimelineIndicator.h
AbstractTimelineModel.h
TimelineView.h
#timeline_export.h
Event.h
TestTimelineModel.h
EventGraphic.h
LineMap.h
)
SET(timeline_UICS
Ui/TimelineWidgetUi.ui
)
NGL_WRAP_UI(timeline_SRCS ${timeline_UICS})
NGL_ADD_LIBRARY2(WS LtsTimelineClassic SHARED ${timeline_SRCS} ${timeline_UICS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsTimelineClassic
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsTimelineClassic
PRIVATE
NGLLogger
ngltools # DTG
Qt5::Core
Qt5::Gui
Qt5::Widgets
LtsSessionDataDds
)
IF(NEVER)
NGL_ADD_EXECUTABLE(WS LtsTimelineClassicTest main.cpp)
NGL_TARGET_LINK_LIBRARIES(LtsTimelineClassicTest
nglapplication
NGLLogger
Qt5::Core
Qt5::Gui
Qt5::Widgets
LtsTimelineClassic
svcEemCommon
svcEemClient
PdmDataPluginInterface
)
ENDIF()
NGL_INSTALL_TARGET(LtsTimelineClassic)
+225
View File
@@ -0,0 +1,225 @@
/**
* @RDE_HEADER@
* base class for received events
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "Event.h"
#include <LtsSession/lts_session.h>
#include <QRect>
#include <QPainter>
#include <QPainterPath>
#include <time.h>
namespace LtsTimelineClassic
{
Event::Event(int uniqueId, int typeId, qint64 startTime,
qint64 endTime, int playerId, int partyId, int equipmentId, const QStringList &path,
const EventPaintFunc &paintFunc, const QColor &paintColor, const QJsonObject &detailData, int subEventId,
const LtsUtils::QPoint3D &pos, LtsSession::LtsSession *session)
: m_path(path)
, m_detailData(detailData)
, m_position(pos)
, m_paintFunc(paintFunc)
, m_session(session)
, m_eventColor(paintColor)
, m_startTime(startTime)
, m_endTime(endTime)
, m_uniqueId(uniqueId)
, m_typeId(typeId)
, m_subEventId(subEventId) // header says -1 (invalid / not an sub event)
, m_playerId(playerId)
, m_partyId(partyId)
, m_equipmentId(equipmentId)
, m_deleted(false)
, m_visible(true) // every event should be shown after creation
// absolute overkill: , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Event"))
{
checkDuration();
}
void Event::setVisible(bool visibility)
{
if (visibility)
show();
else
hide();
}
void Event::hide()
{
this->m_visible = false;
}
void Event::show()
{
this->m_visible = true;
}
QString Event::getShortActuator() const
{
return m_path.isEmpty() ? QString() : m_path.last(); //data.shortName;
}
//qint64 Event::endTime() const
//{
// if (m_endTime == -1) // infinite (open end)
// return -1; // model will take current (exercise) time then
// if (m_endTime == 0) // not specified - event ends when it starts
// return m_startTime;
// return m_endTime; // in any other case
//}
void Event::setEndT(qint64 newEnd)
{
m_endTime = newEnd;
checkDuration();
}
bool Event::checkDuration()
{
// maximum 4 weeks duration (because of timeline's limited 'view distance')
// Deactivated for now..
//#define MAX_DURATION (4 * 7 * 24 * 60 * 60)
// int _duration = duration();
// if (_duration > MAX_DURATION)
// {
// WARNING_LOG(m_logger) << "Event::checkDuration: event (unique# " << m_uniqueId << ") duration too high! max: " << MAX_DURATION << " - is: " << _duration;
// return false;
// }
// if (_duration < -1 /* NOTE: -1 is a valid duration */)
// {
// WARNING_LOG(m_logger) << "Event::checkDuration: event (unique# " << m_uniqueId << ") duration too low! min: -1 - is: " << _duration;
// return false;
// }
return true;
}
qint64 Event::duration(bool ignoreLiveEvents) const
{
if (m_endTime > m_startTime) // valid!
{
if (m_duration == -1)
m_duration = m_endTime - m_startTime;
return m_duration;
}
if (m_endTime < 0 && !ignoreLiveEvents) // valid - but INFINITE (give back seconds from start until now!)
{
qint64 currentTime = (m_session != nullptr) ? m_session->partitionTime() : time(nullptr) /*bad fall-back*/;
qint64 duration = currentTime - m_startTime;
return duration;
}
return 0; // m_endTime == 0 (no duration / no end time) - endTime < startTime (error - return 0)
}
void Event::drawMultipleEventGraphic(QPainter *painter, const QRect &rect)
{
painter->save();
painter->setBrush(Qt::white);
int x = rect.x();
int y = rect.y();
int h = rect.height();
int w = rect.width();
// rectangle
painter->drawRect(rect);
// seperators
int s1 = y + h - (h * 0.4);
int s2 = y + h - (h * 0.2);
painter->drawLine(QPoint(x, s1),
QPoint(x + w, s1));
painter->drawLine(QPoint(x, s2),
QPoint(x + w, s2));
painter->restore();
}
void Event::drawCustomEventGraphicTriangle(QPainter *painter, const QRect &rect)
{
int x = rect.x();
int y = rect.y();
int h = rect.height();
int w = rect.width();
QPolygon pol;
pol.append(QPoint(x + 0, y + h));
pol.append(QPoint(x + (w / 2), y + 0));
pol.append(QPoint(x + w, y + h));
pol.append(QPoint(x + 0, y + h));
QPainterPath path;
path.moveTo(x, y);
path.addPolygon(pol);
painter->drawPath(path);
}
void Event::drawCustomEventGraphicInvertTriangle(QPainter *painter, const QRect &rect)
{
int x = rect.x();
int y = rect.y();
int h = rect.height();
int w = rect.width();
QPolygon pol;
pol.append(QPoint( x + 0 , y + 0));
pol.append(QPoint( x+ w , y + 0));
pol.append(QPoint(x + ( w / 2 ), y + h));
pol.append(QPoint( x + 0 , y + 0));
QPainterPath path;
path.moveTo(x, y);
path.addPolygon(pol);
painter->drawPath(path);
}
void Event::drawCustomEventGraphicEllipse(QPainter *painter, const QRect &rect)
{
QPoint ellipsePos = rect.center();
ellipsePos.rx()++; // reference!
ellipsePos.ry()++; // reference!
painter->drawEllipse(ellipsePos, rect.width() / 2, rect.height() / 2);
}
void Event::drawCustomEventGraphicRect(QPainter *painter, const QRect &rect)
{
painter->drawRect(rect);
}
//warning looks terrible on events with duration... qt bug?
void Event::drawCustomEventGraphicRoundedRect(QPainter *painter, const QRect &rect)
{
painter->drawRoundedRect(rect, 50, 50, Qt::RelativeSize);
}
}
+179
View File
@@ -0,0 +1,179 @@
/**
* @RDE_HEADER@
* base class for received events
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
#include <QColor>
#include <QJsonObject>
#include <QStringList>
#include <LtsUtilsCore/qpoint3d.h>
class QPainter;
class QRect;
namespace LtsSession
{
class LtsSession;
}
namespace LtsTimelineClassic
{
/**
* base container class for received events (event sets and markers)
* to avoid a total rewrite, Event class still exists and the getters access the EventSet class
*/
class LTSTIMELINECLASSIC_EXPORT Event // contains informations about an event, which is visualisized in the second column of the treeview, and can be painted on something
{
public:
enum ButtonType
{
ActiveEvent,
EventBlock,
};
typedef void (*EventPaintFunc)(QPainter *, const QRect &);
static void drawCustomEventGraphicTriangle(QPainter *painter, const QRect &rect);
static void drawCustomEventGraphicInvertTriangle(QPainter *painter, const QRect &rect);
static void drawCustomEventGraphicEllipse(QPainter *painter, const QRect &rect);
static void drawCustomEventGraphicRect(QPainter *painter, const QRect &rect);
static void drawCustomEventGraphicRoundedRect(QPainter *painter, const QRect &rect);
static void drawMultipleEventGraphic(QPainter *painter, const QRect &rect);
Event() = default;
Event(int uniqueId, int typeId, qint64 startTime, qint64 endTime,
int playerId, int partyId, int equipmentId, const QStringList &path,
const EventPaintFunc &paintFunc, const QColor &paintColor,
const QJsonObject &detailData, int subEventId = -1,
const LtsUtils::QPoint3D &pos = LtsUtils::QPoint3D(),
LtsSession::LtsSession *session = nullptr);
/**
* @brief returns event actuator
* @return actuator string list
*/
QStringList getActuator() const { return m_path; }
int typeId() const { return m_typeId; }
int uniqueId() const { return m_uniqueId; }
int getSubEventID() const;
QColor getColor() const { return m_eventColor; }
EventPaintFunc getPaintFunc() const { return m_paintFunc; }
/**
* @brief returns event start time as qint64
* @return start time as msecssinceepoch
*/
qint64 startTime() const { return m_startTime; }
qint64 endTime() const { return m_endTime; }
/**
* @brief returns a manipulated version of event end time as qint64 (returns current time, when end time equals -1)
* @return end time as msecssinceepoch
*/
//qint64 endTime() const;
/**
* @brief other than getEndT, this method returns the real end time (can be 0 or -1)
* @return
*/
//qint64 realEndTime() const { return m_endTime; }
const QJsonObject &detailData() const { return m_detailData; }
void setDetailData(const QJsonObject &detailData) { m_detailData = detailData; }
void setEndT(qint64 newEnd);
/**
* @brief returns event duration in seconds
* @return duration in seconds
*/
qint64 duration(bool ignoreLiveEvents = false) const;
int playerId() const { return m_playerId; }
void setPlayerId(int playerId) { m_playerId = playerId; }
int partyId() const { return m_partyId; }
void setPartyId(int teamId) { m_partyId = teamId; }
int equipmentId() const { return m_equipmentId; }
void setEquipmentId(int eqId) { m_equipmentId = eqId; }
QString getShortActuator() const;
bool positionValid() const { return !std::isnan(m_position.x()) && !std::isnan(m_position.y()); }
const LtsUtils::QPoint3D &position() const { return m_position; }
QPointF pos2d() const { return QPointF(m_position.x(), m_position.y()); }
void setPosition(const LtsUtils::QPoint3D &pos) { m_position = pos; }
/**
* @brief hide tells the model to not be shown on the view
*/
void hide();
/**
* @brief show tells the model to be shown on the view
*/
void show();
/**
* @brief tells if this event should be shown or not
* @return true for show, false for hide
*/
bool isVisible() const { return m_visible; }
/**
* @brief sets the visibility on the value of 'visibility'
* @param visibility true if visible, false if hidden
*/
void setVisible(bool visibility = true);
void setDeleted() { m_deleted = true; }
bool isDeleted() const { return m_deleted; }
protected:
QStringList m_path;
QJsonObject m_detailData;
// geo pos:
LtsUtils::QPoint3D m_position;
EventPaintFunc m_paintFunc = nullptr; // TODO: replace me with a more secure std::function !
LtsSession::LtsSession *m_session = nullptr;
QColor m_eventColor;
qint64 m_startTime = -1;
qint64 m_endTime = -1;
mutable qint64 m_duration = -1;
int m_uniqueId = -1;
int m_typeId = -1;
int m_subEventId = -1; // for events which are one sub-events of many (sub-event-lists)
int m_playerId = -1;
int m_partyId = -1;
int m_equipmentId = -1;
bool m_deleted = false; // would take a while to implement a real free, so this is a quick workaround (do not draw if deleted)
bool m_visible = false;
private:
//NGL::INGLLogger &m_logger;
static bool checkDuration(); // currently not needed..
};
}
+68
View File
@@ -0,0 +1,68 @@
/**
* @RDE_HEADER@
* manages each row in view which type of container is suitable for current situation
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "EventDelegate.h"
#include "PassiveEventContainer.h"
#include <QModelIndex>
#include <QDebug>
namespace LtsTimelineClassic
{
EventDelegate::EventDelegate(QWidget *parent)
: QStyledItemDelegate(parent)
, m_liveTime(0)
{
}
QSize EventDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
(void)option;
(void)index;
return QSize(50, 20);
}
void EventDelegate::setKnownRanges(const QVector<Range> &knownRanges)
{
m_knownRanges = knownRanges;
}
void EventDelegate::setLiveTime(qint64 liveTime)
{
m_liveTime = liveTime;
}
void EventDelegate::slotAddLine(int posX, qint64 time) // add posX to a list, so that the view can draw them on itself
{
m_lineMap.insert(posX, time);
}
void EventDelegate::slotResetLineList()
{
m_lineMap.clear();
}
void EventDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.data().canConvert<PassiveEventContainer *>())
{
PassiveEventContainer *passiveEventContainer = qvariant_cast<PassiveEventContainer *>(index.data());
passiveEventContainer->paint(painter, option, m_lineMap, m_knownRanges);
}
else
QStyledItemDelegate::paint(painter, option, index);
}
} // namespace LtsTimelineClassic
+82
View File
@@ -0,0 +1,82 @@
/**
* @RDE_HEADER@
* manages each row in view which type of container is suitable for current situation
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "AbstractTimelineModel.h"
#include "Range.h"
#include <QStyledItemDelegate>
namespace LtsTimelineClassic
{
/**
* @brief manages each row in view which type of container is suitable for current situation
*/
class EventDelegate : public QStyledItemDelegate // manages all EventContainer and kind of
{
Q_OBJECT
public:
/**
* @brief delegate which manages all containers (active/passive event containers) for the view
* @param parent on which parent this delegates lies on
* @param timelineModel model to get data for containers
*/
EventDelegate(QWidget *parent = nullptr);
void setKnownRanges(const QVector<Range> &knownRanges);
/**
* @brief minimum size a row / column should take
* @param option style options of coherant row & column
* @param index which row & column is affected
* @return
*/
QSize sizeHint(const QStyleOptionViewItem &option = QStyleOptionViewItem(), const QModelIndex &index = QModelIndex()) const;
void setLiveTime(qint64 liveTime);
private:
/**
* @brief paint draws all containers on itself (which lies directly on the view)
* @param painter painter to draw all containers on the view
* @param option style options of coherant row & column
* @param index which row & column to paint
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
/**
* @brief creates or returns an active event container so that users can interact with events on the view
* @param parent on which delegate/parent this editor lies on
* @param option style options of coherant row & column
* @param index which row & column to lay the editor on
* @return
*/
//QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
LineMap m_lineMap;
QVector<Range> m_knownRanges;
qint64 m_liveTime;
public slots:
/**
* @brief deletes line list (date text line lists)
*/
void slotResetLineList();
/**
* @brief add date text line pixel position which should be added to a list,
* so that the view can draw them on itself
* @param posX pixel position of date text line
*/
void slotAddLine(int posX, qint64 time);
};
}
+181
View File
@@ -0,0 +1,181 @@
/**
* @RDE_HEADER@
* provides draw-functionality for one or multiple events (created multiple times for each event which must be drawn)
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2015-09-23
*/
#include "EventGraphic.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QGuiApplication>
#include <QPoint>
#include <QPalette>
#include <QDebug>
namespace LtsTimelineClassic
{
EventGraphic::EventGraphic(Event *event)
: m_lowest(0)
, m_duration(-1)
, m_isMultipleEvent(false)
{
m_eventList += event;
}
void EventGraphic::addEvent(Event *event)
{
if (event == nullptr)
return;
m_eventList += event;
m_isMultipleEvent = true; // at this point, there was already anoter event in the list (the one from the constructor!)
m_lowest = 0; // reset
m_duration = -1; // reset
}
void EventGraphic::addEventList(const QVector<Event *> &eventList)
{
if (eventList.isEmpty())
return;
m_eventList += eventList;
m_isMultipleEvent = true; // at this point, there was already anoter event in the list (the one from the constructor!)
m_lowest = 0; // reset
m_duration = -1; // reset
}
qint64 EventGraphic::getDuration()
{
if (m_duration != -1)
return m_duration;
qint64 highestDuration = 0;
qint64 lowest = lowestTime();
for (Event *event : qAsConst(m_eventList))
{
// \/-> lowest (eg start) \/-> event->startTime() \/-> eventEnd
// |#############################|++++++++++++++++++++++++++++|......
// <----------------------------------------------------------> <= duration
// \/
// duration = (event->startTime() - lowest) + event->duration();
qint64 duration = (event->startTime() - lowest) + event->duration();
if (duration > highestDuration)
highestDuration = duration;
}
if (highestDuration < 0)
{
qWarning() << "duration < 0 !!" << highestDuration;
Q_ASSERT(false);
return 0;
}
m_duration = highestDuration;
return m_duration;
}
qint64 EventGraphic::lowestTime() const
{
if (m_lowest != 0)
return m_lowest; // cache hit!
auto i = std::min_element(m_eventList.begin(), m_eventList.end(), [](const Event *a, const Event *b)
{
return a->startTime() < b->startTime();
});
if (i == m_eventList.end())
return 0;
m_lowest = (*i)->startTime();
return m_lowest;
}
bool EventGraphic::isMultipleEvent() const
{
return m_isMultipleEvent;
}
void EventGraphic::drawEventGraphic(QPainter *painter, const QRect &rect) const
{
painter->save();
painter->setPen(QPen(QBrush(QGuiApplication::palette().text()), 1.0));
if (isMultipleEvent())
Event::drawMultipleEventGraphic(painter, rect);
else
{
Event *e = m_eventList.first();
if (e == nullptr)
{
painter->restore();
return;
}
painter->setBrush(QBrush(e->getColor()));
if (e->getPaintFunc() == nullptr)
{
painter->restore();
return;
}
e->getPaintFunc()(painter, rect);
}
painter->restore();
}
void EventGraphic::drawDurationBackground(QPainter *painter, const QRect &rect) const // draw a event with duration
{
painter->save();
if (!isMultipleEvent())
{
Event *e = m_eventList.first();
if (e == nullptr)
{
painter->restore();
return;
}
painter->setBrush(QBrush(e->getColor())); // draw rectangle withoput border
painter->setPen(QPen(QBrush(e->getColor()), 0)); // draw rectangle withoput border
}
else
{
painter->setBrush(QBrush(QColor("white"))); // draw rectangle withoput border
}
painter->drawRect(rect.adjusted(0, 0, -2, 0));
painter->setPen(QPen(QBrush(QGuiApplication::palette().text()), 1.0)); // draw top and bottom lines
painter->drawLine(rect.topLeft(), rect.topRight());
painter->drawLine(rect.bottomLeft() + QPoint(0, 1), rect.bottomRight() + QPoint(1, 1));
painter->restore();
}
} // namespace LtsTimelineClassic
+54
View File
@@ -0,0 +1,54 @@
/**
* @RDE_HEADER@
* provides draw-functionality for one or multiple events (created multiple times for each event which must be drawn)
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2015-09-23
*/
#pragma once
#include "EventList.h"
#include "Event.h"
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
namespace LtsTimelineClassic
{
class LTSTIMELINECLASSIC_EXPORT EventGraphic
{
public:
enum Action
{
ACTION_LEFT_CLICKED,
ACTION_RIGHT_CLICKED,
ACTION_TOOLTIP,
};
explicit EventGraphic(Event *event);
bool isMultipleEvent() const;
const QVector<Event *> eventList() const { return m_eventList; }
void addEventList(const QVector<Event *> &eventList);
void addEvent(Event *event);
qint64 lowestTime() const;
qint64 getDuration();
void drawEventGraphic(QPainter *painter, const QRect &rect) const;
void drawDurationBackground(QPainter *painter, const QRect &rect) const;
private:
EventList m_eventList; // bool isMultipleEvent; <= when eventList.count > 1 !!
// mutable for caches, because of speed reasons
mutable qint64 m_lowest; // this is for speedup purpose
mutable qint64 m_duration;
bool m_isMultipleEvent;
//qint64 highestTime;
};
} // namespace LtsTimelineClassic
+23
View File
@@ -0,0 +1,23 @@
/**
* @RDE_HEADER@
* typedefs for EventList
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include <QVector>
namespace LtsTimelineClassic
{
class Event;
using EventList = QVector<Event *>;
class EventGraphic;
using EventGraphicList = QVector<EventGraphic *>;
}
+427
View File
@@ -0,0 +1,427 @@
/**
* @RDE_HEADER@
* calculates, converts, and draws events by entering time or pixel position as value
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "EventTools.h"
#include "EventGraphic.h"
#include "Event.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <math.h>
#include <QPainter>
#include <QDebug>
namespace LtsTimelineClassic
{
#define logger NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.EventTools") // for static functions
#ifdef LTS_DEBUG
//#define VERBOSE
#endif
#ifdef VERBOSE
#define dbg_print(x) qWarning() << #x << x
#else
#define dbg_print(x) (void)x;
#endif
qint64 EventTools::s_minAdjusted = 0;
qint64 EventTools::s_maxAdjusted = 1;
qint64 EventTools::s_minTimeline = 0;
qint64 EventTools::s_maxTimeline = 1;
qint64 EventTools::s_sbPos = 0;
qint64 EventTools::s_zoomFactor = 1;
int EventTools::s_paintMin = 0;
int EventTools::s_paintDiff = 0;
bool EventTools::s_isValid = false;
qint64 EventTools::s_diff = 0;
int EventTools::s_sectionHeight = 0;
int EventTools::s_test = 0;
qint64 EventTools::s_blockSizeTime = 0;
bool EventTools::s_inUse = false;
// creates relative event tools
// do NOT use references, as setValues HAS TO be called when values change or all calculations will probably fail
void EventTools::setValues(const qint64 min, const qint64 max, const qint64 sbPos, const qint64 zoomFactor)
{
if (s_minTimeline == min && s_maxTimeline == max && s_sbPos == sbPos && s_zoomFactor == zoomFactor) // no need to recalc same values
return;
s_sbPos = sbPos;
s_zoomFactor = zoomFactor;
//qWarning() << "setValues: sbPos =" << s_sbPos;
//qWarning() << "setValues: zoomFactor =" << s_zoomFactor;
s_isValid = false;
s_minAdjusted = s_sbPos;
double d_max = s_minAdjusted;
d_max += s_zoomFactor;
s_maxAdjusted = d_max;
s_diff = s_maxAdjusted - s_minAdjusted;
s_minTimeline = min;
s_maxTimeline = max - EventTools::s_diff + (EventTools::getBlockSizeTime() * 2); // add one event width to the end, so that the last event is not cut off when live
recalcBlockSizeTime();
if (min > 0 && max > 1)
{
s_isValid = true;
//#warning TODO: ERST AB HIER IST DIE TIMELINE ZU GEBRAUCHEN !
//qWarning() << "EventTools: min/max okay => "<<min<<"/"<<max;
}
else
{
//qWarning() << "EventTools: min/max weird => "<<min<<"/"<<max;
}
#ifdef VERBOSE
qWarning() << "\r\n\r\nEventTools::setValues: " << "\r\n"
<< "s_sbPos" << s_sbPos << "\r\n"
<< "s_zoomFactor" << s_zoomFactor << "\r\n"
<< "s_minTimeline" << s_minTimeline << "\r\n"
<< "s_maxTimeline" << s_maxTimeline << "\r\n"
<< "s_isValid" << s_isValid << "\r\n"
<< "s_minAdjusted" << s_minAdjusted << "\r\n"
<< "s_maxAdjusted" << (long)s_maxAdjusted << "\r\n"
<< "s_diff" << s_diff << "\r\n"
;
#endif
}
void EventTools::setSection(const int x, const int length)
{
s_paintMin = x;
s_paintDiff = length;
//qWarning() << "EventTools::setSection: x" << x << "length" << length;
}
bool EventTools::isValid()
{
#ifdef VERBOSE
if (!s_isValid)
qWarning() << "<<<< INVALID EVENT TOOLS!!";
qWarning() << "\r\nEventTools::isValid: " << "\r\n"
<< "s_isValid" << s_isValid << "\r\n"
;
#endif
return s_isValid;
}
qint64 EventTools::getAdjustedMin()
{
if (!isValid())
return 0;
return s_minAdjusted;
}
qint64 EventTools::getAdjustedMax()
{
if (!isValid())
return 1;
return s_maxAdjusted;
}
qint64 EventTools::getTimelineMin()
{
if (!isValid())
return 0;
return s_minTimeline;
}
qint64 EventTools::getScrollbarPosition()
{
return s_sbPos;
}
qint64 EventTools::getTimelineMax()
{
if (!isValid())
return 1;
return s_maxTimeline;
}
int EventTools::getEventPosition(qint64 eventTime) // time to pixels
{
qint64 min = s_minAdjusted;
//dbg_print(min);
qint64 diff = s_diff; // = max - min
//dbg_print(s_maxTimeline);
//dbg_print(s_maxAdjusted);
if (diff == 0)
{
#ifdef VERBOSE
qWarning() << "\n\ngetEventPosition: diff == 0\n";
#endif
return 0;
}
double pDiff = s_paintDiff; // 500px (700[right] - 200[left])
double pMin = s_paintMin; // => 200[left]
double x1 = eventTime; // => 1443000000
x1 -= min; // => 1443000000 - 1440000000 => 3.000.000
x1 /= diff; // => (max - min) => 1445000000 - 1440000000 => 5.000.000 => 3/5 = 0,6
double x2 = x1; // => 0,6
x2 *= pDiff; // => 0,6 * 500px => 300
x2 += pMin; // => 300 + 200 x (pixel) relative position of event in widget (set reference as valid)
#ifdef VERBOSE
qWarning() << "\r\n\r\nEventTools::getEventPosition: " << "\r\n"
<< "min" << min << "\r\n"
<< "diff" << diff << "\r\n"
<< "pDiff" << pDiff << "\r\n"
<< "pMin" << pMin << "\r\n"
<< (long)x1 << "[x1]" << "= (" << eventTime << "[eventTime]" << "-" << min << "[min]" << ") /" << diff << "[diff]" << "\r\n"
<< (long)x2 << "[x2]" << "= (" << (long)x1 << "[x1]" << "*" << pDiff << "[pDiff]" << ") +" << pMin << "[pMin]" << "\r\n"
;
dbg_print(min);
#endif
return qRound(x2);
}
qint64 EventTools::getEventTime(double eventPosition) // pixels to qint64 (time), starting from minimum
{
if (!isValid())
{
//qWarning() << "! getEventTime: invalid ET";
return 0;
}
if (eventPosition > 999999)
{
//WARNING_LOG(GET_LOGGER_ET) << "eventPosition > 999999: returning 0" ;
WARNING_LOG(logger) << "getEventTime: pos > 999999 => returning 0" ;
return 0;
}
double min = s_minAdjusted;
double diff = s_diff;
double pDiff = s_paintDiff;
if (pDiff < 1)
{
//WARNING_LOG(GET_LOGGER_ET) << "pDiff = " << pDiff << ", which is smaller than 1! avoiding 0-division now! returning 0" ;
WARNING_LOG(logger) << "getEventTime: pDiff < 1" ;
return 0;
}
double pMin = s_paintMin;
double x1 = eventPosition;
x1 -= pMin;
x1 /= pDiff;
double x2 = x1;
x2 *= diff;
x2 += min;
return qRound64(x2);
}
void EventTools::drawMarkLines(QPainter *painter, const EventList &eventList, int viewHeight)
{
if (!isValid())
return;
//painter->setPen(QColor("red"));
for (int k = 0; k < eventList.count(); k++)
{
Event *e = eventList.at(k);
int pos = getEventPosition(e->startTime());
if (pos < (s_paintMin) || pos > (s_paintMin + s_paintDiff)) // check boundaries
continue;
painter->drawLine(pos, 0, pos, viewHeight);
}
}
void EventTools::recalcBlockSizeTime()
{
s_blockSizeTime = _getBlockSizeTime();
}
qint64 EventTools::_getBlockSizeTime()
{
// TODO: use s_diff instead:
int startPos = getEventPosition(s_minAdjusted); // => 50px (WARNING: why is minAdjusted's position not 0, but is going to be hidden by section 0 ????
int endPos = getEventPosition(s_maxAdjusted); // => 350px the diff between min & max is 300px
int posDiff = endPos - startPos;
if (posDiff < 1)
{
//qWarning() << "posDiff < 1" << posDiff;
return 0; // event tools are not ready yet..
}
qint64 diff = s_maxAdjusted - s_minAdjusted; // => 50.000s
double pixelWidthInSeconds = diff;
pixelWidthInSeconds /= posDiff; // => 50.000s / 300px = 167s 1 pixel "has"/"contains"/"is" 167 seconds ("in width")
int eventGraphicsWidth = eventWidth(); // usually 8px
qint64 result = eventGraphicsWidth; result *= pixelWidthInSeconds; // the block is 1337 :) seconds long
#ifdef VERBOSE
qWarning() << "\r\n\r\nEventTools::recalcBlockSizeTime: " << "\r\n"
<< "minAdj" << s_minAdjusted << "\r\n"
<< "maxAdj" << s_maxAdjusted << "\r\n"
<< "endPos" << endPos << "\r\n"
<< "diff" << diff << "\r\n"
<< "pixelWidthInSeconds" << pixelWidthInSeconds << "\r\n"
<< "eventGraphicsWidth" << eventGraphicsWidth << "\r\n"
<< "result" << result << "\r\n"
<< "(duration > result) ?" << (duration > result)
;
#endif
return result;
}
qint64 EventTools::getBlockSizeTime(qint64 duration)
{
if (duration < s_blockSizeTime)
return s_blockSizeTime;
else
return duration;
}
void EventTools::drawEvents(QPainter *painter, const EventGraphicList &eventGraphicList, const int verticalPosition /* y(0) = event line to draw on */)
{
painter->save();
//painter->setRenderHint(QPainter::RenderHint::Antialiasing); looks super ugly ^^
painter->setClipping(true); // do not draw outside of the section & line of the events ..
painter->setClipRect(QRect(s_paintMin, verticalPosition, s_paintDiff, s_sectionHeight)); // clip the complete timeline view to that single event line & section
//WARNING_LOG(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.EventTools")) << "drawEvents > eventList.count = " << eventList.count() ;
for (int i = 0; i < eventGraphicList.count(); i++)
{
EventGraphic *eg = eventGraphicList.at(i);
if (!eg)
{
//WARNING_LOG(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.")) << "" ;
//WARNING_LOG(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.EventTools")) << "drawEvents > (!event) !!" ;
continue;
}
qint64 eventStart = eg->lowestTime();
if (eventStart == 0)
continue;
#ifdef VERBOSE
qWarning() << "\r\n\r\nEventTools::drawEvents" << "\r\n"
<< "eventStart" << eventStart << "\r\n";
#endif
qint64 eventDuration = eg->getDuration(); // take the highest duration
int newEventPosition = getEventPosition(eventStart);
int newEventEndPosition = 0;
int finalEventEndPos = 0;
int eventGraphicsWidth = eventWidth(); // event graphic width of ALL event graphics (DO NOT RESIZE EVENT GRAPHICS WHEN A SIZE HAS ALREADY BEEN CHOSEN & USED!!!)
if (eventDuration > 1) // if events have a duration, calculate its end position before checking for boundaries
{
qint64 eventPosition = eventStart;
eventPosition += eventDuration;
newEventEndPosition = getEventPosition(eventPosition);
finalEventEndPos = GetEndPos(newEventEndPosition, eventGraphicsWidth); // move the event graphics, so that the middle point of the event graphic is at the start-position (s_start)
}
else
{
newEventEndPosition = newEventPosition; // events without an duration end at the same position as they start
}
if (newEventPosition + (newEventEndPosition - newEventPosition) < ((-eventGraphicsWidth) + s_paintMin) || // eventGraphicsWidth makes sure that event is visible until it is out of scope
newEventPosition > (s_paintMin + s_paintDiff)) // check boundaries
{
#ifdef VERBOSE
qWarning() << "\n\ndrawEvents: boundary check result was negative\n" <<
" newEventPosition=" << newEventPosition << "\n" <<
" newEventEndPosition=" << newEventPosition << "\n" <<
" eventGraphicsWidth=" << eventGraphicsWidth << "\n" <<
" s_paintMin=" << s_paintMin << "\n" <<
" s_paintDiff=" << s_paintDiff << "\n";
#endif
continue;
}
// draw event graphics first, so that the duration background will overlap it!
int finalEventPos = newEventPosition;
QRect eventGraphicRect(finalEventPos, verticalPosition + 2, eventGraphicsWidth, s_sectionHeight - 6); // position on which the passive drawer can draw its event graphic on
//qWarning() << ">> DRAW: " << eventGraphicRect;
eg->drawEventGraphic(painter, eventGraphicRect); // tell the passive drawer to draw his event graphic on this painter at the given position (QRect)
#ifdef VERBOSE
qWarning() << "drawEvents > actually draw an event at "
"x=" << eventGraphicRect.x() << " "
"y=" << eventGraphicRect.y() << " "
"eventStart=" << eventStart ;
#endif
if (eventDuration > 1 && // if there is a duration, get another event position (eventPosition + duration) for the terminatory event graphic
(finalEventEndPos - 1) > finalEventPos) // draw only if scale is ideal to show duration events
{
QRect eventGraphicEndRect(finalEventEndPos, verticalPosition + 2, eventGraphicsWidth, s_sectionHeight - 6);
eg->drawEventGraphic(painter, eventGraphicEndRect); // draw terminatory event graphic
// this is a event with a duration - so calculate the rect for the whole event and draw the duration background
// overlap event graphics now!
QRect eventWholeRect(newEventPosition + (eventGraphicsWidth / 2) /* because the drawings start pos is exactly
at the event start position and we must draw the duration background on the middle of the drawing
(because of symetrics - see the geometric forms the timeline uses to draw events) */
, verticalPosition + 2
, (newEventEndPosition - newEventPosition) - (eventGraphicsWidth) /* s.a.a. */
, s_sectionHeight - 6); // this is the WHOLE rect of the event with duration
eg->drawDurationBackground(painter, eventWholeRect);
}
}
painter->restore();
}
} // namespace LtsTimelineClassic
+213
View File
@@ -0,0 +1,213 @@
/**
* @RDE_HEADER@
* calculates, converts, and draws events by entering time or pixel position as value
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "EventList.h"
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
#include <QRect>
#include <time.h>
class QPainter;
namespace LtsTimelineClassic
{
/**
* @brief calculates, converts, and draws events by entering time or pixel position as value
*/
class LTSTIMELINECLASSIC_EXPORT EventTools
{
static const int s_EventTopPadding = 2;
static const int s_EventHeightPadding = -6;
static const int s_ButtonTopPadding = s_EventTopPadding + 2;
static const int s_ButtonHeightPadding = s_EventHeightPadding + 7;
public:
static int s_test;
static qint64 fromMsecsToSeconds(qint64 msecs)
{
return (msecs / 1000);
}
static qint64 fromSecondsToMsecs(qint64 minutes)
{
return (minutes * 1000);
}
static qint64 getBlockSizeTime(qint64 duration = 0);
/**
* @brief creates this class with all needed values and calculates all values to their relative values (relative)
* @param max last event in timeline in seconds since epoch
* @param min first event in timeline in seconds since epoch
* @param sbPos position of scrollbar in seconds since epoch
* @param zoomFactor percent points of how much to zoom into the view
* @param paintingRange boundaries of drawn on surface
*/
static void setValues(const qint64 min, const qint64 max, const qint64 sbPos, const qint64 zoomFactor);
/**
* @brief calculates all values relating to painting and measuring
* @param paintingRect rectangle to calculate with
*/
//static void setPaintingRect(const QRect &paintingRect);
static void setSection(const int x, const int length);
/**
* @brief returns true if EventTools has been correctly initialized with min > 0 & max > 1
* @return
*/
static bool isValid();
/**
* @brief tells if event tools has been created in relative mode
* @return true if event tools has been created in relative mode
*/
static bool isRelative();
/**
* @brief converts time/ seconds sicne epoch to pixel position by considering the classes values
* @param eventTime seconds since epoch
*/
static int getEventPosition(qint64 eventTime);
/**
* @brief returns minimum timeline position by respecting scrollbar position and zoom factor
* @return adjusted minimum timeline position
*/
static qint64 getAdjustedMin();
/**
* @brief returns maximum timeline position by respecting scrollbar position and zoom factor
* @return adjusted maximum timeline position
*/
static qint64 getAdjustedMax();
/**
* @brief returns minimum timeline position
* @return minimum timeline position
*/
static qint64 getTimelineMin();
/**
* @brief returns maximum timeline position
* @return maximum timeline position
*/
static qint64 getTimelineMax();
static qint64 getScrollbarPosition();
static void recalcBlockSizeTime();
/**
* @brief converts pixel position to time/ seconds since epoch by considering the classes values from minimum
* @param eventPosition pixel position
* @return seconds since epoch
*/
static qint64 getEventTime(double eventPosition);//, const QRect &paintingRect = QRect());
/**
* @brief draws all events from event list on the given painter by considering the classes values
* @param painter surface (container) on which the events have to be drawn
* @param eventList list of events to draw
* @param rect boundaries to draw in
*/
static void drawEvents(QPainter *painter, const EventGraphicList &eventGraphicList, const int verticalPosition);
/**
* @brief draws an line at the position of all mark events
* @param painter surface (container) on which the line have to be drawn
* @param eventList list of mark lines to draw
* @param viewHeight
* @param paintingRect
*/
static void drawMarkLines(QPainter *painter, const EventList &eventList, int viewHeight);//, const QRect &paintingRect = QRect());
/**
* @brief sets all events widgets geometry to rect on parent by considering the classes values
* @param eventList list of events to update
* @param rect boundaries to update geometries in
* @param parent parent to set widgets on
*/
//static void setGeometryForEvents(const EventGraphicList &eventGraphicList, const QRect &rect, QWidget* parent);
/**
* @brief calculates size of visual event by considering the classes values
* @param event event to calculate size
* @param paintingRect boundaries to respect
* @return rectangle of the event
*/
//static QRect getEventGraphicRect(ActiveEvent* event, const QRect& paintingRect = QRect());
/**
* @brief calculates the event graphics width
* @param x width to modify
* @return modified width
*/
static inline int eventWidth() // by height
{
return s_sectionHeight / 2;
}
/**
* @brief calculates the event end position respecting the graphicsWidth
* @param position old position
* @param graphicsWidth graphics width
* @return adjusted event position
*/
static inline int GetEndPos(int position, int graphicsWidth)
{
// -1 for cosmetical issues
return position - graphicsWidth - 1;
}
static inline float zoomFactor() { return s_zoomFactor; }
static inline void setSectionHeight(int sectionHeight) { s_sectionHeight = sectionHeight; }
static bool s_inUse;
private:
static qint64 _getBlockSizeTime();
/*
* @brief creates this class with all needed values without calculating all values (absolute)
* @param max last event in timeline in seconds since epoch
* @param min first event in timeline in seconds since epoch
* @param paintingRange boundaries of drawn on surface
*
static void setValues(const qint64 min, const qint64 max);*/
static qint64 s_minAdjusted;
static qint64 s_maxAdjusted;
static qint64 s_minTimeline;
static qint64 s_maxTimeline;
static qint64 s_sbPos;
static qint64 s_zoomFactor;
static int s_paintMin;
static int s_paintDiff;
static bool s_isValid; // check whether class has valid values (min > 0 && max > 2)
static int s_sectionHeight;
static qint64 s_blockSizeTime;
public:
static qint64 s_diff;
};
} // namespace LtsTimelineClassic
+394
View File
@@ -0,0 +1,394 @@
/**
* @RDE_HEADER@
* manages & represents a row in view and contains children items & events
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include <QDebug>
#include "Item.h"
#include "EventTools.h"
#include "EventGraphic.h"
#ifdef LTS_DEBUG
//#define VERBOSE
#endif
namespace LtsTimelineClassic
{
Item::Item(const QString &name, const QString &identifier)
: m_name(name)
, m_identifier(identifier)
, m_children(nullptr)
, m_parent(nullptr)
, m_pevcon(nullptr)
, m_eventListChanged(true) // true, as a NEW list is always changed
{
}
Item::~Item()
{
delete m_pevcon;
if (m_children)
{
qDeleteAll(*m_children);
delete m_children;
}
qDeleteAll(m_eventList);
}
PassiveEventContainer *Item::getPevcon()
{
if (!m_pevcon) // create a new pevcon if there is no pevcon
m_pevcon = new PassiveEventContainer(m_name == QStringLiteral("Markers")); // this is a speedup!
if (m_eventListChanged)
{
m_eventListChanged = false;
m_pevcon->setEventList(getEventGraphics());
}
return m_pevcon;
}
const QString Item::path() const
{
if (m_parent == nullptr)
return m_name;
return QStringLiteral("%1/%2").arg(m_parent->path(), m_name);
}
const QString &Item::getName() const
{
return m_name;
}
Item *Item::getParent() const
{
return m_parent;
}
ItemList Item::getFullItemList(bool recursive) const
{
ItemList il;
if (m_children == nullptr)
return il;
il += *m_children; // add current children list to list (dereference)
if (!recursive) // if search is not recursive, done
return il;
auto end = m_children->constEnd();
for (auto i = m_children->constBegin(); i != end; ++i) // if recursive search is wanted => recursive getFullItemList calls to children and their children, a.s.o.
{
il += (*i)->getFullItemList(recursive); // probably yes.. ;)
}
return il;
}
///* PRIVATE*/ const EventGraphicList Item::getEventGraphicsRecursive() const
//{
// EventGraphicList egl;
// // first modify the HIGHEST item, then the children items (or grouped events will be grouped again in higher items, which is not working)
// //auto end = ;
// for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i)
// {
// egl += (*i)->getEventGraphics(true); // true, as this method is for recursive search
// }
// return egl;
//}
const EventGraphicList Item::getCurrentEGLFromPevcon() const // .. without recreating one or refresh them
{
if (m_pevcon == nullptr)
return EventGraphicList();
return m_pevcon->getEventGraphicList();
}
const EventGraphicList Item::getEventGraphics() const // create active events and group events if they overlap in timeline view
{
EventGraphicList egl;
EventList el = getFullEventList(true, false); // now get all events, but not hidden ones as we are going to draw all visible events only
std::sort(el.begin(), el.end(), [](const Event *a, const Event *b)
{
return a->startTime() < b->startTime();
});
for (auto i1 = el.begin(); i1 != el.end(); ++i1)
{
Event *e1 = (*i1);
if (e1->isDeleted())
continue;
qint64 egStart = e1->startTime(); // list is sorted, so start time will never change in this process (so taking e1's start time is fine)
qint64 egEnd = egStart + EventTools::getBlockSizeTime(e1->duration()); // don't use endTime directly as using the block size time on the duration is essential!
qint64 blockSizeTime = EventTools::getBlockSizeTime();
bool egChanged = false;
EventGraphic *eg = new EventGraphic(e1);
egl += eg;
auto i2 = i1 + 1; /* start at i (+1) as everything behind it is already done (list is sorted!) */
for (; i2 != el.end(); ++i2)
{
if (egChanged)
{
egEnd = egStart + eg->getDuration();
if (egEnd - egStart < blockSizeTime) // the collision check always refers to the complete event craphic - it must always be at least blockSizeTime wide
egEnd = egStart + blockSizeTime;
egChanged = false;
}
Event *e2 = (*i2);
if (e2->isDeleted())
continue;
qint64 start2 = e2->startTime();
if (start2 >= egStart && egEnd >= start2) // e2 is inside eg, put e2 into eg
{
eg->addEvent(e2);
egChanged = true;
}
else // if this event is not inside the current eg, then the following events won't either - loop can be stopped here!
{
// [never is begin, as i2 was initialized with 'i1 + 1'] if (i2 != el.begin()) // the primary loop has to use this event as a new eg, but the iterator will be ++'ed and therefore skipped, so do a --, but -- on an interator seems ugly..
--i2;
break;
}
} // i2
if (i2 == el.end()) // end if the secondary loop reached the end (must be done because the end of the loop would (probably) otherwise do a ++ on an invalid iterator)
break;
i1 = i2; // skip processed events (don't +1 here as the primary loop will do this anyways)
} // i1
return egl;
}
EventList Item::getFullEventList(bool recursive, bool getHiddenEvents) const
{
EventList el;
if (getHiddenEvents)
{
el = m_eventList; // add event list from this item first
}
else // only get visible events
{
el.reserve(m_eventList.count()); // TODO: spill some memory for a speedup ?
//auto end = ;
for (Event *e : m_eventList)
{
if (e == nullptr || !e->isVisible())
continue;
el.append(e);
}
}
if (!m_children || !recursive) // recursive search in EXISTING children list ?
return el;
for (const Item *item : qAsConst(*m_children))
el += item->getFullEventList(recursive, getHiddenEvents);
return el;
}
void Item::setParent(Item *parent)
{
m_parent = parent;
}
void Item::addEvent(Event *event) // private! call TimelineModel::addEventToItem(...) to set possible latest event (by looking for the greates number in position)
{
m_eventList.append(event);
setEventListChanged(); // say that the list has been changed and new event list has to be created when asked
}
void Item::setEventListChanged()
{
m_eventListChanged = true;
if (m_parent != nullptr)
{
// tell the parent that his event list changed, too, because it has to show all its children's events, too
m_parent->setEventListChanged();
}
}
bool Item::removeEvent(int eventId)
{
for (auto i = m_eventList.begin(); i != m_eventList.end(); ++i)
{
Event *event = *i;
if (event->uniqueId() == eventId)
{
m_eventList.erase(i);
setEventListChanged();
return true;
}
}
return false;
}
void Item::addItem(Item *item)
{
if (!m_children)
m_children = new ItemList();
item->setParent(this); // set given item's parent on this, because it was added to this item
m_children->append(item);
}
bool Item::hasVisibleEvents() const
{
// check if this item itself has events ..
if (!m_eventList.isEmpty() &&
std::find_if(m_eventList.constBegin(), m_eventList.constEnd(), [](Event *e) { return e->isVisible(); }) != m_eventList.constEnd())
return true;
// .. no subitems, no chance to have events at this point
if (m_children == nullptr)
return false;
// .. otherwise check if subitems have events, what makes this item have events, too, as the subitems' events are printed as a summary channel on this item
for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i)
{
Item *item = *i;
if (item->hasVisibleEvents())
return true;
}
// no subitem had events? then this item has definitely no events
return false;
}
Item *Item::child(int row) const
{
if (m_children == nullptr)
return nullptr;
int idx = 0;
// only show children that have events aka. are not invisible
for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i)
{
Item *item = *i;
if (item->hasVisibleEvents())
{
if (row == idx)
return item;
else
idx++;
}
}
return nullptr;
}
int Item::childCount() const
{
if (m_children)
return m_children->count();
return 0;
}
int Item::visibleChildCount() const
{
if (m_children == nullptr)
return 0;
int result = 0;
for (auto i = m_children->constBegin(); i != m_children->constEnd(); ++i)
{
Item *item = *i;
if (item->hasVisibleEvents())
result++;
}
return result;
}
ItemList *Item::getChildren()
{
return m_children; // returns 0 if no list exists
}
int Item::indexOfParent() const
{
if (m_parent == nullptr || m_parent->getChildren() == nullptr)
return -1;
int idx = 0;
ItemList *parentItemList = m_parent->getChildren();
// only show children that have events aka. are not invisible
for (auto i = parentItemList->constBegin(); i != parentItemList->constEnd(); ++i)
{
Item *item = *i;
if (item == this)
return idx;
idx++;
}
Q_ASSERT(false);
return -1;
}
} // namespace LtsTimelineClassic
+179
View File
@@ -0,0 +1,179 @@
/**
* @RDE_HEADER@
* manages & represents a row in view and contains children items & events
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
#include "EventList.h"
#include "PassiveEventContainer.h"
namespace LtsTimelineClassic
{
class Item; // predefine to use Item in itself
using ItemList = QList<Item *>;
/**
* @brief manages & represents a row in view and contains children items & events
* contains a list of events and is used to describe the tree of the treeview
* can be a Player ("BlueRecon 75") or a Group ("Recons")
*/
class LTSTIMELINECLASSIC_EXPORT Item
{
// NOT WORKING! JUST PUTS EGLs ON TOP OF EACH OTHER! const EventGraphicList getEventGraphicsRecursive() const;
public:
// DISALLOW COPY OF ITEM (create a pointer of item instead)
Item(const Item &) = delete;
Item &operator=(const Item &) = delete;
const EventGraphicList getEventGraphics() const; // create active events and group events if they overlap in timeline view old: //void groupEventsRecursive();
/**
* @brief creates this class with a user-readable name
* @param name user-readable name
*/
explicit Item(const QString &name, const QString &identifier = QString());
/**
* @brief ???
*
*/
~Item();
/**
* @brief adds a item to this item (for tree view)
* @param item item to add to this item
*/
void addItem(Item *item);
bool removeEvent(int eventId);
void setEventListChanged();
const QString path() const;
/**
* @brief triggers the timeline (if it has the command to repaint) to redraw this item - must be called when event data is changed from outside (e.g. event data's end time has changed)
*/
//void dataChanged();
/**
* @brief get event pevcon pointer
* @return pevcon pointer of row (/item)
*/
PassiveEventContainer *getPevcon();
const EventGraphicList getCurrentEGLFromPevcon() const; // .. without recreating one or refresh them
/*
* @brief sets pevcon pointer
* @pevcon new pevcon pointer of row (/item)
*
void setPevcon(PassiveEventContainer* pevcon);*/
/**
* @brief sets item pointer
* @parent new item pointer parent
*/
void setParent(Item *parent);
/**
* @brief get event name
* @return event name of row (/item)
*/
const QString &getName() const;
void setName(const QString &name) { m_name = name; }
const QString &getIdentifier() const { return m_identifier; }
/**
* @brief get event children pointer
* @return event children reference of row (/item)
*/
ItemList *getChildren();
/*/** USE GETFULLEVENTLIST(false) INSTEAD !!
* @brief get event list
* @return event list of row (/item)
*/
//EventList getEventList();*/
/**
* @brief get event parent pointer
* @return event parent pointer of row (/item)
*/
Item *getParent() const;
/**
* @brief class which MUST NOT be called by the user
* only allow to be called by addEventToItem !!!
* @param event event to add to this item
*/
void addEvent(Event *event);
/**
* @brief get event list of this item (of ALL event lists of all children if recursive = true)
* @param recursive search recursivly in this item and it's children
* @param get events that are even hidden ones (which are not marked to be shown)
* @return event list with events of all children
*/
EventList getFullEventList(bool recursive = true, bool getHiddenEvents = false) const;
/**
* @brief get item list of all items (of ALL items of all children if recursive = true)
* @param recursive search recursivly in this item and it's children
* @return item list
*/
ItemList getFullItemList(bool recursive = true) const;
/**
* @brief returns the item's children at row x
* @param row children of which row
* @return children of row x
*/
Item *child(int row) const;
/**
* @brief returns the row number of the parent's children list of this item
* @return row number
*/
int indexOfParent() const;
/*
* @brief returns the row count of all this item's children
* @return row count
*
int rowCountOfAllItsChildren() const;*/
/**
* @brief get count of children of this item
* @return children count of this item
*/
int childCount() const;
int visibleChildCount() const;
bool hasVisibleEvents() const;
// OLD!!void groupEvents(bool recursive = true); // group events if they overlap in timeline view
private:
QString m_name; // displayed in the treeview to indicate the player for the user
QString m_identifier;
ItemList *m_children; // list of all children
Item *m_parent; // parent item, 1 level higher
PassiveEventContainer *m_pevcon; // every item has to own a pevcon, because no other class can hold scope on an pevcon !
EventList m_eventList; // lsit of all events of the item
//EventList m_originalEventList;
bool m_eventListChanged; // say that the list has been changed and new event list has to be created when asked
};
}
+19
View File
@@ -0,0 +1,19 @@
/**
* @RDE_HEADER@
* typedefs for LineMap
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2016-16-02
*/
#pragma once
#include <QMap>
namespace LtsTimelineClassic
{
using LineMap = QMap<int, qint64>;
}
+139
View File
@@ -0,0 +1,139 @@
/**
* @RDE_HEADER@
* passive container for view which only draws QPushButtons to save ressources
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "PassiveEventContainer.h"
#include "EventGraphic.h"
#include "EventTools.h"
#include <QGuiApplication>
#include <QPainter>
#include <QPen>
#include <QStyleOptionViewItem>
#include <QBrush>
#include <math.h>
#include <time.h>
#include <QDebug>
namespace LtsTimelineClassic
{
PassiveEventContainer::PassiveEventContainer(bool ignoreKnownRanges, QObject *parent)
: QObject(parent)
, m_ignoreKnownRanges(ignoreKnownRanges)
{
}
PassiveEventContainer::~PassiveEventContainer()
{
qDeleteAll(m_eventGraphicList);
}
EventGraphicList PassiveEventContainer::getEventGraphicList() const
{
return m_eventGraphicList;
}
void PassiveEventContainer::setEventList(const EventGraphicList &eventGraphicList)
{
qDeleteAll(m_eventGraphicList);
m_eventGraphicList = eventGraphicList; // copy
}
void PassiveEventContainer::paint(QPainter *painter, const QStyleOptionViewItem &option, const LineMap &lineMap,
const QVector<Range> &knownRanges) const
{
EventTools::s_test++;
bool alternate = option.features.testFlag(QStyleOptionViewItem::ViewItemFeature::Alternate);
const QColor bg = QColor(alternate ? QRgb(0xF6D8CE) : QRgb(0xFBF2EF));
QColor cachedBg(Qt::white);
if (alternate)
cachedBg = cachedBg.darker(110);
painter->fillRect(option.rect, QBrush(bg));
int posX1 = option.rect.x();
int posX2 = option.rect.right();
int posY1 = option.rect.y();
int posY2 = option.rect.bottom();
int h = posY2 - posY1 + 1;
painter->save();
painter->setClipping(true); // no drawing outside
painter->setClipRect(option.rect.adjusted(0, 0, 1, 1));
int eventWidth = EventTools::eventWidth();
if (m_ignoreKnownRanges)
{
painter->fillRect(QRect(posX1, posY1, posX2 - posX1, h), cachedBg);///*option.backgroundBrush);//*/QBrush(QColor("white")));
}
else // draw known ranges
{
for (const auto &range : knownRanges)
{
int rangeStart = EventTools::getEventPosition(range.start);
int rangeEnd = EventTools::getEventPosition(range.end);
//if (posX1 >= option.rect.x() && posX1 < option.rect.x() + option.rect.width() + 1)
if (rangeEnd >= posX1 && rangeStart <= posX2) // do not draw if out of view
painter->fillRect(QRect(rangeStart, posY1, rangeEnd - rangeStart + eventWidth + 1, h), cachedBg);///*option.backgroundBrush);//*/QBrush(QColor("white")));
}
}
// // draw live time as known (if live)
// if (liveTime > 0)
// {
// int liveStart = EventTools::getEventPosition(liveTime);
// if (liveStart <= posX2) // do not draw if out of view
// painter->fillRect(QRect(liveStart, posY1, posX2 - liveTime, h), QBrush(QColor("white")));
// }
painter->restore();
// draw date text lines first, can be overdrawn
//for (int i = 0; i < lineMap.count(); i++)
for (auto i = lineMap.constBegin(); i != lineMap.constEnd(); ++i)
{
int posX = i.key(); //lineMap.at(i);
painter->setPen(QPen(QBrush(QGuiApplication::palette().shadow()), 1.0));
if (posX > option.rect.x() &&
posX < (option.rect.x() + option.rect.width())) // do not draw if out of view
painter->drawLine(posX, posY1, posX, posY2);
}
//EventTools::setValues(min, max, scrollBarPosition, zoomFactor);
//refreshEventList();
// NOTE: painter's viewport is the whole timeline (without the header part)
// NOTE: option.rect is the line and section the events get draw on, only
EventTools::drawEvents(painter, m_eventGraphicList, option.rect.y());
// qWarning() << "<<< viewport" << painter->viewport() << "rect" << option.rect;
}
} // namespace LtsTimelineClassic
+76
View File
@@ -0,0 +1,76 @@
/**
* @RDE_HEADER@
* passive container for view which only draws QPushButtons to save ressources
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "EventList.h"
#include "LineMap.h"
#include "Range.h"
#include <QObject>
class QPainter;
class QStyleOptionViewItem;
namespace LtsTimelineClassic
{
/**
* @brief passive container for view which only draws QPushButtons to save ressources
* contains many Events (EventWidget)
* only paints the widgets (widgets are not user-accessible)
*/
class PassiveEventContainer: public QObject
{
Q_OBJECT
public:
/**
* @brief sets all required values in class and with the given event list
* @param eventList event list to manage by this pevcon
* @param parent parent
*/
explicit PassiveEventContainer(bool ignoreKnownRanges, QObject *parent = nullptr);
~PassiveEventContainer();
/**
* @brief draws all widgets on itself (which lies directly on the view) by considering the parameters
* @param painter painter to draw all containers on the view
* @param option style options of coherant row & column
* @param max last event in timeline in seconds since epoch
* @param min first event in timeline in seconds since epoch
* @param scrollBarPosition scrollbar positiion in seconds since epoch
* @param zoomFactor zoom factor in percent points
*/
void paint(QPainter *painter, const QStyleOptionViewItem &option, const LineMap &lineMap, const QVector<Range> &knownRanges) const;
/**
* @brief returns event list of pevcon (to create a new acecon which needs a event list)
* @return event list
*/
EventGraphicList getEventGraphicList() const;
/**
* @brief sets event list of pevcon
* @eventList new event list
*/
void setEventList(const EventGraphicList &eventGraphicList);
private:
EventGraphicList m_eventGraphicList;
bool m_ignoreKnownRanges;
};
}
Q_DECLARE_METATYPE(LtsTimelineClassic::PassiveEventContainer *)
+24
View File
@@ -0,0 +1,24 @@
/**
* @RDE_HEADER@
* A simple range with begine and end time
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, NLE, 2020-02-04
*/
#pragma once
#include <qglobal.h>
namespace LtsTimelineClassic
{
struct Range
{
qint64 start = 0;
qint64 end = 0;
Range() = default;
Range(qint64 s, qint64 e) : start(s), end(e) {}
};
}
+46
View File
@@ -0,0 +1,46 @@
/**
* @RDE_HEADER@
* test-case model to test timeline
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2015-09-23
*/
#include "TestTimelineModel.h"
#include "EventGraphic.h"
#include <QToolTip>
#include <QDebug>
namespace LtsTimelineClassic
{
TestTimelineModel::TestTimelineModel()
: AbstractTimelineModel()
{
}
void TestTimelineModel::addTestEvent(Event *event)
{
AbstractTimelineModel::addEvent(event);
}
void TestTimelineModel::slotEventAction(EventGraphic *eg, int actionType) // for clicking an event
{
(void)actionType;
QString content = tr("Event count: %1").arg(QString::number(eg->eventList().count()));
for (int i = 0; i < eg->eventList().count(); i++)
{
content += QLatin1String("\r\nTime: ");
content += QString::number(eg->eventList().at(i)->startTime());
}
}
}
+31
View File
@@ -0,0 +1,31 @@
/**
* @RDE_HEADER@
* test-case model to test timeline
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2015-09-23
*/
#pragma once
#include "AbstractTimelineModel.h"
namespace LtsTimelineClassic
{
class LTSTIMELINECLASSIC_EXPORT TestTimelineModel : public AbstractTimelineModel
{
Q_OBJECT
public:
TestTimelineModel();
void addTestEvent(Event *event);
private slots:
void slotEventAction(EventGraphic *, int);
};
}
+155
View File
@@ -0,0 +1,155 @@
/**
* @RDE_HEADER@
* replaces the original header of the view - offers a user-movable position mark
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "TimelineWidget.h"
#include "TimelineHeaderView.h"
#include <QPainter>
#include <QPainterPath>
#include <QPen>
#include <QPoint>
#include <QRect>
#include <QGuiApplication>
namespace LtsTimelineClassic
{
TimelineHeaderView::TimelineHeaderView(QWidget *parent)
: QHeaderView(Qt::Horizontal, parent)
, m_posMarkTime(0)
, m_paintingRange(rect()) // has to be reset @ slotSectionResized
, m_liveTime(0)
{
setSectionsClickable(true);
setStretchLastSection(true); // so that the whole viewport is filled with the last section (Event Section)
connect(this, &TimelineHeaderView::sectionResized,
this, &TimelineHeaderView::slotSectionResized);
// now wait for slotSectionResized (which is definitly going to be called)
}
#define GET_LOGGER NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.HeaderView")
void TimelineHeaderView::setPosMarkTime(qint64 time)
{
m_posMarkTime = time;
refreshData(); // new value, now recalc and show on view
}
void TimelineHeaderView::setLiveTime(qint64 time)
{
m_liveTime = time;
refreshData(); // new value, now recalc and show on view
}
void TimelineHeaderView::setPosMarkPos()
{
//QRect sectionRect(0, 0, sectionSize(1), 0);
if (m_posMarkTime > 0) // must be a valid time
{
int posMarkPos = EventTools::getEventPosition(m_posMarkTime);//, sectionRect);
emit signalSetPosMarkPos(posMarkPos);
refreshData();
}
}
void TimelineHeaderView::refreshData()
{
if (EventTools::getTimelineMin() > 0 && EventTools::getTimelineMax() > 1)
{
updateSection(1);
updateSection(0); // because this section is often drawn on when position is very low
}
}
void TimelineHeaderView::slotSectionResized(int logicalIndex, int oldSize, int newSize)
{
(void)oldSize;
if (logicalIndex == 1)
m_paintingRange = QRect(0, 0, newSize, height());
refreshData();
}
void TimelineHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save(); // otherwise we can not draw.... dunno what paintSection does wrong
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore(); // s.a.
// draw posMark
if (logicalIndex == 1)
{
int h = (rect.height() / 3);
int y = rect.height() - h;
int w = h;
//QRect sectionRect(sectionPosition(1), 0, sectionSize(1), 0);
painter->save();
painter->setClipping(true);
if (m_posMarkTime != 0) // if user changed posMark to a valid value
{
double posMark = EventTools::getEventPosition(m_posMarkTime);//, sectionRect);
int x = /*sectionPosition(1)*/ + posMark;
if (x >= sectionPosition(1))
{
painter->setPen(QPen(QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight)));
painter->setBrush(QGuiApplication::palette().highlight());
QPolygon pol;
pol.append(QPoint(x + (-w / 2), y + 0)); // p1
pol.append(QPoint(x + 0, y + h)); // p2
pol.append(QPoint(x + (w / 2), y + 0)); // p3
pol.append(QPoint(x + (-w / 2), y + 0)); // p1
QPainterPath path;
path.moveTo(x, y);
path.addPolygon(pol);
painter->drawPath(path);
}
}
if (m_liveTime != 0)
{
int posLive = EventTools::getEventPosition(m_liveTime);//, sectionRect);
int x = posLive;
if (x >= sectionPosition(1))
{
painter->setPen(QPen(QColor("green")));
painter->setBrush(QBrush(QColor("green")));
QPolygon pol;
pol.append(QPoint(x + (-w / 2), y + 0)); // p1
pol.append(QPoint(x + 0, y + h)); // p2
pol.append(QPoint(x + (w / 2), y + 0)); // p3
pol.append(QPoint(x + (-w / 2), y + 0)); // p1
QPainterPath path;
path.moveTo(x, y);
path.addPolygon(pol);
painter->drawPath(path);
}
}
painter->restore();
}
}
}
+86
View File
@@ -0,0 +1,86 @@
/**
* @RDE_HEADER@
* replaces the original header of the view - offers a user-movable position mark
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "EventTools.h"
#include <QHeaderView>
class QPainter;
namespace LtsTimelineClassic
{
/**
* @brief replaces the original header of the view - offers a user-movable position mark
* since Qt does NOT provide delegates for QHeaderViews (although it inherits from QAbstractItemView),
* we must reimplement this class and use paintSection(..) instead
*/
class TimelineHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit TimelineHeaderView(QWidget *parent = nullptr);
/**
* @brief refreshes the header view data, which triggers all views to refresh also
*/
void refreshData();
void setLiveTime(qint64 time);
private:
qint64 m_posMarkTime; // posMarkTime is set exactly ONE time when clicked on the header > then the time will be et'd so that posMarkPos can vary
QRect m_paintingRange; // seen from the widget itself (so y is 0!)
// double posMark; DO NOT USE POSMARK INSTEAD OF POSMARKTIME !
qint64 m_liveTime;
protected:
/**
* @brief paints the coherant section with a position mark (if set)
* @param painter painter which draws on the coherant section
* @param rect boundaries to draw in
* @param logicalIndex which section to draw on
*/
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const;
signals:
/**
* @brief gives new position mark pixel position to the view
* @param pos pixel position of new position mark
*/
void signalSetPosMarkPos(int pos); // header clicked -> new position mark position
public slots:
/**
* @brief sets new values to this class
* @param index affected column
* @param oldSize old width of column
* @param newSize new width of column
*/
void slotSectionResized(int logicalIndex, int oldSize, int newSize);
/**
* @brief setPosMarkTime sets m_posMarkTime - should be called if posMarker was set manually, not with mouse in header!
* @param time time to set the posMarker's position to
*/
void setPosMarkTime(qint64 time);
private:
/**
* @brief sets new position of the position mark in all necessary classes
*/
void setPosMarkPos();
};
}
+539
View File
@@ -0,0 +1,539 @@
/**
* @RDE_HEADER@
* offers zoom-buttons, current zoom in percent and date-time indicators coresponding to the viewport
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "TimelineWidget.h"
#include "TimelineIndicator.h"
#include "EventTools.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
//#include <nglsettings/nglsettings_settings.h>
#include <ngltools/ngltools_dtg.h>
#include <QGuiApplication>
#include <QHeaderView>
#include <QFont>
#include <QFontMetrics>
#include <QPainter>
#include <QPalette>
#include <QPushButton>
#include <QApplication>
#include <QHBoxLayout>
#include <QDateTime>
#include <QPoint>
#include <QLabel>
#include <QComboBox>
#include <QDebug>
#ifdef LTS_DEBUG
// #define VERBOSE
#endif
#ifdef VERBOSE
#define dbg_print(x) qWarning() << #x << x
#else
#define dbg_print(x) (void)x;
#endif
namespace LtsTimelineClassic
{
TimelineIndicator::TimelineIndicator(QWidget *parent)
: QWidget(parent)
, m_zoomFactorPoints(0)
, m_pbZoom(new QPushButton(this))
, m_pbUnzoom(new QPushButton(this))
, m_cbZoomLevel(new QComboBox(this))
, m_currentTextHeight(0) // text height of last drawn text
, m_currentTextWidth(0)
, m_sectionPos(0)
, m_sectionSize(0)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Indicator"))
{
m_timeArray.reserve(11 + 11 + 7 + 10);
m_timeArray << // see addEventToItem when adding / removing times from this array
// pos 0 1 2 3 4 5 6 7 8 9 10
/* seconds */ 1 << 2 << 3 << 4 << 5 << 6 << 12 << 15 << 20 << 30 << 60 <<
// pos 11 12 13 14 15 16 17 18 19 20 21
/* minutes */ 1 * 60 << 2 * 60 << 3 * 60 << 4 * 60 << 5 * 60 << 6 * 60 << 12 * 60 << 15 * 60 << 20 * 60 << 30 * 60 << 60 * 60 <<
// pos 22 23 24 25 26 27 28
/* hours */ 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 12 * 60 * 60 << 24 * 60 * 60 <<
// pos 29 30 31 32 33 34 35
/* da ys */ 1 * 60 * 60 << 2 * 60 * 60 << 4 * 60 * 60 << 6 * 60 * 60 << 8 * 60 * 60 << 10 * 60 * 60 << 14 * 60 * 60 << 18 * 60 * 60 << 22 * 60 * 60 << 26 * 60 * 60 << 30 * 60 * 60;
QHBoxLayout *centralLayout = new QHBoxLayout();
centralLayout->setSpacing(0);
setLayout(centralLayout);
m_pbZoom->setText(QLatin1String("+"));
m_pbZoom->setFixedSize(s_pbSize, s_pbSize);
m_pbUnzoom->setText(QLatin1String("-"));
m_pbUnzoom->setFixedSize(s_pbSize, s_pbSize);
// adding zoom levels (in seconds)
m_cbZoomLevel->addItem(tr("10 min"), 600); // zoom-in
m_cbZoomLevel->addItem(tr("30 min"), 1800); // ...
m_cbZoomLevel->addItem(tr("1 h"), 3600);
m_cbZoomLevel->addItem(tr("2 h"), 7200);
m_cbZoomLevel->addItem(tr("4 h"), 14400); // ...
m_cbZoomLevel->addItem(tr("8 h"), 28800); // zoom-out
m_cbZoomLevel->setCurrentIndex(0);
//m_cbZoomLevel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_cbZoomLevel->setFixedHeight(s_pbSize);
m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong();
connect(m_cbZoomLevel, &QComboBox::currentTextChanged,
this, &TimelineIndicator::afterZoom);
connect(m_pbZoom, &QPushButton::clicked,
this, &TimelineIndicator::slotZoom);
connect(m_pbUnzoom, &QPushButton::clicked,
this, &TimelineIndicator::slotUnzoom);
connect(this, &TimelineIndicator::signalAddLine,
this, &TimelineIndicator::slotAddLine);
connect(this, &TimelineIndicator::signalResetLineList,
this, &TimelineIndicator::slotResetLineList);
centralLayout->addWidget(m_pbZoom, 0, Qt::AlignLeft | Qt::AlignTop);
centralLayout->addWidget(m_pbUnzoom, 0, Qt::AlignLeft | Qt::AlignTop);
centralLayout->addWidget(m_cbZoomLevel, 1, Qt::AlignLeft | Qt::AlignTop);
// can fontmetrics change while runtime? (configuration, xml, display manager, etc.)
// no better solution: this routine only works good with raster fonts
// measure font to perfect fit at indicator's end
QFontMetrics fm(QApplication::font());
m_currentTextHeight = fm.height();
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
m_currentTextWidth = fm.width(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); // also example-text-width (with 01.01.1970) - enough to see how much a general datetime's size is
#else
m_currentTextWidth = fm.horizontalAdvance(DTG::toString(QDateTime::currentDateTimeUtc(), DTG::Short)); // also example-text-width (with 01.01.1970) - enough to see how much a general datetime's size is
#endif
dbg_print(m_currentTextWidth);
// waiting for section resize to set paintingRange
}
TimelineIndicator::~TimelineIndicator()
{
//delete m_settings;
}
void TimelineIndicator::afterZoom() // called when cb changes (slotZoom/Unzoom)
{
m_zoomFactorPoints = m_cbZoomLevel->currentData().toLongLong() * 1000; // to msecs
emit signalSetZoomFactor(m_zoomFactorPoints);
update(); // to make changes visually visible
}
void TimelineIndicator::slotZoom()
{
int pos = m_cbZoomLevel->currentIndex();
if (pos == 0) // can't go lower in cb
return;
m_cbZoomLevel->setCurrentIndex(pos - 1);
emit signalZoomChanged();
}
void TimelineIndicator::slotUnzoom()
{
int count = m_cbZoomLevel->count();
int pos = m_cbZoomLevel->currentIndex();
if (pos >= count - 1) // can't go higher in cb
return;
m_cbZoomLevel->setCurrentIndex(pos + 1);
emit signalZoomChanged();
}
enum highestUnit
{
HU_INVALID,
//HU_WEEKS,
HU_DAYS,
HU_HOURS,
HU_MINUTES,
HU_SECONDS
};
void TimelineIndicator::refreshData()
{
#ifdef VERBOSE
qWarning() << "<refreshData>";
#endif
//WARNING_LOG(m_logger) << "refreshData" ;
emit signalResetLineList(); // reset line list in view
#ifdef VERBOSE
qWarning() << "section x" << m_sectionPos << "length" << m_sectionSize;
#endif
/*/
*
* Formel zur Berechnung von Text-Abstaenden und zur Entscheidung zu Zeit-Einheiten
*
* beispiel: minimum TimelineView= 09.10.14 12:35
* 1. groesste Einheit vom Abstand VON TEXTWIDTH UND TOLERANCEPIXELS ermitteln (bei 10.11.14 11:04 und 10.11.14 23:49
* Unterschied von 0 Years, 0 Months, 0 Days, 12 Hours, 45 Minuten, 0 Sekunden) >> Stunden (da: 12:45 Stunden Unterschied)
* 2. TextWidth + TolerancePixels zu Time umwandeln (wird bei EventTools mit adjusted Min & adjusted Max automatisch gemacht)
* 3. Ergebnis als QDateTime
* 4. groessere Zahl [fuer groesste Einheit - findet die Funktion aber auch ohne highestUnit] suchen (bei 1:44 Stunden ist die groessere
* Zahl 2 Stunden [z.B. fuer 8 Stunden => 12 und 12 Stunden => 24])
* 5. von Minimum alle kleineren Einheiten & sich selbst abziehen >> 09.10.14 00:00
* 6. dem Minimum nun die groessere Zahl einsetzen >> 09.10.14 02:00
* 7. ab Minimum zeichnen, alle higherValue mal, bis zum Ende
*
/*/
//QRect paintRect(m_paintingRange.x(), 0, m_paintingRange.y(), 0); // 2x'0' = dummy (no need to draw, so this can be ignored)
// 1.:
double abstand = m_sectionPos + m_currentTextWidth + s_TimeTextDistanceTolerancePixels; // m_paintingRange.x() because the graphics starts there, everything lower than m_paintingRange.x() is a negative value !!1
dbg_print(abstand);
// 2.:
qint64 eventTimeOfAbstand = EventTools::getEventTime(abstand);
dbg_print(eventTimeOfAbstand);
qint64 adjustedMin = EventTools::getAdjustedMin();
dbg_print(adjustedMin);
qint64 textWidthTime = (eventTimeOfAbstand - adjustedMin);
if (textWidthTime < 0)
{
qWarning() << "TimelineIndicator: paintEvent: textWidthTime invalid! (" << textWidthTime << ")";
return;
}
qint64 eventTime = EventTools::getEventTime(abstand);
dbg_print(eventTime);
// 3.:
QDateTime textWidthDt = QDateTime::fromMSecsSinceEpoch(textWidthTime);
// 4.:
//int weeks = textWidthDt.date().weekNumber(); // 0
int days = textWidthDt.date().day() - 1; // 0
int hours = textWidthDt.time().hour() - 1; // 12
int minutes = textWidthDt.time().minute();//-1; // 45
int seconds = textWidthDt.time().second() - 1; // 59
highestUnit unit = HU_INVALID;
if (days == 0)
if (hours == 0)
if (minutes == 0)
if (seconds == 0)
{
#ifdef VERBOSE
qWarning() << "refreshData: no lower time unit available" ;
#endif
return;
}
else
{
// seconds
unit = HU_SECONDS;
}
else
{
// minutes
unit = HU_MINUTES;
}
else
{
// hours
unit = HU_HOURS;
}
else
{
// days
unit = HU_DAYS;
}
if (unit == HU_INVALID)
{
#ifdef VERBOSE
qWarning() << "unit == HU_INVALID";
#endif
return; // do not draw if it is not possible to draw something
}
int higherValue = 0;
for (int i = 0; i < m_timeArray.size(); i++) // lookup int list on which time would fit best
{
qint64 time = (m_timeArray[i] * 1000); // MSECS!
if (time >= textWidthTime) // 1:44 Stunden auf 2 Stunden
{
higherValue = time; // perfect fitting time found, return the value BEFORE this value (because this value is <= 0)
//WARNING_LOG(m_logger) << "refreshData: higherValue = " << higherValue ;
break;
}
else
{
continue; // so that next declarations are only called if no fitting value has been found
}
// warning never reaches this point!
// no value found:
#ifdef VERBOSE
qWarning() << "refreshData: no fitting highest unit found !" ;
#endif
return;
}
//warning other solution?
if (higherValue < 1)
{
//WARNING_LOG(m_logger) << "refreshData: no fitting highest unit found !" ;
return;
}
//dbg_print(higherValue);
// 5.:
//QDateTime minDt = QDateTime::fromMSecsSinceEpoch(EventTools::getTimelineMin()); // minTimeline, because we want to start from real minimum, so that the timeline can be smoothly scrolled in horizontal direction
QDateTime minDt = QDateTime::fromMSecsSinceEpoch(adjustedMin);
dbg_print(minDt);
dbg_print(unit);
switch (unit)
{
case HU_SECONDS:
{
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
#ifdef VERBOSE
qCritical() << "HU_SECONDS";
#endif
break;
}
case HU_MINUTES:
{
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
#ifdef VERBOSE
qCritical() << "HU_MINUTES";
#endif
break;
}
case HU_HOURS:
{
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); // no hours
#ifdef VERBOSE
qCritical() << "HU_HOURS";
#endif
break;
}
case HU_DAYS:
{
minDt = minDt.addSecs(-(minDt.time().second())); // no seconds
minDt = minDt.addSecs(-(minDt.time().minute() * 60)); // no minutes
minDt = minDt.addSecs(-(minDt.time().hour() * 60 * 60)); // no hours
minDt = minDt.addDays(-(minDt.date().day())); // no days
#ifdef VERBOSE
qCritical() << "HU_DAYS";
#endif
break;
}
default:
{
#ifdef VERBOSE
qWarning() << "unit / scaling is off da charz !";
#endif
return;
}
}
dbg_print(minDt);
// 6.:
minDt = minDt.addMSecs(-higherValue); // *MINUS*, so that the first text can scroll into the widget smoothly (because minTimeline is exactly on the left of section 1
dbg_print(minDt);
// 7.:
qint64 maxTimelineView = EventTools::getAdjustedMax() + (higherValue /* because msecs */); // also add higher value to max, as min could be bigger than max
qint64 additional = textWidthTime / 2; // only the half of the text - because there is m_currentTextWidth/2 more space after max!
maxTimelineView += additional;
qint64 minTimeline = minDt.toMSecsSinceEpoch();
dbg_print(minTimeline);
dbg_print(maxTimelineView);
dbg_print(higherValue);
qint64 steps = (maxTimelineView - minTimeline) / higherValue;
if (steps < 0)
steps = steps * (-1);
if (steps > 60000 || steps < 1)
{
ERROR_LOG(m_logger) << "More than 60000 ("<<steps<<") or less than 1 lines to draw. Loop will exit after 60000 lines.";
}
if (steps < 1)
steps = 2; // draw at least 2 lines instead of nothing - otherwise if there are x events at the same time, then no event would be visible
uint16_t currentStep = 0;
dbg_print(steps);
//warning calculation failure anywhere, because some time texts are sooner hidden than others
for (qint64 indicatorTime = minTimeline;
indicatorTime < maxTimelineView + 1 /* HACK: always draw at least 1 time */;
indicatorTime += higherValue) // start at minimal position (on view, in time) and until the
// width of the view (in time) and go standardAddTime-Steps
{
if (currentStep > 60000)
break;
currentStep++;
int pos = EventTools::getEventPosition(indicatorTime);
dbg_print(indicatorTime);
if (pos + (m_currentTextWidth / 4) + 1 /* tol */ < m_sectionPos)
{
#ifdef VERBOSE
qCritical() << "Indicator: pos" << pos << "invisible #1";
#endif
continue;
}
else
if (pos - (m_currentTextWidth /*/ 2*/) > (m_sectionPos + m_sectionSize)) // do not draw invisible texts
{
#ifdef VERBOSE
qCritical() << "Indicator: pos" << pos << "invisible #2";
#endif
continue;
}
else
{
#ifdef VERBOSE
qCritical() << "Indicator: OKAY!! pos" << pos << "visible";
#endif
}
//WARNING_LOG(m_logger) << "refreshData: signalAddLine: pos=" << pos << " i=" << i ;
emit signalAddLine(pos, indicatorTime); // add a new line to the view (pos, time)
}
dbg_print(currentStep);
QFontMetrics fm(QApplication::font());
m_currentTextHeight = fm.height();
update();
}
void TimelineIndicator::paintEvent(QPaintEvent *)
{
//qWarning() << "<paint>";
QPainter painter(this);
painter.save();
painter.setClipping(true);
painter.setClipRect(QRect(x() - 8 + m_sectionPos, 0, m_sectionSize, height()));
#ifdef VERBOSE
int textY = (height() - m_currentTextHeight - 5);
#endif
int h = height();
for (auto i = m_lineMap.constBegin(); i != m_lineMap.constEnd(); ++i)
{
//QPair<double, qint64> pair = m_lineList.at(i);
int pos = i.key(); //pair.first + 1; // (+1) unknown offset!
//painter.setPen(QPen(QBrush(QGuiApplication::palette().button()), 1));
painter.setPen(QPen(QColor("black"), 1.0));
painter.drawLine(pos, m_currentTextHeight, pos, h); // draw line for text
int centeredPos = pos - (m_currentTextWidth / 2);
qint64 time = (*i); // pair.second;
//painter.setPen(QPen(QBrush(QGuiApplication::palette().brightText()), 1));
//painter.setPen(QPen(QColor("black"), 1));
painter.drawText(centeredPos, (m_currentTextHeight / 2) + 4,
DTG::toStringMSec(time, DTG::Short)); // draw text
#ifdef VERBOSE
WARNING_LOG(m_logger) << "paintEvent: actual drawing position: x=" << centeredPos << " y=" << textY;
#endif
}
painter.restore();
//qWarning() << "</paint>";
}
void TimelineIndicator::slotSectionResized(int logicalIndex, int oldSize, int newSize)
{
(void)oldSize;
// is called from a QHeaderView which contains all sections ("actuator" & "events")
QHeaderView *header = qobject_cast<QHeaderView *>(QObject::sender());
if (!header)
return;
if (logicalIndex == 1) // event column
{
m_sectionPos = header->sectionPosition(1);
m_sectionSize = newSize;
//qWarning() << "setting painting range - m_sectionPos" << m_sectionPos << "m_sectionSize" << m_sectionSize;
}
update();
}
void TimelineIndicator::slotAddLine(double posX, qint64 time) // add posX to a list, so that the view can draw them on itself
{
#ifdef VERBOSE
qCritical() << "slotAddLine: posX" << posX << "time" << time;
#endif
m_lineMap.insert(posX, time); //(QPair<double, qint64>(posX, time));
}
void TimelineIndicator::slotResetLineList()
{
m_lineMap.clear();
}
} // namespace LtsTimelineClassic
+139
View File
@@ -0,0 +1,139 @@
/**
* @RDE_HEADER@
* offers zoom-buttons, current zoom in percent and date-time indicators coresponding to the viewport
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "EventTools.h"
#include "LineMap.h"
#include <QWidget>
#include <QPoint>
#include <QPair>
namespace NGL
{
class INGLLogger;
//class Settings;
}
class QPushButton;
class QLabel;
class QComboBox;
namespace LtsTimelineClassic
{
/**
* @brief offers zoom-buttons, current zoom in percent and date-time indicators coresponding to the viewport
*/
class TimelineIndicator : public QWidget
{
Q_OBJECT
//static const int s_MinimalZoomPoints = 1; // no need to zoom out of 100% (logically)
static const int s_TimeTextDistanceTolerancePixels = 10;
static const int s_pbSize = 18;
public:
explicit TimelineIndicator(QWidget *parent);
~TimelineIndicator();
qint64 zoomFactorPoints() const { return m_zoomFactorPoints * 1000; }
/**
* @brief refreshes the indicator data, which triggers all views to refresh also
*/
void refreshData();
private:
QVector<qint64> m_timeArray;
qint64 m_zoomFactorPoints;
QPushButton *m_pbZoom;
QPushButton *m_pbUnzoom;
QComboBox *m_cbZoomLevel;
int m_currentTextHeight; // text height of last drawn text
int m_currentTextWidth;
LineMap m_lineMap;
int m_sectionPos;
int m_sectionSize;
NGL::INGLLogger &m_logger;
protected:
/**
* @brief draws all date texts on itself
*/
void paintEvent(QPaintEvent *);
signals:
/**
* @brief gives connected classes new max & min values
* @param min new first event in timeline in seconds since epoch
* @param max new last event in timeline in seconds since epoch
*/
void signalSetTimelineRange(qint64 min, qint64 max);
/**
* @brief gives connected classes new zoom factor value (seconds)
* @param zoomFactor new zoom factor
*/
void signalSetZoomFactor(qint64 zoomFactor);
/**
* @brief gives connected classes the command to delete their line list (date text line lists)
*/
void signalResetLineList();
/**
* @brief gives connected classes date text line pixel position which should be added to a list,
* so that the view can draw them on itself
* @param posX pixel position of date text line
*/
void signalAddLine(int posX, qint64 time);
void signalZoomChanged();
private slots:
void afterZoom();
public slots:
/**
* @brief sets new values to this class
* @param index affected column
* @param oldSize old width of column
* @param newSize new width of column
*/
void slotSectionResized(int logicalIndex, int oldSize, int newSize);
/**
* @brief is called when push button "zoom" (+) is pressed
*/
void slotZoom();
/**
* @brief is called when push button "zoom" (+) is pressed
*/
void slotUnzoom();
/**
* @brief deletes line list (date text line lists)
*/
void slotResetLineList();
/**
* @brief add date text line pixel position which should be added to a list,
* so that the view can draw them on itself
* @param posX pixel position of date text line
*/
void slotAddLine(double posX, qint64 time);
};
}
+412
View File
@@ -0,0 +1,412 @@
/**
* @RDE_HEADER@
* MVC view (QTreeView) - shows items (rows), events, date-time lines and more
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "TimelineView.h"
#include "TimelineHeaderView.h"
#include "TimelineWidget.h"
#include "EventDelegate.h"
#include "EventGraphic.h"
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QGuiApplication>
#include <QHeaderView>
#include <QHelpEvent>
#include <QModelIndex>
#include <QRect>
#include <QDebug>
namespace LtsTimelineClassic
{
//TimelineView::TimelineView(TimelineHeaderView* headerView, QWidget *parent) :
TimelineView::TimelineView(QWidget *parent)
: QTreeView(parent)
//, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.View"))
, m_model(nullptr)
, m_drawCache(false)
{
m_posMarkTime = 0; // invalid - do not draw
//setMouseTracking(true);
setAlternatingRowColors(true); // so we can differ between specific rows
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionBehavior(QAbstractItemView::SelectItems);
setSelectionMode(QAbstractItemView::NoSelection);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // no horizontal scroll bar, because the TimelineWidget manages this and events are only virtually drawn on the view
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}
void TimelineView::setHeader(QHeaderView *header)
{
if (header)
{
QTreeView::setHeader(header); // cannot use a member attribute, because setHeader takes ownership of given header, very cautious! please use qobject_cast when TimelineHeaderView needed
header->setMinimumSectionSize(150); // minimum size for visualisation issues
}
else
{
//qDebug() << "ERROR: invalid header!";
}
}
AbstractTimelineModel *TimelineView::timelineModel()
{
return m_model;
}
void TimelineView::setTimelineModel(AbstractTimelineModel *model)
{
m_model = model;
setMouseTracking(true); // if it is not enabled by default
setModel(m_model);
EventDelegate *ed = new EventDelegate(this);
EventTools::setSectionHeight(ed->sizeHint().height());
setItemDelegate(ed); // a delegate, only for column 1 (events-column)
}
EventGraphic *TimelineView::getEventOnPosition(QPoint position)
{
position = viewport()->mapFromGlobal(position);
QModelIndex index = indexAt(position);
if (index.isValid() && position.x() > header()->sectionSize(0)) // do not allow clicking on section 0
{
Item *item = static_cast<Item *>(index.internalPointer());
if (!item)
return nullptr;
qint64 curTime = EventTools::getEventTime(position.x());
EventGraphicList egl = item->getCurrentEGLFromPevcon(); // must be the current egl from pevcon (not eventlist), as events can be grouped (stacked events)
qint64 blockSizeTime = EventTools::getBlockSizeTime();
for (int i = 0; i < egl.count(); i++)
{
EventGraphic *eg = egl.at(i);
if (!eg)
continue;
qint64 eventStartTime = eg->lowestTime();
if (eventStartTime == 0)
continue;
qint64 eventEndTime = eventStartTime;
qint64 eglDuration = eg->getDuration();
if (eglDuration > blockSizeTime)
eventEndTime += eglDuration;
else
eventEndTime += blockSizeTime;
if (curTime >= eventStartTime && curTime <= eventEndTime)
return eg;
}
}
return nullptr;
}
bool TimelineView::event(QEvent *event)
{
switch (event->type())
{
case QEvent::ToolTip:
{
//emit signalEventAction(getEventOnPosition(((QHelpEvent*)static_cast<QHelpEvent*>(event))->globalPos()), EventGraphic::ACTION_TOOLTIP);
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
if (!helpEvent)
return true;
EventGraphic *eg = getEventOnPosition(helpEvent->globalPos());
if (!eg)
return true;
emit signalEventAction(eg, EventGraphic::ACTION_TOOLTIP);
return true;
}
default:
{
break;
}
}
return QTreeView::event(event); // otherwise...
}
void TimelineView::wheelEvent(QWheelEvent *event)
{
QTreeView::wheelEvent(event);
if (QGuiApplication::queryKeyboardModifiers() != Qt::ControlModifier) // only scroll when CTRL is pressed
return;
int scrolls = event->delta() / 120;
if (scrolls > 0)
{
for (int i = 0; i < scrolls; i++)
emit signalZoom();
}
if (scrolls < 0)
{
for (int i = 0; i > scrolls; i--)
emit signalUnzoom();
}
}
void TimelineView::mouseReleaseEvent(QMouseEvent *event)
{
QTreeView::mouseReleaseEvent(event);
QPoint position = event->globalPos();
EventGraphic *eg = getEventOnPosition(position);
if (!eg)
return;
switch (event->button())
{
case Qt::LeftButton:
{
emit signalEventAction(eg, EventGraphic::ACTION_LEFT_CLICKED);
break;
}
case Qt::RightButton:
{
emit signalEventAction(eg, EventGraphic::ACTION_RIGHT_CLICKED);
break;
}
default:
{
break;
}
}
}
void TimelineView::slotAddLine(int posX, qint64 time) // add posX to a list, so that the view can draw them on itself
{
//m_lineList.append(QPair<double, qint64>(posX, time));
m_lineMap.insert(posX, time); // HACK! position as key, so only one line per position (which is perfectly okay and speedy)
}
void TimelineView::slotResetLineList()
{
m_lineMap.clear();
}
/*void TimelineView::slotSetPosMarkPos(int posMarkPos) // hint: must be INT, because it's the position as PIXELS (not time as qint64)
{
//int sectionSize = this->header()->sectionSize(1);
//QRect sectionRect(0, 0, sectionSize, 0); // only width matters for calculating a pixel position dependent on a fixed position
qint64 posMarkTime = EventTools::getEventTime(posMarkPos);
m_posMarkTime = posMarkTime;
TimelineHeaderView* headerView = qobject_cast<TimelineHeaderView*>(header());
if (headerView)
{
headerView->setPosMarkTime(posMarkTime);
}
refreshData();
}*/
void TimelineView::slotSetPosMarkTime(qint64 posMarkTime) // does not have to be a slot anymore !!
{
m_posMarkTime = posMarkTime;
TimelineHeaderView *headerView = qobject_cast<TimelineHeaderView *>(header());
if (headerView)
headerView->setPosMarkTime(posMarkTime);
refreshData();
}
void TimelineView::refreshData()
{
update();
viewport()->update();
}
void TimelineView::paintEvent(QPaintEvent *event) // draw lines to show where next texts are
{
if (!m_model) // cannot draw without a model
return;
QWidget *viewp0rt = viewport();
//qWarning() << "viewp0rt ptr" << (unsigned long long)viewp0rt;
QPainter painter(viewp0rt);
if (!painter.isActive())
{
//qWarning() << "painter not active";
}
painter.save();
int viewPortHeight = viewp0rt->height();
QHeaderView *head0r = header();
//qWarning() << "head0r ptr" << (unsigned long long)head0r;
int sectionPos = head0r->sectionPosition(1);
int sectionSize = head0r->sectionSize(1);
int minSection = sectionPos; // minimum x position
int maxSection = minSection + sectionSize; // maximum x position
//QRect paintRect(head0r->sectionPosition(1), 0,
// head0r->sectionSize(1), height());
QRect paintRect(minSection, 0,
maxSection - minSection, height());
painter.setClipping(true);
painter.setClipRect(paintRect);
// draw date text lines first, can be overdrawn
//for (int i = 0; i < m_lineList.count(); i++)
for (auto i = m_lineMap.constBegin(); i != m_lineMap.constEnd(); ++i)
{
int pos = i.key(); //m_lineList.at(i).first;
painter.setPen(QPen(QBrush(QGuiApplication::palette().shadow()), 1.0));
if (pos > minSection &&
pos < maxSection) // do not draw if out of view
painter.drawLine(pos, 0, pos, viewPortHeight);
else
{
#ifdef VERBOSE
qCritical() << "out of view => pos" << pos << "min" << minSection << "max" << maxSection;
#endif
}
}
// workaround for date text lines: first draw lines, then draw containers (which are transparent)
painter.restore();
QTreeView::paintEvent(event);
painter.save();
if (m_drawCache)
{
// draw gray bg (indicate uncached ranges)
painter.setPen(QPen(QBrush("gray"), 1));
painter.setBrush(QBrush("gray"));
painter.drawRect(minSection, 0, maxSection, height());
// draw cached ranges
painter.setPen(QPen(QBrush("white"), 1));
painter.setBrush(QBrush("white"));
int h = height();
for (const auto &range : qAsConst(m_knownRanges))
{
int start = EventTools::getEventPosition(range.start);
if (start < minSection)
start = minSection;
int end = EventTools::getEventPosition(range.end);
if (end > maxSection)
end = maxSection;
painter.drawRect(start, 0, end - start, h);
}
}
// draw marker lines
// QRect areaRect(sectionPos, viewp0rt->x(),
// sectionSize, height());
//EventTools::setValues(m_min, m_max, m_sbPos, m_zoomFactor); // DO NOT SET VALUES HERE!!! Because old values are needed, plus this saves CPU & RAM
ItemList il = m_model->getRootItem()->getFullItemList(true);
for (int i = 0; i < il.count(); i++)
{
if (il.at(i)->getName() == tr("Markers"))
EventTools::drawMarkLines(&painter, il.at(i)->getFullEventList(true), height());//, areaRect);
}
//QRect sectionRect(0, 0, header()->sectionSize(1), 0);
// draw pos mark line - is more important than a marker line
if (m_posMarkTime > 0) // != 0
{
int posMarkPosition = EventTools::getEventPosition(m_posMarkTime); // pretend that the posMark is an event, does not matter
if (posMarkPosition > sectionPos &&
posMarkPosition < sectionSize + sectionPos)
{
painter.setPen(QPen(QGuiApplication::palette().highlight(), 1.0));
int xpos = posMarkPosition;// + sectionPos;
painter.drawLine(xpos, 0, xpos, height());
}
}
painter.restore();
//event->ignore();
}
}
+135
View File
@@ -0,0 +1,135 @@
/**
* @RDE_HEADER@
* MVC view (QTreeView) - shows items (rows), events, date-time lines and more
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include "AbstractTimelineModel.h"
#include "TimelineIndicator.h"
#include "TimelineHeaderView.h"
#include "LineMap.h"
#include "Range.h"
#include <QTreeView>
namespace LtsTimelineClassic
{
/**
* @brief MVC view (QTreeView) - shows items (rows), events, date-time lines and more
*/
class TimelineView : public QTreeView
{
Q_OBJECT
public:
/**
* @brief creates a specialized QTreeView
* @param parent parent of this widget
*/
explicit TimelineView(QWidget *parent);
EventGraphic *getEventOnPosition(QPoint position);
/**
* @brief sets the (Timeline-)Model by calling setModel of base class QTreeView with additional options
* @param model new TimelineModel
*/
void setTimelineModel(AbstractTimelineModel *model);
/**
* @brief returns the timeline model
* @return timeline model
*/
AbstractTimelineModel *timelineModel();
/**
* @brief refreshes the view data, which triggers all views to refresh also
*/
void refreshData();
/**
* @brief setHeader overloaded, does more than the original
* @param header header to set
*/
void setHeader(QHeaderView *header);
void setKnownRanges(const QVector<Range> &newRanges)
{
m_knownRanges = newRanges;
}
void setDrawCache(bool show)
{
m_drawCache = show;
}
private:
qint64 m_posMarkTime;
AbstractTimelineModel *m_model;
LineMap m_lineMap;
QVector<Range> m_knownRanges;
bool m_drawCache;
protected:
bool event(QEvent *event) override;
void mouseReleaseEvent(QMouseEvent *) override;
void wheelEvent(QWheelEvent *event) override;
/**
* @brief checks whether it is possible to start an acecon on the clicked position
* @param event event
*/
//void mouseMoveEvent(QMouseEvent* event);
/**
* @brief draws lines to show where next date texts are
* @param event event
*/
virtual void paintEvent(QPaintEvent *event) override;
public slots:
/**
* @brief deletes line list (date text line lists)
*/
void slotResetLineList();
/**
* @brief add date text line pixel position which should be added to a list,
* so that the view can draw them on itself
* @param posX pixel position of date text line
*/
void slotAddLine(int posX, qint64 time);
/**
* @brief sets new position mark pixel position to the view (used by mouse click event)
* @param posMarkPos pixel position of new position mark
*/
//void slotSetPosMarkPos(int posMarkPos);
/**
* @brief sets new position mark time position to the view
* @param posmarTime time position of new position mark
*/
void slotSetPosMarkTime(qint64 posMarkTime);
signals:
void signalEventAction(EventGraphic *eg, int); // int => action type
void signalZoom();
void signalUnzoom();
};
} // namespace LtsTimelineClassic
+334
View File
@@ -0,0 +1,334 @@
/**
* @RDE_HEADER@
* widget in MVC pattern which manages the entire timeline to add on a layout or widget
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "TimelineWidget.h"
#include "EventDelegate.h"
#include "ui_TimelineWidgetUi.h"
#include "AbstractTimelineModel.h"
#include "EventGraphic.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QSize>
#include <QPoint>
#include <QRect>
#include <QDateTime>
#include <QDebug>
namespace LtsTimelineClassic
{
TimelineWidget::TimelineWidget(QWidget *parent) // model cannot be set here, as qt has to create this Widget and does not know how to handle more parameters as the parent parameter
: QWidget(parent)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("Timeline.Widget"))
, m_timelineUi(new Ui::Timeline)
, m_min(0)
, m_max(1)
, m_sbPos(0)
, m_zoomFactor(1) // 100%
, m_headerView(new TimelineHeaderView)
, m_model(nullptr)
{
m_timelineUi->setupUi(this);
m_timelineUi->view->setHeader(m_headerView);
m_headerView->setMinimumSectionSize(150);
m_timelineUi->scrollbar->setMouseTracking(true);
connect(m_timelineUi->scrollbar, &QScrollBar::sliderReleased, this, &TimelineWidget::scrollBarFilter2);
connect(m_timelineUi->scrollbar, &QScrollBar::actionTriggered, this, &TimelineWidget::scrollBarFilter1);
connect(m_headerView, &QHeaderView::sectionResized, m_timelineUi->indicator, &TimelineIndicator::slotSectionResized);
connect(m_headerView, &QHeaderView::sectionResized, this, &TimelineWidget::slotSectionResized);
connect(m_timelineUi->view, &TimelineView::signalZoom, m_timelineUi->indicator, &TimelineIndicator::slotZoom);
connect(m_timelineUi->view, &TimelineView::signalUnzoom, m_timelineUi->indicator, &TimelineIndicator::slotUnzoom);
connect(m_timelineUi->view, &TimelineView::signalZoom, this, &TimelineWidget::signalZoomChanged);
connect(m_timelineUi->view, &TimelineView::signalUnzoom, this, &TimelineWidget::signalZoomChanged);
connect(m_timelineUi->indicator, &TimelineIndicator::signalZoomChanged, this, &TimelineWidget::signalZoomChanged);
connect(m_timelineUi->indicator, &TimelineIndicator::signalSetZoomFactor, this, &TimelineWidget::slotSetZoomFactor);
connect(m_timelineUi->view, &TimelineView::signalEventAction,
this, &TimelineWidget::signalEventAction); // use old connect style because grrrr, EventGraphic !
connect(m_timelineUi->cbDrawCache, &QCheckBox::toggled,
this, [this](bool toggled)
{
m_timelineUi->view->setDrawCache(toggled);
update(); // schedule repaint
});
connect(m_timelineUi->cbArm, &QCheckBox::toggled,
this, &TimelineWidget::signalArm);
connect(m_timelineUi->updateTimeline, &QPushButton::clicked,
this, &TimelineWidget::slotUpdateTimeline);
}
TimelineWidget::~TimelineWidget()
{
delete m_timelineUi;
}
void TimelineWidget::setLiveTime(qint64 time)
{
m_headerView->setLiveTime(time);
EventDelegate *ed = qobject_cast<EventDelegate *>(m_timelineUi->view->itemDelegate());
if (ed == nullptr)
{
qWarning() << "TimelineWidget::setLiveTime: ed == nullptr";
return;
}
ed->setLiveTime(time);
}
void TimelineWidget::hideDebugWidget()
{
m_timelineUi->debugWidget->hide();
m_timelineUi->lbMinMax->hide();
}
void TimelineWidget::knownRangesChanged(const QVector<Range> &newRanges)
{
EventDelegate *ed = qobject_cast<EventDelegate *>(m_timelineUi->view->itemDelegate());
if (ed == nullptr)
{
WARNING_LOG(m_logger) << "ed == nullptr";
return;
}
m_timelineUi->view->setKnownRanges(newRanges);
ed->setKnownRanges(newRanges);
m_timelineUi->view->refreshData(); // calls update in both views (itself & header)
}
void TimelineWidget::setMaxPos()
{
m_timelineUi->scrollbar->setValue(m_timelineUi->scrollbar->maximum());
}
int TimelineWidget::getMaxPos() const
{
return m_timelineUi->scrollbar->maximum();
}
int TimelineWidget::getPos() const
{
return m_timelineUi->scrollbar->value();
}
void TimelineWidget::setPos(qint64 pos)
{
m_timelineUi->scrollbar->setValue(pos / 1000); // in sec
}
void TimelineWidget::setPositionMarkerTime(const qint64 time)
{
m_timelineUi->view->slotSetPosMarkTime(time);
}
void TimelineWidget::slotUpdateTimeline() // called when either the scrollbar, zoom level or section is moved (refetch events!)
{
qint64 min = m_model->lowestPositionedItem();
qint64 max = m_model->highestPositionedItem();
if (m_timelineUi->cbMinMax->isChecked())
{
QString minmax(QStringLiteral("%1 (%2) - %3 (%4)")
.arg(QDateTime::fromMSecsSinceEpoch(min).toString())
.arg(min)
.arg(QDateTime::fromMSecsSinceEpoch(max).toString())
.arg(max));
m_timelineUi->lbMinMax->setText(minmax);
}
setTimelineRange(min, max);
emit signalRangeChanged();
}
void TimelineWidget::setTimelineModel(AbstractTimelineModel *model)
{
m_model = model;
connect(m_model, &AbstractTimelineModel::dataChanged,
this, &TimelineWidget::slotRefreshData /* no need to recalc all values, just the data changed, just render them */);
// redirect signalPlayback from model to widget (signal must now be connected to desired class from extern)
m_timelineUi->view->setTimelineModel(m_model);
// AddLine & ResetLineList
connect(m_timelineUi->indicator, &TimelineIndicator::signalAddLine,
m_timelineUi->view, &TimelineView::slotAddLine);
connect(m_timelineUi->indicator, &TimelineIndicator::signalResetLineList,
m_timelineUi->view, &TimelineView::slotResetLineList);
EventDelegate *ed = qobject_cast<EventDelegate *>(m_timelineUi->view->itemDelegate());
if (ed != nullptr)
{
connect(m_timelineUi->indicator, &TimelineIndicator::signalAddLine,
ed, &EventDelegate::slotAddLine);
connect(m_timelineUi->indicator, &TimelineIndicator::signalResetLineList,
ed, &EventDelegate::slotResetLineList);
}
m_timelineUi->view->expandToDepth(1);
m_headerView->resizeSection(0, m_headerView->minimumSectionSize()); // widget needs help to set minimum section size
m_headerView->resizeSection(1, m_headerView->minimumSectionSize()); // s.a.a
slotSetZoomFactor(m_timelineUi->indicator->zoomFactorPoints()); // initial z00m
}
qint64 TimelineWidget::getRangeMinimum() const
{
return EventTools::getAdjustedMin();
}
qint64 TimelineWidget::getRangeMaximum() const
{
return EventTools::getAdjustedMax();
}
void TimelineWidget::setTimelineRange(qint64 min, qint64 max) // also sets scrollbar range
{
m_min = min;
m_max = max;
if (m_min > 0 && m_max > 2) // only set scrollbar values if there are events
{
m_sbPos = EventTools::fromSecondsToMsecs(m_timelineUi->scrollbar->sliderPosition());
if (m_max <= m_min)
{
if (m_min > (10 * 60))
m_min -= (10 * 60); // be at least 10 minutes long (from the start until now)
}
else
{
qint64 diff = m_max - m_min;
if (diff < (10 * 60))
if (m_min > (10 * 60))
m_min -= (10 * 60); // be at least 10 minutes long (from the start until now) if the difference between min & max is lower than 10 minutes
}
// sadly we cannot use getBlockSizeTime here, as setValues would invalidate everything calculated by getBlockSizeTime, because it uses values calculated by setValues, which gets called next..
// else
// m_max = m_max - EventTools::s_diff + EventTools::getBlockSizeTime(0); // add one event width to the end, so that it is not cut off when live
// NOTE: every calculation done before the call in the next line invalidates every value calculated by EventTools !!
EventTools::setValues(m_min, m_max, m_sbPos, m_zoomFactor);
//qWarning() << "set scrollbar range" << m_min << m_max;
m_timelineUi->scrollbar->setRange(EventTools::fromMsecsToSeconds(EventTools::getTimelineMin()), EventTools::fromMsecsToSeconds(EventTools::getTimelineMax()));
if (m_sbPos < m_min) // sbPos can still be smaller than min
{
m_sbPos = m_min;
//qWarning() << "set slider pos" << m_sbPos;
m_timelineUi->scrollbar->setSliderPosition(EventTools::fromMsecsToSeconds(m_sbPos));
}
//qWarning() << "min" << EventTools::fromMsecsToSeconds(m_min) << "max" << EventTools::fromMsecsToSeconds(m_max) << "pagestep" << pagestep;
int pagestep = EventTools::fromMsecsToSeconds(EventTools::s_diff);
if (pagestep < 0)
pagestep = 1;
//qWarning() << "set scrollbar page step" << pagestep;
m_timelineUi->scrollbar->setPageStep(pagestep);
/*
qWarning() << "scrollbar: min" << m_timelineUi->scrollbar->minimum()
<< "max" << m_timelineUi->scrollbar->maximum()
<< "pagestep" << m_timelineUi->scrollbar->pageStep()
<< "value" << m_timelineUi->scrollbar->value();
*/
m_timelineUi->bsz->setText(QString::number(EventTools::getBlockSizeTime()));
}
refreshData();
}
void TimelineWidget::slotRefreshData()
{
refreshData();
}
void TimelineWidget::refreshData()
{
m_headerView->refreshData(); // calls update
m_timelineUi->indicator->refreshData(); // ONLY calls update
m_timelineUi->view->refreshData(); // calls update in both views (itself & header)
}
void TimelineWidget::slotSetZoomFactor(qint64 zoomFactor)
{
m_zoomFactor = zoomFactor;
slotUpdateTimeline(); // reuse method to set new pagestep of scrollbar FIMXE: method "refreshScrollBar" to do this?
}
void TimelineWidget::scrollBarFilter1(int )
{
scrollBarFilter2();
}
void TimelineWidget::scrollBarFilter2()
{
slotUpdateTimeline(); // updates all (views) containers
}
void TimelineWidget::slotSectionResized(int logicalIndex, int oldSize, int newSize)
{
(void)oldSize;
if (logicalIndex == 1) // event section [should always be called, even if col0 was resized, as the last column is always strechted, there would be no space for col0 to take that was not occupied by col1]
{
int viewScrollbarWidth = m_timelineUi->view->verticalScrollBar()->width();
m_timelineUi->scrollbar->setMinimumHeight(viewScrollbarWidth); // set scrollbar to same size as scrollbar in view
m_timelineUi->scrollbar->setMaximumHeight(viewScrollbarWidth); // set scrollbar to same size as scrollbar in view
m_timelineUi->scrollbar->setMaximumWidth(newSize);
m_timelineUi->scrollbar->setMinimumWidth(newSize);
EventTools::setSection(m_headerView->sectionPosition(1),
newSize /* as it was not yet set in m_headerView->sectionSize(1) ! */);
}
slotUpdateTimeline();
}
} // namespace LtsTimelineClassic
+156
View File
@@ -0,0 +1,156 @@
/**
* @RDE_HEADER@
* widget in MVC pattern which manages the entire timeline to add on a layout or widget
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#pragma once
#include <LtsTimelineClassic/ltstimelineclassic_export.h>
#include "Range.h"
#include <NGLLogger/INGLLogger.h>
#include <QWidget>
namespace Ui
{
class Timeline;
}
namespace LtsTimelineClassic
{
class AbstractTimelineModel;
class EventGraphic;
class TimelineHeaderView;
/**
* @brief widget in MVC pattern which manages the entire timeline to add on a layout or widget
*/
class LTSTIMELINECLASSIC_EXPORT TimelineWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief sets, creates, connects, etc. all needed components, classes and widgets
* @param parent parent qwidget to lie on
*/
explicit TimelineWidget(QWidget *parent = nullptr);
~TimelineWidget();
/**
* @brief sets the position marker at the position at time
* @param time time to set the position marker to
*/
void setPositionMarkerTime(const qint64 time);
qint64 getRangeMinimum() const;
qint64 getRangeMaximum() const;
void setPos(qint64 pos);
void setMaxPos();
int getPos() const;
int getMaxPos() const;
void knownRangesChanged(const QVector<Range> &newRanges);
void hideDebugWidget();
void setLiveTime(qint64 time);
private:
/**
* @brief sets new timeline boundaries and is called by updateTimeline() after the model has its data updated
* @param min new first event in timeline in seconds since epoch
* @param max new last event in timeline in seconds since epoch
*/
void setTimelineRange(qint64 min, qint64 max);
void refreshData();
NGL::INGLLogger &m_logger;
Ui::Timeline *m_timelineUi;
// could have used vars from EventTools
qint64 m_min;
qint64 m_max;
qint64 m_sbPos;
qint64 m_zoomFactor;
TimelineHeaderView *m_headerView;
AbstractTimelineModel *m_model;
signals:
/**
* @brief tells the parent of the timeline controller to load new events from 'from' to 'to'
* @param from x
* @param to y
*/
void signalLoadNewEvents(qint64 from, qint64 to);
/**
* @brief to output playback request to desired class
* @param eventSet EventSet to play
*/
//void signalPlayback(const NGLDAO::EventSet &eventSet);
void signalEventAction(EventGraphic *eg, int); // int => action type
void signalRangeChanged();
void signalArm(bool arm);
void signalZoomChanged();
public slots:
/**
* @brief filters the scrollbar events and gives new scrollbar position to necessary classes
* @param x scrollbar event
*/
void scrollBarFilter1(int x);
/**
* @brief gives new scrollbar position to necessary classes
*/
void scrollBarFilter2();
/**
* @brief sets new zoom factor
* @param zoomFactor zoom factor in percent points
*/
void slotSetZoomFactor(qint64 zoomFactor);
/**
* @brief sets new values to this class
* @param index affected column
* @param oldSize old width of column
* @param newSize new width of column
*/
void slotSectionResized(int logicalIndex, int oldSize, int newSize);
/**
* @brief set timeline model for the timeline widget's view
* @param model model of the view - of the widget
*/
void setTimelineModel(AbstractTimelineModel *model);
/**
* @brief slotUpdateTimeline updates all data of the timeline by setting the
* new range (if new), shoes new events (if any), etc.. and is emitted by
* the model after there is no new data anymore arriving, after data has arrived - saves resources and prevents flickering
*/
void slotUpdateTimeline();
void slotRefreshData();
};
} // namespace LtsTimelineClassic
+167
View File
@@ -0,0 +1,167 @@
<?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>628</width>
<height>393</height>
</rect>
</property>
<property name="windowTitle">
<string>Timeline</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,999,0,0,0">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="debugWidget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="cbDrawCache">
<property name="text">
<string>[draw cache]</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbArm">
<property name="text">
<string>[arm]</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cbMinMax">
<property name="text">
<string>[min/max]</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="updateTimeline">
<property name="text">
<string notr="true">[update timeline]</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Block size time:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="bsz">
<property name="text">
<string>TextLabel</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>
</layout>
</widget>
</item>
<item>
<widget class="LtsTimelineClassic::TimelineView" name="view" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QScrollBar" name="scrollbar">
<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="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="sliderPosition">
<number>0</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="LtsTimelineClassic::TimelineIndicator" name="indicator" native="true">
<property name="minimumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>30</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbMinMax">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsTimelineClassic::TimelineView</class>
<extends>QWidget</extends>
<header>LtsTimelineClassic/TimelineView.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LtsTimelineClassic::TimelineIndicator</class>
<extends>QWidget</extends>
<header>LtsTimelineClassic/TimelineIndicator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+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:
+88
View File
@@ -0,0 +1,88 @@
/**
* @RDE_HEADER@
* example main file for testing purpose
* $Id$
* $HeadURL$
* $Author$
* Created by Mike Wille, SLSE, 2014-10-01
*/
#include "TimelineWidget.h"
#include "TestTimelineModel.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <nglapplication/nglapplication.h>
#include <Services/ProjectDataManagement/pdm_enums.hpp>
#include <Services/ProjectDataManagement/pdm_translation.h>
#include <time.h>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
LtsTimelineClassic::TestTimelineModel *ttm;
void add(qint64 time_offset)
{
qint64 time = 1505292189000;
time += time_offset;
ttm->addTestEvent(new LtsTimelineClassic::Event(0, (int)ProjectDataManagement::EventType_def::Hit, time, 0, 0, 0, 0,
QStringList() << QStringLiteral("Test"),
LtsTimelineClassic::Event::drawCustomEventGraphicEllipse, QColor("green"), QString()));
}
int main(int argc, char *argv[])
{
qputenv("TDS_DEVEL", "yes, of course");
qWarning() << "LtsTimelineClassicTest";
NGLApplication app(argc, argv);
app.createLogger("Timeline", "log4cxx_timeline.props");
STARTUP_LOG(app.baseLogger());
int ret = 0;
#if 1
QWidget *widget = new QWidget();
widget->setMinimumWidth(500);
QVBoxLayout *layout = new QVBoxLayout();
widget->setLayout(layout);
LtsTimelineClassic::TimelineWidget *timelineWidget = new LtsTimelineClassic::TimelineWidget();
layout->addWidget(timelineWidget);
QHBoxLayout *btnLayout = new QHBoxLayout();
QWidget *btnLayoutWidget = new QWidget();
btnLayoutWidget->setLayout(btnLayout);
layout->addWidget(btnLayoutWidget);
btnLayout->addWidget(new QPushButton(QStringLiteral("Filter"))); // dummy
btnLayout->addWidget(new QPushButton(QStringLiteral("CSV Export"))); // dummy
btnLayout->addStretch(1);
widget->show();
ttm = new LtsTimelineClassic::TestTimelineModel();
timelineWidget->setTimelineModel(ttm);
//QObject::connect(timelineWidget, SIGNAL(signalEventAction(EventGraphic*,int)), ttm, SLOT(slotEventAction(EventGraphic*,int))); // make events clickable
add(0);
add(100 * 60 * 1000);
add(3000 * 60 * 1000);
ret = app.exec();
SHUTDOWN_LOG(app.baseLogger());
#endif
return ret;
}