File indexing completed on 2024-04-28 04:37:20
0001 /* 0002 SPDX-FileCopyrightText: 2008 Andreas Pakulat <apaku@gmx.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "openprojectdialog.h" 0008 #include "openprojectpage.h" 0009 #include "projectinfopage.h" 0010 0011 #include <QPushButton> 0012 #include <QFileInfo> 0013 #include <QFileDialog> 0014 0015 #include <KColorScheme> 0016 #include <KIO/StatJob> 0017 #include <KIO/ListJob> 0018 #include <KJobWidgets> 0019 #include <KLocalizedString> 0020 0021 #include "core.h" 0022 #include "uicontroller.h" 0023 #include "plugincontroller.h" 0024 #include "mainwindow.h" 0025 #include "shellextension.h" 0026 #include "projectsourcepage.h" 0027 #include <debug.h> 0028 #include <interfaces/iprojectcontroller.h> 0029 0030 namespace 0031 { 0032 struct URLInfo 0033 { 0034 bool isValid; 0035 bool isDir; 0036 QString extension; 0037 }; 0038 0039 URLInfo urlInfo(const QUrl& url) 0040 { 0041 URLInfo ret; 0042 ret.isValid = false; 0043 0044 if (url.isLocalFile()) { 0045 QFileInfo info(url.toLocalFile()); 0046 ret.isValid = info.exists(); 0047 if (ret.isValid) { 0048 ret.isDir = info.isDir(); 0049 ret.extension = info.suffix(); 0050 } 0051 } else if (url.isValid()) { 0052 KIO::StatJob* statJob = KIO::stat(url, KIO::HideProgressInfo); 0053 KJobWidgets::setWindow(statJob, KDevelop::Core::self()->uiControllerInternal()->defaultMainWindow()); 0054 ret.isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname 0055 if (ret.isValid) { 0056 KIO::UDSEntry entry = statJob->statResult(); 0057 ret.isDir = entry.isDir(); 0058 ret.extension = QFileInfo(entry.stringValue(KIO::UDSEntry::UDS_NAME)).suffix(); 0059 } 0060 } 0061 return ret; 0062 } 0063 } 0064 0065 namespace KDevelop 0066 { 0067 0068 OpenProjectDialog::OpenProjectDialog(bool fetch, const QUrl& startUrl, 0069 const QUrl& repoUrl, IPlugin* vcsOrProviderPlugin, 0070 QWidget* parent) 0071 : KAssistantDialog( parent ) 0072 , m_urlIsDirectory(false) 0073 , sourcePage(nullptr) 0074 , openPage(nullptr) 0075 , projectInfoPage(nullptr) 0076 { 0077 resize(QSize(700, 500)); 0078 0079 // KAssistantDialog creates a help button by default, no option to prevent that 0080 auto helpButton = button(QDialogButtonBox::Help); 0081 if (helpButton) { 0082 buttonBox()->removeButton(helpButton); 0083 delete helpButton; 0084 } 0085 0086 const bool useKdeFileDialog = qEnvironmentVariableIsSet("KDE_FULL_SESSION"); 0087 QStringList filters, allEntry; 0088 QString filterFormat = useKdeFileDialog 0089 ? QStringLiteral("%1|%2 (%1)") 0090 : QStringLiteral("%2 (%1)"); 0091 allEntry << QLatin1String("*.") + ShellExtension::getInstance()->projectFileExtension(); 0092 filters << filterFormat.arg(QLatin1String("*.") + ShellExtension::getInstance()->projectFileExtension(), ShellExtension::getInstance()->projectFileDescription()); 0093 const QVector<KPluginMetaData> plugins = ICore::self()->pluginController()->queryExtensionPlugins(QStringLiteral("org.kdevelop.IProjectFileManager")); 0094 for (const KPluginMetaData& info : plugins) { 0095 m_projectPlugins.insert(info.name(), info); 0096 0097 const auto filter = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilter"), QStringList()); 0098 0099 // some project file manager plugins like KDevGenericManager have no file filter set 0100 if (filter.isEmpty()) { 0101 m_genericProjectPlugins << info.name(); 0102 continue; 0103 } 0104 QString desc = info.value(QStringLiteral("X-KDevelop-ProjectFilesFilterDescription")); 0105 0106 m_projectFilters.insert(info.name(), filter); 0107 allEntry += filter; 0108 filters << filterFormat.arg(filter.join(QLatin1Char(' ')), desc); 0109 } 0110 0111 if (useKdeFileDialog) 0112 filters.prepend(i18n("%1|All Project Files (%1)", allEntry.join(QLatin1Char(' ')))); 0113 0114 QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); 0115 start = start.adjusted(QUrl::NormalizePathSegments); 0116 KPageWidgetItem* currentPage = nullptr; 0117 0118 if( fetch ) { 0119 sourcePageWidget = new ProjectSourcePage(start, repoUrl, vcsOrProviderPlugin, this); 0120 connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); 0121 sourcePage = addPage( sourcePageWidget, i18nc("@title:tab", "Select Source") ); 0122 currentPage = sourcePage; 0123 } 0124 0125 if (useKdeFileDialog) { 0126 openPageWidget = new OpenProjectPage( start, filters, this ); 0127 connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); 0128 connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); 0129 openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " 0130 "or any folder to open as a project") ); 0131 0132 if (!currentPage) { 0133 currentPage = openPage; 0134 } 0135 } else { 0136 nativeDialog.assign(parent, i18nc("@title:window", "Open Project")); 0137 nativeDialog->setDirectoryUrl(start); 0138 nativeDialog->setFileMode(QFileDialog::Directory); 0139 } 0140 0141 auto* page = new ProjectInfoPage( this ); 0142 connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); 0143 connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); 0144 projectInfoPage = addPage( page, i18nc("@title:tab", "Project Information") ); 0145 0146 if (!currentPage) { 0147 currentPage = projectInfoPage; 0148 } 0149 0150 setValid( sourcePage, false ); 0151 setValid( openPage, false ); 0152 setValid( projectInfoPage, false); 0153 setAppropriate( projectInfoPage, false ); 0154 0155 setCurrentPage( currentPage ); 0156 setWindowTitle(i18nc("@title:window", "Open Project")); 0157 } 0158 0159 OpenProjectDialog::~OpenProjectDialog() = default; 0160 0161 bool OpenProjectDialog::execNativeDialog() 0162 { 0163 while (true) 0164 { 0165 if (nativeDialog->exec()) { 0166 QUrl selectedUrl = nativeDialog->selectedUrls().at(0); 0167 if (urlInfo(selectedUrl).isValid) { 0168 // validate directory first to populate m_projectName and m_projectManager 0169 validateOpenUrl(selectedUrl.adjusted(QUrl::RemoveFilename)); 0170 validateOpenUrl(selectedUrl); 0171 return true; 0172 } 0173 } 0174 else { 0175 return false; 0176 } 0177 } 0178 } 0179 0180 int OpenProjectDialog::exec() 0181 { 0182 if (nativeDialog && !execNativeDialog()) { 0183 reject(); 0184 return QDialog::Rejected; 0185 } 0186 return KAssistantDialog::exec(); 0187 } 0188 0189 void OpenProjectDialog::validateSourcePage(bool valid) 0190 { 0191 setValid(sourcePage, valid); 0192 if (!nativeDialog) { 0193 openPageWidget->setUrl(sourcePageWidget->workingDir()); 0194 } 0195 } 0196 0197 void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) 0198 { 0199 URLInfo urlInfo = ::urlInfo(url_); 0200 0201 const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); 0202 0203 // openPage is used only in KDE 0204 if (openPage) { 0205 if ( urlInfo.isValid ) { 0206 // reset header 0207 openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); 0208 } else { 0209 // report error 0210 KColorScheme scheme(palette().currentColorGroup()); 0211 const QString errorMsg = i18n("Selected URL is invalid"); 0212 openPage->setHeader(QStringLiteral("<font color='%1'>%2</font>") 0213 .arg(scheme.foreground(KColorScheme::NegativeText).color().name(), errorMsg) 0214 ); 0215 setAppropriate( projectInfoPage, false ); 0216 setAppropriate( openPage, true ); 0217 setValid( openPage, false ); 0218 return; 0219 } 0220 } 0221 0222 m_selected = url; 0223 0224 if( urlInfo.isDir || urlInfo.extension != ShellExtension::getInstance()->projectFileExtension() ) 0225 { 0226 m_urlIsDirectory = urlInfo.isDir; 0227 setAppropriate( projectInfoPage, true ); 0228 m_url = url; 0229 if( !urlInfo.isDir ) { 0230 m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); 0231 } 0232 auto* page = qobject_cast<ProjectInfoPage*>( projectInfoPage->widget() ); 0233 if( page ) 0234 { 0235 page->setProjectName( m_url.fileName() ); 0236 // clear the filelist 0237 m_fileList.clear(); 0238 0239 if( urlInfo.isDir ) { 0240 // If a dir was selected fetch all files in it 0241 KIO::ListJob* job = KIO::listDir( m_url ); 0242 connect( job, &KIO::ListJob::entries, 0243 this, &OpenProjectDialog::storeFileList); 0244 KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); 0245 job->exec(); 0246 } else { 0247 // Else we'll just take the given file 0248 m_fileList << url.fileName(); 0249 } 0250 // Now find a manager for the file(s) in our filelist. 0251 QVector<ProjectFileChoice> choices; 0252 for (const auto& file : qAsConst(m_fileList)) { 0253 auto plugins = projectManagerForFile(file); 0254 if ( plugins.contains(QStringLiteral("<built-in>")) ) { 0255 plugins.removeAll(QStringLiteral("<built-in>")); 0256 choices.append({i18nc("@item:inlistbox", "Open existing file \"%1\"", file), QStringLiteral("<built-in>"), QString(), QString()}); 0257 } 0258 choices.reserve(choices.size() + plugins.size()); 0259 for (const auto& plugin : qAsConst(plugins)) { 0260 auto meta = m_projectPlugins.value(plugin); 0261 choices.append({file + QLatin1String(" (") + plugin + QLatin1Char(')'), meta.pluginId(), meta.iconName(), file}); 0262 } 0263 } 0264 // add managers that work in any case (e.g. KDevGenericManager) 0265 choices.reserve(choices.size() + m_genericProjectPlugins.size()); 0266 for (const auto& plugin : qAsConst(m_genericProjectPlugins)) { 0267 qCDebug(SHELL) << plugin; 0268 auto meta = m_projectPlugins.value(plugin); 0269 choices.append({plugin, meta.pluginId(), meta.iconName(), QString()}); 0270 } 0271 page->populateProjectFileCombo(choices); 0272 } 0273 m_url.setPath( m_url.path() + QLatin1Char('/') + m_url.fileName() + QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension() ); 0274 } else { 0275 setAppropriate( projectInfoPage, false ); 0276 m_url = url; 0277 m_urlIsDirectory = false; 0278 } 0279 validateProjectInfo(); 0280 setValid( openPage, true ); 0281 } 0282 0283 QStringList OpenProjectDialog::projectManagerForFile(const QString& file) const 0284 { 0285 QStringList ret; 0286 for (auto it = m_projectFilters.begin(), end = m_projectFilters.end(); it != end; ++it) { 0287 const QString& manager = it.key(); 0288 for (const QString& filterexp : it.value()) { 0289 QRegExp exp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ); 0290 if ( exp.exactMatch(file) ) { 0291 ret.append(manager); 0292 } 0293 } 0294 } 0295 if ( file.endsWith(ShellExtension::getInstance()->projectFileExtension()) ) { 0296 ret.append(QStringLiteral("<built-in>")); 0297 } 0298 return ret; 0299 } 0300 0301 void OpenProjectDialog::openPageAccepted() 0302 { 0303 if ( isValid( openPage ) ) { 0304 next(); 0305 } 0306 } 0307 0308 void OpenProjectDialog::validateProjectName( const QString& name ) 0309 { 0310 m_projectName = name; 0311 validateProjectInfo(); 0312 } 0313 0314 void OpenProjectDialog::validateProjectInfo() 0315 { 0316 setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); 0317 } 0318 0319 void OpenProjectDialog::validateProjectManager( const QString& manager, const QString & fileName ) 0320 { 0321 m_projectManager = manager; 0322 0323 if ( m_urlIsDirectory ) 0324 { 0325 m_selected = m_url.resolved( QUrl(QLatin1String("./") + fileName) ); 0326 } 0327 0328 validateProjectInfo(); 0329 } 0330 0331 QUrl OpenProjectDialog::projectFileUrl() const 0332 { 0333 return m_url; 0334 } 0335 0336 QUrl OpenProjectDialog::selectedUrl() const 0337 { 0338 return m_selected; 0339 } 0340 0341 QString OpenProjectDialog::projectName() const 0342 { 0343 return m_projectName; 0344 } 0345 0346 QString OpenProjectDialog::projectManager() const 0347 { 0348 return m_projectManager; 0349 } 0350 0351 void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) 0352 { 0353 for (const KIO::UDSEntry& entry : list) { 0354 QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); 0355 if( name != QLatin1String(".") && name != QLatin1String("..") && !entry.isDir() ) 0356 { 0357 m_fileList << name; 0358 } 0359 } 0360 } 0361 0362 } 0363 0364 #include "moc_openprojectdialog.cpp"