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 wx.lib.mixins.listctrl as listmix
  25. from core.gcmd import GError, RunCommand
  26. from core.utils import GetListOfLocations, GetListOfMapsets
  27. from startup.utils import (
  28. get_lockfile_if_present, get_possible_database_path,
  29. create_database_directory)
  30. from startup.guiutils import (SetSessionMapset,
  31. create_mapset_interactively,
  32. create_location_interactively,
  33. rename_mapset_interactively,
  34. rename_location_interactively,
  35. delete_mapset_interactively,
  36. delete_location_interactively,
  37. download_location_interactively)
  38. import startup.guiutils as sgui
  39. from gui_core.widgets import StaticWrapText
  40. from gui_core.wrap import Button, ListCtrl, StaticText, StaticBox, \
  41. TextCtrl, BitmapFromImage
  42. class GRASSStartup(wx.Frame):
  43. exit_success = 0
  44. # 2 is file not found from python interpreter
  45. exit_user_requested = 5
  46. """GRASS start-up screen"""
  47. def __init__(self, parent=None, id=wx.ID_ANY,
  48. style=wx.DEFAULT_FRAME_STYLE):
  49. #
  50. # GRASS variables
  51. #
  52. self.gisbase = os.getenv("GISBASE")
  53. self.grassrc = sgui.read_gisrc()
  54. self.gisdbase = self.GetRCValue("GISDBASE")
  55. #
  56. # list of locations/mapsets
  57. #
  58. self.listOfLocations = []
  59. self.listOfMapsets = []
  60. self.listOfMapsetsSelectable = []
  61. wx.Frame.__init__(self, parent=parent, id=id, style=style)
  62. self.locale = wx.Locale(language=wx.LANGUAGE_DEFAULT)
  63. # scroll panel was used here but not properly and is probably not need
  64. # as long as it is not high too much
  65. self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
  66. # i18N
  67. #
  68. # graphical elements
  69. #
  70. # image
  71. try:
  72. if os.getenv('ISISROOT'):
  73. name = os.path.join(
  74. globalvar.GUIDIR,
  75. "images",
  76. "startup_banner_isis.png")
  77. else:
  78. name = os.path.join(
  79. globalvar.GUIDIR, "images", "startup_banner.png")
  80. self.hbitmap = wx.StaticBitmap(self.panel, wx.ID_ANY,
  81. wx.Bitmap(name=name,
  82. type=wx.BITMAP_TYPE_PNG))
  83. except:
  84. self.hbitmap = wx.StaticBitmap(
  85. self.panel, wx.ID_ANY, BitmapFromImage(
  86. wx.EmptyImage(530, 150)))
  87. # labels
  88. # crashes when LOCATION doesn't exist
  89. # get version & revision
  90. grassVersion, grassRevisionStr = sgui.GetVersion()
  91. self.gisdbase_box = StaticBox(
  92. parent=self.panel, id=wx.ID_ANY, label=" %s " %
  93. _("1. Select GRASS GIS database directory"))
  94. self.location_box = StaticBox(
  95. parent=self.panel, id=wx.ID_ANY, label=" %s " %
  96. _("2. Select GRASS Location"))
  97. self.mapset_box = StaticBox(
  98. parent=self.panel, id=wx.ID_ANY, label=" %s " %
  99. _("3. Select GRASS Mapset"))
  100. self.lmessage = StaticWrapText(parent=self.panel)
  101. # It is not clear if all wx versions supports color, so try-except.
  102. # The color itself may not be correct for all platforms/system settings
  103. # but in http://xoomer.virgilio.it/infinity77/wxPython/Widgets/wx.SystemSettings.html
  104. # there is no 'warning' color.
  105. try:
  106. self.lmessage.SetForegroundColour(wx.Colour(255, 0, 0))
  107. except AttributeError:
  108. pass
  109. self.gisdbase_panel = wx.Panel(parent=self.panel)
  110. self.location_panel = wx.Panel(parent=self.panel)
  111. self.mapset_panel = wx.Panel(parent=self.panel)
  112. self.ldbase = StaticText(
  113. parent=self.gisdbase_panel, id=wx.ID_ANY,
  114. label=_("GRASS GIS database directory contains Locations."))
  115. self.llocation = StaticWrapText(
  116. parent=self.location_panel, id=wx.ID_ANY,
  117. label=_("All data in one Location is in the same "
  118. " coordinate reference system (projection)."
  119. " One Location can be one project."
  120. " Location contains Mapsets."),
  121. style=wx.ALIGN_LEFT)
  122. self.lmapset = StaticWrapText(
  123. parent=self.mapset_panel, id=wx.ID_ANY,
  124. label=_("Mapset contains GIS data related"
  125. " to one project, task within one project,"
  126. " subregion or user."),
  127. style=wx.ALIGN_LEFT)
  128. try:
  129. for label in [self.ldbase, self.llocation, self.lmapset]:
  130. label.SetForegroundColour(
  131. wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
  132. except AttributeError:
  133. # for explanation of try-except see above
  134. pass
  135. # buttons
  136. self.bstart = Button(parent=self.panel, id=wx.ID_ANY,
  137. label=_("Start &GRASS session"))
  138. self.bstart.SetDefault()
  139. self.bexit = Button(parent=self.panel, id=wx.ID_EXIT)
  140. self.bstart.SetMinSize((180, self.bexit.GetSize()[1]))
  141. self.bhelp = Button(parent=self.panel, id=wx.ID_HELP)
  142. self.bbrowse = Button(parent=self.gisdbase_panel, id=wx.ID_ANY,
  143. label=_("&Browse"))
  144. self.bmapset = Button(parent=self.mapset_panel, id=wx.ID_ANY,
  145. # GTC New mapset
  146. label=_("&New"))
  147. self.bmapset.SetToolTip(_("Create a new Mapset in selected Location"))
  148. self.bwizard = Button(parent=self.location_panel, id=wx.ID_ANY,
  149. # GTC New location
  150. label=_("N&ew"))
  151. self.bwizard.SetToolTip(
  152. _(
  153. "Create a new location using location wizard."
  154. " After location is created successfully,"
  155. " GRASS session is started."))
  156. self.rename_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
  157. # GTC Rename location
  158. label=_("Ren&ame"))
  159. self.rename_location_button.SetToolTip(_("Rename selected location"))
  160. self.delete_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
  161. # GTC Delete location
  162. label=_("De&lete"))
  163. self.delete_location_button.SetToolTip(_("Delete selected location"))
  164. self.download_location_button = Button(parent=self.location_panel, id=wx.ID_ANY,
  165. label=_("Do&wnload"))
  166. self.download_location_button.SetToolTip(_("Download sample location"))
  167. self.rename_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY,
  168. # GTC Rename mapset
  169. label=_("&Rename"))
  170. self.rename_mapset_button.SetToolTip(_("Rename selected mapset"))
  171. self.delete_mapset_button = Button(parent=self.mapset_panel, id=wx.ID_ANY,
  172. # GTC Delete mapset
  173. label=_("&Delete"))
  174. self.delete_mapset_button.SetToolTip(_("Delete selected mapset"))
  175. # textinputs
  176. self.tgisdbase = TextCtrl(
  177. parent=self.gisdbase_panel, id=wx.ID_ANY, value="", size=(
  178. 300, -1), style=wx.TE_PROCESS_ENTER)
  179. # Locations
  180. self.lblocations = GListBox(parent=self.location_panel,
  181. id=wx.ID_ANY, size=(180, 200),
  182. choices=self.listOfLocations)
  183. self.lblocations.SetColumnWidth(0, 180)
  184. # TODO: sort; but keep PERMANENT on top of list
  185. # Mapsets
  186. self.lbmapsets = GListBox(parent=self.mapset_panel,
  187. id=wx.ID_ANY, size=(180, 200),
  188. choices=self.listOfMapsets)
  189. self.lbmapsets.SetColumnWidth(0, 180)
  190. # layout & properties, first do layout so everything is created
  191. self._do_layout()
  192. self._set_properties(grassVersion, grassRevisionStr)
  193. # events
  194. self.bbrowse.Bind(wx.EVT_BUTTON, self.OnBrowse)
  195. self.bstart.Bind(wx.EVT_BUTTON, self.OnStart)
  196. self.bexit.Bind(wx.EVT_BUTTON, self.OnExit)
  197. self.bhelp.Bind(wx.EVT_BUTTON, self.OnHelp)
  198. self.bmapset.Bind(wx.EVT_BUTTON, self.OnCreateMapset)
  199. self.bwizard.Bind(wx.EVT_BUTTON, self.OnCreateLocation)
  200. self.rename_location_button.Bind(wx.EVT_BUTTON, self.OnRenameLocation)
  201. self.delete_location_button.Bind(wx.EVT_BUTTON, self.OnDeleteLocation)
  202. self.download_location_button.Bind(wx.EVT_BUTTON, self.OnDownloadLocation)
  203. self.rename_mapset_button.Bind(wx.EVT_BUTTON, self.OnRenameMapset)
  204. self.delete_mapset_button.Bind(wx.EVT_BUTTON, self.OnDeleteMapset)
  205. self.lblocations.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectLocation)
  206. self.lbmapsets.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelectMapset)
  207. self.lbmapsets.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnStart)
  208. self.tgisdbase.Bind(wx.EVT_TEXT_ENTER, self.OnSetDatabase)
  209. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  210. def _set_properties(self, version, revision):
  211. """Set frame properties
  212. :param version: Version in the form of X.Y.Z
  213. :param revision: Version control revision with leading space
  214. *revision* should be an empty string in case of release and
  215. otherwise it needs a leading space to be separated from the rest
  216. of the title.
  217. """
  218. self.SetTitle(_("GRASS GIS %s Startup%s") % (version, revision))
  219. self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, "grass.ico"),
  220. wx.BITMAP_TYPE_ICO))
  221. self.bstart.SetForegroundColour(wx.Colour(35, 142, 35))
  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. try:
  518. if (delete_mapset_interactively(self, self.gisdbase,
  519. location, mapset)):
  520. self.OnSelectLocation(None)
  521. self.lbmapsets.SetSelection(0)
  522. except Exception as e:
  523. GError(parent=self,
  524. message=_("Unable to delete mapset: %s") % e,
  525. showTraceback=False)
  526. def OnDeleteLocation(self, event):
  527. """
  528. Delete selected location
  529. """
  530. location = self.listOfLocations[self.lblocations.GetSelection()]
  531. try:
  532. if (delete_location_interactively(self, self.gisdbase, location)):
  533. self.UpdateLocations(self.gisdbase)
  534. self.lblocations.SetSelection(0)
  535. self.OnSelectLocation(None)
  536. self.lbmapsets.SetSelection(0)
  537. except Exception as e:
  538. GError(parent=self,
  539. message=_("Unable to delete location: %s") % e,
  540. showTraceback=False)
  541. def OnDownloadLocation(self, event):
  542. """
  543. Download location online
  544. """
  545. grassdatabase, location, mapset = download_location_interactively(
  546. self, self.gisdbase
  547. )
  548. if location:
  549. # get the new location to the list
  550. self.UpdateLocations(grassdatabase)
  551. # seems to be used in similar context
  552. self.UpdateMapsets(os.path.join(grassdatabase, location))
  553. self.lblocations.SetSelection(
  554. self.listOfLocations.index(location))
  555. # wizard does this as well, not sure if needed
  556. self.SetLocation(grassdatabase, location, mapset)
  557. # seems to be used in similar context
  558. self.OnSelectLocation(None)
  559. def UpdateLocations(self, dbase):
  560. """Update list of locations"""
  561. try:
  562. self.listOfLocations = GetListOfLocations(dbase)
  563. except (UnicodeEncodeError, UnicodeDecodeError) as e:
  564. GError(parent=self,
  565. message=_("Unicode error detected. "
  566. "Check your locale settings. Details: {0}").format(e),
  567. showTraceback=False)
  568. self.lblocations.Clear()
  569. self.lblocations.InsertItems(self.listOfLocations, 0)
  570. if len(self.listOfLocations) > 0:
  571. self._hideMessage()
  572. self.lblocations.SetSelection(0)
  573. else:
  574. self.lblocations.SetSelection(wx.NOT_FOUND)
  575. self._showWarning(_("No GRASS Location found in '%s'."
  576. " Create a new Location or choose different"
  577. " GRASS database directory.")
  578. % self.gisdbase)
  579. return self.listOfLocations
  580. def UpdateMapsets(self, location):
  581. """Update list of mapsets"""
  582. self.FormerMapsetSelection = wx.NOT_FOUND # for non-selectable item
  583. self.listOfMapsetsSelectable = list()
  584. self.listOfMapsets = GetListOfMapsets(self.gisdbase, location)
  585. self.lbmapsets.Clear()
  586. # disable mapset with denied permission
  587. locationName = os.path.basename(location)
  588. ret = RunCommand('g.mapset',
  589. read=True,
  590. flags='l',
  591. location=locationName,
  592. gisdbase=self.gisdbase)
  593. if ret:
  594. for line in ret.splitlines():
  595. self.listOfMapsetsSelectable += line.split(' ')
  596. else:
  597. self.SetLocation(self.gisdbase, locationName, "PERMANENT")
  598. # first run only
  599. self.listOfMapsetsSelectable = copy.copy(self.listOfMapsets)
  600. disabled = []
  601. idx = 0
  602. for mapset in self.listOfMapsets:
  603. if mapset not in self.listOfMapsetsSelectable or \
  604. get_lockfile_if_present(self.gisdbase,
  605. locationName, mapset):
  606. disabled.append(idx)
  607. idx += 1
  608. self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled)
  609. return self.listOfMapsets
  610. def OnSelectLocation(self, event):
  611. """Location selected"""
  612. if event:
  613. self.lblocations.SetSelection(event.GetIndex())
  614. if self.lblocations.GetSelection() != wx.NOT_FOUND:
  615. self.UpdateMapsets(
  616. os.path.join(
  617. self.gisdbase,
  618. self.listOfLocations[
  619. self.lblocations.GetSelection()]))
  620. else:
  621. self.listOfMapsets = []
  622. disabled = []
  623. idx = 0
  624. try:
  625. locationName = self.listOfLocations[
  626. self.lblocations.GetSelection()]
  627. except IndexError:
  628. locationName = ''
  629. for mapset in self.listOfMapsets:
  630. if mapset not in self.listOfMapsetsSelectable or \
  631. get_lockfile_if_present(self.gisdbase,
  632. locationName, mapset):
  633. disabled.append(idx)
  634. idx += 1
  635. self.lbmapsets.Clear()
  636. self.lbmapsets.InsertItems(self.listOfMapsets, 0, disabled=disabled)
  637. if len(self.listOfMapsets) > 0:
  638. self.lbmapsets.SetSelection(0)
  639. if locationName:
  640. # enable start button when location and mapset is selected
  641. self.bstart.Enable()
  642. self.bstart.SetFocus()
  643. self.bmapset.Enable()
  644. # replacing disabled choice, perhaps just mapset needed
  645. self.rename_location_button.Enable()
  646. self.delete_location_button.Enable()
  647. self.rename_mapset_button.Enable()
  648. self.delete_mapset_button.Enable()
  649. else:
  650. self.lbmapsets.SetSelection(wx.NOT_FOUND)
  651. self.bstart.Enable(False)
  652. self.bmapset.Enable(False)
  653. # this all was originally a choice, perhaps just mapset needed
  654. self.rename_location_button.Enable(False)
  655. self.delete_location_button.Enable(False)
  656. self.rename_mapset_button.Enable(False)
  657. self.delete_mapset_button.Enable(False)
  658. def OnSelectMapset(self, event):
  659. """Mapset selected"""
  660. self.lbmapsets.SetSelection(event.GetIndex())
  661. if event.GetText() not in self.listOfMapsetsSelectable:
  662. self.lbmapsets.SetSelection(self.FormerMapsetSelection)
  663. else:
  664. self.FormerMapsetSelection = event.GetIndex()
  665. event.Skip()
  666. def OnSetDatabase(self, event):
  667. """Database set"""
  668. gisdbase = self.tgisdbase.GetValue()
  669. self._hideMessage()
  670. if not os.path.exists(gisdbase):
  671. self._showError(_("Path '%s' doesn't exist.") % gisdbase)
  672. return
  673. self.gisdbase = self.tgisdbase.GetValue()
  674. self.UpdateLocations(self.gisdbase)
  675. self.OnSelectLocation(None)
  676. def OnBrowse(self, event):
  677. """'Browse' button clicked"""
  678. if not event:
  679. defaultPath = os.getenv('HOME')
  680. else:
  681. defaultPath = ""
  682. dlg = wx.DirDialog(parent=self, message=_("Choose GIS Data Directory"),
  683. defaultPath=defaultPath, style=wx.DD_DEFAULT_STYLE)
  684. if dlg.ShowModal() == wx.ID_OK:
  685. self.gisdbase = dlg.GetPath()
  686. self.tgisdbase.SetValue(self.gisdbase)
  687. self.OnSetDatabase(event)
  688. dlg.Destroy()
  689. def OnCreateMapset(self, event):
  690. """Create new mapset"""
  691. gisdbase = self.tgisdbase.GetValue()
  692. location = self.listOfLocations[self.lblocations.GetSelection()]
  693. try:
  694. mapset = create_mapset_interactively(self, gisdbase, location)
  695. if mapset:
  696. self.OnSelectLocation(None)
  697. self.lbmapsets.SetSelection(self.listOfMapsets.index(mapset))
  698. self.bstart.SetFocus()
  699. except Exception as e:
  700. GError(parent=self,
  701. message=_("Unable to create new mapset: %s") % e,
  702. showTraceback=False)
  703. def OnStart(self, event):
  704. """'Start GRASS' button clicked"""
  705. dbase = self.tgisdbase.GetValue()
  706. location = self.listOfLocations[self.lblocations.GetSelection()]
  707. mapset = self.listOfMapsets[self.lbmapsets.GetSelection()]
  708. lockfile = get_lockfile_if_present(dbase, location, mapset)
  709. if lockfile:
  710. dlg = wx.MessageDialog(
  711. parent=self,
  712. message=_(
  713. "GRASS is already running in selected mapset <%(mapset)s>\n"
  714. "(file %(lock)s found).\n\n"
  715. "Concurrent use not allowed.\n\n"
  716. "Do you want to try to remove .gislock (note that you "
  717. "need permission for this operation) and continue?") %
  718. {'mapset': mapset, 'lock': lockfile},
  719. caption=_("Lock file found"),
  720. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  721. ret = dlg.ShowModal()
  722. dlg.Destroy()
  723. if ret == wx.ID_YES:
  724. dlg1 = wx.MessageDialog(
  725. parent=self,
  726. message=_(
  727. "ARE YOU REALLY SURE?\n\n"
  728. "If you really are running another GRASS session doing this "
  729. "could corrupt your data. Have another look in the processor "
  730. "manager just to be sure..."),
  731. caption=_("Lock file found"),
  732. style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  733. ret = dlg1.ShowModal()
  734. dlg1.Destroy()
  735. if ret == wx.ID_YES:
  736. try:
  737. os.remove(lockfile)
  738. except IOError as e:
  739. GError(_("Unable to remove '%(lock)s'.\n\n"
  740. "Details: %(reason)s") % {'lock': lockfile, 'reason': e})
  741. else:
  742. return
  743. else:
  744. return
  745. self.SetLocation(dbase, location, mapset)
  746. self.ExitSuccessfully()
  747. def SetLocation(self, dbase, location, mapset):
  748. SetSessionMapset(dbase, location, mapset)
  749. def ExitSuccessfully(self):
  750. self.Destroy()
  751. sys.exit(self.exit_success)
  752. def OnExit(self, event):
  753. """'Exit' button clicked"""
  754. self.Destroy()
  755. sys.exit(self.exit_user_requested)
  756. def OnHelp(self, event):
  757. """'Help' button clicked"""
  758. # help text in lib/init/helptext.html
  759. RunCommand('g.manual', entry='helptext')
  760. def OnCloseWindow(self, event):
  761. """Close window event"""
  762. event.Skip()
  763. sys.exit(self.exit_user_requested)
  764. class GListBox(ListCtrl, listmix.ListCtrlAutoWidthMixin):
  765. """Use wx.ListCtrl instead of wx.ListBox, different style for
  766. non-selectable items (e.g. mapsets with denied permission)"""
  767. def __init__(self, parent, id, size,
  768. choices, disabled=[]):
  769. ListCtrl.__init__(
  770. self, parent, id, size=size, style=wx.LC_REPORT | wx.LC_NO_HEADER |
  771. wx.LC_SINGLE_SEL | wx.BORDER_SUNKEN)
  772. listmix.ListCtrlAutoWidthMixin.__init__(self)
  773. self.InsertColumn(0, '')
  774. self.selected = wx.NOT_FOUND
  775. self._LoadData(choices, disabled)
  776. def _LoadData(self, choices, disabled=[]):
  777. """Load data into list
  778. :param choices: list of item
  779. :param disabled: list of indices of non-selectable items
  780. """
  781. idx = 0
  782. count = self.GetItemCount()
  783. for item in choices:
  784. index = self.InsertItem(count + idx, item)
  785. self.SetItem(index, 0, item)
  786. if idx in disabled:
  787. self.SetItemTextColour(idx, wx.Colour(150, 150, 150))
  788. idx += 1
  789. def Clear(self):
  790. self.DeleteAllItems()
  791. def InsertItems(self, choices, pos, disabled=[]):
  792. self._LoadData(choices, disabled)
  793. def SetSelection(self, item, force=False):
  794. if item != wx.NOT_FOUND and \
  795. (platform.system() != 'Windows' or force):
  796. # Windows -> FIXME
  797. self.SetItemState(
  798. item,
  799. wx.LIST_STATE_SELECTED,
  800. wx.LIST_STATE_SELECTED)
  801. self.selected = item
  802. def GetSelection(self):
  803. return self.selected
  804. class StartUp(wx.App):
  805. """Start-up application"""
  806. def OnInit(self):
  807. StartUp = GRASSStartup()
  808. StartUp.CenterOnScreen()
  809. self.SetTopWindow(StartUp)
  810. StartUp.Show()
  811. StartUp.SuggestDatabase()
  812. return 1
  813. if __name__ == "__main__":
  814. if os.getenv("GISBASE") is None:
  815. sys.exit("Failed to start GUI, GRASS GIS is not running.")
  816. GRASSStartUp = StartUp(0)
  817. GRASSStartUp.MainLoop()