1515import shutil
1616import sys
1717import sqlite3
18- import webbrowser
1918import xml .etree .ElementTree as ET
2019
2120from typing import Literal , Dict , Optional , Union , Any , List , Tuple
2726from datetime import datetime
2827
2928from qgis .PyQt .QtCore import Qt , QStringListModel , QVariant , QDate , QRegularExpression , \
30- QItemSelectionModel , QTimer , QSettings
29+ QItemSelectionModel , QTimer , QSettings , QUrl
3130from qgis .PyQt .QtGui import QCursor , QPixmap , QColor , QStandardItemModel , QIcon , QStandardItem , \
32- QIntValidator , QDoubleValidator , QRegularExpressionValidator , QPalette , QFont
31+ QIntValidator , QDoubleValidator , QRegularExpressionValidator , QPalette , QFont , QDesktopServices
3332from qgis .PyQt .QtSql import QSqlTableModel
3433from qgis .PyQt .QtWidgets import QSpacerItem , QSizePolicy , QLineEdit , QLabel , QComboBox , QGridLayout , QTabWidget , \
3534 QCompleter , QPushButton , QTableView , QFrame , QCheckBox , QDoubleSpinBox , QSpinBox , QDateEdit , QTextEdit , \
@@ -606,11 +605,30 @@ def add_btn_help(dlg):
606605 btn_help .clicked .connect (partial (open_help_link , context , uiname , dlg ))
607606
608607
608+ def _open_external_url (url ):
609+ """Open a URL via Qt instead of Python webbrowser/subprocess.
610+
611+ webbrowser.open() leaves a live Popen; on Python 3.12+ its __del__ emits
612+ ResourceWarning, and QGIS logging that from a destructor can abort the process.
613+ """
614+ if not url :
615+ return False
616+ try :
617+ return QDesktopServices .openUrl (QUrl (str (url )))
618+ except Exception as e :
619+ msg = "Could not open URL: {0}"
620+ msg_params = (e ,)
621+ tools_log .log_warning (msg , msg_params = msg_params )
622+ return False
623+
624+
609625def open_help_link (context , uiname , dlg = None ):
610626 """ Opens the help link for the given dialog, or a default link if not found. """
611627
612- # Base URL for the documentation
613- domain = get_config_value ('help_domain' , table = 'config_param_system' )
628+ # Skip the DB lookup when no schema is loaded (admin / create project).
629+ domain = None
630+ if lib_vars .schema_name :
631+ domain = get_config_value ('help_domain' , table = 'config_param_system' )
614632 if domain is None :
615633 domain = "https://docs.giswater.org"
616634 else :
@@ -621,21 +639,24 @@ def open_help_link(context, uiname, dlg=None):
621639 # Always use 'latest' to avoid errors when plugin version is diferent than docs
622640 base_url = f"{ domain } /latest/{ language } /docs/giswater/for-users"
623641
624- uiname = uiname .replace ("_" , "-" ).replace (" " , "-" ).lower () + ".html" # sanitize uiname
642+ if uiname :
643+ uiname = uiname .replace ("_" , "-" ).replace (" " , "-" ).lower () + ".html"
625644
626645 tabname = 'tab_none'
627- tab_widgets = dlg .findChildren (QTabWidget )
628- if tab_widgets :
629- tab_widget = tab_widgets [0 ]
630- index_tab = tab_widget .currentIndex ()
631- tabname = tab_widget .widget (index_tab ).objectName ()
646+ if dlg is not None :
647+ tab_widgets = dlg .findChildren (QTabWidget )
648+ if tab_widgets :
649+ tab_widget = tab_widgets [0 ]
650+ index_tab = tab_widget .currentIndex ()
651+ tab = tab_widget .widget (index_tab )
652+ if tab is not None and tab .objectName ():
653+ tabname = tab .objectName ()
632654
633655 # Construct the path dynamically
634656 if uiname :
635657 if uiname == 'info-feature.html' :
636658 feature = dlg .windowTitle ().split (' ' )[0 ]
637659 sql = f"SELECT feature_type FROM { lib_vars .schema_name } .cat_feature WHERE id = '{ feature } '"
638- print (sql )
639660 feature_type = tools_db .get_rows (sql )[0 ]['feature_type' ]
640661 if tabname .lower () == 'tab_data' :
641662 file_path = f"{ base_url } /dialogs/info_feature/{ global_vars .project_type .lower ()} /{ feature_type .lower ()} /{ feature .lower ()} /tab_data.html"
@@ -651,8 +672,7 @@ def open_help_link(context, uiname, dlg=None):
651672 # Fallback to the general manual link if context and uiname are missing
652673 file_path = f"{ base_url } /index.html"
653674
654- print (file_path )
655- tools_os .open_file (file_path )
675+ _open_external_url (file_path )
656676
657677
658678def open_dialog (dlg , dlg_name = None , stay_on_top = False , title = None , title_params = None , hide_config_widgets = False , plugin_dir = lib_vars .plugin_dir , plugin_name = lib_vars .plugin_name , skip_db_check = False ):
@@ -7566,15 +7586,15 @@ def open_dlg_help():
75667586 parser = configparser .ConfigParser (comment_prefixes = ";" , allow_no_value = True , strict = False )
75677587 path = f"{ lib_vars .plugin_dir } { os .sep } config{ os .sep } giswater.config"
75687588 if not os .path .exists (path ):
7569- webbrowser . open_new_tab ('https://giswater.gitbook.io/giswater-manual' )
7589+ _open_external_url ('https://giswater.gitbook.io/giswater-manual' )
75707590 return True
75717591
75727592 try :
75737593 parser .read (path )
75747594 web_tag = parser .get ('web_tag' , lib_vars .session_vars ['last_focus' ])
7575- webbrowser . open_new_tab (f'https://giswater.gitbook.io/giswater-manual/{ web_tag } ' )
7595+ _open_external_url (f'https://giswater.gitbook.io/giswater-manual/{ web_tag } ' )
75767596 except Exception :
7577- webbrowser . open_new_tab ('https://giswater.gitbook.io/giswater-manual' )
7597+ _open_external_url ('https://giswater.gitbook.io/giswater-manual' )
75787598 finally :
75797599 return True
75807600
0 commit comments