|
@@ -25,18 +25,65 @@ import os
|
|
|
import sys
|
|
|
import copy
|
|
|
import wx
|
|
|
+import json
|
|
|
|
|
|
from core import globalvar
|
|
|
from core.gcmd import GException, GError
|
|
|
from core.utils import GetSettingsPath, PathJoin, rgb2str
|
|
|
|
|
|
|
|
|
+class SettingsJSONEncoder(json.JSONEncoder):
|
|
|
+ """Custom JSON encoder.
|
|
|
+
|
|
|
+ Encodes color represented internally as tuple
|
|
|
+ to hexadecimal color (tuple is represented as
|
|
|
+ list in JSON, however GRASS expects tuple for colors).
|
|
|
+ """
|
|
|
+ def default(self, obj):
|
|
|
+ """Encode not automatically serializable objects.
|
|
|
+ """
|
|
|
+ # we could use dictionary mapping as in wxplot
|
|
|
+ if isinstance(obj, (wx.FontFamily, wx.FontStyle, wx.FontWeight)):
|
|
|
+ return int(obj)
|
|
|
+ return json.JSONEncoder.default(self, obj)
|
|
|
+
|
|
|
+ def iterencode(self, obj):
|
|
|
+ """Encode color tuple"""
|
|
|
+ def color(item):
|
|
|
+ if isinstance(item, tuple):
|
|
|
+ if len(item) == 3:
|
|
|
+ return "#{0:02x}{1:02x}{2:02x}".format(*item)
|
|
|
+ if len(item) == 4:
|
|
|
+ return "#{0:02x}{1:02x}{2:02x}{3:02x}".format(*item)
|
|
|
+ if isinstance(item, list):
|
|
|
+ return [color(e) for e in item]
|
|
|
+ if isinstance(item, dict):
|
|
|
+ return {key: color(value) for key, value in item.items()}
|
|
|
+ else:
|
|
|
+ return item
|
|
|
+
|
|
|
+ return super(SettingsJSONEncoder, self).iterencode(color(obj))
|
|
|
+
|
|
|
+
|
|
|
+def settings_JSON_decode_hook(obj):
|
|
|
+ """Decode hex color saved in settings into tuple"""
|
|
|
+ def colorhex2tuple(hexcode):
|
|
|
+ hexcode = hexcode.lstrip('#')
|
|
|
+ return tuple(int(hexcode[i:i + 2], 16) for i in range(0, len(hexcode), 2))
|
|
|
+
|
|
|
+ for k, v in obj.items():
|
|
|
+ if isinstance(v, str) and v.startswith('#') and len(v) in [7, 9]:
|
|
|
+ obj[k] = colorhex2tuple(v)
|
|
|
+ return obj
|
|
|
+
|
|
|
+
|
|
|
class Settings:
|
|
|
"""Generic class where to store settings"""
|
|
|
|
|
|
def __init__(self):
|
|
|
# settings file
|
|
|
- self.filePath = os.path.join(GetSettingsPath(), 'wx')
|
|
|
+ self.filePath = os.path.join(GetSettingsPath(), 'wx.json')
|
|
|
+ self.legacyFilePath = os.path.join(GetSettingsPath(), 'wx')
|
|
|
|
|
|
# key/value separator
|
|
|
self.sep = ';'
|
|
@@ -150,7 +197,7 @@ class Settings:
|
|
|
'appearance': {
|
|
|
'outputfont': {
|
|
|
'type': 'Courier New',
|
|
|
- 'size': '10',
|
|
|
+ 'size': 10,
|
|
|
},
|
|
|
# expand/collapse element list
|
|
|
'elementListExpand': {
|
|
@@ -216,7 +263,7 @@ class Settings:
|
|
|
'selection': 0,
|
|
|
},
|
|
|
'nvizDepthBuffer': {
|
|
|
- 'value': '16',
|
|
|
+ 'value': 16,
|
|
|
},
|
|
|
},
|
|
|
#
|
|
@@ -979,7 +1026,10 @@ class Settings:
|
|
|
if settings is None:
|
|
|
settings = self.userSettings
|
|
|
|
|
|
- self._readFile(self.filePath, settings)
|
|
|
+ if os.path.exists(self.filePath):
|
|
|
+ self._readFile(settings)
|
|
|
+ elif os.path.exists(self.legacyFilePath):
|
|
|
+ self._readLegacyFile(settings)
|
|
|
|
|
|
# set environment variables
|
|
|
font = self.Get(group='display', key='font', subkey='type')
|
|
@@ -989,24 +1039,34 @@ class Settings:
|
|
|
if enc:
|
|
|
os.environ["GRASS_ENCODING"] = enc
|
|
|
|
|
|
- def _readFile(self, filename, settings=None):
|
|
|
- """Read settings from file to dict
|
|
|
+ def _readFile(self, settings=None):
|
|
|
+ """Read settings from file (wx.json) to dict,
|
|
|
+ assumes file exists.
|
|
|
+
|
|
|
+ :param settings: dict where to store settings (None for self.userSettings)
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ with open(self.filePath, 'r') as f:
|
|
|
+ settings.update(json.load(f, object_hook=settings_JSON_decode_hook))
|
|
|
+ except json.JSONDecodeError as e:
|
|
|
+ sys.stderr.write(
|
|
|
+ _("Unable to read settings file <{path}>:\n{err}").format(path=self.filePath, err=e))
|
|
|
+
|
|
|
+ def _readLegacyFile(self, settings=None):
|
|
|
+ """Read settings from legacy file (wx) to dict,
|
|
|
+ assumes file exists.
|
|
|
|
|
|
- :param filename: settings file path
|
|
|
:param settings: dict where to store settings (None for self.userSettings)
|
|
|
"""
|
|
|
if settings is None:
|
|
|
settings = self.userSettings
|
|
|
|
|
|
- if not os.path.exists(filename):
|
|
|
- return
|
|
|
-
|
|
|
try:
|
|
|
- fd = open(filename, "r")
|
|
|
+ fd = open(self.legacyFilePath, "r")
|
|
|
except IOError:
|
|
|
sys.stderr.write(
|
|
|
_("Unable to read settings file <%s>\n") %
|
|
|
- filename)
|
|
|
+ self.legacyFilePath)
|
|
|
return
|
|
|
|
|
|
try:
|
|
@@ -1034,7 +1094,7 @@ class Settings:
|
|
|
"Error: Reading settings from file <%(file)s> failed.\n"
|
|
|
"\t\tDetails: %(detail)s\n"
|
|
|
"\t\tLine: '%(line)s'\n") % {
|
|
|
- 'file': filename, 'detail': e, 'line': line},
|
|
|
+ 'file': self.legacyFilePath, 'detail': e, 'line': line},
|
|
|
file=sys.stderr)
|
|
|
fd.close()
|
|
|
|
|
@@ -1052,57 +1112,15 @@ class Settings:
|
|
|
except:
|
|
|
GError(_('Unable to create settings directory'))
|
|
|
return
|
|
|
-
|
|
|
try:
|
|
|
- newline = '\n'
|
|
|
- file = open(self.filePath, "w")
|
|
|
- for group in list(settings.keys()):
|
|
|
- for key in list(settings[group].keys()):
|
|
|
- subkeys = list(settings[group][key].keys())
|
|
|
- file.write('%s%s%s%s' % (group, self.sep, key, self.sep))
|
|
|
- for idx in range(len(subkeys)):
|
|
|
- value = settings[group][key][subkeys[idx]]
|
|
|
- if isinstance(value, dict):
|
|
|
- if idx > 0:
|
|
|
- file.write(
|
|
|
- '%s%s%s%s%s' %
|
|
|
- (newline, group, self.sep, key, self.sep))
|
|
|
- file.write('%s%s' % (subkeys[idx], self.sep))
|
|
|
- kvalues = list(settings[group][key][subkeys[idx]].keys())
|
|
|
- srange = range(len(kvalues))
|
|
|
- for sidx in srange:
|
|
|
- svalue = self._parseValue(
|
|
|
- settings[group][key][
|
|
|
- subkeys[idx]][
|
|
|
- kvalues[sidx]])
|
|
|
- file.write('%s%s%s' % (kvalues[sidx], self.sep,
|
|
|
- svalue))
|
|
|
- if sidx < len(kvalues) - 1:
|
|
|
- file.write('%s' % self.sep)
|
|
|
- else:
|
|
|
- if idx > 0 and isinstance(
|
|
|
- settings[group][key][subkeys[idx - 1]],
|
|
|
- dict):
|
|
|
- file.write(
|
|
|
- '%s%s%s%s%s' %
|
|
|
- (newline, group, self.sep, key, self.sep))
|
|
|
- value = self._parseValue(
|
|
|
- settings[group][key][subkeys[idx]])
|
|
|
- file.write(
|
|
|
- '%s%s%s' %
|
|
|
- (subkeys[idx], self.sep, value))
|
|
|
- if idx < len(subkeys) - 1 and not isinstance(
|
|
|
- settings[group][key][subkeys[idx + 1]],
|
|
|
- dict):
|
|
|
- file.write('%s' % self.sep)
|
|
|
- file.write(newline)
|
|
|
+ with open(self.filePath, 'w') as f:
|
|
|
+ json.dump(settings, f, indent=2, cls=SettingsJSONEncoder)
|
|
|
except IOError as e:
|
|
|
raise GException(e)
|
|
|
except Exception as e:
|
|
|
raise GException(_('Writing settings to file <%(file)s> failed.'
|
|
|
'\n\nDetails: %(detail)s') %
|
|
|
{'file': self.filePath, 'detail': e})
|
|
|
- file.close()
|
|
|
return self.filePath
|
|
|
|
|
|
def _parseValue(self, value, read=False):
|