Git commit d5e6b9abef86023bcb492bac3df68cba083976b3 by Thomas Eschenbacher.
Committed on 19/09/2026 at 17:11.
Pushed by eschenbacher into branch 'master'.

sonagram plugin: implemented loading from file + added commands for loading and 
saving

M  +3    -0    CHANGES
M  +64   -1    doc/en/index.docbook
M  +2    -1    kwave/FileContext.cpp
M  +6    -3    libkwave/Plugin.h
M  +1    -1    libkwave/modules/RateConverter.cpp
M  +207  -81   plugins/sonagram/SonagramPlugin.cpp
M  +24   -0    plugins/sonagram/SonagramPlugin.h
M  +62   -55   plugins/sonagram/SonagramWindow.cpp
M  +30   -4    plugins/sonagram/SonagramWindow.h

https://invent.kde.org/multimedia/kwave/-/commit/d5e6b9abef86023bcb492bac3df68cba083976b3

diff --git a/CHANGES b/CHANGES
index 2e58c652..fac87b64 100644
--- a/CHANGES
+++ b/CHANGES
@@ -57,6 +57,9 @@
    - suppress notice about changed sample format when switching to A-Law/U-Law
    - improved error handling
  * stringenter plugin: keep a history with the previous 20 commands
+ * sonagram plugin:
+   - implemented loading from file
+   - added commands for loading and saving
 
 24.12.0 [2024-11-09]
 
diff --git a/doc/en/index.docbook b/doc/en/index.docbook
index e32776d5..0c1295d1 100644
--- a/doc/en/index.docbook
+++ b/doc/en/index.docbook
@@ -6862,7 +6862,7 @@
            </listitem>
        </varlistentry>
        <varlistentry>
-           <term><emphasis 
role="bold">&i18n-plugin_lbl_parameters;</emphasis></term>
+           <term><emphasis role="bold">&i18n-plugin_lbl_parameters; (default 
mode)</emphasis></term>
            <listitem>
                <variablelist>
                    <varlistentry>
@@ -6963,6 +6963,69 @@
                </variablelist>
            </listitem>
        </varlistentry>
+
+       <varlistentry>
+           <term><emphasis role="bold">&i18n-plugin_lbl_parameters; (load from 
file mode)</emphasis></term>
+           <listitem>
+               <variablelist>
+                       <varlistentry>
+                               <term><literal>load</literal></term>
+                               <listitem>
+                                       <para>
+                                       If the first parameter is the word 
"<literal>load</literal>"
+                                       then the following parameter will be 
interpreted as a file name
+                                       for loading.
+                                       </para>
+                               </listitem>
+                       </varlistentry>
+                   <varlistentry>
+                       <term><replaceable>filename</replaceable></term>
+                       <listitem>
+                           <para>
+                               Name of the file to load.
+                           </para>
+                       </listitem>
+                   </varlistentry>
+               </variablelist>
+               </listitem>
+       </varlistentry>
+
+       <varlistentry>
+           <term><emphasis role="bold">&i18n-plugin_lbl_parameters; (save to 
file mode)</emphasis></term>
+           <listitem>
+               <variablelist>
+                       <varlistentry>
+                               <term><literal>save</literal></term>
+                               <listitem>
+                                       <para>
+                                       If the first parameter is the word 
"<literal>save</literal>"
+                                       then the following parameters will be 
interpreted as an
+                                       index of the sonagram and a file name 
for saving.
+                                       </para>
+                               </listitem>
+                       </varlistentry>
+                   <varlistentry>
+                       <term><replaceable>index</replaceable></term>
+                       <listitem>
+                           <para>
+                               The numeric index of the sonagram window that 
shows the sonagram to be saved.
+                               This is an index which is determined from the 
internal list of currently
+                               open sonagram windows, starting from zero.
+                           </para>
+                       </listitem>
+                   </varlistentry>
+                   <varlistentry>
+                       <term><replaceable>filename</replaceable></term>
+                       <listitem>
+                           <para>
+                               Name of the file to load.
+                           </para>
+                       </listitem>
+                   </varlistentry>
+               </variablelist>
+               </listitem>
+       </varlistentry>
+
     </variablelist>
     </sect1>
 
