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 "iwizardfactory.h"
27 
28 #include "actionmanager/actionmanager.h"
29 #include "documentmanager.h"
30 #include "icore.h"
31 #include "featureprovider.h"
32 
33 #include <extensionsystem/pluginspec.h>
34 #include <extensionsystem/pluginmanager.h>
35 
36 #include <utils/fileutils.h>
37 #include <utils/qtcassert.h>
38 #include <utils/wizard.h>
39 
40 #include <QAction>
41 
42 /*!
43     \class Core::IWizardFactory
44     \inheaderfile coreplugin/iwizardfactory.h
45     \inmodule QtCreator
46     \ingroup mainclasses
47 
48     \brief The IWizardFactory class is the base class for all wizard factories.
49 
50     \note Instead of using this class, we recommend that you create JSON-based
51     wizards, as instructed in \l{https://doc.qt.io/qtcreator/creator-project-wizards.html}
52     {Adding New Custom Wizards}.
53 
54     The wizard interface is a very thin abstraction for the wizards in
55     \uicontrol File > \uicontrol {New File or Project}.
56     Basically, it defines what to show to the user in the wizard selection dialogs,
57     and a hook that is called if the user selects the wizard.
58 
59     Wizards can then perform any operations they like, including showing dialogs and
60     creating files. Often it is not necessary to create your own wizard from scratch.
61     Use one of the predefined wizards and adapt it to your needs.
62 
63     To make your wizard known to the system, add your IWizardFactory instance to the
64     plugin manager's object pool in your plugin's initialize function:
65     \code
66         bool MyPlugin::initialize(const QStringList &arguments, QString *errorString)
67         {
68             // ... do setup
69             addAutoReleasedObject(new MyWizardFactory);
70             // ... do more setup
71         }
72     \endcode
73 
74     \sa Core::BaseFileWizardFactory, Core::BaseFileWizard
75 */
76 
77 /*!
78     \enum Core::IWizardFactory::WizardKind
79     Used to specify what kind of objects the wizard creates. This information is used
80     to show e.g. only wizards that create projects when selecting a \uicontrol{New Project}
81     menu item.
82     \value FileWizard
83         The wizard creates one or more files.
84     \value ProjectWizard
85         The wizard creates a new project.
86 */
87 
88 /*!
89     \fn Core::IWizardFactory::WizardKind Core::IWizardFactory::kind() const
90     Returns what kind of objects are created by the wizard.
91 */
92 
93 /*!
94     \fn QIcon Core::IWizardFactory::icon() const
95     Returns an icon to show in the wizard selection dialog.
96 */
97 
98 /*!
99     \fn QString Core::IWizardFactory::description() const
100     Returns a translated description to show when this wizard is selected
101     in the dialog.
102 */
103 
104 /*!
105     \fn QString Core::IWizardFactory::displayName() const
106     Returns the translated name of the wizard, how it should appear in the
107     dialog.
108 */
109 
110 /*!
111     \fn QString Core::IWizardFactory::id() const
112     Returns an arbitrary id that is used for sorting within the category.
113 */
114 
115 
116 /*!
117     \fn QString Core::IWizardFactory::category() const
118     Returns a category ID to add the wizard to.
119 */
120 
121 /*!
122     \fn QString Core::IWizardFactory::displayCategory() const
123     Returns the translated string of the category, how it should appear
124     in the dialog.
125 */
126 
127 using namespace Core;
128 using namespace Utils;
129 
130 namespace {
131 static QList<IFeatureProvider *> s_providerList;
132 QList<IWizardFactory *> s_allFactories;
133 QList<IWizardFactory::FactoryCreator> s_factoryCreators;
134 QAction *s_inspectWizardAction = nullptr;
135 bool s_areFactoriesLoaded = false;
136 bool s_isWizardRunning = false;
137 QWidget *s_currentWizard = nullptr;
138 
139 // NewItemDialog reopening data:
140 class NewItemDialogData
141 {
142 public:
setData(const QString & t,const QList<IWizardFactory * > & f,const QString & dl,const QVariantMap & ev)143     void setData(const QString &t, const QList<IWizardFactory *> &f,
144                  const QString &dl, const QVariantMap &ev)
145     {
146         QTC_ASSERT(!hasData(), return);
147 
148         QTC_ASSERT(!t.isEmpty(), return);
149         QTC_ASSERT(!f.isEmpty(), return);
150 
151         title = t;
152         factories = f;
153         defaultLocation = dl;
154         extraVariables = ev;
155     }
156 
hasData() const157     bool hasData() const { return !factories.isEmpty(); }
158 
clear()159     void clear() {
160         title.clear();
161         factories.clear();
162         defaultLocation.clear();
163         extraVariables.clear();
164     }
165 
reopen()166     void reopen() {
167         if (!hasData())
168             return;
169         ICore::showNewItemDialog(title, factories, defaultLocation, extraVariables);
170         clear();
171     }
172 
173 private:
174     QString title;
175     QList<IWizardFactory *> factories;
176     QString defaultLocation;
177     QVariantMap extraVariables;
178 };
179 
180 NewItemDialogData s_reopenData;
181 }
182 
actionId(const IWizardFactory * factory)183 static Id actionId(const IWizardFactory *factory)
184 {
185     return factory->id().withPrefix("Wizard.Impl.");
186 }
187 
allWizardFactories()188 QList<IWizardFactory*> IWizardFactory::allWizardFactories()
189 {
190     if (!s_areFactoriesLoaded) {
191         QTC_ASSERT(s_allFactories.isEmpty(), return s_allFactories);
192 
193         s_areFactoriesLoaded = true;
194 
195         QHash<Id, IWizardFactory *> sanityCheck;
196         foreach (const FactoryCreator &fc, s_factoryCreators) {
197             QList<IWizardFactory *> tmp = fc();
198             foreach (IWizardFactory *newFactory, tmp) {
199                 QTC_ASSERT(newFactory, continue);
200                 IWizardFactory *existingFactory = sanityCheck.value(newFactory->id());
201 
202                 QTC_ASSERT(existingFactory != newFactory, continue);
203                 if (existingFactory) {
204                     qWarning("%s", qPrintable(tr("Factory with id=\"%1\" already registered. Deleting.")
205                                               .arg(existingFactory->id().toString())));
206                     delete newFactory;
207                     continue;
208                 }
209 
210                 QTC_ASSERT(!newFactory->m_action, continue);
211                 newFactory->m_action = new QAction(newFactory->displayName(), newFactory);
212                 ActionManager::registerAction(newFactory->m_action, actionId(newFactory));
213 
214                 connect(newFactory->m_action, &QAction::triggered, newFactory, [newFactory]() {
215                     if (!ICore::isNewItemDialogRunning()) {
216                         QString path = newFactory->runPath(QString());
217                         newFactory->runWizard(path, ICore::dialogParent(), Id(), QVariantMap());
218                     }
219                 });
220 
221                 sanityCheck.insert(newFactory->id(), newFactory);
222                 s_allFactories << newFactory;
223             }
224         }
225     }
226 
227     return s_allFactories;
228 }
229 
runPath(const QString & defaultPath) const230 QString IWizardFactory::runPath(const QString &defaultPath) const
231 {
232     QString path = defaultPath;
233     if (path.isEmpty()) {
234         switch (kind()) {
235         case IWizardFactory::ProjectWizard:
236             // Project wizards: Check for projects directory or
237             // use last visited directory of file dialog. Never start
238             // at current.
239             path = DocumentManager::useProjectsDirectory()
240                        ? DocumentManager::projectsDirectory().toString()
241                        : DocumentManager::fileDialogLastVisitedDirectory();
242             break;
243         default:
244             path = DocumentManager::fileDialogInitialDirectory();
245             break;
246         }
247     }
248     return path;
249 }
250 
251 /*!
252     Creates the wizard that the user selected for execution on the operating
253     system \a platform with \a variables.
254 
255     Any dialogs the wizard opens should use the given \a parent.
256     The \a path argument is a suggestion for the location where files should be
257     created. The wizard should fill this in its path selection elements as a
258     default path.
259 */
runWizard(const QString & path,QWidget * parent,Id platform,const QVariantMap & variables)260 Utils::Wizard *IWizardFactory::runWizard(const QString &path, QWidget *parent, Id platform, const QVariantMap &variables)
261 {
262     QTC_ASSERT(!s_isWizardRunning, return nullptr);
263 
264     s_isWizardRunning = true;
265     ICore::updateNewItemDialogState();
266 
267     Utils::Wizard *wizard = runWizardImpl(path, parent, platform, variables);
268 
269     if (wizard) {
270         s_currentWizard = wizard;
271         // Connect while wizard exists:
272         if (m_action)
273             connect(m_action, &QAction::triggered, wizard, [wizard]() { ICore::raiseWindow(wizard); });
274         connect(s_inspectWizardAction, &QAction::triggered,
275                 wizard, [wizard]() { wizard->showVariables(); });
276         connect(wizard, &Utils::Wizard::finished, this, [wizard](int result) {
277             if (result != QDialog::Accepted)
278                 s_reopenData.clear();
279             wizard->deleteLater();
280         });
281         connect(wizard, &QObject::destroyed, this, []() {
282             s_isWizardRunning = false;
283             s_currentWizard = nullptr;
284             s_inspectWizardAction->setEnabled(false);
285             ICore::updateNewItemDialogState();
286             s_reopenData.reopen();
287         });
288         s_inspectWizardAction->setEnabled(true);
289         wizard->show();
290         Core::ICore::registerWindow(wizard, Core::Context("Core.NewWizard"));
291     } else {
292         s_isWizardRunning = false;
293         ICore::updateNewItemDialogState();
294         s_reopenData.reopen();
295     }
296     return wizard;
297 }
298 
isAvailable(Id platformId) const299 bool IWizardFactory::isAvailable(Id platformId) const
300 {
301     if (!platformId.isValid())
302         return true;
303 
304     return availableFeatures(platformId).contains(requiredFeatures());
305 }
306 
supportedPlatforms() const307 QSet<Id> IWizardFactory::supportedPlatforms() const
308 {
309     QSet<Id> platformIds;
310 
311     foreach (Id platform, allAvailablePlatforms()) {
312         if (isAvailable(platform))
313             platformIds.insert(platform);
314     }
315 
316     return platformIds;
317 }
318 
registerFactoryCreator(const IWizardFactory::FactoryCreator & creator)319 void IWizardFactory::registerFactoryCreator(const IWizardFactory::FactoryCreator &creator)
320 {
321     s_factoryCreators << creator;
322 }
323 
allAvailablePlatforms()324 QSet<Id> IWizardFactory::allAvailablePlatforms()
325 {
326     QSet<Id> platforms;
327     foreach (const IFeatureProvider *featureManager, s_providerList)
328         platforms.unite(featureManager->availablePlatforms());
329 
330     return platforms;
331 }
332 
displayNameForPlatform(Id i)333 QString IWizardFactory::displayNameForPlatform(Id i)
334 {
335     foreach (const IFeatureProvider *featureManager, s_providerList) {
336         const QString displayName = featureManager->displayNameForPlatform(i);
337         if (!displayName.isEmpty())
338             return displayName;
339     }
340     return QString();
341 }
342 
registerFeatureProvider(IFeatureProvider * provider)343 void IWizardFactory::registerFeatureProvider(IFeatureProvider *provider)
344 {
345     QTC_ASSERT(!s_providerList.contains(provider), return);
346     s_providerList.append(provider);
347 }
348 
isWizardRunning()349 bool IWizardFactory::isWizardRunning()
350 {
351     return s_isWizardRunning;
352 }
353 
currentWizard()354 QWidget *IWizardFactory::currentWizard()
355 {
356     return s_currentWizard;
357 }
358 
requestNewItemDialog(const QString & title,const QList<IWizardFactory * > & factories,const QString & defaultLocation,const QVariantMap & extraVariables)359 void IWizardFactory::requestNewItemDialog(const QString &title,
360                                           const QList<IWizardFactory *> &factories,
361                                           const QString &defaultLocation,
362                                           const QVariantMap &extraVariables)
363 {
364     s_reopenData.setData(title, factories, defaultLocation, extraVariables);
365 }
366 
destroyFeatureProvider()367 void IWizardFactory::destroyFeatureProvider()
368 {
369     qDeleteAll(s_providerList);
370     s_providerList.clear();
371 }
372 
clearWizardFactories()373 void IWizardFactory::clearWizardFactories()
374 {
375     foreach (IWizardFactory *factory, s_allFactories)
376         ActionManager::unregisterAction(factory->m_action, actionId(factory));
377 
378     qDeleteAll(s_allFactories);
379     s_allFactories.clear();
380 
381     s_areFactoriesLoaded = false;
382 }
383 
pluginFeatures()384 QSet<Id> IWizardFactory::pluginFeatures()
385 {
386     static QSet<Id> plugins;
387     if (plugins.isEmpty()) {
388         // Implicitly create a feature for each plugin loaded:
389         foreach (ExtensionSystem::PluginSpec *s, ExtensionSystem::PluginManager::plugins()) {
390             if (s->state() == ExtensionSystem::PluginSpec::Running)
391                 plugins.insert(Id::fromString(s->name()));
392         }
393     }
394     return plugins;
395 }
396 
availableFeatures(Id platformId)397 QSet<Id> IWizardFactory::availableFeatures(Id platformId)
398 {
399     QSet<Id> availableFeatures;
400 
401     foreach (const IFeatureProvider *featureManager, s_providerList)
402         availableFeatures.unite(featureManager->availableFeatures(platformId));
403 
404     return availableFeatures;
405 }
406 
initialize()407 void IWizardFactory::initialize()
408 {
409     connect(ICore::instance(), &ICore::coreAboutToClose, &IWizardFactory::clearWizardFactories);
410 
411     auto resetAction = new QAction(tr("Reload All Wizards"), ActionManager::instance());
412     ActionManager::registerAction(resetAction, "Wizard.Factory.Reset");
413 
414     connect(resetAction, &QAction::triggered, &IWizardFactory::clearWizardFactories);
415     connect(ICore::instance(), &ICore::newItemDialogStateChanged, resetAction,
416             [resetAction]() { resetAction->setEnabled(!ICore::isNewItemDialogRunning()); });
417 
418     s_inspectWizardAction = new QAction(tr("Inspect Wizard State"), ActionManager::instance());
419     ActionManager::registerAction(s_inspectWizardAction, "Wizard.Inspect");
420 }
421