Skip to content
Merged
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
99 changes: 74 additions & 25 deletions profile_manager/profile_manager_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self, profile_manager, parent=None):

self.__setup_connections()

# initial population of things on import tab
# making sure that the combo boxes are set up correctly
self.comboBoxNamesSource.currentTextChanged.emit(
self.comboBoxNamesSource.currentText()
)
Expand Down Expand Up @@ -84,6 +84,29 @@ def __setup_connections(self):
self.__conditionally_enable_profile_buttons
)

# update import/remove buttons on selection changes
self.treeWidgetSource.itemClicked.connect(
self.__conditionally_enable_import_buttons
)
self.treeWidgetTarget.itemClicked.connect(
self.__conditionally_enable_import_buttons
)
self.list_plugins.itemClicked.connect(
self.__conditionally_enable_import_buttons
)
checkboxes = [
self.bookmark_check,
self.favourites_check,
self.models_check,
self.scripts_check,
self.styles_check,
self.expressions_check,
self.checkBox_checkAll,
self.customization_check,
]
for checkbox in checkboxes:
checkbox.stateChanged.connect(self.__conditionally_enable_import_buttons)

def get_list_selection_profile_name(self) -> Optional[str]:
"""Get selected profile name from list

Expand Down Expand Up @@ -158,13 +181,34 @@ def export_qdt_handler(self) -> None:
def __conditionally_enable_import_buttons(self):
source = self.__profile_manager.source_profile_name
target = self.__profile_manager.target_profile_name
if source == target:
self.removeThingsButton.setEnabled(True)
any_thing_is_selected = any(
[
self.__selected_plugins(),
self.__selected_data_sources(),
self.bookmark_check.isChecked(),
self.favourites_check.isChecked(),
self.models_check.isChecked(),
self.scripts_check.isChecked(),
self.styles_check.isChecked(),
self.expressions_check.isChecked(),
self.customization_check.isChecked(),
]
)

if not any_thing_is_selected:
# Nothing to do? Don't enable any of the buttons.
self.importThingsButton.setEnabled(False)
elif source is None and target is not None:
self.removeThingsButton.setEnabled(False)
elif source is None:
# Both importing and deleting need a *source* profile to be selected.
self.importThingsButton.setEnabled(False)
elif source is not None and target is None:
self.removeThingsButton.setEnabled(False)
elif source == target and any_thing_is_selected:
# Don't allow importing into itself, but allow deletion of the selected things in the source profile.
self.importThingsButton.setEnabled(False)
self.removeThingsButton.setEnabled(True)
elif source is not None and target is None and any_thing_is_selected:
# Only allow deletion of the selected things in the source profile.
self.importThingsButton.setEnabled(False)
self.removeThingsButton.setEnabled(True)
else:
Expand Down Expand Up @@ -215,7 +259,6 @@ def __conditionally_enable_profile_buttons(self):

def __on_source_profile_changed(self, profile_name: str):
self.__profile_manager.change_source_profile(profile_name)
self.__conditionally_enable_import_buttons()

if profile_name is None:
self.treeWidgetSource.clear()
Expand All @@ -227,10 +270,10 @@ def __on_source_profile_changed(self, profile_name: str):
self.__update_plugins_widget(
"source", self.__profile_manager.source_plugins
)
self.__conditionally_enable_import_buttons()

def __on_target_profile_changed(self, profile_name: str):
self.__profile_manager.change_target_profile(profile_name)
self.__conditionally_enable_import_buttons()

if profile_name is None:
self.treeWidgetTarget.clear()
Expand All @@ -242,6 +285,7 @@ def __on_target_profile_changed(self, profile_name: str):
self.__update_plugins_widget(
"target", self.__profile_manager.target_plugins
)
self.__conditionally_enable_import_buttons()

def populate_profile_listings(self):
"""Populates the main list as well as the comboboxes with available profile names.
Expand All @@ -250,12 +294,9 @@ def populate_profile_listings(self):

