settings.py 43 KB

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