gis_set.py 46 KB

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