linuxcnc/lib/python/pyngcgui.py
2022-10-16 17:52:38 -07:00

3564 lines
127 KiB
Python

#!/usr/bin/env python3
# Notes:
# 1) INI file items:
# NGCGUI_PREAMBLE
# NGCGUI_SUBFILE
# NGCGUI_POSTAMBLE
# NGCGUI_OPTIONS
# nonew disallow new tabs
# noremove disallow removal of tabs
# noauto don't automatically send result file
# noexpand (ngcgui used, not supported pyngcgui)
# nom2 (no m2 terminator (use %))
# 2) To make pyngcgui embedded fit in small screen:
# Try:
# max_parms=10|20|30 (will reject otherwise valid subfiles)
# image_width=240
# reduce subroutine parm name lengths and/or comment string length
#------------------------------------------------------------------------------
# Copyright: 2013-6
# Author: Dewey Garrett <dgarrett@panix.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#------------------------------------------------------------------------------
""" python classes to implement an ngcgui-like application
These INI file items are compatible with ngcgui.tcl:
[DISPLAY]NGCGUI_PREAMBLE single specifier
[DISPLAY]NGCGUI_POSTAMBLE single specifier
[DISPLAY]NGCGUI_SUBFILE multiples allowed, use "" for Custom tab
[DISPLAY]NGCGUI_OPTIONS
noremove disallow tabpage removal
nonew disallow tabpage creation
noiframe don't show image in tabpage
noauto don't automatically send result file
[DISPLAY]PROGRAM_PREFIX subroutine path: start
[RS274NGC]SUBROUTINE_PATH subroutine path: middle
[WIZARD]WIZARD_ROOT subroutine path: end
[DISPLAY]NGCGUI_FONT not used
[DISPLAY]TKPKG not applicable
"""
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GObject
from types import * # IntType etc
import os
import sys
import re
import getopt
import datetime
import subprocess
import linuxcnc
import hashlib
import glob
import shutil
import popupkeyboard
import traceback # for debug printing
import hal # notused except for debug
from gladevcp import hal_actions
g_ui_dir = linuxcnc.SHARE + "/linuxcnc"
# determine if glade interface designer is running
# in order to prevent connection of most signals
g_is_glade = False
if ( ('glade' in sys.argv[0])
and ('gladevcp' not in sys.argv[0])):
for d in os.environ['PATH'].split(':'):
f = os.path.join(d,sys.argv[0])
if ( os.path.isfile(f)
and os.access(f, os.X_OK)):
g_is_glade = True
break
g_alive = not g_is_glade
import gettext
LOCALEDIR = linuxcnc.SHARE + "/locale"
gettext.install("linuxcnc", localedir=LOCALEDIR)
#------------------------------------------------------------------------------
g_debug = False
g_verbose = False
g_nom2 = False # True for no m2 terminator (use %)
g_strict = False # enforce additional subfile formatting requirements
g_tmode = 0 # for development
g_entry_height = 20 # default parm entry height
# (override for popupkeyboard)
g_big_height = 35 # increased parm entry height value
g_image_width = 320 # image size
g_image_height = 240 # image size
g_check_interval = 2 # periodic check (seconds)
g_label_id = 0 # subroutine labels modifier when expanding in place
g_progname = os.path.splitext(os.path.basename(__file__))[0]
g_dtfmt = "%y%m%d:%H.%M.%S"
g_stat = None # linuxcnc.stat object
g_popkbd = None # PopupKeyboard object
g_candidate_files = None # CandidateFiles object
g_send_function = None # function object f(fname) return True for success
g_tab_controls_loc ='top' # 'top' | 'bottom'
g_keyboardfile = os.path.join(g_ui_dir,'popupkeyboard.ui')
g_control_font = None
g_font_users = []
g_auto_file_ct = 1
INTERP_SUB_PARAMS = 30 # (1-based) conform to:
# src/emc/rs274ngc/interp_internal.hh:#define INTERP_SUB_PARAMS 30
g_max_parm = INTERP_SUB_PARAMS
g_max_msg_len = 500 # limit popup msg len for errant gcmc input
g_gcmc_exe = None
g_gcmc_funcname = 'tmpgcmc'
g_gcmc_id = 0
black_color = Gdk.color_parse('black')
white_color = Gdk.color_parse('white')
error_color = Gdk.color_parse('red')
green_color = Gdk.color_parse('green')
blue_color = Gdk.color_parse('blue')
yellow_color = Gdk.color_parse('yellow')
purple_color = Gdk.color_parse('purple')
feature_color = Gdk.color_parse('lightslategray')
label_normal_color = Gdk.color_parse('lightsteelblue2')
label_active_color = Gdk.color_parse('ivory2')
base_entry_color = Gdk.color_parse('azure1')
fg_created_color = Gdk.color_parse('palegreen')
fg_multiple_color = Gdk.color_parse('cyan')
fg_normal_color = black_color
bg_dvalue_color = Gdk.color_parse('darkseagreen2')
#------------------------------------------------------------------------------
def exception_show(ename,detail,src=''):
print('\n%s:' % src )
print('Exception: %s' % ename )
print(' detail: %s' % detail )
if type(detail) == ValueError:
for x in detail:
if isinstance(x,str):
print('detail(s):',x)
else:
for y in x:
print('detail(d):',y,)
elif isinstance(detail,str):
print('detail(s):',detail)
elif isinstance(detail,list):
for x in detail:
print('detail(l):',x)
else:
print(ename,detail)
if g_debug:
#print(sys.exc_info())
print( traceback.format_exc())
def save_a_copy(fname,archive_dir='/tmp/old_ngc'):
if fname is None:
return
try:
if not os.path.exists(archive_dir):
os.mkdir(archive_dir)
shutil.copyfile(fname
,os.path.join(archive_dir,dt() + '_' + os.path.basename(fname)))
except IOError as msg:
print(_('save_a_copy: IOError copying file to %s') % archive_dir)
print(msg)
except Exception as detail:
exception_show(Exception,detail,src='save_a_copy')
print(traceback.format_exc())
sys.exit(1)
def get_linuxcnc_ini_file():
ps = subprocess.Popen('ps -C linuxcncsvr --no-header -o args'.split(),
stdout=subprocess.PIPE
)
p,e = ps.communicate()
if ps.returncode:
print(_('get_linuxcnc_ini_file: stdout= %s') % p)
print(_('get_linuxcnc_ini_file: stderr= %s') % e)
return None
if p is not None: p = p.decode()
if e is not None: e = e.decode()
ans = p.split()[p.split().index('-ini')+1]
return ans
def dummy_send(filename):
return False # always fail
def default_send(filename):
import gladevcp.hal_filechooser
try:
s = linuxcnc.stat().poll()
except:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('linuxcnc not running')
,msg = _('cannot send, linuxcnc not running'))
return False
try:
fchooser = gladevcp.hal_filechooser.EMC_Action_Open()
fchooser._hal_init()
fchooser._load_file(filename)
return True
except:
return False
def send_to_axis(filename): # return True for success
# NB: file with errors may hang in axis gui
s = subprocess.Popen(['axis-remote',filename]
,stdout=subprocess.PIPE
,stderr=subprocess.PIPE
)
p,e = s.communicate()
if s.returncode:
print(_('%s:send_to_axis: stdout= %s') % (g_progname,p))
print(_('%s:send_to_axis: stderr= %s') % (g_progname,e))
return False
if p: print(_('%s:send_to_axis: stdout= %s') % (g_progname,p))
if e: print(_('%s:send_to_axis: stderr= %s') % (g_progname,e))
return True
def file_save(fname,title_message='Save File'):
start_dir = os.path.dirname(fname)
if start_dir == '': start_dir = os.path.curdir
fc = Gtk.FileChooserDialog(title=title_message
,parent=None
,action=Gtk.FileChooserAction.SAVE
,buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
,Gtk.STOCK_OK, Gtk.ResponseType.OK
)
)
fc.set_current_folder(start_dir)
fc.set_do_overwrite_confirmation(True)
filter = Gtk.FileFilter()
filter.set_name('NGC files')
filter.add_pattern('*.ngc')
filter.add_pattern('*.NGC')
filter.add_pattern('*.nc')
filter.add_pattern('*.NC')
filter.add_pattern('*.gcmc')
filter.add_pattern('*.GCMC')
fc.add_filter(filter)
fc.set_current_name(os.path.basename(fname)) # suggest name (for save)
fname = None
ans = fc.run()
if ans == Gtk.ResponseType.OK:
fname = fc.get_filename()
elif ans == Gtk.ResponseType.CANCEL:
print(_('file_save:canceled'))
elif ans == Gtk.ResponseType.DELETE_EVENT: # window close
print(_('file_save:window closed'))
else:
raise IOError(_('file_save:unexpected'))
fc.destroy()
return(fname)
def is_comment(s):
if s[0] == ';': return bool(1) # ;xxx
elif s[0] == '(':
if s[-2] == ')': return bool(1) # (yyy)
else: return bool(1) # (yyy)zzz maybe bogus
return bool(0)
def get_info_item(line):
# expect line as unaltered line with whitespace
l = line.translate(' \t').lower()
r = re.search(r'^\(info:(.*)\)',l)
if r:
r = re.search(r'.*info:(.*)\)',line)
if r: return r.group(1)
return None
def check_sub_start(s):
r = re.search(r'^o<(.*)> *sub.*',s.strip())
if r:
#print('check_sub_start:g0:',r.group(0))
#print('check_sub_start:g1:',r.group(1))
return r.group(1)
return None
def check_sub_end(s):
r = re.search(r'^o<(.*)> *endsub.*',s)
if r:
#print('check_sub_end:g0:',r.group(0))
#print('check_sub_end:g1:',r.group(1))
return r.group(1)
return None
def check_for_label(s):
r = re.search(r'^o<(.*?)> *(sub|endsub).*',s)
if r:
return 'ignoreme' # do not include on expand
r = re.search(r'^o<(.*?)> *(call).*',s)
if r:
return None # do not mod label on expand
r = re.search(r'^o<(.*?)>.*',s)
if r:
return r.group(1) # make label unique on expand
return None
def check_positional_parm_range(s,min,max):
r = re.search(r'#([0-9]+)',s)
if r: pnum = int(r.group(1))
# here check is against system limit; g_max_parm applied elsewhere
if r and (pnum <= INTERP_SUB_PARAMS):
if pnum < min: min = pnum
if pnum > max: max = pnum
return pnum,min,max
return None,None,None
def find_positional_parms(s):
# requires original line (mixed case with whitespace)
# find special association lines for positional parameters
# The '*', '+', and '?' qualifiers are all greedy.
# Greedy <.*> matches all of <H1>title</H1>
# NonGreedy <.*?> matches the only first <H1>
# case1 #<parmname>=#n (=defaultvalue comment_text)
# case2 #<parmname>=#n (=defaultvalue)
# case3 #<parmname>=#n (comment_text)
# case4 #<parmname>=#n
name = None
pnum = None
dvalue = None
comment = None
s = s.expandtabs() # tabs to spaces
r = re.search(
r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\(= *([0-9.+-]+[0-9.]*?) *(.*)\)'
,s,re.I)
#case1 1name 2pnum 3dvalue 4comment
if r is None: r=re.search(
r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\( *([0-9.+-]+)\)',s,re.I)
#case2 1name 2pnum 3dvalue
if r is None: r=re.search(
r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *\((.*)\)',s,re.I)
#case3 1name 2pnum 3comment
if r is None: r=re.search(
r' *# *<([a-z0-9_-]+)> *= *#([0-9]+) *$',s,re.I)
#case4 1name 2pnum
# if r:
# for i in range(0,1+len(r.groups())):
# print('PARSE groups',len(r.groups()),i,r.group(i))
if r:
n = len(r.groups())
if r and n >= 2:
name = comment = r.group(1) # use name as comment if not specified
pnum = int(r.group(2))
# here check is against system limit; g_max_parm applied elsewhere
if pnum > INTERP_SUB_PARAMS:
return None,None,None,None
if n == 3:
if r.group(3)[0] == '=': dvalue = r.group(3)[1:]
else: comment = r.group(3)[:]
if n == 4:
dvalue = r.group(3)
if dvalue.find('.') >= 0:
dvalue = float(dvalue)
else:
dvalue = int(dvalue)
if r.group(4): comment = r.group(4)
if n > 4:
print('find_positional_parameters unexpected n>4',s,)
comment = r.group(4)
if comment is None:
print('find_positional_parameters:NOCOMMENT') # can't happen
comment = ''
return name,pnum,dvalue,comment
def user_message(title=""
,mtype=Gtk.MessageType.INFO
,flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT
,msg=None):
if msg is None: return(None)
if isinstance(msg,list):
txt = "".join(msg)
else:
txt = msg
vprint('USER_MESSAGE:\n%s' % txt)
popup = Gtk.MessageDialog(parent = None
,flags=flags
,type=mtype
,buttons = Gtk.ButtonsType.OK
,message_format = txt
)
popup.set_title(title)
result = popup.run()
popup.destroy()
return(result)
def dt():
return(datetime.datetime.now().strftime(g_dtfmt))
def md5sum(fname):
if not fname: return None
return(hashlib.md5(open(fname, 'r').read().encode()).hexdigest())
def find_image(fname):
found = False
for suffix in ('png','gif','jpg','pgm'):
name = os.path.splitext(os.path.basename(fname))[0]
dir = os.path.dirname(fname)
ifile = os.path.join(dir,name + '.' + suffix)
if os.path.isfile(ifile):
found = True
break
if not found: return None
return ifile
def sized_image(ifile):
twidth = g_image_width
theight = g_image_height
img = Gtk.Image()
img.set_from_file(ifile)
pixbuf = img.get_pixbuf()
iwidth = pixbuf.get_width() # image size
iheight = pixbuf.get_height()
scale = min(float(twidth)/iwidth, float(theight)/iheight)
#print('iw,ih %d,%d tw,th=%d,%d, scale=%f' % (
# iwidth,iheight,twidth,theight,scale))
new_width = int(scale*iwidth)
new_height = int(scale*iheight)
from gi.repository.GdkPixbuf import Pixbuf, InterpType
pixbuf = pixbuf.scale_simple(new_width,new_height
,InterpType.BILINEAR)
img.set_from_pixbuf(pixbuf)
return(img)
def show_dir(x,tag=''):
l = []
for name in sorted(dir(x)):
if name[0:2] == '__': continue
item = getattr(x,name)
ty = type(item)
if ty == MethodType:
l.append('%-8s %s()' % ('0 Meth',name))
elif isinstance(ty,list):
i = 0
for v in item:
try:
vnonewline = v[:-1] if v.endswith('\n') else v
l.append('%-8s %s[%2s] = %s' % ('2 List',name,i,vnonewline))
i += 1
except:
l.append('xxx %s %s' % (name,str(item)))
elif ty == DictionaryType:
for k in sorted(item):
l.append('%-8s %s[%2s] = %s' % ('3 Dict',name,k,item[k]))
elif ty == BooleanType:
l.append('%-8s %s = %s' % ('4 Bool',name,str(item)))
elif ty == IntType:
l.append('%-8s %s = %s' % ('5 Int',name,str(item)))
elif ty == FloatType:
l.append('%-8s %s = %s' % ('6 Float',name,str(item)))
elif isinstance(ty,str):
l.append('%-8s %s = %s' % ('7 Str',name,item))
else:
s = str(item).split(' ')[0] + '>'
s=item
l.append('%-8s %s = %s' % ('1 Obj',name,s))
print('\n')
print('%s----------------------------------------------------------' % tag)
for i in sorted(l):
print(i)
print('%s==========================================================' % tag)
def dprint(txt):
if g_debug:
print(':' + txt)
def vprint(txt):
if g_verbose:
print('::' + txt)
def spath_from_inifile(fname):
if not fname:
return []
ini = linuxcnc.ini(fname)
homedir = os.path.dirname(os.path.realpath(fname))
# http://www.linuxcnc.org/docs/devel/html/config/ini_config.html
l = []
p = ini.find('DISPLAY','PROGRAM_PREFIX')
if p:
l = [p]
p = ini.find('RS274NGC','SUBROUTINE_PATH')
if p:
newdirs = p.split(':')
for dir in newdirs:
# dont add duplicates
if dir not in l:
l.append(dir)
p = ini.find('WIZARD','WIZARD_ROOT')
if p:
l.extend(p.split(':'))
lfull = []
for d in l:
d = os.path.expanduser(d)
if os.path.isabs(d):
lfull.append(d)
else:
# relative path implies cwd is correct
d2 = os.path.join(homedir,d)
lfull.append(os.path.abspath(d2))
if lfull:
return lfull
return []
def mpath_from_inifile(fname):
if not fname:
return None
ini = linuxcnc.ini(ifname)
homedir = os.path.dirname(os.path.abspath(fname))
l = []
p = ini.find('DISPLAY','PROGRAM_PREFIX')
if p:
l = [p]
else:
l = 'nc_files'
p = ini.find('RS274NGC','USER_M_PATH')
if p:
l.extend(p.split(':'))
lfull = []
for d in l:
if os.path.isabs(d):
lfull.append(d)
else:
d2 = os.path.join(homedir,d)
lfull.append(os.path.abspath(d2))
if lfull:
return lfull
return None
def spath_from_files(pre_file,sub_files,pst_file):
# when there is no INI file for path because
# linuxcnc not running
# and
# no INI specified on cmd line
l = []
slist = []
if isinstance(sub_files,str) and sub_files:
slist.append(sub_files)
else:
slist = sub_files
for sub_file in slist:
dir = os.path.dirname(os.path.abspath(sub_file))
if dir not in l:
l.append(dir)
if pre_file:
dir = os.path.dirname(os.path.abspath(pre_file))
if dir not in l:
l.append(dir)
if pst_file:
dir = os.path.dirname(os.path.abspath(pst_file))
if dir not in l:
l.append(dir)
if l:
return l
return []
def long_name(name):
if name == 'pre':
return 'Preamble'
elif name == 'sub':
return 'Subroutine'
elif name == 'pst':
return 'Postamble'
else:
return 'Unknown'
def show_parent(w,ct=0):
if w is None:
print('show_parent: None')
return
print('show_parent:',ct,w)
if w.is_toplevel():
print('TOP\n')
return
else:
show_parent(w.get_parent(),ct+1)
def all_coords(iterable):
ans = ''
for t in iterable:
ans = ans + '%7.3f' % t
return ans
def show_position():
g_stat.poll()
print('POSITION-----------------------------------------------------')
print(' ap',all_coords(g_stat.actual_position))
print(' p',all_coords(g_stat.position))
l = []
p = g_stat.actual_position
for i in range(9): l.append(p[i]
- g_stat.g5x_offset[i]
- g_stat.tool_offset[i]
)
print('offset ap',all_coords(l))
l = []
p = g_stat.position
for i in range(9): l.append(p[i]
- g_stat.g5x_offset[i]
- g_stat.tool_offset[i]
)
print('offset p',all_coords(l))
print('POSITION=====================================================')
def coord_value(char):
# offset calc from emc_interface.py (touchy et al)
# char = 'x' | 'y' | ...
# 'd' is for diameter
c = char.lower()
g_stat.poll()
p = g_stat.position # tuple: (xvalue, yvalue, ...
if (c == 'd'):
if (1 & g_stat.axis_mask):
# diam = 2 * x
return (p[0] - g_stat.g5x_offset[0] - g_stat.tool_offset[0])* 2
else:
return 'xxx' # return a string that will convert with float()
axno = 'xyzabcuvw'.find(c)
if not ( (1 << axno) & g_stat.axis_mask ):
return 'xxx' # return a string that will convert with float()
return p[axno] - g_stat.g5x_offset[axno] - g_stat.tool_offset[axno]
def make_g_styles():
dummylabel = Gtk.Label()
global g_lbl_style_default
g_lbl_style_default = dummylabel.get_style().copy()
#g_lbl_style_default.bg[Gtk.StateType.NORMAL] = label_normal_color TODO:
#g_lbl_style_default.bg[Gtk.StateType.ACTIVE] = label_active_color
global g_lbl_style_created
g_lbl_style_created = dummylabel.get_style().copy()
global g_lbl_style_multiple
g_lbl_style_multiple = dummylabel.get_style().copy()
#g_lbl_style_multiple.bg[Gtk.StateType.NORMAL] = feature_color
#g_lbl_style_multiple.bg[Gtk.StateType.ACTIVE] = feature_color
#g_lbl_style_created.bg[Gtk.StateType.NORMAL] = feature_color
#g_lbl_style_created.bg[Gtk.StateType.ACTIVE] = feature_color
del dummylabel
dummyentry = Gtk.Entry()
global g_ent_style_normal
g_ent_style_normal = dummyentry.get_style().copy()
global g_ent_style_default
g_ent_style_default = dummyentry.get_style().copy()
global g_ent_style_error
g_ent_style_error = dummyentry.get_style().copy()
#g_ent_style_normal.base[Gtk.StateType.NORMAL] = base_entry_color
#g_ent_style_default.base[Gtk.StateType.NORMAL] = bg_dvalue_color
#g_ent_style_error.text[Gtk.StateType.NORMAL] = error_color
#g_ent_style_error.base[Gtk.StateType.NORMAL] = base_entry_color
del dummyentry
def mod_font_by_category(obj,mode='control'):
#dbg; crippled at gtk3
return
# currently mode = control (only)
# touchy has 4 font categories: control,dro,error,listing
if mode == 'control':
font = g_control_font
else:
print('mod_font_by_category:unknown mode %s' % mode)
return
targetobj = None
if type(obj) == type(Gtk.Label()):
targetobj = obj
elif type(obj) == type(Gtk.Entry()):
targetobj = obj
elif type(obj) == type(Gtk.Button()):
#Gtk.Alignment object
if isinstance(obj.child, Gtk.Label):
targetobj = obj.child
elif isinstance(obj.child, Gtk.Alignment):
pass
elif hasattr(obj,'modify_font'):
targetobj = obj
else:
raise ValueError('mod_font_by_category: no child')
return
else:
raise ValueError('mod_font_by_category: unsupported:').with_traceback(type(obj))
return
if targetobj is None:
return
if font is None:
#print('mod_font_by_category:nofont available for %s' % mode)
return # silently
targetobj.modify_font(g_control_font)
global g_font_users
if targetobj not in g_font_users:
g_font_users.append(targetobj)
def update_fonts(fontname):
global g_control_font
g_control_font = fontname
for obj in g_font_users:
mod_font_by_category(obj)
def clean_tmpgcmc(odir):
if odir == "":
odir = g_searchpath[0]
savedir = os.path.join("/tmp", g_gcmc_funcname) # typ /tmp/tmpgcmc
if not os.path.isdir(savedir):
os.mkdir(savedir,0o755)
for f in glob.glob(os.path.join(odir,g_gcmc_funcname + "*.ngc")):
# rename ng across file systems
shutil.move(f,os.path.join(savedir,os.path.basename(f)))
def find_gcmc():
global g_gcmc_exe # find on first request
if g_gcmc_exe == "NOTFOUND": return False # earlier search failed
if g_gcmc_exe is not None: return True # already found
for dir in os.environ["PATH"].split(os.pathsep):
exe = os.path.join(dir,'gcmc')
if os.path.isfile(exe):
if os.access(exe,os.X_OK):
clean_tmpgcmc("") # clean on first find_gcmc
g_gcmc_exe = exe
return True # success
g_gcmc_exe = "NOTFOUND"
user_message(mtype=Gtk.MessageType.ERROR
,title=_('Error for:')
,msg = _('gcmc executable not available:'
+ '\nCheck path and permissions'))
return False # fail
#-----------------------------------------------------------------------------
make_g_styles()
class CandidateDialog():
"""CandidateDialog: dialog with a treeview in a scrollwindow"""
def __init__(self,ftype=''):
self.ftype = ftype
lname = long_name(self.ftype)
title = "Choose %s file" % lname
btns=(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT
,Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
if ( (self.ftype == 'pre') or (self.ftype == 'pst') ):
# NO used to allow 'nofile' for 'pre','pst'
btns = btns + ('No %s File' % lname, Gtk.ResponseType.NO)
self.fdialog = Gtk.Dialog(title=title
,parent=None
,flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT
,buttons=btns
)
self.fdialog.set_size_request(600,600)
scrollw = Gtk.ScrolledWindow()
scrollw.set_border_width(5)
scrollw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
scrollw.show()
box = self.fdialog.get_content_area()
box.pack_start(scrollw, True, True, 0)
global g_candidate_files
self.canfiles = g_candidate_files
self.canfiles.refresh()
self.treestore = g_candidate_files.treestore
self.treeview = Gtk.TreeView(self.treestore)
if g_alive: self.treeview.connect('row-activated',self.row_activated)
column0 = Gtk.TreeViewColumn('Subroutine Directories')
self.treeview.append_column(column0)
cell0 = Gtk.CellRendererText()
column0.pack_start(cell0, True)
column0.add_attribute(cell0, 'text', 0)
column1 = Gtk.TreeViewColumn('Hint')
self.treeview.append_column(column1)
cell1 = Gtk.CellRendererText()
column1.pack_start(cell1, True)
column1.add_attribute(cell1, 'text', 1)
column2 = Gtk.TreeViewColumn('mtime')
self.treeview.append_column(column2)
cell2 = Gtk.CellRendererText()
column2.pack_start(cell2, True)
column2.add_attribute(cell2, 'text', 2)
scrollw.add_with_viewport(self.treeview)
scrollw.show_all()
def get_file_result(self):
# return: (name,errmsg)
try:
(model,iter) = self.treeview.get_selection().get_selected()
except AttributeError:
return(None,'') # nothing selected
if not iter:
return(None,'')
fname,status,mtime = self.canfiles.get_tree_data(iter)
if os.path.isdir(fname):
return(None,'') # cannot use a selected dir
ok = True # contradict this
if (self.ftype == 'pre') or (self.ftype == 'pst'):
if status.find('not_a_subfile') >= 0: ok = True
if status.find('Preempted') >= 0: ok = False
else:
if status.find('not_a_subfile') >= 0: ok = False
if status.find('not_allowed') >= 0: ok = False
if status.find('Preempted') >= 0: ok = False
if ok:
return (fname,'')
emsg = (_('The selected file is not usable\n'
'as a %s file\n'
'(%s)') % (long_name(self.ftype),status)
)
return('TRYAGAIN',emsg)
def row_activated(self,tview,iter,column):
self.fdialog.response(Gtk.ResponseType.ACCEPT)
pass
def run(self):
return(self.fdialog.run())
def destroy(self):
self.fdialog.destroy()
class CandidateFiles():
"""CandidateFiles treestore for candidate files"""
def __init__(self,dirlist):
self.dirlist=dirlist
self.treestore = Gtk.TreeStore(str,str,str)
self.tdict = {}
self.make_tree()
def refresh(self):
# currently, just do over
# potential to reread only files with modified mtimes
self.__init__(self.dirlist)
def make_tree(self):
didx = 0
flist = []
for dir in self.dirlist:
self.tdict[didx,] = dir
# row must be a tuple or list containing as many items
# as the number of columns
try:
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(dir))
except OSError as detail:
print(_('%s:make_tree:%s' % (g_progname,detail) ))
continue # try to skip this dir with message
mtime = mtime.strftime(g_dtfmt) # truncate fractional seconds
iter = self.treestore.append(None, [dir,"Directory",mtime])
fidx = 0
for f in ( sorted(glob.glob(os.path.join(dir,"*.ngc")))
+ sorted(glob.glob(os.path.join(dir,"*.NGC")))
+ sorted(glob.glob(os.path.join(dir,"*.gcmc")))
+ sorted(glob.glob(os.path.join(dir,"*.GCMC")))
):
fname = os.path.basename(f)
self.tdict[didx,fidx] = fname
stat = ""
fd = open(f)
ftxt = fd.read()
fd.close()
if os.path.splitext(fname)[-1] in ['.gcmc','.GCMC']:
stat = '%sgcmc:ok' % stat
if ftxt.find('not_a_subfile') >= 0:
stat = '%snot_a_subfile ' % stat
if ftxt.find('(info:') >= 0:
stat = '%sngcgui-ok ' % stat
if fname in flist:
stat = '%sPreempted ' % stat
if ftxt.find('FEATURE') >= 0:
stat = '%snot_allowed ' % stat
if stat == "":
stat = "?"
if stat.find("Preempted") >= 0:
stat = "Preempted" # suppress ok
flist.append(fname)
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(f))
mtime = mtime.strftime(g_dtfmt) # truncate fractional seconds
self.treestore.append(iter, [fname,stat,mtime])
fidx += 1
didx += 1
def get_tree_data(self,iter):
path = self.treestore.get_path(iter)
if len(path) > 1:
row,col = path
dir = self.tdict[row,]
fname = self.treestore.get_value(iter,0)
status = self.treestore.get_value(iter,1)
mtime = self.treestore.get_value(iter,2)
else:
dir = self.tdict[path]
fname = ''
status = ''
mtime = ''
return os.path.join(dir,fname),status,mtime
class LinuxcncInterface():
"""LinuxcncInterface: INI file and running linuxcnc data"""
def __init__(self,cmdline_ini_file=''):
self.lrunning = False
self.ini_data = None
self.subroutine_path = []
self.user_m_path = None
self.ini_file = None
self.ngcgui_options = []
self.editor = os.environ.get("VISUAL")
use_ini_file = None
l_ini_file = ''
stat = linuxcnc.stat()
try:
global g_stat
g_stat = linuxcnc.stat()
g_stat.poll() # poll faults if linuxcnc not running
self.lrunning = True
l_ini_file = get_linuxcnc_ini_file()
except linuxcnc.error as msg:
g_stat = None
print('INTFC:err:',msg)
print('INTFC:' + _('Warning: linuxcnc not running'))
print('%s:INTFC:linuxcnc running=%d' % (g_progname,self.lrunning))
print('%s:INTFC:ini_file=<%s>' % (g_progname,l_ini_file))
# cmdline_ini_file can be specified on cmdline and from intfc:
# if neither ok: if no cmdline subfile, make custom page
# if cmdonly ok
# if runonly ok
# if both ok: warn message and continue
if cmdline_ini_file:
cmdline_spath = spath_from_inifile(cmdline_ini_file)
if l_ini_file:
l_spath = spath_from_inifile(l_ini_file)
if not cmdline_ini_file and not l_ini_file:
ini_file = None
spath = []
#print('NEITHER')
if not cmdline_ini_file and l_ini_file:
ini_file = l_ini_file
spath = l_spath
#print("OK running only <,",cmdline_ini_file,l_ini_file,">")
if cmdline_ini_file and not l_ini_file:
ini_file = cmdline_ini_file
spath = cmdline_spath
#print('OK cmdline only')
if cmdline_ini_file and l_ini_file:
#print("INI file on both cmdline and running linuxcnc")
msg = ""
if os.path.abspath(cmdline_ini_file) != l_ini_file:
ini_file = l_ini_file
msg = (_('The ini file specified on cmdline') + ':\n'
+ os.path.abspath(cmdline_ini_file) + '\n\n'
+ _('is different from the one used by the running linuxcnc')
+ ':\n'
+ l_ini_file + '\n\n'
)
if cmdline_spath == l_spath:
ini_file = cmdline_ini_file
spath = cmdline_spath
msg = msg + _('Using cmd line INI file (same paths)')
else:
ini_file = l_ini_file
spath = l_spath
msg = msg + _('Ignoring cmd line INI file (different paths)')
user_message(mtype=Gtk.MessageType.WARNING
,title=_('Warning')
,msg=msg
)
if ini_file:
self.ini_file = ini_file
self.ini_data = linuxcnc.ini(self.ini_file)
# get it again to avoid (unlikely) race
self.subroutine_path = spath_from_inifile(ini_file)
self.ngcgui_options = self.ini_data.find('DISPLAY','NGCGUI_OPTIONS')
self.editor = ( self.editor
or self.ini_data.find('DISPLAY','EDITOR'))
# create at startup, refresh as required
global g_candidate_files
g_candidate_files = CandidateFiles(self.get_subroutine_path())
def addto_spath(self,pathtoadd):
if not isinstance(pathtoadd,list):
raise ValueError(
'addto_spath: List required not: %s %s'
% (pathtoadd,type(pathtoadd)))
# dont add duplicates
if pathtoadd not in self.subroutine_path:
self.subroutine_path.extend(pathtoadd)
def get_editor(self):
return self.editor or 'gedit'
def get_ini_file(self):
return(self.ini_file)
def get_subroutine_path(self):
return(self.subroutine_path)
def get_user_m_path(self):
return(self.user_m_path)
def find_file_in_path(self,fname):
# return tuple:
# '', 'NULLFILE' if fname None or ''
# fname, 'NOPATH' no path defined (eg no INI file)
# foundfilename, 'FOUND' found in path
# fname, 'NOTFOUND' not in path (may exist)
if not fname:
return('','NULLFILE')
if not self.subroutine_path:
return(fname,'NOPATH')
bname = os.path.basename(fname) # only basename used
foundlist = []
foundfilename = None
for p in self.subroutine_path:
f = os.path.join(p,bname)
if os.path.isfile(f):
if not foundfilename:
foundfilename = f #first one wins
foundlist.append(f)
if len(foundlist) > 1:
print(_('find_file_in_path:Multiple Results: %s') % foundlist)
print(_(' Search path: %s') % self.subroutine_path)
if foundfilename:
vprint('find_file_in_path:%s' % foundfilename)
return(foundfilename,'FOUND')
print('find_file_in_path<%s> NOTFOUND' % fname)
return(fname,'NOTFOUND')
def get_subfiles(self):
if self.ini_data:
#returns list
return(self.ini_data.findall('DISPLAY','NGCGUI_SUBFILE'))
else:
return(None)
def get_preamble(self):
if self.ini_data:
return(self.ini_data.find('DISPLAY','NGCGUI_PREAMBLE'))
else:
return(None)
def get_postamble(self):
if self.ini_data:
return(self.ini_data.find('DISPLAY','NGCGUI_POSTAMBLE'))
else:
return(None)
def get_font(self):
if self.ini_data:
return(self.ini_data.find('DISPLAY','NGCGUI_FONT'))
else:
return(None)
def get_ngcgui_options(self):
return(self.ngcgui_options or [])
def get_gcmc_include_path(self):
dirs = (self.ini_data.find('DISPLAY','GCMC_INCLUDE_PATH'))
return(dirs)
def get_program_prefix(self):
if self.ini_data:
dir = self.ini_data.find('DISPLAY','PROGRAM_PREFIX')
if not dir: return(None)
dir = os.path.expanduser(dir)
if not os.path.isabs(dir):
# relative, base on inidir
dir = os.path.join(os.path.dirname(self.ini_file),dir)
return(dir)
else:
return(None)
class PreFile():
"""PreFile: preamble file data"""
def __init__(self,thefile):
self.pre_file = thefile
self.read()
def clear(self):
self.pre_file = ''
self.inputlines=[]
def read(self):
#print('PreFile read')
self.md5 = None
self.mtime = None
self.inputlines = []
if self.pre_file == "": return
self.mtime = os.path.getmtime(self.pre_file)
f = open(self.pre_file)
for l in f.readlines():
# dont include not_a_subfile lines
if (l.find('not_a_subfile') < 0) and (l.strip() != ''):
self.inputlines.append(l)
f.close()
self.md5 = md5sum(self.pre_file)
class PstFile():
"""PstFile: postamble file data"""
def __init__(self,thefile):
self.pst_file = thefile
self.read()
def clear(self):
self.pst_file = ''
self.inputlines = []
def read(self):
#print('PstFile read')
self.md5 = None
self.mtime = None
self.inputlines = []
if self.pst_file == "": return
self.mtime = os.path.getmtime(self.pst_file)
f = open(self.pst_file)
for l in f.readlines():
# dont include not_a_subfile lines
if (l.find('not_a_subfile') < 0) and (l.strip() != ''):
self.inputlines.append(l)
f.close()
self.md5 = md5sum(self.pst_file)
class SubFile():
"""SubFile: subfile data"""
def __init__(self,thefile):
self.sub_file = thefile
self.min_num = sys.maxsize
self.max_num = 0
self.pdict = {} # named items: pdict[keyword] = value
self.ndict = {} # ordinal items: ndict[idx] = (name,dvalue,comment)
self.ldict = {} # label items: ldict[lno] = thelabel
self.pdict['info'] = ''
self.pdict['lastparm'] = 0
self.pdict['subname'] = ''
self.inputlines = []
self.errlist=[]
self.md5 = None
self.mtime = None
if self.sub_file == '': return
self.mtime = os.path.getmtime(self.sub_file)
self.md5 = md5sum(self.sub_file)
if os.path.splitext(self.sub_file)[-1] in ['.ngc','.NGC','.nc','.NC']:
self.read_ngc()
elif os.path.splitext(self.sub_file)[-1] in ['.gcmc','.GCMC']:
self.read_gcmc()
else:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('Unknown file suffix')
,msg = _('Unknown suffix for: %s:')
% os.path.basename(self.sub_file)
)
return
def clear(self):
self.sub_file = ''
self.pdict = {}
self.ndict = {}
self.ldict = {}
self.inputlines = []
def flagerror(self,e):
# accumulate errors from read() so entire file can be processed
self.errlist.append(e)
def specialcomments_ngc(self,s):
if s.find(' FEATURE ') >= 0 :
self.flagerror(
"Disallowed use of ngcgui generated file as Subfile")
if s.find('not_a_subfile') >= 0 :
self.flagerror(
"marked (not_a_subfile)\nNot intended for use as a subfile")
def re_read(self):
if 'isgcmc' in self.pdict:
self.read_gcmc()
else:
self.read_ngc()
def read_ngc(self):
thesubname = os.path.splitext(os.path.basename(self.sub_file))[0]
f = open(self.sub_file)
self.inputlines = [] # in case rereading
for l in f.readlines():
self.specialcomments_ngc(l) # for compat, check on unaltered line
self.inputlines.append(l)
idx = 1 # 1 based for labels ldict
nextparm = 0
subname = None
endsubname = None
for line in self.inputlines:
# rs274: no whitespace, simplify with lowercase
info = get_info_item(line) # check on unaltered line
l = line.translate(' \t').lower()
lineiscomment = is_comment(l)
if info is not None: self.pdict['info'] = info
sname = check_sub_start(l)
if subname is not None and sname is not None:
self.flagerror("Multiple subroutines in file not allowed")
if subname is None and sname is not None:
subname = sname
if subname is not None and subname != thesubname:
self.flagerror("sub label "
"%s does not match subroutine file name" % thesubname)
if endsubname is not None:
if lineiscomment or (l.strip() == ''):
pass
elif l.find('m2') >= 0:
# linuxcnc ignores m2 after endsub in
# single-file subroutines
# mark as ignored here for use with expandsub option
self.inputlines[-1] = (';' + g_progname +
' ignoring: ' + self.inputlines[-1])
pass
else:
self.flagerror('file contains lines after subend:\n'
'%s' % l)
ename = check_sub_end(l)
if subname is None and ename is not None:
self.flagerror("endsub before sub %s" % ename)
if subname is not None and ename is not None:
endsubname = ename
if endsubname != subname:
self.flagerror("endsubname different from subname")
label = check_for_label(l)
if label: self.ldict[idx] = label
if ( subname is not None
and endsubname is None
and (not lineiscomment)):
pparm,min,max= check_positional_parm_range(l
,self.min_num,self.max_num)
if pparm is not None and pparm > g_max_parm:
self.flagerror(
_('parm #%s exceeds config limit on no. of parms= %d\n')
% (pparm,g_max_parm))
if pparm:
self.min_num = min
self.max_num = max
# blanks required for this, use line not l
name,pnum,dvalue,comment = find_positional_parms(line)
if name:
self.ndict[pnum] = (name,dvalue,comment)
# require parms in sequence to minimize user errors
nextparm = nextparm + 1
if g_strict:
if pnum != nextparm:
self.flagerror(
_('out of sequence positional parameter'
'%d expected: %d')
% (pnum, nextparm))
while pnum > nextparm:
makename = "#"+str(nextparm)
self.ndict[nextparm] = makename,"",makename
nextparm = nextparm + 1
self.pdict['lastparm'] = pnum
idx = idx + 1
f.close()
if subname is None: self.flagerror(_('no sub found in file\n'))
if endsubname is None: self.flagerror(_('no endsub found in file\n'))
if g_strict:
if nextparm == 0: self.flagerror(_('no subroutine parms found\n'))
self.pdict['subname'] = subname
if self.pdict['info'] == '':
self.pdict['info'] = 'sub: '+str(subname)
if self.errlist:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('Error for: %s ')
% os.path.basename(self.sub_file)
,msg = self.errlist)
self.errlist.append('SUBERROR')
raise ValueError(self.errlist)
def read_gcmc(self):
self.gcmc_opts = [] # list of options for gcmc
pnum = 0
f = open(self.sub_file)
for l in f.readlines():
rinfo = re.search(r'^ *\/\/ *ngcgui *: *info: *(.*)' ,l)
if rinfo:
#print 'info read_gcmc:g1:',rinfo.group(1)
self.pdict['info'] = rinfo.group(1) # last one wins
continue
ropt = re.search(r'^ *\/\/ *ngcgui *: *(-.*)$' ,l)
if ropt:
gopt = ropt.group(1)
gopt = gopt.split("//")[0] ;# trailing comment
gopt = gopt.split(";")[0] ;# convenience
gopt = gopt.strip() ;# leading/trailing spaces
self.gcmc_opts.append(gopt)
continue
name = None
dvalue = None
comment = ''
r3 = re.search(r'^ *\/\/ *ngcgui *: *(.*?) *= *(.*?) *\, *(.*?) *$', l)
r2 = re.search(r'^ *\/\/ *ngcgui *: *(.*?) *= *(.*?) *$', l)
r1 = re.search(r'^ *\\/\\/ *ngcgui *: *\(.*?\) *$', l)
if r3:
name = r3.group(1)
dvalue = r3.group(2)
comment = r3.group(3)
elif r2:
name = r2.group(1)
dvalue = r2.group(2)
elif r1:
print('r1-1 opt read_gcmc:g1:',r1.group(1))
name = r1.group(1)
if dvalue:
# this is a convenience to make it simple to edit to
# add a var without removing the semicolon
# xstart = 10;
# //ngcgui: xstart = 10;
dvalue = dvalue.split(";")[0] # ignore all past a ;
else:
dvalue = ''
if name:
if comment == '':
comment = name
pnum += 1
self.ndict[pnum] = (name,dvalue,comment)
self.pdict['isgcmc'] = True
self.pdict['lastparm'] = pnum
self.pdict['subname'] = os.path.splitext(os.path.basename(self.sub_file))[0]
if self.pdict['info'] == '':
self.pdict['info'] = 'gcmc: '+ self.pdict['subname']
f.close()
return True # ok
class FileSet():
"""FileSet: set of preamble,subfile,postamble files"""
def __init__(self,pre_file
,sub_file
,pst_file
):
# sub_file=='' is not an error, opens Custom
self.pre_data = PreFile(pre_file)
self.sub_data = SubFile(sub_file)
self.pst_data = PstFile(pst_file)
class OneParmEntry():
"""OneParmEntry: one parameter labels and entry box"""
def __init__(self,ltxt='ltxt' ,etxt='etxt' ,rtxt='rtxt'):
self.box = Gtk.HBox()
self.ll = Gtk.Label()
self.en = Gtk.Entry()
self.lr = Gtk.Label()
self.dv = None
ww = -1
hh = g_entry_height
self.ll.set_label(ltxt)
self.ll.set_width_chars(2)
self.ll.set_justify(Gtk.Justification.RIGHT)
self.ll.set_alignment(xalign=.90,yalign=0.5) # right aligned
self.ll.set_size_request(ww,hh)
self.en.set_text(etxt)
self.en.set_width_chars(6)
self.en.set_alignment(xalign=.90) # right aligned
self.en.set_size_request(ww,hh)
self.en.hide()
#self.en.connect("button-press-event",self.grabit)
if g_popkbd is not None:
if g_alive: self.en.connect("button-press-event",self.popkeyboard)
if g_alive: self.en.connect('changed', self.entry_changed) #-->w + txt
self.lr.set_label(rtxt)
self.lr.set_width_chars(0) # allow any width for compat with ngcgui
self.lr.set_justify(Gtk.Justification.LEFT)
self.lr.set_alignment(xalign=0.2,yalign=0.5) # left aligned
self.lr.set_size_request(ww,hh)
self.lr.hide()
mod_font_by_category(self.lr,'control')
self.tbtns = Gtk.HBox(homogeneous=0,spacing=2)
self.tbtns.set_border_width(0)
self.box.pack_start(self.tbtns, expand=0, fill=0, padding=0)
self.tbtns.pack_start(self.ll, expand=0, fill=0, padding=0)
self.tbtns.pack_start(self.en, expand=0, fill=0, padding=0)
self.tbtns.pack_start(self.lr, expand=0, fill=0, padding=0)
def grabit(self,*args,**kwargs):
#print 'grabit',self,args,kwargs
print('\ngrabit:can_get_focus:',self.en.get_can_focus())
self.en.grab_focus()
print('grabit:has_focus',self.en.has_focus())
print('grabit: is_focus',self.en.is_focus())
def popkeyboard(self,widget,v):
origtxt = self.en.get_text()
title = '#%s, <%s> %s' % (self.ll.get_text()
,self.en.get_text()
,self.lr.get_text()
)
self.en.set_text('')
if g_popkbd.run(initial_value='',title=title):
self.en.set_text(g_popkbd.get_result())
else:
# user canceled
self.en.set_text(origtxt)
def entry_changed(self,w):
v = w.get_text().lower()
if g_stat:
r = re.search('[xyzabcuvwd]',v)
if r:
char = r.group(0)
try:
w.set_text("%.4f" % coord_value(char))
except TypeError:
pass
except Exception as detail:
exception_show(Exception,detail,'entry_changed')
pass
if v == '':
w.set_style(g_ent_style_normal)
return
else:
try:
float(v)
w.set_style(g_ent_style_normal)
except ValueError:
w.set_style(g_ent_style_error)
return
try:
if ( (self.dv is not None)
and (float(v) == float(self.dv)) ):
w.set_style(g_ent_style_default)
return
except ValueError:
pass
w.set_style(g_ent_style_normal)
return
def getentry(self):
return(self.en.get_text())
def setentry(self,v):
self.en.set_text(v)
def clear_pentry(self):
self.ll.set_text('')
self.en.set_text('')
self.lr.set_text('')
self.ll.hide()
self.en.hide()
self.lr.hide()
def make_pentry(self,ll,dvalue,lr,emode='initial'):
# modes 'initial'
# 'keep'
self.dv = dvalue
if dvalue is None:
en = ''
else:
en = dvalue
if ll is None: ll=''
if lr is None: lr=''
self.ll.set_text(str(ll))
if emode == 'initial':
self.en.set_text(str(en))
# on reread, may be new parms with no text so use default
# if (emode == 'keep') and (not self.en.get_text()):
if (emode == 'keep') and (self.en.get_text() is None):
self.en.set_text(str(en))
self.lr.set_text(str(lr))
if dvalue is None or dvalue == '':
self.en.set_style(g_ent_style_normal) # normal (not a dvalue)
else:
self.en.set_style(g_ent_style_default) # a dvalue
self.ll.show()
self.en.show()
self.lr.show()
self.entry_changed(self.en)
class EntryFields():
"""EntryFields: Positional Parameters entry fields in a frame """
def __init__(self,nparms=INTERP_SUB_PARAMS):
if nparms > g_max_parm:
raise ValueError(_(
'EntryFields:nparms=%d g_max_parm=%d')
% (nparms,g_max_parm))
self.ebox = Gtk.Frame()
self.ebox.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
self.ebox.set_border_width(2)
efbox = Gtk.VBox()
evb = Gtk.VBox(homogeneous=0,spacing=2)
xpositionalp = Gtk.Label('Positional Parameters')
xpositionalp.set_alignment(xalign=0.0,yalign=0.5) # left aligned
epositionalp = Gtk.EventBox()
epositionalp.add(xpositionalp)
epositionalp.modify_bg(Gtk.StateType.NORMAL,label_normal_color)
lpositionalp = Gtk.Frame()
lpositionalp.set_shadow_type(Gtk.ShadowType.IN)
lpositionalp.set_border_width(0)
lpositionalp.add(epositionalp)
self.boxofcolumns = Gtk.HBox(homogeneous=0,spacing=2)
evb.pack_start(lpositionalp,expand=0,fill=1,padding=0)
evb.pack_start(self.boxofcolumns, expand=1,fill=1,padding=4)
efbox.pack_start(evb, expand=1,fill=1,padding=0)
self.ebox.add(efbox)
self.make_entryfields(nparms) # initialize for EntryFields
def make_entryfields(self,nparms):
self.no_of_entries = nparms
# make VBoxes as required to accommodate entries
# destroy them when starting over -- this occurs
# when a OnePg is reused for a different subfile
try:
type(self.columnbox) # test for existence
# destroy prior VBoxes packed in self.boxofcolumns
for c in self.boxofcolumns.children():
self.boxofcolumns.remove(c)
c.destroy()
del(c)
except AttributeError:
# first-time: create initial VBox for entries
self.columnbox = Gtk.VBox(homogeneous=0,spacing=2)
self.boxofcolumns.pack_start(self.columnbox,expand=0,fill=0,padding=0)
# try to use minimum height if less than 3 columns
if nparms > 20:
rowmax = 10
else:
rowmax = int(nparms/2 + 0.5)
self.pentries = {}
row = 0
idx = 1 # 1-based to agree with parm no.s
for i in range(0,nparms):
if row >= rowmax:
row = 0
# make a new VBox for next column of entries
self.columnbox = Gtk.VBox(homogeneous=0,spacing=2)
self.boxofcolumns.pack_start(self.columnbox
,expand=0,fill=0,padding=0)
self.pentries[idx] = OneParmEntry('','','')
self.columnbox.pack_start(self.pentries[idx].box
,expand=0,fill=0,padding=0)
row += 1
idx += 1
self.boxofcolumns.show_all()
def getentry_byidx(self,idx):
return(self.pentries[idx].getentry())
def clear_pentry_byidx(self,idx):
self.pentries[idx].clear_pentry()
def make_pentry_byidx(self,idx,ll,en,lr,emode='initial'):
self.pentries[idx].make_pentry(ll,en,lr,emode)
def getstuff_byidx(self,idx):
print("1getstuff idx=",idx)
self.pentries[idx].getstuff()
def get_box(self):
return self.ebox
def clear_parm_entries(self):
for pidx in range(1,self.no_of_entries+1):
self.clear_pentry_byidx(pidx)
def set_parm_entries(self,parms,emode='initial'):
lastpidx = 0
for pidx in sorted(parms.sub_data.ndict):
name,dvalue,comment = parms.sub_data.ndict[pidx]
self.make_pentry_byidx(pidx
,str(pidx)
,dvalue
,comment
,emode
)
lastpidx = pidx
class TestButtons():
"""TestButtons: debugging buttons"""
def __init__(self,mypg):
self.box = Gtk.HBox()
self.mypg = mypg
lbl = Gtk.Label('Debug:')
lbl.set_alignment(xalign=0.9,yalign=0.5) # rt aligned
self.box.pack_start(lbl,expand=0,fill=0,padding=2)
for item in ('info'
,'intfc'
,'nset'
,'nb'
,'page'
,'fset'
,'pre'
,'sub'
,'pst'
,'ent'
,'cp'
,'lcnc'
,'hal'
,'pos'
,'glo'
,'loc'
,'tst'
):
button = Gtk.Button(item)
if g_alive: button.connect("clicked", self.btest, item)
button.show_all()
self.box.pack_start(button,expand=0,fill=0,padding=2)
bclose = Gtk.Button('Close')
if g_alive: bclose.connect("clicked", lambda x: self.delete())
self.box.pack_start(bclose,expand=0,fill=0,padding=2)
def btest(self,widget,v):
m = self.mypg
if v == 'info':
p = m.nset
print('INFO--------------------------------------------------')
print(' sys.argv = %s' % sys.argv)
print(' cwd = %s' % os.getcwd())
print(' sys.path = %s' % sys.path)
print(' ini_file = %s' % p.intfc.get_ini_file())
print(' auto_file = %s' % p.auto_file)
print('subroutine_path = %s' % p.intfc.get_subroutine_path())
print(' user_m_path = %s' % p.intfc.get_user_m_path())
print(' pre_file = %s' % p.intfc.get_preamble())
print(' sublist = %s' % p.intfc.get_subfiles())
print(' pst_file = %s' % p.intfc.get_postamble())
print(' startpage_idx = %s' % p.startpage_idx)
print('')
print(' __file__ = %s' % __file__)
print('g_send_function = %s' % g_send_function)
print(' g_popkbd = %s' % g_popkbd)
print(' g_stat = %s' % g_stat)
print(' g_progname = %s' % g_progname)
print(' g_verbose = %s' % g_verbose)
print(' g_debug = %s' % g_debug)
print(' g_tmode = %s' % g_tmode)
print(' g_label_id = %s' % g_label_id)
elif v == 'ent':
print('ENTRIES--------------------------------------------------')
x = m.efields.pentries
pmax = m.fset.sub_data.pdict['lastparm']
print('efields.pentries[]')
for pidx in range(1,pmax+1):
print("%2d: %4s %-8s %-20s" % (pidx
,x[pidx].ll.get_text()
,x[pidx].en.get_text()
,x[pidx].lr.get_text()
))
print('ENTRIES==================================================')
elif v == 'intfc': d = m.nset.intfc; show_dir(d,tag='intfc')
elif v == 'page':
d = m; show_dir(d,tag='mypg')
x=self.mypg.efields.pentries[1].en
print('x=',x)
print(' has_focus:',x.has_focus())
print(' is_focus:',x.is_focus())
print(' get_can_focus:',x.get_can_focus())
elif v == 'pre': d = m.fset.pre_data; show_dir(d,tag='pre_data')
elif v == 'sub': d = m.fset.sub_data; show_dir(d,tag='sub_data')
elif v == 'pst': d = m.fset.pst_data; show_dir(d,tag='pst_data')
elif v == 'fset': d = m.fset; show_dir(d,tag='fset')
elif v == 'nset': d = m.nset; show_dir(d,tag='nset')
elif v == 'cp': d = m.cpanel; show_dir(d,tag='cpanel')
elif v == 'loc': show_dir(locals(),tag='locals')
elif v == 'glo': show_dir(globals(),tag='globals')
elif v == 'lcnc': show_dir(linuxcnc,tag='lcnc')
elif v == 'hal': show_dir(hal,tag='hal')
elif v == 'pos': show_position()
elif v == 'tst':
print('cpanel size:',m.cpanel.box.size_request())
print('mtable size:',m.mtable.size_request())
elif v == 'nb':
print('NB--------------------------------------------------')
for pno in range(m.nset.startpage_idx
,m.mynb.get_n_pages()):
npage = m.mynb.get_nth_page(pno)
pg = m.nset.pg_for_npage[npage]
ltxt = pg.the_lbl.get_text()
print('%10s %s' % (ltxt,pg))
print('NB==================================================')
else: print('btest unknown:',v)
def delete(self):
Gtk.main_quit()
return False
class ControlPanel():
"""ControlPanel: Controls and image display"""
def __init__(self
,mypg
,pre_file=''
,sub_file=''
,pst_file=''
):
self.mypg = mypg
frame = Gtk.Frame()
frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
frame.set_border_width(2)
self.box = frame
cpbox = Gtk.VBox()
# fixed width so it doesn't change when switching tabs
# fixed height to allow room for buttons below image
#cpbox.set_size_request(g_image_width,g_image_height)
bw = 1
bpre = Gtk.Button(_('Preamble'))
bpre.set_border_width(bw)
mod_font_by_category(bpre)
bsub = Gtk.Button(_('Subfile'))
bsub.set_border_width(bw)
mod_font_by_category(bsub)
bpst = Gtk.Button(_('Postamble'))
bpst.set_border_width(bw)
mod_font_by_category(bpst)
self.pre_entry = Gtk.Entry()
self.pre_entry.set_state(Gtk.StateType.INSENSITIVE)
self.sub_entry = Gtk.Entry()
self.sub_entry.set_state(Gtk.StateType.INSENSITIVE)
self.pst_entry = Gtk.Entry()
self.pst_entry.set_state(Gtk.StateType.INSENSITIVE)
chars=10
self.pre_entry.set_width_chars(chars)
self.pre_entry.set_alignment(xalign=0.1)
self.pre_entry.set_text(os.path.basename(pre_file))
if g_alive: self.pre_entry.connect("activate", self.file_choose, 'pre')
self.sub_entry.set_width_chars(chars)
self.sub_entry.set_alignment(xalign=0.1)
self.sub_entry.set_text(os.path.basename(sub_file))
if g_alive: self.sub_entry.connect("activate", self.file_choose, 'sub')
self.pst_entry.set_width_chars(chars)
self.pst_entry.set_alignment(xalign=0.1)
self.pst_entry.set_text(os.path.basename(pst_file))
if g_alive: self.pst_entry.connect("activate", self.file_choose, 'pst')
xcontrol = Gtk.Label('Controls')
xcontrol.set_alignment(xalign=0.0,yalign=0.5) # left aligned
econtrol = Gtk.EventBox()
econtrol.add(xcontrol)
econtrol.modify_bg(Gtk.StateType.NORMAL,label_normal_color)
lcontrol= Gtk.Frame()
lcontrol.set_shadow_type(Gtk.ShadowType.IN)
lcontrol.set_border_width(1)
lcontrol.add(econtrol)
tfiles = Gtk.Table(rows=3, columns=2, homogeneous=0)
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = 0
tfiles.attach(bpre,0,1,0,1,xoptions=bx,yoptions=by)
tfiles.attach(bsub,0,1,1,2,xoptions=bx,yoptions=by)
tfiles.attach(bpst,0,1,2,3,xoptions=bx,yoptions=by)
tfiles.attach(self.pre_entry,1,2,0,1,xoptions=bx,yoptions=by)
tfiles.attach(self.sub_entry,1,2,1,2,xoptions=bx,yoptions=by)
tfiles.attach(self.pst_entry,1,2,2,3,xoptions=bx,yoptions=by)
if g_alive: bpre.connect("clicked", self.file_choose, 'pre')
if g_alive: bsub.connect("clicked", self.file_choose, 'sub')
if g_alive: bpst.connect("clicked", self.file_choose, 'pst')
#bretain = Gtk.CheckButton('Retain values on Subfile read')
self.bexpand = Gtk.CheckButton('Expand Subroutine')
self.bexpand.set_active(self.mypg.expandsub)
if g_alive: self.bexpand.connect("toggled", self.toggle_expandsub)
self.bautosend = Gtk.CheckButton('Autosend')
self.bautosend.set_active(self.mypg.autosend)
if g_alive: self.bautosend.connect("toggled", self.toggle_autosend)
tchkbs = Gtk.Table(rows=3, columns=1, homogeneous=0)
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
#tchkbs.attach(bretain, 0,1,0,1,xoptions=bx,yoptions=by)
tchkbs.attach(self.bexpand, 0,1,1,2,xoptions=bx,yoptions=by)
nopts = self.mypg.nset.intfc.get_ngcgui_options()
if (nopts is None) or ('noauto' not in nopts):
tchkbs.attach(self.bautosend,0,1,2,3,xoptions=bx,yoptions=by)
bw = 1
bcreate = Gtk.Button(_('Create Feature'))
bcreate.set_border_width(bw)
if g_alive: bcreate.connect("clicked", lambda x: self.create_feature())
mod_font_by_category(bcreate)
bfinalize = Gtk.Button(_('Finalize'))
bfinalize.set_border_width(bw)
if g_alive: bfinalize.connect("clicked"
,lambda x: self.finalize_features())
mod_font_by_category(bfinalize)
self.lfct = Gtk.Label(str(mypg.feature_ct))
self.lfct.set_alignment(xalign=0.9,yalign=0.5) # right aligned
mod_font_by_category(self.lfct)
lfctf = Gtk.Frame()
lfctf.set_shadow_type(Gtk.ShadowType.IN)
lfctf.set_border_width(2)
lfctf.add(self.lfct)
self.breread = Gtk.Button(_('Reread'))
self.breread.set_border_width(bw)
if g_alive: self.breread.connect("clicked"
,lambda x: self.reread_files())
mod_font_by_category(self.breread)
brestart = Gtk.Button(_('Restart'))
brestart.set_border_width(bw)
if g_alive: brestart.connect("clicked"
,lambda x: self.restart_features())
mod_font_by_category(brestart)
self.lmsg = Gtk.Label(_('Ctrl-k for key shortcuts'))
self.lmsg.set_alignment(xalign=0.05,yalign=0.5) # left aligned
lmsgf = Gtk.Frame()
lmsgf.set_shadow_type(Gtk.ShadowType.IN)
lmsgf.set_border_width(2)
lmsgf.add(self.lmsg)
tactions = Gtk.Table(rows=3, columns=3, homogeneous=1)
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
tactions.attach(bcreate, 0,2,0,1,xoptions=bx,yoptions=by)
tactions.attach(bfinalize,2,3,0,1,xoptions=bx,yoptions=by)
# only if image (see below)
# tactions.attach(self.breread ,0,1,1,2,xoptions=bx,yoptions=by)
tactions.attach(brestart, 2,3,1,2,xoptions=bx,yoptions=by)
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = 0
#tactions.attach(self.lmsg,0,3,2,3,xoptions=bx,yoptions=by)
tactions.attach(lmsgf,0,3,2,3,xoptions=bx,yoptions=by)
nopts = self.mypg.nset.intfc.get_ngcgui_options()
image_file = find_image(sub_file)
if image_file:
img = sized_image(image_file)
if ( (not image_file)
or (nopts is not None and 'noiframe' in nopts)
or mypg.imageoffpage
):
# show all controls
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
tactions.attach(self.breread, 0,1,1,2,xoptions=bx,yoptions=by)
tactions.attach(lfctf, 1,2,1,2,xoptions=bx,yoptions=by)
cpbox.pack_start(lcontrol,expand=0,fill=0,padding=0)
cpbox.pack_start(tfiles, expand=0,fill=0,padding=0)
cpbox.pack_start(tchkbs, expand=0,fill=0,padding=0)
if image_file:
self.separate_image(img,sub_file,show=False)
mypg.imageoffpage = True
else:
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
tactions.attach(lfctf, 0,2,1,2,xoptions=bx,yoptions=by)
# show image instead of controls
if image_file:
cpbox.pack_start(img,expand=0,fill=0,padding=0)
mypg.imageoffpage = False
cpbox.pack_start(tactions,expand=1,fill=1,padding=0)
cpbox.show()
frame.add(cpbox)
def separate_image(self,img,fname='',show=True):
self.mypg.imgw = Gtk.Window(Gtk.WindowType.TOPLEVEL)
w = self.mypg.imgw
w.hide()
w.iconify()
w.set_title(os.path.basename(fname))
w.add(img)
if g_alive: w.connect("destroy",self.wdestroy)
if show:
w.show_all()
w.deiconify()
def wdestroy(self,widget):
del self.mypg.imgw
def set_message(self,msg):
self.lmsg.set_label(msg)
def reread_files(self):
vprint('REREAD')
# user can edit file and use button to reread it
if self.mypg.sub_file == '':
vprint('reread_files NULL subfile')
return False
self.mypg.fset.pre_data.read()
self.mypg.fset.sub_data.re_read() # handle ngc or gcmc
self.mypg.fset.pst_data.read()
self.mypg.update_onepage('pre',self.mypg.pre_file)
self.mypg.update_onepage('sub',self.mypg.sub_file)
self.mypg.update_onepage('pst',self.mypg.pst_file)
self.set_message(_('Reread files'))
return True # success
def restart_features(self):
try:
type(self.mypg.savesec) # test for existence
self.mypg.savesec = []
except AttributeError:
pass
self.mypg.feature_ct = 0
self.lfct.set_label(str(self.mypg.feature_ct))
self.mypg.savesec = []
self.mypg.update_tab_label('default')
self.set_message(_('Restart'))
def toggle_autosend(self, widget):
self.mypg.autosend = (0,1)[widget.get_active()]
self.set_message(_('Toggle autosend %s ') % str(self.mypg.autosend))
def toggle_expandsub(self, widget):
self.mypg.expandsub = (0,1)[widget.get_active()]
self.set_message(_('Toggle expandsub %s') % str(self.mypg.expandsub))
def checkb_toggle(self, widget, var):
print('1T',var,type(var))
var = (0,1)[widget.get_active()]
print('2T',var,type(var))
def create_feature(self):
m=self.mypg
p=self.mypg.fset
fpre,fprestat = m.nset.intfc.find_file_in_path(m.pre_file)
fsub,fsubstat = m.nset.intfc.find_file_in_path(m.sub_file)
fpst,fpststat = m.nset.intfc.find_file_in_path(m.pst_file)
if fsubstat == 'NULLFILE':
vprint('create_feature: NULLFILE')
return
# the test for NOPATH is for special cases
if ( (fpre != p.pre_data.pre_file) and fprestat != 'NOPATH'
or (fsub != p.sub_data.sub_file) and fsubstat != 'NOPATH'
or (fpst != p.pst_data.pst_file) and fpststat != 'NOPATH'
):
print('\nUSER changed filename entry without loading\n')
try:
type(self.mypg.savesec) # test for existence
except AttributeError:
self.mypg.savesec = []
self.set_message(_('Create feature'))
# update for current entry filenames
p.pre_data = PreFile(m.pre_file) # may be ''
p.sub_data = SubFile(m.sub_file) # error for ''
p.pst_data = PstFile(m.pst_file) # may be ''
if 'isgcmc' in p.sub_data.pdict:
stat = self.savesection_gcmc()
else:
stat = self.savesection_ngc()
if stat:
if m.feature_ct > 0:
self.mypg.update_tab_label('multiple')
else:
self.mypg.update_tab_label('created')
m.feature_ct = m.feature_ct + 1
self.lfct.set_label(str(m.feature_ct))
self.set_message(_('Created Feature #%d') % m.feature_ct)
else:
#print "savesection fail"
pass
def savesection_ngc(self):
m=self.mypg
p=self.mypg.fset
force_expand = False
# if file not in path and got this far, force expand
fname,stat = m.nset.intfc.find_file_in_path(m.sub_file)
if stat == 'NOTFOUND':
force_expand = True
user_message(mtype=Gtk.MessageType.INFO
,title=_('Expand Subroutine')
,msg=_('The selected file') + ':\n\n'
+ '%s\n\n'
+ _('is not in the linuxcnc path\n'
'Expanding in place.\n\n'
'Note: linuxcnc will fail if it calls\n'
'subfiles that are not in path\n')
% fname)
try:
self.mypg.savesec.append(
SaveSection(mypg = self.mypg
,pre_info = p.pre_data
,sub_info = p.sub_data
,pst_info = p.pst_data
,force_expand = force_expand
)
)
except ValueError:
dprint('SAVESECTION_ngc: failed')
return True # success
def savesection_gcmc(self):
m=self.mypg
p=self.mypg.fset
intfc = self.mypg.nset.intfc
global g_gcmc_exe
if g_gcmc_exe is None:
if not find_gcmc():
return False ;# fail
xcmd = []
xcmd.append(g_gcmc_exe)
global g_gcmc_funcname
global g_gcmc_id
g_gcmc_id += 1
# gcmc chars in funcname: (allowed: [a-z0-9_-])
funcname = "%s_%02d"%(g_gcmc_funcname,g_gcmc_id)
p.sub_data.pdict['subname'] = funcname
include_path = intfc.get_gcmc_include_path()
if include_path is not None:
for dir in include_path.split(":"):
xcmd.append("--include")
xcmd.append(os.path.expanduser(dir))
# maybe: xcmd.append("--include")
# maybe: xcmd.append(os.path.dirname(m.sub_file))
# note: gcmc also adds the current directory
# to the search path as last entry.
outdir = g_searchpath[0] # first in path
ofile = os.path.join(outdir,funcname) + ".ngc"
xcmd.append("--output")
xcmd.append(ofile)
xcmd.append('--gcode-function')
xcmd.append(funcname)
for opt in p.sub_data.gcmc_opts:
splitopts = opt.split(' ')
xcmd.append(str(splitopts[0]))
if len(splitopts) > 1:
xcmd.append(str(splitopts[1])) # presumes only one token
for k in list(p.sub_data.ndict.keys()):
#print 'k=',k,p.sub_data.ndict[k]
name,dvalue,comment = p.sub_data.ndict[k]
# make all entry box values explicitly floating point
try:
fvalue = str(float(m.efields.pentries[k].getentry()))
except ValueError:
user_message(mtype=Gtk.MessageType.ERROR
,title='gcmc input ERROR'
,msg=_('<%s> must be a number' % m.efields.pentries[k].getentry())
)
return False ;# fail
xcmd.append('--define=' + name + '=' + fvalue)
xcmd.append(m.sub_file)
print("xcmd=",xcmd)
e_message = ".*Runtime message\(\): *(.*)"
e_warning = ".*Runtime warning\(\): *(.*)"
e_error = ".*Runtime error\(\): *(.*)"
s = subprocess.Popen(xcmd
,stdout=subprocess.PIPE
,stderr=subprocess.PIPE
)
sout,eout = s.communicate()
m_txt = ""
w_txt = ""
e_txt = ""
compile_txt = ""
if eout:
if (len(eout) > g_max_msg_len):
# limit overlong, errant msgs
eout = eout[0:g_max_msg_len] + "..."
for line in eout.split("\n"):
r_message = re.search(e_message,line)
r_warning = re.search(e_warning,line)
r_error = re.search(e_error,line)
if r_message:
m_txt += r_message.group(1) + "\n"
elif r_warning:
w_txt += r_warning.group(1) + "\n"
elif r_error:
e_txt += r_error.group(1) + "\n"
else:
compile_txt += line
if m_txt != "":
user_message(mtype=Gtk.MessageType.INFO
,title='gcmc INFO'
,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,m_txt)
)
if w_txt != "":
user_message(mtype=Gtk.MessageType.WARNING
,title='gcmc WARNING'
,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,w_txt)
)
if e_txt != "":
user_message(mtype=Gtk.MessageType.ERROR
,title='gcmc ERROR'
,msg="gcmc File:\n%s\n\n%s"%(m.sub_file,e_txt)
)
if compile_txt != "":
user_message(mtype=Gtk.MessageType.ERROR
,title='gcmc Compile ERROR'
,msg="gcmc File:%s"%(compile_txt)
)
if s.returncode:
return False ;# fail
self.mypg.savesec.append(
SaveSection(mypg = self.mypg
,pre_info = p.pre_data
,sub_info = p.sub_data
,pst_info = p.pst_data
,force_expand = False # never for gcmc
)
)
return True # success
def finalize_features(self):
mypg = self.mypg
nb = self.mypg.mynb
nset = self.mypg.nset
if mypg.feature_ct <= 0:
msg = _('No features specified on this page')
self.set_message(msg)
user_message(mtype=Gtk.MessageType.WARNING
,title='No Features'
,msg=msg)
return
if len(mypg.savesec) == 0:
msg = 'finalize_features: Unexpected: No features'
self.set_message(_('No features'))
raise ValueError(msg)
return
txt = ''
plist = []
sequence = ""
# these are in left-to-right order
for pno in range(nset.startpage_idx,nb.get_n_pages()):
npage = nb.get_nth_page(pno)
#Using EventBox for tabpage labels: dont use get_tab_label_text()
pg = nset.pg_for_npage[npage]
ltxt = pg.the_lbl.get_text()
howmany = len(pg.savesec)
if howmany > 0:
plist.append(pg)
sequence = sequence + " " + ltxt
txt = txt + "%s has %d features\n" % (ltxt,howmany)
vprint(txt)
if len(plist) > 1:
msg = (_('Finalize all Tabs?\n\n'
'No: Current page only\n'
'Yes: All pages\n'
'Cancel: Nevermind\n\n'
'Order:'
)
+ '\n<' + sequence + '>\n\n'
'You can Cancel and change the order with the\n'
'Forward and Back buttons\n'
)
popup = Gtk.Dialog(title='Page Selection'
,parent=None
,flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT
,buttons=(Gtk.STOCK_NO, Gtk.ResponseType.NO
,Gtk.STOCK_YES, Gtk.ResponseType.YES
,Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL
)
)
finbox = popup.get_content_area()
l = Gtk.Label(msg)
finbox.pack_start(l,expand=0,fill=0,padding=0)
popup.show_all()
ans = popup.run()
popup.destroy()
if ans == Gtk.ResponseType.YES:
pass # use plist for all pages
elif ans == Gtk.ResponseType.NO:
pno = self.mypg.mynb.get_current_page()
npage = nb.get_nth_page(pno)
plist = [nset.pg_for_npage[npage]]
elif ( ans == Gtk.ResponseType.CANCEL
or ans == Gtk.ResponseType.DELETE_EVENT): # window close
return # do nothing
else:
raise ValueError('finalize_features:unknown ans<%d>'%ans)
# make a unique filename
# (avoids problems with gremlin ignoring new file with same name)
global g_auto_file_ct
autoname = nset.auto_file
dirname = os.path.realpath(os.path.dirname(autoname))
basename = str(g_auto_file_ct) + "." + os.path.basename(autoname)
tmpname = os.path.join(dirname,basename)
if os.path.exists(tmpname):
os.remove(tmpname)
# hack: alternate names (0,1) to force gremlin file loading
# and touchy filechooser updates
g_auto_file_ct = (g_auto_file_ct + 1)%2
basename = str(g_auto_file_ct) + "." + os.path.basename(autoname)
tmpname = os.path.join(dirname,basename)
self.mypg.nset.last_file = tmpname
savename = None
f = open(tmpname,'w')
nopts = self.mypg.nset.intfc.get_ngcgui_options()
if (('nom2' in nopts) or g_nom2):
f.write("%\n")
f.write("(%s: nom2 option)\n" % g_progname)
featurect = 0; features_total=0
for pg in plist:
features_total = features_total + len(pg.savesec)
for pg in plist:
ct = self.write_to_file(f,pg,featurect,features_total)
featurect += ct
pg.feature_ct = 0
self.lfct.set_label(str(pg.feature_ct))
pg.savesec = []
if (('nom2' in nopts) or g_nom2):
f.write("%\n")
else:
f.write("(%s: m2 line added) m2 (g54 activated)\n" % g_progname)
f.close()
user_must_save = True # disprove with send_function
title_message = ''
if self.mypg.autosend:
if g_send_function(tmpname):
user_must_save = False
self.set_message(_('Finalize: Sent file'))
save_a_copy(tmpname)
print('%s:SENT: %s' % (g_progname,tmpname))
print('%s:SENT:using: %s' % (g_progname,g_send_function.__name__))
else:
title_message = (
_('Sending file failed using function: <%s>, user must save')
% g_send_function.__name__)
self.set_message(_('Finalize: Sent file failed'))
print('%s:SAVEDFILE: after send failed: %s'
% (g_progname,tmpname))
if user_must_save:
fname = os.path.abspath(nset.auto_file)
if self.mypg.nset.last_file is not None:
fname = self.mypg.nset.last_file # last user choice
savename = file_save(fname,title_message) # user may change name
if savename is not None:
shutil.move(tmpname,savename)
save_a_copy(savename)
self.mypg.nset.last_file = savename
for pg in plist:
pg.cpanel.restart_features()
pg.update_tab_label('default')
global g_label_id
g_label_id = 0 # reinitialize
return
def write_to_file(self,file,pg,featurect,features_total):
ct = 0
for i in range(0,len(pg.savesec) ):
ct += 1
for l in pg.savesec[i].sdata:
if l.find("#<_feature:>") == 0:
file.write(
"(%s: feature line added) #<_feature:> = %d\n"\
% (g_progname,featurect))
featurect += 1
file.write(
"(%s: remaining_features line added) "
" #<_remaining_features:> = %d\n"\
% (g_progname,features_total - featurect))
else:
file.write(l)
return(ct)
def file_choose(self,widget,ftype):
mydiag = CandidateDialog(ftype=ftype)
while True:
response = mydiag.run()
fname,errmsg = mydiag.get_file_result()
if response == Gtk.ResponseType.ACCEPT:
vprint('file_choose: ACCEPT')
self.mypg.cpanel.set_message(_('file_choose ACCEPT'))
pass
elif response == Gtk.ResponseType.REJECT:
self.mypg.cpanel.set_message(_('file_choose REJECT'))
vprint('file_choose: REJECT')
mydiag.destroy()
return None
elif response == Gtk.ResponseType.NO:
self.mypg.cpanel.set_message(_('No File'))
fname = 'nofile' # allow pre,pst nofile
vprint('file_choose: No File')
else:
self.mypg.cpanel.set_message(_('file_choose OTHER'))
mydiag.destroy()
raise ValueError(_('file_choose OTHER %s') % str(response))
return None
if fname == 'TRYAGAIN':
user_message(mtype=Gtk.MessageType.INFO
,title=_('Try Again')
,msg=errmsg
)
continue
break
mydiag.destroy()
if ftype == 'pre':
self.mypg.fset.pre_file = fname
elif ftype == 'sub':
self.mypg.fset.sub_file = fname
elif ftype == 'pst':
self.mypg.fset.pst_file = fname
else:
raise ValueError("file_choose ftype?").with_traceback(ftype)
# None for no file selected, null out field could be useful
if not fname:
self.mypg.cpanel.set_message(_('file_choose no file?'))
return None
if ftype == 'pre':
if fname == 'nofile':
fname = ''
self.pre_entry.set_text(os.path.basename(fname))
self.mypg.update_onepage('pre',fname)
elif ftype == 'sub':
image_file = find_image(fname)
if image_file:
img = sized_image(image_file)
self.separate_image(img,fname,show=True)
self.mypg.imageoffpage = True
if self.mypg.update_onepage('sub',fname):
self.sub_entry.set_text(os.path.basename(fname))
elif ftype == 'pst':
if fname == 'nofile':
fname = ''
self.pst_entry.set_text(os.path.basename(fname))
self.mypg.update_onepage('pst',fname)
else:
raise ValueError('file_choose:Unexpected ftype <%s>' %ftype)
self.mypg.cpanel.set_message(_('Read %s') % os.path.basename(fname))
return
class OnePg():
"""OnePg: ngcgui info for one tab page"""
def __init__(self
,pre_file
,sub_file
,pst_file
,mynb
,nset
,imageoffpage=False
):
self.imageoffpage = imageoffpage # for clone of Custom pages
self.garbagecollect = False
self.key_enable = False
self.pre_file,stat = nset.intfc.find_file_in_path(pre_file)
self.sub_file,stat = nset.intfc.find_file_in_path(sub_file)
self.pst_file,stat = nset.intfc.find_file_in_path(pst_file)
self.nset = nset
self.mynb = mynb
self.autosend = nset.autosend
self.expandsub = nset.expandsub
self.feature_ct = 0
self.savesec = []
self.cpanel = ControlPanel(mypg=self
,pre_file=self.pre_file
,sub_file=self.sub_file
,pst_file=self.pst_file
)
bw = 1
#bremove = Gtk.Button(_('Remove'))
bremove = Gtk.Button(stock=Gtk.STOCK_DELETE)
bremove.set_border_width(bw)
if g_alive: bremove.connect("clicked", lambda x: self.remove_page())
#bclone = Gtk.Button(_('Clone'))
bclone = Gtk.Button(stock=Gtk.STOCK_ADD)
bclone.set_border_width(bw)
if g_alive: bclone.connect("clicked", lambda x: self.clone_page())
#bnew = Gtk.Button(_('New'))
bnew = Gtk.Button(stock=Gtk.STOCK_NEW)
bnew.set_border_width(bw)
if g_alive: bnew.connect("clicked", lambda x: self.new_empty_page())
#bmoveleft = Gtk.Button(_('<==Move'))
bmoveleft = Gtk.Button(stock=Gtk.STOCK_GO_BACK,label='')
bmoveleft.set_border_width(bw)
if g_alive: bmoveleft.connect("clicked", lambda x: self.move_left())
#bmoveright = Gtk.Button(_('Move==>'))
bmoveright = Gtk.Button(stock=Gtk.STOCK_GO_FORWARD,label='')
bmoveright.set_border_width(bw)
if g_alive: bmoveright.connect("clicked", lambda x: self.move_right())
# stock buttons notwork with mod_font_by_category
#mod_font_by_category(bremove)
#mod_font_by_category(bclone)
#mod_font_by_category(bnew)
#mod_font_by_category(bmoveleft)
#mod_font_by_category(bmoveright)
tabarrange_buttons = Gtk.HBox() # main buttons
self.mtable = Gtk.Table(rows=1, columns=2, homogeneous=0)
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND; by = 0
no_of_parms = g_max_parm
self.make_fileset()
no_of_parms = self.fset.sub_data.pdict['lastparm']
self.efields = EntryFields(no_of_parms) # uses MultipleParmEntries item
self.fill_entrypage(emode='initial')
bx = 0; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
self.mtable.attach(self.cpanel.box, 0,1,0,1,xoptions=bx,yoptions=by)
bx = Gtk.AttachOptions.FILL; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
bx = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND ; by = Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND
entrystuff = self.efields.get_box()
self.mtable.attach(entrystuff, 1,2,0,1,xoptions=bx,yoptions=by)
tbtns = TestButtons(mypg=self) # TestButtons
nopts = nset.intfc.get_ngcgui_options()
if (nopts is None) or ('noremove' not in nopts):
tabarrange_buttons.pack_start(bremove,expand=0,fill=0,padding=0)
if (nopts is None) or ('nonew' not in nopts):
tabarrange_buttons.pack_start(bclone,expand=0,fill=0,padding=0)
tabarrange_buttons.pack_start(bnew,expand=0,fill=0,padding=0)
tabarrange_buttons.pack_start(bmoveleft,expand=0,fill=0,padding=0)
tabarrange_buttons.pack_start(bmoveright,expand=0,fill=0,padding=0)
op_box = Gtk.VBox()
if g_tab_controls_loc == 'top':
op_box.pack_start(tabarrange_buttons,expand=0,fill=0,padding=0)
elif g_tab_controls_loc == 'bottom':
op_box.pack_end(tabarrange_buttons,expand=0,fill=0,padding=0)
else:
raise ValueError(g_progname
+ ' unknown tab_controls_loc %s' % g_tab_controls_loc)
op_box.pack_start(self.linfof, expand=0,fill=0,padding=0)
op_box.pack_start(self.mtable, expand=1,fill=1,padding=0)
ct=0
if g_debug:
op_box.pack_end(tbtns.box, expand=0,fill=0,padding=5)
op_box.show_all()
self.pgbox = Gtk.EventBox()
self.pgbox.add(op_box)
self.pgbox.show_all()
if g_alive: self.pgbox.connect('event',self.any_event)
# establish size with max no of entries
#wip: gtk3 notworking:
# "cannot unpack non-iterable Requisition object
#ww,hh = self.mtable.size_request()
#print('size for mtable:',ww,hh)
#self.mtable.set_size_request(ww,hh)
lastpidx = self.fset.sub_data.pdict['lastparm']
GObject.timeout_add_seconds(g_check_interval,self.periodic_check)
def periodic_check(self):
try:
for i in ('pre','sub','pst'):
o_entry = getattr(self.cpanel,i + '_entry')
if o_entry.get_text().strip() == '': continue
o_file = getattr(self, i + '_file')
o_data = getattr(self.fset, i + '_data')
o_md5 = getattr(o_data, 'md5')
o_mtime = getattr(o_data, 'mtime')
if ( (o_mtime != None)
and (o_mtime == os.path.getmtime(o_file))):
state = o_entry.get_state()
o_entry.modify_text(state,black_color)
continue
if (o_md5 != md5sum(o_file)):
#print('%s,%s>' % (o_md5,md5sum(o_file)))
#print(i,'CHANGED md5',o_file,o_md5)
state = o_entry.get_state()
o_entry.modify_text(state,purple_color)
else:
#print(i,'SAME md5',o_file,o_md5)
o_entry.modify_text(Gtk.StateType.NORMAL,black_color)
except OSError as detail:
print((_('%s:periodic_check:OSError:%s') % detail))
pass # continue without checks after showing message
except Exception as detail:
exception_show(Exception,detail,'periodic_check')
raise Exception(detail) # reraise
if self.garbagecollect:
return False # False to norepeat (respond to del for self)
return True # True to repeat
def any_event(self,widget,event):
if event.type == Gdk.EventType.ENTER_NOTIFY:
#widget.set_can_focus(True)
self.key_enable = True
#print('ENTER enable')
return
elif event.type == Gdk.EventType.LEAVE_NOTIFY:
#print "LEAVE can, is",widget.is_focus(),widget.get_can_focus(),'\n'
if widget.get_can_focus():
#widget.set_can_focus(False)
self.key_enable = False
#print('LEAVE disable')
return
elif event.type == Gdk.EventType.EXPOSE:
widget.grab_focus()
return
elif event.type == Gdk.EventType.KEY_PRESS:
if not self.key_enable:
#print('IGNORE')
return
keyname = Gdk.keyval_name(event.keyval)
kl = keyname.lower()
# ignore special keys (until they modify)
if kl in ['alt_r','alt_l'] : return
if kl in ['control_r','control_l'] : return
if kl in ['shift_r','shift_l'] : return
pre = ''
if event.state & Gdk.ModifierType.CONTROL_MASK:
pre = "Control-"
elif event.state & Gdk.ModifierType.MOD1_MASK:
pre = "Alt-"
elif event.state & Gdk.ModifierType.SHIFT_MASK:
pre = "Shift-"
k = pre + keyname
#print("%10s (%03d=%#2X)" % (k, event.keyval,event.keyval))
self.handle_key(k)
return False # allow other handlers
def handle_key(self,k):
if k == 'Control-d':
self.make_fileset()
self.fill_entrypage(emode='initial')
if k == 'Control-a':
self.cpanel.bautosend.clicked()
if k == 'Control-#':
self.cpanel.bexpand.clicked()
if k == 'Control-k':
self.show_special_keys()
if k == 'Control-r':
# was ctrl-p,P,r in ngcgui
self.cpanel.breread.clicked()
if k == 'Control-e':
self.edit_any_file(self.nset.last_file,'last')
if k == 'Control-E':
self.cpanel.bexpand.clicked()
if k == 'Control-u':
self.edit_std_file('sub')
if k == 'Control-U':
self.edit_std_file('pre')
#else:
# print('handle_key: k=',k)
return False # False: allow more handlers
def edit_any_file(self,fname,ftype=''):
if not fname:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('No file')
,msg=_('No %s file specified') % ftype
)
return
subprocess.Popen([self.nset.intfc.editor, fname])
def edit_std_file(self,which):
o_file = getattr(self, which + '_file')
self.edit_any_file(o_file,which)
#NB some key bindings are claimed on touchy
def show_special_keys(self):
msg = []
msg.append('Control-a ' + _('Toggle autosend') + '\n')
msg.append('Control-e ' + _('Edit last result file') + '\n')
msg.append('Control-E ' + _('Toggle expandsubroutines') + '\n')
msg.append('Control-d ' + _('Set Entry defaults') + '\n')
msg.append('Control-k ' + _('Show keys (this)') + '\n')
msg.append('Control-r ' + _('Reread files') + '\n')
msg.append('Control-u ' + _('Edit sub file') + '\n')
msg.append('Control-U ' + _('Edit preamble file') + '\n')
user_message(mtype=Gtk.MessageType.INFO
,title=_('Special Keys')
,flags=0 #still MODAL ??
,msg=msg)
def set_page_label(self,lbl):
self.lbl = lbl
def save_onepage_tablabel(self,eb_lbl,the_lbl):
self.eb_lbl = eb_lbl
self.the_lbl = the_lbl
def update_tab_label(self,umode):
if umode == 'created':
newcolor = fg_created_color
newstyle = g_lbl_style_created
elif umode == 'multiple':
newcolor = fg_multiple_color
newstyle = g_lbl_style_multiple
elif umode == 'default':
newcolor = fg_normal_color
newstyle = g_lbl_style_default
else:
newstyle = g_lbl_style_default
newcolor = fg_normal_color
self.eb_lbl.set_style(newstyle)
self.the_lbl.modify_fg(Gtk.StateType.NORMAL, newcolor)
self.the_lbl.modify_fg(Gtk.StateType.ACTIVE, newcolor)
def make_fileset(self):
try:
self.fset = FileSet(pre_file=self.pre_file
,sub_file=self.sub_file
,pst_file=self.pst_file
)
except OSError as detail:
print(_('%s:make_fileset:%s' % (g_progname,detail) ))
raise OSError(detail) # reraise
def fill_entrypage(self,emode='initial'):
self.efields.set_parm_entries(self.fset,emode)
try:
type(self.info_label) # test for existence
except AttributeError:
self.info_label = Gtk.Label()
self.linfof = Gtk.Frame()
self.linfof.set_shadow_type(Gtk.ShadowType.IN)
self.linfof.set_border_width(2)
self.linfof.add(self.info_label)
self.info_label.set_label(self.fset.sub_data.pdict['info'])
self.info_label.set_alignment(xalign=0.0,yalign=0.5) # left aligned
self.cpanel.set_message(_('Set Entry defaults'))
def clear_entrypage(self):
self.efields.clear_parm_entries()
self.info_label.set_label('')
def update_onepage(self,type,fname):
vprint('UPDATE_PAGE %s file=%s' % (type,fname))
if type == 'pre':
foundname,stat = self.nset.intfc.find_file_in_path(fname)
if stat == 'NOTFOUND':
self.clear_entries('pre')
return
self.pre_file = foundname
self.fset.pre_data = PreFile(self.pre_file)
elif type == 'sub':
foundname,stat = self.nset.intfc.find_file_in_path(fname)
if stat == 'NOTFOUND':
self.clear_entries('sub')
return
self.sub_file = foundname
try:
self.make_fileset()
lastparm = self.fset.sub_data.pdict['lastparm']
self.efields.make_entryfields(lastparm) # update_onepage
self.fill_entrypage()
self.info_label.set_label(self.fset.sub_data.pdict['info'])
lbltxt = self.fset.sub_data.pdict['subname']
lbltxt = self.nset.make_unique_tab_name(lbltxt)
self.the_lbl.set_text(lbltxt)
return True
except Exception as detail:
exception_show(Exception,detail,'update_onepage')
return False
elif type == 'pst':
foundname,stat = self.nset.intfc.find_file_in_path(fname)
if stat == 'NOTFOUND':
self.clear_entries('pst')
return
self.pst_file = foundname
self.fset.pst_data = PstFile(self.pst_file)
else:
raise ValueError('update_onepage unexpected type <%s>' % type)
return
def clear_entries(self,fmode):
if fmode == 'pre':
self.pre_file = ''
self.cpanel.pre_entry.set_text('')
self.fset.pre_data.clear()
elif fmode == 'sub':
self.sub_file = ''
self.cpanel.sub_entry.set_text('')
self.clear_entrypage()
self.fset.sub_data.clear()
elif fmode == 'pst':
self.pst_file = ''
self.cpanel.pst_entry.set_text('')
self.fset.pst_data.clear()
else:
raise ValueError('clear_entries:unexpected fmode= %s' % fmode)
def move_left(self):
page_idx = self.mynb.get_current_page()
page_ct = self.mynb.get_n_pages()
page = self.mynb.get_nth_page(page_idx)
new_pg_idx = page_idx - 1
if new_pg_idx < self.nset.startpage_idx:
new_pg_idx = page_ct -1
self.mynb.reorder_child(page,new_pg_idx%page_ct)
def move_right(self):
page_idx = self.mynb.get_current_page()
page_ct = self.mynb.get_n_pages()
page = self.mynb.get_nth_page(page_idx)
new_pg_idx = (page_idx + 1)%page_ct
if new_pg_idx < self.nset.startpage_idx:
new_pg_idx = self.nset.startpage_idx
self.mynb.reorder_child(page,new_pg_idx%page_ct)
def clone_page(self):
newpage = self.nset.add_page(self.pre_file
,self.sub_file
,self.pst_file
,self.imageoffpage #preserve for clone
)
for idx in self.efields.pentries:
ev = self.efields.pentries[idx].getentry()
newpage.efields.pentries[idx].setentry(ev)
def new_empty_page(self):
self.nset.add_page('','','')
def remove_page(self):
page_ct = self.mynb.get_n_pages()
if page_ct - self.nset.startpage_idx == 1:
user_message(mtype=Gtk.MessageType.INFO
,title=_('Remove not allowed')
,msg=_('One tabpage must remain')
)
else:
current_pno = self.mynb.get_current_page()
npage = self.mynb.get_nth_page(current_pno)
self.mynb.remove_page(current_pno)
thispg = self.nset.pg_for_npage[npage]
thispg.garbagecollect = True
del thispg
del npage
class NgcGui():
"""NgcGui: set of ngcgui OnePg items"""
# make a set of pages in parent that depends on type(w)
def __init__(self,w=None
,verbose=False
,debug=False
,noauto=False
,keyboardfile='' # None | ['default'|'yes'] | fullfilename
,tmode=0
,send_function=default_send # prototype: (fname)
,ini_file=''
,auto_file=''
,pre_file=''
,sub_files=''
,pst_file=''
,tab_controls_loc='top' # option for touchy
,control_font=None # option for touchy
,max_parm=None # for small display, reject some subs
,image_width=None # for small display
):
global g_send_function; g_send_function = send_function
global g_tmode; g_tmode = tmode
global g_verbose; g_verbose = verbose
global g_debug; g_debug = debug
global g_tab_controls_loc; g_tab_controls_loc = tab_controls_loc
global g_control_font; g_control_font = control_font
try:
type(g_send_function) # test existence
if g_send_function == None:
g_send_function = dummy_send
except AttributeError:
print('INVALID send_function, using dummy')
g_send_function = dummy_send
if max_parm is not None:
global g_max_parm
g_max_parm = max_parm
if image_width is not None:
global g_image_width
if image_width > g_image_width:
raise ValueError(_('NgcGui image_width=%d too big, max=%d')
% (image_width,g_image_width))
g_image_width = image_width
if g_max_parm > INTERP_SUB_PARAMS:
raise ValueError(_('max_parms=%d exceeds INTERP_SUB_PARAMS=%d')
% (g_max_parm,INTERP_SUB_PARAMS))
ct_of_pages = 0
try:
import popupkeyboard
import glib # for glib.GError
if keyboardfile is not None:
global g_popkbd
if (keyboardfile in ('default','yes') ):
keyboardfile = g_keyboardfile
g_popkbd = popupkeyboard.PopupKeyboard(glade_file=keyboardfile
,use_coord_buttons=True
)
global g_entry_height
g_entry_height = g_big_height # bigger for popupkeyboard
except ImportError as msg:
print('\nImportError:\n%s', msg)
print('keyboardfile=%s' % keyboardfile)
print('popup keyboard unavailable\n')
except glib.GError as msg:
# can occur for toohigh version in ui file
print('\nglib.GError:\n%s' % msg)
print('keyboardfile=%s' % keyboardfile)
print('popup keyboard unavailable\n')
self.last_file = None
self.nb = None
self.autosend = not noauto
self.expandsub = False
self.nextpage_idx = 0
self.startpage_idx = 0
self.pg_for_npage = {}
if w is None:
# standalone operation
self.nb = Gtk.Notebook()
w = Gtk.Window(Gtk.WindowType.TOPLEVEL)
if g_alive: w.connect("destroy", Gtk.main_quit)
w.set_title(sys.argv[0])
w.add(self.nb)
self.nb.show()
w.show()
elif type(w) == Gtk.Frame:
# demo -- embed as a notebook in a provider's frame
self.nb = Gtk.Notebook()
w.add(self.nb)
self.nb.show()
w.show()
elif type(w) == Gtk.Notebook:
# demo -- embed as additional pages in a provider's notebook
self.nb = w
self.startpage_idx = self.nb.get_n_pages()
else:
raise ValueError('NgcGui:bogus w= %s' % type(w))
self.nb.set_scrollable(True)
self.intfc = LinuxcncInterface(ini_file)
if len(self.intfc.subroutine_path) == 0:
self.intfc.addto_spath(
spath_from_files(pre_file,sub_files,pst_file))
if len(self.intfc.subroutine_path) != 0:
user_message(mtype=Gtk.MessageType.WARNING
,title=_('Simulated subroutine path')
,msg=_('No subroutine path available.\n'
'Simulating subroutine path:\n\n')
+ str(self.intfc.subroutine_path)
+ '\n'
+ _('Generated results may not be usable with linuxcnc')
)
if len(self.intfc.subroutine_path) == 0:
if g_alive:
# no message if glade designer is running:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('No Subroutine Paths')
,msg='\n' +
_('No paths available!\n'
'Make sure there is a valid\n'
' [RS274]SUBROUTINE_PATH\n\n'
' 1) Start linuxcnc\n'
'or\n'
' 2) Specify an ini file\n'
'or\n'
' 3) Specify at least one subfile\n'
'\n')
)
sys.exit(1)
global g_searchpath; g_searchpath = self.intfc.subroutine_path
# multiple pages can be specified with __init__()
initsublist= []
if isinstance(sub_files,str) and sub_files:
initsublist.append(sub_files)
else:
initsublist = sub_files
nogo_l = []
for sub_file in initsublist:
if not g_alive: continue
if os.path.dirname(sub_file) in self.intfc.subroutine_path:
self.add_page(pre_file,sub_file,pst_file)
ct_of_pages += 1
else:
nogo_l.append(sub_file)
if nogo_l:
user_message(mtype=Gtk.MessageType.INFO
,title=_('Cannot use files not in subroutine path')
,msg=_('Files not in subroutine path:\n')
+ str(nogo_l) +
'\n\n'
+ _('Subroutine path is:\n')
+ str(self.intfc.subroutine_path)
)
nogo_l = []
# multiple pages can be specified with an INI file
sublist = self.intfc.get_subfiles() #returns list
pre_file = self.intfc.get_preamble()
pst_file = self.intfc.get_postamble()
# auto_file directory:
# if specified, verify in path, give message if not
# if nil
# if PROGRAM_PREFIX put there
# else put in cwd
if auto_file:
dir = os.path.abspath(os.path.dirname(auto_file))
spath = self.intfc.get_subroutine_path()
try:
spath.index(dir) # check that auto_file dir is in path
# auto_file ok
except ValueError:
# it's called autofile in --help
pass
#user_message(mtype=Gtk.MessageType.WARNING
# ,title=_('Warning: autofile not in path')
# ,msg=_('autofile==%s is not in linuxcnc\n'
# 'subroutine search path:\n'
# ' %s\n') % (auto_file,spath)
# )
self.auto_file = auto_file
else:
pprefix = self.intfc.get_program_prefix()
if pprefix:
self.auto_file = os.path.join(pprefix,'auto.ngc')
else:
self.auto_file = os.path.join(os.path.curdir,'auto.ngc')
dprint('input for auto_file=%s\nfinal auto_file=%s'
% (auto_file,self.auto_file))
if pre_file is None: pre_file = ''
if pst_file is None: pst_file = ''
# vprint('SAVE_FILE: %s' % self.auto_file)
if sublist and g_alive:
for sub_file in sublist:
if sub_file == '""': #beware code for custom is '""'
sub_file = ''
try:
self.add_page(pre_file,sub_file,pst_file)
ct_of_pages += 1
except Exception as detail:
exception_show(Exception,detail,src='NgcGui init')
print(_('CONTINUING without %s') % sub_file)
else:
if not sub_files:
vprint('NgcGui: no INI file with sublist '
'and no cmdline sub_file:'
'making Custom page')
self.add_page('','','')
ct_of_pages += 1
pass
self.current_page = None
# self.nb.set_current_page(self.startpage_idx)
# start at page 0 to respect caller's ordering
self.nb.set_current_page(0)
if g_alive: self.nb.connect('switch-page', self.page_switched)
w.show_all()
if ct_of_pages == 0:
usage()
print(_('No valid subfiles specified'))
sys.exit(1)
return
def update_fonts(self,fontname):
update_fonts(fontname)
def page_switched(self,notebook,npage,pno):
if self.current_page:
curpage = self.current_page
if hasattr(curpage,'imgw'):
w = getattr(curpage,'imgw')
w.iconify()
try:
mypg = self.pg_for_npage[self.nb.get_nth_page(pno)]
if hasattr(mypg,'imgw'):
w = getattr(mypg,'imgw')
w.deiconify()
w.show_all()
self.current_page = mypg
except KeyError as msg:
# can occur when embedded in providers notebook
# print('page_switched: Caught KeyError')
pass
def add_page(self,pre_file,sub_file,pst_file,imageoffpage=False):
# look for gcmc on first request for .gcmc file:
if os.path.splitext(sub_file)[-1] in ['.gcmc','.GCMC']:
if not find_gcmc(): return None
self.nextpage_idx = self.nextpage_idx + 1
opage = OnePg(pre_file=pre_file
,sub_file=sub_file
,pst_file=pst_file
,mynb=self.nb
,nset=self # an NgcGui set of pages
,imageoffpage=imageoffpage
)
if opage.fset.sub_data.pdict['subname'] == '':
ltxt = 'Custom'
else:
ltxt = opage.fset.sub_data.pdict['subname']
ltxt = self.make_unique_tab_name(ltxt)
eb_lbl = Gtk.EventBox()
mylbl = Gtk.Label(ltxt)
if g_popkbd is not None:
mylbl.set_size_request(-1,g_big_height)
eb_lbl.add(mylbl)
mylbl.show()
eb_lbl.set_style(g_lbl_style_default)
opage.pgbox.set_border_width(2)
pno = self.nb.append_page(opage.pgbox,eb_lbl)
if g_control_font is not None:
mod_font_by_category(mylbl)
# An EventBox is needed to change bg of tabpage label
# When using EventBox:
# don't use get_tab_label_text()
opage.save_onepage_tablabel(eb_lbl,mylbl)
self.pg_for_npage[self.nb.get_nth_page(pno)] = opage
self.nb.set_current_page(pno) # move to the new page
return opage
def make_unique_tab_name(self,name):
l = []
if not name: return None
for pno in range(self.startpage_idx,self.nb.get_n_pages()):
npage = self.nb.get_nth_page(pno)
pg = self.pg_for_npage[npage]
# using EventBox for label, dont use get_tab_label_text()
ltxt = pg.the_lbl.get_text()
if ltxt.find(name) == 0:
l.append(ltxt)
if len(l) == 0:
return(name)
if len(l) == 1:
return(name + '-1')
last = l[-1]
idx = last.find('-')
return(name + '-' + str(int(last[idx+1:]) + 1) )
class SaveSection():
"""SaveSection: lines ready for result file"""
def __init__(self,mypg,pre_info,sub_info,pst_info,force_expand=False):
global g_label_id
g_label_id += 1
self.sdata=[]
self.sdata.append("(%s: FEATURE %s)\n"% (g_progname,dt() ))
self.sdata.append("(%s: files: <%s,%s,%s>)\n"
% (g_progname
,pre_info.pre_file
,sub_info.sub_file
,pst_info.pst_file
)
)
# note: this line will be replaced on file output with a count
# that can span multiple pages
self.sdata.append("#<_feature:> = 0\n")
self.sdata.append("(%s: preamble file: %s)\n" % (
g_progname,pre_info.pre_file))
self.sdata.extend(pre_info.inputlines)
emsg = '' # accumulate errors for emsg
calltxt = 'o<%s> call ' % sub_info.pdict['subname']
parmlist = []
tmpsdata = []
for idx in sub_info.ndict:
name,dvalue,comment = sub_info.ndict[idx]
value=mypg.efields.getentry_byidx(idx)
try:
v = float(value)
except ValueError:
emsg = emsg + (
_('Entry for parm %2d is not a number\n <%s>\n')
% (int(idx),value))
#note: e formats not accepted by linuxcnc (like 1e2)
# but using float(value) --->mmm.nnnnn everywhere
# makes long call line
# so try to send entry value, but if it has e, use float
if 'e' in value:
value = str(float(value.lower() ))
parmlist.append(value)
if 'isgcmc' in sub_info.pdict:
# just print value of gcmc parm embedded in gcmc result
# the call requires no parms
pass
else:
calltxt = calltxt + '[%s]' % value
# these appear only for not-expandsub
tmpsdata.append("(%11s = %12s = %12s)\n" % (
'#'+str(idx),name,value))
if emsg:
user_message(mtype=Gtk.MessageType.ERROR
,title=_('SaveSection Error')
,msg=emsg)
mypg.cpanel.set_message(_('Failed to create feature'))
raise ValueError
calltxt = calltxt + '\n'
# expandsub not honored for gcmc
if (mypg.expandsub and 'isgcmc' in sub_info.pdict):
print(_('expandsub not honored for gcmc file: %s')%
os.path.basename(sub_info.sub_file))
mypg.expandsub = 0
#---------------------------------------------------------------------
if (not mypg.expandsub) and (not force_expand):
self.sdata.append("(%s: call subroutine file: %s)\n" % (
g_progname,sub_info.sub_file) )
self.sdata.append("(%s: positional parameters:)\n"% g_progname)
self.sdata.extend(tmpsdata)
self.sdata.append(calltxt) # call the subroutine
else:
# expand the subroutine in place with unique labels
self.sdata.append('(Positional parameters for %s)\n'
% mypg.sub_file)
for i in range(0,idx):
self.sdata.append(' #%d = %s\n' % (i+1,parmlist[i]))
self.sdata.append('(expanded file: %s)\n' % mypg.sub_file)
blank = ''
idx = 0
for line in sub_info.inputlines:
idx += 1
if line.strip() == '':
continue
if idx in sub_info.ldict:
modlabel = sub_info.ldict[idx]
if modlabel == 'ignoreme':
continue
modlabel = 'o<%03d%s>' % (g_label_id,modlabel)
r = re.search(r'^o<(.*?)>(.*)',line.strip())
if r:
modline = r.group(2) + '\n'
else:
print('SaveSection__init__:unexpected:',line)
self.sdata.append('%11s %s' % (modlabel,modline))
else:
theline = '%11s %s' % (blank,line)
# hack: try to reduce long line length so linuxcnc wont
# choke on files that work otherwise but fail
# when expanded here
# example: 246 chars observed for
# qpex --> the call to qpocket uses many named parms
# hardcoded for # config.h.in #define LINELEN 255
# hardcoded 252 empirically determined
if len(theline) >= 252:
theline = line
self.sdata.append(theline)
#---------------------------------------------------------------------
if pst_info.inputlines:
self.sdata.append("(%s: postamble file: %s)\n" % (
g_progname,pst_info.pst_file))
self.sdata.extend(pst_info.inputlines)
#for line in self.sdata:
# print('line:',line,)
def usage():
print("""
Usage:
%s [Options] [sub_filename]
Options requiring values:
[-d | --demo] [0|1|2] (0: DEMO standalone toplevel)
(1: DEMO embed new notebook)
(2: DEMO embed within existing notebook)
[-S | --subfile sub_filename]
[-p | --preamble preamble_filename]
[-P | --postamble postamble_filename]
[-i | --ini inifile_name]
[-a | --autofile auto_filename]
[-t | --test testno]
[-K | --keyboardfile glade_file] (use custom popupkeyboard glade file)
Solo Options:
[-v | --verbose]
[-D | --debug]
[-N | --nom2] (no m2 terminator (use %%))
[-n | --noauto] (save but do not automatically send result)
[-k | --keyboard] (use default popupkeybaord)
[-s | --sendtoaxis] (send generated ngc file to axis gui)
Notes:
A set of files is comprised of a preamble, subfile, postamble.
The preamble and postamble are optional.
One set of files can be specified from cmdline.
Multiple sets of files can be specified from an INI file.
If --ini is NOT specified:
search for a running linuxcnc and use its INI file
""" % g_progname)
#-----------------------------------------------------------------------------
# Standalone (and demo) usage:
def standalone_pyngcgui():
# make widgets for test cases:
top = Gtk.Window(Gtk.WindowType.TOPLEVEL)
top.set_title('top')
hbox = Gtk.HBox()
top.add(hbox)
l1 = Gtk.Label('LABEL')
hbox.pack_start(l1,expand=0,fill=0,padding=0)
e1 = Gtk.Entry()
hbox.pack_start(e1,expand=0,fill=0,padding=0)
e1.set_width_chars(4)
f1 = Gtk.Frame()
hbox.pack_start(f1,expand=0,fill=0,padding=0)
f2 = Gtk.Frame()
hbox.pack_start(f2,expand=0,fill=0,padding=0)
n = Gtk.Notebook()
n.set_scrollable(True)
b1 = Gtk.Button('b1-filler')
b2 = Gtk.Button('b2-filler')
n.append_page(b1,Gtk.Label('Mb1-filler'))
n.append_page(b2,Gtk.Label('Mb2-filler'))
f1.add(n)
top.show_all()
demo = 0 # 0 ==> standalone operation
subfilenames = ''
prefilename = ''
pstfilename = ''
vbose = False
dbg = False
noauto = False
keyboard = False
keyboardfile = 'default'
ini_file = ''
auto_file = ''
tmode = 0
send_f = default_send
try:
options,remainder = getopt.getopt(sys.argv[1:]
,'a:Dd:hi:kK:Nnp:P:sS:t:v'
, ['autofile'
,'demo='
,'debug'
,'help'
,'ini='
,'keyboard'
,'keyboardfile='
,'noauto'
,'preamble='
,'postamble='
,'subfile='
,'verbose'
,'sendtoaxis'
,'nom2'
]
)
except getopt.GetoptError as msg:
usage()
print('\nGetoptError:%s' % msg)
sys.exit(1)
except Exception as detail:
exception_show(Exception,detail,'__main__')
sys.exit(1)
for opt,arg in options:
#print('#opt=%s arg=%s' % (opt,arg))
if opt in ('-h','--help'): usage(),sys.exit(0)
if opt in ('-d','--demo'): demo = arg
if opt in ('-i','--ini'): ini_file = arg
if opt in ('-a','--autofile'): auto_file = arg
if opt in ('-p','--preamble'): prefilename=arg
if opt in ('-P','--postamble'): pstfilename=arg
if opt in ('-S','--subfile'): subfilenames=arg
if opt in ('-t','--test'): tmode=arg
if opt in ('-k','--keyboard'): keyboard=True
if opt in ('-K','--keyboardfile'):
keyboard=True
keyboardfile=arg
if opt in ('-N','--nom2'): dbg = g_nom2 = True
if opt in ('-D','--debug'): dbg = True
if opt in ('-n','--noauto'): noauto = True
if opt in ('-v','--verbose'):
vbose = True
continue
if opt in ('-s','--sendtoaxis'):
send_f = send_to_axis
continue
if remainder: subfilenames = remainder # ok for shell glob e.g., *.ngc
demo = int(demo)
if not keyboard: keyboardfile=None
if (dbg):
print(g_progname + ' BEGIN-----------------------------------------------')
print(' __file__= %s' % __file__)
print(' ini_file= %s' % ini_file)
print(' sys.argv= %s' % sys.argv)
print(' os.getcwd= %s' % os.getcwd())
print(' sys.path= %s' % sys.path)
print(' demo= %s' % demo)
print(' prefilename= %s' % prefilename)
print('subfilenames= %s' % subfilenames)
print(' pstfilename= %s' % pstfilename)
print(' keyboard= %s, keyboardfile= <%s>' % (keyboard,keyboardfile))
try:
if demo == 0:
top.hide()
NgcGui(w=None
,verbose=vbose,debug=dbg,noauto=noauto
,keyboardfile=keyboardfile
,tmode=tmode
,send_function=send_f # prototype: (fname)
,ini_file=ini_file,auto_file=auto_file
,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename
)
elif demo == 1:
NgcGui(w=f2
,verbose=vbose,debug=dbg,noauto=noauto
,keyboardfile=keyboardfile
,tmode=tmode
,send_function=send_f # prototype: (fname)
,ini_file=ini_file,auto_file=auto_file
,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename
)
top.set_title('Create OnePg inside a new frame')
elif demo == 2:
NgcGui(w=n
,verbose=vbose,debug=dbg,noauto=noauto
,keyboardfile=keyboardfile
,tmode=tmode
,send_function=send_f # prototype: (fname)
,ini_file=ini_file,auto_file=auto_file
,pre_file=prefilename,sub_files=subfilenames,pst_file=pstfilename
)
top.set_title('Create OnePg inside an existing notebook')
else:
print('unknown demo',demo)
usage()
sys.exit(1)
except Exception as detail:
exception_show(Exception,detail,'__main__')
print('in main()')
sys.exit(11)
try:
Gtk.main()
except KeyboardInterrupt:
sys.exit(0)
# vim: sts=4 sw=4 et