Simply put, I would like to use a small USB keypad/numpad to type into a text input field on a kivy app running on android via the kivy launcher.
I am using an OTG USB connector to connect a small keypad to my tablet. The connector works in general on the tablet (I can type in numbers in the browser and other apps). In a kivy app however, when I use the touchscreen to select a text input field, it focuses awaiting input, then if I try to type in with the numpad, nothing happens. After doing some testing with a normal sized USB keyboard, it works (but the numpad on it doesn't).
Here is the basic kivy app I used to test this out with:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
KV = """
<MyScreenManager>:
KeyboardInput:
name: 'KeyInput'
<KeyboardInput>:
GridLayout:
rows: 3
TextInput:
id: input1
text:''
multiline: False
Widget:
TextInput:
id: input2
text:''
multiline: False
"""
class KeyboardInput(Screen):
pass
class MyScreenManager(ScreenManager):
def changescreen(self, value):
try:
if value!='Go To...':
self.current = value
except:
print('No screen named ' + value)
#Main application
class packagingDiscardmentApp(App):
def build(self):
Builder.load_string(KV)
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
packagingDiscardmentApp().run()
My guess is that there is some issue with the ascii characters on the numpad and Kivy. Any help would be greatly appreciated.
Related
I'm writing a program with Kivy.
Under "desktop" OSes it would be normal to use the keyboard for input, and my code does so.
class ProgLayout( FloatLayout ):
def __init__(self, **kwargs):
super( ProgLayout, self ).__init__(**kwargs)
# Keyboard Handling (if any)
self.keyboard = Window.request_keyboard(self.keyboard_closed, self)
self.keyboard.bind(on_key_down=self.on_keyboard_down)
self.keyboard.bind(on_key_up=self.on_keyboard_up)
self.key_states = { 'up':False, 'down':False, 'left':False, 'right':False }
def on_keyboard_down( self, keyboard, keycode, text, modifiers ):
#print("ON_KEYBOARD_DOWN")
#print('The key', keycode, 'have been pressed')
#print(' - text is %r' % text)
#print(' - modifiers are %r' % modifiers)
junk, key = keycode
keymap = { 'up':Directions.NORTH, 'down':Directions.SOUTH, 'left':Directions.WEST, 'right':Directions.EAST }
if ( key in ( 'up', 'down', 'left', 'right' ) ):
self.key_states[ key ] = True
self.handleKeys()
def on_keyboard_up( self, keyboard, keycode ):
#print("ON_KEYBOARD_UP")
junk, key = keycode
if ( key in ( 'up', 'down', 'left', 'right' ) ):
self.key_states[ key ] = False
self.handleKeys()
However, when the app starts under Android (I suspect) opening the keyboard induces the onscreen-keyboard to appear. Obviously the soft-keyboard can be dismissed, but a better solution is to only open the keyboard if a hardware keyboard is present - or just not automatically use it for Android.
Is there a way to determine if a physical keyboard is present on Kivy?
Is there a way to determine if my app is running on Android (ah, I guess platform.system() or sys.platform).
EDIT: buildozer fails to build the .APK if I include sys or platform in the buildozer.spec manifest. Importing sys still seems to work despite this. But on my android tablet, sys.platform reports "linux", so that's not a way forward.
As the commenter #Erik points out, the solution to determining the current platform is kivy.utils.platform.
from kivy.utils import platform
...
if ( kivy.utils.platform != 'android' ):
# open the keyboard, since we're not android
As the documentation states:
A string identifying the current operating system. It is one of: ‘win’, ‘linux’, ‘android’, ‘macosx’, ‘ios’ or ‘unknown’.
For what concerns the platform, it's sufficient to:
import kivy
print(kivy.platform)
For what concerns the virtual keyboard, there is the VKeyboard module in Kivy, but I can't really read any of the attributes this class should have... strange enough.
I'm writing a GPS aircraft navigation app in Kivy. It runs well on all the devices I have tested it on including various phones and the Samsung Galaxy Tab 4 10.1" but fails on the Samsung Galaxy Tab 4 8". Assuming a faulty device I sent it back and got another one. Same problem.
Using Tito's GPSdemo app and trapping the exception,
at the self.gps.start() line it throws:
File "jnius_utils.pxi", line 43, in jnius.jnius.check_exception
(jnius/jnius.c3316) JavaException: JVM exception occured.
The 8" tab is a good size device for my app so I'd like to use it. Other GPS apps work fine on it including GPS Arrow Pro and Sky Demon for example.
Any ideas how I can fix this?
The code is:
from kivy.lang import Builder
from plyer import gps
from kivy.app import App
from kivy.properties import StringProperty
from kivy.clock import Clock
kv = '''
BoxLayout:
orientation: 'vertical'
Label:
text: app.gps_location
Label:
text: app.gps_status
BoxLayout:
size_hint_y: None
height: '48dp'
padding: '4dp'
ToggleButton:
text: 'Start' if self.state == 'normal' else 'Stop'
on_state: app.gps.start() if self.state == 'down' else app.gps.stop()
'''
class GpsTest(App):
gps_location = StringProperty()
gps_status = StringProperty('')
def build(self):
self.gps = gps
try:
self.gps.configure(on_location=self.on_location, on_status=self.on_status)
self.gps.start() #exception occurs here
except:
import traceback;
import sys;
formatted_lines = traceback.format_exc().splitlines()
self.gps_status = formatted_lines[-2]+'\n'+formatted_lines[-1]
print self.gps_status
return Builder.load_string(kv)
def on_location(self, **kwargs):
self.gps_location = '\n'.join(['{}={}'.format(k, v) for k, v in kwargs.items()])
def on_status(self, stype, status):
self.gps_status = 'type={}\n{}'.format(stype, status)
if __name__ == '__main__':
GpsTest().run()
I would like to create a function which only returns its value once the user input text into a TextInput and click on an ok button. For example
n = PopupInput("What number should I add?")
print 5+n
I can't figure out how to write a kivy dialog which will pause execution and wait until the user closes it. In other GUI toolkits, I would have used something like
while True:
if dialog.visable == False:
return int(textbox.text)
else:
wx.Yield()
To allow my code to just sit in one spot while allowing the GUI framework to do its thing. However, I can find no equivalent method for Kivy.
EDIT:
Here's my unsuccessful attempt (its messy)
def PopupOk(text, title='', btn_text='Continue'):
btnclose = Button(text=btn_text, size_hint_y=None, height='50sp')
content = BoxLayout(orientation='vertical')
p = Popup(title=title, content=content, size=('300dp', '300dp'),
size_hint=(None, None))
content.add_widget(Label(text=text))
ti = TextInput(height='50sp', font_size='50sp', input_type='number')
content.add_widget(ti)
def _on_d(*args):
p.is_visable = False
p.bind(on_dismiss=_on_d)
p.is_visable = True
content.add_widget(btnclose)
btnclose.bind(on_release=p.dismiss)
p.open()
while not p.is_visable:
EventLoop.idle()
return ti.text
I would think about this the other way around - what you really want to do is print the number when the popup is closed.
Assuming you have a popup with a textinput for the user to write in, you can do popup.bind(on_dismiss=some_function) to run that some_function when the popup is closed. That means all you need to do is write a function that takes a popup, retrieves the textbox text, and prints whatever answer you want.
I'm not sure how directly this fits with whatever you're really trying to do, but it's a more natural way to work with Kivy's event system. I can maybe answer differently if you have some strongly different requirement.
Edit: Seeing your edit, this is almost what you do, but I think it is a bad idea to try and beat the eventloop into submission this way rather than going with the flow. I would create a new function (as I said above) that takes a textinput and does whatever you really want. By binding on_dismiss to this function, you let kivy take care of starting your computation later whenever the user gets around to dismissing the popup.
Kivy is really built around the principle of events and async callbacks. Because it uses OpenGL and relies upon frames rendered on the GPU, not the CPU, you never want to use blocking code. So kivy uses event binding to circumvent the issue.
Here is one approach.
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MainApp(App):
def build(self):
self.button = Button(text="Click",
on_release=self.get_caption)
return self.button
def get_caption(self, btn):
Popup(title="Enter text here",
content=TextInput(focus=True),
size_hint=(0.6, 0.6),
on_dismiss=self.set_caption).open()
def set_caption(self, popup):
self.button.text = popup.content.text
MainApp().run()
You place content in a popup, when give it a "set_caption" function to call when it's dismissed. There you respond to the change. No blocking. No waiting. Having worked with threading to stop GUI blocking in wxWidgets, I really think this is a better way...;-)
Cheers
What you are looking for can be achieved with the following code. You need to specify routines as methods in the main program:
import kivy
kivy.require('1.5.0') # replace with your current kivy version !
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.clock import Clock
class YourApp(App):
def build(self):
return Button(text='Press for popup!', on_press=self.callpopup)
def callpopup(self, event):
dlg = MessageBox(titleheader="Titel Header", message="Any Message", options={"YES": "printyes()", "NO": "printno()"})
print "Messagebox shows as kivy popup and we wait for the \nuser action and callback to go to either routine"
def printyes(self):
# routine for going yes
print "You chose the Yes routine"
def printno(self):
# routine for going no
print "You chose the No routine"
class MessageBox(YourApp):
def __init__(self, titleheader="Title", message="Message", options={"OK": "self.ok()", "NO": "self.no()"}):
def popup_callback(instance):
"callback for button press"
# print('Popup returns:', instance.text)
self.retvalue = instance.text
self.popup.dismiss()
self.retvalue = None
self.options = options
box = BoxLayout(orientation='vertical')
box.add_widget(Label(text=message, font_size=20))
b_list = []
buttonbox = BoxLayout(orientation='horizontal')
for b in options:
b_list.append(Button(text=b, size_hint=(1,.35), font_size=20))
b_list[-1].bind(on_press=popup_callback)
buttonbox.add_widget(b_list[-1])
box.add_widget(buttonbox)
self.popup = Popup(title=titleheader, content=box, size_hint=(None, None), size=(400, 400))
self.popup.open()
self.popup.bind(on_dismiss=self.OnClose)
def OnClose(self, event):
self.popup.unbind(on_dismiss=self.OnClose)
self.popup.dismiss()
if self.retvalue != None:
command = "super(MessageBox, self)."+self.options[self.retvalue]
# print "command", command
exec command
if __name__ == '__main__':
YourApp().run()
in Kivy, when I press the back button on my android device it throws me out of the application. is there a way to return back to the previous screen using the Kivy language and not python? this is what I have written in kivy:
<MyAppClass>:
AnchorLayout:
anchor_x : 'center'
anchor_y : 'top'
ScreenManager:
size_hint : 1, .9
id: _screen_manager
Screen:
name:'screen1'
StackLayout:
# irrelevant code
Screen:
name:'screen2'
StackLayout:
# irrelevant code
I need to manipulate the screen manager and its screens from python... if I can do so I will be ok with python.
Kivy on android binds the back button to the esc button so binding and listening to esc button in your app would help you handle how your app behaves when the back button is pressed.
In other words in your app when testing it on your desktop listen to the escape key from the system keyboard, this will be automatically be translated to being the back button on your android device. Something like::
def on_start():
from kivy.base import EventLoop
EventLoop.window.bind(on_keyboard=self.hook_keyboard)
def hook_keyboard(self, window, key, *largs):
if key == 27:
# do what you want, return True for stopping the propagation
return True
i guess that i have solved it but should thank both #inclement and #qua-non! your answers guys led me to the right way! so in kv i assume that i gave an id to my screen manager (please refer to my question where i have written the kv code) , in python i should do the following:
from kivy.core.window import Window
from kivy.properties import ObjectProperty
class MyAppClass(FloatLayout):#its a FloatLayout in my case
_screen_manager=ObjectProperty(None)
def __init__(self,**kwargs):
super(MyAppClass,self).__init__(**kwargs)
#code goes here and add:
Window.bind(on_keyboard=self.Android_back_click)
def Android_back_click(self,window,key,*largs):
if key == 27:
self._scree_manager.current='screen1'#you can create a method here to cache in a list the number of screens and then pop the last visited screen.
return True
class MyApp(App):
def build(self):
return MyAppClass()
if __name__=='__main__':
MyApp().run()
This is certainly possible. Here's a short example app with the method I use to do this:
from kivy.utils import platform
from kivy.core.window import Window
class ExampleApp(App):
manager = ObjectProperty()
def build(self):
sm = MyScreenManager()
self.manager = sm
self.bind(on_start=self.post_build_init)
return sm
def post_build_init(self, *args):
if platform() == 'android':
import android
android.map_key(android.KEYCODE_BACK, 1001)
win = Window
win.bind(on_keyboard=self.my_key_handler)
def my_key_handler(self, window, keycode1, keycode2, text, modifiers):
if keycode1 in [27, 1001]:
self.manager.go_back()
return True
return False
This should give the right basic idea, but a few notes:
ScreenManager doesn't keep track of the previous screens, it's up to you to implement this how you like. My example assumes you defined a class MyScreenManager with a go_back method.
It might not be necessary to bind to on_start and run post_build_init, this is just how the example I originally used did it (see below). It might be important sometimes though, possibly if the window is not initialised when build() is run, and the original mailing list post suggests the author needed it for some reason.
The example listens for keycodes 27 or 1001. As qua-non said while I was writing this, the former listens for esc, so you can get the same behaviour on desktop.
I didn't try without the android.map_key line, but it seems like it may not be necessary.
You mention you want to use kivy language and not python. You need to do some python to get this result, and I don't see a way around that (it's not really the domain of the kv language). I guess you could shift some stuff to kv by defining a 'go_back' event somewhere and triggering this when the key is pressed, along with binding your screenmanager to watch that event, but it seems like a long way around.
I based my code on the mailing list thread at https://groups.google.com/forum/#!topic/kivy-users/7rOZGMMIFXI . There might be a better way, but this is quite functional.
Now all the way in 2020 I'm using:
Clock.schedule_once(lambda x: Window.bind(on_keyboard=self.hook_keyboard))
in combination with a similar hook_keyboard method to the other answers, to delay the bind in my build method. Works fine, but none of these other ways ways seemed to work for me anymore.
the code in question
txt = TextInput(text='%s'%default, multiline=False, size_hint=(0.5,1))
txt.font_name = gAssets + "FreeSans.ttf"
Txt.font_size = 14
If I comment out the font_name attribute the text in the input lines up about right. (still sits a little bit high in the box but workable)
(using the normal TextInput with the default font (DroidSans.ttf))
However once I uncomment the line that sets it to FreeSans.ttf (larger character set) It now sits way to high in the text field
(using normal TextInput with FreeSans.ttf)
I am using kivy 1.3 and have been unsuccessful at getting the padding attribute to work(however I would be happy to use it if someone could demonstrate how to use it with a TextInput.)
You can alter padding inside your code using VariableListPropery. Example:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.properties import VariableListProperty
class MyTextInput(TextInput):
padding = VariableListProperty(['24dp', '48dp'])
class MyApp(App):
def build(self):
return MyTextInput(text='This is an example text', multiline=False)
if __name__ == '__main__':
MyApp().run()
This code requires 1.7 version, as noted in documentation of the widget. I recommend uprgrading as I don't even see any API archive anywhere to check how it was setted before.