OSDN Git Service

Introduce QUTF8 macro and replace ".toUtf8().constData()" everywhere.
[lamexp/LameXP.git] / src / Model_FileList.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2013 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 "Model_FileList.h"
23
24 #include "Global.h"
25
26 #include <QFileInfo>
27 #include <QDir>
28 #include <QFile>
29 #include <QTextCodec>
30 #include <QTextStream>
31 #include <QInputDialog>
32
33 #define EXPAND(STR) QString(STR).leftJustified(96, ' ')
34 #define CHECK_HDR(STR,NAM) (!(STR).compare((NAM), Qt::CaseInsensitive))
35 #define MAKE_KEY(PATH) (QDir::fromNativeSeparators(PATH).toLower())
36
37 ////////////////////////////////////////////////////////////
38 // Constructor & Destructor
39 ////////////////////////////////////////////////////////////
40
41 FileListModel::FileListModel(void)
42 :
43         m_blockUpdates(false),
44         m_fileIcon(":/icons/page_white_cd.png")
45 {
46 }
47
48 FileListModel::~FileListModel(void)
49 {
50 }
51
52 ////////////////////////////////////////////////////////////
53 // Public Functions
54 ////////////////////////////////////////////////////////////
55
56 int FileListModel::columnCount(const QModelIndex &parent) const
57 {
58         return 2;
59 }
60
61 int FileListModel::rowCount(const QModelIndex &parent) const
62 {
63         return m_fileList.count();
64 }
65
66 QVariant FileListModel::data(const QModelIndex &index, int role) const
67 {
68         if((role == Qt::DisplayRole || role == Qt::ToolTipRole) && index.row() < m_fileList.count() && index.row() >= 0)
69         {
70                 switch(index.column())
71                 {
72                 case 0:
73                         return m_fileStore.value(m_fileList.at(index.row())).metaInfo().title();
74                         break;
75                 case 1:
76                         return QDir::toNativeSeparators(m_fileStore.value(m_fileList.at(index.row())).filePath());
77                         break;
78                 default:
79                         return QVariant();
80                         break;
81                 }               
82         }
83         else if(role == Qt::DecorationRole && index.column() == 0)
84         {
85                 return m_fileIcon;
86         }
87         else
88         {
89                 return QVariant();
90         }
91 }
92
93 QVariant FileListModel::headerData(int section, Qt::Orientation orientation, int role) const
94 {
95         if(role == Qt::DisplayRole)
96         {
97                 if(orientation == Qt::Horizontal)
98                 {
99                         switch(section)
100                         {
101                         case 0:
102                                 return QVariant(tr("Title"));
103                                 break;
104                         case 1:
105                                 return QVariant(tr("Full Path"));
106                                 break;
107                         default:
108                                 return QVariant();
109                                 break;
110                         }
111                 }
112                 else
113                 {
114                         if(m_fileList.count() < 10)
115                         {
116                                 return QVariant(QString().sprintf("%d", section + 1));
117                         }
118                         else if(m_fileList.count() < 100)
119                         {
120                                 return QVariant(QString().sprintf("%02d", section + 1));
121                         }
122                         else if(m_fileList.count() < 1000)
123                         {
124                                 return QVariant(QString().sprintf("%03d", section + 1));
125                         }
126                         else
127                         {
128                                 return QVariant(QString().sprintf("%04d", section + 1));
129                         }
130                 }
131         }
132         else
133         {
134                 return QVariant();
135         }
136 }
137
138 void FileListModel::addFile(const QString &filePath)
139 {
140         QFileInfo fileInfo(filePath);
141         const QString key = MAKE_KEY(fileInfo.canonicalFilePath()); 
142         const bool flag = (!m_blockUpdates);
143
144         if(!m_fileStore.contains(key))
145         {
146                 AudioFileModel audioFile(fileInfo.canonicalFilePath());
147                 audioFile.metaInfo().setTitle(fileInfo.baseName());
148                 if(flag) beginInsertRows(QModelIndex(), m_fileList.count(), m_fileList.count());
149                 m_fileStore.insert(key, audioFile);
150                 m_fileList.append(key);
151                 if(flag) endInsertRows();
152                 emit rowAppended();
153         }
154 }
155
156 void FileListModel::addFile(const AudioFileModel &file)
157 {
158         const QString key = MAKE_KEY(file.filePath()); 
159         const bool flag = (!m_blockUpdates);
160
161         if(!m_fileStore.contains(key))
162         {
163                 if(flag) beginInsertRows(QModelIndex(), m_fileList.count(), m_fileList.count());
164                 m_fileStore.insert(key, file);
165                 m_fileList.append(key);
166                 if(flag) endInsertRows();
167                 emit rowAppended();
168         }
169 }
170
171 bool FileListModel::removeFile(const QModelIndex &index)
172 {
173         if(index.row() >= 0 && index.row() < m_fileList.count())
174         {
175                 beginResetModel();
176                 m_fileStore.remove(m_fileList.at(index.row()));
177                 m_fileList.removeAt(index.row());
178                 endResetModel();
179                 return true;
180         }
181         else
182         {
183                 return false;
184         }
185 }
186
187 void FileListModel::clearFiles(void)
188 {
189         beginResetModel();
190         m_fileList.clear();
191         m_fileStore.clear();
192         endResetModel();
193 }
194
195 bool FileListModel::moveFile(const QModelIndex &index, int delta)
196 {
197         if(delta != 0 && index.row() >= 0 && index.row() < m_fileList.count() && index.row() + delta >= 0 && index.row() + delta < m_fileList.count())
198         {
199                 beginResetModel();
200                 m_fileList.move(index.row(), index.row() + delta);
201                 endResetModel();
202                 return true;
203         }
204         else
205         {
206                 return false;
207         }
208 }
209
210 const AudioFileModel &FileListModel::getFile(const QModelIndex &index)
211 {
212         if(index.row() >= 0 && index.row() < m_fileList.count())
213         {
214                 return m_fileStore[m_fileList.at(index.row())];         //return m_fileStore.value(m_fileList.at(index.row()));
215         }
216         else
217         {
218                 return m_nullAudioFile;
219         }
220 }
221
222 AudioFileModel &FileListModel::operator[] (const QModelIndex &index)
223 {
224         const QString key = m_fileList.at(index.row());
225         return m_fileStore[key];
226 }
227
228 bool FileListModel::setFile(const QModelIndex &index, const AudioFileModel &audioFile)
229 {
230         if(index.row() >= 0 && index.row() < m_fileList.count())
231         {
232                 const QString oldKey = m_fileList.at(index.row());
233                 const QString newKey = MAKE_KEY(audioFile.filePath());
234                 
235                 beginResetModel();
236                 m_fileList.replace(index.row(), newKey);
237                 m_fileStore.remove(oldKey);
238                 m_fileStore.insert(newKey, audioFile);
239                 endResetModel();
240                 return true;
241         }
242         else
243         {
244                 return false;
245         }
246 }
247
248 int FileListModel::exportToCsv(const QString &outFile)
249 {
250         const int nFiles = m_fileList.count();
251         
252         bool havePosition = false, haveTitle = false, haveArtist = false, haveAlbum = false, haveGenre = false, haveYear = false, haveComment = false;
253         
254         for(int i = 0; i < nFiles; i++)
255         {
256                 const AudioFileModel &current = m_fileStore.value(m_fileList.at(i));
257                 const AudioFileModel_MetaInfo &metaInfo = current.metaInfo();
258                 
259                 if(metaInfo.position() > 0) havePosition = true;
260                 if(!metaInfo.title().isEmpty()) haveTitle = true;
261                 if(!metaInfo.artist().isEmpty()) haveArtist = true;
262                 if(!metaInfo.album().isEmpty()) haveAlbum = true;
263                 if(!metaInfo.genre().isEmpty()) haveGenre = true;
264                 if(metaInfo.year() > 0) haveYear = true;
265                 if(!metaInfo.comment().isEmpty()) haveComment = true;
266         }
267
268         if(!(haveTitle || haveArtist || haveAlbum || haveGenre || haveYear || haveComment))
269         {
270                 return CsvError_NoTags;
271         }
272
273         QFile file(outFile);
274
275         if(!file.open(QIODevice::WriteOnly))
276         {
277                 return CsvError_FileOpen;
278         }
279         else
280         {
281                 QStringList line;
282                 
283                 if(havePosition) line << "POSITION";
284                 if(haveTitle) line << "TITLE";
285                 if(haveArtist) line << "ARTIST";
286                 if(haveAlbum) line << "ALBUM";
287                 if(haveGenre) line << "GENRE";
288                 if(haveYear) line << "YEAR";
289                 if(haveComment) line << "COMMENT";
290
291                 if(file.write(line.join(";").append("\r\n").toUtf8().prepend("\xef\xbb\xbf")) < 1)
292                 {
293                         file.close();
294                         return CsvError_FileWrite;
295                 }
296         }
297
298         for(int i = 0; i < nFiles; i++)
299         {
300                 QStringList line;
301                 const AudioFileModel &current = m_fileStore.value(m_fileList.at(i));
302                 const AudioFileModel_MetaInfo &metaInfo = current.metaInfo();
303                 
304                 if(havePosition) line << QString::number(metaInfo.position());
305                 if(haveTitle) line << metaInfo.title().trimmed();
306                 if(haveArtist) line << metaInfo.artist().trimmed();
307                 if(haveAlbum) line << metaInfo.album().trimmed();
308                 if(haveGenre) line << metaInfo.genre().trimmed();
309                 if(haveYear) line << QString::number(metaInfo.year());
310                 if(haveComment) line << metaInfo.comment().trimmed();
311
312                 if(file.write(line.replaceInStrings(";", ",").join(";").append("\r\n").toUtf8()) < 1)
313                 {
314                         file.close();
315                         return CsvError_FileWrite;
316                 }
317         }
318
319         file.close();
320         return CsvError_OK;
321 }
322
323 int FileListModel::importFromCsv(QWidget *parent, const QString &inFile)
324 {
325         QFile file(inFile);
326         if(!file.open(QIODevice::ReadOnly))
327         {
328                 return CsvError_FileOpen;
329         }
330
331         QTextCodec *codec = NULL;
332         QByteArray bomCheck = file.peek(16);
333
334         if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
335         {
336                 codec = QTextCodec::codecForName("UTF-8");
337         }
338         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
339         {
340                 codec = QTextCodec::codecForName("UTF-16LE");
341         }
342         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
343         {
344                 codec = QTextCodec::codecForName("UTF-16BE");
345         }
346         else
347         {
348                 const QString systemDefault = tr("(System Default)");
349
350                 QStringList codecList;
351                 codecList.append(systemDefault);
352                 codecList.append(lamexp_available_codepages());
353
354                 QInputDialog *input = new QInputDialog(parent);
355                 input->setLabelText(EXPAND(tr("Select ANSI Codepage for CSV file:")));
356                 input->setOkButtonText(tr("OK"));
357                 input->setCancelButtonText(tr("Cancel"));
358                 input->setTextEchoMode(QLineEdit::Normal);
359                 input->setComboBoxItems(codecList);
360         
361                 if(input->exec() < 1)
362                 {
363                         LAMEXP_DELETE(input);
364                         return CsvError_Aborted;
365                 }
366         
367                 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
368                 {
369                         qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
370                         codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
371                 }
372                 else
373                 {
374                         qDebug("Going to use the system's default codec!");
375                         codec = QTextCodec::codecForName("System");
376                 }
377
378                 LAMEXP_DELETE(input);
379         }
380
381         bomCheck.clear();
382
383         //----------------------//
384
385         QTextStream stream(&file);
386         stream.setAutoDetectUnicode(false);
387         stream.setCodec(codec);
388
389         QString headerLine = stream.readLine().simplified();
390
391         while(headerLine.isEmpty())
392         {
393                 if(stream.atEnd())
394                 {
395                         qWarning("The file appears to be empty!");
396                         return CsvError_FileRead;
397                 }
398                 qWarning("Skipping a blank line at beginning of CSV file!");
399                 headerLine = stream.readLine().simplified();
400         }
401
402         QStringList header = headerLine.split(";", QString::KeepEmptyParts);
403
404         const int nCols = header.count();
405         const int nFiles = m_fileList.count();
406
407         if(nCols < 1)
408         {
409                 qWarning("Header appears to be empty!");
410                 return CsvError_FileRead;
411         }
412
413         bool *ignore = new bool[nCols];
414         memset(ignore, 0, sizeof(bool) * nCols);
415
416         for(int i = 0; i < nCols; i++)
417         {
418                 if((header[i] = header[i].trimmed()).isEmpty())
419                 {
420                         ignore[i] = true;
421                 }
422         }
423
424         //----------------------//
425
426         for(int i = 0; i < nFiles; i++)
427         {
428                 if(stream.atEnd())
429                 {
430                         LAMEXP_DELETE_ARRAY(ignore);
431                         return CsvError_Incomplete;
432                 }
433                 
434                 QString line = stream.readLine().simplified();
435                 
436                 if(line.isEmpty())
437                 {
438                         qWarning("Skipping a blank line in CSV file!");
439                         continue;
440                 }
441                 
442                 QStringList data = line.split(";", QString::KeepEmptyParts);
443
444                 if(data.count() < header.count())
445                 {
446                         qWarning("Skipping an incomplete line in CSV file!");
447                         continue;
448                 }
449
450                 const QString key = m_fileList[i];
451
452                 for(int j = 0; j < nCols; j++)
453                 {
454                         if(ignore[j])
455                         {
456                                 continue;
457                         }
458                         else if(CHECK_HDR(header.at(j), "POSITION"))
459                         {
460                                 bool ok = false;
461                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
462                                 if(ok) m_fileStore[key].metaInfo().setPosition(temp);
463                         }
464                         else if(CHECK_HDR(header.at(j), "TITLE"))
465                         {
466                                 QString temp = data.at(j).trimmed();
467                                 if(!temp.isEmpty()) m_fileStore[key].metaInfo().setTitle(temp);
468                         }
469                         else if(CHECK_HDR(header.at(j), "ARTIST"))
470                         {
471                                 QString temp = data.at(j).trimmed();
472                                 if(!temp.isEmpty()) m_fileStore[key].metaInfo().setArtist(temp);
473                         }
474                         else if(CHECK_HDR(header.at(j), "ALBUM"))
475                         {
476                                 QString temp = data.at(j).trimmed();
477                                 if(!temp.isEmpty()) m_fileStore[key].metaInfo().setAlbum(temp);
478                         }
479                         else if(CHECK_HDR(header.at(j), "GENRE"))
480                         {
481                                 QString temp = data.at(j).trimmed();
482                                 if(!temp.isEmpty()) m_fileStore[key].metaInfo().setGenre(temp);
483                         }
484                         else if(CHECK_HDR(header.at(j), "YEAR"))
485                         {
486                                 bool ok = false;
487                                 unsigned int temp = data.at(j).trimmed().toUInt(&ok);
488                                 if(ok) m_fileStore[key].metaInfo().setYear(temp);
489                         }
490                         else if(CHECK_HDR(header.at(j), "COMMENT"))
491                         {
492                                 QString temp = data.at(j).trimmed();
493                                 if(!temp.isEmpty()) m_fileStore[key].metaInfo().setComment(temp);
494                         }
495                         else
496                         {
497                                 qWarning("Unkonw field '%s' will be ignored!", QUTF8(header.at(j)));
498                                 ignore[j] = true;
499                                 
500                                 if(!checkArray(ignore, false, nCols))
501                                 {
502                                         qWarning("No known fields left, aborting!");
503                                         return CsvError_NoTags;
504                                 }
505                         }
506                 }
507         }
508
509         //----------------------//
510
511         LAMEXP_DELETE_ARRAY(ignore);
512         return CsvError_OK;
513 }
514
515 bool FileListModel::checkArray(const bool *a, const bool val, size_t len)
516 {
517         for(size_t i = 0; i < len; i++)
518         {
519                 if(a[i] == val) return true;
520         }
521
522         return false;
523 }