1''' Lib for Plugin 2Authors: 3 Andrey Kvichansky (kvichans on github.com) 4Version: 5 '2.1.17 2021-04-16' 6Content 7 log Logger with timing 8 get_translation i18n 9 dlg_wrapper Wrapper for dlg_custom: pack/unpack values, h-align controls 10ToDo: (see end of file) 11''' 12 13import sys, os, gettext, logging, inspect, time, collections, json, re, subprocess 14from time import perf_counter 15 16try: 17 import cudatext as app 18 from cudatext import ed 19 import cudax_lib as apx 20except: 21 import sw as app 22 from sw import ed 23 from . import cudax_lib as apx 24 25pass; # Logging 26pass; from pprint import pformat 27pass; import tempfile 28 29odict = collections.OrderedDict 30T,F,N = True, False, None 31GAP = 5 32c13,c10,c9 = chr(13),chr(10),chr(9) 33REDUCTIONS = {'lb' :'label' 34 , 'ln-lb' :'linklabel' 35 , 'llb' :'linklabel' 36 , 'ed' :'edit' # ro_mono_brd 37 , 'ed_pw' :'edit_pwd' 38 , 'sp-ed' :'spinedit' # min_max_inc 39 , 'sed' :'spinedit' # min_max_inc 40 , 'me' :'memo' # ro_mono_brd 41 , 'bt' :'button' # def_bt 42 , 'rd' :'radio' 43 , 'ch' :'check' 44 , 'ch-bt' :'checkbutton' 45 , 'ch-b' :'checkbutton' 46 , 'chb' :'checkbutton' 47 , 'ch-gp' :'checkgroup' 48 , 'rd-gp' :'radiogroup' 49 , 'cb' :'combo' 50 , 'cb-ro' :'combo_ro' 51 , 'cb-r' :'combo_ro' 52 , 'cbr' :'combo_ro' 53 , 'lbx' :'listbox' 54 , 'ch-lbx' :'checklistbox' 55 , 'clx' :'checklistbox' 56 , 'lvw' :'listview' 57 , 'ch-lvw' :'checklistview' 58 , 'tabs' :'tabs' 59 , 'clr' :'colorpanel' 60 , 'im' :'image' 61 , 'f-lb' :'filter_listbox' 62 , 'f-lvw' :'filter_listview' 63 , 'fr' :'bevel' 64 , 'pn' :'panel' 65 , 'gr' :'group' 66 , 'sp' :'splitter' 67 68 , 'tvw' :'treeview' 69 , 'edr' :'editor' 70 , 'sb' :'statusbar' 71 , 'bte' :'button_ex' 72 73# , 'fid' :'focused' 74 , 'cols' :'columns' 75 } 76 77def f(s, *args, **kwargs):return s.format(*args, **kwargs) 78 79def log(msg='', *args, **kwargs): 80 if args or kwargs: 81 msg = msg.format(*args, **kwargs) 82 if Tr.tr is None: 83 Tr.tr=Tr() 84 return Tr.tr.log(msg) 85 86class Tr : 87 tr=None 88 """ Трассировщик. 89 Основной (единственный) метод: log(строка) - выводит указанную строку в лог. 90 Управляется через команды в строках для вывода. 91 Команды: 92 >> Увеличить сдвиг при выводе будущих строк (пока жив возвращенный объект) 93 (:) Начать замер нового вложенного периода, закончить когда умрет возвращенный объект 94 (== Начать замер нового вложенного периода 95 ==> Вывести длительность последнего периода 96 ==) Вывести длительность последнего периода и закончить его замер 97 =}} Отменить все замеры 98 Вызов log с командой >> (увеличить сдвиг) возвращает объект, 99 который при уничтожении уменьшит сдвиг 100 """ 101 sec_digs = 2 # Точность отображения секунд, кол-во дробных знаков 102 se_fmt = '' 103 mise_fmt = '' 104 homise_fmt = '' 105 def __init__(self, log_to_file=None) : 106 # Поля объекта 107 self.gap = '' # Отступ 108 self.tm = perf_counter() # Отметка времени о запуске 109 self.stms = [] # Отметки времени о начале замера спец.периода 110 111 if log_to_file: 112 logging.basicConfig( filename=log_to_file 113 ,filemode='w' 114 ,level=logging.DEBUG 115 ,format='%(message)s' 116 ,datefmt='%H:%M:%S' 117 ,style='%') 118 else: # to stdout 119 logging.basicConfig( stream=sys.stdout 120 ,level=logging.DEBUG 121 ,format='%(message)s' 122 ,datefmt='%H:%M:%S' 123 ,style='%') 124 # Tr() 125 def __del__(self): 126 logging.shutdown() 127 128 class TrLiver : 129 cnt = 0 130 """ Автоматически сокращает gap при уничножении 131 Показывает время своей жизни""" 132 def __init__(self, tr, ops) : 133 # Поля объекта 134 self.tr = tr 135 self.ops= ops 136 self.tm = 0 137 self.nm = Tr.TrLiver.cnt 138 if '(:)' in self.ops : 139 # Начать замер нового интервала 140 self.tm = perf_counter() 141 def log(self, msg='') : 142 if '(:)' in self.ops : 143 msg = '{}(:)=[{}]{}'.format( self.nm, Tr.format_tm( perf_counter() - self.tm ), msg ) 144 logging.debug( self.tr.format_msg(msg, ops='') ) 145 def __del__(self) : 146 #pass; logging.debug('in del') 147 if '(:)' in self.ops : 148 msg = '{}(:)=[{}]'.format( self.nm, Tr.format_tm( perf_counter() - self.tm ) ) 149 logging.debug( self.tr.format_msg(msg, ops='') ) 150 if '>>' in self.ops : 151 self.tr.gap = self.tr.gap[:-1] 152 153 def log(self, msg='') : 154 if '(:)' in msg : 155 Tr.TrLiver.cnt += 1 156 msg = msg.replace( '(:)', '{}(:)'.format(Tr.TrLiver.cnt) ) 157 logging.debug( self.format_msg(msg) ) 158 if '>>' in msg : 159 self.gap = self.gap + c9 160 # Создаем объект, который при разрушении сократит gap 161 if '>>' in msg or '(:)' in msg: 162 return Tr.TrLiver(self,('>>' if '>>' in msg else '')+('(:)' if '(:)' in msg else '')) 163 # return Tr.TrLiver(self,iif('>>' in msg,'>>','')+iif('(:)' in msg,'(:)','')) 164 else : 165 return self 166 # Tr.log 167 168# def format_msg(self, msg, dpth=2, ops='+fun:ln +wait==') : 169 def format_msg(self, msg, dpth=3, ops='+fun:ln +wait==') : 170 if '(==' in msg : 171 # Начать замер нового интервала 172 self.stms = self.stms + [perf_counter()] 173 msg = msg.replace( '(==', '(==[' + Tr.format_tm(0) + ']' ) 174 175 if '###' in msg : 176 # Показать стек 177 st_inf = '\n###' 178 for fr in inspect.stack()[1+dpth:]: 179 try: 180 cls = fr[0].f_locals['self'].__class__.__name__ + '.' 181 except: 182 cls = '' 183 fun = (cls + fr[3]).replace('.__init__','()') 184 ln = fr[2] 185 st_inf += ' {}:{}'.format(fun, ln) 186 msg += st_inf 187 188 if '+fun:ln' in ops : 189 frCaller= inspect.stack()[dpth] # 0-format_msg, 1-Tr.log|Tr.TrLiver, 2-log, 3-need func 190 try: 191 cls = frCaller[0].f_locals['self'].__class__.__name__ + '.' 192 except: 193 cls = '' 194 fun = (cls + frCaller[3]).replace('.__init__','()') 195 ln = frCaller[2] 196 msg = '[{}]{}{}:{} '.format( Tr.format_tm( perf_counter() - self.tm ), self.gap, fun, ln ) + msg 197 else : 198 msg = '[{}]{}'.format( Tr.format_tm( perf_counter() - self.tm ), self.gap ) + msg 199 200 if '+wait==' in ops : 201 if ( '==)' in msg or '==>' in msg ) and len(self.stms)>0 : 202 # Закончить/продолжить замер последнего интервала и вывести его длительность 203 sign = '==)' if '==)' in msg else '==>' 204 # sign = icase( '==)' in msg, '==)', '==>' ) 205 stm = '[{}]'.format( Tr.format_tm( perf_counter() - self.stms[-1] ) ) 206 msg = msg.replace( sign, sign+stm ) 207 if '==)' in msg : 208 del self.stms[-1] 209 210 if '=}}' in msg : 211 # Отменить все замеры 212 self.stms = [] 213 214 return msg.replace('¬',c9).replace('¶',c10) 215 # Tr.format 216 217 @staticmethod 218 def format_tm(secs) : 219 """ Конвертация количества секунд в 12h34'56.78" """ 220 if 0==len(Tr.se_fmt) : 221 Tr.se_fmt = '{:'+str(3+Tr.sec_digs)+'.'+str(Tr.sec_digs)+'f}"' 222 Tr.mise_fmt = "{:2d}'"+Tr.se_fmt 223 Tr.homise_fmt = "{:2d}h"+Tr.mise_fmt 224 h = int( secs / 3600 ) 225 secs = secs % 3600 226 m = int( secs / 60 ) 227 s = secs % 60 228 return Tr.se_fmt.format(s) \ 229 if 0==h+m else \ 230 Tr.mise_fmt.format(m,s) \ 231 if 0==h else \ 232 Tr.homise_fmt.format(h,m,s) 233 # return icase( 0==h+m, Tr.se_fmt.format(s) 234 # , 0==h, Tr.mise_fmt.format(m,s) 235 # , Tr.homise_fmt.format(h,m,s) ) 236 # Tr.format_tm 237 # Tr 238 239def get_desktop_environment(): 240 #From http://stackoverflow.com/questions/2035657/what-is-my-current-desktop-environment 241 # and http://ubuntuforums.org/showthread.php?t=652320 242 # and http://ubuntuforums.org/showthread.php?t=652320 243 # and http://ubuntuforums.org/showthread.php?t=1139057 244 if sys.platform in ["win32", "cygwin"]: 245 return "win" 246 elif sys.platform == "darwin": 247 return "mac" 248 else: #Most likely either a POSIX system or something less common 249 desktop_session = os.environ.get("DESKTOP_SESSION") 250 if desktop_session is not None: #easier to match if we don' have to deal with character cases 251 desktop_session = desktop_session.lower() 252 if desktop_session in ["gnome","unity", "cinnamon", "mate", "xfce4", "lxde", "fluxbox", 253 "blackbox", "openbox", "icewm", "jwm", "afterstep","trinity", "kde"]: 254 return desktop_session 255 ## Special cases ## 256 # Canonical sets $DESKTOP_SESSION to Lubuntu rather than LXDE if using LXDE. 257 # There is no guarantee that they will not do the same with the other desktop environments. 258 elif "xfce" in desktop_session or desktop_session.startswith("xubuntu"): 259 return "xfce4" 260 elif desktop_session.startswith("ubuntu"): 261 return "unity" 262 elif desktop_session.startswith("lubuntu"): 263 return "lxde" 264 elif desktop_session.startswith("kubuntu"): 265 return "kde" 266 elif desktop_session.startswith("razor"): # e.g. razorkwin 267 return "razor-qt" 268 elif desktop_session.startswith("wmaker"): # e.g. wmaker-common 269 return "windowmaker" 270 if os.environ.get('KDE_FULL_SESSION') == 'true': 271 return "kde" 272 elif os.environ.get('GNOME_DESKTOP_SESSION_ID'): 273 if not "deprecated" in os.environ.get('GNOME_DESKTOP_SESSION_ID'): 274 return "gnome2" 275 #From http://ubuntuforums.org/showthread.php?t=652320 276 elif is_running("xfce-mcs-manage"): 277 return "xfce4" 278 elif is_running("ksmserver"): 279 return "kde" 280 return "unknown" 281def is_running(process): 282 #From http://www.bloggerpolis.com/2011/05/how-to-check-if-a-process-is-running-using-python/ 283 # and http://richarddingwall.name/2009/06/18/windows-equivalents-of-ps-and-kill-commands/ 284 try: #Linux/Unix 285 s = subprocess.Popen(["ps", "axw"],stdout=subprocess.PIPE) 286 except: #Windows 287 s = subprocess.Popen(["tasklist", "/v"],stdout=subprocess.PIPE) 288 for x in s.stdout: 289 if re.search(process, str(x)): 290 return True 291 return False 292 293ENV2FITS= {'win': 294 {'check' :-2 295 ,'radio' :-2 296 ,'edit' :-3 297 ,'button' :-4 298 ,'combo_ro' :-4 299 ,'combo' :-3 300 ,'checkbutton':-5 301 ,'linklabel' : 0 302 ,'spinedit' :-3 303 } 304 ,'unity': 305 {'check' :-3 306 ,'radio' :-3 307 ,'edit' :-5 308 ,'button' :-4 309 ,'combo_ro' :-5 310 ,'combo' :-6 311 ,'checkbutton':-4 312 ,'linklabel' : 0 313 ,'spinedit' :-6 314 } 315 ,'mac': 316 {'check' :-1 317 ,'radio' :-1 318 ,'edit' :-3 319 ,'button' :-3 320 ,'combo_ro' :-2 321 ,'combo' :-3 322 ,'checkbutton':-2 323 ,'linklabel' : 0 324 ,'spinedit' : 0 ##?? 325 } 326 } 327fit_top_by_env__cash = {} 328def fit_top_by_env__clear(): 329 global fit_top_by_env__cash 330 fit_top_by_env__cash = {} 331def fit_top_by_env(what_tp, base_tp='label'): 332 """ Get "fitting" to add to top of first control to vertical align inside text with text into second control. 333 The fittings rely to platform: win, unix(kde,gnome,...), mac 334 """ 335 if what_tp==base_tp: 336 return 0 337 if (what_tp, base_tp) in fit_top_by_env__cash: 338 pass; #log('cashed what_tp, base_tp={}',(what_tp, base_tp)) 339 return fit_top_by_env__cash[(what_tp, base_tp)] 340 env = get_desktop_environment() 341 pass; #env = 'mac' 342 fit4lb = ENV2FITS.get(env, ENV2FITS.get('win')) 343 fit = 0 344 if base_tp=='label': 345 fit = apx.get_opt('dlg_wrapper_fit_va_for_'+what_tp, fit4lb.get(what_tp, 0)) 346 pass; #fit_o=fit 347 fit = _os_scale(app.DLG_PROP_GET, {'y':fit})['y'] 348 pass; #log('what_tp,fit_o,fit,h={}',(what_tp,fit_o,fit,get_gui_height(what_tp))) 349 else: 350 fit = fit_top_by_env(what_tp) - fit_top_by_env(base_tp) 351 pass; #log('what_tp, base_tp, fit={}',(what_tp, base_tp, fit)) 352 return fit_top_by_env__cash.setdefault((what_tp, base_tp), fit) 353 #def fit_top_by_env 354 355def rgb_to_int(r,g,b): 356 return r | (g<<8) | (b<<16) 357def dlg_wrapper(title, w, h, cnts, in_vals={}, focus_cid=None): 358 """ Wrapper for dlg_custom. 359 Params 360 title, w, h Title, Width, Height 361 cnts List of static control properties 362 [{cid:'*', tp:'*', t:1,l:1,w:1,r:1,b;1,h:1,tid:'cid', cap:'*', hint:'*', en:'0', props:'*', items:[*], act='0'}] 363 cid (opt)(str) C(ontrol)id. Need only for buttons and controls with value (and for tid) 364 tp (str) Control types from wiki or short names 365 t (opt)(int) Top 366 tid (opt)(str) Ref to other control cid for horz-align text in both controls 367 l (int) Left 368 r,b,w,h (opt)(int) Position. w>>>r=l+w, h>>>b=t+h, b can be omitted 369 cap (str) Caption for labels and buttons 370 hint (opt)(str) Tooltip 371 en (opt)('0'|'1'|True|False) Enabled-state 372 props (opt)(str) See wiki 373 act (opt)('0'|'1'|True|False) Will close dlg when changed 374 items (str|list) String as in wiki. List structure by types: 375 [v1,v2,] For combo, combo_ro, listbox, checkgroup, radiogroup, checklistbox 376 (head, body) For listview, checklistview 377 head [(cap,width),(cap,width),] 378 body [[r0c0,r0c1,],[r1c0,r1c1,],[r2c0,r2c1,],] 379 in_vals Dict of start values for some controls 380 {'cid':val} 381 focus_cid (opt) Control cid for start focus 382 Return 383 btn_cid Clicked/changed control cid 384 {'cid':val} Dict of new values for the same (as in_vals) controls 385 Format of values is same too. 386 focus_cid Focused control cid 387 [cid] List of controls with changed values 388 Short names for types 389 lb label 390 ln-lb linklabel 391 ed edit 392 sp-ed spinedit 393 me memo 394 bt button 395 rd radio 396 ch check 397 ch-bt checkbutton 398 ch-gp checkgroup 399 rd-gp radiogroup 400 cb combo 401 cb-ro combo_ro 402 lbx listbox 403 ch-lbx checklistbox 404 lvw listview 405 ch-lvw checklistview 406 Example. 407 def ask_number(ask, def_val): 408 cnts=[dict( tp='lb',tid='v',l=3 ,w=70,cap=ask) 409 ,dict(cid='v',tp='ed',t=3 ,l=73,w=70) 410 ,dict(cid='!',tp='bt',t=45 ,l=3 ,w=70,cap='OK',props='1') 411 ,dict(cid='-',tp='bt',t=45 ,l=73,w=70,cap='Cancel')] 412 vals={'v':def_val} 413 while True: 414 aid,vals,fid,chds=dlg_wrapper('Example',146,75,cnts,vals,'v') 415 if aid is None or btn=='-': return def_val 416 if not re.match(r'\d+$', vals['v']): continue 417 return vals['v'] 418 """ 419 pass; #log('in_vals={}',pformat(in_vals, width=120)) 420 cnts = [cnt for cnt in cnts if cnt.get('vis', True) in (True, '1')] 421 cid2i = {cnt['cid']:i for i,cnt in enumerate(cnts) if 'cid' in cnt} 422 if True: 423 # Checks 424 no_tids = {cnt['tid'] for cnt in cnts if 'tid' in cnt and cnt['tid'] not in cid2i} 425 if no_tids: 426 raise Exception(f('No cid(s) for tid(s): {}', no_tids)) 427 no_vids = {cid for cid in in_vals if cid not in cid2i} 428 if no_vids: 429 raise Exception(f('No cid(s) for vals: {}', no_vids)) 430 431 simpp = ['cap','hint' 432 ,'props' 433 ,'color' 434 ,'font_name', 'font_size', 'font_color', 'font' 435 ,'act' 436 ,'en','vis' 437 #,'tag' 438 ] 439 ctrls_l = [] 440 for cnt in cnts: 441 tp = cnt['tp'] 442 tp = REDUCTIONS.get(tp, tp) 443 if tp=='--': 444 # Horz-line 445 t = cnt.get('t') 446 l = cnt.get('l', 0) # def: from DlgLeft 447 r = cnt.get('r', l+cnt.get('w', w)) # def: to DlgRight 448 lst = ['type=label'] 449 lst+= ['cap='+'—'*1000] 450 lst+= ['font_color='+str(rgb_to_int(185,185,185))] 451 lst+= ['pos={l},{t},{r},0'.format(l=l,t=t,r=r)] 452 ctrls_l+= [chr(1).join(lst)] 453 continue#for cnt 454 455 lst = ['type='+tp] 456 457 # Preprocessor 458 if 'props' in cnt: 459 pass 460 elif tp=='label' and cnt['cap'][0]=='>': 461 # cap='>smth' --> cap='smth', props='1' (r-align) 462 cnt['cap'] = cnt['cap'][1:] 463 cnt['props']= '1' 464 elif tp=='label' and cnt.get('ralign'): 465 cnt['props']= cnt.get('ralign') 466 elif tp=='button' and cnt.get('def_bt') in ('1', True): 467 cnt['props']= '1' 468 elif tp=='spinedit' and cnt.get('min_max_inc'): 469 cnt['props']= cnt.get('min_max_inc') 470 elif tp=='linklabel' and cnt.get('url'): 471 cnt['props']= cnt.get('url') 472 elif tp=='listview' and cnt.get('grid'): 473 cnt['props']= cnt.get('grid') 474 elif tp=='tabs' and cnt.get('at_botttom'): 475 cnt['props']= cnt.get('at_botttom') 476 elif tp=='colorpanel' and cnt.get('brdW_fillC_fontC_brdC'): 477 cnt['props']= cnt.get('brdW_fillC_fontC_brdC') 478 elif tp in ('edit', 'memo') and cnt.get('ro_mono_brd'): 479 cnt['props']= cnt.get('ro_mono_brd') 480 481# # Simple props 482# for k in ['cap', 'hint', 'props', 'font_name', 'font_size', 'font_color', 'font', 'name']: 483# lst += [k+'='+str(cnt[k])] 484 485 if 'props' in cnt: 486 props = cnt.pop('props').split(',') 487 for p_i, p_s in enumerate(props): 488 lst += ['ex'+str(p_i)+'='+p_s] 489 490 # Position: 491 # t[op] or tid, l[eft] required 492 # w[idth] >>> r[ight ]=l+w 493 # h[eight] >>> b[ottom]=t+h 494 # b dont need for buttons, edit, labels 495 l = cnt['l'] 496 t = cnt.get('t', 0) 497 if 'tid' in cnt: 498 # cid for horz-align text 499 bs_cnt = cnts[cid2i[cnt['tid']]] 500 bs_tp = bs_cnt['tp'] 501 t = bs_cnt['t'] + fit_top_by_env(tp, REDUCTIONS.get(bs_tp, bs_tp)) 502 r = cnt.get('r', l+cnt.get('w', 0)) 503 b = cnt.get('b', t+cnt.get('h', 0)) 504 lst += ['pos={l},{t},{r},{b}'.format(l=l,t=t,r=r,b=b)] 505# if 'en' in cnt: 506# val = cnt['en'] 507# lst += ['en='+('1' if val in [True, '1'] else '0')] 508 509 if 'items' in cnt: 510 items = cnt['items'] 511 if isinstance(items, str): 512 pass 513 elif tp in ['listview', 'checklistview']: 514 # For listview, checklistview: "\t"-separated items. 515 # first item is column headers: title1+"="+size1 + "\r" + title2+"="+size2 + "\r" +... 516 # other items are data: cell1+"\r"+cell2+"\r"+... 517 # ([(hd,wd)], [[cells],[cells],]) 518 items = '\t'.join(['\r'.join(['='.join((hd,sz)) for hd,sz in items[0]])] 519 +['\r'.join(row) for row in items[1]] 520 ) 521 else: 522 # For combo, combo_ro, listbox, checkgroup, radiogroup, checklistbox: "\t"-separated lines 523 items = '\t'.join(items) 524 lst+= ['items='+items] 525 526 # Prepare val 527 if cnt.get('cid') in in_vals: 528 in_val = in_vals[cnt['cid']] 529 if False:pass 530 elif tp in ['check', 'radio', 'checkbutton'] and isinstance(in_val, bool): 531 # For check, radio, checkbutton: value "0"/"1" 532 in_val = '1' if in_val else '0' 533 elif tp=='memo': 534 # For memo: "\t"-separated lines (in lines "\t" must be replaced to chr(2)) 535 if isinstance(in_val, list): 536 in_val = '\t'.join([v.replace('\t', chr(2)) for v in in_val]) 537 else: 538 in_val = in_val.replace('\t', chr(2)).replace('\r\n','\n').replace('\r','\n').replace('\n','\t') 539 elif tp=='checkgroup' and isinstance(in_val, list): 540 # For checkgroup: ","-separated checks (values "0"/"1") 541 in_val = ','.join(in_val) 542 elif tp in ['checklistbox', 'checklistview'] and isinstance(in_val, tuple): 543 # For checklistbox, checklistview: index+";"+checks 544 in_val = ';'.join( (str(in_val[0]), ','.join( in_val[1]) ) ) 545 lst+= ['val='+str(in_val)] 546 547# if 'act' in cnt: # must be last in lst 548# val = cnt['act'] 549# lst += ['act='+('1' if val in [True, '1'] else '0')] 550 551 # Simple props 552 for k in simpp: 553 if k in cnt: 554 v = cnt[k] 555 v = ('1' if v else '0') if isinstance(v, bool) else str(v) 556 lst += [k+'='+v] 557 pass; #log('lst={}',lst) 558 ctrls_l+= [chr(1).join(lst)] 559 #for cnt 560 pass; #log('ok ctrls_l={}',pformat(ctrls_l, width=120)) 561 562 pass; #ctrls_fn=tempfile.gettempdir()+os.sep+'dlg_custom_ctrls.txt' 563 pass; #open(ctrls_fn, 'w', encoding='UTF-8').write('\n'.join(ctrls_l).replace('\r','')) 564 pass; #log(f(r'app.dlg_custom("{t}",{w},{h},open(r"{fn}",encoding="UTF-8").read(), {f})',t=title, w=w, h=h, fn=ctrls_fn, f=cid2i.get(focus_cid, -1))) 565 ans = app.dlg_custom(title, w, h, '\n'.join(ctrls_l), cid2i.get(focus_cid, -1)) 566 if ans is None: return None, None, None, None # btn_cid, {cid:v}, focus_cid, [cid] 567 pass; #log('ans={})',ans) 568 569 btn_i, \ 570 vals_ls = ans[0], ans[1].splitlines() 571 pass; #log('btn_i,vals_ls={})',(btn_i,vals_ls)) 572 573 focus_cid = '' 574 if vals_ls[-1].startswith('focused='): 575 # From API 1.0.156 dlg_custom also returns index of active control 576 focus_n_s = vals_ls.pop() 577 focus_i = int(focus_n_s.split('=')[1]) 578 focus_cid = cnts[focus_i].get('cid', '') 579 pass; #log('btn_i,vals_ls,focus_cid={})',(btn_i,vals_ls,focus_cid)) 580 581 act_cid = cnts[btn_i]['cid'] 582 # Parse output values 583 an_vals = {cid:vals_ls[cid2i[cid]] for cid in in_vals} 584 for cid in an_vals: 585 cnt = cnts[cid2i[cid]] 586 tp = cnt['tp'] 587 tp = REDUCTIONS.get(tp, tp) 588 in_val = in_vals[cid] 589 an_val = an_vals[cid] 590 pass; #log('tp,in_val,an_val={})',(tp,in_val,an_val)) 591 if False:pass 592 elif tp=='memo': 593 # For memo: "\t"-separated lines (in lines "\t" must be replaced to chr(2)) 594 if isinstance(in_val, list): 595 an_val = [v.replace(chr(2), '\t') for v in an_val.split('\t')] 596 #in_val = '\t'.join([v.replace('\t', chr(2)) for v in in_val]) 597 else: 598 an_val = an_val.replace('\t','\n').replace(chr(2), '\t') 599 #in_val = in_val.replace('\t', chr(2)).replace('\r\n','\n').replace('\r','\n').replace('\n','\t') 600 elif tp=='checkgroup' and isinstance(in_val, list): 601 # For checkgroup: ","-separated checks (values "0"/"1") 602 an_val = an_val.split(',') 603 #in_val = ','.join(in_val) 604 elif tp in ['checklistbox', 'checklistview'] and isinstance(in_val, tuple): 605 an_val = an_val.split(';') 606 an_val = (an_val[0], an_val[1].split(',')) 607 #in_val = ';'.join(in_val[0], ','.join(in_val[1])) 608 elif isinstance(in_val, bool): 609 an_val = an_val=='1' 610 elif tp=='listview': 611 an_val = -1 if an_val=='' else int(an_val) 612 else: 613 an_val = type(in_val)(an_val) 614 pass; #log('type(in_val),an_val={})',(type(in_val),an_val)) 615 an_vals[cid] = an_val 616 #for cid 617 chds = [cid for cid in in_vals if in_vals[cid]!=an_vals[cid]] 618 if focus_cid: 619 # If out focus points to button then will point to a unique changed control 620 focus_tp= cnts[cid2i[focus_cid]]['tp'] 621 focus_tp= REDUCTIONS.get(focus_tp, focus_tp) 622 if focus_tp in ('button'): 623 focus_cid = '' if len(chds)!=1 else chds[0] 624 return act_cid \ 625 , an_vals \ 626 , focus_cid \ 627 , chds 628 #def dlg_wrapper 629 630DLG_CTL_ADD_SET = 26 631DLG_PROC_I2S={ 6320:'DLG_CREATE' 633,1:'DLG_FREE' 634,5:'DLG_SHOW_MODAL' 635,6:'DLG_SHOW_NONMODAL' 636,7:'DLG_HIDE' 637,8:'DLG_FOCUS' 638,9:'DLG_SCALE' 639,10:'DLG_PROP_GET' 640,11:'DLG_PROP_SET' 641,12:'DLG_DOCK' 642,13:'DLG_UNDOCK' 643,20:'DLG_CTL_COUNT' 644,21:'DLG_CTL_ADD' 645,26:'DLG_CTL_ADD_SET' 646,22:'DLG_CTL_PROP_GET' 647,23:'DLG_CTL_PROP_SET' 648,24:'DLG_CTL_DELETE' 649,25:'DLG_CTL_DELETE_ALL' 650,30:'DLG_CTL_FOCUS' 651,31:'DLG_CTL_FIND' 652,32:'DLG_CTL_HANDLE' 653} 654_SCALED_KEYS = ('x', 'y', 'w', 'h' 655 , 'w_min', 'w_max', 'h_min', 'h_max' 656 , 'sp_l', 'sp_r', 'sp_t', 'sp_b', 'sp_a' 657 ) 658def _os_scale(id_action, prop=None, index=-1, index2=-1, name=''): 659 pass; #return prop 660 pass; #log('prop={}',({k:prop[k] for k in prop if k in ('x','y')})) 661 pass; #logb=name in ('tolx', 'tofi') and id_action==app.DLG_CTL_PROP_GET 662 pass; #log('name,prop={}',(name,{k:prop[k] for k in prop if k in ('h','_ready_h')})) if logb else 0 663 ppi = app.app_proc(app.PROC_GET_SYSTEM_PPI, '') 664 if ppi==96: 665 return prop 666 scale = ppi/96 667 pass; #log('id_dialog, id_action,scale={}',(id_dialog, DLG_PROC_I2S[id_action],scale)) 668 if False:pass 669 elif id_action in (app.DLG_PROP_SET , app.DLG_PROP_GET 670 ,app.DLG_CTL_PROP_SET , app.DLG_CTL_PROP_GET 671 ,'scale', 'unscale'): 672 673 def scale_up(prop_dct): 674 for k in _SCALED_KEYS: 675 if k in prop_dct and '_ready_'+k not in prop_dct: 676 prop_dct[k] = round(prop_dct[k] * scale) # Scale! 677 678 def scale_dn(prop_dct): 679 for k in _SCALED_KEYS: 680 if k in prop_dct and '_ready_'+k not in prop_dct: 681 prop_dct[k] = round(prop_dct[k] / scale) # UnScale! 682 683# pass; print('a={}, ?? pr={}'.format(DLG_PROC_I2S[id_action], {k:prop[k] for k in prop if k in _SCALED_KEYS or k=='name'})) 684 if False:pass 685 elif id_action==app.DLG_PROP_SET: scale_up(prop) 686 elif id_action==app.DLG_CTL_PROP_SET and -1!=index: scale_up(prop) 687 elif id_action==app.DLG_CTL_PROP_SET and ''!=name: scale_up(prop) 688 elif id_action==app.DLG_PROP_GET: scale_dn(prop) 689 elif id_action==app.DLG_CTL_PROP_GET and -1!=index: scale_dn(prop) 690 elif id_action==app.DLG_CTL_PROP_GET and ''!=name: scale_dn(prop) 691 692 elif id_action== 'scale': scale_up(prop) 693 elif id_action=='unscale': scale_dn(prop) 694 pass; #print('a={}, ok pr={}'.format(DLG_PROC_I2S[id_action], {k:prop[k] for k in prop if k in _SCALED_KEYS or k=='name'})) 695 pass; #log('name,prop={}',(name,{k:prop[k] for k in prop if k in ('h','_ready_h')})) if logb else 0 696 return prop 697 #def _os_scale 698 699gui_height_cache= { 'button' :0 700 , 'label' :0 701 , 'linklabel' :0 702 , 'combo' :0 703 , 'combo_ro' :0 704 , 'edit' :0 705 , 'spinedit' :0 706 , 'check' :0 707 , 'radio' :0 708 , 'checkbutton' :0 709 , 'filter_listbox' :0 710 , 'filter_listview' :0 711# , 'scrollbar' :0 712 } 713def get_gui_height(ctrl_type): 714 """ Return real OS-specific height of some control 715 'button' 716 'label' 'linklabel' 717 'combo' 'combo_ro' 718 'edit' 'spinedit' 719 'check' 'radio' 'checkbutton' 720 'filter_listbox' 'filter_listview' 721 'scrollbar' 722 """ 723 global gui_height_cache 724 if 0 == gui_height_cache['button']: 725 for tpc in gui_height_cache: 726 gui_height_cache[tpc] = app.app_proc(app.PROC_GET_GUI_HEIGHT, tpc) 727 pass; #log('gui_height_cache={}',(gui_height_cache)) 728 idd=app.dlg_proc( 0, app.DLG_CREATE) 729 for tpc in gui_height_cache: 730 idc=app.dlg_proc( idd, app.DLG_CTL_ADD, tpc) 731 pass; #log('tpc,idc={}',(tpc,idc)) 732 prc = {'name':tpc, 'x':0, 'y':0, 'w':1, 'cap':tpc 733 , 'h':gui_height_cache[tpc]} 734 if tpc in ('combo' 'combo_ro'): 735 prc['items']='item0' 736 app.dlg_proc( idd, app.DLG_CTL_PROP_SET, index=idc, prop=prc) 737 app.dlg_proc( idd, app.DLG_PROP_SET, prop={'x':-1000, 'y':-1000, 'w':100, 'h':100}) 738 app.dlg_proc( idd, app.DLG_SHOW_NONMODAL) 739 740 ppi = app.app_proc(app.PROC_GET_SYSTEM_PPI, '') 741 if ppi!=96: 742 # Try to scale height of controls 743 scale = ppi/96 744 for tpc in gui_height_cache: 745 prc = app.dlg_proc( idd, app.DLG_CTL_PROP_GET, name=tpc) 746 sc_h = round(prc['h'] * scale) 747 app.dlg_proc( idd, app.DLG_CTL_PROP_SET, name=tpc, prop=dict(h=sc_h)) 748 749 for tpc in gui_height_cache: 750 prc = app.dlg_proc( idd, app.DLG_CTL_PROP_GET, name=tpc) 751 pass; #log('prc={}',(prc)) 752 gui_height_cache[tpc] = prc['h'] 753 app.dlg_proc( idd, app.DLG_FREE) 754 pass; #log('gui_height_cache={}',(gui_height_cache)) 755 756 return gui_height_cache.get(ctrl_type, app.app_proc(app.PROC_GET_GUI_HEIGHT, ctrl_type)) 757 #def get_gui_height 758 759def dlg_proc_wpr(id_dialog, id_action, prop='', index=-1, index2=-1, name=''): 760 """ Wrapper on app.dlg_proc 761 1. To set/get dlg-props in scaled OS 762 2. New command DLG_CTL_ADD_SET to set props of created ctrl 763 3. Correct prop for ('label', 'button', 'checkbutton'): if no 'h' then set 'h' as OS default 764 """ 765 if id_action==app.DLG_SCALE: 766 return 767# if id_dialog==0: 768# print('idd=dlg_proc(0, {})'.format(DLG_PROC_I2S[id_action])) 769# else: 770# print('dlg_proc(idd, {}, index={}, name="{}", prop={}, index2={})'.format( DLG_PROC_I2S[id_action], 771# index, name, 772# '""' if type(prop)==str else prop, 773## '""' if type(prop)==str else {k:prop[k] for k in prop if k not in ['on_change']}, 774# index2)) 775 #print('#dlg_proc id_action='+str(id_action)+' prop='+repr(prop)) 776 777 scale_on_set = id_action in (app.DLG_PROP_SET, app.DLG_CTL_PROP_SET) 778 scale_on_get = id_action in (app.DLG_PROP_GET, app.DLG_CTL_PROP_GET) 779 res = '' 780 if id_action==DLG_CTL_ADD_SET: # Join ADD and SET for a control 781 res = ctl_ind = \ 782 app.dlg_proc(id_dialog, app.DLG_CTL_ADD, name, -1, -1, '') # type in name 783# if name in ('label', 'button', 'checkbutton') and 'h' not in prop: 784# prop['h'] = app.dlg_proc(id_dialog, app.DLG_CTL_PROP_GET, index=ctl_ind)['h'] 785 _os_scale( app.DLG_CTL_PROP_SET, prop, ctl_ind, -1, '') 786 app.dlg_proc(id_dialog, app.DLG_CTL_PROP_SET, prop, ctl_ind, -1, '') 787 else: 788 _os_scale( id_action, prop, index, index2, name) if scale_on_set else 0 789 res = app.dlg_proc(id_dialog, id_action, prop, index, index2, name) 790 pass; #log('res={}',({k:res[k] for k in res if k in ('x','y')})) if id_action==app.DLG_PROP_GET else 0 791 792 _os_scale(id_action, res, index, index2, name) if scale_on_get else 0 793 return res 794 #def dlg_proc_wpr 795 796LMBD_HIDE = lambda cid,ag:None 797class BaseDlgAgent: 798 """ 799 Simple helper to use dlg_proc(). See wiki.freepascal.org/CudaText_API#dlg_proc 800 801 Main features: 802 - All controls are created once then some of them can be changed via callbacks (attribute 'call'). 803 - The helper stores config version of form's and control's attributes. 804 So the helper can return two versions of attributes: configured and actual (live). 805 - Helper marshals complex data ('items' for type=list/combo/..., 'val' for type=memo) 806 - Helper can to save/restore form position and sizes to/from file "settings/forms data.json". 807 Key for saving is form's 'cap' (default) or a value in call BaseDlgAgent(..., option={'form data key':'smth'}) 808 If value of 'form data key' is empty then helper doesnt save/restore. 809 810 Format of constructor 811 BaseDlgAgent(ctrls, form=None, focused=None) 812 ctrls [(name,{...})] To create controls with the names 813 All attributes from 814 wiki.freepascal.org/CudaText_API#Control_properties 815 excluded: name, callback 816 form {...} To configure form 817 All attributes from 818 wiki.freepascal.org/CudaText_API#Form_properties 819 focused name_to_focus To set focus 820 821 Format of callback for a control 822 def callname(name_of_event_control, agent): 823 # Reactions 824 return None # To hide form 825 return {} # To keep controls and form state 826 return {'ctrls': [(name,{...})] # To change controls with the names 827 ,'form': {...} # To change form 828 ,'focused':name_to_focus # To set focus 829 } # Any key ('ctrls','form','focused') can be omitted. 830 Callback cannot add new controls or change type values. 831 832 Useful methods of agent 833 - agent.cattr(name, '??', live=T) To get a control actual/configured attribute 834 - agent.cattrs(name, ['??'], live=T) To get dict of a control listed actual/configured attributes 835 - agent.fattr('??', live=T) To get actual/configured form attribute 836 - agent.fattrs(live=T, ['??']=None) To get actual/configured all/listed form attributes 837 838 Tricks 839 Automatically set some values for attributes 840 - 'act':True If 'call' set in a control (not 'button') 841 - 'w_min':w If 'resize' set in the form 842 - 'h_min':h If 'resize' set in the form 843 844 Example. Dialog with two buttons. 845 BaseDlgAgent( 846 ctrls=[('b1', dict(type='button', cap='Click', x=0, y= 0, w=100, 847 call=lambda name,ag:{'ctrls':[('b1',{'cap':'OK', 'w':70})]} 848 )) 849 ,('b2', dict(type='button', cap='Close', x=0, y=30, w=100, 850 call=lambda name,ag:None) 851 )] 852 , form=dict(cap='Two buttons', x=0, y=0, w=100, h=60) 853 , focused='b1' 854 ).show() 855 """ 856 857 def activate(self): 858 """ Set focus to the form """ 859 app.dlg_proc(self.id_dlg, app.DLG_FOCUS) 860 #def activate 861 862 def hide(self): 863 app.dlg_proc(self.id_dlg, app.DLG_HIDE) 864 #def hide 865 866 def show(self, callbk_on_exit=None): 867 """ Show the form """ 868 ed_caller = ed 869 870# app.dlg_proc(self.id_dlg, app.DLG_SCALE) #?? 871# pass; pr_ = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, name='edch') 872# pass; log('exit,pr_={}',('edch', {k:v for k,v in pr_.items() if k in ('h','y')})) 873 app.dlg_proc(self.id_dlg, app.DLG_SHOW_MODAL) 874# pass; pr_ = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, name='edch') 875# pass; log('exit,pr_={}',('edch', {k:v for k,v in pr_.items() if k in ('h','y')})) 876 pass; #log('ok DLG_SHOW_MODAL',()) 877 878 BaseDlgAgent._form_acts('save', id_dlg=self.id_dlg, key4store=self.opts.get('form data key')) 879 if callbk_on_exit: callbk_on_exit(self) 880 dlg_proc_wpr(self.id_dlg, app.DLG_FREE) 881 882 ed_to_fcs = ed_caller \ 883 if 'on_exit_focus_to_ed' not in self.opts else \ 884 self.opts['on_exit_focus_to_ed'] 885 pass; #log('self.opts={}',(self.opts)) 886 pass; #log('ed_to_fcs.get_filename()={}',(ed_to_fcs.get_filename())) 887 if ed_to_fcs: 888 ed_to_fcs.focus() 889# pass; log('self.opts={}',(self.opts)) 890# self.opts['on_exit_focus_to_ed'].focus() 891# else: 892# ed_caller.focus() 893 #def show 894 895 def fattr(self, attr, live=True, defv=None): 896 """ Return one form property """ 897 attr= 'focused' if attr=='fid' else attr 898 pr = dlg_proc_wpr(self.id_dlg 899 , app.DLG_PROP_GET) if live else self.form 900 pass; #log('pr={}',(pr)) 901 rsp = pr.get(attr, defv) 902 if live and attr=='focused': 903 prf = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, index=rsp) 904 rsp = prf['name'] if prf else None 905 return rsp 906 #def fattr 907 908 def fattrs(self, live=True, attrs=None): 909 """ Return form properties """ 910 pr = dlg_proc_wpr(self.id_dlg 911 , app.DLG_PROP_GET) if live else self.form 912 return pr if not attrs else {attr:pr.get(attr) for attr in attrs} 913 #def fattrs 914 915 def cattr(self, name, attr, live=True, defv=None): 916 """ Return one the control property """ 917 live= False if attr in ('type',) else live # Unchangable 918 pr = dlg_proc_wpr(self.id_dlg 919 , app.DLG_CTL_PROP_GET 920 , name=name) if live else self.ctrls[name] 921 attr = REDUCTIONS.get(attr, attr) 922 if attr not in pr: return defv 923 rsp = pr[attr] 924 if not live: return rsp 925 if attr=='val': return self._take_val(name, rsp, defv) 926 return self._take_it_cl(name, attr, rsp, defv) 927# return self._take_val(name, rsp, defv) if attr=='val' and live else rsp 928 #def cattr 929 930 def cattrs(self, name, attrs=None, live=True): 931 """ Return the control properties """ 932 pr = dlg_proc_wpr(self.id_dlg 933 , app.DLG_CTL_PROP_GET 934 , name=name) if live else self.ctrls[name] 935 attrs = attrs if attrs else list(pr.keys()) 936 pass; #log('pr={}',(pr)) 937 rsp = {attr:pr.get(attr) for attr in attrs if attr not in ('val','on_change','callback')} 938 if 'val' in attrs: 939 rsp['val'] = self._take_val(name, pr.get('val')) if live else pr.get('val') 940 return rsp 941 #def cattrs 942 943 def chandle(self, name): 944 return app.dlg_proc(self.id_dlg, app.DLG_CTL_HANDLE, name=name) 945 946 def bind_do(self, names=None, gui2data=True): 947 names = names if names else self.binds.keys() 948 assert self.bindof 949 for name in names: 950 if name not in self.binds: continue 951 attr = 'val' 952 if gui2data: 953 self.bindof.__setattr__(self.binds[name], self.cattr(name, attr)) 954 else: 955 val = self.bindof.__getattr(self.binds[name]) 956 self.update({'ctrls':[(name, {attr:val})]}) 957 #def bind_do 958 959 def __init__(self, ctrls, form=None, focused=None, options=None): 960 # Fields 961 self.opts = options if options else {} 962 963 self.id_dlg = dlg_proc_wpr(0, app.DLG_CREATE) 964 self.ctrls = None # Conf-attrs of all controls by name (may be with 'val') 965 self.form = None # Conf-attrs of form 966# self.callof = self.opts.get('callof') # Object for callbacks 967 self.bindof = self.opts.get('bindof') # Object for bind control's values to object's fields 968 self.binds = {} # {name:'other obj field name'} 969 970 self._setup_base(ctrls, form, focused) 971 972 rtf = self.opts.get('gen_repro_to_file', False) 973 rtf_fn = rtf if isinstance(rtf, str) else 'repro_dlg_proc.py' 974 if rtf: self._gen_repro_code(tempfile.gettempdir()+os.sep+rtf_fn) 975 #def __init__ 976 977 def _setup_base(self, ctrls, form, focused=None): 978 """ Arrange and fill all: controls attrs, form attrs, focus. 979 Params 980 ctrls [(id, {})] 981 form {} 982 focused id 983 """ 984 #NOTE: DlgAg init 985 self.ctrls = odict(ctrls) 986 self.form = form.copy() if form else {} 987 988# if 'checks'=='checks': 989# if focused and focused not in self.ctrls: 990# raise Exception(f('Unknown focused: {}', focused)) 991 992 # Create controls 993 for name, cfg_ctrl in ctrls: 994 assert 'type' in cfg_ctrl 995 # Create control 996 cfg_ctrl.pop('callback', None) 997 cfg_ctrl.pop('on_change', None) 998 ind_c = dlg_proc_wpr(self.id_dlg 999 , DLG_CTL_ADD_SET 1000 , name=cfg_ctrl['type'] 1001 , prop=self._prepare_c_pr(name, cfg_ctrl)) 1002 pass; #cfg_ctrl['_idc'] = ind_c # While API bug: name isnot work if control is in panel 1003 pass; #log('ind_c,cfg_ctrl[type]={}',(ind_c,cfg_ctrl['type'])) 1004 #for cnt 1005 1006 if self.form: 1007 fpr = self.form 1008 if fpr.get('resize', False): 1009 fpr['w_min'] = fpr.get('w_min', fpr['w']) 1010 fpr['h_min'] = fpr.get('h_min', fpr['h']) 1011 fpr = BaseDlgAgent._form_acts('move', form=fpr # Move and (maybe) resize 1012 , key4store=self.opts.get('form data key')) 1013 fpr['topmost'] = app.app_api_version()<'1.0.270' or app.app_proc(app.PROC_WINDOW_TOPMOST_GET, '') 1014# fpr['topmost'] = True 1015 dlg_proc_wpr( self.id_dlg 1016 , app.DLG_PROP_SET 1017 , prop=fpr) 1018 1019 if focused in self.ctrls: 1020 self.form['focused'] = focused 1021 app.dlg_proc( self.id_dlg 1022 , app.DLG_CTL_FOCUS 1023 , name=focused) 1024 #def _setup_base 1025 1026 def _take_val(self, name, liv_val, defv=None): 1027 tp = self.ctrls[name]['type'] 1028 old_val = self.ctrls[name].get('val', defv) 1029 new_val = liv_val 1030 if False:pass 1031 elif tp=='memo': 1032 # For memo: "\t"-separated lines (in lines "\t" must be replaced to chr(2)) 1033 if isinstance(old_val, list): 1034 new_val = [v.replace(chr(2), '\t') for v in liv_val.split('\t')] 1035 #liv_val = '\t'.join([v.replace('\t', chr(2)) for v in old_val]) 1036 else: 1037 new_val = liv_val.replace('\t','\n').replace(chr(2), '\t') 1038 #liv_val = old_val.replace('\t', chr(2)).replace('\r\n','\n').replace('\r','\n').replace('\n','\t') 1039 elif tp=='checkgroup' and isinstance(old_val, list): 1040 # For checkgroup: ","-separated checks (values "0"/"1") 1041 new_val = liv_val.split(',') 1042 #in_val = ','.join(in_val) 1043 elif tp in ['checklistbox', 'checklistview'] and isinstance(old_val, tuple): 1044 new_val = liv_val.split(';') 1045 new_val = (new_val[0], new_val[1].split(',')) 1046 #liv_val = ';'.join(old_val[0], ','.join(old_val[1])) 1047 elif isinstance(old_val, bool): 1048 new_val = liv_val=='1' 1049 elif tp=='listview': 1050 new_val = -1 if liv_val=='' else int(liv_val) 1051 elif old_val is not None: 1052 new_val = type(old_val)(liv_val) 1053 return new_val 1054 #def _take_val 1055 1056 def _take_it_cl(self, name, attr, liv_val, defv=None): 1057 tp = self.ctrls[name]['type'] 1058 old_val = self.ctrls[name].get(attr, defv) 1059 pass; #log('name, attr, isinstance(old_val, str)={}',(name, attr, isinstance(old_val, str))) 1060 if isinstance(old_val, str): 1061 # No need parsing - config was by string 1062 return liv_val 1063 new_val = liv_val 1064 1065 if attr=='items': 1066 if tp in ['listview', 'checklistview']: 1067 # For listview, checklistview: "\t"-separated items. 1068 # first item is column headers: title1+"="+size1 + "\r" + title2+"="+size2 + "\r" +... 1069 # other items are data: cell1+"\r"+cell2+"\r"+... 1070 # ([(hd,wd)], [[cells],[cells],]) 1071 header_rows = new_val.split('\t') 1072 new_val =[[h.split('=') for h in header_rows[0].split('\r')] 1073 ,[r.split('\r') for r in header_rows[1:]] 1074 ] 1075 else: 1076 # For combo, combo_ro, listbox, checkgroup, radiogroup, checklistbox: "\t"-separated lines 1077 new_val = new_val.split('\t') 1078 1079 if attr=='columns': 1080 # For listview, checklistview: 1081 # "\t"-separated of 1082 # "\r"-separated 1083 # Name, Width, Min Width, Max Width, Alignment (str), Autosize('0'/'1'), Visible('0'/'1') 1084 # [{nm:str, wd:num, mi:num, ma:num, al:str, au:bool, vi:bool}] 1085 pass; #log('new_val={}',repr(new_val)) 1086 new_val= [ci.split('\r') for ci in new_val.split('\t')] 1087# new_val= [ci.split('\r')[:-1] for ci in new_val.split('\t')[:-1]] # API bug 1088 pass; #log('new_val={}',repr(new_val)) 1089 int_sc = lambda s: _os_scale('unscale', {'w':int(s)})['w'] 1090 new_val= [dict(nm= ci[0] 1091# ,wd=int( ci[1]) 1092# ,mi=int( ci[2]) 1093# ,ma=int( ci[3]) 1094 ,wd=int_sc(ci[1]) 1095 ,mi=int_sc(ci[2]) 1096 ,ma=int_sc(ci[3]) 1097 ,au='1'== ci[4] 1098 ,vi='1'== ci[5] 1099 ) for ci in new_val] 1100 pass; #log('new_val={}',(new_val)) 1101 1102 return new_val 1103 #def _take_it_cl 1104 1105 def _prepare_it_vl(self, c_pr, cfg_ctrl, opts={}): 1106 tp = cfg_ctrl['type'] 1107 1108 if 'val' in cfg_ctrl and opts.get('prepare val', True): 1109 in_val = cfg_ctrl['val'] 1110 if False:pass 1111 elif tp=='memo': 1112 # For memo: "\t"-separated lines (in lines "\t" must be replaced to chr(2)) 1113 if isinstance(in_val, list): 1114 in_val = '\t'.join([v.replace('\t', chr(2)) for v in in_val]) 1115 else: 1116 in_val = in_val.replace('\t', chr(2)).replace('\r\n','\n').replace('\r','\n').replace('\n','\t') 1117 elif tp=='checkgroup' and isinstance(in_val, list): 1118 # For checkgroup: ","-separated checks (values "0"/"1") 1119 in_val = ','.join(in_val) 1120 elif tp in ['checklistbox', 'checklistview'] and isinstance(in_val, tuple): 1121 # For checklistbox, checklistview: index+";"+checks 1122 in_val = ';'.join( (str(in_val[0]), ','.join( in_val[1]) ) ) 1123 c_pr['val'] = in_val 1124 1125 if 'items' in cfg_ctrl and opts.get('prepare items', True): 1126 items = cfg_ctrl['items'] 1127 if isinstance(items, str): 1128 pass 1129 elif tp in ['listview', 'checklistview']: 1130 # For listview, checklistview: "\t"-separated items. 1131 # first item is column headers: title1+"="+size1 + "\r" + title2+"="+size2 + "\r" +... 1132 # other items are data: cell1+"\r"+cell2+"\r"+... 1133 # ([(hd,wd)], [[cells],[cells],]) 1134 items = '\t'.join(['\r'.join(['='.join((hd,sz)) for hd,sz in items[0]])] 1135 +['\r'.join(row) for row in items[1]] 1136 ) 1137 pass; #log('items={}',repr(items)) 1138 else: 1139 # For combo, combo_ro, listbox, checkgroup, radiogroup, checklistbox: "\t"-separated lines 1140 items = '\t'.join(items) 1141 c_pr['items'] = items 1142 1143 if 'cols' in cfg_ctrl and opts.get('prepare cols', True): 1144 cols = cfg_ctrl['cols'] 1145 cfg_ctrl['columns'] = cols 1146 if isinstance(cols, str): 1147 pass 1148 else: 1149 # For listview, checklistview: 1150 # "\t"-separated of 1151 # "\r"-separated 1152 # Name, Width, Min Width, Max Width, Alignment (str), Autosize('0'/'1'), Visible('0'/'1') 1153 pass; #log('cols={}',(cols)) 1154 str_sc = lambda n: str(_os_scale('scale', {'w':n})['w']) 1155 cols = '\t'.join(['\r'.join([ cd[ 'nm'] 1156# ,str( cd[ 'wd'] ) 1157# ,str( cd.get('mi' ,0)) 1158# ,str( cd.get('ma' ,0)) 1159 ,str_sc(cd[ 'wd'] ) 1160 ,str_sc(cd.get('mi' ,0)) 1161 ,str_sc(cd.get('ma' ,0)) 1162 , cd.get('al','') 1163 ,'1' if cd.get('au',False) else '0' 1164 ,'1' if cd.get('vi',True) else '0' 1165 ]) 1166 for cd in cols] 1167 ) 1168 pass; #log('cols={}',repr(cols)) 1169 c_pr['columns'] = cols 1170 pass; #log('isinstance(cfg_ctrl[columns], str)={}',(isinstance(cfg_ctrl['columns'], str))) 1171 1172 return c_pr 1173 #def _prepare_it_vl 1174 1175 def _prepare_c_pr(self, name, cfg_ctrl, opts={}): 1176 pass; #log('name, cfg_ctrl={}',(name, cfg_ctrl)) 1177 c_pr = {k:v for (k,v) in cfg_ctrl.items() if k not in ['call', 'bind', 'items', 'val']} 1178 c_pr['name'] = name 1179 tp = cfg_ctrl['type'] 1180 1181 c_pr = self._prepare_it_vl(c_pr, cfg_ctrl, opts) 1182 1183 if cfg_ctrl.get('bind'): 1184 self.binds[name] = cfg_ctrl['bind'] 1185 1186 if callable(cfg_ctrl.get('call')) and opts.get('prepare call', True): 1187 if tp!='button': 1188 c_pr['act'] = True 1189 user_callbk = cfg_ctrl['call'] 1190 1191 def bda_c_callbk(idd, idc, data): 1192 pass; #log('idc,name={}',(idc,name)) 1193 upds = user_callbk(name, self) 1194 if upds is None: # To hide/close 1195 app.dlg_proc(self.id_dlg, app.DLG_HIDE) 1196 return 1197 elif not upds: # No changes 1198 return 1199 self.update( ctrls =odict(upds.get('ctrls', [])) 1200 ,form =upds.get('form', {}) 1201 ,focused=upds.get('focused',None)) 1202 #def bda_c_callbk 1203 on_what = 'on_select' \ 1204 if tp in ('listview', 'treeview') else \ 1205 'on_click' \ 1206 if tp in ('linklabel') else \ 1207 'on_change' 1208 c_pr[on_what] = bda_c_callbk 1209 1210 return c_pr 1211 #def _prepare_c_pr 1212 1213 def update(self, ctrls={}, form={}, focused=None): 1214 """ Change some attrs of form/controls """ 1215 pass; #log('',()) 1216 pass; #log('ctrls={}',(ctrls)) 1217 pass; #log('form={}',(form)) 1218 pass; #log('focused={}',(focused)) 1219 if form: 1220 self.form.update(form) 1221 pass; #log('form={}',(self.fattrs(live=F))) 1222 pass; #log('form={}',(self.fattrs())) 1223 pass; #log('form={}',(form)) 1224 dlg_proc_wpr( self.id_dlg 1225 , app.DLG_PROP_SET 1226 , prop=form) 1227 1228 for name, new_ctrl in ctrls.items(): 1229 pass; #log('name, new_ctrl={}',(name, new_ctrl)) 1230 1231 cfg_ctrl= self.ctrls[name] 1232 cfg_ctrl.update(new_ctrl) 1233 new_ctrl['type'] = cfg_ctrl['type'] 1234 c_prop =self._prepare_c_pr(name, new_ctrl, {'ctrls':ctrls}) 1235 pass; #log('c_prop={}',(c_prop)) if new_ctrl['type']=='listview' else None 1236 dlg_proc_wpr( self.id_dlg 1237 , app.DLG_CTL_PROP_SET 1238 , name=name 1239 , prop=c_prop 1240 ) 1241 1242 if focused in self.ctrls: 1243 self.form['focused'] = focused 1244 app.dlg_proc( self.id_dlg 1245 , app.DLG_CTL_FOCUS 1246 , name=focused) 1247 #def _update 1248 1249 def _gen_repro_code(self, rerpo_fn): 1250 # Repro-code 1251 l = '\n' 1252 cattrs = [ ('type', 'name', 'tag', 'act') 1253 ,('x', 'y', 'w', 'h', 'w_min', 'h_min', 'w_max', 'h_max', 'cap', 'hint', 'p') 1254 ,('en', 'vis', 'focused', 'tab_stop', 'tab_order' 1255 ,'props', 'ex0', 'ex1', 'ex2', 'ex3', 'ex4', 'ex5', 'ex6', 'ex7', 'ex8', 'ex9' 1256 ,'sp_l', 'sp_r', 'sp_t', 'sp_b', 'sp_a', 'a_l', 'a_r', 'a_t', 'a_b', 'align') 1257 ,('val', 'items', 'columns') 1258 ,('tp', 't', 'b', 'l', 'r', 'tid', 'a') 1259 ] 1260 fattrs = [ ('x', 'y', 'w', 'h', 'cap', 'tag') 1261 ,('resize', 'w_min', 'w_max', 'h_min', 'h_max') 1262 ,('vis', 'keypreview') 1263 ] 1264 def out_attrs(pr, attrs, out=''): 1265 pr = pr.copy() 1266 out += '{' 1267 afix = '' 1268 for ats in attrs: 1269 apr = {k:pr.pop(k) for k in ats if k in pr} 1270 if apr: 1271 out += afix + ', '.join(repr(k) + ':' + repr(apr[k]) for k in ats if k in apr) 1272 afix= '\n,' 1273 apr = {k:pr.pop(k) for k in pr.copy() if k[0:3]!='on_'} 1274 if apr: 1275 out += afix + repr(apr).strip('{}') 1276 for k in pr: 1277# pr = {k:(lambda idd,idc,data:print(repr(k))) for k in pr} 1278 out += afix + f('"{}":(lambda idd,idc,data:print("{}"))', k, k) 1279 out += '}' 1280 return out 1281 srp = '' 1282 srp += 'idd=dlg_proc(0, DLG_CREATE)' 1283 pass; #log('app.dlg_proc(self.id_dlg, app.DLG_CTL_COUNT)={}',(app.dlg_proc(self.id_dlg, app.DLG_CTL_COUNT))) 1284 for idC in range(app.dlg_proc(self.id_dlg, app.DLG_CTL_COUNT)): 1285 prC = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, index=idC) 1286 if ''==prC.get('hint', ''): prC.pop('hint', None) 1287 if ''==prC.get('tag', ''): prC.pop('tag', None) 1288 if ''==prC.get('cap', ''): prC.pop('cap', None) 1289 if ''==prC.get('items', None): prC.pop('items') 1290 if prC.get('tab_stop', None): prC.pop('tab_stop') 1291 if prC['type'] in ('label',): prC.pop('tab_stop', None) 1292 if prC['type'] in ('bevel',): (prC.pop('tab_stop', None) 1293 ,prC.pop('tab_order', None)) 1294 if prC['type'] not in ('listview' 1295 ,'checklistview'): prC.pop('columns', None) 1296 if prC['type'] in ('label' 1297 ,'bevel' 1298 ,'button'): prC.pop('val', None) 1299 if prC['type'] in ('button'): prC.pop('act', None) 1300 if not prC.get('act', False): prC.pop('act', None) 1301 if not prC.get('focused', False): prC.pop('focused', None) 1302 if prC.get('vis', True): prC.pop('vis', None) 1303 if prC.get('en', True): prC.pop('en', None) 1304 name = prC['name'] 1305 c_pr = self.ctrls[name].copy() 1306 c_pr = self._prepare_it_vl(c_pr, c_pr) 1307 prC.update({k:v for k,v in c_pr.items() if k not in ('callback','call')}) 1308 srp+=l+f('idc=dlg_proc(idd, DLG_CTL_ADD,"{}")', prC['type']) 1309 prC.pop('type', None) 1310 srp+=l+f('dlg_proc(idd, DLG_CTL_PROP_SET, index=idc, prop={})', out_attrs(prC, cattrs)) 1311 prD = dlg_proc_wpr(self.id_dlg, app.DLG_PROP_GET) 1312 prD.update(self.form) 1313 srp +=l+f('dlg_proc(idd, DLG_PROP_SET, prop={})', out_attrs(prD, fattrs)) 1314 srp +=l+f('dlg_proc(idd, DLG_CTL_FOCUS, name="{}")', prD['focused']) 1315 srp +=l+ 'dlg_proc(idd, DLG_SHOW_MODAL)' 1316 srp +=l+ 'dlg_proc(idd, DLG_FREE)' 1317 open(rerpo_fn, 'w', encoding='UTF-8').write(srp) 1318 pass; log(r'exec(open(r"{}", encoding="UTF-8").read())', rerpo_fn) 1319 #def _gen_repro_code 1320 1321 @staticmethod 1322 def _form_acts(act, form=None, id_dlg=None, key4store=None): 1323 """ Save/Restore pos of form """ 1324 pass; #log('act, form, id_dlg={}',(act, form, id_dlg)) 1325 CFG_JSON= app.app_path(app.APP_DIR_SETTINGS)+os.sep+'forms data.json' 1326 stores = json.loads(open(CFG_JSON).read(), object_pairs_hook=odict) \ 1327 if os.path.exists(CFG_JSON) and os.path.getsize(CFG_JSON) != 0 else \ 1328 odict() 1329 1330 def get_form_key(prs): 1331 fm_cap = prs['cap'] 1332 fm_cap = fm_cap[:fm_cap.rindex(' (')] if ' (' in fm_cap else fm_cap 1333 fm_cap = fm_cap[:fm_cap.rindex(' [')] if ' [' in fm_cap else fm_cap 1334 return fm_cap #if ' (' not in fm_cap else fm_cap[:fm_cap.rindex(' (')] 1335 1336 if False:pass 1337 1338 if act=='move' and form: 1339 fm_key = key4store if key4store else get_form_key(form) 1340 pass; #log('fm_key={}',(fm_key)) 1341 if fm_key not in stores: return form 1342 prev = stores[fm_key] 1343 if not form.get('resize', False): 1344 prev.pop('w', None) 1345 prev.pop('h', None) 1346 form.update(prev) 1347 pass; #log('!upd form={}',(form)) 1348 return form 1349 1350 if act=='save' and id_dlg: 1351 dlg_pr = dlg_proc_wpr(id_dlg, app.DLG_PROP_GET) 1352 fm_key = key4store if key4store else get_form_key(dlg_pr) 1353 pass; #log('{}={}', fm_key,{k:v for k,v in dlg_pr.items() if k in ('x','y','w','h')}) 1354 stores[fm_key] = {k:v for k,v in dlg_pr.items() if k in ('x','y','w','h')} 1355 open(CFG_JSON, 'w').write(json.dumps(stores, indent=4)) 1356 #def _form_acts 1357 1358 #class BaseDlgAgent 1359 1360ALI_CL = app.ALIGN_CLIENT 1361ALI_LF = app.ALIGN_LEFT 1362ALI_RT = app.ALIGN_RIGHT 1363ALI_TP = app.ALIGN_TOP 1364ALI_BT = app.ALIGN_BOTTOM 1365class DlgAgent(BaseDlgAgent): 1366 """ 1367 Helper to use dlg_proc(). See wiki.freepascal.org/CudaText_API#dlg_proc 1368 1369 Main base features : 1370 - All controls are created once then some of them can be changed via callbacks (attribute 'call'). 1371 - The helper stores config version of form's and control's attributes. 1372 So the helper can return two versions of attributes: configured and actual (live). 1373 - Helper marshals complex data ('items' for type=list/combo/..., 'val' for type=memo) 1374 - Helper can to save/restore form position and sizes to/from file "settings/forms data.json". 1375 1376 Main extra features: 1377 - Helper handles attributes names of controls 1378 Helper adds short synonyms: 1379 cid is name 1380 tp is type 1381 fid is focused 1382 Helper adds new attributes to simplify config: 1383 l,r,t,b,tid,a,aid 1384 are translated to live 1385 x,y,w,h,a_*,sp* 1386 - Helper allows to aligns texts from linear controls (by tid attribute) 1387 1388 Terms 1389 conf-attr - configured attribute (key and value passed to agent from plugin) 1390 live-attr - actual attribute (key and value taked from dlg_proc) 1391 1392 Rules 1393 1. All controls have conf-attr 'cid'|'name'. It must be unique. 1394 2. All controls have conf-attr 'tp'|'type'. 1395 Value of 'tp'|'type' can be any API values or shortened variants from REDUCTIONS. 1396 3. Control position can be set 1397 - directly by x,y,w,h 1398 - computed by enough subset of l,r,w,t,b,h (x=l, y=t, l+w=r, t+h=b) 1399 - computed by tid (refer to cid|name of control, from which to get t and add a bit to align texts) 1400 4. Controls values (attribute 'val') can be passed to agent as separate collection: 1401 - parameter 'vals' to call constructor 1402 - key 'vals' in dict to return from callback 1403 5. Need focused control cid can be passed to agent 1404 - parameter 'fid' to call constructor 1405 - key 'fid' in dict to return from callback 1406 - key 'fid' into parameter 'form' 1407 Live focused control cid can be asked as agent.fattr('fid') 1408 6. Anchors 'aid' and 'a' are used to plan control's position on form resize. 1409 Note: agent's anchors cannot set initial control position (except to center control). 1410 'aid' points to a tagret: form (empty value, default) or cid. 1411 'a' has string value which can include character: 1412 - | to center the control by horz/vert with the target 1413 t T to anchor top of the control to top/bottom of the target 1414 b B to anchor bottom of the control to top/bottom of the target 1415 l L to anchor left of the control to left/right of the target 1416 r R to anchor right of the control to left/right of the target 1417 Padding with target (live-attrs 'sp_*') are auto-calculated by initial positions of both elements. 1418 Examples 1419 a='|lR' 1420 7. Format of callback for a control 1421 def callname(name_of_event_control, agent): 1422 'smth' 1423 return None # To hide form 1424 return {} # To keep controls and form state 1425 return {'ctrls':[(nm,{...})] # To change controls with pointed names 1426 ,'vals':{nm:...} # To change controls 'val' with pointed names 1427 ,'form':{...} # To change form attributes 1428 ,'focused':name_to_focus # To change focus 1429 ,'fid':cid_to_focus # To change focus 1430 } # Any key ('ctrls','vals','form','fid','focused') can be omitted. 1431 Callback cannot add/del controls or change cid,type,a,aid 1432 Callback have to conside the form as it has initial size - agent will recalculate to actual state. 1433 Useful methods of agent 1434 - agent.cattr(cid, '??', live=T) To get a control actual/configured attribute 1435 - agent.cattrs(cid, ['??'], live=T) To get dict of a control listed actual/configured attributes 1436 - agent.cval(cid, live=T) To get actual/configured 'val' attribute (short of agent.cattr(cid, 'val')) 1437 - agent.cvals([cid], live=T) To get dict of actual/configured 'val' the listed attributes 1438 - agent.fattr('??', live=T) To get actual/configured form attribute 1439 - agent.fattrs(live=T, ['??']=None) To get actual/configured all/listed form attributes 1440 8. Match of different conf-attr and live-attr keys: 1441 cid name 1442 tp type 1443 l x 1444 r x+w 1445 t y 1446 b y+h 1447 tid y 1448 aid a_*[0] 1449 a a_*[1], sp* 1450 fid focused 1451 call callback 1452 9. List of same conf-attr and live-attr keys 1453 w h 1454 cap hint 1455 props 1456 color 1457 font_name font_size font_color font 1458 val 1459 act 1460 en vis 1461 tag 1462 tab_stop tab_order 1463 10. Tricks 1464 - 'tid' vertical aligns not a pair of controls themself but text in its. 1465 For this it uses platform specific data. The data can be configured by call dlg_valign_consts() 1466 - If cap of a label starts with '>' then the character is cut and 'prop' set to '1' to right alignment 1467 - Attributes 1468 def_bt spinedit url grid at_botttom brdW_fillC_fontC_brdC ro_mono_brd 1469 are used as temporary and readable version of 'prop'. 1470 They can be use to configure as others but key to ask is 'prop'. 1471 - Attribute 1472 sto tab_stop 1473 is used as temporary version of 'tab_stop'. 1474 1475 Example. Horz-resized modal dialog with two buttons. First button is stretched, second is pinned to right. 1476 DlgAgent( 1477 ctrls=[('b1', dict(type='button', cap='Click', x=0, y= 0, w=100, a='lR' 1478 call=lambda name,ag:{'ctrls':[('b1',{'cap':'OK', 'w':70})]} 1479 )) 1480 ,('b2', dict(type='button', cap='Close', x=0, y=30, w=100, a='LR' 1481 call=lambda name,ag:None) 1482 )] 1483 , form=dict(cap='Two buttons', x=0, y=0, w=100, h=60) 1484 , focused='b1' 1485 ).show() 1486 """ 1487 1488 def __init__(self, ctrls, vals=None, form=None, fid=None, focused=None, options=None): 1489 options = options or {} 1490 super().__init__([], options={k:v for k,v in options.items() if k not in ['gen_repro_to_file']}) 1491 # Inherited Fields 1492# self.opts 1493# self.id_dlg 1494# self.ctrls 1495# self.form 1496 self._setup(ctrls, vals, form, focused or fid) 1497 1498 rtf = options.get('gen_repro_to_file', False) 1499 rtf_fn = rtf if isinstance(rtf, str) else 'repro_dlg_proc.py' 1500 if rtf: self._gen_repro_code(tempfile.gettempdir()+os.sep+rtf_fn) 1501 #def __init__ 1502 1503 def cval(self, cid, live=True, defv=None): 1504 """ Return the control val property """ 1505 return self.cattr(cid, 'val', live=live, defv=defv) 1506 def cvals(self, cids, live=True): 1507 """ Return the controls val property """ 1508 return {cid:self.cattr(cid, 'val', live=live) for cid in cids} 1509 1510 def _setup(self, ctrls, vals=None, form=None, fid=None): 1511 """ Arrange and fill all: controls static/dynamic attrs, form attrs, focus. 1512 Params 1513 ctrls [{}] 1514 vals {cid:v} 1515 form {} 1516 fid str 1517 """ 1518 #NOTE: DlgAg init 1519 self.ctrls = odict(ctrls) if isinstance(ctrls, list) else ctrls 1520 self.form = form.copy() if form else {} 1521 1522 if 'checks'=='checks': 1523 no_tids = {cnt['tid'] for cnt in ctrls if 'tid' in cnt and cnt['tid'] not in self.ctrls} 1524 if no_tids: 1525 raise Exception(f('No cid for tid: {}', no_tids)) 1526 no_vids = {cid for cid in vals if cid not in self.ctrls} if vals else None 1527 if no_vids: 1528 raise Exception(f('No cid for val: {}', no_vids)) 1529# if fid and fid not in self.ctrls: 1530# raise Exception(f('No fid: {}', fid)) 1531 1532 if vals: 1533 for cid, val in vals.items(): 1534 self.ctrls[cid]['val'] = val 1535 1536 # Create controls 1537 for cid,cfg_ctrl in self.ctrls.items(): 1538 cfg_ctrl.pop('callback', None) 1539 cfg_ctrl.pop('on_change', None) 1540# cid = cfg_ctrl.get('cid', cfg_ctrl.get('name')) 1541# cfg_ctrl['cid'] = cid 1542# cfg_ctrl['name'] = cid 1543 assert 'type' in cfg_ctrl or 'tp' in cfg_ctrl 1544 tp = cfg_ctrl.get('tp', cfg_ctrl.get('type')) 1545 cfg_ctrl['tp'] = tp 1546 cfg_ctrl['type'] = REDUCTIONS.get(tp, tp) 1547 ind_c = dlg_proc_wpr(self.id_dlg 1548 , DLG_CTL_ADD_SET 1549 , name=cfg_ctrl['type'] 1550 , prop=self._prepare_c_pr(cid, cfg_ctrl)) 1551 pass; #cfg_ctrl['_idc'] = ind_c # While API bug: name isnot work if control is in panel 1552 #for cnt 1553 1554 # Resize callback 1555 if 'on_resize' in self.form: 1556 user_callbk = self.form['on_resize'] 1557 def da_rs_callbk(id_dlg, id_ctl=-1, data=''): 1558 upds = user_callbk(self) 1559 if upds: 1560 self._update_on_call(upds) 1561 self.form['on_resize'] = da_rs_callbk 1562 1563 # Resize on start 1564 fpr = self.form 1565 w0 = fpr['w'] 1566 h0 = fpr['h'] 1567 if fpr.get('resize', False): 1568 self._prepare_anchors() # a,aid -> a_*,sp_* 1569 fpr['w_min'] = fpr.get('w_min', fpr['w']) 1570 fpr['h_min'] = fpr.get('h_min', fpr['h']) 1571 pass; #log('fpr is self.form={}',(fpr is self.form)) 1572 fpr = BaseDlgAgent._form_acts('move', form=fpr) # Move and (maybe) resize 1573 pass; #log('fpr is self.form={}',(fpr is self.form)) 1574 if 'on_resize' in self.form and \ 1575 (fpr['w'] != w0 or \ 1576 fpr['h'] != h0): 1577 pass; #log('fpr[w],fpr[h],w0,h0={}',(fpr['w'], fpr['h'], w0,h0)) 1578 self.form['on_resize'](self) 1579 1580 fpr['topmost'] = app.app_api_version()<'1.0.270' or app.app_proc(app.PROC_WINDOW_TOPMOST_GET, '') 1581# fpr['topmost'] = True 1582 dlg_proc_wpr( self.id_dlg 1583 , app.DLG_PROP_SET 1584 , prop=fpr) # Upd live-attrs 1585 1586 fid = fid if fid in self.ctrls else self.form.get('fid') 1587 if fid in self.ctrls: 1588 self.form['fid'] = fid # Upd conf-attrs 1589 self.form['focused']= fid 1590 app.dlg_proc( self.id_dlg 1591 , app.DLG_CTL_FOCUS 1592 , name=fid) # Upd live-attrs 1593 1594 pass; self._gen_repro_code(tempfile.gettempdir()+os.sep+'repro_dlg_proc.py') if F else None 1595 #def _setup 1596 1597 EXTRA_C_ATTRS = ['tp','l','t','r','b','tid','a','aid'] 1598 def _prepare_c_pr(self, cid, cfg_ctrl, opts={}): 1599 pass; #log('cid, cfg_ctrl={}',(cid, cfg_ctrl)) 1600# cid = cfg_ctrl['cid'] 1601 tp = cfg_ctrl['type'] # reduced 1602 DlgAgent._preprocessor(cfg_ctrl, tp) # sto -> tab_stop, ... -> props 1603 c_pr = super()._prepare_c_pr(cid 1604 , {k:v for k,v in cfg_ctrl.items() if k not in DlgAgent.EXTRA_C_ATTRS} 1605 , {'prepare call':False}) # items -> items, val -> val 1606 c_pr.update(self._prep_pos_attrs(cfg_ctrl, cid, opts.get('ctrls'))) # l,r,t,b,tid -> x,y,w,h 1607 pass; #log('c_pr={}',(c_pr)) 1608 1609 def get_proxy_cb(u_callbk, event): 1610 def da_c_callbk(idd, idc, data): 1611 pass; #log('ev,idc,cid,data={}',(event,idc,cid,data)) 1612 if tp in ('listview',) and type(data) in (tuple, list): 1613 if not data[1]: return # Skip event "selection loss" 1614 # Crutch for Linux! Wait fix in core 1615 event_val = app.dlg_proc(idd, app.DLG_CTL_PROP_GET, index=idc)['val'] 1616 if event_val!=data[0]: 1617 app.dlg_proc(idd, app.DLG_CTL_PROP_SET, index=idc, prop={'val':data[0]}) 1618 pass; #log('?? u_callbk',()) 1619 upds = u_callbk(cid, self, data) 1620 pass; #log('ok u_callbk upds={}',(upds)) 1621 if upds is None: # To hide/close 1622 app.dlg_proc(self.id_dlg, app.DLG_HIDE) 1623 return 1624 elif not upds: # No changes 1625 return 1626 pass; #log('upds={}',(upds)) 1627 pass; #log('?? _update_on_call',()) 1628 self._update_on_call(upds) 1629 pass; #log('ok _update_on_call',()) 1630 #def da_c_callbk 1631 return da_c_callbk 1632 #def get_proxy_cb 1633 1634 for on_key in [k for k in cfg_ctrl if (k=='call' or k.startswith('on_')) and callable(cfg_ctrl.get(k))]: 1635 user_callbk = cfg_ctrl[on_key] 1636 on_what = on_key \ 1637 if on_key.startswith('on_') else \ 1638 'on_select' \ 1639 if tp in ('listview', 'treeview') else \ 1640 'on_click' \ 1641 if tp in ('linklabel') else \ 1642 'on_change' 1643 pass; #log('cid,tp,on_what={}',(cid,tp,on_what)) 1644 if tp!='button': 1645 c_pr['act'] = True 1646 c_pr[on_what] = get_proxy_cb(user_callbk, on_what) 1647# pass; log('?? on_what={}',on_what) 1648# pass; log(' c_pr[on_what]()={}',(c_pr[on_what](0,0,''))) 1649# pass; log('ok on_what={}',on_what) 1650 #for on_key 1651 1652 if callable(cfg_ctrl.get('menu')): 1653 c_pr['on_menu'] = lambda idd, idc, data: cfg_ctrl['menu'](cid, self) 1654 1655 pass; #log('c_pr={}',(c_pr)) if c_pr['type']=='checkbutton' else 0 1656 return c_pr 1657 #def _prepare_c_pr 1658 1659 def show_menu(self, cid, mn_content, where='+h'): 1660 """ cid Control to show menu near it 1661 mn_content [{cap:'', tag:'', en:T, ch:F, rd:F, cmd:(lambda ag, tag:''), sub:[]}] 1662 where Menu position 1663 '+h' - under the control 1664 '+w' - righter the control 1665 """ 1666 pr = self.cattrs(cid, ('x','y','w','h')) 1667 x, y = pr['x']+(pr['w'] if '+w' in where else 0) \ 1668 , pr['y']+(pr['h'] if '+h' in where else 0) 1669 pass; #log('x, y={}',(x, y)) 1670 prXY = _os_scale('scale', {'x':x, 'y':y}) 1671 x, y = prXY['x'], prXY['y'] 1672 pass; #log('x, y={}',(x, y)) 1673 x, y = app.dlg_proc(self.id_dlg, app.DLG_COORD_LOCAL_TO_SCREEN, index=x, index2=y) 1674 pass; #log('x, y={}',(x, y)) 1675 1676 def da_mn_callbk(it): 1677 pass; #log('it[tag]={}',(it['tag'])) 1678 u_callbk= it['cmd'] 1679 upds = u_callbk(self, it.get('tag', '')) 1680 if upds is None: # To hide/close 1681 app.dlg_proc(self.id_dlg, app.DLG_HIDE) 1682 return 1683 if not upds: return # No changes 1684 self._update_on_call(upds) 1685 #def da_mn_callbk 1686 1687 def fill_mn(mid_prn, its): 1688 for it in its: 1689 if it['cap']=='-': 1690 app.menu_proc( mid_prn, app.MENU_ADD, caption='-'); 1691 continue 1692 mid =(app.menu_proc(mid_prn, app.MENU_ADD, caption=it['cap'], command= lambda _it=it:da_mn_callbk(_it)) # _it=it solves lambda closure problem 1693 if 'cmd' in it else 1694 app.menu_proc(mid_prn, app.MENU_ADD, caption=it['cap']) 1695 ) 1696 if 'key' in it and it['key']: 1697 app.menu_proc( mid, app.MENU_SET_HOTKEY , command= it['key']) 1698 if 'en' in it: 1699 app.menu_proc( mid, app.MENU_SET_ENABLED , command=bool(it['en'])) 1700 if 'ch' in it: 1701 app.menu_proc( mid, app.MENU_SET_CHECKED , command=bool(it['ch'])) 1702 if 'rd' in it: 1703 app.menu_proc( mid, app.MENU_SET_RADIOITEM , command=bool(it['rd'])) 1704 if 'sub' in it: 1705 fill_mn(mid, it['sub']) 1706 #def fill_mn 1707 1708 mid_top = app.menu_proc( 0, app.MENU_CREATE) 1709 fill_mn(mid_top, mn_content) 1710 app.menu_proc( mid_top, app.MENU_SHOW , command=f('{},{}', x, y)) 1711 #def show_menu 1712 1713 def _update_on_call(self, upds): 1714 if isinstance(upds, tuple) or isinstance(upds, list) : # Allow to use list of upd data 1715 upds = deep_upd(upds) 1716 pass; #log('upds={}',(upds)) 1717 ctrls_u = odict(upds.get('ctrls', [])) 1718 pass; #log('ctrls_u={}',(ctrls_u)) 1719 vals = upds.get('vals', {}) 1720 form = upds.get('form', {}) 1721 fid = upds.get('fid' , upds.get('focused', form.get('fid', form.get('focused')))) 1722 if False:pass 1723 elif vals and not ctrls_u: 1724 ctrls_u = { cid_ : {'val':val} for cid_, val in vals.items()} 1725 elif vals and ctrls_u: 1726 for cid_, val in vals.items(): 1727 if cid_ not in ctrls_u: 1728 ctrls_u[cid_] = {'val':val} 1729 else: 1730 ctrls_u[cid_]['val'] = val 1731 for cid_, c in ctrls_u.items(): 1732 pass; #log('cid_, c={}',(cid_, c)) 1733 c.pop('callback', None) 1734 c.pop('on_change', None) 1735 c.pop('call', None) 1736 c['type'] = self.ctrls[cid_]['type'] 1737 super(DlgAgent,self).update( ctrls =ctrls_u 1738 ,form =form 1739 ,focused=fid) 1740 if fid in self.ctrls: 1741 self.form['fid'] = fid 1742 #def _update_on_call 1743 1744 def _prepare_anchors(self): 1745 """ Translate attrs 'a' 'aid' to 'a_*','sp_*' 1746 Values for 'a' are str-mask with signs 1747 'l' 'L' fixed distanse ctrl-left to trg-left or trg-right 1748 't' 'T' fixed distanse ctrl-top to trg-top or trg-bottom 1749 'r' 'R' fixed distanse ctrl-right to trg-left or trg-right 1750 'b' 'B' fixed distanse ctrl-bottom to trg-top or trg-bottom 1751 """ 1752 fm_w = self.form['w'] 1753 fm_h = self.form['h'] 1754 for cid,cnt in self.ctrls.items(): 1755 anc = cnt.get('a' , '') 1756 if not anc: continue 1757 aid = cnt.get('aid', cnt.get('p', '')) # '' anchor to form 1758 trg_w, \ 1759 trg_h = fm_w, fm_h 1760 if aid in self.ctrls: 1761 prTrg = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, name=aid) 1762 trg_w, \ 1763 trg_h = prTrg['w'], prTrg['h'] 1764 prOld = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, name=cid) 1765 pass; #prOld = prOld if prOld else \ 1766 #dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, index=self.ctrls[cid]['_idc']) # While API bug: name isnot work if control is in panel 1767 pass; logb=cid in ('tolx', 'tofi') 1768 pass; #nat_prOld=app.dlg_proc(self.id_dlg, app.DLG_CTL_PROP_GET, name=cid) 1769 pass; #log('cid,nat-prOld={}',(cid,{k:v for k,v in nat_prOld.items() if k in ('x','y','w','h','_ready_h')})) if logb else 0 1770 pass; #log('cid, prOld={}',(cid,{k:v for k,v in prOld.items() if k in ('x','y','w','h','_ready_h')})) if logb else 0 1771 pass; #log('cid,anc,trg_w,trg_h,prOld={}',(cid,anc,trg_w,trg_h, {k:v for k,v in prOld.items() if k in ('x','y','w','h')})) \ 1772 # if logb else 0 1773 prAnc = {} 1774 if '-' in anc: 1775 # Center by horz 1776 prAnc.update(dict( a_l=(aid, '-') 1777 ,a_r=(aid, '-'))) 1778 if 'L' in anc and 'R' in anc: 1779 # Both left/right to form right 1780 pass; #log('+L +R') if logb else 0 1781 prAnc.update(dict( a_l=None # (aid, ']'), sp_l=trg_w-prOld['x'] 1782 ,a_r=(aid, ']'), sp_r=trg_w-prOld['x']-prOld['w'])) 1783 if 'L' in anc and 'R' not in anc: 1784 # Left to form right 1785 pass; #log('+L -R') if logb else 0 1786 prAnc.update(dict( a_l=(aid, '['), sp_l=trg_w-prOld['x'] 1787 ,a_r=None)) 1788 if 'l' in anc and 'R' in anc: 1789 # Left to form left. Right to form right. 1790 pass; #log('+l +R') if logb else 0 1791 prAnc.update(dict( a_l=(aid, '['), sp_l= prOld['x'] 1792 ,a_r=(aid, ']'), sp_r=trg_w-prOld['x']-prOld['w'])) 1793 if '|' in anc: 1794 # Center by vert 1795 prAnc.update(dict( a_t=(aid, '-') 1796 ,a_b=(aid, '-'))) 1797 if 'T' in anc and 'B' in anc: 1798 # Both top/bottom to form bottom 1799 pass; #log('+T +B') if logb else 0 1800 prAnc.update(dict( a_t=None #, sp_t=trg_h-prOld['y'] # a_t=(aid, ']') - API bug 1801 ,a_b=(aid, ']'), sp_b=trg_h-prOld['y']-prOld['h'])) 1802 elif 'T' in anc and 'B' not in anc: 1803 # Top to form bottom 1804 pass; #log('+T -B') if logb else 0 1805 prAnc.update(dict( a_t=(aid, ']'), sp_t=trg_h-prOld['y'] # a_t=(aid, ']') - API bug 1806 ,a_b=None)) 1807 if 't' in anc and 'B' in anc: 1808 # Top to form top. Bottom to form bottom. 1809 pass; #log('+t +B') if logb else 0 1810 prAnc.update(dict( a_t=(aid, '['), sp_t= prOld['y'] 1811 ,a_b=(aid, ']'), sp_b=trg_h-prOld['y']-prOld['h'])) 1812 if prAnc: 1813 pass; #log('aid,prAnc={}',(cid, prAnc)) if logb else 0 1814 cnt.update(prAnc) 1815 dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_SET, name=cid, prop=prAnc) 1816# pass; pr_ = dlg_proc_wpr(self.id_dlg, app.DLG_CTL_PROP_GET, name=cid) 1817# pass; log('cid,pr_={}',(cid, {k:v for k,v in pr_.items() if k in ('h','y', 'sp_t', 'sp_b', 'a_t', 'a_b')})) 1818 #def _prepare_anchors 1819 1820 def _prep_pos_attrs(self, cnt, cid, ctrls4t=None): 1821 # Position: 1822 # t[op] or tid, l[eft] required 1823 # w[idth] >>> r[ight ]=l+w 1824 # h[eight] >>> b[ottom]=t+h 1825 # b dont need for buttons, edit, labels 1826# if not [k for k in cnt.keys() if k in ('l','t','r','b','tid')]: 1827# return {k:v for (k,v) in cnt.items() if k in ('x','y','w','h')} 1828 1829 pass; #log('cid, cnt={}',(cid, cnt)) 1830 prP = {} 1831 1832 if 'h' not in cnt \ 1833 and cnt['type'] in ( 'button', 'checkbutton' 1834 , 'label' 1835 , 'combo', 'combo_ro' 1836 , 'edit', 'spinedit' 1837 , 'check', 'radio' 1838 , 'filter_listbox', 'filter_listview' 1839 ): 1840 # OS specific control height 1841 cnt['h'] = get_gui_height(cnt['type']) 1842 prP['_ready_h'] = True 1843# cnt['h'] = app.app_proc(app.PROC_GET_GUI_HEIGHT, cnt['type']) 1844 1845 if 'l' in cnt: 1846 prP['x'] = cnt['l'] 1847 if 'r' in cnt and 'x' in prP: 1848 prP['w'] = cnt['r'] - prP['x'] 1849 if 'w' in cnt: 1850 prP['w'] = cnt['w'] 1851 1852 if 't' in cnt: 1853 prP['y'] = cnt['t'] 1854# t = cnt.get('t', 0) if 't' in cnt else self.cattr(cid, 't', live=False) 1855 elif 'tid' in cnt: 1856 ctrls4t = ctrls4t if ctrls4t else self.ctrls 1857 assert cnt['tid'] in ctrls4t 1858 # cid for horz-align text 1859 bs_cnt4t= ctrls4t[ cnt['tid']] 1860 bs_cnt = self.ctrls[cnt['tid']] 1861 bs_tp = bs_cnt['tp'] 1862 bs_tp = REDUCTIONS.get(bs_tp, bs_tp) 1863 tp = self.ctrls[cid]['tp'] 1864 tp = REDUCTIONS.get(tp, tp) 1865 pass; #log('tp, bs_tp, fit, bs_cnt={}',(tp, bs_tp, fit_top_by_env(tp, bs_tp), bs_cnt)) 1866 t = bs_cnt4t['t'] + fit_top_by_env(tp, bs_tp) 1867 prP['y'] = t 1868 if 'b' in cnt and 'y' in prP: 1869 prP['h'] = cnt['b'] - prP['y'] 1870 if 'h' in cnt: 1871 prP['h'] = cnt['h'] 1872 1873# b = cnt.get('b', t+cnt.get('h', 0)) 1874 1875# l = cnt['l'] if 'l' in cnt else self.cattr(cid, 'l', live=False) 1876# r = cnt.get('r', l+cnt.get('w', 0)) 1877# prP = dict(x=l, y=t, w=r-l) 1878# prP.update(dict(h=cnt.get('h'))) if 0!=cnt.get('h', 0) else 0 1879 pass; #log('cid, prP={}',(cid, prP)) 1880 return prP 1881 #def _prep_pos_attrs 1882 1883 @staticmethod 1884 def _preprocessor(cnt, tp): 1885 if 'ali' in cnt: 1886 cnt['align'] = cnt.pop('ali') 1887 if 'sp_lr' in cnt: 1888 cnt['sp_l'] = cnt['sp_r'] = cnt.pop('sp_lr') 1889 if 'sp_lrt' in cnt: 1890 cnt['sp_l'] = cnt['sp_r'] = cnt['sp_t'] = cnt.pop('sp_lrt') 1891 if 'sp_lrb' in cnt: 1892 cnt['sp_l'] = cnt['sp_r'] = cnt['sp_b'] = cnt.pop('sp_lrb') 1893 1894 if 'sto' in cnt: 1895 cnt['tab_stop'] = cnt.pop('sto') 1896 if 'tor' in cnt: 1897 cnt['tab_order'] = cnt.pop('tor') 1898 1899 if 'props' in cnt: 1900 pass 1901 elif tp=='label' and 'cap' in cnt and cnt['cap'][0]=='>': 1902 # cap='>smth' --> cap='smth', props='1' (r-align) 1903 cnt['cap'] = cnt['cap'][1:] 1904 cnt['props']= '1' 1905 elif tp=='label' and cnt.get('ralign'): 1906 cnt['props']= cnt.pop('ralign') 1907 elif tp=='button' and cnt.get('def_bt') in ('1', True): 1908 cnt['props']= '1' 1909 elif tp=='spinedit' and cnt.get('min_max_inc'): 1910 cnt['props']= cnt.pop('min_max_inc') 1911 elif tp=='linklabel' and cnt.get('url'): 1912 cnt['props']= cnt.pop('url') 1913 elif tp=='listview' and cnt.get('grid'): 1914 cnt['props']= cnt.pop('grid') 1915 elif tp=='tabs' and cnt.get('at_botttom'): 1916 cnt['props']= cnt.pop('at_botttom') 1917 elif tp=='colorpanel' and cnt.get('brdW_fillC_fontC_brdC'): 1918 cnt['props']= cnt.pop('brdW_fillC_fontC_brdC') 1919 elif tp in ('edit', 'memo') and cnt.get('ro_mono_brd'): 1920 cnt['props']= cnt.pop('ro_mono_brd') 1921 1922 if 'props' in cnt and app.app_api_version()>='1.0.224': 1923 # Convert props to ex0..ex9 1924 # See 'Prop "ex"' at wiki.freepascal.org/CudaText_API 1925 lsPr = cnt.pop('props').split(',') 1926 if False:pass 1927 elif tp=='button': 1928 cnt['ex0'] = '1'==lsPr[0] #bool: default for Enter key 1929 elif tp in ('edit', 'memo'): 1930 cnt['ex0'] = '1'==lsPr[0] #bool: read-only 1931 cnt['ex1'] = '1'==lsPr[1] #bool: font is monospaced 1932 cnt['ex2'] = '1'==lsPr[2] #bool: show border 1933 elif tp=='spinedit': 1934 cnt['ex0'] = int(lsPr[0]) #int: min value 1935 cnt['ex1'] = int(lsPr[1]) #int: max value 1936 cnt['ex2'] = int(lsPr[2]) #int: increment 1937 elif tp=='label': 1938 cnt['ex0'] = '1'==lsPr[0] #bool: right aligned 1939 elif tp=='linklabel': 1940 cnt['ex0'] = lsPr[0] #str: URL. Should not have ','. Clicking on http:/mailto: URLs should work, result of clicking on other kinds depends on OS. 1941 elif tp=='listview': 1942 cnt['ex0'] = '1'==lsPr[0] #bool: show grid lines 1943 elif tp=='tabs': 1944 cnt['ex0'] = '1'==lsPr[0] #bool: show tabs at bottom 1945 elif tp=='colorpanel': 1946 cnt['ex0'] = int(lsPr[0]) #int: border width (from 0) 1947 cnt['ex1'] = int(lsPr[1]) #int: color of fill 1948 cnt['ex2'] = int(lsPr[2]) #int: color of font 1949 cnt['ex3'] = int(lsPr[3]) #int: color of border 1950 elif tp=='filter_listview': 1951 cnt['ex0'] = '1'==lsPr[0] #bool: filter works for all columns 1952 elif tp=='image': 1953 cnt['ex0'] = '1'==lsPr[0] #bool: center picture 1954 cnt['ex1'] = '1'==lsPr[1] #bool: stretch picture 1955 cnt['ex2'] = '1'==lsPr[2] #bool: allow stretch in 1956 cnt['ex3'] = '1'==lsPr[3] #bool: allow stretch out 1957 cnt['ex4'] = '1'==lsPr[4] #bool: keep origin x, when big picture clipped 1958 cnt['ex5'] = '1'==lsPr[5] #bool: keep origin y, when big picture clipped 1959 elif tp=='trackbar': 1960 cnt['ex0'] = int(lsPr[0]) #int: orientation (0: horz, 1: vert) 1961 cnt['ex1'] = int(lsPr[1]) #int: min value 1962 cnt['ex2'] = int(lsPr[2]) #int: max value 1963 cnt['ex3'] = int(lsPr[3]) #int: line size 1964 cnt['ex4'] = int(lsPr[4]) #int: page size 1965 cnt['ex5'] = '1'==lsPr[5] #bool: reversed 1966 cnt['ex6'] = int(lsPr[6]) #int: tick marks position (0: bottom-right, 1: top-left, 2: both) 1967 cnt['ex7'] = int(lsPr[7]) #int: tick style (0: none, 1: auto, 2: manual) 1968 elif tp=='progressbar': 1969 cnt['ex0'] = int(lsPr[0]) #int: orientation (0: horz, 1: vert, 2: right-to-left, 3: top-down) 1970 cnt['ex1'] = int(lsPr[1]) #int: min value 1971 cnt['ex2'] = int(lsPr[2]) #int: max value 1972 cnt['ex3'] = '1'==lsPr[3] #bool: smooth bar 1973 cnt['ex4'] = int(lsPr[4]) #int: step 1974 cnt['ex5'] = int(lsPr[5]) #int: style (0: normal, 1: marquee) 1975 cnt['ex6'] = '1'==lsPr[6] #bool: show text (only for some OSes) 1976 elif tp=='progressbar_ex': 1977 cnt['ex0'] = int(lsPr[0]) #int: style (0: text only, 1: horz bar, 2: vert bar, 3: pie, 4: needle, 5: half-pie) 1978 cnt['ex1'] = int(lsPr[1]) #int: min value 1979 cnt['ex2'] = int(lsPr[2]) #int: max value 1980 cnt['ex3'] = '1'==lsPr[3] #bool: show text 1981 cnt['ex4'] = int(lsPr[4]) #int: color of background 1982 cnt['ex5'] = int(lsPr[5]) #int: color of foreground 1983 cnt['ex6'] = int(lsPr[6]) #int: color of border 1984 elif tp=='bevel': 1985 cnt['ex0'] = int(lsPr[0]) #int: shape (0: sunken panel, 1: 4 separate lines - use it as border for group of controls, 2: top line, 3: bottom line, 4: left line, 5: right line, 6: no lines, empty space) 1986 elif tp=='splitter': 1987 cnt['ex0'] = '1'==lsPr[0] #bool: beveled style 1988 cnt['ex1'] = '1'==lsPr[1] #bool: instant repainting 1989 cnt['ex2'] = '1'==lsPr[2] #bool: auto snap to edge 1990 cnt['ex3'] = int(lsPr[3]) #int: min size 1991 #def _preprocessor 1992 1993#class DlgAgent 1994 1995 1996###################################### 1997#NOTE: dlg_valign_consts 1998###################################### 1999def dlg_valign_consts(): 2000 pass; #log('ok') 2001 rsp = False 2002 UP,DN = '↑↑','↓↓' 2003 DLG_W, \ 2004 DLG_H = 335, 310 2005 ctrls = ['check' 2006 ,'edit' 2007 ,'button' 2008 ,'combo_ro' 2009 ,'combo' 2010 ,'checkbutton' 2011 ,'linklabel' 2012 ,'spinedit' 2013 ,'radio' 2014 ] 2015 ctrls_sp= [('_sp'+str(1+ic),nc) for ic, nc in enumerate(ctrls)] 2016 fits = {sp:fit_top_by_env(nc) for sp, nc in ctrls_sp} 2017 hints = {sp:nc+': '+str(fits[sp]) for sp, nc in ctrls_sp} 2018 2019 def save(): 2020 nonlocal rsp 2021 scam = app.app_proc(app.PROC_GET_KEYSTATE, '') if app.app_api_version()>='1.0.143' else '' 2022 if not scam:#aid_m=='save': 2023 for sp, nc in ctrls_sp: 2024 fit = fits[sp] 2025 if fit==fit_top_by_env(nc): continue#for ic, nc 2026 apx.set_opt('dlg_wrapper_fit_va_for_'+nc, fit) 2027 #for ic, nc 2028 fit_top_by_env__clear() 2029 rsp = True 2030 return None#break#while 2031 2032 if scam=='c':#aid_m=='c/save': # Report 2033 rpt = 'env:'+get_desktop_environment() 2034 rpt+= c13 + c13.join(hints.values()) 2035 aid_r, *_t = dlg_wrapper(_('Report'), 230,310, 2036 [dict(cid='rprt',tp='me' ,t=5 ,l=5 ,h=200 ,w=220) 2037 ,dict( tp='lb' ,t=215 ,l=5 ,w=220 ,cap=_('Send the report to the address')) 2038 ,dict(cid='mail',tp='ed' ,t=235 ,l=5 ,w=220) 2039 ,dict( tp='lb' ,t=265 ,l=5 ,w=150 ,cap=_('or post it on')) 2040 ,dict(cid='gith',tp='ln-lb' ,t=265 ,l=155 ,w=70 ,cap='GitHub',props='https://github.com/kvichans/cuda_fit_v_alignments/issues') 2041 ,dict(cid='-' ,tp='bt' ,t=280 ,l=205-80 ,w=80 ,cap=_('Close')) 2042 ], dict(rprt=rpt 2043 ,mail='kvichans@mail.ru') 2044 , focus_cid='rprt') 2045 return {} 2046 #def save 2047 2048 def up_dn(ag, cid, sht): 2049 pass; #log('cid,sht={}',(cid,sht)) 2050 sign = cid[-1] 2051 sp = '_sp'+sign 2052 fits[sp]= fits[sp] + sht 2053 nonlocal hints 2054 hints = {sp:nc+': '+str(fits[sp]) for sp, nc in ctrls_sp} 2055 return {'ctrls':[(cid ,dict(y=ag.cattr(cid, 'y')+sht ,hint=hints[sp] ))]} 2056 #def up_dn 2057 2058 cs = ctrls 2059 cnts = \ 2060 [('lb1' ,dict(tp='lb' ,t= 10 ,l= 5 ,w=100 ,cap=cs[0]+' ===============' )) 2061 ,('ch1' ,dict(tp='ch' ,t= 10+fits['_sp1'] ,l=115 ,w=100 ,cap='=================',hint=hints['_sp1'] ,val=F)) 2062 ,('up1' ,dict(tp='bt' ,t= 10-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'ch1',-1) )) 2063 ,('dn1' ,dict(tp='bt' ,t= 10-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'ch1', 1) )) 2064 2065 ,('lb2' ,dict(tp='lb' ,t= 40 ,l= 5 ,w=100 ,cap=cs[1]+' ===============' )) 2066 ,('ed2' ,dict(tp='ed' ,t= 40+fits['_sp2'] ,l=115 ,w=100 ,hint=hints['_sp2'] ,val='=================')) 2067 ,('up2' ,dict(tp='bt' ,t= 40-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'ed2',-1) )) 2068 ,('dn2' ,dict(tp='bt' ,t= 40-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'ed2', 1) )) 2069 2070 ,('lb3' ,dict(tp='lb' ,t= 70 ,l= 5 ,w=100 ,cap=cs[2]+' ===============' )) 2071 ,('bt3' ,dict(tp='bt' ,t= 70+fits['_sp3'] ,l=115 ,w=100 ,cap='=================',hint=hints['_sp3'] )) 2072 ,('up3' ,dict(tp='bt' ,t= 70-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'bt3',-1) )) 2073 ,('dn3' ,dict(tp='bt' ,t= 70-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'bt3', 1) )) 2074 2075 ,('lb4' ,dict(tp='lb' ,t=100 ,l= 5 ,w=100 ,cap=cs[3]+' ===============' )) 2076 ,('cbo4',dict(tp='cb-ro' ,t=100+fits['_sp4'] ,l=115 ,w=100 ,items=['============='],hint=hints['_sp4'] ,val=0)) 2077 ,('up4' ,dict(tp='bt' ,t=100-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'cbo4',-1))) 2078 ,('dn4' ,dict(tp='bt' ,t=100-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'cbo4', 1))) 2079 2080 ,('lb5' ,dict(tp='lb' ,t=130 ,l= 5 ,w=100 ,cap=cs[4]+' ===============' )) 2081 ,('cb5' ,dict(tp='cb' ,t=130+fits['_sp5'] ,l=115 ,w=100 ,items=['============='],hint=hints['_sp5'] ,val='=============')) 2082 ,('up5' ,dict(tp='bt' ,t=130-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'cb5',-1) )) 2083 ,('dn5' ,dict(tp='bt' ,t=130-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'cb5', 1) )) 2084 2085 ,('lb6' ,dict(tp='lb' ,t=160 ,l= 5 ,w=100 ,cap=cs[5]+' ===============' )) 2086 ,('chb6',dict(tp='ch-bt' ,t=160+fits['_sp6'] ,l=115 ,w=100 ,cap='===========' ,hint=hints['_sp6'] ,val=0)) 2087 ,('up6' ,dict(tp='bt' ,t=160-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'chb6',-1))) 2088 ,('dn6' ,dict(tp='bt' ,t=160-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'chb6', 1))) 2089 2090 ,('lb7', dict(tp='lb' ,t=190 ,l= 5 ,w=100 ,cap=cs[6]+' ===============' )) 2091 ,('lnb7',dict(tp='ln-lb' ,t=190+fits['_sp7'] ,l=115 ,w=100 ,cap='=================',props=hints['_sp7'] )) 2092 ,('up7' ,dict(tp='bt' ,t=190-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'lnb7',-1))) 2093 ,('dn7' ,dict(tp='bt' ,t=190-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'lnb7', 1))) 2094 2095 ,('lb8' ,dict(tp='lb' ,t=220 ,l= 5 ,w=100 ,cap=cs[7]+' 4444444444444444' )) 2096 ,('sp8' ,dict(tp='sp-ed' ,t=220+fits['_sp8'] ,l=115 ,w=100 ,props='0,444444444,1' ,hint=hints['_sp8'] ,val=444444444)) 2097 ,('up8' ,dict(tp='bt' ,t=220-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'sp8',-1) )) 2098 ,('dn8' ,dict(tp='bt' ,t=220-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'sp8', 1) )) 2099 2100 ,('lb9' ,dict(tp='lb' ,t=250 ,l= 5 ,w=100 ,cap=cs[8]+' ===============' )) 2101 ,('rd9' ,dict(tp='rd' ,t=250+fits['_sp9'] ,l=115 ,w=100 ,cap='=================',hint=hints['_sp9'] ,val=F)) 2102 ,('up9' ,dict(tp='bt' ,t=250-3 ,l=230 ,w=50 ,cap=UP ,call=lambda cid,ag,d: up_dn(ag,'rd9',-1) )) 2103 ,('dn9' ,dict(tp='bt' ,t=250-3 ,l=280 ,w=50 ,cap=DN ,call=lambda cid,ag,d: up_dn(ag,'rd9', 1) )) 2104 2105 ,('save',dict(tp='bt' ,t=DLG_H-30 ,l=115 ,w=100 ,cap=_('&Save') ,call=lambda cid,ag,d: save() 2106 ,hint=_('Apply the fittings to controls of all dialogs.' 2107 '\rCtrl+Click - Show data to mail report.'))) 2108 ,('-' ,dict(tp='bt' ,t=DLG_H-30 ,l=230 ,w=100 ,cap=_('Cancel') ,call=(lambda cid,ag,d: None) )) 2109 ] 2110 agent = DlgAgent( form=dict(cap=_('Adjust vertical alignments'), w=DLG_W, h=DLG_H) 2111 ,ctrls=cnts ,fid = '-' 2112 #,options={'gen_repro_to_file':'repro_dlg_valign_consts.py'} 2113 ).show() #NOTE: dlg_valign 2114 return rsp 2115 #def dlg_valign_consts 2116 2117def get_hotkeys_desc(cmd_id, ext_id=None, keys_js=None, def_ans=''): 2118 """ Read one or two hotkeys for command 2119 cmd_id [+ext_id] 2120 from 2121 settings\keys.json 2122 Return 2123 def_ans If no hotkeys for the command 2124 'Ctrl+Q' 2125 'Ctrl+Q * Ctrl+W' If one hotkey for the command 2126 'Ctrl+Q/Ctrl+T' 2127 'Ctrl+Q * Ctrl+W/Ctrl+T' If two hotkeys for the command 2128 """ 2129 if keys_js is None: 2130 keys_json = app.app_path(app.APP_DIR_SETTINGS)+os.sep+'keys.json' 2131 keys_js = apx._json_loads(open(keys_json).read()) if os.path.exists(keys_json) else {} 2132 2133 cmd_id = f('{},{}', cmd_id, ext_id) if ext_id else cmd_id 2134 if cmd_id not in keys_js: 2135 return def_ans 2136 cmd_keys= keys_js[cmd_id] 2137 desc = '/'.join([' * '.join(cmd_keys.get('s1', [])) 2138 ,' * '.join(cmd_keys.get('s2', [])) 2139 ]).strip('/') 2140 return desc 2141 #def get_hotkeys_desc 2142 2143###################################### 2144#NOTE: plugins history 2145###################################### 2146PLING_HISTORY_JSON = app.app_path(app.APP_DIR_SETTINGS)+os.sep+'plugin history.json' 2147def get_hist(key_or_path, default=None, module_name='_auto_detect', to_file=PLING_HISTORY_JSON): 2148 """ Read from "plugin history.json" one value by string key or path (list of keys). 2149 Parameters 2150 key_or_path Key(s) to navigate in json tree 2151 Type: str or [str] 2152 default Value to return if no suitable node in json tree 2153 module_name Start node to navigate. 2154 If it is '_auto_detect' then name of caller module is used. 2155 If it is None then it is skipped. 2156 to_file Name of file to read. APP_DIR_SETTING will be joined if no full path. 2157 2158 Return Found value or default 2159 2160 Examples (caller module is 'plg') 2161 1. If no "plugin history.json" 2162 get_hist('k') returns None 2163 get_hist(['p', 'k'], 0) returns 0 2164 2. If "plugin history.json" contains 2165 {"k":1, "plg":{"k":2, "p":{"m":3}, "t":[0,1]}, "q":{"n":4}} 2166 get_hist('k', 0, None) returns 1 2167 get_hist('k', 0) returns 0 2168 get_hist('k', 0, 'plg') returns 2 2169 get_hist('k', 0, 'oth') returns 0 2170 get_hist(['p','m'], 0) returns 3 2171 get_hist(['p','t'], []) returns [0,1] 2172 get_hist('q', 0, None) returns {'n':4} 2173 get_hist(['q','n'], 0, None) returns 4 2174 """ 2175 to_file = to_file if os.sep in to_file else app.app_path(app.APP_DIR_SETTINGS)+os.sep+to_file 2176 if not os.path.exists(to_file): 2177 pass; #log('not exists',()) 2178 return default 2179 data = None 2180 try: 2181 data = json.loads(open(to_file).read()) 2182 except: 2183 pass; log('not load: {}',sys.exc_info()) 2184 return default 2185 if module_name=='_auto_detect': 2186 # commented is for Py3.5+, fails on Py3.4 2187 # caller_globals = inspect.stack()[1].frame.f_globals 2188 caller_globals = inspect.stack()[1][0].f_globals 2189 module_name = inspect.getmodulename(caller_globals['__file__']) \ 2190 if '__file__' in caller_globals else None 2191 keys = [key_or_path] if type(key_or_path)==str else key_or_path 2192 keys = keys if module_name is None else [module_name]+keys 2193 parents,\ 2194 key = keys[:-1], keys[-1] 2195 for parent in parents: 2196 data= data.get(parent) 2197 if type(data)!=dict: 2198 pass; log('not dict parent={}',(parent)) 2199 return default 2200 return data.get(key, default) 2201 #def get_hist 2202 2203def set_hist(key_or_path, value, module_name='_auto_detect', kill=False, to_file=PLING_HISTORY_JSON): 2204 """ Write to "plugin history.json" one value by key or path (list of keys). 2205 If any of node doesnot exist it will be added. 2206 Or remove (if kill) one key+value pair (if suitable key exists). 2207 Parameters 2208 key_or_path Key(s) to navigate in json tree 2209 Type: str or [str] 2210 value Value to set if suitable item in json tree exists 2211 module_name Start node to navigate. 2212 If it is '_auto_detect' then name of caller module is used. 2213 If it is None then it is skipped. 2214 kill Need to remove node in tree. 2215 if kill==True parm value is ignored 2216 to_file Name of file to write. APP_DIR_SETTING will be joined if no full path. 2217 2218 Return value (param) if !kill and modification is successful 2219 value (killed) if kill and modification is successful 2220 None if kill and no path in tree (no changes) 2221 KeyError if !kill and path has problem 2222 Return value 2223 2224 Examples (caller module is 'plg') 2225 1. If no "plugin history.json" it will become 2226 set_hist('k',0,None) {"k":0} 2227 set_hist('k',1) {"plg":{"k":1}} 2228 set_hist('k',1,'plg') {"plg":{"k":1}} 2229 set_hist('k',1,'oth') {"oth":{"k":1}} 2230 set_hist('k',[1,2]) {"plg":{"k":[1,2]}} 2231 set_hist(['p','k'], 1) {"plg":{"p":{"k":1}}} 2232 2233 2. If "plugin history.json" contains {"plg":{"k":1, "p":{"m":2}}} 2234 it will contain 2235 set_hist('k',0,None) {"plg":{"k":1, "p":{"m":2}},"k":0} 2236 set_hist('k',0) {"plg":{"k":0, "p":{"m":2}}} 2237 set_hist('k',0,'plg') {"plg":{"k":0, "p":{"m":2}}} 2238 set_hist('n',3) {"plg":{"k":1, "p":{"m":2}, "n":3}} 2239 set_hist(['p','m'], 4) {"plg":{"k":1, "p":{"m":4}}} 2240 set_hist('p',{'m':4}) {"plg":{"k":1, "p":{"m":4}}} 2241 set_hist(['p','m','k'], 1) KeyError (old m is not branch node) 2242 2243 3. If "plugin history.json" contains {"plg":{"k":1, "p":{"m":2}}} 2244 it will contain 2245 set_hist('k', kill=True) {"plg":{ "p":{"m":2}}} 2246 set_hist('p', kill=True) {"plg":{"k":1}} 2247 set_hist(['p','m'], kill=True) {"plg":{"k":1, "p":{}}} 2248 set_hist('n', kill=True) {"plg":{"k":1, "p":{"m":2}}} (nothing to kill) 2249 """ 2250 to_file = to_file if os.sep in to_file else app.app_path(app.APP_DIR_SETTINGS)+os.sep+to_file 2251 body = json.loads(open(to_file).read(), object_pairs_hook=odict) \ 2252 if os.path.exists(to_file) and os.path.getsize(to_file) != 0 else \ 2253 odict() 2254 2255 if module_name=='_auto_detect': 2256 # commented is for Py3.5+, fails of Py3.4 2257 # caller_globals = inspect.stack()[1].frame.f_globals 2258 caller_globals = inspect.stack()[1][0].f_globals 2259 module_name = inspect.getmodulename(caller_globals['__file__']) \ 2260 if '__file__' in caller_globals else None 2261 keys = [key_or_path] if type(key_or_path)==str else key_or_path 2262 keys = keys if module_name is None else [module_name]+keys 2263 parents,\ 2264 key = keys[:-1], keys[-1] 2265 data = body 2266 for parent in parents: 2267 if kill and parent not in data: 2268 return None 2269 data= data.setdefault(parent, odict()) 2270 if type(data)!=odict: 2271 raise KeyError() 2272 if kill: 2273 if key not in data: 2274 return None 2275 value = data.pop(key) 2276 else: 2277 data[key] = value 2278 open(to_file, 'w').write(json.dumps(body, indent=2)) 2279 return value 2280 #def set_hist 2281###################################### 2282###################################### 2283 2284def get_translation(plug_file): 2285 ''' Part of i18n. 2286 Full i18n-cycle: 2287 1. All GUI-string in code are used in form 2288 _('') 2289 2. These string are extracted from code to 2290 lang/messages.pot 2291 with run 2292 python.exe <python-root>\Tools\i18n\pygettext.py -p lang <plugin>.py 2293 3. Poedit (or same program) create 2294 <module>\lang\ru_RU\LC_MESSAGES\<module>.po 2295 from (cmd "Update from POT") 2296 lang/messages.pot 2297 It allows to translate all "strings" 2298 It creates (cmd "Save") 2299 <module>\lang\ru_RU\LC_MESSAGES\<module>.mo 2300 4. <module>.mo can be placed also in dir 2301 CudaText\data\langpy\ru_RU\LC_MESSAGES\<module>.mo 2302 The dir is used first. 2303 5. get_translation uses the file to realize 2304 _('') 2305 ''' 2306 lng = app.app_proc(app.PROC_GET_LANG, '') 2307 plug_dir= os.path.dirname(plug_file) 2308 plug_mod= os.path.basename(plug_dir) 2309 lng_dirs= [ 2310 app.app_path(app.APP_DIR_DATA) +os.sep+'langpy', 2311 plug_dir +os.sep+'lang', 2312 ] 2313 _ = lambda x: x 2314 pass; #return _ 2315 for lng_dir in lng_dirs: 2316 lng_mo = lng_dir+'/{}/LC_MESSAGES/{}.mo'.format(lng, plug_mod) 2317 if os.path.isfile(lng_mo): 2318 t = gettext.translation(plug_mod, lng_dir, languages=[lng]) 2319 _ = t.gettext 2320 t.install() 2321 break 2322 return _ 2323 #def get_translation 2324 2325_ = get_translation(__file__) # I18N 2326 2327 2328###################################### 2329#NOTE: misc 2330###################################### 2331def upd_dict(d1, d2): 2332 rsp = d1.copy() 2333 rsp.update(d2) 2334 return rsp 2335 #def upd_dict 2336 2337def deep_upd(dcts): 2338 pass; #log('dcts={}',(dcts)) 2339 if not dcts: 2340 return dcts 2341 if isinstance(dcts, dict): 2342 return dcts 2343 2344 dct1, *dcts = dcts 2345 pass; #log('dct1, dcts={}',(dct1, dcts)) 2346 rsp = dct1.copy() 2347 for dct in dcts: 2348 for k,v in dct.items(): 2349 if False:pass 2350 elif k not in rsp: 2351 rsp[k] = v 2352 elif isinstance(rsp[k], dict) and isinstance(v, dict): 2353 rsp[k].update(v) 2354 else: 2355 rsp[k] = v 2356 pass; #log('rsp={}',(rsp)) 2357 return rsp 2358 #def deep_upd 2359 2360def isint(what): return isinstance(what, int) 2361 2362def ed_of_file_open(op_file): 2363 if not app.file_open(op_file): 2364 return None 2365 for h in app.ed_handles(): 2366 op_ed = app.Editor(h) 2367 if op_ed.get_filename() and os.path.samefile(op_file, op_ed.get_filename()): 2368 return op_ed 2369 return None 2370 #def ed_of_file_open 2371 2372if __name__ == '__main__' : # Tests 2373 class C: 2374 def m1(self, p): 2375 print('m1',p) 2376 def m2(self, p): 2377 print('m2',p) 2378 2379 c = C() 2380# print('c.m1',dir(c.m1)) 2381# print('c',dir(c)) 2382# print('c.m1.__self__',dir(c.m1.__self__)) 2383 2384 c.m1('0') 2385 rm = c.m1 2386 rm('0') 2387 rm(c, '0') 2388 2389# DlgAgent( 2390# ctrls=[('b1', dict(type='button', cap='Click', x=0, y= 0, w=100, a='lR', 2391# call=lambda name,ag:{'ctrls':[('b1',{'cap':'OK', 'w':70})]} ##!! new w <> a 2392# )) 2393# ,('b2', dict(type='button', cap='Close', x=0, y=30, w=100, a='LR', 2394# call=lambda name,ag:None) 2395# )] 2396# , form=dict(cap='Two buttons', x=0, y=0, w=100, h=60, h_max=60, resize=True) 2397# , focused='b1' 2398# ).show() 2399 2400# BaseDlgAgent( 2401# ctrls=[('b1', dict(type='button', cap='Click', x=0, y= 0, w=100, 2402# call=lambda name,ag:{'ctrls':[('b1',{'cap':'OK', 'w':70})]})) 2403# ,('b2', dict(type='button', cap='Close', x=0, y=30, w=100, 2404# call=lambda name,ag:None))] 2405# , form=dict(cap='Two buttons', x=0, y=0, w=100, h=60) 2406# , focused='b1' 2407# ).show() 2408 2409# pass 2410# def test_ask_number(ask, def_val): 2411# cnts=[dict( tp='lb',tid='v',l=3 ,w=70,cap=ask) 2412# ,dict(cid='v',tp='ed',t=3 ,l=73,w=70) 2413# ,dict(cid='!',tp='bt',t=45 ,l=3 ,w=70,cap='OK',props='1') 2414# ,dict(cid='-',tp='bt',t=45 ,l=73,w=70,cap='Cancel')] 2415# vals={'v':def_val} 2416# while True: 2417# btn,vals,fid,chds=dlg_wrapper('Example',146,75,cnts,vals,'v') 2418# if btn is None or btn=='-': return def_val 2419# if not re.match(r'\d+$', vals['v']): continue 2420# return vals['v'] 2421# ask_number('ask_____________', '____smth') 2422 2423''' 2424ToDo 2425[ ][kv-kv][14may18] Remove keys dlg_wrapper_fit_va_for_* if it's same as def 2426''' 2427