kwave  18.07.70
TrackView.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  TrackView.cpp - signal views that shows the track in time space
3  -------------------
4  begin : Sat Jan 30 2010
5  copyright : (C) 2010 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 <QtGlobal>
21 #include <QIcon>
22 #include <QMenu>
23 #include <QPainter>
24 #include <QPalette>
25 #include <QResizeEvent>
26 #include <QTime>
27 #include <QVBoxLayout>
28 
29 #include "libkwave/Label.h"
30 #include "libkwave/LabelList.h"
31 #include "libkwave/SignalManager.h"
32 #include "libkwave/String.h"
33 #include "libkwave/Track.h"
34 #include "libkwave/Utils.h"
35 
36 #include "libgui/LabelItem.h"
39 #include "libgui/SelectionItem.h"
40 #include "libgui/TrackView.h"
41 #include "libgui/ViewItem.h"
42 
44 #define MINIMUM_HEIGHT 100
45 
46 //***************************************************************************
47 Kwave::TrackView::TrackView(QWidget *parent, QWidget *controls,
48  Kwave::SignalManager *signal_manager,
49  Kwave::Track *track)
50  :Kwave::SignalView(parent, controls, signal_manager,
51  Kwave::SignalView::AboveTrackTop),
52  m_pixmap(*track),
53  m_last_width(-1),
54  m_last_height(-1),
55  m_image(),
56  m_img_signal(),
57  m_img_selection(),
58  m_img_markers(),
59  m_img_signal_needs_refresh(true),
60  m_img_selection_needs_refresh(true),
61  m_img_markers_needs_refresh(true),
62  m_mouse_click_position(0),
63  m_cursor_pos(SAMPLE_INDEX_MAX)
64 {
65  setMinimumSize(400, MINIMUM_HEIGHT);
66 
67  // trigger a repaint request when the signal has been modified
68  connect(&m_pixmap, SIGNAL(sigModified()),
69  this, SLOT(refreshSignalLayer()));
70 
71  if (controls) {
72  // add the channel controls, for "enabled" / "disabled"
73 
74  QVBoxLayout *layout = new QVBoxLayout(controls);
75  Q_ASSERT(layout);
76  if (!layout) return;
77 
79  new(std::nothrow) Kwave::MultiStateWidget(Q_NULLPTR, 0);
80  Q_ASSERT(msw);
81  if (!msw) {
82  delete layout;
83  return;
84  }
85 
86  // add a bitmap for off (0 and on (1)
87  msw->addPixmap(_("light_off.xpm"));
88  msw->addPixmap(_("light_on.xpm"));
89 
90  // connect widget <-> track
91  connect(
92  msw, SIGNAL(clicked(int)),
93  track, SLOT(toggleSelection())
94  );
95  connect(
96  track, SIGNAL(sigSelectionChanged(bool)),
97  msw, SLOT(switchState(bool))
98  );
99 
100  msw->setMinimumSize(20, 20);
101  msw->switchState(track->selected());
102  layout->addWidget(msw);
103  }
104 
105  // get informed about meta data changes
106  connect(signal_manager, SIGNAL(
107  sigMetaDataChanged(Kwave::MetaDataList)),
108  this, SLOT(refreshMarkersLayer()),
109  Qt::QueuedConnection);
110 
111  // get informed about selection changes
112  connect(&(signal_manager->selection()),
113  SIGNAL(changed(sample_index_t,sample_index_t)),
114  this,
115  SLOT(refreshSelectionLayer()));
116 
117  // update the playback position
118  connect(&(signal_manager->playbackController()),
119  SIGNAL(sigPlaybackPos(sample_index_t)),
120  this,
121  SLOT(showCursor(sample_index_t)));
122  connect(&(signal_manager->playbackController()),
123  SIGNAL(sigPlaybackStopped()),
124  this,
125  SLOT(showCursor()));
126 
127  // update when the track selection changed
128  connect(track, SIGNAL(sigSelectionChanged(bool)),
129  this, SLOT(refreshSignalLayer()));
130 }
131 
132 //***************************************************************************
134 {
135 }
136 
137 //***************************************************************************
139 {
140 // qDebug("Kwave::TrackView[%d]::refresh()", track());
141  m_pixmap.repaint();
142  repaint();
143 }
144 
145 //***************************************************************************
147 {
148  Q_ASSERT(zoom >= 0.0);
150  m_pixmap.setZoom(zoom);
151  m_pixmap.setOffset(offset);
152  if (m_pixmap.isModified()) {
154  }
155 }
156 
157 //***************************************************************************
159 {
160  const int old_height = this->height();
161  const double old_zoom = verticalZoom();
162 
163  if (old_height > MINIMUM_HEIGHT * old_zoom) {
164  // stretched mode
165  zoom *= double(old_height) / (old_zoom * double(MINIMUM_HEIGHT));
166  }
167 
169  setMinimumHeight(Kwave::toInt(MINIMUM_HEIGHT * zoom));
170  emit contentSizeChanged();
171 }
172 
173 //***************************************************************************
174 QSharedPointer<Kwave::ViewItem> Kwave::TrackView::findItem(const QPoint &pos)
175 {
176  QSharedPointer<Kwave::ViewItem> item =
177  QSharedPointer<Kwave::ViewItem>(Q_NULLPTR);
178  Q_ASSERT(m_signal_manager);
179  if (!m_signal_manager) return item;
180 
181  const double offset = m_offset + pixels2samples(pos.x()); // [samples]
182  const double tolerance = m_zoom * selectionTolerance(); // [samples]
183  const double fine_pos = static_cast<double>(m_offset) +
184  (static_cast<double>(pos.x()) * m_zoom);
185 
186  // we support the following items (with this priority):
187  // 1. a label, which can be moved
188  // 2. the border of a selection (left or right), which can be moved
189  // 3. the body of a selection, which can be dragged
190 
191  // find the nearest label
192  Kwave::Label nearest_label;
193  unsigned int nearest_label_index = 0;
194  double d_label = tolerance;
195  {
196  unsigned int index = 0;
197  foreach (const Kwave::Label &label,
199  {
200  double d = qAbs(static_cast<double>(label.pos()) - fine_pos);
201  if (d < qMin(d_label, tolerance)) {
202  d_label = d;
203  nearest_label = label;
204  nearest_label_index = index;
205  }
206  index++;
207  }
208  }
209 
210  // get information about the current selection
211  double selection_first = static_cast<double>(
213  double selection_last = static_cast<double>(
215  bool selection_is_empty = (m_signal_manager->selection().length() == 0);
216  const double d_selection_left = qAbs(selection_first - fine_pos);
217  const double d_selection_right = qAbs(selection_last - fine_pos);
218 
219  // special case: label is near selection left and cursor is left
220  // of selection -> take the label
221  // (or vice versa at the right border)
222  bool prefer_the_label =
223  ((d_selection_left < tolerance) && (fine_pos < selection_first)) ||
224  ((d_selection_right < tolerance) && (fine_pos > selection_last));
225  bool selection_is_nearer =
226  (d_selection_left <= d_label) || (d_selection_right <= d_label);
227  if (selection_is_nearer && !prefer_the_label) {
228  // one of the selection borders is nearer
229  d_label = d_selection_left + d_selection_right;
230  }
231 
232  if ( (d_label <= qMin(d_selection_left, d_selection_right)) &&
233  !nearest_label.isNull() ) {
234  // found a label
235  return QSharedPointer<Kwave::ViewItem>(new(std::nothrow)
237  nearest_label_index, nearest_label));
238  }
239 
240  if ( (d_selection_left < qMin(tolerance, d_selection_right)) ||
241  ((d_selection_left < tolerance) && selection_is_empty) )
242  {
243  // found selection border (left) or empty selection
244  return QSharedPointer<Kwave::ViewItem>(new(std::nothrow)
246  *this, *m_signal_manager,
248  }
249 
250  if (d_selection_right < qMin(tolerance, d_selection_left)) {
251  // found selection border (right)
252  return QSharedPointer<Kwave::ViewItem>(new(std::nothrow)
254  *this, *m_signal_manager,
256  }
257 
258  if ((offset >= selection_first) && (offset <= selection_last)) {
259  // found selection body
260  return QSharedPointer<Kwave::ViewItem>(new(std::nothrow)
262  }
263 
264  // nothing found
265  return QSharedPointer<Kwave::ViewItem>(Q_NULLPTR);
266 }
267 
268 //***************************************************************************
269 void Kwave::TrackView::handleContextMenu(const QPoint &pos, QMenu *menu)
270 {
271  QMenu *submenu_label = menu->addMenu(i18n("Label"));
272  Q_ASSERT(submenu_label);
273  if (!submenu_label) return;
274 
275  // add label
276  QAction *action_label_new = submenu_label->addAction(
277  QIcon::fromTheme(_("list-add")),
278  i18n("New"), this, SLOT(contextMenuLabelNew()));
279  Q_ASSERT(action_label_new);
280  if (!action_label_new) return;
281 
282  // store the menu position
284 }
285 
286 //***************************************************************************
288 {
289  emit sigCommand(_("label:add(%1)").arg(m_mouse_click_position));
290 }
291 
292 //***************************************************************************
293 void Kwave::TrackView::resizeEvent(QResizeEvent *event)
294 {
295  Kwave::SignalView::resizeEvent(event);
296  if (!event) return;
297 
298  // request a repaint on all size changes, but not on horizontal shrink
299  if ((event->size().width() > m_last_width) ||
300  (event->size().height() != m_last_height))
301  {
303  }
304 }
305 
306 //***************************************************************************
308 {
310  emit sigNeedRepaint(this);
311 }
312 
313 //***************************************************************************
315 {
317  emit sigNeedRepaint(this);
318 }
319 
320 //***************************************************************************
322 {
324  emit sigNeedRepaint(this);
325 }
326 
327 //***************************************************************************
329 {
333  emit sigNeedRepaint(this);
334 }
335 
336 //***************************************************************************
337 void Kwave::TrackView::paintEvent(QPaintEvent *)
338 {
339  Q_ASSERT(m_signal_manager);
340  if (!m_signal_manager) return;
341 
342 // qDebug("TrackView::paintEvent()");
343 // #define DEBUG_REPAINT_TIMES
344 #ifdef DEBUG_REPAINT_TIMES
345  QTime time;
346  time.start();
347 #endif /* DEBUG_REPAINT_TIMES */
348 
349  QPainter p;
350  const int width = QWidget::width();
351  const int height = QWidget::height();
352 
353 // qDebug("TrackView::paintEvent(): width=%d, height=%d", width, height);
354 
355  // --- detect size changes and refresh the whole image ---
356  if ((width > m_last_width) || (height != m_last_height)) {
357 // qDebug("TrackView::paintEvent(): window size changed from "
358 // "%dx%d to %dx%d", m_last_width, m_last_height, width, height);
359 
360  // create new images for the layers
361  const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
362  m_img_signal = QImage(width, height, format);
363  m_img_selection = QImage(width, height, format);
364  m_img_markers = QImage(width, height, format);
365 
366  // create a new target image
367  m_image = QImage(width, height, format);
368 
369  // mark all images as "need refresh"
373 
374  // remember the last width
375  m_last_width = width;
376  m_last_height = height;
377  }
378 
379  // --- repaint of the signal layer ---
381 // qDebug("TrackView::paintEvent(): - redraw of signal layer -");
382 
383  p.begin(&m_img_signal);
384 
385  // fix the width and height of the track pixmap
386  if ((m_pixmap.width() < width) || (m_pixmap.height() != height))
387  m_pixmap.resize(width, height);
388 
389  // refresh the pixmap
390  if (m_pixmap.isModified())
391  m_pixmap.repaint();
392 
393  p.setCompositionMode(QPainter::CompositionMode_Source);
394  p.drawPixmap(0, 0, m_pixmap.pixmap());
395  p.end();
396 
398  }
399 
400  // --- repaint of the markers layer ---
402 // qDebug("TrackView::paintEvent(): - redraw of markers layer -");
403 
404  p.begin(&m_img_markers);
405  p.fillRect(0, 0, width, height, Qt::black);
406 
407  int last_marker = -1;
408  const sample_index_t last_visible = lastVisible();
409  foreach (const Kwave::Label &label,
411  {
412  sample_index_t pos = label.pos();
413  if (pos < m_offset) continue; // outside left
414  if (pos > last_visible) break; // far outside right, done
415  int x = samples2pixels(pos - m_offset);
416  if (x >= width) break; // outside right, done
417 
418  // position must differ from the last one, otherwise we
419  // would wipe out the last one with XOR mode
420  if (x == last_marker) continue;
421 
422  p.setPen(Qt::cyan);
423  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
424  p.drawLine(x, 0, x, height);
425 
426  last_marker = x;
427  }
428 
429  p.end();
430 
432  }
433 
434  // --- repaint of the selection layer ---
436 // qDebug("TrackView::paintEvent(): - redraw of selection layer -");
437 
438  p.begin(&m_img_selection);
439  p.fillRect(0, 0, width, height, Qt::black);
440 
443  const sample_index_t visible = pixels2samples(width);
444 
445  if ((right > 0) && (right >= m_offset)) {
446 
447  // shift and clip the selection, relative to m_offset
448  left = (left > m_offset) ? (left - m_offset) : 0;
449  if (left <= visible) {
450  right -= m_offset;
451  if (right > visible) right = visible + 1;
452 
453  // transform to pixel coordinates
454  int l = samples2pixels(left);
455  int r = samples2pixels(right);
456 
457  // clip to the widget's size
458  if (r >= width) r = width - 1;
459  if (l > r) l = r;
460 
461  p.setPen(Qt::yellow);
462  if (l == r) {
463  p.drawLine(l, 0, l, height);
464  } else {
465  p.setBrush(Qt::yellow);
466  p.drawRect(l, 0, r - l + 1, height);
467  }
468  }
469  }
470  p.end();
471 
473  }
474 
475  // bitBlt all layers together
476  p.begin(&m_image);
477  p.fillRect(0, 0, width, height, Qt::black);
478 
479  // paint the signal layer (copy mode)
480  p.setCompositionMode(QPainter::CompositionMode_Source);
481  p.drawImage(0, 0, m_img_signal);
482 
483  // paint the selection layer (XOR mode)
484  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
485  p.drawImage(0, 0, m_img_selection);
486 
487  // paint the markers/labels layer (XOR mode)
488  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
489  p.drawImage(0, 0, m_img_markers);
490 
491  // --- show the cursor position ---
492  do
493  {
494  if (m_cursor_pos == SAMPLE_INDEX_MAX) break;
495  if (m_cursor_pos < m_offset) break;
496  const sample_index_t visible = pixels2samples(width);
497  if (m_cursor_pos >= m_offset + visible) break;
498 
500  if (x >= width) break;
501 
502  p.setPen(Qt::yellow);
503  p.setCompositionMode(QPainter::CompositionMode_Exclusion);
504  p.drawLine(x, 0, x, height);
505  } while (0);
506 
507  p.end();
508 
509  // draw the result
510  p.begin(this);
511  p.drawImage(0, 0, m_image);
512  p.end();
513 
514 #ifdef DEBUG_REPAINT_TIMES
515  qDebug("TrackView::paintEvent() -- done, t=%d ms --", time.elapsed());
516 #endif /* DEBUG_REPAINT_TIMES */
517 }
518 
519 //***************************************************************************
521 {
522  m_cursor_pos = pos;
523  emit sigNeedRepaint(this);
524 }
525 
526 //***************************************************************************
527 //***************************************************************************
sample_index_t lastVisible() const
Definition: SignalView.h:130
QImage m_img_selection
Definition: TrackView.h:155
bool selected() const
Definition: Track.h:137
bool m_img_signal_needs_refresh
Definition: TrackView.h:161
void sigCommand(const QString &command)
bool m_img_markers_needs_refresh
Definition: TrackView.h:167
sample_index_t m_offset
Definition: SignalView.h:348
sample_index_t first() const
Definition: Selection.h:71
virtual int width() const
Definition: TrackPixmap.h:90
Definition: App.h:33
virtual int selectionTolerance() const
Definition: SignalView.cpp:542
virtual const QPixmap & pixmap() const
Definition: TrackPixmap.h:102
sample_index_t m_mouse_click_position
Definition: TrackView.h:170
sample_index_t last() const
Definition: Selection.h:76
#define MINIMUM_HEIGHT
Definition: TrackView.cpp:44
QImage m_img_signal
Definition: TrackView.h:152
virtual void handleContextMenu(const QPoint &pos, QMenu *menu) Q_DECL_OVERRIDE
Definition: TrackView.cpp:269
Kwave::MetaDataList & metaData()
virtual void repaint()
void refreshAllLayers()
Definition: TrackView.cpp:328
Kwave::Selection & selection()
virtual sample_index_t pos() const
Definition: Label.cpp:56
virtual int height() const
Definition: TrackPixmap.h:96
quint64 sample_index_t
Definition: Sample.h:28
Kwave::PlaybackController & playbackController()
void contentSizeChanged()
virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE
Definition: TrackView.cpp:337
sample_index_t offset() const
Definition: SignalView.h:110
void setOffset(sample_index_t offset)
Definition: TrackPixmap.cpp:82
virtual void setVerticalZoom(double zoom)
Definition: SignalView.cpp:137
virtual QSharedPointer< Kwave::ViewItem > findItem(const QPoint &pos) Q_DECL_OVERRIDE
Definition: TrackView.cpp:174
virtual void resize(int width, int height)
void addPixmap(const QString &filename)
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
Kwave::TrackPixmap m_pixmap
Definition: TrackView.h:140
void refreshSignalLayer()
Definition: TrackView.cpp:307
sample_index_t length() const
Definition: Selection.h:66
virtual void setZoomAndOffset(double zoom, sample_index_t offset) Q_DECL_OVERRIDE
Definition: TrackView.cpp:146
void contextMenuLabelNew()
Definition: TrackView.cpp:287
int toInt(T x)
Definition: Utils.h:127
virtual bool isNull() const
Definition: MetaData.cpp:69
virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE
Definition: TrackView.cpp:293
TrackView(QWidget *parent, QWidget *controls, Kwave::SignalManager *signal_manager, Kwave::Track *track)
Definition: TrackView.cpp:47
void refreshSelectionLayer()
Definition: TrackView.cpp:314
void refreshMarkersLayer()
Definition: TrackView.cpp:321
QImage m_img_markers
Definition: TrackView.h:158
double verticalZoom() const
Definition: SignalView.h:120
sample_index_t pixels2samples(int pixels) const
Definition: SignalView.cpp:155
Kwave::SignalManager * m_signal_manager
Definition: SignalView.h:336
virtual bool isModified()
double zoom() const
Definition: SignalView.h:115
virtual void showCursor(sample_index_t pos=SAMPLE_INDEX_MAX) Q_DECL_OVERRIDE
Definition: TrackView.cpp:520
#define _(m)
Definition: memcpy.c:66
virtual void refresh() Q_DECL_OVERRIDE
Definition: TrackView.cpp:138
bool m_img_selection_needs_refresh
Definition: TrackView.h:164
sample_index_t m_cursor_pos
Definition: TrackView.h:173
virtual ~TrackView() Q_DECL_OVERRIDE
Definition: TrackView.cpp:133
virtual void setVerticalZoom(double zoom) Q_DECL_OVERRIDE
Definition: TrackView.cpp:158
void setZoom(double zoom)
virtual void setZoomAndOffset(double zoom, sample_index_t offset)
Definition: SignalView.cpp:119
void sigNeedRepaint(Kwave::SignalView *view)
#define SAMPLE_INDEX_MAX
Definition: Sample.h:31
int samples2pixels(sample_index_t samples) const
Definition: SignalView.cpp:149