Forum Archive

'Enter'-keypress/softpress to call action in custom form input

boegh

I just started on Pythonista a few days ago (and so far I love it), in order to learn a bit more of Python (I have no professional programming experience).

So after having stolen and modified the code by @cvp here and modified it a bit I ended up with the following little snippet, to convert a temperature from Fahrenheit to Celcius (just to have it do something more than print hello world):

from dialogs import _FormDialogController
from console import set_color

c = _FormDialogController('Input Box', [('', [{'title':'Fahrenheit:','type':'text','value':''}])], done_button_title='Done')
c.container_view.frame = (0, 0, 400,130)
 c.container_view.present('sheet')
 c.container_view.wait_modal()
 c.container_view = None
 if c.was_canceled:
    set_color(1,0,0)
    print('! ', end='')
    set_color()
    print('Cancelled')
 else:
    try:
       float(c.values['Fahrenheit:'])
       set_color(1,1,1)
       print(round((int(c.values['Fahrenheit:'])-32)*5/9,2), end='')
       set_color()
       print('˚C')
    except:
       set_color(1,0,0)
       print('! ', end='')
       set_color()

I went the way with the custom box, in order to generate a simple single-line input form, which the dialogs in the dialogs-module does not seem to have, so this seemed like the appropriate way.
I am missing a way to to press enter after having entered the Fahrenheit value, rather than having pto press Done. Is this doable?

On a side-note: The documentation for console.set_color doesn't actually mention this, unlike for console.set_font, but set_color() (without parameters) does actually set the color back to default color.

cvp

@boegh try something like

import ui
import dialogs
from console import set_color

class mytf_delegate(object):
  global c
  def textfield_did_end_editing(self, textfield):
    c.done_action('sender') # unused parameter
  def textfield_did_change(self, tf):
    c.values[tf.name] = tf.text

def myform_dialog(title='', fields=None,sections=None, done_button_title='ok'):
    global c

    sections = [('', fields)]
    c = dialogs._FormDialogController(title, sections, done_button_title=done_button_title)

    for s in range(0,len(c.sections)):
      for i in range(0,len(c.cells[s])):            # loop on rows of section s
        cell = c.cells[s][i]                                    # ui.TableViewCell of row i
        tf = cell.content_view.subviews[0]      # ui.TextField of value in row
        tf.delegate = mytf_delegate()

    c.container_view.frame = (0, 0, 400, 130)
    c.container_view.present('sheet')
    c.container_view.wait_modal()
    # Get rid of the view to avoid a retain cycle:
    c.container_view = None
    if c.was_canceled:
      set_color(1,0,0)
      print('! ', end='')
      set_color()
      print('Cancelled')
      return None
    else:
      try:
        #print(c.values['Fahrenheit:'])
        float(c.values['Fahrenheit:'])
        set_color(0,0,1)    # not 1,1,1 because background is also white 
        print(round((int(c.values['Fahrenheit:'])-32)*5/9,2), end='')
        set_color()
        print('˚C')
      except Exception as e:
        print(e)
        set_color(1,0,0)
        print('! ', end='')
        set_color()
      return c.values

fields = [{'title':'Fahrenheit:','type':'text','value':''}]
f = myform_dialog(title='dialog title', done_button_title='ok',fields=fields, sections=None) 
cvp

@boegh this is even better/shorter because in the standard _FormDialogController, delegate is self, thus overriding the textfield_did_end_editing delegate can be set to the done_action even if their (unused) parameter TextField or sender are different but here unused.
Complex thinking but easy solution. Complex? Ok, I've exaggerated 😀

import ui
import dialogs
from console import set_color

def myform_dialog(title='', fields=None,sections=None, done_button_title='ok'):
    global c

    sections = [('', fields)]
    c = dialogs._FormDialogController(title, sections, done_button_title=done_button_title)
    c.textfield_did_end_editing = c.done_action

    c.container_view.frame = (0, 0, 400, 130)
    c.container_view.present('sheet')
    c.container_view.wait_modal()
    # Get rid of the view to avoid a retain cycle:
    c.container_view = None
    if c.was_canceled:
      set_color(1,0,0)
      print('! ', end='')
      set_color()
      print('Cancelled')
      return None
    else:
      try:
        #print(c.values['Fahrenheit:'])
        float(c.values['Fahrenheit:'])
        set_color(0,0,1)    # not 1,1,1 because background is also white 
        print(round((int(c.values['Fahrenheit:'])-32)*5/9,2), end='')
        set_color()
        print('˚C')
      except Exception as e:
        print(e)
        set_color(1,0,0)
        print('! ', end='')
        set_color()
      return c.values

fields = [{'title':'Fahrenheit:','type':'text','value':''}]
f = myform_dialog(title='dialog title', done_button_title='ok',fields=fields, sections=None) 
boegh

@cvp, thank you for your quick response.

