From 67d1ecb7f77498dbe68b3245995680c5e5973a04 Mon Sep 17 00:00:00 2001 From: Alexander Bruy Date: Mon, 13 Jan 2025 13:34:57 +0000 Subject: [PATCH] make plugin compatibile with Qt6 --- Mergin/attachment_fields_model.py | 6 +- Mergin/clone_project_dialog.py | 2 +- Mergin/collapsible_message_box.py | 10 +-- Mergin/configuration_dialog.py | 2 +- Mergin/create_project_wizard.py | 38 +++++------ Mergin/diff_dialog.py | 4 +- Mergin/metadata.txt | 2 + Mergin/plugin.py | 46 ++++++++----- .../processing/algs/download_vector_tiles.py | 2 +- Mergin/project_selection_dialog.py | 26 ++++---- Mergin/project_settings_widget.py | 4 +- Mergin/project_status_dialog.py | 8 +-- Mergin/projects_manager.py | 64 +++++++++++-------- Mergin/sync_dialog.py | 16 ++--- Mergin/utils.py | 11 ++-- Mergin/workspace_selection_dialog.py | 16 ++--- 16 files changed, 146 insertions(+), 111 deletions(-) diff --git a/Mergin/attachment_fields_model.py b/Mergin/attachment_fields_model.py index 60479748..ff8fbd60 100644 --- a/Mergin/attachment_fields_model.py +++ b/Mergin/attachment_fields_model.py @@ -12,9 +12,9 @@ class AttachmentFieldsModel(QStandardItemModel): - LAYER_ID = Qt.UserRole + 1 - FIELD_NAME = Qt.UserRole + 2 - EXPRESSION = Qt.UserRole + 3 + LAYER_ID = Qt.ItemDataRole.UserRole + 1 + FIELD_NAME = Qt.ItemDataRole.UserRole + 2 + EXPRESSION = Qt.ItemDataRole.UserRole + 3 def __init__(self, parent=None): super().__init__(parent) diff --git a/Mergin/clone_project_dialog.py b/Mergin/clone_project_dialog.py index 1347d13c..c542aaf6 100644 --- a/Mergin/clone_project_dialog.py +++ b/Mergin/clone_project_dialog.py @@ -52,7 +52,7 @@ def __init__(self, user_info, default_workspace=None): self.invalid = False def validate_input(self): - is_writable = bool(self.ui.projectNamespace.currentData(Qt.UserRole)) + is_writable = bool(self.ui.projectNamespace.currentData(Qt.ItemDataRole.UserRole)) if not is_writable: msg = "You do not have permissions to create a project in this workspace!" self.ui.edit_project_name.setEnabled(False) diff --git a/Mergin/collapsible_message_box.py b/Mergin/collapsible_message_box.py index 41c72cd3..fd1d6156 100644 --- a/Mergin/collapsible_message_box.py +++ b/Mergin/collapsible_message_box.py @@ -1,5 +1,5 @@ -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMessageBox +from qgis.PyQt.QtCore import Qt +from qgis.PyQt.QtWidgets import QMessageBox from qgis.PyQt import QtWidgets @@ -7,10 +7,10 @@ class CollapsibleBox(QtWidgets.QWidget): def __init__(self, text, details, title="Mergin Maps error", parent=None): msg = QMessageBox() msg.setWindowTitle(title) - msg.setTextFormat(Qt.RichText) + msg.setTextFormat(Qt.TextFormat.RichText) msg.setText(text) msg.setIcon(QMessageBox.Icon.Warning) - msg.setStandardButtons(QMessageBox.Close) - msg.setDefaultButton(QMessageBox.Close) + msg.setStandardButtons(QMessageBox.StandardButton.Close) + msg.setDefaultButton(QMessageBox.StandardButton.Close) msg.setDetailedText(details) msg.exec() diff --git a/Mergin/configuration_dialog.py b/Mergin/configuration_dialog.py index 76aaafe6..98d34de0 100644 --- a/Mergin/configuration_dialog.py +++ b/Mergin/configuration_dialog.py @@ -128,7 +128,7 @@ def writeSettings(self): return mc def test_connection(self): - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) ok, msg = test_server_connection(self.server_url(), self.ui.username.text(), self.ui.password.text()) QApplication.restoreOverrideCursor() self.ui.test_status.setText(msg) diff --git a/Mergin/create_project_wizard.py b/Mergin/create_project_wizard.py index ddfa87f5..7e976b39 100644 --- a/Mergin/create_project_wizard.py +++ b/Mergin/create_project_wizard.py @@ -13,7 +13,7 @@ QWizard, ) -from qgis.core import QgsProject, QgsLayerTreeNode, QgsLayerTreeModel +from qgis.core import QgsProject, QgsLayerTreeNode, QgsLayerTreeModel, NULL from qgis.utils import iface from .utils import ( @@ -170,7 +170,7 @@ def set_info(self, info=None): def check_input(self): """Check if entered path is not already a Mergin Maps project dir and has at most a single QGIS project file.""" # TODO: check if the project exists on the server - if not self.project_owner_cbo.currentData(Qt.UserRole): + if not self.project_owner_cbo.currentData(Qt.ItemDataRole.UserRole): self.create_warning("You do not have permissions to create a project in this workspace!") return proj_name = self.project_name_ledit.text().strip() @@ -261,8 +261,8 @@ def columnCount(self, parent): return 4 def headerData(self, section, orientation, role): - if orientation == Qt.Horizontal: - if role == Qt.DisplayRole: + if orientation == Qt.AlignmentFlag.Horizontal: + if role == Qt.ItemDataRole.DisplayRole: if section == self.LAYER_COL: return "Layer" elif section == self.PACK_COL: @@ -281,8 +281,10 @@ def index(self, row, column, parent): return idx def toggle_item(self, idx): - is_checked = self.data(idx, Qt.CheckStateRole) == Qt.Checked - self.setData(idx, Qt.Unchecked if is_checked else Qt.Checked, Qt.CheckStateRole) + is_checked = self.data(idx, Qt.ItemDataRole.CheckStateRole) == Qt.CheckState.Checked + self.setData( + idx, Qt.CheckState.Unchecked if is_checked else Qt.CheckState.Checked, Qt.ItemDataRole.CheckStateRole + ) def map_layer(self, idx): if idx.column() == self.LAYER_COL: @@ -307,17 +309,17 @@ def data(self, idx, role): return self.layer_tree_model.data(self.mapToSource(idx), role) layer = self.map_layer(idx) if not layer: - return QVariant() - if role == Qt.CheckStateRole or role == Qt.UserRole: + return NULL + if role == Qt.ItemDataRole.CheckStateRole or role == Qt.ItemDataRole.UserRole: state = self.layers_state[layer.id()] if idx.column() == state: - return Qt.Checked + return Qt.CheckState.Checked else: - return Qt.Unchecked - return QVariant() + return Qt.CheckState.Unchecked + return NULL def setData(self, index, value, role): - if role == Qt.CheckStateRole: + if role == Qt.ItemDataRole.CheckStateRole: layer = self.map_layer(index) if not layer: return False @@ -357,14 +359,14 @@ def node_shown(self, node): def flags(self, idx): if idx.column() == self.LAYER_COL: - return Qt.ItemIsEnabled + return Qt.ItemFlags.ItemIsEnabled layer = self.map_layer(idx) if not layer: - return Qt.NoItemFlags + return Qt.ItemFlags.NoItemFlags else: - enabled_flags = Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsUserCheckable + enabled_flags = Qt.ItemFlags.ItemIsEnabled | Qt.ItemFlags.ItemIsEditable | Qt.ItemFlags.ItemIsUserCheckable if idx.column() == self.LAYER_COL: - return Qt.ItemIsEnabled + return Qt.ItemFlags.ItemIsEnabled elif idx.column() == self.PACK_COL: return enabled_flags if layer.id() in self.packable else Qt.ItemFlags() elif idx.column() in (self.KEEP_COL, self.IGNORE_COL): @@ -386,7 +388,7 @@ def __init__(self, parent=None): self.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) - self.setEditTriggers(QTreeView.NoEditTriggers) + self.setEditTriggers(QAbstractItemView.EditTriggers.NoEditTriggers) self.clicked.connect(self.model().toggle_item) @@ -463,7 +465,7 @@ def accept(self): reload_project = False failed_packaging = [] - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) QApplication.processEvents() if not self.init_page.cur_proj_no_pack_btn.isChecked(): diff --git a/Mergin/diff_dialog.py b/Mergin/diff_dialog.py index 654ca19a..f583c4ec 100644 --- a/Mergin/diff_dialog.py +++ b/Mergin/diff_dialog.py @@ -29,7 +29,7 @@ def __init__(self, parent=None): QDialog.__init__(self, parent) self.ui = uic.loadUi(ui_file, self) - with OverrideCursor(Qt.WaitCursor): + with OverrideCursor(Qt.CursorShape.WaitCursor): QgsGui.instance().enableAutoGeometryRestore(self) settings = QSettings() state = settings.value("Mergin/changesViewerSplitterSize") @@ -80,7 +80,7 @@ def __init__(self, parent=None): self.toolbar.setIconSize(iface.iconSize()) self.map_canvas.enableAntiAliasing(True) - self.map_canvas.setSelectionColor(QColor(Qt.cyan)) + self.map_canvas.setSelectionColor(QColor(Qt.GlobalColor.cyan)) self.pan_tool = QgsMapToolPan(self.map_canvas) self.map_canvas.setMapTool(self.pan_tool) diff --git a/Mergin/metadata.txt b/Mergin/metadata.txt index 338b681b..99e99d84 100644 --- a/Mergin/metadata.txt +++ b/Mergin/metadata.txt @@ -231,3 +231,5 @@ experimental=False ; deprecated flag (applies to the whole plugin and not only to the uploaded version) deprecated=False + +supportsQt6=yes diff --git a/Mergin/plugin.py b/Mergin/plugin.py index 40a8f873..fb6d9805 100644 --- a/Mergin/plugin.py +++ b/Mergin/plugin.py @@ -4,7 +4,11 @@ # Copyright Lutra Consulting Limited from math import floor -import sip + +try: + import sip +except ImportError: + from PyQt6 import sip import os import shutil from pathlib import Path @@ -284,8 +288,8 @@ def show_browser_panel(self): q += "Would you like to open it and see your Mergin projects?" if not browser.isVisible(): res = QMessageBox.question(None, "Mergin Maps - QGIS Browser Panel", q) - if res == QMessageBox.Yes: - self.iface.addDockWidget(Qt.LeftDockWidgetArea, browser) + if res == QMessageBox.StandardButton.Yes: + self.iface.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, browser) def configure(self): """Open plugin configuration dialog.""" @@ -327,7 +331,9 @@ def show_no_workspaces_dialog(self): "Click on the button below to create one. \n\n" "A minimum of one workspace is required to use Mergin Maps." ) - msg_box = QMessageBox(QMessageBox.Icon.Critical, "You do not have any workspace", msg, QMessageBox.Close) + msg_box = QMessageBox( + QMessageBox.Icon.Critical, "You do not have any workspace", msg, QMessageBox.StandardButton.Close + ) create_button = msg_box.addButton("Create workspace", msg_box.ActionRole) create_button.clicked.disconnect() create_button.clicked.connect(partial(self.open_configured_url, "/workspaces")) @@ -615,13 +621,13 @@ def clone_remote_project(self): self.mc.clone_project(self.project_name, dlg.project_name, dlg.project_namespace) except (URLError, ClientError) as e: msg = "Failed to clone project {}:\n\n{}".format(self.project_name, str(e)) - QMessageBox.critical(None, "Clone project", msg, QMessageBox.Close) + QMessageBox.critical(None, "Clone project", msg, QMessageBox.StandardButton.Close) return except LoginError as e: login_error_message(e) return msg = "Mergin Maps project cloned successfully." - QMessageBox.information(None, "Clone project", msg, QMessageBox.Close) + QMessageBox.information(None, "Clone project", msg, QMessageBox.StandardButton.Close) self.parent().reload() # we also need to reload My projects group as the cloned project could appear there group_items = self.project_manager.get_mergin_browser_groups() @@ -630,17 +636,17 @@ def clone_remote_project(self): def remove_remote_project(self): dlg = RemoveProjectDialog(self.project_name) - if dlg.exec() == QDialog.Rejected: + if dlg.exec() == QDialog.DialogCode.Rejected: return try: self.mc.delete_project(self.project_name) msg = "Mergin Maps project removed successfully." - QMessageBox.information(None, "Remove project", msg, QMessageBox.Close) + QMessageBox.information(None, "Remove project", msg, QMessageBox.StandardButton.Close) self.parent().reload() except (URLError, ClientError) as e: msg = "Failed to remove project {}:\n\n{}".format(self.project_name, str(e)) - QMessageBox.critical(None, "Remove project", msg, QMessageBox.Close) + QMessageBox.critical(None, "Remove project", msg, QMessageBox.StandardButton.Close) except LoginError as e: login_error_message(e) @@ -697,9 +703,13 @@ def remove_local_project(self): "Do you want to proceed?".format(self.project_name) ) btn_reply = QMessageBox.question( - None, "Remove local project", msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes + None, + "Remove local project", + msg, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes, ) - if btn_reply == QMessageBox.No: + if btn_reply == QMessageBox.StandardButton.No: return if os.path.exists(self.path): @@ -710,9 +720,13 @@ def remove_local_project(self): "Proceed anyway?".format(self.project_name) ) btn_reply = QMessageBox.question( - None, "Remove local project", msg, QMessageBox.No | QMessageBox.No, QMessageBox.Yes + None, + "Remove local project", + msg, + QMessageBox.StandardButton.No | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes, ) - if btn_reply == QMessageBox.No: + if btn_reply == QMessageBox.StandardButton.No: return cur_proj.clear() @@ -739,7 +753,7 @@ def remove_local_project(self): f"Failed to delete your project {self.project_name} because it is open.\n" "You might need to close project or QGIS to remove its files." ) - QMessageBox.critical(None, "Project delete", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project delete", msg, QMessageBox.StandardButton.Close) return settings = QSettings() @@ -761,11 +775,11 @@ def clone_remote_project(self): try: self.mc.clone_project(self.project_name, dlg.project_name, dlg.project_namespace) msg = "Mergin Maps project cloned successfully." - QMessageBox.information(None, "Clone project", msg, QMessageBox.Close) + QMessageBox.information(None, "Clone project", msg, QMessageBox.StandardButton.Close) self.parent().reload() except (URLError, ClientError) as e: msg = "Failed to clone project {}:\n\n{}".format(self.project_name, str(e)) - QMessageBox.critical(None, "Clone project", msg, QMessageBox.Close) + QMessageBox.critical(None, "Clone project", msg, QMessageBox.StandardButton.Close) except LoginError as e: login_error_message(e) diff --git a/Mergin/processing/algs/download_vector_tiles.py b/Mergin/processing/algs/download_vector_tiles.py index df8c4bba..954fef6a 100644 --- a/Mergin/processing/algs/download_vector_tiles.py +++ b/Mergin/processing/algs/download_vector_tiles.py @@ -238,7 +238,7 @@ def processAlgorithm(self, parameters, context, feedback): req = QgsBlockingNetworkRequest() res = req.get(nr, False, feedback) - if res == QgsBlockingNetworkRequest.NoError: + if res == QgsBlockingNetworkRequest.ErrorCode.NoError: data = req.reply().content() comp_obj = zlib.compressobj( diff --git a/Mergin/project_selection_dialog.py b/Mergin/project_selection_dialog.py index 4acd9b0f..eb7989ec 100644 --- a/Mergin/project_selection_dialog.py +++ b/Mergin/project_selection_dialog.py @@ -37,13 +37,13 @@ class SyncStatus(Enum): class ProjectsModel(QStandardItemModel): - PROJECT = Qt.UserRole + 1 - NAME = Qt.UserRole + 2 - NAMESPACE = Qt.UserRole + 3 - NAME_WITH_NAMESPACE = Qt.UserRole + 4 - STATUS = Qt.UserRole + 5 - LOCAL_DIRECTORY = Qt.UserRole + 6 - ICON = Qt.UserRole + 7 + PROJECT = Qt.ItemDataRole.UserRole + 1 + NAME = Qt.ItemDataRole.UserRole + 2 + NAMESPACE = Qt.ItemDataRole.UserRole + 3 + NAME_WITH_NAMESPACE = Qt.ItemDataRole.UserRole + 4 + STATUS = Qt.ItemDataRole.UserRole + 5 + LOCAL_DIRECTORY = Qt.ItemDataRole.UserRole + 6 + ICON = Qt.ItemDataRole.UserRole + 7 def __init__(self, projects=None): super(ProjectsModel, self).__init__() @@ -77,7 +77,7 @@ def createItems(projects): icon = "refresh.svg" name_with_namespace = f"{project['namespace']}/{project['name']}" - item.setData(name_with_namespace, Qt.DisplayRole) + item.setData(name_with_namespace, Qt.ItemDataRole.DisplayRole) item.setData(name_with_namespace, ProjectsModel.NAME_WITH_NAMESPACE) item.setData(project, ProjectsModel.PROJECT) item.setData(project["name"], ProjectsModel.NAME) @@ -144,7 +144,7 @@ def paint(self, painter, option, index): iconRect = iconRect.marginsRemoved(QMargins(12, 12, 12, 12)) painter.save() - if option.state & QStyle.State_Selected: + if option.state & QStyle.StateFlag.State_Selected: painter.fillRect(borderRect, option.palette.highlight()) painter.drawRect(borderRect) painter.setFont(nameFont) @@ -152,12 +152,12 @@ def paint(self, painter, option, index): text = index.data(ProjectsModel.NAME_WITH_NAMESPACE) else: text = index.data(ProjectsModel.NAME) - elided_text = fm.elidedText(text, Qt.ElideRight, nameRect.width()) - painter.drawText(nameRect, Qt.AlignLeading, elided_text) + elided_text = fm.elidedText(text, Qt.TextElideMode.ElideRight, nameRect.width()) + painter.drawText(nameRect, Qt.AlignmentFlag.AlignLeading, elided_text) painter.setFont(option.font) fm = QFontMetrics(QFont(option.font)) - elided_status = fm.elidedText(index.data(ProjectsModel.STATUS), Qt.ElideRight, infoRect.width()) - painter.drawText(infoRect, Qt.AlignLeading, elided_status) + elided_status = fm.elidedText(index.data(ProjectsModel.STATUS), Qt.TextElideModeElideRight, infoRect.width()) + painter.drawText(infoRect, Qt.AlignmentFlag.AlignLeading, elided_status) icon = index.data(ProjectsModel.ICON) if icon: icon = QIcon(icon_path(icon)) diff --git a/Mergin/project_settings_widget.py b/Mergin/project_settings_widget.py index 668b34d9..23ce8b64 100644 --- a/Mergin/project_settings_widget.py +++ b/Mergin/project_settings_widget.py @@ -206,11 +206,11 @@ def check_project(self, state): "to place required files.", ) self.chk_tracking_enabled.blockSignals(True) - self.chk_tracking_enabled.setCheckState(Qt.Unchecked) + self.chk_tracking_enabled.setCheckState(Qt.CheckState.Unchecked) self.chk_tracking_enabled.blockSignals(False) def setup_tracking(self): - if self.chk_tracking_enabled.checkState() == Qt.Unchecked: + if self.chk_tracking_enabled.checkState() == Qt.CheckState.Unchecked: return # check if tracking layer already exists diff --git a/Mergin/project_status_dialog.py b/Mergin/project_status_dialog.py index ea493f5a..608f757c 100644 --- a/Mergin/project_status_dialog.py +++ b/Mergin/project_status_dialog.py @@ -53,7 +53,7 @@ def __init__( self.push_changes = push_changes self.file_to_reset = None - with OverrideCursor(Qt.WaitCursor): + with OverrideCursor(Qt.CursorShape.WaitCursor): QgsGui.instance().enableAutoGeometryRestore(self) self.btn_sync = QPushButton("Sync") @@ -262,9 +262,9 @@ def reset_local_changes(self, file_to_reset=None): None, "Reset changes", text, - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes, ) - if btn_reply != QMessageBox.Yes: + if btn_reply != QMessageBox.StandardButton.Yes: return return self.done(self.RESET_CHANGES) diff --git a/Mergin/projects_manager.py b/Mergin/projects_manager.py index 1c06aa42..a2d0746e 100644 --- a/Mergin/projects_manager.py +++ b/Mergin/projects_manager.py @@ -5,7 +5,7 @@ from qgis.core import QgsProject, Qgis, QgsApplication from qgis.utils import iface -from qgis.PyQt.QtWidgets import QMessageBox, QApplication, QPushButton, QFileDialog +from qgis.PyQt.QtWidgets import QMessageBox, QDialog, QApplication, QPushButton, QFileDialog from qgis.PyQt.QtCore import QSettings, Qt, QTimer from urllib.error import URLError @@ -77,7 +77,7 @@ def fix_pull(): if len(qgis_files) == 0 else "Plugin can only load project with single QGIS project file but {} found.".format(len(qgis_files)) ) - QMessageBox.warning(None, "Load QGIS project", msg, QMessageBox.Close) + QMessageBox.warning(None, "Load QGIS project", msg, QMessageBox.StandardButton.Close) def create_project(self, project_name, project_dir, is_public, namespace): """ @@ -86,7 +86,7 @@ def create_project(self, project_name, project_dir, is_public, namespace): """ full_project_name = "{}/{}".format(namespace, project_name) - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: self.mc.create_project(full_project_name, is_public) except ClientError as e: @@ -117,12 +117,15 @@ def create_project(self, project_name, project_dir, is_public, namespace): if not project_dir: # not going to upload anything so just pop a "success" message and exit QMessageBox.information( - None, "Create Project", "An empty project has been created on the server", QMessageBox.Close + None, + "Create Project", + "An empty project has been created on the server", + QMessageBox.StandardButton.Close, ) return True # get project's metadata from the server and store it locally - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: project_info = self.mc.project_info(full_project_name) MerginProject.write_metadata(project_dir, project_info) @@ -173,7 +176,10 @@ def create_project(self, project_name, project_dir, is_public, namespace): write_project_variables(self.mc.username(), project_name, full_project_name, "v1", server_url) QMessageBox.information( - None, "Create Project", "Mergin Maps project created and uploaded successfully", QMessageBox.Close + None, + "Create Project", + "Mergin Maps project created and uploaded successfully", + QMessageBox.StandardButton.Close, ) return True @@ -190,7 +196,7 @@ def project_status(self, project_dir): project_name = mp.project_full_name() except InvalidProject as e: msg = f"Failed to get project status:\n\n{str(e)}" - QMessageBox.critical(None, "Project status", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project status", msg, QMessageBox.StandardButton.Close) return if not self.check_project_server(project_dir): @@ -210,14 +216,14 @@ def project_status(self, project_dir): # accepted we start sync return_value = dlg.exec() - if return_value == ProjectStatusDialog.Accepted: + if return_value == QDialog.DialogCode.Accepted: self.sync_project(project_dir) elif return_value == ProjectStatusDialog.RESET_CHANGES: self.reset_local_changes(project_dir, dlg.file_to_reset) except (URLError, ClientError, InvalidProject) as e: msg = f"Failed to get status for project {project_name}:\n\n{str(e)}" - QMessageBox.critical(None, "Project status", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project status", msg, QMessageBox.StandardButton.Close) except LoginError as e: login_error_message(e) @@ -267,11 +273,11 @@ def reset_local_changes(self, project_dir: str, files_to_reset=None): msg = f"File {files_to_reset} was successfully reset" else: msg = "Project local changes were successfully reset" - QMessageBox.information(None, "Project reset local changes", msg, QMessageBox.Close) + QMessageBox.information(None, "Project reset local changes", msg, QMessageBox.StandardButton.Close) except Exception as e: msg = f"Failed to reset local changes:\n\n{str(e)}" - QMessageBox.critical(None, "Project reset local changes", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project reset local changes", msg, QMessageBox.StandardButton.Close) self.open_project(os.path.dirname(current_project_filename)) @@ -286,7 +292,7 @@ def sync_project(self, project_dir, project_name=None): project_name = mp.project_full_name() except InvalidProject as e: msg = f"Failed to sync project:\n\n{str(e)}" - QMessageBox.critical(None, "Project syncing", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project syncing", msg, QMessageBox.StandardButton.Close) return if not self.check_project_server(project_dir): return @@ -304,11 +310,13 @@ def sync_project(self, project_dir, project_name=None): pull_changes, push_changes, push_changes_summary = self.mc.project_status(project_dir) except InvalidProject as e: msg = f"Project is invalid:\n\n{str(e)}" - QMessageBox.critical(None, "Project syncing", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project syncing", msg, QMessageBox.StandardButton.Close) return if not sum(len(v) for v in list(pull_changes.values()) + list(push_changes.values())): - QMessageBox.information(None, "Project sync", "Project is already up-to-date", QMessageBox.Close) + QMessageBox.information( + None, "Project sync", "Project is already up-to-date", QMessageBox.StandardButton.Close + ) return dlg = SyncDialog() @@ -349,7 +357,7 @@ def sync_project(self, project_dir, project_name=None): # pull finished, start push if any(push_changes.values()) and not self.mc.has_writing_permissions(project_name): QMessageBox.information( - None, "Project sync", "You have no writing rights to this project", QMessageBox.Close + None, "Project sync", "You have no writing rights to this project", QMessageBox.StandardButton.Close ) return @@ -391,7 +399,7 @@ def sync_project(self, project_dir, project_name=None): if dlg.is_complete: # TODO: report success only when we have actually done anything msg = "Mergin Maps project {} synchronised successfully".format(project_name) - QMessageBox.information(None, "Project sync", msg, QMessageBox.Close) + QMessageBox.information(None, "Project sync", msg, QMessageBox.StandardButton.Close) # clear canvas cache so any changes become immediately visible to users self.iface.mapCanvas().clearCache() self.iface.mapCanvas().refresh() @@ -411,11 +419,13 @@ def submit_logs(self, project_dir): "Please click OK if you want to proceed.".format(logs_path) ) - btn_reply = QMessageBox.question(None, "Submit diagnostic logs", msg, QMessageBox.Ok | QMessageBox.Cancel) - if btn_reply != QMessageBox.Ok: + btn_reply = QMessageBox.question( + None, "Submit diagnostic logs", msg, QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel + ) + if btn_reply != QMessageBox.StandardButton.Ok: return - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) log_file_name, error = send_logs(self.mc.username(), logs_path) QApplication.restoreOverrideCursor() @@ -428,7 +438,7 @@ def submit_logs(self, project_dir): None, "Submit diagnostic logs", "Diagnostic logs successfully submitted - thank you!\n\n{}".format(log_file_name), - QMessageBox.Close, + QMessageBox.StandardButton.Close, ) def get_mergin_browser_groups(self): @@ -470,8 +480,8 @@ def report_conflicts(self, conflicts): msg_box = QMessageBox() msg_box.setWindowTitle("Conflicts found") msg_box.setIcon(QMessageBox.Icon.Warning) - msg_box.setTextFormat(Qt.RichText) - msg_box.setStandardButtons(QMessageBox.Ok) + msg_box.setTextFormat(Qt.TextFormat.RichText) + msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) msg_box.setText(msg) msg_box.exec() @@ -532,7 +542,7 @@ def download_project(self, project): "Failed to download your project {}.\n" "Please make sure your Mergin Maps settings are correct".format(project_name) ) - QMessageBox.critical(None, "Project download", msg, QMessageBox.Close) + QMessageBox.critical(None, "Project download", msg, QMessageBox.StandardButton.Close) elif isinstance(dlg.exception, LoginError): login_error_message(dlg.exception) else: @@ -550,9 +560,13 @@ def download_project(self, project): settings.setValue("Mergin/localProjects/{}/path".format(project_name), target_dir) msg = "Your project {} has been successfully downloaded. Do you want to open project file?".format(project_name) btn_reply = QMessageBox.question( - None, "Project download", msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes + None, + "Project download", + msg, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.Yes, ) - if btn_reply == QMessageBox.Yes: + if btn_reply == QMessageBox.StandardButton.Yes: self.open_project(target_dir) # reload the two browser groups (in case server is old) diff --git a/Mergin/sync_dialog.py b/Mergin/sync_dialog.py index 1747720f..209a3f7c 100644 --- a/Mergin/sync_dialog.py +++ b/Mergin/sync_dialog.py @@ -1,10 +1,10 @@ import os import sys import traceback -from PyQt5.QtWidgets import QDialog, QApplication -from PyQt5 import uic -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtGui import QPixmap +from qgis.PyQt.QtWidgets import QDialog, QApplication +from qgis.PyQt import uic +from qgis.PyQt.QtCore import Qt, QTimer +from qgis.PyQt.QtGui import QPixmap from .utils import ( download_project_async, @@ -107,7 +107,7 @@ def download_start(self, mergin_client, target_dir, project_name): QTimer.singleShot(250, self.download_start_internal) def download_start_internal(self): - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: self.job = download_project_async(self.mergin_client, self.project_name, self.target_dir) @@ -174,7 +174,7 @@ def push_start(self, mergin_client, target_dir, project_name): QTimer.singleShot(250, self.push_start_internal) def push_start_internal(self): - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: self.job = push_project_async(self.mergin_client, self.target_dir) @@ -244,7 +244,7 @@ def pull_start(self, mergin_client, target_dir, project_name): QTimer.singleShot(250, self.pull_start_internal) def pull_start_internal(self): - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) try: self.job = pull_project_async(self.mergin_client, self.target_dir) @@ -304,7 +304,7 @@ def pull_cancel(self): def cancel_sync_operation(self, msg, cancel_func): self.timer.stop() self.labelStatus.setText(msg) - QApplication.setOverrideCursor(Qt.WaitCursor) + QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor) cancel_func(self.job) QApplication.restoreOverrideCursor() self.reset_operation(success=False, close=True) diff --git a/Mergin/utils.py b/Mergin/utils.py index 88f2f07e..1b2bf073 100644 --- a/Mergin/utils.py +++ b/Mergin/utils.py @@ -466,9 +466,12 @@ def unsaved_project_check(): ): msg = "There are some unsaved changes. Do you want save them before continue?" btn_reply = QMessageBox.warning( - None, "Unsaved changes", msg, QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel + None, + "Unsaved changes", + msg, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel, ) - if btn_reply == QMessageBox.Yes: + if btn_reply == QMessageBox.StandardButton.Yes: for layer in QgsProject.instance().mapLayers().values(): if type(layer) is QgsVectorLayer and layer.isModified(): layer.commitChanges() @@ -488,7 +491,7 @@ def unsaved_project_check(): else: return UnsavedChangesStrategy.HasUnsavedChanges return UnsavedChangesStrategy.NoUnsavedChanges - elif btn_reply == QMessageBox.No: + elif btn_reply == QMessageBox.StandardButton.No: return UnsavedChangesStrategy.HasUnsavedChangesButIgnore else: return UnsavedChangesStrategy.HasUnsavedChanges @@ -899,7 +902,7 @@ def write_raster(raster_layer, raster_writer, write_path): def login_error_message(e): QgsApplication.messageLog().logMessage(f"Mergin Maps plugin: {str(e)}") msg = "Security token has been expired, failed to renew. Check your username and password " - QMessageBox.critical(None, "Login failed", msg, QMessageBox.Close) + QMessageBox.critical(None, "Login failed", msg, QMessageBox.StandardButton.Close) def unhandled_exception_message(error_details, dialog_title, error_text, log_file=None, username=None): diff --git a/Mergin/workspace_selection_dialog.py b/Mergin/workspace_selection_dialog.py index f8879a18..017e9a47 100644 --- a/Mergin/workspace_selection_dialog.py +++ b/Mergin/workspace_selection_dialog.py @@ -28,9 +28,9 @@ def rowCount(self, parent=None, *args, **kwargs): def data(self, index, role): workspace = self.workspaces[index.row()] - if role == Qt.UserRole: + if role == Qt.ItemDataRole.UserRole: return workspace - if role == Qt.ToolTipRole: + if role == Qt.ItemDataRole.ToolTipRole: name = workspace["name"] desc = workspace["description"] or "" count = workspace["project_count"] @@ -47,7 +47,7 @@ def sizeHint(self, option, index): return QSize(150, fm.height() * 3 + fm.leading()) def paint(self, painter, option, index): - workspace = index.data(Qt.UserRole) + workspace = index.data(Qt.ItemDataRole.UserRole) description = workspace["description"] if description: description = description.replace("\n", " ") @@ -69,15 +69,15 @@ def paint(self, painter, option, index): borderRect = QRect(option.rect.marginsRemoved(QMargins(4, 4, 4, 4))) painter.save() - if option.state & QStyle.State_Selected: + if option.state & QStyle.StateFlag.State_Selected: painter.fillRect(borderRect, option.palette.highlight()) painter.drawRect(borderRect) painter.setFont(nameFont) - painter.drawText(nameRect, Qt.AlignLeading, workspace["name"]) + painter.drawText(nameRect, Qt.AlignmentFlag.AlignLeading, workspace["name"]) painter.setFont(option.font) fm = QFontMetrics(QFont(option.font)) - elided_description = fm.elidedText(description, Qt.ElideRight, infoRect.width()) - painter.drawText(infoRect, Qt.AlignLeading, elided_description) + elided_description = fm.elidedText(description, Qt.TextElideMode.ElideRight, infoRect.width()) + painter.drawText(infoRect, Qt.AlignmentFlag.AlignLeading, elided_description) painter.restore() @@ -140,5 +140,5 @@ def accept(self): if not index.isValid(): return - self.workspace = self.proxy.data(index, Qt.UserRole) + self.workspace = self.proxy.data(index, Qt.ItemDataRole.UserRole) QDialog.accept(self)