gis_set.py 45 KB

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