gis_set.py 36 KB

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