1 /***************************************************************************
2    SaveBlocksPlugin.cpp  -  Plugin for saving blocks between labels
3                              -------------------
4     begin                : Thu Mar 01 2007
5     copyright            : (C) 2007 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 
22 #include <QDir>
23 #include <QFile>
24 #include <QFileInfo>
25 #include <QPointer>
26 #include <QRegExp>
27 #include <QStringList>
28 
29 #include <KLocalizedString>
30 
31 #include "libkwave/CodecManager.h"
32 #include "libkwave/Encoder.h"
33 #include "libkwave/FileInfo.h"
34 #include "libkwave/Label.h"
35 #include "libkwave/LabelList.h"
36 #include "libkwave/MessageBox.h"
37 #include "libkwave/MetaDataList.h"
38 #include "libkwave/Parser.h"
39 #include "libkwave/SignalManager.h"
40 #include "libkwave/String.h"
41 #include "libkwave/Utils.h"
42 
43 #include "SaveBlocksDialog.h"
44 #include "SaveBlocksPlugin.h"
45 
KWAVE_PLUGIN(saveblocks,SaveBlocksPlugin)46 KWAVE_PLUGIN(saveblocks, SaveBlocksPlugin)
47 
48 //***************************************************************************
49 Kwave::SaveBlocksPlugin::SaveBlocksPlugin(QObject *parent,
50                                           const QVariantList &args)
51     :Kwave::Plugin(parent, args),
52      m_url(), m_pattern(), m_numbering_mode(CONTINUE),
53      m_selection_only(true), m_block_info()
54 {
55 }
56 
57 //***************************************************************************
~SaveBlocksPlugin()58 Kwave::SaveBlocksPlugin::~SaveBlocksPlugin()
59 {
60 }
61 
62 //***************************************************************************
setup(QStringList & previous_params)63 QStringList *Kwave::SaveBlocksPlugin::setup(QStringList &previous_params)
64 {
65     // try to interpret the previous parameters
66     interpreteParameters(previous_params);
67 
68     // create the setup dialog
69     sample_index_t selection_left  = 0;
70     sample_index_t selection_right = 0;
71     selection(Q_NULLPTR, &selection_left, &selection_right, false);
72 
73     // enable the "selection only" checkbox only if there is something
74     // selected but not everything
75     bool selected_something = (selection_left != selection_right);
76     bool selected_all = ((selection_left == 0) &&
77                          (selection_right + 1 >= signalLength()));
78     bool enable_selection_only = selected_something && !selected_all;
79 
80     QString filename = m_url.path();
81     QString base = findBase(filename, m_pattern);
82     scanBlocksToSave(base, m_selection_only && enable_selection_only);
83 
84     QPointer<Kwave::SaveBlocksDialog> dialog =
85 	new(std::nothrow) Kwave::SaveBlocksDialog(
86 	    _("kfiledialog:///kwave_save_blocks"),
87 	    Kwave::CodecManager::encodingFilter(),
88 	    parentWidget(),
89 	    Kwave::URLfromUserInput(signalName()),
90 	    _("*.wav"),
91 	    m_pattern,
92 	    m_numbering_mode,
93 	    m_selection_only,
94 	    enable_selection_only
95 	);
96         if (!dialog) return Q_NULLPTR;
97 
98     // connect the signals/slots from the plugin and the dialog
99     connect(dialog, SIGNAL(sigSelectionChanged(QString,
100 	QString,Kwave::SaveBlocksPlugin::numbering_mode_t,bool)),
101 	this, SLOT(updateExample(QString,QString,
102 	Kwave::SaveBlocksPlugin::numbering_mode_t,bool)));
103     connect(this, SIGNAL(sigNewExample(QString)),
104 	dialog, SLOT(setNewExample(QString)));
105 
106     dialog->setWindowTitle(description());
107     dialog->emitUpdate();
108     if ((dialog->exec() != QDialog::Accepted) || !dialog) {
109 	delete dialog;
110         return Q_NULLPTR;
111     }
112 
113     QStringList *list = new(std::nothrow) QStringList();
114     Q_ASSERT(list);
115     if (list) {
116 	// user has pressed "OK"
117 	QString pattern;
118 
119 	QUrl url = dialog->selectedUrl();
120 	if (url.isEmpty()) {
121 	    delete dialog;
122 	    delete list;
123             return Q_NULLPTR;
124 	}
125 	QString name = url.path();
126 	QFileInfo path(name);
127 
128 	// add the correct extension if necessary
129 	if (!path.suffix().length()) {
130 	    QString ext = dialog->selectedExtension();
131 	    QStringList extensions = ext.split(_(" "));
132 	    ext = extensions.first();
133 	    name += ext.midRef(1);
134 	    path = name;
135 	    url.setPath(name);
136 	}
137 
138 	name     = Kwave::Parser::escape(name);
139 	pattern  = Kwave::Parser::escape(dialog->pattern());
140 	int mode = static_cast<int>(dialog->numberingMode());
141 	bool selection_only = (enable_selection_only) ?
142 	    dialog->selectionOnly() : m_selection_only;
143 
144 	*list << name;
145 	*list << pattern;
146 	*list << QString::number(mode);
147 	*list << QString::number(selection_only);
148 
149 	emitCommand(_("plugin:execute(saveblocks,") +
150 	    name + _(",") + pattern + _(",") +
151 	    QString::number(mode) + _(",") +
152 	    QString::number(selection_only) + _(")")
153 	);
154     }
155 
156     if (dialog) delete dialog;
157     return list;
158 }
159 
160 //***************************************************************************
createDisplayList(const QStringList & list,unsigned int max_entries) const161 QString Kwave::SaveBlocksPlugin::createDisplayList(
162     const QStringList &list,
163     unsigned int max_entries) const
164 {
165     if (!max_entries || list.isEmpty()) return QString();
166 
167     QString retval;
168     unsigned int count = 0;
169 
170     foreach (const QString &entry, list) {
171 	if (count == 0) // first entry
172 	    retval = _("<br><br>");
173 	if (count < max_entries)
174 	    retval += entry + _("<br>");
175 	else if (count == max_entries)
176 	    retval += i18n("...") + _("<br>");
177 
178 	if (++count > max_entries)
179 	    break;
180     }
181 
182     return retval;
183 }
184 
185 //***************************************************************************
start(QStringList & params)186 int Kwave::SaveBlocksPlugin::start(QStringList &params)
187 {
188     qDebug("SaveBlocksPlugin::start()");
189 
190     // interpret the parameters
191     int result = interpreteParameters(params);
192     if (result) return result;
193 
194     QString filename = m_url.path();
195     QFileInfo file(filename);
196     QString path = file.absolutePath();
197     QString ext  = file.suffix();
198     QString base = findBase(filename, m_pattern);
199     QByteArray sep("/");
200 
201     // determine the selection settings
202     sample_index_t selection_left  = 0;
203     sample_index_t selection_right = 0;
204     selection(Q_NULLPTR, &selection_left, &selection_right, false);
205 
206     bool selected_something = (selection_left != selection_right);
207     bool selected_all = ( (selection_left == 0) &&
208                           ((selection_right + 1) >= signalLength()) );
209     bool enable_selection_only = selected_something && !selected_all;
210     bool selection_only = enable_selection_only && m_selection_only;
211     if (!selection_only) {
212 	selection_left  = 0;
213 	selection_right = signalLength() - 1;
214     }
215 
216     // get the index range
217     scanBlocksToSave(base, selection_only);
218     unsigned int count = m_block_info.count();
219     unsigned int first = firstIndex(path, base, ext, m_pattern,
220                                     m_numbering_mode, count);
221 
222 //     qDebug("m_url            = '%s'", m_url.prettyURL().local8Bit().data());
223 //     qDebug("m_pattern        = '%s'", m_pattern.local8Bit().data());
224 //     qDebug("m_numbering_mode = %d", (int)m_numbering_mode);
225 //     qDebug("selection_only   = %d", selection_only);
226 //     qDebug("indices          = %u...%u (count=%u)", first, first+count-1,count);
227 
228     // remember the original file info and determine the list of unsupported
229     // properties, we need that later to avoid that the signal manager
230     // complains on saving each and every block, again and again...
231     const Kwave::FileInfo orig_file_info(signalManager().metaData());
232     Kwave::FileInfo file_info(orig_file_info);
233     QList<Kwave::FileProperty> unsupported_properties;
234     {
235 	QString mimetype = Kwave::CodecManager::mimeTypeOf(m_url);
236 	Kwave::Encoder *encoder = Kwave::CodecManager::encoder(mimetype);
237 	if (encoder) {
238 	    unsupported_properties = encoder->unsupportedProperties(
239 		file_info.properties().keys());
240 	    delete encoder;
241 	}
242     }
243 
244     // iterate over all blocks to check for overwritten files and missing dirs
245     QStringList  overwritten_files;
246     QStringList  missing_dirs;
247     for (unsigned int i = first; i < (first + count); i++) {
248 	QString name = createFileName(base, ext, m_pattern, i, count,
249 	                              first + count - 1);
250 	QString display_name = Kwave::Parser::unescape(name);
251 
252 	// split the name into directory and file name
253 	name = QString::fromLatin1(QUrl::toPercentEncoding(display_name, sep));
254 	QUrl url = m_url.adjusted(QUrl::RemoveFilename);
255 	url.setPath(url.path(QUrl::FullyEncoded) + name, QUrl::StrictMode);
256 
257 	QString p = url.path();
258 	QFileInfo fi(p);
259 
260 	// check for potentially overwritten file
261 	if (fi.exists())
262 	    overwritten_files += Kwave::Parser::unescape(display_name);
263 
264 	// check for missing subdirectory
265 	if (!fi.dir().exists()) {
266 	    QString missing_dir = fi.absolutePath();
267 	    if (!missing_dirs.contains(missing_dir))
268 		missing_dirs += missing_dir;
269 	}
270     }
271 
272     // inform about overwritten files
273     if (!overwritten_files.isEmpty()) {
274 	// ask the user for confirmation if he really wants to overwrite...
275 	if (Kwave::MessageBox::warningYesNo(parentWidget(),
276 	    _("<html>") +
277 	    i18n("This would overwrite the following file(s): %1" \
278 	    "Do you really want to continue?",
279 	    createDisplayList(overwritten_files, 5)) +
280 	    _("</html>") ) != KMessageBox::Yes)
281 	{
282 	    return -1;
283 	}
284     }
285 
286     // handle missing directories
287     if (!missing_dirs.isEmpty()) {
288 	// ask the user if he wants to continue and create the directory
289 	if (Kwave::MessageBox::warningContinueCancel(parentWidget(),
290 	    i18n("The following directories do not exist: %1"
291 	         "Do you want to create them and continue?",
292 	         createDisplayList(missing_dirs, 5)),
293 	    QString(),
294 	    QString(),
295 	    QString(),
296 	    _("saveblocks_create_missing_dirs")
297 	    ) != KMessageBox::Continue)
298 	{
299 	    return -1;
300 	}
301 
302 	// create all missing directories
303 	QUrl base_url = m_url.adjusted(QUrl::RemoveFilename);
304 	foreach (const QString &missing, missing_dirs) {
305 	    QUrl url(base_url);
306 	    url.setPath(
307 		base_url.path(QUrl::FullyEncoded) +
308 		QString::fromLatin1(QUrl::toPercentEncoding(missing)),
309 		QUrl::StrictMode
310 	    );
311 	    QString p = url.path();
312 	    QDir dir;
313 	    if (!dir.mkpath(p))
314 		qWarning("creating path '%s' failed", DBG(p));
315 	}
316     }
317 
318     // save the current selection, we have to restore it afterwards!
319     sample_index_t saved_selection_left  = 0;
320     sample_index_t saved_selection_right = 0;
321     selection(Q_NULLPTR, &saved_selection_left, &saved_selection_right, false);
322 
323     // now we can loop over all blocks and save them
324     sample_index_t block_start;
325     sample_index_t block_end = 0;
326     Kwave::LabelList labels(signalManager().metaData());
327     Kwave::LabelListIterator it(labels);
328     Kwave::Label label = it.hasNext() ? it.next() : Kwave::Label();
329 
330     for (unsigned int index = first;;) {
331 	block_start = block_end;
332 	block_end   = (label.isNull()) ? signalLength() : label.pos();
333 
334 	if ((selection_left < block_end) && (selection_right > block_start)) {
335 	    // found a block to save...
336 	    Q_ASSERT(index < first + count);
337 
338 	    sample_index_t left  = block_start;
339 	    sample_index_t right = block_end - 1;
340 	    if (left  < selection_left)  left  = selection_left;
341 	    if (right > selection_right) right = selection_right;
342 	    Q_ASSERT(right > left);
343 	    if (right <= left) break; // zero-length ?
344 
345 	    // select the range of samples
346 	    selectRange(left, right - left + 1);
347 
348 	    // determine the filename
349 	    QString name = createFileName(base, ext, m_pattern, index, count,
350                                           first + count - 1);
351 	    name = Kwave::Parser::unescape(name);
352 	    // use URL encoding for the filename
353 	    name = QString::fromLatin1(QUrl::toPercentEncoding(name, sep));
354 	    QUrl url = m_url.adjusted(QUrl::RemoveFilename);
355 	    url.setPath(url.path(QUrl::FullyEncoded) + name, QUrl::StrictMode);
356 
357 	    // enter the title of the block into the meta data if supported
358 	    if (!unsupported_properties.contains(INF_NAME)) {
359 		QString title = orig_file_info.get(INF_NAME).toString();
360 		int idx = index - first;
361 		if ((idx >= 0) && (idx < m_block_info.count())) {
362 		    QString block_title = m_block_info[idx].m_title;
363 		    if (block_title.length())
364 			title = title + _(", ") + block_title;
365 		}
366 		file_info.set(INF_NAME, QVariant(title));
367 		signalManager().metaData().replace(
368 		    Kwave::MetaDataList(file_info));
369 	    }
370 
371 	    qDebug("saving %9lu...%9lu -> '%s'",
372 		   static_cast<unsigned long int>(left),
373 		   static_cast<unsigned long int>(right),
374 		   DBG(url.toDisplayString()));
375 	    if (signalManager().save(url, true) < 0)
376 		break;
377 
378 	    // if there were unsupported properties, the user might have been
379 	    // asked whether it is ok to continue or not. If he answered with
380 	    // "Cancel", we do not reach this point, otherwise we can continue
381 	    // and prevent any further annoying questions by removing all
382 	    // unsupported file info before the next run...
383 	    if ((index == first) && !unsupported_properties.isEmpty()) {
384 		foreach (const Kwave::FileProperty &p, unsupported_properties) {
385 		    file_info.set(p, QVariant());
386 		}
387 		signalManager().metaData().replace(
388 		    Kwave::MetaDataList(file_info));
389 	    }
390 
391 	    // increment the index for the next filename
392 	    index++;
393 	}
394 	if (label.isNull()) break;
395 	label = (it.hasNext()) ? it.next() : Kwave::Label();
396     }
397 
398     // restore the original file info
399     signalManager().metaData().replace(
400 	Kwave::MetaDataList(orig_file_info));
401 
402     // restore the previous selection
403     selectRange(saved_selection_left,
404 	(saved_selection_left != saved_selection_right) ?
405 	(saved_selection_right - saved_selection_left + 1) : 0);
406 
407     return result;
408 }
409 
410 //***************************************************************************
interpreteParameters(QStringList & params)411 int Kwave::SaveBlocksPlugin::interpreteParameters(QStringList &params)
412 {
413     bool ok;
414     QString param;
415 
416     // evaluate the parameter list
417     if (params.count() != 4) {
418 	return -EINVAL;
419     }
420 
421     // the selected URL
422     m_url = Kwave::URLfromUserInput(Kwave::Parser::unescape(params[0]));
423     if (!m_url.isValid()) return -EINVAL;
424 
425     // filename pattern
426     m_pattern = Kwave::Parser::unescape(params[1]);
427     if (!m_pattern.length()) return -EINVAL;
428 
429     // numbering mode
430     param = params[2];
431     int mode = param.toInt(&ok);
432     Q_ASSERT(ok);
433     if (!ok) return -EINVAL;
434     if ((mode != CONTINUE) &&
435         (mode != START_AT_ONE)) return -EINVAL;
436     m_numbering_mode = static_cast<numbering_mode_t>(mode);
437 
438     // flag: save only the selection
439     param = params[3];
440     m_selection_only = (param.toUInt(&ok) != 0);
441     Q_ASSERT(ok);
442     if (!ok) return -EINVAL;
443 
444     return 0;
445 }
446 
447 //***************************************************************************
scanBlocksToSave(const QString & base,bool selection_only)448 void Kwave::SaveBlocksPlugin::scanBlocksToSave(const QString &base,
449                                                bool selection_only)
450 {
451     sample_index_t selection_left, selection_right;
452 
453     sample_index_t block_start;
454     sample_index_t block_end = 0;
455     QString        block_title;
456     Kwave::LabelList labels(signalManager().metaData());
457     Kwave::LabelListIterator it(labels);
458     Kwave::Label label = (it.hasNext()) ? it.next() : Kwave::Label();
459 
460     if (selection_only) {
461         selection(Q_NULLPTR, &selection_left, &selection_right, true);
462     } else {
463 	selection_left  = 0;
464 	selection_right = signalLength() - 1;
465     }
466 
467     // get the title of the whole file, in case that a block does not have
468     // an own title
469     FileInfo info(signalManager().metaData());
470     QString file_title = info.get(INF_NAME).toString();
471 
472     // fallback: if there is no INF_NAME either, fall back to the file
473     //           name as last resort
474     if (!file_title.length()) file_title = base;
475 
476     m_block_info.clear();
477     QString prev_title;
478     for (;;) {
479 	block_start = block_end;
480 	block_end   = (label.isNull()) ? signalLength() : label.pos();
481 	block_title = prev_title;
482 	prev_title  = (label.isNull()) ? file_title     : label.name();
483 
484 	if ((block_end > selection_left) && (block_start <= selection_right)) {
485 	    BlockInfo block;
486 	    block.m_start  = block_start;
487 	    block.m_length = block_end - block_start;
488 	    block.m_title  = block_title;
489 	    if (!block.m_title.length()) block.m_title = file_title;
490 	    m_block_info.append(block);
491 	}
492 
493 	if (label.isNull()) break;
494 	label = (it.hasNext()) ? it.next() : Kwave::Label();
495     }
496 }
497 
498 //***************************************************************************
createFileName(const QString & base,const QString & ext,const QString & pattern,unsigned int index,int count,int total)499 QString Kwave::SaveBlocksPlugin::createFileName(const QString &base,
500     const QString &ext, const QString &pattern,
501     unsigned int index, int count, int total)
502 {
503     QString p = QRegExp::escape(pattern);
504     QString nr;
505 
506     // format the "index" parameter
507     QRegExp rx_nr(_("(\\\\\\[%(\\d*)nr\\\\\\])"), Qt::CaseInsensitive);
508     while (rx_nr.indexIn(p) >= 0) {
509 	QString format = rx_nr.cap(1);
510 	format = format.mid(2, format.length() - 6);
511 	QString ex = _("(\\\\\\[") + format + _("nr\\\\\\])");
512 	QRegExp rx(ex, Qt::CaseInsensitive);
513 	format += _("u");
514 	p.replace(rx, nr.asprintf(format.toLatin1(), index));
515     }
516 
517     // format the "count" parameter
518     QRegExp rx_count(_("(\\\\\\[%\\d*count\\\\\\])"), Qt::CaseInsensitive);
519     while (rx_count.indexIn(p) >= 0) {
520 	if (count >= 0) {
521 	    QString format = rx_count.cap(1);
522 	    format = format.mid(2, format.length() - 9);
523 	    QString ex = _("(\\\\\\[") + format + _("count\\\\\\])");
524 	    QRegExp rx(ex, Qt::CaseInsensitive);
525 	    format += _("u");
526 	    p.replace(rx, nr.asprintf(format.toLatin1(), count));
527 	} else {
528 	    p.replace(rx_count, _("(\\d+)"));
529 	}
530     }
531 
532     // format the "total" parameter
533     QRegExp rx_total(_("(\\\\\\[%\\d*total\\\\\\])"), Qt::CaseInsensitive);
534     while (rx_total.indexIn(p) >= 0) {
535 	if (total >= 0) {
536 	    QString format = rx_total.cap(1);
537 	    format = format.mid(2, format.length() - 9);
538 	    QString ex = _("(\\\\\\[") + format + _("total\\\\\\])");
539 	    QRegExp rx(ex, Qt::CaseInsensitive);
540 	    format += _("u");
541 	    p.replace(rx, nr.asprintf(format.toLatin1(), total));
542 	} else {
543 	    p.replace(rx_total, _("(\\d+)"));
544 	}
545     }
546 
547     // format the "filename" parameter
548     QRegExp rx_filename(_("\\\\\\[%filename\\\\\\]"), Qt::CaseInsensitive);
549     if (rx_filename.indexIn(p) >= 0) {
550 	p.replace(rx_filename, QRegExp::escape(base));
551     }
552 
553     // support for file info
554     QRegExp rx_fileinfo(
555 	_("\\\\\\[%(\\d*)fileinfo\\\\\\{([\\w\\s]+)\\\\\\}\\\\\\]"),
556 	Qt::CaseInsensitive
557     );
558     Kwave::FileInfo info(signalManager().metaData());
559     while (rx_fileinfo.indexIn(p) >= 0) {
560 	const QString format = rx_fileinfo.cap(1);
561 	const QString id     = rx_fileinfo.cap(2);
562 	QString value;
563 	FileProperty property = info.fromName(id);
564 	if (property != Kwave::INF_UNKNOWN) {
565 	    QVariant val = info.get(property);
566 	    if (!val.isNull()) {
567 		// we have a property value
568 		value = val.toString();
569 
570 		// check for format (desired minimum string length)
571 		bool ok  = false;
572 		int  len = format.toUInt(&ok);
573 		if ((len > 0) && ok) {
574 		    Kwave::FileInfo::Flags flags = info.flags(property);
575 		    if (flags & Kwave::FileInfo::FP_FORMAT_NUMERIC) {
576 			// numeric format, pad with leading zeros or spaces
577 			QString pad = (format.startsWith(QLatin1Char('0'))) ?
578 			    _("0") : _(" ");
579 			while (value.length() < len)
580 			    value = pad + value;
581 		    } else {
582 			// string format, pad with trailing spaces
583 			while (value.length() < len)
584 			    value = value + _(" ");
585 		    }
586 		}
587 		value = Kwave::Parser::escapeForFileName(value);
588 	    }
589 	}
590 
591 	QString ex(_("(\\\\\\[%") + format + _("fileinfo\\\\\\{") + id +
592 	           _("\\\\\\}\\\\\\])"));
593 	QRegExp rx(ex, Qt::CaseInsensitive);
594 	p.replace(rx, value);
595     }
596 
597     // format the "title" parameter
598     QRegExp rx_title(_("\\\\\\[%title\\\\\\]"), Qt::CaseInsensitive);
599     if (rx_title.indexIn(p) >= 0) {
600 	QString title;
601 	int idx = (index - 1) - (total - count);
602 	if ((idx >= 0) && (idx < m_block_info.count()))
603 	    title = m_block_info[idx].m_title;
604 	if (title.length()) {
605 	    title = Kwave::Parser::escapeForFileName(title);
606 	    p.replace(rx_title, title);
607 	}
608     }
609 
610     if (ext.length()) p += _(".") + ext;
611 
612     // sanitize the filename/path, make sure that there are no spaces
613     // before and after all path separators
614     QString sep = _("/");
615     QRegExp rx_sep(_("\\s*") + sep + _("\\s*"));
616     p.replace(rx_sep, sep);
617 
618     return p;
619 }
620 
621 //***************************************************************************
firstIndex(const QString & path,const QString & base,const QString & ext,const QString & pattern,Kwave::SaveBlocksPlugin::numbering_mode_t mode,unsigned int count)622 unsigned int Kwave::SaveBlocksPlugin::firstIndex(const QString &path,
623     const QString &base, const QString &ext, const QString &pattern,
624     Kwave::SaveBlocksPlugin::numbering_mode_t mode, unsigned int count)
625 {
626     unsigned int first = 1;
627     switch (mode) {
628 	case START_AT_ONE:
629 	    first = 1;
630 	    break;
631 	case CONTINUE: {
632 	    QDir dir(path, _("*"));
633 	    QStringList files;
634 	    files = dir.entryList();
635 	    for (unsigned int i = first; i < (first + count); i++) {
636 		QString name = createFileName(base, ext, pattern, i, -1, -1);
637 		QRegExp rx(_("^(") + name + _(")$"),
638 		           Qt::CaseInsensitive);
639 		QStringList matches = files.filter(rx);
640 		if (matches.count() > 0) first = i + 1;
641 	    }
642 	    break;
643 	}
644     }
645 
646     return first;
647 }
648 
649 //***************************************************************************
findBase(const QString & filename,const QString & pattern)650 QString Kwave::SaveBlocksPlugin::findBase(const QString &filename,
651                                           const QString &pattern)
652 {
653     QFileInfo file(filename);
654     QString name = file.fileName();
655     QString base = file.completeBaseName();
656     QString ext  = file.suffix();
657 
658     // convert the pattern into a regular expression in order to check if
659     // the current name already is produced by the current pattern
660     // \[%[0-9]?nr\]      -> \d+
661     // \[%[0-9]?count\]   -> \d+
662     // \[%[0-9]?total\]   -> \d+
663     // \[%filename\]      -> base
664     // \[%fileinfo\]      -> .
665     // \[%title\]         -> .
666     QRegExp rx_nr(_("\\\\\\[%\\d*nr\\\\\\]"), Qt::CaseInsensitive);
667     QRegExp rx_count(_("\\\\\\[%\\d*count\\\\\\]"), Qt::CaseInsensitive);
668     QRegExp rx_total(_("\\\\\\[%\\d*total\\\\\\]"), Qt::CaseInsensitive);
669     QRegExp rx_filename(_("\\\\\\[%filename\\\\\\]"), Qt::CaseInsensitive);
670     QRegExp rx_fileinfo(_("\\\\\\[%fileinfo\\\\\\]"), Qt::CaseInsensitive);
671     QRegExp rx_title(_("\\\\\\[%title\\\\\\]"), Qt::CaseInsensitive);
672 
673     QString p = QRegExp::escape(pattern);
674     int idx_nr       = rx_nr.indexIn(p);
675     int idx_count    = rx_count.indexIn(p);
676     int idx_total    = rx_total.indexIn(p);
677     int idx_filename = rx_filename.indexIn(p);
678     int idx_fileinfo = rx_fileinfo.indexIn(p);
679     int idx_title    = rx_fileinfo.indexIn(p);
680     p.replace(rx_nr,       _("(\\d+)"));
681     p.replace(rx_count,    _("(\\d+)"));
682     p.replace(rx_total,    _("(\\d+)"));
683     p.replace(rx_filename, _("(.+)"));
684     p.replace(rx_fileinfo, _("(.+)"));
685     p.replace(rx_title,    _("(.+)"));
686 
687     int max = 0;
688     for (int i = 0; i < pattern.length(); i++) {
689 	if (idx_nr       == max) max++;
690 	if (idx_count    == max) max++;
691 	if (idx_total    == max) max++;
692 	if (idx_filename == max) max++;
693 	if (idx_fileinfo == max) max++;
694 	if (idx_title    == max) max++;
695 
696 	if (idx_nr       > max) idx_nr--;
697 	if (idx_count    > max) idx_count--;
698 	if (idx_total    > max) idx_total--;
699 	if (idx_filename > max) idx_filename--;
700 	if (idx_fileinfo > max) idx_fileinfo--;
701 	if (idx_title    > max) idx_title--;
702     }
703 
704     if (ext.length()) p += _(".") + ext;
705     QRegExp rx_current(p, Qt::CaseInsensitive);
706     if (rx_current.indexIn(name) >= 0) {
707 	// filename already produced by this pattern
708 	base = rx_current.cap(idx_filename + 1);
709     }
710 
711     return base;
712 }
713 
714 //***************************************************************************
firstFileName(const QString & filename,const QString & pattern,Kwave::SaveBlocksPlugin::numbering_mode_t mode,bool selection_only)715 QString Kwave::SaveBlocksPlugin::firstFileName(const QString &filename,
716     const QString &pattern, Kwave::SaveBlocksPlugin::numbering_mode_t mode,
717     bool selection_only)
718 {
719     QFileInfo file(filename);
720     QString path = file.absolutePath();
721     QString ext  = file.suffix();
722     QString base = findBase(filename, pattern);
723 
724     // now we have a new name, base and extension
725     // -> find out the numbering, min/max etc...
726     scanBlocksToSave(base, selection_only);
727     unsigned int count = m_block_info.count();
728     unsigned int first = firstIndex(path, base, ext, pattern, mode, count);
729     unsigned int total = first + count - 1;
730 
731     // create the complete filename, including extension but without path
732     return createFileName(base, ext, pattern, first, count, total);
733 }
734 
735 //***************************************************************************
updateExample(const QString & filename,const QString & pattern,Kwave::SaveBlocksPlugin::numbering_mode_t mode,bool selection_only)736 void Kwave::SaveBlocksPlugin::updateExample(const QString &filename,
737     const QString &pattern, Kwave::SaveBlocksPlugin::numbering_mode_t mode,
738     bool selection_only)
739 {
740     QString example = firstFileName(filename, pattern, mode, selection_only);
741     emit sigNewExample(Kwave::Parser::unescape(example));
742 }
743 
744 //***************************************************************************
745 #include "SaveBlocksPlugin.moc"
746 //***************************************************************************
747 //***************************************************************************
748