diff --git a/plugins/appwizard/projectselectionpage.cpp b/plugins/appwizard/projectselectionpage.cpp index 7d9b861a7..6eecc3ede 100644 --- a/plugins/appwizard/projectselectionpage.cpp +++ b/plugins/appwizard/projectselectionpage.cpp @@ -1,363 +1,373 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2011 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "projectselectionpage.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include "ui_projectselectionpage.h" #include "projecttemplatesmodel.h" using namespace KDevelop; ProjectSelectionPage::ProjectSelectionPage(ProjectTemplatesModel *templatesModel, AppWizardDialog *wizardDialog) : AppWizardPageWidget(wizardDialog), m_templatesModel(templatesModel) { ui = new Ui::ProjectSelectionPage(); ui->setupUi(this); setContentsMargins(0,0,0,0); ui->descriptionContent->setBackgroundRole(QPalette::Base); ui->descriptionContent->setForegroundRole(QPalette::Text); ui->locationUrl->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly ); ui->locationUrl->setUrl(KDevelop::ICore::self()->projectController()->projectsBaseDirectory()); ui->locationValidWidget->hide(); ui->locationValidWidget->setMessageType(KMessageWidget::Error); ui->locationValidWidget->setCloseButtonVisible(false); connect( ui->locationUrl->lineEdit(), &KLineEdit::textEdited, this, &ProjectSelectionPage::urlEdited); connect( ui->locationUrl, &KUrlRequester::urlSelected, this, &ProjectSelectionPage::urlEdited); connect( ui->appNameEdit, &QLineEdit::textEdited, this, &ProjectSelectionPage::nameChanged ); m_listView = new KDevelop::MultiLevelListView(this); m_listView->setLevels(2); m_listView->setHeaderLabels(QStringList() << i18n("Category") << i18n("Project Type")); m_listView->setModel(templatesModel); m_listView->setLastModelsFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); m_listView->setContentsMargins(0, 0, 0, 0); connect (m_listView, &MultiLevelListView::currentIndexChanged, this, &ProjectSelectionPage::typeChanged); ui->gridLayout->addWidget(m_listView, 0, 0, 1, 1); typeChanged(m_listView->currentIndex()); connect( ui->templateType, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSelectionPage::templateChanged ); QPushButton* getMoreButton = new QPushButton(i18n("Get More Templates"), m_listView); getMoreButton->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); connect (getMoreButton, &QPushButton::clicked, this, &ProjectSelectionPage::moreTemplatesClicked); m_listView->addWidget(0, getMoreButton); QPushButton* loadButton = new QPushButton(m_listView); loadButton->setText(i18n("Load Template From File")); loadButton->setIcon(QIcon::fromTheme(QStringLiteral("application-x-archive"))); connect (loadButton, &QPushButton::clicked, this, &ProjectSelectionPage::loadFileClicked); m_listView->addWidget(0, loadButton); m_wizardDialog = wizardDialog; } void ProjectSelectionPage::nameChanged() { validateData(); emit locationChanged( location() ); } ProjectSelectionPage::~ProjectSelectionPage() { delete ui; } void ProjectSelectionPage::typeChanged(const QModelIndex& idx) { if (!idx.model()) { qCDebug(PLUGIN_APPWIZARD) << "Index with no model"; return; } int children = idx.model()->rowCount(idx); ui->templateType->setVisible(children); ui->templateType->setEnabled(children > 1); if (children) { ui->templateType->setModel(m_templatesModel); ui->templateType->setRootModelIndex(idx); ui->templateType->setCurrentIndex(0); itemChanged(idx.model()->index(0, 0, idx)); } else { itemChanged(idx); } } void ProjectSelectionPage::templateChanged(int current) { QModelIndex idx=m_templatesModel->index(current, 0, ui->templateType->rootModelIndex()); itemChanged(idx); } void ProjectSelectionPage::itemChanged( const QModelIndex& current) { QString picPath = current.data( KDevelop::TemplatesModel::IconNameRole ).toString(); if( picPath.isEmpty() ) { QIcon icon(QStringLiteral("kdevelop")); ui->icon->setPixmap(icon.pixmap(128, 128)); ui->icon->setFixedHeight(128); } else { QPixmap pixmap( picPath ); ui->icon->setPixmap( pixmap ); ui->icon->setFixedHeight( pixmap.height() ); } // header name is either from this index directly or the parents if we show the combo box const QVariant headerData = ui->templateType->isVisible() ? current.parent().data() : current.data(); ui->header->setText(QStringLiteral("

%1

