|
36 | 36 |
|
37 | 37 | from qgis.PyQt.QtCore import QObject, QSettings, Qt, QTranslator, QFileInfo, QCoreApplication, qVersion |
38 | 38 | from qgis.PyQt.QtGui import QIcon |
39 | | -from qgis.PyQt.QtWidgets import QAction |
40 | | -from qgis.core import Qgis, QgsProject, QgsRasterLayer, QgsVectorLayer, QgsUnitTypes, QgsApplication |
| 39 | +from qgis.PyQt.QtWidgets import QAction, QMessageBox, QFileDialog, QInputDialog |
| 40 | +from qgis.core import Qgis, QgsProject, QgsRasterLayer, QgsVectorLayer, QgsApplication |
41 | 41 | from qgis.analysis import QgsNativeAlgorithms |
42 | 42 | from processing.core.Processing import Processing # type: ignore # @UnusedImport |
43 | 43 |
|
|
46 | 46 | import time |
47 | 47 | import sys |
48 | 48 | import traceback |
| 49 | +from zipfile import ZipFile |
| 50 | +import shutil |
49 | 51 | from typing import Dict, List, Set, Tuple, Optional, Union, Any, TYPE_CHECKING, cast # @UnusedImport |
50 | 52 |
|
51 | 53 | # Import the code for the dialog |
@@ -77,7 +79,7 @@ class QSwat(QObject): |
77 | 79 | """QGIS plugin to prepare geographic data for SWAT Editor.""" |
78 | 80 | _SWATEDITORVERSION = Parameters._SWATEDITORVERSION |
79 | 81 |
|
80 | | - __version__ = '2.0.2' |
| 82 | + __version__ = '2.0.3' |
81 | 83 |
|
82 | 84 | def __init__(self, iface: Any) -> None: |
83 | 85 | """Constructor.""" |
@@ -230,27 +232,114 @@ def about(self) -> None: |
230 | 232 |
|
231 | 233 | def newProject(self) -> None: |
232 | 234 | """Call QGIS actions to create and name a new project.""" |
233 | | - self._iface.actionNewProject().trigger() |
234 | | - # save the project to force user to supply a name and location |
235 | | - self._iface.actionSaveProjectAs().trigger() |
236 | | - self.initButtons() |
237 | | - # allow time for project to be created |
238 | | - time.sleep(2) |
239 | 235 | proj = QgsProject.instance() |
240 | | - projFile = proj.fileName() |
241 | | - if not projFile or projFile == '': |
242 | | - # QSWATUtils.error('No project created', False) |
243 | | - return |
244 | | - if not QFileInfo(projFile).baseName()[0].isalpha(): |
245 | | - QSWATUtils.error('Project name must start with a letter', False) |
246 | | - if os.path.exists(projFile): |
247 | | - os.remove(projFile) |
248 | | - return |
| 236 | + madeCopy = False |
| 237 | + if os.path.isfile(proj.fileName()): |
| 238 | + query = QSWATUtils.question('Do you want to create a new copy of the current project?', False, False) |
| 239 | + if query == QMessageBox.Yes: |
| 240 | + self.copyProject(proj) |
| 241 | + proj = QgsProject.instance() |
| 242 | + madeCopy = True |
| 243 | + if not madeCopy: |
| 244 | + self._iface.actionNewProject().trigger() |
| 245 | + # save the project to force user to supply a name and location |
| 246 | + self._iface.actionSaveProjectAs().trigger() |
| 247 | + self.initButtons() |
| 248 | + # allow time for project to be created |
| 249 | + time.sleep(2) |
| 250 | + proj = QgsProject.instance() |
| 251 | + projFile = proj.fileName() |
| 252 | + if not projFile or projFile == '': |
| 253 | + # QSWATUtils.error('No project created', False) |
| 254 | + return |
| 255 | + if not QFileInfo(projFile).baseName()[0].isalpha(): |
| 256 | + QSWATUtils.error('Project name must start with a letter', False) |
| 257 | + if os.path.exists(projFile): |
| 258 | + os.remove(projFile) |
| 259 | + return |
249 | 260 | self._odlg.raise_() |
250 | 261 | self.setupProject(proj, False) |
251 | 262 | assert self._gv is not None |
252 | 263 | self._gv.writeMasterProgress(0, 0) |
253 | 264 |
|
| 265 | + def copyProject(self, proj: QgsProject): |
| 266 | + title = 'Select directory for copied project. This is the directory where the .qgs file will be stored.' |
| 267 | + projDir, qgsorzFile = os.path.split(proj.fileName()) |
| 268 | + projDir = os.path.abspath(projDir) |
| 269 | + projName = os.path.splitext(qgsorzFile)[0] |
| 270 | + projPath = os.path.join(projDir, projName) |
| 271 | + while True: |
| 272 | + try: |
| 273 | + newProjDir = QFileDialog.getExistingDirectory(None, title, projDir) |
| 274 | + if not newProjDir: |
| 275 | + return '' |
| 276 | + newProjDir = os.path.abspath(newProjDir) |
| 277 | + newProjName, ok = QInputDialog.getText(None, u'QSWAT project name', u'Please enter the new project name, starting with a letter:') |
| 278 | + if not ok: |
| 279 | + return '' |
| 280 | + if not str(newProjName[0]).isalpha(): |
| 281 | + QSWATUtils.error(u'Project name must start with a letter', False) |
| 282 | + continue |
| 283 | + newProjPath = os.path.join(newProjDir, newProjName) |
| 284 | + if projPath == newProjPath: |
| 285 | + QSWATUtils.error('You are trying to copy a project to itself. Please choose a different directory or a different project name.', False) |
| 286 | + continue |
| 287 | + elif os.path.samefile(projDir, newProjDir): |
| 288 | + # same directory but different project names: no problem |
| 289 | + break |
| 290 | + elif newProjDir.startswith(projDir): |
| 291 | + QSWATUtils.error('Current project inside new project: please choose another directory', False) |
| 292 | + continue |
| 293 | + elif projDir.startswith(newProjDir): |
| 294 | + QSWATUtils.error('New project inside current project: please choose another directory', False) |
| 295 | + continue |
| 296 | + else: # ok |
| 297 | + break |
| 298 | + except: |
| 299 | + QSWATUtils.error('Failed to copy project: {0}'.format(traceback.format_exc()), False) |
| 300 | + return |
| 301 | + if os.path.isdir(newProjPath): |
| 302 | + result = QSWATUtils.question('Directory {0} already exists. Do you want to overwrite it?'.format(newProjPath), False, False) |
| 303 | + if result == QMessageBox.No: |
| 304 | + return |
| 305 | + # copy files |
| 306 | + shutil.copytree(projPath, newProjPath, dirs_exist_ok=True) |
| 307 | + # expand .qgz if necessary |
| 308 | + if qgsorzFile.endswith('.qgz'): |
| 309 | + with ZipFile(proj.fileName()) as zf: |
| 310 | + zf.extract(member=projName + '.qgs', path=newProjDir) |
| 311 | + qgsFile = os.path.join(newProjDir, projName + '.qgs') |
| 312 | + qgsExtracted = True |
| 313 | + else: |
| 314 | + qgsFile = proj.fileName() |
| 315 | + qgsExtracted = False |
| 316 | + sameProjName = projName == newProjName |
| 317 | + if not sameProjName: # new and old qgs will be same file if project names the same, and won't need fixing |
| 318 | + # make new .qgs by editing old one, changing project name |
| 319 | + newQgsFile = os.path.join(newProjDir, newProjName + '.qgs') |
| 320 | + with open(qgsFile, 'r') as inqgs, open(newQgsFile, 'w', newline='') as outqgs: |
| 321 | + for line in inqgs: |
| 322 | + line = line.replace('projectname="{0}"'.format(projName), 'projectname="{0}"'.format(newProjName)) |
| 323 | + line = line.replace('<title>{0}</title>'.format(projName), '<title>{0}</title>'.format(newProjName)) |
| 324 | + line = line.replace(' <{0}>'.format(projName), ' <{0}>'.format(newProjName)) |
| 325 | + line = line.replace(' </{0}>'.format(projName), ' </{0}>'.format(newProjName)) |
| 326 | + line = line.replace('./{0}/'.format(projName), './{0}/'.format(newProjName)) |
| 327 | + outqgs.write(line) |
| 328 | + # clean up |
| 329 | + if qgsExtracted: |
| 330 | + os.remove(qgsFile) |
| 331 | + # rename project database |
| 332 | + mdb2 = os.path.join(newProjPath, projName) + '.mdb' |
| 333 | + newMdb = os.path.join(newProjPath, newProjName) + '.mdb' |
| 334 | + # Cannot rename to existing file |
| 335 | + if os.path.isfile(newMdb): |
| 336 | + os.remove(newMdb) |
| 337 | + os.rename(mdb2, newMdb) |
| 338 | + proj.clear() |
| 339 | + proj.read(newQgsFile) |
| 340 | + |
| 341 | + |
| 342 | + |
254 | 343 | def existingProject(self) -> None: |
255 | 344 | """Open an existing QGIS project.""" |
256 | 345 | self._iface.actionOpenProject().trigger() |
@@ -385,6 +474,16 @@ def setupProject(self, proj, isBatch, isHUC=False, isHAWQS=False, useSQLite=Fals |
385 | 474 | QSWATUtils.getLayerByFilename(root.findLayers(), pointSourcesFile, FileTypes._EXTRAPTSRC, self._gv, None, QSWATUtils._WATERSHED_GROUP_NAME) |
386 | 475 | canvas = self._iface.mapCanvas() |
387 | 476 | canvas.zoomToFullExtent() |
| 477 | + # if running existing watershed and inlets/outlets file is 'points.shp' then make editor available |
| 478 | + if self._gv.existingWshed: |
| 479 | + useOutlets, found = proj.readBoolEntry(self._gv.attTitle, 'delin/useOutlets', True) |
| 480 | + if found and useOutlets: |
| 481 | + outletFile, found = proj.readEntry(self._gv.attTitle, 'delin/outlets', '') |
| 482 | + if found and outletFile != '': |
| 483 | + outletFile = QSWATUtils.join(self._gv.projDir, outletFile) |
| 484 | + if os.path.split(outletFile)[1] == 'points.shp': |
| 485 | + self._odlg.editLabel.setEnabled(True) |
| 486 | + self._odlg.editButton.setEnabled(True) |
388 | 487 | self._odlg.projPath.setText(self._gv.projDir) |
389 | 488 | self._odlg.mainBox.setEnabled(True) |
390 | 489 | self._odlg.setCursor(Qt.ArrowCursor) |
|
0 commit comments