TODO this docstring seems not correct anymore.
TODO how//where IS the profile model updated?
TODO document WHY blocksignals is used
"""
self.comboBoxNamesSource.blockSignals(True)
active_profile_name = Path(QgsApplication.qgisSettingsDirPath()).name
self.comboBoxNamesSource.setCurrentText(active_profile_name)
self.comboBoxNamesSource.blockSignals(False)

self.__conditionally_enable_profile_buttons()

Expand Down Expand Up @@ -303,38 +344,46 @@ def __populate_plugins_list(
for item in items:
plugins_widget.addItem(item)

def __set_checkstates(self, checkstate: Qt.CheckState):
def __set_all_checkstates(self, checkstate: Qt.CheckState):
"""Sets the specified checkstate for all enabled checkboxes."""
for item in self.treeWidgetSource.findItems(
"", Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchRecursive
):
item.setCheckState(0, checkstate)
if item.flags() & Qt.ItemFlag.ItemIsEnabled:
item.setCheckState(0, checkstate)

for item in self.list_plugins.findItems(
"", Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchRecursive
):
item.setCheckState(checkstate)

self.bookmark_check.setCheckState(checkstate)
self.favourites_check.setCheckState(checkstate)
self.models_check.setCheckState(checkstate)
self.scripts_check.setCheckState(checkstate)
self.styles_check.setCheckState(checkstate)
self.expressions_check.setCheckState(checkstate)
self.checkBox_checkAll.setCheckState(checkstate)
self.customization_check.setCheckState(checkstate)
if item.flags() & Qt.ItemFlag.ItemIsEnabled:
item.setCheckState(checkstate)

checkboxes = [
self.bookmark_check,
self.favourites_check,
self.models_check,
self.scripts_check,
self.styles_check,
self.expressions_check,
self.checkBox_checkAll,
self.customization_check,
]
for checkbox in checkboxes:
if checkbox.isEnabled():
checkbox.setCheckState(checkstate)

def __toggle_all_items(self):
"""Checks/Unchecks every checkbox in the gui"""
"""Checks/Unchecks every enabled checkbox in the gui"""
if self.__everything_is_checked:
checkstate = Qt.CheckState.Unchecked
else:
checkstate = Qt.CheckState.Checked
self.__set_checkstates(checkstate)
self.__set_all_checkstates(checkstate)
self.__everything_is_checked = not self.__everything_is_checked

def __uncheck_everything(self):
"""Unchecks every checkbox"""
self.__set_checkstates(Qt.CheckState.Unchecked)
self.__set_all_checkstates(Qt.CheckState.Unchecked)
self.__everything_is_checked = False

def __update_data_sources_widget(
Expand Down
41 changes: 36 additions & 5 deletions profile_manager/profiles/profile_handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import errno
import re
from os import rename
from pathlib import Path
from shutil import copytree, rmtree
from sys import platform

from qgis.core import QgsApplication, QgsUserProfileManager
from qgis.core import QgsApplication, QgsError, QgsUserProfileManager

from profile_manager.profiles.utils import qgis_profiles_path

Expand All @@ -19,8 +20,18 @@ def create_profile(profile_name: str):
if not re.match(VALID_PROJECT_NAME_REGEX, profile_name):
raise ValueError("Invalid profile name")

# Unfortunately QgsUserProfileManager.createUserProfile() does not notice if
# the target directory already exists as of QGIS 3.40.3...
# So we have our own check here:
if Path(qgis_profiles_path() / profile_name).exists():
raise ValueError("Target directory already exists")

qgs_profile_manager = QgsUserProfileManager(str(qgis_profiles_path()))
qgs_profile_manager.createUserProfile(profile_name)
error: QgsError = qgs_profile_manager.createUserProfile(profile_name)
if not error.isEmpty():
raise Exception(
f"QGIS could not create the new user profile: {error.summary()}"
)

# Right now there is only the profile directory and the qgis.db in its root.
# We want to be able to write things to the profile's QGIS3.ini file so:
Expand All @@ -44,7 +55,13 @@ def remove_profile(profile_name: str):
raise ValueError("Cannot remove the profile that is currently active")

profile_path = qgis_profiles_path() / profile_name
rmtree(profile_path)
try:
rmtree(profile_path)
except OSError as e:
if e.errno == errno.EEXIST:
raise ValueError("A profile with the same name already exists.")
else:
raise


def copy_profile(source_profile_name: str, target_profile_name: str):
Expand All @@ -62,7 +79,13 @@ def copy_profile(source_profile_name: str, target_profile_name: str):
source_profile_path = qgis_profiles_path() / source_profile_name
profile_path = qgis_profiles_path() / target_profile_name

copytree(source_profile_path, profile_path)
try:
copytree(source_profile_path, profile_path)
except OSError as e:
if e.errno == errno.EEXIST:
raise ValueError("A profile with the same name already exists.")
else:
raise


def rename_profile(old_profile_name: str, new_profile_name: str):
Expand All @@ -71,6 +94,8 @@ def rename_profile(old_profile_name: str, new_profile_name: str):
raise ValueError("Empty old profile name provided")
if not old_profile_name:
raise ValueError("Empty new profile name provided")
if old_profile_name == new_profile_name:
raise ValueError("New name is identical to old name")
if not re.match(VALID_PROJECT_NAME_REGEX, old_profile_name):
raise ValueError("Invalid old profile name")
if not re.match(VALID_PROJECT_NAME_REGEX, new_profile_name):
Expand All @@ -81,4 +106,10 @@ def rename_profile(old_profile_name: str, new_profile_name: str):
profile_before_change = qgis_profiles_path() / old_profile_name
profile_after_change = qgis_profiles_path() / new_profile_name

rename(profile_before_change, profile_after_change)
try:
rename(profile_before_change, profile_after_change)
except OSError as e:
if e.errno == errno.ENOTEMPTY:
raise ValueError("A profile with the same name already exists.")
else:
raise