Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions app/projectsmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ void ProjectsModel::initializeProjectsModel()
QObject::connect( mLocalProjectsManager, &LocalProjectsManager::localProjectAdded, this, &ProjectsModel::onProjectAdded );
QObject::connect( mLocalProjectsManager, &LocalProjectsManager::aboutToRemoveLocalProject, this, &ProjectsModel::onAboutToRemoveProject );
QObject::connect( mLocalProjectsManager, &LocalProjectsManager::localProjectDataChanged, this, &ProjectsModel::onProjectDataChanged );
QObject::connect( mLocalProjectsManager, &LocalProjectsManager::localProjectRenamed, this, &ProjectsModel::onProjectRenamed );
QObject::connect( mLocalProjectsManager, &LocalProjectsManager::dataDirReloaded, this, &ProjectsModel::loadLocalProjects );

emit modelInitialized();
Expand Down Expand Up @@ -380,6 +381,37 @@ void ProjectsModel::removeLocalProject( const QString &projectId )
mLocalProjectsManager->removeLocalProject( projectId );
}

QString ProjectsModel::renameLocalProject( const QString &projectId, const QString &newName )
{
return mLocalProjectsManager->renameLocalProject( projectId, newName );
}

void ProjectsModel::onProjectRenamed( const QString &oldProjectId, const LocalProject &localProject )
{
int ix = projectIndexFromId( oldProjectId );

if ( ix < 0 )
return;

Project &project = mProjects[ix];

project.local = localProject;

if ( project.isMergin() )
{
project.mergin.status = ProjectStatus::projectStatus( project, mBackend->supportsSelectiveSync() );
}

QModelIndex editIndex = index( ix );
emit dataChanged( editIndex, editIndex );

if ( mActiveProjectId == oldProjectId )
{
mActiveProjectId = localProject.id();
emit activeProjectIdChanged( mActiveProjectId );
}
}

void ProjectsModel::migrateProject( const QString &projectId )
{
int ix = projectIndexFromId( projectId );
Expand Down
4 changes: 4 additions & 0 deletions app/projectsmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class ProjectsModel : public QAbstractListModel
//! Forwards call to LocalProjectsManager to remove local project
Q_INVOKABLE void removeLocalProject( const QString &projectId );

//! Forwards call to LocalProjectsManager to rename local project
Q_INVOKABLE QString renameLocalProject( const QString &projectId, const QString &newName );

//! Migrates local project to mergin
Q_INVOKABLE void migrateProject( const QString &projectId );

Expand Down Expand Up @@ -170,6 +173,7 @@ class ProjectsModel : public QAbstractListModel
void onProjectAdded( const LocalProject &project );
void onAboutToRemoveProject( const LocalProject &project );
void onProjectDataChanged( const LocalProject &project );
void onProjectRenamed( const QString &oldProjectId, const LocalProject &project );

void onAuthChanged();

Expand Down
1 change: 1 addition & 0 deletions app/qml/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ set(MM_QML
project/components/MMProjectStatusItem.qml
project/components/MMProjectWizardDelegate.qml
project/components/MMProjectDelegate.qml
project/components/MMRenameProjectDialog.qml
settings/MMAboutPage.qml
settings/MMChangelogPage.qml
settings/MMLogPage.qml
Expand Down
26 changes: 25 additions & 1 deletion app/qml/project/MMProjectList.qml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Item {
property string searchText: ""
property int spacing: 0
property bool activeProjectAlwaysFirst: false
property string projectIdToRename: ""
property alias projectsProxyModel: viewModel
property alias projectsModel: controllerModel
property alias listHeader: listview.header
Expand Down Expand Up @@ -139,7 +140,7 @@ Item {
return ["changes", "remove"]
}
else if ( !model.ProjectIsMergin && model.ProjectIsLocal ) {
return ["upload", "remove"]
return ["upload", "remove", "rename"]
}
return ["download"]
}
Expand Down Expand Up @@ -176,6 +177,10 @@ Item {
}
onStopSyncRequested: controllerModel.stopProjectSync( projectId )
onShowChangesRequested: root.showLocalChangesRequested( projectId )
onRenameRequested: {
root.projectIdToRename = projectId
renameDialog.open()
}
Comment on lines +180 to +183

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
onRenameRequested: {
renameDialog.relatedProjectId = projectId
renameDialog.newProjectName = model.ProjectName
renameDialog.clearRenameError()
renameDialog.open()
}
onRenameRequested: () => {
renameDialog.clearRenameError()
renameDialog.open()
}

}
}

