CustusX  16.5.0-rc9
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include "cxTimelineWidget.h"
34 
35 #include <vtkPiecewiseFunction.h>
36 #include <QPainter>
37 #include <QToolTip>
38 #include <QMouseEvent>
39 #include "cxHelperWidgets.h"
40 #include "cxTime.h"
41 
42 
43 #include "cxTypeConversions.h"
44 
45 namespace cx
46 {
47 
49  QWidget(parent), mBorder(4), mStart(0), mStop(0), mPos(0), mCloseToGlyph(false), mTolerance_p(5)
50 {
51  mForward = vtkPiecewiseFunctionPtr::New();
52  mBackward = vtkPiecewiseFunctionPtr::New();
53 
54 // int s = 255;
55 // int v = 192;
56 // mEventColors.push_back(QColor::fromHsv(110, s, v));
57 // mEventColors.push_back(QColor::fromHsv(80, s, v));
58 // mEventColors.push_back(QColor::fromHsv(140, s, v));
59 // mEventColors.push_back(QColor::fromHsv(95, s, v));
60 // mEventColors.push_back(QColor::fromHsv(125, s, v));
61 
62  this->setFocusPolicy(Qt::StrongFocus);
63  this->setMouseTracking(true);
64 }
65 
67 {
68 
69 }
70 
71 void TimelineWidget::setPos(double pos)
72 {
73  mPos = pos;
74  this->update();
75  emit positionChanged();
76 }
77 
78 double TimelineWidget::getPos() const
79 {
80  return mPos;
81 }
82 
86 double TimelineWidget::findCompactedTime(double timeInterval, double totalUsedTime, double totalTime) const
87 {
88  // compact free space using t' = t * c, where
89  // c = w*c1 + (1-w)*c0
90  // w is this free space's fraction of total time
91  // c1 is compactingFactor1, set so that even a space using more than 99%
92  // of the total time will be reduced to 20%.
93  // c0 is compactingFactor0, set to 0.2. Used for small spaces.
94  // constant factor: apply even to small spaces
95  double compactingFactor0 = 0.2;
96  double compactingFactor1 = totalUsedTime/totalTime * 0.2;
97 
98  // find compacting factor c as described above
99  double w = timeInterval/totalTime;
100  double c = w*compactingFactor1 + (1-w)*compactingFactor0;
101  // use c to compact, but also set an absolute limit of fraction of total used time. This handles pauses of days or months.
102  double compactedTime = std::min(timeInterval * c, 0.2*totalUsedTime);
103 
104  return compactedTime;
105 }
106 
115 void TimelineWidget::createCompactingTransforms()
116 {
117  // identify empty zones
118 
119  std::vector<TimelineEvent> temp = mEvents;
120 
121  // grow
122  double fat = 2000; // additional space around each event.
123  for (unsigned i = 0; i < temp.size(); ++i)
124  {
125  temp[i].mDescription = "merged";
126  temp[i].mStartTime = std::max(temp[i].mStartTime - fat, mStart);
127  temp[i].mEndTime = std::min(temp[i].mEndTime + fat, mStop);
128  }
129 
130  // merge
131  for (unsigned i = 0; i < temp.size(); ++i)
132  {
133  // try to merge other events into this one
134 
135  for (unsigned j = i + 1; j < temp.size();)
136  {
137  if (temp[i].isOverlap(temp[j]))
138  {
139  temp[i].mStartTime = std::min(temp[i].mStartTime, temp[j].mStartTime);
140  temp[i].mEndTime = std::max(temp[i].mEndTime, temp[j].mEndTime);
141  temp.erase(temp.begin() + j);
142  }
143  else
144  {
145  ++j;
146  }
147  }
148  }
149 
150  // sort
151  std::sort(temp.begin(), temp.end());
152 
153  double totalUsedTime = 0;
154  for (unsigned i = 0; i < temp.size(); ++i)
155  totalUsedTime += temp[i].mEndTime - temp[i].mStartTime;
156  // fraction of total time used in events:
157 // double usageFactor = totalUsedTime / (mStop-mStart);
158 
159  // function from real time (x) to compact time (y)
160  mForward = vtkPiecewiseFunctionPtr::New();
161 
162  double y = mStart;
163  mForward->AddPoint(mStart, y);
164  double x_last = mStart;
165  for (unsigned i = 0; i < temp.size(); ++i)
166  {
167  // add first point
168  y = y + this->findCompactedTime(temp[i].mStartTime - x_last, totalUsedTime, mStop-mStart);
169  mForward->AddPoint(temp[i].mStartTime, y);
170  // add last point
171  y = y + temp[i].mEndTime - temp[i].mStartTime;
172  mForward->AddPoint(temp[i].mEndTime, y);
173 
174  x_last = temp[i].mEndTime;
175  }
176 
177  // add endpoint
178  y = y + this->findCompactedTime(mStop - x_last, totalUsedTime, mStop-mStart);
179  mForward->AddPoint(mStop, y);
180 
181  // normalize y:
182  double range[2];
183  mForward->GetRange(range);
184  double y_min = mForward->GetValue(range[0]);
185  double y_max = mForward->GetValue(range[1]);
186  for (int i = 0; i < mForward->GetSize(); ++i)
187  {
188  double val[4];
189  mForward->GetNodeValue(i, val);
190  val[1] = (val[1] - y_min) / (y_max - y_min);
191  mForward->SetNodeValue(i, val);
192  }
193 
194  // create inverse function:
195  mBackward = vtkPiecewiseFunctionPtr::New();
196  for (int i = 0; i < mForward->GetSize(); ++i)
197  {
198  double val[4];
199  mForward->GetNodeValue(i, val);
200  mBackward->AddPoint(val[1], val[0]);
201  }
202 
203  mNoncompactedIntervals = temp;
204 }
205 
206 void TimelineWidget::setEvents(std::vector<TimelineEvent> events)
207 {
208  mEvents = events;
209 
210  if (mEvents.empty())
211  return;
212 
213  this->createCompactingTransforms();
214 
215 // // // test: add to events to display
216 // std::copy(mNoncompactedIntervals.begin(), mNoncompactedIntervals.end(), std::back_inserter(mEvents));
217 
218  // identify continous events
219  for (unsigned i = 0; i < mEvents.size(); ++i)
220  {
221  if (mEvents[i].isSingular())
222  continue;
223 // if (!std::count(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mDescription))
224 // mContinousEvents.push_back(mEvents[i].mDescription);
225  if (!std::count(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mGroup))
226  mContinousEvents.push_back(mEvents[i].mGroup);
227  }
228 
229 }
230 
231 void TimelineWidget::setRange(double start, double stop)
232 {
233  mStart = start;
234  mStop = stop;
235 }
236 
240 int TimelineWidget::mapTime2PlotX(double time) const
241 {
242  double normalized = mForward->GetValue(time);
243 // double normalized = (time - mStart) / (mStop - mStart);
244  int retval = normalized * mPlotArea.width() + mPlotArea.left();
245  return retval;
246 }
247 
250 double TimelineWidget::mapPlotX2Time(int plotX) const
251 {
252  double normalized = double(plotX - mPlotArea.left()) / mPlotArea.width();
253  double retval = mBackward->GetValue(normalized);
254 // double retval = mStart + normalized*(mStop - mStart);
255  return retval;
256 }
257 
258 void TimelineWidget::paintEvent(QPaintEvent* event)
259 {
260  if (similar(mStart,mStop))
261  return;
262  QWidget::paintEvent(event);
263 
264  QPainter painter(this);
265 // QPen pointPen, pointLinePen;
266  painter.setRenderHint(QPainter::Antialiasing);
267  painter.setPen(Qt::NoPen);
268 
269  QColor gray0(240, 240, 240);
270  QColor gray01(220, 220, 220);
271  QColor gray1(200, 200, 200);
272  QColor gray2(170, 170, 170); // darker
273  QColor gray3(150, 150, 150); // even darker
274  QColor gray4(100, 100, 100); // even darker
275  QColor highlight(110, 214, 255); // color around highlighted circle in qslider on KDE.
276 
277  // Fill with white background color and grey plot area background color
278  QBrush brush(Qt::SolidPattern); // = painter.brush();
279  brush.setColor(gray2);
280  painter.setBrush(brush);
281 
282  painter.drawRoundedRect(this->mFullArea, 4, 4);
283 // brush.setColor(gray01);
284  brush.setColor(gray1);
285  painter.setBrush(brush);
286  painter.drawRoundedRect(this->mPlotArea, 4, 4);
287 
288  int margin = 1;
289 
290  // draw noncompacted interval
291  for (unsigned i = 0; i < mNoncompactedIntervals.size(); ++i)
292  {
293  int start_p = this->mapTime2PlotX(mNoncompactedIntervals[i].mStartTime);
294  int stop_p = this->mapTime2PlotX(mNoncompactedIntervals[i].mEndTime);
295  QColor color = gray01;
296  painter.fillRect(QRect(start_p, mPlotArea.top(), stop_p - start_p, mPlotArea.height()), color);
297  }
298 
299  // draw all continous events
300  for (unsigned i = 0; i < mEvents.size(); ++i)
301  {
302  if (!mContinousEvents.contains(mEvents[i].mGroup))
303  continue;
304  int start_p = this->mapTime2PlotX(mEvents[i].mStartTime);
305  int stop_p = this->mapTime2PlotX(mEvents[i].mEndTime);
306  int level = std::distance(mContinousEvents.begin(),
307  std::find(mContinousEvents.begin(), mContinousEvents.end(), mEvents[i].mGroup));
308  int level_max = mContinousEvents.size();
309  int thisHeight = (mPlotArea.height()) / level_max - margin * (level_max - 1) / level_max;
310  int thisTop = mPlotArea.top() + level * thisHeight + level * margin;
311 
312 // QColor color = mEventColors[level % mEventColors.size()];
313  QColor color = mEvents[i].mColor;
314 
315  painter.fillRect(QRect(start_p, thisTop, stop_p - start_p, thisHeight), color);
316  }
317 
318  // draw all singular events
319  for (unsigned i = 0; i < mEvents.size(); ++i)
320  {
321  if (mContinousEvents.contains(mEvents[i].mGroup))
322  continue;
323 
324  int start_p = this->mapTime2PlotX(mEvents[i].mStartTime);
325 // int stop_p = this->mapTime2PlotX(mEvents[i].mEndTime);
326 
327  int glyphWidth = 3;
328  QRect rect(start_p - glyphWidth / 2, mPlotArea.top(), glyphWidth, mPlotArea.height());
329 
330  brush.setColor(QColor(50, 50, 50));
331  painter.setBrush(brush);
332  painter.drawRoundedRect(rect, 2, 2);
333 
334 // painter.fillRect(rect, gray4);
335  if (rect.width() > 2 && rect.height() > 2)
336  {
337  rect.adjust(1, 1, -1, -1);
338  painter.fillRect(rect, gray2);
339  }
340  }
341 
342  int offset_p = this->mapTime2PlotX(mPos);
343  QPolygonF glyph;
344  int z = 5;
345  int h = mPlotArea.height();
346  glyph.push_back(QPointF(-z, 0));
347  glyph.push_back(QPointF(z, 0));
348  glyph.push_back(QPointF(z, 0.7 * h));
349  glyph.push_back(QPointF(0, h));
350  glyph.push_back(QPointF(-z, 0.7 * h));
351  glyph.translate(offset_p, 0);
352  if (this->hasFocus() || mCloseToGlyph)
353  painter.setPen(highlight);
354  else
355  painter.setPen(gray4);
356 // QBrush brush(Qt::SolidPattern);// = painter.brush();
357 
358  QRadialGradient radialGrad(QPointF(offset_p, h / 3), 2 * h / 3);
359  radialGrad.setColorAt(0, gray0);
360 // radialGrad.setColorAt(0.5, Qt::blue);
361  radialGrad.setColorAt(1, gray2);
362 
363  brush = QBrush(radialGrad);
364 // brush.setColor(gray0);
365  painter.setBrush(brush);
366  painter.drawPolygon(glyph);
367 }
368 
369 void TimelineWidget::resizeEvent(QResizeEvent* evt)
370 {
371  QWidget::resizeEvent(evt);
372 
373  // Calculate areas
374  this->mFullArea = QRect(0, 0, width(), height());
375  this->mPlotArea = QRect(mBorder, mBorder, width() - mBorder * 2, height() - mBorder * 2);
376 }
377 
378 void TimelineWidget::setPositionFromScreenPos(int x, int y)
379 {
380  mPos = this->mapPlotX2Time(x);
381  this->update();
382  emit positionChanged();
383 }
384 
385 void TimelineWidget::mousePressEvent(QMouseEvent* event)
386 {
387  QWidget::mousePressEvent(event);
388 
389  if (event->button() == Qt::LeftButton)
390  {
391  this->setPositionFromScreenPos(event->x(), event->y());
392  }
393 }
394 void TimelineWidget::mouseReleaseEvent(QMouseEvent* event)
395 {
396  QWidget::mouseReleaseEvent(event);
397 }
398 
399 bool TimelineWidget::event(QEvent *event)
400 {
401  if (event->type() == QEvent::ToolTip)
402  {
403  QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
404  if (!this->showHelp(helpEvent->pos()))
405  {
406  event->ignore();
407  }
408 
409  return true;
410  }
411  return QWidget::event(event);
412 }
413 
414 bool TimelineWidget::showHelp(QPoint pos)
415 {
416  double mouseTimePos = this->mapPlotX2Time(pos.x());
417  double tol_ms = this->mapPlotX2Time(pos.x()+mTolerance_p) - mouseTimePos;
418 
419  QStringList text;
420 
421  for (unsigned i = 0; i < mEvents.size(); ++i)
422  {
423  if (mEvents[i].isInside(mouseTimePos, tol_ms))
424  {
425  text << mEvents[i].mDescription;
426 // std::cout << "inside: " << mEvents[i].mDescription << " [" << mouseTimePos << "] " << mEvents[i].mStartTime << "-" << mEvents[i].mEndTime << " " << tol_ms << std::endl;
427  }
428  }
429 
430  if (text.isEmpty())
431  {
432  QToolTip::hideText();
433  return false;
434  }
435  else
436  {
437 // QToolTip::hideText();
438 // QToolTip::showText(this->mapToGlobal(pos), "", this); // hide - caused undefinable hiding of text after a sec.
439  QToolTip::showText(this->mapToGlobal(pos), text.join("\n"), this); // show in new place
440  return true;
441  }
442 }
443 
444 void TimelineWidget::mouseMoveEvent(QMouseEvent* event)
445 {
446  QWidget::mouseMoveEvent(event);
447 
448  if (event->buttons() == Qt::LeftButton)
449  {
450  this->setPositionFromScreenPos(event->x(), event->y());
451  }
452 
453  bool newCloseToGlyph = fabs((double)(this->mapTime2PlotX(mPos) - event->x())) < 5;
454  if (mCloseToGlyph != newCloseToGlyph)
455  this->update();
456 
457 // this->showHelp(event->pos());
458 
459  mCloseToGlyph = newCloseToGlyph;
460 }
461 
463 {
464  return QSize(100, 30);
465 }
466 
468 {
469  return QSize(100, 20);
470 }
471 
472 } /* 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)
bool similar(const DoubleBoundingBox3D &a, const DoubleBoundingBox3D &b, double tol)
void setPos(double pos)
TimelineWidget(QWidget *parent)
double getPos() const
virtual void mouseMoveEvent(QMouseEvent *event)
void setEvents(std::vector< TimelineEvent > events)