gis_set.py 37 KB

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