1 /*
2     SPDX-FileCopyrightText: 2001 Bernd Gehrmann <bernd@kdevelop.org>
3     SPDX-FileCopyrightText: 2004-2005 Sascha Cunz <sascha@kdevelop.org>
4     SPDX-FileCopyrightText: 2005 Ian Reinhart Geiser <ian@geiseri.com>
5     SPDX-FileCopyrightText: 2007 Alexander Dymo <adymo@kdevelop.org>
6     SPDX-FileCopyrightText: 2008 Evgeniy Ivanov <powerfox@kde.ru>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "appwizardplugin.h"
12 
13 #include <QAction>
14 #include <QDir>
15 #include <QDirIterator>
16 #include <QFile>
17 #include <QFileInfo>
18 #include <QMimeType>
19 #include <QMimeDatabase>
20 #include <QStandardPaths>
21 #include <QTemporaryDir>
22 #include <QTextCodec>
23 #include <QTextStream>
24 #include <qplatformdefs.h>
25 
26 #include <KActionCollection>
27 #include <KConfigGroup>
28 #include <KIO/CopyJob>
29 #include <KIO/DeleteJob>
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 #include <KParts/MainWindow>
33 #include <KPluginFactory>
34 #include <KSharedConfig>
35 #include <KTar>
36 #include <KZip>
37 #include <KMacroExpander>
38 
39 #include <interfaces/icore.h>
40 #include <interfaces/iprojectcontroller.h>
41 #include <interfaces/iplugincontroller.h>
42 #include <interfaces/iuicontroller.h>
43 #include <interfaces/idocumentcontroller.h>
44 #include <interfaces/context.h>
45 #include <interfaces/contextmenuextension.h>
46 #include <util/scopeddialog.h>
47 #include <sublime/message.h>
48 #include <vcs/vcsjob.h>
49 #include <vcs/interfaces/icentralizedversioncontrol.h>
50 #include <vcs/interfaces/idistributedversioncontrol.h>
51 
52 #include "appwizarddialog.h"
53 #include "projectselectionpage.h"
54 #include "projectvcspage.h"
55 #include "projecttemplatesmodel.h"
56 #include "debug.h"
57 
58 using namespace KDevelop;
59 
60 K_PLUGIN_FACTORY_WITH_JSON(AppWizardFactory, "kdevappwizard.json", registerPlugin<AppWizardPlugin>();)
61 
AppWizardPlugin(QObject * parent,const QVariantList &)62 AppWizardPlugin::AppWizardPlugin(QObject *parent, const QVariantList &)
63     : KDevelop::IPlugin(QStringLiteral("kdevappwizard"), parent)
64 {
65     setXMLFile(QStringLiteral("kdevappwizard.rc"));
66 
67     m_newFromTemplate = actionCollection()->addAction(QStringLiteral("project_new"));
68     m_newFromTemplate->setIcon(QIcon::fromTheme(QStringLiteral("project-development-new-template")));
69     m_newFromTemplate->setText(i18nc("@action", "New from Template..."));
70     connect(m_newFromTemplate, &QAction::triggered, this, &AppWizardPlugin::slotNewProject);
71     m_newFromTemplate->setToolTip( i18nc("@info:tooltip", "Generate a new project from a template") );
72     m_newFromTemplate->setWhatsThis( i18nc("@info:whatsthis", "This starts KDevelop's application wizard. "
73                                           "It helps you to generate a skeleton for your "
74                                           "application from a set of templates.") );
75 }
76 
~AppWizardPlugin()77 AppWizardPlugin::~AppWizardPlugin()
78 {
79 }
80 
slotNewProject()81 void AppWizardPlugin::slotNewProject()
82 {
83     model()->refresh();
84 
85     ScopedDialog<AppWizardDialog> dlg(core()->pluginController(), m_templatesModel);
86 
87     if (dlg->exec() == QDialog::Accepted)
88     {
89         QString project = createProject( dlg->appInfo() );
90         if (!project.isEmpty())
91         {
92             core()->projectController()->openProject(QUrl::fromLocalFile(project));
93 
94             KConfig templateConfig(dlg->appInfo().appTemplate);
95             KConfigGroup general(&templateConfig, "General");
96 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
97             const QStringList fileArgs = general.readEntry("ShowFilesAfterGeneration").split(QLatin1Char(','), Qt::SkipEmptyParts);
98 #else
99             const QStringList fileArgs = general.readEntry("ShowFilesAfterGeneration").split(QLatin1Char(','), QString::SkipEmptyParts);
100 #endif
101             for (const auto& fileArg : fileArgs) {
102                 QString file = KMacroExpander::expandMacros(fileArg.trimmed(), m_variables);
103                 if (QDir::isRelativePath(file)) {
104                     file = m_variables[QStringLiteral("PROJECTDIR")] + QLatin1Char('/') + file;
105                 }
106                 core()->documentController()->openDocument(QUrl::fromUserInput(file));
107             }
108         } else {
109             const QString messageText = i18n("Could not create project from template.");
110             auto* message = new Sublime::Message(messageText, Sublime::Message::Error);
111             ICore::self()->uiController()->postMessage(message);
112        }
113     }
114 }
115 
116 namespace
117 {
118 
toDVCS(IPlugin * plugin)119 IDistributedVersionControl* toDVCS(IPlugin* plugin)
120 {
121     Q_ASSERT(plugin);
122     return plugin->extension<IDistributedVersionControl>();
123 }
124 
toCVCS(IPlugin * plugin)125 ICentralizedVersionControl* toCVCS(IPlugin* plugin)
126 {
127     Q_ASSERT(plugin);
128     return plugin->extension<ICentralizedVersionControl>();
129 }
130 
131 /*! Trouble while initializing version control. Show failure message to user. */
vcsError(const QString & errorMsg,QTemporaryDir & tmpdir,const QUrl & dest,const QString & details=QString ())132 void vcsError(const QString &errorMsg, QTemporaryDir &tmpdir, const QUrl &dest, const QString &details = QString())
133 {
134     QString displayDetails = details;
135     if (displayDetails.isEmpty())
136     {
137         displayDetails = i18n("Please see the Version Control tool view.");
138     }
139     KMessageBox::detailedError(nullptr, errorMsg, displayDetails, i18nc("@title:window", "Version Control System Error"));
140     KIO::del(dest, KIO::HideProgressInfo)->exec();
141     tmpdir.remove();
142 }
143 
144 /*! Setup distributed version control for a new project defined by @p info. Use @p scratchArea for temporary files  */
initializeDVCS(IDistributedVersionControl * dvcs,const ApplicationInfo & info,QTemporaryDir & scratchArea)145 bool initializeDVCS(IDistributedVersionControl* dvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea)
146 {
147     Q_ASSERT(dvcs);
148     qCDebug(PLUGIN_APPWIZARD) << "DVCS system is used, just initializing DVCS";
149 
150     const QUrl& dest = info.location;
151     //TODO: check if we want to handle KDevelop project files (like now) or only SRC dir
152     VcsJob* job = dvcs->init(dest);
153     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
154     {
155         vcsError(i18n("Could not initialize DVCS repository"), scratchArea, dest);
156         return false;
157     }
158     qCDebug(PLUGIN_APPWIZARD) << "Initializing DVCS repository:" << dest;
159 
160     qCDebug(PLUGIN_APPWIZARD) << "Checking for valid files in the DVCS repository:" << dest;
161     job = dvcs->status({dest}, KDevelop::IBasicVersionControl::Recursive);
162     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
163     {
164         vcsError(i18n("Could not check status of the DVCS repository"), scratchArea, dest);
165         return false;
166     }
167 
168     if (job->fetchResults().toList().isEmpty())
169     {
170         qCDebug(PLUGIN_APPWIZARD) << "No files to add, skipping commit in the DVCS repository:" << dest;
171         return true;
172     }
173 
174     job = dvcs->add({dest}, KDevelop::IBasicVersionControl::Recursive);
175     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
176     {
177         vcsError(i18n("Could not add files to the DVCS repository"), scratchArea, dest);
178         return false;
179     }
180 
181     job = dvcs->commit(info.importCommitMessage, {dest},
182                             KDevelop::IBasicVersionControl::Recursive);
183     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded)
184     {
185         vcsError(i18n("Could not import project into %1.", dvcs->name()), scratchArea, dest, job ? job->errorString() : QString());
186         return false;
187     }
188 
189     return true; // We're good
190 }
191 
192 /*! Setup version control for a new project defined by @p info. Use @p scratchArea for temporary files  */
initializeCVCS(ICentralizedVersionControl * cvcs,const ApplicationInfo & info,QTemporaryDir & scratchArea)193 bool initializeCVCS(ICentralizedVersionControl* cvcs, const ApplicationInfo& info, QTemporaryDir& scratchArea)
194 {
195     Q_ASSERT(cvcs);
196 
197     qCDebug(PLUGIN_APPWIZARD) << "Importing" << info.sourceLocation << "to"
198              << info.repository.repositoryServer();
199     VcsJob* job = cvcs->import( info.importCommitMessage, QUrl::fromLocalFile(scratchArea.path()), info.repository);
200     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded )
201     {
202         vcsError(i18n("Could not import project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer()));
203         return false;
204     }
205 
206     qCDebug(PLUGIN_APPWIZARD) << "Checking out";
207     job = cvcs->createWorkingCopy( info.repository, info.location, IBasicVersionControl::Recursive);
208     if (!job || !job->exec() || job->status() != VcsJob::JobSucceeded )
209     {
210         vcsError(i18n("Could not checkout imported project"), scratchArea, QUrl::fromUserInput(info.repository.repositoryServer()));
211         return false;
212     }
213 
214     return true; // initialization phase complete
215 }
216 
generateIdentifier(const QString & appname)217 QString generateIdentifier( const QString& appname )
218 {
219     QString tmp = appname;
220     QRegExp re(QStringLiteral("[^a-zA-Z0-9_]"));
221     return tmp.replace(re, QStringLiteral("_"));
222 }
223 
224 } // end anonymous namespace
225 
createProject(const ApplicationInfo & info)226 QString AppWizardPlugin::createProject(const ApplicationInfo& info)
227 {
228     QFileInfo templateInfo(info.appTemplate);
229     if (!templateInfo.exists()) {
230         qCWarning(PLUGIN_APPWIZARD) << "Project app template does not exist:" << info.appTemplate;
231         return QString();
232     }
233 
234     QString templateName = templateInfo.baseName();
235     qCDebug(PLUGIN_APPWIZARD) << "Searching archive for template name:" << templateName;
236 
237     QString templateArchive;
238     const QStringList filters = {templateName + QStringLiteral(".*")};
239     const QStringList matchesPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdevappwizard/templates/"), QStandardPaths::LocateDirectory);
240     for (const QString& matchesPath : matchesPaths) {
241         const QStringList files = QDir(matchesPath).entryList(filters);
242         if(!files.isEmpty()) {
243             templateArchive = matchesPath + files.first();
244         }
245     }
246 
247     if(templateArchive.isEmpty()) {
248         qCWarning(PLUGIN_APPWIZARD) << "Template name does not exist in the template list";
249         return QString();
250     }
251 
252     qCDebug(PLUGIN_APPWIZARD) << "Using template archive:" << templateArchive;
253 
254     QUrl dest = info.location;
255 
256     //prepare variable substitution hash
257     m_variables.clear();
258     m_variables[QStringLiteral("APPNAME")] = info.name;
259     m_variables[QStringLiteral("APPNAMEUC")] = generateIdentifier(info.name.toUpper());
260     m_variables[QStringLiteral("APPNAMELC")] = info.name.toLower();
261     m_variables[QStringLiteral("APPNAMEID")] = generateIdentifier(info.name);
262     m_variables[QStringLiteral("PROJECTDIR")] = dest.toLocalFile();
263     // backwards compatibility
264     m_variables[QStringLiteral("dest")] = m_variables[QStringLiteral("PROJECTDIR")];
265     m_variables[QStringLiteral("PROJECTDIRNAME")] = dest.fileName();
266     m_variables[QStringLiteral("VERSIONCONTROLPLUGIN")] = info.vcsPluginName;
267 
268     KArchive* arch = nullptr;
269     if( templateArchive.endsWith(QLatin1String(".zip")) )
270     {
271         arch = new KZip(templateArchive);
272     }
273     else
274     {
275         arch = new KTar(templateArchive, QStringLiteral("application/x-bzip"));
276     }
277     if (arch->open(QIODevice::ReadOnly))
278     {
279         QTemporaryDir tmpdir;
280         QString unpackDir = tmpdir.path(); //the default value for all Centralized VCS
281         IPlugin* plugin = core()->pluginController()->loadPlugin( info.vcsPluginName );
282         if( info.vcsPluginName.isEmpty() || ( plugin && plugin->extension<KDevelop::IDistributedVersionControl>() ) )
283         {
284             if( !QFileInfo::exists( dest.toLocalFile() ) )
285             {
286                 QDir::root().mkpath( dest.toLocalFile() );
287             }
288             unpackDir = dest.toLocalFile(); //in DVCS we unpack template directly to the project's directory
289         }
290         else
291         {
292             QUrl url = KIO::upUrl(dest);
293             if(!QFileInfo::exists(url.toLocalFile())) {
294                 QDir::root().mkpath(url.toLocalFile());
295             }
296         }
297 
298         // estimate metadata files which should not be copied
299         QStringList metaDataFileNames;
300 
301         // try by same name
302         const KArchiveEntry *templateEntry =
303             arch->directory()->entry(templateName + QLatin1String(".kdevtemplate"));
304 
305         // but could be different name, if e.g. downloaded, so make a guess
306         if (!templateEntry || !templateEntry->isFile()) {
307             const auto& entries = arch->directory()->entries();
308             for (const auto& entryName : entries) {
309                 if (entryName.endsWith(QLatin1String(".kdevtemplate"))) {
310                     templateEntry = arch->directory()->entry(entryName);
311                     break;
312                 }
313             }
314         }
315 
316         if (templateEntry && templateEntry->isFile()) {
317             metaDataFileNames << templateEntry->name();
318 
319             // check if a preview file is to be ignored
320             const auto *templateFile = static_cast<const KArchiveFile*>(templateEntry);
321             QTemporaryDir temporaryDir;
322             templateFile->copyTo(temporaryDir.path());
323 
324             KConfig config(temporaryDir.path() + QLatin1Char('/') + templateEntry->name());
325             KConfigGroup group(&config, "General");
326             if (group.hasKey("Icon")) {
327                 const KArchiveEntry* iconEntry = arch->directory()->entry(group.readEntry("Icon"));
328                 if (iconEntry && iconEntry->isFile()) {
329                     metaDataFileNames << iconEntry->name();
330                 }
331             }
332         }
333 
334         if (!unpackArchive(arch->directory(), unpackDir, metaDataFileNames)) {
335             QString errorMsg = i18n("Could not create new project");
336             vcsError(errorMsg, tmpdir, QUrl::fromLocalFile(unpackDir));
337             return QString();
338         }
339 
340         if( !info.vcsPluginName.isEmpty() )
341         {
342             if (!plugin)
343             {
344                 // Red Alert, serious program corruption.
345                 // This should never happen, the vcs dialog presented a list of vcs
346                 // systems and now the chosen system doesn't exist anymore??
347                 tmpdir.remove();
348                 return QString();
349             }
350 
351             IDistributedVersionControl* dvcs = toDVCS(plugin);
352             ICentralizedVersionControl* cvcs = toCVCS(plugin);
353             bool success = false;
354             if (dvcs)
355             {
356                 success = initializeDVCS(dvcs, info, tmpdir);
357             }
358             else if (cvcs)
359             {
360                 success = initializeCVCS(cvcs, info, tmpdir);
361             }
362             else
363             {
364                 if (KMessageBox::Continue ==
365                     KMessageBox::warningContinueCancel(nullptr,
366                     QStringLiteral("Failed to initialize version control system, "
367                     "plugin is neither VCS nor DVCS.")))
368                     success = true;
369             }
370             if (!success) return QString();
371         }
372         tmpdir.remove();
373     }else
374     {
375         qCDebug(PLUGIN_APPWIZARD) << "failed to open template archive";
376         return QString();
377     }
378 
379     QString projectFileName = QDir::cleanPath(dest.toLocalFile() + QLatin1Char('/') + info.name + QLatin1String(".kdev4"));
380 
381     // Loop through the new project directory and try to detect the first .kdev4 file.
382     // If one is found this file will be used. So .kdev4 file can be stored in any subdirectory and the
383     // project templates can be more complex.
384     QDirIterator it(QDir::cleanPath( dest.toLocalFile()), QStringList() << QStringLiteral("*.kdev4"), QDir::NoFilter, QDirIterator::Subdirectories);
385     if(it.hasNext() == true)
386     {
387         projectFileName = it.next();
388     }
389 
390     qCDebug(PLUGIN_APPWIZARD) << "Returning" << projectFileName << QFileInfo::exists( projectFileName ) ;
391 
392     const QFileInfo projectFileInfo(projectFileName);
393     if (!projectFileInfo.exists()) {
394         qCDebug(PLUGIN_APPWIZARD) << "creating .kdev4 file";
395         KSharedConfigPtr cfg = KSharedConfig::openConfig( projectFileName, KConfig::SimpleConfig );
396         KConfigGroup project = cfg->group( "Project" );
397         project.writeEntry( "Name", info.name );
398         QString manager = QStringLiteral("KDevGenericManager");
399 
400         QDir d( dest.toLocalFile() );
401         const auto data = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager"));
402         for (const KPluginMetaData& info : data) {
403             QStringList filter = KPluginMetaData::readStringList(info.rawData(), QStringLiteral("X-KDevelop-ProjectFilesFilter"));
404             if (!filter.isEmpty()) {
405                 if (!d.entryList(filter).isEmpty()) {
406                     manager = info.pluginId();
407                     break;
408                 }
409             }
410         }
411         project.writeEntry( "Manager", manager );
412         project.sync();
413         cfg->sync();
414         KConfigGroup project2 = cfg->group( "Project" );
415         qCDebug(PLUGIN_APPWIZARD) << "kdev4 file contents:" << project2.readEntry("Name", "") << project2.readEntry("Manager", "" );
416     }
417 
418     // create developer .kde4 file
419     const QString developerProjectFileName = projectFileInfo.canonicalPath() + QLatin1String("/.kdev4/") + projectFileInfo.fileName();
420 
421     qCDebug(PLUGIN_APPWIZARD) << "creating developer .kdev4 file:" << developerProjectFileName;
422     KSharedConfigPtr developerCfg = KSharedConfig::openConfig(developerProjectFileName, KConfig::SimpleConfig);
423     KConfigGroup developerProjectGroup = developerCfg->group("Project");
424     developerProjectGroup.writeEntry("VersionControlSupport", info.vcsPluginName);
425     developerProjectGroup.sync();
426 
427     developerCfg->sync();
428 
429     return projectFileName;
430 }
431 
unpackArchive(const KArchiveDirectory * dir,const QString & dest,const QStringList & skipList)432 bool AppWizardPlugin::unpackArchive(const KArchiveDirectory* dir, const QString& dest, const QStringList& skipList)
433 {
434     qCDebug(PLUGIN_APPWIZARD) << "unpacking dir:" << dir->name() << "to" << dest;
435     const QStringList entries = dir->entries();
436     qCDebug(PLUGIN_APPWIZARD) << "entries:" << entries.join(QLatin1Char(','));
437 
438     //This extra tempdir is needed just for the files that have special names,
439     //which may contain macros also files contain content with macros. So the
440     //easiest way to extract the files from the archive and then rename them
441     //and replace the macros is to use a tempdir and copy the file (and
442     //replacing while copying). This also allows one to easily remove all files,
443     //by just unlinking the tempdir
444     QTemporaryDir tdir;
445 
446     bool ret = true;
447 
448     for (const auto& entryName : entries) {
449         if (skipList.contains(entryName)) {
450             continue;
451         }
452 
453         const auto entry = dir->entry(entryName);
454         if (entry->isDirectory()) {
455             const auto* subdir = static_cast<const KArchiveDirectory*>(entry);
456             const QString newdest = dest + QLatin1Char('/') + KMacroExpander::expandMacros(subdir->name(), m_variables);
457             if( !QFileInfo::exists( newdest ) )
458             {
459                 QDir::root().mkdir( newdest  );
460             }
461             ret |= unpackArchive(subdir, newdest);
462         }
463         else if (entry->isFile()) {
464             const auto* file = static_cast<const KArchiveFile*>(entry);
465             file->copyTo(tdir.path());
466             const QString destName = dest + QLatin1Char('/') + file->name();
467             if (!copyFileAndExpandMacros(QDir::cleanPath(tdir.path() + QLatin1Char('/') + file->name()),
468                     KMacroExpander::expandMacros(destName, m_variables)))
469             {
470                 KMessageBox::sorry(nullptr, i18n("The file %1 cannot be created.", dest));
471                 return false;
472             }
473         }
474     }
475     tdir.remove();
476     return ret;
477 }
478 
copyFileAndExpandMacros(const QString & source,const QString & dest)479 bool AppWizardPlugin::copyFileAndExpandMacros(const QString &source, const QString &dest)
480 {
481     qCDebug(PLUGIN_APPWIZARD) << "copy:" << source << "to" << dest;
482     QMimeDatabase db;
483     QMimeType mime = db.mimeTypeForFile(source);
484     if( !mime.inherits(QStringLiteral("text/plain")) )
485     {
486         KIO::CopyJob* job = KIO::copy( QUrl::fromUserInput(source), QUrl::fromUserInput(dest), KIO::HideProgressInfo );
487         if( !job->exec() )
488         {
489             return false;
490         }
491         return true;
492     } else
493     {
494         QFile inputFile(source);
495         QFile outputFile(dest);
496 
497 
498         if (inputFile.open(QFile::ReadOnly) && outputFile.open(QFile::WriteOnly))
499         {
500             QTextStream input(&inputFile);
501             input.setCodec(QTextCodec::codecForName("UTF-8"));
502             QTextStream output(&outputFile);
503             output.setCodec(QTextCodec::codecForName("UTF-8"));
504             while(!input.atEnd())
505             {
506                 QString line = input.readLine();
507                 output << KMacroExpander::expandMacros(line, m_variables) << "\n";
508             }
509 #ifndef Q_OS_WIN
510             // Preserve file mode...
511             QT_STATBUF statBuf;
512             QT_FSTAT(inputFile.handle(), &statBuf);
513             // Unix only, won't work in Windows, maybe KIO::chmod could be used
514             ::fchmod(outputFile.handle(), statBuf.st_mode);
515 #endif
516             return true;
517         }
518         else
519         {
520             inputFile.close();
521             outputFile.close();
522             return false;
523         }
524     }
525 }
contextMenuExtension(KDevelop::Context * context,QWidget * parent)526 KDevelop::ContextMenuExtension AppWizardPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
527 {
528     Q_UNUSED(parent);
529     KDevelop::ContextMenuExtension ext;
530     if ( context->type() != KDevelop::Context::ProjectItemContext || !static_cast<KDevelop::ProjectItemContext*>(context)->items().isEmpty() ) {
531         return ext;
532     }
533     ext.addAction(KDevelop::ContextMenuExtension::ProjectGroup, m_newFromTemplate);
534     return ext;
535 }
536 
model() const537 ProjectTemplatesModel* AppWizardPlugin::model() const
538 {
539     if(!m_templatesModel) {
540         auto* self = const_cast<AppWizardPlugin*>(this);
541         m_templatesModel = new ProjectTemplatesModel(self);
542     }
543     return m_templatesModel;
544 }
545 
templatesModel() const546 QAbstractItemModel* AppWizardPlugin::templatesModel() const
547 {
548     return model();
549 }
550 
knsConfigurationFile() const551 QString AppWizardPlugin::knsConfigurationFile() const
552 {
553     return QStringLiteral("kdevappwizard.knsrc");
554 }
555 
supportedMimeTypes() const556 QStringList AppWizardPlugin::supportedMimeTypes() const
557 {
558     const QStringList types{
559         QStringLiteral("application/x-desktop"),
560         QStringLiteral("application/x-bzip-compressed-tar"),
561         QStringLiteral("application/zip"),
562     };
563     return types;
564 }
565 
icon() const566 QIcon AppWizardPlugin::icon() const
567 {
568     return QIcon::fromTheme(QStringLiteral("project-development-new-template"));
569 }
570 
name() const571 QString AppWizardPlugin::name() const
572 {
573     return i18n("Project Templates");
574 }
575 
loadTemplate(const QString & fileName)576 void AppWizardPlugin::loadTemplate(const QString& fileName)
577 {
578     model()->loadTemplateFile(fileName);
579 }
580 
reload()581 void AppWizardPlugin::reload()
582 {
583     model()->refresh();
584 }
585 
586 
587 #include "appwizardplugin.moc"
588