gis_set.py 36 KB

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