1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "resourceview.h"
27 
28 #include "undocommands_p.h"
29 
30 #include <coreplugin/fileutils.h>
31 #include <coreplugin/icore.h>
32 
33 #include <QDebug>
34 
35 #include <QAction>
36 #include <QApplication>
37 #include <QFileDialog>
38 #include <QHeaderView>
39 #include <QInputDialog>
40 #include <QMenu>
41 #include <QMouseEvent>
42 #include <QUndoStack>
43 
44 using namespace ResourceEditor;
45 using namespace ResourceEditor::Internal;
46 
ResourceView(RelativeResourceModel * model,QUndoStack * history,QWidget * parent)47 ResourceView::ResourceView(RelativeResourceModel *model, QUndoStack *history, QWidget *parent) :
48     Utils::TreeView(parent),
49     m_qrcModel(model),
50     m_history(history),
51     m_mergeId(-1)
52 {
53     advanceMergeId();
54     setModel(m_qrcModel);
55     setContextMenuPolicy(Qt::CustomContextMenu);
56     setEditTriggers(EditKeyPressed);
57 
58     header()->hide();
59 
60     connect(this, &QWidget::customContextMenuRequested, this, &ResourceView::showContextMenu);
61     connect(this, &QAbstractItemView::activated, this, &ResourceView::onItemActivated);
62 }
63 
64 ResourceView::~ResourceView() = default;
65 
findSamePlacePostDeletionModelIndex(int & row,QModelIndex & parent) const66 void ResourceView::findSamePlacePostDeletionModelIndex(int &row, QModelIndex &parent) const
67 {
68     // Concept:
69     // - Make selection stay on same Y level
70     // - Enable user to hit delete several times in row
71     const bool hasLowerBrother = m_qrcModel->hasIndex(row + 1,
72             0, parent);
73     if (hasLowerBrother) {
74         // First or mid child -> lower brother
75         //  o
76         //  +--o
77         //  +-[o]  <-- deleted
78         //  +--o   <-- chosen
79         //  o
80         // --> return unmodified
81     } else {
82         if (parent == QModelIndex()) {
83             // Last prefix node
84             if (row == 0) {
85                 // Last and only prefix node
86                 // [o]  <-- deleted
87                 //  +--o
88                 //  +--o
89                 row = -1;
90                 parent = QModelIndex();
91             } else {
92                 const QModelIndex upperBrother = m_qrcModel->index(row - 1,
93                         0, parent);
94                 if (m_qrcModel->hasChildren(upperBrother)) {
95                     //  o
96                     //  +--o  <-- selected
97                     // [o]    <-- deleted
98                     row = m_qrcModel->rowCount(upperBrother) - 1;
99                     parent = upperBrother;
100                 } else {
101                     //  o
102                     //  o  <-- selected
103                     // [o] <-- deleted
104                     row--;
105                 }
106             }
107         } else {
108             // Last file node
109             const bool hasPrefixBelow = m_qrcModel->hasIndex(parent.row() + 1,
110                     parent.column(), QModelIndex());
111             if (hasPrefixBelow) {
112                 // Last child or parent with lower brother -> lower brother of parent
113                 //  o
114                 //  +--o
115                 //  +-[o]  <-- deleted
116                 //  o      <-- chosen
117                 row = parent.row() + 1;
118                 parent = QModelIndex();
119             } else {
120                 const bool onlyChild = row == 0;
121                 if (onlyChild) {
122                     // Last and only child of last parent -> parent
123                     //  o      <-- chosen
124                     //  +-[o]  <-- deleted
125                     row = parent.row();
126                     parent = m_qrcModel->parent(parent);
127                 } else {
128                     // Last child of last parent -> upper brother
129                     //  o
130                     //  +--o   <-- chosen
131                     //  +-[o]  <-- deleted
132                     row--;
133                 }
134             }
135         }
136     }
137 }
138 
removeEntry(const QModelIndex & index)139 EntryBackup * ResourceView::removeEntry(const QModelIndex &index)
140 {
141     Q_ASSERT(m_qrcModel);
142     return m_qrcModel->removeEntry(index);
143 }
144 
existingFilesSubtracted(int prefixIndex,const QStringList & fileNames) const145 QStringList ResourceView::existingFilesSubtracted(int prefixIndex, const QStringList &fileNames) const
146 {
147     return m_qrcModel->existingFilesSubtracted(prefixIndex, fileNames);
148 }
149 
addFiles(int prefixIndex,const QStringList & fileNames,int cursorFile,int & firstFile,int & lastFile)150 void ResourceView::addFiles(int prefixIndex, const QStringList &fileNames, int cursorFile,
151         int &firstFile, int &lastFile)
152 {
153     Q_ASSERT(m_qrcModel);
154     m_qrcModel->addFiles(prefixIndex, fileNames, cursorFile, firstFile, lastFile);
155 
156     // Expand prefix node
157     const QModelIndex prefixModelIndex = m_qrcModel->index(prefixIndex, 0, QModelIndex());
158     if (prefixModelIndex.isValid())
159         this->setExpanded(prefixModelIndex, true);
160 }
161 
removeFiles(int prefixIndex,int firstFileIndex,int lastFileIndex)162 void ResourceView::removeFiles(int prefixIndex, int firstFileIndex, int lastFileIndex)
163 {
164     Q_ASSERT(prefixIndex >= 0 && prefixIndex < m_qrcModel->rowCount(QModelIndex()));
165     const QModelIndex prefixModelIndex = m_qrcModel->index(prefixIndex, 0, QModelIndex());
166     Q_ASSERT(prefixModelIndex != QModelIndex());
167     Q_ASSERT(firstFileIndex >= 0 && firstFileIndex < m_qrcModel->rowCount(prefixModelIndex));
168     Q_ASSERT(lastFileIndex >= 0 && lastFileIndex < m_qrcModel->rowCount(prefixModelIndex));
169 
170     for (int i = lastFileIndex; i >= firstFileIndex; i--) {
171         const QModelIndex index = m_qrcModel->index(i, 0, prefixModelIndex);
172         delete removeEntry(index);
173     }
174 }
175 
keyPressEvent(QKeyEvent * e)176 void ResourceView::keyPressEvent(QKeyEvent *e)
177 {
178     if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)
179         emit removeItem();
180     else
181         Utils::TreeView::keyPressEvent(e);
182 }
183 
addPrefix()184 QModelIndex ResourceView::addPrefix()
185 {
186     const QModelIndex idx = m_qrcModel->addNewPrefix();
187     selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
188     return idx;
189 }
190 
nonExistingFiles()191 QList<QModelIndex> ResourceView::nonExistingFiles()
192 {
193     return m_qrcModel->nonExistingFiles();
194 }
195 
refresh()196 void ResourceView::refresh()
197 {
198     m_qrcModel->refresh();
199     QModelIndex idx = currentIndex();
200     setModel(nullptr);
201     setModel(m_qrcModel);
202     setCurrentIndex(idx);
203     expandAll();
204 }
205 
fileNamesToAdd()206 QStringList ResourceView::fileNamesToAdd()
207 {
208     return QFileDialog::getOpenFileNames(this, tr("Open File"),
209             m_qrcModel->absolutePath(QString()),
210             tr("All files (*)"));
211 }
212 
currentAlias() const213 QString ResourceView::currentAlias() const
214 {
215     const QModelIndex current = currentIndex();
216     if (!current.isValid())
217         return QString();
218     return m_qrcModel->alias(current);
219 }
220 
currentPrefix() const221 QString ResourceView::currentPrefix() const
222 {
223     const QModelIndex current = currentIndex();
224     if (!current.isValid())
225         return QString();
226     const QModelIndex preindex = m_qrcModel->prefixIndex(current);
227     QString prefix, file;
228     m_qrcModel->getItem(preindex, prefix, file);
229     return prefix;
230 }
231 
currentLanguage() const232 QString ResourceView::currentLanguage() const
233 {
234     const QModelIndex current = currentIndex();
235     if (!current.isValid())
236         return QString();
237     const QModelIndex preindex = m_qrcModel->prefixIndex(current);
238     return m_qrcModel->lang(preindex);
239 }
240 
currentResourcePath() const241 QString ResourceView::currentResourcePath() const
242 {
243     const QModelIndex current = currentIndex();
244     if (!current.isValid())
245         return QString();
246 
247     const QString alias = m_qrcModel->alias(current);
248     if (!alias.isEmpty())
249         return QLatin1Char(':') + currentPrefix() + QLatin1Char('/') + alias;
250 
251     return QLatin1Char(':') + currentPrefix() + QLatin1Char('/') + m_qrcModel->relativePath(m_qrcModel->file(current));
252 }
253 
getCurrentValue(NodeProperty property) const254 QString ResourceView::getCurrentValue(NodeProperty property) const
255 {
256     switch (property) {
257     case AliasProperty: return currentAlias();
258     case PrefixProperty: return currentPrefix();
259     case LanguageProperty: return currentLanguage();
260     default: Q_ASSERT(false); return QString(); // Kill warning
261     }
262 }
263 
changeValue(const QModelIndex & nodeIndex,NodeProperty property,const QString & value)264 void ResourceView::changeValue(const QModelIndex &nodeIndex, NodeProperty property,
265         const QString &value)
266 {
267     switch (property) {
268     case AliasProperty: m_qrcModel->changeAlias(nodeIndex, value); return;
269     case PrefixProperty: m_qrcModel->changePrefix(nodeIndex, value); return;
270     case LanguageProperty: m_qrcModel->changeLang(nodeIndex, value); return;
271     default: Q_ASSERT(false);
272     }
273 }
274 
onItemActivated(const QModelIndex & index)275 void ResourceView::onItemActivated(const QModelIndex &index)
276 {
277     const QString fileName = m_qrcModel->file(index);
278     if (fileName.isEmpty())
279         return;
280     emit itemActivated(fileName);
281 }
282 
showContextMenu(const QPoint & pos)283 void ResourceView::showContextMenu(const QPoint &pos)
284 {
285     const QModelIndex index = indexAt(pos);
286     const QString fileName = m_qrcModel->file(index);
287     if (fileName.isEmpty())
288         return;
289     emit contextMenuShown(mapToGlobal(pos), fileName);
290 }
291 
advanceMergeId()292 void ResourceView::advanceMergeId()
293 {
294     m_mergeId++;
295     if (m_mergeId < 0)
296         m_mergeId = 0;
297 }
298 
addUndoCommand(const QModelIndex & nodeIndex,NodeProperty property,const QString & before,const QString & after)299 void ResourceView::addUndoCommand(const QModelIndex &nodeIndex, NodeProperty property,
300         const QString &before, const QString &after)
301 {
302     QUndoCommand * const command = new ModifyPropertyCommand(this, nodeIndex, property,
303             m_mergeId, before, after);
304     m_history->push(command);
305 }
306 
setCurrentAlias(const QString & before,const QString & after)307 void ResourceView::setCurrentAlias(const QString &before, const QString &after)
308 {
309     const QModelIndex current = currentIndex();
310     if (!current.isValid())
311         return;
312 
313     addUndoCommand(current, AliasProperty, before, after);
314 }
315 
setCurrentPrefix(const QString & before,const QString & after)316 void ResourceView::setCurrentPrefix(const QString &before, const QString &after)
317 {
318     const QModelIndex current = currentIndex();
319     if (!current.isValid())
320         return;
321     const QModelIndex preindex = m_qrcModel->prefixIndex(current);
322 
323     addUndoCommand(preindex, PrefixProperty, before, after);
324 }
325 
setCurrentLanguage(const QString & before,const QString & after)326 void ResourceView::setCurrentLanguage(const QString &before, const QString &after)
327 {
328     const QModelIndex current = currentIndex();
329     if (!current.isValid())
330         return;
331     const QModelIndex preindex = m_qrcModel->prefixIndex(current);
332 
333     addUndoCommand(preindex, LanguageProperty, before, after);
334 }
335 
isPrefix(const QModelIndex & index) const336 bool ResourceView::isPrefix(const QModelIndex &index) const
337 {
338     if (!index.isValid())
339         return false;
340     const QModelIndex preindex = m_qrcModel->prefixIndex(index);
341     if (preindex == index)
342         return true;
343     return false;
344 }
345 
fileName() const346 QString ResourceView::fileName() const
347 {
348     return m_qrcModel->fileName();
349 }
350 
setResourceDragEnabled(bool e)351 void ResourceView::setResourceDragEnabled(bool e)
352 {
353     setDragEnabled(e);
354     m_qrcModel->setResourceDragEnabled(e);
355 }
356 
resourceDragEnabled() const357 bool ResourceView::resourceDragEnabled() const
358 {
359     return m_qrcModel->resourceDragEnabled();
360 }
361