OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / shared / qrceditor / resourcefile.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "resourcefile_p.h"
34
35 #include <QtCore/QCoreApplication>
36 #include <QtCore/QDebug>
37 #include <QtCore/QDir>
38 #include <QtCore/QFile>
39 #include <QtCore/QMimeData>
40 #include <QtCore/QtAlgorithms>
41 #include <QtCore/QTextStream>
42
43 #include <QtGui/QIcon>
44 #include <QtGui/QImageReader>
45
46 #include <QtXml/QDomDocument>
47
48 QT_BEGIN_NAMESPACE
49
50
51 /*
52 TRANSLATOR qdesigner_internal::ResourceModel
53 */
54
55 static QString msgFileNameEmpty()
56 {
57     return QCoreApplication::translate("Designer", "The file name is empty.");
58 }
59
60 namespace qdesigner_internal {
61
62
63 /******************************************************************************
64 ** FileList
65 */
66
67 bool FileList::containsFile(File *file)
68 {
69     foreach (const File *tmpFile, *this)
70         if (tmpFile->name == file->name && tmpFile->prefix() == file->prefix())
71             return true;
72     return false;
73 }
74
75 /******************************************************************************
76 ** ResourceFile
77 */
78
79 ResourceFile::ResourceFile(const QString &file_name)
80 {
81     setFileName(file_name);
82 }
83
84 ResourceFile::~ResourceFile()
85 {
86     clearPrefixList();
87 }
88
89 bool ResourceFile::load()
90 {
91     m_error_message.clear();
92
93     if (m_file_name.isEmpty()) {
94         m_error_message = msgFileNameEmpty();
95         return false;
96     }
97
98     QFile file(m_file_name);
99     if (!file.open(QIODevice::ReadOnly)) {
100         m_error_message = file.errorString();
101         return false;
102     }
103
104     clearPrefixList();
105
106     QDomDocument doc;
107
108     QString error_msg;
109     int error_line, error_col;
110     if (!doc.setContent(&file, &error_msg, &error_line, &error_col)) {
111         m_error_message = QCoreApplication::translate("Designer", "XML error on line %1, col %2: %3")
112                     .arg(error_line).arg(error_col).arg(error_msg);
113         return false;
114     }
115
116     QDomElement root = doc.firstChildElement(QLatin1String("RCC"));
117     if (root.isNull()) {
118         m_error_message = QCoreApplication::translate("Designer", "The <RCC> root element is missing.");
119         return false;
120     }
121
122     QDomElement relt = root.firstChildElement(QLatin1String("qresource"));
123     for (; !relt.isNull(); relt = relt.nextSiblingElement(QLatin1String("qresource"))) {
124
125         QString prefix = fixPrefix(relt.attribute(QLatin1String("prefix")));
126         if (prefix.isEmpty())
127             prefix = QString(QLatin1Char('/'));
128         const QString language = relt.attribute(QLatin1String("lang"));
129
130         const int idx = indexOfPrefix(prefix);
131         Prefix * p = 0;
132         if (idx == -1) {
133             p = new Prefix(prefix, language);
134             m_prefix_list.append(p);
135         } else {
136             p = m_prefix_list[idx];
137         }
138         Q_ASSERT(p);
139
140         QDomElement felt = relt.firstChildElement(QLatin1String("file"));
141         for (; !felt.isNull(); felt = felt.nextSiblingElement(QLatin1String("file"))) {
142             const QString fileName = absolutePath(felt.text());
143             const QString alias = felt.attribute(QLatin1String("alias"));
144             File * const file = new File(p, fileName, alias);
145             p->file_list.append(file);
146         }
147     }
148
149     return true;
150 }
151
152 bool ResourceFile::save()
153 {
154     m_error_message.clear();
155
156     if (m_file_name.isEmpty()) {
157         m_error_message = msgFileNameEmpty();
158         return false;
159     }
160
161     QFile file(m_file_name);
162     if (!file.open(QIODevice::WriteOnly)) {
163         m_error_message = file.errorString();
164         return false;
165     }
166
167     QDomDocument doc;
168     QDomElement root = doc.createElement(QLatin1String("RCC"));
169     doc.appendChild(root);
170
171     const QStringList name_list = prefixList();
172
173     foreach (const QString &name, name_list) {
174         FileList file_list;
175         QString lang;
176         foreach (const Prefix *pref, m_prefix_list) {
177             if (pref->name == name){
178                 file_list += pref->file_list;
179                 lang = pref->lang;
180             }
181         }
182
183         QDomElement relt = doc.createElement(QLatin1String("qresource"));
184         root.appendChild(relt);
185         relt.setAttribute(QLatin1String("prefix"), name);
186         if (!lang.isEmpty())
187             relt.setAttribute(QLatin1String("lang"), lang);
188
189         foreach (const File *f, file_list) {
190             const File &file = *f;
191             QDomElement felt = doc.createElement(QLatin1String("file"));
192             relt.appendChild(felt);
193             const QString conv_file = relativePath(file.name).replace(QDir::separator(), QLatin1Char('/'));
194             const QDomText text = doc.createTextNode(conv_file);
195             felt.appendChild(text);
196             if (!file.alias.isEmpty())
197                 felt.setAttribute(QLatin1String("alias"), file.alias);
198         }
199     }
200
201     QTextStream stream(&file);
202     doc.save(stream, 4);
203
204     return true;
205 }
206
207 bool ResourceFile::split(const QString &_path, QString *prefix, QString *file) const
208 {
209     prefix->clear();
210     file->clear();
211
212     QString path = _path;
213     if (!path.startsWith(QLatin1Char(':')))
214         return false;
215     path = path.mid(1);
216
217     for (int i = 0; i < m_prefix_list.size(); ++i) {
218         Prefix const * const &pref = m_prefix_list.at(i);
219         if (!path.startsWith(pref->name))
220             continue;
221
222         *prefix = pref->name;
223         if (pref->name == QString(QLatin1Char('/')))
224             *file = path.mid(1);
225         else
226             *file = path.mid(pref->name.size() + 1);
227
228         const QString filePath = absolutePath(*file);
229
230         for (int j = 0; j < pref->file_list.count(); j++) {
231             File const * const &f = pref->file_list.at(j);
232             if (!f->alias.isEmpty()) {
233                 if (absolutePath(f->alias) == filePath) {
234                     *file = f->name;
235                     return true;
236                 }
237             } else if (f->name == filePath)
238                 return true;
239         }
240     }
241
242     return false;
243 }
244
245 QString ResourceFile::resolvePath(const QString &path) const
246 {
247     QString prefix, file;
248     if (split(path, &prefix, &file))
249         return absolutePath(file);
250
251     return QString();
252 }
253
254 QStringList ResourceFile::prefixList() const
255 {
256     QStringList result;
257     for (int i = 0; i < m_prefix_list.size(); ++i)
258         result.append(m_prefix_list.at(i)->name);
259     return result;
260 }
261
262 bool ResourceFile::isEmpty() const
263 {
264     return m_file_name.isEmpty() && m_prefix_list.isEmpty();
265 }
266
267 QStringList ResourceFile::fileList(int pref_idx) const
268 {
269     QStringList result;
270     Q_ASSERT(pref_idx >= 0 && pref_idx < m_prefix_list.count());
271     const FileList &abs_file_list = m_prefix_list.at(pref_idx)->file_list;
272     foreach (const File *abs_file, abs_file_list)
273         result.append(relativePath(abs_file->name));
274     return result;
275 }
276
277 void ResourceFile::addFile(int prefix_idx, const QString &file, int file_idx)
278 {
279     Prefix * const p = m_prefix_list[prefix_idx];
280     Q_ASSERT(p);
281     FileList &files = p->file_list;
282     Q_ASSERT(file_idx >= -1 && file_idx <= files.size());
283     if (file_idx == -1)
284         file_idx = files.size();
285     files.insert(file_idx, new File(p, absolutePath(file)));
286 }
287
288 void ResourceFile::addPrefix(const QString &prefix, int prefix_idx)
289 {
290     QString fixed_prefix = fixPrefix(prefix);
291     if (indexOfPrefix(fixed_prefix) != -1)
292         return;
293
294     Q_ASSERT(prefix_idx >= -1 && prefix_idx <= m_prefix_list.size());
295     if (prefix_idx == -1)
296         prefix_idx = m_prefix_list.size();
297     m_prefix_list.insert(prefix_idx, new Prefix(fixed_prefix));
298 }
299
300 void ResourceFile::removePrefix(int prefix_idx)
301 {
302     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
303     Prefix * const p = m_prefix_list.at(prefix_idx);
304     delete p;
305     m_prefix_list.removeAt(prefix_idx);
306 }
307
308 void ResourceFile::removeFile(int prefix_idx, int file_idx)
309 {
310     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
311     FileList &fileList = m_prefix_list[prefix_idx]->file_list;
312     Q_ASSERT(file_idx >= 0 && file_idx < fileList.count());
313     delete fileList.at(file_idx);
314     fileList.removeAt(file_idx);
315 }
316
317 void ResourceFile::replacePrefix(int prefix_idx, const QString &prefix)
318 {
319     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
320     m_prefix_list[prefix_idx]->name = fixPrefix(prefix);
321 }
322
323 void ResourceFile::replaceLang(int prefix_idx, const QString &lang)
324 {
325     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
326     m_prefix_list[prefix_idx]->lang = lang;
327 }
328
329 void ResourceFile::replaceAlias(int prefix_idx, int file_idx, const QString &alias)
330 {
331     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
332     FileList &fileList = m_prefix_list.at(prefix_idx)->file_list;
333     Q_ASSERT(file_idx >= 0 && file_idx < fileList.count());
334     fileList[file_idx]->alias = alias;
335 }
336
337
338 void ResourceFile::replaceFile(int pref_idx, int file_idx, const QString &file)
339 {
340     Q_ASSERT(pref_idx >= 0 && pref_idx < m_prefix_list.count());
341     FileList &fileList = m_prefix_list.at(pref_idx)->file_list;
342     Q_ASSERT(file_idx >= 0 && file_idx < fileList.count());
343     fileList[file_idx]->name = file;
344 }
345
346 int ResourceFile::indexOfPrefix(const QString &prefix) const
347 {
348     QString fixed_prefix = fixPrefix(prefix);
349     for (int i = 0; i < m_prefix_list.size(); ++i) {
350         if (m_prefix_list.at(i)->name == fixed_prefix)
351             return i;
352     }
353     return -1;
354 }
355
356 int ResourceFile::indexOfFile(int pref_idx, const QString &file) const
357 {
358     Q_ASSERT(pref_idx >= 0 && pref_idx < m_prefix_list.count());
359     Prefix * const p = m_prefix_list.at(pref_idx);
360     File equalFile(p, absolutePath(file));
361     return p->file_list.indexOf(&equalFile);
362 }
363
364 QString ResourceFile::relativePath(const QString &abs_path) const
365 {
366     if (m_file_name.isEmpty() || QFileInfo(abs_path).isRelative())
367          return abs_path;
368
369     QFileInfo fileInfo(m_file_name);
370     return fileInfo.absoluteDir().relativeFilePath(abs_path);
371 }
372
373 QString ResourceFile::absolutePath(const QString &rel_path) const
374 {
375     const QFileInfo fi(rel_path);
376     if (fi.isAbsolute())
377         return rel_path;
378
379     QString rc = QFileInfo(m_file_name).path();
380     rc +=  QDir::separator();
381     rc += rel_path;
382     return QDir::cleanPath(rc);
383 }
384
385 bool ResourceFile::contains(const QString &prefix, const QString &file) const
386 {
387     int pref_idx = indexOfPrefix(prefix);
388     if (pref_idx == -1)
389         return false;
390     if (file.isEmpty())
391         return true;
392     Q_ASSERT(pref_idx >= 0 && pref_idx < m_prefix_list.count());
393     Prefix * const p = m_prefix_list.at(pref_idx);
394     Q_ASSERT(p);
395     File equalFile(p, absolutePath(file));
396     return p->file_list.containsFile(&equalFile);
397 }
398
399 bool ResourceFile::contains(int pref_idx, const QString &file) const
400 {
401     Q_ASSERT(pref_idx >= 0 && pref_idx < m_prefix_list.count());
402     Prefix * const p = m_prefix_list.at(pref_idx);
403     File equalFile(p, absolutePath(file));
404     return p->file_list.containsFile(&equalFile);
405 }
406
407 /*static*/ QString ResourceFile::fixPrefix(const QString &prefix)
408 {
409     const QChar slash = QLatin1Char('/');
410     QString result = QString(slash);
411     for (int i = 0; i < prefix.size(); ++i) {
412         const QChar c = prefix.at(i);
413         if (c == slash && result.at(result.size() - 1) == slash)
414             continue;
415         result.append(c);
416     }
417
418     if (result.size() > 1 && result.endsWith(slash))
419         result = result.mid(0, result.size() - 1);
420
421     return result;
422 }
423
424 int ResourceFile::prefixCount() const
425 {
426     return m_prefix_list.size();
427 }
428
429 QString ResourceFile::prefix(int idx) const
430 {
431     Q_ASSERT((idx >= 0) && (idx < m_prefix_list.count()));
432     return m_prefix_list.at(idx)->name;
433 }
434
435 QString ResourceFile::lang(int idx) const
436 {
437     Q_ASSERT(idx >= 0 && idx < m_prefix_list.count());
438     return m_prefix_list.at(idx)->lang;
439 }
440
441 int ResourceFile::fileCount(int prefix_idx) const
442 {
443     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
444     return m_prefix_list.at(prefix_idx)->file_list.size();
445 }
446
447 QString ResourceFile::file(int prefix_idx, int file_idx) const
448 {
449     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
450     FileList &fileList = m_prefix_list.at(prefix_idx)->file_list;
451     Q_ASSERT(file_idx >= 0 && file_idx < fileList.count());
452     return fileList.at(file_idx)->name;
453 }
454
455 QString ResourceFile::alias(int prefix_idx, int file_idx) const
456 {
457     Q_ASSERT(prefix_idx >= 0 && prefix_idx < m_prefix_list.count());
458     FileList &fileList = m_prefix_list.at(prefix_idx)->file_list;
459     Q_ASSERT(file_idx >= 0 && file_idx < fileList.count());
460     return fileList.at(file_idx)->alias;
461 }
462
463 void * ResourceFile::prefixPointer(int prefixIndex) const
464 {
465     Q_ASSERT(prefixIndex >= 0 && prefixIndex < m_prefix_list.count());
466     return m_prefix_list.at(prefixIndex);
467 }
468
469 void * ResourceFile::filePointer(int prefixIndex, int fileIndex) const
470 {
471     Q_ASSERT(prefixIndex >= 0 && prefixIndex < m_prefix_list.count());
472     FileList &fileList = m_prefix_list.at(prefixIndex)->file_list;
473     Q_ASSERT(fileIndex >= 0 && fileIndex < fileList.count());
474     return fileList.at(fileIndex);
475 }
476
477 int ResourceFile::prefixPointerIndex(const Prefix *prefix) const
478 {
479     int const count = m_prefix_list.count();
480     for (int i = 0; i < count; i++) {
481         Prefix * const other = m_prefix_list.at(i);
482         if (*other == *prefix)
483             return i;
484     }
485     return -1;
486 }
487
488 void ResourceFile::clearPrefixList()
489 {
490     qDeleteAll(m_prefix_list);
491     m_prefix_list.clear();
492 }
493
494 /******************************************************************************
495 ** ResourceModel
496 */
497
498 ResourceModel::ResourceModel(const ResourceFile &resource_file, QObject *parent)
499     : QAbstractItemModel(parent), m_resource_file(resource_file),  m_dirty(false)
500 {
501     // Only action that works for QListWidget and the like.
502     setSupportedDragActions(Qt::CopyAction);
503 }
504
505 void ResourceModel::setDirty(bool b)
506 {
507     if (b == m_dirty)
508         return;
509
510     m_dirty = b;
511     emit dirtyChanged(b);
512 }
513
514 QModelIndex ResourceModel::index(int row, int column, const QModelIndex &parent) const
515 {
516     if (column != 0)
517         return QModelIndex();
518
519     void * internalPointer = 0;
520     if (parent.isValid()) {
521         void * const pip = parent.internalPointer();
522         if (pip == 0)
523             return QModelIndex();
524
525         // File node
526         Node * const node = reinterpret_cast<Node *>(pip);
527         Prefix * const prefix = node->prefix();
528         Q_ASSERT(prefix);
529         if (row < 0 || row >= prefix->file_list.count())
530             return QModelIndex();
531         const int prefixIndex = m_resource_file.prefixPointerIndex(prefix);
532         const int fileIndex = row;
533         internalPointer = m_resource_file.filePointer(prefixIndex, fileIndex);
534     } else {
535         // Prefix node
536         if (row < 0 || row >= m_resource_file.prefixCount())
537             return QModelIndex();
538         internalPointer = m_resource_file.prefixPointer(row);
539     }
540     Q_ASSERT(internalPointer);
541     return createIndex(row, 0, internalPointer);
542 }
543
544 QModelIndex ResourceModel::parent(const QModelIndex &index) const
545 {
546     if (!index.isValid())
547         return QModelIndex();
548
549     void * const internalPointer = index.internalPointer();
550     if (internalPointer == 0)
551         return QModelIndex();
552     Node * const node = reinterpret_cast<Node *>(internalPointer);
553     Prefix * const prefix = node->prefix();
554     Q_ASSERT(prefix);
555     bool const isFileNode = (prefix != node);
556
557     if (isFileNode) {
558         const int row = m_resource_file.prefixPointerIndex(prefix);
559         Q_ASSERT(row >= 0);
560         return createIndex(row, 0, prefix);
561     } else {
562         return QModelIndex();
563     }
564 }
565
566 int ResourceModel::rowCount(const QModelIndex &parent) const
567 {
568     if (parent.isValid()) {
569         void * const internalPointer = parent.internalPointer();
570         Node * const node = reinterpret_cast<Node *>(internalPointer);
571         Prefix * const prefix = node->prefix();
572         Q_ASSERT(prefix);
573         bool const isFileNode = (prefix != node);
574
575         if (isFileNode) {
576             return 0;
577         } else {
578             return prefix->file_list.count();
579         }
580     } else {
581         return m_resource_file.prefixCount();
582     }
583 }
584
585 int ResourceModel::columnCount(const QModelIndex &) const
586 {
587     return 1;
588 }
589
590 bool ResourceModel::hasChildren(const QModelIndex &parent) const
591 {
592     return rowCount(parent) != 0;
593 }
594
595 bool ResourceModel::iconFileExtension(const QString &path)
596 {
597     static QStringList ext_list;
598     if (ext_list.isEmpty()) {
599         const QList<QByteArray> _ext_list = QImageReader::supportedImageFormats();
600         foreach (const QByteArray &ext, _ext_list) {
601             QString dotExt = QString(QLatin1Char('.'));
602             dotExt  += QString::fromAscii(ext);
603             ext_list.append(dotExt);
604         }
605     }
606
607     foreach (const QString &ext, ext_list) {
608         if (path.endsWith(ext, Qt::CaseInsensitive))
609             return true;
610     }
611
612     return false;
613 }
614
615 static inline void appendParenthesized(const QString &what, QString &s)
616 {
617     s += QLatin1String(" (");
618     s += what;
619     s += QLatin1Char(')');
620 }
621
622 QVariant ResourceModel::data(const QModelIndex &index, int role) const
623 {
624     if (!index.isValid())
625         return QVariant();
626
627     const void *internalPointer = index.internalPointer();
628     const Node *node = reinterpret_cast<const Node *>(internalPointer);
629     const Prefix *prefix = node->prefix();
630     File *file = node->file();
631     Q_ASSERT(prefix);
632     const bool isFileNode = (prefix != node);
633
634     QVariant result;
635
636     switch (role) {
637     case Qt::DisplayRole:
638         {
639             QString stringRes;
640             if (!isFileNode) {
641                 // Prefix node
642                 stringRes = prefix->name;
643                 const QString &lang = prefix->lang;
644                 if (!lang.isEmpty())
645                     appendParenthesized(lang, stringRes);
646             } else  {
647                 // File node
648                 Q_ASSERT(file);
649                 QString conv_file = m_resource_file.relativePath(file->name);
650                 stringRes = conv_file.replace(QDir::separator(), QLatin1Char('/'));
651                 const QString alias = file->alias;
652                 if (!alias.isEmpty())
653                     appendParenthesized(alias, stringRes);
654             }
655             result = stringRes;
656         }
657         break;
658     case Qt::DecorationRole:
659         if (isFileNode) {
660             // File node
661             Q_ASSERT(file);
662             if (file->icon.isNull()) {
663                 const QString path = m_resource_file.absolutePath(file->name);
664                 if (iconFileExtension(path))
665                     file->icon = QIcon(path);
666             }
667             if (!file->icon.isNull())
668                 result = file->icon;
669         }
670         break;
671     default:
672         break;
673     }
674     return result;
675 }
676
677 void ResourceModel::getItem(const QModelIndex &index, QString &prefix, QString &file) const
678 {
679     prefix.clear();
680     file.clear();
681
682     if (!index.isValid())
683         return;
684
685     const void *internalPointer = index.internalPointer();
686     const Node *node = reinterpret_cast<const Node *>(internalPointer);
687     const Prefix *p = node->prefix();
688     Q_ASSERT(p);
689     const bool isFileNode = (p != node);
690
691     if (isFileNode) {
692         const File *f = node->file();
693         Q_ASSERT(f);
694         if (!f->alias.isEmpty())
695             file = f->alias;
696         else
697             file = f->name;
698     } else {
699         prefix = p->name;
700     }
701 }
702
703 QString ResourceModel::lang(const QModelIndex &index) const
704 {
705     if (!index.isValid())
706         return QString();
707
708     return m_resource_file.lang(index.row());
709 }
710
711 QString ResourceModel::alias(const QModelIndex &index) const
712 {
713     if (!index.isValid() || !index.parent().isValid())
714         return QString();
715     return m_resource_file.alias(index.parent().row(), index.row());
716 }
717
718 QString ResourceModel::file(const QModelIndex &index) const
719 {
720     if (!index.isValid() || !index.parent().isValid())
721         return QString();
722     return m_resource_file.file(index.parent().row(), index.row());
723 }
724
725 QModelIndex ResourceModel::getIndex(const QString &prefixed_file)
726 {
727     QString prefix, file;
728     if (!m_resource_file.split(prefixed_file, &prefix, &file))
729         return QModelIndex();
730     return getIndex(prefix, file);
731 }
732
733 QModelIndex ResourceModel::getIndex(const QString &prefix, const QString &file)
734 {
735     if (prefix.isEmpty())
736         return QModelIndex();
737
738     const int pref_idx = m_resource_file.indexOfPrefix(prefix);
739     if (pref_idx == -1)
740         return QModelIndex();
741
742     const QModelIndex pref_model_idx = index(pref_idx, 0, QModelIndex());
743     if (file.isEmpty())
744         return pref_model_idx;
745
746     const int file_idx = m_resource_file.indexOfFile(pref_idx, file);
747     if (file_idx == -1)
748         return QModelIndex();
749
750     return index(file_idx, 0, pref_model_idx);
751 }
752
753 QModelIndex ResourceModel::prefixIndex(const QModelIndex &sel_idx) const
754 {
755     if (!sel_idx.isValid())
756         return QModelIndex();
757     const QModelIndex parentIndex = parent(sel_idx);
758     return parentIndex.isValid() ? parentIndex : sel_idx;
759 }
760
761 QModelIndex ResourceModel::addNewPrefix()
762 {
763     const QString format = QLatin1String("/new/prefix%1");
764     int i = 1;
765     QString prefix = format.arg(i);
766     for ( ; m_resource_file.contains(prefix); i++)
767         prefix = format.arg(i);
768
769     i = rowCount(QModelIndex());
770     beginInsertRows(QModelIndex(), i, i);
771     m_resource_file.addPrefix(prefix);
772     endInsertRows();
773
774     setDirty(true);
775
776     return index(i, 0, QModelIndex());
777 }
778
779 QModelIndex ResourceModel::addFiles(const QModelIndex &model_idx, const QStringList &file_list)
780 {
781     const QModelIndex prefixModelIndex = prefixIndex(model_idx);
782     const int prefixArrayIndex = prefixModelIndex.row();
783     const int cursorFileArrayIndex = (prefixModelIndex == model_idx) ? 0 : model_idx.row();
784     int dummy;
785     int lastFileArrayIndex;
786     addFiles(prefixArrayIndex, file_list, cursorFileArrayIndex, dummy, lastFileArrayIndex);
787     return index(lastFileArrayIndex, 0, prefixModelIndex);
788 }
789
790 void ResourceModel::addFiles(int prefixIndex, const QStringList &fileNames, int cursorFile,
791         int &firstFile, int &lastFile)
792 {
793     Q_UNUSED(cursorFile)
794     const QModelIndex prefix_model_idx = index(prefixIndex, 0, QModelIndex());
795     const QStringList &file_list = fileNames;
796     firstFile = -1;
797     lastFile = -1;
798
799     if (!prefix_model_idx.isValid()) {
800         return;
801     }
802     const int prefix_idx = prefixIndex;
803
804     QStringList unique_list;
805     foreach (const QString &file, file_list) {
806         if (!m_resource_file.contains(prefix_idx, file) && !unique_list.contains(file))
807             unique_list.append(file);
808     }
809
810     if (unique_list.isEmpty()) {
811         return;
812     }
813     const int cnt = m_resource_file.fileCount(prefix_idx);
814     beginInsertRows(prefix_model_idx, cnt, cnt + unique_list.count() - 1); // ### FIXME
815
816     foreach (const QString &file, unique_list)
817         m_resource_file.addFile(prefix_idx, file);
818
819     const QFileInfo fi(file_list.last());
820     m_lastResourceDir = fi.absolutePath();
821
822     endInsertRows();
823     setDirty(true);
824
825     firstFile = cnt;
826     lastFile = cnt + unique_list.count() - 1;
827 }
828
829
830 void ResourceModel::insertPrefix(int prefixIndex, const QString &prefix,
831         const QString &lang)
832 {
833     beginInsertRows(QModelIndex(), prefixIndex, prefixIndex);
834     m_resource_file.addPrefix(prefix, prefixIndex);
835     m_resource_file.replaceLang(prefixIndex, lang);
836     endInsertRows();
837     setDirty(true);
838 }
839
840 void ResourceModel::insertFile(int prefixIndex, int fileIndex,
841         const QString &fileName, const QString &alias)
842 {
843     const QModelIndex parent = index(prefixIndex, 0, QModelIndex());
844     beginInsertRows(parent, fileIndex, fileIndex);
845     m_resource_file.addFile(prefixIndex, fileName, fileIndex);
846     m_resource_file.replaceAlias(prefixIndex, fileIndex, alias);
847     endInsertRows();
848     setDirty(true);
849 }
850
851 void ResourceModel::changePrefix(const QModelIndex &model_idx, const QString &prefix)
852 {
853     if (!model_idx.isValid())
854         return;
855
856     const QModelIndex prefix_model_idx = prefixIndex(model_idx);
857     const int prefix_idx = model_idx.row();
858     if (m_resource_file.prefix(prefix_idx) == ResourceFile::fixPrefix(prefix))
859         return;
860
861     if (m_resource_file.contains(prefix))
862         return;
863
864     m_resource_file.replacePrefix(prefix_idx, prefix);
865     emit dataChanged(prefix_model_idx, prefix_model_idx);
866     setDirty(true);
867 }
868
869 void ResourceModel::changeLang(const QModelIndex &model_idx, const QString &lang)
870 {
871     if (!model_idx.isValid())
872         return;
873
874     const QModelIndex prefix_model_idx = prefixIndex(model_idx);
875     const int prefix_idx = model_idx.row();
876     if (m_resource_file.lang(prefix_idx) == lang)
877         return;
878
879     m_resource_file.replaceLang(prefix_idx, lang);
880     emit dataChanged(prefix_model_idx, prefix_model_idx);
881     setDirty(true);
882 }
883
884 void ResourceModel::changeAlias(const QModelIndex &index, const QString &alias)
885 {
886     if (!index.parent().isValid())
887         return;
888
889     if (m_resource_file.alias(index.parent().row(), index.row()) == alias)
890         return;
891     m_resource_file.replaceAlias(index.parent().row(), index.row(), alias);
892     emit dataChanged(index, index);
893     setDirty(true);
894 }
895
896 QModelIndex ResourceModel::deleteItem(const QModelIndex &idx)
897 {
898     if (!idx.isValid())
899         return QModelIndex();
900
901     QString dummy, file;
902     getItem(idx, dummy, file);
903     int prefix_idx = -1;
904     int file_idx = -1;
905
906     beginRemoveRows(parent(idx), idx.row(), idx.row());
907     if (file.isEmpty()) {
908         // Remove prefix
909         prefix_idx = idx.row();
910         m_resource_file.removePrefix(prefix_idx);
911         if (prefix_idx == m_resource_file.prefixCount())
912             --prefix_idx;
913     } else {
914         // Remove file
915         prefix_idx = prefixIndex(idx).row();
916         file_idx = idx.row();
917         m_resource_file.removeFile(prefix_idx, file_idx);
918         if (file_idx == m_resource_file.fileCount(prefix_idx))
919             --file_idx;
920     }
921     endRemoveRows();
922
923     setDirty(true);
924
925     if (prefix_idx == -1)
926         return QModelIndex();
927     const QModelIndex prefix_model_idx = index(prefix_idx, 0, QModelIndex());
928     if (file_idx == -1)
929         return prefix_model_idx;
930     return index(file_idx, 0, prefix_model_idx);
931 }
932
933 bool ResourceModel::reload()
934 {
935     const bool result = m_resource_file.load();
936     if (result)
937         setDirty(false);
938     reset();
939     return result;
940 }
941
942 bool ResourceModel::save()
943 {
944     const bool result = m_resource_file.save();
945     if (result)
946         setDirty(false);
947     return result;
948 }
949
950 QString ResourceModel::lastResourceOpenDirectory() const
951 {
952     if (m_lastResourceDir.isEmpty())
953         return absolutePath(QString());
954     return m_lastResourceDir;
955 }
956
957 // Create a resource path 'prefix:/file'
958 QString ResourceModel::resourcePath(const QString &prefix, const QString &file)
959 {
960     QString rc = QString(QLatin1Char(':'));
961     rc += prefix;
962     rc += QLatin1Char('/');
963     rc += file;
964     return QDir::cleanPath(rc);
965 }
966
967 QMimeData *ResourceModel::mimeData(const QModelIndexList &indexes) const
968 {
969     if (indexes.size() != 1)
970         return 0;
971
972     QString prefix, file;
973     getItem(indexes.front(), prefix, file);
974     if (prefix.isEmpty() || file.isEmpty())
975         return 0;
976
977     // DnD format of Designer 4.4
978     QDomDocument doc;
979     QDomElement elem = doc.createElement(QLatin1String("resource"));
980     elem.setAttribute(QLatin1String("type"), QLatin1String("image"));
981     elem.setAttribute(QLatin1String("file"), resourcePath(prefix, file));
982     doc.appendChild(elem);
983
984     QMimeData *rc = new QMimeData;
985     rc->setText(doc.toString());
986     return rc;
987 }
988
989 } // namespace qdesigner_internal
990
991 QT_END_NAMESPACE