oqtopus 0.1.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oqtopus/__init__.py +4 -0
- oqtopus/core/module.py +115 -0
- oqtopus/core/module_asset.py +16 -0
- oqtopus/core/module_package.py +118 -0
- oqtopus/core/modules_config.py +11 -0
- oqtopus/core/package_prepare_task.py +148 -0
- oqtopus/gui/__init__.py +0 -0
- oqtopus/gui/about_dialog.py +61 -0
- oqtopus/gui/database_connection_widget.py +154 -0
- oqtopus/gui/database_create_dialog.py +210 -0
- oqtopus/gui/database_duplicate_dialog.py +100 -0
- oqtopus/gui/logs_widget.py +177 -0
- oqtopus/gui/main_dialog.py +168 -0
- oqtopus/gui/module_selection_widget.py +350 -0
- oqtopus/gui/module_widget.py +199 -0
- oqtopus/gui/parameters_groupbox.py +75 -0
- oqtopus/gui/project_widget.py +170 -0
- oqtopus/gui/settings_dialog.py +37 -0
- oqtopus/oqtopus.py +67 -0
- oqtopus/oqtopus_plugin.py +184 -0
- oqtopus/ui/__init__.py +0 -0
- oqtopus/utils/__init__.py +0 -0
- oqtopus/utils/plugin_utils.py +172 -0
- oqtopus/utils/qt_utils.py +94 -0
- oqtopus/utils/tmmtlogging.py +50 -0
- oqtopus/utils/translation.py +84 -0
- oqtopus-0.1.14.dist-info/METADATA +363 -0
- oqtopus-0.1.14.dist-info/RECORD +33 -0
- oqtopus-0.1.14.dist-info/WHEEL +5 -0
- oqtopus-0.1.14.dist-info/licenses/LICENSE +339 -0
- oqtopus-0.1.14.dist-info/top_level.txt +2 -0
- tests/__init__.py +12 -0
- tests/test_plugin_load.py +22 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Profile
|
4
|
+
# Copyright (C) 2025 Damiano Lombardi
|
5
|
+
# -----------------------------------------------------------
|
6
|
+
#
|
7
|
+
# licensed under the terms of GNU GPL 2
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
10
|
+
# it under the terms of the GNU General Public License as published by
|
11
|
+
# the Free Software Foundation; either version 2 of the License, or
|
12
|
+
# (at your option) any later version.
|
13
|
+
#
|
14
|
+
# This program is distributed in the hope that it will be useful,
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# GNU General Public License for more details.
|
18
|
+
#
|
19
|
+
# You should have received a copy of the GNU General Public License along
|
20
|
+
# with this program; if not, print to the Free Software Foundation, Inc.,
|
21
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
22
|
+
#
|
23
|
+
# ---------------------------------------------------------------------
|
24
|
+
|
25
|
+
import psycopg
|
26
|
+
from pgserviceparser import service_config as pgserviceparser_service_config
|
27
|
+
from pgserviceparser import service_names as pgserviceparser_service_names
|
28
|
+
from pgserviceparser import write_service as pgserviceparser_write_service
|
29
|
+
from qgis.PyQt.QtCore import Qt
|
30
|
+
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
|
31
|
+
|
32
|
+
from ..utils.plugin_utils import PluginUtils, logger
|
33
|
+
from ..utils.qt_utils import OverrideCursor
|
34
|
+
|
35
|
+
DIALOG_UI = PluginUtils.get_ui_class("database_create_dialog.ui")
|
36
|
+
|
37
|
+
|
38
|
+
class DatabaseCreateDialog(QDialog, DIALOG_UI):
|
39
|
+
def __init__(self, selected_service=None, parent=None):
|
40
|
+
QDialog.__init__(self, parent)
|
41
|
+
self.setupUi(self)
|
42
|
+
|
43
|
+
self.existingService_comboBox.clear()
|
44
|
+
for service_name in pgserviceparser_service_names():
|
45
|
+
self.existingService_comboBox.addItem(service_name)
|
46
|
+
|
47
|
+
if selected_service:
|
48
|
+
self.existingService_comboBox.setCurrentText(selected_service)
|
49
|
+
|
50
|
+
self.existingService_comboBox.currentIndexChanged.connect(self._serviceChanged)
|
51
|
+
|
52
|
+
self.enterManually_radioButton.toggled.connect(self._enterManuallyToggled)
|
53
|
+
|
54
|
+
self.parameters_ssl_comboBox.clear()
|
55
|
+
self.parameters_ssl_comboBox.addItem("Not set", None)
|
56
|
+
notSetFont = self.parameters_ssl_comboBox.font()
|
57
|
+
notSetFont.setItalic(True)
|
58
|
+
self.parameters_ssl_comboBox.setItemData(0, notSetFont, Qt.ItemDataRole.FontRole)
|
59
|
+
self.parameters_ssl_comboBox.addItem("disable", "disable")
|
60
|
+
self.parameters_ssl_comboBox.addItem("allow", "allow")
|
61
|
+
self.parameters_ssl_comboBox.addItem("prefer", "prefer")
|
62
|
+
self.parameters_ssl_comboBox.addItem("require", "require")
|
63
|
+
self.parameters_ssl_comboBox.addItem("verify-ca", "verify-ca")
|
64
|
+
self.parameters_ssl_comboBox.addItem("verify-full", "verify-full")
|
65
|
+
|
66
|
+
self.database_lineEdit.textChanged.connect(self._databaseTextChanged)
|
67
|
+
|
68
|
+
self.buttonBox.accepted.connect(self._accept)
|
69
|
+
|
70
|
+
if self.existingService_comboBox.count() > 0:
|
71
|
+
self._serviceChanged()
|
72
|
+
|
73
|
+
def created_service_name(self):
|
74
|
+
return self.service_lineEdit.text()
|
75
|
+
|
76
|
+
def _serviceChanged(self):
|
77
|
+
service_name = self.existingService_comboBox.currentText()
|
78
|
+
service_config = pgserviceparser_service_config(service_name)
|
79
|
+
|
80
|
+
service_host = service_config.get("host", None)
|
81
|
+
service_port = service_config.get("port", None)
|
82
|
+
service_ssl = service_config.get("sslmode", None)
|
83
|
+
service_dbname = service_config.get("dbname", None)
|
84
|
+
service_user = service_config.get("user", None)
|
85
|
+
service_password = service_config.get("password", None)
|
86
|
+
|
87
|
+
self.parameters_host_lineEdit.setText(service_host)
|
88
|
+
self.parameters_port_lineEdit.setText(service_port)
|
89
|
+
|
90
|
+
parameter_ssl_index = self.parameters_ssl_comboBox.findData(service_ssl)
|
91
|
+
self.parameters_ssl_comboBox.setCurrentIndex(parameter_ssl_index)
|
92
|
+
self.parameters_user_lineEdit.setText(service_user)
|
93
|
+
self.parameters_password_lineEdit.setText(service_password)
|
94
|
+
|
95
|
+
self.database_lineEdit.setText(service_dbname)
|
96
|
+
|
97
|
+
def _enterManuallyToggled(self, checked):
|
98
|
+
self.parameters_frame.setEnabled(checked)
|
99
|
+
|
100
|
+
def _databaseTextChanged(self, text):
|
101
|
+
self.database_label.setText(text)
|
102
|
+
|
103
|
+
def _accept(self):
|
104
|
+
service_name = self.created_service_name()
|
105
|
+
|
106
|
+
if service_name == "":
|
107
|
+
QMessageBox.critical(self, "Error", "Please enter a service name.")
|
108
|
+
return
|
109
|
+
|
110
|
+
# Check if the service name is already in use
|
111
|
+
if service_name in pgserviceparser_service_names():
|
112
|
+
QMessageBox.critical(
|
113
|
+
self, "Error", self.tr(f"Service name '{service_name}' already exists.")
|
114
|
+
)
|
115
|
+
return
|
116
|
+
|
117
|
+
new_database_name = self.database_lineEdit.text()
|
118
|
+
if new_database_name == "":
|
119
|
+
QMessageBox.critical(self, "Error", "Please enter a database name.")
|
120
|
+
return
|
121
|
+
|
122
|
+
database_connection = None
|
123
|
+
try:
|
124
|
+
with OverrideCursor(Qt.CursorShape.WaitCursor):
|
125
|
+
database_connection = psycopg.connect(**self._get_connection_parameters())
|
126
|
+
database_connection.autocommit = True
|
127
|
+
with database_connection.cursor() as cursor:
|
128
|
+
cursor.execute(
|
129
|
+
psycopg.sql.SQL("CREATE DATABASE {}").format(
|
130
|
+
psycopg.sql.Identifier(new_database_name)
|
131
|
+
)
|
132
|
+
)
|
133
|
+
|
134
|
+
except Exception as e:
|
135
|
+
errorText = self.tr(f"Error creating the new database:\n{e}.")
|
136
|
+
logger.error(errorText)
|
137
|
+
QMessageBox.critical(self, "Error", errorText)
|
138
|
+
return
|
139
|
+
|
140
|
+
finally:
|
141
|
+
if database_connection:
|
142
|
+
database_connection.close()
|
143
|
+
|
144
|
+
# Proceed with writing the new service configuration
|
145
|
+
service_settings = self._get_new_service_settings()
|
146
|
+
|
147
|
+
try:
|
148
|
+
pgserviceparser_write_service(
|
149
|
+
service_name=service_name,
|
150
|
+
settings=service_settings,
|
151
|
+
create_if_not_found=True,
|
152
|
+
)
|
153
|
+
except Exception as e:
|
154
|
+
errorText = self.tr(f"Error writing the new service configuration:\n{e}.")
|
155
|
+
logger.error(errorText)
|
156
|
+
QMessageBox.critical(self, "Error", errorText)
|
157
|
+
return
|
158
|
+
|
159
|
+
super().accept()
|
160
|
+
|
161
|
+
def _get_connection_parameters(self):
|
162
|
+
"""
|
163
|
+
Returns a dictionary of connection parameters suitable for psycopg.connect().
|
164
|
+
Uses manual input if 'Enter manually' is checked, otherwise uses the selected service name.
|
165
|
+
"""
|
166
|
+
settings = dict()
|
167
|
+
if self.enterManually_radioButton.isChecked():
|
168
|
+
settings.update(self._get_manual_connection_parameters())
|
169
|
+
else:
|
170
|
+
# Use the selected service name
|
171
|
+
service_name = self.existingService_comboBox.currentText()
|
172
|
+
if service_name:
|
173
|
+
settings["service"] = service_name
|
174
|
+
|
175
|
+
return settings
|
176
|
+
|
177
|
+
def _get_new_service_settings(self):
|
178
|
+
settings = dict()
|
179
|
+
|
180
|
+
if self.enterManually_radioButton.isChecked():
|
181
|
+
settings.update(self._get_manual_connection_parameters())
|
182
|
+
else:
|
183
|
+
# Copy settings from the selected existing service
|
184
|
+
service_name = self.existingService_comboBox.currentText()
|
185
|
+
existing_settings = pgserviceparser_service_config(service_name)
|
186
|
+
settings.update(existing_settings)
|
187
|
+
# Overwrite dbname with the new database name
|
188
|
+
if self.database_lineEdit.text():
|
189
|
+
settings["dbname"] = self.database_lineEdit.text()
|
190
|
+
|
191
|
+
return settings
|
192
|
+
|
193
|
+
def _get_manual_connection_parameters(self):
|
194
|
+
parameters = dict()
|
195
|
+
|
196
|
+
# Collect parameters from manual input fields
|
197
|
+
if self.parameters_host_lineEdit.text():
|
198
|
+
parameters["host"] = self.parameters_host_lineEdit.text()
|
199
|
+
if self.parameters_port_lineEdit.text():
|
200
|
+
parameters["port"] = self.parameters_port_lineEdit.text()
|
201
|
+
if self.parameters_ssl_comboBox.currentData():
|
202
|
+
parameters["sslmode"] = self.parameters_ssl_comboBox.currentData()
|
203
|
+
if self.parameters_user_lineEdit.text():
|
204
|
+
parameters["user"] = self.parameters_user_lineEdit.text()
|
205
|
+
if self.parameters_password_lineEdit.text():
|
206
|
+
parameters["password"] = self.parameters_password_lineEdit.text()
|
207
|
+
if self.database_lineEdit.text():
|
208
|
+
parameters["dbname"] = self.database_lineEdit.text()
|
209
|
+
|
210
|
+
return parameters
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Profile
|
4
|
+
# Copyright (C) 2025 Damiano Lombardi
|
5
|
+
# -----------------------------------------------------------
|
6
|
+
#
|
7
|
+
# licensed under the terms of GNU GPL 2
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
10
|
+
# it under the terms of the GNU General Public License as published by
|
11
|
+
# the Free Software Foundation; either version 2 of the License, or
|
12
|
+
# (at your option) any later version.
|
13
|
+
#
|
14
|
+
# This program is distributed in the hope that it will be useful,
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# GNU General Public License for more details.
|
18
|
+
#
|
19
|
+
# You should have received a copy of the GNU General Public License along
|
20
|
+
# with this program; if not, print to the Free Software Foundation, Inc.,
|
21
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
22
|
+
#
|
23
|
+
# ---------------------------------------------------------------------
|
24
|
+
|
25
|
+
import psycopg
|
26
|
+
from pgserviceparser import service_config as pgserviceparser_service_config
|
27
|
+
from qgis.PyQt.QtCore import Qt
|
28
|
+
from qgis.PyQt.QtWidgets import QDialog, QMessageBox
|
29
|
+
|
30
|
+
from ..utils.plugin_utils import PluginUtils
|
31
|
+
from ..utils.qt_utils import OverrideCursor
|
32
|
+
|
33
|
+
DIALOG_UI = PluginUtils.get_ui_class("database_duplicate_dialog.ui")
|
34
|
+
|
35
|
+
|
36
|
+
class DatabaseDuplicateDialog(QDialog, DIALOG_UI):
|
37
|
+
def __init__(self, selected_service=None, parent=None):
|
38
|
+
QDialog.__init__(self, parent)
|
39
|
+
self.setupUi(self)
|
40
|
+
|
41
|
+
self.existingService_label.setText(selected_service)
|
42
|
+
|
43
|
+
self.__existing_service_config = pgserviceparser_service_config(selected_service)
|
44
|
+
self.existingDatabase_label.setText(self.__existing_service_config.get("dbname", ""))
|
45
|
+
|
46
|
+
self.buttonBox.accepted.connect(self._accept)
|
47
|
+
|
48
|
+
def _accept(self):
|
49
|
+
|
50
|
+
if self.newDatabase_lineEdit.text() == "":
|
51
|
+
QMessageBox.critical(self, "Error", "Please enter a database name.")
|
52
|
+
return
|
53
|
+
|
54
|
+
if self.newService_lineEdit.text() == "":
|
55
|
+
QMessageBox.critical(self, "Error", "Please enter a service name.")
|
56
|
+
return
|
57
|
+
|
58
|
+
service_name = self.existingService_label.text()
|
59
|
+
try:
|
60
|
+
database_connection = psycopg.connect(service=service_name)
|
61
|
+
|
62
|
+
except Exception as exception:
|
63
|
+
errorText = self.tr(f"Can't connect to service '{service_name}':\n{exception}.")
|
64
|
+
QMessageBox.critical(self, "Error", errorText)
|
65
|
+
return
|
66
|
+
|
67
|
+
# Duplicate the database
|
68
|
+
new_database_name = self.newDatabase_lineEdit.text()
|
69
|
+
try:
|
70
|
+
database_connection.autocommit = True
|
71
|
+
with OverrideCursor(Qt.CursorShape.WaitCursor):
|
72
|
+
with database_connection.cursor() as cursor:
|
73
|
+
cursor.execute(
|
74
|
+
f"CREATE DATABASE {new_database_name} TEMPLATE {self.__existing_service_config.get('dbname')}"
|
75
|
+
)
|
76
|
+
except psycopg.Error as e:
|
77
|
+
errorText = self.tr(f"Error duplicating database:\n{e}.")
|
78
|
+
QMessageBox.critical(self, "Error", errorText)
|
79
|
+
return
|
80
|
+
finally:
|
81
|
+
database_connection.close()
|
82
|
+
|
83
|
+
# Create new service configuration
|
84
|
+
new_service_name = self.newService_lineEdit.text()
|
85
|
+
new_service_config = pgserviceparser_service_config(
|
86
|
+
new_service_name,
|
87
|
+
dbname=new_database_name,
|
88
|
+
host=self.__existing_service_config.get("host", ""),
|
89
|
+
port=self.__existing_service_config.get("port", ""),
|
90
|
+
user=self.__existing_service_config.get("user", ""),
|
91
|
+
password=self.__existing_service_config.get("password", ""),
|
92
|
+
)
|
93
|
+
try:
|
94
|
+
pgserviceparser_service_config.write_service_config(new_service_config)
|
95
|
+
except Exception as e:
|
96
|
+
errorText = self.tr(f"Error writing new service configuration:\n{e}.")
|
97
|
+
QMessageBox.critical(self, "Error", errorText)
|
98
|
+
return
|
99
|
+
|
100
|
+
super().accept()
|
@@ -0,0 +1,177 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from qgis.PyQt.QtCore import QAbstractItemModel, QModelIndex, QSortFilterProxyModel, Qt
|
4
|
+
from qgis.PyQt.QtWidgets import QAbstractItemView, QApplication, QStyle, QWidget
|
5
|
+
|
6
|
+
from ..utils.plugin_utils import LoggingBridge, PluginUtils
|
7
|
+
|
8
|
+
DIALOG_UI = PluginUtils.get_ui_class("logs_widget.ui")
|
9
|
+
|
10
|
+
|
11
|
+
COLUMNS = ["Level", "Module", "Message"]
|
12
|
+
|
13
|
+
|
14
|
+
class LogModel(QAbstractItemModel):
|
15
|
+
def __init__(self, parent=None):
|
16
|
+
QAbstractItemModel.__init__(self, parent)
|
17
|
+
self.logs = []
|
18
|
+
|
19
|
+
def add_log(self, log):
|
20
|
+
self.beginInsertRows(QModelIndex(), len(self.logs), len(self.logs))
|
21
|
+
self.logs.append(log)
|
22
|
+
self.endInsertRows()
|
23
|
+
|
24
|
+
def headerData(self, section: int, orientation: Qt.Orientation, role: Qt.ItemDataRole = None):
|
25
|
+
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
|
26
|
+
return COLUMNS[section]
|
27
|
+
return None
|
28
|
+
|
29
|
+
def rowCount(self, parent=None):
|
30
|
+
return len(self.logs)
|
31
|
+
|
32
|
+
def columnCount(self, parent=None):
|
33
|
+
return len(COLUMNS)
|
34
|
+
|
35
|
+
def data(self, index: QModelIndex, role: Qt.ItemDataRole = None):
|
36
|
+
if not index.isValid() or role != Qt.ItemDataRole.DisplayRole:
|
37
|
+
return None
|
38
|
+
if (
|
39
|
+
index.row() < 0
|
40
|
+
or index.row() >= len(self.logs)
|
41
|
+
or index.column() < 0
|
42
|
+
or index.column() >= len(COLUMNS)
|
43
|
+
):
|
44
|
+
return None
|
45
|
+
log = self.logs[index.row()]
|
46
|
+
return log[COLUMNS[index.column()]]
|
47
|
+
|
48
|
+
def index(self, row: int, column: int, parent=None):
|
49
|
+
if row < 0 or row >= len(self.logs) or column < 0 or column >= len(COLUMNS):
|
50
|
+
return QModelIndex()
|
51
|
+
return self.createIndex(row, column)
|
52
|
+
|
53
|
+
def parent(self, index: QModelIndex):
|
54
|
+
if not index.isValid():
|
55
|
+
return QModelIndex()
|
56
|
+
return QModelIndex()
|
57
|
+
|
58
|
+
def flags(self, index: QModelIndex):
|
59
|
+
return (
|
60
|
+
Qt.ItemFlag.ItemIsEnabled
|
61
|
+
| Qt.ItemFlag.ItemIsSelectable
|
62
|
+
| Qt.ItemFlag.ItemNeverHasChildren
|
63
|
+
)
|
64
|
+
|
65
|
+
|
66
|
+
class LogFilterProxyModel(QSortFilterProxyModel):
|
67
|
+
LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
68
|
+
|
69
|
+
def __init__(self, parent=None):
|
70
|
+
super().__init__(parent)
|
71
|
+
self.level_filter = None
|
72
|
+
|
73
|
+
def setLevelFilter(self, level):
|
74
|
+
self.level_filter = level
|
75
|
+
self.invalidateFilter()
|
76
|
+
|
77
|
+
def filterAcceptsRow(self, source_row, source_parent):
|
78
|
+
model = self.sourceModel()
|
79
|
+
index_level = model.index(source_row, 0, source_parent) # Level column
|
80
|
+
index_message = model.index(source_row, 2, source_parent) # Message column
|
81
|
+
index_module = model.index(source_row, 1, source_parent) # Module column
|
82
|
+
# Level filter (show entries with at least the selected level)
|
83
|
+
if self.level_filter and self.level_filter != "ALL":
|
84
|
+
level = model.data(index_level, Qt.ItemDataRole.DisplayRole)
|
85
|
+
try:
|
86
|
+
filter_idx = self.LEVELS.index(self.level_filter)
|
87
|
+
level_idx = self.LEVELS.index(level)
|
88
|
+
if level_idx < filter_idx:
|
89
|
+
return False
|
90
|
+
except ValueError:
|
91
|
+
return False
|
92
|
+
# Text filter (from QLineEdit)
|
93
|
+
filter_text = self.filterRegularExpression().pattern()
|
94
|
+
if filter_text:
|
95
|
+
msg = model.data(index_message, Qt.ItemDataRole.DisplayRole) or ""
|
96
|
+
mod = model.data(index_module, Qt.ItemDataRole.DisplayRole) or ""
|
97
|
+
if filter_text.lower() not in msg.lower() and filter_text.lower() not in mod.lower():
|
98
|
+
return False
|
99
|
+
return True
|
100
|
+
|
101
|
+
|
102
|
+
class LogsWidget(QWidget, DIALOG_UI):
|
103
|
+
|
104
|
+
def __init__(self, parent=None):
|
105
|
+
QWidget.__init__(self, parent)
|
106
|
+
self.setupUi(self)
|
107
|
+
self.loggingBridge = LoggingBridge(
|
108
|
+
level=logging.NOTSET, excluded_modules=["urllib3.connectionpool"]
|
109
|
+
)
|
110
|
+
self.logs_model = LogModel(self)
|
111
|
+
|
112
|
+
# Use custom proxy model
|
113
|
+
self.proxy_model = LogFilterProxyModel(self)
|
114
|
+
self.proxy_model.setSourceModel(self.logs_model)
|
115
|
+
self.proxy_model.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
116
|
+
self.proxy_model.setFilterKeyColumn(-1)
|
117
|
+
|
118
|
+
self.logs_treeView.setModel(self.proxy_model)
|
119
|
+
self.logs_treeView.setAlternatingRowColors(True)
|
120
|
+
self.logs_treeView.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
121
|
+
self.logs_treeView.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
122
|
+
self.logs_treeView.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
123
|
+
self.loggingBridge.loggedLine.connect(self.__logged_line)
|
124
|
+
logging.getLogger().addHandler(self.loggingBridge)
|
125
|
+
|
126
|
+
self.logs_level_comboBox.addItems(
|
127
|
+
[
|
128
|
+
"DEBUG",
|
129
|
+
"INFO",
|
130
|
+
"WARNING",
|
131
|
+
"ERROR",
|
132
|
+
"CRITICAL",
|
133
|
+
]
|
134
|
+
)
|
135
|
+
self.logs_level_comboBox.currentTextChanged.connect(self.proxy_model.setLevelFilter)
|
136
|
+
self.logs_level_comboBox.setCurrentText("INFO")
|
137
|
+
|
138
|
+
self.logs_openFile_toolButton.setIcon(
|
139
|
+
QApplication.style().standardIcon(QStyle.StandardPixmap.SP_FileIcon)
|
140
|
+
)
|
141
|
+
self.logs_openFolder_toolButton.setIcon(
|
142
|
+
QApplication.style().standardIcon(QStyle.StandardPixmap.SP_DirOpenIcon)
|
143
|
+
)
|
144
|
+
self.logs_clear_toolButton.setIcon(
|
145
|
+
QApplication.style().standardIcon(QStyle.StandardPixmap.SP_TitleBarCloseButton)
|
146
|
+
)
|
147
|
+
self.logs_openFile_toolButton.clicked.connect(self.__logsOpenFileClicked)
|
148
|
+
self.logs_openFolder_toolButton.clicked.connect(self.__logsOpenFolderClicked)
|
149
|
+
self.logs_clear_toolButton.clicked.connect(self.__logsClearClicked)
|
150
|
+
self.logs_filter_LineEdit.textChanged.connect(self.proxy_model.setFilterFixedString)
|
151
|
+
|
152
|
+
def close(self):
|
153
|
+
# uninstall the logging bridge
|
154
|
+
logging.getLogger().removeHandler(self.loggingBridge)
|
155
|
+
|
156
|
+
def __logged_line(self, record, line):
|
157
|
+
|
158
|
+
log_entry = {
|
159
|
+
"Level": record.levelname,
|
160
|
+
"Module": record.name,
|
161
|
+
"Message": record.msg,
|
162
|
+
}
|
163
|
+
|
164
|
+
self.logs_model.add_log(log_entry)
|
165
|
+
|
166
|
+
# Automatically scroll to the bottom of the logs
|
167
|
+
scroll_bar = self.logs_treeView.verticalScrollBar()
|
168
|
+
scroll_bar.setValue(scroll_bar.maximum())
|
169
|
+
|
170
|
+
def __logsOpenFileClicked(self):
|
171
|
+
PluginUtils.open_log_file()
|
172
|
+
|
173
|
+
def __logsOpenFolderClicked(self):
|
174
|
+
PluginUtils.open_logs_folder()
|
175
|
+
|
176
|
+
def __logsClearClicked(self):
|
177
|
+
self.logs_model.clear()
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# -----------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Profile
|
4
|
+
# Copyright (C) 2025 Damiano Lombardi
|
5
|
+
# -----------------------------------------------------------
|
6
|
+
#
|
7
|
+
# licensed under the terms of GNU GPL 2
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
10
|
+
# it under the terms of the GNU General Public License as published by
|
11
|
+
# the Free Software Foundation; either version 2 of the License, or
|
12
|
+
# (at your option) any later version.
|
13
|
+
#
|
14
|
+
# This program is distributed in the hope that it will be useful,
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
17
|
+
# GNU General Public License for more details.
|
18
|
+
#
|
19
|
+
# You should have received a copy of the GNU General Public License along
|
20
|
+
# with this program; if not, print to the Free Software Foundation, Inc.,
|
21
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
22
|
+
#
|
23
|
+
# ---------------------------------------------------------------------
|
24
|
+
|
25
|
+
|
26
|
+
import os
|
27
|
+
import sys
|
28
|
+
|
29
|
+
from qgis.PyQt.QtCore import QUrl
|
30
|
+
from qgis.PyQt.QtGui import QAction, QDesktopServices
|
31
|
+
from qgis.PyQt.QtWidgets import (
|
32
|
+
QDialog,
|
33
|
+
QMenuBar,
|
34
|
+
)
|
35
|
+
|
36
|
+
from ..utils.plugin_utils import PluginUtils, logger
|
37
|
+
from .about_dialog import AboutDialog
|
38
|
+
from .settings_dialog import SettingsDialog
|
39
|
+
|
40
|
+
libs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "libs"))
|
41
|
+
if libs_path not in sys.path:
|
42
|
+
sys.path.insert(0, libs_path)
|
43
|
+
|
44
|
+
from .database_connection_widget import DatabaseConnectionWidget # noqa: E402
|
45
|
+
from .logs_widget import LogsWidget # noqa: E402
|
46
|
+
from .module_selection_widget import ModuleSelectionWidget # noqa: E402
|
47
|
+
from .module_widget import ModuleWidget # noqa: E402
|
48
|
+
from .project_widget import ProjectWidget # noqa: E402
|
49
|
+
|
50
|
+
DIALOG_UI = PluginUtils.get_ui_class("main_dialog.ui")
|
51
|
+
|
52
|
+
|
53
|
+
class MainDialog(QDialog, DIALOG_UI):
|
54
|
+
|
55
|
+
def __init__(self, modules_config, parent=None):
|
56
|
+
QDialog.__init__(self, parent)
|
57
|
+
self.setupUi(self)
|
58
|
+
|
59
|
+
self.buttonBox.rejected.connect(self.__closeDialog)
|
60
|
+
self.buttonBox.helpRequested.connect(self.__helpRequested)
|
61
|
+
|
62
|
+
# Init GUI Modules
|
63
|
+
self.__moduleSelectionWidget = ModuleSelectionWidget(modules_config, self)
|
64
|
+
self.moduleSelection_groupBox.layout().addWidget(self.__moduleSelectionWidget)
|
65
|
+
|
66
|
+
# Init GUI Database
|
67
|
+
self.__databaseConnectionWidget = DatabaseConnectionWidget(self)
|
68
|
+
self.db_groupBox.layout().addWidget(self.__databaseConnectionWidget)
|
69
|
+
|
70
|
+
# Init GUI Module Info
|
71
|
+
self.__moduleWidget = ModuleWidget(self)
|
72
|
+
self.module_tab.layout().addWidget(self.__moduleWidget)
|
73
|
+
|
74
|
+
# Init GUI Project
|
75
|
+
self.__projectWidget = ProjectWidget(self)
|
76
|
+
self.project_tab.layout().addWidget(self.__projectWidget)
|
77
|
+
|
78
|
+
# Init GUI Logs
|
79
|
+
self.__logsWidget = LogsWidget(self)
|
80
|
+
self.logs_groupBox.layout().addWidget(self.__logsWidget)
|
81
|
+
|
82
|
+
# Add menubar
|
83
|
+
self.menubar = QMenuBar(self)
|
84
|
+
# On macOS, setNativeMenuBar(False) to show the menu bar inside the dialog window
|
85
|
+
if sys.platform == "darwin":
|
86
|
+
self.menubar.setNativeMenuBar(False)
|
87
|
+
self.layout().setMenuBar(self.menubar)
|
88
|
+
|
89
|
+
# Settings action
|
90
|
+
settings_action = QAction(self.tr("Settings"), self)
|
91
|
+
settings_action.triggered.connect(self.__open_settings_dialog)
|
92
|
+
|
93
|
+
# About action
|
94
|
+
about_action = QAction(self.tr("About"), self)
|
95
|
+
about_action.triggered.connect(self.__show_about_dialog)
|
96
|
+
|
97
|
+
# Add actions to menubar
|
98
|
+
self.menubar.addAction(settings_action)
|
99
|
+
self.menubar.addAction(about_action)
|
100
|
+
|
101
|
+
self.__moduleSelectionWidget.signal_loadingStarted.connect(
|
102
|
+
self.__moduleSelection_loadingStarted
|
103
|
+
)
|
104
|
+
self.__moduleSelectionWidget.signal_loadingFinished.connect(
|
105
|
+
self.__moduleSelection_loadingFinished
|
106
|
+
)
|
107
|
+
|
108
|
+
self.__databaseConnectionWidget.signal_connectionChanged.connect(
|
109
|
+
self.__databaseConnectionWidget_connectionChanged
|
110
|
+
)
|
111
|
+
|
112
|
+
self.module_tab.setEnabled(False)
|
113
|
+
self.plugin_tab.setEnabled(False)
|
114
|
+
self.project_tab.setEnabled(False)
|
115
|
+
|
116
|
+
logger.info("Ready.")
|
117
|
+
|
118
|
+
def __closeDialog(self):
|
119
|
+
self.__moduleSelectionWidget.close()
|
120
|
+
self.__logsWidget.close()
|
121
|
+
self.accept()
|
122
|
+
|
123
|
+
def __helpRequested(self):
|
124
|
+
help_page = "https://github.com/oqtopus/Oqtopus"
|
125
|
+
logger.info(f"Opening help page {help_page}")
|
126
|
+
QDesktopServices.openUrl(QUrl(help_page))
|
127
|
+
|
128
|
+
def __open_settings_dialog(self):
|
129
|
+
dlg = SettingsDialog(self)
|
130
|
+
dlg.exec()
|
131
|
+
|
132
|
+
def __show_about_dialog(self):
|
133
|
+
dialog = AboutDialog(self)
|
134
|
+
dialog.exec()
|
135
|
+
|
136
|
+
def __moduleSelection_loadingStarted(self):
|
137
|
+
self.db_groupBox.setEnabled(False)
|
138
|
+
self.module_tab.setEnabled(False)
|
139
|
+
self.plugin_tab.setEnabled(False)
|
140
|
+
self.project_tab.setEnabled(False)
|
141
|
+
|
142
|
+
def __moduleSelection_loadingFinished(self):
|
143
|
+
self.db_groupBox.setEnabled(True)
|
144
|
+
|
145
|
+
module_package = self.__moduleSelectionWidget.getSelectedModulePackage()
|
146
|
+
if module_package is None:
|
147
|
+
return
|
148
|
+
|
149
|
+
if self.__moduleSelectionWidget.lastError() is not None:
|
150
|
+
return
|
151
|
+
|
152
|
+
self.module_tab.setEnabled(True)
|
153
|
+
|
154
|
+
if module_package.asset_plugin is not None:
|
155
|
+
self.plugin_tab.setEnabled(True)
|
156
|
+
|
157
|
+
if module_package.asset_project is not None:
|
158
|
+
self.project_tab.setEnabled(True)
|
159
|
+
|
160
|
+
self.__moduleWidget.setModulePackage(
|
161
|
+
self.__moduleSelectionWidget.getSelectedModulePackage()
|
162
|
+
)
|
163
|
+
self.__projectWidget.setModulePackage(
|
164
|
+
self.__moduleSelectionWidget.getSelectedModulePackage()
|
165
|
+
)
|
166
|
+
|
167
|
+
def __databaseConnectionWidget_connectionChanged(self):
|
168
|
+
self.__moduleWidget.setDatabaseConnection(self.__databaseConnectionWidget.getConnection())
|