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