diff --git a/pg_service_parser/conf/service_settings.py b/pg_service_parser/conf/service_settings.py
new file mode 100644
index 0000000..8511c56
--- /dev/null
+++ b/pg_service_parser/conf/service_settings.py
@@ -0,0 +1,17 @@
+# Settings available for manual addition
+# See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
+SERVICE_SETTINGS = {
+ "host": "localhost",
+ "port": "5432",
+ "dbname": "test",
+ "user": "",
+ "password": "",
+ "passfile": "",
+}
+
+# Settings to initialize new files
+SETTINGS_TEMPLATE = {
+ "host": "localhost",
+ "port": "5432",
+ "dbname": "test",
+}
diff --git a/pg_service_parser/core/item_models.py b/pg_service_parser/core/item_models.py
index 4c72be8..cc05d0a 100644
--- a/pg_service_parser/core/item_models.py
+++ b/pg_service_parser/core/item_models.py
@@ -1,4 +1,4 @@
-from qgis.PyQt.QtCore import QAbstractTableModel, Qt, pyqtSignal
+from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal
from qgis.PyQt.QtGui import QColorConstants, QFont
@@ -8,19 +8,43 @@ class ServiceConfigModel(QAbstractTableModel):
is_dirty_changed = pyqtSignal(bool) # Whether the model gets dirty or not
- def __init__(self, service_name, service_config):
+ def __init__(self, service_name: str, service_config: dict):
super().__init__()
self.__service_name = service_name
self.__model_data = service_config
self.__original_data = service_config.copy()
self.__dirty = False
- def rowCount(self, parent):
+ def rowCount(self, parent=QModelIndex()):
return len(self.__model_data)
- def columnCount(self, parent):
+ def columnCount(self, parent=QModelIndex()):
return 2
+ def index_to_setting_key(self, index):
+ return list(self.__model_data.keys())[index.row()]
+
+ def add_settings(self, settings: dict):
+ self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount() + len(settings) - 1)
+ self.__model_data.update(settings)
+ self.__set_dirty_status(True)
+ self.endInsertRows()
+
+ if self.__model_data == self.__original_data:
+ self.__set_dirty_status(False)
+
+ def remove_setting(self, index: QModelIndex):
+ if not index.isValid():
+ return
+
+ self.beginRemoveRows(QModelIndex(), index.row(), index.row())
+ del self.__model_data[list(self.__model_data.keys())[index.row()]]
+ self.__set_dirty_status(True)
+ self.endRemoveRows()
+
+ if self.__model_data == self.__original_data:
+ self.__set_dirty_status(False)
+
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
@@ -38,15 +62,18 @@ def data(self, index, role=Qt.DisplayRole):
font = QFont()
font.setBold(True)
return font
- elif (
- index.column() == self.VALUE_COL
- and self.__model_data[key] != self.__original_data[key]
+ elif index.column() == self.VALUE_COL and (
+ key not in self.__original_data
+ or self.__model_data[key] != self.__original_data[key]
):
font = QFont()
font.setItalic(True)
return font
elif role == Qt.ForegroundRole and index.column() == self.VALUE_COL:
- if self.__model_data[key] != self.__original_data[key]:
+ if (
+ key not in self.__original_data
+ or self.__model_data[key] != self.__original_data[key]
+ ):
return QColorConstants.DarkGreen
return None
@@ -59,13 +86,11 @@ def setData(self, index, value, role=Qt.EditRole) -> bool:
if value != self.__model_data[key]:
self.__model_data[key] = value
- if value != self.__original_data[key]:
- self.__dirty = True
- self.is_dirty_changed.emit(True)
+ if key not in self.__original_data or value != self.__original_data[key]:
+ self.__set_dirty_status(True)
else:
if self.__model_data == self.__original_data:
- self.__dirty = False
- self.is_dirty_changed.emit(False)
+ self.__set_dirty_status(False)
return True
@@ -84,6 +109,13 @@ def flags(self, idx):
def is_dirty(self):
return self.__dirty
+ def __set_dirty_status(self, status: bool):
+ self.__dirty = status
+ self.is_dirty_changed.emit(status)
+
+ def current_setting_keys(self) -> list[str]:
+ return list(self.__model_data.keys())
+
def service_config(self):
return self.__model_data.copy()
@@ -93,5 +125,12 @@ def service_name(self):
def set_not_dirty(self):
# Data saved in the provider
self.__original_data = self.__model_data.copy()
- self.__dirty = False
- self.is_dirty_changed.emit(False)
+ self.__set_dirty_status(False)
+
+ def invalid_settings(self):
+ """
+ Validation for service entries.
+
+ :return: List of invalid settings.
+ """
+ return [k for k, v in self.__model_data.items() if v.strip() == ""]
diff --git a/pg_service_parser/core/pg_service_parser_wrapper.py b/pg_service_parser/core/pg_service_parser_wrapper.py
index 9895c45..66be49d 100644
--- a/pg_service_parser/core/pg_service_parser_wrapper.py
+++ b/pg_service_parser/core/pg_service_parser_wrapper.py
@@ -5,14 +5,17 @@
def conf_path() -> Path:
- path = pgserviceparser.conf_path()
- return path if path.exists() else None
+ return pgserviceparser.conf_path()
def service_names(conf_file_path: Optional[str] = None) -> List[str]:
return pgserviceparser.service_names(conf_file_path)
+def add_new_service(service_name: str, conf_file_path: Optional[str] = None) -> bool:
+ return create_service(service_name, {}, conf_file_path)
+
+
def service_config(service_name: str, conf_file_path: Optional[str] = None) -> dict:
return pgserviceparser.service_config(service_name, conf_file_path)
@@ -37,7 +40,8 @@ def create_service(
config.write(f)
if service_name in config:
- pgserviceparser.write_service(service_name, settings)
+ if settings:
+ pgserviceparser.write_service(service_name, settings)
return True
return False
diff --git a/pg_service_parser/gui/dlg_pg_service.py b/pg_service_parser/gui/dlg_pg_service.py
index d502922..689df51 100644
--- a/pg_service_parser/gui/dlg_pg_service.py
+++ b/pg_service_parser/gui/dlg_pg_service.py
@@ -1,15 +1,22 @@
+from pathlib import Path
+
+from qgis.core import QgsApplication
from qgis.gui import QgsMessageBar
from qgis.PyQt.QtCore import Qt, pyqtSlot
from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QSizePolicy
+from pg_service_parser.conf.service_settings import SERVICE_SETTINGS, SETTINGS_TEMPLATE
from pg_service_parser.core.item_models import ServiceConfigModel
from pg_service_parser.core.pg_service_parser_wrapper import (
+ add_new_service,
conf_path,
copy_service_settings,
service_config,
service_names,
write_service,
)
+from pg_service_parser.gui.dlg_service_name import ServiceNameDialog
+from pg_service_parser.gui.dlg_service_settings import ServiceSettingsDialog
from pg_service_parser.utils import get_ui_class
DIALOG_UI = get_ui_class("pg_service_dialog.ui")
@@ -23,26 +30,46 @@ def __init__(self, parent):
QDialog.__init__(self, parent)
self.setupUi(self)
+ # Flag to handle initialization of new files
+ self.__new_empty_file = False
+
conf_file_path = conf_path()
- if not conf_file_path:
+ self.__initialize_dialog(conf_file_path)
+
+ def __initialize_dialog(self, conf_file_path):
+ if not conf_file_path.exists():
+ self.btnCreateServiceFile.setIcon(QgsApplication.getThemeIcon("/mActionNewPage.svg"))
+ self.btnCreateServiceFile.clicked.connect(self.__create_file_clicked)
self.lblConfFile.setText("Config file not found!")
- self.lblConfFile.setToolTip(
- "Set your PGSERVICEFILE environment variable and reopen the dialog."
+ not_found_tooltip = (
+ "Create a config file at a default location or\n"
+ "set your PGSERVICEFILE environment variable and reopen the dialog."
)
+ self.lblConfFile.setToolTip(not_found_tooltip)
+ self.lblWarning.setToolTip(not_found_tooltip)
self.txtConfFile.setVisible(False)
self.tabWidget.setEnabled(False)
return
self.__edit_model = None
+ self.btnAddSettings.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg"))
+ self.btnRemoveSetting.setIcon(QgsApplication.getThemeIcon("/symbologyRemove.svg"))
self.txtConfFile.setText(str(conf_file_path))
self.lblWarning.setVisible(False)
+ self.lblConfFile.setText("Config file path found at ")
+ self.lblConfFile.setToolTip("")
+ self.txtConfFile.setVisible(True)
+ self.tabWidget.setEnabled(True)
+ self.btnCreateServiceFile.setVisible(False)
self.radOverwrite.toggled.connect(self.__update_target_controls)
self.btnCopyService.clicked.connect(self.__copy_service)
self.cboSourceService.currentIndexChanged.connect(self.__source_service_changed)
self.tabWidget.currentChanged.connect(self.__current_tab_changed)
self.cboEditService.currentIndexChanged.connect(self.__edit_service_changed)
+ self.btnAddSettings.clicked.connect(self.__add_settings_clicked)
+ self.btnRemoveSetting.clicked.connect(self.__remove_setting_clicked)
self.btnUpdateService.clicked.connect(self.__update_service_clicked)
self.__initialize_edit_services()
@@ -53,6 +80,19 @@ def __init__(self, parent):
self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
self.layout().insertWidget(0, self.bar)
+ @pyqtSlot()
+ def __create_file_clicked(self):
+ dlg = ServiceNameDialog(self)
+ dlg.exec_()
+ if dlg.result() == QDialog.Accepted:
+ path = conf_path()
+ Path.touch(path)
+ add_new_service(dlg.service_name)
+
+ # Set flag to get a template after some initialization
+ self.__new_empty_file = True
+ self.__initialize_dialog(path)
+
@pyqtSlot(bool)
def __update_target_controls(self, checked):
self.cboTargetService.setEnabled(self.radOverwrite.isChecked())
@@ -157,9 +197,50 @@ def __edit_service_changed(self, index):
self.__edit_model.is_dirty_changed.connect(self.btnUpdateService.setEnabled)
self.btnUpdateService.setDisabled(True)
+ if self.__new_empty_file:
+ # Add service template
+ self.__edit_model.add_settings(SETTINGS_TEMPLATE)
+ self.__new_empty_file = False
+
+ @pyqtSlot()
+ def __add_settings_clicked(self):
+ dlg = ServiceSettingsDialog(self, self.__edit_model.current_setting_keys())
+ dlg.exec_()
+
+ if dlg.settings_to_add:
+ settings = {k: v for k, v in SERVICE_SETTINGS.items() if k in dlg.settings_to_add}
+ self.__edit_model.add_settings(settings)
+
+ @pyqtSlot()
+ def __remove_setting_clicked(self):
+ selected_indexes = self.tblServiceConfig.selectedIndexes()
+ if selected_indexes:
+ setting_key = self.__edit_model.index_to_setting_key(selected_indexes[0])
+ if (
+ QMessageBox.question(
+ self,
+ "Remove service setting",
+ f"Are you sure you want to remove the '{setting_key}' setting?",
+ QMessageBox.Yes | QMessageBox.No,
+ QMessageBox.No,
+ )
+ == QMessageBox.Yes
+ ):
+ self.__edit_model.remove_setting(selected_indexes[0])
+
@pyqtSlot()
def __update_service_clicked(self):
if self.__edit_model and self.__edit_model.is_dirty():
+ invalid = self.__edit_model.invalid_settings()
+ if invalid:
+ self.bar.pushWarning(
+ "PG service",
+ "Settings '{}' have invalid values. Adjust them and try again.".format(
+ "', '".join(invalid)
+ ),
+ )
+ return
+
target_service = self.cboEditService.currentText()
write_service(target_service, self.__edit_model.service_config())
self.bar.pushSuccess("PG service", f"PG service '{target_service}' updated!")
diff --git a/pg_service_parser/gui/dlg_service_name.py b/pg_service_parser/gui/dlg_service_name.py
new file mode 100644
index 0000000..edb7798
--- /dev/null
+++ b/pg_service_parser/gui/dlg_service_name.py
@@ -0,0 +1,21 @@
+from qgis.PyQt.QtCore import pyqtSlot
+from qgis.PyQt.QtWidgets import QDialog
+
+from pg_service_parser.utils import get_ui_class
+
+DIALOG_UI = get_ui_class("service_name_dialog.ui")
+
+
+class ServiceNameDialog(QDialog, DIALOG_UI):
+
+ def __init__(self, parent):
+ QDialog.__init__(self, parent)
+ self.setupUi(self)
+
+ self.buttonBox.accepted.connect(self.__accepted)
+ self.service_name = "my-service"
+
+ @pyqtSlot()
+ def __accepted(self):
+ if self.txtServiceName.text().strip():
+ self.service_name = self.txtServiceName.text().replace(" ", "-")
diff --git a/pg_service_parser/gui/dlg_service_settings.py b/pg_service_parser/gui/dlg_service_settings.py
new file mode 100644
index 0000000..e74d591
--- /dev/null
+++ b/pg_service_parser/gui/dlg_service_settings.py
@@ -0,0 +1,24 @@
+from qgis.PyQt.QtCore import pyqtSlot
+from qgis.PyQt.QtWidgets import QDialog
+
+from pg_service_parser.conf.service_settings import SERVICE_SETTINGS
+from pg_service_parser.utils import get_ui_class
+
+DIALOG_UI = get_ui_class("service_settings_dialog.ui")
+
+
+class ServiceSettingsDialog(QDialog, DIALOG_UI):
+
+ def __init__(self, parent, settings_to_hide: list[str]):
+ QDialog.__init__(self, parent)
+ self.setupUi(self)
+
+ self.buttonBox.accepted.connect(self.__accepted)
+
+ settings = set(SERVICE_SETTINGS.keys()) - set(settings_to_hide)
+ self.lstSettings.addItems(settings)
+ self.settings_to_add = []
+
+ @pyqtSlot()
+ def __accepted(self):
+ self.settings_to_add = [item.text() for item in self.lstSettings.selectedItems()]
diff --git a/pg_service_parser/ui/pg_service_dialog.ui b/pg_service_parser/ui/pg_service_dialog.ui
index 6ec4fa8..c43e301 100644
--- a/pg_service_parser/ui/pg_service_dialog.ui
+++ b/pg_service_parser/ui/pg_service_dialog.ui
@@ -67,6 +67,13 @@
+ -
+
+
+ Create file at default location
+
+
+
-
@@ -81,42 +88,8 @@
Edit
-
-
-
-
-
-
-
-
- Edit service
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
- -
+
+
-
QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed
@@ -144,7 +117,56 @@
- -
+
-
+
+
-
+
+
+
+ 25
+ 25
+
+
+
+ Add settings to current service
+
+
+
+
+
+
+ -
+
+
+
+ 25
+ 25
+
+
+
+ Remove setting from current service
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+ -
-
@@ -168,6 +190,40 @@
+ -
+
+
-
+
+
+ Edit service
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
diff --git a/pg_service_parser/ui/service_name_dialog.ui b/pg_service_parser/ui/service_name_dialog.ui
new file mode 100644
index 0000000..69a0c96
--- /dev/null
+++ b/pg_service_parser/ui/service_name_dialog.ui
@@ -0,0 +1,99 @@
+
+
+ dlgServiceName
+
+
+
+ 0
+ 0
+ 260
+ 164
+
+
+
+ Service name
+
+
+ -
+
+
+ Enter a service name
+
+
+
+ -
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ e.g., my-service
+
+
+ true
+
+
+
+ -
+
+
+
+ true
+
+
+
+ color: rgb(154, 153, 150);
+
+
+ Leave empty to use a default name
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ dlgServiceName
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ dlgServiceName
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/pg_service_parser/ui/service_settings_dialog.ui b/pg_service_parser/ui/service_settings_dialog.ui
new file mode 100644
index 0000000..033691e
--- /dev/null
+++ b/pg_service_parser/ui/service_settings_dialog.ui
@@ -0,0 +1,74 @@
+
+
+ dlgServiceSettings
+
+
+
+ 0
+ 0
+ 226
+ 205
+
+
+
+ Add service settings
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+ -
+
+
+ true
+
+
+ QAbstractItemView::ExtendedSelection
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ dlgServiceSettings
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ dlgServiceSettings
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+