gis_set.py 42 KB

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