kwave  18.07.70
FileContext.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  kwave/FileContext.cpp - Context of a Loaded File
3  -------------------
4  begin : 2010-01-02
5  copyright : (C) 2010 by Thomas.Eschenbacher
6  email : Thomas.Eschenbacher@gmx.de
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <errno.h>
21 #include <new>
22 
23 #include <QApplication>
24 #include <QFile>
25 #include <QLocale>
26 #include <QPointer>
27 #include <QTextStream>
28 #include <QMdiSubWindow>
29 #include <QStandardPaths>
30 
31 #include <KLocalizedString>
32 
33 #include "libkwave/CodecManager.h"
34 #include "libkwave/Encoder.h"
35 #include "libkwave/Logger.h"
36 #include "libkwave/MessageBox.h"
37 #include "libkwave/Parser.h"
39 #include "libkwave/PluginManager.h"
40 #include "libkwave/SignalManager.h"
41 #include "libkwave/Utils.h"
42 
43 #include "libgui/FileDialog.h"
44 
45 #include "App.h"
46 #include "FileContext.h"
47 #include "MainWidget.h"
48 #include "Splash.h"
49 #include "TopWidget.h"
50 
54 #define CASE_COMMAND(x) } else if (parser.command() == _(x)) {
55 
56 //***************************************************************************
61 namespace Kwave {
62  typedef struct {
63  qint64 pos;
64  unsigned int hits;
65  } label_t;
66 }
67 
68 //***************************************************************************
70  :QObject(),
71  m_use_count(1),
72  m_application(app),
73  m_top_widget(Q_NULLPTR),
74  m_main_widget(Q_NULLPTR),
75  m_signal_manager(Q_NULLPTR),
76  m_plugin_manager(Q_NULLPTR),
77  m_active(true),
78  m_last_zoom(0),
79  m_last_playback_pos(0),
80  m_last_status_message_text(),
81  m_last_status_message_timer(),
82  m_last_status_message_ms(0),
83  m_last_undo(QString()),
84  m_last_redo(QString()),
85  m_instance_nr(-1),
86  m_delayed_command_timer(),
87  m_delayed_command_queue()
88 {
89  m_delayed_command_timer.setSingleShot(true);
90  connect(&m_delayed_command_timer, SIGNAL(timeout()),
91  this, SLOT(processDelayedCommand()));
92 }
93 
94 //***************************************************************************
96 {
97  if (m_main_widget) delete m_main_widget;
98  m_main_widget = Q_NULLPTR;
99 
100  m_top_widget = Q_NULLPTR;
101 
103  m_plugin_manager = Q_NULLPTR;
104 
106  m_signal_manager = Q_NULLPTR;
107 }
108 
109 //***************************************************************************
111 {
112  Q_ASSERT(int(m_use_count) > 0);
113  m_use_count.ref();
114 }
115 
116 //***************************************************************************
118 {
119  Q_ASSERT(int(m_use_count) > 0);
120  if (m_use_count.deref() == false) {
121  disconnect();
122  deleteLater();
123  }
124 }
125 
126 //***************************************************************************
127 bool Kwave::FileContext::createMainWidget(const QSize &preferred_size)
128 {
129  Q_ASSERT(!m_main_widget);
130 
131  // create the main widget
132  m_main_widget = new(std::nothrow) Kwave::MainWidget(
133  m_top_widget, *this, preferred_size
134  );
135  Q_ASSERT(m_main_widget);
136  if (!m_main_widget) return false;
137  if (!(m_main_widget->isOK())) {
138  delete m_main_widget;
139  m_main_widget = Q_NULLPTR;
140  return false;
141  }
142 
143  // connect the main widget
144  connect(&(m_signal_manager->playbackController()),
145  SIGNAL(sigSeekDone(sample_index_t)),
146  m_main_widget, SLOT(scrollTo(sample_index_t)));
147  connect(m_main_widget, SIGNAL(sigCommand(QString)),
148  this, SLOT(executeCommand(QString)));
149  connect(m_main_widget, SIGNAL(sigZoomChanged(double)),
150  this, SLOT(forwardZoomChanged(double)));
155 
156  return true;
157 }
158 
159 //***************************************************************************
161 {
162  Kwave::FileContext::UsageGuard _keep(this);
163 
164  m_top_widget = top_widget;
165  Q_ASSERT(m_top_widget);
166  if (!m_top_widget) return false;
167 
168  m_signal_manager = new(std::nothrow)
170  Q_ASSERT(m_signal_manager);
171  if (!m_signal_manager) return false;
172 
173  m_plugin_manager = new(std::nothrow)
175  Q_ASSERT(m_plugin_manager);
176  if (!m_plugin_manager) return false;
177 
178  // connect the signal manager
180  this, SLOT(metaDataChanged(Kwave::MetaDataList)));
181  connect(&(m_signal_manager->selection()),
182  SIGNAL(changed(sample_index_t,sample_index_t)),
183  this,
185  connect(m_signal_manager, SIGNAL(sigUndoRedoInfo(const QString&,
186  const QString&)),
187  this, SLOT(setUndoRedoInfo(QString,QString)));
189  this, SLOT(modifiedChanged()));
190 
191  // connect the plugin manager
192  connect(m_plugin_manager, SIGNAL(sigCommand(QString)),
193  this, SLOT(executeCommand(QString)));
194 
195  // connect the playback controller
196  connect(&(m_signal_manager->playbackController()),
197  SIGNAL(sigPlaybackPos(sample_index_t)),
198  this, SLOT(updatePlaybackPos(sample_index_t)));
199 
200  setParent(top_widget);
201 
202  Kwave::Splash::showMessage(i18n("Scanning plugins..."));
203  m_plugin_manager->searchPluginModules();
204 
205  // load the menu from file
206  QFile menufile(QStandardPaths::locate(
207  QStandardPaths::GenericDataLocation,
208  _("kwave/menus.config")
209  ));
210  menufile.open(QIODevice::ReadOnly);
211  QTextStream stream(&menufile);
212  Q_ASSERT(!stream.atEnd());
213  if (!stream.atEnd()) parseCommands(stream);
214  menufile.close();
215 
216  // now we are initialized, load all plugins
217  Kwave::Splash::showMessage(i18n("Loading plugins..."));
218  statusBarMessage(i18n("Loading plugins..."), 0);
219  if (!m_plugin_manager->loadAllPlugins()) {
220  statusBarMessage(i18n("Failed"), 1000);
221  QApplication::restoreOverrideCursor();
222  Kwave::MessageBox::error(top_widget,
223  i18n("Kwave has not been properly installed. "\
224  "No plugins found!")
225  );
226  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
227  return false;
228  }
229  statusBarMessage(i18n("Ready"), 1000);
230 
231  return true;
232 }
233 
234 //***************************************************************************
236 {
237  if (m_top_widget) {
239 
240  // disconnect all old signal/slot relationships
241  disconnect(m_plugin_manager, SIGNAL(sigProgress(QString)),
242  old, SLOT(showInSplashSreen(QString)));
243  disconnect(old, SIGNAL(sigFileContextSwitched(Kwave::FileContext*)),
244  this, SLOT(contextSwitched(Kwave::FileContext*)));
245 
246  if (m_signal_manager) m_signal_manager->setParentWidget(Q_NULLPTR);
247  if (m_plugin_manager) m_plugin_manager->setParentWidget(Q_NULLPTR);
248  if (m_main_widget) m_main_widget->setParent(Q_NULLPTR);
249 
250  m_active = false;
251  }
252 
253  // set the new top widget
254  m_top_widget = top_widget;
255 
256  if (m_top_widget) {
257  QWidget *top = m_top_widget;
258 
259  connect(top, SIGNAL(sigFileContextSwitched(Kwave::FileContext*)),
260  this, SLOT(contextSwitched(Kwave::FileContext*)));
261  connect(m_plugin_manager, SIGNAL(sigProgress(QString)),
262  top, SLOT(showInSplashSreen(QString)));
263 
264  if (m_signal_manager) m_signal_manager->setParentWidget(m_top_widget);
265  if (m_plugin_manager) m_plugin_manager->setParentWidget(m_top_widget);
266  if (m_main_widget) m_main_widget->setParent(m_top_widget);
267  }
268 }
269 
270 //***************************************************************************
272 {
273  return static_cast<QWidget *>(m_main_widget);
274 }
275 
276 //***************************************************************************
278 {
279  Q_ASSERT(m_signal_manager);
280  return m_signal_manager;
281 }
282 
283 //***************************************************************************
285 {
286  return m_plugin_manager;
287 }
288 
289 //***************************************************************************
291 {
292  return m_main_widget;
293 }
294 
295 //***************************************************************************
296 int Kwave::FileContext::delegateCommand(const char *plugin,
297  Kwave::Parser &parser,
298  unsigned int param_count)
299 {
300  if (!m_plugin_manager) return -1;
301  if (parser.count() != param_count) return -EINVAL;
302 
303  QStringList params;
304  params.append(parser.command());
305  params.append(parser.remainingParams());
306  int result = m_plugin_manager->setupPlugin(_(plugin), params);
307  if (result > 0) result = 0;
308  return result;
309 }
310 
311 //***************************************************************************
312 int Kwave::FileContext::executeCommand(const QString &line)
313 {
314  Kwave::FileContext::UsageGuard _keep(this);
315 
316  int result = 0;
317  bool use_recorder = true;
318  QString command = line;
319 
320 // qDebug("Kwave::FileContext[%p]::executeCommand(%s)", this, DBG(command));
321 
322  Q_ASSERT(m_plugin_manager);
323  Q_ASSERT(m_top_widget);
324  if (!m_plugin_manager || !m_top_widget) return -ENOMEM;
325 
326  if (!command.length()) return 0; // empty line -> nothing to do
327  if (command.trimmed().startsWith(_("#")))
328  return 0; // only a comment
329 
330  // special case: if the command contains ";" it is a list of
331  // commands -> macro !
332  Kwave::Parser parse_list(command);
333  if (parse_list.hasMultipleCommands()) {
334  QStringList macro = parse_list.commandList();
335  foreach (const QString &it, macro) {
336  result = executeCommand(_("nomacro:") + it);
337  Q_ASSERT(!result);
338  if (result) {
339  qWarning("macro execution of '%s' failed: %d",
340  DBG(it), result);
341  return result; // macro failed :-(
342  }
343 
344  // wait until the command has completed !
345  m_plugin_manager->sync();
346  }
347  return result;
348  }
349 
350  // check if the macro recorder has to be disabled for this command
351  if (command.startsWith(_("nomacro:"))) {
352  use_recorder = false;
353  command = command.mid(QString(_("nomacro:")).length());
354  }
355 
356  // expand variables
357  if (command.contains(_("${"))) {
358  // current language
359  if (command.contains(_("${LANG}"))) {
360  QLocale locale;
361  if (!m_main_widget.isNull()) locale = m_main_widget->locale();
362  QString lang = locale.name().split(_("-")).at(0);
363  command.replace(_("${LANG}"), lang);
364  }
365  }
366 
367  // log all commands to the log file if enabled
368  Kwave::Logger::log(this, Kwave::Logger::Info, _("CMD: ") + line);
369 
370  // parse one single command
371  Kwave::Parser parser(command);
372  QString cmd = parser.command();
373 
374  // exclude menu commands from the recorder
375  if (cmd == _("menu")) use_recorder = false;
376 
377  // only record plugin:execute, not plugin without parameters
378  if (cmd == _("plugin")) use_recorder = false;
379 
380  // let through all commands that handle zoom/view or playback like fwd/rew
381  bool allow_always =
382  (cmd == _("playback")) ||
383  cmd.startsWith(_("view:")) ||
384  cmd.startsWith(_("playback:")) ||
385  cmd.startsWith(_("select_track:")) ||
386  (cmd == _("close")) ||
387  (cmd == _("quit")) ||
388  (cmd == _("window:screenshot")) ||
389  (cmd == _("window:sendkey"))
390  ;
391 
392  // all others only if no plugin is currently running
393  if (!allow_always && m_plugin_manager->onePluginRunning())
394  {
395  qWarning("FileContext::executeCommand('%s') - currently not possible, "
396  "a plugin is running :-(",
397  DBG(cmd));
398  return -1;
399  }
400 
401  if (use_recorder) {
402  // append the command to the macro recorder
403  // @TODO macro recording...
404  qDebug("# %s ", DBG(command));
405  }
406 
407  if ((result = m_top_widget->executeCommand(command)) != ENOSYS)
408  return result;
409 
410  if (false) {
411  CASE_COMMAND("close")
412  result = closeFile() ? 0 : 1;
413  CASE_COMMAND("delayed")
414  if (parser.count() != 2)
415  return -EINVAL;
416  unsigned int delay = parser.firstParam().toUInt();
417  QString delayed_cmd = parser.nextParam();
418  enqueueCommand(delay, delayed_cmd);
419  result = 0;
420  CASE_COMMAND("loadbatch")
421  result = loadBatch(QUrl(parser.nextParam()));
422  CASE_COMMAND("plugin")
423  QString name(parser.firstParam());
424  QStringList params(parser.remainingParams());
425  qDebug("FileContext::executeCommand(): loading plugin '%s'", DBG(name));
426  qDebug("FileContext::executeCommand(): with %d parameter(s)",
427  params.count());
428  result = m_plugin_manager->executePlugin(
429  name, params.count() ? &params : Q_NULLPTR);
430  CASE_COMMAND("plugin:execute")
431  QString name(parser.firstParam());
432  QStringList params(parser.remainingParams());
433  result = m_plugin_manager->executePlugin(name, &params);
434  CASE_COMMAND("plugin:setup")
435  QString name(parser.firstParam());
436  QStringList params(parser.remainingParams());
437  result = m_plugin_manager->setupPlugin(name, params);
438  if (result > 0) result = 0;
439  CASE_COMMAND("revert")
440  result = revert();
441  CASE_COMMAND("save")
442  result = saveFile();
443  CASE_COMMAND("saveas")
444  result = saveFileAs(parser.nextParam(), false);
445  CASE_COMMAND("saveselect")
446  result = saveFileAs(QString(), true);
447  CASE_COMMAND("sync")
448  while (!m_delayed_command_queue.isEmpty()) {
449  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
450  }
451  result = 0;
452  CASE_COMMAND("window:click")
453  result = delegateCommand("debug", parser, 3);
454  CASE_COMMAND("window:close")
455  result = delegateCommand("debug", parser, 1);
456  CASE_COMMAND("window:mousemove")
457  result = delegateCommand("debug", parser, 3);
458  CASE_COMMAND("window:resize")
459  result = delegateCommand("debug", parser, 3);
460  CASE_COMMAND("window:sendkey")
461  result = delegateCommand("debug", parser, 2);
462  CASE_COMMAND("window:screenshot")
463  result = delegateCommand("debug", parser, 2);
464  } else {
465  // pass the command to the layer below (main widget)
466  Kwave::CommandHandler *layer_below = m_main_widget;
467  result = (layer_below) ? layer_below->executeCommand(command) : -ENOSYS;
468  }
469 
470  return result;
471 }
472 
473 //***************************************************************************
475 {
476  m_last_zoom = zoom;
477  emit sigZoomChanged(this, zoom);
478 }
479 
480 //***************************************************************************
481 void Kwave::FileContext::statusBarMessage(const QString &msg, unsigned int ms)
482 {
485  if (ms)
487  else
488  m_last_status_message_timer.invalidate();
489 
490  if (isActive())
491  emit sigStatusBarMessage(msg, ms);
492 }
493 
494 //***************************************************************************
496 {
497  if (!m_plugin_manager) return;
498  if (!m_main_widget) return;
499 
500  bool playing = m_signal_manager->playbackController().running();
501  if (!playing) return;
502 
503  QString txt;
504  double rate = m_plugin_manager->signalRate();
505  if (rate > 0) {
506  double ms = static_cast<double>(offset) * 1E3 / rate;
507  txt = i18n("Playback: %1", Kwave::ms2string(ms));
508  } else {
509  txt = i18n("Playback: %1 samples", Kwave::samples2string(offset));
510  }
511 
512  statusBarMessage(txt, 2000);
513 
514  // make sure that the current playback position is visible
515  m_last_playback_pos = offset;
516  Kwave::Zoomable *z = zoomable();
517  if (z) z->scrollTo(offset);
518 }
519 
520 //***************************************************************************
522 {
523  // find out the instance ID
524  if (m_instance_nr == -1) {
525  // build a list of all currently open files/instances (including this)
526  QList<Kwave::App::FileAndInstance> files = m_application.openFiles();
527 
528  // filter out all instances of our file name
529  QString our_name = signalName();
530  QList<int> existing_instances;
531  foreach (const Kwave::App::FileAndInstance &it, files) {
532  const QString &name = it.first;
533  int inst = it.second;
534  if (name == our_name) existing_instances.append(inst);
535  }
536 
537  // remove our own entry
538  if (existing_instances.contains(m_instance_nr))
539  existing_instances.removeOne(m_instance_nr);
540 
541  // find an empty slot
542  if (!existing_instances.isEmpty())
543  while (existing_instances.contains(m_instance_nr))
544  m_instance_nr = (m_instance_nr != -1) ? (m_instance_nr + 1) : 2;
545  }
546 
547  if (isActive()) {
548  // we are active -> emit the meta data immediately
549  emit sigMetaDataChanged(meta_data);
550  } // else: we are inactive -> emit the meta data later, when activated
551 
552  // update the caption of the sub window
554  m_main_widget->setWindowTitle(windowCaption(true));
555 }
556 
557 //***************************************************************************
559  sample_index_t length)
560 {
561  if (isActive()) {
562  // we are active -> emit the selection change immediately
563  emit sigSelectionChanged(offset, length);
564  } // else: we are inactive -> not of interest / ignore
565 }
566 
567 //***************************************************************************
568 void Kwave::FileContext::setUndoRedoInfo(const QString &undo,
569  const QString &redo)
570 {
571  m_last_undo = undo;
572  m_last_redo = redo;
573 
574  if (isActive()) {
575  // we are active -> emit the undo/redo info immediately
576  emit sigUndoRedoInfo(undo, redo);
577  } // else: we are inactive -> emit the undo/redo info later, when activated
578 }
579 
580 //***************************************************************************
582  sample_index_t visible,
583  sample_index_t total)
584 {
585  if (isActive()) {
586  // we are active -> emit the view info immediately
587  emit sigVisibleRangeChanged(offset, visible, total);
588  } // else: we are inactive -> emit the view info later, when activated
589 }
590 
591 //***************************************************************************
593 {
594  if (isActive()) {
595  // we are active -> emit the modified state immediately
596  emit sigModified();
597  } // else: we are inactive -> emit the modified state later, when activated
598 
599  // update the caption of our main widget
601  m_main_widget->setWindowTitle(windowCaption(true));
602 }
603 
604 //***************************************************************************
606 {
607  Kwave::FileContext::UsageGuard _keep(this);
608 
609  if (context == this) {
610  if (!m_active) {
611  m_active = true;
612  activated();
613  }
614  } else
615  m_active = false;
616 }
617 
618 //***************************************************************************
620 {
621  // let our plugin manager be the active one
622  if (m_plugin_manager) m_plugin_manager->setActive();
623 
624  // emit last playback position if playback is running
625  if (m_signal_manager && m_signal_manager->playbackController().running())
627 
628  // emit last zoom factor
630 
631  // erase the status message of the previous context
632  emit sigStatusBarMessage(QString(), 0);
633 
634  // emit our last status bar message if it has not expired
635  if (m_last_status_message_timer.isValid()) {
636  quint64 elapsed = m_last_status_message_timer.elapsed();
637  if (elapsed < m_last_status_message_ms) {
638  unsigned int remaining =
641  } else
642  m_last_status_message_timer.invalidate();
643  } else if (m_last_status_message_ms == 0) {
644  // static message without expiration
645  if (m_last_status_message_text.length()) {
647  }
648  }
649 
650  // emit latest meta data
651  if (m_signal_manager)
652  emit sigMetaDataChanged(m_signal_manager->metaData());
653 
654  // emit latest view range change
656  sample_index_t offset = m_main_widget->visibleOffset();
657  sample_index_t visible = m_main_widget->visibleSamples();
658  sample_index_t total = m_signal_manager->length();
659  emit sigVisibleRangeChanged(offset, visible, total);
660  }
661 
662  // force update of the "modified" state
663  emit sigModified();
664 
665  // emit last undo/redo info
667 
668 }
669 
670 //***************************************************************************
671 int Kwave::FileContext::parseCommands(QTextStream &stream)
672 {
673  Kwave::FileContext::UsageGuard _keep(this);
674 
675  int result = 0;
676  QMap<QString, label_t> labels;
677 
678  // set hourglass cursor
679  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
680 
681  QString label; // label, jump target of a "GOTO"
682  while (!stream.atEnd() && !result) {
683  QString line = stream.readLine().simplified();
684  if (line.startsWith(_("#"))) continue; // skip comments
685  if (!line.length()) continue; // skip empty lines
686 
687  if (line.endsWith(QLatin1Char(':'))) {
688  // this line seems to be a "label"
689  QString name = line.left(line.length() - 1).simplified();
690  if (!labels.contains(name)) {
691  // qDebug("new label '%s' at %llu", DBG(name), stream.pos());
692  label_t label_pos;
693  label_pos.pos = stream.pos();
694  label_pos.hits = 0;
695  labels[name] = label_pos;
696  }
697 
698  // special handling for a label at the end of the file
699  if (label.length() && (label == name)) {
700  // label found
701  label = QString();
702  }
703  continue;
704  }
705 
706  Kwave::Parser parser(line);
707 
708  // the "GOTO" command
709  if ( !label.length() &&
710  (line.split(QLatin1Char(' ')).at(0) == _("GOTO")) ) {
711  label = line.split(QLatin1Char(' ')).at(1).simplified();
712  }
713 
714  // jump to a label, scan/seek mode
715  if (label.length()) {
716  if (labels.contains(label)) {
717  labels[label].hits++;
718  qDebug(">>> GOTO '%s' @ offset %llu (pass #%d)", DBG(label),
719  labels[label].pos,
720  labels[label].hits
721  );
722  stream.seek(labels[label].pos);
723 
724  // reset the label to search for
725  label = QString();
726  }
727  // else: maybe the label will follow somewhere below,
728  // scan forward...
729  continue;
730  }
731 
732  // synchronize before the command
733  if (m_plugin_manager)
734  m_plugin_manager->sync();
735 
736  // the "msgbox" command (useful for debugging)
737  if (false) { ;
738  CASE_COMMAND("msgbox")
739  QApplication::restoreOverrideCursor();
741  parser.firstParam()) == KMessageBox::Yes) ? 0 : 1;
742  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
743  continue;
744  }
745 
746  // prevent this command from being re-added to the macro recorder
747  if (!line.startsWith(_("nomacro:"), Qt::CaseInsensitive))
748  line.prepend(_("nomacro:"));
749 
750  // process the command in the current context
751  // NOTE: this could theoretically also be a command that modifies
752  // or even deletes the current context!
753  result = EAGAIN;
754  Kwave::FileContext *current_ctx = (m_top_widget) ?
755  m_top_widget->currentContext() : Q_NULLPTR;
756  if (current_ctx && (current_ctx != this))
757  result = m_top_widget->forwardCommand(line);
758 
759  // If the call returned with EAGAIN, then the context in duty is
760  // different from this instance but not yet completely set up.
761  // In that case this context is still responsible for executing the
762  // current command.
763  if (result == EAGAIN)
764  result = executeCommand(line);
765 
766  if (result)
767  qDebug(">>> '%s' - result=%d", DBG(line), result);
768 
769  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
770 
771  // synchronize after the command
772  if (m_plugin_manager)
773  m_plugin_manager->sync();
774 
775  // special handling of the "quit" command
776  if (parser.command() == _("quit")) {
777  result = ECANCELED;
778  break;
779  }
780  }
781 
782  if (label.length()) {
783  // oops, if we get here then we have searched for a non-exising label
784  qWarning("label '%s' not found", DBG(label));
785  result = -ENOENT;
786  }
787 
788  // remove hourglass
789  QApplication::restoreOverrideCursor();
790 
791  return result;
792 }
793 
794 //***************************************************************************
795 void Kwave::FileContext::enqueueCommand(unsigned int delay,
796  const QString &command)
797 {
798  use();
799 
801  QPair<unsigned int, QString>(delay, command)
802  );
803  if (!m_delayed_command_timer.isActive())
804  m_delayed_command_timer.start(delay);
805 }
806 
807 //***************************************************************************
809 {
810  if (m_delayed_command_queue.isEmpty()) return;
811 
812  QPair<unsigned int, QString> current = m_delayed_command_queue.takeFirst();
813  executeCommand(current.second);
814  if (m_delayed_command_queue.isEmpty()) return;
815 
816  QPair<unsigned int, QString> next = m_delayed_command_queue.first();
817  m_delayed_command_timer.start(next.first);
818 
819  release();
820 }
821 
822 //***************************************************************************
824 {
825  if (m_use_count >= 2) return true;
826  return (m_signal_manager && !m_signal_manager->isEmpty());
827 }
828 
829 //***************************************************************************
831 {
832  return (m_signal_manager) ? m_signal_manager->signalName() : QString();
833 }
834 
835 //***************************************************************************
836 QString Kwave::FileContext::windowCaption(bool with_modified) const
837 {
838  QString name = signalName();
839 
840  // shortcut if no file loaded
841  if (!name.length()) return QString();
842 
843  // if not in SDI mode we have to take care of multiple instances on our
844  // own and append a " <n>" manually !
846  if (m_instance_nr != -1)
847  name = i18nc(
848  "for window title: "
849  "%1 = Name of the file, "
850  "%2 = Instance number when opened multiple times",
851  "%1 <%2>", name, m_instance_nr);
852 
853  if (with_modified) {
854  bool modified = (m_signal_manager) ?
855  m_signal_manager->isModified() : false;
856  if (modified)
857  return i18nc("%1 = Path to modified file", "* %1 (modified)", name);
858  }
859  return name;
860 }
861 
862 //***************************************************************************
863 int Kwave::FileContext::loadBatch(const QUrl &url)
864 {
865  Kwave::FileContext::UsageGuard _keep(this);
866 
867  // open the URL, read-only mode is enough
868  QFile file(url.path());
869  if (!file.open(QIODevice::ReadOnly)) {
870  qWarning("unable to open source in read-only mode!");
871  return -EIO;
872  }
873 
874  // use a text stream for parsing the commands
875  QTextStream stream(&file);
876  int result = parseCommands(stream);
877  file.close();
878 
879  return result;
880 }
881 
882 //***************************************************************************
884 {
885  Kwave::FileContext::UsageGuard _keep(this);
886 
887  QUrl url(signalName());
888  if (!url.isValid() || !m_signal_manager) return -EINVAL;
889 
890  if (m_signal_manager->isModified()) {
892  i18n("This file has been modified, changes will be lost.\n"
893  "Do you want to continue?"));
894  if (res == KMessageBox::Cancel) return ECANCELED;
895  }
896 
897  return m_signal_manager->loadFile(url);
898 }
899 
900 //***************************************************************************
902 {
903  if (!m_signal_manager) return -EINVAL;
904 
905  int res = 0;
906 
907  if (signalName() != NEW_FILENAME) {
908  QUrl url;
909  url = QUrl(signalName());
910  res = m_signal_manager->save(url, false);
911 
912  // if saving in current format is not possible (no encoder),
913  // then try to "save/as" instead...
914  if (res == -EINVAL) res = saveFileAs(QString(), false);
915  } else res = saveFileAs(QString(), false);
916 
917  return res;
918 }
919 
920 //***************************************************************************
921 int Kwave::FileContext::saveFileAs(const QString &filename, bool selection)
922 {
923  if (!m_signal_manager) return -EINVAL;
924 
925  QString name = filename;
926  QUrl url;
927  int res = 0;
928 
929  if (name.length()) {
930  /* name given -> take it */
931  url = QUrl(name);
932  } else {
933  /*
934  * no name given -> show the File/SaveAs dialog...
935  */
936  QUrl current_url;
937  current_url = QUrl(signalName());
938 
939  QString what = Kwave::CodecManager::mimeTypeOf(current_url);
941  QString extension; // = "*.wav";
942  if (!encoder) {
943  // no extension selected yet, use mime type from file info
944  QString mime_type = Kwave::FileInfo(
945  m_signal_manager->metaData()).get(
946  Kwave::INF_MIMETYPE).toString();
947  encoder = Kwave::CodecManager::encoder(mime_type);
948  if (encoder) {
949  QStringList extensions = encoder->extensions(mime_type);
950  if (!extensions.isEmpty()) {
951  QString ext = extensions.first().split(_(" ")).first();
952  if (ext.length()) {
953  extension = ext;
954  QString new_filename = current_url.fileName();
955  new_filename += extension.mid(1); // remove the "*"
956  current_url = current_url.adjusted(QUrl::RemoveFilename);
957  current_url.setPath(current_url.path() + new_filename);
958  }
959  }
960  }
961  }
962 
963  QString filter = Kwave::CodecManager::encodingFilter();
964  QPointer<Kwave::FileDialog> dlg = new(std::nothrow)Kwave::FileDialog(
965  _("kfiledialog:///kwave_save_as"),
967  filter, m_top_widget, current_url, extension
968  );
969  if (!dlg) return 0;
970  dlg->setWindowTitle(i18n("Save As"));
971  if (dlg->exec() != QDialog::Accepted) {
972  delete dlg;
973  return -1;
974  }
975 
976  url = dlg->selectedUrl();
977  if (url.isEmpty()) {
978  delete dlg;
979  return 0;
980  }
981 
982  QString new_name = url.path();
983  QFileInfo path(new_name);
984 
985  // add the correct extension if necessary
986  if (!path.suffix().length()) {
987  QString ext = dlg->selectedExtension();
988  QStringList extensions = ext.split(_(" "));
989  ext = extensions.first();
990  new_name += ext.mid(1);
991  path = new_name;
992  url.setPath(new_name);
993  }
994 
995  delete dlg;
996  }
997 
998  // check if the file exists and ask before overwriting it
999  // if it is not the old filename
1000  name = url.path();
1001  if ((url.toDisplayString() != QUrl(signalName()).toDisplayString()) &&
1002  (QFileInfo(name).exists()))
1003  {
1005  i18n("The file '%1' already exists.\n"
1006  "Do you really want to overwrite it?", name)) !=
1007  KMessageBox::Yes)
1008  {
1009  return -1;
1010  }
1011  }
1012 
1013  // maybe we now have a new mime type
1014  QString previous_mimetype_name =
1015  Kwave::FileInfo(m_signal_manager->metaData()).get(
1016  Kwave::INF_MIMETYPE).toString();
1017 
1018  QString new_mimetype_name = Kwave::CodecManager::mimeTypeOf(url);
1019 
1020  if (new_mimetype_name != previous_mimetype_name) {
1021  // saving to a different mime type
1022  // now we have to do as if the mime type and file name
1023  // has already been selected to satisfy the fileinfo
1024  // plugin
1025  qDebug("TopWidget::saveAs(%s) - [%s] (previous:'%s')",
1026  DBG(url.toDisplayString()), DBG(new_mimetype_name),
1027  DBG(previous_mimetype_name) );
1028 
1029  // set the new mimetype
1030  Kwave::FileInfo info(m_signal_manager->metaData());
1031  info.set(Kwave::INF_MIMETYPE, new_mimetype_name);
1032 
1033  // set the new filename
1034  info.set(Kwave::INF_FILENAME, url.toDisplayString());
1035  m_signal_manager->setFileInfo(info, false);
1036 
1037  // now call the fileinfo plugin with the new filename and
1038  // mimetype
1039  res = (m_plugin_manager) ?
1040  m_plugin_manager->setupPlugin(_("fileinfo"), QStringList())
1041  : -1;
1042 
1043  // restore the mime type and the filename
1044  info = Kwave::FileInfo(m_signal_manager->metaData());
1045  info.set(Kwave::INF_MIMETYPE, previous_mimetype_name);
1046  info.set(Kwave::INF_FILENAME, url.toDisplayString());
1047  m_signal_manager->setFileInfo(info, false);
1048  }
1049 
1050  // now we have a file name -> do the "save" operation
1051  if (!res) res = m_signal_manager->save(url, selection);
1052 
1053  // if saving was successful, add the file to the list of recent files
1054  if (!res) m_application.addRecentFile(signalName());
1055 
1056  return res;
1057 }
1058 
1059 //***************************************************************************
1061 {
1062  Kwave::FileContext::UsageGuard _keep(this);
1063 
1064  if (m_plugin_manager && !m_plugin_manager->canClose())
1065  {
1066  qWarning("FileContext::closeFile() - currently not possible, "\
1067  "a plugin is running :-(");
1068  return false;
1069  }
1070 
1071  if (m_signal_manager && m_signal_manager->isModified()) {
1073  i18n("This file has been modified.\nDo you want to save it?"));
1074  if (res == KMessageBox::Cancel) return false;
1075  if (res == KMessageBox::Yes) {
1076  // user decided to save
1077  res = saveFile();
1078  qDebug("FileContext::closeFile()::saveFile, res=%d",res);
1079  if (res) return false;
1080  }
1081  }
1082 
1083  // close all plugins that still might use the current signal
1084  if (m_plugin_manager) {
1085  m_plugin_manager->stopAllPlugins();
1086  m_plugin_manager->signalClosed();
1087  }
1088 
1089  if (m_signal_manager) m_signal_manager->close();
1090 
1091  switch (m_application.guiType()) {
1092  case Kwave::App::GUI_MDI: /* FALLTHROUGH */
1093  case Kwave::App::GUI_TAB:
1094  // close the main widget
1095  if (m_main_widget) delete m_main_widget;
1096  break;
1097  case Kwave::App::GUI_SDI:
1098  break;
1099  }
1100 
1101  return true;
1102 }
1103 
1104 //***************************************************************************
1105 //***************************************************************************
static QString encodingFilter()
bool isActive() const
Definition: FileContext.h:124
int loadBatch(const QUrl &url)
QUrl selectedUrl() const
Definition: FileDialog.cpp:253
QPointer< Kwave::MainWidget > m_main_widget
Definition: FileContext.h:398
QTimer m_delayed_command_timer
Definition: FileContext.h:434
Definition: App.h:33
int parseCommands(QTextStream &stream)
#define NEW_FILENAME
Definition: SignalManager.h:46
void forwardZoomChanged(double zoom)
QString Q_DECL_EXPORT ms2string(double ms, int precision=6)
Definition: Utils.cpp:66
QString Q_DECL_EXPORT samples2string(sample_index_t samples)
Definition: Utils.cpp:98
QElapsedTimer m_last_status_message_timer
Definition: FileContext.h:419
QList< QPair< unsigned int, QString > > m_delayed_command_queue
Definition: FileContext.h:437
void enqueueCommand(unsigned int delay, const QString &command)
static void showMessage(const QString &message)
Definition: Splash.cpp:115
void setParent(Kwave::TopWidget *top_widget)
bool isInUse() const
void sigMetaDataChanged(Kwave::MetaDataList meta_data)
void sigZoomChanged(Kwave::FileContext *context, double zoom)
static int questionYesNo(QWidget *widget, QString message, QString caption=QString(), const QString buttonYes=QString(), const QString buttonNo=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:63
quint64 sample_index_t
Definition: Sample.h:28
QList< FileAndInstance > openFiles() const
Definition: App.cpp:294
bool init(Kwave::TopWidget *top_widget)
static QString mimeTypeOf(const QUrl &url)
void sigVisibleRangeChanged(sample_index_t offset, sample_index_t visible, sample_index_t total)
void processDelayedCommand()
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
void metaDataChanged(Kwave::MetaDataList meta_data)
QAtomicInt m_use_count
Definition: FileContext.h:389
void set(FileProperty key, const QVariant &value)
Definition: FileInfo.cpp:363
QString windowCaption(bool with_modified) const
void selectionChanged(sample_index_t offset, sample_index_t length)
void statusBarMessage(const QString &msg, unsigned int ms)
Definition: App.h:44
#define CASE_COMMAND(x)
Definition: FileContext.cpp:54
const char name[16]
Definition: memcpy.c:510
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
sample_index_t m_last_playback_pos
Definition: FileContext.h:413
void setUndoRedoInfo(const QString &undo, const QString &redo)
QString signalName() const
bool hasMultipleCommands()
Definition: Parser.h:62
GuiType guiType() const
Definition: App.h:119
virtual void scrollTo(sample_index_t pos)=0
QStringList commandList()
Definition: Parser.h:67
bool createMainWidget(const QSize &preferred_size)
void sigStatusBarMessage(const QString &message, unsigned int ms)
static int warningYesNoCancel(QWidget *widget, QString message, QString caption=QString(), const QString buttonYes=QString(), const QString buttonNo=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:104
static int warningYesNo(QWidget *widget, QString message, QString caption=QString(), const QString buttonYes=QString(), const QString buttonNo=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:93
Kwave::Zoomable * zoomable() const
virtual ~FileContext()
Definition: FileContext.cpp:95
static int warningContinueCancel(QWidget *widget, QString message, QString caption=QString(), const QString buttonContinue=QString(), const QString buttonCancel=QString(), const QString &dontAskAgainName=QString())
Definition: MessageBox.cpp:115
QPointer< Kwave::PluginManager > m_plugin_manager
Definition: FileContext.h:404
QPointer< Kwave::SignalManager > m_signal_manager
Definition: FileContext.h:401
QWidget * mainWidget() const
static Kwave::Encoder * encoder(const QString &mimetype_name)
QString m_last_status_message_text
Definition: FileContext.h:416
void updatePlaybackPos(sample_index_t offset)
const QString & firstParam()
Definition: Parser.cpp:168
#define _(m)
Definition: memcpy.c:66
#define DBG(qs)
Definition: String.h:55
void sigSelectionChanged(sample_index_t offset, sample_index_t length)
unsigned int toUint(T x)
Definition: Utils.h:109
QStringList remainingParams()
Definition: Parser.cpp:189
QPair< QString, int > FileAndInstance
Definition: App.h:60
static void Q_DECL_EXPORT log(const QObject *sender, LogLevel level, const QString &msg)
Definition: Logger.cpp:103
void visibleRangeChanged(sample_index_t offset, sample_index_t visible, sample_index_t total)
Kwave::PluginManager * pluginManager() const
virtual int executeCommand(const QString &command)=0
Kwave::SignalManager * signalManager() const
unsigned int count() const
Definition: Parser.h:75
void addRecentFile(const QString &filename)
Definition: App.cpp:202
FileContext(Kwave::App &app)
Definition: FileContext.cpp:69
virtual QStringList extensions(const QString &mimetype_name) const
Definition: CodecBase.cpp:115
unsigned int m_last_status_message_ms
Definition: FileContext.h:422
int saveFileAs(const QString &filename, bool selection=false)
QPointer< Kwave::TopWidget > m_top_widget
Definition: FileContext.h:395
Kwave::App & m_application
Definition: FileContext.h:392
QString command()
Definition: Parser.h:47
const QString & nextParam()
Definition: Parser.cpp:175
int executeCommand(const QString &command)
int delegateCommand(const char *plugin, Kwave::Parser &parser, unsigned int param_count)
unsigned int hits
Definition: FileContext.cpp:64
void sigUndoRedoInfo(const QString &undo, const QString &redo)
void contextSwitched(Kwave::FileContext *context)