diff --git a/kwave/FileContext.cpp b/kwave/FileContext.cpp index 29f3f63c..2996d448 100644 --- a/kwave/FileContext.cpp +++ b/kwave/FileContext.cpp @@ -1,1105 +1,1105 @@ /*************************************************************************** kwave/FileContext.cpp - Context of a Loaded File ------------------- begin : 2010-01-02 copyright : (C) 2010 by Thomas.Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "libkwave/CodecManager.h" #include "libkwave/Encoder.h" #include "libkwave/Logger.h" #include "libkwave/MessageBox.h" #include "libkwave/Parser.h" #include "libkwave/PlaybackController.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/Utils.h" #include "libgui/FileDialog.h" #include "App.h" #include "FileContext.h" #include "MainWidget.h" #include "Splash.h" #include "TopWidget.h" /** * useful macro for command parsing */ #define CASE_COMMAND(x) } else if (parser.command() == _(x)) { //*************************************************************************** /** * struct for info about a label within a Kwave script * @internal */ namespace Kwave { typedef struct { qint64 pos; /**< position within the stream */ unsigned int hits; /**< number of "goto"s to this label */ } label_t; } //*************************************************************************** Kwave::FileContext::FileContext(Kwave::App &app) :QObject(), m_use_count(1), m_application(app), m_top_widget(Q_NULLPTR), m_main_widget(Q_NULLPTR), m_signal_manager(Q_NULLPTR), m_plugin_manager(Q_NULLPTR), m_active(true), m_last_zoom(0), m_last_playback_pos(0), m_last_status_message_text(), m_last_status_message_timer(), m_last_status_message_ms(0), m_last_undo(QString()), m_last_redo(QString()), m_instance_nr(-1), m_delayed_command_timer(), m_delayed_command_queue() { m_delayed_command_timer.setSingleShot(true); connect(&m_delayed_command_timer, SIGNAL(timeout()), this, SLOT(processDelayedCommand())); } //*************************************************************************** Kwave::FileContext::~FileContext() { if (m_main_widget) delete m_main_widget; m_main_widget = Q_NULLPTR; m_top_widget = Q_NULLPTR; if (m_plugin_manager) delete m_plugin_manager; m_plugin_manager = Q_NULLPTR; if (m_signal_manager) delete m_signal_manager; m_signal_manager = Q_NULLPTR; } //*************************************************************************** void Kwave::FileContext::use() { Q_ASSERT(int(m_use_count) > 0); m_use_count.ref(); } //*************************************************************************** void Kwave::FileContext::release() { Q_ASSERT(int(m_use_count) > 0); if (m_use_count.deref() == false) { disconnect(); deleteLater(); } } //*************************************************************************** bool Kwave::FileContext::createMainWidget(const QSize &preferred_size) { Q_ASSERT(!m_main_widget); // create the main widget m_main_widget = new(std::nothrow) Kwave::MainWidget( m_top_widget, *this, preferred_size ); Q_ASSERT(m_main_widget); if (!m_main_widget) return false; if (!(m_main_widget->isOK())) { delete m_main_widget; m_main_widget = Q_NULLPTR; return false; } // connect the main widget connect(&(m_signal_manager->playbackController()), SIGNAL(sigSeekDone(sample_index_t)), m_main_widget, SLOT(scrollTo(sample_index_t))); connect(m_main_widget, SIGNAL(sigCommand(QString)), this, SLOT(executeCommand(QString))); connect(m_main_widget, SIGNAL(sigZoomChanged(double)), this, SLOT(forwardZoomChanged(double))); connect(m_main_widget, SIGNAL(sigVisibleRangeChanged(sample_index_t, sample_index_t, sample_index_t)), this, SLOT(visibleRangeChanged(sample_index_t, sample_index_t, sample_index_t)) ); return true; } //*************************************************************************** bool Kwave::FileContext::init(Kwave::TopWidget *top_widget) { Kwave::FileContext::UsageGuard _keep(this); m_top_widget = top_widget; Q_ASSERT(m_top_widget); if (!m_top_widget) return false; m_signal_manager = new(std::nothrow) Kwave::SignalManager(m_top_widget); Q_ASSERT(m_signal_manager); if (!m_signal_manager) return false; m_plugin_manager = new(std::nothrow) Kwave::PluginManager(m_top_widget, *m_signal_manager); Q_ASSERT(m_plugin_manager); if (!m_plugin_manager) return false; // connect the signal manager connect(m_signal_manager, SIGNAL(sigMetaDataChanged(Kwave::MetaDataList)), this, SLOT(metaDataChanged(Kwave::MetaDataList))); connect(&(m_signal_manager->selection()), SIGNAL(changed(sample_index_t,sample_index_t)), this, SLOT(selectionChanged(sample_index_t,sample_index_t))); connect(m_signal_manager, SIGNAL(sigUndoRedoInfo(const QString&, const QString&)), this, SLOT(setUndoRedoInfo(QString,QString))); connect(m_signal_manager, SIGNAL(sigModified()), this, SLOT(modifiedChanged())); // connect the plugin manager connect(m_plugin_manager, SIGNAL(sigCommand(QString)), this, SLOT(executeCommand(QString))); // connect the playback controller connect(&(m_signal_manager->playbackController()), SIGNAL(sigPlaybackPos(sample_index_t)), this, SLOT(updatePlaybackPos(sample_index_t))); setParent(top_widget); Kwave::Splash::showMessage(i18n("Scanning plugins...")); m_plugin_manager->searchPluginModules(); // load the menu from file QFile menufile(QStandardPaths::locate( QStandardPaths::GenericDataLocation, _("kwave/menus.config") )); menufile.open(QIODevice::ReadOnly); QTextStream stream(&menufile); Q_ASSERT(!stream.atEnd()); if (!stream.atEnd()) parseCommands(stream); menufile.close(); // now we are initialized, load all plugins Kwave::Splash::showMessage(i18n("Loading plugins...")); statusBarMessage(i18n("Loading plugins..."), 0); if (!m_plugin_manager->loadAllPlugins()) { statusBarMessage(i18n("Failed"), 1000); QApplication::restoreOverrideCursor(); Kwave::MessageBox::error(top_widget, i18n("Kwave has not been properly installed. "\ "No plugins found!") ); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return false; } statusBarMessage(i18n("Ready"), 1000); return true; } //*************************************************************************** void Kwave::FileContext::setParent(Kwave::TopWidget *top_widget) { if (m_top_widget) { Kwave::TopWidget *old = m_top_widget; // disconnect all old signal/slot relationships disconnect(m_plugin_manager, SIGNAL(sigProgress(QString)), old, SLOT(showInSplashSreen(QString))); disconnect(old, SIGNAL(sigFileContextSwitched(Kwave::FileContext*)), this, SLOT(contextSwitched(Kwave::FileContext*))); if (m_signal_manager) m_signal_manager->setParentWidget(Q_NULLPTR); if (m_plugin_manager) m_plugin_manager->setParentWidget(Q_NULLPTR); if (m_main_widget) m_main_widget->setParent(Q_NULLPTR); m_active = false; } // set the new top widget m_top_widget = top_widget; if (m_top_widget) { QWidget *top = m_top_widget; connect(top, SIGNAL(sigFileContextSwitched(Kwave::FileContext*)), this, SLOT(contextSwitched(Kwave::FileContext*))); connect(m_plugin_manager, SIGNAL(sigProgress(QString)), top, SLOT(showInSplashSreen(QString))); if (m_signal_manager) m_signal_manager->setParentWidget(m_top_widget); if (m_plugin_manager) m_plugin_manager->setParentWidget(m_top_widget); if (m_main_widget) m_main_widget->setParent(m_top_widget); } } //*************************************************************************** QWidget *Kwave::FileContext::mainWidget() const { return static_cast(m_main_widget); } //*************************************************************************** Kwave::SignalManager *Kwave::FileContext::signalManager() const { Q_ASSERT(m_signal_manager); return m_signal_manager; } //*************************************************************************** Kwave::PluginManager *Kwave::FileContext::pluginManager() const { return m_plugin_manager; } //*************************************************************************** Kwave::Zoomable* Kwave::FileContext::zoomable() const { return m_main_widget; } //*************************************************************************** int Kwave::FileContext::delegateCommand(const char *plugin, Kwave::Parser &parser, unsigned int param_count) { if (!m_plugin_manager) return -1; if (parser.count() != param_count) return -EINVAL; QStringList params; params.append(parser.command()); params.append(parser.remainingParams()); int result = m_plugin_manager->setupPlugin(_(plugin), params); if (result > 0) result = 0; return result; } //*************************************************************************** int Kwave::FileContext::executeCommand(const QString &line) { Kwave::FileContext::UsageGuard _keep(this); int result = 0; bool use_recorder = true; QString command = line; // qDebug("Kwave::FileContext[%p]::executeCommand(%s)", this, DBG(command)); Q_ASSERT(m_plugin_manager); Q_ASSERT(m_top_widget); if (!m_plugin_manager || !m_top_widget) return -ENOMEM; if (!command.length()) return 0; // empty line -> nothing to do if (command.trimmed().startsWith(_("#"))) return 0; // only a comment // special case: if the command contains ";" it is a list of // commands -> macro ! Kwave::Parser parse_list(command); if (parse_list.hasMultipleCommands()) { QStringList macro = parse_list.commandList(); foreach (const QString &it, macro) { result = executeCommand(_("nomacro:") + it); Q_ASSERT(!result); if (result) { qWarning("macro execution of '%s' failed: %d", DBG(it), result); return result; // macro failed :-( } // wait until the command has completed ! m_plugin_manager->sync(); } return result; } // check if the macro recorder has to be disabled for this command if (command.startsWith(_("nomacro:"))) { use_recorder = false; command = command.mid(QString(_("nomacro:")).length()); } // expand variables if (command.contains(_("${"))) { // current language if (command.contains(_("${LANG}"))) { QLocale locale; if (!m_main_widget.isNull()) locale = m_main_widget->locale(); QString lang = locale.name().split(_("-")).at(0); command.replace(_("${LANG}"), lang); } } // log all commands to the log file if enabled Kwave::Logger::log(this, Kwave::Logger::Info, _("CMD: ") + line); // parse one single command Kwave::Parser parser(command); QString cmd = parser.command(); // exclude menu commands from the recorder if (cmd == _("menu")) use_recorder = false; // only record plugin:execute, not plugin without parameters if (cmd == _("plugin")) use_recorder = false; // let through all commands that handle zoom/view or playback like fwd/rew bool allow_always = (cmd == _("playback")) || cmd.startsWith(_("view:")) || cmd.startsWith(_("playback:")) || cmd.startsWith(_("select_track:")) || (cmd == _("close")) || (cmd == _("quit")) || (cmd == _("window:screenshot")) || (cmd == _("window:sendkey")) ; // all others only if no plugin is currently running if (!allow_always && m_plugin_manager->onePluginRunning()) { qWarning("FileContext::executeCommand('%s') - currently not possible, " "a plugin is running :-(", DBG(cmd)); return -1; } if (use_recorder) { // append the command to the macro recorder // @TODO macro recording... qDebug("# %s ", DBG(command)); } if ((result = m_top_widget->executeCommand(command)) != ENOSYS) return result; if (false) { CASE_COMMAND("close") result = closeFile() ? 0 : 1; CASE_COMMAND("delayed") if (parser.count() != 2) return -EINVAL; unsigned int delay = parser.firstParam().toUInt(); QString delayed_cmd = parser.nextParam(); enqueueCommand(delay, delayed_cmd); result = 0; CASE_COMMAND("loadbatch") result = loadBatch(QUrl(parser.nextParam())); CASE_COMMAND("plugin") QString name(parser.firstParam()); QStringList params(parser.remainingParams()); qDebug("FileContext::executeCommand(): loading plugin '%s'", DBG(name)); qDebug("FileContext::executeCommand(): with %d parameter(s)", params.count()); result = m_plugin_manager->executePlugin( name, params.count() ? ¶ms : Q_NULLPTR); CASE_COMMAND("plugin:execute") QString name(parser.firstParam()); QStringList params(parser.remainingParams()); result = m_plugin_manager->executePlugin(name, ¶ms); CASE_COMMAND("plugin:setup") QString name(parser.firstParam()); QStringList params(parser.remainingParams()); result = m_plugin_manager->setupPlugin(name, params); if (result > 0) result = 0; CASE_COMMAND("revert") result = revert(); CASE_COMMAND("save") result = saveFile(); CASE_COMMAND("saveas") result = saveFileAs(parser.nextParam(), false); CASE_COMMAND("saveselect") result = saveFileAs(QString(), true); CASE_COMMAND("sync") while (!m_delayed_command_queue.isEmpty()) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } result = 0; CASE_COMMAND("window:click") result = delegateCommand("debug", parser, 3); CASE_COMMAND("window:close") result = delegateCommand("debug", parser, 1); CASE_COMMAND("window:mousemove") result = delegateCommand("debug", parser, 3); CASE_COMMAND("window:resize") result = delegateCommand("debug", parser, 3); CASE_COMMAND("window:sendkey") result = delegateCommand("debug", parser, 2); CASE_COMMAND("window:screenshot") result = delegateCommand("debug", parser, 2); } else { // pass the command to the layer below (main widget) Kwave::CommandHandler *layer_below = m_main_widget; result = (layer_below) ? layer_below->executeCommand(command) : -ENOSYS; } return result; } //*************************************************************************** void Kwave::FileContext::forwardZoomChanged(double zoom) { m_last_zoom = zoom; emit sigZoomChanged(this, zoom); } //*************************************************************************** void Kwave::FileContext::statusBarMessage(const QString &msg, unsigned int ms) { m_last_status_message_text = msg; m_last_status_message_ms = ms; if (ms) m_last_status_message_timer.start(); else m_last_status_message_timer.invalidate(); if (isActive()) emit sigStatusBarMessage(msg, ms); } //*************************************************************************** void Kwave::FileContext::updatePlaybackPos(sample_index_t offset) { if (!m_plugin_manager) return; if (!m_main_widget) return; bool playing = m_signal_manager->playbackController().running(); if (!playing) return; QString txt; double rate = m_plugin_manager->signalRate(); if (rate > 0) { double ms = static_cast(offset) * 1E3 / rate; txt = i18n("Playback: %1", Kwave::ms2string(ms)); } else { txt = i18n("Playback: %1 samples", Kwave::samples2string(offset)); } statusBarMessage(txt, 2000); // make sure that the current playback position is visible m_last_playback_pos = offset; Kwave::Zoomable *z = zoomable(); if (z) z->scrollTo(offset); } //*************************************************************************** void Kwave::FileContext::metaDataChanged(Kwave::MetaDataList meta_data) { // find out the instance ID if (m_instance_nr == -1) { // build a list of all currently open files/instances (including this) QList files = m_application.openFiles(); // filter out all instances of our file name QString our_name = signalName(); QList existing_instances; foreach (const Kwave::App::FileAndInstance &it, files) { const QString &name = it.first; int inst = it.second; if (name == our_name) existing_instances.append(inst); } // remove our own entry if (existing_instances.contains(m_instance_nr)) existing_instances.removeOne(m_instance_nr); // find an empty slot if (!existing_instances.isEmpty()) while (existing_instances.contains(m_instance_nr)) m_instance_nr = (m_instance_nr != -1) ? (m_instance_nr + 1) : 2; } if (isActive()) { // we are active -> emit the meta data immediately emit sigMetaDataChanged(meta_data); } // else: we are inactive -> emit the meta data later, when activated // update the caption of the sub window if (m_main_widget && (m_application.guiType() != Kwave::App::GUI_SDI)) m_main_widget->setWindowTitle(windowCaption(true)); } //*************************************************************************** void Kwave::FileContext::selectionChanged(sample_index_t offset, sample_index_t length) { if (isActive()) { // we are active -> emit the selection change immediately emit sigSelectionChanged(offset, length); } // else: we are inactive -> not of interest / ignore } //*************************************************************************** void Kwave::FileContext::setUndoRedoInfo(const QString &undo, const QString &redo) { m_last_undo = undo; m_last_redo = redo; if (isActive()) { // we are active -> emit the undo/redo info immediately emit sigUndoRedoInfo(undo, redo); } // else: we are inactive -> emit the undo/redo info later, when activated } //*************************************************************************** void Kwave::FileContext::visibleRangeChanged(sample_index_t offset, sample_index_t visible, sample_index_t total) { if (isActive()) { // we are active -> emit the view info immediately emit sigVisibleRangeChanged(offset, visible, total); } // else: we are inactive -> emit the view info later, when activated } //*************************************************************************** void Kwave::FileContext::modifiedChanged() { if (isActive()) { // we are active -> emit the modified state immediately emit sigModified(); } // else: we are inactive -> emit the modified state later, when activated // update the caption of our main widget if (m_main_widget && (m_application.guiType() != Kwave::App::GUI_SDI)) m_main_widget->setWindowTitle(windowCaption(true)); } //*************************************************************************** void Kwave::FileContext::contextSwitched(Kwave::FileContext *context) { Kwave::FileContext::UsageGuard _keep(this); if (context == this) { if (!m_active) { m_active = true; activated(); } } else m_active = false; } //*************************************************************************** void Kwave::FileContext::activated() { // let our plugin manager be the active one if (m_plugin_manager) m_plugin_manager->setActive(); // emit last playback position if playback is running if (m_signal_manager && m_signal_manager->playbackController().running()) updatePlaybackPos(m_last_playback_pos); // emit last zoom factor forwardZoomChanged(m_last_zoom); // erase the status message of the previous context emit sigStatusBarMessage(QString(), 0); // emit our last status bar message if it has not expired if (m_last_status_message_timer.isValid()) { quint64 elapsed = m_last_status_message_timer.elapsed(); if (elapsed < m_last_status_message_ms) { unsigned int remaining = Kwave::toUint(m_last_status_message_ms - elapsed); emit sigStatusBarMessage(m_last_status_message_text, remaining); } else m_last_status_message_timer.invalidate(); } else if (m_last_status_message_ms == 0) { // static message without expiration if (m_last_status_message_text.length()) { emit sigStatusBarMessage(m_last_status_message_text, 0); } } // emit latest meta data if (m_signal_manager) emit sigMetaDataChanged(m_signal_manager->metaData()); // emit latest view range change if (m_main_widget && m_signal_manager) { sample_index_t offset = m_main_widget->visibleOffset(); sample_index_t visible = m_main_widget->visibleSamples(); sample_index_t total = m_signal_manager->length(); emit sigVisibleRangeChanged(offset, visible, total); } // force update of the "modified" state emit sigModified(); // emit last undo/redo info emit sigUndoRedoInfo(m_last_undo, m_last_redo); } //*************************************************************************** int Kwave::FileContext::parseCommands(QTextStream &stream) { Kwave::FileContext::UsageGuard _keep(this); int result = 0; QMap labels; // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); QString label; // label, jump target of a "GOTO" while (!stream.atEnd() && !result) { QString line = stream.readLine().simplified(); if (line.startsWith(_("#"))) continue; // skip comments if (!line.length()) continue; // skip empty lines if (line.endsWith(QLatin1Char(':'))) { // this line seems to be a "label" QString name = line.left(line.length() - 1).simplified(); if (!labels.contains(name)) { // qDebug("new label '%s' at %llu", DBG(name), stream.pos()); label_t label_pos; label_pos.pos = stream.pos(); label_pos.hits = 0; labels[name] = label_pos; } // special handling for a label at the end of the file if (label.length() && (label == name)) { // label found label = QString(); } continue; } Kwave::Parser parser(line); // the "GOTO" command if ( !label.length() && (line.split(QLatin1Char(' ')).at(0) == _("GOTO")) ) { label = line.split(QLatin1Char(' ')).at(1).simplified(); } // jump to a label, scan/seek mode if (label.length()) { if (labels.contains(label)) { labels[label].hits++; qDebug(">>> GOTO '%s' @ offset %llu (pass #%d)", DBG(label), labels[label].pos, labels[label].hits ); stream.seek(labels[label].pos); // reset the label to search for label = QString(); } // else: maybe the label will follow somewhere below, // scan forward... continue; } // synchronize before the command if (m_plugin_manager) m_plugin_manager->sync(); // the "msgbox" command (useful for debugging) if (false) { ; CASE_COMMAND("msgbox") QApplication::restoreOverrideCursor(); result = (Kwave::MessageBox::questionYesNo(mainWidget(), parser.firstParam()) == KMessageBox::Yes) ? 0 : 1; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); continue; } // prevent this command from being re-added to the macro recorder if (!line.startsWith(_("nomacro:"), Qt::CaseInsensitive)) line.prepend(_("nomacro:")); // process the command in the current context // NOTE: this could theoretically also be a command that modifies // or even deletes the current context! result = EAGAIN; Kwave::FileContext *current_ctx = (m_top_widget) ? m_top_widget->currentContext() : Q_NULLPTR; if (current_ctx && (current_ctx != this)) result = m_top_widget->forwardCommand(line); // If the call returned with EAGAIN, then the context in duty is // different from this instance but not yet completely set up. // In that case this context is still responsible for executing the // current command. if (result == EAGAIN) result = executeCommand(line); if (result) qDebug(">>> '%s' - result=%d", DBG(line), result); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); // synchronize after the command if (m_plugin_manager) m_plugin_manager->sync(); // special handling of the "quit" command if (parser.command() == _("quit")) { result = ECANCELED; break; } } if (label.length()) { // oops, if we get here then we have searched for a non-exising label qWarning("label '%s' not found", DBG(label)); result = -ENOENT; } // remove hourglass QApplication::restoreOverrideCursor(); return result; } //*************************************************************************** void Kwave::FileContext::enqueueCommand(unsigned int delay, const QString &command) { use(); m_delayed_command_queue.append( QPair(delay, command) ); if (!m_delayed_command_timer.isActive()) m_delayed_command_timer.start(delay); } //*************************************************************************** void Kwave::FileContext::processDelayedCommand() { if (m_delayed_command_queue.isEmpty()) return; QPair current = m_delayed_command_queue.takeFirst(); executeCommand(current.second); if (m_delayed_command_queue.isEmpty()) return; QPair next = m_delayed_command_queue.first(); m_delayed_command_timer.start(next.first); release(); } //*************************************************************************** bool Kwave::FileContext::isInUse() const { if (m_use_count >= 2) return true; return (m_signal_manager && !m_signal_manager->isEmpty()); } //*************************************************************************** QString Kwave::FileContext::signalName() const { return (m_signal_manager) ? m_signal_manager->signalName() : QString(); } //*************************************************************************** QString Kwave::FileContext::windowCaption(bool with_modified) const { QString name = signalName(); // shortcut if no file loaded if (!name.length()) return QString(); // if not in SDI mode we have to take care of multiple instances on our // own and append a " " manually ! if (m_application.guiType() != Kwave::App::GUI_SDI) if (m_instance_nr != -1) name = i18nc( "for window title: " "%1 = Name of the file, " "%2 = Instance number when opened multiple times", "%1 <%2>", name, m_instance_nr); if (with_modified) { bool modified = (m_signal_manager) ? m_signal_manager->isModified() : false; if (modified) return i18nc("%1 = Path to modified file", "* %1 (modified)", name); } return name; } //*************************************************************************** int Kwave::FileContext::loadBatch(const QUrl &url) { Kwave::FileContext::UsageGuard _keep(this); // open the URL, read-only mode is enough QFile file(url.path()); if (!file.open(QIODevice::ReadOnly)) { qWarning("unable to open source in read-only mode!"); return -EIO; } // use a text stream for parsing the commands QTextStream stream(&file); int result = parseCommands(stream); file.close(); return result; } //*************************************************************************** int Kwave::FileContext::revert() { Kwave::FileContext::UsageGuard _keep(this); QUrl url(signalName()); if (!url.isValid() || !m_signal_manager) return -EINVAL; if (m_signal_manager->isModified()) { int res = Kwave::MessageBox::warningContinueCancel(m_top_widget, i18n("This file has been modified, changes will be lost.\n" "Do you want to continue?")); if (res == KMessageBox::Cancel) return ECANCELED; } return m_signal_manager->loadFile(url); } //*************************************************************************** int Kwave::FileContext::saveFile() { if (!m_signal_manager) return -EINVAL; int res = 0; if (signalName() != NEW_FILENAME) { QUrl url; url = QUrl(signalName()); res = m_signal_manager->save(url, false); // if saving in current format is not possible (no encoder), // then try to "save/as" instead... if (res == -EINVAL) res = saveFileAs(QString(), false); } else res = saveFileAs(QString(), false); return res; } //*************************************************************************** int Kwave::FileContext::saveFileAs(const QString &filename, bool selection) { if (!m_signal_manager) return -EINVAL; QString name = filename; QUrl url; int res = 0; if (name.length()) { /* name given -> take it */ url = QUrl(name); } else { /* * no name given -> show the File/SaveAs dialog... */ QUrl current_url; current_url = QUrl(signalName()); QString what = Kwave::CodecManager::mimeTypeOf(current_url); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(what); QString extension; // = "*.wav"; if (!encoder) { // no extension selected yet, use mime type from file info QString mime_type = Kwave::FileInfo( m_signal_manager->metaData()).get( Kwave::INF_MIMETYPE).toString(); encoder = Kwave::CodecManager::encoder(mime_type); if (encoder) { QStringList extensions = encoder->extensions(mime_type); if (!extensions.isEmpty()) { QString ext = extensions.first().split(_(" ")).first(); if (ext.length()) { extension = ext; QString new_filename = current_url.fileName(); new_filename += extension.mid(1); // remove the "*" current_url = current_url.adjusted(QUrl::RemoveFilename); current_url.setPath(current_url.path() + new_filename); } } } } QString filter = Kwave::CodecManager::encodingFilter(); QPointer dlg = new(std::nothrow)Kwave::FileDialog( _("kfiledialog:///kwave_save_as"), Kwave::FileDialog::SaveFile, filter, m_top_widget, current_url, extension ); if (!dlg) return 0; dlg->setWindowTitle(i18n("Save As")); if (dlg->exec() != QDialog::Accepted) { delete dlg; return -1; } - url = dlg->selectedUrl(); + if (dlg) url = dlg->selectedUrl(); if (url.isEmpty()) { delete dlg; return 0; } QString new_name = url.path(); QFileInfo path(new_name); // add the correct extension if necessary if (!path.suffix().length()) { QString ext = dlg->selectedExtension(); QStringList extensions = ext.split(_(" ")); ext = extensions.first(); new_name += ext.mid(1); path = new_name; url.setPath(new_name); } delete dlg; } // check if the file exists and ask before overwriting it // if it is not the old filename name = url.path(); if ((url.toDisplayString() != QUrl(signalName()).toDisplayString()) && (QFileInfo(name).exists())) { if (Kwave::MessageBox::warningYesNo(m_top_widget, i18n("The file '%1' already exists.\n" "Do you really want to overwrite it?", name)) != KMessageBox::Yes) { return -1; } } // maybe we now have a new mime type QString previous_mimetype_name = Kwave::FileInfo(m_signal_manager->metaData()).get( Kwave::INF_MIMETYPE).toString(); QString new_mimetype_name = Kwave::CodecManager::mimeTypeOf(url); if (new_mimetype_name != previous_mimetype_name) { // saving to a different mime type // now we have to do as if the mime type and file name // has already been selected to satisfy the fileinfo // plugin qDebug("TopWidget::saveAs(%s) - [%s] (previous:'%s')", DBG(url.toDisplayString()), DBG(new_mimetype_name), DBG(previous_mimetype_name) ); // set the new mimetype Kwave::FileInfo info(m_signal_manager->metaData()); info.set(Kwave::INF_MIMETYPE, new_mimetype_name); // set the new filename info.set(Kwave::INF_FILENAME, url.toDisplayString()); m_signal_manager->setFileInfo(info, false); // now call the fileinfo plugin with the new filename and // mimetype res = (m_plugin_manager) ? m_plugin_manager->setupPlugin(_("fileinfo"), QStringList()) : -1; // restore the mime type and the filename info = Kwave::FileInfo(m_signal_manager->metaData()); info.set(Kwave::INF_MIMETYPE, previous_mimetype_name); info.set(Kwave::INF_FILENAME, url.toDisplayString()); m_signal_manager->setFileInfo(info, false); } // now we have a file name -> do the "save" operation if (!res) res = m_signal_manager->save(url, selection); // if saving was successful, add the file to the list of recent files if (!res) m_application.addRecentFile(signalName()); return res; } //*************************************************************************** bool Kwave::FileContext::closeFile() { Kwave::FileContext::UsageGuard _keep(this); if (m_plugin_manager && !m_plugin_manager->canClose()) { qWarning("FileContext::closeFile() - currently not possible, "\ "a plugin is running :-("); return false; } if (m_signal_manager && m_signal_manager->isModified()) { int res = Kwave::MessageBox::warningYesNoCancel(m_top_widget, i18n("This file has been modified.\nDo you want to save it?")); if (res == KMessageBox::Cancel) return false; if (res == KMessageBox::Yes) { // user decided to save res = saveFile(); qDebug("FileContext::closeFile()::saveFile, res=%d",res); if (res) return false; } } // close all plugins that still might use the current signal if (m_plugin_manager) { m_plugin_manager->stopAllPlugins(); m_plugin_manager->signalClosed(); } if (m_signal_manager) m_signal_manager->close(); switch (m_application.guiType()) { case Kwave::App::GUI_MDI: /* FALLTHROUGH */ case Kwave::App::GUI_TAB: // close the main widget if (m_main_widget) delete m_main_widget; break; case Kwave::App::GUI_SDI: break; } return true; } //*************************************************************************** //*************************************************************************** diff --git a/kwave/MainWidget.cpp b/kwave/MainWidget.cpp index 9fd094a7..97b11336 100644 --- a/kwave/MainWidget.cpp +++ b/kwave/MainWidget.cpp @@ -1,1352 +1,1352 @@ /*************************************************************************** MainWidget.cpp - main widget of the Kwave TopWidget ------------------- begin : 1999 copyright : (C) 1999 by Martin Wilz email : Martin Wilz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/Drag.h" #include "libkwave/FileDrag.h" #include "libkwave/FileInfo.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/Logger.h" #include "libkwave/MessageBox.h" #include "libkwave/Parser.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "libgui/FileDialog.h" #include "libgui/LabelPropertiesWidget.h" #include "libgui/OverViewWidget.h" #include "libgui/SignalWidget.h" #include "App.h" #include "FileContext.h" #include "MainWidget.h" /** * useful macro for command parsing */ #define CASE_COMMAND(x) } else if (parser.command() == _(x)) { /** * Limits the zoom to a minimum number of samples visible in one * screen. */ #define MINIMUM_SAMPLES_PER_SCREEN 5 /** * Default widht of the display in seconds when in streaming mode, * where no initial length information is available (guess: 5min) */ #define DEFAULT_DISPLAY_TIME (5 * 60.0) /** * File extension used for label lists */ #define LABEL_LIST_EXT _("*.label") /** * Filter for file dialogs for loading/saving labels */ #define LABEL_LIST_FILTER \ LABEL_LIST_EXT + _("|") + \ i18n("Kwave label list") + \ _(" (") + LABEL_LIST_EXT + _(")") //*************************************************************************** Kwave::MainWidget::MainWidget(QWidget *parent, Kwave::FileContext &context, const QSize &preferred_size) :QWidget(parent), Kwave::CommandHandler(), Kwave::Zoomable(), m_context(context), m_upper_dock(), m_lower_dock(), m_scroll_area(this), m_horizontal_scrollbar(Q_NULLPTR), m_signal_widget( &m_scroll_area, context.signalManager(), &m_upper_dock, &m_lower_dock ), m_overview(Q_NULLPTR), m_offset(0), m_zoom(1.0), m_preferred_size(preferred_size), m_delayed_update_timer(this) { // qDebug("MainWidget::MainWidget()"); setAcceptDrops(true); // enable drag&drop Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; Kwave::PluginManager *plugin_manager = context.pluginManager(); Q_ASSERT(plugin_manager); if (!plugin_manager) return; plugin_manager->registerViewManager(&m_signal_widget); // topLayout: // - upper dock // - view port // - lower dock // - overview // - horizontal scroll bar QVBoxLayout *topLayout = new(std::nothrow) QVBoxLayout(this); Q_ASSERT(topLayout); if (!topLayout) return; topLayout->setSpacing(0); // -- upper dock -- topLayout->addLayout(&m_upper_dock); // -- view port with signal widget -- m_signal_widget.setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); m_scroll_area.setFrameStyle(QFrame::NoFrame); m_scroll_area.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_scroll_area.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_scroll_area.setWidgetResizable(true); m_scroll_area.setWidget(&m_signal_widget); topLayout->addWidget(&m_scroll_area); // -- lower dock -- topLayout->addLayout(&m_lower_dock); // -- overview widget -- m_overview = new(std::nothrow) Kwave::OverViewWidget(*signal_manager, this); Q_ASSERT(m_overview); if (!m_overview) return; m_overview->setMinimumHeight(m_overview->sizeHint().height()); m_overview->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ); topLayout->addWidget(m_overview); connect(m_overview, SIGNAL(valueChanged(sample_index_t)), this, SLOT(setOffset(sample_index_t))); connect(m_overview, SIGNAL(sigCommand(QString)), this, SIGNAL(sigCommand(QString))); m_overview->metaDataChanged(signal_manager->metaData()); m_overview->hide(); // -- horizontal scrollbar -- m_horizontal_scrollbar = new(std::nothrow) QScrollBar(this); Q_ASSERT(m_horizontal_scrollbar); if (!m_horizontal_scrollbar) return; m_horizontal_scrollbar->setOrientation(Qt::Horizontal); topLayout->addWidget(m_horizontal_scrollbar); connect(m_horizontal_scrollbar, SIGNAL(valueChanged(int)), this, SLOT(horizontalScrollBarMoved(int))); m_horizontal_scrollbar->hide(); // -- playback position update -- Kwave::PlaybackController &playback = signal_manager->playbackController(); connect(&playback, SIGNAL(sigPlaybackPos(sample_index_t)), m_overview, SLOT(showCursor(sample_index_t))); connect(&playback, SIGNAL(sigPlaybackStopped()), m_overview, SLOT(showCursor())); // -- connect all signals from/to the signal widget -- connect(&m_signal_widget, SIGNAL(sigCommand(QString)), this, SIGNAL(sigCommand(QString))); connect(&m_signal_widget, SIGNAL(sigCursorChanged(sample_index_t)), m_overview, SLOT(showCursor(sample_index_t))); // -- connect all signals from/to the signal manager -- connect(signal_manager, SIGNAL(sigTrackInserted(uint,Kwave::Track*)), this, SLOT(slotTrackInserted(uint,Kwave::Track*))); connect(signal_manager, SIGNAL(sigTrackDeleted(uint,Kwave::Track*)), this, SLOT(slotTrackDeleted(uint,Kwave::Track*))); connect(signal_manager, SIGNAL(sigMetaDataChanged(Kwave::MetaDataList)), this, SLOT(updateViewRange())); // set up the timer for delayed view range update m_delayed_update_timer.setSingleShot(true); connect(&m_delayed_update_timer, SIGNAL(timeout()), this, SLOT(updateViewRange())); setLayout(topLayout); } //*************************************************************************** bool Kwave::MainWidget::isOK() { return (m_horizontal_scrollbar && m_overview); } //*************************************************************************** Kwave::MainWidget::~MainWidget() { Kwave::PluginManager *plugin_manager = m_context.pluginManager(); Q_ASSERT(plugin_manager); if (plugin_manager) plugin_manager->registerViewManager(Q_NULLPTR); } //*************************************************************************** void Kwave::MainWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); if (!m_context.isActive() && (m_context.app().guiType() == Kwave::App::GUI_TAB)) { // HACK: this is a workaround for stupid bug in Qt, which sends us a // resize event with bogus size, when we are in tab mode, just // before getting deactivated !? if (!m_delayed_update_timer.isActive()) m_delayed_update_timer.start(0); } else { // update immediately updateViewRange(); } } //*************************************************************************** void Kwave::MainWidget::dragEnterEvent(QDragEnterEvent *event) { if (!event) return; if ((event->proposedAction() != Qt::MoveAction) && (event->proposedAction() != Qt::CopyAction)) return; /* unsupported action */ if (Kwave::FileDrag::canDecode(event->mimeData())) event->acceptProposedAction(); } //*************************************************************************** void Kwave::MainWidget::dropEvent(QDropEvent *event) { if (!event) return; if (!event->mimeData()) return; Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; if (signal_manager->isEmpty() && Kwave::Drag::canDecode(event->mimeData())) { sample_index_t pos = m_offset + pixels2samples(event->pos().x()); sample_index_t len = 0; if ((len = Kwave::Drag::decode(this, event->mimeData(), *signal_manager, pos))) { // set selection to the new area where the drop was done signal_manager->selectRange(pos, len); event->acceptProposedAction(); } else { QStringList formats = event->mimeData()->formats(); QString fmt = (!formats.isEmpty()) ? formats.first() : QString(); qWarning("MainWidget::dropEvent(%s): failed !", DBG(fmt)); event->ignore(); } } } //*************************************************************************** void Kwave::MainWidget::wheelEvent(QWheelEvent *event) { if (!event || !m_overview) return; // process only wheel events on the signal and overview frame, // not on the channel controls or scrollbars if (!m_scroll_area.geometry().contains(event->pos()) && !m_overview->geometry().contains(event->pos()) ) { event->ignore(); return; } switch (event->modifiers()) { case Qt::NoModifier: { // no modifier + => scroll left/right if (event->delta() > 0) executeCommand(_("view:scroll_left()")); else if (event->delta() < 0) executeCommand(_("view:scroll_right()")); event->accept(); break; } case Qt::ShiftModifier: // + => page up/down if (event->delta() > 0) executeCommand(_("view:scroll_prev()")); else if (event->delta() < 0) executeCommand(_("view:scroll_next()")); event->accept(); break; case Qt::ControlModifier: { // + => zoom in/out int x = qMax(m_signal_widget.mapToViewPort(event->globalPos()), 0); if (event->delta() > 0) executeCommand(_("view:zoom_in(%1)").arg(x)); else if (event->delta() < 0) executeCommand(_("view:zoom_out(%1)").arg(x)); event->accept(); break; } default: event->ignore(); } } //*************************************************************************** void Kwave::MainWidget::closeEvent(QCloseEvent *e) { m_context.closeFile() ? e->accept() : e->ignore(); } //*************************************************************************** void Kwave::MainWidget::slotTrackInserted(unsigned int index, Kwave::Track *track) { // qDebug("MainWidget::slotTrackInserted(%u, %p)", // index, static_cast(track)); Q_UNUSED(index); Q_UNUSED(track); // when the first track has been inserted, set some reasonable zoom Kwave::SignalManager *signal_manager = m_context.signalManager(); bool first_track = (signal_manager && (signal_manager->tracks() == 1)); if (first_track) zoomAll(); else updateViewRange(); } //*************************************************************************** void Kwave::MainWidget::slotTrackDeleted(unsigned int index, Kwave::Track *track) { // qDebug("MainWidget::slotTrackDeleted(%u)", index); Q_UNUSED(index); Q_UNUSED(track); updateViewRange(); } //*************************************************************************** double Kwave::MainWidget::zoom() const { return m_zoom; } //*************************************************************************** int Kwave::MainWidget::executeCommand(const QString &command) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return -EINVAL; if (!command.length()) return -EINVAL; Kwave::Parser parser(command); const sample_index_t visible_samples = visibleSamples(); const sample_index_t signal_length = signal_manager->length(); if (false) { // -- zoom -- CASE_COMMAND("view:zoom_selection") zoomSelection(); CASE_COMMAND("view:zoom_in") int x = parser.hasParams() ? parser.toInt() : -1; zoomIn(x); CASE_COMMAND("view:zoom_out") int x = parser.hasParams() ? parser.toInt() : -1; zoomOut(x); CASE_COMMAND("view:zoom_normal") zoomNormal(); CASE_COMMAND("view:zoom_all") zoomAll(); // -- navigation -- CASE_COMMAND("goto") sample_index_t offset = parser.toSampleIndex(); setOffset((offset > (visible_samples / 2)) ? (offset - (visible_samples / 2)) : 0); signal_manager->selectRange(offset, 0); CASE_COMMAND("view:scroll_right") const sample_index_t step = visible_samples / 10; setOffset(m_offset + step); CASE_COMMAND("view:scroll_left") const sample_index_t step = visible_samples / 10; setOffset((step < m_offset) ? (m_offset - step) : 0); CASE_COMMAND("view:scroll_start") setOffset(0); signal_manager->selectRange(0, 0); CASE_COMMAND("view:scroll_end") if (signal_length >= visible_samples) setOffset(signal_length - visible_samples); CASE_COMMAND("view:scroll_next") setOffset(m_offset + visible_samples); CASE_COMMAND("view:scroll_prev") setOffset((visible_samples < m_offset) ? (m_offset - visible_samples) : 0); CASE_COMMAND("view:scroll_next_label") sample_index_t ofs = Kwave::LabelList(signal_manager->metaData()).nextLabelRight( m_offset + (visible_samples / 2)); if (ofs > signal_length) ofs = signal_length - 1; setOffset((ofs > (visible_samples / 2)) ? (ofs - (visible_samples / 2)) : 0); CASE_COMMAND("view:scroll_prev_label") sample_index_t ofs = Kwave::LabelList(signal_manager->metaData()).nextLabelLeft( m_offset + (visible_samples / 2)); setOffset((ofs > (visible_samples / 2)) ? (ofs - (visible_samples / 2)) : 0); // -- selection -- CASE_COMMAND("selectall") signal_manager->selectRange(0, signal_manager->length()); CASE_COMMAND("selectnext") if (signal_manager->selection().length()) signal_manager->selectRange(signal_manager->selection().last() + 1, signal_manager->selection().length()); else signal_manager->selectRange(signal_manager->length() - 1, 0); CASE_COMMAND("selectprev") sample_index_t ofs = signal_manager->selection().first(); sample_index_t len = signal_manager->selection().length(); if (!len) len = 1; if (len > ofs) len = ofs; signal_manager->selectRange(ofs - len, len); CASE_COMMAND("selecttoleft") signal_manager->selectRange(0, signal_manager->selection().last() + 1); CASE_COMMAND("selecttoright") signal_manager->selectRange(signal_manager->selection().first(), signal_manager->length() - signal_manager->selection().first() ); CASE_COMMAND("selectvisible") signal_manager->selectRange(m_offset, visibleSamples()); CASE_COMMAND("selectnone") signal_manager->selectRange(m_offset, 0); // label handling CASE_COMMAND("label:add") sample_index_t pos = parser.toSampleIndex(); if (!parser.isDone()) { // 2 parameters: position + description QString description = parser.nextParam(); signal_manager->addLabel(pos, description); } else { // 1 parameter only: open dialog for editing the description addLabel(pos, QString()); } CASE_COMMAND("label:edit") int index = parser.toInt(); Kwave::LabelList labels(signal_manager->metaData()); if ((index >= labels.count()) || (index < 0)) return -EINVAL; Kwave::Label label = labels.at(index); labelProperties(label); CASE_COMMAND("label:load") QString filename = parser.nextParam(); return loadLabels(filename); CASE_COMMAND("label:save") QString filename = parser.nextParam(); return saveLabels(filename); // CASE_COMMAND("label:by_intensity") // markSignal(command); // CASE_COMMAND("label:to_pitch") // convertMarkstoPitch(command); // CASE_COMMAND("label:by_period") // markPeriods(command); } else { return (signal_manager) ? signal_manager->executeCommand(command) : -ENOSYS; } return 0; } //*************************************************************************** void Kwave::MainWidget::refreshHorizontalScrollBar() { if (!m_horizontal_scrollbar || !m_context.signalManager()) return; m_horizontal_scrollbar->blockSignals(true); // show/hide the overview widget if (!m_context.signalManager()->isEmpty() && !m_overview->isVisible()) m_overview->show(); if (m_context.signalManager()->isEmpty() && m_overview->isVisible()) m_overview->hide(); // adjust the limits of the horizontal scrollbar if (m_context.signalManager()->length() > 1) { // get the view information in samples sample_index_t length = m_context.signalManager()->length(); sample_index_t visible = visibleSamples(); if (visible > length) visible = length; // calculate the scrollbar ranges in scrollbar's units // // NOTE: we must take care of possible numeric overflows // as the scrollbar internally works with "int" and // the offsets we use for the samples might be bigger! // // [-------------------------------------------##############] // ^ ^ ^ // min max page // // max + page = x | x < INT_MAX (!) // x := length / f // page = x * (visible / length) = visible / f // max = length / f - page // pos = (m_offset / length) * x = m_offset / f const sample_index_t int_max = std::numeric_limits::max(); const sample_index_t f = qMax( sample_index_t(1), sample_index_t((length + int_max - 1) / int_max) ); int page = Kwave::toInt(visible / f); int min = 0; int max = Kwave::toInt(length / f) - page; int pos = Kwave::toInt(m_offset / f); int single = qMax(1, (page / (10 * qApp->wheelScrollLines()))); if (page < single) page = single; // qDebug("width=%d, max=%d, page=%d, single=%d, pos=%d, visible=%d", // m_width, max, page, single, pos, visible); // qDebug(" last=%d", pos + visible - 1); m_horizontal_scrollbar->setRange(min, max); m_horizontal_scrollbar->setValue(pos); m_horizontal_scrollbar->setSingleStep(single); m_horizontal_scrollbar->setPageStep(page); } else { m_horizontal_scrollbar->setRange(0, 0); } m_horizontal_scrollbar->blockSignals(false); } //*************************************************************************** void Kwave::MainWidget::horizontalScrollBarMoved(int newval) { // new offset = pos * f const sample_index_t length = m_context.signalManager()->length(); const sample_index_t int_max = std::numeric_limits::max(); const sample_index_t f = qMax( sample_index_t(1), sample_index_t((length + int_max - 1) / int_max) ); const sample_index_t pos = newval * f; setOffset(pos); } //*************************************************************************** void Kwave::MainWidget::updateViewRange() { Kwave::SignalManager *signal_manager = m_context.signalManager(); sample_index_t total = (signal_manager) ? signal_manager->length() : 0; if (m_overview) { m_overview->setRange(m_offset, visibleSamples(), total); // show/hide the overview widget and the horizontal scroll bar if (!m_context.signalManager()->isEmpty()) { if (!m_overview->isVisible()) m_overview->show(); if (!m_horizontal_scrollbar->isVisible()) m_horizontal_scrollbar->show(); } if (m_context.signalManager()->isEmpty()) { if (m_overview->isVisible()) m_overview->hide(); if (m_horizontal_scrollbar->isVisible()) m_horizontal_scrollbar->hide(); } } // forward the zoom and offset to the signal widget and overview fixZoomAndOffset(m_zoom, m_offset); m_signal_widget.setZoomAndOffset(m_zoom, m_offset); refreshHorizontalScrollBar(); } //*************************************************************************** sample_index_t Kwave::MainWidget::ms2samples(double ms) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return 0; return static_cast( rint(ms * signal_manager->rate() / 1E3)); } //*************************************************************************** sample_index_t Kwave::MainWidget::pixels2samples(unsigned int pixels) const { if ((pixels == 0) || (m_zoom <= 0.0)) return 0; return static_cast(static_cast(pixels) * m_zoom); } //*************************************************************************** int Kwave::MainWidget::samples2pixels(sample_index_t samples) const { if (m_zoom <= 0.0) return 0; return Kwave::toInt(rint(static_cast(samples) / m_zoom)); } //*************************************************************************** int Kwave::MainWidget::visibleWidth() const { return m_signal_widget.visibleWidth(); } //*************************************************************************** sample_index_t Kwave::MainWidget::visibleSamples() const { return pixels2samples(visibleWidth() - 1) + 1; } //*************************************************************************** double Kwave::MainWidget::fullZoom() const { const int width = visibleWidth(); if (width <= 0) return 0.0; // no zoom info, window is not yet ready Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return 0.0; if (signal_manager->isEmpty()) return 0.0; // no zoom if no signal sample_index_t length = signal_manager->length(); if (!length) { // no length: streaming mode -> try to use "estimated length" // and add some extra, 10% should be ok Kwave::FileInfo info(signal_manager->metaData()); if (info.contains(Kwave::INF_ESTIMATED_LENGTH)) { // estimated length in samples length = info.get(Kwave::INF_ESTIMATED_LENGTH).toULongLong(); length += length / 10; } if (length <= 0) { // fallback: start with some default zoom, // use five minutes (just guessed) length = static_cast(ceil(DEFAULT_DISPLAY_TIME * signal_manager->rate())); } } if (!length) return 0; // still no length !? -> bail out // example: width = 100 [pixels] and length = 3 [samples] // -> samples should be at positions 0, 49.5 and 99 // -> 49.5 [pixels / sample] // -> zoom = 1 / 49.5 [samples / pixel] // => full zoom [samples/pixel] = (length - 1) / (width - 1) if (length < static_cast(width) * 2) { // zoom is small enough, no error compensation needed return (static_cast(length) - 1.0) / static_cast(width - 1); } else { // zoom is big, pre-compensate rounding errors return (static_cast(length) - 0.5) / static_cast(width - 1); } } //*************************************************************************** void Kwave::MainWidget::fixZoomAndOffset(double zoom, sample_index_t offset) { double max_zoom; double min_zoom; sample_index_t length; const double old_zoom = m_zoom; const sample_index_t old_offset = m_offset; const int width = visibleWidth(); m_zoom = zoom; m_offset = offset; Q_ASSERT(m_zoom >= 0.0); Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; if (!width) return; length = signal_manager->length(); if (!length) { // in streaming mode we have to use a guessed length length = static_cast(ceil(width * fullZoom())); } if (!length) { m_offset = 0; return; }; // ensure that m_offset is [0...length-1] if (m_offset >= length) m_offset = (length - 1); // ensure that the zoom is in a reasonable range max_zoom = fullZoom(); min_zoom = static_cast(MINIMUM_SAMPLES_PER_SCREEN) / static_cast(width); // ensure that pixel coordinates are within signed int range min_zoom = qMax(min_zoom, static_cast(length) / double(std::numeric_limits::max())); if (m_zoom < min_zoom) m_zoom = min_zoom; if (m_zoom > max_zoom) m_zoom = max_zoom; // try to correct the offset if there is not enough data to fill // the current window // example: width=100 [pixels], length=3 [samples], // offset=1 [sample], zoom=1/49.5 [samples/pixel] (full) // -> current last displayed sample = length-offset // = 3 - 1 = 2 // -> available space = pixels2samples(width-1) + 1 // = (99/49.5) + 1 = 3 // -> decrease offset by 3 - 2 = 1 Q_ASSERT(length >= m_offset); if ((pixels2samples(width - 1) + 1) > (length - m_offset)) { // there is space after the signal -> move offset right sample_index_t shift = pixels2samples(width - 1) + 1 - (length - m_offset); if (shift >= m_offset) { m_offset = 0; } else { m_offset -= shift; } } // emit change in the zoom factor if (!qFuzzyCompare(m_zoom, old_zoom)) emit sigZoomChanged(m_zoom); if ((m_offset != old_offset) || !qFuzzyCompare(m_zoom, old_zoom)) { emit sigVisibleRangeChanged(m_offset, visibleSamples(), length); updateViewRange(); } } //*************************************************************************** void Kwave::MainWidget::setZoom(double new_zoom) { fixZoomAndOffset(new_zoom, m_offset); } //*************************************************************************** void Kwave::MainWidget::setOffset(sample_index_t new_offset) { fixZoomAndOffset(m_zoom, new_offset); } //*************************************************************************** void Kwave::MainWidget::scrollTo(sample_index_t pos) { sample_index_t visible = visibleSamples(); if ( (pos < (m_offset + (visible / 10))) || (pos > (m_offset + (visible / 2))) ) { // new position is out of range or too close to the border sample_index_t offset = (visible / 2); pos = (pos > offset) ? (pos - offset) : 0; if (pos != m_offset) { // check: is the difference >= 1 pixel ? sample_index_t diff = (pos > m_offset) ? (pos - m_offset) : (m_offset - pos); if (samples2pixels(diff) >= 1) setOffset(pos); } } } //*************************************************************************** void Kwave::MainWidget::zoomSelection() { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; sample_index_t ofs = signal_manager->selection().offset(); sample_index_t len = signal_manager->selection().length(); if (len) { int width = visibleWidth(); m_offset = ofs; setZoom((static_cast(len)) / static_cast(width - 1)); } } //*************************************************************************** void Kwave::MainWidget::zoomAll() { setZoom(fullZoom()); } //*************************************************************************** void Kwave::MainWidget::zoomNormal() { const sample_index_t shift1 = visibleWidth() / 2; const sample_index_t shift2 = visibleSamples() / 2; m_offset = (m_offset + shift2 >= m_offset) ? (m_offset + shift2) : -shift2; m_offset = (m_offset > shift1) ? (m_offset - shift1) : 0; setZoom(1.0); } //*************************************************************************** void Kwave::MainWidget::zoomIn(int pos) { if (pos >= 0) { // position given, calculate shift: // ofs_old + (pos * m_zoom) := ofs_new + (pos * (m_zoom / 3)) // ofs_new = ofs_old + (pos * m_zoom) - (pos * (m_zoom / 3)) fixZoomAndOffset( m_zoom / 3, m_offset + Kwave::toInt(pos * (m_zoom * (2.0 / 3.0))) ); } else { // no position given, show centered fixZoomAndOffset( m_zoom / 3, m_offset + (visibleSamples() / 3) ); } } //*************************************************************************** void Kwave::MainWidget::zoomOut(int pos) { sample_index_t shift; if (pos >= 0) { // position given, calculate shift // ofs_old + (pos * m_zoom) := ofs_new + (pos * (m_zoom * 3)) // ofs_new = ofs_old + (pos * m_zoom) - (pos * (m_zoom * 3)) shift = Kwave::toInt(m_zoom * 2.0) * pos; } else { // no position given, show centered shift = visibleSamples(); } fixZoomAndOffset( m_zoom * 3, (m_offset > shift) ? (m_offset - shift) : 0 ); } //*************************************************************************** void Kwave::MainWidget::addLabel(sample_index_t pos, const QString &description) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return; // remember the last "modified" state, in case that we abort bool was_modified = signal_manager->isModified(); // create an own undo transaction Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Add Label")); // add a new label, with undo Kwave::Label label = signal_manager->addLabel(pos, description); if (label.isNull()) { signal_manager->abortUndoTransaction(); if (!was_modified) signal_manager->setModified(false); return; } // edit the properties of the new label if (!labelProperties(label)) { // aborted or failed -> delete (without undo) int index = signal_manager->labelIndex(label); signal_manager->deleteLabel(index, false); signal_manager->abortUndoTransaction(); if (!was_modified) signal_manager->setModified(false); } } //**************************************************************************** int Kwave::MainWidget::loadLabels(const QString &filename) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return -1; QUrl url; if (!filename.length()) { QString name(filename); QPointer dlg = new (std::nothrow)Kwave::FileDialog( _("kfiledialog:///kwave_label_dir"), Kwave::FileDialog::OpenFile, LABEL_LIST_FILTER, this, QUrl(), LABEL_LIST_EXT); if (!dlg) return -1; dlg->setWindowTitle(i18n("Load Labels")); - if (dlg->exec() != QDialog::Accepted) { + if ((dlg->exec() != QDialog::Accepted) || !dlg) { delete dlg; return 0; } else { url = dlg->selectedUrl(); delete dlg; } } else { url = QUrl::fromUserInput(filename); } // create an own undo transaction Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Load Labels")); return m_context.loadBatch(url); } //**************************************************************************** int Kwave::MainWidget::saveLabels(const QString &filename) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return false; QUrl url; url = QUrl(m_context.signalName()); if (!filename.length()) { QString name(filename); QPointer dlg = new(std::nothrow)Kwave::FileDialog( _("kfiledialog:///kwave_label_dir"), Kwave::FileDialog::SaveFile, LABEL_LIST_FILTER, this, url, LABEL_LIST_EXT); if (!dlg) return 0; dlg->setWindowTitle(i18n("Save Labels")); - if (dlg->exec() != QDialog::Accepted) { + if ((dlg->exec() != QDialog::Accepted) || !dlg) { delete dlg; return -1; } url = dlg->selectedUrl(); if (url.isEmpty()) { delete dlg; return 0; } delete dlg; // add an extension if necessary name = url.path(); if (!QFileInfo(name).suffix().length()) { url.setPath(name + _(".label")); name = url.path(); } // check if the file exists and ask before overwriting it // if it is not the old filename if (QFileInfo(name).exists()) { if (Kwave::MessageBox::warningYesNo(this, i18n("The file '%1' already exists.\n" "Do you really want to overwrite it?", name)) != KMessageBox::Yes) { return -1; } } } else { url = QUrl::fromUserInput(filename); } // now we have a file name -> save all labels... Kwave::Logger::log(this, Kwave::Logger::Info, _("saving labels to '") + url.toDisplayString() + _("'")); QFile file(url.path()); file.open(QIODevice::WriteOnly); QTextStream out(&file); Kwave::LabelList labels(signal_manager->metaData()); foreach (const Kwave::Label &label, labels) { sample_index_t pos = label.pos(); const QString name = Kwave::Parser::escape(label.name()); out << _("label:add(") << pos; if (name.length()) out << _(", ") << name; out << _(")") << endl; } file.close(); return 0; } //*************************************************************************** bool Kwave::MainWidget::labelProperties(Kwave::Label &label) { Kwave::SignalManager *signal_manager = m_context.signalManager(); Q_ASSERT(signal_manager); if (!signal_manager) return false; if (label.isNull()) return false; int index = signal_manager->labelIndex(label); Q_ASSERT(index >= 0); if (index < 0) return false; // try to modify the label. just in case that the user moves it // to a position where we already have one, catch this situation // and ask if he wants to abort, re-enter the label properties // dialog or just replace (remove) the label at the target position bool accepted; sample_index_t new_pos = label.pos(); QString new_name = label.name(); int old_index = -1; while (true) { // create and prepare the dialog Kwave::LabelPropertiesWidget *dlg = new(std::nothrow) Kwave::LabelPropertiesWidget(this); Q_ASSERT(dlg); if (!dlg) return false; dlg->setLabelIndex(index); dlg->setLabelPosition(new_pos, signal_manager->length(), signal_manager->rate()); dlg->setLabelName(new_name); // execute the dialog accepted = (dlg->exec() == QDialog::Accepted); - if (!accepted) { + if (!accepted || !dlg) { // user pressed "cancel" delete dlg; break; } // if we get here the user pressed "OK" new_pos = dlg->labelPosition(); new_name = dlg->labelName(); dlg->saveSettings(); delete dlg; // check: if there already is a label at the new position // -> ask the user if he wants to overwrite that one if ((new_pos != label.pos()) && !signal_manager->findLabel(new_pos).isNull()) { int res = Kwave::MessageBox::warningYesNoCancel(this, i18n( "There already is a label at the position you have chosen.\n"\ "Do you want to replace it?")); if (res == KMessageBox::Yes) { // delete the label at the target position (with undo) Kwave::Label old = signal_manager->findLabel(new_pos); old_index = signal_manager->labelIndex(old); break; } if (res == KMessageBox::No) { // make another try -> re-enter the dialog continue; } // cancel -> abort the whole action accepted = false; break; } else { // ok, we can put it there break; } } if (accepted) { // shortcut: abort if nothing has changed if ((new_name == label.name()) && (new_pos == label.pos())) return true; Kwave::UndoTransactionGuard undo(*signal_manager, i18n("Modify Label")); // if there is a label at the target position, remove it first if (old_index >= 0) { signal_manager->deleteLabel(old_index, true); // this might have changed the current index! index = signal_manager->labelIndex(label); } // modify the label through the signal manager if (!signal_manager->modifyLabel(index, new_pos, new_name, true)) { // position is already occupied signal_manager->abortUndoTransaction(); return false; } // reflect the change in the passed label label.moveTo(new_pos); label.rename(new_name); // NOTE: moving might also change the index, so the complete // markers layer has to be refreshed } else signal_manager->abortUndoTransaction(); return accepted; } // ////**************************************************************************** // //void MainWidget::markSignal (const char *str) // //{ // // if (signalmanage) { // // Kwave::Label *newmark; // // // // Kwave::Parser parser (str); // // // // int level = (int) (parser.toDouble() / 100 * (1 << 23)); // // // // int len = signalmanage->getLength(); // // int *sam = signalmanage->getSignal(0)->getSample(); // ### @@@ ### // // LabelType *start = findMarkerType(parser.getNextParam()); // // LabelType *stop = findMarkerType (parser.getNextParam()); // // int time = (int) (parser.toDouble () * signalmanage->getRate() / 1000); // // // // printf ("%d %d\n", level, time); // // printf ("%s %s\n", start->name, stop->name); // // // // ProgressDialog *dialog = // // new ProgressDialog (len, "Searching for Signal portions..."); // // // // if (dialog && start && stop) { // // dialog->show(); // // // // newmark = new Kwave::Label(0, start); //generate initial Kwave::Label // // // // labels->inSort (newmark); // // // // for (int i = 0; i < len; i++) { // // if (qAbs(sam[i]) < level) { // // int j = i; // // while ((i < len) && (qAbs(sam[i]) < level)) i++; // // // // if (i - j > time) { // // //insert labels... // // newmark = new Kwave::Label(i, start); // // labels->inSort (newmark); // // // // if (start != stop) { // // newmark = new Kwave::Label(j, stop); // // labels->inSort (newmark); // // } // // } // // } // // dialog->setProgress (i); // // } // // // // newmark = new Kwave::Label(len - 1, stop); // // labels->inSort (newmark); // // // // refresh (); // // delete dialog; // // } // // } // //} // ////**************************************************************************** // //void MainWidget::markPeriods (const char *str) // //{ // // if (signalmanage) { // // Kwave::Parser parser (str); // // // // int high = signalmanage->getRate() / parser.toInt(); // // int low = signalmanage->getRate() / parser.toInt(); // // int octave = parser.toBool ("true"); // // double adjust = parser.toDouble (); // // // // for (int i = 0; i < AUTOKORRWIN; i++) // // autotable[i] = 1 - (((double)i * i * i) / (AUTOKORRWIN * AUTOKORRWIN * AUTOKORRWIN)); //generate static weighting function // // // // if (octave) for (int i = 0; i < AUTOKORRWIN; i++) weighttable[i] = 1; //initialise moving weight table // // // // Kwave::Label *newmark; // // int next; // // int len = signalmanage->getLength(); // // int *sam = signalmanage->getSignal(0)->getSample(); // ### @@@ ### // // LabelType *start = markertype; // // int cnt = findFirstMark (sam, len); // // // // ProgressDialog *dialog = new ProgressDialog (len - AUTOKORRWIN, "Correlating Signal to find Periods:"); // // if (dialog) { // // dialog->show(); // // // // newmark = new Kwave::Label(cnt, start); // // labels->inSort (newmark); // // // // while (cnt < len - 2*AUTOKORRWIN) { // // if (octave) // // next = findNextRepeatOctave (&sam[cnt], high, adjust); // // else // // next = findNextRepeat (&sam[cnt], high); // // // // if ((next < low) && (next > high)) { // // newmark = new Kwave::Label(cnt, start); // // // // labels->inSort (newmark); // // } // // if (next < AUTOKORRWIN) cnt += next; // // else // // if (cnt < len - AUTOKORRWIN) { // // int a = findFirstMark (&sam[cnt], len - cnt); // // if (a > 0) cnt += a; // // else cnt += high; // // } else cnt = len; // // // // dialog->setProgress (cnt); // // } // // // // delete dialog; // // // // refresh (); // // } // // } // //} // ////***************************************************************************** // //int findNextRepeat (int *sample, int high) // ////autocorellation of a windowed part of the sample // ////returns length of period, if found // //{ // // int i, j; // // double gmax = 0, max, c; // // int maxpos = AUTOKORRWIN; // // int down, up; //flags // // // // max = 0; // // for (j = 0; j < AUTOKORRWIN; j++) // // gmax += ((double)sample[j]) * sample [j]; // // // // //correlate signal with itself for finding maximum integral // // // // down = 0; // // up = 0; // // i = high; // // max = 0; // // while (i < AUTOKORRWIN) { // // c = 0; // // for (j = 0; j < AUTOKORRWIN; j++) c += ((double)sample[j]) * sample [i + j]; // // c = c * autotable[i]; //multiply window with weight for preference of high frequencies // // if (c > max) max = c, maxpos = i; // // i++; // // } // // return maxpos; // //} // ////***************************************************************************** // //int findNextRepeatOctave (int *sample, int high, double adjust = 1.005) // ////autocorellation of a windowed part of the sample // ////same as above only with an adaptive weighting to decrease fast period changes // //{ // // int i, j; // // double gmax = 0, max, c; // // int maxpos = AUTOKORRWIN; // // int down, up; //flags // // // // max = 0; // // for (j = 0; j < AUTOKORRWIN; j++) // // gmax += ((double)sample[j]) * sample [j]; // // // // //correlate signal with itself for finding maximum integral // // // // down = 0; // // up = 0; // // i = high; // // max = 0; // // while (i < AUTOKORRWIN) { // // c = 0; // // for (j = 0; j < AUTOKORRWIN; j++) c += ((double)sample[j]) * sample [i + j]; // // c = c * autotable[i] * weighttable[i]; // // //multiply window with weight for preference of high frequencies // // if (c > max) max = c, maxpos = i; // // i++; // // } // // // // for (int i = 0; i < AUTOKORRWIN; i++) weighttable[i] /= adjust; // // // // weighttable[maxpos] = 1; // // weighttable[maxpos + 1] = .9; // // weighttable[maxpos - 1] = .9; // // weighttable[maxpos + 2] = .8; // // weighttable[maxpos - 2] = .8; // // // // float buf[7]; // // // // for (int i = 0; i < 7; buf[i++] = .1) // // // // //low pass filter // // for (int i = high; i < AUTOKORRWIN - 3; i++) { // // buf[i % 7] = weighttable[i + 3]; // // weighttable[i] = (buf[0] + buf[1] + buf[2] + buf[3] + buf[4] + buf[5] + buf[6]) / 7; // // } // // // // return maxpos; // //} // //***************************************************************************** // //int findFirstMark (int *sample, int len) // ////finds first sample that is non-zero, or one that preceeds a zero crossing // //{ // // int i = 1; // // int last = sample[0]; // // int act = last; // // if ((last < 100) && (last > -100)) i = 0; // // else // // while (i < len) { // // act = sample[i]; // // if ((act < 0) && (last >= 0)) break; // // if ((act > 0) && (last <= 0)) break; // // last = act; // // i++; // // } // // return i; // //} //*************************************************************************** //*************************************************************************** diff --git a/libgui/CurveWidget.cpp b/libgui/CurveWidget.cpp index fe8d2ba3..013b5271 100644 --- a/libgui/CurveWidget.cpp +++ b/libgui/CurveWidget.cpp @@ -1,497 +1,497 @@ /*************************************************************************** CurveWidget.cpp - widget for editing an interpolated curve ------------------- begin : Sep 16 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas Eschenbacher ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/Curve.h" #include "libkwave/Interpolation.h" #include "libkwave/Logger.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libgui/CurveWidget.h" #include "libgui/FileDialog.h" //*************************************************************************** Kwave::CurveWidget::CurveWidget(QWidget *parent) :QWidget(parent), m_width(0), m_height(0), m_curve(), m_menu(Q_NULLPTR), m_preset_menu(Q_NULLPTR), m_current(Kwave::Curve::NoPoint), m_last(Kwave::Curve::NoPoint), m_down(false), m_knob(), m_selected_knob() { KIconLoader *icon_loader = KIconLoader::global(); // set the default curve m_curve.fromCommand(_("curve(linear,0,0,1,1)")); QPalette pal = palette(); pal.setColor(QPalette::Window, Qt::black); setPalette(pal); // create the pixmaps for the selected and non-selected knob if (icon_loader) { m_knob = icon_loader->loadIcon(_("knob.xpm"), KIconLoader::Small); m_selected_knob = icon_loader->loadIcon(_("selectedknob.xpm"), KIconLoader::Small); } // set up the context menu for the right mouse button m_menu = new(std::nothrow) QMenu(this); Q_ASSERT(m_menu); if (!m_menu) return; QMenu *interpolation = m_menu->addMenu(i18n("Interpolation")); Q_ASSERT(interpolation); if (!interpolation) return; m_menu->addSeparator(); QMenu *transform = m_menu->addMenu(i18n("Transform")); Q_ASSERT(transform); if (!transform) return; transform->addAction(i18n("Flip horizontal"), this, SLOT(HFlip())); transform->addAction(i18n("Flip vertical"), this, SLOT(VFlip())); transform->addSeparator(); transform->addAction(i18n("Into first half"), this, SLOT(firstHalf())); transform->addAction(i18n("Into second half"), this, SLOT(secondHalf())); QMenu *del = m_menu->addMenu(i18n("Delete")); Q_ASSERT(del); if (!del) return; m_menu->addAction(i18n("Fit In"), this, SLOT(scaleFit())); m_menu->addSeparator(); /* list of presets */ m_preset_menu = m_menu->addMenu(i18n("Presets")); Q_ASSERT(m_preset_menu); if (!m_preset_menu) return; loadPresetList(); connect(m_preset_menu, SIGNAL(triggered(QAction*)), this, SLOT(loadPreset(QAction*))); m_menu->addAction( QIcon::fromTheme(_("document-export")), i18n("Save Preset"), this, SLOT(savePreset())); del->addAction( QIcon::fromTheme(_("edit-delete")), i18n("Currently Selected Point"), this, SLOT(deleteLast()), QKeySequence::Delete); del->addAction(i18n("Every Second Point"), this, SLOT(deleteSecond())); QStringList types = Kwave::Interpolation::descriptions(true); int id = 0; foreach (const QString &text, types) { QAction *action = new(std::nothrow) QAction(interpolation); action->setText(text); action->setData(id++); interpolation->addAction(action); } connect(interpolation, SIGNAL(triggered(QAction*)), this, SLOT(selectInterpolationType(QAction*))); setMouseTracking(true); QShortcut *delkey = new(std::nothrow) QShortcut(this); Q_ASSERT(delkey); if (!delkey) return; delkey->setKey(Qt::Key_Delete); connect(delkey, SIGNAL(activated()), this, SLOT (deleteLast())); } //*************************************************************************** Kwave::CurveWidget::~CurveWidget() { if (m_menu) delete m_menu; } //*************************************************************************** QString Kwave::CurveWidget::getCommand() { return m_curve.getCommand(); } //*************************************************************************** void Kwave::CurveWidget::setCurve(const QString &command) { m_curve.fromCommand(command); repaint(); } //*************************************************************************** void Kwave::CurveWidget::selectInterpolationType(QAction *action) { if (!action) return; QVariant data = action->data(); int index = data.toInt(); m_curve.setInterpolationType(Kwave::Interpolation::findByIndex(index)); repaint(); } //*************************************************************************** void Kwave::CurveWidget::savePreset() { QString presetSubDir = _("presets") + QDir::separator() + _("curves"); QString presetPath = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation) + QDir::separator() + presetSubDir; if (!QDir(presetPath).exists()) { Kwave::Logger::log(this, Logger::Info, _("curve preset directory did not exist, creating '%1'").arg( presetPath)); QDir(presetPath).mkpath(presetPath); } QPointer dlg = new(std::nothrow) Kwave::FileDialog( presetPath, Kwave::FileDialog::SaveFile, _("*.curve *.CURVE|") + i18nc("Filter description for Kwave curve presets, " "for use in a FileDialog", "Kwave curve preset (*.curve)"), this, QUrl(), _("*.curve")); if (!dlg) return; dlg->setWindowTitle(i18n("Save Curve Preset")); - if (dlg->exec() != QDialog::Accepted) { + if ((dlg->exec() != QDialog::Accepted) || !dlg) { delete dlg; return; } QString name = dlg->selectedUrl().toLocalFile(); delete dlg; // append the extension if not given if (!name.endsWith(_(".curve"))) name.append(_(".curve")); QFile out(name); out.open(QIODevice::WriteOnly); QString cmd = m_curve.getCommand(); out.write(DBG(cmd), cmd.length()); // reload the list of known presets loadPresetList(); } //*************************************************************************** void Kwave::CurveWidget::loadPresetList() { const QChar s = QDir::separator(); QString presetSubDir = s + _("kwave") + s + _("presets") + s + _("curves"); QStringList files; QStringList presetPaths = QStandardPaths::standardLocations( QStandardPaths::GenericDataLocation); foreach (const QString &path, presetPaths) { QDir d(path + presetSubDir); QStringList f = d.entryList(QDir::Files, QDir::Name); foreach (const QString &file, f) { QString preset = d.path() + s + file; if (!files.contains(preset)) files.append(preset); } } files.sort(); m_preset_menu->clear(); foreach (const QString &file, files) { QFileInfo fi(file); QString name = fi.baseName(); QAction *action = new(std::nothrow) QAction(name, m_preset_menu); Q_ASSERT(action); if (!action) continue; action->setData(file); m_preset_menu->addAction(action); } } //*************************************************************************** void Kwave::CurveWidget::loadPreset(QAction *action) { Q_ASSERT(m_preset_menu); Q_ASSERT(action); if (!m_preset_menu || !action) return; if (!action->data().isValid()) return; // invalidate the current selection m_current = Kwave::Curve::NoPoint; m_last = Kwave::Curve::NoPoint; // get the path of the file and check whether it (still) exists QString filename = action->data().toString(); QFileInfo fi(filename); if (!fi.exists(filename)) return; // load the file QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { qWarning("CurveWidget::loadPreset('%s') - FAILED", DBG(filename)); return; } QTextStream stream(&file); m_curve.fromCommand(stream.readLine()); file.close(); repaint(); } //*************************************************************************** void Kwave::CurveWidget::secondHalf() { m_curve.secondHalf (); m_last = Kwave::Curve::NoPoint; repaint(); } //*************************************************************************** void Kwave::CurveWidget::firstHalf() { m_curve.firstHalf (); m_last = Kwave::Curve::NoPoint; repaint(); } //**************************************************************************** void Kwave::CurveWidget::deleteSecond() { m_curve.deleteSecondPoint(); m_last = Kwave::Curve::NoPoint; repaint (); } //**************************************************************************** void Kwave::CurveWidget::deleteLast() { if (m_last != Kwave::Curve::NoPoint) { m_curve.deletePoint(m_last, true); m_last = Kwave::Curve::NoPoint; repaint(); } } //*************************************************************************** void Kwave::CurveWidget::HFlip() { m_curve.HFlip(); repaint(); } //*************************************************************************** void Kwave::CurveWidget::VFlip() { m_curve.VFlip(); repaint(); } //*************************************************************************** void Kwave::CurveWidget::scaleFit() { m_curve.scaleFit(); repaint(); } //*************************************************************************** void Kwave::CurveWidget::addPoint(double newx, double newy) { m_curve.insert(newx, newy); m_last = Kwave::Curve::NoPoint; repaint(); } //*************************************************************************** Kwave::Curve::Point Kwave::CurveWidget::findPoint(int sx, int sy) // checks, if given coordinates fit to a control point in the list... { Q_ASSERT(m_width > 1); Q_ASSERT(m_height > 1); if ((m_width <= 1) || (m_height <= 1)) return Kwave::Curve::NoPoint; return m_curve.findPoint((static_cast(sx)) / (m_width - 1), (static_cast(m_height) - sy) / (m_height - 1)); } //*************************************************************************** void Kwave::CurveWidget::mousePressEvent(QMouseEvent *e) { Q_ASSERT(e); Q_ASSERT(m_width > 1); Q_ASSERT(m_height > 1); if (!e || (m_width <= 1) || (m_height <= 1)) return; if (e->buttons() == Qt::RightButton) { // right mouse button -> context menu QPoint popup = QCursor::pos(); if (m_menu) m_menu->popup(popup); } else if (e->buttons() == Qt::LeftButton) { // left mouse button -> select existing or create new point m_down = true; m_current = findPoint(e->pos().x(), e->pos().y()); if (m_current == Kwave::Curve::NoPoint) { // no matching point is found -> generate a new one ! addPoint(static_cast(e->pos().x()) / (m_width - 1), static_cast(m_height - e->pos().y()) / (m_height - 1)); m_current = findPoint(e->pos().x(), e->pos().y()); } repaint(); } } //*************************************************************************** void Kwave::CurveWidget::mouseReleaseEvent(QMouseEvent *) { m_last = m_current; m_current = Kwave::Curve::NoPoint; m_down = false; repaint(); } //*************************************************************************** void Kwave::CurveWidget::mouseMoveEvent(QMouseEvent *e ) { Q_ASSERT(e); Q_ASSERT(m_width > 1); Q_ASSERT(m_height > 1); if (!e || (m_width <= 1) || (m_height <= 1)) return; int x = e->pos().x(); int y = e->pos().y(); // if a point is selected... if (m_current != Kwave::Curve::NoPoint) { if (m_current == m_curve.first()) x = 0; if (m_current == m_curve.last()) x = m_width - 1; m_curve.deletePoint(m_current, false); m_current.setX(static_cast(x) / (m_width - 1)); m_current.setY(static_cast(m_height - y) / (m_height - 1)); if (m_current.x() < 0.0) m_current.setX(0.0); if (m_current.y() < 0.0) m_current.setY(0.0); if (m_current.x() > 1.0) m_current.setX(1.0); if (m_current.y() > 1.0) m_current.setY(1.0); double dx = (1.0 / static_cast(m_width - 1)); do { Kwave::Curve::Point nearest = m_curve.findPoint( m_current.x(), m_current.y(), 1.0); if (qFuzzyCompare(nearest.x(), m_current.x())) { if (nearest == m_curve.last()) m_current.setX(m_current.x() - dx); else m_current.setX(m_current.x() + dx); } else break; } while (true); m_curve.insert(m_current.x(), m_current.y()); repaint (); } else { if (findPoint(x, y) != Kwave::Curve::NoPoint) setCursor(Qt::SizeAllCursor); else setCursor(Qt::ArrowCursor); } } //*************************************************************************** void Kwave::CurveWidget::paintEvent(QPaintEvent *) { // qDebug("CurveWidget::paintEvent (QPaintEvent *)"); QPainter p; int ly; m_height = rect().height(); m_width = rect().width(); if (!m_curve.count()) return; // nothing to draw const int kw = m_knob.width(); const int kh = m_knob.height(); QVector y = m_curve.interpolation(m_width); Q_ASSERT(Kwave::toInt(y.count()) == m_width); if (Kwave::toInt(y.count()) < m_width) { qWarning("CurveWidget: unable to get interpolation !"); return; } p.begin(this); p.fillRect(rect(), QBrush(palette().dark())); p.setPen(palette().text().color()); // draw the lines ly = (m_height-1) - Kwave::toInt(y[0] * (m_height - 1)); for (int i = 1; i < m_width; i++) { int ay = (m_height-1) - Kwave::toInt(y[i] * (m_height - 1)); p.drawLine (i - 1, ly, i, ay); ly = ay; } // draw the points (knobs) foreach (const Kwave::Curve::Point &pt, m_curve) { int lx = Kwave::toInt(pt.x() * (m_width - 1)); ly = (m_height - 1) - Kwave::toInt(pt.y() * (m_height - 1)); if ((pt == m_current) || (!m_down && (pt == m_last)) ) p.drawPixmap(lx - (kw >> 1), ly - (kh >> 1), m_selected_knob); else p.drawPixmap(lx - (kw >> 1), ly - (kh >> 1), m_knob); } p.end(); } //*************************************************************************** //*************************************************************************** diff --git a/libkwave/SignalManager.cpp b/libkwave/SignalManager.cpp index 669a032c..f4ff70c9 100644 --- a/libkwave/SignalManager.cpp +++ b/libkwave/SignalManager.cpp @@ -1,2019 +1,2018 @@ /*************************************************************************** SignalManager.cpp - manager class for multi channel signals ------------------- begin : Sun Oct 15 2000 copyright : (C) 2000 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/ClipBoard.h" #include "libkwave/CodecManager.h" #include "libkwave/Decoder.h" #include "libkwave/Encoder.h" #include "libkwave/FileProgress.h" #include "libkwave/InsertMode.h" #include "libkwave/LabelList.h" #include "libkwave/MemoryManager.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/Parser.h" #include "libkwave/Sample.h" #include "libkwave/Signal.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Track.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "libkwave/undo/UndoAction.h" #include "libkwave/undo/UndoAddMetaDataAction.h" #include "libkwave/undo/UndoDeleteAction.h" #include "libkwave/undo/UndoDeleteMetaDataAction.h" #include "libkwave/undo/UndoDeleteTrack.h" #include "libkwave/undo/UndoInsertAction.h" #include "libkwave/undo/UndoInsertTrack.h" #include "libkwave/undo/UndoModifyAction.h" #include "libkwave/undo/UndoModifyMetaDataAction.h" #include "libkwave/undo/UndoSelection.h" #include "libkwave/undo/UndoTransaction.h" #include "libkwave/undo/UndoTransactionGuard.h" #define CASE_COMMAND(x) } else if (parser.command() == _(x)) { //*************************************************************************** Kwave::SignalManager::SignalManager(QWidget *parent) :QObject(), m_parent_widget(parent), m_closed(true), m_empty(true), m_modified(false), m_modified_enabled(true), m_signal(), m_selection(0,0), m_last_selection(0,0), m_last_track_selection(), m_last_length(0), m_playback_controller(*this), m_undo_enabled(false), m_undo_buffer(), m_redo_buffer(), m_undo_transaction(Q_NULLPTR), m_undo_transaction_level(0), m_undo_transaction_lock(QMutex::Recursive), m_meta_data() { // connect to the track's signals Kwave::Signal *sig = &m_signal; connect(sig, SIGNAL(sigTrackInserted(uint,Kwave::Track*)), this, SLOT(slotTrackInserted(uint,Kwave::Track*))); connect(sig, SIGNAL(sigTrackDeleted(uint,Kwave::Track*)), this, SLOT(slotTrackDeleted(uint,Kwave::Track*))); connect(sig, SIGNAL(sigTrackSelectionChanged(bool)), this,SIGNAL(sigTrackSelectionChanged(bool))); connect(sig, SIGNAL(sigSamplesDeleted(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesDeleted(unsigned int, sample_index_t, sample_index_t))); connect(sig, SIGNAL(sigSamplesInserted(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesInserted(unsigned int, sample_index_t, sample_index_t))); connect(sig, SIGNAL(sigSamplesModified(unsigned int, sample_index_t, sample_index_t)), this, SLOT(slotSamplesModified(unsigned int, sample_index_t, sample_index_t))); } //*************************************************************************** Kwave::SignalManager::~SignalManager() { close(); } //*************************************************************************** int Kwave::SignalManager::loadFile(const QUrl &url) { int res = 0; Kwave::FileProgress *dialog = Q_NULLPTR; // take over the new file name, so that we have a valid signal // name during loading QString filename = url.path(); QFile src(filename); QFileInfo fi(src); { Kwave::FileInfo info(m_meta_data); info.set(Kwave::INF_FILENAME, fi.absoluteFilePath()); m_meta_data.replace(Kwave::MetaDataList(info)); } // work with a copy of meta data, to avoid flicker effects Kwave::MetaDataList meta_data(m_meta_data); // enter and stay in not modified state enableModifiedChange(true); setModified(false); enableModifiedChange(false); // disable undo (discards all undo/redo data) disableUndo(); QString mimetype = Kwave::CodecManager::mimeTypeOf(url); qDebug("SignalManager::loadFile(%s) - [%s]", DBG(url.toDisplayString()), DBG(mimetype)); Kwave::Decoder *decoder = Kwave::CodecManager::decoder(mimetype); while (decoder) { // be sure that the current signal is really closed m_signal.close(); // open the source file if (!(res = decoder->open(m_parent_widget, src))) { qWarning("unable to open source: '%s'", DBG(url.toDisplayString())); res = -EIO; break; } // get the initial meta data from the decoder meta_data = decoder->metaData(); Kwave::FileInfo info(meta_data); // take the preliminary meta data, needed for estimated length m_meta_data = meta_data; // detect stream mode. if so, use one sample as display bool streaming = (!info.length()); // we must change to open state to see the file while // it is loaded m_closed = false; m_empty = false; // create all tracks (empty) unsigned int track; const unsigned int tracks = info.tracks(); const sample_index_t length = info.length(); Q_ASSERT(tracks); if (!tracks) break; for (track = 0; track < tracks; ++track) { Kwave::Track *t = m_signal.appendTrack(length, Q_NULLPTR); Q_ASSERT(t); if (!t || (t->length() != length)) { qWarning("SignalManager::loadFile: out of memory"); res = -ENOMEM; break; } } if (track < tracks) break; // create the multitrack writer as destination // if length was zero -> append mode / decode a stream ? Kwave::InsertMode mode = (streaming) ? Kwave::Append : Kwave::Overwrite; Kwave::MultiTrackWriter writers(*this, allTracks(), mode, 0, (length) ? length-1 : 0); // try to calculate the resulting length, but if this is // not possible, we try to use the source length instead quint64 resulting_size = info.tracks() * info.length() * (info.bits() >> 3); bool use_src_size = (!resulting_size); if (use_src_size) resulting_size = src.size(); // prepare and show the progress dialog dialog = new(std::nothrow) Kwave::FileProgress(m_parent_widget, QUrl(filename), resulting_size, info.length(), info.rate(), info.bits(), info.tracks()); Q_ASSERT(dialog); if (dialog && use_src_size) { // use source size for progress / stream mode QObject::connect(decoder, SIGNAL(sourceProcessed(quint64)), dialog, SLOT(setBytePosition(quint64))); QObject::connect(&writers, SIGNAL(written(quint64)), dialog, SLOT(setLength(quint64))); } else { // use resulting size percentage for progress QObject::connect(&writers, SIGNAL(progress(qreal)), dialog, SLOT(setValue(qreal))); } QObject::connect(dialog, SIGNAL(canceled()), &writers, SLOT(cancel())); // now decode res = 0; if (!decoder->decode(m_parent_widget, writers)) { qWarning("decoding failed."); res = -EIO; } else { // read information back from the decoder, some settings // might have become available during the decoding process meta_data = decoder->metaData(); info = Kwave::FileInfo(meta_data); } decoder->close(); // check for length info in stream mode if (!res && streaming) { // source was opened in stream mode -> now we have the length writers.flush(); sample_index_t new_length = writers.last(); if (new_length) new_length++; info.setLength(new_length); } else { info.setLength(this->length()); info.setTracks(tracks); } // enter the filename/mimetype and size into the file info info.set(Kwave::INF_FILENAME, fi.absoluteFilePath()); info.set(Kwave::INF_FILESIZE, src.size()); if (!info.contains(Kwave::INF_MIMETYPE)) info.set(Kwave::INF_MIMETYPE, mimetype); // remove the estimated length again, it is no longer needed info.set(Kwave::INF_ESTIMATED_LENGTH, QVariant()); // take over the decoded and updated file info meta_data.replace(Kwave::MetaDataList(info)); m_meta_data = meta_data; // update the length info in the progress dialog if needed if (dialog && use_src_size) { dialog->setLength( quint64(info.length()) * quint64(info.tracks())); dialog->setBytePosition(src.size()); } break; } if (!decoder) { qWarning("unknown file type"); res = -EINVAL; } else { delete decoder; } // process any queued events of the writers, like "sigSamplesInserted" qApp->processEvents(QEventLoop::ExcludeUserInputEvents); // remember the last length and selection m_last_length = length(); rememberCurrentSelection(); // from now on, undo is enabled enableUndo(); // modified can change from now on enableModifiedChange(true); if (dialog) delete dialog; if (res) close(); m_meta_data.dump(); // we now have new meta data emit sigMetaDataChanged(m_meta_data); return res; } //*************************************************************************** int Kwave::SignalManager::save(const QUrl &url, bool selection) { int res = 0; sample_index_t ofs = 0; sample_index_t len = length(); unsigned int tracks = this->tracks(); unsigned int bits = this->bits(); if (selection) { // zero-length -> nothing to do ofs = m_selection.offset(); len = m_selection.length(); tracks = selectedTracks().count(); } if (!tracks || !len) { Kwave::MessageBox::error(m_parent_widget, i18n("Signal is empty, nothing to save.")); return 0; } QString mimetype_name; mimetype_name = Kwave::CodecManager::mimeTypeOf(url); qDebug("SignalManager::save(%s) - [%s] (%d bit, selection=%d)", DBG(url.toDisplayString()), DBG(mimetype_name), bits, selection); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype_name); Kwave::FileInfo file_info(m_meta_data); if (encoder) { // maybe we now have a new mime type file_info.set(Kwave::INF_MIMETYPE, mimetype_name); // check if we lose information and ask the user if this would // be acceptable QList unsupported = encoder->unsupportedProperties( file_info.properties().keys()); if (!unsupported.isEmpty()) { QString list_of_lost_properties = _("\n"); foreach (const Kwave::FileProperty &p, unsupported) { list_of_lost_properties += i18n(UTF8(file_info.name(p))) + _("\n"); } // show a warning to the user and ask him if he wants to continue if (Kwave::MessageBox::warningContinueCancel(m_parent_widget, i18n("Saving in this format will lose the following " "additional file attribute(s):\n" "%1\n" "Do you still want to continue?", list_of_lost_properties), QString(), QString(), QString(), _("accept_lose_attributes_on_export") ) != KMessageBox::Continue) { delete encoder; return -1; } } // open the destination file QString filename = url.path(); QFile dst(filename); Kwave::MultiTrackReader src(Kwave::SinglePassForward, *this, (selection) ? selectedTracks() : allTracks(), ofs, ofs + len - 1); // update the file information file_info.setLength(len); file_info.setRate(rate()); file_info.setBits(bits); file_info.setTracks(tracks); if (!file_info.contains(Kwave::INF_SOFTWARE) && encoder->supportedProperties().contains(Kwave::INF_SOFTWARE)) { // add our Kwave Software tag const KAboutData about_data = KAboutData::applicationData(); QString software = about_data.displayName() + _("-") + about_data.version() + i18n("(built with KDE Frameworks %1)", _(KXMLGUI_VERSION_STRING)); file_info.set(Kwave::INF_SOFTWARE, software); } if (!file_info.contains(Kwave::INF_CREATION_DATE) && encoder->supportedProperties().contains(Kwave::INF_CREATION_DATE)) { // add a date tag QDate now(QDate::currentDate()); QString date; date = date.sprintf("%04d-%02d-%02d", now.year(), now.month(), now.day()); qDebug("adding date tag: '%s'", DBG(date)); file_info.set(Kwave::INF_CREATION_DATE, date); } // prepare and show the progress dialog Kwave::FileProgress *dialog = new(std::nothrow) Kwave::FileProgress(m_parent_widget, QUrl(filename), file_info.length() * file_info.tracks() * (file_info.bits() >> 3), file_info.length(), file_info.rate(), file_info.bits(), file_info.tracks() ); Q_ASSERT(dialog); QObject::connect(&src, SIGNAL(progress(qreal)), dialog, SLOT(setValue(qreal)), Qt::QueuedConnection); QObject::connect(dialog, SIGNAL(canceled()), &src, SLOT(cancel())); // invoke the encoder... bool encoded = false; m_meta_data.replace(Kwave::MetaDataList(file_info)); if (selection) { // use a copy, don't touch the original ! Kwave::MetaDataList meta = m_meta_data; // we have to adjust all position aware meta data meta.cropByRange(ofs, ofs + len - 1); // set the filename in the copy of the fileinfo, the original // file which is currently open keeps it's name Kwave::FileInfo info(meta); info.set(Kwave::INF_FILENAME, filename); meta.replace(Kwave::MetaDataList(info)); encoded = encoder->encode(m_parent_widget, src, dst, meta); } else { // in case of a "save as" -> modify the current filename file_info.set(Kwave::INF_FILENAME, filename); m_meta_data.replace(Kwave::MetaDataList(file_info)); encoded = encoder->encode(m_parent_widget, src, dst, m_meta_data); } if (!encoded) { Kwave::MessageBox::error(m_parent_widget, i18n("An error occurred while saving the file.")); res = -1; } delete encoder; encoder = Q_NULLPTR; if (dialog) { qApp->processEvents(); if (dialog->isCanceled()) { // user really pressed cancel ! Kwave::MessageBox::error(m_parent_widget, i18n("The file has been truncated and " "might be corrupted.")); res = -EINTR; } delete dialog; dialog = Q_NULLPTR; } } else { Kwave::MessageBox::error(m_parent_widget, i18n("Sorry, the file type is not supported.")); res = -EINVAL; } if (!res && !selection) { // saved without error -> no longer modified flushUndoBuffers(); enableModifiedChange(true); setModified(false); } emit sigMetaDataChanged(m_meta_data); qDebug("SignalManager::save(): res=%d",res); return res; } //*************************************************************************** void Kwave::SignalManager::newSignal(sample_index_t samples, double rate, unsigned int bits, unsigned int tracks) { // enter and stay in modified state enableModifiedChange(true); setModified(true); enableModifiedChange(false); // disable undo (discards all undo/redo data) disableUndo(); m_meta_data.clear(); Kwave::FileInfo file_info(m_meta_data); file_info.setRate(rate); file_info.setBits(bits); file_info.setTracks(tracks); m_meta_data.replace(Kwave::MetaDataList(file_info)); // now the signal is considered not to be empty m_closed = false; m_empty = false; // add all empty tracks while (tracks) { m_signal.appendTrack(samples, Q_NULLPTR); tracks--; } // remember the last length m_last_length = samples; file_info.setLength(length()); m_meta_data.replace(Kwave::MetaDataList(file_info)); rememberCurrentSelection(); // from now on, undo is enabled enableUndo(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::close() { // stop the playback m_playback_controller.playbackStop(); m_playback_controller.reset(); // fix the modified flag to false enableModifiedChange(true); setModified(false); enableModifiedChange(false); // reset the last length of the signal m_last_length = 0; // disable undo and discard all undo buffers // undo will be re-enabled when a signal is loaded or created disableUndo(); // for safety: flush all undo/redo buffers flushUndoBuffers(); flushRedoBuffer(); // reset the selection m_selection.clear(); m_empty = true; while (tracks()) deleteTrack(tracks() - 1); m_signal.close(); // clear all meta data m_meta_data.clear(); m_closed = true; rememberCurrentSelection(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** QString Kwave::SignalManager::signalName() { // if a file is loaded -> path of the URL if it has one QUrl url; url = QUrl(Kwave::FileInfo(m_meta_data).get(Kwave::INF_FILENAME).toString()); if (url.isValid()) return url.path(); // we have something, but no name yet if (!isClosed()) return QString(NEW_FILENAME); // otherwise: closed, nothing loaded return _(""); } //*************************************************************************** const QList Kwave::SignalManager::selectedTracks() { QList list; const unsigned int tracks = this->tracks(); for (unsigned int track = 0; track < tracks; track++) { if (!m_signal.trackSelected(track)) continue; list.append(track); } return list; } //*************************************************************************** const QList Kwave::SignalManager::allTracks() { return m_signal.allTracks(); } //*************************************************************************** int Kwave::SignalManager::executeCommand(const QString &command) { sample_index_t offset = m_selection.offset(); sample_index_t length = m_selection.length(); if (!command.length()) return -EINVAL; Kwave::Parser parser(command); if (false) { // --- undo / redo --- CASE_COMMAND("undo") undo(); CASE_COMMAND("redo") redo(); CASE_COMMAND("undo_all") while (m_undo_enabled && !m_undo_buffer.isEmpty()) undo(); CASE_COMMAND("redo_all") while (!m_redo_buffer.isEmpty()) redo(); // --- copy & paste + clipboard --- CASE_COMMAND("copy") if (length) { Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); clip.copy( m_parent_widget, *this, selectedTracks(), offset, length ); // remember the last selection rememberCurrentSelection(); } CASE_COMMAND("insert_at") Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); if (clip.isEmpty()) return 0; if (!selectedTracks().size()) return 0; sample_index_t ofs = parser.toSampleIndex(); Kwave::UndoTransactionGuard undo(*this, i18n("Insert Clipboard at position")); selectRange(ofs, 0); clip.paste(m_parent_widget, *this, ofs, 0); CASE_COMMAND("paste") Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); if (clip.isEmpty()) return 0; if (!selectedTracks().size()) return 0; Kwave::UndoTransactionGuard undo(*this, i18n("Paste")); clip.paste(m_parent_widget, *this, offset, length); CASE_COMMAND("cut") if (length) { // remember the last selection rememberCurrentSelection(); Kwave::ClipBoard &clip = Kwave::ClipBoard::instance(); clip.copy( m_parent_widget, *this, selectedTracks(), offset, length ); Kwave::UndoTransactionGuard undo(*this, i18n("Cut")); deleteRange(offset, length); selectRange(m_selection.offset(), 0); } CASE_COMMAND("clipboard_flush") Kwave::ClipBoard::instance().clear(); CASE_COMMAND("crop") if (length) { Kwave::UndoTransactionGuard undo(*this, i18n("Crop")); sample_index_t rest = this->length() - offset; rest = (rest > length) ? (rest-length) : 0; QList tracks = selectedTracks(); if (saveUndoDelete(tracks, offset+length, rest) && saveUndoDelete(tracks, 0, offset)) { // remember the last selection rememberCurrentSelection(); unsigned int count = tracks.count(); while (count--) { m_signal.deleteRange(count, offset+length, rest); m_signal.deleteRange(count, 0, offset); } selectRange(0, length); } } CASE_COMMAND("delete") Kwave::UndoTransactionGuard undo(*this, i18n("Delete")); deleteRange(offset, length); selectRange(m_selection.offset(), 0); // CASE_COMMAND("mixpaste") // if (globals.clipboard) { // SignalManager *toinsert = globals.clipboard->getSignal(); // if (toinsert) { // unsigned int clipchan = toinsert->channels(); // unsigned int sourcechan = 0; // // /* ### check if the signal has to be re-sampled ### */ // // for (unsigned int i = 0; i < m_channels; i++) { // Q_ASSERT(signal.at(i)); // if (signal.at(i)) { // signal.at(i)->mixPaste( // toinsert->getSignal(sourcechan) // ); // } // sourcechan++; // sourcechan %= clipchan; // } // } // } CASE_COMMAND("label:delete") int index = parser.toInt(); deleteLabel(index, true); CASE_COMMAND("expandtolabel") Kwave::UndoTransactionGuard undo(*this, i18n("Expand Selection to Label")); sample_index_t selection_left = m_selection.first(); sample_index_t selection_right = m_selection.last(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); // the last label <= selection start -> label_left // the first label >= selection end -> label_right foreach (const Kwave::Label &label, labels) { sample_index_t lp = label.pos(); if (lp <= selection_left) label_left = label; if ((lp >= selection_right) && (label_right.isNull())) { label_right = label; break; // done } } // default left label = start of file selection_left = (label_left.isNull()) ? 0 : label_left.pos(); // default right label = end of file selection_right = (label_right.isNull()) ? (this->length() - 1) : label_right.pos(); sample_index_t len = selection_right - selection_left + 1; selectRange(selection_left, len); CASE_COMMAND("selectnextlabels") Kwave::UndoTransactionGuard undo(*this, i18n("Select Next Labels")); sample_index_t selection_left; sample_index_t selection_right = m_selection.last(); Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this // special case: nothing selected -> select up to the first label if (selection_right == 0) { label_right = labels.first(); selection_left = 0; } else { // find the first label starting after the current selection LabelListIterator it(labels); while (it.hasNext()) { Kwave::Label label = it.next(); if (label.pos() >= selection_right) { // take it as selection start label_left = label; // and it's next one as selection end (might be null) label_right = it.hasNext() ? it.next() : Kwave::Label(); break; } } // default selection start = last label if (label_left.isNull()) label_left = labels.last(); if (label_left.isNull()) return false; // no labels at all !? selection_left = label_left.pos(); } // default selection end = end of the file selection_right = (label_right.isNull()) ? (this->length() - 1) : label_right.pos(); sample_index_t len = (selection_right > selection_left) ? (selection_right - selection_left + 1) : 1; selectRange(selection_left, len); CASE_COMMAND("selectprevlabels") Kwave::UndoTransactionGuard undo(*this, i18n("Select Previous Labels")); sample_index_t selection_left = selection().first(); sample_index_t selection_right = selection().last(); Kwave::Label label_left = Kwave::Label(); Kwave::Label label_right = Kwave::Label(); Kwave::LabelList labels(m_meta_data); if (labels.isEmpty()) return false; // we need labels for this // find the last label before the start of the selection foreach (const Kwave::Label &label, labels) { if (label.pos() > selection_left) break; // done label_left = label_right; label_right = label; } // default selection start = start of file selection_left = (label_left.isNull()) ? 0 : label_left.pos(); // default selection end = first label if (label_right.isNull()) label_right = labels.first(); if (label_right.isNull()) return false; // no labels at all !? selection_right = label_right.pos(); sample_index_t len = selection_right - selection_left + 1; selectRange(selection_left, len); // --- track related functions --- CASE_COMMAND("add_track") appendTrack(); CASE_COMMAND("delete_track") Kwave::Parser p(command); unsigned int track = p.toUInt(); if (track >= tracks()) return -EINVAL; deleteTrack(track); CASE_COMMAND("insert_track") Kwave::Parser p(command); unsigned int track = p.toUInt(); insertTrack(track); // track selection CASE_COMMAND("select_track:all") Kwave::UndoTransactionGuard undo(*this, i18n("Select All Tracks")); foreach (unsigned int track, allTracks()) selectTrack(track, true); CASE_COMMAND("select_track:none") Kwave::UndoTransactionGuard undo(*this, i18n("Deselect all tracks")); foreach (unsigned int track, allTracks()) selectTrack(track, false); CASE_COMMAND("select_track:invert") Kwave::UndoTransactionGuard undo(*this, i18n("Invert Track Selection")); foreach (unsigned int track, allTracks()) selectTrack(track, !trackSelected(track)); CASE_COMMAND("select_track:on") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Select Track")); selectTrack(track, true); CASE_COMMAND("select_track:off") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Deselect Track")); selectTrack(track, false); CASE_COMMAND("select_track:toggle") unsigned int track = parser.toUInt(); if (track >= tracks()) return -EINVAL; Kwave::UndoTransactionGuard undo(*this, i18n("Toggle Track Selection")); selectTrack(track, !(trackSelected(track))); // playback control CASE_COMMAND("playback_start") m_playback_controller.playbackStart(); CASE_COMMAND("fileinfo") QString property = parser.firstParam(); QString value = parser.nextParam(); Kwave::FileInfo info(m_meta_data); bool found = false; foreach (Kwave::FileProperty p, info.allKnownProperties()) { if (info.name(p) == property) { if (value.length()) info.set(p, QVariant(value)); // add/modify else info.set(p, QVariant()); // delete found = true; break; } } if (found) { m_meta_data.replace(Kwave::MetaDataList(info)); // we now have new meta data emit sigMetaDataChanged(m_meta_data); } else return -EINVAL; CASE_COMMAND("dump_metadata") qDebug("DUMP OF META DATA => %s", DBG(parser.firstParam())); m_meta_data.dump(); } else { return -ENOSYS; } return 0; } //*************************************************************************** void Kwave::SignalManager::appendTrack() { Kwave::UndoTransactionGuard undo(*this, i18n("Append Track")); insertTrack(tracks()); } //*************************************************************************** void Kwave::SignalManager::insertTrack(unsigned int index) { Kwave::UndoTransactionGuard undo(*this, i18n("Insert Track")); const unsigned int count = tracks(); Q_ASSERT(index <= count); if (index > count) index = count; // undo action for the track insert if (m_undo_enabled && !registerUndoAction( new(std::nothrow) Kwave::UndoInsertTrack(m_signal, index))) return; // if the signal is currently empty, use the last // known length instead of the current one sample_index_t len = (count) ? length() : m_last_length; if (index >= count) { // do an "append" m_signal.appendTrack(len, Q_NULLPTR); } else { // insert into the list m_signal.insertTrack(index, len, Q_NULLPTR); } // remember the last length m_last_length = length(); } //*************************************************************************** void Kwave::SignalManager::deleteTrack(unsigned int index) { Kwave::UndoTransactionGuard undo(*this, i18n("Delete Track")); const unsigned int count = tracks(); Q_ASSERT(index <= count); if (index > count) return; if (m_undo_enabled) { // undo action for the track deletion if (!registerUndoAction(new(std::nothrow) UndoDeleteTrack(m_signal, index))) return; } setModified(true); m_signal.deleteTrack(index); } //*************************************************************************** void Kwave::SignalManager::slotTrackInserted(unsigned int index, Kwave::Track *track) { setModified(true); Kwave::FileInfo file_info(m_meta_data); file_info.setTracks(tracks()); m_meta_data.replace(Kwave::MetaDataList(file_info)); emit sigTrackInserted(index, track); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotTrackDeleted(unsigned int index, Kwave::Track *track) { setModified(true); Kwave::FileInfo file_info(m_meta_data); file_info.setTracks(tracks()); m_meta_data.replace(Kwave::MetaDataList(file_info)); emit sigTrackDeleted(index, track); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesInserted(unsigned int track, sample_index_t offset, sample_index_t length) { // remember the last known length m_last_length = m_signal.length(); setModified(true); // only adjust the meta data once per operation QList tracks = selectedTracks(); if (track == tracks.first()) { m_meta_data.shiftRight(offset, length); } emit sigSamplesInserted(track, offset, length); Kwave::FileInfo info(m_meta_data); info.setLength(m_last_length); m_meta_data.replace(Kwave::MetaDataList(info)); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesDeleted(unsigned int track, sample_index_t offset, sample_index_t length) { // remember the last known length m_last_length = m_signal.length(); setModified(true); // only adjust the meta data once per operation QList tracks = selectedTracks(); if (track == tracks.first()) { m_meta_data.shiftLeft(offset, length); } emit sigSamplesDeleted(track, offset, length); Kwave::FileInfo info(m_meta_data); info.setLength(m_last_length); m_meta_data.replace(Kwave::MetaDataList(info)); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::slotSamplesModified(unsigned int track, sample_index_t offset, sample_index_t length) { setModified(true); emit sigSamplesModified(track, offset, length); } //*************************************************************************** bool Kwave::SignalManager::deleteRange(sample_index_t offset, sample_index_t length, const QList &track_list) { if (!length || track_list.isEmpty()) return true; // nothing to do // put the selected meta data into a undo action if (m_undo_enabled) { if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction( m_meta_data.copy(offset, length)))) { abortUndoTransaction(); return false; } m_meta_data.deleteRange(offset, length); // store undo data for all audio data (without meta data) if (!registerUndoAction(new(std::nothrow) UndoDeleteAction( m_parent_widget, track_list, offset, length))) { abortUndoTransaction(); return false; } } else { // delete without undo m_meta_data.deleteRange(offset, length); } // delete the ranges in all tracks // (this makes all metadata positions after the selected range invalid) foreach (unsigned int track, track_list) { m_signal.deleteRange(track, offset, length); } emit sigMetaDataChanged(m_meta_data); return true; } //*************************************************************************** bool Kwave::SignalManager::deleteRange(sample_index_t offset, sample_index_t length) { return deleteRange(offset, length, selectedTracks()); } //*************************************************************************** bool Kwave::SignalManager::insertSpace(sample_index_t offset, sample_index_t length, const QList &track_list) { if (!length) return true; // nothing to do Kwave::UndoTransactionGuard undo(*this, i18n("Insert Space")); unsigned int count = track_list.count(); if (!count) return true; // nothing to do // first store undo data for all tracks - unsigned int track; if (m_undo_enabled) { if (!registerUndoAction(new(std::nothrow) Kwave::UndoInsertAction( m_parent_widget, track_list, offset, length))) return false; } // then insert space into all tracks - foreach (track, track_list) { + foreach (unsigned int track, track_list) { m_signal.insertSpace(track, offset, length); } return true; } //*************************************************************************** void Kwave::SignalManager::selectRange(sample_index_t offset, sample_index_t length) { // first do some range checking sample_index_t len = this->length(); if (length > len) length = len; if (offset >= len) offset = (len) ? (len - 1) : 0; if ((offset + length) > len) length = len - offset; m_selection.select(offset, length); } //*************************************************************************** void Kwave::SignalManager::selectTracks(QList &track_list) { unsigned int track; unsigned int n_tracks = tracks(); for (track = 0; track < n_tracks; track++) { bool old_select = m_signal.trackSelected(track); bool new_select = track_list.contains(track); if (new_select != old_select) { m_signal.selectTrack(track, new_select); } } } //*************************************************************************** void Kwave::SignalManager::selectTrack(unsigned int track, bool select) { bool old_select = m_signal.trackSelected(track); if (select != old_select) { m_signal.selectTrack(track, select); } } //*************************************************************************** QList Kwave::SignalManager::stripes( const QList &track_list, sample_index_t left, sample_index_t right) { QList stripes; foreach (unsigned int track, track_list) { Kwave::Stripe::List s = m_signal.stripes(track, left, right); if (s.isEmpty()) { stripes.clear(); // something went wrong -> abort break; } stripes.append(s); } return stripes; } //*************************************************************************** bool Kwave::SignalManager::mergeStripes( const QList &stripes, const QList &track_list) { Q_ASSERT(stripes.count() == track_list.count()); if (stripes.count() != track_list.count()) return false; QListIterator it_s(stripes); QListIterator it_t(track_list); while (it_s.hasNext() && it_t.hasNext()) { Kwave::Stripe::List s = it_s.next(); unsigned int t = it_t.next(); if (!m_signal.mergeStripes(s, t)) return false; // operation failed } return true; } //*************************************************************************** Kwave::PlaybackController &Kwave::SignalManager::playbackController() { return m_playback_controller; } //*************************************************************************** void Kwave::SignalManager::startUndoTransaction(const QString &name) { if (!m_undo_enabled) return; // undo is currently not enabled QMutexLocker lock(&m_undo_transaction_lock); // check for modified selection checkSelectionChange(); // increase recursion level m_undo_transaction_level++; // start/create a new transaction if none existing if (!m_undo_transaction) { // if a new action starts, discard all redo actions ! flushRedoBuffer(); m_undo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(m_undo_transaction); if (!m_undo_transaction) return; // give all registered undo handlers a chance to register their own // undo actions if (!m_undo_manager.startUndoTransaction(m_undo_transaction)) { delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } // if it is the start of the transaction, also create one // for the selection UndoAction *selection = new(std::nothrow) Kwave::UndoSelection(*this); Q_ASSERT(selection); if (selection && selection->store(*this)) { m_undo_transaction->append(selection); } else { // out of memory delete selection; delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } } } //*************************************************************************** void Kwave::SignalManager::closeUndoTransaction() { QMutexLocker lock(&m_undo_transaction_lock); // decrease recursion level if (!m_undo_transaction_level) return; // undo was not enabled ? m_undo_transaction_level--; if (!m_undo_transaction_level) { // append the current transaction to the undo buffer if // not empty if (m_undo_transaction) { if (!m_undo_transaction->isEmpty()) { // if the transaction has been aborted, undo all actions // that have currently been queued but do not // use the transaction any more, instead delete it. if (m_undo_transaction->isAborted()) { qDebug("SignalManager::closeUndoTransaction(): aborted"); while (!m_undo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action undo_action = m_undo_transaction->takeLast(); Q_ASSERT(undo_action); if (!undo_action) continue; // execute the undo operation redo_action = undo_action->undo(*this, false); // remove the old undo action if no longer used if (redo_action && (redo_action != undo_action)) delete redo_action; delete undo_action; } delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } else { m_undo_buffer.append(m_undo_transaction); } } else { qDebug("SignalManager::closeUndoTransaction(): empty"); delete m_undo_transaction; m_undo_transaction = Q_NULLPTR; } } // dump, for debugging // if (m_undo_transaction) // m_undo_transaction->dump("closed undo transaction: "); // declare the current transaction as "closed" rememberCurrentSelection(); m_undo_transaction = Q_NULLPTR; emitUndoRedoInfo(); } } //*************************************************************************** void Kwave::SignalManager::enableUndo() { m_undo_enabled = true; emitUndoRedoInfo(); } //*************************************************************************** void Kwave::SignalManager::disableUndo() { if (m_undo_transaction_level) qWarning("SignalManager::disableUndo(): undo transaction level=%u", m_undo_transaction_level); flushUndoBuffers(); m_undo_enabled = false; } //*************************************************************************** void Kwave::SignalManager::flushUndoBuffers() { QMutexLocker lock(&m_undo_transaction_lock); // if the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo if ((!m_undo_buffer.isEmpty()) && (m_modified)) { enableModifiedChange(false); } // clear all buffers qDeleteAll(m_undo_buffer); qDeleteAll(m_redo_buffer); m_undo_buffer.clear(); m_redo_buffer.clear(); emitUndoRedoInfo(); } //*************************************************************************** void Kwave::SignalManager::abortUndoTransaction() { // abort the current transaction if (m_undo_transaction) m_undo_transaction->abort(); } //*************************************************************************** void Kwave::SignalManager::flushRedoBuffer() { qDeleteAll(m_redo_buffer); m_redo_buffer.clear(); emitUndoRedoInfo(); } //*************************************************************************** bool Kwave::SignalManager::continueWithoutUndo() { // undo has not been enabled before? if (!m_undo_transaction) return true; // transaction has been aborted before if (m_undo_transaction->isAborted()) return false; // transaction is empty -> must have been flushed before, otherwise // it would contain at least a undo action for the selection // => user already has pressed "continue" if (m_undo_transaction->isEmpty()) return true; if (Kwave::MessageBox::warningContinueCancel(m_parent_widget, _("") + i18n("Not enough memory for saving undo information.") + _("

") + i18n("Do you want to continue without the possibility to undo?") + _("

") + i18n("Hint: you can configure the amount of memory
" "available for undo under '%1'/'%2'.", i18n("Settings"), i18n("Memory") + _("
"))) == KMessageBox::Continue) { // the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo // from now on... setModified(true); enableModifiedChange(false); // flush the current undo transaction while (!m_undo_transaction->isEmpty()) { Kwave::UndoAction *undo_action = m_undo_transaction->takeLast(); if (undo_action) delete undo_action; } // flush all undo/redo buffers flushUndoBuffers(); return true; } // Set the undo transaction into "aborted" state. The final // closeUndoTransaction() will take care of the rest when // detecting that state and clean up... abortUndoTransaction(); return false; } //*************************************************************************** bool Kwave::SignalManager::registerUndoAction(Kwave::UndoAction *action) { QMutexLocker lock(&m_undo_transaction_lock); if (!action) return continueWithoutUndo(); // if undo is not enabled, this will fail -> discard the action if (!m_undo_enabled) { delete action; return true; } // check if the undo action is too large qint64 limit_mb = Kwave::MemoryManager::instance().undoLimit(); qint64 needed_size = action->undoSize(); qint64 needed_mb = needed_size >> 20; if (needed_mb > limit_mb) { delete action; return continueWithoutUndo(); } // undo has been aborted before ? if (!m_undo_transaction) return true; // transaction has been aborted before if (m_undo_transaction->isAborted()) { delete action; return true; } // make room... freeUndoMemory(needed_size); // now we might have enough place to append the undo action // and store all undo info if (!action->store(*this)) { delete action; return continueWithoutUndo(); } // everything went ok, register internally m_undo_transaction->append(action); return true; } //*************************************************************************** bool Kwave::SignalManager::saveUndoDelete(QList &track_list, sample_index_t offset, sample_index_t length) { if (!m_undo_enabled) return true; if (track_list.isEmpty()) return true; // create a undo action for deletion UndoDeleteAction *action = new(std::nothrow) UndoDeleteAction(m_parent_widget, track_list, offset, length); if (!registerUndoAction(action)) return false; return true; } //*************************************************************************** qint64 Kwave::SignalManager::usedUndoRedoMemory() { qint64 size = 0; foreach (Kwave::UndoTransaction *undo, m_undo_buffer) if (undo) size += undo->undoSize(); foreach (Kwave::UndoTransaction *redo, m_redo_buffer) if (redo) size += redo->undoSize(); return size; } //*************************************************************************** void Kwave::SignalManager::freeUndoMemory(qint64 needed) { qint64 size = usedUndoRedoMemory() + needed; qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; // remove old undo actions if not enough free memory while (!m_undo_buffer.isEmpty() && (size > undo_limit)) { Kwave::UndoTransaction *undo = m_undo_buffer.takeFirst(); if (!undo) continue; qint64 s = undo->undoSize(); size = (size >= s) ? (size - s) : 0; delete undo; // if the signal was modified, it will stay in this state, it is // not possible to change to "non-modified" state through undo if (m_modified) enableModifiedChange(false); } // remove old redo actions if still not enough memory while (!m_redo_buffer.isEmpty() && (size > undo_limit)) { Kwave::UndoTransaction *redo = m_redo_buffer.takeLast(); if (!redo) continue; qint64 s = redo->undoSize(); size = (size >= s) ? (size - s) : 0; delete redo; } } //*************************************************************************** void Kwave::SignalManager::emitUndoRedoInfo() { QString undo_name = QString(); QString redo_name = QString(); if (m_undo_enabled) { Kwave::UndoTransaction *transaction; // get the description of the last undo action if (!m_undo_buffer.isEmpty()) { transaction = m_undo_buffer.last(); if (transaction) undo_name = transaction->description(); if (!undo_name.length()) undo_name = i18n("Last Action"); } // get the description of the last redo action if (!m_redo_buffer.isEmpty()) { transaction = m_redo_buffer.first(); if (transaction) redo_name = transaction->description(); if (!redo_name.length()) redo_name = i18n("Last Action"); } } // now emit the undo/redo transaction names emit sigUndoRedoInfo(undo_name, redo_name); } //*************************************************************************** void Kwave::SignalManager::undo() { QMutexLocker lock(&m_undo_transaction_lock); // check for modified selection checkSelectionChange(); // remember the last selection rememberCurrentSelection(); // get the last undo transaction and abort if none present if (m_undo_buffer.isEmpty()) return; Kwave::UndoTransaction *undo_transaction = m_undo_buffer.takeLast(); if (!undo_transaction) return; // dump, for debugging // undo_transaction->dump("before undo: "); // temporarily disable undo while undo itself is running bool old_undo_enabled = m_undo_enabled; m_undo_enabled = false; // get free memory for redo qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; qint64 redo_size = undo_transaction->redoSize(); qint64 undo_size = undo_transaction->undoSize(); Kwave::UndoTransaction *redo_transaction = Q_NULLPTR; if ((redo_size > undo_size) && (redo_size - undo_size > undo_limit)) { // not enough memory for redo qWarning("SignalManager::undo(): not enough memory for redo !"); } else { // only free the memory if it will be used freeUndoMemory(redo_size); // create a new redo transaction QString name = undo_transaction->description(); redo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(redo_transaction); } // if *one* redo fails, all following redoes will also fail or // produce inconsistent data -> remove all of them ! if (!redo_transaction) { flushRedoBuffer(); qDebug("SignalManager::undo(): redo buffer flushed!"); } // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // execute all undo actions and store the resulting redo // actions into the redo transaction while (!undo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action undo_action = undo_transaction->takeLast(); Q_ASSERT(undo_action); if (!undo_action) continue; // execute the undo operation redo_action = undo_action->undo(*this, (redo_transaction != Q_NULLPTR)); // remove the old undo action if no longer used if (redo_action != undo_action) { delete undo_action; } // queue the action into the redo transaction if (redo_action) { if (redo_transaction) { redo_transaction->prepend(redo_action); } else { // redo is not usable :-( delete redo_action; } } } // now the undo_transaction should be empty -> get rid of it Q_ASSERT(undo_transaction->isEmpty()); delete undo_transaction; if (redo_transaction && (redo_transaction->count() < 1)) { // if there is no redo action -> no redo possible qWarning("SignalManager::undo(): no redo possible"); delete redo_transaction; redo_transaction = Q_NULLPTR; } // check whether the selection has changed, if yes: put a undo action // for this selection change at the end of the redo transaction if (redo_transaction) { bool range_modified = !(m_selection == m_last_selection); QList tracks = selectedTracks(); bool tracks_modified = !(tracks == m_last_track_selection); if (range_modified || tracks_modified) { UndoAction *redo_action = new(std::nothrow) Kwave::UndoSelection( *this, m_last_track_selection, m_last_selection.offset(), m_last_selection.length()); Q_ASSERT(redo_action); if (redo_action) redo_transaction->append(redo_action); } } // find out if there is still an action in the undo buffer // that has to do with modification of the signal if (m_modified) { bool stay_modified = false; foreach (Kwave::UndoTransaction *transaction, m_undo_buffer) { if (!transaction) continue; if (transaction->containsModification()) { stay_modified = true; break; } } if (!stay_modified) { // try to return to non-modified mode (might be a nop if // not enabled) setModified(false); } } // save "redo" information if possible if (redo_transaction) m_redo_buffer.prepend(redo_transaction); // remember the last selection rememberCurrentSelection(); // re-enable undo m_undo_enabled = old_undo_enabled; // finished / buffers have changed, emit new undo/redo info emitUndoRedoInfo(); // maybe the meta data has changed emit sigMetaDataChanged(m_meta_data); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** void Kwave::SignalManager::redo() { QMutexLocker lock(&m_undo_transaction_lock); // get the last redo transaction and abort if none present if (m_redo_buffer.isEmpty()) return; Kwave::UndoTransaction *redo_transaction = m_redo_buffer.takeFirst(); if (!redo_transaction) return; // check for modified selection checkSelectionChange(); // temporarily disable undo while redo is running bool old_undo_enabled = m_undo_enabled; m_undo_enabled = false; // get free memory for undo qint64 undo_limit = Kwave::MemoryManager::instance().undoLimit() << 20; qint64 undo_size = redo_transaction->undoSize(); qint64 redo_size = redo_transaction->redoSize(); Kwave::UndoTransaction *undo_transaction = Q_NULLPTR; if ((undo_size > redo_size) && (undo_size - redo_size > undo_limit)) { // not enough memory for undo qWarning("SignalManager::redo(): not enough memory for undo !"); } else { // only free the memory if it will be used freeUndoMemory(undo_size); // create a new undo transaction QString name = redo_transaction->description(); undo_transaction = new(std::nothrow) Kwave::UndoTransaction(name); Q_ASSERT(undo_transaction); } // if *one* undo fails, all following undoes will also fail or // produce inconsistent data -> remove all of them ! if (!undo_transaction) { qDeleteAll(m_undo_buffer); m_undo_buffer.clear(); } else { m_undo_buffer.append(undo_transaction); } // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // execute all redo actions and store the resulting undo // actions into the undo transaction bool modified = false; while (!redo_transaction->isEmpty()) { UndoAction *undo_action; UndoAction *redo_action; // unqueue the undo action redo_action = redo_transaction->takeFirst(); // execute the redo operation Q_ASSERT(redo_action); if (!redo_action) continue; modified |= redo_transaction->containsModification(); undo_action = redo_action->undo(*this, (undo_transaction != Q_NULLPTR)); // remove the old redo action if no longer used if (redo_action != undo_action) { delete redo_action; } // queue the action into the undo transaction if (undo_action) { if (undo_transaction) { undo_transaction->append(undo_action); } else { // undo is not usable :-( delete undo_action; } } } // now the redo_transaction should be empty -> get rid of it Q_ASSERT(redo_transaction->isEmpty()); delete redo_transaction; if (undo_transaction && (undo_transaction->count() < 1)) { // if there is no undo action -> no undo possible qWarning("SignalManager::redo(): no undo possible"); m_undo_buffer.removeAll(undo_transaction); delete undo_transaction; } // if the redo operation modified something, // we have to update the "modified" flag of the current signal if (modified) setModified(true); // remember the last selection rememberCurrentSelection(); // re-enable undo m_undo_enabled = old_undo_enabled; // finished / buffers have changed, emit new undo/redo info emitUndoRedoInfo(); // maybe the meta data has changed emit sigMetaDataChanged(m_meta_data); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** void Kwave::SignalManager::setModified(bool mod) { if (!m_modified_enabled) return; if (m_modified != mod) { m_modified = mod; // qDebug("SignalManager::setModified(%d)",mod); emit sigModified(); } } //*************************************************************************** void Kwave::SignalManager::enableModifiedChange(bool en) { m_modified_enabled = en; } //*************************************************************************** void Kwave::SignalManager::setFileInfo(const Kwave::FileInfo &new_info, bool with_undo) { if (m_undo_enabled && with_undo) { /* save data for undo */ Kwave::UndoTransactionGuard undo_transaction(*this, i18n("Modify File Info")); Kwave::FileInfo old_inf(m_meta_data); if (!registerUndoAction( new(std::nothrow) Kwave::UndoModifyMetaDataAction( Kwave::MetaDataList(old_inf)))) return; } m_meta_data.replace(Kwave::MetaDataList(new_info)); setModified(true); emitUndoRedoInfo(); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** Kwave::Label Kwave::SignalManager::findLabel(sample_index_t pos) { Kwave::LabelList labels(m_meta_data); foreach (const Kwave::Label &label, labels) { if (label.pos() == pos) return label; // found it } return Kwave::Label(); // nothing found } //*************************************************************************** int Kwave::SignalManager::labelIndex(const Kwave::Label &label) const { int index = 0; Kwave::LabelList labels(m_meta_data); foreach (const Kwave::Label &l, labels) { if (l == label) return index; // found it index++; } return -1; // nothing found*/ } //*************************************************************************** Kwave::Label Kwave::SignalManager::addLabel(sample_index_t pos, const QString &name) { // if there already is a label at the given position, do nothing if (!findLabel(pos).isNull()) return Kwave::Label(); // create a new label Kwave::Label label(pos, name); // register the undo action if (m_undo_enabled) { Kwave::UndoTransactionGuard undo(*this, i18n("Add Label")); if (!registerUndoAction(new(std::nothrow) UndoAddMetaDataAction( Kwave::MetaDataList(label)))) return Kwave::Label(); } // put the label into the list m_meta_data.add(label); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); return label; } //*************************************************************************** void Kwave::SignalManager::deleteLabel(int index, bool with_undo) { Kwave::LabelList labels(m_meta_data); int count = Kwave::toInt(labels.count()); if (!count) return; if (index == -1) { // special handling for index == -1 -> delete all labels if (with_undo) startUndoTransaction(i18n("Delete All Labels")); for (index = count - 1; index >= 0; --index) { Kwave::MetaData label(labels.at(index)); if (with_undo) { if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction(Kwave::MetaDataList(label)))) break; } m_meta_data.remove(label); } } else { // delete a single label if ((index < 0) || (index >= count)) return; Kwave::MetaData label(labels.at(index)); // register the undo action if (with_undo) { startUndoTransaction(i18n("Delete Label")); if (!registerUndoAction(new(std::nothrow) UndoDeleteMetaDataAction(Kwave::MetaDataList(label)))) { abortUndoTransaction(); return; } } m_meta_data.remove(label); } if (with_undo) closeUndoTransaction(); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** bool Kwave::SignalManager::modifyLabel(int index, sample_index_t pos, const QString &name, bool with_undo) { Kwave::LabelList labels(m_meta_data); if ((index < 0) || (index >= Kwave::toInt(labels.count()))) return false; Kwave::Label label = labels.at(index); // check: if the label should be moved and there already is a label // at the new position -> fail if ((pos != label.pos()) && !findLabel(pos).isNull()) return false; // add a undo action if (with_undo && m_undo_enabled) { Kwave::UndoModifyMetaDataAction *undo_modify = new(std::nothrow) Kwave::UndoModifyMetaDataAction( Kwave::MetaDataList(label)); if (!registerUndoAction(undo_modify)) return false; } // now modify the label label.moveTo(pos); label.rename(name); m_meta_data.add(label); // register this as a modification setModified(true); emit sigMetaDataChanged(m_meta_data); return true; } //*************************************************************************** void Kwave::SignalManager::mergeMetaData(const Kwave::MetaDataList &meta_data) { m_meta_data.add(meta_data); emit sigMetaDataChanged(m_meta_data); } //*************************************************************************** void Kwave::SignalManager::rememberCurrentSelection() { m_last_selection = m_selection; m_last_track_selection = selectedTracks(); } //*************************************************************************** void Kwave::SignalManager::checkSelectionChange() { if (m_undo_transaction_level) return; // detect sample selection change bool range_modified = !(m_selection == m_last_selection); // detect track selection change QList tracks = selectedTracks(); bool tracks_modified = !(tracks == m_last_track_selection); if (range_modified || tracks_modified) { // selection has changed since last undo/redo operation // qDebug("SignalManager::checkSelectionChange() => manually modified"); // qDebug(" before: [%8u...%8u]", m_last_selection.first(), m_last_selection.last()); // qDebug(" after : [%8u...%8u]", m_selection.first(), m_selection.last()); // temporarily activate the previous selection (last stored) Kwave::Selection new_selection(m_selection); m_selection = m_last_selection; selectTracks(m_last_track_selection); // save the last selection into a undo action if (tracks_modified && !range_modified) Kwave::UndoTransactionGuard undo(*this, i18n("Manual Track Selection")); else Kwave::UndoTransactionGuard undo(*this, i18n("Manual Selection")); // restore the current selection again m_selection = new_selection; selectTracks(tracks); } } //*************************************************************************** //*************************************************************************** diff --git a/plugins/about/AboutPlugin.cpp b/plugins/about/AboutPlugin.cpp index 2f8a679d..100cb5a0 100644 --- a/plugins/about/AboutPlugin.cpp +++ b/plugins/about/AboutPlugin.cpp @@ -1,60 +1,62 @@ /*************************************************************************** AboutPlugin.cpp - plugin that shows the Kwave's about dialog ------------------- begin : Sun Oct 29 2000 copyright : (C) 2000 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include +#include + #include #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "AboutDialog.h" #include "AboutPlugin.h" KWAVE_PLUGIN(about, AboutPlugin) //*************************************************************************** Kwave::AboutPlugin::AboutPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args) { } //*************************************************************************** int Kwave::AboutPlugin::start(QStringList& params) { Q_UNUSED(params); // create a new "about" dialog and show it - Kwave::AboutDialog *dlg = new(std::nothrow) Kwave::AboutDialog( + QPointer dlg = new(std::nothrow) Kwave::AboutDialog( parentWidget(), manager().pluginInfoList() ); Q_ASSERT(dlg); if (!dlg) return ENOMEM; dlg->exec(); delete dlg; return 0; } //*************************************************************************** #include "AboutPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/amplifyfree/AmplifyFreePlugin.cpp b/plugins/amplifyfree/AmplifyFreePlugin.cpp index 60468495..4795bccd 100644 --- a/plugins/amplifyfree/AmplifyFreePlugin.cpp +++ b/plugins/amplifyfree/AmplifyFreePlugin.cpp @@ -1,197 +1,198 @@ /*************************************************************************** AmplifyFreePlugin.cpp - Plugin for free amplification curves ------------------- begin : Sun Sep 02 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include +#include #include #include #include "libkwave/Connect.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackSource.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/Parser.h" #include "libkwave/PluginManager.h" #include "libkwave/String.h" #include "libkwave/modules/CurveStreamAdapter.h" #include "libkwave/modules/Mul.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "AmplifyFreeDialog.h" #include "AmplifyFreePlugin.h" KWAVE_PLUGIN(amplifyfree, AmplifyFreePlugin) //*************************************************************************** Kwave::AmplifyFreePlugin::AmplifyFreePlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_action_name(), m_params(), m_curve(), m_cmd_map() { m_cmd_map[_("fade in")] = i18n("Fade In"); m_cmd_map[_("fade out")] = i18n("Fade Out"); m_cmd_map[_("fade intro")] = i18n("Fade Intro"); m_cmd_map[_("fade leadout")] = i18n("Fade Leadout"); } //*************************************************************************** Kwave::AmplifyFreePlugin::~AmplifyFreePlugin() { } //*************************************************************************** int Kwave::AmplifyFreePlugin::interpreteParameters(QStringList ¶ms) { // store last parameters m_params = params; m_action_name = _(""); if (params.count() < 2) return -1; if (params.count() & 1) return -1; // no. of params must be even // first list entry == name of operation m_action_name = (params[0].length() && m_cmd_map.contains(params[0])) ? m_cmd_map[params[0]] : i18n("Amplify Free"); // convert string list into command again... QString cmd = _("curve("); for (int i = 1; i < params.count(); ++i) { cmd += params[i]; if ((i + 1) < params.count()) cmd += _(","); } cmd += _(")"); // and initialize our curve with it m_curve.fromCommand(cmd); return 0; } //*************************************************************************** QStringList *Kwave::AmplifyFreePlugin::setup(QStringList &previous_params) { // try to interprete the previous parameters interpreteParameters(previous_params); // create the setup dialog - Kwave::AmplifyFreeDialog *dialog = + QPointer dialog = new(std::nothrow) Kwave::AmplifyFreeDialog(parentWidget()); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; // remove the first list entry (action name), the rest is for the dialog if ((m_params.count() > 2) && !(m_params.count() & 1)) { QStringList curve_params = m_params; curve_params.takeFirst(); // ignore action name dialog->setParams(curve_params); } QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" *list << _("amplify free"); QString cmd = dialog->getCommand(); Kwave::Parser p(cmd); while (!p.isDone()) *list << p.nextParam(); qDebug("setup -> emitCommand('%s')", DBG(cmd)); emitCommand(cmd); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** QString Kwave::AmplifyFreePlugin::progressText() { return m_action_name; } //*************************************************************************** int Kwave::AmplifyFreePlugin::start(QStringList ¶ms) { interpreteParameters(params); return Kwave::Plugin::start(params); } //*************************************************************************** void Kwave::AmplifyFreePlugin::run(QStringList params) { sample_index_t first, last; QList track_list; interpreteParameters(params); Kwave::UndoTransactionGuard undo_guard(*this, m_action_name); sample_index_t input_length = selection(&track_list, &first, &last, true); unsigned int tracks = track_list.count(); // create all objects Kwave::MultiTrackReader source(Kwave::SinglePassForward, signalManager(), selectedTracks(), first, last); Kwave::CurveStreamAdapter curve(m_curve, input_length); Kwave::MultiTrackWriter sink(signalManager(), track_list, Kwave::Overwrite, first, last); Kwave::MultiTrackSource mul(tracks, this); // break if aborted if (!sink.tracks()) return; // connect them bool ok = true; if (ok) ok = Kwave::connect( source, SIGNAL(output(Kwave::SampleArray)), mul, SLOT(input_a(Kwave::SampleArray))); if (ok) ok = Kwave::connect( curve, SIGNAL(output(Kwave::SampleArray)), mul, SLOT(input_b(Kwave::SampleArray))); if (ok) ok = Kwave::connect( mul, SIGNAL(output(Kwave::SampleArray)), sink, SLOT(input(Kwave::SampleArray))); if (!ok) { return; } // connect the progress dialog connect(&sink, SIGNAL(progress(qreal)), this, SLOT(updateProgress(qreal)), Qt::BlockingQueuedConnection); // transport the samples qDebug("AmplifyFreePlugin: filter started..."); while (!shouldStop() && !source.done()) { source.goOn(); curve.goOn(); /* mul.goOn(); */ } qDebug("AmplifyFreePlugin: filter done."); } //*************************************************************************** #include "AmplifyFreePlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/codec_mp3/MP3CodecPlugin.cpp b/plugins/codec_mp3/MP3CodecPlugin.cpp index a6e21786..ff790385 100644 --- a/plugins/codec_mp3/MP3CodecPlugin.cpp +++ b/plugins/codec_mp3/MP3CodecPlugin.cpp @@ -1,95 +1,97 @@ /************************************************************************* MP3CodecPlugin.cpp - import and export of MP3 data ------------------- begin : Mon May 28 2012 copyright : (C) 2012 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" +#include + #include "libkwave/String.h" #include "MP3CodecPlugin.h" #include "MP3Decoder.h" #include "MP3Encoder.h" #include "MP3EncoderDialog.h" KWAVE_PLUGIN(codec_mp3, MP3CodecPlugin) // static instance of the codec container Kwave::CodecPlugin::Codec Kwave::MP3CodecPlugin::m_codec = EMPTY_CODEC; /***************************************************************************/ Kwave::MP3CodecPlugin::MP3CodecPlugin(QObject *parent, const QVariantList &args) :Kwave::CodecPlugin(parent, args, m_codec) { } /***************************************************************************/ Kwave::MP3CodecPlugin::~MP3CodecPlugin() { } /***************************************************************************/ void Kwave::MP3CodecPlugin::load(QStringList ¶ms) { emitCommand(_("menu (plugin:setup(codec_mp3), Settings/%1)").arg( _(I18N_NOOP2("menu: /Settings/MP3 Encoder Setup", "MP3 Encoder Setup")))); Kwave::CodecPlugin::load(params); } //*************************************************************************** QStringList *Kwave::MP3CodecPlugin::setup(QStringList &previous_params) { Q_UNUSED(previous_params); // create the setup dialog - MP3EncoderDialog *dialog = + QPointer dialog = new(std::nothrow) MP3EncoderDialog(parentWidget()); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" dialog->save(); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } /***************************************************************************/ QList Kwave::MP3CodecPlugin::createDecoder() { return singleDecoder(); } /***************************************************************************/ QList Kwave::MP3CodecPlugin::createEncoder() { return singleEncoder(); } //*************************************************************************** #include "MP3CodecPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/export_k3b/K3BExportPlugin.cpp b/plugins/export_k3b/K3BExportPlugin.cpp index 85b36308..25c509d2 100644 --- a/plugins/export_k3b/K3BExportPlugin.cpp +++ b/plugins/export_k3b/K3BExportPlugin.cpp @@ -1,791 +1,792 @@ /************************************************************************* * K3BExportPlugin.cpp - export of K3b project files * ------------------- * begin : Thu Apr 13 2017 * copyright : (C) 2017 by Thomas Eschenbacher * email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include // for the i18n macro #include #include "libkwave/CodecManager.h" #include "libkwave/Encoder.h" #include "libkwave/FileInfo.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/Logger.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/Parser.h" #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "K3BExportDialog.h" #include "K3BExportPlugin.h" KWAVE_PLUGIN(export_k3b, K3BExportPlugin) /** mime type of K3b project files */ #define K3B_PROJECT_MIME_TYPE "application/x-k3b" /** file suffix of K3b project files */ #define K3B_FILE_SUFFIX _("*.k3b") /** number of digits to use for out files */ #define OUTFILE_DIGITS 4 /** file name pattern for out files */ #define OUTFILE_PATTERN (_("[%0") + _("%1nr]").arg(OUTFILE_DIGITS)) /** file suffix for out files */ #define OUTFILE_SUFFIX _(".wav") //*************************************************************************** Kwave::K3BExportPlugin::K3BExportPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_url(), m_pattern(), m_selection_only(false), m_export_location(EXPORT_TO_SUB_DIR), m_overwrite_policy(USE_NEW_FILE_NAMES), m_block_info() { } //*************************************************************************** Kwave::K3BExportPlugin::~K3BExportPlugin() { } //*************************************************************************** int Kwave::K3BExportPlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; // evaluate the parameter list if (params.count() != 5) return -EINVAL; // the selected URL m_url = QUrl::fromUserInput(Kwave::Parser::unescape(params[0])); if (!m_url.isValid()) return -EINVAL; // label pattern m_pattern = Kwave::Parser::unescape(params[1]); // selection only param = params[2]; int v = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; m_selection_only = (v != 0); // export location param = params[3]; int where = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; if ((where != EXPORT_TO_SAME_DIR) && (where != EXPORT_TO_SUB_DIR)) return -EINVAL; m_export_location = static_cast(where); // overwrite policy param = params[4]; int overwrite = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; if ((overwrite != OVERWRITE_EXISTING_FILES) && (overwrite != USE_NEW_FILE_NAMES)) return -EINVAL; m_overwrite_policy = static_cast(overwrite); return 0; } //*************************************************************************** void Kwave::K3BExportPlugin::scanBlocksToSave(const QString &base, sample_index_t selection_left, sample_index_t selection_right) { sample_index_t block_start; sample_index_t block_end = 0; Kwave::LabelList labels(signalManager().metaData()); Kwave::LabelListIterator it(labels); Kwave::Label label = (it.hasNext()) ? it.next() : Kwave::Label(); // get the title of the whole file, in case that a block does not have // an own title FileInfo info(signalManager().metaData()); QString file_title = info.get(INF_NAME).toString(); QString file_artist = info.get(INF_AUTHOR).toString(); // fallback: if there is no INF_NAME either, fall back to the file // name as last resort if (!file_title.length()) file_title = base; m_block_info.clear(); QString prev_title = file_title; for (unsigned int index = 1; ; ++index) { block_start = block_end; block_end = (label.isNull()) ? signalLength() : label.pos(); QString block_title = (!label.isNull() && label.name().length()) ? label.name() : prev_title; if ((block_end > selection_left) && (block_start <= selection_right)) { BlockInfo block; // init and set reasonable defaults block.m_index = index; block.m_filename = QString(); block.m_start = block_start; block.m_length = block_end - block_start; block.m_title = prev_title; block.m_artist = file_artist; // detect title and artist detectBlockMetaData(prev_title, m_pattern, block); m_block_info.append(block); prev_title = block_title; // qDebug("#%d [%llu...%llu]", index, block_start, block_end); // qDebug(" title = '%s'", DBG(block.m_title)); // qDebug(" artist = '%s'", DBG(block.m_artist)); } else { prev_title = block_title; } if (label.isNull()) break; label = (it.hasNext()) ? it.next() : Kwave::Label(); } } //*************************************************************************** QString Kwave::K3BExportPlugin::createFileName(const QString &pattern, unsigned int index) { QString name = pattern; QString num = _("%1").arg(index, OUTFILE_DIGITS, 10, QLatin1Char('0')); name.replace(OUTFILE_PATTERN, num); name += OUTFILE_SUFFIX; return name; } //*************************************************************************** bool Kwave::K3BExportPlugin::detectBlockMetaData( const QString &text, const QString &pattern, Kwave::K3BExportPlugin::BlockInfo &block ) { if (!pattern.length()) { // auto detect -> try all known patterns foreach (const QString &p, knownPatterns()) if (detectBlockMetaData(text, p, block)) return true; return false; } // list of placeholders and pointers to the resulting strings QMap map_patterns; map_patterns.insert(_("[%artist]"), &block.m_artist); map_patterns.insert(_("[%title]"), &block.m_title); // try to find the placeholders within the pattern // NOTE: we use a map because it will automatically be sorted (by pos) QString pattern_esc = Kwave::Parser::escape(pattern); QMap map_result; foreach (const QString &placeholder, map_patterns.keys()) { QString placeholder_esc; placeholder_esc = Kwave::Parser::escape(placeholder); if (pattern_esc.contains(placeholder_esc)) { const QString rx_string = _("(.+)"); int pos = pattern.indexOf(placeholder); pattern_esc.replace(placeholder_esc, rx_string); map_result.insert(pos, map_patterns[placeholder]); } } if (map_result.isEmpty()) return false; // no placeholders found in the patterns // relax the pattern: turn single whitespace to one or more whitespaces pattern_esc.replace(QRegExp(_("(\\\\\\s)+")), _("\\s+")); // try to match the pattern on the given text QRegExp rx(pattern_esc, Qt::CaseInsensitive); if (!rx.exactMatch(text.trimmed())) return false; // does not match :-( // we found a match // -> now map the results into the corresponding result strings for (int index = 0; index < map_result.count(); ++index) { QString value = rx.cap(index + 1).trimmed(); if (value.length()) { QString *result = map_result[map_result.keys()[index]]; if (result) *result = value; } } return true; } //*************************************************************************** void Kwave::K3BExportPlugin::load(QStringList ¶ms) { Q_UNUSED(params); QString menu_path = _("File/Save/%1").arg(_(I18N_NOOP2( "menu: /File/Save/Export to K3b Project...", "Export to K3b Project..." ))); emitCommand(_("menu(plugin:setup(export_k3b),%1%2)").arg( menu_path).arg(_("/#group(@SIGNAL)"))); emitCommand(_("menu(plugin:setup(export_k3b),%1%2)").arg( menu_path).arg(_("/#icon(application-x-k3b)"))); } //*************************************************************************** QStringList *Kwave::K3BExportPlugin::setup(QStringList ¶ms) { // try to interpret the previous parameters interpreteParameters(params); sample_index_t selection_left = 0; sample_index_t selection_right = 0; selection(Q_NULLPTR, &selection_left, &selection_right, false); // enable the "selection only" checkbox only if there is something // selected but not everything bool selected_something = (selection_left != selection_right); bool selected_all = ((selection_left == 0) && (selection_right + 1 >= signalLength())); bool enable_selection_only = selected_something && !selected_all; // show a "File / Save As..." dialog for the *.k3b file QPointer dialog = new(std::nothrow) Kwave::K3BExportDialog( _("kfiledialog:///kwave_export_k3b"), K3B_FILE_SUFFIX + _("|") + i18nc( "file type filter when exporting to K3b", "K3b project file (*.k3b)" ), parentWidget(), QUrl::fromUserInput(signalName()), _("*.k3b"), m_pattern, m_selection_only, enable_selection_only, m_export_location, m_overwrite_policy ); if (!dialog) return Q_NULLPTR; dialog->setWindowTitle(description()); - if (dialog->exec() != QDialog::Accepted) { + if ((dialog->exec() != QDialog::Accepted) || !dialog) { delete dialog; return Q_NULLPTR; } QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); if (!list) { delete dialog; return Q_NULLPTR; } // user has pressed "OK" QUrl url = dialog->selectedUrl(); if (url.isEmpty()) { delete dialog; delete list; return Q_NULLPTR; } QString name = url.path(); QFileInfo path(name); // add the correct extension if necessary if (path.suffix() != K3B_FILE_SUFFIX.mid(2)) url.setPath(name + K3B_FILE_SUFFIX.mid(1)); name = Kwave::Parser::escape(url.toString()); QString pattern = Kwave::Parser::escape(dialog->pattern()); int export_location = static_cast(dialog->exportLocation()); int overwrite_policy = static_cast(dialog->overwritePolicy()); bool selection_only = (enable_selection_only) ? dialog->selectionOnly() : m_selection_only; *list << name; // url *list << pattern; // pattern *list << QString::number(selection_only); // selection only *list << QString::number(export_location); // export location *list << QString::number(overwrite_policy); // overwrite policy emitCommand(_("plugin:execute(export_k3b,") + name + _(",") + pattern + _(",") + QString::number(selection_only) + _(",") + QString::number(export_location) + _(",") + QString::number(overwrite_policy) + _(")") ); if (dialog) delete dialog; return list; } //*************************************************************************** /* * taken from K3b, libk3b/projects/k3bdoc.cpp * * Copyright (C) 2003-2008 Sebastian Trueg * */ void Kwave::K3BExportPlugin::saveGeneralDocumentData(QDomElement *part) { QDomDocument doc = part->ownerDocument(); QDomElement mainElem = doc.createElement(_("general")); QDomElement propElem = doc.createElement(_("writing_mode")); propElem.appendChild(doc.createTextNode(_("auto"))); mainElem.appendChild(propElem); propElem = doc.createElement(_("dummy")); propElem.setAttribute(_("activated"), _("no")); mainElem.appendChild(propElem); propElem = doc.createElement(_("on_the_fly")); propElem.setAttribute(_("activated"), _("true")); mainElem.appendChild(propElem); propElem = doc.createElement(_("only_create_images")); propElem.setAttribute(_("activated"), _("no")); mainElem.appendChild(propElem); propElem = doc.createElement(_("remove_images")); propElem.setAttribute(_("activated"), _("no")); mainElem.appendChild(propElem); part->appendChild( mainElem ); } //*************************************************************************** /* * taken from K3b, libk3b/projects/audiocd/k3baudiodoc.cpp * * Copyright (C) 2003-2008 Sebastian Trueg * Copyright (C) 2009 Gustavo Pichorim Boiko * Copyright (C) 2010 Michal Malek */ void Kwave::K3BExportPlugin::saveDocumentData(QDomElement *docElem) { #define GET_INF(inf) doc.createTextNode(info.get(inf).toString()) const Kwave::FileInfo info(signalManager().metaData()); QDomDocument doc = docElem->ownerDocument(); saveGeneralDocumentData(docElem); // add normalize QDomElement normalizeElem = doc.createElement(_("normalize")); normalizeElem.appendChild(doc.createTextNode(_("no"))); docElem->appendChild(normalizeElem); // add hide track QDomElement hideFirstTrackElem = doc.createElement(_("hide_first_track")); hideFirstTrackElem.appendChild(doc.createTextNode(_("no"))); docElem->appendChild(hideFirstTrackElem); // save the audio cd ripping settings // paranoia mode, read retries, and ignore read errors // ------------------------------------------------------------ QDomElement ripMain = doc.createElement(_("audio_ripping")); docElem->appendChild(ripMain); QDomElement ripElem = doc.createElement(_("paranoia_mode")); ripElem.appendChild(doc.createTextNode(_("0"))); ripMain.appendChild(ripElem); ripElem = doc.createElement(_("read_retries")); ripElem.appendChild(doc.createTextNode(_("0"))); ripMain.appendChild(ripElem); ripElem = doc.createElement(_("ignore_read_errors")); ripElem.appendChild(doc.createTextNode(_("no"))); ripMain.appendChild(ripElem); // ------------------------------------------------------------ // save disc cd-text // ------------------------------------------------------------- QDomElement cdTextMain = doc.createElement(_("cd-text")); cdTextMain.setAttribute(_("activated"), _("yes")); QDomElement cdTextElem = doc.createElement(_("title")); cdTextElem.appendChild(GET_INF(INF_NAME)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("artist")); cdTextElem.appendChild(GET_INF(INF_AUTHOR)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("arranger")); cdTextElem.appendChild(GET_INF(INF_TECHNICAN)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("songwriter")); cdTextElem.appendChild(GET_INF(INF_PERFORMER)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("composer")); cdTextElem.appendChild(GET_INF(INF_ORGANIZATION)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("disc_id")); cdTextElem.appendChild(GET_INF(INF_CD)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("upc_ean")); cdTextElem.appendChild(GET_INF(INF_ISRC)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("message")); cdTextElem.appendChild(GET_INF(INF_COMMENTS)); cdTextMain.appendChild(cdTextElem); docElem->appendChild( cdTextMain ); // ------------------------------------------------------------- // save the tracks // ------------------------------------------------------------- QDomElement contentsElem = doc.createElement(_("contents")); unsigned int index = 1; foreach (const Kwave::K3BExportPlugin::BlockInfo &block, m_block_info) { QString title = block.m_title; QString artist = block.m_artist; QString songwriter = QString(); QString url = block.m_filename; QDomElement trackElem = doc.createElement(_("track")); // add sources QDomElement sourcesParent = doc.createElement(_("sources")); QDomElement sourceElem = doc.createElement(_("file")); sourceElem.setAttribute(_("url"), url); sourceElem.setAttribute(_("start_offset"), _("00:00:00")); sourceElem.setAttribute(_("end_offset"), _("00:00:00")); sourcesParent.appendChild(sourceElem); trackElem.appendChild(sourcesParent); // index 0 QDomElement index0Elem = doc.createElement(_("index0")); index0Elem.appendChild(doc.createTextNode(QString::number(index))); trackElem.appendChild(index0Elem); // add cd-text cdTextMain = doc.createElement(_("cd-text")); cdTextElem = doc.createElement(_("title")); cdTextElem.appendChild(doc.createTextNode(title)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("artist")); cdTextElem.appendChild(doc.createTextNode(artist)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("arranger")); cdTextElem.appendChild(GET_INF(INF_TECHNICAN)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("songwriter")); cdTextElem.appendChild(doc.createTextNode(songwriter)); cdTextMain.appendChild(cdTextElem ); cdTextElem = doc.createElement(_("composer")); cdTextElem.appendChild(GET_INF(INF_ORGANIZATION)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("isrc")); cdTextElem.appendChild(GET_INF(INF_ISRC)); cdTextMain.appendChild(cdTextElem); cdTextElem = doc.createElement(_("message")); cdTextElem.appendChild(GET_INF(INF_COMMENTS)); cdTextMain.appendChild(cdTextElem); trackElem.appendChild(cdTextMain); // add copy protection QDomElement copyElem = doc.createElement(_("copy_protection")); copyElem.appendChild(doc.createTextNode( info.get(INF_COPYRIGHTED).toInt() ? _("yes") : _("no") )); trackElem.appendChild(copyElem); // add pre emphasis copyElem = doc.createElement(_("pre_emphasis")); copyElem.appendChild(doc.createTextNode(_("no"))); trackElem.appendChild(copyElem); contentsElem.appendChild(trackElem); index++; } // ------------------------------------------------------------- docElem->appendChild(contentsElem); } //*************************************************************************** int Kwave::K3BExportPlugin::start(QStringList ¶ms) { qDebug("K3BExportPlugin::start()"); // interpret the parameters int result = interpreteParameters(params); if (result) return result; // check the output file if (!m_url.isLocalFile()) return -EINVAL; // sorry, KZip supports only local files // determine output directory and file name pattern QString k3b_filename = m_url.path(); QFileInfo fi(k3b_filename); QString base = fi.completeBaseName(); QString out_dir; QString out_pattern; if (m_export_location == Kwave::K3BExportPlugin::EXPORT_TO_SUB_DIR) { // export to a subdir with the name ".dir" out_dir = fi.absolutePath() + QDir::separator() + base + _(".dir"); out_pattern = _("track-") + OUTFILE_PATTERN; } else { // use the same directory as the *.k3b file out_dir = fi.absolutePath(); out_pattern = base + _("-track-") + OUTFILE_PATTERN; } qDebug("out_dir = '%s'", DBG(out_dir)); qDebug("out_pattern = '%s'", DBG(out_pattern)); // determine the selection settings sample_index_t selection_left = 0; sample_index_t selection_right = 0; QList tracks; selection(&tracks, &selection_left, &selection_right, false); // check: only mono or stereo files are supported if ((tracks.count() != 1) && (tracks.count() != 2)) { qWarning("sorry, K3b can not handle %u tracks", tracks.count()); Kwave::MessageBox::sorry(parentWidget(), i18n( "Only mono and stereo files can be used for an audio CD. " "You can either deselect some channels or export the file " "in a different file format that supports mono and stereo " "only (for example FLAC) and then try again." )); return -EINVAL; } bool selected_something = (selection_left != selection_right); bool selected_all = ( (selection_left == 0) && ((selection_right + 1) >= signalLength()) ); bool enable_selection_only = selected_something && !selected_all; bool selection_only = enable_selection_only && m_selection_only; if (!selection_only) { selection_left = 0; selection_right = signalLength() - 1; } // create a list of blocks to save, but not yet the output file names scanBlocksToSave(base, selection_left, selection_right); unsigned int count = m_block_info.count(); if (!count) return -EINVAL; // find the start index of the file numbering unsigned int first = 1; if (m_overwrite_policy == Kwave::K3BExportPlugin::USE_NEW_FILE_NAMES) { // use new files, find out the highest existing index QString pat = out_pattern; pat.replace(OUTFILE_PATTERN, _("*")); pat += OUTFILE_SUFFIX; QDir dir(out_dir, pat); QStringList files; files = dir.entryList(); for (unsigned int i = first; i < (first + count); ++i) { QString name = createFileName(out_pattern, i); QRegExp rx(_("^(") + name + _(")$"), Qt::CaseInsensitive); QStringList matches = files.filter(rx); if (matches.count() > 0) first = i + 1; } qDebug("found first usable index -> %d", first); } else { // overwrite mode, always start at 1 } // create the complete file names for (unsigned int i = 0; i < count; ++i) { m_block_info[i].m_filename = out_dir + QDir::separator() + createFileName(out_pattern, first + i); } result = saveBlocks(selection_only, out_dir, out_pattern); if (result != 0) return result; // aborted or failed -> do not create a k3b file result = saveK3BFile(k3b_filename); if (result != 0) return result; // aborted or failed -> do not ask about starting k3b if (Kwave::MessageBox::questionYesNo(parentWidget(), i18n( "A K3b project file has been created and audio files have " "been exported.\n" "Should I start K3b and open the audio CD project now?" )) == KMessageBox::Yes) { // call k3b and pass the project file name (must be full path) QStringList args; args << k3b_filename; if (!QProcess::startDetached(_("k3b"), args)) { return -EIO; } } return result; } //*************************************************************************** int Kwave::K3BExportPlugin::saveBlocks(bool selection_only, const QString &out_dir, const QString &out_pattern) { QString first_filename = Kwave::Parser::escapeForFileName( QUrl::fromLocalFile(createFileName(out_pattern, 1)).toString()); // remember the original file info remove all unsupported/ properties, // to avoid that the saveblocks plugin complains... const Kwave::FileInfo orig_file_info(signalManager().metaData()); Kwave::FileInfo file_info(orig_file_info); QList unsupported_properties; { QString mimetype = Kwave::CodecManager::mimeTypeOf(m_url); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype); if (encoder) { unsupported_properties = encoder->unsupportedProperties( file_info.properties().keys()); delete encoder; } if (!unsupported_properties.isEmpty()) { foreach (const Kwave::FileProperty &p, unsupported_properties) { file_info.set(p, QVariant()); } } } // make sure that the file uses 16 bits/sample only file_info.setBits(16); signalManager().metaData().replace(Kwave::MetaDataList(file_info)); // call the saveblocks plugin and let it do the main work of exporting // the *.wav files with all the tracks... QStringList params; params << out_dir + QDir::separator() + first_filename; params << Kwave::Parser::escape(out_pattern); params << ((m_overwrite_policy == USE_NEW_FILE_NAMES) ? _("0") : _("1")); params << (selection_only ? _("1") : _("0")); int result = manager().executePlugin(_("saveblocks"), ¶ms); // restore the original file info signalManager().metaData().replace(Kwave::MetaDataList(orig_file_info)); return result; } //*************************************************************************** int Kwave::K3BExportPlugin::saveK3BFile(const QString &k3b_filename) { // create the K3B file KZip zip(k3b_filename); bool ok = zip.open(QIODevice::WriteOnly); if (!ok) return -EIO; // write the mime type QByteArray app_type(K3B_PROJECT_MIME_TYPE); zip.setCompression(KZip::NoCompression); zip.setExtraField(KZip::NoExtraField); zip.writeFile(_("mimetype"), app_type); // export file global data QByteArray xml; QBuffer out(&xml); out.open(QIODevice::WriteOnly); // save the data in the document QDomDocument xmlDoc(_("k3b_audio_project")); xmlDoc.appendChild(xmlDoc.createProcessingInstruction( _("xml"), _("version=\"1.0\" encoding=\"UTF-8\"") )); QDomElement docElem = xmlDoc.createElement(_("k3b_audio_project")); xmlDoc.appendChild(docElem); saveDocumentData(&docElem); QTextStream xmlStream(&out); xmlDoc.save(xmlStream, 0); out.close(); zip.setCompression(KZip::NoCompression); zip.setExtraField(KZip::NoExtraField); zip.writeFile(_("maindata.xml"), xml.data()); zip.close(); return 0; } //*************************************************************************** QStringList Kwave::K3BExportPlugin::knownPatterns() { // list of all known detection patterns QStringList patterns; patterns << _("[%title] ([%artist])"); patterns << _("[%title], [%artist]"); patterns << _("[%artist]: [%title]"); patterns << _("[%artist] - [%title]"); return patterns; } //*************************************************************************** #include "K3BExportPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/fileinfo/FileInfoPlugin.cpp b/plugins/fileinfo/FileInfoPlugin.cpp index 46cc38e3..f810fa16 100644 --- a/plugins/fileinfo/FileInfoPlugin.cpp +++ b/plugins/fileinfo/FileInfoPlugin.cpp @@ -1,123 +1,125 @@ /*************************************************************************** FileInfoPlugin.cpp - plugin for editing file properties ------------------- begin : Fri Jul 19 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include +#include + #include "libkwave/MessageBox.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "FileInfoDialog.h" #include "FileInfoPlugin.h" KWAVE_PLUGIN(fileinfo, FileInfoPlugin) //*************************************************************************** Kwave::FileInfoPlugin::FileInfoPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args) { } //*************************************************************************** Kwave::FileInfoPlugin::~FileInfoPlugin() { } //*************************************************************************** QStringList *Kwave::FileInfoPlugin::setup(QStringList &) { Kwave::FileInfo oldInfo(signalManager().metaData()); // create the setup dialog - Kwave::FileInfoDialog *dialog = + QPointer dialog = new(std::nothrow) Kwave::FileInfoDialog(parentWidget(), oldInfo); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" -> apply the new properties apply(dialog->info()); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** void Kwave::FileInfoPlugin::apply(Kwave::FileInfo &new_info) { Kwave::FileInfo old_info(signalManager().metaData()); if (old_info == new_info) return; // nothing to do /* sample rate */ if (!qFuzzyCompare(old_info.rate(), new_info.rate())) { // sample rate changed -> only change rate or resample ? double new_rate = new_info.rate(); int res = Kwave::MessageBox::questionYesNoCancel(parentWidget(), i18n("You have changed the sample rate. Do you want to convert " "the whole file to the new sample rate or do " "you only want to set the rate information in order " "to repair a damaged file? Note: changing only the sample " "rate can cause \"mickey mouse\" effects."), QString(), i18n("&Convert"), i18n("&Set Rate")); if (res == KMessageBox::Yes) { // Yes -> resample // take over all properties except the new sample rate, this will // be detected and changed in the sample rate plugin new_info.setRate(old_info.rate()); if (new_info != old_info) { signalManager().setFileInfo(new_info, true); } // else: nothing except sample rate changed // NOTE: this command could be executed asynchronously, thus // we cannot change the sample rate afterwards emitCommand(_("plugin:execute(samplerate,%1,all)").arg(new_rate)); return; } else if (res == KMessageBox::No) { // No -> only change the rate in the file info new_info.setRate(new_rate); } else { // canceled -> use old sample rate new_info.setRate(old_info.rate()); } } // just copy all other properties if (new_info != old_info) { signalManager().setFileInfo(new_info, true); } } //*************************************************************************** #include "FileInfoPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/goto/GotoPluginBase.cpp b/plugins/goto/GotoPluginBase.cpp index 79d7a376..03bac2b9 100644 --- a/plugins/goto/GotoPluginBase.cpp +++ b/plugins/goto/GotoPluginBase.cpp @@ -1,150 +1,152 @@ /*************************************************************************** GotoPluginBase.cpp - base class for the goto plugin ------------------- begin : Thu May 12 2011 copyright : (C) 2011 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include -#include #include +#include #include +#include + #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "GotoDialog.h" #include "GotoPluginBase.h" //*************************************************************************** Kwave::GotoPluginBase::GotoPluginBase(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_mode(Kwave::SelectTimeWidget::bySamples), m_position(0) { } //*************************************************************************** Kwave::GotoPluginBase::~GotoPluginBase() { } //*************************************************************************** QStringList *Kwave::GotoPluginBase::setup(QStringList &previous_params) { // try to interpret the previous parameters interpreteParameters(previous_params); // create the setup dialog double rate = signalRate(); sample_index_t length = signalLength(); // get the name of the help section QString help_section = _("plugin_sect_") + command(); - Kwave::GotoDialog *dialog = new(std::nothrow) + QPointer dialog = new(std::nothrow) Kwave::GotoDialog(parentWidget(), m_mode, m_position, rate, length, help_section ); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; // set the title of the dialog, depending on the derived class dialog->setWindowTitle(title()); QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" *list << QString::number(dialog->mode()); *list << QString::number(dialog->pos()); emitCommand(_("plugin:execute(") + command() + _(",") + QString::number(dialog->mode()) + _(",") + QString::number(dialog->pos()) + _(")") ); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** int Kwave::GotoPluginBase::start(QStringList ¶ms) { // interprete the parameters int result = interpreteParameters(params); if (result) return result; // get current offset of the signal sample_index_t offset = Kwave::SelectTimeWidget::timeToSamples( m_mode, m_position, signalRate(), signalLength()); // change the selection through the signal manager QString cmd = _("nomacro:") + command() + _("(%1)"); emitCommand(cmd.arg(offset)); return result; } //*************************************************************************** int Kwave::GotoPluginBase::interpreteParameters(QStringList ¶ms) { bool ok; QString param; int mode; // evaluate the parameter list if (params.count() != 2) return -EINVAL; // selection mode for start param = params[0]; mode = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; Q_ASSERT( (mode == static_cast(Kwave::SelectTimeWidget::byTime)) || (mode == static_cast(Kwave::SelectTimeWidget::bySamples)) || (mode == static_cast(Kwave::SelectTimeWidget::byPercents)) ); if ((mode != static_cast(Kwave::SelectTimeWidget::byTime)) && (mode != static_cast(Kwave::SelectTimeWidget::bySamples)) && (mode != static_cast(Kwave::SelectTimeWidget::byPercents))) { return -EINVAL; } m_mode = static_cast(mode); // position in ms, samples or percent param = params[1]; m_position = param.toUInt(&ok); if (!ok) return -EINVAL; return 0; } //*************************************************************************** //*************************************************************************** diff --git a/plugins/memory/MemoryDialog.cpp b/plugins/memory/MemoryDialog.cpp index 6a16aa99..197ecfce 100644 --- a/plugins/memory/MemoryDialog.cpp +++ b/plugins/memory/MemoryDialog.cpp @@ -1,170 +1,170 @@ /*************************************************************************** MemoryDialog.cpp - setup dialog of Kwave's memory management ------------------- begin : Sun Aug 05 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/MemoryManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libgui/FileDialog.h" #include "MemoryDialog.h" //*************************************************************************** Kwave::MemoryDialog::MemoryDialog(QWidget* parent, bool physical_limited, unsigned int physical_limit, bool virtual_enabled, bool virtual_limited, unsigned int virtual_limit, const QString &virtual_dir, unsigned int undo_limit) :QDialog(parent), Ui::MemDlg() { setupUi(this); setModal(true); Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); int total_physical = Kwave::toInt(qMin( mem.totalPhysical(), static_cast(std::numeric_limits::max()) )); if (!isOK()) return; physical_limit = qMin(physical_limit, Kwave::toUint(total_physical)); // connect the controls connect(chkEnableVirtual, SIGNAL(toggled(bool)), this, SLOT(virtualMemoryEnabled(bool))); connect(btSearch, SIGNAL(clicked()), this, SLOT(searchSwapDir())); connect(buttonBox->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(invokeHelp())); // initialize all controls chkLimitPhysical->setChecked(physical_limited); slPhysical->setMaximum(total_physical); sbPhysical->setMaximum(total_physical); slPhysical->setValue(physical_limit); sbPhysical->setValue(physical_limit); chkEnableVirtual->setChecked(virtual_enabled); chkLimitVirtual->setChecked(virtual_limited); sbVirtual->setValue(virtual_limit); edDirectory->setText(virtual_dir); slUndo->setMaximum(total_physical / 2); sbUndo->setMaximum(slUndo->maximum()); sbUndo->setValue(undo_limit); virtualMemoryEnabled(virtual_enabled); // set fixed size setFixedWidth(sizeHint().width()); setFixedHeight(sizeHint().height()); // set the focus onto the "OK" button buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } //*************************************************************************** Kwave::MemoryDialog::~MemoryDialog() { } //*************************************************************************** bool Kwave::MemoryDialog::isOK() { Q_ASSERT(chkEnableVirtual); Q_ASSERT(chkLimitPhysical); Q_ASSERT(chkLimitVirtual); Q_ASSERT(edDirectory); Q_ASSERT(sbPhysical); Q_ASSERT(sbVirtual); Q_ASSERT(slPhysical); Q_ASSERT(slVirtual); return chkEnableVirtual && chkLimitPhysical && chkLimitVirtual && edDirectory && sbPhysical && sbVirtual && slPhysical && slVirtual; } //*************************************************************************** void Kwave::MemoryDialog::params(QStringList &par) { par.clear(); par << QString::number(chkLimitPhysical->isChecked() ? 1 : 0); par << QString::number(sbPhysical->value()); par << QString::number(chkEnableVirtual->isChecked() ? 1 : 0); par << QString::number(chkLimitVirtual->isChecked() ? 1 : 0); par << QString::number(sbVirtual->value()); par << edDirectory->text(); par << QString::number(sbUndo->value()); } //*************************************************************************** void Kwave::MemoryDialog::virtualMemoryEnabled(bool enable) { bool limit = enable && (chkLimitVirtual->isChecked()); chkLimitVirtual->setEnabled(enable); slVirtual->setEnabled(limit); sbVirtual->setEnabled(limit); txtDirectory->setEnabled(enable); edDirectory->setEnabled(enable); btSearch->setEnabled(enable); } //*************************************************************************** void Kwave::MemoryDialog::searchSwapDir() { QPointer dlg = new(std::nothrow) Kwave::FileDialog( edDirectory->text(), Kwave::FileDialog::SelectDir, QString(), this); if (!dlg) return; dlg->setWindowTitle(i18n("Select Swap File Directory")); - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { QString dir = dlg->selectedUrl().toLocalFile(); if (dir.length()) edDirectory->setText(dir); } delete dlg; } //*************************************************************************** void Kwave::MemoryDialog::invokeHelp() { KHelpClient::invokeHelp(_("memory-setup")); } //*************************************************************************** //*************************************************************************** diff --git a/plugins/memory/MemoryPlugin.cpp b/plugins/memory/MemoryPlugin.cpp index c53a38ab..6d806178 100644 --- a/plugins/memory/MemoryPlugin.cpp +++ b/plugins/memory/MemoryPlugin.cpp @@ -1,173 +1,174 @@ /*************************************************************************** MemoryPlugin.cpp - setup of Kwave's memory management ------------------- begin : Sun Aug 05 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include +#include #include #include #include #include "libkwave/MemoryManager.h" #include "libkwave/Plugin.h" #include "libkwave/String.h" #include "MemoryDialog.h" #include "MemoryPlugin.h" KWAVE_PLUGIN(memory, MemoryPlugin) /** default memory limit for physical memory [MB] */ #define DEFAULT_PHYSICAL_LIMIT 2048 /** default memory limit for swap space [MB] */ #define DEFAULT_VIRTUAL_LIMIT 2048 /** default memory limit for undo memory [MB] */ #define DEFAULT_UNDO_LIMIT 1024 //*************************************************************************** Kwave::MemoryPlugin::MemoryPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_physical_limited(true), m_physical_limit(DEFAULT_PHYSICAL_LIMIT), m_virtual_enabled(true), m_virtual_limited(false), m_virtual_limit(DEFAULT_VIRTUAL_LIMIT), m_virtual_directory(_("/var/tmp")), m_undo_limit(DEFAULT_UNDO_LIMIT) { } //*************************************************************************** Kwave::MemoryPlugin::~MemoryPlugin() { } //*************************************************************************** int Kwave::MemoryPlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; // evaluate the parameter list if (params.count() < 6) return -EINVAL; // parameter #0: physical memory is limited ? param = params[0]; m_physical_limited = param.toUInt(&ok) != 0; if (!ok) return -EINVAL; // parameter #1: limit for physical memory param = params[1]; m_physical_limit = param.toUInt(&ok); if (!ok) return -EINVAL; // parameter #2: virtual memory is enabled ? param = params[2]; m_virtual_enabled = param.toUInt(&ok) != 0; if (!ok) return -EINVAL; // parameter #3: virtual memory is limited ? param = params[3]; m_virtual_limited = param.toUInt(&ok) != 0; if (!ok) return -EINVAL; // parameter #4: limit for virtual memory if (m_virtual_limited) { param = params[4]; m_virtual_limit = param.toUInt(&ok); if (!ok) return -EINVAL; } else { m_virtual_limit = std::numeric_limits::max(); } // parameter #5: directory for virtual memory files param = params[5]; m_virtual_directory = param; // parameter #6: limit for undo/redo if (params.count() >= 7) { param = params[6]; m_undo_limit = param.toUInt(&ok); if (!ok) return -EINVAL; } return 0; } //*************************************************************************** void Kwave::MemoryPlugin::load(QStringList ¶ms) { interpreteParameters(params); applySettings(); } //*************************************************************************** void Kwave::MemoryPlugin::applySettings() { Kwave::MemoryManager &mem = Kwave::MemoryManager::instance(); mem.setPhysicalLimit(m_physical_limited ? m_physical_limit : 4096); mem.setVirtualLimit(m_virtual_enabled ? (m_virtual_limited ? m_virtual_limit : std::numeric_limits::max()) : 0); mem.setSwapDirectory(m_virtual_directory); mem.setUndoLimit(m_undo_limit); } //*************************************************************************** QStringList *Kwave::MemoryPlugin::setup(QStringList &previous_params) { QStringList *result = Q_NULLPTR; // try to interpret the list of previous parameters, ignore errors if (previous_params.count()) interpreteParameters(previous_params); - Kwave::MemoryDialog *dlg = new(std::nothrow) Kwave::MemoryDialog( + QPointer dlg = new(std::nothrow) Kwave::MemoryDialog( parentWidget(), m_physical_limited, m_physical_limit, m_virtual_enabled, m_virtual_limited, m_virtual_limit, m_virtual_directory, m_undo_limit); Q_ASSERT(dlg); if (!dlg) return Q_NULLPTR; - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { // get the new parameters and let them take effect result = new(std::nothrow) QStringList(); Q_ASSERT(result); - if (result) { + if (result && dlg) { dlg->params(*result); interpreteParameters(*result); applySettings(); } }; delete dlg; return result; } //*************************************************************************** #include "MemoryPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/newsignal/NewSignalPlugin.cpp b/plugins/newsignal/NewSignalPlugin.cpp index 01e68928..1b951737 100644 --- a/plugins/newsignal/NewSignalPlugin.cpp +++ b/plugins/newsignal/NewSignalPlugin.cpp @@ -1,126 +1,128 @@ /*************************************************************************** NewSignalPlugin.cpp - plugin for creating a new signal ------------------- begin : Wed Jul 18 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include +#include #include #include #include "libkwave/String.h" #include "libkwave/Utils.h" #include "NewSignalDialog.h" #include "NewSignalPlugin.h" KWAVE_PLUGIN(newsignal, NewSignalPlugin) //*************************************************************************** Kwave::NewSignalPlugin::NewSignalPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_samples(2646000), m_rate(44100), m_bits(16), m_tracks(2), m_bytime(true) { } //*************************************************************************** Kwave::NewSignalPlugin::~NewSignalPlugin() { } //*************************************************************************** int Kwave::NewSignalPlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; // evaluate the parameter list if (params.count() != 5) return -EINVAL; param = params[0]; m_samples = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; param = params[1]; m_rate = Kwave::toUint(param.toDouble(&ok)); Q_ASSERT(ok); if (!ok) return -EINVAL; param = params[2]; m_bits = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; param = params[3]; m_tracks = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; param = params[4]; m_bytime = (param.toUInt(&ok) != 0); Q_ASSERT(ok); if (!ok) return -EINVAL; return 0; } //*************************************************************************** QStringList *Kwave::NewSignalPlugin::setup(QStringList &previous_params) { // try to interprete the previous parameters interpreteParameters(previous_params); // create the setup dialog - Kwave::NewSignalDialog *dialog = new(std::nothrow) Kwave::NewSignalDialog( - parentWidget(), m_samples, m_rate, m_bits, m_tracks, m_bytime); + QPointer dialog = new(std::nothrow) + Kwave::NewSignalDialog( + parentWidget(), m_samples, m_rate, m_bits, m_tracks, m_bytime); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" *list << QString::number(dialog->samples()); *list << QString::number(dialog->rate()); *list << QString::number(dialog->bitsPerSample()); *list << QString::number(dialog->tracks()); *list << _(dialog->byTime() ? "1" : "0"); emitCommand(_("newsignal(") + QString::number(dialog->samples()) + _(",") + QString::number(dialog->rate()) + _(",") + QString::number(dialog->bitsPerSample()) + _(",") + QString::number(dialog->tracks()) + _(")") ); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** #include "NewSignalPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/playback/PlayBackDialog.cpp b/plugins/playback/PlayBackDialog.cpp index 1e944746..07cab6a8 100644 --- a/plugins/playback/PlayBackDialog.cpp +++ b/plugins/playback/PlayBackDialog.cpp @@ -1,667 +1,667 @@ /*************************************************************************** PlayBackDialog.cpp - dialog for configuring the playback ------------------- begin : Sun May 13 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/PlayBackDevice.h" #include "libkwave/PlayBackTypesMap.h" #include "libkwave/PlaybackController.h" #include "libkwave/Plugin.h" #include "libkwave/String.h" #include "libgui/FileDialog.h" #include "PlayBackDialog.h" #include "PlayBackPlugin.h" //*************************************************************************** Kwave::PlayBackDialog::PlayBackDialog( Kwave::Plugin &p, Kwave::PlaybackController &playback_controller, const Kwave::PlayBackParam ¶ms ) :QDialog(p.parentWidget()), PlayBackDlg(), m_playback_controller(playback_controller), m_device(Q_NULLPTR), m_playback_params(params), m_methods_map(), m_file_filter(_("")), m_devices_list_map(), m_enable_setDevice(true) { setupUi(this); setModal(true); // fill the combo box with playback methods unsigned int index=0; for (index = 0; index < m_methods_map.count(); index++) { cbMethod->addItem( m_methods_map.description(index, true), static_cast(m_methods_map.data(index)) ); } cbMethod->setEnabled(cbMethod->count() > 1); connect(cbMethod, SIGNAL(activated(int)), SLOT(methodSelected(int))); connect(cbDevice, SIGNAL(editTextChanged(QString)), SLOT(setDevice(QString))); connect(cbDevice, SIGNAL(activated(QString)), SLOT(setDevice(QString))); connect(cbBitsPerSample, SIGNAL(editTextChanged(QString)), SLOT(bitsPerSampleSelected(QString))); connect(cbBitsPerSample, SIGNAL(activated(QString)), SLOT(bitsPerSampleSelected(QString))); connect(sbChannels, SIGNAL(valueChanged(int)), SLOT(setChannels(int))); connect(slBufferSize, SIGNAL(valueChanged(int)), SLOT(setBufferSize(int))); connect(btSelectDevice, SIGNAL(clicked()), SLOT(selectPlaybackDevice())); connect(listDevices, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(listEntrySelected(QTreeWidgetItem*,QTreeWidgetItem*))); connect(listDevices, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(listItemExpanded(QTreeWidgetItem*))); connect(listDevices, SIGNAL(focusLost()), SLOT(updateListSelection())); connect(btTest, SIGNAL(clicked()), SIGNAL(sigTestPlayback())); connect(btHelp->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(invokeHelp())); // remove the header of the tree view listDevices->headerItem()->setHidden(true); // fix the dialog size setFixedHeight(sizeHint().height()); // update the GUI elements // order is: Method -> Device -> "Select..."-button // -> Channels -> Bits per Sample setMethod(params.method); setDevice(params.device); setBitsPerSample(params.bits_per_sample); setChannels(params.channels); // buffer size is independent setBufferSize(params.bufbase); // set the focus onto the "OK" button buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } //*************************************************************************** Kwave::PlayBackDialog::~PlayBackDialog() { } //*************************************************************************** void Kwave::PlayBackDialog::setMethod(Kwave::playback_method_t method) { Kwave::playback_method_t old_method = m_playback_params.method; m_playback_params.method = method; // update the selection in the combo box if necessary int index = cbMethod->findData(static_cast(method)); if (cbMethod->currentIndex() != index) { cbMethod->setCurrentIndex(index); return; // we will get called again, through "methodSelected(...)" } qDebug("PlayBackDialog::setMethod('%s' [%d])", DBG(m_methods_map.name(m_methods_map.findFromData(method))), static_cast(method) ); // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // change the playback method (class PlayBackDevice) if (m_device) delete m_device; m_device = Q_NULLPTR; // remember the device selection, just for the GUI // for the next time this method gets selected // change in method -> save the current device and use // the previous one QString device = _(""); QString section = _("plugin playback"); KConfigGroup cfg = KSharedConfig::openConfig()->group(section); // save the current device cfg.writeEntry( QString(_("last_device_%1")).arg(static_cast(old_method)), m_playback_params.device); qDebug("SAVE: '%s' (%d) -> '%s'", DBG(m_methods_map.name(m_methods_map.findFromData(old_method))), static_cast(old_method), DBG(m_playback_params.device.split(_("|")).at(0))); cfg.sync(); // NOTE: the "method" may get modified here if not supported! m_playback_controller.checkMethod(method); if (method != m_playback_params.method) { // method has been modified to some fallback -> start through qDebug(" method has changed: %d -> %d", static_cast(m_playback_params.method), static_cast(method)); setMethod(method); // -> recursion // remove hourglass QApplication::restoreOverrideCursor(); return; } // if we found no playback method if (method == Kwave::PLAYBACK_INVALID) { qWarning("found no valid playback method"); } // create a new playback device (will fail if method is unsupported) m_device = m_playback_controller.createDevice(method); if (!m_device) { // oops, something has failed :-( setSupportedDevices(QStringList()); setDevice(QString()); // remove hourglass QApplication::restoreOverrideCursor(); return; } // restore the previous settings of the new method device = cfg.readEntry( _("last_device_%1").arg(static_cast(method))); qDebug("RESTORE: '%s' (%d) -> '%s'", DBG(m_methods_map.name(m_methods_map.findFromData(method))), static_cast(method), DBG(device.split(_("|")).at(0))); m_playback_params.device = device; // set list of supported devices setSupportedDevices(m_device->supportedDevices()); // set current device, no matter if supported or not, // the dialog will take care of this. setDevice(m_playback_params.device); // check the filter for the "select..." dialog. If it is // empty, the "select" dialog will be disabled setFileFilter(m_device->fileFilter()); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** void Kwave::PlayBackDialog::methodSelected(int index) { Kwave::playback_method_t method = static_cast( cbMethod->itemData(index).toInt()); qDebug("PlayBackDialog::methodSelected(%d) -> %s [%d]", index, DBG(m_methods_map.name(m_methods_map.findFromData(method))), static_cast(method) ); if (method <= Kwave::PLAYBACK_NONE) return; if (method >= Kwave::PLAYBACK_INVALID) return; setMethod(method); } //*************************************************************************** void Kwave::PlayBackDialog::setSupportedDevices(QStringList devices) { Q_ASSERT(cbDevice); Q_ASSERT(listDevices); if (!cbDevice || !listDevices) return; QString current_device = m_playback_params.device; // disable all that noisy stuff that comes from modifying the // device controls... m_enable_setDevice = false; // qDebug("PlayBackDialog::setSupportedDevices():"); // foreach (const QString &d, devices) // qDebug(" '%s'", DBG(d)); cbDevice->clearEditText(); cbDevice->clear(); listDevices->clear(); if (devices.contains(_("#EDIT#"))) { devices.removeAll(_("#EDIT#")); cbDevice->setEditable(true); } else { cbDevice->setEditable(false); } if (devices.contains(_("#SELECT#"))) { devices.removeAll(_("#SELECT#")); btSelectDevice->setEnabled(true); btSelectDevice->show(); } else { btSelectDevice->setEnabled(false); btSelectDevice->hide(); } if (devices.contains(_("#TREE#"))) { // treeview mode KIconLoader *icon_loader = KIconLoader::global(); devices.removeAll((_("#TREE#"))); listDevices->setEnabled(true); cbDevice->setEnabled(false); cbDevice->hide(); m_devices_list_map.clear(); // build a tree with all nodes in the list foreach (const QString &dev_id, devices) { QTreeWidgetItem *parent = Q_NULLPTR; QStringList list = dev_id.split(_("||"), QString::KeepEmptyParts); foreach (const QString &t, list) { QString token(t); QTreeWidgetItem *item = Q_NULLPTR; // split the icon name from the token QString icon_name; int pos = token.indexOf(QLatin1Char('|')); if (pos > 0) { icon_name = token.mid(pos+1); token = token.left(pos); } // find the first item with the same text // and the same root if (parent) { for (int i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *node = parent->child(i); if (node && node->text(0) == token) { item = node; break; } } } else { QList matches = listDevices->findItems(token, Qt::MatchExactly); if (matches.count()) item = matches.takeFirst(); } if (item) { // already in the list parent = item; } else if (parent) { // new leaf, add to the parent item = new(std::nothrow) QTreeWidgetItem(parent); Q_ASSERT(item); if (item) { item->setText(0, token); m_devices_list_map.insert(item, dev_id); } parent->setExpanded(true); parent->setFlags(parent->flags() & ~(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable)); if (m_devices_list_map.contains(parent)) { // make the parent not selectable m_devices_list_map.remove(parent); } } else { // new root node item = new(std::nothrow) QTreeWidgetItem(listDevices); Q_ASSERT(item); if (item) { item->setText(0, token); m_devices_list_map.insert(item, dev_id); } } if (item && icon_name.length() && icon_loader) { QIcon icon = icon_loader->loadIcon( icon_name, KIconLoader::User); item->setIcon(0, icon); } // use the current item as parent for the next pass parent = item; } } } else { // combo box mode cbDevice->addItems(devices); cbDevice->show(); listDevices->setEnabled(false); if (devices.contains(current_device)) { // current device is in the list cbDevice->setCurrentIndex(cbDevice->findText(current_device)); } else { if (cbDevice->isEditable() && current_device.length()) { // user defined device name cbDevice->setEditText(current_device); } else if (devices.count()) { // one or more other possibilities -> take the first one cbDevice->setCurrentIndex(0); } else { // empty list of possibilities cbDevice->clearEditText(); cbDevice->clear(); } } cbDevice->setEnabled(devices.count() > 1); } // enable changes in the device controls again m_enable_setDevice = true; } //*************************************************************************** void Kwave::PlayBackDialog::listEntrySelected(QTreeWidgetItem *current, QTreeWidgetItem *previous) { Q_ASSERT(listDevices); Q_UNUSED(previous); if (!current || !listDevices) return; if (m_devices_list_map.contains(current)) setDevice(m_devices_list_map[current]); } //*************************************************************************** void Kwave::PlayBackDialog::listItemExpanded(QTreeWidgetItem *item) { Q_UNUSED(item); updateListSelection(); } //*************************************************************************** void Kwave::PlayBackDialog::updateListSelection() { // set the current device again, otherwise nothing will be selected setDevice(m_playback_params.device); } //*************************************************************************** void Kwave::PlayBackDialog::setDevice(const QString &device) { Q_ASSERT(cbDevice); Q_ASSERT(listDevices); if (!cbDevice || !listDevices) return; if (!m_enable_setDevice) return; qDebug("PlayBackDialog::setDevice(): '%s' -> '%s'", DBG(m_playback_params.device.split(_("|")).at(0)), DBG(device.split(_("|")).at(0))); if (listDevices->isEnabled()) { // treeview mode QTreeWidgetItem *node = m_devices_list_map.key(device, Q_NULLPTR); if (node) { node->setSelected(true); listDevices->scrollToItem(node); listDevices->setCurrentItem(node); } } else if (cbDevice->isEditable() && device.length()) { // user defined device name if (!device.isEmpty() && (cbDevice->currentText() != device)) { cbDevice->setCurrentIndex(cbDevice->findText(device)); cbDevice->setEditText(device); } } else { // just take one from the list if (cbDevice->findText(device) >= 0) { cbDevice->setCurrentIndex(cbDevice->findText(device)); } else if (cbDevice->count()) { cbDevice->setCurrentIndex(0); } } // select the default device if new one is not supported QString dev = device; if (m_device) { QStringList supported = m_device->supportedDevices(); supported.removeAll(_("#EDIT#")); supported.removeAll(_("#SELECT#")); supported.removeAll(_("#TREE#")); if (!supported.isEmpty() && !supported.contains(device)) { // use the first entry as default dev = supported.first(); qDebug("PlayBackPlugin::setDevice(%s) -> fallback to '%s'", DBG(device.split(_("|")).at(0)), DBG(dev.split(_("|")).at(0))); } } // take over the device, please note that this one might differ from // the device we got as parameter, maybe it is a fallback m_playback_params.device = dev; QList supported_bits; if (m_device) supported_bits = m_device->supportedBits(dev); setSupportedBits(supported_bits); unsigned int min = 0; unsigned int max = 0; if (m_device) m_device->detectChannels(dev, min, max); setSupportedChannels(min, max); } //*************************************************************************** void Kwave::PlayBackDialog::setBufferSize(int exp) { Q_ASSERT(slBufferSize); Q_ASSERT(txtBufferSize); if (!slBufferSize || !txtBufferSize) return; if (exp < 8) exp = 8; if (exp > 18) exp = 18; // update the slider if necessary if (slBufferSize->value() != exp) slBufferSize->setValue(exp); // take the value into our struct m_playback_params.bufbase = exp; // update the text unsigned int buffer_size = (1 << exp); QString text; if (buffer_size < 1024) { text = i18n("%1 Bytes", buffer_size); } else { text = i18n("%1 kB", buffer_size >> 10); } txtBufferSize->setText(text); } //*************************************************************************** void Kwave::PlayBackDialog::setSupportedBits(const QList &bits) { Q_ASSERT(cbBitsPerSample); if (!cbBitsPerSample) return; int current_bits = m_playback_params.bits_per_sample; cbBitsPerSample->clear(); QString txt; foreach (unsigned int b, bits) { txt.setNum(b); cbBitsPerSample->addItem(txt); } // if possibilities are "unknown" -> use last known setting if (!bits.count()) { txt.setNum(current_bits); cbBitsPerSample->addItem(txt); } if (!bits.contains(current_bits) && bits.count()) current_bits = bits.last(); setBitsPerSample(current_bits); cbBitsPerSample->setEnabled(bits.count() > 0); } //*************************************************************************** void Kwave::PlayBackDialog::bitsPerSampleSelected(const QString &text) { bool ok = false; unsigned int bits = text.toUInt(&ok); if (!ok) bits = 0; setBitsPerSample(bits); } //*************************************************************************** void Kwave::PlayBackDialog::setBitsPerSample(unsigned int bits) { Q_ASSERT(cbBitsPerSample); if (!cbBitsPerSample) return; qDebug("PlayBackDialog::setBitsPerSample(): %u -> %u", m_playback_params.bits_per_sample, bits); QString txt; txt.setNum(bits); if (cbBitsPerSample->findText(txt) >= 0) { cbBitsPerSample->setCurrentIndex(cbBitsPerSample->findText(txt)); m_playback_params.bits_per_sample = bits; } } //*************************************************************************** void Kwave::PlayBackDialog::setSupportedChannels(unsigned int min, unsigned int max) { Q_ASSERT(sbChannels); if (!sbChannels) return; int current_channels = m_playback_params.channels; // if possibilities are "unknown" -> use last known setting if (!min && !max && current_channels) min = max = current_channels; sbChannels->setMinimum(min); sbChannels->setMaximum(max); setChannels(current_channels); sbChannels->setEnabled(min != max); } //*************************************************************************** void Kwave::PlayBackDialog::setChannels(int channels) { Q_ASSERT(sbChannels); if (!sbChannels) return; if ((sbChannels->value() != channels) && (sbChannels->minimum() != sbChannels->maximum()) && (sbChannels->maximum() > 0)) { sbChannels->setValue(channels); channels = sbChannels->value(); } qDebug("PlayBackDialog::setChannels(): %d -> %d", m_playback_params.channels, channels); m_playback_params.channels = channels; QString txt; switch (channels) { case 1: txt = i18n("(mono)"); break; case 2: txt = i18n("(stereo)"); break; case 4: txt = i18n("(quadro)"); break; default: txt = _(""); } lblChannels->setText(txt); } //*************************************************************************** const Kwave::PlayBackParam &Kwave::PlayBackDialog::params() { return m_playback_params; } //*************************************************************************** void Kwave::PlayBackDialog::setFileFilter(const QString &filter) { m_file_filter = filter; if (btSelectDevice) btSelectDevice->setEnabled(m_file_filter.length()); } //*************************************************************************** void Kwave::PlayBackDialog::selectPlaybackDevice() { QString filter = m_file_filter; QPointer dlg = new(std::nothrow) Kwave::FileDialog( _("kfiledialog:///kwave_playback_device"), Kwave::FileDialog::OpenFile, filter, this, QUrl(_("file:/dev")) ); if (!dlg) return; dlg->setWindowTitle(i18n("Select Playback Device")); if (!m_playback_params.device.startsWith(_("#"))) dlg->selectUrl(QUrl(_("file:") + m_playback_params.device)); else dlg->selectUrl(QUrl(_("file:/dev/*"))); - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { QString new_device = dlg->selectedUrl().fileName(); // selected new device if (cbDevice) cbDevice->setEditText(new_device); } delete dlg; } //*************************************************************************** void Kwave::PlayBackDialog::invokeHelp() { KHelpClient::invokeHelp(_("playback")); } //*************************************************************************** //*************************************************************************** diff --git a/plugins/playback/PlayBackPlugin.cpp b/plugins/playback/PlayBackPlugin.cpp index 015f3b9f..30efc79f 100644 --- a/plugins/playback/PlayBackPlugin.cpp +++ b/plugins/playback/PlayBackPlugin.cpp @@ -1,487 +1,487 @@ /*************************************************************************** PlayBackPlugin.cpp - plugin for playback and playback configuration ------------------- begin : Sun May 13 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/Connect.h" #include "libkwave/Curve.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiPlaybackSink.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackSource.h" #include "libkwave/PlayBackDevice.h" #include "libkwave/PlayBackTypesMap.h" #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/modules/CurveStreamAdapter.h" #include "libkwave/modules/Delay.h" #include "libkwave/modules/Mul.h" #include "libkwave/modules/Osc.h" #include "PlayBack-ALSA.h" #include "PlayBack-OSS.h" #include "PlayBack-PulseAudio.h" #include "PlayBack-Qt.h" #include "PlayBackDialog.h" #include "PlayBackPlugin.h" KWAVE_PLUGIN(playback, PlayBackPlugin) /** test frequency [Hz] */ #define PLAYBACK_TEST_FREQUENCY 440.0 //*************************************************************************** Kwave::PlayBackPlugin::PlayBackPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_dialog(Q_NULLPTR), m_playback_controller(manager().playbackController()), m_playback_sink(Q_NULLPTR) { } //*************************************************************************** Kwave::PlayBackPlugin::~PlayBackPlugin() { // make sure the dialog is gone if (m_dialog) delete m_dialog; m_dialog = Q_NULLPTR; Q_ASSERT(!m_playback_sink); } //*************************************************************************** Kwave::PlayBackParam Kwave::PlayBackPlugin::interpreteParameters( QStringList ¶ms) { Kwave::PlayBackParam playback_params; Kwave::PlayBackParam default_params; bool ok; QString param; // evaluate the parameter list if (params.count() != 5) return default_params; // parameter #0: playback method param = params[0]; unsigned int method = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return default_params; if (method >= Kwave::PLAYBACK_INVALID) method = Kwave::PLAYBACK_NONE; playback_params.method = static_cast(method); // parameter #1: playback device [/dev/dsp , ... ] param = params[1]; playback_params.device = param; // parameter #2: number of channels [1 | 2] param = params[2]; playback_params.channels = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return default_params; // parameter #3: bits per sample [8 | 16 ] param = params[3]; playback_params.bits_per_sample = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return default_params; // parameter #4: base of buffer size [4...16] param = params[4]; playback_params.bufbase = param.toUInt(&ok); Q_ASSERT(ok); if (!ok) return default_params; return playback_params; } //*************************************************************************** void Kwave::PlayBackPlugin::load(QStringList ¶ms) { use(); // stay loaded // register as a factory for playback devices m_playback_controller.registerPlaybackDeviceFactory(this); m_playback_controller.setDefaultParams(interpreteParameters(params)); } /***************************************************************************/ void Kwave::PlayBackPlugin::unload() { // unregister from the list playback factories m_playback_controller.unregisterPlaybackDeviceFactory(this); release(); } //*************************************************************************** QStringList *Kwave::PlayBackPlugin::setup(QStringList &previous_params) { QStringList *result = Q_NULLPTR; // try to interpret the list of previous parameters, ignore errors Kwave::PlayBackParam playback_params = interpreteParameters(previous_params); Q_ASSERT(!m_dialog); if (m_dialog) delete m_dialog; m_dialog = new(std::nothrow) Kwave::PlayBackDialog( *this, manager().playbackController(), playback_params ); Q_ASSERT(m_dialog); if (!m_dialog) return Q_NULLPTR; connect(m_dialog, SIGNAL(sigTestPlayback()), this, SLOT(testPlayBack())); // activate the playback method m_dialog->setMethod(playback_params.method); - if (m_dialog->exec() == QDialog::Accepted) { + if ((m_dialog->exec() == QDialog::Accepted) && m_dialog) { // get the new parameters and let them take effect result = new(std::nothrow) QStringList(); Q_ASSERT(result); if (result) { QString param; playback_params = m_dialog->params(); // parameter #0: playback method param = param.setNum( static_cast(playback_params.method)); result->append(param); // parameter #1: playback device [/dev/dsp , ... ] param = playback_params.device; result->append(param); // parameter #2: number of channels [1, 2, ... n] param = param.setNum(playback_params.channels); result->append(param); // parameter #3: bits per sample [8, 16, 24, ...] param = param.setNum(playback_params.bits_per_sample); result->append(param); // parameter #4: base of buffer size [8 ... 16] param = param.setNum(playback_params.bufbase); result->append(param); qDebug("new playback params: '%s", DBG(result->join(_("','")) + _("'"))); // take over the new playback parameters signalManager().playbackController().setDefaultParams( playback_params ); } } delete m_dialog; m_dialog = Q_NULLPTR; return result; } //*************************************************************************** QList Kwave::PlayBackPlugin::supportedMethods() { QList methods; #ifdef HAVE_QT_AUDIO_SUPPORT methods.append(Kwave::PLAYBACK_QT_AUDIO); #endif /* HAVE_QT_AUDIO_SUPPORT */ #ifdef HAVE_PULSEAUDIO_SUPPORT methods.append(Kwave::PLAYBACK_PULSEAUDIO); #endif /* HAVE_PULSEAUDIO_SUPPORT */ #ifdef HAVE_ALSA_SUPPORT methods.append(Kwave::PLAYBACK_ALSA); #endif /* HAVE_ALSA_SUPPORT */ #ifdef HAVE_OSS_SUPPORT methods.append(Kwave::PLAYBACK_OSS); #endif /* HAVE_OSS_SUPPORT */ return methods; } //*************************************************************************** Kwave::PlayBackDevice *Kwave::PlayBackPlugin::createDevice( Kwave::playback_method_t method) { Kwave::PlayBackTypesMap methods; qDebug("PlayBackPlugin::createDevice('%s' [%d])", DBG(methods.name(methods.findFromData(method))), static_cast(method) ); switch (method) { #ifdef HAVE_QT_AUDIO_SUPPORT case Kwave::PLAYBACK_QT_AUDIO: return new(std::nothrow) Kwave::PlayBackQt(); #endif /* HAVE_QT_AUDIO_SUPPORT */ #ifdef HAVE_PULSEAUDIO_SUPPORT case Kwave::PLAYBACK_PULSEAUDIO: return new(std::nothrow) Kwave::PlayBackPulseAudio( Kwave::FileInfo(signalManager().metaData())); #endif /* HAVE_PULSEAUDIO_SUPPORT */ #ifdef HAVE_ALSA_SUPPORT case Kwave::PLAYBACK_ALSA: return new(std::nothrow) Kwave::PlayBackALSA(); #endif /* HAVE_ALSA_SUPPORT */ #ifdef HAVE_OSS_SUPPORT case Kwave::PLAYBACK_OSS: return new(std::nothrow) Kwave::PlayBackOSS(); #endif /* HAVE_OSS_SUPPORT */ default: break; } return Q_NULLPTR; // nothing found :-( } //*************************************************************************** void Kwave::PlayBackPlugin::run(QStringList params) { const double t_sweep = 1.0; /* seconds per speaker */ const unsigned int periods = 3; /* number of periods to play */ qDebug("PlayBackPlugin::run()"); Q_UNUSED(params); Q_ASSERT(m_dialog); Q_ASSERT(m_playback_sink); if (!m_dialog || !m_playback_sink) return; Kwave::PlayBackParam playback_params = m_dialog->params(); unsigned int channels = playback_params.channels; double rate = playback_params.rate; Q_ASSERT(channels); Q_ASSERT(rate > 1.0); if (!channels || (rate <= 1.0)) return; // settings are valid -> take them double t_period = t_sweep * channels; unsigned int curve_length = Kwave::toUint(t_period * rate); // create all objects Kwave::Curve curve; curve.insert(0.0, 0.0); if (channels < 2) { // mono curve.insert(0.5, 1.0); } else { // all above curve.insert(0.5 / static_cast(channels), 1.0); curve.insert(1.0 / static_cast(channels), 0.0); } curve.insert(1.0, 0.0); Kwave::CurveStreamAdapter curve_adapter(curve, curve_length); Kwave::MultiTrackSource delay(channels); for (unsigned int i = 0; i < channels; i++) { Q_ASSERT(delay[i]); if (!delay[i]) break; delay[i]->setAttribute(SLOT(setDelay(QVariant)), QVariant(i * t_sweep * rate)); } Kwave::Osc osc; osc.setAttribute(SLOT(setFrequency(QVariant)), QVariant(rate / PLAYBACK_TEST_FREQUENCY)); Kwave::MultiTrackSource mul(channels); // connect everything together... // // curve -> delay --. // | // v // mul -> sink // ^ // | // osc --' Kwave::connect( curve_adapter, SIGNAL(output(Kwave::SampleArray)), delay, SLOT(input(Kwave::SampleArray))); Kwave::connect( delay, SIGNAL(output(Kwave::SampleArray)), mul, SLOT(input_a(Kwave::SampleArray))); Kwave::connect( osc, SIGNAL(output(Kwave::SampleArray)), mul, SLOT(input_b(Kwave::SampleArray))); Kwave::connect( mul, SIGNAL(output(Kwave::SampleArray)), *m_playback_sink, SLOT(input(Kwave::SampleArray))); // show a progress dialog // transport the samples sample_index_t samples_max = static_cast( periods * t_period * rate); sample_index_t samples_written = 0; while (!shouldStop() && (samples_written <= samples_max)) { osc.goOn(); curve_adapter.goOn(); delay.goOn(); mul.goOn(); samples_written += osc.blockSize(); double percent = (static_cast(samples_written) * 100.0) / static_cast(samples_max); emit sigTestProgress(Kwave::toInt(percent)); } } //*************************************************************************** void Kwave::PlayBackPlugin::testPlayBack() { qDebug("PlayBackPlugin::testPlayBack()"); Q_ASSERT(m_dialog); if (!m_dialog) return; Kwave::PlayBackParam playback_params = m_dialog->params(); // check if we really have selected a playback device if (!playback_params.device.length()) { Kwave::MessageBox::sorry(m_dialog, i18n( "Please select a playback device first")); return; } unsigned int channels = playback_params.channels; double rate = playback_params.rate; Q_ASSERT(channels); Q_ASSERT(rate > 1.0); if (!channels || (rate <= 1.0)) return; // settings are valid -> take them // create the multi track playback sink // NOTE: this must be done in main thread context! Q_ASSERT(!m_playback_sink); if (m_playback_sink) return; m_playback_sink = manager().openMultiTrackPlayback( channels, &playback_params ); if (!m_playback_sink) return; m_playback_sink->setInteractive(true); // show a progress dialog QProgressDialog *progress = Q_NULLPTR; progress = new(std::nothrow) QProgressDialog(m_dialog); Q_ASSERT(progress); if (progress) { progress->setWindowTitle(i18n("Playback Test")); progress->setModal(true); progress->setMinimumDuration(0); progress->setMinimum(0); progress->setMaximum(100); progress->setAutoClose(false); progress->setValue(0); progress->setLabelText( _("


") + i18n("You should now hear a %1 Hz test tone.

" "(If you hear clicks or dropouts, please increase
" "the buffer size and try again)", Kwave::toInt(PLAYBACK_TEST_FREQUENCY)) + _("

") ); connect(progress, SIGNAL(canceled()), this, SLOT(cancel()), Qt::QueuedConnection); connect(this, SIGNAL(sigDone(Kwave::Plugin*)), progress, SLOT(close()), Qt::QueuedConnection); connect(this, SIGNAL(sigTestProgress(int)), progress, SLOT(setValue(int)), Qt::QueuedConnection); QStringList params; execute(params); progress->exec(); cancel(); } // set hourglass cursor, waiting for shutdown could take some time... QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // wait through manual polling here, no timeout qDebug("waiting..."); while (isRunning()) { cancel(); sleep(1); qDebug("."); } qDebug("done."); // close the playback sink again (here in main thread context) m_playback_sink->setInteractive(false); delete m_playback_sink; m_playback_sink = Q_NULLPTR; // free the progress dialog delete progress; // stop the worker thread through the Runnable API stop(); // remove hourglass QApplication::restoreOverrideCursor(); } //*************************************************************************** #include "PlayBackPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/playback/PlayBackPlugin.h b/plugins/playback/PlayBackPlugin.h index 611e971a..42f3789b 100644 --- a/plugins/playback/PlayBackPlugin.h +++ b/plugins/playback/PlayBackPlugin.h @@ -1,131 +1,132 @@ /*************************************************************************** PlayBackPlugin.h - plugin for playback and playback configuration ------------------- begin : Sun May 13 2001 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef PLAY_BACK_PLUGIN_H #define PLAY_BACK_PLUGIN_H #include "config.h" #include +#include #include #include "libkwave/PlayBackParam.h" #include "libkwave/PlaybackDeviceFactory.h" #include "libkwave/Plugin.h" #include "libkwave/Sample.h" class QStringList; namespace Kwave { class PlaybackController; class PlayBackDevice; class PlayBackDialog; class PluginContext; class SampleSink; class PlayBackPlugin :public Kwave::Plugin, public Kwave::PlaybackDeviceFactory { Q_OBJECT public: /** * Constructor * @param parent reference to our plugin manager * @param args argument list [unused] */ PlayBackPlugin(QObject *parent, const QVariantList &args); /** Destructor */ virtual ~PlayBackPlugin() Q_DECL_OVERRIDE; /** * Gets called when the plugin is first loaded and connects itself * to the playback controller and the current signal. */ virtual void load(QStringList ¶ms) Q_DECL_OVERRIDE; /** * Gets called before the plugin is unloaded. */ virtual void unload() Q_DECL_OVERRIDE; /** @see Kwave::Plugin::setup() */ virtual QStringList *setup(QStringList &previous_params) Q_DECL_OVERRIDE; /** * Starts a playback test sequence * @param params list of strings with parameters (unused) */ virtual void run(QStringList params) Q_DECL_OVERRIDE; signals: /** emits the progress of the playback test, from thread context */ void sigTestProgress(int percent); public slots: /** * Plays a sample sound for testing the playback */ void testPlayBack(); protected: /** * Interpretes a given parameter list and sets up internal * parameters accordingly. * @param params reference to a QStringList with parameters * @return the detected playback parameters */ Kwave::PlayBackParam interpreteParameters(QStringList ¶ms); /** * Create a playback device matching the given playback method. * @param method a playback_method_t (aRts, ALSA, OSS...) * @return a new PlayBackDevice or 0 if failed */ virtual Kwave::PlayBackDevice *createDevice(Kwave::playback_method_t method) Q_DECL_OVERRIDE; /** * Returns a list of supported playback methods. * @return list of all supported playback methods, should not contain * "any" or "invalid" */ virtual QList supportedMethods() Q_DECL_OVERRIDE; private: /** dialog for the playback setup */ - Kwave::PlayBackDialog *m_dialog; + QPointer m_dialog; /** reference to the playback controller */ Kwave::PlaybackController &m_playback_controller; /** sample sink, for playback test */ Kwave::SampleSink *m_playback_sink; }; } #endif /* PLAY_BACK_PLUGIN_H */ //*************************************************************************** //*************************************************************************** diff --git a/plugins/record/RecordDialog.cpp b/plugins/record/RecordDialog.cpp index 73532873..cb8df9e7 100644 --- a/plugins/record/RecordDialog.cpp +++ b/plugins/record/RecordDialog.cpp @@ -1,1364 +1,1365 @@ /************************************************************************* RecordDialog.cpp - dialog window for controlling audio recording ------------------- begin : Wed Aug 20 2003 copyright : (C) 2003 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/Compression.h" #include "libkwave/SampleFormat.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libgui/FileDialog.h" #include "libgui/HMSTimeWidget.h" #include "LevelMeter.h" #include "RecordDevice.h" #include "RecordDialog.h" #include "RecordParams.h" #include "RecordState.h" // status bar icons #include "ledgreen.xpm" #include "ledlightgreen.xpm" #include "ledred.xpm" #include "ledyellow.xpm" #include "ok.xpm" #include "stop_hand.xpm" #include "walk_r1.xpm" #include "walk_r2.xpm" #include "walk_r3.xpm" #include "walk_r4.xpm" #include "walk_r5.xpm" #include "walk_r6.xpm" #include "walk_r7.xpm" #include "walk_r8.xpm" /* some macros, for laziness ;-) */ #define SETUP(enabled,property,check,value) \ check->setChecked(m_params.enabled); \ value->setValue(m_params.property); \ #define STD_SETUP(enabled,property,control) \ SETUP(enabled,property,chk##control,sb##control); \ sb##control->setEnabled(chk##control->isEnabled() && \ chk##control->isChecked()); #define STD_SETUP_SLIDER(enabled,property,control) \ STD_SETUP(enabled,property,control); \ sl##control->setEnabled(sb##control->isEnabled()); //*************************************************************************** Kwave::RecordDialog::RecordDialog(QWidget *parent, QStringList ¶ms, Kwave::RecordController *controller, Kwave::RecordDialog::Mode mode) :QDialog(parent), Ui::RecordDlg(), m_methods_map(), m_file_filter(), m_devices_list_map(), m_state(Kwave::REC_EMPTY), m_params(), m_supported_resolutions(), m_buffer_progress_count(0), m_buffer_progress_total(0), m_buffer_progress_timer(this), m_record_enabled(true), m_samples_recorded(0), m_enable_setDevice(true), m_state_icon_widget(Q_NULLPTR) { m_status_bar.m_state = Q_NULLPTR; m_status_bar.m_time = Q_NULLPTR; m_status_bar.m_sample_rate = Q_NULLPTR; m_status_bar.m_bits_per_sample = Q_NULLPTR; m_status_bar.m_tracks = Q_NULLPTR; setupUi(this); /* get initial parameters */ m_params.fromList(params); /* set the icons of the record control buttons */ btNew->setIcon( QIcon::fromTheme(_("document-new"))); btStop->setIcon( QIcon::fromTheme(_("kwave_player_stop"))); btPause->setIcon( QIcon::fromTheme(_("kwave_player_pause"))); btRecord->setIcon(QIcon::fromTheme(_("kwave_player_record"))); // fill the combo box with playback methods unsigned int index=0; for (index = 0; index < m_methods_map.count(); ++index) { cbMethod->addItem(m_methods_map.description(index, true)); } cbMethod->setEnabled(cbMethod->count() > 1); /* --- set up all controls with their default/startup values --- */ // pre-record STD_SETUP_SLIDER(pre_record_enabled, pre_record_time, RecordPre); connect(chkRecordPre, SIGNAL(toggled(bool)), this, SLOT(preRecordingChecked(bool))); connect(sbRecordPre, SIGNAL(valueChanged(int)), this, SLOT(preRecordingTimeChanged(int))); // record time (duration) STD_SETUP(record_time_limited, record_time, RecordTime); // start time (date & time) chkRecordStartTime->setChecked(m_params.start_time_enabled); startTime->setDateTime(m_params.start_time); // record trigger STD_SETUP_SLIDER(record_trigger_enabled, record_trigger, RecordTrigger); // amplification STD_SETUP_SLIDER(amplification_enabled, amplification, LevelAmplify); // AGC STD_SETUP_SLIDER(agc_enabled, agc_decay, LevelAGC); // fade in STD_SETUP(fade_in_enabled, fade_in_time, LevelFadeIn); // fade out STD_SETUP(fade_out_enabled, fade_out_time, LevelFadeOut); // sample rate, bits per sample, track // -> will be initialized later, by the plugin // number of buffers slSourceBufferCount->setValue(m_params.buffer_count); // power of buffer size slSourceBufferSize->setValue(m_params.buffer_size); sourceBufferSizeChanged(m_params.buffer_size); // after this point all controls have their initial values /* --- connect some missing low level GUI functionality --- */ connect(cbMethod, SIGNAL(activated(int)), this, SLOT(methodSelected(int))); // record buffer size and count slSourceBufferCount->setValue(m_params.buffer_count); slSourceBufferSize->setValue(m_params.buffer_size); connect(slSourceBufferSize, SIGNAL(valueChanged(int)), this, SLOT(sourceBufferSizeChanged(int))); connect(slSourceBufferCount, SIGNAL(valueChanged(int)), this, SLOT(sourceBufferCountChanged(int))); // device treeview connect(listDevices, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(listEntrySelected(QTreeWidgetItem*,QTreeWidgetItem*))); connect(listDevices, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(listItemExpanded(QTreeWidgetItem*))); connect(listDevices, SIGNAL(focusLost()), SLOT(updateListSelection())); // "select device..." button connect(btSourceSelect, SIGNAL(clicked()), this, SLOT(selectRecordDevice())); connect(cbDevice, SIGNAL(activated(QString)), this, SLOT(setDevice(QString))); // setup controls connect(chkRecordTime, SIGNAL(toggled(bool)), this, SLOT(recordTimeChecked(bool))); connect(sbRecordTime, SIGNAL(valueChanged(int)), this, SLOT(recordTimeChanged(int))); connect(chkRecordStartTime, SIGNAL(toggled(bool)), this, SLOT(startTimeChecked(bool))); connect(startTime, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(startTimeChanged(QDateTime))); connect(chkRecordTrigger, SIGNAL(toggled(bool)), this, SLOT(triggerChecked(bool))); connect(sbRecordTrigger, SIGNAL(valueChanged(int)), this, SLOT(triggerChanged(int))); connect(cbFormatSampleRate, SIGNAL(editTextChanged(QString)), this, SLOT(sampleRateChanged(QString))); connect(cbFormatSampleRate, SIGNAL(activated(QString)), this, SLOT(sampleRateChanged(QString))); connect(sbFormatTracks, SIGNAL(valueChanged(int)), this, SLOT(tracksChanged(int))); connect(cbFormatCompression, SIGNAL(activated(int)), this, SLOT(compressionChanged(int))); connect(sbFormatResolution, SIGNAL(valueChanged(int)), this, SLOT(bitsPerSampleChanged(int))); connect(cbFormatSampleFormat, SIGNAL(activated(int)), this, SLOT(sampleFormatChanged(int))); // connect the buttons to the record controller connect(btNew, SIGNAL(clicked()), controller, SLOT(actionReset())); connect(btStop, SIGNAL(clicked()), controller, SLOT(actionStop())); connect(btPause, SIGNAL(clicked()), controller, SLOT(actionPause())); connect(btRecord, SIGNAL(clicked()), controller, SLOT(actionStart())); // stop recording when the window gets closed connect(this, SIGNAL(rejected()), controller, SLOT(actionStop())); connect(this, SIGNAL(accepted()), controller, SLOT(actionStop())); // connect the notifications/commands of the record controller connect(controller, SIGNAL(stateChanged(Kwave::RecordState)), this, SLOT(setState(Kwave::RecordState))); // timer for updating the buffer progress bar connect(&m_buffer_progress_timer, SIGNAL(timeout()), this, SLOT(updateBufferProgressBar())); // help button connect(buttonBox->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(invokeHelp())); // status bar m_state_icon_widget = new(std::nothrow) Kwave::StatusWidget(this); Q_ASSERT(m_state_icon_widget); if (!m_state_icon_widget) return; m_state_icon_widget->setFixedSize(16, 16); lbl_state->addWidget(m_state_icon_widget); m_status_bar.m_state = new(std::nothrow) QLabel(_(" ")); Q_ASSERT(m_status_bar.m_state); if (!m_status_bar.m_state) return; m_status_bar.m_state->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); lbl_state->addWidget(m_status_bar.m_state); m_status_bar.m_time = new(std::nothrow) QLabel(_(" ")); Q_ASSERT(m_status_bar.m_time); if (!m_status_bar.m_time) return; m_status_bar.m_time->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); lbl_state->addWidget(m_status_bar.m_time); m_status_bar.m_sample_rate = new(std::nothrow) QLabel(_(" ")); Q_ASSERT(m_status_bar.m_sample_rate); if (!m_status_bar.m_sample_rate) return; m_status_bar.m_sample_rate->setAlignment(Qt::AlignRight | Qt::AlignVCenter); lbl_state->addWidget(m_status_bar.m_sample_rate); m_status_bar.m_bits_per_sample = new(std::nothrow) QLabel(_(" ")); Q_ASSERT(m_status_bar.m_bits_per_sample); if (!m_status_bar.m_bits_per_sample) return; m_status_bar.m_bits_per_sample->setAlignment(Qt::AlignRight | Qt::AlignVCenter); lbl_state->addWidget(m_status_bar.m_bits_per_sample); m_status_bar.m_tracks = new(std::nothrow) QLabel(_(" ")); Q_ASSERT(m_status_bar.m_tracks); if (!m_status_bar.m_tracks) return; m_status_bar.m_tracks->setAlignment(Qt::AlignRight | Qt::AlignVCenter); lbl_state->addWidget(m_status_bar.m_tracks); m_state_icon_widget->setFixedSize(16, lbl_state->childrenRect().height()); // set the initial state of the dialog to "Reset/Empty" setState(Kwave::REC_EMPTY); // disable the "level" tab, it is not implemented yet tabRecord->setCurrentIndex(1); QWidget *page = tabRecord->currentWidget(); tabRecord->setCurrentIndex(0); if (page) delete page; // add the "Done" button manually, otherwise it would have "Cancel" semantic QPushButton *bt_done = buttonBox->addButton(i18n("&Done"), QDialogButtonBox::AcceptRole); Q_ASSERT(bt_done); if (!bt_done) return; connect(bt_done, SIGNAL(clicked(bool)), this, SLOT(accept())); switch (mode) { case Kwave::RecordDialog::SETTINGS_FORMAT: tabRecord->setCurrentIndex(1); break; case Kwave::RecordDialog::SETTINGS_SOURCE: tabRecord->setCurrentIndex(2); break; case Kwave::RecordDialog::START_RECORDING: /* FALLTHROUGH */ case Kwave::RecordDialog::SETTINGS_DEFAULT: /* FALLTHROUGH */ default: tabRecord->setCurrentIndex(0); // set the focus onto the "Record" button btRecord->setFocus(); break; } } //*************************************************************************** Kwave::RecordDialog::~RecordDialog() { updateBufferState(0,0); } //*************************************************************************** Kwave::RecordParams &Kwave::RecordDialog::params() { return m_params; } //*************************************************************************** void Kwave::RecordDialog::setMethod(Kwave::record_method_t method) { m_params.method = method; cbMethod->setCurrentIndex(m_methods_map.findFromData( m_params.method)); } //*************************************************************************** void Kwave::RecordDialog::methodSelected(int index) { Kwave::record_method_t method = m_methods_map.data(index); // qDebug("RecordDialog::methodSelected(%d) - %d", index, (int)method); Q_ASSERT(method > Kwave::RECORD_NONE); Q_ASSERT(method < Kwave::RECORD_INVALID); if (method <= Kwave::RECORD_NONE) return; if (method >= Kwave::RECORD_INVALID) return; if (method != m_params.method) { setMethod(method); emit sigMethodChanged(method); } } //*************************************************************************** void Kwave::RecordDialog::setSupportedDevices(QStringList devices) { // qDebug("RecordDialog::setSupportedDevices(QStringList devices)"); Q_ASSERT(cbDevice); Q_ASSERT(listDevices); if (!cbDevice || !listDevices) return; QString current_device = m_params.device_name; // disable all that noisy stuff that comes from modifying the // device controls... m_enable_setDevice = false; KIconLoader *icon_loader = KIconLoader::global(); cbDevice->clearEditText(); cbDevice->clear(); listDevices->clear(); if (devices.contains(_("#EDIT#"))) { devices.removeAll(_("#EDIT#")); cbDevice->setEditable(true); } else { cbDevice->setEditable(false); } if (devices.contains(_("#SELECT#"))) { devices.removeAll(_("#SELECT#")); btSourceSelect->setEnabled(true); btSourceSelect->show(); } else { btSourceSelect->setEnabled(false); btSourceSelect->hide(); } if (devices.contains(_("#TREE#"))) { // treeview mode devices.removeAll(_("#TREE#")); listDevices->setEnabled(true); cbDevice->setEnabled(false); cbDevice->hide(); m_devices_list_map.clear(); // build a tree with all nodes in the list foreach (QString dev_id, devices) { QTreeWidgetItem *parent = Q_NULLPTR; QStringList list = dev_id.split(_("||"), QString::KeepEmptyParts); foreach (QString token, list) { QTreeWidgetItem *item = Q_NULLPTR; // split the icon name from the token QString icon_name; int pos = token.indexOf(QLatin1Char('|')); if (pos > 0) { icon_name = token.mid(pos+1); token = token.left(pos); } // find the first item with the same text // and the same root if (parent) { for (int i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *node = parent->child(i); if (node && node->text(0) == token) { item = node; break; } } } else { QList matches = listDevices->findItems(token, Qt::MatchExactly); if (matches.count()) item = matches.takeFirst(); } if (item) { // already in the list parent = item; } else if (parent) { // new leaf, add to the parent item = new(std::nothrow) QTreeWidgetItem(parent); Q_ASSERT(item); if (item) { item->setText(0, token); m_devices_list_map.insert(item, dev_id); } parent->setExpanded(true); parent->setFlags(parent->flags() & ~(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable)); if (m_devices_list_map.contains(parent)) { // make the parent not selectable m_devices_list_map.remove(parent); } } else { // new root node item = new(std::nothrow) QTreeWidgetItem(listDevices); Q_ASSERT(item); if (item) { item->setText(0, token); m_devices_list_map.insert(item, dev_id); } } if (item && icon_name.length() && icon_loader) { QIcon icon = icon_loader->loadIcon( icon_name, KIconLoader::User); item->setIcon(0, icon); } // use the current item as parent for the next pass parent = item; } } } else { // combo box mode cbDevice->addItems(devices); cbDevice->show(); listDevices->setEnabled(false); if (devices.contains(current_device)) { // current device is in the list cbDevice->setCurrentIndex(cbDevice->findText(current_device)); } else { if (cbDevice->isEditable() && current_device.length()) { // user defined device name cbDevice->setEditText(current_device); } else if (devices.count()) { // one or more other possibilities -> take the first one cbDevice->setCurrentIndex(0); } else { // empty list of possibilities cbDevice->clearEditText(); cbDevice->clear(); } } cbDevice->setEnabled(devices.count() > 1); } // enable changes in the device controls again m_enable_setDevice = true; } //*************************************************************************** void Kwave::RecordDialog::listEntrySelected(QTreeWidgetItem *current, QTreeWidgetItem *previous) { Q_ASSERT(listDevices); Q_UNUSED(previous); if (!current || !listDevices) return; if (m_devices_list_map.contains(current)) setDevice(m_devices_list_map[current]); } //*************************************************************************** void Kwave::RecordDialog::listItemExpanded(QTreeWidgetItem *item) { Q_UNUSED(item); updateListSelection(); } //*************************************************************************** void Kwave::RecordDialog::updateListSelection() { // set the current device again, otherwise nothing will be selected setDevice(m_params.device_name); } //*************************************************************************** void Kwave::RecordDialog::setDevice(const QString &device) { Q_ASSERT(cbDevice); Q_ASSERT(listDevices); if (!cbDevice || !listDevices) return; bool device_changed = (device != m_params.device_name); m_params.device_name = device; // qDebug("RecordDialog::setDevice(%s)", device.local8Bit().data()); if (listDevices->isEnabled()) { // treeview mode QTreeWidgetItem *node = m_devices_list_map.key(device, Q_NULLPTR); if (node) { node->setSelected(true); listDevices->scrollToItem(node); listDevices->setCurrentItem(node); } } else if (cbDevice->isEditable() && device.length()) { // user defined device name if (!device.isEmpty() && (cbDevice->currentText() != device)) { cbDevice->setCurrentIndex(cbDevice->findText(device)); cbDevice->setEditText(device); } } else { // just take one from the list if (cbDevice->findText(device) >= 0) { cbDevice->setCurrentIndex(cbDevice->findText(device)); } else if (cbDevice->count()) { cbDevice->setCurrentIndex(0); } } if (device_changed) emit sigDeviceChanged(device); } //*************************************************************************** void Kwave::RecordDialog::sourceBufferCountChanged(int value) { Q_ASSERT(value >= 4); Q_ASSERT(value <= 64); if (value < 4) value = 4; if (value > 64) value = 64; // take the value into our struct m_params.buffer_count = value; emit sigBuffersChanged(); } //*************************************************************************** void Kwave::RecordDialog::sourceBufferSizeChanged(int value) { Q_ASSERT(value >= 10); Q_ASSERT(value <= 18); if (value < 10) value = 10; if (value > 18) value = 18; // take the value into our struct m_params.buffer_size = value; // update the text unsigned int buffer_size = (1 << value); txtSourceBuffer->setText(i18n("%1 samples", buffer_size)); emit sigBuffersChanged(); } //*************************************************************************** void Kwave::RecordDialog::setFileFilter(const QString &filter) { m_file_filter = filter; if (btSourceSelect) btSourceSelect->setEnabled(m_file_filter.length()); } //*************************************************************************** void Kwave::RecordDialog::selectRecordDevice() { if (!m_enable_setDevice) return; QString filter; filter += _("dsp*|") + i18n("OSS record device (dsp*)"); filter += _("\nadsp*|") + i18n("ALSA record device (adsp*)"); filter += _("\n*|") + i18n("Any device (*)"); QPointer dlg = new(std::nothrow) Kwave::FileDialog( _("kfiledialog:///kwave_record_device"), Kwave::FileDialog::OpenFile, filter, this, QUrl(_("file:/dev")) ); if (!dlg) return; dlg->setWindowTitle(i18n("Select Record Device")); if (!m_params.device_name.startsWith(_("#"))) dlg->selectUrl(QUrl(_("file:") + m_params.device_name)); else dlg->selectUrl(QUrl(_("file:/dev/*"))); - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { // selected new device QString new_device = dlg->selectedUrl().path(); if (new_device != m_params.device_name) emit sigDeviceChanged(new_device); } delete dlg; } //*************************************************************************** QString Kwave::RecordDialog::rate2string(double rate) const { QLocale locale; const QString dot = locale.decimalPoint(); const QString tsep = locale.groupSeparator(); // format number with 3 digits QString s = locale.toString(rate, 'f', 3); // remove thousands separator (looks ugly) s.remove(tsep); // remove trailing zeroes while (s.endsWith(_("0"))) s.remove(s.length()-1, 1); // remove decimal point if necessary if (s.endsWith(dot)) s.remove(s.length()-1, 1); return s; } //*************************************************************************** double Kwave::RecordDialog::string2rate(const QString &rate) const { QLocale locale; const QString s = rate; double r; bool ok; r = locale.toDouble(rate, &ok); Q_ASSERT(ok); if (!ok) return s.toDouble(); return r; } //*************************************************************************** void Kwave::RecordDialog::setSupportedTracks(unsigned int min, unsigned int max) { Q_ASSERT(sbFormatTracks); if (!sbFormatTracks) return; if ((min == max) || (!max)) { sbFormatTracks->setEnabled(false); return; } else sbFormatTracks->setEnabled(true); if (sbFormatTracks->value() < sbFormatTracks->minimum()) { sbFormatTracks->setMaximum(max); sbFormatTracks->setMinimum(min); } else { sbFormatTracks->setMinimum(min); sbFormatTracks->setMaximum(max); } } //*************************************************************************** void Kwave::RecordDialog::setTracks(unsigned int tracks) { // qDebug("+++ RecordDialog::setTracks(%u)", tracks); Q_ASSERT(sbFormatTracks); Q_ASSERT(m_status_bar.m_tracks); if (!sbFormatTracks || !m_status_bar.m_tracks) return; if (!tracks) return; m_params.tracks = tracks; QString tracks_str; switch (tracks) { case 1: tracks_str = i18n("Mono"); break; case 2: tracks_str = i18n("Stereo"); break; case 4: tracks_str = i18n("Quadro"); break; default: tracks_str = _(""); } if (tracks_str.length()) { lblTracksVerbose->setText(_("(") + tracks_str + _(")")); m_status_bar.m_tracks->setText(tracks_str); } else { lblTracksVerbose->setText(_("")); m_status_bar.m_tracks->setText(i18n("%1 tracks", tracks)); } sbFormatTracks->setValue(tracks); } //*************************************************************************** void Kwave::RecordDialog::tracksChanged(int tracks) { if (tracks < 1) return; // no device if (tracks == Kwave::toInt(m_params.tracks)) return; m_params.tracks = tracks; emit sigTracksChanged(tracks); } //*************************************************************************** void Kwave::RecordDialog::setSupportedSampleRates(const QList &rates) { Q_ASSERT(cbFormatSampleRate); if (!cbFormatSampleRate) return; cbFormatSampleRate->clearEditText(); cbFormatSampleRate->setEditable(false); cbFormatSampleRate->clear(); foreach (double r, rates) { QString rate = rate2string(r); Q_ASSERT(rate.length()); if (!rate.length()) continue; // string was zero? cbFormatSampleRate->addItem(rate); } bool have_choice = (cbFormatSampleRate->count() > 1); cbFormatSampleRate->setEnabled(have_choice); } //*************************************************************************** void Kwave::RecordDialog::setSampleRate(double new_rate) { Q_ASSERT(cbFormatSampleRate); Q_ASSERT(m_status_bar.m_sample_rate); if (!cbFormatSampleRate || !m_status_bar.m_sample_rate) return; if (new_rate <= 0) { cbFormatSampleRate->setEnabled(false); return; } else { bool have_choice = (cbFormatSampleRate->count() > 1); cbFormatSampleRate->setEnabled(have_choice); m_params.sample_rate = new_rate; } QString rate; rate = rate2string(new_rate); cbFormatSampleRate->setCurrentItem(rate, true); m_status_bar.m_sample_rate->setText(i18n("%1 Hz", rate)); } //*************************************************************************** void Kwave::RecordDialog::sampleRateChanged(const QString &rate) { if (!rate.length()) return; // no rate selected, combo box clear double sample_rate = string2rate(rate); if (qFuzzyCompare(sample_rate, m_params.sample_rate)) return; m_params.sample_rate = sample_rate; emit sampleRateChanged(sample_rate); } //*************************************************************************** void Kwave::RecordDialog::setSupportedCompressions( const QList &comps ) { Q_ASSERT(cbFormatCompression); if (!cbFormatCompression) return; cbFormatCompression->clear(); if (comps.isEmpty()) { // no compressions -> add "none" manually const Kwave::Compression comp(Kwave::Compression::NONE); cbFormatCompression->addItem(comp.name()); } else { foreach (Kwave::Compression::Type c, comps) { const Kwave::Compression comp(c); cbFormatCompression->addItem(comp.name(), comp.toInt()); } } bool have_choice = (cbFormatCompression->count() > 1); cbFormatCompression->setEnabled(have_choice); } //*************************************************************************** void Kwave::RecordDialog::setCompression(int compression) { Q_ASSERT(cbFormatCompression); if (!cbFormatCompression) return; if (compression < 0) { cbFormatCompression->setEnabled(false); return; } else { bool have_choice = (cbFormatCompression->count() > 1); cbFormatCompression->setEnabled(have_choice); m_params.compression = Kwave::Compression::fromInt(compression); } const Kwave::Compression comp(Kwave::Compression::fromInt(compression)); cbFormatCompression->setCurrentItem(comp.name(), true); } //*************************************************************************** void Kwave::RecordDialog::compressionChanged(int index) { Kwave::Compression::Type compression = Kwave::Compression::fromInt( cbFormatCompression->itemData(index).toInt()); if (compression != m_params.compression) emit sigCompressionChanged(compression); } //*************************************************************************** void Kwave::RecordDialog::setSupportedBits(const QList &bits) { Q_ASSERT(sbFormatResolution); if (!sbFormatResolution) return; m_supported_resolutions = bits; if (bits.count()) { sbFormatResolution->setMinimum(bits.first()); sbFormatResolution->setMaximum(bits.last()); } // enable only if there is a choice sbFormatResolution->setEnabled(bits.count() > 1); } //*************************************************************************** void Kwave::RecordDialog::setBitsPerSample(unsigned int bits) { Q_ASSERT(sbFormatResolution); Q_ASSERT(m_status_bar.m_bits_per_sample); if (!sbFormatResolution || !m_status_bar.m_bits_per_sample) return; if (!bits ) { sbFormatResolution->setEnabled(false); return; } else { sbFormatResolution->setEnabled(m_supported_resolutions.count() > 1); m_params.bits_per_sample = bits; } m_status_bar.m_bits_per_sample->setText(i18n("%1 bit", bits)); sbFormatResolution->setValue(bits); } //*************************************************************************** void Kwave::RecordDialog::bitsPerSampleChanged(int bits) { if (bits < 1) return; // no device int last = m_params.bits_per_sample; if (bits == last) return; // round up or down to the next supported resolution in bits per sample if (!m_supported_resolutions.isEmpty()) { if (bits > last) { // step up to the next supported value QListIterator it(m_supported_resolutions); while (it.hasNext()) { bits = it.next(); if (bits > last) break; } if (bits < last) bits = m_supported_resolutions.last(); } else { // step down to the next supported value QListIterator it(m_supported_resolutions); it.toBack(); while (it.hasPrevious()) { bits = it.previous(); if (bits < last) break; } if (bits > last) bits = m_supported_resolutions.first(); } } m_params.bits_per_sample = bits; if (sbFormatResolution && (bits != sbFormatResolution->value())) sbFormatResolution->setValue(bits); emit sigBitsPerSampleChanged(bits); } //*************************************************************************** void Kwave::RecordDialog::setSupportedSampleFormats( const QList &formats) { Q_ASSERT(cbFormatSampleFormat); if (!cbFormatSampleFormat) return; cbFormatSampleFormat->clear(); Kwave::SampleFormat::Map types; foreach (Kwave::SampleFormat::Format format, formats) { int index = types.findFromData(format); cbFormatSampleFormat->addItem( types.description(index, true), Kwave::SampleFormat(format).toInt() ); } bool have_choice = (cbFormatSampleFormat->count() > 1); cbFormatSampleFormat->setEnabled(have_choice); } //*************************************************************************** void Kwave::RecordDialog::setSampleFormat( Kwave::SampleFormat::Format sample_format) { Q_ASSERT(cbFormatSampleFormat); if (!cbFormatSampleFormat) return; if (sample_format == Kwave::SampleFormat::Unknown) { cbFormatSampleFormat->setEnabled(false); return; } else { bool have_choice = (cbFormatSampleFormat->count() > 1); cbFormatSampleFormat->setEnabled(have_choice); m_params.sample_format = sample_format; } int cb_index = cbFormatSampleFormat->findData( Kwave::SampleFormat(sample_format).toInt()); cbFormatSampleFormat->setCurrentIndex(cb_index); } //*************************************************************************** void Kwave::RecordDialog::sampleFormatChanged(int index) { Q_ASSERT(cbFormatSampleFormat); if (!cbFormatSampleFormat) return; Kwave::SampleFormat format; format.fromInt(cbFormatSampleFormat->itemData(index).toInt()); if (format == m_params.sample_format) return; emit sigSampleFormatChanged(format); } //*************************************************************************** void Kwave::RecordDialog::setState(Kwave::RecordState state) { Q_ASSERT(m_status_bar.m_state); if (!m_status_bar.m_state) return; bool enable_new = false; bool enable_pause = false; bool enable_stop = false; bool enable_record = false; bool enable_settings = false; bool enable_trigger = false; QString state_text = _(""); QVector pixmaps; unsigned int animation_time = 500; m_state = state; switch (state) { case Kwave::REC_UNINITIALIZED: state_text = i18n("Please check the source device settings..."); enable_new = true; enable_pause = false; enable_stop = false; enable_record = false; enable_settings = true; enable_trigger = true; pixmaps.push_back(QPixmap(stop_hand_xpm)); pixmaps.push_back(QPixmap(ledred_xpm)); m_status_bar.m_time->setText(_("")); break; case Kwave::REC_EMPTY: state_text = i18n("(empty)"); enable_new = true; enable_pause = false; enable_stop = false; enable_record = m_params.device_name.length(); enable_settings = true; enable_trigger = true; pixmaps.push_back(QPixmap(ledgreen_xpm)); m_status_bar.m_time->setText(_("")); break; case Kwave::REC_BUFFERING: state_text = i18n("Buffering..."); enable_new = true; /* throw away current FIFO content */ enable_pause = false; enable_stop = true; enable_record = true; /* acts as "trigger now" */ enable_settings = false; enable_trigger = true; pixmaps.push_back(QPixmap(ledgreen_xpm)); pixmaps.push_back(QPixmap(ledlightgreen_xpm)); break; case Kwave::REC_PRERECORDING: state_text = i18n("Prerecording..."); enable_new = false; enable_pause = false; enable_stop = true; enable_record = true; enable_settings = false; enable_trigger = true; pixmaps.push_back(QPixmap(ledgreen_xpm)); pixmaps.push_back(QPixmap(ledlightgreen_xpm)); break; case Kwave::REC_WAITING_FOR_TRIGGER: state_text = i18n("Waiting for trigger..."); enable_new = false; enable_pause = false; enable_stop = true; enable_record = true; /* acts as "trigger now" */ enable_settings = false; enable_trigger = true; pixmaps.push_back(QPixmap(ledgreen_xpm)); pixmaps.push_back(QPixmap(ledlightgreen_xpm)); break; case Kwave::REC_RECORDING: state_text = i18n("Recording..."); enable_new = false; enable_pause = true; enable_stop = true; enable_record = false; enable_settings = false; enable_trigger = false; pixmaps.push_back(QPixmap(walk_r1_xpm)); pixmaps.push_back(QPixmap(walk_r2_xpm)); pixmaps.push_back(QPixmap(walk_r3_xpm)); pixmaps.push_back(QPixmap(walk_r4_xpm)); pixmaps.push_back(QPixmap(walk_r5_xpm)); pixmaps.push_back(QPixmap(walk_r6_xpm)); pixmaps.push_back(QPixmap(walk_r7_xpm)); pixmaps.push_back(QPixmap(walk_r8_xpm)); animation_time = 100; break; case Kwave::REC_PAUSED: state_text = i18n("Paused"); enable_new = true; /* start again */ enable_pause = true; /* used for "continue" */ enable_stop = true; enable_record = true; /* used for "continue" */ enable_settings = false; enable_trigger = false; pixmaps.push_back(QPixmap(ledgreen_xpm)); pixmaps.push_back(QPixmap(ledyellow_xpm)); break; case Kwave::REC_DONE: state_text = i18n("Done"); enable_new = true; enable_pause = false; enable_stop = false; enable_record = true; enable_settings = true; enable_trigger = true; pixmaps.push_back(QPixmap(ok_xpm)); break; } m_status_bar.m_state->setText(state_text); m_state_icon_widget->setPixmaps(pixmaps, animation_time); // enable/disable the record control buttons btNew->setEnabled(enable_new); btPause->setEnabled(enable_pause); btStop->setEnabled(enable_stop); m_record_enabled = enable_record; updateRecordButton(); // enable disable all controls (groups) for setup chkRecordPre->setEnabled(enable_settings); sbRecordPre->setEnabled(enable_settings && chkRecordPre->isChecked()); slRecordPre->setEnabled(enable_settings && chkRecordPre->isChecked()); chkRecordStartTime->setEnabled(enable_settings); chkRecordTime->setEnabled(enable_settings); sbRecordTime->setEnabled(enable_settings && chkRecordTime->isChecked()); chkRecordTrigger->setEnabled(enable_settings); // it is not really necessary to disable these ;-) sbRecordTrigger->setEnabled(enable_trigger && chkRecordTrigger->isChecked()); slRecordTrigger->setEnabled(enable_trigger && chkRecordTrigger->isChecked()); startTime->setEnabled(enable_settings && chkRecordStartTime->isChecked()); grpFormat->setEnabled(enable_settings); grpSource->setEnabled(enable_settings); } //*************************************************************************** void Kwave::RecordDialog::updateBufferState(unsigned int count, unsigned int total) { Q_ASSERT(progress_bar); Q_ASSERT(m_status_bar.m_state); if (!progress_bar || !m_status_bar.m_state) return; if (total == 0) { // we are done: stop update timer and reset buffer percentage m_buffer_progress_timer.stop(); m_buffer_progress_count = 0; m_buffer_progress_total = 0; progress_bar->setTextVisible(false); progress_bar->setMinimum(0); progress_bar->setMaximum(100); progress_bar->setValue(0); progress_bar->reset(); } else { m_buffer_progress_count = count; m_buffer_progress_total = total; if (!m_buffer_progress_timer.isActive()) updateBufferProgressBar(); } // update recording time QString txt; switch (m_state) { case Kwave::REC_UNINITIALIZED: case Kwave::REC_EMPTY: case Kwave::REC_BUFFERING: case Kwave::REC_PRERECORDING: txt = _(""); break; case Kwave::REC_WAITING_FOR_TRIGGER: { txt = _(""); QString state_text; QDateTime now = QDateTime::currentDateTime(); QDateTime t_start = m_params.start_time; if (m_params.start_time_enabled && (now < t_start)) { // waiting for start time to come... int s = Kwave::toInt(now.secsTo(t_start)); int m = s / 60; s %= 60; int h = m / 60; m %= 60; int d = h / 24; h %= 24; QString days = (d) ? i18np("one day ", "%1 days ", d) : _(""); QString hours = (h) ? i18np("one hour ", "%1 hours ", h) : _(""); QString minutes = (m) ? i18np("one minute ", "%1 minutes ", m) : _(""); QString seconds = (d | h | m) ? i18np("and %1 second", "and %1 seconds", s) : i18np("%1 second", "%1 seconds", s); state_text = i18nc( "%1=days; %2=hours; %3=minutes; %4=seconds", "Waiting for start in %1%2%3%4...", days, hours, minutes, seconds); } else { // waiting for trigger... state_text = i18n("Waiting for trigger..."); } m_status_bar.m_state->setText(state_text); break; } case Kwave::REC_RECORDING: case Kwave::REC_PAUSED: case Kwave::REC_DONE: { if (m_samples_recorded > 1) { double rate = m_params.sample_rate; double ms = (rate > 0) ? ((static_cast(m_samples_recorded) / rate) * 1E3) : 0; txt = _(" ") + i18n("Length: %1", Kwave::ms2string(ms)) + _(" ") + i18n("(%1 samples)", Kwave::samples2string(m_samples_recorded)); } else txt = _(""); break; } } m_status_bar.m_time->setText(txt); } //*************************************************************************** void Kwave::RecordDialog::preRecordingChecked(bool enabled) { m_params.pre_record_enabled = enabled; emit sigPreRecordingChanged(enabled); } //*************************************************************************** void Kwave::RecordDialog::preRecordingTimeChanged(int time) { m_params.pre_record_time = time; } //*************************************************************************** void Kwave::RecordDialog::recordTimeChecked(bool limited) { m_params.record_time_limited = limited; emit sigRecordTimeChanged(limited ? sbRecordTime->value() : -1); } //*************************************************************************** void Kwave::RecordDialog::recordTimeChanged(int limit) { m_params.record_time = limit; emit sigRecordTimeChanged(chkRecordTime->isChecked() ? limit : -1); updateRecordButton(); } //*************************************************************************** void Kwave::RecordDialog::startTimeChecked(bool enabled) { m_params.start_time_enabled = enabled; emit sigTriggerChanged(enabled || m_params.record_trigger_enabled); } //*************************************************************************** void Kwave::RecordDialog::startTimeChanged(const QDateTime &datetime) { m_params.start_time = datetime; // force seconds to zero QTime t = m_params.start_time.time(); t.setHMS(t.hour(), t.minute(), 0, 0); m_params.start_time.setTime(t); } //*************************************************************************** void Kwave::RecordDialog::triggerChecked(bool enabled) { m_params.record_trigger_enabled = enabled; emit sigTriggerChanged(enabled || m_params.start_time_enabled); } //*************************************************************************** void Kwave::RecordDialog::triggerChanged(int trigger) { m_params.record_trigger = trigger; } //*************************************************************************** void Kwave::RecordDialog::updateBufferProgressBar() { unsigned int count = m_buffer_progress_count; unsigned int total = m_buffer_progress_total; // qDebug("RecordDialog::updateBufferProgressBar(): %u/%u", // count, total); /* * @note: QProgressBar has a bug when handling small numbers, * therefore we multiply everything by 100 */ progress_bar->setTextVisible(true); progress_bar->setMinimum(0); progress_bar->setMaximum(100 * total); progress_bar->setValue(100 * count); m_buffer_progress_timer.setSingleShot(true); m_buffer_progress_timer.setInterval(100); m_buffer_progress_timer.start(); } //*************************************************************************** void Kwave::RecordDialog::updateEffects(unsigned int track, Kwave::SampleArray &buffer) { if (!buffer.size()) return; if (level_meter) { level_meter->setTracks(m_params.tracks); level_meter->setSampleRate(m_params.sample_rate); level_meter->updateTrack(track, buffer); } } //*************************************************************************** void Kwave::RecordDialog::setRecordedSamples(sample_index_t samples_recorded) { // if (!m_params.record_time_limited) return; // not of interest m_samples_recorded = samples_recorded; updateRecordButton(); } //*************************************************************************** void Kwave::RecordDialog::updateRecordButton() { bool old_enable = btRecord->isEnabled(); bool new_enable; // enabled if not disabled by status and also not limited or // less than the limit has been recorded new_enable = m_record_enabled && (!m_params.record_time_limited || (m_samples_recorded < m_params.record_time * m_params.sample_rate)); if (new_enable != old_enable) btRecord->setEnabled(new_enable); } //*************************************************************************** void Kwave::RecordDialog::invokeHelp() { KHelpClient::invokeHelp(_("recording")); } //*************************************************************************** void Kwave::RecordDialog::message(const QString &message) { if (lbl_state) lbl_state->showMessage(message, 3000); } //*************************************************************************** void Kwave::RecordDialog::showDevicePage() { if (tabRecord) tabRecord->setCurrentIndex(2); } //*************************************************************************** //*************************************************************************** diff --git a/plugins/record/RecordPlugin.cpp b/plugins/record/RecordPlugin.cpp index bbce4d0f..3972faf3 100644 --- a/plugins/record/RecordPlugin.cpp +++ b/plugins/record/RecordPlugin.cpp @@ -1,1619 +1,1619 @@ /************************************************************************* RecordPlugin.cpp - plugin for recording audio data ------------------- begin : Wed Jul 09 2003 copyright : (C) 2003 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libkwave/Compression.h" #include "libkwave/FileInfo.h" #include "libkwave/InsertMode.h" #include "libkwave/MessageBox.h" #include "libkwave/PluginManager.h" #include "libkwave/Sample.h" #include "libkwave/SampleFIFO.h" #include "libkwave/SampleFormat.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/Writer.h" #include "Record-ALSA.h" #include "Record-OSS.h" #include "Record-PulseAudio.h" #include "Record-Qt.h" #include "RecordDevice.h" #include "RecordDialog.h" #include "RecordPlugin.h" #include "RecordThread.h" #include "SampleDecoderLinear.h" KWAVE_PLUGIN(record, RecordPlugin) #define OPEN_RETRY_TIME 1000 /**< time interval for trying to open [ms] */ //*************************************************************************** Kwave::RecordPlugin::RecordPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_method(Kwave::RECORD_NONE), m_device_name(), m_controller(), m_state(Kwave::REC_EMPTY), m_device(Q_NULLPTR), m_dialog(Q_NULLPTR), m_thread(Q_NULLPTR), m_decoder(Q_NULLPTR), m_prerecording_queue(), m_writers(Q_NULLPTR), m_buffers_recorded(0), m_inhibit_count(0), m_trigger_value(), m_retry_timer() { m_retry_timer.setSingleShot(true); connect(&m_retry_timer, SIGNAL(timeout()), this, SLOT(retryOpen()), Qt::QueuedConnection ); } //*************************************************************************** Kwave::RecordPlugin::~RecordPlugin() { Q_ASSERT(!m_dialog); if (m_dialog) delete m_dialog; m_dialog = Q_NULLPTR; Q_ASSERT(!m_thread); if (m_thread) delete m_thread; m_thread = Q_NULLPTR; Q_ASSERT(!m_decoder); if (m_decoder) delete m_decoder; m_decoder = Q_NULLPTR; if (m_device) delete m_device; m_device = Q_NULLPTR; } //*************************************************************************** QStringList *Kwave::RecordPlugin::setup(QStringList &previous_params) { Kwave::RecordDialog::Mode mode = Kwave::RecordDialog::SETTINGS_DEFAULT; qDebug("RecordPlugin::setup(%s)", DBG(previous_params.join(_(","))));; // if we have only one parameter, then we got called with a specific // mode, e.g. "show format settings only" if (previous_params.count() == 1) { const QString m = previous_params[0].toLower(); if (m == _("format")) mode = Kwave::RecordDialog::SETTINGS_FORMAT; else if (m == _("source")) mode = Kwave::RecordDialog::SETTINGS_SOURCE; else if (m == _("start_now")) mode = Kwave::RecordDialog::START_RECORDING; // get previous parameters for the setup dialog previous_params = manager().defaultParams(name()); qDebug("RecordPlugin::setup(%s) - MODE=%d", DBG(previous_params.join(_(","))), static_cast(mode)); } // create the setup dialog m_dialog = new(std::nothrow) Kwave::RecordDialog( parentWidget(), previous_params, &m_controller, mode ); Q_ASSERT(m_dialog); if (!m_dialog) return Q_NULLPTR; // create the lowlevel recording thread m_thread = new(std::nothrow) Kwave::RecordThread(); Q_ASSERT(m_thread); if (!m_thread) { delete m_dialog; m_dialog = Q_NULLPTR; return Q_NULLPTR; } // connect some signals of the setup dialog connect(m_dialog, SIGNAL(sigMethodChanged(Kwave::record_method_t)), this, SLOT(setMethod(Kwave::record_method_t))); connect(m_dialog, SIGNAL(sigDeviceChanged(QString)), this, SLOT(setDevice(QString))); connect(m_dialog, SIGNAL(sigTracksChanged(uint)), this, SLOT(changeTracks(uint))); connect(m_dialog, SIGNAL(sampleRateChanged(double)), this, SLOT(changeSampleRate(double))); connect(m_dialog, SIGNAL(sigCompressionChanged(Kwave::Compression::Type)), this, SLOT(changeCompression(Kwave::Compression::Type))); connect(m_dialog, SIGNAL(sigBitsPerSampleChanged(uint)), this, SLOT(changeBitsPerSample(uint))); connect(m_dialog, SIGNAL(sigSampleFormatChanged(Kwave::SampleFormat::Format)), this, SLOT(changeSampleFormat(Kwave::SampleFormat::Format))); connect(m_dialog, SIGNAL(sigBuffersChanged()), this, SLOT(buffersChanged())); connect(this, SIGNAL(sigRecordedSamples(sample_index_t)), m_dialog, SLOT(setRecordedSamples(sample_index_t))); connect(m_dialog, SIGNAL(sigTriggerChanged(bool)), &m_controller, SLOT(enableTrigger(bool))); m_controller.enableTrigger( m_dialog->params().record_trigger_enabled || m_dialog->params().start_time_enabled ); connect(m_dialog, SIGNAL(sigPreRecordingChanged(bool)), &m_controller, SLOT(enablePrerecording(bool))); connect(m_dialog, SIGNAL(sigPreRecordingChanged(bool)), this, SLOT(prerecordingChanged(bool))); m_controller.enablePrerecording(m_dialog->params().pre_record_enabled); // connect the record controller and this connect(&m_controller, SIGNAL(sigReset(bool&)), this, SLOT(resetRecording(bool&))); connect(&m_controller, SIGNAL(sigStartRecord()), this, SLOT(startRecording())); connect(&m_controller, SIGNAL(sigStopRecord(int)), &m_controller, SLOT(deviceRecordStopped(int))); connect(&m_controller, SIGNAL(stateChanged(Kwave::RecordState)), this, SLOT(stateChanged(Kwave::RecordState))); // connect record controller and record thread connect(m_thread, SIGNAL(stopped(int)), &m_controller, SLOT(deviceRecordStopped(int))); // connect us to the record thread connect(m_thread, SIGNAL(stopped(int)), this, SLOT(recordStopped(int))); connect(m_thread, SIGNAL(bufferFull()), this, SLOT(processBuffer()), Qt::QueuedConnection); // dummy init -> disable format settings m_dialog->setSupportedTracks(0, 0); // activate the recording method setMethod(m_dialog->params().method); // directly start recording if requested if (mode == Kwave::RecordDialog::START_RECORDING) m_controller.actionStart(); QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && (m_dialog->exec() == QDialog::Accepted)) { + if (list && (m_dialog->exec() == QDialog::Accepted) && m_dialog) { // user has pressed "OK" *list = m_dialog->params().toList(); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } /* de-queue all buffers that are pending and remove the record thread */ if (m_thread) { m_thread->stop(); while (m_thread->queuedBuffers()) processBuffer(); delete m_thread; m_thread = Q_NULLPTR; } if (m_decoder) delete m_decoder; m_decoder = Q_NULLPTR; delete m_dialog; m_dialog = Q_NULLPTR; // flush away all prerecording buffers m_prerecording_queue.clear(); // enable undo again if we recorded something if (!signalManager().isEmpty()) signalManager().enableUndo(); return list; } //*************************************************************************** void Kwave::RecordPlugin::notice(QString message) { Q_ASSERT(m_dialog); if (m_dialog) m_dialog->message(message); } //*************************************************************************** void Kwave::RecordPlugin::closeDevice() { if (m_retry_timer.isActive()) m_retry_timer.stop(); if (m_device) { m_device->close(); delete m_device; m_device = Q_NULLPTR; } } //*************************************************************************** void Kwave::RecordPlugin::setMethod(Kwave::record_method_t method) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change qDebug("RecordPlugin::setMethod(%d)", static_cast(method)); // change the recording method (class RecordDevice) if ((method != m_method) || !m_device) { if (m_device) delete m_device; m_device = Q_NULLPTR; bool searching = false; // use the previous device QString section = _("plugin ") + name(); KConfigGroup cfg = KSharedConfig::openConfig()->group(section); // restore the previous device QString device = cfg.readEntry( _("last_device_%1").arg(static_cast(method))); // qDebug("<<< %d -> '%s'", static_cast(method), device.data()); m_device_name = device; do { switch (method) { #ifdef HAVE_OSS_SUPPORT case Kwave::RECORD_OSS: m_device = new(std::nothrow) Kwave::RecordOSS(); Q_ASSERT(m_device); break; #endif /* HAVE_OSS_SUPPORT */ #ifdef HAVE_ALSA_SUPPORT case Kwave::RECORD_ALSA: m_device = new(std::nothrow) Kwave::RecordALSA(); Q_ASSERT(m_device); break; #endif /* HAVE_ALSA_SUPPORT */ #ifdef HAVE_PULSEAUDIO_SUPPORT case Kwave::RECORD_PULSEAUDIO: m_device = new(std::nothrow) Kwave::RecordPulseAudio(); Q_ASSERT(m_device); break; #endif /* HAVE_PULSEAUDIO_SUPPORT */ #ifdef HAVE_QT_AUDIO_SUPPORT case Kwave::RECORD_QT: m_device = new(std::nothrow) Kwave::RecordQt(); Q_ASSERT(m_device); break; #endif /* HAVE_QT_AUDIO_SUPPORT */ default: qDebug("unsupported recording method (%d)", static_cast(method)); if (!searching) { // start trying all other methods searching = true; method = Kwave::RECORD_NONE; ++method; continue; } else { // try next method ++method; } qDebug("unsupported recording method - trying next (%d)", static_cast(method)); if (method != Kwave::RECORD_INVALID) continue; } break; } while (true); } Q_ASSERT(m_device); // if we found no recording method if (method == Kwave::RECORD_INVALID) { qWarning("found no valid recording method"); } // take the change in the method m_method = method; // activate the cange in the dialog m_dialog->setMethod(method); // set list of supported devices QStringList supported_devices; Q_ASSERT(m_device); if (m_device) supported_devices = m_device->supportedDevices(); m_dialog->setSupportedDevices(supported_devices); // set current device (again), no matter if supported or not, // the dialog will take care of this. setDevice(m_device_name); // check the filter for the "select..." dialog. If it is // empty, the "select" dialog will be disabled QString file_filter; if (m_device) file_filter = m_device->fileFilter(); m_dialog->setFileFilter(file_filter); } //*************************************************************************** void Kwave::RecordPlugin::retryOpen() { qDebug("RecordPlugin::retryOpen()"); setDevice(m_device_name); } //*************************************************************************** void Kwave::RecordPlugin::setDevice(const QString &device) { Q_ASSERT(m_dialog); Q_ASSERT(m_device); if (!m_dialog || !m_device) return; InhibitRecordGuard _lock(*this); // don't record while settings change qDebug("RecordPlugin::setDevice('%s')", DBG(device)); if (m_retry_timer.isActive()) m_retry_timer.stop(); // select the default device if this one is not supported QString dev = device; QStringList supported = m_device->supportedDevices(); if (!supported.isEmpty() && !supported.contains(device)) { // use the first entry as default dev = supported.first(); qDebug("RecordPlugin::setDevice(%s) -> fallback to '%s'", DBG(device), DBG(dev)); } // if there was no valid device name, fall back to default device if (dev.startsWith(_("#"))) { dev = _("/dev/dsp"); qDebug("RecordPlugin::setDevice(%s) -> no valid device, using '%s'", DBG(device), DBG(dev)); } // open and initialize the device QString result = m_device->open(dev); // set the device in the dialog m_device_name = dev; m_dialog->setDevice(dev); // remember the device selection, just for the GUI // for the next change in the method QString section = _("plugin ") + name(); KConfigGroup cfg = KSharedConfig::openConfig()->group(section); cfg.writeEntry(_("last_device_%1").arg( static_cast(m_method)), m_device_name); // qDebug(">>> %d -> '%s'", static_cast(m_method), DBG(m_device_name)); cfg.sync(); if (!result.isNull()) { bool shouldRetry = false; qWarning("RecordPlugin::setDevice('%s'): " "opening the device failed. error message='%s'", DBG(device), DBG(result)); m_controller.setInitialized(false); if (m_device_name.length()) { // build a short device name for showing to the user QString short_device_name = m_device_name; if (m_device_name.contains(_("|"))) { // tree syntax: extract card + device short_device_name = m_device_name.section(_("|"), 0, 0); if (m_device_name.section(_("|"), 3, 3).length()) short_device_name += _(", ") + m_device_name.section(_("|"), 3, 3); } bool errIsNumeric = false; int errNumber = result.toInt(&errIsNumeric); if (errIsNumeric) { if (errNumber == ENODEV) { result = i18n( "Maybe your system lacks support for the "\ "corresponding hardware or the hardware is not "\ "connected." ); } else if (errNumber == EBUSY) { result = i18n( "The audio device seems to be occupied by another "\ "application. Retrying..." ); shouldRetry = true; } else { result = i18n( "Some unexpected error happened (%1). "\ "You may try an other recording method or "\ "recording device.", QString::fromLocal8Bit(strerror(errNumber)) ); } } if (result.length()) { if (shouldRetry) { notice(result); } else { m_dialog->showDevicePage(); Kwave::MessageBox::sorry(parentWidget(), result, i18nc("%1 = a device name", "Unable to open the recording device (%1)", short_device_name)); } } } if (shouldRetry) { // retry later... m_retry_timer.start(OPEN_RETRY_TIME); } else { m_device_name = QString(); changeTracks(0); } } else { changeTracks(m_dialog->params().tracks); } if (paramsValid()) { m_controller.setInitialized(true); } else { qDebug("RecordPlugin::setDevice('%s') failed, " "returning to 'UNINITIALIZED'", DBG(device)); m_controller.setInitialized(false); } } //*************************************************************************** void Kwave::RecordPlugin::changeTracks(unsigned int new_tracks) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change // qDebug("RecordPlugin::changeTracks(%u)", new_tracks); if (!m_device || m_device_name.isNull()) { // no device -> dummy/shortcut m_dialog->setSupportedTracks(0, 0); m_dialog->setTracks(0); changeSampleRate(0); return; } // check the supported tracks unsigned int min = 0; unsigned int max = 0; if ((m_device->detectTracks(min, max) < 0) || (max < 1)) min = max = 0; if (min > max) min = max; unsigned int channels = new_tracks; if ((channels < min) || (channels > max)) { // clip to the supported number of tracks if (channels < min) channels = min; if (channels > max) channels = max; qDebug("RecordPlugin::changeTracks(%u) -> clipped to %u", new_tracks, channels); if ((new_tracks && channels) && (new_tracks != channels)) { QString s1; switch (new_tracks) { case 1: s1 = i18n("Mono"); break; case 2: s1 = i18n("Stereo"); break; case 4: s1 = i18n("Quadro"); break; default: s1 = i18n("%1 channels", new_tracks); } QString s2; switch (channels) { case 1: s2 = i18n("Mono"); break; case 2: s2 = i18n("Stereo"); break; case 4: s2 = i18n("Quadro"); break; default: s2 = i18n("%1 channels", channels); } notice(i18n("%1 is not supported, using %2", s1, s2)); } } Q_ASSERT(channels >= min); Q_ASSERT(channels <= max); m_dialog->setSupportedTracks(min, max); // try to activate the new number of tracks int err = m_device->setTracks(channels); if (err < 0) { // revert to the current device setting if failed int t = m_device->tracks(); if (t > 0) { // current device state seems to be valid channels = t; if (channels < min) channels = min; if (channels > max) channels = max; } else { // current device state is invalid channels = 0; } if (new_tracks && (channels > 0)) notice( i18n("Recording with %1 channel(s) failed, "\ "using %2 channel(s)", new_tracks, channels)); } m_dialog->setTracks(channels); // activate the new sample rate changeSampleRate(m_dialog->params().sample_rate); } //*************************************************************************** void Kwave::RecordPlugin::changeSampleRate(double new_rate) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change // qDebug("RecordPlugin::changeSampleRate(%u)", Kwave::toInt(new_rate)); if (!m_device || m_device_name.isNull()) { // no device -> dummy/shortcut m_dialog->setSampleRate(0); changeCompression(Kwave::Compression::INVALID); return; } // check the supported sample rates QList supported_rates = m_device->detectSampleRates(); bool is_supported = false; foreach (const double &r, supported_rates) if (qFuzzyCompare(new_rate, r)) { is_supported = true; break; } double rate = new_rate; if (!is_supported && !supported_rates.isEmpty()) { // find the nearest sample rate double nearest = supported_rates.last(); foreach (double r, supported_rates) { if (fabs(r - rate) <= fabs(nearest - rate)) nearest = r; } rate = nearest; const QString sr1(m_dialog->rate2string(new_rate)); const QString sr2(m_dialog->rate2string(rate)); if ((Kwave::toInt(new_rate) > 0) && (Kwave::toInt(rate) > 0) && (Kwave::toInt(new_rate) != Kwave::toInt(rate))) notice(i18n("%1 Hz is not supported, "\ "using %2 Hz", sr1, sr2)); } m_dialog->setSupportedSampleRates(supported_rates); // try to activate the new sample rate int err = m_device->setSampleRate(rate); if (err < 0) { // revert to the current device setting if failed rate = m_device->sampleRate(); if (rate < 0) rate = 0; const QString sr1(m_dialog->rate2string(new_rate)); const QString sr2(m_dialog->rate2string(rate)); if ((Kwave::toInt(new_rate) > 0) && (Kwave::toInt(rate) > 0) && (Kwave::toInt(new_rate) != Kwave::toInt(rate))) notice(i18n("%1 Hz failed, using %2 Hz", sr1, sr2)); } m_dialog->setSampleRate(rate); // set the compression again changeCompression(m_dialog->params().compression); } //*************************************************************************** void Kwave::RecordPlugin::changeCompression( Kwave::Compression::Type new_compression ) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change // qDebug("RecordPlugin::changeCompression(%d)", new_compression); if (!m_device || m_device_name.isNull()) { // no device -> dummy/shortcut m_dialog->setCompression(-1); changeBitsPerSample(0); return; } // check the supported compressions QList supported_comps = m_device->detectCompressions(); Kwave::Compression::Type compression = new_compression; if (!supported_comps.contains(compression) && (compression != Kwave::Compression::NONE)) { // try to disable the compression (type 0) compression = Kwave::Compression::NONE; if (!supported_comps.isEmpty() && !supported_comps.contains(compression)) { // what now, "None" is not supported // -> take the first supported one compression = supported_comps[0]; } if (compression != new_compression) { const QString c1(Kwave::Compression(new_compression).name()); const QString c2(Kwave::Compression(compression).name()); notice(i18n("Compression '%1' not supported, using '%2'", c1, c2)); } } m_dialog->setSupportedCompressions(supported_comps); // try to activate the new compression int err = m_device->setCompression(compression); if (err < 0) { // revert to the current device setting if failed if (compression != m_device->compression()) { const QString c1(Kwave::Compression(compression).name()); const QString c2(Kwave::Compression(m_device->compression()).name()); notice(i18n("Compression '%1' failed, using '%2'.", c1 ,c2)); } compression = m_device->compression(); } m_dialog->setCompression(compression); // set the resolution in bits per sample again changeBitsPerSample(m_dialog->params().bits_per_sample); } //*************************************************************************** void Kwave::RecordPlugin::changeBitsPerSample(unsigned int new_bits) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change // qDebug("RecordPlugin::changeBitsPerSample(%d)", new_bits); if (!m_device || m_device_name.isNull()) { // no device -> dummy/shortcut m_dialog->setBitsPerSample(0); changeSampleFormat(Kwave::SampleFormat::Unknown); return; } // check the supported resolution in bits per sample QList supported_bits = m_device->supportedBits(); int bits = new_bits; if (!supported_bits.contains(bits) && !supported_bits.isEmpty()) { // find the nearest resolution int nearest = supported_bits.last(); foreach (unsigned int b, supported_bits) { if (qAbs(Kwave::toInt(b) - nearest) <= qAbs(bits - nearest)) nearest = Kwave::toInt(b); } bits = nearest; if ((Kwave::toInt(new_bits) > 0) && (bits > 0)) notice( i18n("%1 bits per sample is not supported, "\ "using %2 bits per sample", Kwave::toInt(new_bits), bits)); } m_dialog->setSupportedBits(supported_bits); // try to activate the resolution int err = m_device->setBitsPerSample(bits); if (err < 0) { // revert to the current device setting if failed bits = m_device->bitsPerSample(); if (bits < 0) bits = 0; if ((new_bits > 0) && (bits > 0)) notice( i18n("%1 bits per sample failed, " "using %2 bits per sample", Kwave::toInt(new_bits), bits)); } m_dialog->setBitsPerSample(bits); // set the sample format again changeSampleFormat(m_dialog->params().sample_format); } //*************************************************************************** void Kwave::RecordPlugin::changeSampleFormat( Kwave::SampleFormat::Format new_format) { Q_ASSERT(m_dialog); if (!m_dialog) return; InhibitRecordGuard _lock(*this); // don't record while settings change if (!m_device || m_device_name.isNull()) { // no device -> dummy/shortcut m_dialog->setSampleFormat(Kwave::SampleFormat::Unknown); return; } // check the supported sample formats QList supported_formats = m_device->detectSampleFormats(); Kwave::SampleFormat::Format format = new_format; if (!supported_formats.contains(format) && !supported_formats.isEmpty()) { // use the device default instead format = m_device->sampleFormat(); // if this was also not supported -> stupid device !? if (!supported_formats.contains(format)) { format = supported_formats.first(); // just take the first one :-o } Kwave::SampleFormat::Map sf; const QString s1 = sf.description(sf.findFromData(new_format), true); const QString s2 = sf.description(sf.findFromData(format), true); if (!(new_format == -1) && !(new_format == format)) { notice(i18n("Sample format '%1' is not supported, "\ "using '%2'", s1, s2)); } } m_dialog->setSupportedSampleFormats(supported_formats); // try to activate the new format int err = m_device->setSampleFormat(format); if (err < 0) { // use the device default instead format = m_device->sampleFormat(); Kwave::SampleFormat::Map sf; const QString s1 = sf.description(sf.findFromData(new_format), true); const QString s2 = sf.description(sf.findFromData(format), true); if (format > 0) notice( i18n("Sample format '%1' failed, using '%2'", s1, s2)); } m_dialog->setSampleFormat(format); } //*************************************************************************** void Kwave::RecordPlugin::buffersChanged() { InhibitRecordGuard _lock(*this); // don't record while settings change // this implicitly activates the new settings } //*************************************************************************** void Kwave::RecordPlugin::enterInhibit() { m_inhibit_count++; if ((m_inhibit_count == 1) && m_thread) { // set hourglass cursor QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // qDebug("RecordPlugin::enterInhibit() - STOPPING"); m_thread->stop(); Q_ASSERT(!m_thread->isRunning()); // de-queue all buffers that are still in the queue while (m_thread->queuedBuffers()) processBuffer(); } } //*************************************************************************** void Kwave::RecordPlugin::leaveInhibit() { Q_ASSERT(m_inhibit_count); Q_ASSERT(m_dialog); if (m_inhibit_count) m_inhibit_count--; while (!m_inhibit_count && paramsValid()) { // qDebug("RecordPlugin::leaveInhibit() - STARTING (" // "%d channels, %d bits)", // m_dialog->params().tracks, // m_dialog->params().bits_per_sample); Q_ASSERT(!m_thread->isRunning()); if (m_thread->isRunning()) break; // set new parameters for the recorder setupRecordThread(); // and let the thread run (again) m_thread->start(); break; } // take back the hourglass cursor if (!m_inhibit_count) QApplication::restoreOverrideCursor(); } //*************************************************************************** bool Kwave::RecordPlugin::paramsValid() { if (!m_thread || !m_device || !m_dialog) return false; // check for a valid/usable record device if (m_device_name.isNull()) return false; if ( (m_device->sampleFormat() != Kwave::SampleFormat::Unsigned) && (m_device->sampleFormat() != Kwave::SampleFormat::Signed) ) return false; if (m_device->bitsPerSample() < 1) return false; if (m_device->endianness() == Kwave::UnknownEndian) return false; // check for valid parameters in the dialog const Kwave::RecordParams ¶ms = m_dialog->params(); if (params.tracks < 1) return false; if ( (params.sample_format != Kwave::SampleFormat::Unsigned) && (params.sample_format != Kwave::SampleFormat::Signed) ) return false; return true; } //*************************************************************************** void Kwave::RecordPlugin::resetRecording(bool &accepted) { InhibitRecordGuard _lock(*this); if (m_writers) m_writers->clear(); emitCommand(_("nomacro:close()")); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); accepted = manager().signalManager().isEmpty(); if (!accepted) return; // the parent context might have changed, maybe we have to // re-parent this plugin instance! migrateToActiveContext(); m_buffers_recorded = 0; m_controller.setEmpty(true); emit sigRecordedSamples(0); } //*************************************************************************** void Kwave::RecordPlugin::setupRecordThread() { Q_ASSERT(m_thread); Q_ASSERT(m_dialog); Q_ASSERT(m_device); if (!paramsValid()) return; // stop the thread if necessary (should never happen) Q_ASSERT(!m_thread->isRunning()); if (m_thread->isRunning()) m_thread->stop(); Q_ASSERT(!m_thread->isRunning()); // delete the previous decoder if (m_decoder) delete m_decoder; m_decoder = Q_NULLPTR; // our own reference to the record parameters const Kwave::RecordParams ¶ms = m_dialog->params(); if (!paramsValid()) return; // create a decoder for the current sample format switch (params.compression) { case Kwave::Compression::NONE: switch (params.sample_format) { case Kwave::SampleFormat::Unsigned: /* FALLTHROUGH */ case Kwave::SampleFormat::Signed: // decoder for all linear formats m_decoder = new(std::nothrow) Kwave::SampleDecoderLinear( m_device->sampleFormat(), m_device->bitsPerSample(), m_device->endianness() ); break; default: notice( i18n("The current sample format is not supported!") ); } break; default: notice( i18n("The current compression type is not supported!") ); return; } Q_ASSERT(m_decoder); if (!m_decoder) { Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory")); return; } // set up the prerecording queues m_prerecording_queue.clear(); if (params.pre_record_enabled) { // prepare a queue for each track const unsigned int prerecording_samples = Kwave::toUint( rint(params.pre_record_time * params.sample_rate)); m_prerecording_queue.resize(params.tracks); for (int i=0; i < m_prerecording_queue.size(); i++) m_prerecording_queue[i].setSize(prerecording_samples); if (m_prerecording_queue.size() != Kwave::toInt(params.tracks)) { m_prerecording_queue.clear(); Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory")); return; } } // set up the recording trigger values m_trigger_value.resize(params.tracks); m_trigger_value.fill(0.0); // set up the record thread m_thread->setRecordDevice(m_device); unsigned int buf_count = params.buffer_count; unsigned int buf_size = params.tracks * m_decoder->rawBytesPerSample() * (1 << params.buffer_size); m_thread->setBuffers(buf_count, buf_size); } //*************************************************************************** void Kwave::RecordPlugin::startRecording() { Q_ASSERT(m_dialog); Q_ASSERT(m_thread); Q_ASSERT(m_device); if (!m_dialog || !m_thread || !m_device) return; InhibitRecordGuard _lock(*this); // don't record while settings change if ((m_state != Kwave::REC_PAUSED) || !m_decoder) { double rate = m_dialog->params().sample_rate; unsigned int tracks = m_dialog->params().tracks; unsigned int bits = m_dialog->params().bits_per_sample; if (!tracks) return; /* * if tracks or sample rate has changed * -> start over with a new signal and new settings */ if ((!m_writers) || (m_writers->tracks() != tracks) || !qFuzzyCompare( Kwave::FileInfo(signalManager().metaData()).rate(), rate)) { // create a new and empty signal emitCommand(QString(_("newsignal(0,%1,%2,%3)")).arg( rate).arg(bits).arg(tracks)); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // the parent context might have changed, maybe we have to // re-parent this plugin instance! migrateToActiveContext(); Kwave::SignalManager &mgr = signalManager(); if (!qFuzzyCompare(mgr.rate(), rate) || (mgr.bits() != bits) || (mgr.tracks() != tracks)) { emitCommand(_("close")); return; } // we do not need undo while recording, this would only waste undo // buffers with modified/inserted data signalManager().disableUndo(); // create a sink for our audio data if (m_writers) delete m_writers; m_writers = new(std::nothrow) Kwave::MultiTrackWriter( signalManager(), Kwave::Append); if ((!m_writers) || (m_writers->tracks() != tracks)) { Kwave::MessageBox::sorry(m_dialog, i18n("Out of memory")); return; } } else { // re-use the current signal and append to it } // initialize the file information Kwave::FileInfo fileInfo(signalManager().metaData()); fileInfo.setRate(rate); fileInfo.setBits(bits); fileInfo.setTracks(tracks); fileInfo.set(Kwave::INF_MIMETYPE, _("audio/vnd.wave")); fileInfo.set(Kwave::INF_SAMPLE_FORMAT, Kwave::SampleFormat(m_dialog->params().sample_format).toInt()); fileInfo.set(Kwave::INF_COMPRESSION, m_dialog->params().compression); // add our Kwave Software tag const KAboutData about_data = KAboutData::applicationData(); QString software = about_data.componentName() + _("-") + about_data.version() + _(" ") + i18n("(built with KDE Frameworks %1)", _(KXMLGUI_VERSION_STRING)); fileInfo.set(Kwave::INF_SOFTWARE, software); // add a date tag, ISO format QDate now(QDate::currentDate()); QString date; date = date.sprintf("%04d-%02d-%02d", now.year(), now.month(), now.day()); fileInfo.set(Kwave::INF_CREATION_DATE, date); signalManager().setFileInfo(fileInfo, false); } // now the recording can be considered to be started m_controller.deviceRecordStarted(); } //*************************************************************************** void Kwave::RecordPlugin::recordStopped(int reason) { qDebug("RecordPlugin::recordStopped(%d)", reason); if (reason >= 0) return; // nothing to do // recording was aborted QString err_msg; switch (reason) { case -ENOBUFS: err_msg = i18n("Buffer overrun. Please increase the "\ "number and/or size of the record buffers."); break; case -EBUSY: err_msg = i18n("The recording device seems to be busy."); break; default: err_msg = i18n("Reading from the recording device failed. "\ "Error number = %1 (%2)", -reason, QString::fromLocal8Bit(strerror(-reason))); } Kwave::MessageBox::error(m_dialog, err_msg); if (m_writers) m_writers->flush(); qDebug("RecordPlugin::recordStopped(): last=%lu", static_cast( (m_writers) ? m_writers->last() : 0)); // flush away all prerecording buffers m_prerecording_queue.clear(); // update the file info if we recorded something // NOTE: this implicitly sets the "modified" flag of the signal if (m_writers && m_writers->last()) { Kwave::FileInfo info(signalManager().metaData()); info.setLength(signalLength()); info.setTracks(m_dialog->params().tracks); signalManager().setFileInfo(info, false); } } //*************************************************************************** void Kwave::RecordPlugin::stateChanged(Kwave::RecordState state) { m_state = state; switch (m_state) { case Kwave::REC_UNINITIALIZED: case Kwave::REC_EMPTY: case Kwave::REC_PAUSED: case Kwave::REC_DONE: // reset buffer status if (m_writers) { m_writers->flush(); delete m_writers; m_writers = Q_NULLPTR; } m_buffers_recorded = 0; m_dialog->updateBufferState(0, 0); break; default: ; } } //*************************************************************************** void Kwave::RecordPlugin::updateBufferProgressBar() { Q_ASSERT(m_dialog); Q_ASSERT(m_thread); if (!m_dialog || !m_thread) return; unsigned int buffers_total = m_dialog->params().buffer_count; // if we are still recording: update the progress bar if ((m_state != Kwave::REC_EMPTY) && (m_state != Kwave::REC_PAUSED) && (m_state != Kwave::REC_DONE)) { // count up the number of recorded buffers m_buffers_recorded++; if (m_buffers_recorded <= buffers_total) { // buffers are just in progress of getting filled m_dialog->updateBufferState(m_buffers_recorded, buffers_total); } else { // we have remaining+1 buffers (one is currently filled) unsigned int remaining = m_thread->remainingBuffers() + 1; if (remaining > buffers_total) remaining = buffers_total; m_dialog->updateBufferState(remaining, buffers_total); } } else { // no longer recording: count the buffer downwards unsigned int queued = m_thread->queuedBuffers(); if (!queued) buffers_total = 0; m_dialog->updateBufferState(queued, buffers_total); } } //*************************************************************************** void Kwave::RecordPlugin::split(QByteArray &raw_data, QByteArray &dest, unsigned int bytes_per_sample, unsigned int track, unsigned int tracks) { unsigned int samples = raw_data.size() / bytes_per_sample / tracks; #if 0 // simple sawtooth generator, based on raw data // works for up to 16 channels static int saw[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; raw_data.fill(0x05); for (unsigned int s = 0; s < samples; s++) { int v = saw[track]; for (unsigned int byte = 0; byte < bytes_per_sample; byte++) { quint8 x = (quint8)v; raw_data[(((s * tracks) + track) * bytes_per_sample) + byte] = x; v >>= 8; } const int max = (1 << ((bytes_per_sample * 8) - 1)) - 1; saw[track] += max / 64; if (saw[track] >= max) saw[track] = 0; } #endif if (tracks == 1) { // this would give a 1:1 memcpy dest = raw_data; } else { switch (bytes_per_sample) { case 1: { // 1...8 bits per sample, use 8 bit pointers const quint8 *src = reinterpret_cast(raw_data.constData()); quint8 *dst = reinterpret_cast(dest.data()); src += track; while (samples) { *dst = *src; dst++; src += tracks; samples--; } break; } case 2: { // 9...16 bits per sample, use 16 bit pointers const quint16 *src = reinterpret_cast(raw_data.constData()); quint16 *dst = reinterpret_cast(dest.data()); src += track; while (samples) { *dst = *src; dst++; src += tracks; samples--; } break; } case 3: { // 17...24 bits per sample, use 8 bit pointers, three times const quint8 *src = reinterpret_cast(raw_data.constData()); quint8 *dst = reinterpret_cast(dest.data()); src += track * 3; while (samples) { *(dst++) = *(src++); *(dst++) = *(src++); *(dst++) = *(src++); src += (tracks - 1) * 3; samples--; } break; } case 4: { // 24...32 bits per sample, use 32 bit pointers const quint32 *src = reinterpret_cast(raw_data.constData()); quint32 *dst = reinterpret_cast(dest.data()); src += track; while (samples) { *dst = *src; dst++; src += tracks; samples--; } break; } case 8: { // 64 bits per sample, use 64 bit pointers const quint64 *src = reinterpret_cast(raw_data.constData()); quint64 *dst = reinterpret_cast(dest.data()); src += track; while (samples) { *dst = *src; dst++; src += tracks; samples--; } break; } default: { // default: byte wise operation const quint8 *src = reinterpret_cast(raw_data.constData()); quint8 *dst = reinterpret_cast(dest.data()); src += (track * bytes_per_sample); unsigned int increment = (tracks - 1) * bytes_per_sample; while (samples) { for (unsigned int b = 0; b < bytes_per_sample; b++) { *dst = *src; dst++; src++; samples--; } src += increment; } } } } } //*************************************************************************** bool Kwave::RecordPlugin::checkTrigger(unsigned int track, const Kwave::SampleArray &buffer) { Q_ASSERT(m_dialog); if (!m_dialog) return false; // check if the recording start time has been reached if (m_dialog->params().start_time_enabled) { if (QDateTime::currentDateTime() < m_dialog->params().start_time) return false; } // shortcut if no trigger has been set if (!m_dialog->params().record_trigger_enabled) return true; // check the input parameters if (!buffer.size()) return false; if (!m_writers) return false; if (m_trigger_value.size() != Kwave::toInt(m_writers->tracks())) return false; // pass the buffer through a rectifier and a lowpass with // center frequency about 2Hz to get the amplitude float trigger = static_cast( m_dialog->params().record_trigger / 100.0); const float rate = static_cast( m_dialog->params().sample_rate); /* * simple lowpass calculation: * * 1 + z * H(z) = a0 * ----------- | z = e ^ (j*2*pi*f) * z + b1 * * 1 1 - n * a0 = ----- b1 = -------- * 1 + n 1 + n * * Fg = fg / fa * * n = cot(Pi * Fg) * * y[t] = a0 * x[t] + a1 * x[t-1] - b1 * y[t-1] * */ // rise coefficient: ~20Hz const float f_rise = 20.0f; float Fg = f_rise / rate; float n = 1.0f / tanf(float(M_PI) * Fg); const float a0_r = 1.0f / (1.0f + n); const float b1_r = (1.0f - n) / (1.0f + n); // fall coefficient: ~1.0Hz const float f_fall = 1.0f; Fg = f_fall / rate; n = 1.0f / tanf(float(M_PI) * Fg); const float a0_f = 1.0f / (1.0f + n); const float b1_f = (1.0f - n) / (1.0f + n); float y = m_trigger_value[track]; float last_x = y; for (unsigned int t = 0; t < buffer.size(); ++t) { float x = fabsf(sample2float(buffer[t])); /* rectifier */ if (x > y) { /* diode */ // rise if amplitude is above average (serial R) y = (a0_r * x) + (a0_r * last_x) - (b1_r * y); } // fall (parallel R) y = (a0_f * x) + (a0_f * last_x) - (b1_f * y); // remember x[t-1] last_x = x; // nice for debugging: // buffer[t] = (int)((double)(1 << (SAMPLE_BITS-1)) * y); if (y > trigger) return true; } m_trigger_value[track] = y; qDebug(">> level=%5.3g, trigger=%5.3g", y, trigger); return false; } //*************************************************************************** void Kwave::RecordPlugin::enqueuePrerecording(unsigned int track, const Kwave::SampleArray &decoded) { Q_ASSERT(m_dialog); Q_ASSERT(Kwave::toInt(track) < m_prerecording_queue.size()); if (!m_dialog) return; if (Kwave::toInt(track) >= m_prerecording_queue.size()) return; // append the array with decoded sample to the prerecording buffer m_prerecording_queue[track].put(decoded); } //*************************************************************************** void Kwave::RecordPlugin::flushPrerecordingQueue() { if (!m_prerecording_queue.size()) return; Q_ASSERT(m_dialog); Q_ASSERT(m_thread); Q_ASSERT(m_decoder); if (!m_dialog || !m_thread || !m_decoder) return; const Kwave::RecordParams ¶ms = m_dialog->params(); const unsigned int tracks = params.tracks; Q_ASSERT(tracks); if (!tracks) return; Q_ASSERT(m_writers); if (!m_writers) return; Q_ASSERT(tracks == m_writers->tracks()); if (!tracks || (tracks != m_writers->tracks())) return; for (unsigned int track=0; track < tracks; ++track) { Kwave::SampleFIFO &fifo = m_prerecording_queue[track]; Q_ASSERT(fifo.length()); if (!fifo.length()) continue; fifo.crop(); // enforce the correct size // push all buffers to the writer, starting at the tail Kwave::Writer *writer = (*m_writers)[track]; Q_ASSERT(writer); if (writer) { Kwave::SampleArray buffer(writer->blockSize()); unsigned int rest = fifo.length(); while (rest) { unsigned int read = fifo.get(buffer); if (read < 1) break; writer->write(buffer, read); rest -= read; } } else { // fallback: discard the FIFO content fifo.flush(); } Q_ASSERT(fifo.length() == 0); } // the queues are no longer needed m_prerecording_queue.clear(); // we have transferred data to the writers, we are no longer empty m_controller.setEmpty(false); } //*************************************************************************** void Kwave::RecordPlugin::processBuffer() { bool recording_done = false; // de-queue the buffer from the thread if (!m_thread) return; if (!m_thread->queuedBuffers()) return; QByteArray buffer = m_thread->dequeue(); // abort here if we have no dialog or no decoder if (!m_dialog || !m_decoder) return; // we received a buffer -> update the progress bar updateBufferProgressBar(); const Kwave::RecordParams ¶ms = m_dialog->params(); const unsigned int tracks = params.tracks; Q_ASSERT(tracks); if (!tracks) return; const unsigned int bytes_per_sample = m_decoder->rawBytesPerSample(); Q_ASSERT(bytes_per_sample); if (!bytes_per_sample) return; unsigned int samples = (buffer.size() / bytes_per_sample) / tracks; Q_ASSERT(samples); if (!samples) return; // check for reached recording time limit if enabled if (params.record_time_limited && m_writers) { const sample_index_t last = m_writers->last(); const sample_index_t already_recorded = (last) ? (last + 1) : 0; const sample_index_t limit = static_cast(rint( params.record_time * params.sample_rate)); if (already_recorded + samples >= limit) { // reached end of recording time, we are full if (m_state == Kwave::REC_RECORDING) { samples = Kwave::toUint( (limit > already_recorded) ? (limit - already_recorded) : 0); buffer.resize(samples * tracks * bytes_per_sample); } recording_done = true; } } QByteArray buf; buf.resize(bytes_per_sample * samples); Q_ASSERT(buf.size() == Kwave::toInt(bytes_per_sample * samples)); if (buf.size() != Kwave::toInt(bytes_per_sample * samples)) return; Kwave::SampleArray decoded(samples); Q_ASSERT(decoded.size() == samples); if (decoded.size() != samples) return; // check for trigger // note: this might change the state, which affects the // processing of all tracks ! if ((m_state == Kwave::REC_WAITING_FOR_TRIGGER) || ((m_state == Kwave::REC_PRERECORDING) && params.record_trigger_enabled) || ((m_state == Kwave::REC_PRERECORDING) && params.start_time_enabled)) { for (unsigned int track=0; track < tracks; ++track) { // split off and decode buffer with current track split(buffer, buf, bytes_per_sample, track, tracks); m_decoder->decode(buf, decoded); if (checkTrigger(track, decoded)) { m_controller.deviceTriggerReached(); break; } } } if ((m_state == Kwave::REC_RECORDING) && !m_prerecording_queue.isEmpty()) { // flush all prerecorded buffers to the output flushPrerecordingQueue(); } // use a copy of the state, in case it changes below ;-) Kwave::RecordState state = m_state; for (unsigned int track = 0; track < tracks; ++track) { // decode and care for all special effects, meters and so on // split off and decode buffer with current track split(buffer, buf, bytes_per_sample, track, tracks); m_decoder->decode(buf, decoded); // update the level meter and other effects m_dialog->updateEffects(track, decoded); // if the first buffer is full -> leave REC_BUFFERING // limit state transitions to a point before the first track is // processed (avoid asymmetry) if ((track == 0) && (m_state == Kwave::REC_BUFFERING) && (m_buffers_recorded > 1)) { m_controller.deviceBufferFull(); state = m_state; // might have changed! } switch (state) { case Kwave::REC_UNINITIALIZED: case Kwave::REC_EMPTY: case Kwave::REC_PAUSED: case Kwave::REC_DONE: case Kwave::REC_BUFFERING: case Kwave::REC_WAITING_FOR_TRIGGER: // already handled before or nothing to do... break; case Kwave::REC_PRERECORDING: // enqueue the buffers into a FIFO enqueuePrerecording(track, decoded); break; case Kwave::REC_RECORDING: { // put the decoded track data into the buffer if (!m_writers) break; // (could happen due to queued signal) Q_ASSERT(tracks == m_writers->tracks()); if (!tracks || (tracks != m_writers->tracks())) break; Kwave::Writer *writer = (*m_writers)[track]; Q_ASSERT(writer); if (writer) (*writer) << decoded; m_controller.setEmpty(false); break; } } } // update the number of recorded samples if (m_writers) emit sigRecordedSamples(m_writers->last() + 1); // if this was the last received buffer, change state if (recording_done && (m_state != Kwave::REC_DONE) && (m_state != Kwave::REC_EMPTY)) { m_controller.actionStop(); } } //*************************************************************************** void Kwave::RecordPlugin::prerecordingChanged(bool enable) { Q_UNUSED(enable); InhibitRecordGuard _lock(*this); // activate the change } //*************************************************************************** #include "RecordPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/record/RecordPlugin.h b/plugins/record/RecordPlugin.h index 8c9981d0..e6e697ae 100644 --- a/plugins/record/RecordPlugin.h +++ b/plugins/record/RecordPlugin.h @@ -1,290 +1,291 @@ /************************************************************************* RecordPlugin.h - plugin for recording audio data ------------------- begin : Wed Jul 09 2003 copyright : (C) 2003 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef RECORD_PLUGIN_H #define RECORD_PLUGIN_H #include "config.h" #include #include +#include #include #include #include #include #include "libkwave/MultiTrackWriter.h" #include "libkwave/Plugin.h" #include "libkwave/Sample.h" #include "libkwave/SampleArray.h" #include "libkwave/SampleFIFO.h" #include "libkwave/SampleFormat.h" #include "RecordController.h" #include "RecordParams.h" #include "RecordState.h" class QStringList; namespace Kwave { class RecordDevice; class RecordDialog; class RecordThread; class SampleDecoder; class RecordPlugin: public Kwave::Plugin { Q_OBJECT public: /** * Constructor * @param parent reference to our plugin manager * @param args argument list [unused] */ RecordPlugin(QObject *parent, const QVariantList &args); /** Destructor */ virtual ~RecordPlugin() Q_DECL_OVERRIDE; /** @see Kwave::Plugin::setup() */ virtual QStringList *setup(QStringList &previous_params) Q_DECL_OVERRIDE; signals: /** emitted to promote the recording progress to the dialog */ void sigRecordedSamples(sample_index_t samples_recorded); protected: /** * internal guard class for inhibiting low level recording * at times where it should not occur. */ class InhibitRecordGuard { public: /** Constructor, inhibits recording */ explicit InhibitRecordGuard(Kwave::RecordPlugin &recorder) :m_recorder(recorder) { m_recorder.enterInhibit(); } /** Destructor, re-enables recording */ virtual ~InhibitRecordGuard() { m_recorder.leaveInhibit(); } private: Kwave::RecordPlugin &m_recorder; }; protected: friend class InhibitRecordGuard; /** inhibits recording, stopping the recorder if necessary */ void enterInhibit(); /** leave the area with recording inhibited, restart recorder if needed */ void leaveInhibit(); protected slots: /** * command for resetting all recorded stuff for starting again * @param accepted bool variable that will show if the action was * performed or aborted (not accepted) */ void resetRecording(bool &accepted); /** * command for starting the recording, completion is * signaled with sigStarted() */ void startRecording(); /** called when the recording stopped (for detecting aborts only) */ void recordStopped(int reason); /** * called when the recording engine has changed it's state */ void stateChanged(Kwave::RecordState state); private slots: // setup functions /** * Change the recording method * @param method the new recording method */ void setMethod(Kwave::record_method_t method); /** select a new record device */ void setDevice(const QString &device); /** select a new number of tracks (channels) */ void changeTracks(unsigned int new_tracks); /** select a new sample rate [samples/second] */ void changeSampleRate(double new_rate); /** change compression type */ void changeCompression(Kwave::Compression::Type new_compression); /** select a new resolution [bits/sample] */ void changeBitsPerSample(unsigned int new_bits); /** select a new sample format */ void changeSampleFormat(Kwave::SampleFormat::Format new_format); /** process a raw audio buffer */ void processBuffer(); /** restart recorder with new buffer settings */ void buffersChanged(); /** the prerecording checkbox has changed */ void prerecordingChanged(bool enable); /** try to open the record device, in case it was busy before */ void retryOpen(); private: /** close m_device and delete it */ void closeDevice(); /** * show a short notice which disappears automatically, * e.g. if something is not supported and has been substituted * @param message the notice that should pop up */ void notice(QString message); /** set up the recorder thread and record device (again) */ void setupRecordThread(); /** update the buffer progress bar */ void updateBufferProgressBar(); /** * check if the trigger level has been reached * @param track index of the track that is checked * @param buffer array with Kwave sample data * @return true if trigger reached or no trigger set */ bool checkTrigger(unsigned int track, const Kwave::SampleArray &buffer); /** * Split off one track from a raw buffer with multiple tracks into * a separate buffer * @param raw_data the raw buffer with multiple tracks * @param dest byte array that receives the data of the specified track * @param bytes_per_sample number of bytes for each sample * @param track index of the track to split off [1...n-1] * @param tracks number of total tracks */ void split(QByteArray &raw_data, QByteArray &dest, unsigned int bytes_per_sample, unsigned int track, unsigned int tracks); /** * Enqueue a buffer with decoded samples into a prerecording * buffer of the corresponding track. * * @param track index of the track [0...tracks-1] * @param decoded array with decoded samples, in Kwave's internal format */ void enqueuePrerecording(unsigned int track, const Kwave::SampleArray &decoded); /** * Flush the content of the prerecording queue to the output * @see m_writers */ void flushPrerecordingQueue(); /** * Returns true if all parameters are valid and the recording * (thread) could be started. */ bool paramsValid(); private: /** last recording method */ Kwave::record_method_t m_method; /** last record device */ QString m_device_name; /** controller for the recording engine */ Kwave::RecordController m_controller; /** global state of the plugin */ Kwave::RecordState m_state; /** device used for recording */ Kwave::RecordDevice *m_device; /** setup dialog */ - Kwave::RecordDialog *m_dialog; + QPointer m_dialog; /** the thread for recording */ Kwave::RecordThread *m_thread; /** decoder for converting raw data to samples */ Kwave::SampleDecoder *m_decoder; /** * set of queues for buffering prerecording data, one for each * track */ QVector m_prerecording_queue; /** sink for the audio data */ Kwave::MultiTrackWriter *m_writers; /** * number of recorded buffers since start or continue or the number of * buffers in the queue if recording stopped */ unsigned int m_buffers_recorded; /** recursion level for inhibiting recording */ unsigned int m_inhibit_count; /** buffer for trigger values */ QVector m_trigger_value; /** timer for retrying "open" */ QTimer m_retry_timer; }; } #endif /* RECORD_PLUGIN_H */ //*************************************************************************** //*************************************************************************** diff --git a/plugins/saveblocks/SaveBlocksPlugin.cpp b/plugins/saveblocks/SaveBlocksPlugin.cpp index 94d6b583..5c5c7d66 100644 --- a/plugins/saveblocks/SaveBlocksPlugin.cpp +++ b/plugins/saveblocks/SaveBlocksPlugin.cpp @@ -1,745 +1,746 @@ /*************************************************************************** SaveBlocksPlugin.cpp - Plugin for saving blocks between labels ------------------- begin : Thu Mar 01 2007 copyright : (C) 2007 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include +#include #include #include #include #include "libkwave/CodecManager.h" #include "libkwave/Encoder.h" #include "libkwave/FileInfo.h" #include "libkwave/Label.h" #include "libkwave/LabelList.h" #include "libkwave/MessageBox.h" #include "libkwave/MetaDataList.h" #include "libkwave/Parser.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "SaveBlocksDialog.h" #include "SaveBlocksPlugin.h" KWAVE_PLUGIN(saveblocks, SaveBlocksPlugin) //*************************************************************************** Kwave::SaveBlocksPlugin::SaveBlocksPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_url(), m_pattern(), m_numbering_mode(CONTINUE), m_selection_only(true), m_block_info() { } //*************************************************************************** Kwave::SaveBlocksPlugin::~SaveBlocksPlugin() { } //*************************************************************************** QStringList *Kwave::SaveBlocksPlugin::setup(QStringList &previous_params) { // try to interpret the previous parameters interpreteParameters(previous_params); // create the setup dialog sample_index_t selection_left = 0; sample_index_t selection_right = 0; selection(Q_NULLPTR, &selection_left, &selection_right, false); // enable the "selection only" checkbox only if there is something // selected but not everything bool selected_something = (selection_left != selection_right); bool selected_all = ((selection_left == 0) && (selection_right + 1 >= signalLength())); bool enable_selection_only = selected_something && !selected_all; QString filename = m_url.path(); QString base = findBase(filename, m_pattern); scanBlocksToSave(base, m_selection_only && enable_selection_only); QPointer dialog = new(std::nothrow) Kwave::SaveBlocksDialog( _("kfiledialog:///kwave_save_blocks"), Kwave::CodecManager::encodingFilter(), parentWidget(), QUrl::fromUserInput(signalName()), _("*.wav"), m_pattern, m_numbering_mode, m_selection_only, enable_selection_only ); if (!dialog) return Q_NULLPTR; // connect the signals/slots from the plugin and the dialog connect(dialog, SIGNAL(sigSelectionChanged(QString, QString,Kwave::SaveBlocksPlugin::numbering_mode_t,bool)), this, SLOT(updateExample(QString,QString, Kwave::SaveBlocksPlugin::numbering_mode_t,bool))); connect(this, SIGNAL(sigNewExample(QString)), dialog, SLOT(setNewExample(QString))); dialog->setWindowTitle(description()); dialog->emitUpdate(); - if (dialog->exec() != QDialog::Accepted) { + if ((dialog->exec() != QDialog::Accepted) || !dialog) { delete dialog; return Q_NULLPTR; } QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); if (list) { // user has pressed "OK" QString pattern; QUrl url = dialog->selectedUrl(); if (url.isEmpty()) { delete dialog; delete list; return Q_NULLPTR; } QString name = url.path(); QFileInfo path(name); // add the correct extension if necessary if (!path.suffix().length()) { QString ext = dialog->selectedExtension(); QStringList extensions = ext.split(_(" ")); ext = extensions.first(); name += ext.mid(1); path = name; url.setPath(name); } name = Kwave::Parser::escape(name); pattern = Kwave::Parser::escape(dialog->pattern()); int mode = static_cast(dialog->numberingMode()); bool selection_only = (enable_selection_only) ? dialog->selectionOnly() : m_selection_only; *list << name; *list << pattern; *list << QString::number(mode); *list << QString::number(selection_only); emitCommand(_("plugin:execute(saveblocks,") + name + _(",") + pattern + _(",") + QString::number(mode) + _(",") + QString::number(selection_only) + _(")") ); } if (dialog) delete dialog; return list; } //*************************************************************************** QString Kwave::SaveBlocksPlugin::createDisplayList( const QStringList &list, unsigned int max_entries) const { if (!max_entries || list.isEmpty()) return QString(); QString retval; unsigned int count = 0; foreach (const QString &entry, list) { if (count == 0) // first entry retval = _("

"); if (count < max_entries) retval += entry + _("
"); else if (count == max_entries) retval += i18n("...") + _("
"); if (++count > max_entries) break; } return retval; } //*************************************************************************** int Kwave::SaveBlocksPlugin::start(QStringList ¶ms) { qDebug("SaveBlocksPlugin::start()"); // interpret the parameters int result = interpreteParameters(params); if (result) return result; QString filename = m_url.path(); QFileInfo file(filename); QString path = file.absolutePath(); QString ext = file.suffix(); QString base = findBase(filename, m_pattern); QByteArray sep("/"); // determine the selection settings sample_index_t selection_left = 0; sample_index_t selection_right = 0; selection(Q_NULLPTR, &selection_left, &selection_right, false); bool selected_something = (selection_left != selection_right); bool selected_all = ( (selection_left == 0) && ((selection_right + 1) >= signalLength()) ); bool enable_selection_only = selected_something && !selected_all; bool selection_only = enable_selection_only && m_selection_only; if (!selection_only) { selection_left = 0; selection_right = signalLength() - 1; } // get the index range scanBlocksToSave(base, selection_only); unsigned int count = m_block_info.count(); unsigned int first = firstIndex(path, base, ext, m_pattern, m_numbering_mode, count); // qDebug("m_url = '%s'", m_url.prettyURL().local8Bit().data()); // qDebug("m_pattern = '%s'", m_pattern.local8Bit().data()); // qDebug("m_numbering_mode = %d", (int)m_numbering_mode); // qDebug("selection_only = %d", selection_only); // qDebug("indices = %u...%u (count=%u)", first, first+count-1,count); // remember the original file info and determine the list of unsupported // properties, we need that later to avoid that the signal manager // complains on saving each and every block, again and again... const Kwave::FileInfo orig_file_info(signalManager().metaData()); Kwave::FileInfo file_info(orig_file_info); QList unsupported_properties; { QString mimetype = Kwave::CodecManager::mimeTypeOf(m_url); Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype); if (encoder) { unsupported_properties = encoder->unsupportedProperties( file_info.properties().keys()); delete encoder; } } // iterate over all blocks to check for overwritten files and missing dirs QStringList overwritten_files; QStringList missing_dirs; for (unsigned int i = first; i < (first + count); i++) { QString name = createFileName(base, ext, m_pattern, i, count, first + count - 1); QString display_name = Kwave::Parser::unescape(name); // split the name into directory and file name name = QString::fromLatin1(QUrl::toPercentEncoding(display_name, sep)); QUrl url = m_url.adjusted(QUrl::RemoveFilename); url.setPath(url.path(QUrl::FullyEncoded) + name, QUrl::StrictMode); QString p = url.path(); QFileInfo fi(p); // check for potentially overwritten file if (fi.exists()) overwritten_files += Kwave::Parser::unescape(display_name); // check for missing subdirectory if (!fi.dir().exists()) { QString missing_dir = fi.absolutePath(); if (!missing_dirs.contains(missing_dir)) missing_dirs += missing_dir; } } // inform about overwritten files if (!overwritten_files.isEmpty()) { // ask the user for confirmation if he really wants to overwrite... if (Kwave::MessageBox::warningYesNo(parentWidget(), _("") + i18n("This would overwrite the following file(s): %1" \ "Do you really want to continue?", createDisplayList(overwritten_files, 5)) + _("") ) != KMessageBox::Yes) { return -1; } } // handle missing directories if (!missing_dirs.isEmpty()) { // ask the user if he wants to continue and create the directory if (Kwave::MessageBox::warningContinueCancel(parentWidget(), i18n("The following directories do not exist: %1" "Do you want to create them and continue?", createDisplayList(missing_dirs, 5)), QString(), QString(), QString(), _("saveblocks_create_missing_dirs") ) != KMessageBox::Continue) { return -1; } // create all missing directories QUrl base_url = m_url.adjusted(QUrl::RemoveFilename); foreach (const QString &missing, missing_dirs) { QUrl url(base_url); url.setPath( base_url.path(QUrl::FullyEncoded) + QString::fromLatin1(QUrl::toPercentEncoding(missing)), QUrl::StrictMode ); QString p = url.path(); QDir dir; if (!dir.mkpath(p)) qWarning("creating path '%s' failed", DBG(p)); } } // save the current selection, we have to restore it afterwards! sample_index_t saved_selection_left = 0; sample_index_t saved_selection_right = 0; selection(Q_NULLPTR, &saved_selection_left, &saved_selection_right, false); // now we can loop over all blocks and save them sample_index_t block_start; sample_index_t block_end = 0; Kwave::LabelList labels(signalManager().metaData()); Kwave::LabelListIterator it(labels); Kwave::Label label = it.hasNext() ? it.next() : Kwave::Label(); for (unsigned int index = first;;) { block_start = block_end; block_end = (label.isNull()) ? signalLength() : label.pos(); if ((selection_left < block_end) && (selection_right > block_start)) { // found a block to save... Q_ASSERT(index < first + count); sample_index_t left = block_start; sample_index_t right = block_end - 1; if (left < selection_left) left = selection_left; if (right > selection_right) right = selection_right; Q_ASSERT(right > left); if (right <= left) break; // zero-length ? // select the range of samples selectRange(left, right - left + 1); // determine the filename QString name = createFileName(base, ext, m_pattern, index, count, first + count - 1); name = Kwave::Parser::unescape(name); // use URL encoding for the filename name = QString::fromLatin1(QUrl::toPercentEncoding(name, sep)); QUrl url = m_url.adjusted(QUrl::RemoveFilename); url.setPath(url.path(QUrl::FullyEncoded) + name, QUrl::StrictMode); // enter the title of the block into the meta data if supported if (!unsupported_properties.contains(INF_NAME)) { QString title = orig_file_info.get(INF_NAME).toString(); int idx = index - first; if ((idx >= 0) && (idx < m_block_info.count())) { QString block_title = m_block_info[idx].m_title; if (block_title.length()) title = title + _(", ") + block_title; } file_info.set(INF_NAME, QVariant(title)); signalManager().metaData().replace( Kwave::MetaDataList(file_info)); } qDebug("saving %9lu...%9lu -> '%s'", static_cast(left), static_cast(right), DBG(url.toDisplayString())); if (signalManager().save(url, true) < 0) break; // if there were unsupported properties, the user might have been // asked whether it is ok to continue or not. If he answered with // "Cancel", we do not reach this point, otherwise we can continue // and prevent any further annoying questions by removing all // unsupported file info before the next run... if ((index == first) && !unsupported_properties.isEmpty()) { foreach (const Kwave::FileProperty &p, unsupported_properties) { file_info.set(p, QVariant()); } signalManager().metaData().replace( Kwave::MetaDataList(file_info)); } // increment the index for the next filename index++; } if (label.isNull()) break; label = (it.hasNext()) ? it.next() : Kwave::Label(); } // restore the original file info signalManager().metaData().replace( Kwave::MetaDataList(orig_file_info)); // restore the previous selection selectRange(saved_selection_left, (saved_selection_left != saved_selection_right) ? (saved_selection_right - saved_selection_left + 1) : 0); return result; } //*************************************************************************** int Kwave::SaveBlocksPlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; // evaluate the parameter list if (params.count() != 4) { return -EINVAL; } // the selected URL m_url = QUrl::fromUserInput(Kwave::Parser::unescape(params[0])); if (!m_url.isValid()) return -EINVAL; // filename pattern m_pattern = Kwave::Parser::unescape(params[1]); if (!m_pattern.length()) return -EINVAL; // numbering mode param = params[2]; int mode = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; if ((mode != CONTINUE) && (mode != START_AT_ONE)) return -EINVAL; m_numbering_mode = static_cast(mode); // flag: save only the selection param = params[3]; m_selection_only = (param.toUInt(&ok) != 0); Q_ASSERT(ok); if (!ok) return -EINVAL; return 0; } //*************************************************************************** void Kwave::SaveBlocksPlugin::scanBlocksToSave(const QString &base, bool selection_only) { sample_index_t selection_left, selection_right; sample_index_t block_start; sample_index_t block_end = 0; QString block_title; Kwave::LabelList labels(signalManager().metaData()); Kwave::LabelListIterator it(labels); Kwave::Label label = (it.hasNext()) ? it.next() : Kwave::Label(); if (selection_only) { selection(Q_NULLPTR, &selection_left, &selection_right, true); } else { selection_left = 0; selection_right = signalLength() - 1; } // get the title of the whole file, in case that a block does not have // an own title FileInfo info(signalManager().metaData()); QString file_title = info.get(INF_NAME).toString(); // fallback: if there is no INF_NAME either, fall back to the file // name as last resort if (!file_title.length()) file_title = base; m_block_info.clear(); QString prev_title; for (;;) { block_start = block_end; block_end = (label.isNull()) ? signalLength() : label.pos(); block_title = prev_title; prev_title = (label.isNull()) ? file_title : label.name(); if ((block_end > selection_left) && (block_start <= selection_right)) { BlockInfo block; block.m_start = block_start; block.m_length = block_end - block_start; block.m_title = block_title; if (!block.m_title.length()) block.m_title = file_title; m_block_info.append(block); } if (label.isNull()) break; label = (it.hasNext()) ? it.next() : Kwave::Label(); } } //*************************************************************************** QString Kwave::SaveBlocksPlugin::createFileName(const QString &base, const QString &ext, const QString &pattern, unsigned int index, int count, int total) { QString p = QRegExp::escape(pattern); QString nr; // format the "index" parameter QRegExp rx_nr(_("(\\\\\\[%(\\d*)nr\\\\\\])"), Qt::CaseInsensitive); while (rx_nr.indexIn(p) >= 0) { QString format = rx_nr.cap(1); format = format.mid(2, format.length() - 6); QString ex = _("(\\\\\\[") + format + _("nr\\\\\\])"); QRegExp rx(ex, Qt::CaseInsensitive); format += _("u"); p.replace(rx, nr.sprintf(format.toLatin1(), index)); } // format the "count" parameter QRegExp rx_count(_("(\\\\\\[%\\d*count\\\\\\])"), Qt::CaseInsensitive); while (rx_count.indexIn(p) >= 0) { if (count >= 0) { QString format = rx_count.cap(1); format = format.mid(2, format.length() - 9); QString ex = _("(\\\\\\[") + format + _("count\\\\\\])"); QRegExp rx(ex, Qt::CaseInsensitive); format += _("u"); p.replace(rx, nr.sprintf(format.toLatin1(), count)); } else { p.replace(rx_count, _("(\\d+)")); } } // format the "total" parameter QRegExp rx_total(_("(\\\\\\[%\\d*total\\\\\\])"), Qt::CaseInsensitive); while (rx_total.indexIn(p) >= 0) { if (total >= 0) { QString format = rx_total.cap(1); format = format.mid(2, format.length() - 9); QString ex = _("(\\\\\\[") + format + _("total\\\\\\])"); QRegExp rx(ex, Qt::CaseInsensitive); format += _("u"); p.replace(rx, nr.sprintf(format.toLatin1(), total)); } else { p.replace(rx_total, _("(\\d+)")); } } // format the "filename" parameter QRegExp rx_filename(_("\\\\\\[%filename\\\\\\]"), Qt::CaseInsensitive); if (rx_filename.indexIn(p) >= 0) { p.replace(rx_filename, QRegExp::escape(base)); } // support for file info QRegExp rx_fileinfo( _("\\\\\\[%(\\d*)fileinfo\\\\\\{([\\w\\s]+)\\\\\\}\\\\\\]"), Qt::CaseInsensitive ); Kwave::FileInfo info(signalManager().metaData()); while (rx_fileinfo.indexIn(p) >= 0) { const QString format = rx_fileinfo.cap(1); const QString id = rx_fileinfo.cap(2); QString value; FileProperty property = info.fromName(id); if (property != Kwave::INF_UNKNOWN) { QVariant val = info.get(property); if (!val.isNull()) { // we have a property value value = val.toString(); // check for format (desired minimum string length) bool ok = false; int len = format.toUInt(&ok); if ((len > 0) && ok) { Kwave::FileInfo::Flags flags = info.flags(property); if (flags & Kwave::FileInfo::FP_FORMAT_NUMERIC) { // numeric format, pad with leading zeros or spaces QString pad = (format.startsWith(QLatin1Char('0'))) ? _("0") : _(" "); while (value.length() < len) value = pad + value; } else { // string format, pad with trailing spaces while (value.length() < len) value = value + _(" "); } } value = Kwave::Parser::escapeForFileName(value); } } QString ex(_("(\\\\\\[%") + format + _("fileinfo\\\\\\{") + id + _("\\\\\\}\\\\\\])")); QRegExp rx(ex, Qt::CaseInsensitive); p.replace(rx, value); } // format the "title" parameter QRegExp rx_title(_("\\\\\\[%title\\\\\\]"), Qt::CaseInsensitive); if (rx_title.indexIn(p) >= 0) { QString title; int idx = (index - 1) - (total - count); if ((idx >= 0) && (idx < m_block_info.count())) title = m_block_info[idx].m_title; if (title.length()) { title = Kwave::Parser::escapeForFileName(title); p.replace(rx_title, title); } } if (ext.length()) p += _(".") + ext; // sanitize the filename/path, make sure that there are no spaces // before and after all path separators QString sep = _("/"); QRegExp rx_sep(_("\\s*") + sep + _("\\s*")); p.replace(rx_sep, sep); return p; } //*************************************************************************** unsigned int Kwave::SaveBlocksPlugin::firstIndex(const QString &path, const QString &base, const QString &ext, const QString &pattern, Kwave::SaveBlocksPlugin::numbering_mode_t mode, unsigned int count) { unsigned int first = 1; switch (mode) { case START_AT_ONE: first = 1; break; case CONTINUE: { QDir dir(path, _("*")); QStringList files; files = dir.entryList(); for (unsigned int i = first; i < (first + count); i++) { QString name = createFileName(base, ext, pattern, i, -1, -1); QRegExp rx(_("^(") + name + _(")$"), Qt::CaseInsensitive); QStringList matches = files.filter(rx); if (matches.count() > 0) first = i + 1; } break; } } return first; } //*************************************************************************** QString Kwave::SaveBlocksPlugin::findBase(const QString &filename, const QString &pattern) { QFileInfo file(filename); QString name = file.fileName(); QString base = file.completeBaseName(); QString ext = file.suffix(); // convert the pattern into a regular expression in order to check if // the current name already is produced by the current pattern // \[%[0-9]?nr\] -> \d+ // \[%[0-9]?count\] -> \d+ // \[%[0-9]?total\] -> \d+ // \[%filename\] -> base // \[%fileinfo\] -> . // \[%title\] -> . QRegExp rx_nr(_("\\\\\\[%\\d*nr\\\\\\]"), Qt::CaseInsensitive); QRegExp rx_count(_("\\\\\\[%\\d*count\\\\\\]"), Qt::CaseInsensitive); QRegExp rx_total(_("\\\\\\[%\\d*total\\\\\\]"), Qt::CaseInsensitive); QRegExp rx_filename(_("\\\\\\[%filename\\\\\\]"), Qt::CaseInsensitive); QRegExp rx_fileinfo(_("\\\\\\[%fileinfo\\\\\\]"), Qt::CaseInsensitive); QRegExp rx_title(_("\\\\\\[%title\\\\\\]"), Qt::CaseInsensitive); QString p = QRegExp::escape(pattern); int idx_nr = rx_nr.indexIn(p); int idx_count = rx_count.indexIn(p); int idx_total = rx_total.indexIn(p); int idx_filename = rx_filename.indexIn(p); int idx_fileinfo = rx_fileinfo.indexIn(p); int idx_title = rx_fileinfo.indexIn(p); p.replace(rx_nr, _("(\\d+)")); p.replace(rx_count, _("(\\d+)")); p.replace(rx_total, _("(\\d+)")); p.replace(rx_filename, _("(.+)")); p.replace(rx_fileinfo, _("(.+)")); p.replace(rx_title, _("(.+)")); int max = 0; for (int i = 0; i < pattern.length(); i++) { if (idx_nr == max) max++; if (idx_count == max) max++; if (idx_total == max) max++; if (idx_filename == max) max++; if (idx_fileinfo == max) max++; if (idx_title == max) max++; if (idx_nr > max) idx_nr--; if (idx_count > max) idx_count--; if (idx_total > max) idx_total--; if (idx_filename > max) idx_filename--; if (idx_fileinfo > max) idx_fileinfo--; if (idx_title > max) idx_title--; } if (ext.length()) p += _(".") + ext; QRegExp rx_current(p, Qt::CaseInsensitive); if (rx_current.indexIn(name) >= 0) { // filename already produced by this pattern base = rx_current.cap(idx_filename + 1); } return base; } //*************************************************************************** QString Kwave::SaveBlocksPlugin::firstFileName(const QString &filename, const QString &pattern, Kwave::SaveBlocksPlugin::numbering_mode_t mode, bool selection_only) { QFileInfo file(filename); QString path = file.absolutePath(); QString ext = file.suffix(); QString base = findBase(filename, pattern); // now we have a new name, base and extension // -> find out the numbering, min/max etc... scanBlocksToSave(base, selection_only); unsigned int count = m_block_info.count(); unsigned int first = firstIndex(path, base, ext, pattern, mode, count); unsigned int total = first + count - 1; // create the complete filename, including extension but without path return createFileName(base, ext, pattern, first, count, total); } //*************************************************************************** void Kwave::SaveBlocksPlugin::updateExample(const QString &filename, const QString &pattern, Kwave::SaveBlocksPlugin::numbering_mode_t mode, bool selection_only) { QString example = firstFileName(filename, pattern, mode, selection_only); emit sigNewExample(Kwave::Parser::unescape(example)); } //*************************************************************************** #include "SaveBlocksPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/selectrange/SelectRangePlugin.cpp b/plugins/selectrange/SelectRangePlugin.cpp index 425fd623..e9b2de08 100644 --- a/plugins/selectrange/SelectRangePlugin.cpp +++ b/plugins/selectrange/SelectRangePlugin.cpp @@ -1,193 +1,193 @@ /*************************************************************************** SelectRangePlugin.cpp - Plugin for selecting a range of samples ------------------- begin : Sat Jun 15 2002 copyright : (C) 2002 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/String.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "SelectRangeDialog.h" #include "SelectRangePlugin.h" KWAVE_PLUGIN(selectrange, SelectRangePlugin) //*************************************************************************** Kwave::SelectRangePlugin::SelectRangePlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_start_mode(Kwave::SelectTimeWidget::bySamples), m_range_mode(Kwave::SelectTimeWidget::bySamples), m_start(0), m_range(0) { } //*************************************************************************** Kwave::SelectRangePlugin::~SelectRangePlugin() { } //*************************************************************************** QStringList *Kwave::SelectRangePlugin::setup(QStringList &previous_params) { // try to interpret the previous parameters interpreteParameters(previous_params); // create the setup dialog double rate = signalRate(); sample_index_t offset = manager().selectionStart(); sample_index_t length = signalLength(); - Kwave::SelectRangeDialog *dialog = + QPointer dialog = new(std::nothrow) Kwave::SelectRangeDialog(parentWidget(), m_start_mode, m_range_mode, m_range, rate, offset, length); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" *list << QString::number(dialog->startMode()); *list << QString::number(dialog->rangeMode()); *list << QString::number(dialog->start()); *list << QString::number(dialog->range()); emitCommand(_("plugin:execute(selectrange,") + QString::number(dialog->startMode()) + _(",") + QString::number(dialog->rangeMode()) + _(",") + QString::number(dialog->start()) + _(",") + QString::number(dialog->range())+ _(")") ); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** int Kwave::SelectRangePlugin::start(QStringList ¶ms) { // interprete the parameters int result = interpreteParameters(params); if (result) return result; const sample_index_t signal_length = signalLength(); // get current offset of the signal sample_index_t offset = Kwave::SelectTimeWidget::timeToSamples( m_start_mode, m_start, signalRate(), signal_length); // transform into offset and length [samples] sample_index_t length = Kwave::SelectTimeWidget::timeToSamples( m_range_mode, m_range, signalRate(), signal_length); // limit selection to end of signal if (length > signal_length) length = signal_length; if ((offset + length) >= signal_length) length = signal_length - offset; // change the selection through the signal manager { Kwave::UndoTransactionGuard undo_guard(*this, i18n("Select Range")); selectRange(offset, length); } return result; } //*************************************************************************** int Kwave::SelectRangePlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; int mode; // evaluate the parameter list if (params.count() != 4) { return -EINVAL; } // selection mode for start param = params[0]; mode = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; Q_ASSERT( (mode == static_cast(Kwave::SelectTimeWidget::byTime)) || (mode == static_cast(Kwave::SelectTimeWidget::bySamples)) || (mode == static_cast(Kwave::SelectTimeWidget::byPercents)) ); if ((mode != static_cast(Kwave::SelectTimeWidget::byTime)) && (mode != static_cast(Kwave::SelectTimeWidget::bySamples)) && (mode != static_cast(Kwave::SelectTimeWidget::byPercents))) { return -EINVAL; } m_start_mode = static_cast(mode); // selection mode param = params[1]; mode = param.toInt(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; Q_ASSERT( (mode == static_cast(Kwave::SelectTimeWidget::byTime)) || (mode == static_cast(Kwave::SelectTimeWidget::bySamples)) || (mode == static_cast(Kwave::SelectTimeWidget::byPercents)) ); if ((mode != static_cast(Kwave::SelectTimeWidget::byTime)) && (mode != static_cast(Kwave::SelectTimeWidget::bySamples)) && (mode != static_cast(Kwave::SelectTimeWidget::byPercents))) { return -EINVAL; } m_range_mode = static_cast(mode); // offset in ms, samples or percent param = params[2]; m_start = param.toUInt(&ok); if (!ok) return -EINVAL; // range in ms, samples or percent param = params[3]; m_range = param.toUInt(&ok); if (!ok) return -EINVAL; return 0; } //*************************************************************************** #include "SelectRangePlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/sonagram/SonagramPlugin.cpp b/plugins/sonagram/SonagramPlugin.cpp index bc13a434..04618e6f 100644 --- a/plugins/sonagram/SonagramPlugin.cpp +++ b/plugins/sonagram/SonagramPlugin.cpp @@ -1,624 +1,626 @@ /*************************************************************************** SonagramPlugin.cpp - plugin that shows a sonagram window ------------------- begin : Fri Jul 28 2000 copyright : (C) 2000 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include "libkwave/GlobalLock.h" #include "libkwave/MessageBox.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/Plugin.h" #include "libkwave/PluginManager.h" #include "libkwave/Sample.h" #include "libkwave/SampleReader.h" #include "libkwave/SignalManager.h" #include "libkwave/Track.h" #include "libkwave/Utils.h" #include "libkwave/WindowFunction.h" #include "libgui/OverViewCache.h" #include "libgui/SelectionTracker.h" #include "SonagramDialog.h" #include "SonagramPlugin.h" #include "SonagramWindow.h" KWAVE_PLUGIN(sonagram, SonagramPlugin) /** * interval for limiting the number of repaints per second [ms] */ #define REPAINT_INTERVAL 500 //*************************************************************************** Kwave::SonagramPlugin::SonagramPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_sonagram_window(Q_NULLPTR), m_selection(Q_NULLPTR), m_slices(0), m_fft_points(0), m_window_type(Kwave::WINDOW_FUNC_NONE), m_color(true), m_track_changes(true), m_follow_selection(false), m_image(), m_overview_cache(Q_NULLPTR), m_slice_pool(), m_valid(MAX_SLICES, false), m_pending_jobs(), m_lock_job_list(QMutex::Recursive), m_future(), m_repaint_timer() { i18n("Sonagram"); // connect the output ouf the sonagram worker thread connect(this, SIGNAL(sliceAvailable(Kwave::SonagramPlugin::Slice*)), this, SLOT(insertSlice(Kwave::SonagramPlugin::Slice*)), Qt::QueuedConnection); // connect repaint timer connect(&m_repaint_timer, SIGNAL(timeout()), this, SLOT(validate())); } //*************************************************************************** Kwave::SonagramPlugin::~SonagramPlugin() { m_repaint_timer.stop(); if (m_sonagram_window) delete m_sonagram_window; m_sonagram_window = Q_NULLPTR; if (m_selection) delete m_selection; m_selection = Q_NULLPTR; } //*************************************************************************** QStringList *Kwave::SonagramPlugin::setup(QStringList &previous_params) { QStringList *result = Q_NULLPTR; // try to interprete the list of previous parameters, ignore errors if (previous_params.count()) interpreteParameters(previous_params); - Kwave::SonagramDialog *dlg = new(std::nothrow) Kwave::SonagramDialog(*this); + QPointer dlg = + new(std::nothrow) Kwave::SonagramDialog(*this); Q_ASSERT(dlg); if (!dlg) return Q_NULLPTR; dlg->setWindowFunction(m_window_type); dlg->setColorMode(m_color ? 1 : 0); dlg->setTrackChanges(m_track_changes); dlg->setFollowSelection(m_follow_selection); - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { result = new(std::nothrow) QStringList(); Q_ASSERT(result); if (result) dlg->parameters(*result); }; delete dlg; return result; } //*************************************************************************** int Kwave::SonagramPlugin::interpreteParameters(QStringList ¶ms) { bool ok; 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); if (!ok) return -EINVAL; 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; } //*************************************************************************** int Kwave::SonagramPlugin::start(QStringList ¶ms) { // clean up leftovers from last run if (m_sonagram_window) delete m_sonagram_window; m_sonagram_window = Q_NULLPTR; if (m_selection) delete m_selection; m_selection = Q_NULLPTR; if (m_overview_cache) delete m_overview_cache; m_overview_cache = Q_NULLPTR; Kwave::SignalManager &sig_mgr = signalManager(); // interpret parameter list and abort if it contains invalid data int result = interpreteParameters(params); if (result) return result; // create an empty sonagram window m_sonagram_window = new(std::nothrow) Kwave::SonagramWindow(parentWidget(), signalName()); Q_ASSERT(m_sonagram_window); if (!m_sonagram_window) return -ENOMEM; // if the signal closes, close the sonagram window too QObject::connect(&manager(), SIGNAL(sigClosed()), m_sonagram_window, SLOT(close())); // get the current selection QList selected_channels; sample_index_t offset = 0; sample_index_t length = 0; length = selection(&selected_channels, &offset, Q_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(length) / static_cast(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) >= std::numeric_limits::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(QUuid)), this, SLOT(slotTrackInserted(QUuid))); connect(m_selection, SIGNAL(sigTrackDeleted(QUuid)), this, SLOT(slotTrackDeleted(QUuid))); connect( m_selection, SIGNAL(sigInvalidated(const QUuid*,sample_index_t,sample_index_t)), this, SLOT(slotInvalidated(const QUuid*,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 = Q_NULLPTR; } // connect all needed signals connect(m_sonagram_window, SIGNAL(destroyed()), this, SLOT(windowDestroyed())); // activate the window with an initial image // and all necessary information m_sonagram_window->setColorMode((m_color) ? 1 : 0); m_sonagram_window->setImage(m_image); m_sonagram_window->setPoints(m_fft_points); m_sonagram_window->setRate(signalRate()); m_sonagram_window->show(); if (m_track_changes) { QObject::connect(static_cast(&(manager())), SIGNAL(sigSignalNameChanged(QString)), m_sonagram_window, SLOT(setName(QString))); } // increment the usage counter and release the plugin when the // sonagram window closed use(); return 0; } //*************************************************************************** void Kwave::SonagramPlugin::makeAllValid() { unsigned int fft_points; unsigned int slices; Kwave::window_function_t window_type; sample_index_t first_sample; sample_index_t last_sample; QBitArray valid; QList track_list; { QMutexLocker _lock(&m_lock_job_list); if (!m_selection) return; if (!m_selection->length() || (m_fft_points < 4)) return; fft_points = m_fft_points; slices = m_slices; window_type = m_window_type; first_sample = m_selection->first(); last_sample = m_selection->last(); valid = m_valid; m_valid.fill(true); const QList selected_tracks(m_selection->allTracks()); foreach (unsigned int track, signalManager().allTracks()) if (selected_tracks.contains(signalManager().uuidOfTrack(track))) track_list.append(track); } const unsigned int tracks = track_list.count(); Kwave::WindowFunction func(window_type); const QVector windowfunction = func.points(fft_points); Q_ASSERT(windowfunction.count() == Kwave::toInt(fft_points)); if (windowfunction.count() != Kwave::toInt(fft_points)) return; Kwave::MultiTrackReader source(Kwave::SinglePassForward, signalManager(), track_list, first_sample, last_sample); // qDebug("SonagramPlugin[%p]::makeAllValid() [%llu .. %llu]", // static_cast(this), first_sample, last_sample); QFutureSynchronizer synchronizer; for (unsigned int slice_nr = 0; slice_nr < slices; slice_nr++) { // qDebug("SonagramPlugin::run(): calculating slice %d of %d", // slice_nr, m_slices); if (valid[slice_nr]) continue; // determine start of the stripe sample_index_t pos = first_sample + (slice_nr * fft_points); // get a new slice from the pool and initialize it Kwave::SonagramPlugin::Slice *slice = m_slice_pool.allocate(); Q_ASSERT(slice); slice->m_index = slice_nr; memset(slice->m_input, 0x00, sizeof(slice->m_input)); memset(slice->m_output, 0x00, sizeof(slice->m_output)); if ((pos <= last_sample) && (tracks)) { // initialize result with zeroes memset(slice->m_result, 0x00, sizeof(slice->m_result)); // seek to the start of the slice source.seek(pos); // we have a new slice, now fill it's input buffer double *in = slice->m_input; for (unsigned int j = 0; j < fft_points; j++) { double value = 0.0; if (!(source.eof())) { for (unsigned int t = 0; t < tracks; t++) { sample_t s = 0; Kwave::SampleReader *reader = source[t]; Q_ASSERT(reader); if (reader) *reader >> s; value += sample2double(s); } value /= tracks; } in[j] = value * windowfunction[j]; } // a background job is running soon // (for counterpart, see insertSlice(...) below [main thread]) m_pending_jobs.lockForRead(); // run the FFT in a background thread synchronizer.addFuture(QtConcurrent::run( this, &Kwave::SonagramPlugin::calculateSlice, slice) ); } else { // range has been deleted -> fill with "empty" memset(slice->m_result, 0xFF, sizeof(slice->m_result)); m_pending_jobs.lockForRead(); emit sliceAvailable(slice); } if (shouldStop()) break; } // qDebug("SonagramPlugin::makeAllValid(): waiting for background jobs..."); // wait for all worker threads synchronizer.waitForFinished(); // wait for queued signals m_pending_jobs.lockForWrite(); m_pending_jobs.unlock(); // qDebug("SonagramPlugin::makeAllValid(): done."); } //*************************************************************************** void Kwave::SonagramPlugin::run(QStringList params) { qDebug("SonagramPlugin::run()"); Q_UNUSED(params); { // invalidate all slices QMutexLocker _lock(&m_lock_job_list); m_valid.fill(false); } makeAllValid(); } //*************************************************************************** void Kwave::SonagramPlugin::calculateSlice(Kwave::SonagramPlugin::Slice *slice) { fftw_plan p; // prepare for a 1-dimensional real-to-complex DFT { Kwave::GlobalLock _lock; // libfftw is not threadsafe! p = fftw_plan_dft_r2c_1d( m_fft_points, &(slice->m_input[0]), &(slice->m_output[0]), FFTW_ESTIMATE ); } Q_ASSERT(p); if (!p) return; // calculate the fft (according to the specs, this is the one and only // libfft function that is threadsafe!) fftw_execute(p); // norm all values to [0...254] and use them as pixel value const double scale = static_cast(m_fft_points) / 254.0; for (unsigned int j = 0; j < m_fft_points / 2; j++) { // get singal energy and scale to [0 .. 254] double rea = slice->m_output[j][0]; double ima = slice->m_output[j][1]; double a = ((rea * rea) + (ima * ima)) / scale; slice->m_result[j] = static_cast(qMin(a, double(254.0))); } // free the allocated FFT resources { Kwave::GlobalLock _lock; // libfftw is not threadsafe! fftw_destroy_plan(p); } // emit the slice data to be synchronously inserted into // the current image in the context of the main thread // (Qt does the queuing for us) emit sliceAvailable(slice); } //*************************************************************************** void Kwave::SonagramPlugin::insertSlice(Kwave::SonagramPlugin::Slice *slice) { // check: this must be called from the GUI thread only! Q_ASSERT(this->thread() == QThread::currentThread()); Q_ASSERT(this->thread() == qApp->thread()); Q_ASSERT(slice); if (!slice) return; QByteArray result; result.setRawData(reinterpret_cast(&(slice->m_result[0])), m_fft_points / 2); unsigned int nr = slice->m_index; // forward the slice to the window to display it if (m_sonagram_window) m_sonagram_window->insertSlice(nr, result); // return the slice into the pool m_slice_pool.release(slice); // job is done m_pending_jobs.unlock(); } //*************************************************************************** void Kwave::SonagramPlugin::createNewImage(const unsigned int width, const unsigned int height) { // delete the previous image m_image = QImage(); if (m_sonagram_window) m_sonagram_window->setImage(m_image); // do not create a new image if one dimension is zero! Q_ASSERT(width); Q_ASSERT(height); if (!width || !height) return; // also do not create if the image size is out of range Q_ASSERT(width <= 32767); Q_ASSERT(height <= 32767); if ((width >= 32767) || (height >= 32767)) return; // create the new image object m_image = QImage(width, height, QImage::Format_Indexed8); Q_ASSERT(!m_image.isNull()); if (m_image.isNull()) return; // initialize the image's palette with transparecy m_image.setColorCount(256); for (int i = 0; i < 256; i++) { m_image.setColor(i, 0x00000000); } // fill the image with "empty" (transparent) m_image.fill(0xFF); } //*************************************************************************** void Kwave::SonagramPlugin::refreshOverview() { if (!m_overview_cache || !m_sonagram_window) return; QColor fg = m_sonagram_window->palette().light().color(); QColor bg = m_sonagram_window->palette().mid().color(); QImage overview = m_overview_cache->getOverView( m_sonagram_window->width(), SONAGRAM_OVERVIEW_HEIGHT, fg, bg); m_sonagram_window->setOverView(overview); } //*************************************************************************** void Kwave::SonagramPlugin::requestValidation() { // only re-start the repaint timer, this hides some GUI update artifacts if (!m_repaint_timer.isActive()) { m_repaint_timer.stop(); m_repaint_timer.setSingleShot(true); m_repaint_timer.start(REPAINT_INTERVAL); } } //*************************************************************************** void Kwave::SonagramPlugin::validate() { // wait for previously running jobs to finish if (m_future.isRunning()) { requestValidation(); return; // job is still running, come back later... } // queue a background thread for updates m_future = QtConcurrent::run(this, &Kwave::SonagramPlugin::makeAllValid); } //*************************************************************************** void Kwave::SonagramPlugin::slotTrackInserted(const QUuid &track_id) { QMutexLocker _lock(&m_lock_job_list); Q_UNUSED(track_id); // check for "track changes" mode if (!m_track_changes) return; // invalidate complete signal m_valid.fill(false, m_slices); requestValidation(); } //*************************************************************************** void Kwave::SonagramPlugin::slotTrackDeleted(const QUuid &track_id) { QMutexLocker _lock(&m_lock_job_list); Q_UNUSED(track_id); // check for "track changes" mode if (!m_track_changes) return; // invalidate complete signal m_valid.fill(false, m_slices); requestValidation(); } //*************************************************************************** void Kwave::SonagramPlugin::slotInvalidated(const QUuid *track_id, sample_index_t first, sample_index_t last) { QMutexLocker lock(&m_lock_job_list); Q_UNUSED(track_id); // qDebug("SonagramPlugin[%p]::slotInvalidated(%s, %llu, %llu)", // static_cast(this), // (track_id) ? DBG(track_id->toString()) : "*", first, last); // check for "track changes" mode if (!m_track_changes) return; // adjust offsets, absolute -> relative sample_index_t offset = (m_selection) ? m_selection->offset() : 0; Q_ASSERT(first >= offset); Q_ASSERT(last >= offset); Q_ASSERT(last >= first); first -= offset; last -= offset; unsigned int first_idx = Kwave::toUint(first / m_fft_points); unsigned int last_idx; if (last >= (SAMPLE_INDEX_MAX - (m_fft_points - 1))) last_idx = m_slices - 1; else last_idx = Kwave::toUint(qMin(Kwave::round_up(last, static_cast(m_fft_points)) / m_fft_points, static_cast(m_slices - 1)) ); m_valid.fill(false, first_idx, last_idx + 1); requestValidation(); } //*************************************************************************** void Kwave::SonagramPlugin::windowDestroyed() { cancel(); m_sonagram_window = Q_NULLPTR; // closes itself ! if (m_selection) delete m_selection; m_selection = Q_NULLPTR; if (m_overview_cache) delete m_overview_cache; m_overview_cache = Q_NULLPTR; release(); } //*************************************************************************** #include "SonagramPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/sonagram/SonagramWindow.cpp b/plugins/sonagram/SonagramWindow.cpp index 840f346b..2f41ba1d 100644 --- a/plugins/sonagram/SonagramWindow.cpp +++ b/plugins/sonagram/SonagramWindow.cpp @@ -1,575 +1,575 @@ /*************************************************************************** SonagramWindow.cpp - window for showing a sonagram ------------------- begin : Fri Jul 28 2000 copyright : (C) 2000 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "libkwave/String.h" #include "libkwave/Utils.h" #include "libkwave/WindowFunction.h" #include "libgui/FileDialog.h" #include "libgui/ImageView.h" #include "libgui/ScaleWidget.h" #include "SonagramWindow.h" /** * delay between two screen updates [ms] */ #define REFRESH_DELAY 100 /** * Color values below this limit are cut off when adjusting the * sonagram image's brightness * I found out by experiments that 0.1% seems to be reasonable */ #define COLOR_CUTOFF_RATIO (0.1/100.0) static const char *background[] = { /* width height num_colors chars_per_pixel */ " 20 20 2 1", /* colors */ "# c #808080", ". c None", /* pixels */ "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "##########..........", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########", "..........##########" }; //**************************************************************************** Kwave::SonagramWindow::SonagramWindow(QWidget *parent, const QString &name) :KMainWindow(parent), m_status_time(Q_NULLPTR), m_status_freq(Q_NULLPTR), m_status_ampl(Q_NULLPTR), m_image(), m_color_mode(0), m_view(Q_NULLPTR), m_overview(Q_NULLPTR), m_points(0), m_rate(0), m_xscale(Q_NULLPTR), m_yscale(Q_NULLPTR), m_refresh_timer() { for (unsigned int i = 0; i < 256; ++i) { m_histogram[i] = 0; } QWidget *mainwidget = new(std::nothrow) QWidget(this); Q_ASSERT(mainwidget); if (!mainwidget) return; setCentralWidget(mainwidget); QGridLayout *top_layout = new(std::nothrow) QGridLayout(mainwidget); Q_ASSERT(top_layout); if (!top_layout) return; QMenuBar *bar = menuBar(); Q_ASSERT(bar); if (!bar) return ; // QMenu *spectral = new QMenu(); // Q_ASSERT(spectral); // if (!spectral) return ; QMenu *file = bar->addMenu(i18n("&Sonagram")); 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-export")), i18n("&Export to Bitmap..."), this, SLOT(save()) ); file->addAction( QIcon::fromTheme(_("dialog-close")), i18n("&Close"), this, SLOT(close()), QKeySequence::Close ); // spectral->addAction (i18n("&Retransform to Signal"), this, SLOT(toSignal())); QStatusBar *status = statusBar(); Q_ASSERT(status); if (!status) return ; m_status_time = new(std::nothrow) QLabel(i18n("Time: ------ ms"), status); m_status_freq = new(std::nothrow) QLabel(i18n("Frequency: ------ Hz"), status); m_status_ampl = new(std::nothrow) QLabel(i18n("Amplitude: --- %"), status); status->addPermanentWidget(m_status_time); status->addPermanentWidget(m_status_freq); status->addPermanentWidget(m_status_ampl); m_view = new(std::nothrow) Kwave::ImageView(mainwidget); Q_ASSERT(m_view); if (!m_view) return; top_layout->addWidget(m_view, 0, 1); QPalette palette; palette.setBrush(m_view->backgroundRole(), QBrush(QImage(background))); m_view->setAutoFillBackground(true); m_view->setPalette(palette); m_xscale = new(std::nothrow) Kwave::ScaleWidget(mainwidget, 0, 100, i18n("ms")); Q_ASSERT(m_xscale); if (!m_xscale) return; m_xscale->setFixedHeight(m_xscale->sizeHint().height()); top_layout->addWidget(m_xscale, 1, 1); m_yscale = new(std::nothrow) Kwave::ScaleWidget(mainwidget, 0, 100, i18n("Hz")); Q_ASSERT(m_yscale); if (!m_yscale) return ; m_yscale->setFixedWidth(m_yscale->sizeHint().width()); m_yscale->setMinimumHeight(9*6*5); top_layout->addWidget(m_yscale, 0, 0); m_overview = new(std::nothrow) Kwave::ImageView(mainwidget); Q_ASSERT(m_overview); if (!m_overview) return; m_overview->setFixedHeight(SONAGRAM_OVERVIEW_HEIGHT); top_layout->addWidget(m_overview, 2, 1); connect(m_view, SIGNAL(sigCursorPos(QPoint)), this, SLOT(cursorPosChanged(QPoint))); 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); top_layout->setColumnStretch(0, 0); top_layout->setColumnStretch(1, 100); top_layout->activate(); if (m_status_time) m_status_time->setText(i18n("Time: 0 ms")); if (m_status_freq) m_status_freq->setText(i18n("Frequency: 0 Hz")); if (m_status_ampl) m_status_ampl->setText(i18n("Amplitude: 0 %")); // try to make 5:3 format (looks best) int w = sizeHint().width(); int h = sizeHint().height(); if ((w * 3 / 5) < h) w = (h * 5) / 3; if ((h * 5 / 3) < w) h = (w * 3) / 5; resize(w, h); show(); } //**************************************************************************** void Kwave::SonagramWindow::close() { QWidget::close(); } //**************************************************************************** void Kwave::SonagramWindow::save() { if (m_image.isNull()) return; QPointer dlg = new(std::nothrow) Kwave::FileDialog( _("kfiledialog:///kwave_sonagram"), Kwave::FileDialog::SaveFile, QString(), this, QUrl(), _("*.bmp") ); if (!dlg) return; dlg->setWindowTitle(i18n("Save Sonagram")); - if (dlg->exec() == QDialog::Accepted) { + if ((dlg->exec() == QDialog::Accepted) && dlg) { QString filename = dlg->selectedUrl().toLocalFile(); if (!filename.isEmpty()) m_image.save(filename, "BMP"); } delete dlg; } //**************************************************************************** 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); // } // } } //**************************************************************************** void Kwave::SonagramWindow::setImage(QImage image) { Q_ASSERT(m_view); if (!m_view) return; m_image = image; // re-initialize histogram over all pixels for (unsigned int i = 0; i < 256; i++) m_histogram[i] = 0; if (!m_image.isNull()) { for (int x = 0; x < m_image.width(); x++) { for (int y = 0; y < m_image.height(); y++) { quint8 p = static_cast(m_image.pixelIndex(x, y)); m_histogram[p]++; } } } refresh_view(); } //**************************************************************************** void Kwave::SonagramWindow::setOverView(const QImage &overview) { if (m_overview) m_overview->setImage(overview); } //**************************************************************************** void Kwave::SonagramWindow::insertSlice(const unsigned int slice_nr, const QByteArray &slice) { Q_ASSERT(m_view); if (!m_view) return; if (m_image.isNull()) return; unsigned int image_width = m_image.width(); unsigned int image_height = m_image.height(); // slice is out of range ? if (slice_nr >= image_width) return; unsigned int y; unsigned int size = slice.size(); for (y = 0; y < size; y++) { quint8 p; // remove the current pixel from the histogram p = static_cast(m_image.pixelIndex(slice_nr, y)); m_histogram[p]--; // set the new pixel value p = slice[(size - 1) - y]; m_image.setPixel(slice_nr, y, p); // insert the new pixel into the histogram m_histogram[p]++; } while (y < image_height) { // fill the rest with blank m_image.setPixel(slice_nr, y++, 0xFE); m_histogram[0xFE]++; } if (!m_refresh_timer.isActive()) { m_refresh_timer.setSingleShot(true); m_refresh_timer.start(REFRESH_DELAY); } } //**************************************************************************** void Kwave::SonagramWindow::adjustBrightness() { if (m_image.isNull()) return; // get the sum of pixels != 0 unsigned long int sum = 0; for (unsigned int i = 1; i <= 254; i++) sum += m_histogram[i]; // cut off all parts below the cutoff ratio (e.g. 0.1%) unsigned int cutoff = Kwave::toUint(sum * COLOR_CUTOFF_RATIO); // get the last used color from the histogram int last = 254; while ((last >= 0) && (m_histogram[last] <= cutoff)) last--; QColor c; for (int i = 0; i < 255; i++) { int v; if (i >= last) { v = 254; } else { // map [0...last] to [254...0] v = ((last - i) * 254) / last; } if (m_color_mode == 1) { // rainbow effect c.setHsv( (v * 255) / 255, 255, 255, 255); } else { // greyscale palette c.setRgb(v, v, v, 255); } m_image.setColor(i, c.rgba()); // qDebug("color[%3d] = 0x%08X",i, c.rgba()); } // use color 0xFF for transparency ! m_image.setColor(0xFF, QColor(0, 0, 0, 0).rgba()); } //**************************************************************************** void Kwave::SonagramWindow::refresh_view() { Q_ASSERT(m_view); if (!m_view) return; adjustBrightness(); m_view->setImage(m_image); } //**************************************************************************** void Kwave::SonagramWindow::toSignal() { /** @todo needs to be ported to fftw and re-activated */ // gsl_fft_complex_wavetable table; // // gsl_fft_complex_wavetable_alloc (points, &table); // gsl_fft_complex_init (points, &table); // // Kwave::TopWidget *win = new Kwave::TopWidget(...); // // Q_ASSERT(win); // if (win) { // // Kwave::Signal *newsig = new Kwave::Signal(length, rate); // Q_ASSERT(newsig); // // //assure 10 Hz for correction signal, this should not be audible // int slopesize = rate / 10; // // double *slope = new double [slopesize]; // // if (slope && newsig) { // for (int i = 0; i < slopesize; i++) // slope[i] = 0.5 + 0.5 * cos( ((double) i) * M_PI / slopesize); // // win->show(); // // int *output = newsig->getSample(); //sample data // complex *tmp = new complex [points]; //this window holds the data for ifft and after that part of the signal // // if (output && tmp && data) { // for (int i = 0; i < x; i++) { // if (data[i]) memcpy (tmp, data[i], sizeof(complex)*points); // gsl_fft_complex_inverse (tmp, points, &table); // // for (int j = 0; j < points; j++) // output[i*points + j] = (int)(tmp[j].real * ((1 << 23)-1)); // } // int dif ; // int max; // for (int i = 1; i < x; i++) //remove gaps between windows // { // max = slopesize; // if (max > length - i*points) max = length - i * points; // dif = output[i * points] - output[i * points - 1]; // if (dif < 2) // for (int j = 0; j < max; j++) output[i*points + j] += (int) (slope[j] * dif ); // } // // win->setSignal (new SignalManager (newsig)); // // if (tmp) delete[] tmp; // } else { // if (newsig) delete newsig; // if (win) delete win; // KMsgBox::message (this, i18n("Error"), i18n("Out of memory !"), 2); // } // } // if (slope) delete[] slope; // } } //*************************************************************************** void Kwave::SonagramWindow::translatePixels2TF(const QPoint p, double *ms, double *f) { if (ms) { // get the time coordinate [0...(N_samples-1)* (1/f_sample) ] if (!qFuzzyIsNull(m_rate)) { *ms = static_cast(p.x()) * static_cast(m_points) * 1000.0 / m_rate; } else { *ms = 0; } } if (f) { // get the frequency coordinate 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); } } //*************************************************************************** void Kwave::SonagramWindow::updateScaleWidgets() { double ms; double f; translatePixels2TF(QPoint(m_image.width() - 1, 0), &ms, &f); m_xscale->setMinMax(0, Kwave::toInt(rint(ms))); m_yscale->setMinMax(0, Kwave::toInt(rint(f))); } //*************************************************************************** Kwave::SonagramWindow::~SonagramWindow() { } //*************************************************************************** void Kwave::SonagramWindow::setColorMode(int mode) { Q_ASSERT(mode >= 0); Q_ASSERT(mode <= 1); if (mode != m_color_mode) { m_color_mode = mode; setImage(m_image); } } //*************************************************************************** void Kwave::SonagramWindow::setName(const QString &name) { setWindowTitle((name.length()) ? i18n("Sonagram of %1", name) : i18n("Sonagram") ); } //**************************************************************************** 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; if (qFuzzyIsNull(m_rate)) return; double ms; double f; double a; translatePixels2TF(pos, &ms, &f); // item 1: time in milliseconds if (m_status_time) m_status_time->setText(i18n("Time: %1", Kwave::ms2string(ms))); // item 2: frequency in Hz if (m_status_freq) m_status_freq->setText(i18n("Frequency: %1 Hz", Kwave::toInt(f))); // item 3: amplitude in % if (m_image.valid(pos.x(), pos.y())) { a = m_image.pixelIndex(pos.x(), pos.y()) * (100.0 / 254.0); } else { a = 0.0; } if (m_status_ampl) m_status_ampl->setText(i18n("Amplitude: %1%", Kwave::toInt(a))); } //**************************************************************************** void Kwave::SonagramWindow::setPoints(unsigned int points) { m_points = points; updateScaleWidgets(); } //**************************************************************************** void Kwave::SonagramWindow::setRate(double rate) { m_rate = rate; updateScaleWidgets(); } //*************************************************************************** //*************************************************************************** diff --git a/plugins/stringenter/StringEnterPlugin.cpp b/plugins/stringenter/StringEnterPlugin.cpp index 92b4c644..46cb6a85 100644 --- a/plugins/stringenter/StringEnterPlugin.cpp +++ b/plugins/stringenter/StringEnterPlugin.cpp @@ -1,93 +1,93 @@ /*************************************************************************** StringEnterPlugin.cpp - plugin for entering a text command ------------------- begin : Sat Mar 14 2015 copyright : (C) 2015 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include #include #include #include #include "libkwave/String.h" #include "libkwave/Utils.h" #include "StringEnterDialog.h" #include "StringEnterPlugin.h" KWAVE_PLUGIN(stringenter, StringEnterPlugin) //*************************************************************************** Kwave::StringEnterPlugin::StringEnterPlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args) { } //*************************************************************************** Kwave::StringEnterPlugin::~StringEnterPlugin() { } //*************************************************************************** void Kwave::StringEnterPlugin::load(QStringList ¶ms) { Q_UNUSED(params); QString entry = _("menu(plugin:setup(stringenter),%1/%2/#icon(editor),F12)"); emitCommand(entry.arg(_("Settings")).arg(_(I18N_NOOP2( "menu: /Settings/Enter Command", "Enter Command") ))); } //*************************************************************************** QStringList *Kwave::StringEnterPlugin::setup(QStringList &previous_params) { QString preset; if (previous_params.count() == 1) preset = previous_params[0]; // create the setup dialog QPointer dialog = new(std::nothrow) Kwave::StringEnterDialog(parentWidget(), preset); Q_ASSERT(dialog); if (!dialog) return Q_NULLPTR; QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" QString command = dialog->command(); emitCommand(command); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; return list; } //*************************************************************************** #include "StringEnterPlugin.moc" //*************************************************************************** //*************************************************************************** diff --git a/plugins/volume/VolumePlugin.cpp b/plugins/volume/VolumePlugin.cpp index 0f7d803c..05c2cc0c 100644 --- a/plugins/volume/VolumePlugin.cpp +++ b/plugins/volume/VolumePlugin.cpp @@ -1,171 +1,172 @@ /*************************************************************************** VolumePlugin.cpp - Plugin for adjusting a signal's volume ------------------- begin : Sun Oct 27 2002 copyright : (C) 2001 by Thomas Eschenbacher email : Thomas.Eschenbacher@gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "config.h" #include #include +#include #include #include #include "libkwave/Connect.h" #include "libkwave/MultiTrackReader.h" #include "libkwave/MultiTrackSource.h" #include "libkwave/MultiTrackWriter.h" #include "libkwave/PluginManager.h" #include "libkwave/SignalManager.h" #include "libkwave/modules/Mul.h" #include "libkwave/undo/UndoTransactionGuard.h" #include "libgui/OverViewCache.h" #include "VolumeDialog.h" #include "VolumePlugin.h" KWAVE_PLUGIN(volume, VolumePlugin) //*************************************************************************** Kwave::VolumePlugin::VolumePlugin(QObject *parent, const QVariantList &args) :Kwave::Plugin(parent, args), m_params(), m_factor(1.0) { } //*************************************************************************** Kwave::VolumePlugin::~VolumePlugin() { } //*************************************************************************** int Kwave::VolumePlugin::interpreteParameters(QStringList ¶ms) { bool ok; QString param; // evaluate the parameter list if (params.count() != 2) return -EINVAL; param = params[0]; m_factor = param.toFloat(&ok); Q_ASSERT(ok); if (!ok) return -EINVAL; param = params[1]; unsigned int mode = param.toUInt(&ok); Q_ASSERT(ok); if (!ok || (mode > 2)) return -EINVAL; // all parameters accepted m_params = params; return 0; } //*************************************************************************** QStringList *Kwave::VolumePlugin::setup(QStringList &previous_params) { // try to interprete the previous parameters interpreteParameters(previous_params); // initialize the overview cache Kwave::SignalManager &mgr = manager().signalManager(); QList tracks; sample_index_t first, last; sample_index_t length = selection(&tracks, &first, &last, true); Kwave::OverViewCache *overview_cache = new(std::nothrow) Kwave::OverViewCache( mgr, first, length, tracks.isEmpty() ? Q_NULLPTR : &tracks ); Q_ASSERT(overview_cache); // create the setup dialog - Kwave::VolumeDialog *dialog = + QPointer dialog = new(std::nothrow) Kwave::VolumeDialog(parentWidget(), overview_cache); if (!dialog) { if (overview_cache) delete overview_cache; return Q_NULLPTR; } if (!m_params.isEmpty()) dialog->setParams(m_params); // execute the dialog QStringList *list = new(std::nothrow) QStringList(); Q_ASSERT(list); - if (list && dialog->exec()) { + if (list && dialog->exec() && dialog) { // user has pressed "OK" *list = dialog->params(); } else { // user pressed "Cancel" if (list) delete list; list = Q_NULLPTR; } if (dialog) delete dialog; if (overview_cache) delete overview_cache; return list; } //*************************************************************************** void Kwave::VolumePlugin::run(QStringList params) { QList tracks; sample_index_t first, last; interpreteParameters(params); if (!selection(&tracks, &first, &last, true) || tracks.isEmpty()) return; Kwave::UndoTransactionGuard undo_guard(*this, i18n("Volume")); // create all objects Kwave::MultiTrackReader source(Kwave::SinglePassForward, signalManager(), selectedTracks(), first, last); Kwave::MultiTrackWriter sink(signalManager(), tracks, Kwave::Overwrite, first, last); Kwave::MultiTrackSource mul(tracks.count()); // connect the progress dialog connect(&source, SIGNAL(progress(qreal)), this, SLOT(updateProgress(qreal)), Qt::BlockingQueuedConnection); // connect them Kwave::connect( source, SIGNAL(output(Kwave::SampleArray)), mul, SLOT(input_a(Kwave::SampleArray))); mul.setAttribute(SLOT(set_b(QVariant)), QVariant(m_factor)); Kwave::connect( mul, SIGNAL(output(Kwave::SampleArray)), sink, SLOT(input(Kwave::SampleArray))); // transport the samples qDebug("VolumePlugin: filter started..."); while (!shouldStop() && !source.done()) { source.goOn(); mul.goOn(); } qDebug("VolumePlugin: filter done."); } //*************************************************************************** #include "VolumePlugin.moc" //*************************************************************************** //***************************************************************************