I see the magic lies in utilising c.textfield_did_end_editing and assigning that to a done_action(or like) function.

Again thank you - can I buy you a cup of coffee?

cvp

@boegh it's very nice of you, I did not know buymeacoffee.com and it sounds nice, but it is certain that I do not deserve anything for my (always quick and dirty) posts.

cvp

Just for the fun...
I never thought about it before but there is a way to change the frame of the dialog (and define the return as the done button) without redefining the form_dialog.
You just have to monkey patch the init method of the _FormDialogController class.
The only problem is that, during a rerun, you have to delete the import of dialogs, otherwise a problem of recurrence occurs.
Try this, if you want 😀

import ui
from console import set_color
try:
    del sys.modules['dialogs']
except:
    pass
import dialogs
original__init__ = dialogs._FormDialogController.__init__   
def my__init__(self, *args, **kwargs):
    original__init__(self, *args, **kwargs)
    self.container_view.frame = (0, 0, 400, 130)
    self.textfield_did_end_editing = self.done_action
dialogs._FormDialogController.__init__ = my__init__

fields = [{'title':'Fahrenheit:','type':'text','value':''}]
f = dialogs.form_dialog(title='dialog title', done_button_title='ok',fields=fields, sections=None)
if f:
    try:
        float(f['Fahrenheit:'])
        set_color(0,0,1)    # not 1,1,1 because background is also white 
        print(round((int(f['Fahrenheit:'])-32)*5/9,2), end='')
        set_color()
        print('˚C')
    except Exception as e:
        print(e)
        set_color(1,0,0)
        print('! ', end='')
        set_color()
else:
    set_color(1,0,0)
    print('! ', end='')
    set_color()
    print('Cancelled')

I would like to add that I am proud of myself for having found this, because, sincerely, I am far from mastering Python 😢

boegh

Again thank you for the teaching points @cvp :)

I tried your code, and as you suggested, the rerun did give an error:

```
Traceback (most recent call last):
File "/private/var/mobile/Containers/Shared/AppGroup/60EBD24F-667F-4F6E-B595-9100C20FC784/Pythonista3/Documents/dialogs_2.py", line 16, in
f = dialogs.form_dialog(title='dialog title', done_button_title='ok',fields=fields, sections=None)
File "/var/containers/Bundle/Application/CD376C3C-8193-4F37-B990-A9B960D30F2D/Pythonista3.app/Frameworks/Py3Kit.framework/pylib/site-packages/dialogs.py", line 456, in form_dialog
c = _FormDialogController(title, sections, done_button_title=done_button_title)
File "/private/var/mobile/Containers/Shared/AppGroup/60EBD24F-667F-4F6E-B595-9100C20FC784/Pythonista3/Documents/dialogs_2.py", line 10, in my__init__
original__init__(self, args, kwargs)
File "/private/var/mobile/Containers/Shared/AppGroup/60EBD24F-667F-4F6E-B595-9100C20FC784/Pythonista3/Documents/dialogs_2.py", line 10, in my__init__
original__init__(self,
args, kwargs)
File "/private/var/mobile/Containers/Shared/AppGroup/60EBD24F-667F-4F6E-B595-9100C20FC784/Pythonista3/Documents/dialogs_2.py", line 10, in my__init__
original__init__(self, *args,
kwargs)
[Previous line repeated 494 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
````

Am I missing something here or isn't it the purpose of the del sys.modules['dialogs']-statement to avoid this issue?

cvp

@boegh said:

Am I missing something here or isn't it the purpose of the del sys.modules['dialogs']-statement to avoid this issue?

Normally, it is the purpose of deleting the imported dialogs module, did you remove this part of code?

I retried my code and it works

boegh

@cvp said:

Normally, it is the purpose of deleting the imported dialogs module, did you remove this part of code?

No I kept it exactly as you wrote it (copy/paste). I can offer no explanation, but I did narrow in on the problem:

Removing the try/except-block around the del sys.modules['dialogs']-line, gave me the following error:

Traceback (most recent call last):
  File "/private/var/mobile/Containers/Shared/AppGroup/60EBD24F-667F-4F6E-B595-9100C20FC784/Pythonista3/Documents/dialogs_2.py", line 4, in <module>
    del sys.modules['dialogs']
NameError: name 'sys' is not defined

So I tried to add import sys in the beginning of the file, and it worked well. I am a bit perplexed by this behaviour, as I understood it so, that the sys-module would always be loaded?

Keeping the import-statement in place, I reinserted the try/except-block and it works flawlessly.

I am using Python 3.6 Interpreter in Pythoniste v.3.2 (320000).

cvp

@boegh Strange because, for me, my script runs perfectly several times...
Hoping a Python guru would explain us...

cvp

@boegh I think that you are right, you need to import sys to allow correct working of the del.
In my case, my pythonista_startup imports it thus its is already imported before my script.