Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions lib/python/gladevcp/calculatorwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,15 @@ def set_value( self, value ):
def get_value( self ):
self.compute()
try:
value = self.entry.get_text()
if self.use_localization:
return locale.atof( value )
else:
return float(value)
value = float(self.entry.get_text())
except:
return None
if self.use_localization:
try:
value = locale.atof( value )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small reminder regarding the use of locale.atof() — perhaps it simply got overlooked in the context of the other changes. 🙂

We have spent quite a bit of time dealing with atof() and decimal separator localization in LCNC, and we even have a recommendation about this in the documentation: https://linuxcnc.org/docs/devel/html/en/gui/gui-dev-reference.html#_localization_of_float_numbers_in_guis

atof() can of course work correctly in some situations, but in combination with locales I find it rather tricky and potentially a bit of a time bomb — especially because the problem may not show up under the usual EN locale.

If the intention here is to keep using locale.atof(), I would at least ask for it to be tested with the DE locale, where the decimal separator is a comma. That is where I would expect any potential problems to show up first.

Please don't take this as a blocker for the change, but rather as a reminder based on the experience we've already had with this in LCNC.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is current code that got moved around not something new. That is there for backwards compatibility aimed at Gscreen only not something that Gmoccapy uses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I understand that this is existing code being moved around and that it is only there for Gscreen backwards compatibility.

The reason I mentioned it is precisely because this is how we ended up with problems in the past. atof() had been working without any apparent issues for a long time, and then some surrounding code changed and suddenly the locale-related problems started showing up.

So my concern is not that atof() is being introduced here as something new, but rather that moving it around can potentially change the context in which it runs. That's why I would still consider a quick test with the DE locale worthwhile, just to make sure the existing behaviour remains safe after the change.

If that has already been covered by the existing Gscreen compatibility testing, then of course that's fine.

except:
return None
return value

def get_preset_value( self ):
return self.preset_value
Expand Down Expand Up @@ -198,10 +200,7 @@ def compute( self ):
else:
qualified = qualified + i
try :
if self.integer_only:
b = str( int( eval( qualified ) ) )
else:
b = str( eval( qualified ) )
b = str( eval( qualified ) )
except:
b = "Error"
self.eval_string = ''
Expand Down
35 changes: 26 additions & 9 deletions lib/python/gladevcp/offsetpage_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ def __init__(self, filename = None, *a, **kw):
# check linuxcnc status every half second
GLib.timeout_add(500, self.periodic_check)

def warning_dialog(self, message):
dialog = Gtk.MessageDialog(parent=self.wTree.get_object("window1"),
destroy_with_parent = True,
message_type=Gtk.MessageType.ERROR,
text=message)
dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
dialog.show()
dialog.run()
dialog.destroy()

# Reload the offsets into display
def reload_offsets(self):
g28, g30, g92, g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3 = self.read_file()
Expand Down Expand Up @@ -353,13 +363,16 @@ def set_editing(self, widget):
self.queue_draw()

# When the column is edited this does the work
def col_editted(self, widget, filtered_path, new_text, col):
def col_editted(self, widget, path, new_text, col):
self.validate_input(path, new_text, col)


def validate_input(self, path, new_text, col):
model, treeiter = self.view2.get_selection().get_selected()
path = self.modelfilter.get_path(treeiter)
(store_path,) = self.modelfilter.convert_path_to_child_path(path)
row = store_path
axisnum = col - 1
# print "EDITED:", new_text, col, int(filtered_path), row, "axis num:", axisnum

def system_to_p(system):
convert = { "G54":1, "G55":2, "G56":3, "G57":4, "G58":5, "G59":6, "G59.1":7, "G59.2":8, "G59.3":9}
Expand All @@ -374,23 +387,27 @@ def system_to_p(system):
tmpl = lambda s: self.mm_text_template % s
else:
tmpl = lambda s: self.imperial_text_template % s

