1import os 2from contextlib import contextmanager 3from collections import namedtuple 4 5from cudatext import * 6import cudax_lib as apx 7FILE_OPTS = apx.OPT2PROP # to check if option can be in file scope 8 9import traceback 10 11# dbg 12import time 13 14_ = apx.get_translation(__file__) # I18N 15 16 17TITLE_DEFAULT = _('CudaText Preferences') 18 19OptChange = namedtuple('OptChange', 'name scope value lexer old_value') 20 21fn_icons = { 22 'asc': 'asc.png', # ascending order 23 'desc': 'desc.png', # descending 24} 25PLING_HISTORY_JSON = os.path.join(app_path(APP_DIR_SETTINGS), 'plugin history.json') 26FORMS_CFG_JSON = os.path.join(app_path(APP_DIR_SETTINGS), 'forms data.json') 27PLING_KEY = 'dlg_preferences' 28STATE_KEY_TREE_W = 'tree_w' 29STATE_KEY_DESCR_MEMO_H = 'descr_memo_h' 30STATE_KEY_FILTER_STR = 'filter_str' 31STATE_KEY_FILTER_HIST = 'filter_history' 32STATE_KEY_FILTER_VISIBLE = 'filter_visible' #TODO remove 33STATE_KEY_COL_CFG = 'columns' 34STATE_KEY_SORT_COL = 'sort_column' 35STATE_KEY_SEL_OPT = 'selected_option' 36 37SUBSET_KEYS = [ 38 STATE_KEY_FILTER_STR, 39 STATE_KEY_FILTER_HIST, 40 STATE_KEY_SEL_OPT, 41] 42 43 44IS_DBG = False 45LOG = False 46 47 48VK_ENTER = 13 49VK_F = ord('F') 50VK_ESCAPE = 27 51LIST_SEP = chr(1) 52IS_WIN = os.name=='nt' 53 54BTN_H = app_proc(PROC_GET_GUI_HEIGHT, 'button') 55BTN_W = BTN_H*3 56PAD = 2 57 58# colores 59COL_FONT = 0 60COL_SPLITTER = 0 61 62GLOBAL_OP_CMT = 'Note: this option is global' 63TREE_ITEM_ALL = _('[ All ]') 64 65# columns 66COL_SECTION = 'Section' 67COL_OPT_NAME = 'Option' 68COL_MODIFIED = '!' 69COL_VAL_DEFAULT = 'Default' 70COL_VAL_USER = 'User' 71COL_VAL_LEX = 'Lexer' 72COL_VAL_FILE = 'File' 73COL_VAL_MAIN = 'Value' # current value -- most specific f,l,u,def value 74 75UI_COLUMNS = { 76 COL_SECTION : _('Section'), 77 COL_OPT_NAME : _('Option'), 78 COL_VAL_DEFAULT : _('Default'), 79 COL_VAL_USER : _('User'), 80 COL_VAL_LEX : _('Lexer'), 81 COL_VAL_FILE : _('File'), 82 COL_VAL_MAIN : _('Current value'), 83} 84 85OPTS_COLUMN_MAP = { 86 COL_SECTION : 'chp', 87 COL_OPT_NAME : 'opt', 88 COL_MODIFIED : '!', 89 COL_VAL_DEFAULT : 'def', 90 COL_VAL_USER : 'uval', 91 COL_VAL_LEX : 'lval', 92 COL_VAL_FILE : 'fval', 93 # + Value - most specific scope value 94} 95 96# order in UI 97COLS_LIST = [ 98 COL_SECTION, 99 COL_OPT_NAME, 100 COL_MODIFIED, 101 COL_VAL_DEFAULT, 102 COL_VAL_MAIN, 103 COL_VAL_USER, 104 COL_VAL_LEX, 105 COL_VAL_FILE, 106] 107 108 109opt_col_cfg = [("Option", 70), ("Value", 100)] 110 111ui_max_history_edits = 20 112 113filter_history = [] 114 115def load_imagelist(ic_filename_map): 116 ind_map = {} 117 h_iml = imagelist_proc(0, IMAGELIST_CREATE) 118 _icons_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') 119 for name,fn_icon in ic_filename_map.items(): 120 _path = os.path.join(_icons_dir, fn_icon) 121 imind = imagelist_proc(h_iml, IMAGELIST_ADD, _path) 122 ind_map[name] = imind 123 return h_iml, ind_map 124 125def get_tree_path_names(h_tree, item_id, l=None): 126 ''' returns list, node names starting with deepest 127 ''' 128 if l is None: 129 l = [] 130 prop = tree_proc(h_tree, TREE_ITEM_GET_PROPS, id_item=item_id) 131 l.append(prop['text']) 132 133 parent_id = prop.get('parent') 134 if parent_id: 135 get_tree_path_names(h_tree, parent_id, l) 136 return l 137 138def get_tree_path(h_tree, item_id): 139 """ tree path for tree item 140 """ 141 path_names = get_tree_path_names(h_tree, item_id) 142 path_names.reverse() 143 return '/'.join(path_names) 144 145@contextmanager 146def ignore_edit(h, ed_): 147 """ turns off PROP_RO + deactivates Editor -- then restores 148 ? to not send `on_change` when changing `editor_combo` text 149 """ 150 is_ro = ed_.get_prop(PROP_RO) 151 if is_ro: 152 ed_.set_prop(PROP_RO, False) 153 154 h_ed = ed_.get_prop(PROP_HANDLE_SELF) 155 #NOTE: widgets are never deleted here, so `DLG_CTL_COUNT` should not be a problem 156 for n in range(dlg_proc(h, DLG_CTL_COUNT)): 157 h_ctl = dlg_proc(h, DLG_CTL_HANDLE, index=n) 158 if h_ed == h_ctl: 159 # disable temporarily 160 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={'act': False}) 161 break 162 163 try: 164 yield 165 finally: 166 if is_ro: 167 ed_.set_prop(PROP_RO, True) 168 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={'act': True}) 169 170 171def map_option_value(opt, val=None, caption=None): 172 """ for map options - returns caption for provided 'val'ue, or value for provided 'caption' 173 "val" -- 0; "caption" -- (0) don't activate" 174 """ 175 frm = opt['frm'] 176 if frm in ['int2s', 'str2s']: 177 jdc = opt['jdc'] # list('(a) by Alt+click', ...) 178 dct = opt['dct'] # list(['a', 'by Alt+click'], ...) 179 180 if val is not None: 181 for i,item in enumerate(dct): 182 if item[0] == val: 183 return jdc[i] # return str 184 185 elif caption is not None: 186 ind = jdc.index(caption) 187 val, _cap = dct[ind] 188 return val 189 190 191 else: 192 raise OptionMapValueError('require "val" or "caption"') 193 194 elif frm in ['font', 'strs', 'font-e']: 195 if val is not None: return val 196 if caption is not None: return caption 197 198 else: 199 raise OptionMapValueError('Unsupported option format: {}'.format((opt["opt"], opt["frm"]))) 200 raise OptionMapValueError('Couldn"t find: {}, {}\n + {}'.format(val, caption, opt)) 201 202 203def json_update(path, key, val): 204 """ loads json at 'path' if exists, puts 'k':'v' into it and saves 205 """ 206 import json 207 208 if os.path.exists(path): 209 with open(path, 'r', encoding='utf-8') as f: 210 j = json.load(f) 211 else: 212 j = {} 213 214 j[key] = val 215 j_str = json.dumps(j, indent=2) 216 217 with open(path, 'w', encoding='utf-8') as f: 218 f.write(j_str) 219 220def format_opt_change(ch): 221 scope_str = ch.scope 222 if ch.scope=='u': scope_str = ui_column(COL_VAL_USER) 223 elif ch.scope=='l': scope_str = ui_column(COL_VAL_LEX) +': '+str(ch.lexer) 224 elif ch.scope=='f': scope_str = ui_column(COL_VAL_FILE)+': '+os.path.basename(ed.get_filename()) 225 226 if ch.value is None: val_str = _('reset') 227 else: val_str = '{} -> {}'.format(ch.old_value, ch.value) 228 229 return '{} [{}] {}'.format(ch.name, scope_str, val_str) 230 231def ui_column(colname): 232 return UI_COLUMNS.get(colname, colname) 233 234 235class DialogMK2: 236 237 _h_list_iml = None # handle for option-list's imagelist 238 _lb_icon_inds = {} # listbox icons 239 240 def __init__(self, optman, title=None, subset=None, how=None): 241 """ optman -- cd_opts_dlg.py/OptionsMan 242 how -- 243 * how.get('hide_fil', False) 244 * how.get('hide_lex_fil', False) 245 * how.get('only_for_ul', not ens['tofi']) # Forbid to switch fo File ops 246 247 - how.get('stor_json', 'user.json') 248 - how.get('only_with_def', False) # Forbid to switch fo User+Lexer ops 249 """ 250 global ui_max_history_edits 251 252 #TODO get value from options if not present 253 ui_max_history_edits = optman.get_scope_value('ui_max_history_edits', scope='u', 254 default=ui_max_history_edits) 255 self._form_rect = {} # dict - x,y,w,h 256 self._state = {} 257 258 self.title = title or TITLE_DEFAULT 259 self.optman = optman 260 self.subset = subset # None - ok 261 # gather not-available scopes 262 self.hidden_scopes = [] # 'l' and/or 'f' 263 if (how and how.get('hide_fil') or how.get('hide_lex_fil') or how.get('only_for_ul')) \ 264 or not ed.get_filename(): 265 self.hidden_scopes.append('f') 266 if how.get('hide_lex_fil') or not ed.get_prop(PROP_LEXER_FILE): 267 self.hidden_scopes.append('l') 268 269 270 self._load_dlg_cfg() 271 272 _sort_val = self._state.get(STATE_KEY_SORT_COL, COL_OPT_NAME) 273 self.current_sort = _sort_val.lstrip('-') 274 self.sort_reverse = _sort_val.startswith('-') 275 self._last_applied_filter = None 276 self._cur_opt_name = None 277 self._closing = False 278 279 self.h = None 280 self._h_tree = None 281 self._h_col_menu = None 282 self._h_help = None 283 self._cur_value_ed = 'str' # type for: _set_value_editor() 284 285 self.val_eds = ValueEds(self.on_opt_val_edit) 286 287 self._opt_changes = [] 288 self._list_opt_names = [] # current displayed list of option-names 289 # '' -> User -- showing default value, edited value will be added to 'user' scope 290 self._col_toggle_cmds = {} # column name -> toggle lambda -- for menu, to toggle list columns 291 self._scope_captions = { # expanded alter 292 'u' : ui_column(COL_VAL_USER), 293 '' : ui_column(COL_VAL_USER), 294 'def': ui_column(COL_VAL_USER), 295 } 296 297 298 @property 299 def filter_val(self): 300 if self.h: 301 return self._filter_ed.get_text_all() 302 return '' 303 @filter_val.setter 304 def filter_val(self, value): #SKIP 305 if self.h: 306 val_str = str(value) 307 if val_str != self._filter_ed.get_text_all(): 308 self._filter_ed.set_text_all(val_str) 309 310 @property 311 def columns(self): 312 """ returns (column_captions, column_widths) 313 """ 314 captions = [] 315 widths = [] 316 _col_cfg = opt_col_cfg 317 318 # hide lexer and file scopes if disabled 319 if self.hidden_columns: 320 _col_cfg = [colcfg for colcfg in _col_cfg if colcfg[0] not in self.hidden_columns] 321 322 _total_w = sum(w for name,w in _col_cfg if isinstance(w, int)) 323 for caption,w in _col_cfg: 324 captions.append(caption) 325 326 # width: to negative percentages for listbox -- except '!' <- in px 327 if isinstance(w, int): 328 w = -round(w/_total_w*100) 329 else: 330 w = int(w[:-2]) # "100px" => 100 331 widths.append(w) 332 333 return captions,widths 334 335 @property 336 def hidden_columns(self): 337 if not hasattr(self, '_hidden_columns'): 338 self._hidden_columns = set() 339 340 if self.hidden_scopes: 341 if 'l' in self.hidden_scopes: self._hidden_columns.add(COL_VAL_LEX) 342 if 'f' in self.hidden_scopes: self._hidden_columns.add(COL_VAL_FILE) 343 344 return self._hidden_columns 345 346 @property 347 def scope(self): 348 """ returns current scope char: u,l,f 349 """ 350 scope_str = self.scope_ed.get_text_all() 351 if scope_str == ui_column(COL_VAL_USER): return 'u' 352 elif scope_str.startswith(ui_column(COL_VAL_LEX)): return 'l' 353 elif scope_str.startswith(ui_column(COL_VAL_FILE)): return 'f' 354 355 356 def _load_dlg_cfg(self): 357 import json 358 359 if os.path.exists(FORMS_CFG_JSON): 360 with open(FORMS_CFG_JSON, 'r', encoding='utf-8') as f: 361 j = json.load(f) 362 _old_title = _('CudaText options lite') 363 j_form = j.get(self.title, j.get(_old_title)) 364 if j_form: 365 self._form_rect = {k:v for k,v in j_form.items() 366 if v and k in {'x', 'y', 'w', 'h'}} 367 368 if os.path.exists(PLING_HISTORY_JSON): 369 with open(PLING_HISTORY_JSON, 'r', encoding='utf-8') as f: 370 j_all = json.load(f) 371 372 j = j_all.get(PLING_KEY) 373 if j: 374 _state_keys = { 375 STATE_KEY_TREE_W, 376 STATE_KEY_DESCR_MEMO_H, 377 STATE_KEY_FILTER_STR, 378 STATE_KEY_FILTER_VISIBLE, 379 STATE_KEY_SEL_OPT, 380 STATE_KEY_SORT_COL, 381 } 382 self._state = {k:v for k,v in j.items() if k in _state_keys} 383 384 # if subset - overwrite general values with subset's 385 _subsets = j.get('subsets') 386 if self.subset and _subsets: 387 self._state.update(_subsets.get(self.subset, {})) 388 389 390 # filter history 391 _filt_hist = j.get(STATE_KEY_FILTER_HIST) 392 if _filt_hist: 393 filter_history.clear() 394 filter_history.extend(_filt_hist) 395 396 # list columns 397 _col_cfg = j.get(STATE_KEY_COL_CFG) 398 if _col_cfg: 399 import re 400 401 # check if only integers and str (~"100px") 402 for i in range(len(_col_cfg)): 403 item = _col_cfg[i] 404 colname,w = item 405 if not isinstance(w, int) and not (isinstance(w, str) 406 and re.match('^\d+px$', w)): 407 print(_('NOTE: {}: invalid column width format: {}') 408 .format(self.title, item)) 409 _col_cfg[i] = (colname,100) 410 411 opt_col_cfg.clear() 412 opt_col_cfg.extend(_col_cfg) 413 pass; LOG and print(' --- Loaded state: '+json.dumps(j, indent=4)) 414 415 # no history - load from opted plugin 416 else: 417 j_opted = j_all.get('cd_opts_dlg', {}).get('dlg') 418 if j_opted: 419 opted_state = { 420 STATE_KEY_DESCR_MEMO_H: j_opted.get("df.cmnt_heght"), 421 STATE_KEY_SEL_OPT: j_opted.get("df.cur_op"), 422 } 423 self._state = {k:v for k,v in opted_state.items() if v is not None} 424 425 filter_history.clear() 426 filter_history.extend(j_opted.get('df.h.cond', [])) 427 428 429 def _save_dlg_cfg(self): 430 if self._closing is None: 431 return 432 433 # window position/dimensions 434 form_prop = dlg_proc(self.h, DLG_PROP_GET) 435 j_form = {'x':form_prop['x'], 'y':form_prop['y'], 'w':form_prop['w'], 'h':form_prop['h']} 436 json_update(FORMS_CFG_JSON, key=self.title, val=j_form) 437 438 # states 439 j = {} 440 j[STATE_KEY_TREE_W] = dlg_proc(self.h, DLG_CTL_PROP_GET, name='category_tree')['w'] 441 j[STATE_KEY_DESCR_MEMO_H] = dlg_proc(self.h, DLG_CTL_PROP_GET, name='panel_value')['h'] 442 j[STATE_KEY_FILTER_STR] = self.filter_val 443 j[STATE_KEY_FILTER_HIST] = filter_history 444 j[STATE_KEY_FILTER_VISIBLE] = dlg_proc(self.h, DLG_CTL_PROP_GET, name='panel_filter')['vis'] 445 j[STATE_KEY_SORT_COL] = self.current_sort if not self.sort_reverse else '-'+self.current_sort 446 j[STATE_KEY_SEL_OPT] = self._cur_opt_name 447 448 # save some options separately -- 3rd party options: move from `j` to `j/subsets/<subset>` 449 if self.subset: 450 j_subset = {k:j.pop(k) for k in SUBSET_KEYS} 451 _subsets = j.setdefault('subsets', {}) 452 _subsets[self.subset] = j_subset 453 454 j[STATE_KEY_COL_CFG] = opt_col_cfg 455 456 json_update(PLING_HISTORY_JSON, PLING_KEY, j ) 457 458 def configure_columns(self): 459 global opt_col_cfg 460 461 caption = _('Columns widths. In pixels (50px) or relative (100)') 462 463 _colnames, _widths = zip(*opt_col_cfg) # start values 464 colnames, widths = list(_colnames), list(_widths) # working values 465 while True: 466 flat_columns = [str(a) for item in zip(colnames,widths) for a in item] 467 res = dlg_input_ex(len(colnames), caption, *flat_columns) 468 if not res: 469 break 470 else: # have result -> validate 471 for i in range(len(colnames)): 472 item = res[i] 473 if item.isdecimal(): 474 res[i] = int(item) 475 elif (item.endswith('px') and item[:-2].isdecimal()): 476 pass 477 else: # error 478 widths = res 479 colnames[i] = _colnames[i] + _(' (Error!)') 480 break 481 482 else: # all is well - stop `While` 483 break 484 485 if res: 486 new_cfg = list(zip(_colnames, res)) 487 _start_cfg = opt_col_cfg[:] 488 489 # try to apply new config -- revert if failed (jic) 490 try: 491 opt_col_cfg = new_cfg # global 492 493 self.update_list_layout() 494 _opts = self.get_filtered_opts() 495 self.update_list(_opts) 496 except Exception as ex: 497 opt_col_cfg = _start_cfg # revert changes 498 499 msg = _('failed to apply new columns config: {}. {}').format(new_cfg, ex) 500 print('NOTE: {}: {}'.format(self.title, msg)) 501 502 503 504 def show(self): 505 if not self.h: 506 self.h, self.opt_comment_ed = self.init_form() 507 508 nitems = self._fill_tree(self.optman.tree['kids']) 509 if nitems <= 1: 510 dlg_proc(self.h, DLG_CTL_PROP_SET, name='category_tree', prop={'vis': False}) 511 dlg_proc(self.h, DLG_CTL_PROP_SET, name='splitter_left', prop={'vis': False}) 512 513 self.update_list_layout() 514 515 # restore filter 516 _filter_val = self._state.get(STATE_KEY_FILTER_STR, '') 517 self.set_filter(_filter_val) 518 if _filter_val: 519 self.toggle_filter(show=True) 520 self._filter_ed.set_caret(0,0, len(_filter_val),0) # select filter text on start 521 522 # restore selected-option (+show it) 523 last_sel_opt = self._state.get(STATE_KEY_SEL_OPT) 524 if self._list_opt_names: 525 if last_sel_opt and last_sel_opt in self._list_opt_names: 526 _ind = self._list_opt_names.index(last_sel_opt) 527 else: # if no saved selected opt - select first 528 _ind = 0 529 listbox_proc(self._h_list, LISTBOX_SET_SEL, index=_ind) 530 _top = max(0, _ind-3) 531 listbox_proc(self._h_list, LISTBOX_SET_TOP, index=_top) 532 #### click event 533 self._on_opt_click(id_dlg=self.h, id_ctl=-1) 534 535 # focus filter on start 536 timer_proc(TIMER_START_ONE, callback=lambda *args,**vargs: self._filter_ed.focus(), interval=100) 537 538 # DBG ############# 539 if IS_DBG: 540 DialogMK2._dlg = self 541 cmds = [ 'from cuda_prefs.dlg import DialogMK2', 542 'globals()["dlg"] = DialogMK2._dlg',] 543 app_proc(PROC_EXEC_PYTHON, '\n'.join(cmds)) 544 del DialogMK2._dlg 545 546 dlg_proc(self.h, DLG_SHOW_NONMODAL) 547 return 548 ########### 549 550 dlg_proc(self.h, DLG_SHOW_MODAL) 551 dlg_proc(self.h, DLG_FREE) 552 553 del self.optman 554 del self._list_opt_names 555 del self._col_toggle_cmds 556 557 def init_form(self): 558 global COL_FONT 559 global COL_SPLITTER 560 561 # load icons only once 562 def get_list_imagelist(): #SKIP 563 if not DialogMK2._h_list_iml: 564 DialogMK2._h_list_iml, DialogMK2._lb_icon_inds = load_imagelist(fn_icons) 565 566 return DialogMK2._h_list_iml 567 568 h = dlg_proc(0, DLG_CREATE) 569 570 colors = app_proc(PROC_THEME_UI_DICT_GET, '') 571 COL_FONT = colors['EdTextFont']['color'] 572 COL_SPLITTER = colors['SplitMain']['color'] 573 color_form_bg = colors['TabBg']['color'] 574 575 ###### FORM ####################### 576 dlg_proc(h, DLG_PROP_SET, prop={ 577 'cap': self.title, 578 'w': 848, 'h': 576, 579 'w_min': 550, 'h_min': 250, 580 'border': DBORDER_SIZE, 581 'color': color_form_bg, 582 #'on_mouse_exit': self.dlgcolor_mouse_exit, 583 'keypreview': True, 584 'on_key_down': self._on_key, 585 'on_close': lambda *args, **vargs: self._save_dlg_cfg(), 586 'topmost': True, 587 }) 588 589 ###### MAIN PANEL 590 n = dlg_proc(h, DLG_CTL_ADD, 'panel') 591 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 592 'name': 'panel_main', 593 'align': ALIGN_CLIENT, 594 'sp_l': PAD*2, 'sp_t': PAD*2, 'sp_r': PAD*2, 'sp_b': PAD*2 + BTN_H + PAD*2, 595 }) 596 597 598 ### tree ########################## 599 n = dlg_proc(h, DLG_CTL_ADD, 'treeview') 600 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 601 'name': 'category_tree', 602 'p': 'panel_main', 603 'align': ALIGN_LEFT, 604 'w': 100, 605 #'sp_r': PAD, 606 'on_change': self._on_tree_click, 607 }) 608 self._h_tree = dlg_proc(h, DLG_CTL_HANDLE, index=n) 609 tree_proc(self._h_tree, TREE_THEME) 610 611 612 ### RIGHT PANEL ######################### 613 n = dlg_proc(h, DLG_CTL_ADD, 'panel') 614 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 615 'name': 'panel_right', 616 'p': 'panel_main', 617 'align': ALIGN_CLIENT, 618 #'sp_l': PAD, 619 }) 620 # listbox ########## 621 n = dlg_proc(h, DLG_CTL_ADD, 'listbox_ex') 622 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 623 'p': 'panel_right', 624 'align': ALIGN_CLIENT, 625 'sp_t': PAD, 626 'border': True, 627 'act': True, # to call on_change 628 'on_click': self._on_opt_click, 629 'on_change': self._on_opt_click, 630 'on_click_header': self._on_header_click, 631 'on_menu': self.listbox_menu, 632 }) 633 self._h_list = dlg_proc(h, DLG_CTL_HANDLE, index=n) 634 635 ### FILTER panel ############################ 636 n = dlg_proc(h, DLG_CTL_ADD, 'panel') 637 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 638 'name': 'panel_filter', 639 'p': 'panel_right', 640 'align': ALIGN_TOP, 'h': BTN_H, 'max_h': BTN_H, 641 #'vis': self._state.get(STATE_KEY_FILTER_VISIBLE, False), 642 }) 643 # filter label 644 n = dlg_proc(h, DLG_CTL_ADD, 'label') 645 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 646 'name': 'filter_label', 647 'p': 'panel_filter', 648 'a_l': ('', '['), 'a_t': ('', '-'), 649 'sp_l': PAD*2, 650 'cap': _('Filter: '), 651 'font_color': COL_FONT, 652 }) 653 # filter combo ########## 654 n = dlg_proc(h, DLG_CTL_ADD, 'editor_combo') 655 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 656 'name': 'filter', 657 'p': 'panel_filter', 658 'h': BTN_H, 'max_h': BTN_H, 659 #'align': ALIGN_CLIENT, 660 'sp_r': 20, 661 'a_l': ('filter_label', ']'), 'a_r': ('', ']'), 'a_t': ('filter_label', '-'), 662 'on_change': self._on_filter, 663 'on_key_down': self._on_filter, # for later -- live filter 664 }) 665 h_ed = dlg_proc(h, DLG_CTL_HANDLE, index=n) 666 self._filter_ed = Editor(h_ed) 667 668 669 ### BOTTOM PANEL ############################### 670 n = dlg_proc(h, DLG_CTL_ADD, 'panel') 671 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 672 'name': 'panel_value', 673 'p': 'panel_right', 674 'align': ALIGN_BOTTOM, 675 'h': 120, 676 'h_min': 100, # avoid resizing to zero 677 'y': 4000, # https://github.com/Alexey-T/CudaText/issues/3679#issuecomment-904845613 678 'sp_t': PAD, 679 }) 680 # scope combo ########## 681 n = dlg_proc(h, DLG_CTL_ADD, 'editor_combo') 682 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 683 'name': 'scope', 684 'p': 'panel_value', 685 'h': BTN_H, 'max_h': BTN_H, 'w': 100, 'max_w': 100, 686 'a_l': None, 'a_r': ('', ']'), 'a_t': ('', '['), 687 'act': True, 688 'on_change': self._on_scope_change, 689 }) 690 h_scope_ed = dlg_proc(h, DLG_CTL_HANDLE, index=n) 691 # scope label ### 692 n = dlg_proc(h, DLG_CTL_ADD, 'label') 693 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 694 'name': 'scope_label', 695 'p': 'panel_value', 696 'h': BTN_H, 'max_h': BTN_H, 697 'a_l': None, 'a_r': ('scope', '['), 'a_t': ('scope', '-'), 698 'sp_t': 3, 699 'cap': _('Scope: '), 700 'font_color': COL_FONT, 701 }) 702 # btn reset ########### 703 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 704 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 705 'name': ValueEds.VALUE_ED_RESET, 706 'p': 'panel_value', 707 'h': BTN_H, 'max_h': BTN_H, 708 'w': BTN_W, 'max_w': BTN_W, 709 'a_l': None, 'a_r': ('scope_label', '['), 'a_t': ('', '['), 710 'sp_l': PAD, 'sp_r': 32, 711 'cap': _('Reset'), 712 'on_change': self._on_reset, 713 }) 714 # option description ######### 715 n = dlg_proc(h, DLG_CTL_ADD, 'editor') 716 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 717 'name': 'descr_memo', 718 'p': 'panel_value', 719 'sp_t': BTN_H + PAD, 720 'align': ALIGN_CLIENT, 721 'h': 100, 722 }) 723 h_ed = dlg_proc(h, DLG_CTL_HANDLE, index=n) 724 edt = Editor(h_ed) 725 726 n = dlg_proc(h, DLG_CTL_ADD, 'label') 727 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 728 'name': 'mod_label', 729 'p': 'panel_value', 730 'a_t': ('scope', '-'), 731 'sp_r': PAD, 732 'cap': _('[mod]'), 733 'font_color': COL_FONT, 734 }) 735 736 737 ### SPLITTERS ### 738 # list--opt_description 739 n = dlg_proc(h, DLG_CTL_ADD, 'splitter') 740 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 741 'p': 'panel_right', 742 'align': ALIGN_BOTTOM, 743 'x': 0, 'y': 4000, 'h': 4, 744 'color': COL_SPLITTER, 745 }) 746 # tree--list 747 n = dlg_proc(h, DLG_CTL_ADD, 'splitter') 748 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 749 'name': 'splitter_left', 750 'p': 'panel_main', 751 'align': ALIGN_LEFT, 752 'x': 100, 'y': 0, 'w': 4, 753 'color': COL_SPLITTER, 754 }) 755 756 757 ### Bottom Btns ################### 758 # OK ####### 759 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 760 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 761 'name': 'btn_ok', 762 'h': BTN_H, 'max_h': BTN_H, 763 'w': BTN_W, 'max_w': BTN_W, 764 'a_l': None, 'a_t': None, 'a_r': ('', ']'), 'a_b': ('', ']'), 765 'sp_r': PAD*2, 'sp_b': PAD*2, 766 'cap': _('OK'), 767 'on_change': lambda *args, **vargs: (self.apply_changes(closing=True), self.close()), 768 }) 769 # Apply ####### 770 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 771 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 772 'name': 'btn_apply', 773 'h': BTN_H, 'max_h': BTN_H, 774 'w': BTN_W, 'max_w': BTN_W, 775 'a_l': None, 'a_t': None, 'a_r': ('btn_ok', '['), 'a_b': ('', ']'), 776 'sp_r': PAD*2, 'sp_b': PAD*2, 777 'cap': _('Apply'), 778 'on_change': lambda *args, **vargs: self.apply_changes(), 779 }) 780 # Cancel ####### 781 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 782 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 783 'name': 'btn_cancel', 784 'h': BTN_H, 'max_h': BTN_H, 785 'w': BTN_W, 'max_w': BTN_W, 786 'a_l': None, 'a_t': None, 'a_r': ('btn_apply', '['), 'a_b': ('', ']'), 787 'sp_r': PAD*2, 'sp_b': PAD*2, 788 'cap': _('Cancel'), 789 'on_change': lambda *args, **vargs: self.close(), 790 }) 791 # help ####### 792 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 793 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 794 'h': BTN_H, 'max_h': BTN_H, 795 'w': BTN_W, 'max_w': BTN_W, 796 'a_l': ('', '['), 'a_t': None, 'a_r': None, 'a_b': ('', ']'), 797 'sp_l': PAD*2, 'sp_b': PAD*2, 798 'cap': _('Help'), 799 'on_change': self.dlg_help, 800 }) 801 802 # reverse buttons for Windows: [Cancel, Apply, OK] => [OK, Apply, Cancel] 803 if IS_WIN: 804 dlg_proc(h, DLG_CTL_PROP_SET, name='btn_cancel', prop={'a_r': ('', ']')}) 805 dlg_proc(h, DLG_CTL_PROP_SET, name='btn_apply', prop={'a_r': ('btn_cancel', '[')}) 806 dlg_proc(h, DLG_CTL_PROP_SET, name='btn_ok', prop={'a_r': ('btn_apply', '[')}) 807 808 809 ### listbox 810 listbox_proc(self._h_list, LISTBOX_SET_COLUMN_SEP, text=LIST_SEP) 811 # + icons 812 h_iml = get_list_imagelist() 813 listbox_proc(self._h_list, LISTBOX_SET_HEADER_IMAGELIST, text=h_iml) 814 815 816 edt.set_prop(PROP_RO, True) 817 edt.set_prop(PROP_RULER, False) 818 edt.set_prop(PROP_MARGIN, 2000) 819 edt.set_prop(PROP_GUTTER_ALL, False) 820 edt.set_prop(PROP_MINIMAP, False) 821 edt.set_prop(PROP_MICROMAP, False) 822 edt.set_prop(PROP_LAST_LINE_ON_TOP, False) 823 edt.set_prop(PROP_HILITE_CUR_LINE, False) 824 edt.set_prop(PROP_WRAP, WRAP_ON_WINDOW) 825 826 # scopes combo 827 scopes = [ui_column(COL_VAL_USER)] 828 lex = ed.get_prop(PROP_LEXER_FILE) 829 if lex and 'l' not in self.hidden_scopes: 830 scopes.append(ui_column(COL_VAL_LEX)+': '+lex) 831 self._scope_captions['l'] = scopes[-1] 832 if ed.get_filename() and 'f' not in self.hidden_scopes: 833 filename = os.path.split(ed.get_filename())[1] 834 scopes.append(ui_column(COL_VAL_FILE)+': '+filename) 835 self._scope_captions['f'] = scopes[-1] 836 self.scope_ed = Editor(h_scope_ed) 837 self.scope_ed.set_prop(PROP_RO, True) 838 self.scope_ed.set_prop(PROP_COMBO_ITEMS, '\n'.join(scopes)) 839 840 dlg_proc(h, DLG_SCALE) 841 842 # unscale saved-state dimensions 843 if self._form_rect: 844 dlg_proc(h, DLG_PROP_SET, prop=self._form_rect) 845 if self._state.get(STATE_KEY_TREE_W): 846 dlg_proc(h, DLG_CTL_PROP_SET, name='category_tree', prop={ 847 'w': self._state.get(STATE_KEY_TREE_W), 848 }) 849 if self._state.get(STATE_KEY_DESCR_MEMO_H): 850 dlg_proc(h, DLG_CTL_PROP_SET, name='panel_value', prop={ 851 'h': self._state.get(STATE_KEY_DESCR_MEMO_H), 852 }) 853 854 return h, edt 855 856 def update_list(self, opts): 857 column_names, _col_widths = self.columns 858 columns_items = [] 859 _value_cols = {COL_VAL_DEFAULT, COL_VAL_USER, COL_VAL_LEX, COL_VAL_FILE} 860 for col_title in column_names: 861 if col_title == COL_VAL_MAIN: 862 col_values = [str(self.optman.get_opt_active_value(op, is_ui=True)) for op in opts] 863 864 elif col_title in _value_cols: 865 _col_key = OPTS_COLUMN_MAP.get(col_title) 866 _scope = 'def' if _col_key == 'def' else _col_key[0] # 'uval' -> 'u', 'def' -> 'def' 867 col_values = [str(self.optman.get_opt_scope_value(op, _scope, is_ui=True)) for op in opts] 868 869 else: 870 _col_key = OPTS_COLUMN_MAP.get(col_title) 871 col_values = [str(op.get(_col_key, '')) for op in opts] # can't use generator - _col_key 872 873 columns_items.append(col_values) 874 875 self._list_opt_names = [op['opt'] for op in opts] 876 877 listbox_proc(self._h_list, LISTBOX_DELETE_ALL) 878 879 _addedn = 0 880 _max_seps = 0 881 for row in zip(*columns_items): 882 row_txt = LIST_SEP.join(row) 883 _max_seps = row_txt.count(LIST_SEP) 884 885 listbox_proc(self._h_list, LISTBOX_ADD, index=-1, text=row_txt) 886 _addedn += 1 887 _rows = listbox_proc(self._h_list, LISTBOX_GET_COUNT) 888 889 # select current option 890 if self._list_opt_names: 891 ind = 0 892 if self._cur_opt_name and self._cur_opt_name in self._list_opt_names: 893 ind = self._list_opt_names.index(self._cur_opt_name) 894 #listbox_proc(self._h_list, LISTBOX_SET_TOP, max(0, ind-3)) # selecting is not helpful 895 listbox_proc(self._h_list, LISTBOX_SET_SEL, ind) 896 897 self._on_opt_click(id_dlg=self.h, id_ctl=-1) 898 899 900 def update_list_layout(self): 901 # columns 902 column_captions, column_widths = self.columns 903 column_widths[-1] = 0 # last col to 'fill' - to avoid h-scrollbar 904 _ui_columns = map(lambda cap: ui_column(cap), column_captions) # generator 905 column_captions_str = LIST_SEP.join(_ui_columns) 906 listbox_proc(self._h_list, LISTBOX_SET_COLUMNS, text=column_widths) # width<0 means value in % 907 listbox_proc(self._h_list, LISTBOX_SET_HEADER, text=column_captions_str) 908 909 # sort-icons 910 header_icon_cfg = [] 911 if self.current_sort and self.current_sort in column_captions: 912 sort_col_ind = column_captions.index(self.current_sort) 913 _order_name = 'asc' if not self.sort_reverse else 'desc' 914 _icon_ind = DialogMK2._lb_icon_inds[_order_name] 915 header_icon_cfg = [-1]*sort_col_ind + [_icon_ind] # ~[-1, -1, -1, ind] 916 listbox_proc(self._h_list, LISTBOX_SET_HEADER_IMAGEINDEXES, text=header_icon_cfg) 917 918 def get_filtered_opts(self): 919 sort_field = OPTS_COLUMN_MAP.get(self.current_sort, self.current_sort) 920 return self.optman.get_list(self.filter_val, sort_field, reverse=self.sort_reverse) 921 922 def set_filter(self, filter_str, tree_click=False): 923 if not filter_str: 924 self._filter_ed.set_text_all('') 925 if self._last_applied_filter == filter_str: 926 return 927 928 if tree_click and not filter_str: 929 self.toggle_filter(show=False) 930 931 self._last_applied_filter = filter_str 932 self.filter_val = filter_str 933 934 opts = self.get_filtered_opts() 935 pass; LOG and print(' __ set_filter: opts len: {}'.format(len(opts))) 936 self.update_list(opts) 937 938 # history 939 if filter_str: 940 try: 941 ind = filter_history.index(filter_str) 942 del filter_history[ind] 943 except ValueError: 944 pass 945 946 filter_history.append(filter_str) 947 del filter_history[:-ui_max_history_edits] 948 949 # update combo items 950 self._filter_ed.set_prop(PROP_COMBO_ITEMS, '\n'.join(reversed(filter_history))) 951 952 953 def set_sort(self, sort_name): 954 pass; LOG and print(' setting sort: {}'.format(sort_name)) 955 956 if self.current_sort == sort_name: # switch order 957 self.sort_reverse = not self.sort_reverse 958 else: 959 self.current_sort = sort_name 960 self.sort_reverse = False # back to ascending 961 self.update_list_layout() 962 963 # if not present in map -- special value - send as is 964 opts = self.get_filtered_opts() 965 pass; LOG and print(' __ set_sort: opts len: {}'.format(len(opts))) 966 self.update_list(opts) 967 968 969 # ignore no change 970 def add_opt_change(self, name, scope, val=None): 971 """ val=None -- remove option binding for scope 972 """ 973 _old_val = self.optman.get_scope_value(name, scope) 974 975 # check if already have a opt_change for this option+scope -> ovewrite (delete old) 976 for i,change in enumerate(self._opt_changes): 977 if change.name == name and change.scope == scope: 978 del self._opt_changes[i] 979 break 980 981 if val is not None: ### setting value 982 if val == _old_val: # no change - ignore 983 return 984 else: ### removing value 985 if _old_val is None: # no change - ignore 986 return 987 988 # if resetting value -- ask confirmation 989 scam = app_proc(PROC_GET_KEYSTATE, '') 990 if scam != 'c' and self.scope != 'f' and val is None: 991 _scope_cap = self._scope_captions[self.scope] 992 _jval = self.optman.get_opt_scope_value(self._cur_opt, scope=self.scope, is_ui=True) 993 _msg = _('Remove option [{}]\n {} = {!r}\n?').format(_scope_cap, self._cur_opt_name, _jval) 994 res = msg_box(_msg, MB_OKCANCEL + MB_ICONQUESTION) 995 if res != ID_OK: 996 return 997 998 lex = ed.get_prop(PROP_LEXER_FILE) if scope == 'l' else None 999 opt_change = OptChange(name, scope, val, lexer=lex, old_value=_old_val) 1000 pass; LOG and print('NOTE: new option change: '+str(opt_change)) 1001 msg_status(_('Option: ') + format_opt_change(opt_change)) 1002 self._opt_changes.append(opt_change) 1003 1004 1005 def _fill_tree(self, d, parent=0): 1006 if parent == 0: 1007 item_id = tree_proc(self._h_tree, TREE_ITEM_ADD, text=TREE_ITEM_ALL) 1008 n = 0 1009 for name,d_ in d.items(): 1010 # add item 1011 item_id = tree_proc(self._h_tree, TREE_ITEM_ADD, id_item=parent, index=-1, text=name) 1012 d_['item_id'] = item_id 1013 n += 1 1014 1015 items = d_.get('kids') 1016 if items: 1017 n += self._fill_tree(items, parent=item_id) 1018 return n 1019 1020 1021 def on_opt_val_edit(self, id_dlg, id_ctl, data='', info=''): 1022 """ "change" callback for: option-edit field, 'edit-value' btn 1023 """ 1024 ed_name = self.val_eds.get_name(id_ctl) 1025 prop_type = self._cur_opt['frm'] 1026 pass; LOG and print(' + ed name: {} [{}]'.format(ed_name, prop_type)) 1027 1028 if ed_name == ValueEds.WGT_NAME__EDIT: # str, int, float, -hotk + ### 1029 if prop_type == '#rgb' or prop_type == '#rgb-e': 1030 self._update_rgb_edit() 1031 1032 self.toggle_mod_indicator(by_timer=True) 1033 1034 key_code, key_state = data 1035 if key_code != VK_ENTER: 1036 return 1037 1038 val = self.val_eds.get_edited_value(self._cur_opt) 1039 if val is None: 1040 return 1041 1042 elif ed_name == ValueEds.WGT_NAME__COMBO: # font, int2s, str2s, strs ### 1043 val = self.val_eds.val_combo.get_text_all() 1044 # only accept values from combo-items 1045 if val not in self.val_eds.val_combo.get_prop(PROP_COMBO_ITEMS): 1046 pass; LOG and print('NOTE: val not in combo: {}'.format(val)) 1047 return 1048 1049 val = map_option_value(self._cur_opt, caption=val) 1050 1051 elif ed_name == ValueEds.WGT_NAME__CHECK: # bool ### 1052 val = self.val_eds.cb_value 1053 1054 elif ed_name == ValueEds.WGT_NAME__BTN_EDIT: # edit btn: hotk, color, json, file ### 1055 val = self._dlg_value(prop_type) 1056 if val is not None: 1057 with ignore_edit(self.h, self.val_eds.val_edit): 1058 self.val_eds.val_edit.set_text_all(str(val)) 1059 1060 if prop_type in {'#rgb', '#rgb-e'}: 1061 self._update_rgb_edit() 1062 else: # canceled dialog 1063 return 1064 1065 self.toggle_mod_indicator(show=True) 1066 self.add_opt_change(self._cur_opt_name, self.scope, val) 1067 1068 1069 def _on_opt_click(self, id_dlg, id_ctl, data='', info=''): 1070 #print('LIST CIKCK: {}'.format((id_dlg, id_ctl, data, info))) 1071 1072 _sel_ind = listbox_proc(self._h_list, LISTBOX_GET_SEL) 1073 if _sel_ind == -1 or not self._list_opt_names: # nothing selected disable bottom panel 1074 self._clear_opt_edits() 1075 dlg_proc(self.h, DLG_CTL_PROP_SET, name='panel_value', prop={'en':False}) 1076 return 1077 1078 # enable bottom panel before manipulations 1079 dlg_proc(self.h, DLG_CTL_PROP_SET, name='panel_value', prop={'en':True}) 1080 1081 self._cur_opt_name = self._list_opt_names[_sel_ind] 1082 self._cur_opt = self.optman.get_opt(self._cur_opt_name) 1083 with ignore_edit(id_dlg, self.opt_comment_ed): 1084 self.opt_comment_ed.set_text_all(self._cur_opt.get('cmt', '')) 1085 1086 # if have a change for this option -- show it 1087 is_opt_modified = False 1088 removed_scopes = set(self.hidden_scopes) 1089 for opt_change in reversed(self._opt_changes): 1090 if opt_change.name == self._cur_opt_name: 1091 is_opt_modified = True 1092 if opt_change.value is not None: # setting value 1093 # (scope, val) - [f],[l],[u], [def] 1094 _opt = self.optman.get_opt(opt_change.name) 1095 ui_val = self.optman.value2uival(_opt, opt_change.value) 1096 active_scoped_val = (opt_change.scope, ui_val) 1097 pass; LOG and print('NOTE: using change value: '+str(opt_change)) 1098 break 1099 else: # unsetting option 1100 removed_scopes.add(opt_change.scope) 1101 else: # no matching changes 1102 #active_scoped_val = self.optman.get_opt_active_value(self._cur_opt, is_ui=False, with_scope=True) 1103 # skip values that were reset, 1104 scopes = (scope for scope in ['f', 'l', 'u', 'def'] if scope not in removed_scopes) 1105 scoped_vals = ((sc, self.optman.get_opt_scope_value(self._cur_opt, sc, is_ui=False)) for sc in scopes) 1106 active_scope = next(sc for sc,val in scoped_vals if val is not None) # result - is not None 1107 active_scope_val = self.optman.get_opt_scope_value(self._cur_opt, active_scope, is_ui=True) # for UI 1108 active_scoped_val = (active_scope, active_scope_val) 1109 pass; LOG and print(' *** using option value: {}; removed:{}'.format(active_scoped_val, removed_scopes)) 1110 1111 self.toggle_mod_indicator(show=is_opt_modified) 1112 1113 new_scope, _new_val = active_scoped_val 1114 1115 # set scope 1116 new_scope_name = self._scope_captions[new_scope] 1117 with ignore_edit(self.h, self.scope_ed): 1118 self.scope_ed.set_text_all(new_scope_name) 1119 self.val_eds.set_type(self.h, self._cur_opt, scoped_val=active_scoped_val) 1120 1121 # rgb stuff 1122 prop_type = self._cur_opt['frm'] 1123 if prop_type in {'#rgb', '#rgb-e'}: 1124 self._update_rgb_edit() 1125 1126 1127 def _on_reset(self, id_dlg, id_ctl, data='', info=''): 1128 """ remove option for current scope 1129 """ 1130 self.add_opt_change(self._cur_opt_name, self.scope, val=None) 1131 # update value for current scope 1132 self._on_scope_change(-1,-1) 1133 1134 def _on_scope_change(self, id_dlg, id_ctl, data='', info=''): 1135 if not self._cur_opt: 1136 return 1137 1138 # check if have 'value' for 'scope' in ._opt_changes 1139 for opt_change in reversed(self._opt_changes): 1140 if opt_change.name == self._cur_opt_name and opt_change.scope == self.scope: 1141 cur_scope_val = opt_change.value or '' 1142 self.toggle_mod_indicator(show=True) 1143 break 1144 else: 1145 cur_scope_val = self.optman.get_opt_scope_value(self._cur_opt, scope=self.scope, is_ui=True) 1146 self.toggle_mod_indicator(show=False) 1147 1148 pass; LOG and print(' -- scoped val:{}:[{}]'.format(self.scope, cur_scope_val)) 1149 1150 self.val_eds.set_type(self.h, self._cur_opt, scoped_val=(self.scope, cur_scope_val)) 1151 1152 def _on_filter(self, id_dlg, id_ctl, data='', info=''): 1153 if isinstance(data, tuple): # on_key_down 1154 key_code, key_state = data 1155 if key_code == VK_ENTER and not key_state: 1156 # reset tree selection if selected item not in new filter 1157 selected_node = tree_proc(self._h_tree, TREE_ITEM_GET_SELECTED) 1158 if selected_node: 1159 path = get_tree_path(self._h_tree, item_id=selected_node) 1160 if '@'+path not in self.filter_val.split(): 1161 tree_proc(self._h_tree, TREE_ITEM_SELECT, id_item=0) 1162 1163 _t0 = time.time() 1164 self.set_filter(self.filter_val) 1165 _t1 = time.time() 1166 pass; LOG and print('* set-filter time:{:.3f}s'.format(_t1-_t0)) 1167 1168 #else: # on_change (typing, pasting) 1169 #print(' . CHANGE') 1170 1171 def _on_tree_click(self, id_dlg, id_ctl, data='', info=''): 1172 if data == 0: # deselected items 1173 return 1174 1175 path = get_tree_path(self._h_tree, item_id=data) 1176 if path == TREE_ITEM_ALL: # show all 1177 self.set_filter('', tree_click=True) 1178 else: 1179 new_filter = '@'+path 1180 1181 _keys = app_proc(PROC_GET_KEYSTATE, '') 1182 is_adding = set(_keys) == set('cL') 1183 if is_adding and self.filter_val: 1184 if new_filter in self.filter_val.split(): # already in filter - ignore 1185 return 1186 new_filter = self.filter_val +' '+ new_filter 1187 1188 self.set_filter(new_filter) 1189 1190 def _on_header_click(self, id_dlg, id_ctl, data='', info=''): 1191 pass; LOG and print('--- Header click-: {}'.format((id_dlg, id_ctl, data, info))) 1192 column_captions, _col_ws = self.columns 1193 col_ind = data 1194 self.set_sort(column_captions[col_ind]) 1195 1196 def _on_key(self, id_dlg, id_ctl, data='', info=''): 1197 key_code = id_ctl 1198 state = data 1199 #print(' on -key:{}'.format((key_code, state))) 1200 1201 if key_code == VK_F and state == 'c': # Ctrl+F -- show+focus filter 1202 self.toggle_filter(show=True) 1203 self._filter_ed.focus() 1204 return False # consumed 1205 1206 elif key_code == VK_ESCAPE and not state: # <escape> in filter - clear 1207 if self._filter_ed.get_prop(PROP_FOCUSED) and self.filter_val: 1208 self.set_filter('') 1209 #self.toggle_filter(show=False) 1210 return False # consumed 1211 1212 def listbox_menu(self, id_dlg, id_ctl, data='', info=''): 1213 if data['y'] < listbox_proc(self._h_list, LISTBOX_GET_ITEM_H): # is header click 1214 # create menu on first run 1215 if not self._h_col_menu: 1216 self._h_col_menu = menu_proc(0, MENU_CREATE) 1217 1218 for colname in COLS_LIST: 1219 if colname in self.hidden_columns: 1220 continue 1221 1222 la = lambda col=colname: self.on_toggle_col(col) 1223 ui_col_name = ui_column(colname) 1224 item_id = menu_proc(self._h_col_menu, MENU_ADD, 1225 command=la, caption=ui_col_name, tag=colname) 1226 1227 _enabled = colname != COL_OPT_NAME # 'option name' column - always shown 1228 menu_proc(item_id, MENU_SET_ENABLED, command=_enabled) 1229 1230 menu_proc(self._h_col_menu, MENU_ADD, caption='-') 1231 1232 la = lambda: self.configure_columns() 1233 menu_proc(self._h_col_menu, MENU_ADD, command=la, caption=_('Configure...')) 1234 #end if 1235 1236 1237 # update check state 1238 current_columns, _col_ws = self.columns 1239 for prop in menu_proc(self._h_col_menu, MENU_ENUM): 1240 _checked = prop['tag'] in current_columns 1241 menu_proc(prop['id'], MENU_SET_CHECKED, command=_checked) 1242 1243 menu_proc(self._h_col_menu, MENU_SHOW) 1244 1245 def on_toggle_col(self, info): 1246 pass; LOG and print('NOTE: toggling column: '+str(info)) 1247 1248 col_cfg = opt_col_cfg[:] 1249 1250 colname = info 1251 cur_col_names = [name for name,_w in col_cfg] 1252 if colname in cur_col_names: # disableg column 1253 del opt_col_cfg[cur_col_names.index(colname)] 1254 else: # add new column 1255 new_col_w = 100 1256 if colname == '!': new_col_w = '19px' 1257 elif colname == COL_SECTION: new_col_w = '120px' 1258 1259 opt_col_cfg.append((colname, new_col_w)) 1260 opt_col_cfg.sort(key=lambda item: COLS_LIST.index(item[0])) 1261 pass; LOG and print(' -- new columns: '+str(opt_col_cfg)) 1262 1263 self.update_list_layout() 1264 _opts = self.get_filtered_opts() 1265 self.update_list(_opts) 1266 1267 1268 def _clear_opt_edits(self): 1269 """ disables: 'scope combo', 'option comment' 1270 """ 1271 with ignore_edit(self.h, self.opt_comment_ed): 1272 self.opt_comment_ed.set_text_all('') 1273 with ignore_edit(self.h, self.scope_ed): 1274 self.scope_ed.set_text_all('') 1275 1276 self.val_eds.clear_edits(self.h) 1277 1278 def _dlg_value(self, prop_type): 1279 """ editing option value with a dialog 1280 returns: new value 1281 """ 1282 1283 if prop_type == 'hotk': # HOTKEY 1284 val = dlg_hotkey(title=self._cur_opt_name) 1285 1286 elif prop_type in {'#rgb', '#rgb-e'}: # RGB; val == None or correct html color 1287 # empty ('-e') -- only for edit field 1288 cur_scol = self.val_eds.val_edit.get_text_all() 1289 try: 1290 int_col = apx.html_color_to_int(cur_col) 1291 except: 1292 int_col = 0xffffff 1293 1294 val = dlg_color(int_col) 1295 1296 if val is not None: 1297 try: 1298 val = apx.int_to_html_color(val) 1299 except: 1300 val = None 1301 1302 elif prop_type == 'file': 1303 caption = _('Choose file: {}').format(self._cur_opt_name) 1304 val = dlg_file(is_open=False, init_filename='', init_dir='', filters='', caption=caption) 1305 1306 elif prop_type == 'json': 1307 from .dlg_json import JsonEd 1308 1309 j_ed = JsonEd(self._cur_opt, self.scope) 1310 val = j_ed.edit_json() 1311 #end if 1312 1313 return val 1314 1315 1316 def toggle_filter(self, show=False): 1317 #dlg_proc(self.h, DLG_CTL_PROP_SET, name='panel_filter', prop={'vis': show}) 1318 1319 if show == False: # if hiding filter - reset tree selection to 'All' 1320 for item_id,name in tree_proc(self._h_tree, TREE_ITEM_ENUM): 1321 if name == TREE_ITEM_ALL: 1322 tree_proc(self._h_tree, TREE_ITEM_SELECT, id_item=item_id) 1323 1324 def toggle_mod_indicator(self, tag='', info='', show=True, by_timer=False): 1325 if by_timer: 1326 timer_proc(TIMER_START_ONE, self.toggle_mod_indicator, 30, tag='ed_check_state') 1327 else: 1328 if tag == 'ed_check_state': 1329 ed_line_state = self.val_eds.val_edit.get_prop(PROP_LINE_STATE, 0) 1330 if ed_line_state == LINESTATE_NORMAL: 1331 return 1332 1333 dlg_proc(self.h, DLG_CTL_PROP_SET, name='mod_label', prop={'vis':show}) 1334 1335 def apply_changes(self, closing=False): 1336 """ batch apply qued option changes 1337 """ 1338 pass; LOG and print('APPLY_CHANGES') 1339 1340 # check if current value in edit is changed, create option change if it is 1341 try: 1342 edit_val = self.val_eds.get_edited_value(self._cur_opt) 1343 except ValueError: # exception happens when trying to cast empty str to float|int 1344 edit_val = None 1345 if edit_val is not None and edit_val != '': 1346 self.add_opt_change(self._cur_opt_name, self.scope, edit_val) 1347 1348 if not self._opt_changes and not closing: 1349 msg_status(_("No option changes has been made")) 1350 return 1351 1352 for i,change in enumerate(self._opt_changes): 1353 is_last = i == len(self._opt_changes) - 1 1354 if change.value is not None: # set value 1355 self.optman.set_opt(name=change.name, scope=change.scope, val=change.value, 1356 lexer=change.lexer, apply_=is_last) 1357 else: # removing value 1358 self.optman.reset_opt(name=change.name, scope=change.scope, 1359 lexer=change.lexer, apply_=is_last) 1360 1361 self._opt_changes.clear() 1362 self.optman.on_opts_change() 1363 _opts = self.get_filtered_opts() 1364 self.update_list(_opts) 1365 1366 def _update_rgb_edit(self, tag='', info=''): 1367 """ empty tag - ques in timer 1368 """ 1369 if not tag: 1370 timer_proc(TIMER_START_ONE, self._update_rgb_edit, 100, tag='on_timer') 1371 else: 1372 if self._closing is not None: 1373 ValueEds.update_ed_color(self.val_eds.val_edit) 1374 1375 1376 def dlg_help(self, *args, **vargs): 1377 if self._h_help == None: 1378 w, h = 600, 450 1379 self._h_help = dlg_proc(0, DLG_CREATE) 1380 1381 colors = app_proc(PROC_THEME_UI_DICT_GET, '') 1382 col_ed_bg = colors['EdTextBg']['color'] 1383 col_ed_font = colors['EdTextFont']['color'] 1384 color_form_bg = colors['ButtonBorderPassive']['color'] 1385 1386 dlg_proc(self._h_help, DLG_PROP_SET, 1387 prop={'cap': _('Help'), 1388 'w': w, 1389 'h': h, 1390 'resize': True, 1391 'color': color_form_bg, 1392 } 1393 ) 1394 1395 n = dlg_proc(self._h_help, DLG_CTL_ADD, 'memo') 1396 dlg_proc(self._h_help, DLG_CTL_PROP_SET, index=n, 1397 prop={ 1398 'name': 'help_memo', 1399 'align': ALIGN_CLIENT, 1400 'val': HELP_TEXT, 1401 'sp_a':6, 1402 'color': col_ed_bg, 1403 'font_color': col_ed_font, 1404 } 1405 ) 1406 1407 dlg_proc(self._h_help, DLG_SHOW_MODAL) 1408 1409 def close(self): 1410 self._save_dlg_cfg() 1411 1412 self._closing = True 1413 1414 dlg_proc(self.h, DLG_HIDE) 1415 1416 1417class ValueEds: 1418 """ * Responsible for: widgets for editing different value formats 1419 * Formats: bool, float, font, font-e, hotk, int, int2s, str, str2s, strs, 1420 """ 1421 VALUE_ED_PANEL = 'panel_value' 1422 VALUE_ED_RESET = 'btn_val_reset' 1423 1424 WGT_NAME__EDIT = 'cur_val__edit' 1425 WGT_NAME__COMBO = 'cur_val__combo' 1426 WGT_NAME__CHECK = 'cur_val__check' 1427 1428 WGT_NAME__BTN_EDIT = 'cur_val__edit_btn' 1429 1430 type_map = { 1431 'str': WGT_NAME__EDIT, 1432 'bool': WGT_NAME__CHECK, 1433 'float': WGT_NAME__EDIT, 1434 'font': WGT_NAME__COMBO, 1435 'font-e': WGT_NAME__COMBO, 1436 'hotk': WGT_NAME__EDIT, 1437 'int': WGT_NAME__EDIT, 1438 'int2s': WGT_NAME__COMBO, 1439 'str2s': WGT_NAME__COMBO, 1440 'strs': WGT_NAME__COMBO, 1441 1442 '#rgb': WGT_NAME__EDIT, 1443 '#rgb-e': WGT_NAME__EDIT, 1444 'file': WGT_NAME__EDIT, 1445 'json': WGT_NAME__EDIT, 1446 } 1447 1448 EXTRA_BTN_TYPES = {'hotk', '#rgb', '#rgb-e', 'file', 'json'} 1449 1450 FN_CHECKBOX_ICONS = { 1451 False: 'cb_unckecked.png', 1452 None: 'cb_none.png', 1453 True: 'cb_checked.png', 1454 } 1455 1456 _h_cb_iml = None 1457 _cb_icons = {} # False, None, True -> imagelist index 1458 1459 def __init__(self, val_change_callback): 1460 self._val_change_callback = val_change_callback 1461 self._ctl_names = {} # id_ctl -> name 1462 self._current_type = None 1463 self.val_edit = None 1464 self.val_combo = None 1465 1466 self._ignore_input = False 1467 1468 @property 1469 def cb_value(self): 1470 """ returns True, False, None 1471 """ 1472 imind = button_proc(self._h_cbx, BTN_GET_IMAGEINDEX) 1473 1474 for val,ind in ValueEds._cb_icons.items(): 1475 if ind == imind: 1476 return val 1477 1478 def set_type(self, h, opt, scoped_val): 1479 M = ValueEds 1480 1481 scope, value = scoped_val 1482 1483 newtype = opt.get('frm') 1484 1485 pass; LOG and print('* SET type-value-ed: type:{}, val:{}'.format( 1486 newtype, (scope, value, type(value)))) 1487 1488 self._hide_val_ed(h) 1489 1490 # unsupported option format 1491 if newtype not in M.type_map: 1492 print(_('OptionsError: unsupported option type: ')+str(newtype)) 1493 return 1494 1495 1496 # disable option editing? (some options cannot be a file opt) 1497 if scope == 'f' and not opt['opt'] in FILE_OPTS \ 1498 or scope == 'l' and GLOBAL_OP_CMT in opt['cmt']: 1499 1500 pass; LOG and print('NOTE: option NA: disabling') 1501 n = self._wgt_ind(h, M.type_map['str'], show=True) # ~resets wgt props 1502 self._current_type = 'str' 1503 1504 hint = _('Not available for a file') if scope=='f' else _('Not available for lexers') 1505 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 1506 'en': False, 1507 'texthint': hint, 1508 }) 1509 with ignore_edit(h, self.val_edit): 1510 self.val_edit.set_text_all('') 1511 return 1512 1513 1514 type_wgt_name = M.type_map[newtype] 1515 # show option-edit widget and gets widget index 1516 n = self._wgt_ind(h, type_wgt_name, show=True) # ~resets wgt props 1517 1518 if newtype == 'str': 1519 self.val_edit.set_text_all(value or '') 1520 1521 elif newtype == 'bool': 1522 #NOTE: UI bool values are: True, False, and '' for empty scope value 1523 if value is True: imind = ValueEds._cb_icons[value] 1524 elif value is False: imind = ValueEds._cb_icons[value] 1525 else: imind = ValueEds._cb_icons[None] 1526 1527 button_proc(self._h_cbx, BTN_SET_IMAGEINDEX, imind) 1528 1529 elif newtype == 'float': 1530 self.val_edit.set_text_all(str(value) or '') 1531 1532 elif newtype == 'font' or newtype == 'font-e': # font-e already has empty value in list 1533 self.val_combo.set_prop(PROP_COMBO_ITEMS, '\n'.join(opt['lst'])) 1534 with ignore_edit(h, self.val_combo): 1535 self.val_combo.set_text_all(value) 1536 1537 elif newtype == 'hotk': 1538 self.val_edit.set_text_all(value) 1539 self.val_edit.set_prop(PROP_RO, True) 1540 1541 self.layout_ed_btn(h, n, '...') 1542 1543 elif newtype == 'int': 1544 self.val_edit.set_text_all(str(value)) 1545 self.val_edit.set_prop(PROP_NUMBERS_ONLY, True) 1546 1547 elif newtype == 'int2s': 1548 #ed_val = map_option_value(opt, caption=value) 1549 with ignore_edit(h, self.val_combo): 1550 self.val_combo.set_text_all(value) 1551 self.val_combo.set_prop(PROP_COMBO_ITEMS, '\n'.join(opt['jdc'])) 1552 1553 elif newtype == 'str2s': 1554 #ed_val = map_option_value(opt, caption=value) 1555 with ignore_edit(h, self.val_combo): 1556 self.val_combo.set_text_all(value) 1557 self.val_combo.set_prop(PROP_COMBO_ITEMS, '\n'.join(opt['jdc'])) 1558 1559 elif newtype == 'strs': 1560 with ignore_edit(h, self.val_combo): 1561 self.val_combo.set_text_all(value) 1562 self.val_combo.set_prop(PROP_COMBO_ITEMS, '\n'.join(opt['lst'])) 1563 1564 elif newtype == '#rgb-e' or newtype == '#rgb': 1565 self.val_edit.set_text_all(str(value)) 1566 1567 self.val_edit.set_prop(PROP_GUTTER_ALL, True) 1568 self.val_edit.set_prop(PROP_GUTTER_STATES, False) 1569 self.val_edit.set_prop(PROP_GUTTER_NUM, False) 1570 1571 self.layout_ed_btn(h, n, '...') 1572 1573 elif newtype == 'file': 1574 self.val_edit.set_text_all(str(value)) 1575 1576 self.layout_ed_btn(h, n, _('Choose file...')) 1577 1578 elif newtype == 'json': 1579 self.val_edit.set_text_all(str(value)) 1580 self.val_edit.set_prop(PROP_RO, True) 1581 1582 self.layout_ed_btn(h, n, _('Edit...')) 1583 #end if 1584 1585 # set line state: normal (not edited) 1586 if type_wgt_name == M.WGT_NAME__EDIT: 1587 self.val_edit.set_prop(PROP_LINE_STATE, (0, LINESTATE_NORMAL)) 1588 1589 self._current_type = newtype 1590 1591 def clear_edits(self, h): 1592 M = ValueEds 1593 1594 self._hide_val_ed(h) 1595 _n = self._wgt_ind(h, M.WGT_NAME__EDIT, show=True) # ~resets wgt props 1596 self.val_edit.set_text_all('') 1597 1598 def get_name(self, id_ctl): 1599 return self._ctl_names.get(id_ctl) 1600 1601 def get_edited_value(self, opt): 1602 """ if 'WGT_NAME__EDIT' is current editor - return parsed value 1603 else None 1604 1605 """ 1606 M = ValueEds 1607 1608 type_wgt_name = M.type_map[self._current_type] 1609 1610 if type_wgt_name == M.WGT_NAME__EDIT: 1611 val = self.val_edit.get_text_all() 1612 1613 prop_type = opt['frm'] 1614 if prop_type == 'int': 1615 val = int(val) 1616 elif prop_type == 'float': 1617 val = float(val) 1618 elif prop_type in {'#rgb', '#rgb-e'}: 1619 if val == '': 1620 if prop_type != '#rgb-e': 1621 msg_status(_('Option "{}" does not accept empty value').format(opt['opt'])) 1622 return 1623 else: 1624 try: 1625 apx.html_color_to_int(val) 1626 except: 1627 msg_status(_('Incorrect color token: ') + val) 1628 return 1629 return val 1630 1631 1632 def _wgt_ind(self, h, name, show=False): 1633 """ creates widget if didn't exist 1634 returns: widget's form index 1635 """ 1636 M = ValueEds 1637 1638 default_props = { 1639 'name': name, 1640 'p': M.VALUE_ED_PANEL, 1641 'h': BTN_H, 'max_h': BTN_H, 1642 'a_l': ('mod_label', ']'), 1643 'a_t': (M.VALUE_ED_RESET, '['), 1644 'a_r': (M.VALUE_ED_RESET, '['), 1645 'a_b': (M.VALUE_ED_RESET, ']'), 1646 #'sp_l': PAD, 1647 'act': True, 'en': True, 1648 } 1649 1650 #TODO validate name 1651 if name == M.WGT_NAME__EDIT: 1652 n = dlg_proc(h, DLG_CTL_FIND, prop=name) 1653 if n == -1: # add if not already 1654 n = dlg_proc(h, DLG_CTL_ADD, 'editor_edit') 1655 h_ed = dlg_proc(h, DLG_CTL_HANDLE, index=n) 1656 self._ctl_names[n] = name 1657 self.val_edit = Editor(h_ed) 1658 1659 # resetting to defaults 1660 _props = {**default_props, 1661 'on_key_down': self._val_change_callback, 1662 'texthint': '',} 1663 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop=_props) 1664 1665 ValueEds.reset_edt(self.val_edit) 1666 1667 elif name == M.WGT_NAME__COMBO: 1668 n = dlg_proc(h, DLG_CTL_FIND, prop=name) 1669 if n == -1: # add if not already 1670 n = dlg_proc(h, DLG_CTL_ADD, 'editor_combo') 1671 1672 _props = {**default_props, 'on_change': self._val_change_callback,} 1673 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop=_props) 1674 1675 h_ed = dlg_proc(h, DLG_CTL_HANDLE, index=n) 1676 self._ctl_names[n] = name 1677 self.val_combo = Editor(h_ed) 1678 self.val_combo.set_prop(PROP_RO, True) 1679 1680 elif name == M.WGT_NAME__CHECK: 1681 n = dlg_proc(h, DLG_CTL_FIND, prop=name) 1682 if n == -1: # add if not already 1683 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 1684 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 1685 **default_props, 1686 'cap': _('Enable'), 1687 'act': True, 1688 'on_change': self._on_cb_click_proxy, 1689 'font_color': COL_FONT, 1690 }) 1691 self._h_cbx = dlg_proc(h, DLG_CTL_HANDLE, index=n) 1692 self._ctl_names[n] = name 1693 1694 button_proc(self._h_cbx, BTN_SET_FLAT, True) 1695 button_proc(self._h_cbx, BTN_SET_KIND, BTNKIND_TEXT_ICON_HORZ) 1696 # icons (checkbox) 1697 h_iml = ValueEds._get_checkbox_imagelist() 1698 button_proc(self._h_cbx, BTN_SET_IMAGELIST, h_iml) 1699 #end if 1700 1701 # Extra 1702 elif name == M.WGT_NAME__BTN_EDIT: 1703 n = dlg_proc(h, DLG_CTL_FIND, prop=name) 1704 if n == -1: # add if not already 1705 n = dlg_proc(h, DLG_CTL_ADD, 'button_ex') 1706 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 1707 **default_props, 1708 'cap': '...', 1709 'on_change': self._val_change_callback, 1710 }) 1711 self._ctl_names[n] = name 1712 #end if 1713 1714 if show: # set visible 1715 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={'vis': True}) 1716 1717 return n 1718 1719 def _hide_val_ed(self, h): 1720 M = ValueEds 1721 1722 if not self._current_type: 1723 return 1724 1725 to_hide = [M.type_map[self._current_type]] 1726 1727 if self._current_type in M.EXTRA_BTN_TYPES: 1728 to_hide.append(M.WGT_NAME__BTN_EDIT) 1729 1730 for name in to_hide: 1731 dlg_proc(h, DLG_CTL_PROP_SET, name=name, prop={'vis':False}) 1732 1733 def _on_cb_click_proxy(self, id_dlg, id_ctl, data='', info=''): 1734 """ changes button_ex checkbox icon to proper value, and sends control click to default callback 1735 """ 1736 cb_val = self.cb_value 1737 # cycle: [None => ] True => False => True... 1738 if cb_val is True: nextind = ValueEds._cb_icons[False] 1739 #elif cb_val is False: nextind = ValueEds._cb_icons[True] 1740 else: nextind = ValueEds._cb_icons[True] 1741 1742 button_proc(self._h_cbx, BTN_SET_IMAGEINDEX, nextind) 1743 1744 self._val_change_callback(id_dlg, id_ctl, data, info) 1745 1746 def layout_ed_btn(self, h, n, caption): 1747 M = ValueEds 1748 1749 w = int(BTN_W*0.5) if caption == '...' else BTN_W 1750 1751 btn_n = self._wgt_ind(h, M.WGT_NAME__BTN_EDIT, show=True) 1752 dlg_proc(h, DLG_CTL_PROP_SET, index=btn_n, prop={ 1753 'a_l': None, 'a_r': (M.VALUE_ED_RESET, '['), 1754 'w': w, 'max_w': w, 'sp_l': 2, 1755 'cap': caption, 1756 }) 1757 # ... edit 1758 dlg_proc(h, DLG_CTL_PROP_SET, index=n, prop={ 1759 #'a_l': ('', '['), 1760 'a_r': (M.WGT_NAME__BTN_EDIT, '[') 1761 }) 1762 1763 def update_ed_color(edt): 1764 try: 1765 int_col = apx.html_color_to_int(edt.get_text_all()) 1766 except: # invalid color -> reset to theme color 1767 colors = app_proc(PROC_THEME_UI_DICT_GET, '') 1768 int_col = colors['EdGutterBg']['color'] 1769 edt.set_prop(PROP_COLOR, (COLOR_ID_GutterBg, int_col)) 1770 1771 1772 def reset_edt(edt): 1773 edt.set_prop(PROP_NUMBERS_ONLY, False) 1774 edt.set_prop(PROP_RO, False) 1775 edt.set_prop(PROP_GUTTER_ALL, False) 1776 1777 @classmethod 1778 def _get_checkbox_imagelist(cls): 1779 # load icons only once 1780 if not ValueEds._h_cb_iml: 1781 ValueEds._h_cb_iml, ValueEds._cb_icons = load_imagelist(ValueEds.FN_CHECKBOX_ICONS) 1782 1783 return ValueEds._h_cb_iml 1784 1785class OptionMapValueError(Exception): 1786 pass 1787 1788HELP_TEXT = _("""About "Filter" 1789 Suitable options will contain all specified words. 1790 Tips and tricks: 1791 • Add "#" to search the words also in comments. 1792 • Add "@sec" to show options from section with "sec" in name. 1793 Several sections are allowed. 1794 Click item in sections tree with Ctrl to add it. 1795 • To show only overridden options: 1796 - Add "!" to show only User+Lexer+File. 1797 - Add "!!" to show only Lexer+File 1798 - Add "!!!" to show only File. 1799 • Use "<" or ">" for word boundary. 1800 Example: 1801 size> <tab 1802 selects "tab_size" but not "ui_tab_size" or "tab_size_x". 1803 1804 • Values in table column "!" 1805 ! option is set in "user.json", 1806 !! option is set in "lexer NNN.json", 1807 !!! option is set for current file, 1808 L default value is from "settings_default/lexer NNN.json", 1809 + not CudaText standard option. 1810""") 1811