1""" 2@package gis_set 3 4GRASS start-up screen. 5 6Initialization module for wxPython GRASS GUI. 7Location/mapset management (selection, creation, etc.). 8 9Classes: 10 - gis_set::GRASSStartup 11 - gis_set::GListBox 12 - gis_set::StartUp 13 14(C) 2006-2014 by the GRASS Development Team 15 16This program is free software under the GNU General Public License 17(>=v2). Read the file COPYING that comes with GRASS for details. 18 19@author Michael Barton and Jachym Cepicky (original author) 20@author Martin Landa <landa.martin gmail.com> (various updates) 21""" 22 23import os 24import sys 25import copy 26import platform 27import codecs 28import getpass 29 30# i18n is taken care of in the grass library code. 31# So we need to import it before any of the GUI code. 32from grass.script import core as grass 33 34from core import globalvar 35import wx 36# import adv and html before wx.App is created, otherwise 37# we get annoying "Debug: Adding duplicate image handler for 'Windows bitmap file'" 38# during download location dialog start up, remove when not needed 39import wx.adv 40import wx.html 41import wx.lib.mixins.listctrl as listmix 42 43from core.gcmd import GMessage, GError, DecodeString, RunCommand 44from core.utils import GetListOfLocations, GetListOfMapsets 45from startup.utils import ( 46 get_lockfile_if_present, get_possible_database_path, create_mapset) 47import startup.utils as sutils 48from startup.guiutils import SetSessionMapset, NewMapsetDialog 49import startup.guiutils as sgui 50from location_wizard.dialogs import RegionDef 51from gui_core.dialogs import TextEntryDialog 52from gui_core.widgets import GenericValidator, StaticWrapText 53from gui_core.wrap import Button, ListCtrl, StaticText, StaticBox, \ 54 TextCtrl, BitmapFromImage 55 56 57class GRASSStartup(wx.Frame): 58 exit_success = 0 59 # 2 is file not found from python interpreter 60 exit_user_requested = 5 61 62 """GRASS start-up screen""" 63 64 def __init__(self, parent=None, id=wx.ID_ANY, 65 style=wx.DEFAULT_FRAME_STYLE): 66 67 # 68 # GRASS variables 69 # 70 self.gisbase = os.getenv("GISBASE") 71 self.grassrc = sgui.read_gisrc() 72 self.gisdbase = self.GetRCValue("GISDBASE") 73 74 # 75 # list of locations/mapsets 76 # 77 self.listOfLocations = [] 78 self.listOfMapsets = [] 79 self.listOfMapsetsSelectable = [] 80 81 wx.Frame.__init__(self, parent=parent, id=id, style=style) 82 83 self.locale = wx.Locale(language=wx.LANGUAGE_DEFAULT) 84 85 # scroll panel was used here but not properly and is probably not need 86 # as long as it is not high too much 87 self.panel = wx.Panel(parent=self, id=wx.ID_ANY) 88 89 # i18N 90 91 # 92 # graphical elements 93 # 94 # image 95 try: 96 if os.getenv('ISISROOT'): 97 name = os.path.join( 98 globalvar.GUIDIR, 99 "images", 100 "startup_banner_isis.png") 101 else: 102 name = os.path.join( 103 globalvar.GUIDIR, "images", "startup_banner.png") 104 self.hbitmap = wx.StaticBitmap(self.panel, wx.ID_ANY, 105 wx.Bitmap(name=name, 106 type=wx.BITMAP_TYPE_PNG)) 107 except: 108 self.hbitmap = wx.StaticBitmap( 109 self.panel, wx.ID_ANY, BitmapFromImage( 110 wx.EmptyImage(530, 150))) 111 112 # labels 113 # crashes when LOCATION doesn't exist 114 # get version & revision 115 grassVersion, grassRevisionStr = sgui.GetVersion() 116 117 self.gisdbase_box = StaticBox( 118 parent=self.panel, id=wx.ID_ANY, label=" %s " % 119 _("1. Select GRASS GIS database directory")) 120 self.location_box = StaticBox( 121 parent=self.panel, id=wx.ID_ANY, label=" %s " % 122 _("2. Select GRASS Location")) 123 self.mapset_box = StaticBox( 124 parent=self.panel, id=wx.ID_ANY, label=" %s " % 125 _("3. Select GRASS Mapset")) 126 127 self.lmessage = StaticWrapText(parent=self.panel) 128 # It is not clear if all wx versions supports color, so try-except. 129 # The color itself may not be correct for all platforms/system settings 130 # but in http://xoomer.virgilio.it/infinity77/wxPython/Widgets/wx.SystemSettings.html 131 # there is no 'warning' color. 132 try: 133 self.lmessage.SetForegroundColour(wx.Colour(255, 0, 0)) 134 except AttributeError: 135 pass 136 137 self.gisdbase_panel = wx.Panel(parent=self.panel) 138 self.location_panel = wx.Panel(parent=self.panel) 139 self.mapset_panel = wx.Panel(parent=self.panel) 140 141 self.ldbase = StaticText( 142 parent=self.gisdbase_panel, id=wx.ID_ANY, 143 label=_("GRASS GIS database directory contains Locations.")) 144 145 self.llocation = StaticWrapText( 146 parent=self.location_panel, id=wx.ID_ANY, 147 label=_("All data in one Location is in the same " 148 " coordinate reference system (projection)." 149 " One Location can be one project." 150 " Location contains Mapsets."), 151 style=wx.ALIGN_LEFT) 152 153 self.lmapset = StaticWrapText( 154 parent=self.mapset_panel, id=wx.ID_ANY, 155 label=_("Mapset contains GIS data related" 156 " to one project, task within one project," 157 " subregion or user."), 158 style=wx.ALIGN_LEFT) 159 160 try: 161 for label in [self.ldbase, self.llocation, self.lmapset]: 162 label.SetForegroundColour( 163 wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) 164 except AttributeError: 165 # for explanation of try-except see above 166 pass 167 168 # buttons 169 self.bstart = Button(parent=self.panel, id=wx.ID_ANY, 170 label=_("Start &GRASS session")) 171 self.bstart.SetDefault() 172 self.bexit = Button(parent=self.panel, id=wx.ID_EXIT) 173 self.bstart.SetMinSize((180, self.bexit.GetSize()[1])) 174 self.bhelp = Button(parent=self.panel, id=wx.ID_HELP) 175 self.bbrowse = Button(parent=self.gisdbase_panel, id=wx.ID_ANY, 176 label=_("&Browse")) 177 self.bmapset = Button(parent=self.mapset_panel, id=wx.ID_ANY, 178 # GTC New mapset 179 label=_("&New")) 180 self.bmapset.SetToolTip(_("Create a new Mapset in selected Location")) 181 self.bwizard = Button(parent=self.location_panel, id=wx.ID_ANY, 182 # GTC New location 183 label=_("N&ew")) 184 self.bwizard.SetToolTip( 185 _( 186 "Create a new location using location wizard." 187 " After location is created successfully," 188 " GRASS session is started.")) 189 self.rename_location_button = Button(parent=self.location_panel, id=wx.ID_ANY, 190 # GTC Rename location 191 label=_("Ren&ame")) 192 self.rename_location_button.SetToolTip(_("Rename selected location")) 193 self.delete_location_button = Button(parent=self.location_panel, id=wx.ID_ANY, 194 # GTC Delete location 195 label=_("De&lete")) 196 self.delete_location_button.SetToolTip(_("Delete selected location")) 197 self.download_location_button = Button(parent=self.location_panel, id=wx.ID_ANY, 198 label=_("Do&wnload")) 199 self.download_location_button.SetToolTip(_("Download sample location")) 200 201 self.rename_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY, 202 # GTC Rename mapset 203 label=_("&Rename")) 204 self.rename_mapset_button.SetToolTip(_("Rename selected mapset")) 205 self.delete_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY, 206 # GTC Delete mapset 207 label=_("&Delete")) 208 self.delete_mapset_button.SetToolTip(_("Delete selected mapset")) 209 210 # textinputs 211 self.tgisdbase = TextCtrl( 212 parent=self.gisdbase_panel, id=wx.ID_ANY, value="", size=( 213 300, -1), style=wx.TE_PROCESS_ENTER) 214 215 # Locations 216 self.lblocations = GListBox(parent=self.location_panel, 217 id=wx.ID_ANY, size=(180, 200), 218 choices=self.listOfLocations) 219 self.lblocations.SetColumnWidth(0, 180) 220 221 # TODO: sort; but keep PERMANENT on top of list 222 # Mapsets 223 self.lbmapsets = GListBox(parent=self.mapset_panel, 224 id=wx.ID_ANY, size=(180, 200), 225 choices=self.listOfMapsets) 226 self.lbmapsets.SetColumnWidth(0, 180) 227 228 # layout & properties, first do layout so everything is created 229 self._do_layout() 230 self._set_properties(grassVersion, grassRevisionStr) 231 232 # events 233 self.bbrowse.Bind(wx.EVT_BUTTON, self.OnBrowse) 234 self.bstart.Bind(wx.EVT_BUTTON, self.OnStart) 235 self.bexit.Bind(wx.EVT_BUTTON, self.OnExit) 236 self.bhelp.Bind(wx.EVT_BUTTON, self.OnHelp) 237 self.bmapset.Bind(wx.EVT_BUTTON, self.OnCreateMapset) 238 self.bwizard.Bind(wx.EVT_BUTTON, self.OnWizard) 239 240 self.rename_location_button.Bind(wx.EVT_BUTTON, self.RenameLocation) 241 self.delete_location_button.Bind(wx.EVT_BUTTON, self.DeleteLocation) 242 self.download_location_button.Bind(wx.EVT_BUTTON, self.DownloadLocation) 243 self.rename_mapset_button.Bind(wx.EVT_BUTTON, self.RenameMapset) 244 self.delete_mapset_button.Bind(wx.EVT_BUTTON, self.DeleteMapset) 245 246 self.lblocations.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectLocation) 247 self.lbmapsets.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectMapset) 248 self.lbmapsets.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnStart) 249 self.tgisdbase.Bind(wx.EVT_TEXT_ENTER, self.OnSetDatabase) 250 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 251 252 def _set_properties(self, version, revision): 253 """Set frame properties 254 255 :param version: Version in the form of X.Y.Z 256 :param revision: Version control revision with leading space 257 258 *revision* should be an empty string in case of release and 259 otherwise it needs a leading space to be separated from the rest 260 of the title. 261 """ 262 self.SetTitle(_("GRASS GIS %s Startup%s") % (version, revision)) 263 self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, "grass.ico"), 264 wx.BITMAP_TYPE_ICO)) 265 266 self.bstart.SetToolTip(_("Enter GRASS session")) 267 self.bstart.Enable(False) 268 self.bmapset.Enable(False) 269 # this all was originally a choice, perhaps just mapset needed 270 self.rename_location_button.Enable(False) 271 self.delete_location_button.Enable(False) 272 self.rename_mapset_button.Enable(False) 273 self.delete_mapset_button.Enable(False) 274 275 # set database 276 if not self.gisdbase: 277 # sets an initial path for gisdbase if nothing in GISRC 278 if os.path.isdir(os.getenv("HOME")): 279 self.gisdbase = os.getenv("HOME") 280 else: 281 self.gisdbase = os.getcwd() 282 try: 283 self.tgisdbase.SetValue(self.gisdbase) 284 except UnicodeDecodeError: 285 wx.MessageBox(parent=self, caption=_("Error"), 286 message=_("Unable to set GRASS database. " 287 "Check your locale settings."), 288 style=wx.OK | wx.ICON_ERROR | wx.CENTRE) 289 290 self.OnSetDatabase(None) 291 location = self.GetRCValue("LOCATION_NAME") 292 if location == "<UNKNOWN>" or location is None: 293 return 294 if not os.path.isdir(os.path.join(self.gisdbase, location)): 295 location = None 296 297 # list of locations 298 self.UpdateLocations(self.gisdbase) 299 try: 300 self.lblocations.SetSelection(self.listOfLocations.index(location), 301 force=True) 302 self.lblocations.EnsureVisible( 303 self.listOfLocations.index(location)) 304 except ValueError: 305 sys.stderr.write( 306 _("ERROR: Location <%s> not found\n") % 307 self.GetRCValue("LOCATION_NAME")) 308 if len(self.listOfLocations) > 0: 309 self.lblocations.SetSelection(0, force=True) 310 self.lblocations.EnsureVisible(0) 311 location = self.listOfLocations[0] 312 else: 313 return 314 315 # list of mapsets 316 self.UpdateMapsets(os.path.join(self.gisdbase, location)) 317 mapset = self.GetRCValue("MAPSET") 318 if mapset: 319 try: 320 self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset), 321 force=True) 322 self.lbmapsets.EnsureVisible(self.listOfMapsets.index(mapset)) 323 except ValueError: 324 sys.stderr.write(_("ERROR: Mapset <%s> not found\n") % mapset) 325 self.lbmapsets.SetSelection(0, force=True) 326 self.lbmapsets.EnsureVisible(0) 327 328 def _do_layout(self): 329 sizer = wx.BoxSizer(wx.VERTICAL) 330 self.sizer = sizer # for the layout call after changing message 331 dbase_sizer = wx.BoxSizer(wx.HORIZONTAL) 332 333 location_mapset_sizer = wx.BoxSizer(wx.HORIZONTAL) 334 335 gisdbase_panel_sizer = wx.BoxSizer(wx.VERTICAL) 336 gisdbase_boxsizer = wx.StaticBoxSizer(self.gisdbase_box, wx.VERTICAL) 337 338 btns_sizer = wx.BoxSizer(wx.HORIZONTAL) 339 340 self.gisdbase_panel.SetSizer(gisdbase_panel_sizer) 341 342 # gis data directory 343 344 gisdbase_boxsizer.Add(self.gisdbase_panel, proportion=1, 345 flag=wx.EXPAND | wx.ALL, 346 border=1) 347 348 gisdbase_panel_sizer.Add(dbase_sizer, proportion=1, 349 flag=wx.EXPAND | wx.ALL, 350 border=1) 351 gisdbase_panel_sizer.Add(self.ldbase, proportion=0, 352 flag=wx.EXPAND | wx.ALL, 353 border=1) 354 355 dbase_sizer.Add(self.tgisdbase, proportion=1, 356 flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, 357 border=1) 358 dbase_sizer.Add(self.bbrowse, proportion=0, 359 flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, 360 border=1) 361 362 gisdbase_panel_sizer.Fit(self.gisdbase_panel) 363 364 # location and mapset lists 365 366 def layout_list_box(box, panel, list_box, buttons, description): 367 panel_sizer = wx.BoxSizer(wx.VERTICAL) 368 main_sizer = wx.BoxSizer(wx.HORIZONTAL) 369 box_sizer = wx.StaticBoxSizer(box, wx.VERTICAL) 370 buttons_sizer = wx.BoxSizer(wx.VERTICAL) 371 372 panel.SetSizer(panel_sizer) 373 panel_sizer.Fit(panel) 374 375 main_sizer.Add(list_box, proportion=1, 376 flag=wx.EXPAND | wx.ALL, 377 border=1) 378 main_sizer.Add(buttons_sizer, proportion=0, 379 flag=wx.ALL, 380 border=1) 381 for button in buttons: 382 buttons_sizer.Add(button, proportion=0, 383 flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 384 border=3) 385 box_sizer.Add(panel, proportion=1, 386 flag=wx.EXPAND | wx.ALL, 387 border=1) 388 panel_sizer.Add(main_sizer, proportion=1, 389 flag=wx.EXPAND | wx.ALL, 390 border=1) 391 panel_sizer.Add(description, proportion=0, 392 flag=wx.EXPAND | wx.ALL, 393 border=1) 394 return box_sizer 395 396 location_boxsizer = layout_list_box( 397 box=self.location_box, 398 panel=self.location_panel, 399 list_box=self.lblocations, 400 buttons=[self.bwizard, self.rename_location_button, 401 self.delete_location_button, 402 self.download_location_button], 403 description=self.llocation) 404 mapset_boxsizer = layout_list_box( 405 box=self.mapset_box, 406 panel=self.mapset_panel, 407 list_box=self.lbmapsets, 408 buttons=[self.bmapset, self.rename_mapset_button, 409 self.delete_mapset_button], 410 description=self.lmapset) 411 412 # location and mapset sizer 413 location_mapset_sizer.Add(location_boxsizer, proportion=1, 414 flag=wx.LEFT | wx.RIGHT | wx.EXPAND, 415 border=3) 416 location_mapset_sizer.Add(mapset_boxsizer, proportion=1, 417 flag=wx.RIGHT | wx.EXPAND, 418 border=3) 419 420 # buttons 421 btns_sizer.Add(self.bstart, proportion=0, 422 flag=wx.ALIGN_CENTER_HORIZONTAL | 423 wx.ALIGN_CENTER_VERTICAL | 424 wx.ALL, 425 border=5) 426 btns_sizer.Add(self.bexit, proportion=0, 427 flag=wx.ALIGN_CENTER_HORIZONTAL | 428 wx.ALIGN_CENTER_VERTICAL | 429 wx.ALL, 430 border=5) 431 btns_sizer.Add(self.bhelp, proportion=0, 432 flag=wx.ALIGN_CENTER_HORIZONTAL | 433 wx.ALIGN_CENTER_VERTICAL | 434 wx.ALL, 435 border=5) 436 437 # main sizer 438 sizer.Add(self.hbitmap, 439 proportion=0, 440 flag=wx.ALIGN_CENTER_VERTICAL | 441 wx.ALIGN_CENTER_HORIZONTAL | 442 wx.ALL, 443 border=3) # image 444 sizer.Add(gisdbase_boxsizer, proportion=0, 445 flag=wx.RIGHT | wx.LEFT | wx.TOP | wx.EXPAND, 446 border=3) # GISDBASE setting 447 448 # warning/error message 449 sizer.Add(self.lmessage, 450 proportion=0, 451 flag=wx.ALIGN_LEFT | wx.ALL | wx.EXPAND, border=5) 452 sizer.Add(location_mapset_sizer, proportion=1, 453 flag=wx.RIGHT | wx.LEFT | wx.EXPAND, 454 border=1) 455 sizer.Add(btns_sizer, proportion=0, 456 flag=wx.ALIGN_CENTER_VERTICAL | 457 wx.ALIGN_CENTER_HORIZONTAL | 458 wx.RIGHT | wx.LEFT, 459 border=3) 460 461 self.panel.SetAutoLayout(True) 462 self.panel.SetSizer(sizer) 463 sizer.Fit(self.panel) 464 sizer.SetSizeHints(self) 465 self.Layout() 466 467 def _showWarning(self, text): 468 """Displays a warning, hint or info message to the user. 469 470 This function can be used for all kinds of messages except for 471 error messages. 472 473 .. note:: 474 There is no cleaning procedure. You should call _hideMessage when 475 you know that there is everything correct now. 476 """ 477 self.lmessage.SetLabel(text) 478 self.sizer.Layout() 479 480 def _showError(self, text): 481 """Displays a error message to the user. 482 483 This function should be used only when something serious and unexpected 484 happens, otherwise _showWarning should be used. 485 486 .. note:: 487 There is no cleaning procedure. You should call _hideMessage when 488 you know that there is everything correct now. 489 """ 490 self.lmessage.SetLabel(_("Error: {text}").format(text=text)) 491 self.sizer.Layout() 492 493 def _hideMessage(self): 494 """Clears/hides the error message.""" 495 # we do no hide widget 496 # because we do not want the dialog to change the size 497 self.lmessage.SetLabel("") 498 self.sizer.Layout() 499 500 def GetRCValue(self, value): 501 """Return GRASS variable (read from GISRC) 502 """ 503 if value in self.grassrc: 504 return self.grassrc[value] 505 else: 506 return None 507 508 def SuggestDatabase(self): 509 """Suggest (set) possible GRASS Database value""" 510 # only if nothing is set (<UNKNOWN> comes from init script) 511 if self.GetRCValue("LOCATION_NAME") != "<UNKNOWN>": 512 return 513 path = get_possible_database_path() 514 if path: 515 try: 516 self.tgisdbase.SetValue(path) 517 except UnicodeDecodeError: 518 # restore previous state 519 # wizard gives error in this case, we just ignore 520 path = None 521 self.tgisdbase.SetValue(self.gisdbase) 522 # if we still have path 523 if path: 524 self.gisdbase = path 525 self.OnSetDatabase(None) 526 else: 527 # nothing found 528 # TODO: should it be warning, hint or message? 529 self._showWarning(_( 530 'GRASS needs a directory (GRASS database) ' 531 'in which to store its data. ' 532 'Create one now if you have not already done so. ' 533 'A popular choice is "grassdata", located in ' 534 'your home directory. ' 535 'Press Browse button to select the directory.')) 536 537 def OnWizard(self, event): 538 """Location wizard started""" 539 from location_wizard.wizard import LocationWizard 540 gWizard = LocationWizard(parent=self, 541 grassdatabase=self.tgisdbase.GetValue()) 542 if gWizard.location is not None: 543 self.tgisdbase.SetValue(gWizard.grassdatabase) 544 self.OnSetDatabase(None) 545 self.UpdateMapsets(os.path.join(self.gisdbase, gWizard.location)) 546 self.lblocations.SetSelection( 547 self.listOfLocations.index( 548 gWizard.location)) 549 self.lbmapsets.SetSelection(0) 550 self.SetLocation(self.gisdbase, gWizard.location, 'PERMANENT') 551 if gWizard.georeffile: 552 message = _("Do you want to import <%(name)s> to the newly created location?") % { 553 'name': gWizard.georeffile} 554 dlg = wx.MessageDialog(parent=self, message=message, caption=_( 555 "Import data?"), style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) 556 dlg.CenterOnParent() 557 if dlg.ShowModal() == wx.ID_YES: 558 self.ImportFile(gWizard.georeffile) 559 dlg.Destroy() 560 if gWizard.default_region: 561 defineRegion = RegionDef(self, location=gWizard.location) 562 defineRegion.CenterOnParent() 563 defineRegion.ShowModal() 564 defineRegion.Destroy() 565 566 if gWizard.user_mapset: 567 self.OnCreateMapset(event) 568 569 def ImportFile(self, filePath): 570 """Tries to import file as vector or raster. 571 572 If successfull sets default region from imported map. 573 """ 574 RunCommand('db.connect', flags='c') 575 mapName = os.path.splitext(os.path.basename(filePath))[0] 576 vectors = RunCommand('v.in.ogr', input=filePath, flags='l', 577 read=True) 578 579 wx.BeginBusyCursor() 580 wx.GetApp().Yield() 581 if vectors: 582 # vector detected 583 returncode, error = RunCommand( 584 'v.in.ogr', input=filePath, output=mapName, flags='e', 585 getErrorMsg=True) 586 else: 587 returncode, error = RunCommand( 588 'r.in.gdal', input=filePath, output=mapName, flags='e', 589 getErrorMsg=True) 590 wx.EndBusyCursor() 591 592 if returncode != 0: 593 GError( 594 parent=self, 595 message=_( 596 "Import of <%(name)s> failed.\n" 597 "Reason: %(msg)s") % ({ 598 'name': filePath, 599 'msg': error})) 600 else: 601 GMessage( 602 message=_( 603 "Data file <%(name)s> imported successfully. " 604 "The location's default region was set from this imported map.") % { 605 'name': filePath}, 606 parent=self) 607 608 # the event can be refactored out by using lambda in bind 609 def RenameMapset(self, event): 610 """Rename selected mapset 611 """ 612 location = self.listOfLocations[self.lblocations.GetSelection()] 613 mapset = self.listOfMapsets[self.lbmapsets.GetSelection()] 614 if mapset == 'PERMANENT': 615 GMessage( 616 parent=self, message=_( 617 'Mapset <PERMANENT> is required for valid GRASS location.\n\n' 618 'This mapset cannot be renamed.')) 619 return 620 621 dlg = TextEntryDialog( 622 parent=self, 623 message=_('Current name: %s\n\nEnter new name:') % 624 mapset, 625 caption=_('Rename selected mapset'), 626 validator=GenericValidator( 627 grass.legal_name, 628 self._nameValidationFailed)) 629 630 if dlg.ShowModal() == wx.ID_OK: 631 newmapset = dlg.GetValue() 632 if newmapset == mapset: 633 dlg.Destroy() 634 return 635 636 if newmapset in self.listOfMapsets: 637 wx.MessageBox( 638 parent=self, caption=_('Message'), message=_( 639 'Unable to rename mapset.\n\n' 640 'Mapset <%s> already exists in location.') % 641 newmapset, style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE) 642 else: 643 try: 644 sutils.rename_mapset(self.gisdbase, location, 645 mapset, newmapset) 646 self.OnSelectLocation(None) 647 self.lbmapsets.SetSelection( 648 self.listOfMapsets.index(newmapset)) 649 except Exception as e: 650 wx.MessageBox( 651 parent=self, 652 caption=_('Error'), 653 message=_('Unable to rename mapset.\n\n%s') % 654 e, 655 style=wx.OK | wx.ICON_ERROR | wx.CENTRE) 656 657 dlg.Destroy() 658 659 def RenameLocation(self, event): 660 """Rename selected location 661 """ 662 location = self.listOfLocations[self.lblocations.GetSelection()] 663 664 dlg = TextEntryDialog( 665 parent=self, 666 message=_('Current name: %s\n\nEnter new name:') % 667 location, 668 caption=_('Rename selected location'), 669 validator=GenericValidator( 670 grass.legal_name, 671 self._nameValidationFailed)) 672 673 if dlg.ShowModal() == wx.ID_OK: 674 newlocation = dlg.GetValue() 675 if newlocation == location: 676 dlg.Destroy() 677 return 678 679 if newlocation in self.listOfLocations: 680 wx.MessageBox( 681 parent=self, caption=_('Message'), message=_( 682 'Unable to rename location.\n\n' 683 'Location <%s> already exists in GRASS database.') % 684 newlocation, style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE) 685 else: 686 try: 687 sutils.rename_location(self.gisdbase, 688 location, newlocation) 689 self.UpdateLocations(self.gisdbase) 690 self.lblocations.SetSelection( 691 self.listOfLocations.index(newlocation)) 692 self.UpdateMapsets(newlocation) 693 except Exception as e: 694 wx.MessageBox( 695 parent=self, 696 caption=_('Error'), 697 message=_('Unable to rename location.\n\n%s') % 698 e, 699 style=wx.OK | wx.ICON_ERROR | wx.CENTRE) 700 701 dlg.Destroy() 702 703 def DeleteMapset(self, event): 704 """Delete selected mapset 705 """ 706 location = self.listOfLocations[self.lblocations.GetSelection()] 707 mapset = self.listOfMapsets[self.lbmapsets.GetSelection()] 708 if mapset == 'PERMANENT': 709 GMessage( 710 parent=self, message=_( 711 'Mapset <PERMANENT> is required for valid GRASS location.\n\n' 712 'This mapset cannot be deleted.')) 713 return 714 715 dlg = wx.MessageDialog( 716 parent=self, 717 message=_( 718 "Do you want to continue with deleting mapset <%(mapset)s> " 719 "from location <%(location)s>?\n\n" 720 "ALL MAPS included in this mapset will be " 721 "PERMANENTLY DELETED!") % 722 {'mapset': mapset, 'location': location}, 723 caption=_("Delete selected mapset"), 724 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 725 726 if dlg.ShowModal() == wx.ID_YES: 727 try: 728 sutils.delete_mapset(self.gisdbase, location, mapset) 729 self.OnSelectLocation(None) 730 self.lbmapsets.SetSelection(0) 731 except: 732 wx.MessageBox(message=_('Unable to delete mapset')) 733 734 dlg.Destroy() 735 736 def DeleteLocation(self, event): 737 """ 738 Delete selected location 739 """ 740 741 location = self.listOfLocations[self.lblocations.GetSelection()] 742 743 dlg = wx.MessageDialog( 744 parent=self, 745 message=_( 746 "Do you want to continue with deleting " 747 "location <%s>?\n\n" 748 "ALL MAPS included in this location will be " 749 "PERMANENTLY DELETED!") % 750 (location), 751 caption=_("Delete selected location"), 752 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 753 754 if dlg.ShowModal() == wx.ID_YES: 755 try: 756 sutils.delete_location(self.gisdbase, location) 757 self.UpdateLocations(self.gisdbase) 758 self.lblocations.SetSelection(0) 759 self.OnSelectLocation(None) 760 self.lbmapsets.SetSelection(0) 761 except: 762 wx.MessageBox(message=_('Unable to delete location')) 763 764 dlg.Destroy() 765 766 def DownloadLocation(self, event): 767 """Download location online""" 768 from startup.locdownload import LocationDownloadDialog 769 770 loc_download = LocationDownloadDialog(parent=self, database=self.gisdbase) 771 loc_download.Centre() 772 loc_download.ShowModal() 773 location = loc_download.GetLocation() 774 if location: 775 # get the new location to the list 776 self.UpdateLocations(self.gisdbase) 777 # seems to be used in similar context 778 self.UpdateMapsets(os.path.join(self.gisdbase, location)) 779 self.lblocations.SetSelection( 780 self.listOfLocations.index(location)) 781 # wizard does this as well, not sure if needed 782 self.SetLocation(self.gisdbase, location, 'PERMANENT') 783 # seems to be used in similar context 784 self.OnSelectLocation(None) 785 loc_download.Destroy() 786 787 def UpdateLocations(self, dbase): 788 """Update list of locations""" 789 try: 790 self.listOfLocations = GetListOfLocations(dbase) 791 except (UnicodeEncodeError, UnicodeDecodeError) as e: 792 GError(parent=self, 793 message=_("Unicode error detected. " 794 "Check your locale settings. Details: {0}").format(e), 795 showTraceback=False) 796 797 self.lblocations.Clear() 798 self.lblocations.InsertItems(self.listOfLocations, 0) 799 800 if len(self.listOfLocations) > 0: 801 self._hideMessage() 802 self.lblocations.SetSelection(0) 803 else: 804 self.lblocations.SetSelection(wx.NOT_FOUND) 805 self._showWarning(_("No GRASS Location found in '%s'." 806 " Create a new Location or choose different" 807 " GRASS database directory.") 808 % self.gisdbase) 809 810 return self.listOfLocations 811 812 def UpdateMapsets(self, location): 813 """Update list of mapsets""" 814 self.FormerMapsetSelection = wx.NOT_FOUND # for non-selectable item 815 816 self.listOfMapsetsSelectable = list() 817 self.listOfMapsets = GetListOfMapsets(self.gisdbase, location) 818 819 self.lbmapsets.Clear() 820 821 # disable mapset with denied permission 822 locationName = os.path.basename(location) 823 824 ret = RunCommand('g.mapset', 825 read=True, 826 flags='l', 827 location=locationName, 828 gisdbase=self.gisdbase) 829 830 if ret: 831 for line in ret.splitlines(): 832 self.listOfMapsetsSelectable += line.split(' ') 833 else: 834 self.SetLocation(self.gisdbase, locationName, "PERMANENT") 835 # first run only 836 self.listOfMapsetsSelectable = copy.copy(self.listOfMapsets) 837 838 disabled = [] 839 idx = 0 840 for mapset in self.listOfMapsets: 841 if mapset not in self.listOfMapsetsSelectable or \ 842 get_lockfile_if_present(self.gisdbase, 843 locationName, mapset): 844 disabled.append(idx) 845 idx += 1 846 847 self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled) 848 849 return self.listOfMapsets 850 851 def OnSelectLocation(self, event): 852 """Location selected""" 853 if event: 854 self.lblocations.SetSelection(event.GetIndex()) 855 856 if self.lblocations.GetSelection() != wx.NOT_FOUND: 857 self.UpdateMapsets( 858 os.path.join( 859 self.gisdbase, 860 self.listOfLocations[ 861 self.lblocations.GetSelection()])) 862 else: 863 self.listOfMapsets = [] 864 865 disabled = [] 866 idx = 0 867 try: 868 locationName = self.listOfLocations[ 869 self.lblocations.GetSelection()] 870 except IndexError: 871 locationName = '' 872 873 for mapset in self.listOfMapsets: 874 if mapset not in self.listOfMapsetsSelectable or \ 875 get_lockfile_if_present(self.gisdbase, 876 locationName, mapset): 877 disabled.append(idx) 878 idx += 1 879 880 self.lbmapsets.Clear() 881 self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled) 882 883 if len(self.listOfMapsets) > 0: 884 self.lbmapsets.SetSelection(0) 885 if locationName: 886 # enable start button when location and mapset is selected 887 self.bstart.Enable() 888 self.bstart.SetFocus() 889 self.bmapset.Enable() 890 # replacing disabled choice, perhaps just mapset needed 891 self.rename_location_button.Enable() 892 self.delete_location_button.Enable() 893 self.rename_mapset_button.Enable() 894 self.delete_mapset_button.Enable() 895 else: 896 self.lbmapsets.SetSelection(wx.NOT_FOUND) 897 self.bstart.Enable(False) 898 self.bmapset.Enable(False) 899 # this all was originally a choice, perhaps just mapset needed 900 self.rename_location_button.Enable(False) 901 self.delete_location_button.Enable(False) 902 self.rename_mapset_button.Enable(False) 903 self.delete_mapset_button.Enable(False) 904 905 def OnSelectMapset(self, event): 906 """Mapset selected""" 907 self.lbmapsets.SetSelection(event.GetIndex()) 908 909 if event.GetText() not in self.listOfMapsetsSelectable: 910 self.lbmapsets.SetSelection(self.FormerMapsetSelection) 911 else: 912 self.FormerMapsetSelection = event.GetIndex() 913 event.Skip() 914 915 def OnSetDatabase(self, event): 916 """Database set""" 917 gisdbase = self.tgisdbase.GetValue() 918 self._hideMessage() 919 if not os.path.exists(gisdbase): 920 self._showError(_("Path '%s' doesn't exist.") % gisdbase) 921 return 922 923 self.gisdbase = self.tgisdbase.GetValue() 924 self.UpdateLocations(self.gisdbase) 925 926 self.OnSelectLocation(None) 927 928 def OnBrowse(self, event): 929 """'Browse' button clicked""" 930 if not event: 931 defaultPath = os.getenv('HOME') 932 else: 933 defaultPath = "" 934 935 dlg = wx.DirDialog(parent=self, message=_("Choose GIS Data Directory"), 936 defaultPath=defaultPath, style=wx.DD_DEFAULT_STYLE) 937 938 if dlg.ShowModal() == wx.ID_OK: 939 self.gisdbase = dlg.GetPath() 940 self.tgisdbase.SetValue(self.gisdbase) 941 self.OnSetDatabase(event) 942 943 dlg.Destroy() 944 945 def OnCreateMapset(self, event): 946 """Create new mapset""" 947 dlg = NewMapsetDialog( 948 parent=self, 949 default=self._getDefaultMapsetName(), 950 validation_failed_handler=self._nameValidationFailed, 951 help_hanlder=self.OnHelp, 952 ) 953 if dlg.ShowModal() == wx.ID_OK: 954 mapset = dlg.GetValue() 955 return self.CreateNewMapset(mapset=mapset) 956 else: 957 return False 958 959 def CreateNewMapset(self, mapset): 960 if mapset in self.listOfMapsets: 961 GMessage(parent=self, 962 message=_("Mapset <%s> already exists.") % mapset) 963 return False 964 965 if mapset.lower() == 'ogr': 966 dlg1 = wx.MessageDialog( 967 parent=self, 968 message=_( 969 "Mapset <%s> is reserved for direct " 970 "read access to OGR layers. Please consider to use " 971 "another name for your mapset.\n\n" 972 "Are you really sure that you want to create this mapset?") % 973 mapset, 974 caption=_("Reserved mapset name"), 975 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 976 ret = dlg1.ShowModal() 977 dlg1.Destroy() 978 if ret == wx.ID_NO: 979 dlg1.Destroy() 980 return False 981 982 try: 983 self.gisdbase = self.tgisdbase.GetValue() 984 location = self.listOfLocations[self.lblocations.GetSelection()] 985 create_mapset(self.gisdbase, location, mapset) 986 self.OnSelectLocation(None) 987 self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset)) 988 self.bstart.SetFocus() 989 990 return True 991 except Exception as e: 992 GError(parent=self, 993 message=_("Unable to create new mapset: %s") % e, 994 showTraceback=False) 995 return False 996 997 def OnStart(self, event): 998 """'Start GRASS' button clicked""" 999 dbase = self.tgisdbase.GetValue() 1000 location = self.listOfLocations[self.lblocations.GetSelection()] 1001 mapset = self.listOfMapsets[self.lbmapsets.GetSelection()] 1002 1003 lockfile = get_lockfile_if_present(dbase, location, mapset) 1004 if lockfile: 1005 dlg = wx.MessageDialog( 1006 parent=self, 1007 message=_( 1008 "GRASS is already running in selected mapset <%(mapset)s>\n" 1009 "(file %(lock)s found).\n\n" 1010 "Concurrent use not allowed.\n\n" 1011 "Do you want to try to remove .gislock (note that you " 1012 "need permission for this operation) and continue?") % 1013 {'mapset': mapset, 'lock': lockfile}, 1014 caption=_("Lock file found"), 1015 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) 1016 1017 ret = dlg.ShowModal() 1018 dlg.Destroy() 1019 if ret == wx.ID_YES: 1020 dlg1 = wx.MessageDialog( 1021 parent=self, 1022 message=_( 1023 "ARE YOU REALLY SURE?\n\n" 1024 "If you really are running another GRASS session doing this " 1025 "could corrupt your data. Have another look in the processor " 1026 "manager just to be sure..."), 1027 caption=_("Lock file found"), 1028 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) 1029 1030 ret = dlg1.ShowModal() 1031 dlg1.Destroy() 1032 1033 if ret == wx.ID_YES: 1034 try: 1035 os.remove(lockfile) 1036 except IOError as e: 1037 GError(_("Unable to remove '%(lock)s'.\n\n" 1038 "Details: %(reason)s") % {'lock': lockfile, 'reason': e}) 1039 else: 1040 return 1041 else: 1042 return 1043 self.SetLocation(dbase, location, mapset) 1044 self.ExitSuccessfully() 1045 1046 def SetLocation(self, dbase, location, mapset): 1047 SetSessionMapset(dbase, location, mapset) 1048 1049 def _getDefaultMapsetName(self): 1050 """Returns default name for mapset.""" 1051 try: 1052 defaultName = getpass.getuser() 1053 # raise error if not ascii (not valid mapset name) 1054 defaultName.encode('ascii') 1055 except: # whatever might go wrong 1056 defaultName = 'user' 1057 1058 return defaultName 1059 1060 def ExitSuccessfully(self): 1061 self.Destroy() 1062 sys.exit(self.exit_success) 1063 1064 def OnExit(self, event): 1065 """'Exit' button clicked""" 1066 self.Destroy() 1067 sys.exit(self.exit_user_requested) 1068 1069 def OnHelp(self, event): 1070 """'Help' button clicked""" 1071 1072 # help text in lib/init/helptext.html 1073 RunCommand('g.manual', entry='helptext') 1074 1075 def OnCloseWindow(self, event): 1076 """Close window event""" 1077 event.Skip() 1078 sys.exit(self.exit_user_requested) 1079 1080 def _nameValidationFailed(self, ctrl): 1081 message = _( 1082 "Name <%(name)s> is not a valid name for location or mapset. " 1083 "Please use only ASCII characters excluding %(chars)s " 1084 "and space.") % { 1085 'name': ctrl.GetValue(), 1086 'chars': '/"\'@,=*~'} 1087 GError(parent=self, message=message, caption=_("Invalid name")) 1088 1089 1090class GListBox(ListCtrl, listmix.ListCtrlAutoWidthMixin): 1091 """Use wx.ListCtrl instead of wx.ListBox, different style for 1092 non-selectable items (e.g. mapsets with denied permission)""" 1093 1094 def __init__(self, parent, id, size, 1095 choices, disabled=[]): 1096 ListCtrl.__init__( 1097 self, parent, id, size=size, style=wx.LC_REPORT | wx.LC_NO_HEADER | 1098 wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN) 1099 1100 listmix.ListCtrlAutoWidthMixin.__init__(self) 1101 1102 self.InsertColumn(0, '') 1103 1104 self.selected = wx.NOT_FOUND 1105 1106 self._LoadData(choices, disabled) 1107 1108 def _LoadData(self, choices, disabled=[]): 1109 """Load data into list 1110 1111 :param choices: list of item 1112 :param disabled: list of indices of non-selectable items 1113 """ 1114 idx = 0 1115 count = self.GetItemCount() 1116 for item in choices: 1117 index = self.InsertItem(count + idx, item) 1118 self.SetItem(index, 0, item) 1119 1120 if idx in disabled: 1121 self.SetItemTextColour(idx, wx.Colour(150, 150, 150)) 1122 idx += 1 1123 1124 def Clear(self): 1125 self.DeleteAllItems() 1126 1127 def InsertItems(self, choices, pos, disabled=[]): 1128 self._LoadData(choices, disabled) 1129 1130 def SetSelection(self, item, force=False): 1131 if item != wx.NOT_FOUND and \ 1132 (platform.system() != 'Windows' or force): 1133 # Windows -> FIXME 1134 self.SetItemState( 1135 item, 1136 wx.LIST_STATE_SELECTED, 1137 wx.LIST_STATE_SELECTED) 1138 1139 self.selected = item 1140 1141 def GetSelection(self): 1142 return self.selected 1143 1144 1145class StartUp(wx.App): 1146 """Start-up application""" 1147 1148 def OnInit(self): 1149 StartUp = GRASSStartup() 1150 StartUp.CenterOnScreen() 1151 self.SetTopWindow(StartUp) 1152 StartUp.Show() 1153 StartUp.SuggestDatabase() 1154 1155 return 1 1156 1157if __name__ == "__main__": 1158 if os.getenv("GISBASE") is None: 1159 sys.exit("Failed to start GUI, GRASS GIS is not running.") 1160 1161 GRASSStartUp = StartUp(0) 1162 GRASSStartUp.MainLoop() 1163