/ lib / python / pyngcgui.py
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