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 "basefilewizardfactory.h"
27
28 #include "basefilewizard.h"
29 #include "icontext.h"
30 #include "icore.h"
31 #include "ifilewizardextension.h"
32 #include "editormanager/editormanager.h"
33 #include "dialogs/promptoverwritedialog.h"
34 #include <extensionsystem/pluginmanager.h>
35 #include <utils/filewizardpage.h>
36 #include <utils/mimetypes/mimedatabase.h>
37 #include <utils/qtcassert.h>
38 #include <utils/stringutils.h>
39 #include <utils/wizard.h>
40
41 #include <QDir>
42 #include <QFileInfo>
43 #include <QDebug>
44 #include <QIcon>
45
46 enum { debugWizard = 0 };
47
48 using namespace Utils;
49
50 namespace Core {
51
indexOfFile(const GeneratedFiles & f,const QString & path)52 static int indexOfFile(const GeneratedFiles &f, const QString &path)
53 {
54 const int size = f.size();
55 for (int i = 0; i < size; ++i)
56 if (f.at(i).path() == path)
57 return i;
58 return -1;
59 }
60
61 /*!
62 \class Core::BaseFileWizard
63 \inheaderfile coreplugin/basefilewizardfactory.h
64 \inmodule QtCreator
65
66 \brief The BaseFileWizard class implements a is a convenience class for
67 creating files.
68
69 \sa Core::BaseFileWizardFactory
70 */
71
runWizardImpl(const QString & path,QWidget * parent,Id platform,const QVariantMap & extraValues)72 Utils::Wizard *BaseFileWizardFactory::runWizardImpl(const QString &path, QWidget *parent,
73 Id platform,
74 const QVariantMap &extraValues)
75 {
76 QTC_ASSERT(!path.isEmpty(), return nullptr);
77
78 // Create dialog and run it. Ensure that the dialog is deleted when
79 // leaving the func, but not before the IFileWizardExtension::process
80 // has been called
81
82 WizardDialogParameters::DialogParameterFlags dialogParameterFlags;
83
84 if (flags().testFlag(ForceCapitalLetterForFileName))
85 dialogParameterFlags |= WizardDialogParameters::ForceCapitalLetterForFileName;
86
87 Utils::Wizard *wizard = create(parent, WizardDialogParameters(path, platform,
88 requiredFeatures(),
89 dialogParameterFlags,
90 extraValues));
91 QTC_CHECK(wizard);
92 return wizard;
93 }
94
95 /*!
96 \class Core::BaseFileWizardFactory
97 \inheaderfile coreplugin/basefilewizardfactory.h
98 \inmodule QtCreator
99
100 \brief The BaseFileWizardFactory class implements a generic wizard for
101 creating files.
102
103 The following abstract functions must be implemented:
104 \list
105 \li create(): Called to create the QWizard dialog to be shown.
106 \li generateFiles(): Generates file content.
107 \endlist
108
109 The behavior can be further customized by overwriting the virtual function
110 postGenerateFiles(), which is called after generating the files.
111
112 \note Instead of using this class, we recommend that you create JSON-based
113 wizards, as instructed in \l{https://doc.qt.io/qtcreator/creator-project-wizards.html}
114 {Adding New Custom Wizards}.
115
116 \sa Core::GeneratedFile, Core::WizardDialogParameters, Core::BaseFileWizard
117 */
118
119 /*!
120 \fn Core::BaseFileWizard *Core::BaseFileWizardFactory::create(QWidget *parent,
121 const Core::WizardDialogParameters ¶meters) const
122
123 Creates the wizard on the \a parent with the \a parameters.
124 */
125
126 /*!
127 \fn virtual Core::GeneratedFiles Core::BaseFileWizardFactory::generateFiles(const QWizard *w,
128 QString *errorMessage) const
129 Overwrite to query the parameters from the wizard \a w and generate the
130 files.
131
132 Possible errors are held in \a errorMessage.
133
134 \note This does not generate physical files, but merely the list of
135 Core::GeneratedFile.
136 */
137
138 /*!
139 Physically writes \a files.
140
141 If the files cannot be written, returns \c false and sets \a errorMessage
142 to the message that is displayed to users.
143
144 Re-implement (calling the base implementation) to create files with
145 GeneratedFile::CustomGeneratorAttribute set.
146 */
147
writeFiles(const GeneratedFiles & files,QString * errorMessage) const148 bool BaseFileWizardFactory::writeFiles(const GeneratedFiles &files, QString *errorMessage) const
149 {
150 const GeneratedFile::Attributes noWriteAttributes
151 = GeneratedFile::CustomGeneratorAttribute|GeneratedFile::KeepExistingFileAttribute;
152 foreach (const GeneratedFile &generatedFile, files)
153 if (!(generatedFile.attributes() & noWriteAttributes ))
154 if (!generatedFile.write(errorMessage))
155 return false;
156 return true;
157 }
158
159 /*!
160 Overwrite to perform steps to be done by the wizard \a w after the files
161 specified by \a l are actually created.
162
163 The default implementation opens editors with the newly generated files
164 that have GeneratedFile::OpenEditorAttribute set.
165
166 Returns \a errorMessage if errors occur.
167 */
168
postGenerateFiles(const QWizard *,const GeneratedFiles & l,QString * errorMessage) const169 bool BaseFileWizardFactory::postGenerateFiles(const QWizard *, const GeneratedFiles &l,
170 QString *errorMessage) const
171 {
172 return BaseFileWizardFactory::postGenerateOpenEditors(l, errorMessage);
173 }
174
175 /*!
176 Opens the editors for the files \a l if their
177 GeneratedFile::OpenEditorAttribute attribute
178 is set accordingly.
179
180 If the editorrs cannot be opened, returns \c false and dand sets
181 \a errorMessage to the message that is displayed to users.
182 */
183
postGenerateOpenEditors(const GeneratedFiles & l,QString * errorMessage)184 bool BaseFileWizardFactory::postGenerateOpenEditors(const GeneratedFiles &l, QString *errorMessage)
185 {
186 foreach (const GeneratedFile &file, l) {
187 if (file.attributes() & GeneratedFile::OpenEditorAttribute) {
188 if (!EditorManager::openEditor(file.path(), file.editorId())) {
189 if (errorMessage)
190 *errorMessage = tr("Failed to open an editor for \"%1\".").arg(QDir::toNativeSeparators(file.path()));
191 return false;
192 }
193 }
194 }
195 return true;
196 }
197
198 /*!
199 Performs an overwrite check on a set of \a files. Checks if the file exists and
200 can be overwritten at all, and then prompts the user with a summary.
201
202 Returns \a errorMessage if the file cannot be overwritten.
203 */
204
promptOverwrite(GeneratedFiles * files,QString * errorMessage)205 BaseFileWizardFactory::OverwriteResult BaseFileWizardFactory::promptOverwrite(GeneratedFiles *files,
206 QString *errorMessage)
207 {
208 if (debugWizard)
209 qDebug() << Q_FUNC_INFO << files;
210
211 QStringList existingFiles;
212 bool oddStuffFound = false;
213
214 static const QString readOnlyMsg = tr("[read only]");
215 static const QString directoryMsg = tr("[folder]");
216 static const QString symLinkMsg = tr("[symbolic link]");
217
218 foreach (const GeneratedFile &file, *files) {
219 const QString path = file.path();
220 if (QFileInfo::exists(path))
221 existingFiles.append(path);
222 }
223 if (existingFiles.isEmpty())
224 return OverwriteOk;
225 // Before prompting to overwrite existing files, loop over files and check
226 // if there is anything blocking overwriting them (like them being links or folders).
227 // Format a file list message as ( "<file1> [readonly], <file2> [folder]").
228 const QString commonExistingPath = Utils::commonPath(existingFiles);
229 QString fileNamesMsgPart;
230 foreach (const QString &fileName, existingFiles) {
231 const QFileInfo fi(fileName);
232 if (fi.exists()) {
233 if (!fileNamesMsgPart.isEmpty())
234 fileNamesMsgPart += QLatin1String(", ");
235 fileNamesMsgPart += QDir::toNativeSeparators(fileName.mid(commonExistingPath.size() + 1));
236 do {
237 if (fi.isDir()) {
238 oddStuffFound = true;
239 fileNamesMsgPart += QLatin1Char(' ') + directoryMsg;
240 break;
241 }
242 if (fi.isSymLink()) {
243 oddStuffFound = true;
244 fileNamesMsgPart += QLatin1Char(' ') + symLinkMsg;
245 break;
246 }
247 if (!fi.isWritable()) {
248 oddStuffFound = true;
249 fileNamesMsgPart += QLatin1Char(' ') + readOnlyMsg;
250 }
251 } while (false);
252 }
253 }
254
255 if (oddStuffFound) {
256 *errorMessage = tr("The project directory %1 contains files which cannot be overwritten:\n%2.")
257 .arg(QDir::toNativeSeparators(commonExistingPath)).arg(fileNamesMsgPart);
258 return OverwriteError;
259 }
260 // Prompt to overwrite existing files.
261 PromptOverwriteDialog overwriteDialog;
262 // Scripts cannot handle overwrite
263 overwriteDialog.setFiles(existingFiles);
264 foreach (const GeneratedFile &file, *files)
265 if (file.attributes() & GeneratedFile::CustomGeneratorAttribute)
266 overwriteDialog.setFileEnabled(file.path(), false);
267 if (overwriteDialog.exec() != QDialog::Accepted)
268 return OverwriteCanceled;
269 const QStringList existingFilesToKeep = overwriteDialog.uncheckedFiles();
270 if (existingFilesToKeep.size() == files->size()) // All exist & all unchecked->Cancel.
271 return OverwriteCanceled;
272 // Set 'keep' attribute in files
273 foreach (const QString &keepFile, existingFilesToKeep) {
274 const int i = indexOfFile(*files, keepFile);
275 QTC_ASSERT(i != -1, return OverwriteCanceled);
276 GeneratedFile &file = (*files)[i];
277 file.setAttributes(file.attributes() | GeneratedFile::KeepExistingFileAttribute);
278 }
279 return OverwriteOk;
280 }
281
282 /*!
283 Constructs a file name including \a path, adding the \a extension unless
284 \a baseName already has one.
285 */
286
buildFileName(const QString & path,const QString & baseName,const QString & extension)287 QString BaseFileWizardFactory::buildFileName(const QString &path,
288 const QString &baseName,
289 const QString &extension)
290 {
291 QString rc = path;
292 const QChar slash = QLatin1Char('/');
293 if (!rc.isEmpty() && !rc.endsWith(slash))
294 rc += slash;
295 rc += baseName;
296 // Add extension unless user specified something else
297 const QChar dot = QLatin1Char('.');
298 if (!extension.isEmpty() && !baseName.contains(dot)) {
299 if (!extension.startsWith(dot))
300 rc += dot;
301 rc += extension;
302 }
303 if (debugWizard)
304 qDebug() << Q_FUNC_INFO << rc;
305 return rc;
306 }
307
308 /*!
309 Returns the preferred suffix for \a mimeType.
310 */
311
preferredSuffix(const QString & mimeType)312 QString BaseFileWizardFactory::preferredSuffix(const QString &mimeType)
313 {
314 QString rc;
315 Utils::MimeType mt = Utils::mimeTypeForName(mimeType);
316 if (mt.isValid())
317 rc = mt.preferredSuffix();
318 if (rc.isEmpty())
319 qWarning("%s: WARNING: Unable to find a preferred suffix for %s.",
320 Q_FUNC_INFO, mimeType.toUtf8().constData());
321 return rc;
322 }
323
324 /*!
325 \class Core::WizardDialogParameters
326 \inheaderfile coreplugin/basefilewizardfactory.h
327 \inmodule QtCreator
328
329 \brief The WizardDialogParameters class holds parameters for the new file
330 wizard dialog.
331
332 \sa Core::GeneratedFile, Core::BaseFileWizardFactory
333 */
334
335 /*!
336 \enum Core::WizardDialogParameters::DialogParameterEnum
337 This enum type holds whether to force capital letters for file names.
338 \value ForceCapitalLetterForFileName Forces capital letters for file names.
339 */
340
341 } // namespace Core
342