diff --git a/.gitignore b/.gitignore index c67859c..02b6b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ pg_service_parser/libs/ !pg_service_parser/libs/__init__.py .idea __pycache__ +.DS_Store diff --git a/pg_service_parser/core/copy_shortcuts.py b/pg_service_parser/core/copy_shortcuts.py new file mode 100644 index 0000000..8aad107 --- /dev/null +++ b/pg_service_parser/core/copy_shortcuts.py @@ -0,0 +1,120 @@ +from qgis.PyQt.QtCore import QAbstractTableModel, QModelIndex, QObject, Qt + +from pg_service_parser.core.plugin_settings import PluginSettings + + +class Shortcut: + def __init__(self, service_from: str, service_to: str, name: str = None): + if name: + self.name = name + else: + self.name = f"{service_from} -> {service_to}" + self.service_from = service_from + self.service_to = service_to + + def save(self): + PluginSettings().shortcut_from.setValue(self.service_from, self.name) + PluginSettings().shortcut_to.setValue(self.service_to, self.name) + + def rename(self, name: str): + PluginSettings().shortcuts_node.deleteItem(self.name) + self.name = name + self.save() + + def delete(self): + PluginSettings().shortcuts_node.deleteItem(self.name) + + +class ShortcutsModel(QAbstractTableModel): + def __init__(self, parent: QObject = None): + super().__init__(parent) + self.shortcuts = [] + + for shortcut in PluginSettings().shortcuts_node.items(): + sh_from = PluginSettings().shortcut_from.value(shortcut) + sh_to = PluginSettings().shortcut_to.value(shortcut) + self.shortcuts.append(Shortcut(sh_from, sh_to, shortcut)) + + def add_shortcut(self, service_from: str, service_to: str): + existing_names = [sh.name for sh in self.shortcuts] + base_name = f"{service_from} -> {service_to}" + name = base_name + i = 2 + while name in existing_names: + name = f"{base_name} ({i})" + i += 1 + + shortcut = Shortcut(service_from, service_to, name) + shortcut.save() + row = self.rowCount() + self.beginInsertRows(QModelIndex(), row, row) + self.shortcuts.append(shortcut) + self.endInsertRows() + self.dataChanged.emit(self.index(row, 0), self.index(row, 0)) + + def remove_shortcut(self, index: QModelIndex): + if not index.isValid(): + return + + self.beginRemoveRows(QModelIndex(), index.row(), index.row()) + self.shortcuts[index.row()].delete() + del self.shortcuts[index.row()] + self.endRemoveRows() + self.dataChanged.emit(index, index) + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if orientation == Qt.Vertical: + return None + + if role == Qt.DisplayRole: + if section == 0: + return self.tr("Name") + if section == 1: + return self.tr("From") + if section == 2: + return self.tr("To") + + return super().headerData(section, orientation, role) + + def rowCount(self, parent=QModelIndex()): + return len(self.shortcuts) + + def columnCount(self, parent=QModelIndex()): + return 3 + + def flags(self, index): + if not index.isValid(): + return None + + if index.column() == 0: + return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable + else: + return Qt.ItemIsSelectable | Qt.ItemIsEnabled + + def data(self, index, role=Qt.DisplayRole): + if not index.isValid: + return None + + if role == Qt.DisplayRole: + if index.column() == 0: + return self.shortcuts[index.row()].name + if index.column() == 1: + return self.shortcuts[index.row()].service_from + if index.column() == 2: + return self.shortcuts[index.row()].service_to + + return None + + def setData(self, index, value, role=Qt.EditRole): + if not index.isValid(): + return False + + if role == Qt.EditRole and len(value) > 0: + for sh in self.shortcuts: + if sh.name == value: + return False + self.shortcuts[index.row()].rename(value) + self.dataChanged.emit(index, index) + return True + + return False diff --git a/pg_service_parser/core/plugin_settings.py b/pg_service_parser/core/plugin_settings.py new file mode 100644 index 0000000..62c0c6c --- /dev/null +++ b/pg_service_parser/core/plugin_settings.py @@ -0,0 +1,20 @@ +from qgis.core import QgsSettingsEntryString, QgsSettingsTree + +PLUGIN_NAME = "pg_service_parser" + + +class PluginSettings: + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + + settings_node = QgsSettingsTree.createPluginTreeNode(pluginName=PLUGIN_NAME) + shortcuts_node = settings_node.createNamedListNode("shortcuts") + + cls.shortcut_from = QgsSettingsEntryString("shortcut_from", shortcuts_node) + cls.shortcut_to = QgsSettingsEntryString("shortcut_to", shortcuts_node) + cls.shortcuts_node = shortcuts_node + + return cls.instance diff --git a/pg_service_parser/gui/dlg_pg_service.py b/pg_service_parser/gui/dlg_pg_service.py index b7b9e1a..255ca1f 100644 --- a/pg_service_parser/gui/dlg_pg_service.py +++ b/pg_service_parser/gui/dlg_pg_service.py @@ -7,6 +7,7 @@ from pg_service_parser.conf.service_settings import SERVICE_SETTINGS, SETTINGS_TEMPLATE from pg_service_parser.core.connection_model import ServiceConnectionModel +from pg_service_parser.core.copy_shortcuts import ShortcutsModel from pg_service_parser.core.pg_service_parser_wrapper import ( add_new_service, conf_path, @@ -34,14 +35,15 @@ class PgServiceDialog(QDialog, DIALOG_UI): - - def __init__(self, parent): + def __init__(self, shortcuts_model: ShortcutsModel, parent): QDialog.__init__(self, parent) self.setupUi(self) # Flag to handle initialization of new files self.__new_empty_file = False + self.__shortcuts_model = shortcuts_model + self.__conf_file_path = conf_path() self.__initialize_dialog() @@ -68,6 +70,8 @@ def __initialize_dialog(self): self.btnAddConnection.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg")) self.btnEditConnection.setIcon(QgsApplication.getThemeIcon("/symbologyEdit.svg")) self.btnRemoveConnection.setIcon(QgsApplication.getThemeIcon("/symbologyRemove.svg")) + self.shortcutAddButton.setIcon(QgsApplication.getThemeIcon("/symbologyAdd.svg")) + self.shortcutRemoveButton.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 ") @@ -77,9 +81,12 @@ def __initialize_dialog(self): self.btnCreateServiceFile.setVisible(False) self.tblServiceConnections.horizontalHeader().setVisible(True) self.btnRemoveSetting.setEnabled(False) + self.shortcutRemoveButton.setEnabled(False) self.radOverwrite.toggled.connect(self.__update_target_controls) self.btnCopyService.clicked.connect(self.__copy_service) + self.shortcutAddButton.clicked.connect(self.__create_copy_shortcut) + self.shortcutRemoveButton.clicked.connect(self.__remove_copy_shortcut) self.cboSourceService.currentIndexChanged.connect(self.__source_service_changed) self.tabWidget.currentChanged.connect(self.__current_tab_changed) self.cboEditService.currentIndexChanged.connect(self.__edit_service_changed) @@ -118,6 +125,7 @@ def __create_file_clicked(self): def __update_target_controls(self, checked): self.cboTargetService.setEnabled(self.radOverwrite.isChecked()) self.txtNewService.setEnabled(not self.radOverwrite.isChecked()) + self.shortcutAddButton.setEnabled(self.radOverwrite.isChecked()) def __update_add_settings_button(self): # Make sure to call this method whenever the settings are added/removed @@ -148,6 +156,12 @@ def __initialize_copy_services(self): self.cboSourceService.addItems(service_names(self.__conf_file_path)) self.cboSourceService.setCurrentText(current_text) + self.shortcutsTableView.setModel(self.__shortcuts_model) + self.shortcutsTableView.resizeColumnsToContents() + self.shortcutsTableView.selectionModel().selectionChanged.connect( + self.__shortcuts_selection_changed + ) + def __initialize_edit_services(self): self.__edit_model = None current_text = self.cboEditService.currentText() # Remember latest currentText @@ -199,6 +213,35 @@ def __copy_service(self): if self.radCreate.isChecked(): self.__initialize_copy_services() # Reflect the newly added service + @pyqtSlot() + def __create_copy_shortcut(self): + target_service = self.cboTargetService.currentText() + + if not self.cboTargetService.currentText(): + self.bar.pushInfo("PG service", "Select a valid target service and try again.") + return + self.__shortcuts_model.add_shortcut(self.cboSourceService.currentText(), target_service) + if self.__shortcuts_model.rowCount() == 1: + self.shortcutsTableView.resizeColumnsToContents() + + @pyqtSlot() + def __remove_copy_shortcut(self): + selectedIndexes = self.shortcutsTableView.selectedIndexes() + if len(selectedIndexes) == 0: + return + + shortcut = self.__shortcuts_model.data(selectedIndexes[0]) + + res = QMessageBox.question( + self, + "Remove shortcut", + f"Are you sure you want to remove the shortcut '{shortcut}'?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if res == QMessageBox.StandardButton.Yes: + self.__shortcuts_model.remove_shortcut(selectedIndexes[0]) + @pyqtSlot(int) def __current_tab_changed(self, index): if index == COPY_TAB_INDEX: @@ -374,3 +417,7 @@ def __conn_table_selection_changed(self, selected, deselected): def __update_connection_controls(self, enable): self.btnEditConnection.setEnabled(enable) self.btnRemoveConnection.setEnabled(enable) + + @pyqtSlot(QItemSelection, QItemSelection) + def __shortcuts_selection_changed(self, selected, deselected): + self.shortcutRemoveButton.setEnabled(len(selected) > 0) diff --git a/pg_service_parser/pg_service_parser_plugin.py b/pg_service_parser/pg_service_parser_plugin.py index 36f5cde..26a1511 100644 --- a/pg_service_parser/pg_service_parser_plugin.py +++ b/pg_service_parser/pg_service_parser_plugin.py @@ -1,30 +1,113 @@ +import os from pathlib import Path +from qgis.core import NULL, QgsSettingsTree +from qgis.PyQt.QtCore import QCoreApplication, QLocale, QSettings, QTranslator from qgis.PyQt.QtGui import QIcon -from qgis.PyQt.QtWidgets import QAction +from qgis.PyQt.QtWidgets import QAction, QMenu, QToolButton +from pg_service_parser.core.copy_shortcuts import ShortcutsModel +from pg_service_parser.core.pg_service_parser_wrapper import ( + conf_path, + copy_service_settings, + service_names, +) +from pg_service_parser.core.plugin_settings import PLUGIN_NAME from pg_service_parser.gui.dlg_pg_service import PgServiceDialog class PgServiceParserPlugin: def __init__(self, iface): self.iface = iface + + # initialize translation + qgis_locale = QLocale( + str(QSettings().value("locale/userLocale")).replace(str(NULL), "en_CH") + ) + locale_path = os.path.join(os.path.dirname(__file__), "i18n") + self.translator = QTranslator() + self.translator.load(qgis_locale, "qgis-pg-service-parser-plugin", "_", locale_path) + QCoreApplication.installTranslator(self.translator) + self.action = None + self.shortcuts_model = None + + def tr(self, text: str) -> str: + return QCoreApplication.translate("Plugin", text) def initGui(self): - self.action = QAction( + icon = QIcon(str(Path(__file__).parent / "images" / "logo.png")) + + self.shortcuts_model = ShortcutsModel(self.iface.mainWindow()) + self.shortcuts_model.dataChanged.connect(self.build_menus) + + self.button = QToolButton(self.iface.mainWindow()) + self.button.setIcon(icon) + + self.menu = self.iface.pluginMenu().addMenu(icon, "PG service parser") + + self.default_action = QAction( QIcon(str(Path(__file__).parent / "images" / "logo.png")), "PG service parser", self.iface.mainWindow(), ) - self.action.triggered.connect(self.run) - self.iface.addToolBarIcon(self.action) - self.iface.addPluginToDatabaseMenu("PG service parser", self.action) + self.default_action.triggered.connect(self.run) + self.button.setDefaultAction(self.default_action) + + self.action = self.iface.addToolBarWidget(self.button) + + self.build_menus() + + def build_menus(self): + self.menu.clear() + + button_menu = QMenu() + button_menu.addAction(self.default_action) + + self.menu.addAction(self.default_action) + + if len(self.shortcuts_model.shortcuts): + _conf_path = conf_path() + _services = service_names(_conf_path) + self.button.setPopupMode(QToolButton.MenuButtonPopup) + button_menu.addSeparator() + for shortcut in self.shortcuts_model.shortcuts: + action = QAction(shortcut.name, self.iface.mainWindow()) + action.setToolTip( + self.tr(f"Copy service '{shortcut.service_from}' to '{shortcut.service_to}'.") + ) + action.setEnabled( + _conf_path.exists() + and shortcut.service_from in _services + and shortcut.service_to in _services + ) + action.triggered.connect( + lambda _triggered, _shortcut=shortcut: self.copy_service( + _shortcut.service_from, _shortcut.service_to + ) + ) + button_menu.addAction(action) + self.menu.addAction(action) + else: + self.button.setPopupMode(QToolButton.DelayedPopup) + + self.button.setMenu(button_menu) def unload(self): self.iface.removeToolBarIcon(self.action) self.iface.removePluginDatabaseMenu("PG service parser", self.action) + self.iface.pluginMenu().removeAction(self.menu.menuAction()) + QgsSettingsTree.unregisterPluginTreeNode(PLUGIN_NAME) def run(self): - dlg = PgServiceDialog(self.iface.mainWindow()) + dlg = PgServiceDialog(self.shortcuts_model, self.iface.mainWindow()) dlg.exec() + + def copy_service(self, service_from: str, service_to: str): + print(service_from, service_to) + _conf_path = conf_path() + if _conf_path.exists(): + copy_service_settings(service_from, service_to, _conf_path) + self.iface.messageBar().pushMessage( + "PG service", f"PG service copied to '{service_to}'!" + ) diff --git a/pg_service_parser/ui/pg_service_dialog.ui b/pg_service_parser/ui/pg_service_dialog.ui index c50ebb1..041cce2 100644 --- a/pg_service_parser/ui/pg_service_dialog.ui +++ b/pg_service_parser/ui/pg_service_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 499 - 316 + 693 + 574 @@ -231,22 +231,6 @@ Copy Service - - - - Qt::Vertical - - - QSizePolicy::Preferred - - - - 20 - 71 - - - - @@ -260,6 +244,59 @@ + + + + Copy service + + + + + + + Shortcuts + + + + + + ... + + + + + + + ... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + @@ -317,12 +354,34 @@ - - - - Copy service + + + + Qt::Vertical - + + QSizePolicy::Preferred + + + + 20 + 71 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + +