OSDN Git Service

Don't check for updates when application is run for the very first time (after instal...
[lamexp/LameXP.git] / src / Model_CueSheet.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2011 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Global.h"
23 #include "Model_CueSheet.h"
24 #include "Genres.h"
25
26 #include <QApplication>
27 #include <QDir>
28 #include <QFileInfo>
29 #include <QFont>
30 #include <QTime>
31
32 #include <float.h>
33 #include <limits>
34
35 ////////////////////////////////////////////////////////////
36 // Helper Classes
37 ////////////////////////////////////////////////////////////
38
39 class CueSheetItem
40 {
41 public:
42         virtual const char* type(void) = 0;
43         virtual bool isValid(void) { return false; }
44 };
45
46 class CueSheetTrack : public CueSheetItem
47 {
48 public:
49         CueSheetTrack(CueSheetFile *parent, int trackNo)
50         :
51                 m_parent(parent),
52                 m_trackNo(trackNo)
53         {
54                 m_startIndex = std::numeric_limits<double>::quiet_NaN();
55                 m_duration = std::numeric_limits<double>::infinity();
56         }
57         int trackNo(void) { return m_trackNo; }
58         double startIndex(void) { return m_startIndex; }
59         double duration(void) { return m_duration; }
60         QString title(void) { return m_title; }
61         QString performer(void) { return m_performer; }
62         CueSheetFile *parent(void) { return m_parent; }
63         void setStartIndex(double startIndex) { m_startIndex = startIndex; }
64         void setDuration(double duration) { m_duration = duration; }
65         void setTitle(const QString &title, bool update = false) { if(!update || (m_title.isEmpty() && !title.isEmpty())) m_title = title; }
66         void setPerformer(const QString &performer, bool update = false) { if(!update || (m_performer.isEmpty() && !performer.isEmpty())) m_performer = performer; }
67         virtual bool isValid(void) { return !(_isnan(m_startIndex) || (m_trackNo < 0)); }
68         virtual const char* type(void) { return "CueSheetTrack"; }
69 private:
70         int m_trackNo;
71         double m_startIndex;
72         double m_duration;
73         QString m_title;
74         QString m_performer;
75         CueSheetFile *m_parent;
76 };
77
78 class CueSheetFile : public CueSheetItem
79 {
80 public:
81         CueSheetFile(const QString &fileName) : m_fileName(fileName) {}
82         ~CueSheetFile(void) { while(!m_tracks.isEmpty()) delete m_tracks.takeLast(); }
83         QString fileName(void) { return m_fileName; }
84         void addTrack(CueSheetTrack *track) { m_tracks.append(track); }
85         void clearTracks(void) { while(!m_tracks.isEmpty()) delete m_tracks.takeLast(); }
86         CueSheetTrack *track(int index) { return m_tracks.at(index); }
87         int trackCount(void) { return m_tracks.count(); }
88         virtual bool isValid(void) { return m_tracks.count() > 0; }
89         virtual const char* type(void) { return "CueSheetFile"; }
90 private:
91         const QString m_fileName;
92         QList<CueSheetTrack*> m_tracks;
93 };
94
95 ////////////////////////////////////////////////////////////
96 // Constructor & Destructor
97 ////////////////////////////////////////////////////////////
98
99 QMutex CueSheetModel::m_mutex(QMutex::Recursive);
100
101 CueSheetModel::CueSheetModel()
102 :
103         m_fileIcon(":/icons/music.png"),
104         m_trackIcon(":/icons/control_play_blue.png")
105 {
106         int trackNo = 0;
107         
108         for(int i = 0; i < 5; i++)
109         {
110                 CueSheetFile *currentFile = new CueSheetFile(QString().sprintf("File %02d.wav", i+1));
111                 for(int j = 0; j < 8; j++)
112                 {
113                         CueSheetTrack *currentTrack = new CueSheetTrack(currentFile, trackNo++);
114                         currentTrack->setTitle("ATWA (Air Trees Water Animals)");
115                         currentTrack->setPerformer("System of a Down");
116                         currentFile->addTrack(currentTrack);
117                 }
118                 m_files.append(currentFile);
119         }
120 }
121
122 CueSheetModel::~CueSheetModel(void)
123 {
124         while(!m_files.isEmpty()) delete m_files.takeLast();
125 }
126
127 ////////////////////////////////////////////////////////////
128 // Model Functions
129 ////////////////////////////////////////////////////////////
130
131 QModelIndex CueSheetModel::index(int row, int column, const QModelIndex &parent) const
132 {
133         QMutexLocker lock(&m_mutex);
134         
135         if(!parent.isValid())
136         {
137                 return (row < m_files.count()) ? createIndex(row, column, m_files.at(row)) : QModelIndex();
138         }
139
140         CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
141         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
142         {
143                 return (row < filePtr->trackCount()) ? createIndex(row, column, filePtr->track(row)) : QModelIndex();
144         }
145
146         return QModelIndex();
147 }
148
149 int CueSheetModel::columnCount(const QModelIndex &parent) const
150 {
151         QMutexLocker lock(&m_mutex);
152         return 4;
153 }
154
155 int CueSheetModel::rowCount(const QModelIndex &parent) const
156 {
157         QMutexLocker lock(&m_mutex);
158
159         if(!parent.isValid())
160         {
161                 return m_files.count();
162         }
163
164         CueSheetItem *parentItem = static_cast<CueSheetItem*>(parent.internalPointer());
165         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(parentItem))
166         {
167                 return filePtr->trackCount();
168         }
169
170         return 0;
171 }
172
173 QModelIndex CueSheetModel::parent(const QModelIndex &child) const
174 {
175         QMutexLocker lock(&m_mutex);
176         
177         if(child.isValid())
178         {
179                 CueSheetItem *childItem = static_cast<CueSheetItem*>(child.internalPointer());
180                 if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(childItem))
181                 {
182                         return createIndex(m_files.indexOf(trackPtr->parent()), 0, trackPtr->parent());
183                 }
184         }
185
186         return QModelIndex();
187 }
188
189 QVariant CueSheetModel::headerData (int section, Qt::Orientation orientation, int role) const
190 {
191         QMutexLocker lock(&m_mutex);
192         
193         if(role == Qt::DisplayRole)
194         {
195                 switch(section)
196                 {
197                 case 0:
198                         return tr("No.");
199                         break;
200                 case 1:
201                         return tr("File / Track");
202                         break;
203                 case 2:
204                         return tr("Index");
205                         break;
206                 case 3:
207                         return tr("Duration");
208                         break;
209                 default:
210                         return QVariant();
211                         break;
212                 }
213         }
214         else
215         {
216                 return QVariant();
217         }
218 }
219
220 QVariant CueSheetModel::data(const QModelIndex &index, int role) const
221 {
222         QMutexLocker lock(&m_mutex);
223
224         if(role == Qt::DisplayRole)
225         {
226                 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
227
228                 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
229                 {
230                         switch(index.column())
231                         {
232                         case 0:
233                                 return tr("File %1").arg(QString().sprintf("%02d", index.row() + 1)).append(" ");
234                                 break;
235                         case 1:
236                                 return QFileInfo(filePtr->fileName()).fileName();
237                                 break;
238                         default:
239                                 return QVariant();
240                                 break;
241                         }
242                 }
243                 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
244                 {
245                         switch(index.column())
246                         {
247                         case 0:
248                                 return tr("Track %1").arg(QString().sprintf("%02d", trackPtr->trackNo())).append(" ");
249                                 break;
250                         case 1:
251                                 if(!trackPtr->title().isEmpty() && !trackPtr->performer().isEmpty())
252                                 {
253                                         return QString("%1 - %2").arg(trackPtr->performer(), trackPtr->title());
254                                 }
255                                 else if(!trackPtr->title().isEmpty())
256                                 {
257                                         return QString("%1 - %2").arg(tr("Unknown Artist"), trackPtr->title());
258                                 }
259                                 else if(!trackPtr->performer().isEmpty())
260                                 {
261                                         return QString("%1 - %2").arg(trackPtr->performer(), tr("Unknown Title"));
262                                 }
263                                 else
264                                 {
265                                         return QString("%1 - %2").arg(tr("Unknown Artist"), tr("Unknown Title"));
266                                 }
267                                 break;
268                         case 2:
269                                 return indexToString(trackPtr->startIndex());
270                                 break;
271                         case 3:
272                                 return indexToString(trackPtr->duration());
273                                 break;
274                         default:
275                                 return QVariant();
276                                 break;
277                         }
278                 }
279         }
280         else if(role == Qt::ToolTipRole)
281         {
282                 CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
283
284                 if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
285                 {
286                         return QDir::toNativeSeparators(filePtr->fileName());
287                 }
288                 else if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
289                 {
290                         return QDir::toNativeSeparators(trackPtr->parent()->fileName());
291                 }
292         }
293         else if(role == Qt::DecorationRole)
294         {
295                 if(index.column() == 0)
296                 {
297                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
298
299                         if(dynamic_cast<CueSheetFile*>(item))
300                         {
301                                 return m_fileIcon;
302                         }
303                         else if(dynamic_cast<CueSheetTrack*>(item))
304                         {
305                                 return m_trackIcon;
306                         }
307                 }
308         }
309         else if(role == Qt::FontRole)
310         {
311                 QFont font("Monospace");
312                 font.setStyleHint(QFont::TypeWriter);
313                 if((index.column() == 1))
314                 {
315                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
316                         font.setBold(dynamic_cast<CueSheetFile*>(item) != NULL);
317                 }
318                 return font;
319         }
320         else if(role == Qt::ForegroundRole)
321         {
322                 if((index.column() == 1))
323                 {
324                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
325                         if(CueSheetFile *filePtr = dynamic_cast<CueSheetFile*>(item))
326                         {
327                                 return (QFileInfo(filePtr->fileName()).size() > 4) ? QColor("mediumblue") : QColor("darkred");
328                         }
329                 }
330                 else if((index.column() == 3))
331                 {
332                         CueSheetItem *item = reinterpret_cast<CueSheetItem*>(index.internalPointer());
333                         if(CueSheetTrack *trackPtr = dynamic_cast<CueSheetTrack*>(item))
334                         {
335                                 if(trackPtr->duration() == std::numeric_limits<double>::infinity())
336                                 {
337                                         return QColor("dimgrey");
338                                 }
339                         }
340                 }
341         }
342
343         return QVariant();
344 }
345
346 void CueSheetModel::clearData(void)
347 {
348         QMutexLocker lock(&m_mutex);
349         
350         beginResetModel();
351         while(!m_files.isEmpty()) delete m_files.takeLast();
352         endResetModel();
353 }
354
355 ////////////////////////////////////////////////////////////
356 // External API
357 ////////////////////////////////////////////////////////////
358
359 int CueSheetModel::getFileCount(void)
360 {
361         QMutexLocker lock(&m_mutex);
362         return m_files.count();
363 }
364
365 QString CueSheetModel::getFileName(int fileIndex)
366 {
367         QMutexLocker lock(&m_mutex);
368         
369         if(fileIndex < 0 || fileIndex >= m_files.count())
370         {
371                 return QString();
372         }
373
374         return m_files.at(fileIndex)->fileName();
375 }
376
377 int CueSheetModel::getTrackCount(int fileIndex)
378 {
379         QMutexLocker lock(&m_mutex);
380
381         if(fileIndex < 0 || fileIndex >= m_files.count())
382         {
383                 return -1;
384         }
385
386         return m_files.at(fileIndex)->trackCount();
387 }
388
389 int CueSheetModel::getTrackNo(int fileIndex, int trackIndex)
390 {
391         QMutexLocker lock(&m_mutex);
392         
393         if(fileIndex >= 0 && fileIndex < m_files.count())
394         {
395                 CueSheetFile *currentFile = m_files.at(fileIndex);
396                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
397                 {
398                         return currentFile->track(trackIndex)->trackNo();
399                 }
400         }
401
402         return -1;
403 }
404
405 void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration)
406 {
407         QMutexLocker lock(&m_mutex);
408         
409         *startIndex = std::numeric_limits<double>::quiet_NaN();
410         *duration = std::numeric_limits<double>::quiet_NaN();
411
412         if(fileIndex >= 0 && fileIndex < m_files.count())
413         {
414                 CueSheetFile *currentFile = m_files.at(fileIndex);
415                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
416                 {
417                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
418                         *startIndex = currentTrack->startIndex();
419                         *duration = currentTrack->duration();
420                 }
421         }
422 }
423
424 QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex)
425 {       
426         QMutexLocker lock(&m_mutex);
427         
428         if(fileIndex >= 0 && fileIndex < m_files.count())
429         {
430                 CueSheetFile *currentFile = m_files.at(fileIndex);
431                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
432                 {
433                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
434                         return currentTrack->performer();
435                 }
436         }
437         
438         return QString();
439 }
440
441 QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex)
442 {
443         QMutexLocker lock(&m_mutex);
444         
445         if(fileIndex >= 0 && fileIndex < m_files.count())
446         {
447                 CueSheetFile *currentFile = m_files.at(fileIndex);
448                 if(trackIndex >= 0 && trackIndex < currentFile->trackCount())
449                 {
450                         CueSheetTrack *currentTrack = currentFile->track(trackIndex);
451                         return currentTrack->title();
452                 }
453         }
454         
455         return QString();
456 }
457
458 QString CueSheetModel::getAlbumPerformer(void)
459 {
460         QMutexLocker lock(&m_mutex);
461         return m_albumPerformer;
462 }
463
464 QString CueSheetModel::getAlbumTitle(void)
465 {
466         QMutexLocker lock(&m_mutex);
467         return m_albumTitle;
468 }
469
470 ////////////////////////////////////////////////////////////
471 // Cue Sheet Parser
472 ////////////////////////////////////////////////////////////
473
474 int CueSheetModel::loadCueSheet(const QString &cueFileName, QCoreApplication *application)
475 {
476         QMutexLocker lock(&m_mutex);
477         
478         QFile cueFile(cueFileName);
479         if(!cueFile.open(QIODevice::ReadOnly))
480         {
481                 return ErrorIOFailure;
482         }
483
484         clearData();
485
486         beginResetModel();
487         int iResult = parseCueFile(cueFile, QDir(QFileInfo(cueFile).canonicalPath()), application);
488         endResetModel();
489
490         return iResult;
491 }
492
493 int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplication *application)
494 {
495         cueFile.seek(0);
496         qDebug("\n[Cue Sheet Import]");
497
498         //Reject very large files, as parsing might take until forever
499         if(cueFile.size() >= 10485760i64)
500         {
501                 qWarning("File is very big. Probably not a Cue Sheet. Rejecting...");
502                 return 2;
503         }
504
505         //Check for UTF-8 BOM in order to guess encoding
506         QByteArray bomCheck = cueFile.peek(128);
507         bool bUTF8 = bomCheck.contains("\xef\xbb\xbf");
508         qDebug("Encoding is %s.", (bUTF8 ? "UTF-8" : "Local 8-Bit"));
509         bomCheck.clear();
510
511         QRegExp rxFile("^FILE\\s+\"([^\"]+)\"\\s+(\\w+)$", Qt::CaseInsensitive);
512         QRegExp rxTrack("^TRACK\\s+(\\d+)\\s(\\w+)$", Qt::CaseInsensitive);
513         QRegExp rxIndex("^INDEX\\s+(\\d+)\\s+([0-9:]+)$", Qt::CaseInsensitive);
514         QRegExp rxTitle("^TITLE\\s+\"([^\"]+)\"$", Qt::CaseInsensitive);
515         QRegExp rxPerformer("^PERFORMER\\s+\"([^\"]+)\"$", Qt::CaseInsensitive);
516         
517         bool bPreamble = true;
518         bool bUnsupportedTrack = false;
519
520         CueSheetFile *currentFile = NULL;
521         CueSheetTrack *currentTrack = NULL;
522
523         m_albumTitle.clear();
524         m_albumPerformer.clear();
525
526         //Loop over the Cue Sheet until all lines were processed
527         for(int lines = 0; lines < INT_MAX; lines++)
528         {
529                 if(application)
530                 {
531                         application->processEvents();
532                         if(lines < 128) Sleep(10);
533                 }
534                 
535                 QByteArray lineData = cueFile.readLine();
536                 if(lineData.size() <= 0)
537                 {
538                         qDebug("End of Cue Sheet file.");
539                         break;
540                 }
541
542                 QString line = bUTF8 ? QString::fromUtf8(lineData.constData(), lineData.size()).trimmed() : QString::fromLocal8Bit(lineData.constData(), lineData.size()).trimmed();
543                 
544                 /* --- FILE --- */
545                 if(rxFile.indexIn(line) >= 0)
546                 {
547                         qDebug("%03d File: <%s> <%s>", lines, rxFile.cap(1).toUtf8().constData(), rxFile.cap(2).toUtf8().constData());
548                         if(currentFile)
549                         {
550                                 if(currentTrack)
551                                 {
552                                         if(currentTrack->isValid())
553                                         {
554                                                 currentFile->addTrack(currentTrack);
555                                                 currentTrack = NULL;
556                                         }
557                                         else
558                                         {
559                                                 LAMEXP_DELETE(currentTrack);
560                                         }
561                                 }
562                                 if(currentFile->isValid())
563                                 {
564                                         m_files.append(currentFile);
565                                         currentFile = NULL;
566                                 }
567                                 else
568                                 {
569                                         LAMEXP_DELETE(currentFile);
570                                 }
571                         }
572                         else
573                         {
574                                 LAMEXP_DELETE(currentTrack);
575                         }
576                         if(!rxFile.cap(2).compare("WAVE", Qt::CaseInsensitive) || !rxFile.cap(2).compare("MP3", Qt::CaseInsensitive) || !rxFile.cap(2).compare("AIFF", Qt::CaseInsensitive))
577                         {
578                                 currentFile = new CueSheetFile(baseDir.absoluteFilePath(rxFile.cap(1)));
579                                 qDebug("%03d File path: <%s>", lines, currentFile->fileName().toUtf8().constData());
580                         }
581                         else
582                         {
583                                 bUnsupportedTrack = true;
584                                 qWarning("%03d Skipping unsupported file of type '%s'.", lines, rxFile.cap(2).toUtf8().constData());
585                                 currentFile = NULL;
586                         }
587                         bPreamble = false;
588                         currentTrack = NULL;
589                         continue;
590                 }
591                 
592                 /* --- TRACK --- */
593                 if(rxTrack.indexIn(line) >= 0)
594                 {
595                         if(currentFile)
596                         {
597                                 qDebug("%03d   Track: <%s> <%s>", lines, rxTrack.cap(1).toUtf8().constData(), rxTrack.cap(2).toUtf8().constData());
598                                 if(currentTrack)
599                                 {
600                                         if(currentTrack->isValid())
601                                         {
602                                                 currentFile->addTrack(currentTrack);
603                                                 currentTrack = NULL;
604                                         }
605                                         else
606                                         {
607                                                 LAMEXP_DELETE(currentTrack);
608                                         }
609                                 }
610                                 if(!rxTrack.cap(2).compare("AUDIO", Qt::CaseInsensitive))
611                                 {
612                                         currentTrack = new CueSheetTrack(currentFile, rxTrack.cap(1).toInt());
613                                 }
614                                 else
615                                 {
616                                         bUnsupportedTrack = true;
617                                         qWarning("%03d   Skipping unsupported track of type '%s'.", lines, rxTrack.cap(2).toUtf8().constData());
618                                         currentTrack = NULL;
619                                 }
620                         }
621                         else
622                         {
623                                 LAMEXP_DELETE(currentTrack);
624                         }
625                         bPreamble = false;
626                         continue;
627                 }
628                 
629                 /* --- INDEX --- */
630                 if(rxIndex.indexIn(line) >= 0)
631                 {
632                         if(currentFile && currentTrack)
633                         {
634                                 qDebug("%03d     Index: <%s> <%s>", lines, rxIndex.cap(1).toUtf8().constData(), rxIndex.cap(2).toUtf8().constData());
635                                 if(rxIndex.cap(1).toInt() == 1)
636                                 {
637                                         currentTrack->setStartIndex(parseTimeIndex(rxIndex.cap(2)));
638                                 }
639                         }
640                         continue;
641                 }
642
643                 /* --- TITLE --- */
644                 if(rxTitle.indexIn(line) >= 0)
645                 {
646                         if(bPreamble)
647                         {
648                                 m_albumTitle = rxTitle.cap(1).simplified();
649                         }
650                         else if(currentFile && currentTrack)
651                         {
652                                 qDebug("%03d     Title: <%s>", lines, rxTitle.cap(1).toUtf8().constData());
653                                 currentTrack->setTitle(rxTitle.cap(1));
654                         }
655                         continue;
656                 }
657
658                 /* --- PERFORMER --- */
659                 if(rxPerformer.indexIn(line) >= 0)
660                 {
661                         if(bPreamble)
662                         {
663                                 m_albumPerformer = rxPerformer.cap(1).simplified();
664                         }
665                         else if(currentFile && currentTrack)
666                         {
667                                 qDebug("%03d     Title: <%s>", lines, rxPerformer.cap(1).toUtf8().constData());
668                                 currentTrack->setPerformer(rxPerformer.cap(1));
669                         }
670                         continue;
671                 }
672         }
673
674         //Append the very last track/file that is still pending
675         if(currentFile)
676         {
677                 if(currentTrack)
678                 {
679                         if(currentTrack->isValid())
680                         {
681                                 currentFile->addTrack(currentTrack);
682                                 currentTrack = NULL;
683                         }
684                         else
685                         {
686                                 LAMEXP_DELETE(currentTrack);
687                         }
688                 }
689                 if(currentFile->isValid())
690                 {
691                         m_files.append(currentFile);
692                         currentFile = NULL;
693                 }
694                 else
695                 {
696                         LAMEXP_DELETE(currentFile);
697                 }
698         }
699
700         //Finally calculate duration of each track
701         int nFiles = m_files.count();
702         for(int i = 0; i < nFiles; i++)
703         {
704                 if(application)
705                 {
706                         application->processEvents();
707                         Sleep(10);
708                 }
709
710                 CueSheetFile *currentFile = m_files.at(i);
711                 int nTracks = currentFile->trackCount();
712                 if(nTracks > 1)
713                 {
714                         for(int j = 1; j < nTracks; j++)
715                         {
716                                 CueSheetTrack *currentTrack = currentFile->track(j);
717                                 CueSheetTrack *previousTrack = currentFile->track(j-1);
718                                 double duration = currentTrack->startIndex() - previousTrack->startIndex();
719                                 previousTrack->setDuration(max(0.0, duration));
720                         }
721                 }
722         }
723         
724         //Sanity check of track numbers
725         if(nFiles > 0)
726         {
727                 int previousTrackNo = -1;
728                 bool trackNo[100];
729                 for(int i = 0; i < 100; i++)
730                 {
731                         trackNo[i] = false;
732                 }
733                 for(int i = 0; i < nFiles; i++)
734                 {
735                         if(application)
736                         {
737                                 application->processEvents();
738                                 Sleep(10);
739                         }
740                         CueSheetFile *currentFile = m_files.at(i);
741                         int nTracks = currentFile->trackCount();
742                         if(nTracks > 1)
743                         {
744                                 for(int j = 0; j < nTracks; j++)
745                                 {
746                                         int currentTrackNo = currentFile->track(j)->trackNo();
747                                         if(currentTrackNo > 99)
748                                         {
749                                                 qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo);
750                                                 return ErrorInconsistent;
751                                         }
752                                         if(currentTrackNo <= previousTrackNo)
753                                         {
754                                                 qWarning("Non-increasing track numbers, Cue Sheet is inconsistent!", currentTrackNo);
755                                                 return ErrorInconsistent;
756                                         }
757                                         if(trackNo[currentTrackNo])
758                                         {
759                                                 qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo);
760                                                 return ErrorInconsistent;
761                                         }
762                                         trackNo[currentTrackNo] = true;
763                                         previousTrackNo = currentTrackNo;
764                                 }
765                         }
766                 }
767                 return ErrorSuccess;
768         }
769         else
770         {
771                 return bUnsupportedTrack ? ErrorUnsupported : ErrorBadFile;
772         }
773 }
774
775 double CueSheetModel::parseTimeIndex(const QString &index)
776 {
777         QRegExp rxTimeIndex("\\s*(\\d+)\\s*:\\s*(\\d+)\\s*:\\s*(\\d+)\\s*");
778         
779         if(rxTimeIndex.indexIn(index) >= 0)
780         {
781                 int min, sec, frm;
782                 bool minOK, secOK, frmOK;
783
784                 min = rxTimeIndex.cap(1).toInt(&minOK);
785                 sec = rxTimeIndex.cap(2).toInt(&secOK);
786                 frm = rxTimeIndex.cap(3).toInt(&frmOK);
787
788                 if(minOK && secOK && frmOK)
789                 {
790                         return static_cast<double>(60 * min) + static_cast<double>(sec) + (static_cast<double>(frm) / 75.0);
791                 }
792         }
793
794         qWarning("    Bad time index: '%s'", index.toUtf8().constData());
795         return std::numeric_limits<double>::quiet_NaN();
796 }
797
798 QString CueSheetModel::indexToString(const double index) const
799 {
800         if(!_finite(index) || (index < 0.0) || (index > 86400.0))
801         {
802                 return QString("??:??.???");
803         }
804         
805         QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
806
807         if(time.minute() < 100)
808         {
809                 return time.toString("mm:ss.zzz");
810         }
811         else
812         {
813                 return QString("99:99.999");
814         }
815 }