File indexing completed on 2024-04-28 04:37:23

0001 /*
0002     SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "projectsourcepage.h"
0008 #include "ui_projectsourcepage.h"
0009 #include <interfaces/iplugin.h>
0010 #include <interfaces/icore.h>
0011 #include <interfaces/iplugincontroller.h>
0012 #include <interfaces/iruncontroller.h>
0013 #include <vcs/interfaces/ibasicversioncontrol.h>
0014 #include <vcs/widgets/vcslocationwidget.h>
0015 #include <vcs/vcsjob.h>
0016 #include <vcs/vcslocation.h>
0017 
0018 #include <QVBoxLayout>
0019 
0020 #include <KSharedConfig>
0021 #include <KConfigGroup>
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 
0025 #include <interfaces/iprojectprovider.h>
0026 
0027 using namespace KDevelop;
0028 
0029 static const int FROM_FILESYSTEM_SOURCE_INDEX = 0;
0030 
0031 ProjectSourcePage::ProjectSourcePage(const QUrl& initial, const QUrl& repoUrl, IPlugin* preSelectPlugin,
0032                                      QWidget* parent)
0033     : QWidget(parent)
0034 {
0035     m_ui = new Ui::ProjectSourcePage;
0036     m_ui->setupUi(this);
0037 
0038     m_ui->status->setCloseButtonVisible(false);
0039     m_ui->status->setMessageType(KMessageWidget::Error);
0040 
0041     m_ui->workingDir->setUrl(initial);
0042     m_ui->workingDir->setMode(KFile::Directory);
0043 
0044     m_ui->sources->addItem(QIcon::fromTheme(QStringLiteral("folder")), i18nc("@item:inlistbox", "From File System"));
0045     m_plugins.append(nullptr);
0046 
0047     int preselectIndex = -1;
0048     IPluginController* pluginManager = ICore::self()->pluginController();
0049     const QList<IPlugin*> vcsPlugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IBasicVersionControl") );
0050     m_plugins.reserve(m_plugins.size() + vcsPlugins.size());
0051     for (IPlugin* p : vcsPlugins) {
0052         if (p == preSelectPlugin) {
0053             preselectIndex = m_plugins.count();
0054         }
0055         m_plugins.append(p);
0056         m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension<IBasicVersionControl>()->name());
0057     }
0058 
0059     const QList<IPlugin*> projectPlugins = pluginManager->allPluginsForExtension( QStringLiteral("org.kdevelop.IProjectProvider") );
0060     m_plugins.reserve(m_plugins.size() + projectPlugins.size());
0061     for (IPlugin* p : projectPlugins) {
0062         if (p == preSelectPlugin) {
0063             preselectIndex = m_plugins.count();
0064         }
0065         m_plugins.append(p);
0066         m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension<IProjectProvider>()->name());
0067     }
0068 
0069     if (preselectIndex == -1) {
0070         // "From File System" is quite unlikely to be what the user wants, so default to first real plugin...
0071         const int defaultIndex = (m_plugins.count() > 1) ? 1 : 0;
0072         KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers");
0073         preselectIndex = configGroup.readEntry("LastProviderIndex", defaultIndex);
0074     }
0075     preselectIndex = qBound(0, preselectIndex, m_ui->sources->count() - 1);
0076     m_ui->sources->setCurrentIndex(preselectIndex);
0077     setSourceWidget(preselectIndex, repoUrl);
0078 
0079     // connect as last step, otherwise KMessageWidget could get both animatedHide() and animatedShow()
0080     // during setup and due to a bug will ignore any but the first call
0081     // Only fixed for KF5 5.32
0082     connect(m_ui->workingDir, &KUrlRequester::textChanged, this, &ProjectSourcePage::reevaluateCorrection);
0083     connect(m_ui->sources, QOverload<int>::of(&QComboBox::currentIndexChanged),
0084             this, &ProjectSourcePage::setSourceIndex);
0085     connect(m_ui->get, &QPushButton::clicked, this, &ProjectSourcePage::checkoutVcsProject);
0086 }
0087 
0088 ProjectSourcePage::~ProjectSourcePage()
0089 {
0090     KConfigGroup configGroup = KSharedConfig::openConfig()->group("Providers");
0091     configGroup.writeEntry("LastProviderIndex", m_ui->sources->currentIndex());
0092 
0093     delete m_ui;
0094 }
0095 
0096 void ProjectSourcePage::setSourceIndex(int index)
0097 {
0098     setSourceWidget(index, QUrl());
0099 }
0100 
0101 void ProjectSourcePage::setSourceWidget(int index, const QUrl& repoUrl)
0102 {
0103     m_locationWidget = nullptr;
0104     m_providerWidget = nullptr;
0105     QLayoutItem *child;
0106     while ((child = m_ui->remoteWidgetLayout->takeAt(0)) != nullptr) {
0107         delete child->widget();
0108         delete child;
0109     }
0110 
0111     IBasicVersionControl* vcIface = vcsPerIndex(index);
0112     IProjectProvider* providerIface;
0113     bool found=false;
0114     if(vcIface) {
0115         found=true;
0116         m_locationWidget=vcIface->vcsLocation(m_ui->sourceBox);
0117         connect(m_locationWidget, &VcsLocationWidget::changed, this, &ProjectSourcePage::locationChanged);
0118 
0119         // set after connect, to trigger handler
0120         if (!repoUrl.isEmpty()) {
0121             m_locationWidget->setLocation(repoUrl);
0122         }
0123         m_ui->remoteWidgetLayout->addWidget(m_locationWidget);
0124     } else {
0125         providerIface = providerPerIndex(index);
0126         if(providerIface) {
0127             found=true;
0128             m_providerWidget=providerIface->providerWidget(m_ui->sourceBox);
0129             connect(m_providerWidget, &IProjectProviderWidget::changed, this, &ProjectSourcePage::projectChanged);
0130 
0131             m_ui->remoteWidgetLayout->addWidget(m_providerWidget);
0132         }
0133     }
0134     reevaluateCorrection();
0135 
0136     m_ui->sourceBox->setVisible(found);
0137 }
0138 
0139 IBasicVersionControl* ProjectSourcePage::vcsPerIndex(int index)
0140 {
0141     IPlugin* p = m_plugins.value(index);
0142     if(!p)
0143         return nullptr;
0144     else
0145         return p->extension<KDevelop::IBasicVersionControl>();
0146 }
0147 
0148 IProjectProvider* ProjectSourcePage::providerPerIndex(int index)
0149 {
0150     IPlugin* p = m_plugins.value(index);
0151     if(!p)
0152         return nullptr;
0153     else
0154         return p->extension<KDevelop::IProjectProvider>();
0155 }
0156 
0157 VcsJob* ProjectSourcePage::jobPerCurrent()
0158 {
0159     QUrl url=m_ui->workingDir->url();
0160     IPlugin* p=m_plugins[m_ui->sources->currentIndex()];
0161     VcsJob* job=nullptr;
0162 
0163     if(auto* iface=p->extension<IBasicVersionControl>()) {
0164         Q_ASSERT(iface && m_locationWidget);
0165         job=iface->createWorkingCopy(m_locationWidget->location(), url);
0166     } else if(m_providerWidget) {
0167         job=m_providerWidget->createWorkingCopy(url);
0168     }
0169     return job;
0170 }
0171 
0172 void ProjectSourcePage::checkoutVcsProject()
0173 {
0174     QUrl url=m_ui->workingDir->url();
0175     QDir d(url.toLocalFile());
0176     if(!url.isLocalFile() && !d.exists()) {
0177         bool corr = d.mkpath(d.path());
0178         if(!corr) {
0179             KMessageBox::error(nullptr, i18n("Could not create the directory: %1", d.path()));
0180             return;
0181         }
0182     }
0183 
0184     VcsJob* job=jobPerCurrent();
0185     if (!job) {
0186         return;
0187     }
0188 
0189     m_ui->sources->setEnabled(false);
0190     m_ui->sourceBox->setEnabled(false);
0191     m_ui->workingDir->setEnabled(false);
0192     m_ui->get->setEnabled(false);
0193     m_ui->creationProgress->setValue(m_ui->creationProgress->minimum());
0194     connect(job, &VcsJob::result, this, &ProjectSourcePage::projectReceived);
0195     connect(job, &KJob::percentChanged, this, &ProjectSourcePage::progressChanged);
0196     connect(job, &VcsJob::infoMessage, this, &ProjectSourcePage::infoMessage);
0197     ICore::self()->runController()->registerJob(job);
0198 }
0199 
0200 void ProjectSourcePage::progressChanged(KJob*, unsigned long value)
0201 {
0202     m_ui->creationProgress->setValue(value);
0203 }
0204 
0205 void ProjectSourcePage::infoMessage(KJob* , const QString& text, const QString& /*rich*/)
0206 {
0207     m_ui->creationProgress->setFormat(i18nc("Format of the progress bar text. progress and info",
0208                                             "%1 : %p%", text));
0209 }
0210 
0211 void ProjectSourcePage::projectReceived(KJob* job)
0212 {
0213     if (job->error()) {
0214         m_ui->creationProgress->setValue(0);
0215     } else {
0216         m_ui->creationProgress->setValue(m_ui->creationProgress->maximum());
0217     }
0218 
0219     reevaluateCorrection();
0220     m_ui->creationProgress->setFormat(QStringLiteral("%p%"));
0221 }
0222 
0223 void ProjectSourcePage::reevaluateCorrection()
0224 {
0225     //TODO: Probably we should just ignore remote URL's, I don't think we're ever going
0226     //to support checking out to remote directories
0227     const QUrl cwd = m_ui->workingDir->url();
0228     const QDir dir = cwd.toLocalFile();
0229 
0230     // case where we import a project from local file system
0231     if (m_ui->sources->currentIndex() == FROM_FILESYSTEM_SOURCE_INDEX) {
0232         emit isCorrect(dir.exists());
0233         return;
0234     }
0235 
0236     // all other cases where remote locations need to be specified
0237     bool correct=!cwd.isRelative() && (!cwd.isLocalFile() || QDir(cwd.adjusted(QUrl::RemoveFilename).toLocalFile()).exists());
0238     emit isCorrect(correct && m_ui->creationProgress->value() == m_ui->creationProgress->maximum());
0239 
0240     const bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) ||
0241                        (m_providerWidget && m_providerWidget->isCorrect()));
0242     const bool isFolderEmpty = (correct && cwd.isLocalFile() && dir.exists() && dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty());
0243     const bool validToCheckout = correct && validWidget && (!dir.exists() || isFolderEmpty);
0244 
0245     m_ui->get->setEnabled(validToCheckout);
0246     m_ui->creationProgress->setEnabled(validToCheckout);
0247 
0248     if(!correct)
0249         setStatus(i18n("You need to specify a valid or nonexistent directory to check out a project"));
0250     else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled() && !validWidget)
0251         setStatus(i18n("You need to specify the source for your remote project"));
0252     else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled() && !isFolderEmpty)
0253         setStatus(i18n("You need to specify an empty folder as your project destination"));
0254     else
0255         clearStatus();
0256 }
0257 
0258 void ProjectSourcePage::locationChanged()
0259 {
0260     Q_ASSERT(m_locationWidget);
0261     if(m_locationWidget->isCorrect()) {
0262         QString currentUrl = m_ui->workingDir->text();
0263         currentUrl.truncate(currentUrl.lastIndexOf(QLatin1Char('/'))+1);
0264 
0265         QUrl current = QUrl::fromUserInput(currentUrl + m_locationWidget->projectName());
0266         m_ui->workingDir->setUrl(current);
0267     }
0268     else
0269         reevaluateCorrection();
0270 }
0271 
0272 void ProjectSourcePage::projectChanged(const QString& name)
0273 {
0274     Q_ASSERT(m_providerWidget);
0275     QString currentUrl = m_ui->workingDir->text();
0276     currentUrl.truncate(currentUrl.lastIndexOf(QLatin1Char('/'))+1);
0277 
0278     QUrl current = QUrl::fromUserInput(currentUrl + name);
0279     m_ui->workingDir->setUrl(current);
0280 }
0281 
0282 void ProjectSourcePage::setStatus(const QString& message)
0283 {
0284     m_ui->status->setText(message);
0285     m_ui->status->animatedShow();
0286 }
0287 
0288 void ProjectSourcePage::clearStatus()
0289 {
0290     m_ui->status->animatedHide();
0291 }
0292 
0293 QUrl ProjectSourcePage::workingDir() const
0294 {
0295     return m_ui->workingDir->url();
0296 }
0297 
0298 #include "moc_projectsourcepage.cpp"