CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxTimelineWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxTimelineWidget.h"
13 
14 #include <vtkPiecewiseFunction.h>
15 #include <QPainter>
16 #include <QToolTip>
17 #include <QMouseEvent>
18 #include "cxHelperWidgets.h"
19 #include "cxTime.h"
20 
21 
22 #include "cxTypeConversions.h"
23 
24 namespace cx
25 {
26 
28  QWidget(parent), mBorder(4), mStart(0), mStop(0), mPos(0), mCloseToGlyph(false), mTolerance_p(5)
29 {
30  mForward = vtkPiecewiseFunctionPtr::New();
31  mBackward = vtkPiecewiseFunctionPtr::New();
32 
33 // int s = 255;
34 // int v = 192;
35 // mEventColors.push_back(QColor::fromHsv(110, s, v));
36 // mEventColors.push_back(QColor::fromHsv(80, s, v));
37 // mEventColors.push_back(QColor::fromHsv(140, s, v));
38 // mEventColors.push_back(QColor::fromHsv(95, s, v));
39 // mEventColors.push_back(QColor::fromHsv(125, s, v));
40 
41  this->setFocusPolicy(Qt::StrongFocus);
42  this->setMouseTracking(true);
43 }
44 
46 {
47 
48 }
49 
50 void TimelineWidget::setPos(double pos)
51 {
52  mPos = pos;
53  this->update();
54  emit positionChanged();
55 }
56 
57 double TimelineWidget::getPos() const
58 {
59  return mPos;
60 }
61 
65 double TimelineWidget::findCompactedTime(double timeInterval, double totalUsedTime, double totalTime) const
66 {
67  // compact free space using t' = t * c, where
68  // c = w*c1 + (1-w)*c0
69  // w is this free space's fraction of total time
70  // c1 is compactingFactor1, set so that even a space using more than 99%
71  // of the total time will be reduced to 20%.
72  // c0 is compactingFactor0, set to 0.2. Used for small spaces.
73  // constant factor: apply even to small spaces
74  double compactingFactor0 = 0.2;
75  double compactingFactor1 = totalUsedTime/totalTime * 0.2;
76 
77  // find compacting factor c as described above
78  double w = timeInterval/totalTime;
79  double c = w*compactingFactor1 + (1-w)*compactingFactor0;
80  // use c to compact, but also set an absolute limit of fraction of total used time. This handles pauses of days or months.
81  double compactedTime = std::min(timeInterval * c, 0.2*totalUsedTime);
82 
83  return compactedTime;
84 }
85 
94 void TimelineWidget::createCompactingTransforms()
95 {
96  // identify empty zones
97 
98  std::vector<TimelineEvent> temp = mEvents;
99 
100  // grow
101  double fat = 2000; // additional space around each event.
102  for (unsigned i = 0; i < temp.size(); ++i)
103  {
104  temp[i].mDescription = "merged";
105  temp[i].mStartTime = std::max(temp[i].mStartTime - fat, mStart);
106  temp[i].mEndTime = std::min(temp[i].mEndTime + fat, mStop);
107  }
108 
109  // merge
110  for (unsigned i = 0; i < temp.size(); ++i)
111  {
112  // try to merge other events into this one
113 
114  for (unsigned j = i + 1; j < temp.size();)
115  {
116  if (temp[i].isOverlap(temp[j]))
117  {
118  temp[i].mStartTime = std::min(temp[i].mStartTime, temp[j].mStartTime);
119  temp[i].mEndTime = std::max(temp[i].mEndTime, temp[j].mEndTime);
120  temp.erase(temp.begin() + j);
121  }
122  else
123  {
124  ++j;
125  }
126  }
127  }
128 
129  // sort
130  std::sort(temp.begin(), temp.end());
131 
132  double totalUsedTime = 0;
133  for (unsigned i = 0; i < temp.size(); ++i)
134  totalUsedTime += temp[i].mEndTime - temp[i].mStartTime;
135  // fraction of total time used in events:
136 // double usageFactor = totalUsedTime / (mStop-mStart);
137 
138  // function from real time (x) to compact time (y)
139  mForward = vtkPiecewiseFunctionPtr::New();
140 
141  double y = mStart;
142  mForward->AddPoint(mStart, y);
143  double x_last = mStart;
144  for (unsigned i = 0; i < temp.size(); ++i)
145  {
146  // add first point
147  y = y + this->findCompactedTime(temp[i].mStartTime - x_last, totalUsedTime, mStop-mStart);
148  mForward->AddPoint(temp[i].mStartTime, y);
149  // add last point
150  y = y + temp[i].mEndTime - temp[i].mStartTime;
151  mForward->AddPoint(temp[i].mEndTime, y);
152 
153  x_last = temp[i].mEndTime;
154  }
155 
156  // add endpoint
157  y = y + this->findCompactedTime(mStop - x_last, totalUsedTime, mStop-mStart);
158  mForward->AddPoint(mStop, y);
159 
160  // normalize y:
161  double range[2];
162  mForward->GetRange(range);
163  double y_min = mForward->GetValue(range[0]);
164  double y_max = mForward->GetValue(range[1]);
165  for (int i = 0; i < mForward->GetSize(); ++i)
166  {
167  double val[4];
168  mForward->GetNodeValue(i, val);
169  val[1] = (val[1] - y_min) / (y_max - y_min);
170  mForward->SetNodeValue(i, val);
171  }
172 
173  // create inverse function:
174  mBackward = vtkPiecewiseFunctionPtr::New();
175  for (int i = 0; i < mForward->GetSize(); ++i)
176  {
177  double val[4];
178  mForward->GetNodeValue(i, val);
179  mBackward->AddPoint(val[1], val[0]);
180  }
181 
182  mNoncompactedIntervals = temp;
183 }
184 
185 void TimelineWidget::setEvents(std::vector<TimelineEvent> events)
186 {
187  mEvents = events;
188 
189  if (mEvents.empty())
190  return;
191 
192  this->createCompactingTransforms();
193 
194 // // // test: add to events to display
195 // std::copy(mNoncompactedIntervals.begin(), mNoncompactedIntervals.end(), std::back_inserter(mEvents));
196 
197  // identify continous events
198  for (unsigned i = 0; i < mEvents.size(); ++i)
199  {
200  if (mEvents[i].isSingular())
201  continue;
202 // if (!std::count(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mDescription))
203 // mContinousEvents.push_back(mEvents[i].mDescription);
204  if (!std::count(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mGroup))
205  mContinousEvents.push_back(mEvents[i].mGroup);
206  }
207 
208 }
209 
210 void TimelineWidget::setRange(double start, double stop)
211 {
212  mStart = start;
213  mStop = stop;
214 }
215 
219 int TimelineWidget::mapTime2PlotX(double time) const
220 {
221  double normalized = mForward->GetValue(time);
222 // double normalized = (time - mStart) / (mStop - mStart);
223  int retval = normalized * mPlotArea.width() + mPlotArea.left();
224  return retval;
225 }
226 
229 double TimelineWidget::mapPlotX2Time(int plotX) const
230 {
231  double normalized = double(plotX - mPlotArea.left()) / mPlotArea.width();
232  double retval = mBackward->GetValue(normalized);
233 // double retval = mStart + normalized*(mStop - mStart);
234  return retval;
235 }
236 
238 {
239  if (similar(mStart,mStop))
240  return;
241  QWidget::paintEvent(event);
242 
243  QPainter painter(this);
244 // QPen pointPen, pointLinePen;
245  painter.setRenderHint(QPainter::Antialiasing);
246  painter.setPen(Qt::NoPen);
247 
248  QColor gray0(240, 240, 240);
249  QColor gray01(220, 220, 220);
250  QColor gray1(200, 200, 200);
251  QColor gray2(170, 170, 170); // darker
252  QColor gray3(150, 150, 150); // even darker
253  QColor gray4(100, 100, 100); // even darker
254  QColor highlight(110, 214, 255); // color around highlighted circle in qslider on KDE.
255 
256  // Fill with white background color and grey plot area background color
257  QBrush brush(Qt::SolidPattern); // = painter.brush();
258  brush.setColor(gray2);
259  painter.setBrush(brush);
260 
261  painter.drawRoundedRect(this->mFullArea, 4, 4);
262 // brush.setColor(gray01);
263  brush.setColor(gray1);
264  painter.setBrush(brush);
265  painter.drawRoundedRect(this->mPlotArea, 4, 4);
266 
267  int margin = 1;
268 
269  // draw noncompacted interval
270  for (unsigned i = 0; i < mNoncompactedIntervals.size(); ++i)
271  {
272  int start_p = this->mapTime2PlotX(mNoncompactedIntervals[i].mStartTime);
273  int stop_p = this->mapTime2PlotX(mNoncompactedIntervals[i].mEndTime);
274  QColor color = gray01;
275  painter.fillRect(QRect(start_p, mPlotArea.top(), stop_p - start_p, mPlotArea.height()), color);
276  }
277 
278  // draw all continous events
279  for (unsigned i = 0; i < mEvents.size(); ++i)
280  {
281  if (!mContinousEvents.contains(mEvents[i].mGroup))
282  continue;
283  int start_p = this->mapTime2PlotX(mEvents[i].mStartTime);
284  int stop_p = this->mapTime2PlotX(mEvents[i].mEndTime);
285  int level = std::distance(mContinousEvents.begin(),
286  std::find(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mGroup));
287  int level_max = mContinousEvents.size();
288  int thisHeight = (mPlotArea.height()) / level_max - margin * (level_max - 1) / level_max;
289  int thisTop = mPlotArea.top() + level * thisHeight + level * margin;
290 
291 // QColor color = mEventColors[level % mEventColors.size()];
292  QColor color = mEvents[i].mColor;
293 
294  painter.fillRect(QRect(start_p, thisTop, stop_p - start_p, thisHeight), color);
295  }
296 
297  // draw all singular events
298  for (unsigned i = 0; i < mEvents.size(); ++i)
299  {
300  if (mContinousEvents.contains(mEvents[i].mGroup))
301  continue;
302 
303  int start_p = this->mapTime2PlotX(mEvents[i].mStartTime);
304 // int stop_p = this->mapTime2PlotX(mEvents[i].mEndTime);
305 
306  int glyphWidth = 3;
307  QRect rect(start_p - glyphWidth / 2, mPlotArea.top(), glyphWidth, mPlotArea.height());
308 
309  brush.setColor(QColor(50, 50, 50));
310  painter.setBrush(brush);
311  painter.drawRoundedRect(rect, 2, 2);
312 
313 // painter.fillRect(rect, gray4);
314  if (rect.width() > 2 && rect.height() > 2)
315  {
316  rect.adjust(1, 1, -1, -1);
317  painter.fillRect(rect, gray2);
318  }
319  }
320 
321  int offset_p = this->mapTime2PlotX(mPos);
322  QPolygonF glyph;
323  int z = 5;
324  int h = mPlotArea.height();
325  glyph.push_back(QPointF(-z, 0));
326  glyph.push_back(QPointF(z, 0));
327  glyph.push_back(QPointF(z, 0.7 * h));
328  glyph.push_back(QPointF(0, h));
329  glyph.push_back(QPointF(-z, 0.7 * h));
330  glyph.translate(offset_p, 0);
331  if (this->hasFocus() || mCloseToGlyph)
332  painter.setPen(highlight);
333  else
334  painter.setPen(gray4);
335 // QBrush brush(Qt::SolidPattern);// = painter.brush();
336 
337  QRadialGradient radialGrad(QPointF(offset_p, h / 3), 2 * h / 3);
338  radialGrad.setColorAt(0, gray0);
339 // radialGrad.setColorAt(0.5, Qt::blue);
340  radialGrad.setColorAt(1, gray2);
341 
342  brush = QBrush(radialGrad);
343 // brush.setColor(gray0);
344  painter.setBrush(brush);
345  painter.drawPolygon(glyph);
346 }
347 
348 void TimelineWidget::resizeEvent(QResizeEvent* evt)
349 {
350  QWidget::resizeEvent(evt);
351 
352  // Calculate areas
353  this->mFullArea = QRect(0, 0, width(), height());
354  this->mPlotArea = QRect(mBorder, mBorder, width() - mBorder * 2, height() - mBorder * 2);
355 }
356 
357 void TimelineWidget::setPositionFromScreenPos(int x, int y)
358 {
359  mPos = this->mapPlotX2Time(x);
360  this->update();
361  emit positionChanged();
362 }
363 
365 {
366  QWidget::mousePressEvent(event);
367 
368  if (event->button() == Qt::LeftButton)
369  {
370  this->setPositionFromScreenPos(event->x(), event->y());
371  }
372 }
374 {
375  QWidget::mouseReleaseEvent(event);
376 }
377 
379 {
380  if (event->type() == QEvent::ToolTip)
381  {
382  QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
383  if (!this->showHelp(helpEvent->pos()))
384  {
385  event->ignore();
386  }
387 
388  return true;
389  }
390  return QWidget::event(event);
391 }
392 
393 bool TimelineWidget::showHelp(QPoint pos)
394 {
395  double mouseTimePos = this->mapPlotX2Time(pos.x());
396  double tol_ms = this->mapPlotX2Time(pos.x()+mTolerance_p) - mouseTimePos;
397 
398  QStringList text;
399 
400  for (unsigned i = 0; i < mEvents.size(); ++i)
401  {
402  if (mEvents[i].isInside(mouseTimePos, tol_ms))
403  {
404  text << mEvents[i].mDescription;
405 // std::cout << "inside: " << mEvents[i].mDescription << " [" << mouseTimePos << "] " << mEvents[i].mStartTime << "-" << mEvents[i].mEndTime << " " << tol_ms << std::endl;
406  }
407  }
408 
409  if (text.isEmpty())
410  {
411  QToolTip::hideText();
412  return false;
413  }
414  else
415  {
416 // QToolTip::hideText();
417 // QToolTip::showText(this->mapToGlobal(pos), "", this); // hide - caused undefinable hiding of text after a sec.
418  QToolTip::showText(this->mapToGlobal(pos), text.join("\n"), this); // show in new place
419  return true;
420  }
421 }
422 
424 {
425  QWidget::mouseMoveEvent(event);
426 
427  if (event->buttons() == Qt::LeftButton)
428  {
429  this->setPositionFromScreenPos(event->x(), event->y());
430  }
431 
432  bool newCloseToGlyph = fabs((double)(this->mapTime2PlotX(mPos) - event->x())) < 5;
433  if (mCloseToGlyph != newCloseToGlyph)
434  this->update();
435 
436 // this->showHelp(event->pos());
437 
438  mCloseToGlyph = newCloseToGlyph;
439 }
440 
442 {
443  return QSize(100, 30);
444 }
445 
447 {
448  return QSize(100, 20);
449 }
450 
451 } /* namespace cx */
QSize minimumSizeHint() const
virtual bool event(QEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
virtual void paintEvent(QPaintEvent *event)
Reimplemented from superclass. Paints the transferfunction GUI.
virtual QSize sizeHint() const
virtual void resizeEvent(QResizeEvent *evt)
Reimplemented from superclass.
void setRange(double start, double stop)
virtual void mousePressEvent(QMouseEvent *event)
void setPos(double pos)
TimelineWidget(QWidget *parent)
double getPos() const
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
virtual void mouseMoveEvent(QMouseEvent *event)
void setEvents(std::vector< TimelineEvent > events)
Namespace for all CustusX production code.