Inputbox error handling for length objects


#1

I understand how I can supply a length object as a default for an input box field.
Then user can edit the field and a length object will be returned.

However, if user makes a typo error (say typing a letter O instead of a zero) I see no way to gracefully handle the error. Instead ruby issues an error to the ruby console, and user has no idea what went wrong.

With other kinds of string to numeric conversions I can first check the string for nonnumeric characters and either strip them out or give user a useful message, or reopen the inputbox with an specific error prompt to left of offending value.

So the automatic inputbox handling of length seems inadequate to me.
Has anyone written a “string.is_length?” function that verifies that the contents of a string are validly convertible to a length? Ideally the string.to_l function should return error info if conversion is not possible.


#2

In programming, there are occasions (functions, methods) where it can be guaranteed that it will always succeed. On the other hand, there are occasions where a function or method is unable to complete and return a sensible result.
For example:

desired_result = 1.div(0)

Here the method can not return true/false for success because it already is designed to return the result of the calculation. But it also cannot return a number… That’s why it gives an exception. Exceptions are a second path for methods to return something: namely failure.

It is obvious how to access the primary return value (in the variable desired_result), but how do you access this “second return value” of failure?

You “catch” or “rescue” the exception and let your code resolve the problem. Imagine you have a phone call and ask the receiver a math question. He either tells you the result, or didn’t understand the question (exception 1) or doesn’t know (exception 2). In order to handle exception type 1, you would probably repeat the question. For exception 2 you would either ask an easier question (if you are allowed to change questions) or you don’t handle the exception but just pass it on to your boss.

It is possible to distinguish what type of exception occured and then decide whether it is something you know how to handle. Exceptions can even have attributes attached (that’s why I like to call it some sort of return value): exception_object.message and more. It is important that you catch only the specific exception type that you know to handle.

For details see here: http://ruby-doc.com/docs/ProgrammingRuby/html/tut_exceptions.html
and http://rubylearning.com/satishtalim/ruby_exceptions.html


Coming back to parse errors:

The parsed string either can be parsed to a numerical value, or it is maybe just garbage ("J$KN%&"). If a number cannot be returned, an ArgumentError (“Cannot convert … to Length”) is thrown.

inputs = UI.inputbox(["Length"], ["?"], "Inputbox")
begin
  length = inputs.first.to_l
rescue ArgumentError => exception_object
  UI.messagebox("That was not a valid length. Too bad, the tool cannot continue.") # Or even: UI.messagebox(exception_object.message)
  return
end

The obvious way to handle that would be to ask the user to input a (valid) length again. Therefore we need to bring up the same inputbox again.

length = nil
begin
  inputs = UI.inputbox(["Length"], ["$garbage%&"], "Inputbox")
  begin
    length = inputs.first.to_l
  rescue ArgumentError
    UI.messagebox("That was not a valid length. Please repeat your input.") # You can drop this.
  end
end until length.is_a?(Numeric)

#3

I understand all of what you say. However your approach does not satisfy:
It avoids the automatic feature of inputbox length handling and resorts to strings. That’s OK but them what was the point of the feature.
Let’s just say the inputbox method is weak but we’ll have to put up with it until I write a string.is_length? function.


#4
inputs = UI.inputbox(["Length"], [0.inch], "Inputbox")

Now the user can ONLY input a valid length.
If it’s a number, with no units suffix, then it’s taken a ‘current model-units’ - e.g. if it’s in inches 1 >>> 1"
If the user adds a units suffix - e.g. 25mm then that is converted to current units, e.g. 25mm >> ~1" depending on accuracy units settings…


#5

Then it is inputbox (instead of to_l) which throws the ArgumentError exception. Same exception handling, but put the inputbox into the begin rescue block.


#6

But if user has typo (a letter of some kind or any other character not allowed in a length) I’m back to rescuing.

What I’m going to do give only strings to inputbox and get strings in return.
typically my input boxes have several inputs each of which needs testing seperatly.
Inputbox goes in a while loop as per following pseudocode:

    set initial defaults
    status = "notdone"
 While status == "notdone"
    result = inputbox..........
   if user calcelled then return
         status = "done"
    for each string in result array:
                try to result[n].to_l 
	    if rescue is triggered set new default with what user typed
		and new prompt with error message
		 status = "not done"
	   end #if
     end #for

end while

This way, user sees what he typed in each field and gets specific error message showing which one(s) triggered an error. And loop will continue until user gets all entries correct or he hits “cancel”


#7

@barry_milliken_droid, Andreas is on the right tack. But neglected to show the use of the retry keyword, which makes the begin ... rescue construct make more sense.

This behaviour with the UI.inputbox has been known forever. This is why “sketchup.rb” in the “Tools” folder defines a global wrapper method for inputbox that wraps it in a retry-able begin ... rescue block.

I suggest copying this method into your own library (or plugin) scope rather than using it as is (ie, as a global. Because it should not be a global, and might not be in the future.)

It looks like:

    # This is a wrapper for UI.inputbox.  You call it exactly the same
    # as UI.inputbox.  UI.inputbox will raise an exception if it can't
    # convert the string entered for one of the values into the right type.
    # This method will trap the exception and display an error dialog and
    # then prompt for the values again.
    def inputbox(*args)
      results = nil
      begin
        results = UI.inputbox(*args)
      rescue ArgumentError => error
        UI.messagebox error.message
        if args.length > 0
          retry
        end
      end
      results
    end

But, it has an omission. That is that it resets ALL the input fields back to the defaults, even the good inputs. (This is going to make plugins users unhappy.)

It would be better to modify it within your plugin namespace, so that it when you do a retry loop, it restuffs the defaults.

For an complicated example of wrapping the UI.inputbox, and using rescue modifiers to raise custom ParameterError exceptions, see the
UVPolyGen get_parameters() method ~line 678
Note that for this project we’ll likely clean this up considerably, and switch to using a UI::WebDialog instead of the “clunky” UI.inputbox. (Issue#22)

It just got so complicated wrapping UI.inputbox to workaround it’s quirks (and prompt width bug,) that we feel we might as well just go with a better interface. (Also we abandoned the use of Length defaults, as most our numeric inputs are unitless, or are formulas; … so we switched to all String, and closely control the conversion to Length in a custom Calc class.)