diff --git a/kwave/FileContext.cpp b/kwave/FileContext.cpp
index d36af6e5..b6062671 100644
--- a/kwave/FileContext.cpp
+++ b/kwave/FileContext.cpp
@@ -119,7 +119,8 @@ void Kwave::FileContext::use()
 //***************************************************************************
 void Kwave::FileContext::release()
 {
-    Q_ASSERT(int(m_use_count) > 0);
+    Q_ASSERT(int(m_use_count) >= 0);
+    if (int(m_use_count) == 0) return;
     if (m_use_count.deref() == false) {
         disconnect();
         deleteLater();
diff --git a/libkwave/Plugin.h b/libkwave/Plugin.h
index 56ee842e..2adb57bf 100644
--- a/libkwave/Plugin.h
+++ b/libkwave/Plugin.h
@@ -258,9 +258,6 @@ namespace Kwave
          */
         int execute(QStringList &params);
 
-        /** emits a sigCommand() */
-        void emitCommand(const QString &command);
-
         /** increments the usage counter */
         void use();
 
@@ -329,6 +326,12 @@ namespace Kwave
         /** decrements the usage counter */
         void release();
 
+    protected slots:
+
+        /** emits a sigCommand() */
+        void emitCommand(const QString &command);
+
+
     private slots:
 
         /** closes the progress dialog and the confirm/cancel proxy */
diff --git a/libkwave/modules/RateConverter.cpp 
b/libkwave/modules/RateConverter.cpp
index 2ccd1401..233268b4 100644
--- a/libkwave/modules/RateConverter.cpp
+++ b/libkwave/modules/RateConverter.cpp
@@ -50,7 +50,7 @@ void Kwave::RateConverter::goOn()
 void Kwave::RateConverter::input(Kwave::SampleArray &data)
 {
     // shortcut for ratio == 1:1
-    if (qFuzzyCompare(m_ratio, 0.0) || qFuzzyCompare(m_ratio, 1.0) ||
+    if (qFuzzyIsNull(m_ratio) || qFuzzyCompare(m_ratio, 1.0) ||
         data.isEmpty()) {
         output(data);
         return;
diff --git a/plugins/sonagram/SonagramPlugin.cpp 
b/plugins/sonagram/SonagramPlugin.cpp
index c2bdb5cc..d6206ec6 100644
--- a/plugins/sonagram/SonagramPlugin.cpp
+++ b/plugins/sonagram/SonagramPlugin.cpp
@@ -19,6 +19,7 @@
 
 #include <errno.h>
 #include <math.h>
+#include <qnumeric.h>
 #include <stdlib.h>
 
 #include <limits>
@@ -62,6 +63,7 @@ KWAVE_PLUGIN(sonagram, SonagramPlugin)
 Kwave::SonagramPlugin::SonagramPlugin(QObject *parent,
                                       const QVariantList &args)
     :Kwave::Plugin(parent, args),
+     m_mode(MODE_VIEW),
      m_sonagram_window(nullptr),
      m_selection(nullptr),
      m_slices(0), m_fft_points(0),
@@ -130,29 +132,60 @@ int 
Kwave::SonagramPlugin::interpreteParameters(QStringList &params)
     QString param;
 
     // evaluate the parameter list
-    if (params.count() != 5) return -EINVAL;
-
-    param = params[0];
-    m_fft_points = param.toUInt(&ok);
-    if (!ok) return -EINVAL;
-    if (m_fft_points > MAX_FFT_POINTS) m_fft_points = MAX_FFT_POINTS;
-
-    param = params[1];
-    m_window_type = Kwave::WindowFunction::findFromName(param);
-
-    param = params[2];
-    m_color = (param.toUInt(&ok) != 0);
-    if (!ok) return -EINVAL;
-
-    param = params[3];
-    m_track_changes = (param.toUInt(&ok) != 0);
-    if (!ok) return -EINVAL;
-
-    param = params[4];
-    m_follow_selection = (param.toUInt(&ok) != 0);
-    if (!ok) return -EINVAL;
+    // we have different modes:
+    // 1) <fft points>, <window type>, <color>,
+    //    <track changes>, <follow_selection>,
+    //    ["window"|"view"]
+    // 2) "load", <filename>, ["window"|"view"]
+    // 3) "save", <filename>
+
+    if ((params.count() >= 2) && (params[0] == _("load"))) {
+        // determine the last used window function
+        QStringList last_params = manager().defaultParams(name());
+        param = last_params[1];
+        m_window_type = Kwave::WindowFunction::findFromName(param);
+
+        m_mode = MODE_LOAD;
+        m_fft_points       = 0;
+        m_color            = false;
+        m_track_changes    = false;
+        m_follow_selection = false;
+        return 0;
+    } else if ((params.count() >= 3) && (params[0] == _("save"))) {
+        // nothing to do here, saving is done in start()
+        m_mode = MODE_SAVE;
+        return 0;
+    } else if (params.count() >= 5) {
+        m_mode = MODE_VIEW;
+
+        param = params[0];
+        m_fft_points = param.toUInt(&ok);
+        if (!ok) return -EINVAL;
+        if (m_fft_points > MAX_FFT_POINTS) m_fft_points = MAX_FFT_POINTS;
+
+        param = params[1];
+        m_window_type = Kwave::WindowFunction::findFromName(param);
+
+        param = params[2];
+        m_color = (param.toUInt(&ok) != 0);
+        if (!ok) return -EINVAL;
+
+        param = params[3];
+        m_track_changes = (param.toUInt(&ok) != 0);
+        if (!ok) return -EINVAL;
+
+        param = params[4];
+        m_follow_selection = (param.toUInt(&ok) != 0);
+        if (!ok) return -EINVAL;
+
+        return 0;
+    } else {
+        qWarning("SonagramPlugin::interpreteParameters(): "
+                 "invalid parameter list: %s",
+                 DBG(params.join(_(", "))));
+    }
 
-    return 0;
+    return -EINVAL;
 }
 
 //***************************************************************************
@@ -174,72 +207,95 @@ int Kwave::SonagramPlugin::start(QStringList &params)
 
     // create an empty sonagram window
     m_sonagram_window = new(std::nothrow)
-        Kwave::SonagramWindow(parentWidget(), signalName());
+        Kwave::SonagramWindow(parentWidget());
     Q_ASSERT(m_sonagram_window);
     if (!m_sonagram_window) return -ENOMEM;
+    if (m_mode == MODE_LOAD) {
+        m_sonagram_window->setWindowTitle(
+            i18n("Sonagram loaded from %1", params[1]));
+    } else {
+        m_sonagram_window->setName(signalName());
+    }
 
     // if the signal closes, close the sonagram window too
     QObject::connect(&manager(), SIGNAL(sigClosed()),
                      m_sonagram_window, SLOT(close()));
 
-    // get the current selection
-    QVector<unsigned int> selected_channels;
-    sample_index_t offset = 0;
-    sample_index_t length = 0;
-    length = selection(&selected_channels, &offset, nullptr, true);
-
-    // abort if nothing is selected
-    if (!length || selected_channels.isEmpty())
-        return -EINVAL;
-
-    // calculate the number of slices (width of image)
-    m_slices = Kwave::toUint(ceil(static_cast<double>(length) /
-                                  static_cast<double>(m_fft_points)));
-    if (m_slices > MAX_SLICES) m_slices = MAX_SLICES;
-
-    /* limit selection to INT_MAX samples (limitation of the cache index) */
-    if ((length / m_fft_points) >= SAMPLE_INDEX_MAX) {
-        Kwave::MessageBox::error(parentWidget(),
-                                 i18n("File or selection too large"));
-        return -EFBIG;
-    }
+    // forward the (menu) commands to the plugin
+    QObject::connect(m_sonagram_window, SIGNAL(sigCommand(QString)),
+                     this, SLOT(emitCommand(QString)));
+
+    if (m_mode == MODE_VIEW) {
+        // get the current selection
+        QVector<unsigned int> selected_channels;
+        sample_index_t offset = 0;
+        sample_index_t length = 0;
+        length = selection(&selected_channels, &offset, nullptr, true);
+
+        // abort if nothing is selected
+        if (!length || selected_channels.isEmpty())
+            return -EINVAL;
+
+        // calculate the number of slices (width of image)
+        m_slices = Kwave::toUint(ceil(static_cast<double>(length) /
+                                    static_cast<double>(m_fft_points)));
+        if (m_slices > MAX_SLICES) m_slices = MAX_SLICES;
+
+        /* limit selection to INT_MAX samples (limitation of the cache index) 
*/
+        if ((length / m_fft_points) >= SAMPLE_INDEX_MAX) {
+            Kwave::MessageBox::error(parentWidget(),
+                                    i18n("File or selection too large"));
+            return -EFBIG;
+        }
 
-    // create a selection tracker
-    m_selection = new(std::nothrow) Kwave::SelectionTracker(
-        &sig_mgr, offset, length, &selected_channels);
-    Q_ASSERT(m_selection);
-    if (!m_selection) return -ENOMEM;
-
-    connect(m_selection, SIGNAL(sigTrackInserted(quint64)),
-            this,        SLOT(slotTrackInserted(quint64)));
-    connect(m_selection, SIGNAL(sigTrackDeleted(quint64)),
-            this,        SLOT(slotTrackDeleted(quint64)));
-    connect(
-        m_selection,
-        SIGNAL(sigInvalidated(quint64,sample_index_t,sample_index_t)),
-        this,
-        SLOT(slotInvalidated(quint64,sample_index_t,sample_index_t))
-    );
-
-    // create a new empty image
-    createNewImage(m_slices, m_fft_points / 2);
-
-    // set the overview
-    m_overview_cache = new(std::nothrow)
-        Kwave::OverViewCache(sig_mgr, offset, length, &selected_channels);
-    Q_ASSERT(m_overview_cache);
-    if (!m_overview_cache) return -ENOMEM;
-
-    refreshOverview(); // <- this needs the m_overview_cache
-
-    if (m_track_changes) {
-        // stay informed about changes in the signal
-        connect(m_overview_cache, SIGNAL(changed()),
-                this, SLOT(refreshOverview()));
-    } else {
-        // overview cache is no longer needed
-        delete m_overview_cache;
-        m_overview_cache = nullptr;
+        // create a selection tracker
+        m_selection = new(std::nothrow) Kwave::SelectionTracker(
+            &sig_mgr, offset, length, &selected_channels);
+        Q_ASSERT(m_selection);
+        if (!m_selection) return -ENOMEM;
+
+        connect(m_selection, SIGNAL(sigTrackInserted(quint64)),
+                this,        SLOT(slotTrackInserted(quint64)));
+        connect(m_selection, SIGNAL(sigTrackDeleted(quint64)),
+                this,        SLOT(slotTrackDeleted(quint64)));
+        connect(
+            m_selection,
+            SIGNAL(sigInvalidated(quint64,sample_index_t,sample_index_t)),
+            this,
+            SLOT(slotInvalidated(quint64,sample_index_t,sample_index_t))
+        );
+
+        // create a new empty image
+        createNewImage(m_slices, m_fft_points / 2);
+
+        // set the overview
+        m_overview_cache = new(std::nothrow)
+            Kwave::OverViewCache(sig_mgr, offset, length, &selected_channels);
+        Q_ASSERT(m_overview_cache);
+        if (!m_overview_cache) return -ENOMEM;
+
+        refreshOverview(); // <- this needs the m_overview_cache
+
+        if (m_track_changes && (m_mode == MODE_VIEW)) {
+            // stay informed about changes in the signal
+            connect(m_overview_cache, SIGNAL(changed()),
+                    this, SLOT(refreshOverview()));
+        } else {
+            // overview cache is no longer needed
+            delete m_overview_cache;
+            m_overview_cache = nullptr;
+        }
+    } else if ((m_mode == MODE_LOAD) && (params.count() >= 2)) {
+        // load the sonagram from a file
+        result = loadFromFile(params[1]);
+        if (result) return result;
+    } else if ((m_mode == MODE_SAVE) && (params.count() >= 3)) {
+        // save the sonagram to a file
+        bool ok = false;
+        quint64 id = params[1].toULongLong(&ok);
+        if (!ok) return -EINVAL;
+        result = saveToFile(id, params[2]);
+        return result;
     }
 
     // connect all needed signals
@@ -254,7 +310,7 @@ int Kwave::SonagramPlugin::start(QStringList &params)
     m_sonagram_window->setRate(signalRate());
     m_sonagram_window->show();
 
-    if (m_track_changes) {
+    if (m_track_changes && (m_mode == MODE_VIEW)) {
         QObject::connect(static_cast<QObject*>(&(manager())),
             SIGNAL(sigSignalNameChanged(QString)),
             m_sonagram_window, SLOT(setName(QString)));
@@ -618,6 +674,76 @@ void Kwave::SonagramPlugin::windowDestroyed()
     release();
 }
 
+//***************************************************************************
+int Kwave::SonagramPlugin::loadFromFile(const QString &filename)
+{
+    qDebug("loading %s", DBG(filename));
+    if (filename.isEmpty()) return -EINVAL;
+
+    QImage image(filename);
+    if (image.isNull()) return -ENOENT;
+
+    m_color      = !image.isGrayscale();
+    m_slices     = image.width();
+    m_fft_points = image.height() * 2;
+
+    // try to find out the sample rate
+    if (m_sonagram_window) {
+        // try to find it in file meta data
+        bool ok = false;
+        double rate = m_image.text(_("x-kwave/sample-rate")).toDouble(&ok);
+        if (!ok) {
+            // fallback #1: use dpi setting
+            if (m_image.dotsPerMeterX() == m_image.dotsPerMeterY()) {
+                rate = m_image.dotsPerMeterX();
+                ok = !qFuzzyIsNull(rate) && (rate >= 8000.0);
+            }
+        }
+        if (!ok) {
+            // fallback #2: use current signal
+            Kwave::SignalManager &mgr = signalManager();
+            rate = mgr.rate();
+            ok = !qFuzzyIsNull(rate) && (rate >= 8000.0);
+        }
+        if (!ok) {
+            // fallback #3: assume some default rate
+            rate = 44100.0;
+        }
+        m_sonagram_window->setRate(rate);
+    }
+
+    m_image = std::move(image);
+    return 0;
+}
+
+//***************************************************************************
+int Kwave::SonagramPlugin::saveToFile(quint64 index, const QString &filename)
+{
+    qDebug("saving sonagram #%llu to %s", index, DBG(filename));
+    if (filename.isEmpty()) return -EINVAL;
+
+    // find the sonagram window with the given index
+    Kwave::SonagramWindow *win = Kwave::SonagramWindow::fromIndex(index);
+    if (win == nullptr) return -EINVAL;
+
+    // get a copy of the image
+    QImage image(win->image());
+
+    // attach some meta data to the image for loading it later
+    double rate = win->rate();
+    if (qFuzzyIsNull(rate) || (rate < 8000.0))
+        rate = signalRate();
+    if (!qFuzzyIsNull(rate) && (rate >= 8000.0)) {
+        // try to save as file meta data
+        image.setText(_("x-kwave/sample-rate"), QString::number(rate));
+        // also save as dpi as fallback
+        image.setDotsPerMeterX(Kwave::toInt(rate));
+        image.setDotsPerMeterY(Kwave::toInt(rate));
+    }
+
+    return (image.save(filename, "BMP")) ? 0 : -EIO;
+}
+
 //***************************************************************************
 #include "SonagramPlugin.moc"
 //***************************************************************************
diff --git a/plugins/sonagram/SonagramPlugin.h 
b/plugins/sonagram/SonagramPlugin.h
index 86b47240..b11c3d07 100644
--- a/plugins/sonagram/SonagramPlugin.h
+++ b/plugins/sonagram/SonagramPlugin.h
@@ -205,8 +205,32 @@ namespace Kwave
         void createNewImage(const unsigned int width,
             const unsigned int height);
 
+        /**
+         * Loads a sonagram from a file. The file format is currently
+         * a simple bitmap with 8 bits depth.
+         * @param filename name of the file to load
+         * @return 0 if ok, or an error code if failed
+         */
+        int loadFromFile(const QString &filename);
+
+        /**
+         * Saves the data of an opened sonagram window to a file.
+         * The file format is currently a simple bitmap with 8
+         * bits depth.
+         * @param index the index of the window
+         * @param filename name of the file to save
+         * @return 0 if ok, or an error code if failed
+         */
+        int saveToFile(quint64 index, const QString &filename);
+
     private:
 
+        enum {
+            MODE_VIEW = 0,
+            MODE_LOAD,
+            MODE_SAVE
+        } m_mode;
+
         /** the main view of the plugin, a SonagramWindow */
         Kwave::SonagramWindow *m_sonagram_window;
 
diff --git a/plugins/sonagram/SonagramWindow.cpp 
b/plugins/sonagram/SonagramWindow.cpp
index a4cea7b9..425e7b1b 100644
--- a/plugins/sonagram/SonagramWindow.cpp
+++ b/plugins/sonagram/SonagramWindow.cpp
@@ -23,6 +23,7 @@
 #include <QBitmap>
 #include <QImage>
 #include <QLabel>
+#include <QList>
 #include <QMenuBar>
 #include <QPointer>
 #include <QLayout>
@@ -80,8 +81,11 @@ static const char *background[] = {
 "..........##########"
 };
 
+/** global list with all sonagram windows */
+static QList<Kwave::SonagramWindow *> g_windows;
+
 //****************************************************************************
-Kwave::SonagramWindow::SonagramWindow(QWidget *parent, const QString &name)
+Kwave::SonagramWindow::SonagramWindow(QWidget *parent)
     :KMainWindow(parent),
      m_status_time(nullptr),
      m_status_freq(nullptr),
@@ -96,7 +100,6 @@ Kwave::SonagramWindow::SonagramWindow(QWidget *parent, const 
QString &name)
      m_yscale(nullptr),
      m_refresh_timer()
 {
-
     for (unsigned int i = 0; i < 256; ++i) { m_histogram[i] = 0; }
 
     QWidget *mainwidget = new(std::nothrow) QWidget(this);
@@ -120,9 +123,11 @@ Kwave::SonagramWindow::SonagramWindow(QWidget *parent, 
const QString &name)
     Q_ASSERT(file);
     if (!file) return ;
 
-//    bar->addAction(i18n("&Spectral Data"), spectral);
-//    file->addAction(i18n("&Import from Bitmap..."), this, SLOT(load()));
-
+    file->addAction(
+        QIcon::fromTheme(_("document-import")),
+        i18n("&Import from Bitmap..."),
+        this, SLOT(load())
+    );
     file->addAction(
         QIcon::fromTheme(_("document-export")),
         i18n("&Export to Bitmap..."),
@@ -187,8 +192,6 @@ Kwave::SonagramWindow::SonagramWindow(QWidget *parent, 
const QString &name)
     connect(&m_refresh_timer, SIGNAL(timeout()),
             this, SLOT(refresh_view()));
 
-    setName(name);
-
     top_layout->setRowStretch(0, 100);
     top_layout->setRowStretch(1, 0);
     top_layout->setRowStretch(2, 0);
@@ -207,9 +210,28 @@ Kwave::SonagramWindow::SonagramWindow(QWidget *parent, 
const QString &name)
     if ((h * 5 / 3) < w) h = (w * 3) / 5;
     resize(w, h);
 
+    g_windows.push_back(this);
     show();
 }
 
+//****************************************************************************
+Kwave::SonagramWindow::~SonagramWindow()
+{
+    g_windows.removeAll(this);
+}
+
+//****************************************************************************
+quint64 Kwave::SonagramWindow::index() const
+{
+    return g_windows.indexOf(this);
+}
+
+//****************************************************************************
+Kwave::SonagramWindow *Kwave::SonagramWindow::fromIndex(quint64 index)
+{
+    return (qsizetype(index) < g_windows.size()) ? g_windows[index] : nullptr;
+}
+
 //****************************************************************************
 void Kwave::SonagramWindow::close()
 {
@@ -230,7 +252,11 @@ void Kwave::SonagramWindow::save()
     dlg->setWindowTitle(i18n("Save Sonagram"));
     if (dlg->exec() == QDialog::Accepted) {
         QString filename = dlg->selectedUrl().toLocalFile();
-        if (!filename.isEmpty()) m_image.save(filename, "BMP");
+        if (!filename.isEmpty()) {
+            emit sigCommand(_("plugin:execute(sonagram,save,") +
+                            QString::number(index()) + _(",") +
+                            filename + _(")"));
+        }
     }
     delete dlg;
 }
@@ -238,42 +264,21 @@ void Kwave::SonagramWindow::save()
 //****************************************************************************
 void Kwave::SonagramWindow::load()
 {
-//    if (image) {
-//      QString filename = QFileDialog::getOpenFileName(this, QString(),
-//                                                      "", "*.bmp");
-//      printf ("loading %s\n", filename.local8Bit().data());
-//      if (!filename.isNull()) {
-//          printf ("loading %s\n", filename.local8Bit().data());
-//          QImage *newimage = new QImage (filename);
-//          Q_ASSERT(newimage);
-//          if (newimage) {
-//              if ((image->height() == newimage->height())
-//                  && (image->width() == newimage->width())) {
-//
-//                  for (int i = 0; i < x; i++) {
-//                      for (int j = 0; j < points / 2; j++) {
-//                          if (data[i]) {
-//                              // data[i][j].real;
-//                          }
-//
-//                      }
-//                  }
-//
-//                  delete image;
-//                  image = newimage;
-//                  view->setImage (image);
-//              } else {
-//                  char buf[128];
-//                  delete newimage;
-//                  snprintf(buf, sizeof(buf), i18n("Bitmap must be %dx%d"),
-//                           image->width(), image->height());
-//                  KMsgBox::message (this, "Info", buf, 2);
-//              }
-//          } else
-//              KMsgBox::message (this, i18n("Error"),
-//                                i18n("Could not open Bitmap"), 2);
-//      }
-//    }
+    QPointer<Kwave::FileDialog> dlg = new(std::nothrow) Kwave::FileDialog(
+        _("kfiledialog:///kwave_sonagram_import"),
+        Kwave::FileDialog::OpenFile, QString(),
+        this, QUrl(), _("*.bmp")
+    );
+    if (!dlg) return;
+    dlg->setWindowTitle(i18n("Load Sonagram"));
+    if (dlg->exec() == QDialog::Accepted) {
+        QString filename = dlg->selectedUrl().toLocalFile();
+        if (!filename.isEmpty()) {
+            emit sigCommand(_("plugin:execute(sonagram,load,") +
+                            filename + _(")"));
+        }
+    }
+    delete dlg;
 }
 
 //****************************************************************************
@@ -473,14 +478,16 @@ void Kwave::SonagramWindow::toSignal()
 void Kwave::SonagramWindow::translatePixels2TF(const QPoint p,
                                                double *ms, double *f)
 {
+    if (qFuzzyIsNull(m_rate)) {
+        if (ms) *ms = 0.0;
+        if (f)  *f  = 0.0;
+    }
+
     if (ms) {
-        // get the time coordinate [0...(N_samples-1)* (1/f_sample) ]
-        if (!qFuzzyIsNull(m_rate)) {
-            *ms = static_cast<double>(p.x()) *
-                  static_cast<double>(m_points) * 1000.0 / m_rate;
-        } else {
-            *ms = 0;
-        }
+        // get the time coordinate [0...(N_samples-1) * (1/f_sample) ]
+        double t = static_cast<double>(p.x()) *
+                   static_cast<double>(m_points) * 1000.0 / m_rate;
+        *ms = (!qIsNaN(t) && !qIsInf(t)) ? t : 0.0;
     }
 
     if (f) {
@@ -488,15 +495,16 @@ void Kwave::SonagramWindow::translatePixels2TF(const 
QPoint p,
         double py = (m_points >= 2) ? (m_points / 2) - 1 : 0;
         double y = py - p.y();
         if (y < 0) y = 0;
-        *f = y / py * (m_rate / 2.0);
+        double freq = !qFuzzyIsNull(py) ? (y / py * (m_rate / 2.0)) : 0.0;
+        *f = (!qIsNaN(freq) && !qIsInf(freq)) ? freq : 0.0;
     }
 }
 
 //***************************************************************************
 void Kwave::SonagramWindow::updateScaleWidgets()
 {
-    double ms;
-    double f;
+    double ms = 0.0;
+    double f  = 0.0;
 
     translatePixels2TF(QPoint(m_image.width() - 1, 0), &ms, &f);
 
@@ -531,7 +539,6 @@ void Kwave::SonagramWindow::cursorPosChanged(const QPoint 
pos)
     QStatusBar *status = statusBar();
     Q_ASSERT(status);
     Q_ASSERT(m_points);
-    Q_ASSERT(!qFuzzyIsNull(m_rate));
     if (!status) return;
     if (m_image.isNull()) return;
     if (!m_points) return;
diff --git a/plugins/sonagram/SonagramWindow.h 
b/plugins/sonagram/SonagramWindow.h
index c12ae50d..71fd8442 100644
--- a/plugins/sonagram/SonagramWindow.h
+++ b/plugins/sonagram/SonagramWindow.h
@@ -49,15 +49,24 @@ namespace Kwave
         /**
          * Constructor.
          * @param parent the parent widget
-         * @param name reference to the initial name of the signal (used for
-         *        setting the window title, might be an empty string)
          */
-        SonagramWindow(QWidget *parent, const QString &name);
+        SonagramWindow(QWidget *parent);
 
         /**
          * Destructor.
          */
-        ~SonagramWindow() override = default;
+        ~SonagramWindow() override;
+
+        /** Get the index of this instance */
+        quint64 index() const;
+
+        /**
+         * Get the instance of a sonagram window from index
+         * @param index zero based index within the list of open
+         *              sonagram windows
+         * @return the SonagramWindow corresponding to the index or nullptr
+         */
+        static SonagramWindow *fromIndex(quint64 index);
 
         /**
          * Sets a new sonagram image to display.
@@ -80,6 +89,23 @@ namespace Kwave
          */
         void insertSlice(const unsigned int slice_nr, const QByteArray &slice);
 
+        /**
+         * Returns the sample rate.
+         * @return the sample rate in samples per second
+         */
+        double rate() const { return m_rate; }
+
+        /**
+         * Returns the image, needed for saving
+         * @return the current QImage
+         */
+         const QImage &image() const { return m_image; }
+
+    signals:
+
+        /** forward a sigCommand to the next layer */
+        void sigCommand(const QString &command);
+
     public slots:
 
         /** closes the sonagram window */

Reply via email to