How to break a while loop in Kivy? - android

Here I have a function runStuff that I would like to keep running the function saySomething() repeatedly but will exit upon the pressing of the exit button.
It is currently running the while loop without allowing the input of the exit button.
def saySomething():
ttsSpeak('Say this phrase')
def runStuff(self):
while True:
if App.get_running_app().stop() != True:
saySomething()
time.sleep(2)
else: break
def exit(self):
App.get_running_app().stop()
class AudibleCoachApp(App):
def build(self):
layout = BoxLayout(padding=7, spacing=3, orientation='vertical') # set layout of the screen
btn1 = Button(text='Start it', size_hint=(1, .66)) # create a button instance
btn1.bind(on_press=runStuff) # binding the button with the function below
btn3 = Button(text='Exit', size_hint=(1, .17)) # create a button instance
btn3.bind(on_press=exit) # binding the button with the function below
layout.add_widget(btn1) # physically add the button onto the layout
layout.add_widget(btn2) # physically add the button onto the layout
layout.add_widget(btn3) # physically add the button onto the layout
return layout
if __name__ == '__main__':
AudibleCoachApp().run()

Infinite loops are rarely a good solution in UI programming, because they block the entire thread that they are running on. It would be better to use kivy's Clock object to schedule your method.
from kivy.clock import Clock
def runStuff(self):
Clock.schedule(saySomething, 2)
The callback can then be unscheduled using Clock.unschedule.
def exit(self):
App.get_running_app().stop()
Clock.unschedule(saySomething)
Additionally, the use of get_running_app is generally discouraged, since it is good practice to have all functions with access to the app to be methods of the app. Since you included self arguments in each of the functions, I assume that is what you were planning to do anyway.
class AudibleCoachApp(App):
def runStuff(self):
Clock.schedule(saySomething, 2)
def exit(self):
Clock.unschedule(saySomething)
self.stop()
You'll have to change your call of runStuff and exit in build to self.runStuff and self.exit.
btn1.bind(on_press=self.runStuff) # binding the button with the function
...
btn3.bind(on_press=self.exit) # binding the button with the function below
And, you'll need to explicitly bind the app to stop the Clock from being GC'd.
if __name__ == '__main__':
app = AudibleCoachApp()
app.run()
Altogether, the code becomes the following (don't forget to add the import of Clock from kivy.clock).
import pyttsx
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
# ...
def saySomething():
ttsSpeak('Say this phrase')
class AudibleCoachApp(App):
def runStuff(self):
Clock.schedule_interval(saySomething, 2)
def exit(self):
Clock.unschedule(saySomething)
self.stop()
def build(self):
layout = BoxLayout(padding=7, spacing=3, orientation='vertical') # set layout of the screen
btn1 = Button(text='Start it', size_hint=(1, .66)) # create a button instance
btn1.bind(on_press=self.runStuff) # binding the button with the function below
btn2 = Button()
btn3 = Button(text='Exit', size_hint=(1, .17)) # create a button instance
btn3.bind(on_press=self.exit) # binding the button with the function below
layout.add_widget(btn1) # physically add the button onto the layout
layout.add_widget(btn2) # physically add the button onto the layout
layout.add_widget(btn3) # physically add the button onto the layout
return layout
if __name__ == '__main__':
app = AudibleCoachApp()
app.run()
NOTE that all the code above is untested. If you spot any errors, please leave a comment.

Related

How to deattach webview once attached?

I've successfully attached WebView to my Kivy app following Kivy wiki instructions. It works as expected, but I'd like to deattach and return to my normal Kivy ui. How to I do that?
I've tried to explore WebView documentation, accessing it's methods (the WebView.destroy() complains about destroying a WebView that's still attached), it's parent methods (I'm not even sure if that's the way to go), but I couldn't get rid of the WebView.
Ok, I'm not sure whether this is the best solution, or clean enough, but the only one I know that works. While it works and seems stable, it needs further testing by someone with better knowledge of Kivy and Android API itself.
if platform == 'android':
from jnius import autoclass
from android.runnable import run_on_ui_thread
WebView = autoclass('android.webkit.WebView')
WebViewClient = autoclass('android.webkit.WebViewClient')
activity = autoclass('org.renpy.android.PythonActivity').mActivity
else:
import webbrowser
def run_on_ui_thread(func):
''' just for desktop compatibility '''
return func
class MyScreen(Screen):
view_cached = None # make these object properties?
webview = None
wvc = None # not even needed probably
code = StringProperty() # this property triggers webview to close
url_to_load = None
def on_enter(self):
if platform == 'android':
Clock.schedule_once(self.create_webview, 0) # probably doesn't need clocked call (because decorators will make sure
# function runs on correct thread), but leaving it until tested properly
else:
webbrowser.open_new(self.url_to_load) # on desktop just run the webbrowser
#run_on_ui_thread
def on_code(self, *args):
''' runs when you are ready to detach WebView '''
self.detach_webview()
#run_on_ui_thread
def create_webview(self, *args):
''' attaching webview to app '''
if self.view_cached is None:
self.view_cached = activity.currentFocus # caches current view (the one with kivy) as a view we want to go back to; currentFocus or getCurrentFocus() works
self.webview = WebView(activity)
settings = self.webview.getSettings()
settings.setJavaScriptEnabled(True) # enables js
settings.setUseWideViewPort(True) # enables viewport html meta tags
settings.setLoadWithOverviewMode(True) # uses viewport
settings.setSupportZoom(True) # enables zoom
settings.setBuiltInZoomControls(True) # enables zoom controls
self.wvc = WebViewClient()
self.webview.setWebViewClient(self.wvc)
activity.setContentView(self.webview)
self.webview.loadUrl(self.url_to_load)
#run_on_ui_thread
def key_back_handler(self, *args):
''' sketch for captured "key back" event (in App), not tested properly '''
if self.webview:
if self.webview.canGoBack() == True:
self.webview.goBack()
else:
self.detach_webview()
Clock.schedule_once(self.quit_screen, 0)
else:
App.get_running_app().root.current = 'some_other_screen_to_switch_to'
#run_on_ui_thread
def detach_webview(self, *args):
if self.webview:
self.webview.clearHistory()
self.webview.clearCache(True)
self.webview.loadUrl("about:blank")
self.webview.freeMemory() # probably not needed anymore
self.webview.pauseTimers() # this should stop any playing content like videos etc. in the background; probably not needed because of 'about:blank' above
activity.setContentView(self.view_cached) # sets cached view as an active view
#self.webview = None # still needs testing;
#self.wvc = None
#mainthread
def quit_screen(self, *args):
''' if not called on #mainthread, it will be freezed '''
app = App.get_running_app()
app.root.current = 'some_other_screen_to_switch_to'
I'm creating WebView when entering MyScreen(Screen), and when detaching WebView, switching back to some other Screen.
The view before WebView gets cached (is this efficient? probably would be better to access it some other way) and used again when WebView is destroyed.
The quit_screen() calls maybe should be moved to detach_webview(), but the code as a whole probably needs better organization, so leaving it as it is, since this is tested sample.

Python Kivy Screen Inheritance

Background: I am using solely Python to implement the Kivy screens. I have two screens that both contain a list, and two rows of buttons. I thought it would be good programming practice to create a screen class that has those layouts, and then use inheritance to create the two screens and add buttons to the layouts as needed.
The Problem: However, when I do this I find that in the child screens I cannot access self.manager.current <-- specifically '.current' It also does not have access to self.manager.transition. I wish to understand why this happens, and how/what things are inherited here.
Question: Does anybody know why or how it is not inheriting the parent screen's manager's properties?
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListView, ListItemButton
from kivy.adapters import listadapter
from kivy.uix.button import Button
from kivy.properties import ListProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition
class ListScreen(Screen): # This is the super class that I am trying to inherit through
itemList = ListProperty([])
selected_value = StringProperty()
layout = BoxLayout(orientation ='vertical')
top_buttons=BoxLayout(size_hint_y=0.1)
scrollable_list=ListView(adapter=listadapter, size_hint_y=0.8)
scrollable_list.data=itemList
scrollable_list.selection_mode='single'
scrollable_list.allow_empty_selection=False
# scrollable_list.cls=ListItemButton <-- Unrelated bug here, ignore this line
bot_buttons=BoxLayout(size_hint_y=0.1)
def __init__(self, **kwargs):
super(ListScreen, self).__init__(**kwargs)
def finalize_widgets(self):
self.layout.add_widget(self.top_buttons)
self.layout.add_widget(self.scrollable_list)
self.layout.add_widget(self.bot_buttons)
self.add_widget(self.layout)
def change(self,change):
self.selected_value = 'Selected: {}'.format(change.text)
def change_screen(self, screen_name):
self.manager.current = screen_name # <-- Here is the problem
class SubScreen(ListScreen):
# This is one of the child classes, intended to inherit Screen through the parent ListScreen class.
def __init__(self, **kwargs):
super(SubScreen, self).__init__(**kwargs)
save = Button(text='Save')
load = Button(text='Load')
new_d = Button(text='New')
new_s = Button(text='New Search')
self.top_buttons.add_widget(save)
self.top_buttons.add_widget(load)
self.top_buttons.add_widget(new_d)
self.top_buttons.add_widget(new_s)
new_s.bind(on_press = self.change_screen('search'))
class ListBuilderApp(App):
def build(self):
sm = ScreenManager(transition=SlideTransition())
sm.add_widget(SubScreen(name='list'))
sm.add_widget(SearchResults(name='results'))
sm.add_widget(SearchScreen(name='search'))
return sm
if __name__ == "__main__":
ListBuilderApp().run()
Well, I don't see ScreenManager which is basically what you get with self.manager(object), so...
No ScreenManager with Screens added either in python or kv like widgets(add_widget()), no self.manager inside any of the Screens - you can't access something that isn't there.
Add some class which will be a root for your Screens and make it ScreenManager + add Screens as children.
Edit:
I was blind probably, but I didn't see on_press=.. stuff. There is the problem, because you didn't assign the function to an event, rather called the function right when you put ('search') at the end. (try do self.parent and you'll see)
Kivy events(at least "on_") catch the function, but not the parameters, not directly. That's why you need to use partial
from functools import partial
new_s.bind(on_press = partial(self.change_screen,'search'))
With this the error is gone, but some adapter binding jumps out which I really don't have a clue how to fix as I don't use it that much.

How to access Kivy screens manager.<property> using Python

Background: I have been learning Python - and through it - Kivy, by making an app. I have been using the .kv file and Builder.load_string methods to create my graphics, but have decided to try using solely python, and moving all of my layouts over into python.
The Problem: When I began using screens, I was unable to bind the correct code to the buttons to make the screens transition. When I am writing the line, 'self.manager.etc...' auto-complete shows me a list of valid properties to use.
So after 'self.' it shows that I can use 'manager', and after 'manager.' it does not think that the screen's manager has a 'current' or 'transition' property. I must have messed up in how I connected the screen to the manager, but I cannot fathom how.
class HomePage(Screen):
def __init__(self, **kwargs):
super(HomePage, self).__init__(**kwargs)
layout = FloatLayout()
notification = Label(text='upcoming: ....', font_size='16sp', size_hint=(0,0), pos_hint={'center_x':.5, 'top':0.9})
layout.add_widget(notification)
button_row=BoxLayout(size_hint_y=.1, spacing=20)
profile_button=Label(text='Profile')
button_row.add_widget(profile_button)
layout.add_widget(button_row)
self.add_widget(layout)
def transit():
self.manager.current = profile_button # <- this should work, right?
profile_button.bind(on_press=transit)
class ScreenApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(HomePage(name='home'))
return sm
if __name__ == "__main__":
ScreenApp().run()
You should add in your imports the transition
from kivy.uix.screenmanager import Screen, ScreenManager, FadeTransition
And pass it in the builder
class ScreenApp(App):
def build(self):
sm = ScreenManager(transition=FadeTransition())
as for the current you should add a second screen, give it a name and and use that name to change to that screen.from the https://github.com/kivy/kivy/blob/master/kivy/uix/screenmanager.py
by default, the first added screen will be shown. If you want to
show another one, just set the 'current' property. sm.current = 'second'
Also current is a string property, you can not set it to a label
:attr:current is a :class:~kivy.properties.StringProperty and
defaults to None.
So your full code should be something like
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.screenmanager import Screen, ScreenManager, FadeTransition
class HomePage(Screen):
def __init__(self, **kwargs):
super(HomePage, self).__init__(**kwargs)
layout = FloatLayout()
notification = Label(text='upcoming: ....', font_size='16sp', size_hint=(0,0), pos_hint={'center_x':.5, 'top':0.9})
layout.add_widget(notification)
button_row=BoxLayout(size_hint_y=.1, spacing=20)
profile_button=Button(text='Profile') # changed to a button
button_row.add_widget(profile_button)
profile_button.bind(on_press=self.transit) # moved here the bind action
layout.add_widget(button_row)
self.add_widget(layout)
def transit(self, *args):
# unintended to become a class method and reference to it with self
print "ok"
self.manager.current = "screen2"
class ProfilePage(Screen):
def __init__(self, **kwargs):
super(ProfilePage, self).__init__(**kwargs)
layout = FloatLayout()
labelP = Label(text="Profile Page")
layout.add_widget(labelP)
self.add_widget(layout)
class ScreenApp(App):
def build(self):
sm = ScreenManager(transition=FadeTransition())
# create the first screen
screen1 = HomePage(name='Home') #your home page
screen2 = ProfilePage(name='screen2') # the second screen
sm.add_widget(screen1)
sm.add_widget(screen2)
return sm
if __name__ == "__main__":
ScreenApp().run()

Restarting an app in kivy

I've written a simple game that I want to restart after a certain button is pressed (so that EVERYTHING goes back to where it was when the app was run).
So the question is: what do I need to type into on_press to start Game() ance again?
class Game(Widget):
(...)
def but_add(self, player):
self.add_widget(Button(text=player,
font_size=30,
center_x=self.width/2,
center_y=self.height/2,
size=(self.height, self.height*7/20),
background_normal='katana.jpg',
background_down='katana.jpg',
markup=True,
on_press= ???????? ))
(...)
class Okiya(App):
def build(self):
return Game()
if __name__ == "__main__":
Okiya().run()
You probably don't really want to restart the entire app, but just to reset its state. The mechanism of doing so is up to you, you're the one that knows what the initial state should look like; for instance, you could do on_press=self.reset_func and define the reset_func method to perform all these tasks.
You could also remove the widget and add a new instance, which will have the default properties.
I don't know exactly what it's worth but the following works for an app I'm doing:
Add the following method to your application class:
class Okiya(App):
def restart(self):
self.root.clear_widgets()
self.stop()
return Okiya().run()
Then call the method from a python file where you want to restart the app with:
App.get_running_app().restart()
Here is my test example
from kivy.app import App
from kivy.uix.button import Button
from kivy import platform
if platform == 'android':
from jnius import autoclass
Intent = autoclass("android.content.Intent")
PythonActivity = autoclass("org.kivy.android.PythonActivity")
System = autoclass("java.lang.System")
class RestartApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen = Button(text='Restart', on_release=self.restart)
def build(self):
return self.screen
def restart(self, *args):
if platform == 'android':
activity = PythonActivity.mActivity
intent = Intent(activity.getApplicationContext(), PythonActivity)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
activity.startActivity(intent)
System.exit(0)
else:
self.screen.clear_widgets()
self.stop()
RestartApp().run()
RestartApp().run()

Creation of an input dialog using Kivy

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()

Categories

Resources