1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
|
"""
@package startup.guiutils
@brief General GUI-dependent utilities for GUI startup of GRASS GIS
(C) 2018 by Vaclav Petras the GRASS Development Team
This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.
@author Vaclav Petras <wenzeslaus gmail com>
@author Linda Kladivova <l.kladivova@seznam.cz>
This is for code which depend on something from GUI (wx or wxGUI).
"""
import os
import wx
from grass.grassdb.checks import (
is_mapset_locked,
get_mapset_lock_info,
is_mapset_name_valid,
is_location_name_valid,
get_mapset_name_invalid_reason,
get_location_name_invalid_reason,
get_reason_mapset_not_removable,
get_reasons_mapsets_not_removable,
get_reasons_location_not_removable,
get_reasons_locations_not_removable,
get_reasons_grassdb_not_removable,
is_fallback_session,
)
import grass.grassdb.config as cfg
from grass.grassdb.create import create_mapset, get_default_mapset_name
from grass.grassdb.manage import (
delete_mapset,
delete_location,
delete_grassdb,
rename_mapset,
rename_location,
)
from grass.script.core import create_environment
from grass.script.utils import try_remove
from grass.script import gisenv
from core.gcmd import GError, GMessage, RunCommand
from gui_core.dialogs import TextEntryDialog
from location_wizard.dialogs import RegionDef
from gui_core.widgets import GenericValidator
class MapsetDialog(TextEntryDialog):
def __init__(
self,
parent=None,
default=None,
message=None,
caption=None,
database=None,
location=None,
):
self.database = database
self.location = location
validator = GenericValidator(
self._isMapsetNameValid, self._showMapsetNameInvalidReason
)
TextEntryDialog.__init__(
self,
parent=parent,
message=message,
caption=caption,
defaultValue=default,
validator=validator,
)
def _showMapsetNameInvalidReason(self, ctrl):
message = get_mapset_name_invalid_reason(
self.database, self.location, ctrl.GetValue()
)
GError(parent=self, message=message, caption=_("Invalid mapset name"))
def _isMapsetNameValid(self, text):
"""Check whether user's input location is valid or not."""
return is_mapset_name_valid(self.database, self.location, text)
class LocationDialog(TextEntryDialog):
def __init__(
self, parent=None, default=None, message=None, caption=None, database=None
):
self.database = database
validator = GenericValidator(
self._isLocationNameValid, self._showLocationNameInvalidReason
)
TextEntryDialog.__init__(
self,
parent=parent,
message=message,
caption=caption,
defaultValue=default,
validator=validator,
)
def _showLocationNameInvalidReason(self, ctrl):
message = get_location_name_invalid_reason(self.database, ctrl.GetValue())
GError(parent=self, message=message, caption=_("Invalid project name"))
def _isLocationNameValid(self, text):
"""Check whether user's input location is valid or not."""
return is_location_name_valid(self.database, text)
def initialize_mapset(grassdb, location, mapset):
"""Initialize mapset (database connection)"""
gisrc_file, env = create_environment(grassdb, location, mapset)
RunCommand("db.connect", flags="c", env=env)
try_remove(gisrc_file)
def create_mapset_interactively(guiparent, grassdb, location):
"""
Create new mapset
"""
dlg = MapsetDialog(
parent=guiparent,
default=get_default_mapset_name(),
message=_("Name for the new mapset:"),
caption=_("Create new mapset"),
database=grassdb,
location=location,
)
mapset = None
if dlg.ShowModal() == wx.ID_OK:
mapset = dlg.GetValue()
try:
create_mapset(grassdb, location, mapset)
initialize_mapset(grassdb, location, mapset)
except OSError as err:
mapset = None
GError(
parent=guiparent,
message=_("Unable to create new mapset: {}").format(err),
showTraceback=False,
)
dlg.Destroy()
return mapset
def create_location_interactively(guiparent, grassdb):
"""
Create new location using Location Wizard.
Returns tuple (database, location, mapset) where mapset is "PERMANENT"
by default or another mapset a user created and may want to switch to.
"""
from location_wizard.wizard import LocationWizard
gWizard = LocationWizard(parent=guiparent, grassdatabase=grassdb)
if gWizard.location is None:
gWizard_output = (None, None, None)
# Returns Nones after Cancel
return gWizard_output
if gWizard.georeffile:
message = _("Do you want to import {} to the newly created project?").format(
gWizard.georeffile
)
dlg = wx.MessageDialog(
parent=guiparent,
message=message,
caption=_("Import data?"),
style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION,
)
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_YES:
gisrc_file, env = create_environment(
gWizard.grassdatabase, gWizard.location, "PERMANENT"
)
import_file(guiparent, gWizard.georeffile, env)
try_remove(gisrc_file)
dlg.Destroy()
if gWizard.default_region:
defineRegion = RegionDef(guiparent, location=gWizard.location)
defineRegion.CenterOnParent()
defineRegion.ShowModal()
defineRegion.Destroy()
if gWizard.user_mapset:
mapset = create_mapset_interactively(
guiparent, gWizard.grassdatabase, gWizard.location
)
# Returns database and location created by user
# and a mapset user may want to switch to
gWizard_output = (gWizard.grassdatabase, gWizard.location, mapset)
else:
# Returns PERMANENT mapset when user mapset not defined
gWizard_output = (gWizard.grassdatabase, gWizard.location, "PERMANENT")
return gWizard_output
def rename_mapset_interactively(guiparent, grassdb, location, mapset):
"""Rename mapset with user interaction.
Exceptions during renaming are handled in get_reason_mapset_not_removable
function.
Returns newmapset if there was a change or None if the mapset cannot be
renamed (see reasons given by get_reason_mapset_not_removable
function) or if another error was encountered.
"""
newmapset = None
# Check selected mapset
message = get_reason_mapset_not_removable(
grassdb, location, mapset, check_permanent=True
)
if message:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Cannot rename mapset <{mapset}> for the following reason:\n\n"
"{reason}\n\n"
"No mapset will be renamed."
).format(mapset=mapset, reason=message),
caption=_("Unable to rename selected mapset"),
style=wx.OK | wx.ICON_WARNING,
)
dlg.ShowModal()
dlg.Destroy()
return newmapset
# Display question dialog
dlg = MapsetDialog(
parent=guiparent,
default=mapset,
message=_("Current name: {}\n\nEnter new name:").format(mapset),
caption=_("Rename selected mapset"),
database=grassdb,
location=location,
)
if dlg.ShowModal() == wx.ID_OK:
newmapset = dlg.GetValue()
try:
rename_mapset(grassdb, location, mapset, newmapset)
except OSError as err:
newmapset = None
wx.MessageBox(
parent=guiparent,
caption=_("Error"),
message=_("Unable to rename mapset.\n\n{}").format(err),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
dlg.Destroy()
return newmapset
def rename_location_interactively(guiparent, grassdb, location):
"""Rename location with user interaction.
Exceptions during renaming are handled in get_reasons_location_not_removable
function.
Returns newlocation if there was a change or None if the location cannot be
renamed (see reasons given by get_reasons_location_not_removable
function) or if another error was encountered.
"""
newlocation = None
# Check selected location
messages = get_reasons_location_not_removable(grassdb, location)
if messages:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Cannot rename project <{location}> for the following reasons:\n\n"
"{reasons}\n\n"
"No project will be renamed."
).format(location=location, reasons="\n".join(messages)),
caption=_("Unable to rename selected project"),
style=wx.OK | wx.ICON_WARNING,
)
dlg.ShowModal()
dlg.Destroy()
return newlocation
# Display question dialog
dlg = LocationDialog(
parent=guiparent,
default=location,
message=_("Current name: {}\n\nEnter new name:").format(location),
caption=_("Rename selected project"),
database=grassdb,
)
if dlg.ShowModal() == wx.ID_OK:
newlocation = dlg.GetValue()
try:
rename_location(grassdb, location, newlocation)
except OSError as err:
newlocation = None
wx.MessageBox(
parent=guiparent,
caption=_("Error"),
message=_("Unable to rename project.\n\n{}").format(err),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
dlg.Destroy()
return newlocation
def download_location_interactively(guiparent, grassdb):
"""
Download new location using Location Wizard.
Returns tuple (database, location, mapset) where mapset is "PERMANENT"
by default or in future it could be the mapset the user may want to
switch to.
"""
from startup.locdownload import LocationDownloadDialog
result = (None, None, None)
loc_download = LocationDownloadDialog(parent=guiparent, database=grassdb)
loc_download.Centre()
loc_download.ShowModal()
if loc_download.GetLocation() is not None:
# Returns database and location created by user
# and a mapset user may want to switch to
result = (grassdb, loc_download.GetLocation(), "PERMANENT")
loc_download.Destroy()
return result
def delete_mapset_interactively(guiparent, grassdb, location, mapset):
"""Delete one mapset with user interaction.
This is currently just a convenience wrapper for delete_mapsets_interactively().
"""
mapsets = [(grassdb, location, mapset)]
return delete_mapsets_interactively(guiparent, mapsets)
def delete_mapsets_interactively(guiparent, mapsets):
"""Delete multiple mapsets with user interaction.
Parameter *mapsets* is a list of tuples (database, location, mapset).
Exceptions during deletation are handled in get_reasons_mapsets_not_removable
function.
Returns True if there was a change, i.e., all mapsets were successfully
deleted or at least one mapset was deleted.
Returns False if one or more mapsets cannot be deleted (see reasons given
by get_reasons_mapsets_not_removable function) or if an error was
encountered when deleting the first mapset in the list.
"""
deletes = []
modified = False
# Check selected mapsets
messages = get_reasons_mapsets_not_removable(mapsets, check_permanent=True)
if messages:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Cannot delete one or more mapsets for the following reasons:\n\n"
"{reasons}\n\n"
"No mapsets will be deleted."
).format(reasons="\n".join(messages)),
caption=_("Unable to delete selected mapsets"),
style=wx.OK | wx.ICON_WARNING,
)
dlg.ShowModal()
dlg.Destroy()
return modified
# No error occurs, create list of mapsets for deleting
for grassdb, location, mapset in mapsets:
mapset_path = os.path.join(grassdb, location, mapset)
deletes.append(mapset_path)
# Display question dialog
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Do you want to continue with deleting"
" one or more of the following mapsets?\n\n"
"{deletes}\n\n"
"All maps included in these mapsets will be permanently deleted!"
).format(deletes="\n".join(deletes)),
caption=_("Delete selected mapsets"),
style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
)
if dlg.ShowModal() == wx.ID_YES:
try:
for grassdb, location, mapset in mapsets:
delete_mapset(grassdb, location, mapset)
modified = True
dlg.Destroy()
return modified
except OSError as error:
wx.MessageBox(
parent=guiparent,
caption=_("Error when deleting mapsets"),
message=_(
"The following error occurred when deleting mapset <{path}>:"
"\n\n{error}\n\n"
"Deleting of mapsets was interrupted."
).format(
path=os.path.join(grassdb, location, mapset),
error=error,
),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
dlg.Destroy()
return modified
def delete_location_interactively(guiparent, grassdb, location):
"""Delete one location with user interaction.
This is currently just a convenience wrapper for delete_locations_interactively().
"""
locations = [(grassdb, location)]
return delete_locations_interactively(guiparent, locations)
def delete_locations_interactively(guiparent, locations):
"""Delete multiple locations with user interaction.
Parameter *locations* is a list of tuples (database, location).
Exceptions during deletation are handled in get_reasons_locations_not_removable
function.
Returns True if there was a change, i.e., all locations were successfully
deleted or at least one location was deleted.
Returns False if one or more locations cannot be deleted (see reasons given
by get_reasons_locations_not_removable function) or if an error was
encountered when deleting the first location in the list.
"""
deletes = []
modified = False
# Check selected locations
messages = get_reasons_locations_not_removable(locations)
if messages:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Cannot delete one or more projects for the following reasons:\n\n"
"{reasons}\n\n"
"No projects will be deleted."
).format(reasons="\n".join(messages)),
caption=_("Unable to delete selected projects"),
style=wx.OK | wx.ICON_WARNING,
)
dlg.ShowModal()
dlg.Destroy()
return modified
# No error occurs, create list of locations for deleting
for grassdb, location in locations:
location_path = os.path.join(grassdb, location)
deletes.append(location_path)
# Display question dialog
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Do you want to continue with deleting"
" one or more of the following projects?\n\n"
"{deletes}\n\n"
"All mapsets included in these projects will be permanently deleted!"
).format(deletes="\n".join(deletes)),
caption=_("Delete selected projects"),
style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
)
if dlg.ShowModal() == wx.ID_YES:
try:
for grassdb, location in locations:
delete_location(grassdb, location)
modified = True
dlg.Destroy()
return modified
except OSError as error:
wx.MessageBox(
parent=guiparent,
caption=_("Error when deleting projects"),
message=_(
"The following error occurred when deleting project <{path}>:"
"\n\n{error}\n\n"
"Deleting of projects was interrupted."
).format(
path=os.path.join(grassdb, location),
error=error,
),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
dlg.Destroy()
return modified
def delete_grassdb_interactively(guiparent, grassdb):
"""
Delete grass database if could be deleted.
If current grass database found, desired operation cannot be performed.
Exceptions during deleting are handled in this function.
Returns True if grass database is deleted from the disk. Returns None if
cannot be deleted (see above the possible reasons).
"""
deleted = False
# Check selected grassdb
messages = get_reasons_grassdb_not_removable(grassdb)
if messages:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Cannot delete GRASS database from disk for the following reason:\n\n"
"{reasons}\n\n"
"GRASS database will not be deleted."
).format(reasons="\n".join(messages)),
caption=_("Unable to delete selected GRASS database"),
style=wx.OK | wx.ICON_WARNING,
)
dlg.ShowModal()
else:
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"Do you want to delete"
" the following GRASS database from disk?\n\n"
"{grassdb}\n\n"
"The directory will be permanently deleted!"
).format(grassdb=grassdb),
caption=_("Delete selected GRASS database"),
style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
)
if dlg.ShowModal() == wx.ID_YES:
try:
delete_grassdb(grassdb)
deleted = True
dlg.Destroy()
return deleted
except OSError as error:
wx.MessageBox(
parent=guiparent,
caption=_("Error when deleting GRASS database"),
message=_(
"The following error occurred when deleting database <{path}>:"
"\n\n{error}\n\n"
"Deleting of GRASS database was interrupted."
).format(
path=grassdb,
error=error,
),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
dlg.Destroy()
return deleted
def can_switch_mapset_interactive(guiparent, grassdb, location, mapset):
"""
Checks if mapset is locked and offers to remove the lock file.
Returns True if user wants to switch to the selected mapset in spite of
removing lock. Returns False if a user wants to stay in the current
mapset or if an error was encountered.
"""
can_switch = True
mapset_path = os.path.join(grassdb, location, mapset)
if is_mapset_locked(mapset_path):
info = get_mapset_lock_info(mapset_path)
user = info["owner"] if info["owner"] else _("unknown")
lockpath = info["lockpath"]
timestamp = info["timestamp"]
dlg = wx.MessageDialog(
parent=guiparent,
message=_(
"User {user} is already running GRASS in selected mapset "
"<{mapset}>\n (file {lockpath} created {timestamp} "
"found).\n\n"
"Concurrent use not allowed.\n\n"
"Do you want to stay in the current mapset or remove "
".gislock and switch to selected mapset?"
).format(user=user, mapset=mapset, lockpath=lockpath, timestamp=timestamp),
caption=_("Mapset is in use"),
style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION,
)
dlg.SetYesNoLabels("S&witch to selected mapset", "S&tay in current mapset")
if dlg.ShowModal() == wx.ID_YES:
# Remove lockfile
try:
os.remove(lockpath)
except OSError as e:
wx.MessageBox(
parent=guiparent,
caption=_("Error when removing lock file"),
message=_(
"Unable to remove {lockpath}.\n\n Details: {error}."
).format(lockpath=lockpath, error=e),
style=wx.OK | wx.ICON_ERROR | wx.CENTRE,
)
can_switch = False
else:
can_switch = False
dlg.Destroy()
return can_switch
def import_file(guiparent, filePath, env):
"""Tries to import file as vector or raster.
If successful sets default region from imported map.
"""
RunCommand("db.connect", flags="c", env=env)
mapName = os.path.splitext(os.path.basename(filePath))[0]
vectors = RunCommand("v.in.ogr", input=filePath, flags="l", read=True, env=env)
wx.BeginBusyCursor()
wx.GetApp().Yield()
if vectors:
# vector detected
returncode, error = RunCommand(
"v.in.ogr", input=filePath, output=mapName, getErrorMsg=True, env=env
)
if returncode == 0:
RunCommand("g.region", flags="s", vector=mapName, env=env)
else:
returncode, error = RunCommand(
"r.in.gdal", input=filePath, output=mapName, getErrorMsg=True, env=env
)
if returncode == 0:
RunCommand("g.region", flags="s", raster=mapName, env=env)
wx.EndBusyCursor()
if returncode != 0:
GError(
parent=guiparent,
message=_("Import of <%(name)s> failed.\n" "Reason: %(msg)s")
% ({"name": filePath, "msg": error}),
)
else:
GMessage(
message=_(
"Data file <%(name)s> imported successfully. "
"The project's default region was set from "
"this imported map."
)
% {"name": filePath},
parent=guiparent,
)
def switch_mapset_interactively(
guiparent, giface, dbase, location, mapset, show_confirmation=False
):
"""Switch current mapset. Emits giface.currentMapsetChanged signal."""
# Decide if a user is in a fallback session
fallback_session = is_fallback_session()
if dbase:
if (
RunCommand(
"g.mapset",
parent=guiparent,
project=location,
mapset=mapset,
dbase=dbase,
)
== 0
):
if show_confirmation:
GMessage(
parent=guiparent,
message=_(
"Current GRASS database is <%(dbase)s>.\n"
"Current project is <%(loc)s>.\n"
"Current mapset is <%(mapset)s>."
)
% {"dbase": dbase, "loc": location, "mapset": mapset},
)
giface.currentMapsetChanged.emit(
dbase=dbase, location=location, mapset=mapset
)
elif location:
if (
RunCommand("g.mapset", parent=guiparent, project=location, mapset=mapset)
== 0
):
if show_confirmation:
GMessage(
parent=guiparent,
message=_(
"Current project is <%(loc)s>.\n"
"Current mapset is <%(mapset)s>."
)
% {"loc": location, "mapset": mapset},
)
giface.currentMapsetChanged.emit(
dbase=None, location=location, mapset=mapset
)
else:
if RunCommand("g.mapset", parent=guiparent, mapset=mapset) == 0:
if show_confirmation:
GMessage(
parent=guiparent, message=_("Current mapset is <%s>.") % mapset
)
giface.currentMapsetChanged.emit(dbase=None, location=None, mapset=mapset)
if fallback_session:
tmp_dbase = os.environ["TMPDIR"]
tmp_loc = cfg.temporary_location
if tmp_dbase != gisenv()["GISDBASE"]:
# Delete temporary location
delete_location(tmp_dbase, tmp_loc)
# Remove useless temporary grassdb node
giface.grassdbChanged.emit(
location=location, grassdb=tmp_dbase, action="delete", element="grassdb"
)
|