gis_set.py 36 KB

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