Index: kdevplatform/shell/openprojectdialog.h =================================================================== --- kdevplatform/shell/openprojectdialog.h +++ kdevplatform/shell/openprojectdialog.h @@ -59,14 +59,23 @@ private Q_SLOTS: void validateSourcePage( bool ); void validateOpenUrl( const QUrl& ); + /** + * Validates the name the user entered (or is entering) for the + * project being opened and its project filename, sets that filename + * and then calls validateProjectInfo(). + * The validation procedure ensures that the project filename obtained + * from the project name is valid filename by mapping illegal and + * potentially conflicting characters to placeholders. + */ void validateProjectName( const QString& ); void validateProjectManager( const QString&, const QString & ); void storeFileList(KIO::Job*, const KIO::UDSEntryList&); void openPageAccepted(); private: bool execNativeDialog(); void validateProjectInfo(); + QUrl m_projectDirUrl; QUrl m_url; QUrl m_selected; QString m_projectName; Index: kdevplatform/shell/openprojectdialog.cpp =================================================================== --- kdevplatform/shell/openprojectdialog.cpp +++ kdevplatform/shell/openprojectdialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -272,7 +273,13 @@ } page->populateProjectFileCombo(choices); } - m_url.setPath( m_url.path() + QLatin1Char('/') + m_url.fileName() + QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension() ); + // Turn m_url into the full path to the project filename (default: /path/to/projectSrc/projectSrc.kdev4). + // This is done only when m_url doesn't already point to such a file, typically because the user selected + // one in the project open dialog. Omitting this check could lead to project files of the form + // /path/to/projectSrc/SourceProject.kdev4/projectSrc.kdev4 . + if (!m_url.toLocalFile().endsWith(QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension())) { + m_url.setPath( m_url.path() + QLatin1Char('/') + m_url.fileName() + QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension() ); + } } else { setAppropriate( projectInfoPage, false ); m_url = url; @@ -310,7 +317,39 @@ void OpenProjectDialog::validateProjectName( const QString& name ) { - m_projectName = name; + if (name != m_projectName) { + m_projectName = name; + bool isEnteringProjectName = (currentPage() == projectInfoPage); + // don't interfere with m_url when validateOpenUrl() is also likely to change it + if (isEnteringProjectName) { + if (m_projectDirUrl.isEmpty()) { + // cache the selected project directory + const auto urlInfo = ::urlInfo(m_url); + if (urlInfo.isValid && urlInfo.isDir) { + m_projectDirUrl = m_url.adjusted(QUrl::StripTrailingSlash); + } else { + // if !urlInfo.isValid the url almost certainly refers to a file that doesn't exist (yet) + // With the Generic Makefile proj.manager it can be the project file url, for instance. + m_projectDirUrl = m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); + } + } + + const QUrl url(m_projectDirUrl); + // construct a version of the project name that is safe for use as a filename, i.e. + // a version that does not contain characters that are illegal or are best avoided: + // replace square braces and dir separator-like characters with a '@' placeholder + // replace colons with '=' and whitespace with underscores. + QString safeName = m_projectName; + safeName.replace(QRegularExpression(QStringLiteral("[\\\\/]")), QStringLiteral("@")); + safeName = safeName.replace(QLatin1Char(':'), QLatin1Char('=')) \ + .replace(QRegExp(QStringLiteral("\\s")), QStringLiteral("_")); + safeName += QLatin1Char('.') + ShellExtension::getInstance()->projectFileExtension(); + + m_url.setPath(url.path() + QLatin1Char('/') + safeName); + m_urlIsDirectory = false; + qCDebug(SHELL) << "project name:" << m_projectName << "file name:" << safeName << "in" << url.path(); + } + } validateProjectInfo(); } Index: kdevplatform/shell/projectcontroller.cpp =================================================================== --- kdevplatform/shell/projectcontroller.cpp +++ kdevplatform/shell/projectcontroller.cpp @@ -423,6 +423,9 @@ KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); + qCDebug(SHELL) << "configPath=" << configPath << "defaultName=" << defaultName + << "projName=" << dlg->projectName() << "projectMan=" << dlg->projectManager() + << "grp.Name=" << grp.readEntry( "Name", QString() ) << "grp.Manager=" << grp.readEntry( "Manager", QString() ); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } @@ -438,19 +441,27 @@ } QUrl projectFileUrl = dlg->projectFileUrl(); - qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg->projectName() << dlg->projectManager(); + qCDebug(SHELL) << "selected project:" << projectFileUrl << "selectedUrl=" << dlg->selectedUrl() + << "projectName=" << dlg->projectName() << "projectManager=" << dlg->projectManager(); if ( dlg->projectManager() == QLatin1String("") ) { return projectFileUrl; } // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { - // check whether config is equal - bool shouldAsk = true; - if( projectFileUrl == dlg->selectedUrl() ) + // check whether we should question the user about overriding an existing project file or not. + // We don't need to do that when the file we're importing (dlg->selectedUrl) is already an + // existing .kdev4 project file (we just verified that it exists): + bool isKDevProject = QFileInfo(dlg->selectedUrl().url()).suffix() == QStringLiteral("kdev4"); + bool shouldAsk = !isKDevProject; + if( !isKDevProject && projectFileUrl == dlg->selectedUrl() ) { + // We're importing a project from another type of project file, post the + // override dialog if there's a discrepancy between the project file URL + // and the information stored in the dialog and the project settings. + qCWarning(SHELL) << "Importing a foreign project type:" << projectFileUrl.url(); if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), dlg ); @@ -490,18 +501,24 @@ } else if ( ret == KMessageBox::Cancel ) { return QUrl(); - } // else fall through and write new file + } else { + // confusion can arise when an existing project is overridden and the old settings + // file remains (http://phabricator.kde.org/T6262). The .kdev4 directory can + // contain settings files for other projects which shouldn't be deleted, so + // we delete just the settings file for this project. + Path settingsFile(projectFileUrl); + settingsFile.setLastPathSegment(QStringLiteral(".kdev4")); + settingsFile.addPath(projectFileUrl.fileName()); + qCDebug(SHELL) << "Deleting old settings file before overriding it:" << settingsFile; + auto delJob = KIO::del(settingsFile.toUrl()); + delJob->exec(); + } } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { - Path projectConfigDir(projectFileUrl); - projectConfigDir.setLastPathSegment(QStringLiteral(".kdev4")); - auto delJob = KIO::del(projectConfigDir.toUrl()); - delJob->exec(); - if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg)) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url()));