").arg(headerData.toString().trimmed())); ui->description->setText(current.data(KDevelop::TemplatesModel::CommentRole).toString()); validateData(); ui->propertiesBox->setEnabled(true); } QString ProjectSelectionPage::selectedTemplate() { QStandardItem *item = getCurrentItem(); if (item) return item->data().toString(); else return QString(); } QUrl ProjectSelectionPage::location() { QUrl url = ui->locationUrl->url().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + encodedAppName()); return url; } QString ProjectSelectionPage::appName() { return ui->appNameEdit->text(); } void ProjectSelectionPage::urlEdited() { validateData(); emit locationChanged( location() ); } void ProjectSelectionPage::validateData() { QUrl url = ui->locationUrl->url(); if( !url.isLocalFile() || url.isEmpty() ) { ui->locationValidWidget->setText( i18n("Invalid location") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( appName().isEmpty() ) { ui->locationValidWidget->setText( i18n("Empty project name") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } if( !appName().isEmpty() ) { QString appname = appName(); QString templatefile = m_wizardDialog->appInfo().appTemplate; // Read template file KConfig config(templatefile); KConfigGroup configgroup(&config, "General"); QString pattern = configgroup.readEntry( "ValidProjectName" , "^[a-zA-Z][a-zA-Z0-9_]+$" ); // Validation int pos = 0; QRegExp regex( pattern ); QRegExpValidator validator( regex ); if( validator.validate(appname, pos) == QValidator::Invalid ) { ui->locationValidWidget->setText( i18n("Invalid project name") ); emit invalid(); return; } } QDir tDir(url.toLocalFile()); while (!tDir.exists() && !tDir.isRoot()) { if (!tDir.cdUp()) { break; } } if (tDir.exists()) { QFileInfo tFileInfo(tDir.absolutePath()); if (!tFileInfo.isWritable() || !tFileInfo.isExecutable()) { ui->locationValidWidget->setText( i18n("Unable to create subdirectories, " "missing permissions on: %1", tDir.absolutePath()) ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } QStandardItem* item = getCurrentItem(); if( item && !item->hasChildren() ) { ui->locationValidWidget->animatedHide(); emit valid(); } else { ui->locationValidWidget->setText( i18n("Invalid project template, please choose a leaf item") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } // Check for non-empty target directory. Not an error, but need to display a warning. url.setPath( url.path() + '/' + encodedAppName() ); QFileInfo fi( url.toLocalFile() ); if( fi.exists() && fi.isDir() ) { if( !QDir( fi.absoluteFilePath()).entryList( QDir::NoDotAndDotDot | QDir::AllEntries ).isEmpty() ) { ui->locationValidWidget->setText( i18n("Path already exists and contains files. Open it as a project.") ); ui->locationValidWidget->animatedShow(); emit invalid(); return; } } } QByteArray ProjectSelectionPage::encodedAppName() { // : < > * ? / \ | " are invalid on windows QByteArray tEncodedName = appName().toUtf8(); for (int i = 0; i < tEncodedName.size(); ++i) { QChar tChar(tEncodedName.at( i )); if (tChar.isDigit() || tChar.isSpace() || tChar.isLetter() || tChar == '%') continue; QByteArray tReplace = QUrl::toPercentEncoding( tChar ); tEncodedName.replace( tEncodedName.at( i ) ,tReplace ); i = i + tReplace.size() - 1; } return tEncodedName; } QStandardItem* ProjectSelectionPage::getCurrentItem() const { QStandardItem* item = m_templatesModel->itemFromIndex( m_listView->currentIndex() ); if ( item && item->hasChildren() ) { const int currect = ui->templateType->currentIndex(); const QModelIndex idx = m_templatesModel->index( currect, 0, ui->templateType->rootModelIndex() ); item = m_templatesModel->itemFromIndex(idx); } return item; } bool ProjectSelectionPage::shouldContinue() { QFileInfo fi(location().toLocalFile()); if (fi.exists() && fi.isDir()) { if (!QDir(fi.absoluteFilePath()).entryList(QDir::NoDotAndDotDot | QDir::AllEntries).isEmpty()) { int res = KMessageBox::questionYesNo(this, i18n("The specified path already exists and contains files. " "Are you sure you want to proceed?")); return res == KMessageBox::Yes; } } return true; } void ProjectSelectionPage::loadFileClicked() { - QString filter = QStringLiteral("application/x-desktop application/x-bzip-compressed-tar application/zip"); - const QString fileName = QFileDialog::getOpenFileName(this, i18n("Load Template From File"), QString(), filter); - if (!fileName.isEmpty()) - { + const QStringList supportedMimeTypes { + QStringLiteral("application/x-desktop"), + QStringLiteral("application/x-bzip-compressed-tar"), + QStringLiteral("application/zip") + }; + QFileDialog fileDialog(this, i18n("Load Template From File")); + fileDialog.setMimeTypeFilters(supportedMimeTypes); + fileDialog.setFileMode(QFileDialog::ExistingFiles); + + if (!fileDialog.exec()) { + return; + } + + for (const auto& fileName : fileDialog.selectedFiles()) { QString destination = m_templatesModel->loadTemplateFile(fileName); QModelIndexList indexes = m_templatesModel->templateIndexes(destination); if (indexes.size() > 2) { m_listView->setCurrentIndex(indexes.at(1)); ui->templateType->setCurrentIndex(indexes.at(2).row()); } } } void ProjectSelectionPage::moreTemplatesClicked() { KNS3::DownloadDialog dialog(QStringLiteral("kdevappwizard.knsrc"), this); dialog.exec(); auto entries = dialog.changedEntries(); if (entries.isEmpty()) { return; } m_templatesModel->refresh(); bool updated = false; foreach (const KNS3::Entry& entry, entries) { if (!entry.installedFiles().isEmpty()) { updated = true; setCurrentTemplate(entry.installedFiles().at(0)); break; } } if (!updated) { m_listView->setCurrentIndex(QModelIndex()); } } void ProjectSelectionPage::setCurrentTemplate (const QString& fileName) { QModelIndexList indexes = m_templatesModel->templateIndexes(fileName); if (indexes.size() > 1) { m_listView->setCurrentIndex(indexes.at(1)); } if (indexes.size() > 2) { ui->templateType->setCurrentIndex(indexes.at(2).row()); } } diff --git a/shell/settings/templatepage.cpp b/shell/settings/templatepage.cpp index 6685422de..67c0a676b 100644 --- a/shell/settings/templatepage.cpp +++ b/shell/settings/templatepage.cpp @@ -1,128 +1,134 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "templatepage.h" #include "ui_templatepage.h" #include #include -#include #include #include #include #include #include #include +#include + TemplatePage::TemplatePage (KDevelop::ITemplateProvider* provider, QWidget* parent) : QWidget (parent), m_provider(provider) { ui = new Ui::TemplatePage; ui->setupUi(this); ui->getNewButton->setVisible(!m_provider->knsConfigurationFile().isEmpty()); connect(ui->getNewButton, &QPushButton::clicked, this, &TemplatePage::getMoreTemplates); ui->shareButton->setVisible(!m_provider->knsConfigurationFile().isEmpty()); connect(ui->shareButton, &QPushButton::clicked, this, &TemplatePage::shareTemplates); ui->loadButton->setVisible(!m_provider->supportedMimeTypes().isEmpty()); connect(ui->loadButton, &QPushButton::clicked, this, &TemplatePage::loadFromFile); ui->extractButton->setEnabled(false); connect(ui->extractButton, &QPushButton::clicked, this, &TemplatePage::extractTemplate); provider->reload(); ui->treeView->setModel(provider->templatesModel()); ui->treeView->expandAll(); connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TemplatePage::currentIndexChanged); } TemplatePage::~TemplatePage() { delete ui; } void TemplatePage::loadFromFile() { - const QString filter = m_provider->supportedMimeTypes().join(QStringLiteral(";;")); - const QString filename = QFileDialog::getOpenFileName(this, QString(), QString(), filter); - if (!filename.isEmpty()) { - m_provider->loadTemplate(filename); + QFileDialog fileDialog(this); + fileDialog.setMimeTypeFilters(m_provider->supportedMimeTypes()); + fileDialog.setFileMode(QFileDialog::ExistingFiles); + if (!fileDialog.exec()) { + return; + } + + for (const auto& file : fileDialog.selectedFiles()) { + m_provider->loadTemplate(file); } m_provider->reload(); } void TemplatePage::getMoreTemplates() { KNS3::DownloadDialog dialog(m_provider->knsConfigurationFile(), this); dialog.exec(); if (!dialog.changedEntries().isEmpty()) { m_provider->reload(); } } void TemplatePage::shareTemplates() { KNS3::UploadDialog dialog(m_provider->knsConfigurationFile(), this); dialog.exec(); } void TemplatePage::currentIndexChanged(const QModelIndex& index) { QString archive = ui->treeView->model()->data(index, KDevelop::TemplatesModel::ArchiveFileRole).toString(); ui->extractButton->setEnabled(QFileInfo::exists(archive)); } void TemplatePage::extractTemplate() { QModelIndex index = ui->treeView->currentIndex(); QString archiveName= ui->treeView->model()->data(index, KDevelop::TemplatesModel::ArchiveFileRole).toString(); QFileInfo info(archiveName); if (!info.exists()) { ui->extractButton->setEnabled(false); return; } QScopedPointer archive; if (info.suffix() == QLatin1String("zip")) { archive.reset(new KZip(archiveName)); } else { archive.reset(new KTar(archiveName)); } archive->open(QIODevice::ReadOnly); const QString destination = QFileDialog::getExistingDirectory() + '/' + info.baseName(); archive->directory()->copyTo(destination); }