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