Expand Down Expand Up @@ -306,6 +311,25 @@ Item {
}
}

MMProjectComponents.MMRenameProjectDialog {
id: renameDialog

onRenameClicked: function( newName ) {
if ( !root.projectIdToRename ) {
return
}

const renameResult = controllerModel.renameLocalProject( root.projectIdToRename, newName )

if ( !renameResult ) {
renameDialog.close()
}
else {
renameDialog.errorText = renameResult
}
}
}

MMDownloadProjectDialog {
id: downloadProjectDialog

Expand Down
8 changes: 7 additions & 1 deletion app/qml/project/components/MMProjectDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Control {
property bool projectIsInSync: false
property real projectSyncProgress: 0.0

property var projectActionButtons: [] // possible values: upload, changes, sync, download, remove
property var projectActionButtons: [] // possible values: upload, changes, sync, download, remove, rename

property bool projectIsOpened: false

Expand All @@ -35,6 +35,7 @@ Control {
signal removeRequested()
signal stopSyncRequested()
signal showChangesRequested()
signal renameRequested()

height: implicitHeight

Expand Down Expand Up @@ -307,6 +308,11 @@ Control {
"name": qsTr("Upload"),
"iconSource": __style.uploadIcon,
"callback": () => root.migrateRequested()
},
"rename": {
"name": qsTr("Rename the local project"),
"iconSource": __style.editIcon,
"callback": () => root.renameRequested()
}
}
}
Expand Down
88 changes: 88 additions & 0 deletions app/qml/project/components/MMRenameProjectDialog.qml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the type of dialog we put into app/qml/dialogs. There are just information dialogs which use MMDrawerDialog

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either just inline the dialog or make it a private component of that subdirectory

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/

import QtQuick

import "../../components"
import "../../inputs"

MMDrawer {
id: root

property string errorText: ""

signal renameClicked( string newName )

drawerHeader.title: qsTr( "Rename project" )
drawerHeader.titleFont: __style.t2

onOpened: {
root.errorText = ""
newNameField.text = ""
}

drawerContent: Column {
width: parent.width
spacing: 0

MMTextInput {
id: newNameField

width: parent.width
textFieldBackground.color: root.errorText === "" ? __style.lightGreenColor : __style.negativeUltraLightColor
textFieldBackground.border.width: root.errorText === "" ? 0 : __style.width2
textFieldBackground.border.color: root.errorText === "" ? __style.polarColor : __style.negativeColor

placeholderText: qsTr( "Enter the new name" )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this doesn't show at all as we show old name right away


onTextEdited: root.errorText = ""
}

// Fixed-height slot so the drawer does not grow/shrink when the error message appears.
Item {
width: parent.width
height: __style.spacing40

Row {
anchors.verticalCenter: parent.verticalCenter

width: parent.width
spacing: __style.margin4

visible: root.errorText !== ""

MMIcon {
y: parent.height / 2 - height / 2
source: __style.errorCircleIcon
color: __style.negativeColor
size: __style.icon16
}

MMText {
width: parent.width - __style.icon16 - parent.spacing
text: root.errorText
color: __style.grapeColor
font: __style.t4
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
}
Comment on lines +48 to +76

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MMBaseInput has this functionality already implemented in it. MMBaseInput is parent component of MMTextInput


MMButton {
width: parent.width

text: qsTr( "Confirm" )

onClicked: {
root.renameClicked( newNameField.text )
}
}
}
}
79 changes: 79 additions & 0 deletions core/localprojectsmanager.cpp

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this approach using QSettings as it adds another layer, which can fail and I don't see any added value here by using it. Please rename the project directory and project file right away if there is no issue with the name.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>

LocalProjectsManager::LocalProjectsManager( const QString &dataDir )
: mDataDir( dataDir )
Expand All @@ -25,6 +27,7 @@ LocalProjectsManager::LocalProjectsManager( const QString &dataDir )
void LocalProjectsManager::reloadDataDir()
{
mProjects.clear();

QStringList entryList = QDir( mDataDir ).entryList( QDir::NoDotAndDotDot | QDir::Dirs );
for ( const QString &folderName : entryList )
{
Expand Down Expand Up @@ -249,3 +252,79 @@ void LocalProjectsManager::addProject( const QString &projectDir, const QString
mProjects << project;
emit localProjectAdded( project );
}

QString LocalProjectsManager::renameLocalProject( const QString &projectId, const QString &newName )
{
const QString trimmedName = newName.trimmed();

if ( trimmedName.isEmpty() )
{
return tr( "The project name cannot be empty" );
}
Comment thread
Withalion marked this conversation as resolved.

if ( !CoreUtils::isValidName( trimmedName ) )
{
return tr( "The project name contains invalid characters" );
}

int projectIndex = -1;
for ( int i = 0; i < mProjects.count(); ++i )
{
if ( mProjects[i].id() == projectId )
{
projectIndex = i;
}

if ( i != projectIndex && mProjects[i].projectName == trimmedName )
{
return tr( "A project name is already taken" );
}
}

if ( projectIndex == -1 )
{
return tr( "Project not found" );
}

LocalProject &project = mProjects[projectIndex];

if ( project.projectName == trimmedName )
{
return {}; // name did not change, nothing to rename
}

const QString oldProjectId = project.id();

const QString parentDir = QFileInfo( project.projectDir ).dir().absolutePath();
const QString newProjectDir = CoreUtils::findUniquePath( parentDir + "/" + trimmedName );

if ( !QDir().rename( project.projectDir, newProjectDir ) )
{
CoreUtils::log( "Rename project", QStringLiteral( "Failed to rename directory %1 to %2" ).arg( project.projectDir, newProjectDir ) );
return tr( "Failed to rename the project directory" );
}

if ( !project.qgisProjectFilePath.isEmpty() )
{
const QString relativeFilePath = QDir( project.projectDir ).relativeFilePath( project.qgisProjectFilePath );
const QString oldFilePath = newProjectDir + "/" + relativeFilePath;

QFileInfo oldFileInfo( oldFilePath );
const QString newFilePath = oldFileInfo.dir().absoluteFilePath( trimmedName + "." + oldFileInfo.suffix() );

if ( oldFilePath != newFilePath && QFile::rename( oldFilePath, newFilePath ) )
{
project.qgisProjectFilePath = newFilePath;
}
else
{
project.qgisProjectFilePath = oldFilePath;
}
}

project.projectDir = newProjectDir;
project.projectName = trimmedName;

emit localProjectRenamed( oldProjectId, project );
return {};
}
3 changes: 3 additions & 0 deletions core/localprojectsmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class LocalProjectsManager : public QObject

Q_INVOKABLE QString projectName( const QString &projectId ) const;

QString renameLocalProject( const QString &projectId, const QString &newName );

/**
* Returns changes of a project specified by projectId in the form :
* (pending changes, features in layer survey: 10 addition, 3 updates, 1 deletion. 10 new files)
Expand All @@ -71,6 +73,7 @@ class LocalProjectsManager : public QObject
signals:
void localProjectAdded( const LocalProject &project );
void localProjectDataChanged( const LocalProject &project );
void localProjectRenamed( const QString &oldProjectId, const LocalProject &project );
void aboutToRemoveLocalProject( const LocalProject &project );

void dataDirReloaded();
Expand Down
3 changes: 2 additions & 1 deletion gallery/qml/pages/ProjectItemsPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ MMPage {
projectIsInSync: true
projectSyncProgress: 1/4

projectActionButtons: ["download", "sync", "remove"]
projectActionButtons: ["download", "sync", "remove", "rename"]

state: "NeedsSync"

Expand All @@ -61,6 +61,7 @@ MMPage {
}
onRemoveRequested: console.log("onRemoveRequested")
onMigrateRequested: console.log("onMigrateRequested")
onRenameRequested: console.log("onRenameRequested")

Timer {
id: syncAnimator
Expand Down
Loading