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. },
  126. #
  127. # datacatalog
  128. #
  129. "datacatalog": {
  130. # grassdb string
  131. "grassdbs": {"listAsString": ""},
  132. "lazyLoading": {"enabled": False, "asked": False},
  133. },
  134. "manager": {
  135. # show opacity level widget
  136. "changeOpacityLevel": {"enabled": False},
  137. # ask when removing layer from layer tree
  138. "askOnRemoveLayer": {"enabled": True},
  139. # ask when quiting wxGUI or closing display
  140. "askOnQuit": {"enabled": True},
  141. # hide tabs
  142. "hideTabs": {
  143. "search": False,
  144. "pyshell": False,
  145. },
  146. "copySelectedTextToClipboard": {"enabled": False},
  147. },
  148. #
  149. # appearance
  150. #
  151. "appearance": {
  152. "outputfont": {
  153. "type": "Courier New",
  154. "size": 10,
  155. },
  156. # expand/collapse element list
  157. "elementListExpand": {"selection": 0},
  158. "menustyle": {"selection": 1},
  159. "gSelectPopupHeight": {"value": 200},
  160. "iconTheme": {"type": "grass"},
  161. "commandNotebook": {
  162. "selection": 0 if sys.platform in ("win32", "darwin") else 1
  163. },
  164. },
  165. #
  166. # language
  167. #
  168. "language": {"locale": {"lc_all": id_loc}},
  169. #
  170. # display
  171. #
  172. "display": {
  173. "font": {
  174. "type": "",
  175. "encoding": "UTF-8",
  176. },
  177. "driver": {"type": "cairo"},
  178. "alignExtent": {"enabled": True},
  179. "compResolution": {"enabled": False},
  180. "autoRendering": {"enabled": True},
  181. "autoZooming": {"enabled": False},
  182. "showCompExtent": {"enabled": True},
  183. "statusbarMode": {"selection": 0},
  184. "bgcolor": {
  185. "color": (255, 255, 255, 255),
  186. },
  187. "mouseWheelZoom": {
  188. "selection": 1,
  189. },
  190. "scrollDirection": {
  191. "selection": 0,
  192. },
  193. "nvizDepthBuffer": {
  194. "value": 16,
  195. },
  196. },
  197. #
  198. # projection
  199. #
  200. "projection": {
  201. "statusbar": {
  202. "proj4": "",
  203. "epsg": "",
  204. "projFile": projFile,
  205. },
  206. "format": {
  207. "ll": "DMS",
  208. "precision": 2,
  209. },
  210. },
  211. #
  212. # Attribute Table Manager
  213. #
  214. "atm": {
  215. "highlight": {
  216. "color": (255, 255, 0, 255),
  217. "width": 2,
  218. "auto": True,
  219. },
  220. "leftDbClick": {"selection": 1}, # draw selected
  221. "askOnDeleteRec": {"enabled": True},
  222. "keycolumn": {"value": "cat"},
  223. "encoding": {
  224. "value": "",
  225. },
  226. },
  227. #
  228. # Command
  229. #
  230. "cmd": {
  231. "overwrite": {"enabled": False},
  232. "closeDlg": {"enabled": False},
  233. "verbosity": {"selection": "grassenv"},
  234. "addNewLayer": {
  235. "enabled": True,
  236. },
  237. "interactiveInput": {
  238. "enabled": True,
  239. },
  240. },
  241. #
  242. # d.rast
  243. #
  244. "rasterLayer": {
  245. "opaque": {"enabled": False},
  246. "colorTable": {"enabled": False, "selection": "rainbow"},
  247. },
  248. #
  249. # d.vect
  250. #
  251. "vectorLayer": {
  252. "featureColor": {
  253. "color": (0, 29, 57),
  254. "transparent": {"enabled": False},
  255. },
  256. "areaFillColor": {
  257. "color": (0, 103, 204),
  258. "transparent": {"enabled": False},
  259. },
  260. "line": {
  261. "width": 0,
  262. },
  263. "point": {
  264. "symbol": "basic/x",
  265. "size": 5,
  266. },
  267. "showType": {
  268. "point": {"enabled": True},
  269. "line": {"enabled": True},
  270. "centroid": {"enabled": False},
  271. "boundary": {"enabled": False},
  272. "area": {"enabled": True},
  273. "face": {"enabled": True},
  274. },
  275. "randomColors": {
  276. "enabled": False,
  277. },
  278. },
  279. #
  280. # vdigit
  281. #
  282. "vdigit": {
  283. # symbology
  284. "symbol": {
  285. "newSegment": {"enabled": None, "color": (255, 0, 0, 255)}, # red
  286. "newLine": {
  287. "enabled": None,
  288. "color": (0, 86, 45, 255),
  289. }, # dark green
  290. "highlight": {
  291. "enabled": None,
  292. "color": (255, 255, 0, 255),
  293. }, # yellow
  294. "highlightDupl": {
  295. "enabled": None,
  296. "color": (255, 72, 0, 255),
  297. }, # red
  298. "point": {"enabled": True, "color": (0, 0, 0, 255)}, # black
  299. "line": {"enabled": True, "color": (0, 0, 0, 255)}, # black
  300. "boundaryNo": {
  301. "enabled": True,
  302. "color": (126, 126, 126, 255),
  303. }, # grey
  304. "boundaryOne": {
  305. "enabled": True,
  306. "color": (0, 255, 0, 255),
  307. }, # green
  308. "boundaryTwo": {
  309. "enabled": True,
  310. "color": (255, 135, 0, 255),
  311. }, # orange
  312. "centroidIn": {"enabled": True, "color": (0, 0, 255, 255)}, # blue
  313. "centroidOut": {
  314. "enabled": True,
  315. "color": (165, 42, 42, 255),
  316. }, # brown
  317. "centroidDup": {
  318. "enabled": True,
  319. "color": (156, 62, 206, 255),
  320. }, # violet
  321. "nodeOne": {"enabled": True, "color": (255, 0, 0, 255)}, # red
  322. "nodeTwo": {
  323. "enabled": True,
  324. "color": (0, 86, 45, 255),
  325. }, # dark green
  326. "vertex": {
  327. "enabled": False,
  328. "color": (255, 20, 147, 255),
  329. }, # deep pink
  330. "area": {"enabled": True, "color": (217, 255, 217, 255)}, # green
  331. "direction": {"enabled": False, "color": (255, 0, 0, 255)}, # red
  332. },
  333. # display
  334. "lineWidth": {"value": 2, "units": "screen pixels"},
  335. # snapping
  336. "snapping": {
  337. "value": 10,
  338. "unit": 0, # new
  339. "units": "screen pixels", # old for backwards comp.
  340. },
  341. "snapToVertex": {"enabled": True},
  342. # digitize new record
  343. "addRecord": {"enabled": True},
  344. "layer": {"value": 1},
  345. "category": {"value": 1},
  346. "categoryMode": {"selection": 0},
  347. # delete existing feature(s)
  348. "delRecord": {"enabled": True},
  349. # query tool
  350. "query": {"selection": 0, "box": True},
  351. "queryLength": {"than-selection": 0, "thresh": 0},
  352. "queryDangle": {"than-selection": 0, "thresh": 0},
  353. # select feature (point, line, centroid, boundary)
  354. "selectType": {
  355. "point": {"enabled": True},
  356. "line": {"enabled": True},
  357. "centroid": {"enabled": True},
  358. "boundary": {"enabled": True},
  359. },
  360. "selectThresh": {
  361. "value": 10,
  362. "unit": 0, # new
  363. "units": "screen pixels", # old for backwards comp.
  364. },
  365. "checkForDupl": {"enabled": False},
  366. "selectInside": {"enabled": False},
  367. # exit
  368. "saveOnExit": {
  369. "enabled": False,
  370. },
  371. # break lines on intersection
  372. "breakLines": {
  373. "enabled": True,
  374. },
  375. # close boundary (snap to the first node)
  376. "closeBoundary": {
  377. "enabled": False,
  378. },
  379. },
  380. #
  381. # plots for profiles, histograms, and scatterplots
  382. #
  383. "profile": {
  384. "raster": {
  385. "pcolor": (0, 0, 255, 255), # line color
  386. "pwidth": 1, # line width
  387. "pstyle": "solid", # line pen style
  388. "datatype": "cell", # raster type
  389. },
  390. "font": {
  391. "titleSize": 12,
  392. "axisSize": 11,
  393. "legendSize": 10,
  394. "defaultSize": 11,
  395. "family": wx.FONTFAMILY_SWISS,
  396. "style": wx.FONTSTYLE_NORMAL,
  397. "weight": wx.FONTWEIGHT_NORMAL,
  398. },
  399. "marker": {
  400. "color": (0, 0, 0, 255),
  401. "fill": "transparent",
  402. "size": 2,
  403. "type": "triangle",
  404. "legend": _("Segment break"),
  405. },
  406. "grid": {
  407. "color": (200, 200, 200, 255),
  408. "enabled": True,
  409. },
  410. "x-axis": {
  411. "type": "auto", # axis format
  412. "min": 0, # axis min for custom axis range
  413. "max": 0, # axis max for custom axis range
  414. "log": False,
  415. },
  416. "y-axis": {
  417. "type": "auto", # axis format
  418. "min": 0, # axis min for custom axis range
  419. "max": 0, # axis max for custom axis range
  420. "log": False,
  421. },
  422. "legend": {"enabled": True},
  423. },
  424. "histogram": {
  425. "raster": {
  426. "pcolor": (0, 0, 255, 255), # line color
  427. "pwidth": 1, # line width
  428. "pstyle": "solid", # line pen style
  429. "datatype": "cell", # raster type
  430. },
  431. "font": {
  432. "titleSize": 12,
  433. "axisSize": 11,
  434. "legendSize": 10,
  435. "defaultSize": 11,
  436. "family": wx.FONTFAMILY_SWISS,
  437. "style": wx.FONTSTYLE_NORMAL,
  438. "weight": wx.FONTWEIGHT_NORMAL,
  439. },
  440. "grid": {
  441. "color": (200, 200, 200, 255),
  442. "enabled": True,
  443. },
  444. "x-axis": {
  445. "type": "auto", # axis format
  446. "min": 0, # axis min for custom axis range
  447. "max": 0, # axis max for custom axis range
  448. "log": False,
  449. },
  450. "y-axis": {
  451. "type": "auto", # axis format
  452. "min": 0, # axis min for custom axis range
  453. "max": 0, # axis max for custom axis range
  454. "log": False,
  455. },
  456. "legend": {"enabled": True},
  457. },
  458. "scatter": {
  459. "raster": {
  460. "pcolor": (0, 0, 255, 255),
  461. "pfill": "solid",
  462. "psize": 1,
  463. "ptype": "dot",
  464. # FIXME: this is only a quick fix
  465. # using also names used in a base class for compatibility
  466. # probably used only for initialization
  467. # base should be rewritten to not require this
  468. "pwidth": 1, # required by wxplot/base, maybe useless here
  469. "pstyle": "dot", # line pen style
  470. "plegend": _("Data point"),
  471. 0: {"datatype": "CELL"},
  472. 1: {"datatype": "CELL"},
  473. },
  474. "font": {
  475. "titleSize": 12,
  476. "axisSize": 11,
  477. "legendSize": 10,
  478. "defaultSize": 11,
  479. "family": wx.FONTFAMILY_SWISS,
  480. "style": wx.FONTSTYLE_NORMAL,
  481. "weight": wx.FONTWEIGHT_NORMAL,
  482. },
  483. "grid": {
  484. "color": (200, 200, 200, 255),
  485. "enabled": True,
  486. },
  487. "x-axis": {
  488. "type": "auto", # axis format
  489. "min": 0, # axis min for custom axis range
  490. "max": 0, # axis max for custom axis range
  491. "log": False,
  492. },
  493. "y-axis": {
  494. "type": "auto", # axis format
  495. "min": 0, # axis min for custom axis range
  496. "max": 0, # axis max for custom axis range
  497. "log": False,
  498. },
  499. "legend": {"enabled": True},
  500. },
  501. "gcpman": {
  502. "rms": {
  503. "highestonly": True,
  504. "sdfactor": 1,
  505. },
  506. "symbol": {
  507. "color": (0, 0, 255, 255),
  508. "hcolor": (255, 0, 0, 255),
  509. "scolor": (0, 255, 0, 255),
  510. "ucolor": (255, 165, 0, 255),
  511. "unused": True,
  512. "size": 8,
  513. "width": 2,
  514. },
  515. "map": {
  516. "overwrite": False,
  517. },
  518. },
  519. "nviz": {
  520. "view": {
  521. "persp": {
  522. "value": 20,
  523. "step": 2,
  524. },
  525. "position": {
  526. "x": 0.84,
  527. "y": 0.16,
  528. },
  529. "twist": {
  530. "value": 0,
  531. },
  532. "z-exag": {
  533. "min": 0,
  534. "max": 10,
  535. "value": 1,
  536. },
  537. "background": {
  538. "color": (255, 255, 255, 255), # white
  539. },
  540. },
  541. "fly": {
  542. "exag": {
  543. "move": 5,
  544. "turn": 5,
  545. }
  546. },
  547. "animation": {"fps": 24, "prefix": _("animation")},
  548. "surface": {
  549. "shine": {
  550. "map": False,
  551. "value": 60.0,
  552. },
  553. "color": {
  554. "map": True,
  555. "value": (100, 100, 100, 255), # constant: grey
  556. },
  557. "draw": {
  558. "wire-color": (136, 136, 136, 255),
  559. "mode": 1, # fine
  560. "style": 1, # surface
  561. "shading": 1, # gouraud
  562. "res-fine": 6,
  563. "res-coarse": 9,
  564. },
  565. "position": {
  566. "x": 0,
  567. "y": 0,
  568. "z": 0,
  569. },
  570. },
  571. "constant": {
  572. "color": (100, 100, 100, 255),
  573. "value": 0.0,
  574. "transp": 0,
  575. "resolution": 6,
  576. },
  577. "vector": {
  578. "lines": {
  579. "show": False,
  580. "width": 2,
  581. "color": (0, 0, 0, 255),
  582. "flat": False,
  583. "height": 0,
  584. "rgbcolumn": None,
  585. "sizecolumn": None,
  586. },
  587. "points": {
  588. "show": False,
  589. "size": 100,
  590. "autosize": True,
  591. "width": 2,
  592. "marker": 2,
  593. "color": (0, 0, 0, 255),
  594. "height": 0,
  595. "rgbcolumn": None,
  596. "sizecolumn": None,
  597. },
  598. },
  599. "volume": {
  600. "color": {
  601. "map": True,
  602. "value": (100, 100, 100, 255), # constant: grey
  603. },
  604. "draw": {
  605. "mode": 0, # isosurfaces
  606. "shading": 1, # gouraud
  607. "resolution": 3, # polygon resolution
  608. "box": False, # draw wire box
  609. },
  610. "shine": {
  611. "map": False,
  612. "value": 60,
  613. },
  614. "topo": {"map": None, "value": 0.0},
  615. "transp": {"map": None, "value": 0},
  616. "mask": {"map": None, "value": ""},
  617. "slice_position": {
  618. "x1": 0,
  619. "x2": 1,
  620. "y1": 0,
  621. "y2": 1,
  622. "z1": 0,
  623. "z2": 1,
  624. "axis": 0,
  625. },
  626. },
  627. "cplane": {
  628. "shading": 4,
  629. "rotation": {"rot": 180, "tilt": 0},
  630. "position": {"x": 0, "y": 0, "z": 0},
  631. },
  632. "light": {
  633. "position": {
  634. "x": 0.68,
  635. "y": -0.68,
  636. "z": 80,
  637. },
  638. "bright": 80,
  639. "color": (255, 255, 255, 255), # white
  640. "ambient": 20,
  641. },
  642. "fringe": {
  643. "elev": 55,
  644. "color": (128, 128, 128, 255), # grey
  645. },
  646. "arrow": {
  647. "color": (0, 0, 0),
  648. },
  649. "scalebar": {
  650. "color": (0, 0, 0),
  651. },
  652. },
  653. "modeler": {
  654. "disabled": {
  655. "color": (211, 211, 211, 255), # light grey
  656. },
  657. "action": {
  658. "color": {
  659. "valid": (180, 234, 154, 255), # light green
  660. "invalid": (255, 255, 255, 255), # white
  661. "running": (255, 0, 0, 255), # red
  662. },
  663. "size": {
  664. "width": 125,
  665. "height": 50,
  666. },
  667. "width": {
  668. "parameterized": 2,
  669. "default": 1,
  670. },
  671. },
  672. "data": {
  673. "color": {
  674. "raster": (215, 215, 248, 255), # light blue
  675. "raster3d": (215, 248, 215, 255), # light green
  676. "vector": (248, 215, 215, 255), # light red
  677. "dbtable": (255, 253, 194, 255), # light yellow
  678. },
  679. "size": {
  680. "width": 175,
  681. "height": 50,
  682. },
  683. },
  684. "loop": {
  685. "color": {
  686. "valid": (234, 226, 154, 255), # dark yellow
  687. },
  688. "size": {
  689. "width": 175,
  690. "height": 40,
  691. },
  692. },
  693. "if-else": {
  694. "size": {
  695. "width": 150,
  696. "height": 40,
  697. },
  698. },
  699. "comment": {
  700. "color": (255, 233, 208, 255), # light yellow
  701. "size": {
  702. "width": 200,
  703. "height": 100,
  704. },
  705. },
  706. },
  707. "mapswipe": {
  708. "cursor": {
  709. "color": (0, 0, 0, 255),
  710. "size": 12,
  711. "width": 1,
  712. "type": {
  713. "selection": 0,
  714. },
  715. },
  716. },
  717. "animation": {
  718. "bgcolor": {
  719. "color": (255, 255, 255, 255),
  720. },
  721. "nprocs": {
  722. "value": -1,
  723. },
  724. "font": {
  725. "bgcolor": (255, 255, 255, 255),
  726. "fgcolor": (0, 0, 0, 255),
  727. },
  728. "temporal": {
  729. "format": "%Y-%m-%d %H:%M:%S",
  730. "nodata": {"enable": False},
  731. },
  732. },
  733. }
  734. # quick fix, http://trac.osgeo.org/grass/ticket/1233
  735. # TODO
  736. if sys.platform == "darwin":
  737. self.defaultSettings["general"]["defWindowPos"]["enabled"] = False
  738. def _internalSettings(self):
  739. """Define internal settings (based on user settings)"""
  740. self.internalSettings = {}
  741. for group in list(self.userSettings.keys()):
  742. self.internalSettings[group] = {}
  743. for key in list(self.userSettings[group].keys()):
  744. self.internalSettings[group][key] = {}
  745. # self.internalSettings['general']["mapsetPath"]['value'] = self.GetMapsetPath()
  746. self.internalSettings["appearance"]["elementListExpand"]["choices"] = (
  747. _("Collapse all except PERMANENT and current"),
  748. _("Collapse all except PERMANENT"),
  749. _("Collapse all except current"),
  750. _("Collapse all"),
  751. _("Expand all"),
  752. )
  753. self.internalSettings["language"]["locale"]["choices"] = tuple(self.locs)
  754. self.internalSettings["atm"]["leftDbClick"]["choices"] = (
  755. _("Edit selected record"),
  756. _("Display selected"),
  757. )
  758. self.internalSettings["cmd"]["verbosity"]["choices"] = (
  759. "grassenv",
  760. "verbose",
  761. "quiet",
  762. )
  763. self.internalSettings["appearance"]["iconTheme"]["choices"] = ("grass",)
  764. self.internalSettings["appearance"]["menustyle"]["choices"] = (
  765. _("Classic (labels only)"),
  766. _("Combined (labels and module names)"),
  767. _("Expert (module names only)"),
  768. )
  769. self.internalSettings["appearance"]["gSelectPopupHeight"]["min"] = 50
  770. # there is also maxHeight given to TreeCtrlComboPopup.GetAdjustedSize
  771. self.internalSettings["appearance"]["gSelectPopupHeight"]["max"] = 1000
  772. self.internalSettings["appearance"]["commandNotebook"]["choices"] = (
  773. _("Basic top"),
  774. _("Basic left"),
  775. _("Fancy green"),
  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