diff --git a/pg_service_parser/core/connection_model.py b/pg_service_parser/core/connection_model.py index 9dc548a..6c4a89d 100644 --- a/pg_service_parser/core/connection_model.py +++ b/pg_service_parser/core/connection_model.py @@ -42,17 +42,20 @@ def data(self, index, role=Qt.ItemDataRole.DisplayRole): font = QFont() font.setItalic(True) return font + elif role == Qt.ItemDataRole.ToolTipRole: + if index.column() == self.VALUE_COL: + return self.__model_data[key].uri() return None - def headerData( - self, section: int, orientation: Qt.Orientation, role: Qt.DisplayRole = Qt.DisplayRole - ): - if orientation == Qt.Horizontal: + def headerData(self, section, orientation, role): + if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole: if section == self.KEY_COL: return "Connection name" elif section == self.VALUE_COL: - return "Configuration" + return "URI" + + return QAbstractTableModel.headerData(self, section, orientation, role) def flags(self, idx): if not idx.isValid(): diff --git a/pg_service_parser/core/service_connections.py b/pg_service_parser/core/service_connections.py index bc0f56d..03069f7 100644 --- a/pg_service_parser/core/service_connections.py +++ b/pg_service_parser/core/service_connections.py @@ -18,30 +18,30 @@ def get_connections(service: str) -> dict[str, QgsAbstractDatabaseProviderConnec return res -def create_connection(service: str, name: str) -> None: +def create_connection(service: str, connection_name: str) -> None: config = {} uri = f"service='{service}'" provider = QgsProviderRegistry.instance().providerMetadata("postgres") conn = provider.createConnection(uri, config) - provider.saveConnection(conn, name) + provider.saveConnection(conn, connection_name) # conn.store(name) -def remove_connection(conn_name: str) -> None: +def remove_connection(connection_name: str) -> None: provider = QgsProviderRegistry.instance().providerMetadata("postgres") - provider.deleteConnection(conn_name) + provider.deleteConnection(connection_name) -def edit_connection(conn_name: str) -> None: +def edit_connection(connection_name: str) -> None: provider = QgsProviderRegistry.instance().providerMetadata("postgres") - if conn_name in provider.dbConnections(): + if connection_name in provider.dbConnections(): pg = QgsGui.sourceSelectProviderRegistry().providerByName("postgres") w = pg.createDataSourceWidget() settings = QSettings() settings.value("PostgreSQL/connections/selected") - settings.setValue("PostgreSQL/connections/selected", conn_name) + settings.setValue("PostgreSQL/connections/selected", connection_name) w.refresh() # To reflect the newly selected connection w.btnEdit_clicked() diff --git a/pg_service_parser/core/item_models.py b/pg_service_parser/core/setting_model.py similarity index 100% rename from pg_service_parser/core/item_models.py rename to pg_service_parser/core/setting_model.py diff --git a/pg_service_parser/gui/dlg_new_name.py b/pg_service_parser/gui/dlg_new_name.py new file mode 100644 index 0000000..031d8dc --- /dev/null +++ b/pg_service_parser/gui/dlg_new_name.py @@ -0,0 +1,42 @@ +from enum import Enum + +from qgis.PyQt.QtCore import pyqtSlot +from qgis.PyQt.QtWidgets import QDialog, QWidget + +from pg_service_parser.utils import get_ui_class + +DIALOG_UI = get_ui_class("new_name_dialog.ui") + + +class EnumNewName(Enum): + SERVICE = 0 + CONNECTION = 1 + + +class NewNameDialog(QDialog, DIALOG_UI): + + def __init__(self, mode: EnumNewName, parent: QWidget, data: str = "") -> None: + QDialog.__init__(self, parent) + self.setupUi(self) + self.__mode = mode + + self.buttonBox.accepted.connect(self.__accepted) + + if self.__mode == EnumNewName.SERVICE: + self.setWindowTitle("Service name") + self.label.setText("Enter a service name") + self.txtNewName.setPlaceholderText("e.g., my-service") + self.new_name = "my-service" + elif self.__mode == EnumNewName.CONNECTION: + self.setWindowTitle("Connection name") + self.label.setText("Enter a connection name") + self.txtNewName.setPlaceholderText("e.g., My Service Connection") + self.new_name = f"{data} connection" + + @pyqtSlot() + def __accepted(self): + if self.txtNewName.text().strip(): + if self.__mode == EnumNewName.SERVICE: + self.new_name = self.txtNewName.text().strip().replace(" ", "-") + elif self.__mode == EnumNewName.CONNECTION: + self.new_name = self.txtNewName.text().strip() diff --git a/pg_service_parser/gui/dlg_pg_service.py b/pg_service_parser/gui/dlg_pg_service.py index c4f55d1..8024e06 100644 --- a/pg_service_parser/gui/dlg_pg_service.py +++ b/pg_service_parser/gui/dlg_pg_service.py @@ -2,11 +2,11 @@ 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 qgis.PyQt.QtCore import QItemSelection, QModelIndex, Qt, pyqtSlot +from qgis.PyQt.QtWidgets import QDialog, QHeaderView, 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.connection_model import ServiceConnectionModel from pg_service_parser.core.pg_service_parser_wrapper import ( add_new_service, conf_path, @@ -15,13 +15,21 @@ service_names, write_service, ) -from pg_service_parser.gui.dlg_service_name import ServiceNameDialog +from pg_service_parser.core.service_connections import ( + create_connection, + edit_connection, + get_connections, + remove_connection, +) +from pg_service_parser.core.setting_model import ServiceConfigModel +from pg_service_parser.gui.dlg_new_name import EnumNewName, NewNameDialog 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") EDIT_TAB_INDEX = 0 COPY_TAB_INDEX = 1 +CONNECTION_TAB_INDEX = 2 class PgServiceDialog(QDialog, DIALOG_UI): @@ -52,9 +60,13 @@ def __initialize_dialog(self): return self.__edit_model = None + self.__connection_model = None self.btnAddSettings.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg")) self.btnRemoveSetting.setIcon(QgsApplication.getThemeIcon("/symbologyRemove.svg")) + self.btnAddConnection.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg")) + self.btnEditConnection.setIcon(QgsApplication.getThemeIcon("/symbologyEdit.svg")) + self.btnRemoveConnection.setIcon(QgsApplication.getThemeIcon("/symbologyRemove.svg")) self.txtConfFile.setText(str(self.__conf_file_path)) self.lblWarning.setVisible(False) self.lblConfFile.setText("Config file path found at ") @@ -62,6 +74,7 @@ def __initialize_dialog(self): self.txtConfFile.setVisible(True) self.tabWidget.setEnabled(True) self.btnCreateServiceFile.setVisible(False) + self.tblServiceConnections.horizontalHeader().setVisible(True) self.radOverwrite.toggled.connect(self.__update_target_controls) self.btnCopyService.clicked.connect(self.__copy_service) @@ -71,9 +84,15 @@ def __initialize_dialog(self): 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.cboConnectionService.currentIndexChanged.connect(self.__connection_service_changed) + self.btnAddConnection.clicked.connect(self.__add_connection_clicked) + self.btnEditConnection.clicked.connect(self.__edit_connection_clicked) + self.btnRemoveConnection.clicked.connect(self.__remove_connection_clicked) + self.tblServiceConnections.doubleClicked.connect(self.__edit_double_clicked_connection) self.__initialize_edit_services() self.__initialize_copy_services() + self.__initialize_connection_services() self.__update_target_controls(True) self.bar = QgsMessageBar() @@ -82,11 +101,11 @@ def __initialize_dialog(self): @pyqtSlot() def __create_file_clicked(self): - dlg = ServiceNameDialog(self) + dlg = NewNameDialog(EnumNewName.SERVICE, self) dlg.exec() if dlg.result() == QDialog.DialogCode.Accepted: Path.touch(self.__conf_file_path) - add_new_service(dlg.service_name) + add_new_service(dlg.new_name) # Set flag to get a template after some initialization self.__new_empty_file = True @@ -130,6 +149,15 @@ def __initialize_edit_services(self): self.cboEditService.addItems(service_names(self.__conf_file_path)) self.cboEditService.setCurrentText(current_text) + def __initialize_connection_services(self): + self.__connection_model = None + current_text = self.cboConnectionService.currentText() # Remember latest currentText + self.cboConnectionService.blockSignals(True) # Avoid triggering custom slot while clearing + self.cboConnectionService.clear() + self.cboConnectionService.blockSignals(False) + self.cboConnectionService.addItems(service_names(self.__conf_file_path)) + self.cboConnectionService.setCurrentText(current_text) + @pyqtSlot() def __copy_service(self): # Validations @@ -170,6 +198,8 @@ def __current_tab_changed(self, index): pass # For now, services to be copied won't be altered in other tabs elif index == EDIT_TAB_INDEX: self.__initialize_edit_services() + elif index == CONNECTION_TAB_INDEX: + self.__initialize_connection_services() @pyqtSlot(int) def __edit_service_changed(self, index): @@ -252,3 +282,72 @@ def __update_service_clicked(self): self.__edit_model.set_not_dirty() else: self.bar.pushInfo("PG service", "Edit the service configuration and try again.") + + @pyqtSlot(int) + def __connection_service_changed(self, index): + self.__initialize_service_connections() + + def __initialize_service_connections(self, selected_index=QModelIndex()): + service = self.cboConnectionService.currentText() + self.__connection_model = ServiceConnectionModel(service, get_connections(service)) + self.__update_connection_controls(False) + self.tblServiceConnections.setModel(self.__connection_model) + self.tblServiceConnections.horizontalHeader().setSectionResizeMode( + 0, QHeaderView.ResizeToContents + ) + + self.tblServiceConnections.selectionModel().selectionChanged.connect( + self.__conn_table_selection_changed + ) + self.tblServiceConnections.selectRow(selected_index.row()) # Remember selection + + @pyqtSlot() + def __add_connection_clicked(self): + service = self.cboConnectionService.currentText() + dlg = NewNameDialog(EnumNewName.CONNECTION, self, service) + dlg.exec() + if dlg.result() == QDialog.DialogCode.Accepted: + create_connection(service, dlg.new_name) + self.__initialize_service_connections() + + @pyqtSlot() + def __edit_connection_clicked(self): + selected_indexes = self.tblServiceConnections.selectedIndexes() + if selected_indexes: + self.__edit_connection(selected_indexes[0]) + + @pyqtSlot(QModelIndex) + def __edit_double_clicked_connection(self, index): + self.__edit_connection(index) + + def __edit_connection(self, index): + connection_name = self.__connection_model.index_to_connection_key(index) + edit_connection(connection_name) + self.__initialize_service_connections(index) + + @pyqtSlot() + def __remove_connection_clicked(self): + selected_indexes = self.tblServiceConnections.selectedIndexes() + if selected_indexes: + connection_name = self.__connection_model.index_to_connection_key(selected_indexes[0]) + if ( + QMessageBox.question( + self, + "Remove service connection", + f"Are you sure you want to remove the connection to '{connection_name}'?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + == QMessageBox.StandardButton.Yes + ): + remove_connection(connection_name) + self.__initialize_service_connections() + + @pyqtSlot(QItemSelection, QItemSelection) + def __conn_table_selection_changed(self, selected, deselected): + selected_indexes = bool(self.tblServiceConnections.selectedIndexes()) + self.__update_connection_controls(selected_indexes) + + def __update_connection_controls(self, enable): + self.btnEditConnection.setEnabled(enable) + self.btnRemoveConnection.setEnabled(enable) diff --git a/pg_service_parser/gui/dlg_service_name.py b/pg_service_parser/gui/dlg_service_name.py deleted file mode 100644 index edb7798..0000000 --- a/pg_service_parser/gui/dlg_service_name.py +++ /dev/null @@ -1,21 +0,0 @@ -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/ui/service_name_dialog.ui b/pg_service_parser/ui/new_name_dialog.ui similarity index 86% rename from pg_service_parser/ui/service_name_dialog.ui rename to pg_service_parser/ui/new_name_dialog.ui index 69a0c96..bf54cf3 100644 --- a/pg_service_parser/ui/service_name_dialog.ui +++ b/pg_service_parser/ui/new_name_dialog.ui @@ -1,7 +1,7 @@ - dlgServiceName - + dlgNewName + 0 @@ -11,23 +11,23 @@ - Service name + New name - Enter a service name + Enter a new name - + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - e.g., my-service + e.g., new name true @@ -66,7 +66,7 @@ buttonBox accepted() - dlgServiceName + dlgNewName accept() @@ -82,7 +82,7 @@ buttonBox rejected() - dlgServiceName + dlgNewName reject() diff --git a/pg_service_parser/ui/pg_service_dialog.ui b/pg_service_parser/ui/pg_service_dialog.ui index c43e301..902a0e2 100644 --- a/pg_service_parser/ui/pg_service_dialog.ui +++ b/pg_service_parser/ui/pg_service_dialog.ui @@ -6,7 +6,7 @@ 0 0 - 497 + 499 316 @@ -326,6 +326,143 @@ + + + Connections + + + + + + + + Service + + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::DotLine + + + false + + + false + + + true + + + false + + + + + + + + + + 25 + 25 + + + + Add connection to current service + + + + + + + + + + + 25 + 25 + + + + Edit connection + + + + + + + + + + + 25 + 25 + + + + Remove connection to current service + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + +