pyngcgui.py
1 #!/usr/bin/env python 2 3 # Notes: 4 # 1) ini file items: 5 # NGCGUI_PREAMBLE 6 # NGCGUI_SUBFILE 7 # NGCGUI_POSTAMBLE 8 # NGCGUI_OPTIONS 9 # nonew disallow new tabs 10 # noremove disallow removal of tabs 11 # noauto don't automatically send result file 12 # noexpand (ngcgui used, not supported pyngcgui) 13 # nom2 (no m2 terminator (use %)) 14 # 2) To make pyngcgui embedded fit in small screen: 15 # Try: 16 # max_parms=10|20|30 (will reject otherwise valid subfiles) 17 # image_width=240 18 # reduce subroutine parm name lengths and/or comment string length 19 20 #------------------------------------------------------------------------------ 21 # Copyright: 2013-6 22 # Author: Dewey Garrett <dgarrett@panix.com> 23 # 24 # This program is free software; you can redistribute it and/or modify 25 # it under the terms of the GNU General Public License as published by 26 # the Free Software Foundation; either version 2 of the License, or 27 # (at your option) any later version. 28 # 29 # This program is distributed in the hope that it will be useful, 30 # but WITHOUT ANY WARRANTY; without even the implied warranty of 31 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 # GNU General Public License for more details. 33 # 34 # You should have received a copy of the GNU General Public License 35 # along with this program; if not, write to the Free Software 36 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 37 #------------------------------------------------------------------------------ 38 """ python classes to implement an ngcgui-like application 39 40 These ini file items are compatible with ngcgui.tcl: 41 [DISPLAY]NGCGUI_PREAMBLE single specifier 42 [DISPLAY]NGCGUI_POSTAMBLE single specifier 43 [DISPLAY]NGCGUI_SUBFILE multiples allowed, use "" for Custom tab 44 [DISPLAY]NGCGUI_OPTIONS 45 noremove disallow tabpage removal 46 nonew disallow tabpage creation 47 noiframe don't show image in tabpage 48 noauto don't automatically send result file 49 [DISPLAY]PROGRAM_PREFIX subroutine path: start 50 [RS274NGC]SUBROUTINE_PATH subroutine path: middle 51 [WIZARD]WIZARD_ROOT subroutine path: end 52 [DISPLAY]NGCGUI_FONT not used 53 [DISPLAY]TKPKG not applicable 54 """ 55 from types import * # IntType etc 56 import os 57 import sys 58 import re 59 import gtk 60 import getopt 61 import datetime 62 import subprocess 63 import linuxcnc 64 import hashlib 65 import gobject 66 import glob 67 import shutil 68 import popupkeyboard 69 import exceptions # for debug printing 70 import traceback # for debug printing 71 import hal # notused except for debug 72 from gladevcp import hal_actions 73 74 g_ui_dir = linuxcnc.SHARE + "/linuxcnc" 75 76 # determine if glade interface designer is running 77 # in order to prevent connection of most signals 78 g_is_glade = False 79 if ( ('glade' in sys.argv[0]) 80 and ('gladevcp' not in sys.argv[0])): 81 for d in os.environ['PATH'].split(':'): 82 f = os.path.join(d,sys.argv[0]) 83 if ( os.path.isfile(f) 84 and os.access(f, os.X_OK)): 85 g_is_glade = True 86 break 87 g_alive = not g_is_glade 88 89 import gettext 90 LOCALEDIR = linuxcnc.SHARE + "/locale" 91 gettext.install("linuxcnc", localedir=LOCALEDIR, unicode=True) 92 93 try: 94 import pygtk 95 pygtk.require('2.0') 96 except ImportError,msg: 97 print('import pygtk failed: %s',msg) 98 pass 99 #------------------------------------------------------------------------------ 100 g_debug = False 101 g_verbose = False 102 g_nom2 = False # True for no m2 terminator (use %) 103 g_strict = False # enforce additional subfile formatting requirements 104 g_tmode = 0 # for development 105 g_entry_height = 20 # default parm entry height 106 # (override for popupkeyboard) 107 g_big_height = 35 # increased parm entry height value 108 109 g_image_width = 320 # image size 110 g_image_height = 240 # image size 111 112 g_check_interval = 2 # periodic check (seconds) 113 g_label_id = 0 # subroutine labels modifier when expanding in place 114 g_progname = os.path.splitext(os.path.basename(__file__))[0] 115 g_dtfmt = "%y%m%d:%H.%M.%S" 116 117 g_stat = None # linuxcnc.stat object 118 g_popkbd = None # PopupKeyboard object 119 g_candidate_files = None # CandidateFiles object 120 g_send_function = None # function object f(fname) return True for success 121 g_tab_controls_loc ='top' # 'top' | 'bottom' 122 123 g_keyboardfile = os.path.join(g_ui_dir,'popupkeyboard.ui') 124 125 g_control_font = None 126 g_font_users = [] 127 g_auto_file_ct = 1 128 129 INTERP_SUB_PARAMS = 30 # (1-based) conform to: 130 # src/emc/rs274ngc/interp_internal.hh:#define INTERP_SUB_PARAMS 30 131 g_max_parm = INTERP_SUB_PARAMS 132 g_max_msg_len = 500 # limit popup msg len for errant gcmc input 133 134 g_gcmc_exe = None 135 g_gcmc_funcname = 'tmpgcmc' 136 g_gcmc_id = 0 137 138 black_color = gtk.gdk.color_parse('black') 139 white_color = gtk.gdk.color_parse('white') 140 error_color = gtk.gdk.color_parse('red') 141 green_color = gtk.gdk.color_parse('green') 142 blue_color = gtk.gdk.color_parse('blue') 143 yellow_color = gtk.gdk.color_parse('yellow') 144 purple_color = gtk.gdk.color_parse('purple') 145 feature_color = gtk.gdk.color_parse('lightslategray') 146 147 label_normal_color = gtk.gdk.color_parse('lightsteelblue2') 148 label_active_color = gtk.gdk.color_parse('ivory2') 149 base_entry_color = gtk.gdk.color_parse('azure1') 150 fg_created_color = gtk.gdk.color_parse('palegreen') 151 fg_multiple_color = gtk.gdk.color_parse('cyan') 152 fg_normal_color = black_color 153 154 bg_dvalue_color = gtk.gdk.color_parse('darkseagreen2') 155 #------------------------------------------------------------------------------ 156 157 def exception_show(ename,detail,src=''): 158 print('\n%s:' % src ) 159 print('Exception: %s' % ename ) 160 print(' detail: %s' % detail ) 161 if type(detail) == exceptions.ValueError: 162 for x in detail: 163 if type(x) in (StringType, UnicodeType): 164 print('detail(s):',x) 165 else: 166 for y in x: 167 print('detail(d):',y,) 168 elif type(detail) == StringType: 169 print('detail(s):',detail) 170 elif type(detail) == ListType: 171 for x in detail: 172 print('detail(l):',x) 173 else: 174 print(ename,detail) 175 176 if g_debug: 177 #print(sys.exc_info()) 178 print( traceback.format_exc()) 179 180 def save_a_copy(fname,archive_dir='/tmp/old_ngc'): 181 if fname is None: 182 return 183 try: 184 if not os.path.exists(archive_dir): 185 os.mkdir(archive_dir) 186 shutil.copyfile(fname 187 ,os.path.join(archive_dir,dt() + '_' + os.path.basename(fname))) 188 except IOError,msg: 189 print(_('save_a_copy: IOError copying file to %s') % archive_dir) 190 print(msg) 191 except Exception, detail: 192 exception_show(Exception,detail,src='save_a_copy') 193 print(traceback.format_exc()) 194 sys.exit(1) 195 196 def get_linuxcnc_ini_file(): 197 ps = subprocess.Popen('ps -C linuxcncsvr --no-header -o args'.split(), 198 stdout=subprocess.PIPE 199 ) 200 p,e = ps.communicate() 201 202 if ps.returncode: 203 print(_('get_linuxcnc_ini_file: stdout= %s') % p) 204 print(_('get_linuxcnc_ini_file: stderr= %s') % e) 205 return None 206 207 ans = p.split()[p.split().index('-ini')+1] 208 return ans 209 210 def dummy_send(filename): 211 return False # always fail 212 213 def default_send(filename): 214 import gladevcp.hal_filechooser 215 try: 216 s = linuxcnc.stat().poll() 217 except: 218 user_message(mtype=gtk.MESSAGE_ERROR 219 ,title=_('linuxcnc not running') 220 ,msg = _('cannot send, linuxcnc not running')) 221 return False 222 try: 223 fchooser = gladevcp.hal_filechooser.EMC_Action_Open() 224 fchooser._hal_init() 225 fchooser._load_file(filename) 226 return True 227 except: 228 return False 229 230 def send_to_axis(filename): # return True for success 231 # NB: file with errors may hang in axis gui 232 s = subprocess.Popen(['axis-remote',filename] 233 ,stdout=subprocess.PIPE 234 ,stderr=subprocess.PIPE 235 ) 236 p,e = s.communicate() 237 if s.returncode: 238 print(_('%s:send_to_axis: stdout= %s') % (g_progname,p)) 239 print(_('%s:send_to_axis: stderr= %s') % (g_progname,e)) 240 return False 241 if p: print(_('%s:send_to_axis: stdout= %s') % (g_progname,p)) 242 if e: print(_('%s:send_to_axis: stderr= %s') % (g_progname,e)) 243 return True 244 245 def file_save(fname,title_message='Save File'): 246 start_dir = os.path.dirname(fname) 247 if start_dir == '': start_dir = os.path.curdir 248 fc = gtk.FileChooserDialog(title=title_message 249 ,parent=None 250 ,action=gtk.FILE_CHOOSER_ACTION_SAVE 251 ,buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL 252 ,gtk.STOCK_OK, gtk.RESPONSE_OK 253 ) 254 ,backend=None 255 ) 256 fc.set_current_folder(start_dir) 257 fc.set_do_overwrite_confirmation(True) 258 filter = gtk.FileFilter() 259 filter.set_name('NGC files') 260 filter.add_pattern('*.ngc') 261 filter.add_pattern('*.NGC') 262 filter.add_pattern('*.nc') 263 filter.add_pattern('*.NC') 264 filter.add_pattern('*.gcmc') 265 filter.add_pattern('*.GCMC') 266 fc.add_filter(filter) 267 fc.set_current_name(os.path.basename(fname)) # suggest name (for save) 268 fname = None 269 ans = fc.run() 270 if ans == gtk.RESPONSE_OK: 271 fname = fc.get_filename() 272 elif ans == gtk.RESPONSE_CANCEL: 273 print(_('file_save:canceled')) 274 elif ans == gtk.RESPONSE_DELETE_EVENT: # window close 275 print(_('file_save:window closed')) 276 else: 277 raise IOError,_('file_save:unexpected') 278 fc.destroy() 279 return(fname) 280 281 def is_comment(s): 282 if s[0] == ';': return bool(1) # ;xxx 283 elif s[0] == '(': 284 if s[-2] == ')': return bool(1) # (yyy) 285 else: return bool(1) # (yyy)zzz maybe bogus 286 return bool(0) 287 288 def get_info_item(line): 289 # expect line as unaltered line with whitespace 290 l = line.translate(None,' \t').lower() 291 r = re.search(r'^\(info:(.*)\)',l) 292 if r: 293 r = re.search(r'.*info:(.*)\)',line) 294 if r: return r.group(1) 295 return None 296 297 def check_sub_start(s): 298 r = re.search(r'^o<(.*)>sub.*',s) 299 if r: 300 #print('check_sub_start:g0:',r.group(0)) 301 #print('check_sub_start:g1:',r.group(1)) 302 return r.group(1) 303 return None 304 305 def check_sub_end(s): 306 r = re.search(r'^o<(.*)>endsub.*',s) 307 if r: 308 #print('check_sub_end:g0:',r.group(0)) 309 #print('check_sub_end:g1:',r.group(1)) 310 return r.group(1) 311 return None 312 313 def check_for_label(s): 314 r = re.search(r'^o<(.*?)> *(sub|endsub).*',s) 315 if r: 316 return 'ignoreme' # do not include on expand 317 318 r = re.search(r'^o<(.*?)> *(call).*',s) 319 if r: 320 return None # do not mod label on expand 321 322 r = re.search(r'^o<(.*?)>.*',s) 323 if r: 324 return r.group(1) # make label unique on expand 325 326 return None 327 328 def check_positional_parm_range(s,min,max): 329 r = re.search(r'#([0-9]+)',s) 330 if r: pnum = int(r.group(1)) 331 # here check is against system limit; g_max_parm applied elsewhere 332 if r and (pnum <= INTERP_SUB_PARAMS): 333 if pnum < min: min = pnum 334 if pnum > max: max = pnum 335 return pnum,min,max 336 return None,None,None 337 338 def find_positional_parms(s): 339 # requires original line (mixed case with whitespace) 340 # find special association lines for positional parameters 341 342 # The '*', '+', and '?' qualifiers are all greedy. 343 # Greedy <.*> matches all of <H1>title</H1> 344 # NonGreedy <.*?> matches the only first <H1> 345 346 # case1 #<parmname>=#n (=defaultvalue comment_text) 347 # case2 #<parmname>=#n (=defaultvalue) 348 # case3 #<parmname>=#n (comment_text) 349 # case4 #<parmname>=#n 350 351 name = None 352 pnum = None 353 dvalue = None 354 comment = None 355 s = s.expandtabs() # tabs to spaces 356 357 r = re.search( 358 r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\(= *([0-9.+-]+[0-9.]*?) *(.*)\)' 359 ,s,re.I) 360 #case1 1name 2pnum 3dvalue 4comment 361 362 if r is None: r=re.search( 363 r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\( *([0-9.+-]+)\)',s,re.I) 364 #case2 1name 2pnum 3dvalue 365 366 if r is None: r=re.search( 367 r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\((.*)\)',s,re.I) 368 #case3 1name 2pnum 3comment 369 370 if r is None: r=re.search( 371 r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *$',s,re.I) 372 #case4 1name 2pnum 373 374 # if r: 375 # for i in range(0,1+len(r.groups())): 376 # print('PARSE groups',len(r.groups()),i,r.group(i)) 377 378 if r: 379 n = len(r.groups()) 380 if r and n >= 2: 381 name = comment = r.group(1) # use name as comment if not specified 382 pnum = int(r.group(2)) 383 # here check is against system limit; g_max_parm applied elsewhere 384 if pnum > INTERP_SUB_PARAMS: 385 return None,None,None,None 386 if n == 3: 387 if r.group(3)[0] == '=': dvalue = r.group(3)[1:] 388 else: comment = r.group(3)[:] 389 if n == 4: 390 dvalue = r.group(3) 391 if dvalue.find('.') >= 0: 392 dvalue = float(dvalue) 393 else: 394 dvalue = int(dvalue) 395 if r.group(4): comment = r.group(4) 396 if n > 4: 397 print('find_positional_parametrs unexpected n>4',s,) 398 comment = r.group(4) 399 if comment is None: 400 print('find_positional_parameters:NOCOMMENT') # can't happen 401 comment = '' 402 return name,pnum,dvalue,comment 403 404 def user_message(title="" 405 ,mtype=gtk.MESSAGE_INFO 406 ,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 407 ,msg=None): 408 if msg is None: return(None) 409 if type(msg) == ListType: 410 txt = "".join(msg) 411 else: 412 txt = msg 413 414 vprint('USER_MESSAGE:\n%s' % txt) 415 popup = gtk.MessageDialog(parent = None 416 ,flags=flags 417 ,type=mtype 418 ,buttons = gtk.BUTTONS_OK 419 ,message_format = txt 420 ) 421 popup.set_title(title) 422 result = popup.run() 423 popup.destroy() 424 return(result) 425 426 def dt(): 427 return(datetime.datetime.now().strftime(g_dtfmt)) 428 429 def md5sum(fname): 430 if not fname: return None 431 return(hashlib.md5(open(fname, 'r').read()).hexdigest()) 432 433 def find_image(fname): 434 found = False 435 for suffix in ('png','gif','jpg','pgm'): 436 name = os.path.splitext(os.path.basename(fname))[0] 437 dir = os.path.dirname(fname) 438 ifile = os.path.join(dir,name + '.' + suffix) 439 if os.path.isfile(ifile): 440 found = True 441 break 442 if not found: return None 443 return ifile 444 445 def sized_image(ifile): 446 twidth = g_image_width 447 theight = g_image_height 448 img = gtk.Image() 449 img.set_from_file(ifile) 450 451 pixbuf = img.get_pixbuf() 452 iwidth = pixbuf.get_width() # image size 453 iheight = pixbuf.get_height() 454 scale = min(float(twidth)/iwidth, float(theight)/iheight) 455 #print('iw,ih %d,%d tw,th=%d,%d, scale=%f' % ( 456 # iwidth,iheight,twidth,theight,scale)) 457 new_width = int(scale*iwidth) 458 new_height = int(scale*iheight) 459 pixbuf = pixbuf.scale_simple(new_width,new_height 460 ,gtk.gdk.INTERP_BILINEAR) 461 img.set_from_pixbuf(pixbuf) 462 return(img) 463 464 def show_dir(x,tag=''): 465 l = [] 466 for name in sorted(dir(x)): 467 if name[0:2] == '__': continue 468 item = getattr(x,name) 469 ty = type(item) 470 if ty == MethodType: 471 l.append('%-8s %s()' % ('0 Meth',name)) 472 elif ty == ListType: 473 i = 0 474 for v in item: 475 try: 476 vnonewline = v[:-1] if v.endswith('\n') else v 477 l.append('%-8s %s[%2s] = %s' % ('2 List',name,i,vnonewline)) 478 i += 1 479 except: 480 l.append('xxx %s %s' % (name,str(item))) 481 elif ty == DictionaryType: 482 for k in sorted(item): 483 l.append('%-8s %s[%2s] = %s' % ('3 Dict',name,k,item[k])) 484 elif ty == BooleanType: 485 l.append('%-8s %s = %s' % ('4 Bool',name,str(item))) 486 elif ty == IntType: 487 l.append('%-8s %s = %s' % ('5 Int',name,str(item))) 488 elif ty == FloatType: 489 l.append('%-8s %s = %s' % ('6 Float',name,str(item))) 490 elif ty == StringType: 491 l.append('%-8s %s = %s' % ('7 Str',name,item)) 492 else: 493 s = str(item).split(' ')[0] + '>' 494 s=item 495 l.append('%-8s %s = %s' % ('1 Obj',name,s)) 496 497 print('\n') 498 print('%s----------------------------------------------------------' % tag) 499 for i in sorted(l): 500 print(i) 501 print('%s==========================================================' % tag) 502 503 def dprint(txt): 504 if g_debug: 505 print(':' + txt) 506 507 def vprint(txt): 508 if g_verbose: 509 print('::' + txt) 510 511 def spath_from_inifile(fname): 512 if not fname: 513 return [] 514 ini = linuxcnc.ini(fname) 515 homedir = os.path.dirname(os.path.realpath(fname)) 516 # http://www.linuxcnc.org/docs/devel/html/config/ini_config.html 517 l = [] 518 p = ini.find('DISPLAY','PROGRAM_PREFIX') 519 if p: 520 l = [p] 521 p = ini.find('RS274NGC','SUBROUTINE_PATH') 522 if p: 523 newdirs = p.split(':') 524 for dir in newdirs: 525 # dont add duplicates 526 if dir not in l: 527 l.append(dir) 528 p = ini.find('WIZARD','WIZARD_ROOT') 529 if p: 530 l.extend(p.split(':')) 531 lfull = [] 532 for d in l: 533 d = os.path.expanduser(d) 534 if os.path.isabs(d): 535 lfull.append(d) 536 else: 537 # relative path implies cwd is correct 538 d2 = os.path.join(homedir,d) 539 lfull.append(os.path.abspath(d2)) 540 if lfull: 541 return lfull 542 return [] 543 544 def mpath_from_inifile(fname): 545 if not fname: 546 return None 547 ini = linuxcnc.ini(ifname) 548 homedir = os.path.dirname(os.path.abspath(fname)) 549 l = [] 550 p = ini.find('DISPLAY','PROGRAM_PREFIX') 551 if p: 552 l = [p] 553 else: 554 l = 'nc_files' 555 p = ini.find('RS274NGC','USER_M_PATH') 556 if p: 557 l.extend(p.split(':')) 558 lfull = [] 559 for d in l: 560 if os.path.isabs(d): 561 lfull.append(d) 562 else: 563 d2 = os.path.join(homedir,d) 564 lfull.append(os.path.abspath(d2)) 565 if lfull: 566 return lfull 567 return None 568 569 def spath_from_files(pre_file,sub_files,pst_file): 570 # when there is no ini file for path because 571 # linuxcnc not running 572 # and 573 # no ini specified on cmd line 574 l = [] 575 576 slist = [] 577 if type(sub_files) == StringType and sub_files: 578 slist.append(sub_files) 579 else: 580 slist = sub_files 581 582 for sub_file in slist: 583 dir = os.path.dirname(os.path.abspath(sub_file)) 584 if dir not in l: 585 l.append(dir) 586 587 if pre_file: 588 dir = os.path.dirname(os.path.abspath(pre_file)) 589 if dir not in l: 590 l.append(dir) 591 592 if pst_file: 593 dir = os.path.dirname(os.path.abspath(pst_file)) 594 if dir not in l: 595 l.append(dir) 596 597 if l: 598 return l 599 return [] 600 601 def long_name(name): 602 if name == 'pre': 603 return 'Preamble' 604 elif name == 'sub': 605 return 'Subroutine' 606 elif name == 'pst': 607 return 'Postamble' 608 else: 609 return 'Unknown' 610 611 def show_parent(w,ct=0): 612 if w is None: 613 print('show_parent: None') 614 return 615 print('show_parent:',ct,w) 616 if w.is_toplevel(): 617 print('TOP\n') 618 return 619 else: 620 show_parent(w.get_parent(),ct+1) 621 622 def all_coords(iterable): 623 ans = '' 624 for t in iterable: 625 ans = ans + '%7.3f' % t 626 return ans 627 628 def show_position(): 629 g_stat.poll() 630 print('POSITION-----------------------------------------------------') 631 print(' ap',all_coords(g_stat.actual_position)) 632 print(' p',all_coords(g_stat.position)) 633 l = [] 634 p = g_stat.actual_position 635 for i in range(9): l.append(p[i] 636 - g_stat.g5x_offset[i] 637 - g_stat.tool_offset[i] 638 ) 639 print('offset ap',all_coords(l)) 640 641 l = [] 642 p = g_stat.position 643 for i in range(9): l.append(p[i] 644 - g_stat.g5x_offset[i] 645 - g_stat.tool_offset[i] 646 ) 647 print('offset p',all_coords(l)) 648 print('POSITION=====================================================') 649 650 def coord_value(char): 651 # offset calc from emc_interface.py (touchy et al) 652 # char = 'x' | 'y' | ... 653 # 'd' is for diameter 654 c = char.lower() 655 g_stat.poll() 656 p = g_stat.position # tuple: (xvalue, yvalue, ... 657 if (c == 'd'): 658 if (1 & g_stat.axis_mask): 659 # diam = 2 * x 660 return (p[0] - g_stat.g5x_offset[0] - g_stat.tool_offset[0])* 2 661 else: 662 return 'xxx' # return a string that will convert with float() 663 664 axno = 'xyzabcuvw'.find(c) 665 if not ( (1 << axno) & g_stat.axis_mask ): 666 return 'xxx' # return a string that will convert with float() 667 return p[axno] - g_stat.g5x_offset[axno] - g_stat.tool_offset[axno] 668 669 def make_g_styles(): 670 671 dummylabel = gtk.Label() 672 673 global g_lbl_style_default 674 g_lbl_style_default = dummylabel.get_style().copy() 675 g_lbl_style_default.bg[gtk.STATE_NORMAL] = label_normal_color 676 g_lbl_style_default.bg[gtk.STATE_ACTIVE] = label_active_color 677 678 global g_lbl_style_created 679 g_lbl_style_created = dummylabel.get_style().copy() 680 681 global g_lbl_style_multiple 682 g_lbl_style_multiple = dummylabel.get_style().copy() 683 684 g_lbl_style_multiple.bg[gtk.STATE_NORMAL] = feature_color 685 g_lbl_style_multiple.bg[gtk.STATE_ACTIVE] = feature_color 686 687 g_lbl_style_created.bg[gtk.STATE_NORMAL] = feature_color 688 g_lbl_style_created.bg[gtk.STATE_ACTIVE] = feature_color 689 690 del dummylabel 691 692 693 dummyentry = gtk.Entry() 694 695 global g_ent_style_normal 696 g_ent_style_normal = dummyentry.get_style().copy() 697 698 global g_ent_style_default 699 g_ent_style_default = dummyentry.get_style().copy() 700 701 global g_ent_style_error 702 g_ent_style_error = dummyentry.get_style().copy() 703 704 g_ent_style_normal.base[gtk.STATE_NORMAL] = base_entry_color 705 706 g_ent_style_default.base[gtk.STATE_NORMAL] = bg_dvalue_color 707 708 g_ent_style_error.text[gtk.STATE_NORMAL] = error_color 709 g_ent_style_error.base[gtk.STATE_NORMAL] = base_entry_color 710 711 del dummyentry 712 713 def mod_font_by_category(obj,mode='control'): 714 # currently mode = control (only) 715 # touchy has 4 font categories: control,dro,error,listing 716 if mode == 'control': 717 font = g_control_font 718 else: 719 print('mod_font_by_category:unknown mode %s' % mode) 720 return 721 722 targetobj = None 723 if type(obj) == type(gtk.Label()): 724 targetobj = obj 725 elif type(obj) == type(gtk.Entry()): 726 targetobj = obj 727 elif type(obj) == type(gtk.Button()): 728 #gtk.Alignment object 729 if isinstance(obj.child, gtk.Label): 730 targetobj = obj.child 731 elif isinstance(obj.child, gtk.Alignment): 732 pass 733 elif hasattr(obj,'modify_font'): 734 targetobj = obj 735 else: 736 raise ValueError,'mod_font_by_category: no child' 737 return 738 else: 739 raise ValueError,'mod_font_by_category: unsupported:',type(obj) 740 return 741 742 if targetobj is None: 743 return 744 if font is None: 745 #print('mod_font_by_category:nofont available for %s' % mode) 746 return # silently 747 targetobj.modify_font(g_control_font) 748 749 global g_font_users 750 if targetobj not in g_font_users: 751 g_font_users.append(targetobj) 752 753 def update_fonts(fontname): 754 global g_control_font 755 g_control_font = fontname 756 for obj in g_font_users: 757 mod_font_by_category(obj) 758 759 def clean_tmpgcmc(odir): 760 if odir == "": 761 odir = g_searchpath[0] 762 savedir = os.path.join("/tmp", g_gcmc_funcname) # typ /tmp/tmpgcmc 763 if not os.path.isdir(savedir): 764 os.mkdir(savedir,0755) 765 for f in glob.glob(os.path.join(odir,g_gcmc_funcname + "*.ngc")): 766 # rename ng across file systems 767 shutil.move(f,os.path.join(savedir,os.path.basename(f))) 768 769 def find_gcmc(): 770 global g_gcmc_exe # find on first request 771 if g_gcmc_exe == "NOTFOUND": return False # earlier search failed 772 if g_gcmc_exe is not None: return True # already found 773 774 for dir in os.environ["PATH"].split(os.pathsep): 775 exe = os.path.join(dir,'gcmc') 776 if os.path.isfile(exe): 777 if os.access(exe,os.X_OK): 778 clean_tmpgcmc("") # clean on first find_gcmc 779 g_gcmc_exe = exe 780 return True # success 781 g_gcmc_exe = "NOTFOUND" 782 user_message(mtype=gtk.MESSAGE_ERROR 783 ,title=_('Error for:') 784 ,msg = _('gcmc executable not available:' 785 + '\nCheck path and permissions')) 786 return False # fail 787 788 #----------------------------------------------------------------------------- 789 790 make_g_styles() 791 792 793 class CandidateDialog(): 794 """CandidateDialog: dialog with a treeview in a scrollwindow""" 795 def __init__(self,ftype=''): 796 self.ftype = ftype 797 lname = long_name(self.ftype) 798 title = "Choose %s file" % lname 799 800 btns=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT 801 ,gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) 802 if ( (self.ftype == 'pre') or (self.ftype == 'pst') ): 803 # RESPONSE_NO used to allow 'nofile' for 'pre','pst' 804 btns = btns + ('No %s File' % lname, gtk.RESPONSE_NO) 805 806 self.fdialog = gtk.Dialog(title=title 807 ,parent=None 808 ,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 809 ,buttons=btns 810 ) 811 self.fdialog.set_size_request(600,600) 812 813 scrollw = gtk.ScrolledWindow() 814 scrollw.set_border_width(5) 815 scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) 816 scrollw.show() 817 818 box = self.fdialog.get_content_area() 819 box.pack_start(scrollw, True, True, 0) 820 821 global g_candidate_files 822 self.canfiles = g_candidate_files 823 self.canfiles.refresh() 824 self.treestore = g_candidate_files.treestore 825 826 self.treeview = gtk.TreeView(self.treestore) 827 if g_alive: self.treeview.connect('row-activated',self.row_activated) 828 829 830 column0 = gtk.TreeViewColumn('Subroutine Directories') 831 self.treeview.append_column(column0) 832 cell0 = gtk.CellRendererText() 833 column0.pack_start(cell0, True) 834 column0.add_attribute(cell0, 'text', 0) 835 836 column1 = gtk.TreeViewColumn('Hint') 837 self.treeview.append_column(column1) 838 cell1 = gtk.CellRendererText() 839 column1.pack_start(cell1, True) 840 column1.add_attribute(cell1, 'text', 1) 841 842 column2 = gtk.TreeViewColumn('mtime') 843 self.treeview.append_column(column2) 844 cell2 = gtk.CellRendererText() 845 column2.pack_start(cell2, True) 846 column2.add_attribute(cell2, 'text', 2) 847 848 scrollw.add_with_viewport(self.treeview) 849 scrollw.show_all() 850 851 def get_file_result(self): 852 # return: (name,errmsg) 853 try: 854 (model,iter) = self.treeview.get_selection().get_selected() 855 except AttributeError: 856 return(None,'') # nothing selected 857 if not iter: 858 return(None,'') 859 fname,status,mtime = self.canfiles.get_tree_data(iter) 860 861 if os.path.isdir(fname): 862 return(None,'') # cannot use a selected dir 863 864 ok = True # contradict this 865 if (self.ftype == 'pre') or (self.ftype == 'pst'): 866 if status.find('not_a_subfile') >= 0: ok = True 867 if status.find('Preempted') >= 0: ok = False 868 else: 869 if status.find('not_a_subfile') >= 0: ok = False 870 if status.find('not_allowed') >= 0: ok = False 871 if status.find('Preempted') >= 0: ok = False 872 873 if ok: 874 return (fname,'') 875 876 emsg = (_('The selected file is not usable\n' 877 'as a %s file\n' 878 '(%s)') % (long_name(self.ftype),status) 879 ) 880 return('TRYAGAIN',emsg) 881 882 def row_activated(self,tview,iter,column): 883 self.fdialog.response(gtk.RESPONSE_ACCEPT) 884 pass 885 886 def run(self): 887 return(self.fdialog.run()) 888 889 def destroy(self): 890 self.fdialog.destroy() 891 892 893 class CandidateFiles(): 894 """CandidateFiles treestore for candidate files""" 895 def __init__(self,dirlist): 896 self.dirlist=dirlist 897 self.treestore = gtk.TreeStore(str,str,str) 898 self.tdict = {} 899 self.make_tree() 900 901 def refresh(self): 902 # currently, just do over 903 # potential to reread only files with modified mtimes 904 self.__init__(self.dirlist) 905 906 def make_tree(self): 907 didx = 0 908 flist = [] 909 for dir in self.dirlist: 910 self.tdict[didx,] = dir 911 # row must be a tuple or list containing as many items 912 # as the number of columns 913 try: 914 mtime = datetime.datetime.fromtimestamp(os.path.getmtime(dir)) 915 except OSError,detail: 916 print(_('%s:make_tree:%s' % (g_progname,detail) )) 917 continue # try to skip this dir with message 918 mtime = mtime.strftime(g_dtfmt) # truncate fractional seconds 919 iter = self.treestore.append(None, [dir,"Directory",mtime]) 920 fidx = 0 921 for f in ( sorted(glob.glob(os.path.join(dir,"*.ngc"))) 922 + sorted(glob.glob(os.path.join(dir,"*.NGC"))) 923 + sorted(glob.glob(os.path.join(dir,"*.gcmc"))) 924 + sorted(glob.glob(os.path.join(dir,"*.GCMC"))) 925 ): 926 fname = os.path.basename(f) 927 self.tdict[didx,fidx] = fname 928 929 stat = "" 930 fd = open(f) 931 ftxt = fd.read() 932 fd.close() 933 934 if os.path.splitext(fname)[-1] in ['.gcmc','.GCMC']: 935 stat = '%sgcmc:ok' % stat 936 937 if ftxt.find('not_a_subfile') >= 0: 938 stat = '%snot_a_subfile ' % stat 939 if ftxt.find('(info:') >= 0: 940 stat = '%sngcgui-ok ' % stat 941 if fname in flist: 942 stat = '%sPreempted ' % stat 943 if ftxt.find('FEATURE') >= 0: 944 stat = '%snot_allowed ' % stat 945 if stat == "": 946 stat = "?" 947 if stat.find("Preempted") >= 0: 948 stat = "Preempted" # suppress ok 949 950 flist.append(fname) 951 mtime = datetime.datetime.fromtimestamp(os.path.getmtime(f)) 952 mtime = mtime.strftime(g_dtfmt) # truncate fractional seconds 953 self.treestore.append(iter, [fname,stat,mtime]) 954 fidx += 1 955 didx += 1 956 957 def get_tree_data(self,iter): 958 path = self.treestore.get_path(iter) 959 if len(path) > 1: 960 row,col = path 961 dir = self.tdict[row,] 962 fname = self.treestore.get_value(iter,0) 963 status = self.treestore.get_value(iter,1) 964 mtime = self.treestore.get_value(iter,2) 965 else: 966 dir = self.tdict[path] 967 fname = '' 968 status = '' 969 mtime = '' 970 return os.path.join(dir,fname),status,mtime 971 972 973 class LinuxcncInterface(): 974 """LinuxcncInterface: ini file and running linuxcnc data""" 975 def __init__(self,cmdline_ini_file=''): 976 self.lrunning = False 977 self.ini_data = None 978 self.subroutine_path = [] 979 self.user_m_path = None 980 self.ini_file = None 981 self.ngcgui_options = [] 982 self.editor = os.environ.get("VISUAL") 983 use_ini_file = None 984 985 l_ini_file = '' 986 stat = linuxcnc.stat() 987 988 989 try: 990 global g_stat 991 g_stat = linuxcnc.stat() 992 g_stat.poll() # poll faults if linuxcnc not running 993 self.lrunning = True 994 l_ini_file = get_linuxcnc_ini_file() 995 except linuxcnc.error,msg: 996 g_stat = None 997 print('INTFC:err:',msg) 998 print('INTFC:' + _('Warning: linuxcnc not running')) 999 1000 print('%s:INTFC:linuxcnc running=%d' % (g_progname,self.lrunning)) 1001 print('%s:INTFC:ini_file=<%s>' % (g_progname,l_ini_file)) 1002 1003 # cmdline_ini_file can be specified on cmdline and from intfc: 1004 # if neither ok: if no cmdline subfile, make custom page 1005 # if cmdonly ok 1006 # if runonly ok 1007 # if both ok: warn message and continue 1008 1009 if cmdline_ini_file: 1010 cmdline_spath = spath_from_inifile(cmdline_ini_file) 1011 if l_ini_file: 1012 l_spath = spath_from_inifile(l_ini_file) 1013 1014 if not cmdline_ini_file and not l_ini_file: 1015 ini_file = None 1016 spath = [] 1017 #print('NEITHER') 1018 if not cmdline_ini_file and l_ini_file: 1019 ini_file = l_ini_file 1020 spath = l_spath 1021 #print("OK running only <,",cmdline_ini_file,l_ini_file,">") 1022 if cmdline_ini_file and not l_ini_file: 1023 ini_file = cmdline_ini_file 1024 spath = cmdline_spath 1025 #print('OK cmdline only') 1026 if cmdline_ini_file and l_ini_file: 1027 #print("BOTH ini file on both cmdline and running linuxcnc") 1028 msg = "" 1029 if os.path.abspath(cmdline_ini_file) != l_ini_file: 1030 ini_file = l_ini_file 1031 msg = (_('The ini file specified on cmdline') + ':\n' 1032 + os.path.abspath(cmdline_ini_file) + '\n\n' 1033 + _('is different from the one used by the running linuxcnc') 1034 + ':\n' 1035 + l_ini_file + '\n\n' 1036 ) 1037 1038 if cmdline_spath == l_spath: 1039 ini_file = cmdline_ini_file 1040 spath = cmdline_spath 1041 msg = msg + _('Using cmd line ini file (same paths)') 1042 else: 1043 ini_file = l_ini_file 1044 spath = l_spath 1045 msg = msg + _('Ignoring cmd line ini file (different paths)') 1046 1047 user_message(mtype=gtk.MESSAGE_WARNING 1048 ,title=_('Warning') 1049 ,msg=msg 1050 ) 1051 1052 if ini_file: 1053 self.ini_file = ini_file 1054 self.ini_data = linuxcnc.ini(self.ini_file) 1055 # get it again to avoid (unlikely) race 1056 self.subroutine_path = spath_from_inifile(ini_file) 1057 self.ngcgui_options = self.ini_data.find('DISPLAY','NGCGUI_OPTIONS') 1058 1059 self.editor = ( self.editor 1060 or self.ini_data.find('DISPLAY','EDITOR')) 1061 1062 # create at startup, refresh as required 1063 global g_candidate_files 1064 g_candidate_files = CandidateFiles(self.get_subroutine_path()) 1065 1066 1067 def addto_spath(self,pathtoadd): 1068 if type(pathtoadd) != ListType: 1069 raise ValueError,( 1070 'addto_spath: List required not: %s %s' 1071 % (pathtoadd,type(pathtoadd)) 1072 ) 1073 # dont add duplicates 1074 if pathtoadd not in self.subroutine_path: 1075 self.subroutine_path.extend(pathtoadd) 1076 1077 def get_editor(self): 1078 return self.editor or 'gedit' 1079 1080 def get_ini_file(self): 1081 return(self.ini_file) 1082 1083 def get_subroutine_path(self): 1084 return(self.subroutine_path) 1085 1086 def get_user_m_path(self): 1087 return(self.user_m_path) 1088 1089 def find_file_in_path(self,fname): 1090 # return tuple: 1091 # '', 'NULLFILE' if fname None or '' 1092 # fname, 'NOPATH' no path defined (eg no inifile) 1093 # foundfilename, 'FOUND' found in path 1094 # fname, 'NOTFOUND' not in path (may exist) 1095 if not fname: 1096 return('','NULLFILE') 1097 if not self.subroutine_path: 1098 return(fname,'NOPATH') 1099 bname = os.path.basename(fname) # only basename used 1100 foundlist = [] 1101 foundfilename = None 1102 for p in self.subroutine_path: 1103 f = os.path.join(p,bname) 1104 if os.path.isfile(f): 1105 if not foundfilename: 1106 foundfilename = f #first one wins 1107 foundlist.append(f) 1108 1109 if len(foundlist) > 1: 1110 print(_('find_file_in_path:Multiple Results: %s') % foundlist) 1111 print(_(' Search path: %s') % self.subroutine_path) 1112 if foundfilename: 1113 vprint('find_file_in_path:%s' % foundfilename) 1114 return(foundfilename,'FOUND') 1115 print('find_file_in_path<%s> NOTFOUND' % fname) 1116 return(fname,'NOTFOUND') 1117 1118 def get_subfiles(self): 1119 if self.ini_data: 1120 #returns list 1121 return(self.ini_data.findall('DISPLAY','NGCGUI_SUBFILE')) 1122 else: 1123 return(None) 1124 1125 def get_preamble(self): 1126 if self.ini_data: 1127 return(self.ini_data.find('DISPLAY','NGCGUI_PREAMBLE')) 1128 else: 1129 return(None) 1130 1131 def get_postamble(self): 1132 if self.ini_data: 1133 return(self.ini_data.find('DISPLAY','NGCGUI_POSTAMBLE')) 1134 else: 1135 return(None) 1136 1137 def get_font(self): 1138 if self.ini_data: 1139 return(self.ini_data.find('DISPLAY','NGCGUI_FONT')) 1140 else: 1141 return(None) 1142 1143 def get_ngcgui_options(self): 1144 return(self.ngcgui_options or []) 1145 1146 def get_gcmc_include_path(self): 1147 dirs = (self.ini_data.find('DISPLAY','GCMC_INCLUDE_PATH')) 1148 return(dirs) 1149 1150 def get_program_prefix(self): 1151 if self.ini_data: 1152 dir = self.ini_data.find('DISPLAY','PROGRAM_PREFIX') 1153 dir = os.path.expanduser(dir) 1154 if not os.path.isabs(dir): 1155 # relative, base on inidir 1156 dir = os.path.join(os.path.dirname(self.ini_file),dir) 1157 return(dir) 1158 else: 1159 return(None) 1160 1161 1162 class PreFile(): 1163 """PreFile: preamble file data""" 1164 def __init__(self,thefile): 1165 self.pre_file = thefile 1166 self.read() 1167 1168 def clear(self): 1169 self.pre_file = '' 1170 self.inputlines=[] 1171 1172 def read(self): 1173 #print('PreFile read') 1174 self.md5 = None 1175 self.mtime = None 1176 self.inputlines = [] 1177 if self.pre_file == "": return 1178 1179 self.mtime = os.path.getmtime(self.pre_file) 1180 f = open(self.pre_file) 1181 for l in f.readlines(): 1182 # dont include not_a_subfile lines 1183 if (l.find('not_a_subfile') < 0) and (l.strip() != ''): 1184 self.inputlines.append(l) 1185 f.close() 1186 self.md5 = md5sum(self.pre_file) 1187 1188 1189 class PstFile(): 1190 """PstFile: postamble file data""" 1191 def __init__(self,thefile): 1192 self.pst_file = thefile 1193 self.read() 1194 1195 def clear(self): 1196 self.pst_file = '' 1197 self.inputlines = [] 1198 1199 def read(self): 1200 #print('PstFile read') 1201 self.md5 = None 1202 self.mtime = None 1203 self.inputlines = [] 1204 1205 if self.pst_file == "": return 1206 self.mtime = os.path.getmtime(self.pst_file) 1207 f = open(self.pst_file) 1208 for l in f.readlines(): 1209 # dont include not_a_subfile lines 1210 if (l.find('not_a_subfile') < 0) and (l.strip() != ''): 1211 self.inputlines.append(l) 1212 f.close() 1213 self.md5 = md5sum(self.pst_file) 1214 1215 1216 class SubFile(): 1217 """SubFile: subfile data""" 1218 def __init__(self,thefile): 1219 self.sub_file = thefile 1220 self.min_num = sys.maxint 1221 self.max_num = 0 1222 self.pdict = {} # named items: pdict[keyword] = value 1223 self.ndict = {} # ordinal items: ndict[idx] = (name,dvalue,comment) 1224 self.ldict = {} # label items: ldict[lno] = thelabel 1225 self.pdict['info'] = '' 1226 self.pdict['lastparm'] = 0 1227 self.pdict['subname'] = '' 1228 self.inputlines = [] 1229 self.errlist=[] 1230 self.md5 = None 1231 self.mtime = None 1232 if self.sub_file == '': return 1233 1234 self.mtime = os.path.getmtime(self.sub_file) 1235 self.md5 = md5sum(self.sub_file) 1236 1237 if os.path.splitext(self.sub_file)[-1] in ['.ngc','.NGC','.nc','.NC']: 1238 self.read_ngc() 1239 elif os.path.splitext(self.sub_file)[-1] in ['.gcmc','.GCMC']: 1240 self.read_gcmc() 1241 else: 1242 user_message(mtype=gtk.MESSAGE_ERROR 1243 ,title=_('Unknown file suffix') 1244 ,msg = _('Unknown suffix for: %s:') 1245 % os.path.basename(self.sub_file) 1246 ) 1247 return 1248 1249 def clear(self): 1250 self.sub_file = '' 1251 self.pdict = {} 1252 self.ndict = {} 1253 self.ldict = {} 1254 self.inputlines = [] 1255 1256 def flagerror(self,e): 1257 # accumulate erors from read() so entire file can be processed 1258 self.errlist.append(e) 1259 1260 def specialcomments_ngc(self,s): 1261 if s.find(' FEATURE ') >= 0 : 1262 self.flagerror( 1263 "Disallowed use of ngcgui generated file as Subfile") 1264 if s.find('not_a_subfile') >= 0 : 1265 self.flagerror( 1266 "marked (not_a_subfile)\nNot intended for use as a subfile") 1267 1268 def re_read(self): 1269 if self.pdict.has_key('isgcmc'): 1270 self.read_gcmc() 1271 else: 1272 self.read_ngc() 1273 1274 def read_ngc(self): 1275 1276 thesubname = os.path.splitext(os.path.basename(self.sub_file))[0] 1277 1278 f = open(self.sub_file) 1279 self.inputlines = [] # in case rereading 1280 for l in f.readlines(): 1281 self.specialcomments_ngc(l) # for compat, check on unaltered line 1282 self.inputlines.append(l) 1283 idx = 1 # 1 based for labels ldict 1284 nextparm = 0 1285 subname = None 1286 endsubname = None 1287 for line in self.inputlines: 1288 # rs274: no whitespace, simplify with lowercase 1289 info = get_info_item(line) # check on unaltered line 1290 l = line.translate(None,' \t').lower() 1291 lineiscomment = is_comment(l) 1292 if info is not None: self.pdict['info'] = info 1293 sname = check_sub_start(l) 1294 if subname is not None and sname is not None: 1295 self.flagerror("Multiple subroutines in file not allowed") 1296 if subname is None and sname is not None: 1297 subname = sname 1298 if subname is not None and subname != thesubname: 1299 self.flagerror("sub label " 1300 "%s does not match subroutine file name" % thesubname) 1301 1302 if endsubname is not None: 1303 if lineiscomment or (l.strip() == ''): 1304 pass 1305 elif l.find('m2') >= 0: 1306 # linuxcnc ignores m2 after endsub in 1307 # single-file subroutines 1308 # mark as ignored here for use with expandsub option 1309 self.inputlines[-1] = (';' + g_progname + 1310 ' ignoring: ' + self.inputlines[-1]) 1311 pass 1312 else: 1313 self.flagerror('file contains lines after subend:\n' 1314 '%s' % l) 1315 1316 ename = check_sub_end(l) 1317 if subname is None and ename is not None: 1318 self.flagerror("endsub before sub %s" % ename) 1319 if subname is not None and ename is not None: 1320 endsubname = ename 1321 if endsubname != subname: 1322 self.flagerror("endsubname different from subname") 1323 1324 label = check_for_label(l) 1325 if label: self.ldict[idx] = label 1326 1327 if ( subname is not None 1328 and endsubname is None 1329 and (not lineiscomment)): 1330 1331 pparm,min,max= check_positional_parm_range(l 1332 ,self.min_num,self.max_num) 1333 if pparm > g_max_parm: 1334 self.flagerror( 1335 _('parm #%s exceeds config limit on no. of parms= %d\n') 1336 % (pparm,g_max_parm)) 1337 if pparm: 1338 self.min_num = min 1339 self.max_num = max 1340 1341 # blanks required for this, use line not l 1342 name,pnum,dvalue,comment = find_positional_parms(line) 1343 if name: 1344 self.ndict[pnum] = (name,dvalue,comment) 1345 # require parms in sequence to minimize user errors 1346 nextparm = nextparm + 1 1347 if g_strict: 1348 if pnum != nextparm: 1349 self.flagerror( 1350 _('out of sequence positional parameter' 1351 '%d expected: %d') 1352 % (pnum, nextparm)) 1353 while pnum > nextparm: 1354 makename = "#"+str(nextparm) 1355 self.ndict[nextparm] = makename,"",makename 1356 nextparm = nextparm + 1 1357 self.pdict['lastparm'] = pnum 1358 idx = idx + 1 1359 f.close() 1360 1361 if subname is None: self.flagerror(_('no sub found in file\n')) 1362 if endsubname is None: self.flagerror(_('no endsub found in file\n')) 1363 1364 if g_strict: 1365 if nextparm == 0: self.flagerror(_('no subroutine parms found\n')) 1366 1367 self.pdict['subname'] = subname 1368 if self.pdict['info'] == '': 1369 self.pdict['info'] = 'sub: '+str(subname) 1370 if self.errlist: 1371 user_message(mtype=gtk.MESSAGE_ERROR 1372 ,title=_('Error for: %s ') 1373 % os.path.basename(self.sub_file) 1374 ,msg = self.errlist) 1375 self.errlist.append('SUBERROR') 1376 raise ValueError,self.errlist 1377 1378 def read_gcmc(self): 1379 self.gcmc_opts = [] # list of options for gcmc 1380 pnum = 0 1381 f = open(self.sub_file) 1382 for l in f.readlines(): 1383 rinfo = re.search(r'^ *\/\/ *ngcgui *: *info: *(.*)' ,l) 1384 if rinfo: 1385 #print 'info read_gcmc:g1:',rinfo.group(1) 1386 self.pdict['info'] = rinfo.group(1) # last one wins 1387 continue 1388 1389 ropt = re.search(r'^ *\/\/ *ngcgui *: *(-.*)$' ,l) 1390 if ropt: 1391 gopt = ropt.group(1) 1392 gopt = gopt.split("//")[0] ;# trailing comment 1393 gopt = gopt.split(";")[0] ;# convenience 1394 gopt = gopt.strip() ;# leading/trailing spaces 1395 self.gcmc_opts.append(gopt) 1396 continue 1397 1398 name = None 1399 dvalue = None 1400 comment = '' 1401 r3 = re.search(r'^ *\/\/ *ngcgui *: *(.*?) *= *(.*?) *\, *(.*?) *$', l) 1402 r2 = re.search(r'^ *\/\/ *ngcgui *: *(.*?) *= *(.*?) *$', l) 1403 r1 = re.search(r'^ *\\/\\/ *ngcgui *: *\(.*?\) *$', l) 1404 if r3: 1405 name = r3.group(1) 1406 dvalue = r3.group(2) 1407 comment = r3.group(3) 1408 elif r2: 1409 name = r2.group(1) 1410 dvalue = r2.group(2) 1411 elif r1: 1412 print 'r1-1 opt read_gcmc:g1:',r1.group(1) 1413 name = r1.group(1) 1414 1415 if dvalue: 1416 # this is a convenience to make it simple to edit to 1417 # add a var without removing the semicolon 1418 # xstart = 10; 1419 # //ngcgui: xstart = 10; 1420 dvalue = dvalue.split(";")[0] # ignore all past a ; 1421 else: 1422 dvalue = '' 1423 1424 if name: 1425 if comment is '': 1426 comment = name 1427 pnum += 1 1428 self.ndict[pnum] = (name,dvalue,comment) 1429 1430 self.pdict['isgcmc'] = True 1431 self.pdict['lastparm'] = pnum 1432 self.pdict['subname'] = os.path.splitext(os.path.basename(self.sub_file))[0] 1433 if self.pdict['info'] == '': 1434 self.pdict['info'] = 'gcmc: '+ self.pdict['subname'] 1435 f.close() 1436 return True # ok 1437 1438 class FileSet(): 1439 """FileSet: set of preamble,subfile,postamble files""" 1440 def __init__(self,pre_file 1441 ,sub_file 1442 ,pst_file 1443 ): 1444 # sub_file=='' is not an error, opens Custom 1445 self.pre_data = PreFile(pre_file) 1446 self.sub_data = SubFile(sub_file) 1447 self.pst_data = PstFile(pst_file) 1448 1449 class OneParmEntry(): 1450 """OneParmEntry: one parameter labels and entry box""" 1451 def __init__(self,ltxt='ltxt' ,etxt='etxt' ,rtxt='rtxt'): 1452 1453 self.box = gtk.HBox() 1454 1455 self.ll = gtk.Label() 1456 self.en = gtk.Entry() 1457 self.lr = gtk.Label() 1458 1459 self.dv = None 1460 1461 ww = -1 1462 hh = g_entry_height 1463 1464 self.ll.set_label(ltxt) 1465 self.ll.set_width_chars(2) 1466 self.ll.set_justify(gtk.JUSTIFY_RIGHT) 1467 self.ll.set_alignment(xalign=.90,yalign=0.5) # right aligned 1468 self.ll.set_size_request(ww,hh) 1469 1470 self.en.set_text(etxt) 1471 self.en.set_width_chars(6) 1472 self.en.set_alignment(xalign=.90) # right aligned 1473 self.en.set_size_request(ww,hh) 1474 self.en.hide() 1475 1476 #self.en.connect("button-press-event",self.grabit) 1477 if g_popkbd is not None: 1478 if g_alive: self.en.connect("button-press-event",self.popkeyboard) 1479 1480 if g_alive: self.en.connect('changed', self.entry_changed) #-->w + txt 1481 1482 self.lr.set_label(rtxt) 1483 self.lr.set_width_chars(0) # allow any width for compat with ngcgui 1484 self.lr.set_justify(gtk.JUSTIFY_LEFT) 1485 self.lr.set_alignment(xalign=0.2,yalign=0.5) # left aligned 1486 self.lr.set_size_request(ww,hh) 1487 self.lr.hide() 1488 mod_font_by_category(self.lr,'control') 1489 1490 self.tbtns = gtk.HBox(homogeneous=0,spacing=2) 1491 self.tbtns.set_border_width(0) 1492 1493 self.box.pack_start(self.tbtns, expand=0, fill=0, padding=0) 1494 1495 self.tbtns.pack_start(self.ll, expand=0, fill=0, padding=0) 1496 self.tbtns.pack_start(self.en, expand=0, fill=0, padding=0) 1497 self.tbtns.pack_start(self.lr, expand=0, fill=0, padding=0) 1498 1499 def grabit(self,*args,**kwargs): 1500 #print 'grabit',self,args,kwargs 1501 print '\ngrabit:can_get_focus:',self.en.get_can_focus() 1502 self.en.grab_focus() 1503 print 'grabit:has_focus',self.en.has_focus() 1504 print 'grabit: is_focus',self.en.is_focus() 1505 1506 def popkeyboard(self,widget,v): 1507 origtxt = self.en.get_text() 1508 title = '#%s, <%s> %s' % (self.ll.get_text() 1509 ,self.en.get_text() 1510 ,self.lr.get_text() 1511 ) 1512 self.en.set_text('') 1513 if g_popkbd.run(initial_value='',title=title): 1514 self.en.set_text(g_popkbd.get_result()) 1515 else: 1516 # user canceled 1517 self.en.set_text(origtxt) 1518 1519 def entry_changed(self,w): 1520 v = w.get_text().lower() 1521 if g_stat: 1522 r = re.search('[xyzabcuvwd]',v) 1523 if r: 1524 char = r.group(0) 1525 try: 1526 w.set_text("%.4f" % coord_value(char)) 1527 except TypeError: 1528 pass 1529 except Exception, detail: 1530 exception_show(Exception,detail,'entry_changed') 1531 pass 1532 1533 if v == '': 1534 w.set_style(g_ent_style_normal) 1535 return 1536 else: 1537 try: 1538 float(v) 1539 w.set_style(g_ent_style_normal) 1540 except ValueError: 1541 w.set_style(g_ent_style_error) 1542 return 1543 try: 1544 if ( (self.dv is not None) 1545 and (float(v) == float(self.dv)) ): 1546 w.set_style(g_ent_style_default) 1547 return 1548 except ValueError: 1549 pass 1550 w.set_style(g_ent_style_normal) 1551 return 1552 1553 def getentry(self): 1554 return(self.en.get_text()) 1555 1556 def setentry(self,v): 1557 self.en.set_text(v) 1558 1559 def clear_pentry(self): 1560 self.ll.set_text('') 1561 self.en.set_text('') 1562 self.lr.set_text('') 1563 self.ll.hide() 1564 self.en.hide() 1565 self.lr.hide() 1566 1567 def make_pentry(self,ll,dvalue,lr,emode='initial'): 1568 # modes 'initial' 1569 # 'keep' 1570 self.dv = dvalue 1571 if dvalue is None: 1572 en = '' 1573 else: 1574 en = dvalue 1575 1576 if ll is None: ll='' 1577 if lr is None: lr='' 1578 self.ll.set_text(str(ll)) 1579 1580 if emode == 'initial': 1581 self.en.set_text(str(en)) 1582 1583 # on reread, may be new parms with no text so use default 1584 # if (emode == 'keep') and (not self.en.get_text()): 1585 if (emode == 'keep') and (self.en.get_text() is None): 1586 self.en.set_text(str(en)) 1587 1588 self.lr.set_text(str(lr)) 1589 if dvalue is None or dvalue == '': 1590 self.en.set_style(g_ent_style_normal) # normal (not a dvalue) 1591 else: 1592 self.en.set_style(g_ent_style_default) # a dvalue 1593 1594 self.ll.show() 1595 self.en.show() 1596 self.lr.show() 1597 self.entry_changed(self.en) 1598 1599 1600 class EntryFields(): 1601 """EntryFields: Positional Parameters entry fields in a frame """ 1602 def __init__(self,nparms=INTERP_SUB_PARAMS): 1603 if nparms > g_max_parm: 1604 raise ValueError,(_( 1605 'EntryFields:nparms=%d g_max_parm=%d') 1606 % (nparms,g_max_parm)) 1607 self.ebox = gtk.Frame() 1608 self.ebox.set_shadow_type(gtk.SHADOW_ETCHED_IN) 1609 self.ebox.set_border_width(2) 1610 1611 efbox = gtk.VBox() 1612 evb = gtk.VBox(homogeneous=0,spacing=2) 1613 xpositionalp = gtk.Label('Positional Parameters') 1614 xpositionalp.set_alignment(xalign=0.0,yalign=0.5) # left aligned 1615 epositionalp = gtk.EventBox() 1616 epositionalp.add(xpositionalp) 1617 epositionalp.modify_bg(gtk.STATE_NORMAL,label_normal_color) 1618 lpositionalp = gtk.Frame() 1619 lpositionalp.set_shadow_type(gtk.SHADOW_IN) 1620 lpositionalp.set_border_width(0) 1621 lpositionalp.add(epositionalp) 1622 1623 1624 self.boxofcolumns = gtk.HBox(homogeneous=0,spacing=2) 1625 1626 evb.pack_start(lpositionalp,expand=0,fill=1,padding=0) 1627 evb.pack_start(self.boxofcolumns, expand=1,fill=1,padding=4) 1628 1629 efbox.pack_start(evb, expand=1,fill=1,padding=0) 1630 self.ebox.add(efbox) 1631 1632 self.make_entryfields(nparms) # initialize for EntryFields 1633 1634 def make_entryfields(self,nparms): 1635 self.no_of_entries = nparms 1636 # make VBoxes as required to accomodate entries 1637 # destroy them when starting over -- this occurs 1638 # when a OnePg is reused for a different subfile 1639 try: 1640 type(self.columnbox) # test for existence 1641 # destroy prior VBoxes packed in self.boxofcolumns 1642 for c in self.boxofcolumns.children(): 1643 self.boxofcolumns.remove(c) 1644 c.destroy() 1645 del(c) 1646 except AttributeError: 1647 # first-time: create initial VBox for entries 1648 self.columnbox = gtk.VBox(homogeneous=0,spacing=2) 1649 1650 self.boxofcolumns.pack_start(self.columnbox) 1651 1652 # try to use minimum height if less than 3 columns 1653 if nparms > 20: 1654 rowmax = 10 1655 else: 1656 rowmax = int(nparms/2 + 0.5) 1657 1658 self.pentries = {} 1659 row = 0 1660 idx = 1 # 1-based to agree with parm no.s 1661 for i in range(0,nparms): 1662 if row >= rowmax: 1663 row = 0 1664 # make a new VBox for next column of entries 1665 self.columnbox = gtk.VBox(homogeneous=0,spacing=2) 1666 self.boxofcolumns.pack_start(self.columnbox) 1667 self.pentries[idx] = OneParmEntry('','','') 1668 self.columnbox.pack_start(self.pentries[idx].box 1669 ,expand=0,fill=0,padding=0) 1670 row += 1 1671 idx += 1 1672 self.boxofcolumns.show_all() 1673 1674 def getentry_byidx(self,idx): 1675 return(self.pentries[idx].getentry()) 1676 1677 def clear_pentry_byidx(self,idx): 1678 self.pentries[idx].clear_pentry() 1679 1680 def make_pentry_byidx(self,idx,ll,en,lr,emode='initial'): 1681 self.pentries[idx].make_pentry(ll,en,lr,emode) 1682 1683 def getstuff_byidx(self,idx): 1684 print("1getstuff idx=",idx) 1685 self.pentries[idx].getstuff() 1686 1687 def get_box(self): 1688 return self.ebox 1689 1690 def clear_parm_entries(self): 1691 for pidx in range(1,self.no_of_entries+1): 1692 self.clear_pentry_byidx(pidx) 1693 1694 def set_parm_entries(self,parms,emode='initial'): 1695 lastpidx = 0 1696 for pidx in sorted(parms.sub_data.ndict): 1697 name,dvalue,comment = parms.sub_data.ndict[pidx] 1698 self.make_pentry_byidx(pidx 1699 ,str(pidx) 1700 ,dvalue 1701 ,comment 1702 ,emode 1703 ) 1704 lastpidx = pidx 1705 1706 1707 class TestButtons(): 1708 """TestButtons: debugging buttons""" 1709 def __init__(self,mypg): 1710 self.box = gtk.HBox() 1711 self.mypg = mypg 1712 lbl = gtk.Label('Debug:') 1713 lbl.set_alignment(xalign=0.9,yalign=0.5) # rt aligned 1714 self.box.pack_start(lbl,expand=0,fill=0,padding=2) 1715 for item in ('info' 1716 ,'intfc' 1717 ,'nset' 1718 ,'nb' 1719 ,'page' 1720 ,'fset' 1721 ,'pre' 1722 ,'sub' 1723 ,'pst' 1724 ,'ent' 1725 ,'cp' 1726 ,'lcnc' 1727 ,'hal' 1728 ,'pos' 1729 ,'glo' 1730 ,'loc' 1731 ,'tst' 1732 ): 1733 button = gtk.Button(item) 1734 if g_alive: button.connect("clicked", self.btest, item) 1735 button.show_all() 1736 self.box.pack_start(button,expand=0,fill=0,padding=2) 1737 bclose = gtk.Button('Close') 1738 if g_alive: bclose.connect("clicked", lambda x: self.delete()) 1739 self.box.pack_start(bclose,expand=0,fill=0,padding=2) 1740 1741 def btest(self,widget,v): 1742 m = self.mypg 1743 if v == 'info': 1744 p = m.nset 1745 print('INFO--------------------------------------------------') 1746 print(' sys.argv = %s' % sys.argv) 1747 print(' cwd = %s' % os.getcwd()) 1748 print(' sys.path = %s' % sys.path) 1749 print(' ini_file = %s' % p.intfc.get_ini_file()) 1750 print(' auto_file = %s' % p.auto_file) 1751 print('subroutine_path = %s' % p.intfc.get_subroutine_path()) 1752 print(' user_m_path = %s' % p.intfc.get_user_m_path()) 1753 print(' pre_file = %s' % p.intfc.get_preamble()) 1754 print(' sublist = %s' % p.intfc.get_subfiles()) 1755 print(' pst_file = %s' % p.intfc.get_postamble()) 1756 print(' startpage_idx = %s' % p.startpage_idx) 1757 print('') 1758 print(' __file__ = %s' % __file__) 1759 print('g_send_function = %s' % g_send_function) 1760 print(' g_popkbd = %s' % g_popkbd) 1761 print(' g_stat = %s' % g_stat) 1762 print(' g_progname = %s' % g_progname) 1763 print(' g_verbose = %s' % g_verbose) 1764 print(' g_debug = %s' % g_debug) 1765 print(' g_tmode = %s' % g_tmode) 1766 print(' g_label_id = %s' % g_label_id) 1767 elif v == 'ent': 1768 print('ENTRIES--------------------------------------------------') 1769 x = m.efields.pentries 1770 pmax = m.fset.sub_data.pdict['lastparm'] 1771 print('efields.pentries[]') 1772 for pidx in range(1,pmax+1): 1773 print("%2d: %4s %-8s %-20s" % (pidx 1774 ,x[pidx].ll.get_text() 1775 ,x[pidx].en.get_text() 1776 ,x[pidx].lr.get_text() 1777 )) 1778 print('ENTRIES==================================================') 1779 elif v == 'intfc': d = m.nset.intfc; show_dir(d,tag='intfc') 1780 elif v == 'page': 1781 d = m; show_dir(d,tag='mypg') 1782 x=self.mypg.efields.pentries[1].en 1783 print 'x=',x 1784 print ' has_focus:',x.has_focus() 1785 print ' is_focus:',x.is_focus() 1786 print ' get_can_focus:',x.get_can_focus() 1787 elif v == 'pre': d = m.fset.pre_data; show_dir(d,tag='pre_data') 1788 elif v == 'sub': d = m.fset.sub_data; show_dir(d,tag='sub_data') 1789 elif v == 'pst': d = m.fset.pst_data; show_dir(d,tag='pst_data') 1790 elif v == 'fset': d = m.fset; show_dir(d,tag='fset') 1791 elif v == 'nset': d = m.nset; show_dir(d,tag='nset') 1792 elif v == 'cp': d = m.cpanel; show_dir(d,tag='cpanel') 1793 elif v == 'loc': show_dir(locals(),tag='locals') 1794 elif v == 'glo': show_dir(globals(),tag='globals') 1795 elif v == 'lcnc': show_dir(linuxcnc,tag='lcnc') 1796 elif v == 'hal': show_dir(hal,tag='hal') 1797 elif v == 'pos': show_position() 1798 elif v == 'tst': 1799 print('cpanel size:',m.cpanel.box.size_request()) 1800 print('mtable size:',m.mtable.size_request()) 1801 elif v == 'nb': 1802 print('NB--------------------------------------------------') 1803 for pno in range(m.nset.startpage_idx 1804 ,m.mynb.get_n_pages()): 1805 npage = m.mynb.get_nth_page(pno) 1806 pg = m.nset.pg_for_npage[npage] 1807 ltxt = pg.the_lbl.get_text() 1808 print('%10s %s' % (ltxt,pg)) 1809 print('NB==================================================') 1810 else: print('btest unknown:',v) 1811 1812 def delete(self): 1813 gtk.main_quit() 1814 return False 1815 1816 1817 class ControlPanel(): 1818 """ControlPanel: Controls and image display""" 1819 def __init__(self 1820 ,mypg 1821 ,pre_file='' 1822 ,sub_file='' 1823 ,pst_file='' 1824 ): 1825 self.mypg = mypg 1826 1827 frame = gtk.Frame() 1828 frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) 1829 frame.set_border_width(2) 1830 self.box = frame 1831 1832 cpbox = gtk.VBox() 1833 # fixed width so it doesn't change when switching tabs 1834 # fixed height to allow room for buttons below image 1835 #cpbox.set_size_request(g_image_width,g_image_height) 1836 1837 bw = 1 1838 bpre = gtk.Button(_('Preamble')) 1839 bpre.set_border_width(bw) 1840 mod_font_by_category(bpre) 1841 1842 bsub = gtk.Button(_('Subfile')) 1843 bsub.set_border_width(bw) 1844 mod_font_by_category(bsub) 1845 1846 bpst = gtk.Button(_('Postamble')) 1847 bpst.set_border_width(bw) 1848 mod_font_by_category(bpst) 1849 1850 self.pre_entry = gtk.Entry() 1851 self.pre_entry.set_state(gtk.STATE_INSENSITIVE) 1852 1853 self.sub_entry = gtk.Entry() 1854 self.sub_entry.set_state(gtk.STATE_INSENSITIVE) 1855 1856 self.pst_entry = gtk.Entry() 1857 self.pst_entry.set_state(gtk.STATE_INSENSITIVE) 1858 1859 chars=10 1860 1861 self.pre_entry.set_width_chars(chars) 1862 self.pre_entry.set_alignment(xalign=0.1) 1863 self.pre_entry.set_text(os.path.basename(pre_file)) 1864 if g_alive: self.pre_entry.connect("activate", self.file_choose, 'pre') 1865 1866 self.sub_entry.set_width_chars(chars) 1867 self.sub_entry.set_alignment(xalign=0.1) 1868 self.sub_entry.set_text(os.path.basename(sub_file)) 1869 if g_alive: self.sub_entry.connect("activate", self.file_choose, 'sub') 1870 1871 self.pst_entry.set_width_chars(chars) 1872 self.pst_entry.set_alignment(xalign=0.1) 1873 self.pst_entry.set_text(os.path.basename(pst_file)) 1874 if g_alive: self.pst_entry.connect("activate", self.file_choose, 'pst') 1875 1876 xcontrol = gtk.Label('Controls') 1877 xcontrol.set_alignment(xalign=0.0,yalign=0.5) # left aligned 1878 econtrol = gtk.EventBox() 1879 econtrol.add(xcontrol) 1880 econtrol.modify_bg(gtk.STATE_NORMAL,label_normal_color) 1881 lcontrol= gtk.Frame() 1882 lcontrol.set_shadow_type(gtk.SHADOW_IN) 1883 lcontrol.set_border_width(0) 1884 lcontrol.add(econtrol) 1885 1886 tfiles = gtk.Table(rows=3, columns=2, homogeneous=0) 1887 1888 bx = gtk.FILL|gtk.EXPAND; by = 0 1889 1890 tfiles.attach(bpre,0,1,0,1,xoptions=bx,yoptions=by) 1891 tfiles.attach(bsub,0,1,1,2,xoptions=bx,yoptions=by) 1892 tfiles.attach(bpst,0,1,2,3,xoptions=bx,yoptions=by) 1893 1894 tfiles.attach(self.pre_entry,1,2,0,1,xoptions=bx,yoptions=by) 1895 tfiles.attach(self.sub_entry,1,2,1,2,xoptions=bx,yoptions=by) 1896 tfiles.attach(self.pst_entry,1,2,2,3,xoptions=bx,yoptions=by) 1897 1898 if g_alive: bpre.connect("clicked", self.file_choose, 'pre') 1899 if g_alive: bsub.connect("clicked", self.file_choose, 'sub') 1900 if g_alive: bpst.connect("clicked", self.file_choose, 'pst') 1901 1902 #bretain = gtk.CheckButton('Retain values on Subfile read') 1903 self.bexpand = gtk.CheckButton('Expand Subroutine') 1904 self.bexpand.set_active(self.mypg.expandsub) 1905 if g_alive: self.bexpand.connect("toggled", self.toggle_expandsub) 1906 1907 self.bautosend = gtk.CheckButton('Autosend') 1908 self.bautosend.set_active(self.mypg.autosend) 1909 if g_alive: self.bautosend.connect("toggled", self.toggle_autosend) 1910 1911 tchkbs = gtk.Table(rows=3, columns=1, homogeneous=0) 1912 bx = gtk.FILL|gtk.EXPAND; by = gtk.FILL|gtk.EXPAND 1913 #tchkbs.attach(bretain, 0,1,0,1,xoptions=bx,yoptions=by) 1914 tchkbs.attach(self.bexpand, 0,1,1,2,xoptions=bx,yoptions=by) 1915 1916 nopts = self.mypg.nset.intfc.get_ngcgui_options() 1917 if (nopts is None) or ('noauto' not in nopts): 1918 tchkbs.attach(self.bautosend,0,1,2,3,xoptions=bx,yoptions=by) 1919 1920 bw = 1 1921 1922 bcreate = gtk.Button(_('Create Feature')) 1923 bcreate.set_border_width(bw) 1924 if g_alive: bcreate.connect("clicked", lambda x: self.create_feature()) 1925 mod_font_by_category(bcreate) 1926 1927 bfinalize = gtk.Button(_('Finalize')) 1928 bfinalize.set_border_width(bw) 1929 if g_alive: bfinalize.connect("clicked" 1930 ,lambda x: self.finalize_features()) 1931 mod_font_by_category(bfinalize) 1932 1933 self.lfct = gtk.Label(str(mypg.feature_ct)) 1934 self.lfct.set_alignment(xalign=0.9,yalign=0.5) # right aligned 1935 mod_font_by_category(self.lfct) 1936 1937 lfctf = gtk.Frame() 1938 lfctf.set_shadow_type(gtk.SHADOW_IN) 1939 lfctf.set_border_width(2) 1940 lfctf.add(self.lfct) 1941 1942 self.breread = gtk.Button(_('Reread')) 1943 self.breread.set_border_width(bw) 1944 if g_alive: self.breread.connect("clicked" 1945 ,lambda x: self.reread_files()) 1946 mod_font_by_category(self.breread) 1947 1948 brestart = gtk.Button(_('Restart')) 1949 brestart.set_border_width(bw) 1950 if g_alive: brestart.connect("clicked" 1951 ,lambda x: self.restart_features()) 1952 mod_font_by_category(brestart) 1953 1954 self.lmsg = gtk.Label(_('Ctrl-k for key shortcuts')) 1955 self.lmsg.set_alignment(xalign=0.05,yalign=0.5) # left aligned 1956 1957 lmsgf = gtk.Frame() 1958 lmsgf.set_shadow_type(gtk.SHADOW_IN) 1959 lmsgf.set_border_width(2) 1960 lmsgf.add(self.lmsg) 1961 1962 tactions = gtk.Table(rows=3, columns=3, homogeneous=1) 1963 bx = gtk.FILL|gtk.EXPAND; by = gtk.FILL|gtk.EXPAND 1964 tactions.attach(bcreate, 0,2,0,1,xoptions=bx,yoptions=by) 1965 tactions.attach(bfinalize,2,3,0,1,xoptions=bx,yoptions=by) 1966 1967 # only if image (see below) 1968 # tactions.attach(self.breread ,0,1,1,2,xoptions=bx,yoptions=by) 1969 tactions.attach(brestart, 2,3,1,2,xoptions=bx,yoptions=by) 1970 1971 bx = gtk.FILL|gtk.EXPAND; by = 0 1972 #tactions.attach(self.lmsg,0,3,2,3,xoptions=bx,yoptions=by) 1973 tactions.attach(lmsgf,0,3,2,3,xoptions=bx,yoptions=by) 1974 1975 nopts = self.mypg.nset.intfc.get_ngcgui_options() 1976 image_file = find_image(sub_file) 1977 if image_file: 1978 img = sized_image(image_file) 1979 if ( (not image_file) 1980 or (nopts is not None and 'noiframe' in nopts) 1981 or mypg.imageoffpage 1982 ): 1983 # show all controls 1984 bx = gtk.FILL|gtk.EXPAND; by = gtk.FILL|gtk.EXPAND 1985 tactions.attach(self.breread, 0,1,1,2,xoptions=bx,yoptions=by) 1986 tactions.attach(lfctf, 1,2,1,2,xoptions=bx,yoptions=by) 1987 cpbox.pack_start(lcontrol,expand=0,fill=0,padding=0) 1988 cpbox.pack_start(tfiles, expand=0,fill=0,padding=0) 1989 cpbox.pack_start(tchkbs, expand=0,fill=0,padding=0) 1990 if image_file: 1991 self.separate_image(img,sub_file,show=False) 1992 mypg.imageoffpage = True 1993 else: 1994 bx = gtk.FILL|gtk.EXPAND; by = gtk.FILL|gtk.EXPAND 1995 tactions.attach(lfctf, 0,2,1,2,xoptions=bx,yoptions=by) 1996 # show image instead of controls 1997 if image_file: 1998 cpbox.pack_start(img,expand=0,fill=0,padding=0) 1999 mypg.imageoffpage = False 2000 cpbox.pack_start(tactions,expand=1,fill=1,padding=0) 2001 cpbox.show() 2002 frame.add(cpbox) 2003 2004 def separate_image(self,img,fname='',show=True): 2005 self.mypg.imgw = gtk.Window(gtk.WINDOW_TOPLEVEL) 2006 w = self.mypg.imgw 2007 w.hide() 2008 w.iconify() 2009 w.set_title(os.path.basename(fname)) 2010 w.add(img) 2011 if g_alive: w.connect("destroy",self.wdestroy) 2012 if show: 2013 w.show_all() 2014 w.deiconify() 2015 2016 def wdestroy(self,widget): 2017 del self.mypg.imgw 2018 2019 def set_message(self,msg): 2020 self.lmsg.set_label(msg) 2021 2022 def reread_files(self): 2023 vprint('REREAD') 2024 # user can edit file and use button to reread it 2025 if self.mypg.sub_file == '': 2026 vprint('reread_files NULL subfile') 2027 return False 2028 self.mypg.fset.pre_data.read() 2029 self.mypg.fset.sub_data.re_read() # handle ngc or gcmc 2030 self.mypg.fset.pst_data.read() 2031 2032 self.mypg.update_onepage('pre',self.mypg.pre_file) 2033 self.mypg.update_onepage('sub',self.mypg.sub_file) 2034 self.mypg.update_onepage('pst',self.mypg.pst_file) 2035 self.set_message(_('Reread files')) 2036 return True # success 2037 2038 def restart_features(self): 2039 try: 2040 type(self.mypg.savesec) # test for existence 2041 self.mypg.savesec = [] 2042 except AttributeError: 2043 pass 2044 self.mypg.feature_ct = 0 2045 self.lfct.set_label(str(self.mypg.feature_ct)) 2046 self.mypg.savesec = [] 2047 self.mypg.update_tab_label('default') 2048 self.set_message(_('Restart')) 2049 2050 def toggle_autosend(self, widget): 2051 self.mypg.autosend = (0,1)[widget.get_active()] 2052 self.set_message(_('Toggle autosend %s ') % str(self.mypg.autosend)) 2053 2054 def toggle_expandsub(self, widget): 2055 self.mypg.expandsub = (0,1)[widget.get_active()] 2056 self.set_message(_('Toggle expandsub %s') % str(self.mypg.expandsub)) 2057 2058 def checkb_toggle(self, widget, var): 2059 print('1T',var,type(var)) 2060 var = (0,1)[widget.get_active()] 2061 print('2T',var,type(var)) 2062 2063 def create_feature(self): 2064 m=self.mypg 2065 p=self.mypg.fset 2066 2067 fpre,fprestat = m.nset.intfc.find_file_in_path(m.pre_file) 2068 fsub,fsubstat = m.nset.intfc.find_file_in_path(m.sub_file) 2069 fpst,fpststat = m.nset.intfc.find_file_in_path(m.pst_file) 2070 2071 if fsubstat == 'NULLFILE': 2072 vprint('create_feature: NULLFILE') 2073 return 2074 # the test for NOPATH is for special cases 2075 if ( (fpre != p.pre_data.pre_file) and fprestat != 'NOPATH' 2076 or (fsub != p.sub_data.sub_file) and fsubstat != 'NOPATH' 2077 or (fpst != p.pst_data.pst_file) and fpststat != 'NOPATH' 2078 ): 2079 print('\nUSER changed filename entry without loading\n') 2080 2081 try: 2082 type(self.mypg.savesec) # test for existence 2083 except AttributeError: 2084 self.mypg.savesec = [] 2085 2086 2087 self.set_message(_('Create feature')) 2088 # update for current entry filenames 2089 p.pre_data = PreFile(m.pre_file) # may be '' 2090 p.sub_data = SubFile(m.sub_file) # error for '' 2091 p.pst_data = PstFile(m.pst_file) # may be '' 2092 2093 if p.sub_data.pdict.has_key('isgcmc'): 2094 stat = self.savesection_gcmc() 2095 else: 2096 stat = self.savesection_ngc() 2097 2098 if stat: 2099 if m.feature_ct > 0: 2100 self.mypg.update_tab_label('multiple') 2101 else: 2102 self.mypg.update_tab_label('created') 2103 2104 m.feature_ct = m.feature_ct + 1 2105 self.lfct.set_label(str(m.feature_ct)) 2106 2107 self.set_message(_('Created Feature #%d') % m.feature_ct) 2108 else: 2109 #print "savesection fail" 2110 pass 2111 2112 def savesection_ngc(self): 2113 m=self.mypg 2114 p=self.mypg.fset 2115 force_expand = False 2116 # if file not in path and got this far, force expand 2117 fname,stat = m.nset.intfc.find_file_in_path(m.sub_file) 2118 2119 if stat == 'NOTFOUND': 2120 force_expand = True 2121 user_message(mtype=gtk.MESSAGE_INFO 2122 ,title=_('Expand Subroutine') 2123 ,msg=_('The selected file') + ':\n\n' 2124 + '%s\n\n' 2125 + _('is not in the linuxcnc path\n' 2126 'Expanding in place.\n\n' 2127 'Note: linuxcnc will fail if it calls\n' 2128 'subfiles that are not in path\n') 2129 % fname) 2130 2131 try: 2132 self.mypg.savesec.append( 2133 SaveSection(mypg = self.mypg 2134 ,pre_info = p.pre_data 2135 ,sub_info = p.sub_data 2136 ,pst_info = p.pst_data 2137 ,force_expand = force_expand 2138 ) 2139 ) 2140 except ValueError: 2141 dprint('SAVESECTION_ngc: failed') 2142 return True # success 2143 2144 def savesection_gcmc(self): 2145 m=self.mypg 2146 p=self.mypg.fset 2147 intfc = self.mypg.nset.intfc 2148 2149 global g_gcmc_exe 2150 if g_gcmc_exe is None: 2151 if not find_gcmc(): 2152 return False ;# fail 2153 xcmd = [] 2154 xcmd.append(g_gcmc_exe) 2155 2156 global g_gcmc_funcname 2157 global g_gcmc_id 2158 g_gcmc_id += 1 2159 # gcmc chars in funcname: (allowed: [a-z0-9_-]) 2160 funcname = "%s_%02d"%(g_gcmc_funcname,g_gcmc_id) 2161 2162 p.sub_data.pdict['subname'] = funcname 2163 2164 include_path = intfc.get_gcmc_include_path() 2165 if include_path is not None: 2166 for dir in include_path.split(":"): 2167 xcmd.append("--include") 2168 xcmd.append(os.path.expanduser(dir)) 2169 # maybe: xcmd.append("--include") 2170 # maybe: xcmd.append(os.path.dirname(m.sub_file)) 2171 # note: gcmc also adds the current directory 2172 # to the search path as last entry. 2173 2174 outdir = g_searchpath[0] # first in path 2175 ofile = os.path.join(outdir,funcname) + ".ngc" 2176 2177 xcmd.append("--output") 2178 xcmd.append(ofile) 2179 2180 xcmd.append('--gcode-function') 2181 xcmd.append(funcname) 2182 2183 for opt in p.sub_data.gcmc_opts: 2184 splitopts = opt.split(' ') 2185 xcmd.append(str(splitopts[0])) 2186 if len(splitopts) > 1: 2187 xcmd.append(str(splitopts[1])) # presumes only one token 2188 2189 2190 for k in p.sub_data.ndict.keys(): 2191 #print 'k=',k,p.sub_data.ndict[k] 2192 name,dvalue,comment = p.sub_data.ndict[k] 2193 # make all entry box values explicitly floating point 2194 try: 2195 fvalue = str(float(m.efields.pentries[k].getentry())) 2196 except ValueError: 2197 user_message(mtype=gtk.MESSAGE_ERROR 2198 ,title='gcmc input ERROR' 2199 ,msg=_('<%s> must be a number' % m.efields.pentries[k].getentry()) 2200 ) 2201 return False ;# fail 2202 xcmd.append('--define=' + name + '=' + fvalue) 2203 2204 xcmd.append(m.sub_file) 2205 print "xcmd=",xcmd 2206 e_message = ".*Runtime message\(\): *(.*)" 2207 e_warning = ".*Runtime warning\(\): *(.*)" 2208 e_error = ".*Runtime error\(\): *(.*)" 2209 2210 s = subprocess.Popen(xcmd 2211 ,stdout=subprocess.PIPE 2212 ,stderr=subprocess.PIPE 2213 ) 2214 sout,eout = s.communicate() 2215 m_txt = "" 2216 w_txt = "" 2217 e_txt = "" 2218 compile_txt = "" 2219 2220 if eout: 2221 if (len(eout) > g_max_msg_len): 2222 # limit overlong, errant msgs 2223 eout = eout[0:g_max_msg_len] + "..." 2224 for line in eout.split("\n"): 2225 r_message = re.search(e_message,line) 2226 r_warning = re.search(e_warning,line) 2227 r_error = re.search(e_error,line) 2228 if r_message: 2229 m_txt += r_message.group(1) + "\n" 2230 elif r_warning: 2231 w_txt += r_warning.group(1) + "\n" 2232 elif r_error: 2233 e_txt += r_error.group(1) + "\n" 2234 else: 2235 compile_txt += line 2236 2237 if m_txt != "": 2238 user_message(mtype=gtk.MESSAGE_INFO 2239 ,title='gcmc INFO' 2240 ,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,m_txt) 2241 ) 2242 if w_txt != "": 2243 user_message(mtype=gtk.MESSAGE_WARNING 2244 ,title='gcmc WARNING' 2245 ,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,w_txt) 2246 ) 2247 if e_txt != "": 2248 user_message(mtype=gtk.MESSAGE_ERROR 2249 ,title='gcmc ERROR' 2250 ,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,e_txt) 2251 ) 2252 if compile_txt != "": 2253 user_message(mtype=gtk.MESSAGE_ERROR 2254 ,title='gcmc Compile ERROR' 2255 ,msg="gcmc File:%s"%(compile_txt) 2256 ) 2257 if s.returncode: 2258 return False ;# fail 2259 2260 self.mypg.savesec.append( 2261 SaveSection(mypg = self.mypg 2262 ,pre_info = p.pre_data 2263 ,sub_info = p.sub_data 2264 ,pst_info = p.pst_data 2265 ,force_expand = False # never for gcmc 2266 ) 2267 ) 2268 return True # success 2269 2270 def finalize_features(self): 2271 mypg = self.mypg 2272 nb = self.mypg.mynb 2273 nset = self.mypg.nset 2274 if mypg.feature_ct <= 0: 2275 msg = _('No features specified on this page') 2276 self.set_message(msg) 2277 user_message(mtype=gtk.MESSAGE_WARNING 2278 ,title='No Features' 2279 ,msg=msg) 2280 return 2281 2282 if len(mypg.savesec) == 0: 2283 msg = 'finalize_features: Unexpected: No features' 2284 self.set_message(_('No features')) 2285 raise ValueError,msg 2286 return 2287 txt = '' 2288 plist = [] 2289 sequence = "" 2290 # these are in left-to-right order 2291 for pno in range(nset.startpage_idx,nb.get_n_pages()): 2292 npage = nb.get_nth_page(pno) 2293 #Using EventBox for tabpage labels: dont use get_tab_label_text() 2294 pg = nset.pg_for_npage[npage] 2295 ltxt = pg.the_lbl.get_text() 2296 howmany = len(pg.savesec) 2297 if howmany > 0: 2298 plist.append(pg) 2299 sequence = sequence + " " + ltxt 2300 txt = txt + "%s has %d features\n" % (ltxt,howmany) 2301 vprint(txt) 2302 2303 if len(plist) > 1: 2304 msg = (_('Finalize all Tabs?\n\n' 2305 'No: Current page only\n' 2306 'Yes: All pages\n' 2307 'Cancel: Nevermind\n\n' 2308 'Order:' 2309 ) 2310 + '\n<' + sequence + '>\n\n' 2311 'You can Cancel and change the order with the\n' 2312 'Forward and Back buttons\n' 2313 ) 2314 popup = gtk.Dialog(title='Page Selection' 2315 ,parent=None 2316 ,flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT 2317 ,buttons=(gtk.STOCK_NO, gtk.RESPONSE_NO 2318 ,gtk.STOCK_YES, gtk.RESPONSE_YES 2319 ,gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL 2320 ) 2321 ) 2322 finbox = popup.get_content_area() 2323 l = gtk.Label(msg) 2324 finbox.pack_start(l) 2325 popup.show_all() 2326 ans = popup.run() 2327 popup.destroy() 2328 if ans == gtk.RESPONSE_YES: 2329 pass # use plist for all pages 2330 elif ans == gtk.RESPONSE_NO: 2331 pno = self.mypg.mynb.get_current_page() 2332 npage = nb.get_nth_page(pno) 2333 plist = [nset.pg_for_npage[npage]] 2334 elif ( ans == gtk.RESPONSE_CANCEL 2335 or ans == gtk.REXPONSE_DELETE_EVENT): # window close 2336 return # do nothing 2337 else: 2338 raise ValueError, 'finalize_features:unknown ans<%d>'%ans 2339 2340 # make a unique filename 2341 # (avoids problems with gremlin ignoring new file with same name) 2342 global g_auto_file_ct 2343 autoname = nset.auto_file 2344 dirname = os.path.realpath(os.path.dirname(autoname)) 2345 basename = str(g_auto_file_ct) + "." + os.path.basename(autoname) 2346 tmpname = os.path.join(dirname,basename) 2347 if os.path.exists(tmpname): 2348 os.remove(tmpname) 2349 # hack: alternate names (0,1) to force gremlin file loading 2350 # and touchy filechooser updates 2351 g_auto_file_ct = (g_auto_file_ct + 1)%2 2352 basename = str(g_auto_file_ct) + "." + os.path.basename(autoname) 2353 tmpname = os.path.join(dirname,basename) 2354 self.mypg.nset.last_file = tmpname 2355 2356 savename = None 2357 f = open(tmpname,'w') 2358 nopts = self.mypg.nset.intfc.get_ngcgui_options() 2359 if (('nom2' in nopts) or g_nom2): 2360 f.write("%\n") 2361 f.write("(%s: nom2 option)\n" % g_progname) 2362 2363 featurect = 0; features_total=0 2364 for pg in plist: 2365 features_total = features_total + len(pg.savesec) 2366 for pg in plist: 2367 ct = self.write_to_file(f,pg,featurect,features_total) 2368 featurect += ct 2369 pg.feature_ct = 0 2370 self.lfct.set_label(str(pg.feature_ct)) 2371 pg.savesec = [] 2372 2373 if (('nom2' in nopts) or g_nom2): 2374 f.write("%\n") 2375 else: 2376 f.write("(%s: m2 line added) m2 (g54 activated)\n" % g_progname) 2377 f.close() 2378 2379 user_must_save = True # disprove with send_function 2380 title_message = '' 2381 if self.mypg.autosend: 2382 if g_send_function(tmpname): 2383 user_must_save = False 2384 self.set_message(_('Finalize: Sent file')) 2385 save_a_copy(tmpname) 2386 print('%s:SENT: %s' % (g_progname,tmpname)) 2387 print('%s:SENT:using: %s' % (g_progname,g_send_function.__name__)) 2388 else: 2389 title_message = ( 2390 _('Sending file failed using function: <%s>, user must save') 2391 % g_send_function.__name__) 2392 self.set_message(_('Finalize: Sent file failed')) 2393 print('%s:SAVEDFILE: after send failed: %s' 2394 % (g_progname,tmpname)) 2395 2396 if user_must_save: 2397 fname = os.path.abspath(nset.auto_file) 2398 if self.mypg.nset.last_file is not None: 2399 fname = self.mypg.nset.last_file # last user choice 2400 savename = file_save(fname,title_message) # user may change name 2401 if savename is not None: 2402 shutil.move(tmpname,savename) 2403 save_a_copy(savename) 2404 self.mypg.nset.last_file = savename 2405 2406 for pg in plist: 2407 pg.cpanel.restart_features() 2408 pg.update_tab_label('default') 2409 2410 global g_label_id 2411 g_label_id = 0 # reinitialize 2412 return 2413 2414 def write_to_file(self,file,pg,featurect,features_total): 2415 ct = 0 2416 for i in range(0,len(pg.savesec) ): 2417 ct += 1 2418 for l in pg.savesec[i].sdata: 2419 if l.find("#<_feature:>") == 0: 2420 file.write( 2421 "(%s: feature line added) #<_feature:> = %d\n"\ 2422 % (g_progname,featurect)) 2423 featurect += 1 2424 file.write( 2425 "(%s: remaining_features line added) " 2426 " #<_remaining_features:> = %d\n"\ 2427 % (g_progname,features_total - featurect)) 2428 else: 2429 file.write(l) 2430 return(ct) 2431 2432 def file_choose(self,widget,ftype): 2433 mydiag = CandidateDialog(ftype=ftype) 2434 2435 while True: 2436 response = mydiag.run() 2437 fname,errmsg = mydiag.get_file_result() 2438 if response == gtk.RESPONSE_ACCEPT: 2439 vprint('file_choose: ACCEPT') 2440 self.mypg.cpanel.set_message(_('file_choose ACCEPT')) 2441 pass 2442 elif response == gtk.RESPONSE_REJECT: 2443 self.mypg.cpanel.set_message(_('file_choose REJECT')) 2444 vprint('file_choose: REJECT') 2445 mydiag.destroy() 2446 return None 2447 elif response == gtk.RESPONSE_NO: 2448 self.mypg.cpanel.set_message(_('No File')) 2449 fname = 'nofile' # allow pre,pst nofile 2450 vprint('file_choose: No File') 2451 else: 2452 self.mypg.cpanel.set_message(_('file_choose OTHER')) 2453 mydiag.destroy() 2454 raise ValueError,_('file_choose OTHER %s') % str(response) 2455 return None 2456 2457 if fname == 'TRYAGAIN': 2458 user_message(mtype=gtk.MESSAGE_INFO 2459 ,title=_('Try Again') 2460 ,msg=errmsg 2461 ) 2462 continue 2463 break 2464 mydiag.destroy() 2465 2466 if ftype == 'pre': 2467 self.mypg.fset.pre_file = fname 2468 elif ftype == 'sub': 2469 self.mypg.fset.sub_file = fname 2470 elif ftype == 'pst': 2471 self.mypg.fset.pst_file = fname 2472 else: 2473 raise ValueError,"file_choose ftype?",ftype 2474 2475 # None for no file selected, null out field could be useful 2476 if not fname: 2477 self.mypg.cpanel.set_message(_('file_choose no file?')) 2478 return None 2479 2480 if ftype == 'pre': 2481 if fname == 'nofile': 2482 fname = '' 2483 self.pre_entry.set_text(os.path.basename(fname)) 2484 self.mypg.update_onepage('pre',fname) 2485 elif ftype == 'sub': 2486 image_file = find_image(fname) 2487 if image_file: 2488 img = sized_image(image_file) 2489 self.separate_image(img,fname,show=True) 2490 self.mypg.imageoffpage = True 2491 if self.mypg.update_onepage('sub',fname): 2492 self.sub_entry.set_text(os.path.basename(fname)) 2493 elif ftype == 'pst': 2494 if fname == 'nofile': 2495 fname = '' 2496 self.pst_entry.set_text(os.path.basename(fname)) 2497 self.mypg.update_onepage('pst',fname) 2498 else: 2499 raise ValueError,'file_choose:Unexpected ftype <%s>' %ftype 2500 2501 self.mypg.cpanel.set_message(_('Read %s') % os.path.basename(fname)) 2502 return 2503 2504 2505 class OnePg(): 2506 """OnePg: ngcgui info for one tab page""" 2507 def __init__(self 2508 ,pre_file 2509 ,sub_file 2510 ,pst_file 2511 ,mynb 2512 ,nset 2513 ,imageoffpage=False 2514 ): 2515 2516 self.imageoffpage = imageoffpage # for clone of Custom pages 2517 self.garbagecollect = False 2518 self.key_enable = False 2519 2520 self.pre_file,stat = nset.intfc.find_file_in_path(pre_file) 2521 self.sub_file,stat = nset.intfc.find_file_in_path(sub_file) 2522 self.pst_file,stat = nset.intfc.find_file_in_path(pst_file) 2523 2524 self.nset = nset 2525 self.mynb = mynb 2526 2527 self.autosend = nset.autosend 2528 self.expandsub = nset.expandsub 2529 2530 self.feature_ct = 0 2531 self.savesec = [] 2532 2533 self.cpanel = ControlPanel(mypg=self 2534 ,pre_file=self.pre_file 2535 ,sub_file=self.sub_file 2536 ,pst_file=self.pst_file 2537 ) 2538 2539 bw = 1 2540 2541 #bremove = gtk.Button(_('Remove')) 2542 bremove = gtk.Button(stock=gtk.STOCK_DELETE) 2543 bremove.set_border_width(bw) 2544 if g_alive: bremove.connect("clicked", lambda x: self.remove_page()) 2545 2546 #bclone = gtk.Button(_('Clone')) 2547 bclone = gtk.Button(stock=gtk.STOCK_ADD) 2548 bclone.set_border_width(bw) 2549 if g_alive: bclone.connect("clicked", lambda x: self.clone_page()) 2550 2551 #bnew = gtk.Button(_('New')) 2552 bnew = gtk.Button(stock=gtk.STOCK_NEW) 2553 bnew.set_border_width(bw) 2554 if g_alive: bnew.connect("clicked", lambda x: self.new_empty_page()) 2555 2556 #bmoveleft = gtk.Button(_('<==Move')) 2557 bmoveleft = gtk.Button(stock=gtk.STOCK_GO_BACK,label='') 2558 bmoveleft.set_border_width(bw) 2559 if g_alive: bmoveleft.connect("clicked", lambda x: self.move_left()) 2560 2561 #bmoveright = gtk.Button(_('Move==>')) 2562 bmoveright = gtk.Button(stock=gtk.STOCK_GO_FORWARD,label='') 2563 bmoveright.set_border_width(bw) 2564 if g_alive: bmoveright.connect("clicked", lambda x: self.move_right()) 2565 2566 # stock buttons notwork with mod_font_by_category 2567 #mod_font_by_category(bremove) 2568 #mod_font_by_category(bclone) 2569 #mod_font_by_category(bnew) 2570 #mod_font_by_category(bmoveleft) 2571 #mod_font_by_category(bmoveright) 2572 2573 tabarrange_buttons = gtk.HBox() # main buttons 2574 2575 self.mtable = gtk.Table(rows=1, columns=2, homogeneous=0) 2576 bx = gtk.FILL|gtk.EXPAND; by = 0 2577 no_of_parms = g_max_parm 2578 2579 2580 self.make_fileset() 2581 no_of_parms = self.fset.sub_data.pdict['lastparm'] 2582 2583 self.efields = EntryFields(no_of_parms) # uses MultipleParmEntries item 2584 2585 self.fill_entrypage(emode='initial') 2586 2587 bx = 0; by = gtk.FILL|gtk.EXPAND 2588 self.mtable.attach(self.cpanel.box, 0,1,0,1,xoptions=bx,yoptions=by) 2589 2590 bx = gtk.FILL; by = gtk.FILL|gtk.EXPAND 2591 bx = gtk.FILL|gtk.EXPAND ; by = gtk.FILL|gtk.EXPAND 2592 entrystuff = self.efields.get_box() 2593 self.mtable.attach(entrystuff, 1,2,0,1,xoptions=bx,yoptions=by) 2594 2595 tbtns = TestButtons(mypg=self) # TestButtons 2596 2597 nopts = nset.intfc.get_ngcgui_options() 2598 2599 if (nopts is None) or ('noremove' not in nopts): 2600 tabarrange_buttons.pack_start(bremove) 2601 2602 if (nopts is None) or ('nonew' not in nopts): 2603 tabarrange_buttons.pack_start(bclone) 2604 tabarrange_buttons.pack_start(bnew) 2605 2606 tabarrange_buttons.pack_start(bmoveleft) 2607 tabarrange_buttons.pack_start(bmoveright) 2608 2609 op_box = gtk.VBox() 2610 2611 if g_tab_controls_loc == 'top': 2612 op_box.pack_start(tabarrange_buttons,expand=0,fill=0,padding=0) 2613 elif g_tab_controls_loc == 'bottom': 2614 op_box.pack_end(tabarrange_buttons,expand=0,fill=0,padding=0) 2615 else: 2616 raise ValueError,(g_progname 2617 + ' unknown tab_controls_loc %s' % g_tab_controls_loc) 2618 2619 op_box.pack_start(self.linfof, expand=0,fill=0,padding=0) 2620 op_box.pack_start(self.mtable, expand=1,fill=1,padding=0) 2621 2622 if g_debug: 2623 op_box.pack_end(tbtns.box, expand=0,fill=0,padding=5) 2624 op_box.show_all() 2625 2626 self.pgbox = gtk.EventBox() 2627 self.pgbox.add(op_box) 2628 self.pgbox.show_all() 2629 2630 if g_alive: self.pgbox.connect('event',self.any_event) 2631 2632 # establish size with max no of entries 2633 ww,hh = self.mtable.size_request() 2634 #print('size for mtable:',ww,hh) 2635 #self.mtable.set_size_request(ww,hh) 2636 2637 lastpidx = self.fset.sub_data.pdict['lastparm'] 2638 2639 gobject.timeout_add_seconds(g_check_interval,self.periodic_check) 2640 2641 2642 def periodic_check(self): 2643 try: 2644 for i in ('pre','sub','pst'): 2645 o_entry = getattr(self.cpanel,i + '_entry') 2646 if o_entry.get_text().strip() == '': continue 2647 o_file = getattr(self, i + '_file') 2648 o_data = getattr(self.fset, i + '_data') 2649 o_md5 = getattr(o_data, 'md5') 2650 o_mtime = getattr(o_data, 'mtime') 2651 if ( (o_mtime != None) 2652 and (o_mtime == os.path.getmtime(o_file))): 2653 state = o_entry.get_state() 2654 o_entry.modify_text(state,black_color) 2655 continue 2656 2657 if (o_md5 != md5sum(o_file)): 2658 #print('%s,%s>' % (o_md5,md5sum(o_file))) 2659 #print(i,'CHANGED md5',o_file,o_md5) 2660 state = o_entry.get_state() 2661 o_entry.modify_text(state,purple_color) 2662 else: 2663 #print(i,'SAME md5',o_file,o_md5) 2664 o_entry.modify_text(gtk.STATE_NORMAL,black_color) 2665 except OSError, detail: 2666 print(_('%s:periodic_check:OSError:%s') % detail) 2667 pass # continue without checks after showing message 2668 except Exception, detail: 2669 exception_show(Exception,detail,'periodic_check') 2670 raise Exception, detail # reraise 2671 if self.garbagecollect: 2672 return False # False to norepeat (respond to del for self) 2673 return True # True to repeat 2674 2675 def any_event(self,widget,event): 2676 if event.type == gtk.gdk.ENTER_NOTIFY: 2677 #widget.set_can_focus(True) 2678 self.key_enable = True 2679 #print('ENTER enable') 2680 return 2681 elif event.type == gtk.gdk.LEAVE_NOTIFY: 2682 #print "LEAVE can, is",widget.is_focus(),widget.get_can_focus(),'\n' 2683 if widget.get_can_focus(): 2684 #widget.set_can_focus(False) 2685 self.key_enable = False 2686 #print('LEAVE disable') 2687 return 2688 elif event.type == gtk.gdk.EXPOSE: 2689 widget.grab_focus() 2690 return 2691 elif event.type == gtk.gdk.KEY_PRESS: 2692 if not self.key_enable: 2693 #print('IGNORE') 2694 return 2695 keyname = gtk.gdk.keyval_name(event.keyval) 2696 kl = keyname.lower() 2697 # ignore special keys (until they modify) 2698 if kl in ['alt_r','alt_l'] : return 2699 if kl in ['control_r','control_l'] : return 2700 if kl in ['shift_r','shift_l'] : return 2701 pre = '' 2702 if event.state & gtk.gdk.CONTROL_MASK: 2703 pre = "Control-" 2704 elif event.state & gtk.gdk.MOD1_MASK: 2705 pre = "Alt-" 2706 elif event.state & gtk.gdk.SHIFT_MASK: 2707 pre = "Shift-" 2708 k = pre + keyname 2709 #print("%10s (%03d=%#2X)" % (k, event.keyval,event.keyval)) 2710 self.handle_key(k) 2711 return False # allow other handlers 2712 2713 def handle_key(self,k): 2714 if k == 'Control-d': 2715 self.make_fileset() 2716 self.fill_entrypage(emode='initial') 2717 if k == 'Control-a': 2718 self.cpanel.bautosend.clicked() 2719 if k == 'Control-#': 2720 self.cpanel.bexpand.clicked() 2721 if k == 'Control-k': 2722 self.show_special_keys() 2723 if k == 'Control-r': 2724 # was ctrl-p,P,r in ngcgui 2725 self.cpanel.breread.clicked() 2726 if k == 'Control-e': 2727 self.edit_any_file(self.nset.last_file,'last') 2728 if k == 'Control-E': 2729 self.cpanel.bexpand.clicked() 2730 if k == 'Control-u': 2731 self.edit_std_file('sub') 2732 if k == 'Control-U': 2733 self.edit_std_file('pre') 2734 #else: 2735 # print('handle_key: k=',k) 2736 return False # False: allow more handlers 2737 2738 def edit_any_file(self,fname,ftype=''): 2739 if not fname: 2740 user_message(mtype=gtk.MESSAGE_ERROR 2741 ,title=_('No file') 2742 ,msg=_('No %s file specified') % ftype 2743 ) 2744 return 2745 subprocess.Popen([self.nset.intfc.editor, fname]) 2746 2747 def edit_std_file(self,which): 2748 o_file = getattr(self, which + '_file') 2749 self.edit_any_file(o_file,which) 2750 2751 #NB some key bindings are claimed on touchy 2752 def show_special_keys(self): 2753 msg = [] 2754 msg.append('Control-a ' + _('Toggle autosend') + '\n') 2755 msg.append('Control-e ' + _('Edit last result file') + '\n') 2756 msg.append('Control-E ' + _('Toggle expandsubroutines') + '\n') 2757 msg.append('Control-d ' + _('Set Entry defaults') + '\n') 2758 msg.append('Control-k ' + _('Show keys (this)') + '\n') 2759 msg.append('Control-r ' + _('Reread files') + '\n') 2760 msg.append('Control-u ' + _('Edit sub file') + '\n') 2761 msg.append('Control-U ' + _('Edit preamble file') + '\n') 2762 user_message(mtype=gtk.MESSAGE_INFO 2763 ,title=_('Special Keys') 2764 ,flags=0 #still MODAL ?? 2765 ,msg=msg) 2766 2767 def set_page_label(self,lbl): 2768 self.lbl = lbl 2769 2770 def save_onepage_tablabel(self,eb_lbl,the_lbl): 2771 self.eb_lbl = eb_lbl 2772 self.the_lbl = the_lbl 2773 2774 def update_tab_label(self,umode): 2775 if umode == 'created': 2776 newcolor = fg_created_color 2777 newstyle = g_lbl_style_created 2778 elif umode == 'multiple': 2779 newcolor = fg_multiple_color 2780 newstyle = g_lbl_style_multiple 2781 elif umode == 'default': 2782 newcolor = fg_normal_color 2783 newstyle = g_lbl_style_default 2784 else: 2785 newstyle = g_lbl_style_default 2786 newcolor = fg_normal_color 2787 2788 self.eb_lbl.set_style(newstyle) 2789 self.the_lbl.modify_fg(gtk.STATE_NORMAL, newcolor) 2790 self.the_lbl.modify_fg(gtk.STATE_ACTIVE, newcolor) 2791 2792 def make_fileset(self): 2793 try: 2794 self.fset = FileSet(pre_file=self.pre_file 2795 ,sub_file=self.sub_file 2796 ,pst_file=self.pst_file 2797 ) 2798 except OSError,detail: 2799 print(_('%s:make_fileset:%s' % (g_progname,detail) )) 2800 raise OSError,detail # reraise 2801 2802 def fill_entrypage(self,emode='initial'): 2803 self.efields.set_parm_entries(self.fset,emode) 2804 2805 try: 2806 type(self.info_label) # test for existence 2807 except AttributeError: 2808 self.info_label = gtk.Label() 2809 self.linfof = gtk.Frame() 2810 self.linfof.set_shadow_type(gtk.SHADOW_IN) 2811 self.linfof.set_border_width(2) 2812 self.linfof.add(self.info_label) 2813 2814 self.info_label.set_label(self.fset.sub_data.pdict['info']) 2815 self.info_label.set_alignment(xalign=0.0,yalign=0.5) # left aligned 2816 self.cpanel.set_message(_('Set Entry defaults')) 2817 2818 def clear_entrypage(self): 2819 self.efields.clear_parm_entries() 2820 self.info_label.set_label('') 2821 2822 def update_onepage(self,type,fname): 2823 vprint('UPDATE_PAGE %s file=%s' % (type,fname)) 2824 if type == 'pre': 2825 foundname,stat = self.nset.intfc.find_file_in_path(fname) 2826 if stat == 'NOTFOUND': 2827 self.clear_entries('pre') 2828 return 2829 self.pre_file = foundname 2830 self.fset.pre_data = PreFile(self.pre_file) 2831 elif type == 'sub': 2832 foundname,stat = self.nset.intfc.find_file_in_path(fname) 2833 if stat == 'NOTFOUND': 2834 self.clear_entries('sub') 2835 return 2836 self.sub_file = foundname 2837 try: 2838 self.make_fileset() 2839 lastparm = self.fset.sub_data.pdict['lastparm'] 2840 self.efields.make_entryfields(lastparm) # update_onepage 2841 self.fill_entrypage() 2842 self.info_label.set_label(self.fset.sub_data.pdict['info']) 2843 lbltxt = self.fset.sub_data.pdict['subname'] 2844 lbltxt = self.nset.make_unique_tab_name(lbltxt) 2845 self.the_lbl.set_text(lbltxt) 2846 return True 2847 except Exception, detail: 2848 exception_show(Exception,detail,'update_onepage') 2849 return False 2850 elif type == 'pst': 2851 foundname,stat = self.nset.intfc.find_file_in_path(fname) 2852 if stat == 'NOTFOUND': 2853 self.clear_entries('pst') 2854 return 2855 self.pst_file = foundname 2856 self.fset.pst_data = PstFile(self.pst_file) 2857 else: 2858 raise ValueError,'update_onepage unexpected type <%s>' % type 2859 2860 return 2861 2862 def clear_entries(self,fmode): 2863 if fmode == 'pre': 2864 self.pre_file = '' 2865 self.cpanel.pre_entry.set_text('') 2866 self.fset.pre_data.clear() 2867 elif fmode == 'sub': 2868 self.sub_file = '' 2869 self.cpanel.sub_entry.set_text('') 2870 self.clear_entrypage() 2871 self.fset.sub_data.clear() 2872 elif fmode == 'pst': 2873 self.pst_file = '' 2874 self.cpanel.pst_entry.set_text('') 2875 self.fset.pst_data.clear() 2876 else: 2877 raise ValueError,'clear_entries:unexpected fmode= %s' % fmode 2878 2879 def move_left(self): 2880 page_idx = self.mynb.get_current_page() 2881 page_ct = self.mynb.get_n_pages() 2882 page = self.mynb.get_nth_page(page_idx) 2883 new_pg_idx = page_idx - 1 2884 if new_pg_idx < self.nset.startpage_idx: 2885 new_pg_idx = page_ct -1 2886 self.mynb.reorder_child(page,new_pg_idx%page_ct) 2887 2888 def move_right(self): 2889 page_idx = self.mynb.get_current_page() 2890 page_ct = self.mynb.get_n_pages() 2891 page = self.mynb.get_nth_page(page_idx) 2892 new_pg_idx = (page_idx + 1)%page_ct 2893 if new_pg_idx < self.nset.startpage_idx: 2894 new_pg_idx = self.nset.startpage_idx 2895 self.mynb.reorder_child(page,new_pg_idx%page_ct) 2896 2897 def clone_page(self): 2898 newpage = self.nset.add_page(self.pre_file 2899 ,self.sub_file 2900 ,self.pst_file 2901 ,self.imageoffpage #preserve for clone 2902 ) 2903 for idx in self.efields.pentries: 2904 ev = self.efields.pentries[idx].getentry() 2905 newpage.efields.pentries[idx].setentry(ev) 2906 2907 def new_empty_page(self): 2908 self.nset.add_page('','','') 2909 2910 def remove_page(self): 2911 page_ct = self.mynb.get_n_pages() 2912 if page_ct - self.nset.startpage_idx == 1: 2913 user_message(mtype=gtk.MESSAGE_INFO 2914 ,title=_('Remove not allowed') 2915 ,msg=_('One tabpage must remain') 2916 ) 2917 else: 2918 current_pno = self.mynb.get_current_page() 2919 npage = self.mynb.get_nth_page(current_pno) 2920 2921 self.mynb.remove_page(current_pno) 2922 thispg = self.nset.pg_for_npage[npage] 2923 thispg.garbagecollect = True 2924 del thispg 2925 del npage 2926 2927 2928 2929 class NgcGui(): 2930 """NgcGui: set of ngcgui OnePg items""" 2931 # make a set of pages in parent that depends on type(w) 2932 def __init__(self,w=None 2933 ,verbose=False 2934 ,debug=False 2935 ,noauto=False 2936 ,keyboardfile='' # None | ['default'|'yes'] | fullfilename 2937 ,tmode=0 2938 ,send_function=default_send # prototype: (fname) 2939 ,ini_file='' 2940 ,auto_file='' 2941 ,pre_file='' 2942 ,sub_files='' 2943 ,pst_file='' 2944 ,tab_controls_loc='top' # option for touchy 2945 ,control_font=None # option for touchy 2946 ,gtk_theme_name=None # option for touchy 2947 ,max_parm=None # for small display, reject some subs 2948 ,image_width=None # for small display 2949 ): 2950 2951 global g_send_function; g_send_function = send_function 2952 global g_tmode; g_tmode = tmode 2953 global g_verbose; g_verbose = verbose 2954 global g_debug; g_debug = debug 2955 2956 global g_tab_controls_loc; g_tab_controls_loc = tab_controls_loc 2957 global g_control_font; g_control_font = control_font 2958 2959 try: 2960 type(g_send_function) # test existence 2961 if g_send_function == None: 2962 g_send_function = dummy_send 2963 except AttributeError: 2964 print 'INVALID send_function, using dummy' 2965 g_send_function = dummy_send 2966 2967 if max_parm is not None: 2968 global g_max_parm 2969 g_max_parm = max_parm 2970 2971 if image_width is not None: 2972 global g_image_width 2973 if image_width > g_image_width: 2974 raise ValueError,(_('NgcGui image_width=%d too big, max=%d') 2975 % (image_width,g_image_width)) 2976 g_image_width = image_width 2977 2978 if g_max_parm > INTERP_SUB_PARAMS: 2979 raise ValueError,(_('max_parms=%d exceeds INTERP_SUB_PARAMS=%d') 2980 % (g_max_parm,INTERP_SUB_PARAMS) ) 2981 2982 ct_of_pages = 0 2983 try: 2984 import popupkeyboard 2985 import glib # for glib.GError 2986 if keyboardfile is not None: 2987 global g_popkbd 2988 if (keyboardfile in ('default','yes') ): 2989 keyboardfile = g_keyboardfile 2990 g_popkbd = popupkeyboard.PopupKeyboard(glade_file=keyboardfile 2991 ,use_coord_buttons=True 2992 ) 2993 global g_entry_height 2994 g_entry_height = g_big_height # bigger for popupkeyboard 2995 except ImportError, msg: 2996 print('\nImportError:\n%s', msg) 2997 print('keyboardfile=%s' % keyboardfile) 2998 print('popup keyboard unavailable\n') 2999 except glib.GError, msg: 3000 # can occur for toohigh version in ui file 3001 print('\nglib.GError:\n%s' % msg) 3002 print('keyboardfile=%s' % keyboardfile) 3003 print('popup keyboard unavailable\n') 3004 3005 self.last_file = None 3006 self.nb = None 3007 self.autosend = not noauto 3008 self.expandsub = False 3009 self.nextpage_idx = 0 3010 self.startpage_idx = 0 3011 self.pg_for_npage = {} 3012 if w is None: 3013 # standalone operation 3014 self.nb = gtk.Notebook() 3015 w = gtk.Window(gtk.WINDOW_TOPLEVEL) 3016 if g_alive: w.connect("destroy", gtk.main_quit) 3017 w.set_title(sys.argv[0]) 3018 w.add(self.nb) 3019 self.nb.show() 3020 w.show() 3021 elif type(w) == gtk.Frame: 3022 # demo -- embed as a notebook in a provider's frame 3023 self.nb = gtk.Notebook() 3024 w.add(self.nb) 3025 self.nb.show() 3026 w.show() 3027 elif type(w) == gtk.Notebook: 3028 # demo -- embed as additional pages in a provider's notebook 3029 self.nb = w 3030 self.startpage_idx = self.nb.get_n_pages() 3031 else: 3032 raise ValueError,'NgcGui:bogus w= %s' % type(w) 3033 3034 self.nb.set_scrollable(True) 3035 self.set_theme(w,tname=gtk_theme_name) 3036 3037 self.intfc = LinuxcncInterface(ini_file) 3038 3039 if len(self.intfc.subroutine_path) == 0: 3040 self.intfc.addto_spath( 3041 spath_from_files(pre_file,sub_files,pst_file)) 3042 if len(self.intfc.subroutine_path) != 0: 3043 user_message(mtype=gtk.MESSAGE_WARNING 3044 ,title=_('Simulated subroutine path') 3045 ,msg=_('No subroutine path available.\n' 3046 'Simulating subroutine path:\n\n') 3047 + str(self.intfc.subroutine_path) 3048 + '\n' 3049 + _('Generated results may not be usable with linuxcnc') 3050 ) 3051 if len(self.intfc.subroutine_path) == 0: 3052 if g_alive: 3053 # no message if glade designer is running: 3054 user_message(mtype=gtk.MESSAGE_ERROR 3055 ,title=_('No Subroutine Paths') 3056 ,msg='\n' + 3057 _('No paths available!\n' 3058 'Make sure there is a valid\n' 3059 ' [RS274]SUBROUTINE_PATH\n\n' 3060 ' 1) Start linuxcnc\n' 3061 'or\n' 3062 ' 2) Specify an ini file\n' 3063 'or\n' 3064 ' 3) Specify at least one subfile\n' 3065 '\n') 3066 ) 3067 sys.exit(1) 3068 3069 global g_searchpath; g_searchpath = self.intfc.subroutine_path 3070 3071 3072 # multiple pages can be specified with __init__() 3073 initsublist= [] 3074 if type(sub_files) == StringType and sub_files: 3075 initsublist.append(sub_files) 3076 else: 3077 initsublist = sub_files 3078 3079 nogo_l = [] 3080 for sub_file in initsublist: 3081 if not g_alive: continue 3082 if os.path.dirname(sub_file) in self.intfc.subroutine_path: 3083 self.add_page(pre_file,sub_file,pst_file) 3084 ct_of_pages += 1 3085 else: 3086 nogo_l.append(sub_file) 3087 if nogo_l: 3088 user_message(mtype=gtk.MESSAGE_INFO 3089 ,title=_('Cannot use files not in subroutine path') 3090 ,msg=_('Files not in subroutine path:\n') 3091 + str(nogo_l) + 3092 '\n\n' 3093 + _('Subroutine path is:\n') 3094 + str(self.intfc.subroutine_path) 3095 ) 3096 3097 nogo_l = [] 3098 # multiple pages can be specified with an ini_file 3099 sublist = self.intfc.get_subfiles() #returns list 3100 pre_file = self.intfc.get_preamble() 3101 pst_file = self.intfc.get_postamble() 3102 3103 # auto_file directory: 3104 # if specified, verify in path, give message if not 3105 # if nil 3106 # if PROGRAM_PREFIX put there 3107 # else put in cwd 3108 if auto_file: 3109 dir = os.path.abspath(os.path.dirname(auto_file)) 3110 spath = self.intfc.get_subroutine_path() 3111 try: 3112 spath.index(dir) # check that auto_file dir is in path 3113 # auto_file ok 3114 except ValueError: 3115 # it's called autofile in --help 3116 pass 3117 #user_message(mtype=gtk.MESSAGE_WARNING 3118 # ,title=_('Warning: autofile not in path') 3119 # ,msg=_('autofile==%s is not in linuxcnc\n' 3120 # 'subroutine search path:\n' 3121 # ' %s\n') % (auto_file,spath) 3122 # ) 3123 self.auto_file = auto_file 3124 else: 3125 pprefix = self.intfc.get_program_prefix() 3126 if pprefix: 3127 self.auto_file = os.path.join(pprefix,'auto.ngc') 3128 else: 3129 self.auto_file = os.path.join(os.path.curdir,'auto.ngc') 3130 3131 dprint('input for auto_file=%s\nfinal auto_file=%s' 3132 % (auto_file,self.auto_file)) 3133 3134 if pre_file is None: pre_file = '' 3135 if pst_file is None: pst_file = '' 3136 3137 # vprint('SAVE_FILE: %s' % self.auto_file) 3138 if sublist and g_alive: 3139 for sub_file in sublist: 3140 if sub_file == '""': #beware code for custom is '""' 3141 sub_file = '' 3142 try: 3143 self.add_page(pre_file,sub_file,pst_file) 3144 ct_of_pages += 1 3145 except Exception,detail: 3146 exception_show(Exception,detail,src='NgcGui init') 3147 print(_('CONTINUING without %s') % sub_file) 3148 else: 3149 if not sub_files: 3150 vprint('NgcGui: no ini_file with sublist ' 3151 'and no cmdline sub_file:' 3152 'making Custom page') 3153 self.add_page('','','') 3154 ct_of_pages += 1 3155 pass 3156 3157 self.current_page = None 3158 # self.nb.set_current_page(self.startpage_idx) 3159 # start at page 0 to respect caller's ordering 3160 self.nb.set_current_page(0) 3161 3162 if g_alive: self.nb.connect('switch-page', self.page_switched) 3163 w.show_all() 3164 3165 if ct_of_pages == 0: 3166 usage() 3167 print(_('No valid subfiles specified')) 3168 sys.exit(1) 3169 return 3170 3171 def update_fonts(self,fontname): 3172 update_fonts(fontname) 3173 3174 def set_theme(self,w,tname=None): 3175 screen = w.get_screen() 3176 settings = gtk.settings_get_for_screen(screen) 3177 if (tname is None) or (tname == "") or (tname == "Follow System Theme"): 3178 tname = settings.get_property("gtk-theme-name") 3179 settings.set_string_property('gtk-theme-name',tname,"") 3180 3181 def page_switched(self,notebook,npage,pno): 3182 if self.current_page: 3183 curpage = self.current_page 3184 if hasattr(curpage,'imgw'): 3185 w = getattr(curpage,'imgw') 3186 w.iconify() 3187 try: 3188 mypg = self.pg_for_npage[self.nb.get_nth_page(pno)] 3189 if hasattr(mypg,'imgw'): 3190 w = getattr(mypg,'imgw') 3191 w.deiconify() 3192 w.show_all() 3193 self.current_page = mypg 3194 except KeyError,msg: 3195 # can occur when embedded in providers notebook 3196 # print('page_switched: Caught KeyError') 3197 pass 3198 3199 def add_page(self,pre_file,sub_file,pst_file,imageoffpage=False): 3200 # look for gcmc on first request for .gcmc file: 3201 if os.path.splitext(sub_file)[-1] in ['.gcmc','.GCMC']: 3202 if not find_gcmc(): return None 3203 3204 self.nextpage_idx = self.nextpage_idx + 1 3205 opage = OnePg(pre_file=pre_file 3206 ,sub_file=sub_file 3207 ,pst_file=pst_file 3208 ,mynb=self.nb 3209 ,nset=self # an NgcGui set of pages 3210 ,imageoffpage=imageoffpage 3211 ) 3212 if opage.fset.sub_data.pdict['subname'] == '': 3213 ltxt = 'Custom' 3214 else: 3215 ltxt = opage.fset.sub_data.pdict['subname'] 3216 ltxt = self.make_unique_tab_name(ltxt) 3217 3218 eb_lbl = gtk.EventBox() 3219 mylbl = gtk.Label(ltxt) 3220 if g_popkbd is not None: 3221 mylbl.set_size_request(-1,g_big_height) 3222 eb_lbl.add(mylbl) 3223 mylbl.show() 3224 eb_lbl.set_style(g_lbl_style_default) 3225 3226 pno = self.nb.append_page(opage.pgbox,eb_lbl) 3227 if g_control_font is not None: 3228 mod_font_by_category(mylbl) 3229 3230 # An EventBox is needed to change bg of tabpage label 3231 # When using EventBox: 3232 # don't use get_tab_label_text() 3233 opage.save_onepage_tablabel(eb_lbl,mylbl) 3234 3235 self.pg_for_npage[self.nb.get_nth_page(pno)] = opage 3236 self.nb.set_current_page(pno) # move to the new page 3237 return opage 3238 3239 def make_unique_tab_name(self,name): 3240 l = [] 3241 if not name: return None 3242 for pno in range(self.startpage_idx,self.nb.get_n_pages()): 3243 npage = self.nb.get_nth_page(pno) 3244 pg = self.pg_for_npage[npage] 3245 # using EventBox for label, dont use get_tab_label_text() 3246 ltxt = pg.the_lbl.get_text() 3247 if ltxt.find(name) == 0: 3248 l.append(ltxt) 3249 if len(l) == 0: 3250 return(name) 3251 if len(l) == 1: 3252 return(name + '-1') 3253 last = l[-1] 3254 idx = last.find('-') 3255 return(name + '-' + str(int(last[idx+1:]) + 1) ) 3256 3257 3258 class SaveSection(): 3259 """SaveSection: lines ready for result file""" 3260 def __init__(self,mypg,pre_info,sub_info,pst_info,force_expand=False): 3261 global g_label_id 3262 g_label_id += 1 3263 self.sdata=[] 3264 3265 self.sdata.append("(%s: FEATURE %s)\n"% (g_progname,dt() )) 3266 3267 self.sdata.append("(%s: files: <%s,%s,%s>)\n" 3268 % (g_progname 3269 ,pre_info.pre_file 3270 ,sub_info.sub_file 3271 ,pst_info.pst_file 3272 ) 3273 ) 3274 3275 # note: this line will be replaced on file output with a count 3276 # that can span multiple pages 3277 self.sdata.append("#<_feature:> = 0\n") 3278 3279 self.sdata.append("(%s: preamble file: %s)\n" % ( 3280 g_progname,pre_info.pre_file)) 3281 self.sdata.extend(pre_info.inputlines) 3282 3283 emsg = '' # accumulate errors for emsg 3284 3285 calltxt = 'o<%s> call ' % sub_info.pdict['subname'] 3286 parmlist = [] 3287 tmpsdata = [] 3288 for idx in sub_info.ndict: 3289 name,dvalue,comment = sub_info.ndict[idx] 3290 value=mypg.efields.getentry_byidx(idx) 3291 try: 3292 v = float(value) 3293 except ValueError: 3294 emsg = emsg + ( 3295 _('Entry for parm %2d is not a number\n <%s>\n') 3296 % (int(idx),value)) 3297 #note: e formats not accepted by linuxcnc (like 1e2) 3298 # but using float(value) --->mmm.nnnnn everywhere 3299 # makes long call line 3300 # so try to send entry value, but if it has e, use float 3301 if 'e' in value: 3302 value = str(float(value.lower() )) 3303 3304 parmlist.append(value) 3305 if sub_info.pdict.has_key('isgcmc'): 3306 # just print value of gcmc parm embedded in gcmc result 3307 # the call requires no parms 3308 pass 3309 else: 3310 calltxt = calltxt + '[%s]' % value 3311 # these appear only for not-expandsub 3312 tmpsdata.append("(%11s = %12s = %12s)\n" % ( 3313 '#'+str(idx),name,value)) 3314 if emsg: 3315 user_message(mtype=gtk.MESSAGE_ERROR 3316 ,title=_('SaveSection Error') 3317 ,msg=emsg) 3318 mypg.cpanel.set_message(_('Failed to create feature')) 3319 raise ValueError 3320 calltxt = calltxt + '\n' 3321 # expandsub not honored for gcmc 3322 if (mypg.expandsub and sub_info.pdict.has_key('isgcmc')): 3323 print(_('expandsub not honored for gcmc file: %s')% 3324 os.path.basename(sub_info.sub_file)) 3325 mypg.expandsub = 0 3326 #--------------------------------------------------------------------- 3327 if (not mypg.expandsub) and (not force_expand): 3328 self.sdata.append("(%s: call subroutine file: %s)\n" % ( 3329 g_progname,sub_info.sub_file) ) 3330 self.sdata.append("(%s: positional parameters:)\n"% g_progname) 3331 self.sdata.extend(tmpsdata) 3332 self.sdata.append(calltxt) # call the subroutine 3333 else: 3334 # expand the subroutine in place with unique labels 3335 self.sdata.append('(Positional parameters for %s)\n' 3336 % mypg.sub_file) 3337 for i in range(0,idx): 3338 self.sdata.append(' #%d = %s\n' % (i+1,parmlist[i])) 3339 self.sdata.append('(expanded file: %s)\n' % mypg.sub_file) 3340 blank = '' 3341 idx = 0 3342 for line in sub_info.inputlines: 3343 idx += 1 3344 if line.strip() == '': 3345 continue 3346 if idx in sub_info.ldict: 3347 modlabel = sub_info.ldict[idx] 3348 if modlabel == 'ignoreme': 3349 continue 3350 modlabel = 'o<%03d%s>' % (g_label_id,modlabel) 3351 r = re.search(r'^o<(.*?)>(.*)',line) 3352 if r: 3353 modline = r.group(2) + '\n' 3354 else: 3355 print('SaveSection__init__:unexpected:',line) 3356 self.sdata.append('%11s %s' % (modlabel,modline)) 3357 else: 3358 theline = '%11s %s' % (blank,line) 3359 # hack: try to reduce long line length so linuxcnc wont 3360 # choke on files that work otherwise but fail 3361 # when expanded here 3362 # example: 246 chars observed for 3363 # qpex --> the call to qpocket uses many named parms 3364 # hardcoded for # config.h.in #define LINELEN 255 3365 # hardcoded 252 empiracally determined 3366 if len(theline) >= 252: 3367 theline = line 3368 self.sdata.append(theline) 3369 #--------------------------------------------------------------------- 3370 3371 if pst_info.inputlines: 3372 self.sdata.append("(%s: postamble file: %s)\n" % ( 3373 g_progname,pst_info.pst_file)) 3374 self.sdata.extend(pst_info.inputlines) 3375 #for line in self.sdata: 3376 # print('line:',line,) 3377 3378 3379 def usage(): 3380 print(""" 3381 Usage: 3382 %s [Options] [sub_filename] 3383 Options requiring values: 3384 [-d | --demo] [0|1|2] (0: DEMO standalone toplevel) 3385 (1: DEMO embed new notebook) 3386 (2: DEMO embed within existing notebook) 3387 [-S | --subfile sub_filename] 3388 [-p | --preamble preamble_filename] 3389 [-P | --postamble postamble_filename] 3390 [-i | --ini inifile_name] 3391 [-a | --autofile auto_filename] 3392 [-t | --test testno] 3393 [-K | --keyboardfile glade_file] (use custom popupkeyboard glade file) 3394 Solo Options: 3395 [-v | --verbose] 3396 [-D | --debug] 3397 [-N | --nom2] (no m2 terminator (use %%)) 3398 [-n | --noauto] (save but do not automatically send result) 3399 [-k | --keyboard] (use default popupkeybaord) 3400 [-s | --sendtoaxis] (send generated ngc file to axis gui) 3401 Notes: 3402 A set of files is comprised of a preamble, subfile, postamble. 3403 The preamble and postamble are optional. 3404 One set of files can be specified from cmdline. 3405 Multiple sets of files can be specified from an inifile. 3406 If --ini is NOT specified: 3407 search for a running linuxcnc and use its inifile 3408 """ % g_progname) 3409 #----------------------------------------------------------------------------- 3410 # Standalone (and demo) usage: 3411 3412 def standalone_pyngcgui(): 3413 # make widgets for test cases: 3414 top = gtk.Window(gtk.WINDOW_TOPLEVEL) 3415 top.set_title('top') 3416 hbox = gtk.HBox() 3417 top.add(hbox) 3418 l1 = gtk.Label('LABEL') 3419 hbox.pack_start(l1,expand=0,fill=0) 3420 e1 = gtk.Entry() 3421 hbox.pack_start(e1,expand=0,fill=0) 3422 e1.set_width_chars(4) 3423 f1 = gtk.Frame() 3424 hbox.pack_start(f1,expand=0,fill=0) 3425 f2 = gtk.Frame() 3426 hbox.pack_start(f2,expand=0,fill=0) 3427 3428 n = gtk.Notebook() 3429 n.set_scrollable(True) 3430 b1 = gtk.Button('b1-filler') 3431 b2 = gtk.Button('b2-filler') 3432 n.append_page(b1,gtk.Label('Mb1-filler')) 3433 n.append_page(b2,gtk.Label('Mb2-filler')) 3434 f1.add(n) 3435 top.show_all() 3436 3437 3438 demo = 0 # 0 ==> standalone operation 3439 subfilenames = '' 3440 prefilename = '' 3441 pstfilename = '' 3442 vbose = False 3443 dbg = False 3444 noauto = False 3445 keyboard = False 3446 keyboardfile = 'default' 3447 ini_file = '' 3448 auto_file = '' 3449 tmode = 0 3450 send_f = default_send 3451 try: 3452 options,remainder = getopt.getopt(sys.argv[1:] 3453 ,'a:Dd:hi:kK:Nnp:P:sS:t:v' 3454 , ['autofile' 3455 ,'demo=' 3456 ,'debug' 3457 ,'help' 3458 ,'ini=' 3459 ,'keyboard' 3460 ,'keyboardfile=' 3461 ,'noauto' 3462 ,'preamble=' 3463 ,'postamble=' 3464 ,'subfile=' 3465 ,'verbose' 3466 ,'sendtoaxis' 3467 ,'nom2' 3468 ] 3469 ) 3470 except getopt.GetoptError,msg: 3471 usage() 3472 print('\nGetoptError:%s' % msg) 3473 sys.exit(1) 3474 except Exception, detail: 3475 exception_show(Exception,detail,'__main__') 3476 sys.exit(1) 3477 for opt,arg in options: 3478 #print('#opt=%s arg=%s' % (opt,arg)) 3479 if opt in ('-h','--help'): usage(),sys.exit(0) 3480 if opt in ('-d','--demo'): demo = arg 3481 3482 3483 if opt in ('-i','--ini'): ini_file = arg 3484 if opt in ('-a','--autofile'): auto_file = arg 3485 3486 if opt in ('-p','--preamble'): prefilename=arg 3487 if opt in ('-P','--postamble'): pstfilename=arg 3488 if opt in ('-S','--subfile'): subfilenames=arg 3489 3490 if opt in ('-t','--test'): tmode=arg 3491 3492 3493 if opt in ('-k','--keyboard'): keyboard=True 3494 if opt in ('-K','--keyboardfile'): 3495 keyboard=True 3496 keyboardfile=arg 3497 3498 if opt in ('-N','--nom2'): dbg = g_nom2 = True 3499 if opt in ('-D','--debug'): dbg = True 3500 if opt in ('-n','--noauto'): noauto = True 3501 if opt in ('-v','--verbose'): 3502 vbose = True 3503 continue 3504 if opt in ('-s','--sendtoaxis'): 3505 send_f = send_to_axis 3506 continue 3507 if remainder: subfilenames = remainder # ok for shell glob e.g., *.ngc 3508 demo = int(demo) 3509 if not keyboard: keyboardfile=None 3510 3511 if (dbg): 3512 print(g_progname + ' BEGIN-----------------------------------------------') 3513 print(' __file__= %s' % __file__) 3514 print(' ini_file= %s' % ini_file) 3515 print(' sys.argv= %s' % sys.argv) 3516 print(' os.getcwd= %s' % os.getcwd()) 3517 print(' sys.path= %s' % sys.path) 3518 print(' demo= %s' % demo) 3519 print(' prefilename= %s' % prefilename) 3520 print('subfilenames= %s' % subfilenames) 3521 print(' pstfilename= %s' % pstfilename) 3522 print(' keyboard= %s, keyboardfile= <%s>' % (keyboard,keyboardfile)) 3523 try: 3524 if demo == 0: 3525 top.hide() 3526 NgcGui(w=None 3527 ,verbose=vbose,debug=dbg,noauto=noauto 3528 ,keyboardfile=keyboardfile 3529 ,tmode=tmode 3530 ,send_function=send_f # prototype: (fname) 3531 ,ini_file=ini_file,auto_file=auto_file 3532 ,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename 3533 ) 3534 elif demo == 1: 3535 NgcGui(w=f2 3536 ,verbose=vbose,debug=dbg,noauto=noauto 3537 ,keyboardfile=keyboardfile 3538 ,tmode=tmode 3539 ,send_function=send_f # prototype: (fname) 3540 ,ini_file=ini_file,auto_file=auto_file 3541 ,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename 3542 ) 3543 top.set_title('Create OnePg inside a new frame') 3544 elif demo == 2: 3545 NgcGui(w=n 3546 ,verbose=vbose,debug=dbg,noauto=noauto 3547 ,keyboardfile=keyboardfile 3548 ,tmode=tmode 3549 ,send_function=send_f # prototype: (fname) 3550 ,ini_file=ini_file,auto_file=auto_file 3551 ,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename 3552 ) 3553 top.set_title('Create OnePg inside an existing notebook') 3554 else: 3555 print('unknown demo',demo) 3556 usage() 3557 sys.exit(1) 3558 except Exception, detail: 3559 exception_show(Exception,detail,'__main__') 3560 print('in main()') 3561 sys.exit(11) 3562 3563 try: 3564 gtk.main() 3565 except KeyboardInterrupt: 3566 sys.exit(0) 3567 3568 # vim: sts=4 sw=4 et