File indexing completed on 2024-05-05 04:39:48

0001 #include "filetemplatesplugin.h"
0002 #include "templateclassassistant.h"
0003 #include "templatepreviewtoolview.h"
0004 #include "debug.h"
0005 
0006 #include <language/codegen/templatesmodel.h>
0007 #include <language/interfaces/editorcontext.h>
0008 #include <interfaces/icore.h>
0009 #include <interfaces/iproject.h>
0010 #include <interfaces/iuicontroller.h>
0011 #include <interfaces/context.h>
0012 #include <interfaces/contextmenuextension.h>
0013 #include <interfaces/idocumentcontroller.h>
0014 #include <interfaces/iselectioncontroller.h>
0015 #include <interfaces/iprojectcontroller.h>
0016 #include <project/projectmodel.h>
0017 #include <util/path.h>
0018 
0019 #include <KActionCollection>
0020 #include <KConfigGroup>
0021 #include <KLocalizedString>
0022 #include <KPluginFactory>
0023 
0024 #include <QAction>
0025 #include <QApplication>
0026 #include <QDir>
0027 #include <QIcon>
0028 
0029 using namespace KDevelop;
0030 
0031 K_PLUGIN_FACTORY_WITH_JSON(FileTemplatesFactory, "kdevfiletemplates.json", registerPlugin<FileTemplatesPlugin>();)
0032 
0033 class TemplatePreviewFactory : public KDevelop::IToolViewFactory
0034 {
0035 public:
0036     explicit TemplatePreviewFactory(FileTemplatesPlugin* plugin)
0037     : KDevelop::IToolViewFactory()
0038     , m_plugin(plugin)
0039     {
0040 
0041     }
0042 
0043     QWidget* create(QWidget* parent = nullptr) override
0044     {
0045         return new TemplatePreviewToolView(m_plugin, parent);
0046     }
0047 
0048     QString id() const override
0049     {
0050         return QStringLiteral("org.kdevelop.TemplateFilePreview");
0051     }
0052 
0053     Qt::DockWidgetArea defaultPosition() const override
0054     {
0055         return Qt::RightDockWidgetArea;
0056     }
0057 
0058 private:
0059     FileTemplatesPlugin* m_plugin;
0060 };
0061 
0062 FileTemplatesPlugin::FileTemplatesPlugin(QObject* parent, const QVariantList& args)
0063     : IPlugin(QStringLiteral("kdevfiletemplates"), parent)
0064 {
0065     Q_UNUSED(args);
0066 
0067     setXMLFile(QStringLiteral("kdevfiletemplates.rc"));
0068     QAction* action = actionCollection()->addAction(QStringLiteral("new_from_template"));
0069     action->setText(i18nc("@action", "New from Template..."));
0070     action->setIcon( QIcon::fromTheme( QStringLiteral("code-class") ) );
0071     action->setWhatsThis(i18nc("@info:whatsthis", "Allows you to create new source code files, such as classes or unit tests, using templates." ) );
0072     action->setToolTip( i18nc("@info:tooltip",  "Create new files from a template" ) );
0073     connect (action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate);
0074 
0075     m_toolView = new TemplatePreviewFactory(this);
0076     core()->uiController()->addToolView(i18nc("@title:window", "Template Preview"), m_toolView);
0077 }
0078 
0079 FileTemplatesPlugin::~FileTemplatesPlugin()
0080 {
0081 
0082 }
0083 
0084 void FileTemplatesPlugin::unload()
0085 {
0086     core()->uiController()->removeToolView(m_toolView);
0087 }
0088 
0089 ContextMenuExtension FileTemplatesPlugin::contextMenuExtension(Context* context, QWidget* parent)
0090 {
0091     ContextMenuExtension ext;
0092     QUrl fileUrl;
0093 
0094     if (context->type() == Context::ProjectItemContext)
0095     {
0096         auto* projectContext = static_cast<ProjectItemContext*>(context);
0097         QList<ProjectBaseItem*> items = projectContext->items();
0098         if (items.size() != 1)
0099         {
0100             return ext;
0101         }
0102 
0103         QUrl url;
0104         ProjectBaseItem* item = items.first();
0105         if (item->folder())
0106         {
0107             url = item->path().toUrl();
0108         }
0109         else if (item->target())
0110         {
0111             url = item->parent()->path().toUrl();
0112         }
0113         if (url.isValid())
0114         {
0115             auto* action = new QAction(i18nc("@action:inmenu", "Create from Template..."), parent);
0116             action->setIcon(QIcon::fromTheme(QStringLiteral("code-class")));
0117             action->setData(url);
0118             connect(action, &QAction::triggered, this, &FileTemplatesPlugin::createFromTemplate);
0119             ext.addAction(ContextMenuExtension::FileGroup, action);
0120         }
0121 
0122         if (item->file())
0123         {
0124             fileUrl = item->path().toUrl();
0125         }
0126     }
0127     else if (context->type() == Context::EditorContext)
0128     {
0129         auto* editorContext = static_cast<KDevelop::EditorContext*>(context);
0130         fileUrl = editorContext->url();
0131     }
0132 
0133     if (fileUrl.isValid() && determineTemplateType(fileUrl) != NoTemplate)
0134     {
0135         auto* action = new QAction(i18nc("@action:inmenu", "Show Template Preview"), parent);
0136         action->setIcon(QIcon::fromTheme(QStringLiteral("document-preview")));
0137         action->setData(fileUrl);
0138         connect(action, &QAction::triggered, this, &FileTemplatesPlugin::previewTemplate);
0139         ext.addAction(ContextMenuExtension::ExtensionGroup, action);
0140     }
0141 
0142     return ext;
0143 }
0144 
0145 QString FileTemplatesPlugin::name() const
0146 {
0147     return i18n("File Templates");
0148 }
0149 
0150 QIcon FileTemplatesPlugin::icon() const
0151 {
0152     return QIcon::fromTheme(QStringLiteral("code-class"));
0153 }
0154 
0155 QAbstractItemModel* FileTemplatesPlugin::templatesModel() const
0156 {
0157     if(!m_model) {
0158         auto* self = const_cast<FileTemplatesPlugin*>(this);
0159         m_model = new TemplatesModel(QStringLiteral("kdevfiletemplates"), self);
0160     }
0161     return m_model;
0162 }
0163 
0164 QString FileTemplatesPlugin::knsConfigurationFile() const
0165 {
0166     return QStringLiteral("kdevfiletemplates.knsrc");
0167 }
0168 
0169 QStringList FileTemplatesPlugin::supportedMimeTypes() const
0170 {
0171     const QStringList types{
0172         QStringLiteral("application/x-desktop"),
0173         QStringLiteral("application/x-bzip-compressed-tar"),
0174         QStringLiteral("application/zip"),
0175     };
0176     return types;
0177 }
0178 
0179 void FileTemplatesPlugin::reload()
0180 {
0181     templatesModel();
0182     m_model->refresh();
0183 }
0184 
0185 void FileTemplatesPlugin::loadTemplate(const QString& fileName)
0186 {
0187     templatesModel();
0188     m_model->loadTemplateFile(fileName);
0189 }
0190 
0191 void FileTemplatesPlugin::createFromTemplate()
0192 {
0193     QUrl baseUrl;
0194     if (auto* action = qobject_cast<QAction*>(sender()))
0195     {
0196         baseUrl = action->data().toUrl();
0197     }
0198     if (!baseUrl.isValid()) {
0199         // fall-back to currently active document's parent directory
0200         IDocument* doc = ICore::self()->documentController()->activeDocument();
0201         if (doc && doc->url().isValid()) {
0202             baseUrl = doc->url().adjusted(QUrl::RemoveFilename);
0203         }
0204     }
0205     if (!baseUrl.isValid()) {
0206         // fall-back to currently selected project's or item's base directory
0207         auto* projectContext = dynamic_cast<ProjectItemContext*>(ICore::self()->selectionController()->currentSelection());
0208         if (projectContext) {
0209             const QList<ProjectBaseItem*> items = projectContext->items();
0210             if (items.size() == 1) {
0211                 ProjectBaseItem* item = items.at(0);
0212                 if (item->folder()) {
0213                     baseUrl = item->path().toUrl();
0214                 } else if (item->target()) {
0215                     baseUrl = item->parent()->path().toUrl();
0216                 }
0217             }
0218         }
0219     }
0220     if (!baseUrl.isValid()) {
0221         // fall back to base directory of currently open project, if there is only one
0222         const QList<IProject*> projects = ICore::self()->projectController()->projects();
0223         if (projects.size() == 1) {
0224             baseUrl = projects.at(0)->path().toUrl();
0225         }
0226     }
0227     if (!baseUrl.isValid()) {
0228         // last resort
0229         baseUrl = ICore::self()->projectController()->projectsBaseDirectory();
0230     }
0231     auto* assistant = new TemplateClassAssistant(QApplication::activeWindow(), baseUrl);
0232     assistant->setAttribute(Qt::WA_DeleteOnClose);
0233     assistant->show();
0234 }
0235 
0236 FileTemplatesPlugin::TemplateType FileTemplatesPlugin::determineTemplateType(const QUrl& url)
0237 {
0238     QDir dir(url.toLocalFile());
0239 
0240     /*
0241      * Search for a description file in the url's directory.
0242      * If it is not found there, try cascading up a maximum of 5 directories.
0243      */
0244     int level = 0;
0245     while (dir.cdUp() && level < 5)
0246     {
0247         const QStringList filters{QStringLiteral("*.kdevtemplate"), QStringLiteral("*.desktop")};
0248         const auto entries = dir.entryList(filters);
0249         for (const QString& entry : entries) {
0250             qCDebug(PLUGIN_FILETEMPLATES) << "Trying entry" << entry;
0251             /*
0252             * This logic is not perfect, but it works for most cases.
0253             *
0254             * Project template description files usually have the suffix
0255             * ".kdevtemplate", so those are easy to find. For project templates,
0256             * all the files in the directory are template files.
0257             *
0258             * On the other hand, file templates use the generic suffix ".desktop".
0259             * Fortunately, those explicitly list input and output files, so we
0260             * only match the explicitly listed files
0261             */
0262             if (entry.endsWith(QLatin1String(".kdevtemplate")))
0263             {
0264                 return ProjectTemplate;
0265             }
0266 
0267             auto* config = new KConfig(dir.absoluteFilePath(entry), KConfig::SimpleConfig);
0268             KConfigGroup group = config->group("General");
0269 
0270             qCDebug(PLUGIN_FILETEMPLATES) << "General group keys:" << group.keyList();
0271 
0272             if (!group.hasKey("Name") || !group.hasKey("Category"))
0273             {
0274                 continue;
0275             }
0276 
0277             if (group.hasKey("Files"))
0278             {
0279                 qCDebug(PLUGIN_FILETEMPLATES) << "Group has files " << group.readEntry("Files", QStringList());
0280                 const auto outputFiles = group.readEntry("Files", QStringList());
0281                 for (const QString& outputFile : outputFiles) {
0282                     if (dir.absoluteFilePath(config->group(outputFile).readEntry("File")) == url.toLocalFile())
0283                     {
0284                         return FileTemplate;
0285                     }
0286                 }
0287             }
0288 
0289             if (group.hasKey("ShowFilesAfterGeneration"))
0290             {
0291                 return ProjectTemplate;
0292             }
0293         }
0294 
0295         ++level;
0296     }
0297 
0298     return NoTemplate;
0299 }
0300 
0301 void FileTemplatesPlugin::previewTemplate()
0302 {
0303     auto* action = qobject_cast<QAction*>(sender());
0304     if (!action || !action->data().toUrl().isValid())
0305     {
0306         return;
0307     }
0308     auto* preview = qobject_cast<TemplatePreviewToolView*>(core()->uiController()->findToolView(i18nc("@title:window", "Template Preview"), m_toolView));
0309     if (!preview)
0310     {
0311         return;
0312     }
0313 
0314     core()->documentController()->activateDocument(core()->documentController()->openDocument(action->data().toUrl()));
0315 }
0316 
0317 #include "filetemplatesplugin.moc"
0318 #include "moc_filetemplatesplugin.cpp"