kwave  18.07.70
CurveWidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  CurveWidget.cpp - widget for editing an interpolated curve
3  -------------------
4  begin : Sep 16 2001
5  copyright : (C) 2001 by Thomas Eschenbacher
6  email : Thomas Eschenbacher <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 #include <limits.h>
20 #include <math.h>
21 #include <new>
22 #include <stdio.h>
23 
24 #include <QAction>
25 #include <QCursor>
26 #include <QDir>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QKeySequence>
30 #include <QMenu>
31 #include <QMouseEvent>
32 #include <QPaintEvent>
33 #include <QPainter>
34 #include <QPalette>
35 #include <QPointer>
36 #include <QShortcut>
37 #include <QStandardPaths>
38 #include <QString>
39 #include <QStringList>
40 #include <QTextStream>
41 
42 #include <KLocalizedString>
43 #include <KIconLoader>
44 
45 #include "libkwave/Curve.h"
46 #include "libkwave/Interpolation.h"
47 #include "libkwave/Logger.h"
48 #include "libkwave/String.h"
49 #include "libkwave/Utils.h"
50 
51 #include "libgui/CurveWidget.h"
52 #include "libgui/FileDialog.h"
53 
54 //***************************************************************************
56  :QWidget(parent), m_width(0), m_height(0), m_curve(), m_menu(Q_NULLPTR),
57  m_preset_menu(Q_NULLPTR), m_current(Kwave::Curve::NoPoint),
58  m_last(Kwave::Curve::NoPoint),
59  m_down(false), m_knob(), m_selected_knob()
60 {
61  KIconLoader *icon_loader = KIconLoader::global();
62 
63  // set the default curve
64  m_curve.fromCommand(_("curve(linear,0,0,1,1)"));
65 
66  QPalette pal = palette();
67  pal.setColor(QPalette::Window, Qt::black);
68  setPalette(pal);
69 
70  // create the pixmaps for the selected and non-selected knob
71  if (icon_loader) {
72  m_knob = icon_loader->loadIcon(_("knob.xpm"), KIconLoader::Small);
73  m_selected_knob = icon_loader->loadIcon(_("selectedknob.xpm"),
74  KIconLoader::Small);
75  }
76 
77  // set up the context menu for the right mouse button
78  m_menu = new QMenu(this);
79  Q_ASSERT(m_menu);
80  if (!m_menu) return;
81 
82  QMenu *interpolation = m_menu->addMenu(i18n("Interpolation"));
83  Q_ASSERT(interpolation);
84  if (!interpolation) return;
85 
86  m_menu->addSeparator();
87  QMenu *transform = m_menu->addMenu(i18n("Transform"));
88  Q_ASSERT(transform);
89  if (!transform) return;
90  transform->addAction(i18n("Flip horizontal"),
91  this, SLOT(HFlip()));
92  transform->addAction(i18n("Flip vertical"),
93  this, SLOT(VFlip()));
94  transform->addSeparator();
95  transform->addAction(i18n("Into first half"),
96  this, SLOT(firstHalf()));
97  transform->addAction(i18n("Into second half"),
98  this, SLOT(secondHalf()));
99 
100  QMenu *del = m_menu->addMenu(i18n("Delete"));
101  Q_ASSERT(del);
102  if (!del) return;
103 
104  m_menu->addAction(i18n("Fit In"), this, SLOT(scaleFit()));
105  m_menu->addSeparator();
106 
107  /* list of presets */
108  m_preset_menu = m_menu->addMenu(i18n("Presets"));
109  Q_ASSERT(m_preset_menu);
110  if (!m_preset_menu) return;
111  loadPresetList();
112  connect(m_preset_menu, SIGNAL(triggered(QAction*)),
113  this, SLOT(loadPreset(QAction*)));
114 
115  m_menu->addAction(
116  QIcon::fromTheme(_("document-export")),
117  i18n("Save Preset"),
118  this, SLOT(savePreset()));
119 
120  del->addAction(
121  QIcon::fromTheme(_("edit-delete")),
122  i18n("Currently Selected Point"),
123  this, SLOT(deleteLast()),
124  QKeySequence::Delete);
125  del->addAction(i18n("Every Second Point"),
126  this, SLOT(deleteSecond()));
127 
128  QStringList types = Kwave::Interpolation::descriptions(true);
129  int id = 0;
130  foreach (const QString &text, types) {
131  QAction *action = new QAction(interpolation);
132  action->setText(text);
133  action->setData(id++);
134  interpolation->addAction(action);
135  }
136  connect(interpolation, SIGNAL(triggered(QAction*)),
137  this, SLOT(selectInterpolationType(QAction*)));
138 
139  setMouseTracking(true);
140 
141  QShortcut *delkey = new QShortcut(this);
142  Q_ASSERT(delkey);
143  if (!delkey) return;
144  delkey->setKey(Qt::Key_Delete);
145  connect(delkey, SIGNAL(activated()), this, SLOT (deleteLast()));
146 
147 }
148 
149 //***************************************************************************
151 {
152  if (m_menu) delete m_menu;
153 }
154 
155 //***************************************************************************
157 {
158  return m_curve.getCommand();
159 }
160 
161 //***************************************************************************
162 void Kwave::CurveWidget::setCurve(const QString &command)
163 {
164  m_curve.fromCommand(command);
165  repaint();
166 }
167 
168 //***************************************************************************
170 {
171  if (!action) return;
172 
173  QVariant data = action->data();
174  int index = data.toInt();
175 
177 
178  repaint();
179 }
180 
181 //***************************************************************************
183 {
184  QString presetSubDir = _("presets") + QDir::separator() + _("curves");
185  QString presetPath = QStandardPaths::writableLocation(
186  QStandardPaths::AppDataLocation) +
187  QDir::separator() + presetSubDir;
188  if (!QDir(presetPath).exists()) {
190  _("curve preset directory did not exist, creating '%1'").arg(
191  presetPath));
192  QDir(presetPath).mkpath(presetPath);
193  }
194 
195  QPointer<Kwave::FileDialog> dlg = new (std::nothrow) Kwave::FileDialog(
196  presetPath, Kwave::FileDialog::SaveFile,
197  _("*.curve *.CURVE|") +
198  i18nc("Filter description for Kwave curve presets, "
199  "for use in a FileDialog",
200  "Kwave curve preset (*.curve)"),
201  this, QUrl(), _("*.curve"));
202  if (!dlg) return;
203  dlg->setWindowTitle(i18n("Save Curve Preset"));
204  if (dlg->exec() != QDialog::Accepted) {
205  delete dlg;
206  return;
207  }
208 
209  QString name = dlg->selectedUrl().toLocalFile();
210  delete dlg;
211 
212  // append the extension if not given
213  if (!name.endsWith(_(".curve")))
214  name.append(_(".curve"));
215 
216  QFile out(name);
217  out.open(QIODevice::WriteOnly);
218  QString cmd = m_curve.getCommand();
219  out.write(DBG(cmd), cmd.length());
220 
221  // reload the list of known presets
222  loadPresetList();
223 }
224 
225 //***************************************************************************
227 {
228  const QChar s = QDir::separator();
229  QString presetSubDir = s + _("kwave") + s + _("presets") + s + _("curves");
230  QStringList files;
231  QStringList presetPaths = QStandardPaths::standardLocations(
232  QStandardPaths::GenericDataLocation);
233  foreach (const QString &path, presetPaths) {
234  QDir d(path + presetSubDir);
235  QStringList f = d.entryList(QDir::Files, QDir::Name);
236  foreach (const QString &file, f) {
237  QString preset = d.path() + s + file;
238  if (!files.contains(preset)) files.append(preset);
239  }
240  }
241  files.sort();
242 
243  m_preset_menu->clear();
244  foreach (const QString &file, files) {
245  QFileInfo fi(file);
246  QString name = fi.baseName();
247  QAction *action = new (std::nothrow) QAction(name, m_preset_menu);
248  Q_ASSERT(action);
249  if (!action) continue;
250  action->setData(file);
251  m_preset_menu->addAction(action);
252  }
253 }
254 
255 //***************************************************************************
256 void Kwave::CurveWidget::loadPreset(QAction *action)
257 {
258  Q_ASSERT(m_preset_menu);
259  Q_ASSERT(action);
260  if (!m_preset_menu || !action) return;
261  if (!action->data().isValid()) return;
262 
263  // invalidate the current selection
266 
267  // get the path of the file and check whether it (still) exists
268  QString filename = action->data().toString();
269  QFileInfo fi(filename);
270  if (!fi.exists(filename)) return;
271 
272  // load the file
273  QFile file(filename);
274  if (!file.open(QIODevice::ReadOnly)) {
275  qWarning("CurveWidget::loadPreset('%s') - FAILED", DBG(filename));
276  return;
277  }
278  QTextStream stream(&file);
279  m_curve.fromCommand(stream.readLine());
280  file.close();
281 
282  repaint();
283 }
284 
285 //***************************************************************************
287 {
288  m_curve.secondHalf ();
290  repaint();
291 }
292 
293 //***************************************************************************
295 {
296  m_curve.firstHalf ();
298  repaint();
299 }
300 
301 //****************************************************************************
303 {
306  repaint ();
307 }
308 
309 //****************************************************************************
311 {
312  if (m_last != Kwave::Curve::NoPoint) {
313  m_curve.deletePoint(m_last, true);
315  repaint();
316  }
317 }
318 
319 //***************************************************************************
321 {
322  m_curve.HFlip();
323  repaint();
324 }
325 
326 //***************************************************************************
328 {
329  m_curve.VFlip();
330  repaint();
331 }
332 
333 //***************************************************************************
335 {
336  m_curve.scaleFit();
337  repaint();
338 }
339 
340 //***************************************************************************
341 void Kwave::CurveWidget::addPoint(double newx, double newy)
342 {
343  m_curve.insert(newx, newy);
345  repaint();
346 }
347 
348 //***************************************************************************
350 // checks, if given coordinates fit to a control point in the list...
351 {
352  Q_ASSERT(m_width > 1);
353  Q_ASSERT(m_height > 1);
354  if ((m_width <= 1) || (m_height <= 1)) return Kwave::Curve::NoPoint;
355 
356  return m_curve.findPoint((static_cast<double>(sx)) / (m_width - 1),
357  (static_cast<double>(m_height) - sy) / (m_height - 1));
358 }
359 
360 //***************************************************************************
362 {
363  Q_ASSERT(e);
364  Q_ASSERT(m_width > 1);
365  Q_ASSERT(m_height > 1);
366  if (!e || (m_width <= 1) || (m_height <= 1)) return;
367 
368  if (e->buttons() == Qt::RightButton) {
369  // right mouse button -> context menu
370  QPoint popup = QCursor::pos();
371  if (m_menu) m_menu->popup(popup);
372  } else if (e->buttons() == Qt::LeftButton) {
373  // left mouse button -> select existing or create new point
374  m_down = true;
375  m_current = findPoint(e->pos().x(), e->pos().y());
377  // no matching point is found -> generate a new one !
378  addPoint(static_cast<double>(e->pos().x()) / (m_width - 1),
379  static_cast<double>(m_height - e->pos().y()) /
380  (m_height - 1));
381  m_current = findPoint(e->pos().x(), e->pos().y());
382  }
383  repaint();
384  }
385 }
386 
387 //***************************************************************************
389 {
390  m_last = m_current;
392  m_down = false;
393  repaint();
394 }
395 
396 //***************************************************************************
398 {
399  Q_ASSERT(e);
400  Q_ASSERT(m_width > 1);
401  Q_ASSERT(m_height > 1);
402  if (!e || (m_width <= 1) || (m_height <= 1)) return;
403 
404  int x = e->pos().x();
405  int y = e->pos().y();
406 
407  // if a point is selected...
409  if (m_current == m_curve.first()) x = 0;
410  if (m_current == m_curve.last()) x = m_width - 1;
411 
412  m_curve.deletePoint(m_current, false);
413 
414  m_current.setX(static_cast<double>(x) / (m_width - 1));
415  m_current.setY(static_cast<double>(m_height - y) / (m_height - 1));
416 
417  if (m_current.x() < 0.0) m_current.setX(0.0);
418  if (m_current.y() < 0.0) m_current.setY(0.0);
419  if (m_current.x() > 1.0) m_current.setX(1.0);
420  if (m_current.y() > 1.0) m_current.setY(1.0);
421 
422  double dx = (1.0 / static_cast<double>(m_width - 1));
423  do {
425  m_current.x(), m_current.y(), 1.0);
426  if (qFuzzyCompare(nearest.x(), m_current.x())) {
427  if (nearest == m_curve.last())
428  m_current.setX(m_current.x() - dx);
429  else
430  m_current.setX(m_current.x() + dx);
431  }
432  else
433  break;
434  } while (true);
435 
436  m_curve.insert(m_current.x(), m_current.y());
437 
438  repaint ();
439  } else {
440  if (findPoint(x, y) != Kwave::Curve::NoPoint)
441  setCursor(Qt::SizeAllCursor);
442  else
443  setCursor(Qt::ArrowCursor);
444  }
445 }
446 
447 //***************************************************************************
449 {
450 // qDebug("CurveWidget::paintEvent (QPaintEvent *)");
451  QPainter p;
452  int ly;
453 
454  m_height = rect().height();
455  m_width = rect().width();
456 
457  if (!m_curve.count()) return; // nothing to draw
458 
459  const int kw = m_knob.width();
460  const int kh = m_knob.height();
461 
462  QVector<double> y = m_curve.interpolation(m_width);
463  Q_ASSERT(Kwave::toInt(y.count()) == m_width);
464  if (Kwave::toInt(y.count()) < m_width) {
465  qWarning("CurveWidget: unable to get interpolation !");
466  return;
467  }
468 
469  p.begin(this);
470  p.fillRect(rect(), QBrush(palette().dark()));
471  p.setPen(palette().text().color());
472 
473  // draw the lines
474  ly = (m_height-1) - Kwave::toInt(y[0] * (m_height - 1));
475  for (int i = 1; i < m_width; i++) {
476  int ay = (m_height-1) - Kwave::toInt(y[i] * (m_height - 1));
477  p.drawLine (i - 1, ly, i, ay);
478  ly = ay;
479  }
480 
481  // draw the points (knobs)
482  foreach (const Kwave::Curve::Point &pt, m_curve) {
483  int lx = Kwave::toInt(pt.x() * (m_width - 1));
484  ly = (m_height - 1) - Kwave::toInt(pt.y() * (m_height - 1));
485 
486  if ((pt == m_current) || (!m_down && (pt == m_last)) )
487  p.drawPixmap(lx - (kw >> 1), ly - (kh >> 1), m_selected_knob);
488  else
489  p.drawPixmap(lx - (kw >> 1), ly - (kh >> 1), m_knob);
490  }
491  p.end();
492 
493 }
494 
495 //***************************************************************************
496 //***************************************************************************
void scaleFit(unsigned int range=1024)
Definition: Curve.cpp:200
void setCurve(const QString &command)
QPointF Point
Definition: Curve.h:48
void addPoint(double x, double y)
Definition: App.h:33
virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE
static QPointF NoPoint
Definition: Curve.h:51
Kwave::Curve m_curve
Definition: CurveWidget.h:139
void selectInterpolationType(QAction *action)
Kwave::Curve::Point m_current
Definition: CurveWidget.h:151
void insert(double x, double y)
Definition: Curve.cpp:147
void secondHalf()
Definition: Curve.cpp:121
void fromCommand(const QString &command)
Definition: Curve.cpp:55
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
static Kwave::interpolation_t findByIndex(int index)
Kwave::Interpolation & interpolation()
Definition: Curve.cpp:86
void deleteSecondPoint()
Definition: Curve.cpp:135
const char name[16]
Definition: memcpy.c:510
void setInterpolationType(Kwave::interpolation_t type)
Definition: Curve.cpp:100
void VFlip()
Definition: Curve.cpp:172
virtual void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE
virtual ~CurveWidget() Q_DECL_OVERRIDE
Kwave::Curve::Point findPoint(int sx, int sy)
int toInt(T x)
Definition: Utils.h:127
void firstHalf()
Definition: Curve.cpp:159
QPixmap m_selected_knob
Definition: CurveWidget.h:163
void deletePoint(Point p, bool check)
Definition: Curve.cpp:112
CurveWidget(QWidget *parent)
Definition: CurveWidget.cpp:55
void HFlip()
Definition: Curve.cpp:184
QString getCommand()
Definition: Curve.cpp:72
#define _(m)
Definition: memcpy.c:66
Point findPoint(double x, double y, double tol=.05)
Definition: Curve.cpp:226
#define DBG(qs)
Definition: String.h:55
static QStringList descriptions(bool localized=false)
static void Q_DECL_EXPORT log(const QObject *sender, LogLevel level, const QString &msg)
Definition: Logger.cpp:103
void loadPreset(QAction *action)
Kwave::Curve::Point m_last
Definition: CurveWidget.h:154
static double rect(double param)
Definition: Functions.cpp:29
virtual void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE
virtual void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE