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 &parameters) 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