1''' Plugin for CudaText editor
2Authors:
3    Andrey Kvichansky    (kvichans on github.com)
4Version:
5    '2.3.15 2021-04-02'
6ToDo: (see end of file)
7'''
8
9import  re, os, sys, json, collections, itertools, webbrowser, tempfile, html, pickle, time, datetime
10from    itertools       import *
11from pathlib import PurePath as PPath
12from pathlib import     Path as  Path
13def first_true(iterable, default=False, pred=None):return next(filter(pred, iterable), default) # 10.1.2. Itertools Recipes
14
15import  cudatext            as app
16import  cudatext_cmd        as cmds
17import  cudax_lib           as apx
18from    .cd_plug_lib    import *
19
20d   = dict
21odict = collections.OrderedDict
22#class odict(collections.OrderedDict):  #py3.9 conflict
23#   def __init__(self, *args, **kwargs):
24#       if     args:super().__init__(*args)
25#       elif kwargs:super().__init__(kwargs.items())
26#   def __repr__(self):
27#       return '{%s}' % (', '.join("'%s':%r" % (k,v) for k,v in self.items()))
28
29pass;                           LOG     = (-1== 1) or apx.get_opt('_opts_dlg_log',False)    # Do or dont logging.
30pass;                           from pprint import pformat
31pass;                           pf=lambda d:pformat(d,width=150)
32pass;                           pf80=lambda d:pformat(d,width=80)
33pass;                           pf60=lambda d:pformat(d,width=60)
34pass;                           ##!! waits correction
35
36_   = get_translation(__file__) # I18N
37
38MIN_API_VER     = '1.0.168'
39MIN_API_VER_4WR = '1.0.175'     # vis
40MIN_API_VER     = '1.0.231'     # listview has prop columns
41MIN_API_VER     = '1.0.236'     # p, panel
42MIN_API_VER     = '1.0.237'     # STATUSBAR_SET_CELL_HINT
43VERSION     = re.split('Version:', __doc__)[1].split("'")[1]
44VERSION_V,  \
45VERSION_D   = VERSION.split(' ')
46MAX_HIST    = apx.get_opt('ui_max_history_edits', 20)
47CFG_JSON    = app.app_path(app.APP_DIR_SETTINGS)+os.sep+'cuda_options_editor.json'
48HTM_RPT_FILE= str(Path(tempfile.gettempdir()) / 'CudaText_option_report.html')
49FONT_LST    = ['default'] \
50            + [font
51                for font in app.app_proc(app.PROC_ENUM_FONTS, '')
52                if not font.startswith('@')]
53pass;                          #FONT_LST=FONT_LST[:3]
54
55def load_definitions(defn_path_or_json)->list:
56    """ Return
57            [{  opt:'opt name'
58            ,   def:<def val>
59            ,   cmt:'full comment'
60            ,   frm:'bool'|'float'|'int'|'str'|     # simple
61                    'int2s'|'strs'|'str2s'|         # list/dict
62                    'font'|'font-e'|                # font non-empty/can-empty
63                    '#rgb'|'#rgb-e'|                # color non-empty/can-empty
64                    'hotk'|'file'|'json'|
65                    'unk'
66            ,   lst:[str]       for frm==ints
67            ,   dct:[(num,str)] for frm==int2s
68            ,       [(str,str)] for frm==str2s
69            ,   chp:'chapter/chapter'
70            ,   tgs:['tag',]
71            }]
72    """
73    pass;                      #LOG and log('defn_path_or_json={}',(defn_path_or_json))
74    kinfs   = []
75    lines   = defn_path_or_json \
76                if str==type(defn_path_or_json) else \
77              defn_path_or_json.open(encoding='utf8').readlines()
78    if lines[0][0]=='[':
79        # Data is ready - SKIP parsing
80        json_bd = defn_path_or_json \
81                    if str==type(defn_path_or_json) else \
82                  defn_path_or_json.open(encoding='utf8').read()
83        kinfs   = json.loads(json_bd, object_pairs_hook=odict)
84        for kinf in kinfs:
85            pass;              #LOG and log('opt in kinf={}',('opt' in kinf))
86            if isinstance(kinf['cmt'], list):
87                kinf['cmt'] = '\n'.join(kinf['cmt'])
88
89        upd_cald_vals(kinfs, '+def')
90        for kinf in kinfs:
91            kinf['jdc'] = kinf.get('jdc', kinf.get('dct', []))
92            kinf['jdf'] = kinf.get('jdf', kinf.get('def', ''))
93        return kinfs
94
95    l       = '\n'
96
97    #NOTE: parse_raw
98    reTags  = re.compile(r' *\((#\w+,?)+\)')
99    reN2S   = re.compile(r'^\s*(\d+): *(.+)'    , re.M)
100    reS2S   = re.compile(r'^\s*"(\w*)": *(.+)'  , re.M)
101#   reLike  = re.compile(r' *\(like (\w+)\)')               ##??
102    reFldFr = re.compile(r'\s*Folders from: (.+)')
103    def parse_cmnt(cmnt, frm):#, kinfs):
104        tags= set()
105        mt  = reTags.search(cmnt)
106        while mt:
107            tags_s  = mt.group(0)
108            tags   |= set(tags_s.strip(' ()').replace('#', '').split(','))
109            cmnt    = cmnt.replace(tags_s, '')
110            mt      = reTags.search(cmnt)
111        dctN= [[int(m.group(1)), m.group(2).rstrip(', ')] for m in reN2S.finditer(cmnt+l)]
112        dctS= [[    m.group(1) , m.group(2).rstrip(', ')] for m in reS2S.finditer(cmnt+l)]
113        lstF= None
114        mt  = reFldFr.search(cmnt)
115        if mt:
116            from_short  = mt.group(1)
117            from_dir    = from_short if os.path.isabs(from_short) else os.path.join(app.app_path(app.APP_DIR_DATA), from_short)
118            pass;              #LOG and log('from_dir={}',(from_dir))
119            if not os.path.isdir(from_dir):
120                log(_('No folder "{}" from\n{}'), from_short, cmnt)
121            else:
122                lstF    = [d for d in os.listdir(from_dir)
123                           if os.path.isdir(from_dir+os.sep+d) and d.upper()!='README' and d.strip()]
124                lstF    = sorted(lstF)
125                pass;          #LOG and log('lstF={}',(lstF))
126        frm,\
127        lst = ('strs' , lstF)    if lstF else \
128              (frm    , []  )
129        frm,\
130        dct = ('int2s', dctN)    if dctN else \
131              ('str2s', dctS)    if dctS else \
132              (frm    , []  )
133        return cmnt, frm, dct, lst, list(tags)
134       #def parse_cmnt
135    def jsstr(s):
136        return s[1:-1].replace(r'\"','"').replace(r'\\','\\')
137
138    reChap1 = re.compile(r' *//\[Section: +(.+)\]')
139    reChap2 = re.compile(r' *//\[(.+)\]')
140    reCmnt  = re.compile(r' *//(.+)')
141    reKeyDV = re.compile(r' *"(\w+)" *: *(.+)')
142    reInt   = re.compile(r' *(-?\d+)')
143    reFloat = re.compile(r' *(-?\d+\.\d+)')
144    reFontNm= re.compile(r'font\w*_name')
145    reHotkey= re.compile(r'_hotkey_')
146    reColor = re.compile(r'_color$')
147    chap    = ''
148    pre_cmnt= ''
149    pre_kinf= None
150    cmnt    = ''
151    for line in lines:
152        if False:pass
153        elif    reChap1.match(line):
154            mt= reChap1.match(line)
155            chap    = mt.group(1)
156            cmnt    = ''
157        elif    reChap2.match(line):
158            mt= reChap2.match(line)
159            chap    = mt.group(1)
160            cmnt    = ''
161        elif    reCmnt.match(line):
162            mt= reCmnt.match(line)
163            cmnt   += l+mt.group(1)
164        elif    reKeyDV.match(line):
165            mt= reKeyDV.match(line)
166            key     = mt.group(1)
167            dval_s  = mt.group(2).rstrip(', ')
168            dfrm,dval= \
169                      ('bool', True         )   if dval_s=='true'                       else \
170                      ('bool', False        )   if dval_s=='false'                      else \
171                      ('float',float(dval_s))   if reFloat.match(dval_s)                else \
172                      ('int',  int(  dval_s))   if reInt.match(dval_s)                  else \
173                      ('font', dval_s[1:-1] )   if reFontNm.search(key)                 else \
174                      ('hotk', dval_s[1:-1] )   if reHotkey.search(key)                 else \
175                      ('#rgb', dval_s[1:-1] )   if reColor.search(key)                  else \
176                      ('str',  jsstr(dval_s))   if dval_s[0]=='"' and dval_s[-1]=='"'   else \
177                      ('unk',  dval_s       )
178            dfrm,dval=('#rgb-e',''          )   if dfrm=='#rgb' and dval==''            else \
179                      (dfrm,   dval         )
180            pass;              #LOG and log('key,dval_s,dfrm,dval={}',(key,dval_s,dfrm,dval))
181
182            cmnt    = cmnt.strip(l)     if cmnt else pre_cmnt
183            ref_frm = cmnt[:3]=='...'
184            pre_cmnt= cmnt              if cmnt else pre_cmnt
185            pass;              #LOG and log('ref_frm,pre_cmnt,cmnt={}',(ref_frm,pre_cmnt,cmnt))
186            cmnt    = cmnt.lstrip('.'+l)
187
188            dfrm    = 'font-e' if dfrm=='font' and _('Empty string is allowed') in cmnt   else dfrm
189
190            kinf    = odict()
191            kinfs  += [kinf]
192            kinf['opt']         = key
193            kinf['def']         = dval
194            kinf['cmt']         = cmnt.strip()
195            kinf['frm']         = dfrm
196            if dfrm in ('int','str'):
197                cmnt,frm,\
198                dct,lst,tags    = parse_cmnt(cmnt, dfrm)#, kinfs)
199                kinf['cmt']     = cmnt.strip()
200                if frm!=dfrm:
201                    kinf['frm'] = frm
202                if dct:
203                    kinf['dct'] = dct
204                if lst:
205                    kinf['lst'] = lst
206                if tags:
207                    kinf['tgs'] = tags
208            if dfrm=='font':
209                kinf['lst']     = FONT_LST
210            if dfrm=='font-e':
211                kinf['lst']     = [''] + FONT_LST
212            if chap:
213                kinf['chp']     = chap
214
215            if ref_frm and pre_kinf:
216                # Copy frm data from prev oi
217                pass;          #LOG and log('Copy frm pre_kinf={}',(pre_kinf))
218                kinf[    'frm'] = pre_kinf['frm']
219                if 'dct' in pre_kinf:
220                    kinf['dct'] = pre_kinf['dct']
221                if 'lst' in pre_kinf:
222                    kinf['lst'] = pre_kinf['lst']
223
224            pre_kinf= kinf.copy()
225            cmnt    = ''
226       #for line
227    pass;                      #open(str(defn_path_or_json)+'.p.json', 'w').write(json.dumps(kinfs,indent=2))
228    upd_cald_vals(kinfs, '+def')
229    for kinf in kinfs:
230        kinf['jdc'] = kinf.get('jdc', kinf.get('dct', []))
231        kinf['jdf'] = kinf.get('jdf', kinf.get('def', ''))
232    return kinfs
233   #def load_definitions
234
235def load_vals(opt_dfns:list, lexr_json='', ed_=None, full=False, user_json='user.json')->odict:
236    """ Create reformated copy (as odict) of
237            definitions data opt_dfns (see load_definitions)
238        If ed_ then add
239            'fval'
240            for some options
241        If full==True then append optitions without definition
242            but only with
243            {   opt:'opt name'
244            ,   frm:'int'|'float'|'str'
245            ,   uval:<value from user.json>
246            ,   lval:<value from lexer*.json>
247            }}
248        Return
249            {'opt name':{  opt:'opt name', frm:
250        ?   ,   def:, cmt:, dct:, chp:, tgs:
251        ?   ,   uval:<value from user.json>
252        ?   ,   lval:<value from lexer*.json>
253        ?   ,   fval:<value from ed>
254            }}
255    """
256    user_json       = app.app_path(app.APP_DIR_SETTINGS)+os.sep+user_json
257    lexr_def_json   = apx.get_def_setting_dir()         +os.sep+lexr_json
258    lexr_json       = app.app_path(app.APP_DIR_SETTINGS)+os.sep+lexr_json
259    user_vals       = apx._json_loads(open(user_json    , encoding='utf8').read(), object_pairs_hook=odict) \
260                        if  os.path.isfile(user_json)     else {}
261    lexr_def_vals   = apx._json_loads(open(lexr_def_json, encoding='utf8').read(), object_pairs_hook=odict) \
262                        if  os.path.isfile(lexr_def_json) else {}
263    lexr_vals       = apx._json_loads(open(lexr_json    , encoding='utf8').read(), object_pairs_hook=odict) \
264                        if  os.path.isfile(lexr_json)     else {}
265    pass;                      #LOG and log('lexr_vals={}',(lexr_vals))
266    pass;                      #LOG and log('lexr_def_vals={}',(lexr_def_vals))
267
268    # Fill vals for defined opt
269    pass;                      #LOG and log('no opt={}',([oi for oi in opt_dfns if 'opt' not in oi]))
270    oinf_valed  = odict([(oi['opt'], oi) for oi in opt_dfns])
271    for opt, oinf in oinf_valed.items():
272        if opt in lexr_def_vals:                                # Correct def-vals for lexer
273            oinf['dlx']     = True
274            oinf['def']     = lexr_def_vals[opt]
275            oinf['jdf']     = oinf['def']
276        if opt in user_vals:                                    # Found user-val for defined opt
277            oinf['uval']    = user_vals[opt]
278        if opt in lexr_vals:                                    # Found lexer-val for defined opt
279            oinf['lval']    = lexr_vals[opt]
280        if ed_ and opt in apx.OPT2PROP:                         # Found file-val for defined opt
281            fval            = ed_.get_prop(apx.OPT2PROP[opt])
282            oinf['fval']    =fval
283
284    if full:
285        # Append item for non-defined opt
286        reFontNm    = re.compile(r'font\w*_name')
287        def val2frm(val, opt=''):
288            pass;              #LOG and log('opt,val={}',(opt,val))
289            return  ('bool'     if isinstance(val, bool)    else
290                     'int'      if isinstance(val, int)     else
291                     'float'    if isinstance(val, float)   else
292                     'json'     if isinstance(val, (list, dict))    else
293                     'hotk'     if '_hotkey_' in val        else
294                     'font'     if isinstance(val, str)     and
295                                   reFontNm.search(val)     else
296                     'str')
297        for uop,uval in user_vals.items():
298            if uop in oinf_valed: continue
299            oinf_valed[uop] = odict(
300                [   ('opt'  ,uop)
301                ,   ('frm'  ,val2frm(uval,uop))
302                ,   ('uval' ,uval)
303                ]+([('lval' ,lexr_vals[uop])] if uop in lexr_vals else [])
304                )
305        for lop,lval in lexr_vals.items():
306            if lop in oinf_valed: continue
307            oinf_valed[lop] = odict(
308                [   ('opt'  ,lop)
309                ,   ('frm'  ,val2frm(lval,lop))
310                ,   ('lval' ,lval)
311                ])
312
313    upd_cald_vals(oinf_valed)
314    upd_cald_vals(oinf_valed, '+def') if lexr_def_vals else None    # To update oi['jdf'] by oi['def']
315
316    return oinf_valed
317   #def load_vals
318
319def upd_cald_vals(ois, what=''):
320    # Fill calculated attrs
321    if '+def' in what:
322        for oi in [oi for oi in ois if 'dct' in oi]:
323            dct = oi['dct']
324            dval= oi['def']
325            dc  = odict(dct)
326            pass;              #LOG and log('dct={}',(dct))
327            oi['jdc']   = [f('({}) {}', vl,   cm      ) for vl,cm in dct]
328            oi['jdf']   =  f('({}) {}', dval, dc[dval])
329            pass;              #LOG and log('oi={}',(oi))
330
331
332    # Fill calculated attrs
333    if not what or '+clcd' in what:
334        for op, oi in ois.items():
335            oi['!']     = ('L' if oi.get('dlx') else '') \
336                        + ('+!!' if 'def' not in oi and 'lval' in oi   else
337                            '+!' if 'def' not in oi and 'uval' in oi   else
338                           '!!!' if                     'fval' in oi
339                                    and oi['fval'] != oi.get('lval'
340                                                    , oi.get('uval'
341                                                    , oi.get( 'def'))) else
342                            '!!' if                     'lval' in oi   else
343                             '!' if                     'uval' in oi   else
344                          '')
345            dct         = odict(oi.get('dct', []))
346            oi['juvl']  = oi.get('uval', '') \
347                            if not dct or 'uval' not in oi else \
348                          f('({}) {}', oi['uval'], dct[oi['uval']])
349            oi['jlvl']  = oi.get('lval', '') \
350                            if not dct or 'lval' not in oi else \
351                          f('({}) {}', oi['lval'], dct[oi['lval']])
352            oi['jfvl']  = oi.get('fval', '') \
353                            if not dct or 'fval' not in oi else \
354                          f('({}) {}', oi['fval'], dct[oi['fval']])
355   #def upd_cald_vals
356
357#class OptDt:
358#   """ Options infos to view/change in dlg.
359#       Opt getting is direct - by fields.
360#       Opt setting only by methods.
361#   """
362#
363#   def __init__(self
364#       , keys_info=None            # Ready data
365#       , path_raw_keys_info=''     # default.json
366#       , path_svd_keys_info=''     # To save parsed default.json
367#       , bk_sets=False             # Create backup of settings before the first change
368#       ):
369#       self.defn_path  = Path(path_raw_keys_info)
370#       self.bk_sets    = bk_sets   # Need to backup
371#       self.bk_files   = {}        # Created backup files
372#
373#       self.opts_defn  = {}        # Meta-info for options: format, comment, dict/list of values, chapter, tags
374#       self.ul_opts    = {}        # Total options info for user+cur_lexer
375#      #def __init__
376#
377#  #class OptDt
378
379_SORT_NO    = -1
380_SORT_DN    = 0
381_SORT_UP    = 1
382_SORT_TSGN  = {_SORT_NO:'', _SORT_UP:'↑', _SORT_DN:'↓'}
383_SORT_NSGN  = {-1:'', 0:'', 1:'²', 2:'³'}
384_SORT_NSGN.update({n:str(1+n) for n in range(3,10)})
385_sort_pfx   = lambda to,num: '' if to==_SORT_NO else _SORT_TSGN[to]+_SORT_NSGN[num]+' '
386_next_sort  = lambda to: ((1 + 1+to) % 3) - 1
387_inve_sort  = lambda to: 1 - to
388
389sorts_dflt  = lambda cols: [[_SORT_NO, -1] for c in range(cols)]
390sorts_sign  = lambda sorts, col: _sort_pfx(sorts[col][0], sorts[col][1])
391sorts_on    = lambda sorts, col: sorts[col][0] != _SORT_NO
392
393def sorts_turn(sorts, col, scam=''):
394    """ Switch one of sorts """
395    max_num = max(tn[1] for tn in sorts)
396    tn_col  = sorts[col]
397    if 0:pass
398    elif 'c'==scam and tn_col[1]==max_num:          # Turn col with max number
399        tn_col[0]   = _next_sort(tn_col[0])
400        tn_col[1]   = -1 if tn_col[0]==_SORT_NO else tn_col[1]
401    elif 'c'==scam:                                 # Add new or turn other col
402        tn_col[0]   = _next_sort(tn_col[0]) if -1==tn_col[1] else _inve_sort(tn_col[0])
403        tn_col[1]   = max_num+1             if -1==tn_col[1] else tn_col[1]
404    else:#not  scam:                                # Only col
405        for cl,tn in enumerate(sorts):
406            tn[0]   = _next_sort(tn_col[0]) if cl==col else _SORT_NO
407            tn[1]   = 0                     if cl==col else -1
408    return sorts
409   #def sorts_turn
410
411def sorts_sort(sorts, tdata):
412    """ Sort tdata (must contain only str) by sorts """
413    pass;                      #log('tdata={}',(tdata))
414    pass;                      #log('sorts={}',(sorts))
415    max_num     = max(tn[1] for tn in sorts)
416    if -1==max_num:  return tdata
417
418    def push(lst, v):
419        lst.append(v)
420        return lst
421    prep_str    = lambda s,inv: (chr(0x10FFFF)                              # To move empty to bottom
422                                    if not s else
423                                 s
424                                    if not inv else
425                                 ''.join(chr(0x10FFFF - ord(c)) for c in s) # 0x10FFFF from chr() doc
426                                )
427
428    td_keys     = [[r] for r in tdata]
429    for srt_n in range(1+max_num):
430        srt_ctn = first_true(((c,tn) for c,tn in enumerate(sorts)), None
431                            ,lambda ntn: ntn[1][1]==srt_n)
432        assert srt_ctn is not None
433        srt_c   = srt_ctn[0]
434        inv     = srt_ctn[1][0]==_SORT_UP
435        td_keys = [push(r, prep_str(r[0][srt_c], inv)) for r in td_keys]
436    td_keys.sort(key=lambda r: r[1:])
437    tdata       = [r[0] for r in td_keys]                                   # Remove appended cols
438    return tdata
439   #def sorts_sort
440
441class OptEdD:
442    SCROLL_W= app.app_proc(app.PROC_GET_GUI_HEIGHT, 'scrollbar') if app.app_api_version()>='1.0.233' else 15
443    COL_SEC = 0
444    COL_NAM = 1
445    COL_OVR = 2
446    COL_DEF = 3
447    COL_USR = 4
448    COL_LXR = 5
449    COL_FIL = 6
450    COL_LEXR= _('Lexer')
451    COL_FILE= _('File "{}"')
452    COL_NMS = (_('Section'), _('Option'), '!', _('Default'), _('User'), COL_LEXR, COL_FILE)
453    COL_MWS = [   70,           210,       25,    120,         120,       70,         50]   # Min col widths
454#   COL_MWS = [   70,           150,       25,    120,         120,       70,         50]   # Min col widths
455    COL_N   = len(COL_MWS)
456    CMNT_MHT= 60                            # Min height of Comment
457    STBR_FLT= 10
458    STBR_ALL= 11
459    STBR_MSG= 12
460    STBR_H  = apx.get_opt('ui_statusbar_height',24)
461
462    FILTER_C= _('&Filter')
463    NO_CHAP = _('_no_')
464    CHPS_H  = f(_('Choose section to append in "{}".'
465                '\rHold Ctrl to add several sections.'
466               ), FILTER_C).replace('&', '')
467    FLTR_H  = _('Suitable options will contain all specified words.'
468              '\r Tips and tricks:'
469              '\r • Add "#" to search the words also in comments.'
470              '\r • Add "@sec" to show options from section with "sec" in name.'
471              '\r   Several sections are allowed.'
472              '\r   Click item in menu "Section..." with Ctrl to add it.'
473              '\r • To show only overridden options:'
474              '\r   - Add "!"   to show only User+Lexer+File.'
475              '\r   - Add "!!"  to show only Lexer+File'
476              '\r   - Add "!!!" to show only File.'
477              '\r • Use "<" or ">" for word boundary.'
478              '\r     Example: '
479              '\r       size> <tab'
480              '\r     selects "tab_size" but not "ui_tab_size" or "tab_size_x".'
481              '\r • Alt+L - Clear filter')
482    LOCV_C  = _('Go to "{}" in user/lexer config file')
483    LOCD_C  = _('Go to "{}" in default config file')
484    OPME_H  = _('Edit JSON value')
485    TOOP_H  = f(_('Close dialog and open user/lexer settings file'
486                  '\rto edit the current option.'
487                  '\rSee also menu command'
488                  '\r   {}'), f(LOCD_C, '<option>'))
489    LIFL_C  = _('Instant filtering')
490    FULL_C  = _('Show &all keys in user/lexer configs')
491
492    @staticmethod
493    def prep_sorts(sorts):
494        M   = OptEdD
495        if len(sorts)==len(M.COL_NMS):
496            return sorts
497        return sorts_dflt(len(M.COL_NMS))
498
499    def __init__(self
500        , path_keys_info    =''             # default.json or parsed data (file or list_of_dicts)
501        , subset            =''             # To get/set from/to cuda_options_editor.json
502        , how               ={}             # Details to work
503        ):
504        M,m         = self.__class__,self
505
506        m.ed        = ed
507        m.how       = how
508
509        m.defn_path = Path(path_keys_info)  if str==type(path_keys_info) else json.dumps(path_keys_info)
510
511        m.subset    = subset
512        m.stores    = get_hist('dlg'
513                        , json.loads(open(CFG_JSON).read(), object_pairs_hook=odict)
514                            if os.path.exists(CFG_JSON) else odict())
515        pass;                  #LOG and log('ok',())
516#       m.bk_sets   = m.stores.get(m.subset+'bk_sets'    , False)
517        m.lexr_l    = app.lexer_proc(app.LEXER_GET_LEXERS, False)
518        m.lexr_w_l  = [f('{} {}'
519                        ,'!!' if os.path.isfile(app.app_path(app.APP_DIR_SETTINGS)+os.sep+'lexer '+lxr+'.json') else '  '
520                        , lxr)
521                        for lxr in m.lexr_l]
522
523        m.cur_op    = m.stores.get(m.subset+'cur_op'    , '')           # Name of current option
524        m.col_ws    = m.stores.get(m.subset+'col_ws'    , M.COL_MWS[:])
525        m.col_ws    = m.col_ws if M.COL_N==len(m.col_ws) else M.COL_MWS[:]
526        m.h_cmnt    = m.stores.get(m.subset+'cmnt_heght', M.CMNT_MHT)
527        m.sorts     = m.stores.get(m.subset+'sorts'     , []        )   # Def sorts is no sorts
528        m.live_fltr = m.stores.get(m.subset+'live_fltr' , False)        # To filter after each change and no History
529        m.cond_hl   = [s for s in m.stores.get(m.subset+'h.cond', []) if s] if not m.live_fltr else []
530        m.cond_s    = '' if M.restart_cond is None else M.restart_cond  # String filter
531        m.ops_only  = []        # Subset to show (future)
532
533        m.sorts     = M.prep_sorts(m.sorts)
534
535        m.lexr      = m.ed.get_prop(app.PROP_LEXER_CARET)
536        m.all_ops   = m.stores.get(m.subset+'all_ops'   , False)        # Show also options without definition
537
538        m.opts_defn = {}        # Meta-info for options: format, comment, dict of values, chapter, tags
539        m.opts_full = {}        # Show all options
540        m.chp_tree  = {}        # {'Ui':{ops:[], 'kids':{...}, 'path':'Ui/Tabs'}
541        m.pth2chp   = {}        # path-index for m.chp_tree
542
543        # Cache
544        m.SKWULFs   = []        # Last filtered+sorted
545        m.cols      = []        # Last info about listview columns
546        m.itms      = []        # Last info about listview cells
547
548#       m.bk_files  = {}
549#       m.do_file('backup-user')    if m.bk_sets else 0
550
551        m.do_file('load-data')
552
553        m.for_ulf   = 'u'       # 'u' for User, 'l' for Lexer, 'f' for File
554        m.cur_op    = m.cur_op if m.cur_op in m.opts_full else ''           # First at start
555        m.cur_in    = 0 if m.cur_op else -1
556
557        m.stbr      = None      # Handle for statusbar_proc
558        m.locate_on_exit    = None
559
560        m.chng_rpt  = []        # Report of all changes by user
561        m.apply_one = m.stores.get(m.subset+'apply_one', False) # Do one call OpsReloadAndApply on exit
562        m.apply_need= False                                     # Need to call OpsReloadAndApply
563        m.auto4file = m.stores.get(m.subset+'auto4file', True)  # Auto reset file value to over value def/user/lex
564       #def __init__
565
566    def stbr_act(self, tag=None, val='', opts={}):
567        M,m = self.__class__,self
568        if not m.stbr:  return
569        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_TEXT, tag=tag, value=str(val))
570       #def stbr_act
571
572    def do_file(self, what, data='', opts={}):
573        M,m = self.__class__,self
574        if False:pass
575        elif what=='load-data':
576            pass;              #LOG and log('',)
577            m.opts_defn = load_definitions(m.defn_path)
578            pass;              #LOG and log('m.opts_defn={}',pf([o for o in m.opts_defn]))
579            pass;              #LOG and log('m.opts_defn={}',pf([o for o in m.opts_defn if '2s' in o['frm']]))
580            m.opts_full = load_vals(m.opts_defn
581                                   ,lexr_json='lexer '+m.lexr+'.json'
582                                   ,user_json=m.how.get('stor_json', 'user.json')
583                                   , ed_=m.ed, full=m.all_ops)
584            m.cur_op    = m.cur_op if m.cur_op in m.opts_full else ''
585            pass;              #LOG and log('m.opts_full={}',pf(m.opts_full))
586            m.do_file('build-chp-tree')
587
588        elif what=='build-chp-tree':
589            # Build chapter tree
590            m.chp_tree  = odict(ops=list(m.opts_full.keys())
591                               ,kids=odict()
592                               ,path='')  # {chp:{ops:[], kids:{...}, path:'c1/c2'}
593            m.pth2chp   = {}                                    # {path:chp}
594            for op,oi in m.opts_full.items():
595                chp_s   = oi.get('chp', M.NO_CHAP)
596                chp_s   = chp_s if chp_s else M.NO_CHAP
597                chp_node= m.chp_tree                            # Start root to move
598                kids    = chp_node['kids']
599                path    =''
600                for chp in chp_s.split('/'):
601                    # Move along branch and create nodes if need
602                    chp_node    = kids.setdefault(chp, odict())
603                    path       += ('/'+chp) if path else chp
604                    chp_node['path']= path
605                    m.pth2chp[path] = chp_node
606                    ops_l       = chp_node.setdefault('ops', [])
607                    ops_l      += [op]
608                    if not ('/'+chp_s).endswith('/'+chp):   # not last
609                        kids    = chp_node.setdefault('kids', odict())
610            pass;              #LOG and log('m.chp_tree=¶{}',pf60(m.chp_tree))
611            pass;              #LOG and log('m.pth2chp=¶{}',pf60(m.pth2chp))
612
613        elif what == 'locate_to':
614            to_open = data['path']
615            find_s  = data['find']
616            app.file_open(to_open)      ##!!
617            pass;              #log('to_open={}',(to_open))
618            pass;              #log('ed.get_filename()={}',(ed.get_filename()))
619            m.ag.opts['on_exit_focus_to_ed'] = ed
620            # Locate
621            user_opt= app.app_proc(app.PROC_GET_FINDER_PROP, '') \
622                        if app.app_api_version()>='1.0.248' else \
623                      app.app_proc(app.PROC_GET_FIND_OPTIONS, '')   # Deprecated
624            pass;              #log('ed_to_fcs.get_filename()={}',(ed_to_fcs.get_filename()))
625            pass;              #log('ed.get_filename()={}',(ed.get_filename()))
626            pass;              #LOG and log('find_s={!r}',(find_s))
627            ed.cmd(cmds.cmd_FinderAction, chr(1).join(['findnext', find_s, '', 'fa']))    # f - From-caret,  a - Wrap
628            if app.app_api_version()>='1.0.248':
629                app.app_proc(app.PROC_SET_FINDER_PROP, user_opt)
630            else:
631                app.app_proc(app.PROC_SET_FIND_OPTIONS, user_opt)   # Deprecated
632
633        elif what in ('locate-def', 'locate-opt', 'goto-def', 'goto-opt', ):
634            if not m.cur_op:
635                m.stbr_act(M.STBR_MSG, _('Choose option to find in config file'))
636                return False
637            oi      = m.opts_full[m.cur_op]
638            pass;              #LOG and log('m.cur_op,oi={}',(m.cur_op,oi))
639            to_open = ''
640            if what in ('locate-opt', 'goto-opt'):
641                if 'uval' not in oi and m.for_ulf=='u':
642                    m.stbr_act(M.STBR_MSG, f(_('No user value for option "{}"'), m.cur_op))
643                    return False
644                if 'lval' not in oi and m.for_ulf=='l':
645                    m.stbr_act(M.STBR_MSG, f(_('No lexer "{}" value for option "{}"'), m.lexr, m.cur_op))
646                    return False
647                to_open = 'lexer '+m.lexr+'.json'   if m.for_ulf=='l' else 'user.json'
648                to_open = app.app_path(app.APP_DIR_SETTINGS)+os.sep+to_open
649            else:
650                if 'def' not in oi:
651                    m.stbr_act(M.STBR_MSG, f(_('No default for option "{}"'), m.cur_op))
652                    return False
653                to_open = str(m.defn_path)
654            if not os.path.exists(to_open):
655                log('No file={}',(to_open))
656                return False
657
658            find_s  = f('"{}"', m.cur_op)
659            if what in ('goto-def', 'goto-opt'):
660                m.locate_on_exit  = d(path=to_open, find=find_s)
661                return True #
662            m.do_file('locate_to',  d(path=to_open, find=find_s))
663            return False
664
665       #elif what=='set-dfns':
666       #    m.defn_path = data
667       #    m.do_file('load-data')
668       #    return d(ctrls=odict(m.get_cnts('lvls')))
669
670        elif what=='set-lexr':
671            m.opts_full = load_vals(m.opts_defn
672                                   ,lexr_json='lexer '+m.lexr+'.json'
673                                   ,user_json=m.how.get('stor_json', 'user.json')
674                                   ,ed_=m.ed, full=m.all_ops)
675            return d(ctrls=odict(m.get_cnts('lvls')))
676
677        elif what=='out-rprt':
678            if do_report(HTM_RPT_FILE, 'lexer '+m.lexr+'.json', m.ed):
679                webbrowser.open_new_tab('file://'         +HTM_RPT_FILE)
680                app.msg_status(_('Opened browser with file ')+HTM_RPT_FILE)
681
682        return []
683       #def do_file
684
685    def _prep_opt(self, opts='', ind=-1, nm=None):
686        """ Prepare vars to show info about current option by
687                m.cur_op
688                m.lexr
689            Return
690                {}  vi-attrs
691                {}  en-attrs
692                {}  val-attrs
693                {}  items-attrs
694        """
695        M,m = self.__class__,self
696        if opts=='key2ind':
697            opt_nm  = nm if nm else m.cur_op
698            m.cur_in= index_1([m.SKWULFs[row][1] for row in range(len(m.SKWULFs))], opt_nm, -1)
699            return m.cur_in
700
701        if opts=='ind2key':
702            opt_in  = ind if -1!=ind else m.ag.cval('lvls')
703            m.cur_op= m.SKWULFs[opt_in][1] if -1<opt_in<len(m.SKWULFs) else ''
704            return m.cur_op
705
706        if opts=='fid4ed':
707            if not m.cur_op:    return 'lvls'
708            frm = m.opts_full[m.cur_op]['frm']
709            fid =   'eded'  if frm in ('str', 'int', 'float')                       else \
710                    'edcb'  if frm in ('int2s', 'str2s', 'strs', 'font', 'font-e')  else \
711                    'edrf'  if frm in ('bool',)                                     else \
712                    'brow'  if frm in ('hotk', 'file', '#rgb', '#rgb-e')            else \
713                    'opjs'  if frm in ('json')                                      else \
714                    'lvls'
715            pass;              #LOG and log('m.cur_op,frm,fid={}',(m.cur_op,frm,fid))
716            return fid
717
718        pass;                  #LOG and log('m.cur_op, m.lexr={}',(m.cur_op, m.lexr))
719        vis,ens,vas,its,bcl = {},{},{},{},{}
720        vis['edcl'] = vis['dfcl'] = False
721        bcl['edcl'] = bcl['dfcl'] = 0x20000000
722#       bcl['eded'] = bcl['dfvl'] = 0x20000000
723
724        ens['eded'] = ens['setd']                                                               = False # All un=F
725        vis['eded'] = vis['edcb']=vis['edrf']=vis['edrt']=vis['brow']=vis['toop']=vis['opjs']   = False # All vi=F
726        vas['eded'] = vas['dfvl']=vas['cmnt']= ''                                                       # All ed empty
727        vas['edcb'] = -1
728        vas['edrf'] = vas['edrt'] = False
729        its['edcb'] = []
730
731        ens['dfvl']         = True
732        ens['tofi']         = m.cur_op in apx.OPT2PROP
733        if m.for_ulf=='l' and m.lexr not in m.lexr_l:
734            # Not selected lexer
735            vis['eded']     = True
736            ens['dfvl']     = False
737            return vis,ens,vas,its,bcl
738
739        if m.for_ulf=='f' and m.cur_op not in apx.OPT2PROP:
740            # No the option for File
741            vis['eded']     = True
742            ens['dfvl']     = False
743            return vis,ens,vas,its,bcl
744
745        if not m.cur_op:
746            # No current option
747            vis['eded']     = True
748        else:
749            # Current option
750            oi              = m.opts_full[m.cur_op]
751            pass;              #LOG and log('oi={}',(oi))
752            vas['dfvl']     = str(oi.get('jdf' , '')).replace('True', 'true').replace('False', 'false')
753            vas['uval']     = oi.get('uval', '')
754            vas['lval']     = oi.get('lval', '')
755            vas['fval']     = oi.get('fval', '')
756            vas['cmnt']     = oi.get('cmt' , '')
757            frm             = oi['frm']
758            ulfvl_va        = vas['fval'] \
759                                if m.for_ulf=='f' else \
760                              vas['lval'] \
761                                if m.for_ulf=='l' else \
762                              vas['uval']                       # Cur val with cur state of "For lexer"
763            ens['eded']     = frm not in ('json', 'hotk', 'file')#, '#rgb', '#rgb-e')
764            ens['setd']     = frm not in ('json',) and ulfvl_va is not None
765            if False:pass
766            elif frm in ('json'):
767#               vis['toop'] = True
768                vis['opjs'] = True
769                vis['eded'] = True
770                vas['eded'] = str(ulfvl_va)
771            elif frm in ('str', 'int', 'float'):
772                vis['eded'] = True
773                vas['eded'] = str(ulfvl_va)
774            elif frm in ('hotk', 'file', '#rgb', '#rgb-e'):
775                vis['eded'] = True
776                vis['brow'] = True
777                vas['eded'] = str(ulfvl_va)
778                vis['edcl'] = frm in ('#rgb', '#rgb-e')
779                vis['dfcl'] = frm in ('#rgb', '#rgb-e')
780                bcl['edcl'] = apx.html_color_to_int(ulfvl_va    ) if frm in ('#rgb', '#rgb-e') and ulfvl_va     else 0x20000000
781                bcl['dfcl'] = apx.html_color_to_int(vas['dfvl'] ) if frm in ('#rgb', '#rgb-e') and vas['dfvl']  else 0x20000000
782            elif frm in ('bool',):
783                vis['edrf'] = True
784                vis['edrt'] = True
785                vas['edrf'] = ulfvl_va is False
786                vas['edrt'] = ulfvl_va is True
787            elif frm in ('int2s', 'str2s'):
788                vis['edcb'] = True
789                ens['edcb'] = True
790                its['edcb'] = oi['jdc']
791                vas['edcb'] = index_1([k for (k,v) in oi['dct']], ulfvl_va, -1)
792                pass;          #LOG and log('ulfvl_va, vas[edcb]={}',(ulfvl_va,vas['edcb']))
793            elif frm in ('strs','font','font-e'):
794                vis['edcb'] = True
795                ens['edcb'] = True
796                its['edcb'] = oi['lst']
797                vas['edcb'] = index_1(oi['lst'], ulfvl_va, -1)
798
799        pass;                  #LOG and log('ulfvl_va={}',(ulfvl_va))
800        pass;                  #LOG and log('vis={}',(vis))
801        pass;                  #LOG and log('ens={}',(ens))
802        pass;                  #LOG and log('vas={}',(vas))
803        pass;                  #LOG and log('its={}',(its))
804        return vis,ens,vas,its,bcl
805       #def _prep_opt
806
807    def show(self
808        , title                     # For cap of dlg
809        ):
810        M,m = self.__class__,self
811
812        def when_exit(ag):
813            pass;              #LOG and log('',())
814            pass;              #pr_   = dlg_proc_wpr(ag.id_dlg, app.DLG_CTL_PROP_GET, name='edch')
815            pass;              #log('exit,pr_={}',('edch', {k:v for k,v in pr_.items() if k in ('x','y')}))
816            pass;              #log('cols={}',(ag.cattr('lvls', 'cols')))
817            m.col_ws= [ci['wd'] for ci in ag.cattr('lvls', 'cols')]
818            m.stores[m.subset+'cmnt_heght'] = m.ag.cattr('cmnt', 'h')
819
820            if m.apply_one and m.apply_need:
821                ed.cmd(cmds.cmd_OpsReloadAndApply)
822
823            if m.locate_on_exit:
824                m.do_file('locate_to', m.locate_on_exit)
825           #def when_exit
826
827        repro_py    = apx.get_opt('dlg_cuda_options.repro_py')  # 'repro_dlg_opted.py'
828
829        m.dlg_min_w = 10 + sum(M.COL_MWS) + M.COL_N + M.SCROLL_W
830        m.dlg_w     = 10 + sum(m.col_ws)  + M.COL_N + M.SCROLL_W
831        m.dlg_h     = 380 + m.h_cmnt    +10 + M.STBR_H
832#       m.dlg_h     = 270 + m.h_cmnt    +10 + M.STBR_H
833        pass;                  #log('m.dlg_w,m.dlg_h={}',(m.dlg_w,m.dlg_h))
834        m.ag = DlgAgent(
835            form =dict(cap     = title + f(' ({})', VERSION_V)
836                      ,resize  = True
837                      ,w       = m.dlg_w    ,w_min=m.dlg_min_w
838                      ,h       = m.dlg_h
839                      ,on_resize=m.do_resize
840                      )
841        ,   ctrls=m.get_cnts()
842        ,   vals =m.get_vals()
843        ,   fid  ='cond'
844                                ,options = ({
845                                    'gen_repro_to_file':repro_py,    #NOTE: repro
846                                } if repro_py else {})
847        )
848        # Select on pre-show. Reason: linux skip selection event after show
849        m.ag._update_on_call(m.do_sele('lvls', m.ag))
850
851        m.stbr  = app.dlg_proc(m.ag.id_dlg, app.DLG_CTL_HANDLE, name='stbr')
852        app.statusbar_proc(m.stbr, app.STATUSBAR_ADD_CELL               , tag=M.STBR_ALL)
853        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_SIZE          , tag=M.STBR_ALL, value=40)
854        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_ALIGN         , tag=M.STBR_ALL, value='R')
855        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_HINT          , tag=M.STBR_ALL, value=_('Number of all options'))
856        app.statusbar_proc(m.stbr, app.STATUSBAR_ADD_CELL               , tag=M.STBR_FLT)
857        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_SIZE          , tag=M.STBR_FLT, value=40)
858        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_ALIGN         , tag=M.STBR_FLT, value='R')
859        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_HINT          , tag=M.STBR_FLT, value=_('Number of shown options'))
860        app.statusbar_proc(m.stbr, app.STATUSBAR_ADD_CELL               , tag=M.STBR_MSG)
861        app.statusbar_proc(m.stbr, app.STATUSBAR_SET_CELL_AUTOSTRETCH   , tag=M.STBR_MSG, value=True)
862        m.stbr_act(M.STBR_ALL, len(m.opts_full))
863        m.stbr_act(M.STBR_FLT, len(m.opts_full))
864
865        stor_json   = app.app_path(app.APP_DIR_SETTINGS)+os.sep+m.how.get('stor_json', 'user.json')
866        start_mtime = os.path.getmtime(stor_json) if os.path.exists(stor_json) else 0
867
868        m.ag.show(when_exit)
869        m.ag    = None
870
871        # Save for next using
872        m.stores[m.subset+'cur_op']     = m.cur_op
873        m.stores[m.subset+'col_ws']     = m.col_ws
874        m.stores[m.subset+'sorts']      = m.sorts
875        if not m.live_fltr:
876            m.stores[m.subset+'h.cond'] = m.cond_hl
877        m.stores[m.subset+'all_ops']    = m.all_ops
878        set_hist('dlg', m.stores)
879
880        return start_mtime != (os.path.getmtime(stor_json) if os.path.exists(stor_json) else 0)
881       #def show
882
883    def get_cnts(self, what=''):
884        M,m = self.__class__,self
885
886        reNotWdChar = re.compile(r'\W')
887        def test_fltr(fltr_s, op, oi):
888            if not fltr_s:                                  return True
889            pass;              #LOG and log('fltr_s, op, oi[!]={}',(fltr_s, op, oi['!']))
890            if '!!!' in fltr_s and '!!!' not in oi['!']:    return False
891            if '!!'  in fltr_s and '!!'  not in oi['!']:    return False
892            pass;              #LOG and log('skip !!',())
893            if  '!'  in fltr_s and  '!'  not in oi['!']:    return False
894            pass;              #LOG and log('skip !',())
895            text    = op \
896                    + (' '+oi.get('cmt', '') if '#' in fltr_s else '')
897            text    = text.upper()
898            fltr_s  = fltr_s.replace('!', '').replace('#', '').upper()
899            if '<' in fltr_s or '>' in fltr_s:
900                text    = '·' + reNotWdChar.sub('·', text)    + '·'
901                fltr_s  = ' ' + fltr_s + ' '
902                fltr_s  = fltr_s.replace(' <', ' ·').replace('> ', '· ')
903            pass;              #LOG and log('fltr_s, text={}',(fltr_s, text))
904            return all(map(lambda c:c in text, fltr_s.split()))
905           #def test_fltr
906
907        def get_tbl_cols(sorts, col_ws):
908            cnms    = list(M.COL_NMS)
909            cnms[M.COL_FIL] = f(cnms[M.COL_FIL], m.ed.get_prop(app.PROP_TAB_TITLE))
910            cols    = [d(nm=sorts_sign(sorts, c) + cnms[c]
911                        ,wd=col_ws[c]
912                        ,mi=M.COL_MWS[c]
913                        )   for c in range(M.COL_N)]
914            cols[M.COL_OVR]['al']   = 'C'
915            if m.how.get('hide_fil', False):
916                pos_fil = M.COL_NMS.index(M.COL_FILE)
917                cols[pos_fil]['vi'] = False
918            if m.how.get('hide_lex_fil', False):
919                pos_lex = M.COL_NMS.index(M.COL_LEXR)
920                pos_fil = M.COL_NMS.index(M.COL_FILE)
921                cols[pos_lex]['vi'] = False
922                cols[pos_fil]['vi'] = False
923            return cols
924           #def get_tbl_cols
925
926        def get_tbl_data(opts_full, cond_s, ops_only, sorts, col_ws):
927            # Filter table data
928            pass;              #LOG and log('cond_s={}',(cond_s))
929            pass;              #log('opts_full/tab_s={}',({o:oi for o,oi in opts_full.items() if o.startswith('tab_s')}))
930            chp_cond    = ''
931            chp_no_c    = False
932            if  '@' in cond_s:
933                # Prepare to match chapters
934                chp_cond    = ' '.join([mt.group(1) for mt in re.finditer(r'@([\w/]+)'    , cond_s)]).upper()   # @s+ not empty chp
935                chp_cond    = chp_cond.replace(M.NO_CHAP.upper(), '').strip()
936                chp_no_c    = '@'+M.NO_CHAP in cond_s
937                cond_s      =                                 re.sub(     r'@([\w/]*)', '', cond_s)             # @s* clear @ and cph
938            pass;              #log('chp_cond, chp_no_c, cond_s={}',(chp_cond, chp_no_c, cond_s))
939            SKWULFs = [  (oi.get('chp','')
940                         ,op
941                         ,oi['!']
942                         ,str(oi.get('jdf' ,'')).replace('True', 'true').replace('False', 'false')
943                         ,str(oi.get('juvl','')).replace('True', 'true').replace('False', 'false')
944                         ,str(oi.get('jlvl','')).replace('True', 'true').replace('False', 'false')
945                         ,str(oi.get('jfvl','')).replace('True', 'true').replace('False', 'false')
946                         ,oi['frm']
947                         )
948                            for op,oi in opts_full.items()
949#                           if  (not chp_cond   or      chp_cond in oi.get('chp', '').upper())
950                            if  (not chp_cond   or any((chp_cond in oi.get('chp', '').upper()) for chp_cond in chp_cond.split()))
951                            and (not chp_no_c   or not oi.get('chp', ''))
952                            and (not cond_s     or test_fltr(cond_s, op, oi))
953                            and (not ops_only   or op in ops_only)
954                      ]
955            # Sort table data
956            SKWULFs     = sorts_sort(sorts, SKWULFs)
957            # Fill table
958            pass;              #LOG and log('M.COL_NMS,col_ws,M.COL_MWS={}',(len(M.COL_NMS),len(col_ws),len(M.COL_MWS)))
959            cols    = get_tbl_cols(sorts, col_ws)
960
961            itms    = (list(zip([_('Section'),_('Option'), '', _('Default'), _('User'), _('Lexer'), _('File')], map(str, col_ws)))
962                     #,         [ (str(n)+':'+sc,k         ,w    ,dv           ,uv         ,lv          ,fv)    # for debug
963                     #,         [ (sc+' '+fm    ,k         ,w    ,dv           ,uv         ,lv          ,fv)    # for debug
964                      ,         [ (sc           ,k         ,w    ,dv           ,uv         ,lv          ,fv)    # for user
965                        for  n,(   sc           ,k         ,w    ,dv           ,uv         ,lv          ,fv, fm) in enumerate(SKWULFs) ]
966                      )
967            return SKWULFs, cols, itms
968           #def get_tbl_data
969
970        if not what or '+lvls' in what:
971            m.SKWULFs,\
972            m.cols  ,\
973            m.itms  = get_tbl_data(m.opts_full, m.cond_s, m.ops_only, m.sorts, m.col_ws)
974            if 'stbr' in dir(m):
975                m.stbr_act(M.STBR_FLT, len(m.SKWULFs))
976
977        if '+cols' in what:
978            pass;              #LOG and log('m.col_ws={}',(m.col_ws))
979            m.cols  = get_tbl_cols(m.sorts, m.col_ws)
980            pass;              #LOG and log('m.cols={}',(m.cols))
981
982        # Prepare [Def]Val data by m.cur_op
983        vis,ens,vas,its,bcl = m._prep_opt()
984
985        ed_s_c  = _('>Fil&e:')  if m.for_ulf=='f' else \
986                  _('>L&exer:') if m.for_ulf=='l' else \
987                  _('>Us&er:')
988        cnts    = []
989        if '+cond' in what:
990            cnts   += [0
991            ,('cond',d(items=m.cond_hl))
992            ][1:]
993
994        if '+cols' in what or '=cols' in what:
995            cnts   += [0
996            ,('lvls',d(cols=m.cols))
997            ][1:]
998        if '+lvls' in what or '=lvls' in what:
999            cnts   += [0
1000            ,('lvls',d(cols=m.cols, items=m.itms))
1001            ][1:]
1002
1003        tofi_en = not m.how.get('only_for_ul', not ens['tofi'])         # Forbid to switch fo File ops
1004        if '+cur' in what:
1005            cnts   += [0
1006            ,('ed_s',d(cap=ed_s_c                       ,hint=m.cur_op      ))
1007#           ,('eded',d(vis=vis['eded']                    ,sto=ens['eded']  ,color=bcl['eded']  ))
1008#           ,('eded',d(vis=vis['eded'],ex0=not ens['eded'],sto=ens['eded']  ,color=bcl['eded']  ))
1009#           ,('eded',d(vis=vis['eded'],en=ens['eded']                       ,color=bcl['eded']  ))
1010            ,('eded',d(vis=vis['eded'],en=ens['eded']                       ))
1011            ,('edcl',d(vis=vis['edcl']                                      ,color=bcl['edcl']  ))
1012            ,('edcb',d(vis=vis['edcb']                  ,items=its['edcb']  ))
1013            ,('edrf',d(vis=vis['edrf']                                      ))
1014            ,('edrt',d(vis=vis['edrt']                                      ))
1015            ,('brow',d(vis=vis['brow']                                      ))
1016            ,('toop',d(vis=vis['toop']                                      ))
1017            ,('opjs',d(vis=vis['opjs']                                      ))
1018            ,('dfv_',d(                                  hint=m.cur_op      ))
1019            ,('dfvl',d(                                                     ))
1020#           ,('dfvl',d(                en=ens['dfvl']                       ,color=bcl['dfvl']  ))
1021            ,('dfcl',d(vis=vis['dfcl']                                      ,color=bcl['dfcl']  ))
1022            ,('setd',d(                en=ens['setd']                       ))
1023            ,('tofi',d(                en=tofi_en                           ))
1024            ][1:]
1025
1026        if what and cnts:
1027            # Part info
1028            return cnts
1029
1030        # Full dlg controls info    #NOTE: cnts
1031        edit_h  = get_gui_height('edit')
1032        cmnt_t  = m.dlg_h-m.h_cmnt-5-M.STBR_H
1033        tofi_c  = m.ed.get_prop(app.PROP_TAB_TITLE)
1034        co_tp   = 'ed' if m.live_fltr else 'cb'
1035        cnts    = [0                                                                                                                        #
1036    # Hidden buttons
1037 ,('flt-',d(tp='bt' ,cap='&l'   ,sto=False              ,t=-99,l=0,w=44))  # &l
1038 ,('fltr',d(tp='bt' ,cap=''     ,sto=False  ,def_bt='1' ,t=-99,l=0,w=44))  # Enter
1039 ,('srt0',d(tp='bt' ,cap='&1'   ,sto=False              ,t=-99,l=0,w=44))  # &1
1040 ,('srt1',d(tp='bt' ,cap='&2'   ,sto=False              ,t=-99,l=0,w=44))  # &2
1041 ,('srt2',d(tp='bt' ,cap='&3'   ,sto=False              ,t=-99,l=0,w=44))  # &3
1042 ,('srt3',d(tp='bt' ,cap='&4'   ,sto=False              ,t=-99,l=0,w=44))  # &4
1043 ,('srt4',d(tp='bt' ,cap='&5'   ,sto=False              ,t=-99,l=0,w=44))  # &5
1044 ,('srt5',d(tp='bt' ,cap='&6'   ,sto=False              ,t=-99,l=0,w=44))  # &6
1045 ,('srt6',d(tp='bt' ,cap='&7'   ,sto=False              ,t=-99,l=0,w=44))  # &7
1046 ,('srt-',d(tp='bt' ,cap='&9'   ,sto=False              ,t=-99,l=0,w=44))  # &9
1047 ,('cws-',d(tp='bt' ,cap='&W'   ,sto=False              ,t=-99,l=0,w=44))  # &w
1048 ,('cpnm',d(tp='bt' ,cap='&C'   ,sto=False              ,t=-99,l=0,w=44))  # &c
1049 ,('erpt',d(tp='bt' ,cap='&O'   ,sto=False              ,t=-99,l=0,w=44))  # &o
1050 ,('apnw',d(tp='bt' ,cap='&Y'   ,sto=False              ,t=-99,l=0,w=44))  # &y
1051 ,('help',d(tp='bt' ,cap='&H'   ,sto=False              ,t=-99,l=0,w=44))  # &h
1052    # Top-panel
1053 ,('ptop',d(tp='pn' ,h=    270 ,w=m.dlg_w               ,ali=ALI_CL
1054                    ,h_min=270                                                                                                  ))
1055    # Menu
1056 ,('menu',d(tp='bt' ,tid='cond' ,l=-40-5,w=  40 ,p='ptop'   ,cap='&='                                               ,a='LR'     ))  # &=
1057    # Filter
1058 ,('chps',d(tp='bt' ,tid='cond' ,l=-270 ,r=-180 ,p='ptop'   ,cap=_('+&Section…')    ,hint=M.CHPS_H                  ,a='LR'     ))  # &s
1059 ,('flt_',d(tp='lb' ,tid='cond' ,l=   5 ,w=  70 ,p='ptop'   ,cap='>'+M.FILTER_C+':' ,hint=M.FLTR_H                              ))  # &f
1060 ,('cond',d(tp=co_tp,t=  5      ,l=  78 ,r=-270 ,p='ptop'   ,items=m.cond_hl                                        ,a='lR'     ))  #
1061#,('cond',d(tp='cb' ,t=  5      ,l=  78 ,r=-270 ,p='ptop'   ,items=m.cond_hl                                        ,a='lR'     ))  #
1062    # Table of keys+values
1063 ,('lvls',d(tp='lvw',t= 35,h=160,l=   5 ,r=  -5 ,p='ptop'   ,items=m.itms,cols=m.cols   ,grid='1'                   ,a='tBlR'   ))  #
1064    # Editors for value
1065 ,('ed_s',d(tp='lb' ,t=210      ,l=   5 ,w=  70 ,p='ptop'   ,cap=ed_s_c             ,hint=m.cur_op                  ,a='TB'     ))  # &e
1066 ,('eded',d(tp='ed' ,tid='ed_s' ,l=  78 ,r=-270 ,p='ptop'                           ,vis=vis['eded'],ex0=not ens['eded'],a='TBlR'   ))  #
1067#,('eded',d(tp='ed' ,tid='ed_s' ,l=  78 ,r=-270 ,p='ptop'                           ,vis=vis['eded'],en=ens['eded'] ,a='TBlR'   ))  #
1068 ,('edcl',d(tp='clr',t=210-2    ,l= 210 ,r=-271 ,p='ptop'   ,h=edit_h-4             ,vis=vis['edcl'],border=True    ,a='TBlR'   ))  #
1069 ,('edcb',d(tp='cbr',tid='ed_s' ,l=  78 ,r=-270 ,p='ptop'   ,items=its['edcb']      ,vis=vis['edcb']                ,a='TBlR'   ))  #
1070 ,('edrf',d(tp='ch' ,tid='ed_s' ,l=  78 ,w=  60 ,p='ptop'   ,cap=_('f&alse')        ,vis=vis['edrf']                ,a='TB'     ))  # &a
1071 ,('edrt',d(tp='ch' ,tid='ed_s' ,l= 140 ,w=  60 ,p='ptop'   ,cap=_('t&rue')         ,vis=vis['edrt']                ,a='TB'     ))  # &r
1072 ,('brow',d(tp='bt' ,tid='ed_s' ,l=-270 ,w=  90 ,p='ptop'   ,cap=_('&...')          ,vis=vis['brow']                ,a='TBLR'   ))  # &.
1073 ,('toop',d(tp='bt' ,tid='ed_s' ,l=-270 ,w=  90 ,p='ptop'   ,cap=_('&GoTo')         ,vis=vis['toop'],hint=M.TOOP_H  ,a='TBLR'   ))  # &g
1074 ,('opjs',d(tp='bt' ,tid='ed_s' ,l=-270 ,w=  90 ,p='ptop'   ,cap=_('E&dit')         ,vis=vis['opjs'],hint=M.OPME_H  ,a='TBLR'   ))  # &d
1075    # View def-value
1076 ,('dfv_',d(tp='lb' ,tid='dfvl' ,l=   5 ,w=  70 ,p='ptop'   ,cap=_('>Defa&ult:')    ,hint=m.cur_op                  ,a='TB'     ))  # &u
1077#,('dfvl',d(tp='ed' ,t=235      ,l=  78 ,r=-270 ,p='ptop'   ,en=False               ,sto=False                      ,a='TBlR'   ))  #
1078 ,('dfvl',d(tp='ed' ,t=235      ,l=  78 ,r=-270 ,p='ptop'   ,ex0=True               ,sto=False                      ,a='TBlR'   ))  #
1079#,('dfvl',d(tp='ed' ,t=235      ,l=  78 ,r=-270 ,p='ptop'   ,ro_mono_brd='1,0,1'    ,sto=False                      ,a='TBlR'   ))  #
1080 ,('dfcl',d(tp='clr',t=235+1    ,l= 210 ,r=-271 ,p='ptop'   ,h=edit_h-4             ,vis=vis['dfcl'],border=True    ,a='TBlR'   ))  #
1081 ,('setd',d(tp='bt' ,tid='dfvl' ,l=-270 ,w=  90 ,p='ptop'   ,cap=_('Rese&t')                        ,en=ens['setd'] ,a='TBLR'   ))  # &t
1082    # For lexer/file
1083#,('to__',d(tp='lb' ,tid='ed_s' ,l=-170 ,w=  30 ,p='ptop'   ,cap=_('>For:')                                         ,a='TBLR'   ))  #
1084 ,('to__',d(tp='lb' ,tid='ed_s' ,l=-165 ,w=  30 ,p='ptop'   ,cap=_('For:')                                          ,a='TBLR'   ))  #
1085 ,('tolx',d(tp='ch' ,tid='ed_s' ,l=-140 ,w=  70 ,p='ptop'   ,cap=_('Le&xer')                                        ,a='TBLR'   ))  # &x
1086 ,('tofi',d(tp='ch' ,tid='ed_s' ,l=- 90 ,w=  70 ,p='ptop'   ,cap=_('F&ile')         ,hint=tofi_c    ,en=tofi_en     ,a='TBLR'   ))  # &i
1087 ,('lexr',d(tp='cbr',tid='dfvl' ,l=-165 ,w= 160 ,p='ptop'   ,items=m.lexr_w_l                                       ,a='TBLR'   ))
1088    # Comment
1089 ,('cmsp',d(tp='sp' ,y=cmnt_t-5                         ,ali=ALI_BT,sp_lr=5                                                     ))
1090 ,('cmnt',d(tp='me' ,t=cmnt_t   ,h=    m.h_cmnt
1091                                ,h_min=M.CMNT_MHT       ,ali=ALI_BT,sp_lrb=5       ,ro_mono_brd='1,1,1'                         ))
1092 ,('stbr',d(tp='sb' ,y=-M.STBR_H
1093                    ,h= M.STBR_H                        ,ali=ALI_BT                                                             ))
1094                ][1:]
1095        if 'mac'==get_desktop_environment():
1096            cnts    = [(cid,cnt) for cid,cnt in cnts if cnt.get('cap', '')[:3]!='srt']
1097        cnts    = odict(cnts)
1098        if m.how.get('hide_fil', False):
1099            for cid in ('tofi',):
1100                cnts[cid]['vis'] = False
1101        if m.how.get('hide_lex_fil', False):
1102            for cid in ('to__', 'tolx', 'lexr', 'tofi'):
1103                cnts[cid]['vis'] = False
1104        for cnt in cnts.values():
1105            if 'l' in cnt:  cnt['l']    = m.dlg_w+cnt['l'] if cnt['l']<0 else cnt['l']
1106            if 'r' in cnt:  cnt['r']    = m.dlg_w+cnt['r'] if cnt['r']<0 else cnt['r']
1107            if 'y' in cnt:  cnt['y']    = m.dlg_h+cnt['y'] if cnt['y']<0 else cnt['y']
1108
1109        cnts['menu']['call']            = m.do_menu
1110        cnts['chps']['call']            = m.do_menu
1111        cnts['cpnm']['call']            = m.do_menu
1112        cnts['erpt']['call']            = m.do_menu
1113        cnts['apnw']['call']            = m.do_menu
1114        cnts['flt-']['call']            = m.do_fltr
1115        cnts['fltr']['call']            = m.do_fltr
1116        if m.live_fltr:
1117            cnts['cond']['call']        = m.do_fltr
1118        cnts['lexr']['call']            = m.do_lxfi
1119        cnts['tolx']['call']            = m.do_lxfi
1120        cnts['tofi']['call']            = m.do_lxfi
1121        cnts['lvls']['call']            = m.do_sele
1122        cnts['lvls']['on_click_header'] = m.do_sort
1123        cnts['srt0']['call']            = m.do_sort
1124        cnts['srt1']['call']            = m.do_sort
1125        cnts['srt2']['call']            = m.do_sort
1126        cnts['srt3']['call']            = m.do_sort
1127        cnts['srt4']['call']            = m.do_sort
1128        cnts['srt5']['call']            = m.do_sort
1129        cnts['srt6']['call']            = m.do_sort
1130        cnts['srt-']['call']            = m.do_sort
1131        cnts['cmsp']['call']            = m.do_cust
1132        cnts['cws-']['call']            = m.do_cust
1133        cnts['lvls']['on_click_dbl']    = m.do_dbcl   #lambda idd,idc,data:print('on dbl d=', data)
1134        cnts['setd']['call']            = m.do_setv
1135        cnts['edcb']['call']            = m.do_setv
1136        cnts['edrf']['call']            = m.do_setv
1137        cnts['edrt']['call']            = m.do_setv
1138        cnts['brow']['call']            = m.do_setv
1139        cnts['toop']['call']            = m.do_setv
1140        cnts['opjs']['call']            = m.do_setv
1141        cnts['help']['call']            = m.do_help
1142        return cnts
1143       #def get_cnts
1144
1145    def get_vals(self, what=''):
1146        M,m = self.__class__,self
1147        m.cur_in    = m._prep_opt('key2ind')
1148        if not what or 'cur' in what:
1149            vis,ens,vas,its,bcl = m._prep_opt()
1150        if not what:
1151            # all
1152            return dict(cond=m.cond_s
1153                       ,lvls=m.cur_in
1154                       ,eded=vas['eded']
1155                       ,edcb=vas['edcb']
1156                       ,edrf=vas['edrf']
1157                       ,edrt=vas['edrt']
1158                       ,dfvl=vas['dfvl']
1159                       ,cmnt=vas['cmnt']
1160                       ,tolx=m.for_ulf=='l'
1161                       ,tofi=m.for_ulf=='f'
1162                       ,lexr=m.lexr_l.index(m.lexr)     if m.lexr in m.lexr_l else -1
1163                       )
1164        if '+' in what:
1165            rsp = dict()
1166            if '+lvls' in what:
1167                rsp.update(dict(
1168                        lvls=m.cur_in
1169                        ))
1170            if '+cur' in what:
1171                rsp.update(dict(
1172                        eded=vas['eded']
1173                       ,edcb=vas['edcb']
1174                       ,edrf=vas['edrf']
1175                       ,edrt=vas['edrt']
1176                       ,dfvl=vas['dfvl']
1177                       ,cmnt=vas['cmnt']
1178                       ))
1179            if '+inlxfi' in what:
1180                rsp.update(dict(
1181                        tolx=m.for_ulf=='l'
1182                       ,tofi=m.for_ulf=='f'
1183                       ))
1184            pass;              #LOG and log('rsp={}',(rsp))
1185            return rsp
1186
1187        if what=='lvls':
1188            return dict(lvls=m.cur_in
1189                       )
1190        if what=='lvls-cur':
1191            return dict(lvls=m.cur_in
1192                       ,eded=vas['eded']
1193                       ,edcb=vas['edcb']
1194                       ,edrf=vas['edrf']
1195                       ,edrt=vas['edrt']
1196                       ,dfvl=vas['dfvl']
1197                       ,cmnt=vas['cmnt']
1198                       )
1199        if what=='cur':
1200            return dict(eded=vas['eded']
1201                       ,edcb=vas['edcb']
1202                       ,edrf=vas['edrf']
1203                       ,edrt=vas['edrt']
1204                       ,dfvl=vas['dfvl']
1205                       ,cmnt=vas['cmnt']
1206                       )
1207       #def get_vals
1208
1209    def do_resize(self, ag):
1210        M,m = self.__class__,self
1211        m.stbr_act(M.STBR_MSG, '')
1212        f_w     = ag.fattr('w')
1213        l_w     = ag.cattr('lvls', 'w')
1214        pass;                  #LOG and log('f_w,l_w={}',(f_w,l_w))
1215        if f_w < m.dlg_min_w:           return []   # fake event
1216
1217        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1218        if f_w == m.dlg_min_w and m.col_ws!=M.COL_MWS:
1219            return m.do_cust('cws-', ag)
1220
1221        sum_ws  = sum(m.col_ws)
1222        pass;                  #LOG and log('l_w,sum_ws={}',(l_w,sum_ws))
1223        if sum_ws >= (l_w - M.COL_N - M.SCROLL_W):return []   # decrease dlg - need user choice
1224
1225        # Auto increase widths of def-val and user-val cols
1226        extra   = int((l_w - M.COL_N - M.SCROLL_W - sum_ws)/2)
1227        pass;                  #LOG and log('extra={}',(extra))
1228        pass;                  #LOG and log('m.col_ws={}',(m.col_ws))
1229        m.col_ws[3] += extra
1230        m.col_ws[4] += extra
1231        pass;                  #LOG and log('m.col_ws={}',(m.col_ws))
1232        return d(ctrls=m.get_cnts('+cols'))
1233       #def do_resize
1234
1235    def do_cust(self, aid, ag, data=''):
1236        M,m = self.__class__,self
1237        m.stbr_act(M.STBR_MSG, '')
1238        pass;                  #LOG and log('aid={}',(aid))
1239        if False:pass
1240        elif aid=='cmsp':
1241            # Splitter moved
1242            sp_y    = ag.cattr('cmsp', 'y')
1243            return []
1244            ##??
1245
1246        elif aid=='cws-':
1247            # Set def col widths
1248            m.col_ws    = M.COL_MWS[:]
1249            m.stores.pop(m.subset+'col_ws', None)
1250            return d(ctrls=m.get_cnts('+cols'))
1251
1252        elif aid=='vali':
1253            if dlg_valign_consts():
1254                return d(ctrls=m.get_cnts())
1255            return []
1256
1257        elif aid=='rslt':
1258            # Restore dlg/ctrls sizes
1259            fpr         = ag.fattrs()
1260            layout      = data
1261            m.col_ws    = layout.get('col_ws', m.col_ws)
1262            cmnt_h      = layout.get('cmnt_h', ag.cattr('cmnt', 'h'))
1263            dlg_h       = layout.get('dlg_h' , fpr['h'])
1264            dlg_w       = layout.get('dlg_w' , fpr['w'])
1265            return  d(ctrls=
1266                         m.get_cnts('+cols')+
1267                        [('cmnt', d(h=cmnt_h))
1268                        ,('stbr', d(y=dlg_h))   # Hack to push it at bottom (by Alex)
1269                    ],form=d(
1270                         h=dlg_h
1271                        ,w=dlg_w
1272                    ))
1273        elif aid=='svlt':
1274            # Save dlg/ctrls sizes
1275            m.col_ws        = [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1276            layout          = data
1277            fpr             = ag.fattrs()
1278            layout['dlg_w'] = fpr['w']
1279            layout['dlg_h'] = fpr['h']
1280            layout['cmnt_h']= ag.cattr('cmnt', 'h')
1281            layout['col_ws']= m.col_ws
1282       #def do_cust
1283
1284    def do_menu(self, aid, ag, data=''):
1285        pass;                  #LOG and log('aid={}',(aid))
1286        M,m = self.__class__,self
1287        m.stbr_act(M.STBR_MSG, '')
1288
1289        scam    = app.app_proc(app.PROC_GET_KEYSTATE, '')
1290        if scam=='c' and aid=='menu':
1291            return m.do_cust('vali', ag)
1292
1293        def wnen_menu(ag, tag):
1294            pass;              #LOG and log('tag={}',(tag))
1295            if False:pass
1296
1297            elif tag[:3]=='ch:':
1298                return m.do_fltr('chps', ag, tag[3:])
1299
1300            elif tag=='srt-':
1301                return m.do_sort('', ag, -1)
1302            elif tag[:3]=='srt':
1303                return m.do_sort('', ag, int(tag[3]))
1304
1305            elif tag=='cws-':
1306                return m.do_cust(tag, ag)
1307            elif tag=='vali':
1308                return m.do_cust(tag, ag)
1309
1310#           elif tag=='lubk':
1311#               if app.ID_OK != app.msg_box(
1312#                               _('Restore user settings from backup copy?')
1313#                               , app.MB_OKCANCEL+app.MB_ICONQUESTION): return []
1314#               return m.do_file('restore-user')
1315#           elif tag=='llbk':
1316#               if app.ID_OK != app.msg_box(
1317#                               f(_('Restore lexer "{}" settings from backup copy?'), m.lexr)
1318#                               , app.MB_OKCANCEL+app.MB_ICONQUESTION): return []
1319#               return m.do_file('restore-lexr')
1320#           elif tag=='dobk':
1321#               m.stores[m.subset+'bk_sets'] = m.bk_sets = not m.bk_sets
1322#               return []
1323
1324        #   elif tag=='dfns':
1325        #       m.col_ws    = [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1326        #       new_file    = app.dlg_file(True, m.defn_path.name, str(m.defn_path.parent), 'JSONs|*.json')
1327        #       if not new_file or not os.path.isfile(new_file):    return []
1328        #       return m.do_file('set-dfns', new_file)
1329            elif tag=='full':
1330                m.col_ws    = [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1331                m.all_ops   = not m.all_ops
1332                m.opts_full = load_vals(m.opts_defn
1333                                       ,lexr_json='lexer '+m.lexr+'.json'
1334                                       ,user_json=m.how.get('stor_json', 'user.json')
1335                                       , ed_=m.ed, full=m.all_ops)
1336                m.cur_op    = m.cur_op if m.cur_op in m.opts_full else ''
1337                m.do_file('build-chp-tree')
1338                m.stbr_act(M.STBR_ALL, len(m.opts_full))
1339                return d(ctrls=odict(m.get_cnts('+lvls +cur')))
1340
1341            if tag=='apex':
1342                m.apply_one = not m.apply_one
1343                m.stores[m.subset+'apply_one']  = m.apply_one
1344            if tag=='apnw':
1345                ed.cmd(cmds.cmd_OpsReloadAndApply)
1346            if tag=='aufi':
1347                m.auto4file = not m.auto4file
1348                m.stores[m.subset+'auto4file']  = m.auto4file
1349
1350            if tag=='lifl':
1351                m.stores[m.subset+'live_fltr']  = not m.stores.get(m.subset+'live_fltr' , False)
1352                M.restart       = True
1353                M.restart_cond  = ag.cval('cond')
1354                return None         # Close dlg
1355
1356            elif tag=='cpnm':
1357                app.app_proc(app.PROC_SET_CLIP, m.cur_op)
1358            elif tag=='erpt':
1359                body    = '\n'.join(m.chng_rpt)
1360                dlg_wrapper(_('Сhange log')       ,  500+10     ,400+10,
1361                    [ dict(cid='body',tp='me' ,l=5,w=500  ,t=5,h=400, ro_mono_brd='1,0,0')]
1362                    , dict(body=body), focus_cid='body')
1363            elif tag=='locv':
1364        #       m.do_file('locate-opt')                     # while wait core fix
1365                if m.do_file('goto-opt'):   return None     #   need close dlg
1366            elif tag=='locd':
1367        #       m.do_file('locate-def')                     # while wait core fix
1368                if m.do_file('goto-def'):   return None     #   need close dlg
1369
1370            elif tag[:4] in ('rslt', 'rmlt', 'svlt'):
1371                layouts_l   = m.stores.get(m.subset+'layouts', [])  # [{nm:Nm, dlg_h:H, dlg_w:W, ...}]
1372                layouts_d   = {lt['nm']:lt for lt in layouts_l}
1373                lt_i        = int(tag[4:])      if tag[:4] in ('rslt', 'rmlt')  else -1
1374                layout      = layouts_l[lt_i]   if lt_i>=0                      else None
1375                if 0:pass
1376                elif tag[:4]=='rmlt':
1377                    if  app.ID_OK != app.msg_box(
1378                                f(_('Remove layout "{}"?'), layout['nm'])
1379                                , app.MB_OKCANCEL+app.MB_ICONQUESTION): return []
1380                    del layouts_l[lt_i]
1381                elif tag=='svlt':
1382                    nm_tmpl     = _('#{}')
1383                    layout_nm   = f(nm_tmpl
1384                                   ,first_true(itertools.count(1+len(layouts_d))
1385                                            ,pred=lambda n:f(nm_tmpl, n) not in layouts_d))     # First free #N after len()
1386                    while True:
1387                        pass;  #LOG and log('layout_nm={!r}',(layout_nm))
1388                        layout_nm   = app.dlg_input('Name to save current sizes of the dialog and controls', layout_nm)
1389                        if not layout_nm:   return []
1390                        layout_nm   = layout_nm.strip()
1391                        if not layout_nm:   return []
1392                        if layout_nm in layouts_d and \
1393                            app.ID_OK != app.msg_box(
1394                                    f(_('Name "{}" already used. Overwrite?'), layout_nm)
1395                                    , app.MB_OKCANCEL+app.MB_ICONQUESTION): continue
1396                        break
1397                    layout      = None
1398                    if layout_nm in layouts_d:
1399                        layout  = layouts_d[layout_nm]  # Overwrite
1400                    else:
1401                        layout  = d(nm=layout_nm)       # Create
1402                        layouts_l+=[layout]
1403                    # Fill
1404                    m.do_cust(       'svlt', ag, layout)
1405                elif tag[:4]=='rslt':
1406                    return m.do_cust('rslt', ag, layout)
1407                # Save
1408                m.stores[m.subset+'layouts']    = layouts_l
1409                return []
1410
1411            elif tag=='rprt':
1412                m.do_file('out-rprt')
1413            elif tag=='help':
1414                return m.do_help('', ag)
1415            return []
1416           #def wnen_menu
1417        pass;                  #LOG and log('',())
1418
1419        if aid=='chps':
1420            def tree2menu(node, chp=''):
1421                mn_l    = [    d( tag='ch:'+                node['path']
1422                                , cap=f('{} ({})', chp, len(node['ops']))
1423                                , cmd=wnen_menu)
1424                              ,d( cap='-')
1425                          ] if chp else []
1426                for chp,kid in                              node['kids'].items():
1427                    mn_l   +=([d( cap=f('{} ({})', chp, len(kid['ops']))
1428                                , sub=tree2menu(kid, chp))
1429                              ]
1430                                if 'kids' in kid else
1431                              [d( tag='ch:'+                kid['path']
1432                                , cap=f('{} ({})', chp, len(kid['ops']))
1433                                , cmd=wnen_menu)
1434                              ]
1435                             )
1436                return mn_l
1437               #def tree2menu
1438            mn_its  = tree2menu(m.chp_tree)
1439            ag.show_menu('chps', mn_its)
1440
1441        if aid=='apnw': return wnen_menu(ag, aid)
1442        if aid=='cpnm': return wnen_menu(ag, aid)
1443        if aid=='erpt': return wnen_menu(ag, aid)
1444
1445        if aid=='menu':
1446            locv_c  = f(M.LOCV_C, m.cur_op)
1447            locd_c  = f(M.LOCD_C, m.cur_op)
1448            lts_l   = m.stores.get(m.subset+'layouts', [])  # [{nm:Nm, dlg_h:H, dlg_w:W, ...}]
1449            full_en = not m.how.get('only_with_def', False) # Forbid to switch fo User+Lexer ops
1450            live_fltr=m.stores.get(m.subset+'live_fltr' , False)
1451            pass;              #lts_l   = [d(nm='Nm1'), d(nm='Nm2')]
1452            mn_its  = \
1453    [ d(tag='cpnm'              ,cap=_('&Copy option name')                                     ,key='Alt+C'
1454    ),d(                         cap='-'
1455    ),d(                         cap=_('&Layout')           ,sub=
1456        [ d(tag='svlt'              ,cap=_('&Save current layout...')
1457        ),d(                         cap='-'
1458        )]+     (
1459        [ d(tag='rslt'+str(nlt)     ,cap=f(_('Restore layout "{}"'), lt['nm']))         for nlt, lt in enumerate(lts_l)
1460        ]+
1461        [ d(                         cap=_('&Forget layout'),sub=
1462            [ d(tag='rmlt'+str(nlt)     ,cap=f(_('Forget layout "{}"...'), lt['nm']))   for nlt, lt in enumerate(lts_l)
1463            ])
1464        ]       if lts_l else []) +
1465        [ d(                         cap='-'
1466        ),d(tag='vali'              ,cap=_('Adjust vertical alignments...')
1467        ),d(tag='cws-'              ,cap=_('Set default columns &widths')                       ,key='Alt+W'
1468        )]
1469    ),d(                         cap=_('&Table')            ,sub=
1470        [ d(tag='srt'+str(cn)       ,cap=f(_('Sort by column "{}"'), cs.split()[0])
1471                                                                            ,ch=sorts_on(m.sorts, cn)
1472                                                                                                ,key='Alt+'+str(1+cn))
1473                                                            for cn, cs in enumerate(M.COL_NMS)
1474        ]+
1475        [ d(                         cap='-'
1476        ),d(tag='srt-'              ,cap=_('Reset sorting')                                     ,key='Alt+9'
1477        )]
1478    ),d(                         cap=_('M&ore')             ,sub=
1479        [ d(tag='locv'              ,cap=locv_c                             ,en=bool(m.cur_op)
1480        ),d(tag='locd'              ,cap=locd_c                             ,en=bool(m.cur_op)
1481        ),d(                         cap='-'
1482        ),d(tag='erpt'              ,cap=_('Show rep&ort of changes...')                        ,key='Alt+O'
1483        ),d(tag='apex'              ,cap=_('Apply changes on exit')         ,ch=m.apply_one
1484        ),d(tag='apnw'              ,cap=_('Appl&y changes now')            ,en=m.apply_need    ,key='Alt+Y'
1485        ),d(tag='aufi'              ,cap=_('Auto-update FILE options')      ,ch=m.auto4file
1486        ),d(                         cap='-'
1487        ),d(tag='lifl'              ,cap=M.LIFL_C                           ,ch=live_fltr
1488        ),d(                         cap='-'
1489        ),d(tag='full'              ,cap=M.FULL_C                           ,ch=m.all_ops   ,en=full_en
1490        )]
1491    ),d(                         cap='-'
1492    ),d(    tag='rprt'          ,cap=_('Create HTML &report')
1493    ),d(                         cap='-'
1494    ),d(    tag='help'          ,cap=_('&Help...')                                              ,key='Alt+H'
1495    )]
1496            pass;              #LOG and log('mn_its=¶{}',pf(mn_its))
1497            def add_cmd(its):
1498                for it in its:
1499                    if 'sub' in it: add_cmd(it['sub'])
1500                    else:                   it['cmd']=wnen_menu
1501            add_cmd(mn_its)
1502            ag.show_menu(aid, mn_its)
1503        return []
1504       #def do_menu
1505
1506    def do_fltr(self, aid, ag, data=''):
1507        M,m = self.__class__,self
1508        m.stbr_act(M.STBR_MSG, '')
1509        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1510
1511        fid     = ag.fattr('fid')
1512        pass;                  #LOG and log('aid,fid={}',(aid,fid))
1513        if aid=='fltr' and fid in ('dfvl', 'eded', 'edrf', 'edrt'):
1514            # Imitate default button
1515            return m.do_setv('setd' if fid in ('dfvl',)         else
1516                             'setv' if fid in ('eded',)         else
1517                             fid    if fid in ('edrf', 'edrt')  else
1518                             ''
1519                            , ag)
1520
1521        if aid=='cond':
1522            pass;              #LOG and log('ag.cval(cond)={}',(ag.cval('cond')))
1523            m.cond_s    = ag.cval('cond')
1524            fid         = '' if m.live_fltr else 'lvls'
1525        if aid=='fltr':
1526            m.cond_s    = ag.cval('cond')
1527            m.cond_hl   = add_to_history(m.cond_s, m.cond_hl)       if m.cond_s and not m.live_fltr else m.cond_hl
1528            fid         = 'lvls'
1529        if aid=='flt-':
1530            m.cond_s    = ''
1531            fid         = 'cond'
1532
1533        if aid=='chps':
1534            # Append selected chapter as filter value
1535            scam        = app.app_proc(app.PROC_GET_KEYSTATE, '')
1536            path        = '@'+data
1537            if path not in m.cond_s:
1538                if scam!='c':
1539                    m.cond_s= re.sub(r'@([\w/]*)', '', m.cond_s).strip()    # del old
1540                m.cond_s    = (m.cond_s+' '+path).strip()                   # add new
1541                m.cond_hl   = add_to_history(m.cond_s, m.cond_hl)   if not m.live_fltr else m.cond_hl
1542            fid         = 'cond'
1543
1544        # Select old/new op
1545        m.cur_op= m._prep_opt('ind2key')
1546        ctrls   = m.get_cnts('+lvls')
1547        m.cur_in= m._prep_opt('key2ind')
1548        if m.cur_in<0 and m.SKWULFs:
1549            # Sel top if old hidden
1550            m.cur_in= 0
1551            m.cur_op= m._prep_opt('ind2key', ind=m.cur_in)
1552        return d(ctrls=m.get_cnts('+cond =lvls +cur')
1553                ,vals =m.get_vals()
1554                ,form =d(fid=fid)
1555                )
1556
1557       #def do_fltr
1558
1559    def do_sort(self, aid, ag, col=-1):
1560        scam    = app.app_proc(app.PROC_GET_KEYSTATE, '')
1561        pass;                  #LOG and log('col,scam={}',(col,scam))
1562        pass;                  #return []
1563        M,m = self.__class__,self
1564        m.stbr_act(M.STBR_MSG, '')
1565        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1566
1567        if aid=='srt-' or col==-1:
1568            m.sorts = sorts_dflt(len(M.COL_NMS))
1569        else:
1570            col     = int(aid[3]) if aid[:3]=='srt' else col
1571            pass;              #LOG and log('?? m.sorts={}',(m.sorts))
1572            m.sorts = sorts_turn(m.sorts, col, scam)
1573            pass;              #LOG and log('ok m.sorts={}',(m.sorts))
1574
1575        old_in  = m._prep_opt('key2ind')
1576        ctrls   = m.get_cnts('+lvls')
1577        if old_in==0:
1578            # Set top if old was top
1579            m.cur_in= 0
1580            m.cur_op= m._prep_opt('ind2key', ind=m.cur_in)
1581        else:
1582            # Save old op
1583            m.cur_in= m._prep_opt('key2ind')
1584        return d(ctrls=m.get_cnts('=lvls +cur')
1585                ,vals =m.get_vals()
1586                )
1587       #def do_sort
1588
1589    def do_sele(self, aid, ag, data=''):
1590        M,m = self.__class__,self
1591        m.stbr_act(M.STBR_MSG, '')
1592        pass;                  #LOG and log('data,m.cur_op,m.cur_in={}',(data,m.cur_op,m.cur_in))
1593        m.cur_op= m._prep_opt('ind2key')
1594        pass;                  #LOG and log('m.cur_op,m.cur_in={}',(m.cur_op,m.cur_in))
1595        pass;                  #log('###m.get_cnts(+cur)={}',(m.get_cnts('+cur')))
1596        return d(ctrls=odict(m.get_cnts('+cur'))
1597                ,vals =      m.get_vals('cur')
1598                )
1599       #def do_sele
1600
1601    def do_lxfi(self, aid, ag, data=''):
1602        M,m = self.__class__,self
1603        m.stbr_act(M.STBR_MSG, '')
1604        pass;                  #LOG and log('aid={}',(aid))
1605        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1606
1607        if False:pass
1608        elif aid in ('tolx', 'tofi'):
1609            # Changed "For Lexer/File"
1610            m.for_ulf   = 'l' if aid=='tolx' and ag.cval('tolx') else \
1611                          'f' if aid=='tofi' and ag.cval('tofi') else \
1612                          'u'
1613            fid         = 'lexr' \
1614                            if m.for_ulf=='l' and m.lexr not in m.lexr_l else \
1615                          m._prep_opt('fid4ed')
1616            return d(ctrls=m.get_cnts('+cur')
1617                    ,vals =m.get_vals('+cur+inlxfi')
1618                    ,form =d(fid=fid)
1619                    )
1620        elif aid=='lexr':
1621            # Change current lexer
1622            lexr_n  = ag.cval('lexr')
1623            m.lexr  = m.lexr_l[lexr_n]      if lexr_n>=0 else ''
1624            m.cur_op= m._prep_opt('ind2key')
1625            m.do_file('load-data')
1626            ctrls   = m.get_cnts('+lvls')
1627            m.cur_in= m._prep_opt('key2ind')
1628            if m.cur_in<0 and m.SKWULFs:
1629                # Sel top if old hidden
1630                m.cur_in= 0
1631                m.cur_op= m._prep_opt('ind2key', ind=m.cur_in)
1632            elif m.cur_in<0:
1633                m.cur_op= ''
1634            return d(ctrls=m.get_cnts('=lvls +cur')
1635                    ,vals =m.get_vals()#'+lvls +cur')
1636                    )
1637       #def do_lxfi
1638
1639    def do_dbcl(self, aid, ag, data=''):
1640        M,m = self.__class__,self
1641        m.stbr_act(M.STBR_MSG, '')
1642        pass;                  #LOG and log('data,m.cur_op,m.cur_in={}',(data,m.cur_op,m.cur_in))
1643        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1644
1645        if aid!='lvls':     return []
1646        # Dbl-click on lvls cell
1647        if sum(m.col_ws) > ag.cattr('lvls', 'w') - M.SCROLL_W:
1648            # Has hor-scrolling
1649            pass;              #LOG and log('skip as h-scroll',())
1650            return []
1651        op_r    = ag.cval('lvls')
1652        op_c    = next(filter(                              # next(filter())==first_true
1653                    lambda col_n_sw: col_n_sw[1]>data[0]    # > x from click (x,y)
1654                  , enumerate(accumulate(m.col_ws))         # (n_col, sum(col<=n))
1655                  ), [-1, -1
1656                  ])[0]
1657        pass;                  #LOG and log('op_r,op_c,m.cur_op,m.cur_in={}',(op_r,op_c,m.cur_op,m.cur_in))
1658        pass;                  #LOG and log('op_r,op_c={}',(op_r,op_c))
1659        if False:pass
1660        elif op_c not in (M.COL_DEF,M.COL_USR,M.COL_LXR,M.COL_FIL):
1661            return []
1662        elif -1==op_r:
1663            pass;              #LOG and log('skip as no opt',())
1664            return []
1665        elif -1==op_c:
1666            pass;              #LOG and log('skip as miss col',())
1667            return []
1668        elif M.COL_DEF==op_c:
1669            return d(form =d(fid='setd'))
1670        elif M.COL_USR==op_c and m.for_ulf!='u':
1671            # Switch to user vals
1672            m.for_ulf   = 'u'
1673        elif M.COL_LXR==op_c and m.for_ulf!='l':
1674            # Switch to lexer vals
1675            m.for_ulf   = 'l'
1676        elif M.COL_FIL==op_c and m.for_ulf!='f':
1677            # Switch to lexer vals
1678            m.for_ulf   = 'f'
1679        else:
1680            return []
1681        pass;                   LOG and log('op_r,op_c,m.for_ulf={}',(op_r,op_c,m.for_ulf))
1682        return d(ctrls=m.get_cnts('+cur')
1683                ,vals =m.get_vals('+cur+inlxfi')
1684                ,form =d(fid=m._prep_opt('fid4ed'))
1685                )
1686       #def do_dbcl
1687
1688    def do_setv(self, aid, ag, data=''):
1689        M,m = self.__class__,self
1690        m.stbr_act(M.STBR_MSG, '')
1691        pass;                  #LOG and log('aid,m.cur_op={}',(aid,m.cur_op))
1692        if not m.cur_op:   return []
1693        m.col_ws= [ci['wd'] for ci in m.ag.cattr('lvls', 'cols')]
1694
1695        if aid=='toop':
1696#           m.do_file('locate-opt')                     # while wait core fix
1697            if m.do_file('goto-opt'):   return None     #   need close dlg
1698            return []
1699
1700        trg     = 'lexer '+m.lexr+'.json' if m.for_ulf=='l' else 'user.join'
1701        key4v   = m.for_ulf+'val'
1702        op      = m.cur_op
1703        oi      = m.opts_full[op]
1704        frm     = oi['frm']
1705#       if frm=='json':
1706#           m.stbr_act(M.STBR_MSG, f(_('Edit {!r} to change value'), trg))
1707#           return []
1708        dval    = oi.get( 'def')
1709        uval    = oi.get('uval')
1710        lval    = oi.get('lval')
1711        fval    = oi.get('fval')
1712        ulfvl   = oi.get(key4v ) #fval if m.for_ulf=='f' else lval if m.for_ulf=='l' else uval
1713        jval    = oi['jlvl']    if m.for_ulf=='l' else \
1714                  oi['juvl']    if m.for_ulf=='u' else \
1715                  oi['jfvl']
1716        scam    = app.app_proc(app.PROC_GET_KEYSTATE, '')
1717
1718        # Get new value
1719        newv    = None
1720        erpt_s  = ''
1721        if False:pass
1722
1723        elif aid=='setd'        and \
1724             m.for_ulf=='f'     and \
1725             op in apx.OPT2PROP:
1726            # Remove from file - set over def/user/lex val
1727            newv    = oi.get('lval', oi.get('uval', oi.get('def')))
1728            if newv==ulfvl:
1729                m.stbr_act(M.STBR_MSG, _('No need changes'))
1730                return []
1731            erpt_s  = 'reset-f'
1732            m.ed.set_prop(apx.OPT2PROP[op], newv)
1733
1734        elif aid=='setd'        and \
1735             ulfvl is not None  and \
1736             m.for_ulf!='f':
1737            # Remove from user/lexer
1738            if  scam!='c' and \
1739                app.ID_OK != app.msg_box(f(_('Remove {} option'
1740                                            '\n   {} = {!r}'
1741                                            '\n?'), 'LEXER' if m.for_ulf=='l' else 'USER', op, jval)
1742                                        , app.MB_OKCANCEL+app.MB_ICONQUESTION): return []
1743            newv= None
1744
1745        elif aid=='brow' and frm in ('hotk', 'file', '#rgb', '#rgb-e'):
1746            ulfvl_s = '' if ulfvl is None else ulfvl
1747            m.stbr_act(M.STBR_MSG, f(_('Default value: "{}". Old value: "{}"'), dval, ulfvl_s))
1748            if frm in ('#rgb', '#rgb-e'):
1749                ulfvl_s = ulfvl_s if ulfvl_s else dval if frm=='#rgb' else '#fff'
1750                newv    = app.dlg_color(apx.html_color_to_int(ulfvl_s))
1751                if newv is None:    return []
1752                newv    = apx.int_to_html_color(newv)
1753            else:
1754                newv= (app.dlg_hotkey(op)                                       if frm=='hotk' else
1755                       app.dlg_file(False, '', os.path.expanduser(ulfvl_s), '') if frm=='file' else None)
1756            m.stbr_act(M.STBR_MSG, '')
1757            if not newv:    return []
1758
1759        elif aid=='opjs':
1760            newv    = edit_json_as_dict(op, ulfvl, dval, oi.get('cmt' , ''))
1761            if newv is None:    return []
1762
1763        elif aid=='setv':                   # Add/Set opt for user/lexer/file
1764            # Enter from edit. Need parse some string
1765            newv    = m.ag.cval('eded')
1766            try:
1767                newv    =   int(newv)   if frm=='int'   else \
1768                          float(newv)   if frm=='float' else \
1769                                newv
1770            except Exception as ex:
1771                app.msg_box(f(_('Incorrect value. It\'s needed in format: {}'), frm)
1772                           , app.MB_OK+app.MB_ICONWARNING)
1773                return d(form=d(fid='eded'))
1774            if frm=='#rgb' or frm=='#rgb-e' and newv:       # Testing new val
1775                try:
1776                    apx.html_color_to_int(newv)
1777                except Exception as ex:
1778                    app.msg_box(f(_('Incorrect value. It\'s needed in format: {}'), '#RGB or #RRGGBB')
1779                               , app.MB_OK+app.MB_ICONWARNING)
1780                    return d(form=d(fid='eded'))
1781        elif aid in ('edrf', 'edrt'):       # Add/Set opt for user/lexer/file
1782            newv    = aid=='edrt'
1783            newv    = not newv if newv==ulfvl else newv
1784        elif aid=='edcb':                   # Add/Set opt into user/lexer/file
1785            pass;              #LOG and log('oi={}',(oi))
1786            vl_l    = [k for k,v in oi.get('dct', [])]  if 'dct' in oi else oi.get('lst', [])
1787            pass;              #LOG and log('vl_l={}',(vl_l))
1788            pass;              #LOG and log('m.ag.cval(edcb)={}',(m.ag.cval('edcb')))
1789            newv    = vl_l[m.ag.cval('edcb')]
1790            pass;              #LOG and log('newv={}',(newv))
1791
1792        # Use new value to change env
1793        if newv is not None and newv==ulfvl:
1794            m.stbr_act(M.STBR_MSG, _('No need changes'))
1795            return []
1796
1797        if m.for_ulf=='f' and newv is not None and op in apx.OPT2PROP:
1798            # Change for file
1799            erpt_s  = 'set-f'
1800            ed.set_prop(apx.OPT2PROP[op], newv)
1801
1802        if m.for_ulf!='f':
1803            # Change target file
1804            pass;              #LOG and log('?? do_erpt',())
1805            erpt_s  =('reset-u' if newv  is None and m.for_ulf=='u' else
1806                      'reset-l' if newv  is None and m.for_ulf=='l' else
1807                      'add-u'   if ulfvl is None and m.for_ulf=='u' else
1808                      'add-l'   if ulfvl is None and m.for_ulf=='l' else
1809                      'set-u'   if                   m.for_ulf=='u' else
1810                      'set-l'   if                   m.for_ulf=='l' else '')
1811            pass;              #LOG and log('?? set_opt',())
1812            apx.set_opt(op
1813                       ,newv
1814                       ,apx.CONFIG_LEV_LEX  if m.for_ulf=='l' else apx.CONFIG_LEV_USER
1815                       ,ed_cfg  =None
1816                       ,lexer   =m.lexr     if m.for_ulf=='l' else None
1817                       ,user_json=m.how.get('stor_json', 'user.json')
1818                       )
1819
1820            if not m.apply_one:
1821                pass;          #LOG and log('?? OpsReloadAndApply',())
1822                ed.cmd(cmds.cmd_OpsReloadAndApply)
1823            else:
1824                m.apply_need    = True
1825
1826        # Use new value to change dlg data
1827        pass;                  #LOG and log('?? oi={}',(oi))
1828        pass;                  #LOG and log('?? m.opts_full={}',pf(m.opts_full))
1829        if False:pass
1830        elif aid=='setd':
1831            oi.pop(key4v, None)     if m.for_ulf!='f' else 0
1832        else:
1833            pass;              #LOG and log('key4v, newv={}',(key4v, newv))
1834            oi[key4v] = newv
1835        pass;                  #LOG and log('oi={}',(oi))
1836        upd_cald_vals(m.opts_full)
1837        pass;                  #LOG and log('oi={}',(oi))
1838
1839        jnewv   = oi['jlvl']   if m.for_ulf=='l' else oi['juvl']    if m.for_ulf=='u' else oi['jfvl']
1840        m.do_erpt(erpt_s, jnewv, jval)
1841        pass;                  #LOG and log('ok oi={}',(oi))
1842        pass;                  #LOG and log('ok m.opts_full={}',pf(m.opts_full))
1843
1844        pass;                  #LOG and log('?? get_cnts',())
1845
1846        if m.for_ulf!='f' and m.auto4file and op in apx.OPT2PROP:
1847            # Change FILE to over
1848            newv    = oi.get('lval', oi.get('uval', oi.get('def')))
1849            if newv!=oi.get('fval'):
1850                erpt_s      = 'reset-f'
1851                m.ed.set_prop(apx.OPT2PROP[op], newv)
1852                oi['fval']  = newv
1853                jval        = oi['jfvl']
1854                upd_cald_vals(m.opts_full)
1855                jnewv       = oi['jfvl']
1856                m.do_erpt('auset-f', jnewv, jval)
1857
1858        pass;                  #LOG and log('m.get_vals(lvls-cur)={}',(m.get_vals('lvls-cur')))
1859        return d(ctrls=m.get_cnts('+lvls+cur')
1860                ,vals =m.get_vals('lvls-cur')
1861                )
1862       #def do_setv
1863
1864    def do_erpt(self, what='', jnewv=None, joldv=None):
1865        pass;                  #LOG and log('what, newv={}',(what, newv))
1866        M,m = self.__class__,self
1867
1868        if 0==len(m.chng_rpt):
1869            rpt = f('Starting to change options at {:%Y-%m-%d %H:%M:%S}', datetime.datetime.now())
1870            m.chng_rpt += [rpt]
1871
1872        oi  = m.opts_full[m.cur_op]
1873        oldv= None
1874        rpt = ''
1875        if 0:pass
1876        elif what=='reset-f':
1877            rpt     = f(_('Set FILE option to overridden value {!r}')       ,jnewv)
1878        elif what=='set-f':
1879            rpt     = f(_('Set FILE option to {!r}')                        ,jnewv)
1880        elif what=='auset-f':
1881            rpt     = f(_('Auto-set FILE option to overridden value {!r}')  ,jnewv)
1882        elif what=='reset-l':
1883            rpt     = f(_('Remove LEXER {!r} option')               ,m.lexr       )
1884        elif what=='set-l':
1885            rpt     = f(_('Set LEXER {!r} option to {!r}')          ,m.lexr ,jnewv)
1886        elif what=='add-l':
1887            rpt     = f(_('Add LEXER {!r} option {!r}')             ,m.lexr ,jnewv)
1888        elif what=='reset-u':
1889            rpt     = f(_('Remove USER option')                                   )
1890        elif what=='set-u':
1891            rpt     = f(_('Set USER option to {!r}')                        ,jnewv)
1892        elif what=='add-u':
1893            rpt     = f(_('Add USER option {!r}')                           ,jnewv)
1894        else:
1895            return
1896        rpt         = f('{} (from {!r})', rpt, joldv) \
1897                        if what[:3]!='add' and joldv is not None else rpt
1898        rpt         = rpt.replace('True', 'true').replace('False', 'false')
1899        rpt         = m.cur_op + ': '               + rpt
1900        rpt         = f('{}. ', len(m.chng_rpt))  + rpt
1901#       print(rpt)
1902        m.stbr_act(M.STBR_MSG, rpt + _('   [Alt+O - all changes]'))
1903        m.chng_rpt += [rpt]
1904       #def do_erpt
1905
1906    def do_help(self, aid, ag, data=''):
1907        M,m = self.__class__,self
1908        m.stbr_act(M.STBR_MSG, '')
1909        pass;                  #LOG and log('',())
1910        dlg_wrapper('Help'
1911        ,   680+10, 500+10
1912        ,   [d(cid='body', tp='me', l=5, t=5, w=680, h=500, ro_mono_brd='1,1,0')]
1913        ,   d(      body=   #NOTE: help
1914                 f(
1915  _(  'About "{fltr}"'
1916    '\r '
1917   )
1918   +M.FLTR_H+
1919  _('\r '
1920    '\rOther tips.'
1921    '\r • Use ENTER to filter table and to change or reset value.'
1922    '\r • Use double click on any cell in columns'
1923    '\r     "{c_usr}"'
1924    '\r     "{c_lxr}"'
1925    '\r     "{c_fil}"'
1926    '\r   to change "{in_lxr}" flag and to put focus on the value field.'
1927    '\r • Use double click on any cell in column'
1928    '\r     "{c_def}"'
1929    '\r   to put focus on "{reset}".'
1930    '\r • Clicking "{reset}" will ask for confirmation, for user/lexer options.'
1931    '\r   Hold Ctrl key to skip this confirmation.'
1932    '\r • Click on a column header sorts data in the column.'
1933    '\r     Alt+# (# is 1..8) sorts the N column (not on macOS).'
1934    '\r     Alt+9 resets sorting (not on macOS).'
1935    '\r     Click with Ctrl allows to sort by several columns.'
1936    '\r     Clicking with Ctrl on already sorted column does 2-state loop (down, up).'
1937    '\r     Clicking with Ctrl on already sorted column with maximal sorting index, '
1938    '\r     does 3-state loop (down, up, off).'
1939    '\r • Use option "{lifl}" to see instant update of the list after'
1940    '\r   each changing in the filter field'
1941    '\r   (otherwise you need to press Enter after changing).'
1942    '\r   With this option, no history of the filter is kept'
1943    '\r   (filter combobox has empty dropdown list).'
1944    '\r • If current list line is scrolled out of view, '
1945    '\r   you can still see the option name - in the tooltip'
1946    '\r   of "User" (Lexer/File) label near the value field.'
1947    '\r • Tooltip shows file name (or tag name), when cursor hovers the checkbox "{tofi}".'
1948    '\r • Some plugins store their settings into user.json.'
1949    '\r   So after a while, user.json contains options not present in default.json.'
1950    '\r   To see all these keys, use option "{full}".'
1951    '\r • Values in table column "!"'
1952    '\r     !   option is set in "user.json",'
1953    '\r     !!  option is set in "lexer NNN.json",'
1954    '\r     !!! option is set for current file,'
1955    '\r     L   default value is from "settings_default/lexer NNN.json",'
1956    '\r     +   not CudaText standard option.'
1957   )             , c_usr=M.COL_NMS[M.COL_USR]
1958                 , c_lxr=M.COL_NMS[M.COL_LXR]
1959                 , c_fil=M.COL_NMS[M.COL_FIL].split()[0]
1960                 , c_def=M.COL_NMS[M.COL_DEF]
1961                 , fltr = ag.cattr('flt_', 'cap', live=False).replace('&', '').strip(':')
1962                 , in_lxr=ag.cattr('tolx', 'cap', live=False).replace('&', '')
1963                 , reset= ag.cattr('setd', 'cap', live=False).replace('&', '')
1964                 , tofi = ag.cattr('tofi', 'cap', live=False).replace('&', '')
1965                 , lifl = M.LIFL_C.replace('&', '')
1966                 , full = M.FULL_C.replace('&', '')
1967                 ))
1968        )
1969        return []
1970       #def do_help
1971
1972    restart     = False
1973    restart_cond= None
1974   #class OptEdD
1975
1976
1977def edit_json_as_dict(op, uval, dval, cmnt4v):
1978    """ Allow user to edit JSON value
1979    """
1980    pass;                      #log("op, uval, dval={}",(op, uval, dval))
1981    newv    = None
1982    def acts(aid, ag, data=''):
1983        nonlocal newv
1984        if False:pass
1985        elif aid=='defv':
1986            return d(vals=d(meme=json.dumps(dval, indent=2)),fid='meme')
1987        elif aid=='undo':
1988            return d(vals=d(meme=json.dumps(uval, indent=2)),fid='meme')
1989        elif aid in ('test', 'okok'):
1990            mejs    = ag.cval('meme')
1991            pass;              #log("mejs={!r}",(mejs))
1992            try:
1993                jsvl    = json.loads(mejs, object_pairs_hook=odict)
1994            except Exception as ex:
1995                warn    = str(ex) + c10 + (c10.join('{:>3}|{}'.format(n+1, s.replace(' ','·'))
1996                                                    for n,s in enumerate(mejs.split(c10))))
1997                return d(vals=d(cmnt=warn),fid='meme')
1998#               app.msg_box(str(ex)
1999#                   +c10+(c10.join('{:>3}|{}'.format(n+1, s.replace(' ','·'))
2000#                                   for n,s in enumerate(mejs.split(c10))))
2001#                   , app.MB_OK)
2002#               return d(fid='meme')
2003            if aid=='okok':
2004                newv    = jsvl
2005                return None     # Close
2006            return d(vals=d(cmnt=cmnt4v),fid='meme')
2007       #def acts
2008    DlgAgent(
2009        form =dict(cap     = f(_('Edit JSON option ({})'), op)
2010                  ,resize  = True
2011                  ,w       = 510
2012                  ,h       = 400
2013                  )
2014    ,   ctrls=[0
2015        ,('meme',d(tp='me'  ,l=  5  ,w=500  ,t=  5  ,h=150                         ,a='tBlR'))
2016        ,('cmnt',d(tp='me'  ,l=  5  ,w=500  ,t=160  ,h=200 ,ro_mono_brd='1,1,1'    ,a='TBlR'))
2017        ,('defv',d(tp='bt'  ,l=  5  ,w=110  ,t=370  ,cap=_('Set &default')  ,a='TB'     ,call=acts  ,en=(dval is not None)))
2018        ,('undo',d(tp='bt'  ,l=120  ,w=110  ,t=370  ,cap=_('&Undo changes') ,a='TB'     ,call=acts))
2019        ,('test',d(tp='bt'  ,l=285  ,w= 70  ,t=370  ,cap=_('Chec&k')        ,a='TBLR'   ,call=acts))
2020        ,('cans',d(tp='bt'  ,l=360  ,w= 70  ,t=370  ,cap=_('Cancel')        ,a='TBLR'   ,call=acts))
2021        ,('okok',d(tp='bt'  ,l=435  ,w= 70  ,t=370  ,cap=_('OK')            ,a='TBLR'   ,call=acts  ,def_bt=True))
2022                ][1:]
2023    ,   vals =dict(meme=json.dumps(uval, indent=2)
2024                  ,cmnt=cmnt4v)
2025    ,   fid  ='meme'
2026    ).show()
2027    return newv
2028   #def edit_json_as_dict
2029
2030
2031class Command:
2032    def dlg_cuda_options(self):
2033        while True:
2034            OptEdD.restart    = False
2035            self._dlg_opt()
2036            if not OptEdD.restart:    break
2037       #def dlg_cuda_options
2038
2039    def _dlg_opt(self):
2040        if app.app_api_version()<MIN_API_VER:   return app.msg_status(_('Need update CudaText'))
2041        defs_json   = apx.get_opt('dlg_cuda_options.defs_json', 'default.json')
2042        defs_json   = defs_json     if os.sep in defs_json else     apx.get_def_setting_dir()+os.sep+defs_json
2043        OptEdD(
2044          path_keys_info=defs_json
2045        , subset='df.'
2046        ).show(_('CudaText options'))
2047       #def _dlg_opt
2048   #class Command
2049
2050def add_to_history(val:str, lst:list, max_len=MAX_HIST, unicase=False)->list:
2051    """ Add/Move val to list head. """
2052    lst_u = [ s.upper() for s in lst] if unicase else lst
2053    val_u = val.upper()               if unicase else val
2054    if val_u in lst_u:
2055        if 0 == lst_u.index(val_u):   return lst
2056        del lst[lst_u.index(val_u)]
2057    lst.insert(0, val)
2058    if len(lst)>max_len:
2059        del lst[max_len:]
2060    return lst
2061   #def add_to_history
2062
2063RPT_HEAD = '''
2064<html>
2065<head>
2066    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
2067    <title>CudaText options</title>
2068    <style type="text/css">
2069td, th, body {
2070    color:          #000;
2071    font-family:    Verdana, Arial, Helvetica, sans-serif;
2072    font-size:      12px;
2073}
2074table {
2075    border-width:   1px;
2076    border-spacing: 2px;
2077    border-color:   gray;
2078    border-collapse:collapse;
2079}
2080table td, table th{
2081    border-width:   1px;
2082    padding:        1px;
2083    border-style:   solid;
2084    border-color:   gray;
2085}
2086pre {
2087    margin:         0;
2088    padding:        0;
2089}
2090td.nxt {
2091    color:          grey;
2092    word-break:     break-all;
2093}
2094td.win {
2095    font-weight:    bold;
2096    word-break:     break-all;
2097}
2098    </style>
2099</head>
2100<body>
2101'''
2102RPT_FOOT = '''
2103</body>
2104</html>
2105'''
2106
2107def do_report(fn, lex='', ed_=ed):
2108    def hard_word_wrap(text, rmax):
2109        reShift     = re.compile(r'\s*')
2110        reHeadTail  = re.compile(r'(.{' + str(rmax) + r'}\S*)\s*(.*)')
2111        src_lines   = text.splitlines()
2112        pass;                  #print('src_lines=',src_lines)
2113        trg_lines   = []
2114        for line in src_lines:
2115            pass;              #print('line=', line, 'len=', len(line.rstrip()))
2116            if len(line.rstrip()) <= rmax:
2117                trg_lines.append(line)
2118                continue
2119            shift   = reShift.match(line).group(0)
2120            head,   \
2121            tail    = reHeadTail.match(line).group(1, 2)
2122            if not tail:
2123                tail= line.split()[-1]
2124                head= line[:-len(tail)]
2125            pass;              #print('head=', head, 'tail=', tail)
2126            trg_lines.append(head)
2127            trg_lines.append(shift+tail)
2128        pass;                  #print('trg_lines=',trg_lines)
2129        return '\n'.join(trg_lines)
2130       #def hard_word_wrap
2131
2132#   lex         = ed_.get_prop(app.PROP_LEXER_CARET)
2133    def_json    = apx.get_def_setting_dir()         +os.sep+'default.json'
2134    usr_json    = app.app_path(app.APP_DIR_SETTINGS)+os.sep+'user.json'
2135    lex_json    = app.app_path(app.APP_DIR_SETTINGS)+os.sep+lex                                 if lex else ''
2136
2137    def_opts    = apx._get_file_opts(def_json, {},  object_pairs_hook=collections.OrderedDict)
2138    usr_opts    = apx._get_file_opts(usr_json, {},  object_pairs_hook=collections.OrderedDict)
2139    lex_opts    = apx._get_file_opts(lex_json, {},  object_pairs_hook=collections.OrderedDict)  if lex else None
2140
2141    def_opts    = pickle.loads(pickle.dumps(def_opts))                              # clone to pop
2142    usr_opts    = pickle.loads(pickle.dumps(usr_opts))                              # clone to pop
2143    lex_opts    = pickle.loads(pickle.dumps(lex_opts))  if lex else {}              # clone to pop
2144
2145    fil_opts    = {op:ed_.get_prop(pr) for op,pr in apx.OPT2PROP.items()}
2146#   fil_opts    = get_ovrd_ed_opts(ed)
2147    cmt_opts    = {}
2148    # Find Commentary for def opts in def file
2149    # Rely: _commentary_ is some (0+) lines between opt-line and prev opt-line
2150    def_body    = open(def_json).read()
2151    def_body    = def_body.replace('\r\n', '\n').replace('\r', '\n')
2152    def_body    = def_body[def_body.find('{')+1:]   # Cut head with start '{'
2153    def_body    = def_body.lstrip()
2154    for opt in def_opts.keys():
2155        pos_opt = def_body.find('"{}"'.format(opt))
2156        cmt     = def_body[:pos_opt].strip()
2157        cmt     = ('\n\n'+cmt).split('\n\n')[-1]
2158        cmt     = re.sub('^\s*//', '', cmt, flags=re.M)
2159        cmt     = cmt.strip()
2160        cmt_opts[opt]    = html.escape(cmt)
2161        def_body= def_body[def_body.find('\n', pos_opt)+1:]   # Cut the opt
2162
2163    with open(fn, 'w', encoding='utf8') as f:
2164        f.write(RPT_HEAD)
2165        f.write('<h4>High priority: editor options</h4>\n')
2166        f.write('<table>\n')
2167        f.write(    '<tr>\n')
2168        f.write(    '<th>Option name</th>\n')
2169        f.write(    '<th>Value in<br>default</th>\n')
2170        f.write(    '<th>Value in<br>user</th>\n')
2171        f.write(    '<th>Value in<br>{}</th>\n'.format(lex))                                                            if lex else None
2172        f.write(    '<th title="{}">Value for file<br>{}</th>\n'.format(ed_.get_filename()
2173                                              , os.path.basename(ed_.get_filename())))
2174        f.write(    '<th>Comment</th>\n')
2175        f.write(    '</tr>\n')
2176        for opt in fil_opts.keys():
2177            winner  = 'def'
2178            winner  = 'usr' if opt in usr_opts else winner
2179            winner  = 'lex' if opt in lex_opts else winner
2180            winner  = 'fil' if opt in fil_opts else winner
2181            f.write(    '<tr>\n')
2182            f.write(    '<td>{}</td>\n'.format(opt))
2183            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='def' else 'nxt', def_opts.get(opt, '')))
2184            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='usr' else 'nxt', usr_opts.get(opt, '')))
2185            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='lex' else 'nxt', lex_opts.get(opt, '')))    if lex else None
2186            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='fil' else 'nxt', fil_opts.get(opt, '')))
2187#           f.write(    '<td><pre>{}</pre></td>\n'.format(cmt_opts.get(opt, '')))
2188            f.write(    '<td><pre>{}</pre></td>\n'.format(hard_word_wrap(cmt_opts.get(opt, ''), 50)))
2189            f.write(    '</tr>\n')
2190            def_opts.pop(opt, None)
2191            usr_opts.pop(opt, None)
2192            lex_opts.pop(opt, None)                                                                                     if lex else None
2193        f.write('</table><br/>\n')
2194        f.write('<h4>Overridden default options</h4>\n')
2195        f.write('<table>\n')
2196        f.write(    '<tr>\n')
2197        f.write(    '<th width="15%">Option name</th>\n')
2198        f.write(    '<th width="20%">Value in<br>default</th>\n')
2199        f.write(    '<th width="20%">Value in<br>user</th>\n')
2200        f.write(    '<th width="10%">Value in<br>{}<br></th>\n'.format(lex))                                            if lex else None
2201        f.write(    '<th width="35%">Comment</th>\n')
2202        f.write(    '</tr>\n')
2203        for opt in def_opts.keys():
2204            winner  = 'def'
2205            winner  = 'usr' if opt in usr_opts else winner
2206            winner  = 'lex' if opt in lex_opts else winner
2207            winner  = 'fil' if opt in fil_opts else winner
2208            f.write(    '<tr>\n')
2209            f.write(    '<td>{}</td>\n'.format(opt))
2210            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='def' else 'nxt', def_opts.get(opt, '')))
2211            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='usr' else 'nxt', usr_opts.get(opt, '')))
2212            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='lex' else 'nxt', lex_opts.get(opt, '')))    if lex else None
2213            f.write(    '<td><pre>{}</pre></td>\n'.format(hard_word_wrap(cmt_opts.get(opt, ''), 50)))
2214            f.write(    '</tr>\n')
2215            usr_opts.pop(opt, None)
2216            lex_opts.pop(opt, None)                                                                                     if lex else None
2217        f.write('</table><br/>\n')
2218        f.write('<h4>Overridden user-only options</h4>')
2219        f.write('<table>\n')
2220        f.write(    '<tr>\n')
2221        f.write(    '<th>Option name</th>\n')
2222        f.write(    '<th>Value in<br>user</th>\n')
2223        f.write(    '<th>Value in<br>{}</th>\n'.format(lex))                                                            if lex else None
2224        f.write(    '<th>Comment</th>\n')
2225        f.write(    '</tr>\n')
2226        for opt in usr_opts.keys():
2227            winner  = 'usr'
2228            winner  = 'lex' if opt in lex_opts else winner
2229            f.write(    '<tr>\n')
2230            f.write(    '<td>{}</td>\n'.format(opt))
2231            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='usr' else 'nxt', usr_opts.get(opt, '')))
2232            f.write(    '<td class="{}">{}</td>\n'.format('win' if winner=='lex' else 'nxt', lex_opts.get(opt, '')))    if lex else None
2233            f.write(    '<td><pre>{}</pre></td>\n'.format(cmt_opts.get(opt, '')))
2234            f.write(    '</tr>\n')
2235            lex_opts.pop(opt, None)                                                                                     if lex else None
2236        for opt in lex_opts:
2237            winner  = 'lex'
2238            f.write(    '<tr>\n')
2239            f.write(    '<td>{}</td>\n'.format(opt))
2240            f.write(    '<td class="{}"></td>  \n'.format('non'))
2241            f.write(    '<td class="{}">{}</td>\n'.format('win', lex_opts.get(opt, '')))
2242            f.write(    '<td><pre>{}</pre></td>\n'.format(cmt_opts.get(opt, '')))
2243            f.write(    '</tr>\n')
2244            lex_opts.pop(opt, None)
2245        f.write('</table><br/>\n')
2246        f.write(RPT_FOOT)
2247        return True
2248   #def do_report(fn):
2249
2250def index_1(cllc, val, defans=-1):
2251    return cllc.index(val) if val in cllc else defans
2252
2253if __name__ == '__main__' :     # Tests
2254    # To start the tests run in Console
2255    #   exec(open(path_to_the_file, encoding="UTF-8").read())
2256#   app.app_log(app.LOG_CONSOLE_CLEAR, 'm')
2257#   for smk in [smk for smk
2258#       in  sys.modules                             if 'cuda_options_editor.tests.test_options_editor' in smk]:
2259#       del sys.modules[smk]        # Avoid old module
2260#   import                                              cuda_options_editor.tests.test_options_editor
2261#   import unittest
2262#   suite = unittest.TestLoader().loadTestsFromModule(  cuda_options_editor.tests.test_options_editor)
2263#   unittest.TextTestRunner(verbosity=0).run(suite)
2264
2265    pass
2266
2267'''
2268ToDo
2269[+][kv-kv][02apr17] History for cond
2270[-][kv-kv][02apr17] ? Chapters list and "chap" attr into kinfo
2271[-][kv-kv][02apr17] ? Tags list and "tag" attr into kinfo
2272[-][kv-kv][02apr17] ? Delimiter row in table
2273[ ][kv-kv][02apr17] "Need restart" in Comments
2274[+][kv-kv][02apr17] ? Calc Format by Def_val
2275[ ][kv-kv][02apr17] int_mm for min+max
2276[+][kv-kv][02apr17] VERS in Title
2277[+][at-kv][02apr17] 'enum' вместо 'enum_i'
2278[ ][kv-kv][02apr17] Save top row in table
2279[+][kv-kv][03apr17] Show stat in Chap-combo and tags check-list
2280[-][kv-kv][03apr17] ? Add chap "(No chapter)"
2281[-][kv-kv][03apr17] ? Add tag "#no_tag"
2282[+][kv-kv][03apr17] Call opts report
2283[+][at-kv][04apr17] Format 'font'
2284[-][at-kv][04apr17] ? FilterListView
2285[+][at-kv][04apr17] use new default.json
2286[-][kv-kv][04apr17] Testing for update user.json
2287[+][kv-kv][04apr17] Restore Sec and Tags
2288[+][kv-kv][04apr17] ro-combo hitory for Tags
2289[+][kv-kv][05apr17] Add "default" to fonts if def_val=="default"
2290[+][at-kv][05apr17] Preview for format=fontmay
2291[+][kv-kv][06apr17] Spec filter sign: * - to show only modified
2292[-][kv-kv][06apr17] Format color
2293[+][kv-kv][24apr17] Sort as Def or as User
2294[+][kv-kv][05may17] New type "list of str"
2295[ ][kv-kv][23jun17] ? Filter with tag (part of tag?). "smth #my"
2296[+][kv-kv][15mar18] ? Filter with all text=key+comment
2297[+][kv-kv][19mar18] ? First "+" to filter with comment
2298[-][kv-kv][19mar18] !! Point the fact if value is overed in ed
2299[?][kv-kv][20mar18] Allow to add/remove opt in user/lex
2300[?][kv-kv][21mar18] ? Allow to meta keys in user.json:
2301                        "_fif_LOG__comment":"Comment for fif_LOG"
2302[+][kv-kv][22mar18] Set control's tab_order to always work Alt+E for "Valu&e"
2303[ ][kv-kv][26mar18] Use 'editor' for comment
2304[+][kv-kv][26mar18] Increase w for one col when user increases w of dlg (if no h-scroll)
2305[+][kv-kv][13apr18] DClick on Def-col - focus to Reset
2306[-][kv-kv][16apr18] Open in tag for fmt=json
2307[?][kv-kv][23apr18] ? Show opt from cur line if ed(default.json)
2308[+][at-kv][03may18] Rework ask to confirm removing user/lex opt
2309[+][at-kv][04may18] Report to console all changes
2310[+][at-kv][05may18] Call OpsReloadAndApply
2311[+][kv-kv][05may18] Rework radio to checks (Linux bug: always set one of radio-buttons)
2312[-][kv-kv][05may18] Ask "Set also for current file?" if ops is ed.prop
2313[+][kv-kv][06may18] Menu command "Show changes"
2314[+][kv-kv][06may18] Show all file opt value. !!! only if val!=over-val
2315[+][kv-kv][06may18] Rework Sort
2316[+][kv-kv][14may18] Scale def col widths
2317[ ][at-kv][14may18] DClick over 1-2-3 is bad
2318[+][at-kv][14may18] Allow to refresh table on each changing of filter
2319[+][at-kv][15may18] Allow to extra sort cols with Ctrl+Click
2320[ ][kv-kv][04jun18] Cannot select section @Ui after selected @Ui/Tabs
2321[ ][kv-kv][16jun18] Have 2 filter control to instant and history. Switch by vis
2322[+][kv-kv][18jun18] More then one chap in filter. Append from menu if Ctrl holds
2323[+][at-kv][24apr19] Add types: rgb
2324[ ][at-kv][24apr19] Add type condition: int/float range
2325[+][kv-kv][25apr19] Hide cols "Lexer" and "File", controls []For and lexer list (by init opt)
2326[+][kv-kv][25apr19] Allow store other then user.json
2327[+][kv-kv][25apr19] Return 'was modified' from show()
2328'''