kwave  18.07.70
OverViewWidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  OverViewWidget.cpp - horizontal slider with overview over a signal
3  -------------------
4  begin : Tue Oct 21 2000
5  copyright : (C) 2000 by Thomas Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <math.h>
21 
22 #include <QApplication>
23 #include <QMouseEvent>
24 #include <QPaintEvent>
25 #include <QPainter>
26 #include <QResizeEvent>
27 #include <QVBoxLayout>
28 
29 #include "libkwave/Label.h"
30 #include "libkwave/MetaDataList.h"
31 #include "libkwave/Sample.h"
32 #include "libkwave/SignalManager.h"
33 #include "libkwave/String.h"
34 #include "libkwave/Utils.h"
35 
36 #include "libgui/OverViewWidget.h"
37 
42 #define REPAINT_INTERVAL 250
43 
48 #define REPAINT_INTERVAL_FAST 50
49 
50 #define BAR_BACKGROUND palette().mid().color()
51 #define BAR_FOREGROUND palette().light().color()
52 
53 //***************************************************************************
54 //***************************************************************************
56  Kwave::OverViewWidget *overview)
57  :QThread(), m_overview(overview)
58 {
59 }
60 
61 //***************************************************************************
63 {
64  Q_ASSERT(!isRunning());
65 }
66 
67 //***************************************************************************
69 {
71 }
72 
73 //***************************************************************************
74 //***************************************************************************
76  QWidget *parent)
77  :Kwave::ImageView(parent), m_view_offset(0), m_view_width(0),
80  m_cache(signal, 0, 0, Q_NULLPTR), m_repaint_timer(), m_labels(),
81  m_worker_thread(this)
82 {
83  // check: start() must be called from the GUI thread only!
84  Q_ASSERT(this->thread() == QThread::currentThread());
85  Q_ASSERT(this->thread() == qApp->thread());
86 
87  // update the bitmap if the cache has changed
88  connect(&m_cache, SIGNAL(changed()),
89  this, SLOT(overviewChanged()));
90 
91  // connect repaint timer
92  connect(&m_repaint_timer, SIGNAL(timeout()),
93  this, SLOT(refreshBitmap()));
94 
95  // get informed about selection changes
96  connect(&(signal.selection()),
97  SIGNAL(changed(sample_index_t,sample_index_t)),
98  this,
100 
101  // get informed about meta data changes
102  connect(&signal, SIGNAL(sigMetaDataChanged(Kwave::MetaDataList)),
103  this, SLOT(metaDataChanged(Kwave::MetaDataList)));
104 
105  // transport the image calculated in a background thread
106  // through the signal/slot mechanism
107  connect(this, SIGNAL(newImage(QImage)),
108  this, SLOT(setImage(QImage)),
109  Qt::AutoConnection);
110 
111  setMouseTracking(true);
112 }
113 
114 //***************************************************************************
116 {
117  // check: start() must be called from the GUI thread only!
118  Q_ASSERT(this->thread() == QThread::currentThread());
119  Q_ASSERT(this->thread() == qApp->thread());
120 
121  m_repaint_timer.stop();
122  m_worker_thread.wait(/* 100 * REPAINT_INTERVAL */);
123 }
124 
125 //***************************************************************************
127 {
128  mousePressEvent(e);
129 }
130 
131 //***************************************************************************
133 {
134  Q_ASSERT(e);
135  if (!e) return;
136  if (e->buttons() != Qt::LeftButton) {
137  e->ignore();
138  return;
139  }
140 
141  // move the clicked position to the center of the viewport
142  sample_index_t offset = pixels2offset(e->x());
143  if (offset != m_last_offset) {
144  sample_index_t half = (m_view_width / 2);
145  offset = (offset > half) ? (offset - half) : 0;
146  m_last_offset = offset;
147  emit valueChanged(offset);
148  }
149  e->accept();
150 }
151 
152 //***************************************************************************
154 {
155  Q_ASSERT(e);
156  if (!e) return;
157  if (e->button() != Qt::LeftButton) {
158  e->ignore();
159  return;
160  }
161 
162  // move the clicked position to the center of the viewport
163  sample_index_t offset = pixels2offset(e->x());
164  if (offset != m_last_offset) {
165  m_last_offset = offset;
166  emit valueChanged(offset);
167  }
168 
169  if (e->modifiers() == Qt::NoModifier) {
170  // double click without shift => zoom in
171  emit sigCommand(_("view:zoom_in()"));
172  } else if (e->modifiers() == Qt::ShiftModifier) {
173  // double click with shift => zoom out
174  emit sigCommand(_("view:zoom_out()"));
175  }
176 
177  e->accept();
178 }
179 
180 //***************************************************************************
182 {
183  int width = this->width();
184  if (!width) return 0;
185 
186  if (pixels < 0) pixels = 0;
187  double zoom = static_cast<double>(m_signal_length - 1) /
188  static_cast<double>(width - 1);
189  sample_index_t offset = static_cast<sample_index_t>(rint(
190  static_cast<double>(pixels) * zoom));
191  return offset;
192 }
193 
194 //***************************************************************************
196  sample_index_t viewport,
197  sample_index_t total)
198 {
199  m_view_offset = offset;
200  m_view_width = viewport;
201  m_signal_length = total;
202 
203  overviewChanged();
204 }
205 
206 //***************************************************************************
208  sample_index_t length)
209 {
210  m_selection_start = offset;
211  m_selection_length = length;
212 
213  overviewChanged();
214 }
215 
216 //***************************************************************************
218 {
219  refreshBitmap();
220 }
221 
222 //***************************************************************************
224 {
225  return QSize(30, 30);
226 }
227 
228 //***************************************************************************
230 {
231  return minimumSize();
232 }
233 
234 //***************************************************************************
236 {
237  // check: start() must be called from the GUI thread only!
238  Q_ASSERT(this->thread() == QThread::currentThread());
239  Q_ASSERT(this->thread() == qApp->thread());
240 
241  if (m_repaint_timer.isActive()) {
242  // repainting is inhibited -> wait until the
243  // repaint timer is elapsed
244  return;
245  } else {
246  // repaint once and once later...
247  refreshBitmap();
248 
249  // start the repaint timer
250  m_repaint_timer.setSingleShot(true);
252  }
253 }
254 
255 //***************************************************************************
257 {
258  // check: start() must be called from the GUI thread only!
259  Q_ASSERT(this->thread() == QThread::currentThread());
260  Q_ASSERT(this->thread() == qApp->thread());
261 
262  m_labels = Kwave::LabelList(meta);
263 
264  // only re-start the repaint timer, this hides some GUI update artifacts
265  if (!m_repaint_timer.isActive()) {
266  m_repaint_timer.stop();
267  m_repaint_timer.setSingleShot(true);
269  }
270 }
271 
272 //***************************************************************************
274 {
275  // check: start() must be called from the GUI thread only!
276  Q_ASSERT(this->thread() == QThread::currentThread());
277  Q_ASSERT(this->thread() == qApp->thread());
278 
279  const sample_index_t old_pos = m_cursor_position;
280  const sample_index_t new_pos = pos;
281 
282  if (new_pos == old_pos) return; // no change
283  m_cursor_position = new_pos;
284 
285  if (qMax(old_pos, new_pos) != SAMPLE_INDEX_MAX) {
286  // check for change in pixel units
289  // showing deleted space after signal
290  length = m_view_offset + m_view_width;
291  }
292  if (!length) return;
293  const double scale = static_cast<double>(width()) /
294  static_cast<double>(length);
295  const int old_pixel_pos = Kwave::toInt(
296  static_cast<double>(old_pos) * scale);
297  const int new_pixel_pos = Kwave::toInt(
298  static_cast<double>(new_pos) * scale);
299  if (old_pixel_pos == new_pixel_pos) return;
300  }
301 
302  // some update is required, start the repaint timer in quick mode
303  if (!m_repaint_timer.isActive() ||
304  (m_repaint_timer.interval() != REPAINT_INTERVAL_FAST))
305  {
306  m_repaint_timer.stop();
307  m_repaint_timer.setSingleShot(true);
309  }
310 }
311 
312 //***************************************************************************
313 void Kwave::OverViewWidget::drawMark(QPainter &p, int x, int height,
314  QColor color)
315 {
316  QPolygon mark;
317  const int w = 5;
318  const int y = (height - 1);
319 
320  p.setCompositionMode(QPainter::CompositionMode_SourceOver);
321  color.setAlpha(100);
322  p.setBrush(QBrush(color));
323  p.setPen(QPen(Qt::black));
324 
325  mark.setPoints(3, x - w, 0, x + w, 0, x, w); // upper
326  p.drawPolygon(mark);
327  mark.setPoints(3, x - w, y, x + w, y, x, y - w); // lower
328  p.drawPolygon(mark);
329 }
330 
331 //***************************************************************************
333 {
334  // check: start() must be called from the GUI thread only!
335  Q_ASSERT(this->thread() == QThread::currentThread());
336  Q_ASSERT(this->thread() == qApp->thread());
337 
338  if (m_worker_thread.isRunning()) {
339  // (re)start the repaint timer if the worker thread is still
340  // running, try again later...
341  m_repaint_timer.stop();
342  m_repaint_timer.setSingleShot(true);
344  } else {
345  // start the calculation in a background thread
346  m_worker_thread.start(QThread::IdlePriority);
347  }
348 }
349 
350 //***************************************************************************
352 {
355  // showing deleted space after signal
356  length = m_view_offset + m_view_width;
357  }
358 
359  int width = this->width();
360  int height = this->height();
361  if (!width || !height || !m_view_width || !length)
362  return;
363 
364  const double scale = static_cast<double>(width) /
365  static_cast<double>(length);
366  const int bitmap_width = Kwave::toInt(m_signal_length * scale);
367 
368  // let the bitmap be updated from the cache
369  QImage bitmap = m_cache.getOverView(bitmap_width, height,
371 
372  // draw the bitmap (converted to QImage)
373  QImage image(width, height, QImage::Format_ARGB32_Premultiplied);
374  QPainter p;
375  p.begin(&image);
376  p.fillRect(rect(), BAR_BACKGROUND);
377  p.drawImage(0, 0, bitmap);
378 
379  // highlight the selection
380  if ((m_selection_length > 1) && m_signal_length)
381  {
382  int first = Kwave::toInt(
383  static_cast<double>(m_selection_start) * scale);
384  int len = Kwave::toInt(
385  static_cast<double>(m_selection_length) * scale);
386  if (len < 1) len = 1;
387 
388  // draw the selection as rectangle
389  QBrush hilight(Qt::yellow);
390  hilight.setStyle(Qt::SolidPattern);
391  p.setBrush(hilight);
392  p.setPen(QPen(Qt::yellow));
393  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
394  p.drawRect(first, 0, len, height);
395 
396  // marks at start and end of selection
397  drawMark(p, first, height, Qt::blue);
398  drawMark(p, first + len, height, Qt::blue);
399  }
400 
401  // draw labels
402  int last_label_pos = width + 1;
403  foreach (const Kwave::Label &label, m_labels) {
404  sample_index_t pos = label.pos();
405  int x = Kwave::toInt(static_cast<double>(pos) * scale);
406 
407  // position must differ from the last one, otherwise we
408  // would wipe out the last one with XOR mode
409  if (x == last_label_pos) continue;
410 
411  // draw a line for each label
412  p.setPen(QPen(Qt::cyan));
413  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
414  p.drawLine(x, 0, x, height);
415  drawMark(p, x, height, Qt::cyan);
416 
417  last_label_pos = x;
418  }
419 
420  // draw playback position
422  const sample_index_t pos = m_cursor_position;
423  int x = Kwave::toInt(static_cast<double>(pos) * scale);
424 
425  // draw a line for the playback position
426  QPen pen(Qt::yellow);
427  pen.setWidth(5);
428  p.setPen(pen);
429  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
430  p.drawLine(x, 0, x, height);
431  drawMark(p, x, height, Qt::cyan);
432  }
433 
434  // dim the currently invisible parts
436  {
437  QColor color = BAR_BACKGROUND;
438  color.setAlpha(128);
439  QBrush out_of_view(color);
440  out_of_view.setStyle(Qt::SolidPattern);
441  p.setBrush(out_of_view);
442  p.setPen(QPen(color));
443  p.setCompositionMode(QPainter::CompositionMode_SourceOver);
444 
445  if (m_view_offset > 0) {
446  int x = Kwave::toInt(static_cast<double>(m_view_offset) * scale);
447  p.drawRect(0, 0, x, height);
448  }
449 
451  int x = Kwave::toInt(
452  static_cast<double>(m_view_offset + m_view_width) * scale);
453  p.drawRect(x, 0, width - x, height);
454  }
455  }
456 
457  p.end();
458 
459  // update the widget with the overview
460  emit newImage(image);
461 }
462 
463 //***************************************************************************
464 //***************************************************************************
virtual void mouseDoubleClickEvent(QMouseEvent *e) Q_DECL_OVERRIDE
virtual ~WorkerThread() Q_DECL_OVERRIDE
virtual QImage getOverView(int width, int height, const QColor &fg, const QColor &bg, double gain=1.0)
Definition: App.h:33
virtual QSize sizeHint() const Q_DECL_OVERRIDE
void valueChanged(sample_index_t new_value)
void newImage(QImage image)
#define REPAINT_INTERVAL_FAST
#define REPAINT_INTERVAL
Kwave::Selection & selection()
virtual sample_index_t pos() const
Definition: Label.cpp:56
sample_index_t m_cursor_position
quint64 sample_index_t
Definition: Sample.h:28
Kwave::OverViewCache m_cache
void setRange(sample_index_t offset, sample_index_t viewport, sample_index_t total)
void drawMark(QPainter &p, int x, int height, QColor color)
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE
sample_index_t m_view_width
WorkerThread(Kwave::OverViewWidget *widget)
Kwave::LabelList m_labels
sample_index_t pixels2offset(int pixels)
Kwave::OverViewWidget * m_overview
sample_index_t m_last_offset
#define BAR_BACKGROUND
int toInt(T x)
Definition: Utils.h:127
void metaDataChanged(Kwave::MetaDataList meta)
virtual ~OverViewWidget() Q_DECL_OVERRIDE
virtual void run() Q_DECL_OVERRIDE
sample_index_t m_selection_length
sample_index_t m_view_offset
sample_index_t m_selection_start
void sigCommand(const QString &command)
void showCursor(sample_index_t pos=SAMPLE_INDEX_MAX)
virtual void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE
#define _(m)
Definition: memcpy.c:66
virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE
WorkerThread m_worker_thread
sample_index_t m_signal_length
void setImage(QImage image)
Definition: ImageView.cpp:75
OverViewWidget(Kwave::SignalManager &signal, QWidget *parent=Q_NULLPTR)
#define SAMPLE_INDEX_MAX
Definition: Sample.h:31
virtual QSize minimumSize() const
void setSelection(sample_index_t offset, sample_index_t length)
static double rect(double param)
Definition: Functions.cpp:29
#define BAR_FOREGROUND