settings.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. """
  2. @package core.settings
  3. @brief Default GUI settings
  4. List of classes:
  5. - settings::Settings
  6. Usage:
  7. @code
  8. from core.settings import UserSettings
  9. @endcode
  10. (C) 2007-2017 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 Martin Landa <landa.martin gmail.com>
  14. @author Luca Delucchi <lucadeluge gmail.com> (language choice)
  15. """
  16. from __future__ import print_function
  17. import os
  18. import sys
  19. import copy
  20. import wx
  21. import json
  22. import collections.abc
  23. from core import globalvar
  24. from core.gcmd import GException, GError
  25. from core.utils import GetSettingsPath, PathJoin, rgb2str
  26. class SettingsJSONEncoder(json.JSONEncoder):
  27. """Custom JSON encoder.
  28. Encodes color represented internally as tuple
  29. to hexadecimal color (tuple is represented as
  30. list in JSON, however GRASS expects tuple for colors).
  31. """
  32. def default(self, obj):
  33. """Encode not automatically serializable objects."""
  34. # we could use dictionary mapping as in wxplot
  35. if isinstance(obj, (wx.FontFamily, wx.FontStyle, wx.FontWeight)):
  36. return int(obj)
  37. return json.JSONEncoder.default(self, obj)
  38. def iterencode(self, obj):
  39. """Encode color tuple"""
  40. def color(item):
  41. if isinstance(item, tuple):
  42. if len(item) == 3:
  43. return "#{0:02x}{1:02x}{2:02x}".format(*item)
  44. if len(item) == 4:
  45. return "#{0:02x}{1:02x}{2:02x}{3:02x}".format(*item)
  46. if isinstance(item, list):
  47. return [color(e) for e in item]
  48. if isinstance(item, dict):
  49. return {key: color(value) for key, value in item.items()}
  50. else:
  51. return item
  52. return super(SettingsJSONEncoder, self).iterencode(color(obj))
  53. def settings_JSON_decode_hook(obj):
  54. """Decode hex color saved in settings into tuple"""
  55. def colorhex2tuple(hexcode):
  56. hexcode = hexcode.lstrip("#")
  57. return tuple(int(hexcode[i : i + 2], 16) for i in range(0, len(hexcode), 2))
  58. for k, v in obj.items():
  59. if isinstance(v, str) and v.startswith("#") and len(v) in [7, 9]:
  60. obj[k] = colorhex2tuple(v)
  61. return obj
  62. class Settings:
  63. """Generic class where to store settings"""
  64. def __init__(self):
  65. # settings file
  66. self.filePath = os.path.join(GetSettingsPath(), "wx.json")
  67. self.legacyFilePath = os.path.join(GetSettingsPath(), "wx")
  68. # key/value separator
  69. self.sep = ";"
  70. # define default settings
  71. self._defaultSettings() # -> self.defaultSettings
  72. # read settings from the file
  73. self.userSettings = copy.deepcopy(self.defaultSettings)
  74. try:
  75. self.ReadSettingsFile()
  76. except GException as e:
  77. print(e.value, file=sys.stderr)
  78. # define internal settings
  79. self._internalSettings() # -> self.internalSettings
  80. def _generateLocale(self):
  81. """Generate locales"""
  82. try:
  83. self.locs = os.listdir(os.path.join(os.environ["GISBASE"], "locale"))
  84. self.locs.append("en") # GRASS doesn't ship EN po files
  85. self.locs.sort()
  86. # Add a default choice to not override system locale
  87. self.locs.insert(0, "system")
  88. except:
  89. # No NLS
  90. self.locs = ["system"]
  91. return "system"
  92. def _defaultSettings(self):
  93. """Define default settings"""
  94. try:
  95. projFile = PathJoin(os.environ["GRASS_PROJSHARE"], "epsg")
  96. except KeyError:
  97. projFile = ""
  98. id_loc = self._generateLocale()
  99. self.defaultSettings = {
  100. #
  101. # general
  102. #
  103. "general": {
  104. # use default window layout (layer manager, displays, ...)
  105. "defWindowPos": {
  106. "enabled": True,
  107. "dim": "1,1,%d,%d,%d,1,%d,%d"
  108. % (
  109. globalvar.GM_WINDOW_SIZE[0],
  110. globalvar.GM_WINDOW_SIZE[1],
  111. globalvar.GM_WINDOW_SIZE[0] + 1,
  112. globalvar.MAP_WINDOW_SIZE[0],
  113. globalvar.MAP_WINDOW_SIZE[1],
  114. ),
  115. },
  116. # workspace
  117. "workspace": {
  118. "posDisplay": {"enabled": False},
  119. "posManager": {"enabled": False},
  120. },
  121. # region
  122. "region": {
  123. "resAlign": {"enabled": False},
  124. },
  125. "singleWindow": {"enabled": False},
  126. },
  127. #
  128. # datacatalog
  129. #
  130. "datacatalog": {
  131. # grassdb string
  132. "grassdbs": {"listAsString": ""},
  133. "lazyLoading": {"enabled": False, "asked": False},
  134. },
  135. "manager": {
  136. # show opacity level widget
  137. "changeOpacityLevel": {"enabled": False},
  138. # ask when removing layer from layer tree
  139. "askOnRemoveLayer": {"enabled": True},
  140. # ask when quiting wxGUI or closing display
  141. "askOnQuit": {"enabled": True},
  142. # hide tabs
  143. "hideTabs": {
  144. "search": False,
  145. "pyshell": False,
  146. },
  147. "copySelectedTextToClipboard": {"enabled": False},
  148. },
  149. #
  150. # appearance
  151. #
  152. "appearance": {
  153. "outputfont": {
  154. "type": "Courier New",
  155. "size": 10,
  156. },
  157. # expand/collapse element list
  158. "elementListExpand": {"selection": 0},
  159. "menustyle": {"selection": 1},
  160. "gSelectPopupHeight": {"value": 200},
  161. "iconTheme": {"type": "grass"},
  162. "commandNotebook": {
  163. "selection": 0 if sys.platform in ("win32", "darwin") else 1
  164. },
  165. },
  166. #
  167. # language
  168. #
  169. "language": {"locale": {"lc_all": id_loc}},
  170. #
  171. # display
  172. #
  173. "display": {
  174. "font": {
  175. "type": "",
  176. "encoding": "UTF-8",
  177. },
  178. "driver": {"type": "cairo"},
  179. "alignExtent": {"enabled": True},
  180. "compResolution": {"enabled": False},
  181. "autoRendering": {"enabled": True},
  182. "autoZooming": {"enabled": False},
  183. "showCompExtent": {"enabled": True},
  184. "statusbarMode": {"selection": 0},
  185. "bgcolor": {
  186. "color": (255, 255, 255, 255),
  187. },
  188. "mouseWheelZoom": {
  189. "selection": 1,
  190. },
  191. "scrollDirection": {
  192. "selection": 0,
  193. },
  194. "nvizDepthBuffer": {
  195. "value": 16,
  196. },
  197. },
  198. #
  199. # projection
  200. #
  201. "projection": {
  202. "statusbar": {
  203. "proj4": "",
  204. "epsg": "",
  205. "projFile": projFile,
  206. },
  207. "format": {
  208. "ll": "DMS",
  209. "precision": 2,
  210. },
  211. },
  212. #
  213. # Attribute Table Manager
  214. #
  215. "atm": {
  216. "highlight": {
  217. "color": (255, 255, 0, 255),
  218. "width": 2,
  219. "auto": True,
  220. },
  221. "leftDbClick": {"selection": 1}, # draw selected
  222. "askOnDeleteRec": {"enabled": True},
  223. "keycolumn": {"value": "cat"},
  224. "encoding": {
  225. "value": "",
  226. },
  227. },
  228. #
  229. # Command
  230. #
  231. "cmd": {
  232. "overwrite": {"enabled": False},
  233. "closeDlg": {"enabled": False},
  234. "verbosity": {"selection": "grassenv"},
  235. "addNewLayer": {
  236. "enabled": True,
  237. },
  238. "interactiveInput": {
  239. "enabled": True,
  240. },
  241. },
  242. #
  243. # d.rast
  244. #
  245. "rasterLayer": {
  246. "opaque": {"enabled": False},
  247. "colorTable": {"enabled": False, "selection": "rainbow"},
  248. },
  249. #
  250. # d.vect
  251. #
  252. "vectorLayer": {
  253. "featureColor": {
  254. "color": (0, 29, 57),
  255. "transparent": {"enabled": False},
  256. },
  257. "areaFillColor": {
  258. "color": (0, 103, 204),
  259. "transparent": {"enabled": False},
  260. },
  261. "line": {
  262. "width": 0,
  263. },
  264. "point": {
  265. "symbol": "basic/x",
  266. "size": 5,
  267. },
  268. "showType": {
  269. "point": {"enabled": True},
  270. "line": {"enabled": True},
  271. "centroid": {"enabled": False},
  272. "boundary": {"enabled": False},
  273. "area": {"enabled": True},
  274. "face": {"enabled": True},
  275. },
  276. "randomColors": {
  277. "enabled": False,
  278. },
  279. },
  280. #
  281. # vdigit
  282. #
  283. "vdigit": {
  284. # symbology
  285. "symbol": {
  286. "newSegment": {"enabled": None, "color": (255, 0, 0, 255)}, # red
  287. "newLine": {
  288. "enabled": None,
  289. "color": (0, 86, 45, 255),
  290. }, # dark green
  291. "highlight": {
  292. "enabled": None,
  293. "color": (255, 255, 0, 255),
  294. }, # yellow
  295. "highlightDupl": {
  296. "enabled": None,
  297. "color": (255, 72, 0, 255),
  298. }, # red
  299. "point": {"enabled": True, "color": (0, 0, 0, 255)}, # black
  300. "line": {"enabled": True, "color": (0, 0, 0, 255)}, # black
  301. "boundaryNo": {
  302. "enabled": True,
  303. "color": (126, 126, 126, 255),
  304. }, # grey
  305. "boundaryOne": {
  306. "enabled": True,
  307. "color": (0, 255, 0, 255),
  308. }, # green
  309. "boundaryTwo": {
  310. "enabled": True,
  311. "color": (255, 135, 0, 255),
  312. }, # orange
  313. "centroidIn": {"enabled": True, "color": (0, 0, 255, 255)}, # blue
  314. "centroidOut": {
  315. "enabled": True,
  316. "color": (165, 42, 42, 255),
  317. }, # brown
  318. "centroidDup": {
  319. "enabled": True,
  320. "color": (156, 62, 206, 255),
  321. }, # violet
  322. "nodeOne": {"enabled": True, "color": (255, 0, 0, 255)}, # red
  323. "nodeTwo": {
  324. "enabled": True,
  325. "color": (0, 86, 45, 255),
  326. }, # dark green
  327. "vertex": {
  328. "enabled": False,
  329. "color": (255, 20, 147, 255),
  330. }, # deep pink
  331. "area": {"enabled": True, "color": (217, 255, 217, 255)}, # green
  332. "direction": {"enabled": False, "color": (255, 0, 0, 255)}, # red
  333. },
  334. # display
  335. "lineWidth": {"value": 2, "units": "screen pixels"},
  336. # snapping
  337. "snapping": {
  338. "value": 10,
  339. "unit": 0, # new
  340. "units": "screen pixels", # old for backwards comp.
  341. },
  342. "snapToVertex": {"enabled": True},
  343. # digitize new record
  344. "addRecord": {"enabled": True},
  345. "layer": {"value": 1},
  346. "category": {"value": 1},
  347. "categoryMode": {"selection": 0},
  348. # delete existing feature(s)
  349. "delRecord": {"enabled": True},
  350. # query tool
  351. "query": {"selection": 0, "box": True},
  352. "queryLength": {"than-selection": 0, "thresh": 0},
  353. "queryDangle": {"than-selection": 0, "thresh": 0},
  354. # select feature (point, line, centroid, boundary)
  355. "selectType": {
  356. "point": {"enabled": True},
  357. "line": {"enabled": True},
  358. "centroid": {"enabled": True},
  359. "boundary": {"enabled": True},
  360. },
  361. "selectThresh": {
  362. "value": 10,
  363. "unit": 0, # new
  364. "units": "screen pixels", # old for backwards comp.
  365. },
  366. "checkForDupl": {"enabled": False},
  367. "selectInside": {"enabled": False},
  368. # exit
  369. "saveOnExit": {
  370. "enabled": False,
  371. },
  372. # break lines on intersection
  373. "breakLines": {
  374. "enabled": True,
  375. },
  376. # close boundary (snap to the first node)
  377. "closeBoundary": {
  378. "enabled": False,
  379. },
  380. },
  381. #
  382. # plots for profiles, histograms, and scatterplots
  383. #
  384. "profile": {
  385. "raster": {
  386. "pcolor": (0, 0, 255, 255), # line color
  387. "pwidth": 1, # line width
  388. "pstyle": "solid", # line pen style
  389. "datatype": "cell", # raster type
  390. },
  391. "font": {
  392. "titleSize": 12,
  393. "axisSize": 11,
  394. "legendSize": 10,
  395. "defaultSize": 11,
  396. "family": wx.FONTFAMILY_SWISS,
  397. "style": wx.FONTSTYLE_NORMAL,
  398. "weight": wx.FONTWEIGHT_NORMAL,
  399. },
  400. "marker": {
  401. "color": (0, 0, 0, 255),
  402. "fill": "transparent",
  403. "size": 2,
  404. "type": "triangle",
  405. "legend": _("Segment break"),
  406. },
  407. "grid": {
  408. "color": (200, 200, 200, 255),
  409. "enabled": True,
  410. },
  411. "x-axis": {
  412. "type": "auto", # axis format
  413. "min": 0, # axis min for custom axis range
  414. "max": 0, # axis max for custom axis range
  415. "log": False,
  416. },
  417. "y-axis": {
  418. "type": "auto", # axis format
  419. "min": 0, # axis min for custom axis range
  420. "max": 0, # axis max for custom axis range
  421. "log": False,
  422. },
  423. "legend": {"enabled": True},
  424. },
  425. "histogram": {
  426. "raster": {
  427. "pcolor": (0, 0, 255, 255), # line color
  428. "pwidth": 1, # line width
  429. "pstyle": "solid", # line pen style
  430. "datatype": "cell", # raster type
  431. },
  432. "font": {
  433. "titleSize": 12,
  434. "axisSize": 11,
  435. "legendSize": 10,
  436. "defaultSize": 11,
  437. "family": wx.FONTFAMILY_SWISS,
  438. "style": wx.FONTSTYLE_NORMAL,
  439. "weight": wx.FONTWEIGHT_NORMAL,
  440. },
  441. "grid": {
  442. "color": (200, 200, 200, 255),
  443. "enabled": True,
  444. },
  445. "x-axis": {
  446. "type": "auto", # axis format
  447. "min": 0, # axis min for custom axis range
  448. "max": 0, # axis max for custom axis range
  449. "log": False,
  450. },
  451. "y-axis": {
  452. "type": "auto", # axis format
  453. "min": 0, # axis min for custom axis range
  454. "max": 0, # axis max for custom axis range
  455. "log": False,
  456. },
  457. "legend": {"enabled": True},
  458. },
  459. "scatter": {
  460. "raster": {
  461. "pcolor": (0, 0, 255, 255),
  462. "pfill": "solid",
  463. "psize": 1,
  464. "ptype": "dot",
  465. # FIXME: this is only a quick fix
  466. # using also names used in a base class for compatibility
  467. # probably used only for initialization
  468. # base should be rewritten to not require this
  469. "pwidth": 1, # required by wxplot/base, maybe useless here
  470. "pstyle": "dot", # line pen style
  471. "plegend": _("Data point"),
  472. 0: {"datatype": "CELL"},
  473. 1: {"datatype": "CELL"},
  474. },
  475. "font": {
  476. "titleSize": 12,
  477. "axisSize": 11,
  478. "legendSize": 10,
  479. "defaultSize": 11,
  480. "family": wx.FONTFAMILY_SWISS,
  481. "style": wx.FONTSTYLE_NORMAL,
  482. "weight": wx.FONTWEIGHT_NORMAL,
  483. },
  484. "grid": {
  485. "color": (200, 200, 200, 255),
  486. "enabled": True,
  487. },
  488. "x-axis": {
  489. "type": "auto", # axis format
  490. "min": 0, # axis min for custom axis range
  491. "max": 0, # axis max for custom axis range
  492. "log": False,
  493. },
  494. "y-axis": {
  495. "type": "auto", # axis format
  496. "min": 0, # axis min for custom axis range
  497. "max": 0, # axis max for custom axis range
  498. "log": False,
  499. },
  500. "legend": {"enabled": True},
  501. },
  502. "gcpman": {
  503. "rms": {
  504. "highestonly": True,
  505. "sdfactor": 1,
  506. },
  507. "symbol": {
  508. "color": (0, 0, 255, 255),
  509. "hcolor": (255, 0, 0, 255),
  510. "scolor": (0, 255, 0, 255),
  511. "ucolor": (255, 165, 0, 255),
  512. "unused": True,
  513. "size": 8,
  514. "width": 2,
  515. },
  516. "map": {
  517. "overwrite": False,
  518. },
  519. },
  520. "nviz": {
  521. "view": {
  522. "persp": {
  523. "value": 20,
  524. "step": 2,
  525. },
  526. "position": {
  527. "x": 0.84,
  528. "y": 0.16,
  529. },
  530. "twist": {
  531. "value": 0,
  532. },
  533. "z-exag": {
  534. "min": 0,
  535. "max": 10,
  536. "value": 1,
  537. },
  538. "background": {
  539. "color": (255, 255, 255, 255), # white
  540. },
  541. },
  542. "fly": {
  543. "exag": {
  544. "move": 5,
  545. "turn": 5,
  546. }
  547. },
  548. "animation": {"fps": 24, "prefix": _("animation")},
  549. "surface": {
  550. "shine": {
  551. "map": False,
  552. "value": 60.0,
  553. },
  554. "color": {
  555. "map": True,
  556. "value": (100, 100, 100, 255), # constant: grey
  557. },
  558. "draw": {
  559. "wire-color": (136, 136, 136, 255),
  560. "mode": 1, # fine
  561. "style": 1, # surface
  562. "shading": 1, # gouraud
  563. "res-fine": 6,
  564. "res-coarse": 9,
  565. },
  566. "position": {
  567. "x": 0,
  568. "y": 0,
  569. "z": 0,
  570. },
  571. },
  572. "constant": {
  573. "color": (100, 100, 100, 255),
  574. "value": 0.0,
  575. "transp": 0,
  576. "resolution": 6,
  577. },
  578. "vector": {
  579. "lines": {
  580. "show": False,
  581. "width": 2,
  582. "color": (0, 0, 0, 255),
  583. "flat": False,
  584. "height": 0,
  585. "rgbcolumn": None,
  586. "sizecolumn": None,
  587. },
  588. "points": {
  589. "show": False,
  590. "size": 100,
  591. "autosize": True,
  592. "width": 2,
  593. "marker": 2,
  594. "color": (0, 0, 0, 255),
  595. "height": 0,
  596. "rgbcolumn": None,
  597. "sizecolumn": None,
  598. },
  599. },
  600. "volume": {
  601. "color": {
  602. "map": True,
  603. "value": (100, 100, 100, 255), # constant: grey
  604. },
  605. "draw": {
  606. "mode": 0, # isosurfaces
  607. "shading": 1, # gouraud
  608. "resolution": 3, # polygon resolution
  609. "box": False, # draw wire box
  610. },
  611. "shine": {
  612. "map": False,
  613. "value": 60,
  614. },
  615. "topo": {"map": None, "value": 0.0},
  616. "transp": {"map": None, "value": 0},
  617. "mask": {"map": None, "value": ""},
  618. "slice_position": {
  619. "x1": 0,
  620. "x2": 1,
  621. "y1": 0,
  622. "y2": 1,
  623. "z1": 0,
  624. "z2": 1,
  625. "axis": 0,
  626. },
  627. },
  628. "cplane": {
  629. "shading": 4,
  630. "rotation": {"rot": 180, "tilt": 0},
  631. "position": {"x": 0, "y": 0, "z": 0},
  632. },
  633. "light": {
  634. "position": {
  635. "x": 0.68,
  636. "y": -0.68,
  637. "z": 80,
  638. },
  639. "bright": 80,
  640. "color": (255, 255, 255, 255), # white
  641. "ambient": 20,
  642. },
  643. "fringe": {
  644. "elev": 55,
  645. "color": (128, 128, 128, 255), # grey
  646. },
  647. "arrow": {
  648. "color": (0, 0, 0),
  649. },
  650. "scalebar": {
  651. "color": (0, 0, 0),
  652. },
  653. },
  654. "modeler": {
  655. "disabled": {
  656. "color": (211, 211, 211, 255), # light grey
  657. },
  658. "action": {
  659. "color": {
  660. "valid": (180, 234, 154, 255), # light green
  661. "invalid": (255, 255, 255, 255), # white
  662. "running": (255, 0, 0, 255), # red
  663. },
  664. "size": {
  665. "width": 125,
  666. "height": 50,
  667. },
  668. "width": {
  669. "parameterized": 2,
  670. "default": 1,
  671. },
  672. },
  673. "data": {
  674. "color": {
  675. "raster": (215, 215, 248, 255), # light blue
  676. "raster3d": (215, 248, 215, 255), # light green
  677. "vector": (248, 215, 215, 255), # light red
  678. "dbtable": (255, 253, 194, 255), # light yellow
  679. },
  680. "size": {
  681. "width": 175,
  682. "height": 50,
  683. },
  684. },
  685. "loop": {
  686. "color": {
  687. "valid": (234, 226, 154, 255), # dark yellow
  688. },
  689. "size": {
  690. "width": 175,
  691. "height": 40,
  692. },
  693. },
  694. "if-else": {
  695. "size": {
  696. "width": 150,
  697. "height": 40,
  698. },
  699. },
  700. "comment": {
  701. "color": (255, 233, 208, 255), # light yellow
  702. "size": {
  703. "width": 200,
  704. "height": 100,
  705. },
  706. },
  707. },
  708. "mapswipe": {
  709. "cursor": {
  710. "color": (0, 0, 0, 255),
  711. "size": 12,
  712. "width": 1,
  713. "type": {
  714. "selection": 0,
  715. },
  716. },
  717. },
  718. "animation": {
  719. "bgcolor": {
  720. "color": (255, 255, 255, 255),
  721. },
  722. "nprocs": {
  723. "value": -1,
  724. },
  725. "font": {
  726. "bgcolor": (255, 255, 255, 255),
  727. "fgcolor": (0, 0, 0, 255),
  728. },
  729. "temporal": {
  730. "format": "%Y-%m-%d %H:%M:%S",
  731. "nodata": {"enable": False},
  732. },
  733. },
  734. }
  735. # quick fix, http://trac.osgeo.org/grass/ticket/1233
  736. # TODO
  737. if sys.platform == "darwin":
  738. self.defaultSettings["general"]["defWindowPos"]["enabled"] = False
  739. def _internalSettings(self):
  740. """Define internal settings (based on user settings)"""
  741. self.internalSettings = {}
  742. for group in list(self.userSettings.keys()):
  743. self.internalSettings[group] = {}
  744. for key in list(self.userSettings[group].keys()):
  745. self.internalSettings[group][key] = {}
  746. # self.internalSettings['general']["mapsetPath"]['value'] = self.GetMapsetPath()
  747. self.internalSettings["appearance"]["elementListExpand"]["choices"] = (
  748. _("Collapse all except PERMANENT and current"),
  749. _("Collapse all except PERMANENT"),
  750. _("Collapse all except current"),
  751. _("Collapse all"),
  752. _("Expand all"),
  753. )
  754. self.internalSettings["language"]["locale"]["choices"] = tuple(self.locs)
  755. self.internalSettings["atm"]["leftDbClick"]["choices"] = (
  756. _("Edit selected record"),
  757. _("Display selected"),
  758. )
  759. self.internalSettings["cmd"]["verbosity"]["choices"] = (
  760. "grassenv",
  761. "verbose",
  762. "quiet",
  763. )
  764. self.internalSettings["appearance"]["iconTheme"]["choices"] = ("grass",)
  765. self.internalSettings["appearance"]["menustyle"]["choices"] = (
  766. _("Classic (labels only)"),
  767. _("Combined (labels and tool names)"),
  768. _("Expert (tool names only)"),
  769. )
  770. self.internalSettings["appearance"]["gSelectPopupHeight"]["min"] = 50
  771. # there is also maxHeight given to TreeCtrlComboPopup.GetAdjustedSize
  772. self.internalSettings["appearance"]["gSelectPopupHeight"]["max"] = 1000
  773. self.internalSettings["appearance"]["commandNotebook"]["choices"] = (
  774. _("Basic top"),
  775. _("Basic left"),
  776. _("List left"),
  777. )
  778. self.internalSettings["display"]["driver"]["choices"] = ["cairo", "png"]
  779. self.internalSettings["display"]["statusbarMode"][
  780. "choices"
  781. ] = None # set during MapFrame init
  782. self.internalSettings["display"]["mouseWheelZoom"]["choices"] = (
  783. _("Zoom and recenter"),
  784. _("Zoom to mouse cursor"),
  785. _("Nothing"),
  786. )
  787. self.internalSettings["display"]["scrollDirection"]["choices"] = (
  788. _("Scroll forward to zoom in"),
  789. _("Scroll back to zoom in"),
  790. )
  791. self.internalSettings["nviz"]["view"] = {}
  792. self.internalSettings["nviz"]["view"]["twist"] = {}
  793. self.internalSettings["nviz"]["view"]["twist"]["min"] = -180
  794. self.internalSettings["nviz"]["view"]["twist"]["max"] = 180
  795. self.internalSettings["nviz"]["view"]["persp"] = {}
  796. self.internalSettings["nviz"]["view"]["persp"]["min"] = 1
  797. self.internalSettings["nviz"]["view"]["persp"]["max"] = 100
  798. self.internalSettings["nviz"]["view"]["height"] = {}
  799. self.internalSettings["nviz"]["view"]["height"]["value"] = -1
  800. self.internalSettings["nviz"]["view"]["z-exag"] = {}
  801. self.internalSettings["nviz"]["view"]["z-exag"]["llRatio"] = 1
  802. self.internalSettings["nviz"]["view"]["rotation"] = None
  803. self.internalSettings["nviz"]["view"]["focus"] = {}
  804. self.internalSettings["nviz"]["view"]["focus"]["x"] = -1
  805. self.internalSettings["nviz"]["view"]["focus"]["y"] = -1
  806. self.internalSettings["nviz"]["view"]["focus"]["z"] = -1
  807. self.internalSettings["nviz"]["view"]["dir"] = {}
  808. self.internalSettings["nviz"]["view"]["dir"]["x"] = -1
  809. self.internalSettings["nviz"]["view"]["dir"]["y"] = -1
  810. self.internalSettings["nviz"]["view"]["dir"]["z"] = -1
  811. self.internalSettings["nviz"]["view"]["dir"]["use"] = False
  812. for decor in ("arrow", "scalebar"):
  813. self.internalSettings["nviz"][decor] = {}
  814. self.internalSettings["nviz"][decor]["position"] = {}
  815. self.internalSettings["nviz"][decor]["position"]["x"] = 0
  816. self.internalSettings["nviz"][decor]["position"]["y"] = 0
  817. self.internalSettings["nviz"][decor]["size"] = 100
  818. self.internalSettings["nviz"]["vector"] = {}
  819. self.internalSettings["nviz"]["vector"]["points"] = {}
  820. self.internalSettings["nviz"]["vector"]["points"]["marker"] = (
  821. "x",
  822. _("box"),
  823. _("sphere"),
  824. _("cube"),
  825. _("diamond"),
  826. _("aster"),
  827. _("gyro"),
  828. _("histogram"),
  829. )
  830. self.internalSettings["vdigit"]["bgmap"] = {}
  831. self.internalSettings["vdigit"]["bgmap"]["value"] = ""
  832. self.internalSettings["mapswipe"]["cursor"]["type"] = {}
  833. self.internalSettings["mapswipe"]["cursor"]["type"]["choices"] = (
  834. _("cross"),
  835. _("box"),
  836. _("circle"),
  837. )
  838. def ReadSettingsFile(self, settings=None):
  839. """Reads settings file (mapset, location, gisdbase)"""
  840. if settings is None:
  841. settings = self.userSettings
  842. if os.path.exists(self.filePath):
  843. self._readFile(settings)
  844. elif os.path.exists(self.legacyFilePath):
  845. self._readLegacyFile(settings)
  846. # set environment variables
  847. font = self.Get(group="display", key="font", subkey="type")
  848. enc = self.Get(group="display", key="font", subkey="encoding")
  849. if font:
  850. os.environ["GRASS_FONT"] = font
  851. if enc:
  852. os.environ["GRASS_ENCODING"] = enc
  853. def _readFile(self, settings=None):
  854. """Read settings from file (wx.json) to dict,
  855. assumes file exists.
  856. :param settings: dict where to store settings (None for self.userSettings)
  857. """
  858. def update_nested_dict_by_dict(dictionary, update):
  859. """Recursively update nested dictionary by another nested dictionary"""
  860. for key, value in update.items():
  861. if isinstance(value, collections.abc.Mapping):
  862. dictionary[key] = update_nested_dict_by_dict(
  863. dictionary.get(key, {}), value
  864. )
  865. else:
  866. dictionary[key] = value
  867. return dictionary
  868. try:
  869. with open(self.filePath, "r") as f:
  870. update = json.load(f, object_hook=settings_JSON_decode_hook)
  871. update_nested_dict_by_dict(settings, update)
  872. except json.JSONDecodeError as e:
  873. sys.stderr.write(
  874. _("Unable to read settings file <{path}>:\n{err}").format(
  875. path=self.filePath, err=e
  876. )
  877. )
  878. def _readLegacyFile(self, settings=None):
  879. """Read settings from legacy file (wx) to dict,
  880. assumes file exists.
  881. :param settings: dict where to store settings (None for self.userSettings)
  882. """
  883. if settings is None:
  884. settings = self.userSettings
  885. try:
  886. fd = open(self.legacyFilePath, "r")
  887. except IOError:
  888. sys.stderr.write(
  889. _("Unable to read settings file <%s>\n") % self.legacyFilePath
  890. )
  891. return
  892. try:
  893. line = ""
  894. for line in fd.readlines():
  895. line = line.rstrip("%s" % os.linesep)
  896. group, key = line.split(self.sep)[0:2]
  897. kv = line.split(self.sep)[2:]
  898. subkeyMaster = None
  899. if len(kv) % 2 != 0: # multiple (e.g. nviz)
  900. subkeyMaster = kv[0]
  901. del kv[0]
  902. idx = 0
  903. while idx < len(kv):
  904. if subkeyMaster:
  905. subkey = [subkeyMaster, kv[idx]]
  906. else:
  907. subkey = kv[idx]
  908. value = kv[idx + 1]
  909. value = self._parseValue(value, read=True)
  910. self.Append(settings, group, key, subkey, value)
  911. idx += 2
  912. except ValueError as e:
  913. print(
  914. _(
  915. "Error: Reading settings from file <%(file)s> failed.\n"
  916. "\t\tDetails: %(detail)s\n"
  917. "\t\tLine: '%(line)s'\n"
  918. )
  919. % {"file": self.legacyFilePath, "detail": e, "line": line},
  920. file=sys.stderr,
  921. )
  922. fd.close()
  923. fd.close()
  924. def SaveToFile(self, settings=None):
  925. """Save settings to the file"""
  926. if settings is None:
  927. settings = self.userSettings
  928. dirPath = GetSettingsPath()
  929. if not os.path.exists(dirPath):
  930. try:
  931. os.mkdir(dirPath)
  932. except:
  933. GError(_("Unable to create settings directory"))
  934. return
  935. try:
  936. with open(self.filePath, "w") as f:
  937. json.dump(settings, f, indent=2, cls=SettingsJSONEncoder)
  938. except IOError as e:
  939. raise GException(e)
  940. except Exception as e:
  941. raise GException(
  942. _(
  943. "Writing settings to file <%(file)s> failed."
  944. "\n\nDetails: %(detail)s"
  945. )
  946. % {"file": self.filePath, "detail": e}
  947. )
  948. return self.filePath
  949. def _parseValue(self, value, read=False):
  950. """Parse value to be store in settings file"""
  951. if read: # -> read settings (cast values)
  952. if value == "True":
  953. value = True
  954. elif value == "False":
  955. value = False
  956. elif value == "None":
  957. value = None
  958. elif ":" in value: # -> color
  959. try:
  960. value = tuple(map(int, value.split(":")))
  961. except ValueError: # -> string
  962. pass
  963. else:
  964. try:
  965. value = int(value)
  966. except ValueError:
  967. try:
  968. value = float(value)
  969. except ValueError:
  970. pass
  971. else: # -> write settings
  972. if isinstance(value, type(())): # -> color
  973. value = str(value[0]) + ":" + str(value[1]) + ":" + str(value[2])
  974. return value
  975. def Get(self, group, key=None, subkey=None, settings_type="user"):
  976. """Get value by key/subkey
  977. Raise KeyError if key is not found
  978. :param group: settings group
  979. :param key: (value, None)
  980. :param subkey: (value, list or None)
  981. :param settings_type: 'user', 'internal', 'default'
  982. :return: value
  983. """
  984. if settings_type == "user":
  985. settings = self.userSettings
  986. elif settings_type == "internal":
  987. settings = self.internalSettings
  988. else:
  989. settings = self.defaultSettings
  990. try:
  991. if subkey is None:
  992. if key is None:
  993. return settings[group]
  994. else:
  995. return settings[group][key]
  996. else:
  997. if isinstance(subkey, type(tuple())) or isinstance(
  998. subkey, type(list())
  999. ):
  1000. return settings[group][key][subkey[0]][subkey[1]]
  1001. else:
  1002. return settings[group][key][subkey]
  1003. except KeyError:
  1004. print(
  1005. "Settings: unable to get value '%s:%s:%s'\n" % (group, key, subkey),
  1006. file=sys.stderr,
  1007. )
  1008. def Set(self, group, value, key=None, subkey=None, settings_type="user"):
  1009. """Set value of key/subkey
  1010. Raise KeyError if group/key is not found
  1011. :param group: settings group
  1012. :param key: key (value, None)
  1013. :param subkey: subkey (value, list or None)
  1014. :param value: value
  1015. :param settings_type: 'user', 'internal', 'default'
  1016. """
  1017. if settings_type == "user":
  1018. settings = self.userSettings
  1019. elif settings_type == "internal":
  1020. settings = self.internalSettings
  1021. else:
  1022. settings = self.defaultSettings
  1023. try:
  1024. if subkey is None:
  1025. if key is None:
  1026. settings[group] = value
  1027. else:
  1028. settings[group][key] = value
  1029. else:
  1030. if isinstance(subkey, type(tuple())) or isinstance(
  1031. subkey, type(list())
  1032. ):
  1033. settings[group][key][subkey[0]][subkey[1]] = value
  1034. else:
  1035. settings[group][key][subkey] = value
  1036. except KeyError:
  1037. raise GException(
  1038. "%s '%s:%s:%s'" % (_("Unable to set "), group, key, subkey)
  1039. )
  1040. def Append(self, dict, group, key, subkey, value, overwrite=True):
  1041. """Set value of key/subkey
  1042. Create group/key/subkey if not exists
  1043. :param dict: settings dictionary to use
  1044. :param group: settings group
  1045. :param key: key
  1046. :param subkey: subkey (value or list)
  1047. :param value: value
  1048. :param overwrite: True to overwrite existing value
  1049. """
  1050. hasValue = True
  1051. if group not in dict:
  1052. dict[group] = {}
  1053. hasValue = False
  1054. if key not in dict[group]:
  1055. dict[group][key] = {}
  1056. hasValue = False
  1057. if isinstance(subkey, list):
  1058. # TODO: len(subkey) > 2
  1059. if subkey[0] not in dict[group][key]:
  1060. dict[group][key][subkey[0]] = {}
  1061. hasValue = False
  1062. if subkey[1] not in dict[group][key][subkey[0]]:
  1063. hasValue = False
  1064. try:
  1065. if overwrite or (not overwrite and not hasValue):
  1066. dict[group][key][subkey[0]][subkey[1]] = value
  1067. except TypeError:
  1068. print(
  1069. _("Unable to parse settings '%s'") % value
  1070. + " ("
  1071. + group
  1072. + ":"
  1073. + key
  1074. + ":"
  1075. + subkey[0]
  1076. + ":"
  1077. + subkey[1]
  1078. + ")",
  1079. file=sys.stderr,
  1080. )
  1081. else:
  1082. if subkey not in dict[group][key]:
  1083. hasValue = False
  1084. try:
  1085. if overwrite or (not overwrite and not hasValue):
  1086. dict[group][key][subkey] = value
  1087. except TypeError:
  1088. print(
  1089. _("Unable to parse settings '%s'") % value
  1090. + " ("
  1091. + group
  1092. + ":"
  1093. + key
  1094. + ":"
  1095. + subkey
  1096. + ")",
  1097. file=sys.stderr,
  1098. )
  1099. def GetDefaultSettings(self):
  1100. """Get default user settings"""
  1101. return self.defaultSettings
  1102. def Reset(self, key=None):
  1103. """Reset to default settings
  1104. :param key: key in settings dict (None for all keys)
  1105. """
  1106. if not key:
  1107. self.userSettings = copy.deepcopy(self.defaultSettings)
  1108. else:
  1109. self.userSettings[key] = copy.deepcopy(self.defaultSettings[key])
  1110. UserSettings = Settings()
  1111. def GetDisplayVectSettings():
  1112. settings = list()
  1113. if not UserSettings.Get(
  1114. group="vectorLayer", key="featureColor", subkey=["transparent", "enabled"]
  1115. ):
  1116. featureColor = UserSettings.Get(
  1117. group="vectorLayer", key="featureColor", subkey="color"
  1118. )
  1119. settings.append(
  1120. "color=%s" % rgb2str.get(featureColor, ":".join(map(str, featureColor)))
  1121. )
  1122. else:
  1123. settings.append("color=none")
  1124. if not UserSettings.Get(
  1125. group="vectorLayer", key="areaFillColor", subkey=["transparent", "enabled"]
  1126. ):
  1127. fillColor = UserSettings.Get(
  1128. group="vectorLayer", key="areaFillColor", subkey="color"
  1129. )
  1130. settings.append(
  1131. "fcolor=%s" % rgb2str.get(fillColor, ":".join(map(str, fillColor)))
  1132. )
  1133. else:
  1134. settings.append("fcolor=none")
  1135. settings.append(
  1136. "width=%s" % UserSettings.Get(group="vectorLayer", key="line", subkey="width")
  1137. )
  1138. settings.append(
  1139. "icon=%s" % UserSettings.Get(group="vectorLayer", key="point", subkey="symbol")
  1140. )
  1141. settings.append(
  1142. "size=%s" % UserSettings.Get(group="vectorLayer", key="point", subkey="size")
  1143. )
  1144. types = []
  1145. for ftype in ["point", "line", "boundary", "centroid", "area", "face"]:
  1146. if UserSettings.Get(
  1147. group="vectorLayer", key="showType", subkey=[ftype, "enabled"]
  1148. ):
  1149. types.append(ftype)
  1150. settings.append("type=%s" % ",".join(types))
  1151. if UserSettings.Get(group="vectorLayer", key="randomColors", subkey="enabled"):
  1152. settings.append("-c")
  1153. return settings