From d54816df197644071efc1219f144681f60ca15e6 Mon Sep 17 00:00:00 2001 From: Kelly W Date: Wed, 4 Dec 2024 16:41:22 -0800 Subject: [PATCH] Updates for project export path #537 and #538 --- src/view/frm_export_project.py | 68 +++++++++++++++++++++++++--------- src/view/frm_settings.py | 34 +++++++++++++++++ 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/view/frm_export_project.py b/src/view/frm_export_project.py index 85b22e0b..1aeef914 100644 --- a/src/view/frm_export_project.py +++ b/src/view/frm_export_project.py @@ -9,6 +9,7 @@ from PyQt5.QtWidgets import QMessageBox from qgis.core import QgsVectorLayer, QgsMessageLog from qgis.utils import iface +from qgis.PyQt.QtCore import QSettings from rsxml.project_xml import Project, MetaData, Meta, ProjectBounds, Coords, BoundingBox, Realization, Geopackage, GeopackageLayer, GeoPackageDatasetTypes, Dataset @@ -30,8 +31,12 @@ from .utilities import add_standard_form_buttons, message_box +# Puting these here for now to avoid circular imports - source: qris_toolbar.py +ORGANIZATION = 'Riverscapes' +APPNAME = 'QRiS' PROJECT_MACHINE_NAME = 'RiverscapesStudio' +DEFAULT_EXPORT_PATH = 'default_export_path' class FrmExportProject(QtWidgets.QDialog): @@ -48,7 +53,11 @@ def __init__(self, parent, project: QRiSProject, outpath: str = None): self.setupUi() if outpath is not None: - self.set_output_path(outpath) + self.base_folder = outpath + else: + settings = QSettings(ORGANIZATION, APPNAME) + self.base_folder = settings.value(DEFAULT_EXPORT_PATH, '').replace("/", "\\") + self.set_output_path() # populate the AOI combo box with aoi names for aoi_id, aoi in self.qris_project.masks.items(): @@ -232,13 +241,34 @@ def update_check_state(self, item: QtGui.QStandardItem): self.export_layers_model.itemChanged.connect(self.handle_item_changed) self.update_check_state(item.parent()) - def set_output_path(self, outpath: str): + def set_output_path(self): + + if self.base_folder == "": + return + + name = "" - # outpath = parse_posix_path(os.path.join(self.basepath, project_name.replace(" ", "_"))) + if self.chk_apply_name_to_output.isChecked(): + name = self.txt_rs_name.text().replace(" ", "_").replace(".", "_").replace(",", "_").replace(";", "_").replace(":", "_").replace("/", "_").replace("\\", "_").replace("?", "_").replace("!", "_").replace("'", "_").replace('"', "_").replace("<", "_").replace(">", "_").replace("|", "_").replace("*", "_").replace("(", "_").replace(")", "_").replace("[", "_").replace("]", "_").replace("{", "_").replace("}", "_") + + outpath = os.path.join(self.base_folder, name) self.txt_outpath.setText(outpath) def accept(self) -> None: + if self.txt_rs_name.text() == "": + message_box("Project Name", "Please enter a name for the Riverscapes project.") + return + + if self.txt_outpath.text() == "": + message_box("Output Path", "Please select an output path for the Riverscapes project.") + return + + # make sure the start of the output path is a valid drive letter + if not os.path.exists(self.txt_outpath.text()[0:2]): + message_box("Invalid Output Path", "The output path is invalid. Please select a valid output path.") + return + # check if output directory is empty. If so, prompt user to overwrite or cancel if os.path.exists(self.txt_outpath.text()): if len(os.listdir(self.txt_outpath.text())) > 0: @@ -275,7 +305,7 @@ def accept(self) -> None: # create a new project folder if it doesn't exist if not os.path.exists(self.txt_outpath.text()): - os.mkdir(self.txt_outpath.text()) + os.makedirs(self.txt_outpath.text()) # copy the geopackage layers to the new project folder out_name = 'qris.gpkg' # os.path.split(self.qris_project.project_file)[1] @@ -1011,8 +1041,8 @@ def browse_path(self): ret = msg.exec_() if ret == QtWidgets.QMessageBox.Cancel: return - - self.set_output_path(path) + self.base_folder = path.replace("/", "\\") + self.set_output_path() def change_project_bounds(self): @@ -1067,16 +1097,20 @@ def setupUi(self): self.txt_rs_name.textChanged.connect(self.set_output_path) self.grid.addWidget(self.txt_rs_name, 0, 1, 1, 1) + self.chk_apply_name_to_output = QtWidgets.QCheckBox("Use project name as output folder") + self.chk_apply_name_to_output.setChecked(True) + self.chk_apply_name_to_output.clicked.connect(self.set_output_path) + self.grid.addWidget(self.chk_apply_name_to_output, 1, 1, 1, 1) + # add label and horizontal layout with textbox and small button for output path self.lbl_output = QtWidgets.QLabel("Output Path") self.lbl_output.setToolTip("Select the folder where the Riverscapes project will be saved") - self.grid.addWidget(self.lbl_output, 1, 0, 1, 1) + self.grid.addWidget(self.lbl_output, 2, 0, 1, 1) self.horiz_output = QtWidgets.QHBoxLayout() - self.grid.addLayout(self.horiz_output, 1, 1, 1, 1) + self.grid.addLayout(self.horiz_output, 2, 1, 1, 1) self.txt_outpath = QtWidgets.QLineEdit() - self.txt_outpath.setReadOnly(True) self.horiz_output.addWidget(self.txt_outpath) self.btn_output = QtWidgets.QPushButton("...") @@ -1087,10 +1121,10 @@ def setupUi(self): # Project Bounds self.lbl_project_bounds = QtWidgets.QLabel("Project Bounds") self.lbl_project_bounds.setToolTip("Select the extent of the project. This is used for display purposes on the Riverscapes Data Exchange") - self.grid.addWidget(self.lbl_project_bounds, 2, 0, 1, 1, QtCore.Qt.AlignTop) + self.grid.addWidget(self.lbl_project_bounds, 3, 0, 1, 1, QtCore.Qt.AlignTop) self.vert_project_bounds = QtWidgets.QVBoxLayout() - self.grid.addLayout(self.vert_project_bounds, 2, 1, 1, 1) + self.grid.addLayout(self.vert_project_bounds, 3, 1, 1, 1) self.opt_project_bounds_all = QtWidgets.QRadioButton("Use all QRiS layers") self.opt_project_bounds_all.setToolTip("Use the extent of all QRiS layers in the project") @@ -1114,7 +1148,7 @@ def setupUi(self): # New or Existing Project self.lbl_new_or_existing = QtWidgets.QLabel("Export Type") self.lbl_new_or_existing.setToolTip("Select whether to create a new Riverscapes Studio project or update an existing project") - self.grid.addWidget(self.lbl_new_or_existing, 3, 0, 1, 1) + self.grid.addWidget(self.lbl_new_or_existing, 4, 0, 1, 1) self.group_new_or_existing = QtWidgets.QButtonGroup(self) @@ -1123,16 +1157,16 @@ def setupUi(self): self.group_new_or_existing.addButton(self.rdo_new) self.rdo_new.setChecked(True) self.rdo_new.clicked.connect(self.change_new_or_existing) - self.grid.addWidget(self.rdo_new, 3, 1, 1, 1) + self.grid.addWidget(self.rdo_new, 4, 1, 1, 1) self.rdo_existing = QtWidgets.QRadioButton("Update Existing Riverscapes Studio Project") self.rdo_existing.setToolTip("Update a previously uploaded Riverscapes Studio project") self.group_new_or_existing.addButton(self.rdo_existing) self.rdo_existing.clicked.connect(self.change_new_or_existing) - self.grid.addWidget(self.rdo_existing, 4, 1, 1, 1) + self.grid.addWidget(self.rdo_existing, 5, 1, 1, 1) self.horiz_existing = QtWidgets.QHBoxLayout() - self.grid.addLayout(self.horiz_existing, 5, 1, 1, 1) + self.grid.addLayout(self.horiz_existing, 6, 1, 1, 1) self.lbl_existing = QtWidgets.QLabel("Existing Project rs.xml file") self.lbl_existing.setEnabled(False) @@ -1151,12 +1185,12 @@ def setupUi(self): # add multiline box for description self.lbl_description = QtWidgets.QLabel("Description") - self.grid.addWidget(self.lbl_description, 6, 0, 1, 1, QtCore.Qt.AlignTop) + self.grid.addWidget(self.lbl_description, 7, 0, 1, 1, QtCore.Qt.AlignTop) self.txt_description = QtWidgets.QTextEdit() self.txt_description.setReadOnly(False) self.txt_description.setText(self.qris_project.description) - self.grid.addWidget(self.txt_description, 6, 1, 1, 1) + self.grid.addWidget(self.txt_description, 7, 1, 1, 1) # add vertical spacer self.vert.addStretch() diff --git a/src/view/frm_settings.py b/src/view/frm_settings.py index 273d814b..c32328e1 100644 --- a/src/view/frm_settings.py +++ b/src/view/frm_settings.py @@ -8,6 +8,8 @@ from ..model.project import Project from ..model.metric import METRIC_SCHEMA, insert_metric +from .frm_export_project import DEFAULT_EXPORT_PATH + DOCK_WIDGET_LOCATION = 'dock_widget_location' REMOVE_LAYERS_ON_CLOSE = 'remove_layers_on_close' @@ -41,6 +43,10 @@ def __init__(self, settings: QSettings, qris_project: Project): else: self.chk_remove_layers_on_close.setChecked(False) + # Get the default export path + default_export_path = settings.value(DEFAULT_EXPORT_PATH, '') + self.txt_path_export.setText(default_export_path) + self.load_metrics() def accept(self): @@ -55,6 +61,11 @@ def accept(self): else: self.settings.setValue(REMOVE_LAYERS_ON_CLOSE, False) + if self.txt_path_export.text() != '': + self.settings.setValue(DEFAULT_EXPORT_PATH, self.txt_path_export.text()) + else: + self.settings.setValue(DEFAULT_EXPORT_PATH, '') + super().accept() def load_metrics(self): @@ -248,6 +259,11 @@ def save_metrics(self): else: QMessageBox.information(self, "Save Metrics", "No new metrics to save.") + def select_export_path(self): + path = QFileDialog.getExistingDirectory(self, "Select default export path") + if path: + self.txt_path_export.setText(path) + def setup_ui(self): self.resize(500, 300) @@ -260,6 +276,24 @@ def setup_ui(self): self.vertGeneral = QVBoxLayout() + horiz_export_path = QHBoxLayout() + self.vertGeneral.addLayout(horiz_export_path) + + lbl_path_export = QLabel("Default Export Path") + horiz_export_path.addWidget(lbl_path_export) + + self.txt_path_export = QLineEdit() + self.txt_path_export.setReadOnly(True) + horiz_export_path.addWidget(self.txt_path_export) + + btn_path_export = QPushButton("...") + btn_path_export.clicked.connect(self.select_export_path) + horiz_export_path.addWidget(btn_path_export) + + btn_clear_path_export = QPushButton("Clear") + btn_clear_path_export.clicked.connect(lambda: self.txt_path_export.setText('')) + horiz_export_path.addWidget(btn_clear_path_export) + self.chk_remove_layers_on_close = QCheckBox("Remove QRiS Project map layers on project close") self.chk_remove_layers_on_close.setToolTip("Check this box to remove all layers from the project when it is closed.") self.vertGeneral.addWidget(self.chk_remove_layers_on_close)