msg = None
# allow 'Comment' column text to be arbitrarily changed
if col == 11:
self.store[row][15] = new_text
return
# for all other columns we expect a float value
else:
try:
if self.use_localization:
if self.use_localization:
try:
# using locale settings can lead to issues but we make it optional for backwards compatibility
new_float = float(locale.atof(new_text))
else:
except Exception as e:
print("offsetpage_widget, Error trying to convert to localized float: ", e)
else:
try:
# this is the preferred way, allowing dot or comma as decimal symbol
new_float = float(new_text.replace(',', '.'))
except Exception as error:
print('new_text: ', new_text, error)
print(_("offsetpage widget error: unrecognized float input"))
except:
msg = (_(f"\nMust be a decimal number"))
if msg is not None:
self.warning_dialog(msg)
return

# ignore entries to the Rot column in non-wcs rows
Expand Down
109 changes: 86 additions & 23 deletions lib/python/gladevcp/tooledit_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def __init__(self,toolfile=None, *a, **kw):
self.editable = None
self.edit_path = None
self.edit_column = None

self.tooltable_error_msg = None
# connect the signals from Glade
dic = {
"on_delete_clicked" : self.delete,
Expand Down Expand Up @@ -251,19 +251,29 @@ def match_tool(model, path, iter, pathlist):
except:
print(_("tooledit_widget error: cannot select tool number"),toolnumber)

def add(self,widget,data=[1,0,0,'0','0','0','0','0','0','0','0','0','0','0','0',0,"comment"]):
def add(self, widget, data=None):
liststore = self.model
if data is None: # an empty line is being added
data = [1,0,0,'0','0','0','0','0','0','0','0','0','0','0','0',0,"comment"]
tool_nbrs = [0]
for row in liststore:
values = [ value for value in row ]
tool_nbrs.append(values[1])
missing = sorted(set(range(1, max(tool_nbrs))) - set(tool_nbrs))
if len(missing) > 0:
data[1] = min(missing)
else:
data[1] = max(tool_nbrs)+1
self.model.append(data)
self.num_of_col +=1
liststore = self.model
self.wTree.get_object("treeview1").scroll_to_cell(len(liststore)-1)

# this is for adding a filename path after the tooleditor is already loaded.
def set_filename(self,filename):
self.toolfile = filename
self.reload(None)

def warning_dialog(self, line_number):
message = f"Error in tool table line {line_number} in column orientation.\nValid range is 0 ~ 9."
def warning_dialog(self, message):
dialog = Gtk.MessageDialog(parent=self.wTree.get_object("window1"),
destroy_with_parent = True,
message_type=Gtk.MessageType.ERROR,
Expand All @@ -286,7 +296,15 @@ def reload(self,widget):
logfile = open(self.toolfile, "r").readlines()
self.toolinfo = []
line_number = 0
tool_nbrs = []
rejected_lines = []
error_msg = []
for rawline in logfile:
if rawline.isspace():
continue
elif rawline[0] != ';' and rawline[0] != 'T':
rejected_lines.append(rawline)
continue
# strip the comments from line and add directly to array
# if index = -1 the delimiter ; is missing - clear comments
index = rawline.find(";")
Expand Down Expand Up @@ -314,33 +332,63 @@ def reload(self,widget):
if word.startswith(';'): break
if word.startswith(i):
if offset == 1:
try:
current_tool = int(word.lstrip(i))
tool_nbrs.append(current_tool)
except:
rejected_lines.append(rawline)
continue
if int(word.lstrip(i)) == self.toolinfo_num:
toolinfo_flag = True
if offset in(1,2):
try:
array[offset]= int(word.lstrip(i))
except:
print(_("Tooledit widget int error"))
rejected_lines.append(rawline)
continue
elif offset == 15:
try:
# Accept also float for 'orientation' for backward compatibility
value = int(float(word.lstrip(i)))
array[offset] = value
if value not in range(10):
self.warning_dialog(line_number)
break
msg = _(f"Tool {current_tool} Orientation: <b>'{value}'</b>\nValid range is 0 ~ 9.")
error_msg.append(msg)
except:
print(_("Tooledit widget float error"))
rejected_lines.append(rawline)
continue
else:
try:
array[offset]= f"{float(word.lstrip(i)):10.4f}"
except:
print(_("Tooledit widget float error"))
rejected_lines.append(rawline)
continue
break
if toolinfo_flag:
self.toolinfo = array
# add array line to liststore
self.add(None,array)
# check for duplicate toolnumbers
s = []
dup_tool = []
for n in tool_nbrs:
if n in s:
dup_tool.append(n)
else:
s.append(n)
# compose user message about duplicate tool nummbers
if len(dup_tool) > 0:
msg = _("Duplicate tool number(s): <b>") + str(dup_tool)[1:-1] +"</b>"
error_msg.append(msg)
# write malformed lines to a file and compose user message
if len(rejected_lines) > 0:
file_name = "tooltable_rejected.txt"
self.rejected_lines = rejected_lines
with open(file_name, "w", encoding="utf-8") as f:
f.writelines(rejected_lines)
msg = (_(f"<b>{len(rejected_lines)}</b> malformed line(s) have been rejected and copied to <b>'{file_name}'</b>"))
error_msg.append(msg)
self.tooltable_error_msg = error_msg

def save(self,widget):
if self.toolfile == None: return
Expand All @@ -350,12 +398,19 @@ def save(self,widget):
# pre check before saving the file
# if not done before, the file will be saved only until the erroneous line and the rest will be lost
line_number = 0
tool_nbrs = []
for row in liststore:
values = [ value for value in row ]
line_number += 1
if values[15] > 9:
self.warning_dialog(line_number)
msg = None
if values[1] in tool_nbrs:
msg = f"\nError in tool table:\nDuplicate tool number '{values[1]}'"
elif values[15] not in range(10):
msg = f"\nError in tool table line {line_number} in column orientation.\nValid range is 0 ~ 9."
if msg is not None:
self.warning_dialog(msg)
return
tool_nbrs.append(values[1])

if(locale.getlocale(locale.LC_NUMERIC)[0] is None):
raise ExceptionMessage("\n\n"+_("Something wrong with the locale settings. Will not save the tool table."))
Expand Down Expand Up @@ -509,32 +564,40 @@ def validate_input(self, path, new_text, col):
elif filter == 'tool':
(store_path,) = self.tool_filter.convert_path_to_child_path(path)
path = store_path

msg = None
# validate positive integer for tool and pocket number
if col in(1,2):
try:
self.model[path][col] = int(new_text)
try :
value = float(new_text)
if value != int(value) or value < 0:
raise TypeError
else:
self.model[path][col] = int(value)
except:
pass
msg = (_(f"\nMust be a positive whole number"))
# validate input for float columns
elif col in range(3,15):
try:
self.model[path][col] = f"{float(new_text.replace(',', '.')):10.4f}"
except:
pass
msg = (_(f"\nMust be a decimal number"))
# validate input for orientation: check if int and valid range
elif col == 15:
try:
value = int(new_text)
if value in range(10):
self.model[path][col] = value
try :
value = float(new_text)
if value != int(value) or value not in range(10):
raise TypeError
else:
self.model[path][col] = int(value)
except:
pass
msg = (_(f"\nMust be one of (0,1,2,3,4,5,6,7,8,9)"))
elif col == 16:
try:
self.model[path][col] = (new_text)
except:
pass
#print path,new_text, col
if msg is not None:
self.warning_dialog(msg)
if filter in('wear','tool'):
self.save(None)

Expand Down
5 changes: 1 addition & 4 deletions src/emc/usr_intf/gmoccapy/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,7 @@ def entry_dialog(self, caller, data = None, header = _("Enter value") , label =
dialog.destroy()
if response == Gtk.ResponseType.ACCEPT:
if value != None:
if integer:
return int(value)
else:
return float(value)
return value
else:
return "ERROR"
return "CANCEL"
Expand Down
Loading