Kivy textinput see end - python

This seems like something that would be useful, and maybe I've missed it, but does a Kivy Textinput have a method that is equivalent to tkinter's Text.see(END)? I've looked at do_cursor_movement, but that just moves the cursor, not what is displayed.

I've found that the problem is solved by working with the ScrollView that contains the TextInput. With the following in a .kv file
ScrollView:
id: scroll1
size_hint_x: 0.6
TextInput:
readonly: True
id: main
size_hint: 1, None
height: max(self.minimum_height, scroll1.height)
Then all one needs to do is call self.ids['scroll1'].scroll_y = 0 This will scroll the TextInput to the bottom.

I've found a bug with the cursor not being set properly vs the display on start up - in my example below, after adding the lines to the TextInput the cursor reads as (0, 100) even though the top of the text is actually displayed. This causes the Bottom button to do nothing - until you click in the TextInput (changing the cursor pos) or hit the Top button.
To see what I mean, just comment out Clock.schedule_once(lambda _: setattr(root.ids['ti'], 'cursor', (0, 0))).
I've tested this code as working with Kivy 1.8.1-dev (git: 1149da6bf26ff5f27536222b4ba6a874456cde6e) on Ubuntu 14.04:
import kivy
kivy.require('1.8.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
root = Builder.load_string('''
BoxLayout:
orientation: 'vertical'
BoxLayout:
TextInput:
id: ti
Label:
size_hint_x: None
width: sp(80)
text: str(ti.cursor)
BoxLayout:
size_hint_y: None
height: sp(128)
Widget
Button:
text: 'Top'
on_press: ti.cursor = (0, 0)
Button:
text: 'Bottom'
on_press: ti.cursor = (0, len(ti._lines) - 1)
Widget
''')
class TestApp(App):
def build(self):
text = ''
for i in xrange(100):
text += 'Line %d\n' % (i + 1,)
root.ids['ti'].text = text
# fix the cursor pos
Clock.schedule_once(lambda _: setattr(root.ids['ti'], 'cursor', (0, 0)))
return root
if __name__ == '__main__':
TestApp().run()

I'm not familiar to Kivy at all, nor did I test about my answer, but if I understand the docs correctly, by just setting the attribute cursor to the end of the line could do what you want.
Quoting:
cursor
Tuple of (row, col) values indicating the current cursor position.
You can set a new (row, col) if you want to move the cursor. The
scrolling area will be automatically updated to ensure that the cursor
is visible inside the viewport.

Related

Python Kivy self.add_widget() doesn't update while code running

The idea is to create a texting app that works like Messenger. I am having a problem with the chat history which is a "BoxLayer (or GridLayer)" containing all previous text. I want when I insert a new text, it's will appear as a new label or a box and stay below the previous text like this, but when I run the code and insert input text, it's not appearing. I spent hours to find the answer both myself and on the internet, but it's kind of hard for a beginner like me.
.Py file
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.core.image import Image
from kivy.properties import StringProperty
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
class MainWidget(Widget):
request = StringProperty("This is a previous text, don't mind")
insert_text = StringProperty("Insert Here")
window_size = (305,400)
refresh_key = False
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.Window_Minimum()
def on_size(self,*args):
print(self.width,self.height)
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
Chat_history_update().chat_history(request)
def Window_Minimum(self):
Window.minimum_width,Window.minimum_height=self.window_size
class Chat_history_update(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
l = Label(text="This is a previous text, don't mind",size_hint=(1, None),height=("30dp"))
self.add_widget(l)
def chat_history(self,request): # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Add Label Function
l = Label(text=request, size_hint=(1, None),height=("30dp"))
self.add_widget(l) # <<<<<<<<<<<<< This won't update my app screen
class Assistant(App):
pass
if __name__ == "__main__":
Assistant().run()
Kv file
MainWidget:
<MainWidget>:
BoxLayout:
size: root.size
orientation: "vertical"
GridLayout:
cols: 3
size_hint: 1,None
height: "50dp"
spacing: "10dp"
padding: "10dp"
Label:
text:"Erza Assistant"
Button:
text:"Edit Path"
Button:
text:"Setting"
GridLayout:
size: self.size
rows: 2
spacing: "10dp"
padding: "10dp"
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
TextInput:
size_hint: 1, None
height: "40dp"
text: root.insert_text
multiline: False
on_text_validate: root.on_text_validate(self)
Your code:
Chat_history_update().chat_history(request)
is creating a new instance of Chat_history_update, and calling chat_history() for that new instance. That new instance is not part of your GUI, so you will see no effect. The fix is to access the correct instance of Chat_history_update (the one that is in your GUI). To do that, you can add an id in your kv:
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
id: chu
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
And then use that id in your py code:
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
self.ids.chu.chat_history(request)
I think this here might help. You have to reference widgets in the kivy language by using id or ids.
If you do not yet i strongly suggest you learn how to reference widgets by their ids.

Scroll contents of GridLayout in ScrollView - Kivy

I will say first off I have tried every single example on the web involving kv lang. Not once have I had any success.
The idea is pretty simple: As I swipe up/down/scroll the contents of GridLayout() within ScrollView() are scrolled up or down.
The best I have been able to do is have the scroll bar fade into view when running the program. Not able to scroll unfortunately.
<Root>
grid_layout: grid_layout
ScreenManager:
...
Screen:
...
ScrollView:
GridLayout:
id: grid_layout
size_hint_y: None
cols: 1
height: self.minimum_height
<list of buttons>
Binding minimum_heightin the __init__ method of the root class (RelativeLayout):
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
I have followed https://github.com/kivy/kivy/blob/master/examples/widgets/scrollview.py converting it to kv lang - scroll bar visible, unable to scroll. Also tried every example on Google Groups and here related to using kv lang. Still no scroll :\
Compiling using buildozer and running on Android fails for an unknown reason.
I would appreciate any assistance that can be given.. I am completely clueless at this point
This:
height: self.minimum_height
should be:
minimum_height: self.height
This is unnecessary:
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
It also won't scroll unless the contents are larger than the scrollview's height:
Full code:
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
Builder.load_string('''
<Root>:
ScrollView:
size_hint: 1, .1
# setting the width of the scrollbar to 50pixels
bar_width: 50
# setting the color of the active bar using rgba
bar_color: 5, 10, 15, .8
# setting the color of the inactive bar using rgba
bar_inactive_color: 5, 20, 10, .5
# setting the content only to scroll via bar, not content
scroll_type: ['bars']
GridLayout:
size_hint_y: None
cols: 1
minimum_height: self.height
Button
text: 'one'
Button:
text: 'two'
Button:
text: 'three'
Button:
text: 'four'
''')
class Root(FloatLayout):
pass
class DemoApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DemoApp().run()
Being unable to scroll was due to a misunderstanding of Kivy's touch handlers. Completely unrelated to the code mentioned in my question.
The key is to have GridLayout be larger than ScrollView, so GridLayout can be panned within ScrollView.
For those wanting to use ScrollView inside ScreenManager using kvlang only:
ScreenManager:
id: screen_manager
Screen:
manager: screen_manager
id: main_screen
name: 'main'
ScrollView:
bar_width: 4
# pos_hint defaults to 1,1 so no need to declare it
GridLayout:
size_hint_y: None
cols: 1
# you do not need to manually bind to setter('height') in
# python - perfectly possible with kv lang
# this allows for height to update depending on the
# collective heights of its child widgets
height: self.minimum_height
<----- widgets here ----->
# for scroll to show/work there must be more widgets
# then can fit root.height. If not there is no need
# for scrollview :)

Wrapping a listview to the size of window in Kivy

I'm having real problems wrapping text in Kivy. I'm looking for the results of the function "SearchingModal", to be displayed as a list: but where the results can take up more than one line. Previously i'd looked at chopping up the individual text strings with new line characters, but this seems very unelegant!
#!
#---------------------------------------------------------------------------------------#
# #
# Question Search Using Acquired Database #
# #
#---------------------------------------------------------------------------------------#
# Include sql database support; kivy modules.
import sqlite3
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.properties import ObjectProperty
from kivy.properties import ListProperty
from kivy.properties import StringProperty
from kivy.uix.scrollview import ScrollView
from kivy.uix.modalview import ModalView
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.listview import ListView
from kivy.uix.gridlayout import GridLayout
#---------------------------------------------------------------------------------------#
# Select Database
#---------------------------------------------------------------------------------------#
con = sqlite3.connect('test.db')
con.text_factory = str
#---------------------------------------------------------------------------------------#
class OrkivRoot(BoxLayout):
pass
#---------------------------------------------------------------------------------------#
class QuestionDetailsForm(AnchorLayout):
question_box = ObjectProperty()
answer_box = ObjectProperty()
def submitquestion(self):
search = self.question_box.text
app = Orkiv.get_running_app()
data = app.run_searchquestion(search) #data returns here
# x=0 #
# for x in range(len(data)): # This was to print results to terminal
# print data[x] #
# print "-" * 40 #
# x+=1 #
modal = SearchingModal(data) # comment these 2 lines to remove popup on submit
modal.open() #
def submitanswer(self):
search = self.answer_box.text
app = Orkiv.get_running_app()
data = app.run_searchanswer(search) #data returns here
#
modal = SearchingModal(data) # comment these 2 lines to remove popup on submit
modal.open() #
#------------------------------------------------------------#
#REDESIGN THE SEARCHING MODAL CLASS, USING THE KV LAYOUT FILE#
##############################################################
class SearchingModal(BoxLayout, ModalView, ListView):
def __init__ (self, data):
counter=0
super(SearchingModal, self).__init__(item_strings=[str(data[counter]) for counter in range(len(data))])
self.dismiss()
button = Button(text="Back To Search")
button.size_hint = (0.5, None)
button.height = "40dp"
button.bind(on_press=self.dismiss)
self.add_widget(button)
class Orkiv(App):
def run_searchquestion(self, search):
print 'searching for', search
cur = con.cursor()
cur.execute('SELECT title, content1 FROM content WHERE title LIKE ?', ('%'+search+'%',))
data = cur.fetchall()
#
return data #Returns the accumulated data to the variable calling the function
def run_searchanswer(self, search):
print 'searching for', search
cur = con.cursor()
cur.execute('SELECT title, content1 FROM content JOIN item on content._id=ref_id WHERE word=?', (search,))
data = cur.fetchall()
#
return data #Returns the accumulated data to the variable calling the function
Orkiv().run()
I'll also include the .kv file, as no amount of messing around with that seems to be able to get my results to wrap correctly either!
OrkivRoot:
<OrkivRoot>:
QuestionDetailsForm:
<QuestionDetailsForm>:
anchor_y: "center"
question_box: question_input
answer_box: answer_input
BoxLayout:
orientation: "vertical"
height: "350dp"
size_hint_y: None
GridLayout:
cols: 2
row_default_height: "40dp"
row_force_default: True
spacing: "50dp"
padding: "40dp"
Label:
text: "Question"
TextInput:
id: question_input
Label:
text: "Answer"
TextInput:
id: answer_input
Label:
text: " "
Button:
height: "30dp"
size_hint_y: None
text: "Submit Question"
on_press: root.submitquestion()
Label:
text: " "
Button:
size_hint_y: None
height: "30dp"
text: "Submit Answer"
on_press: root.submitanswer()
<SearchingModal>:
orientation: 'vertical'
BoxLayout:
size_hint_y: 0.005
ListView:
ListItemLabel:
font_size: 70
text_size: (root.width, None)
It's very frustrating. I'm sure some of the code in the layout file is not being executed correctly. For example, it seems impossible to change the font size. I do not actually want the font size changed, but figured that finding a way to do that would lead me to being able to wrap text using "text size: ".
This is my first Kivy program, and my first step into object orientated programming. Any advice would be appreciated, and whilst ill do my best to decipher and make sense, starting very simply would be a great help!
I thought that once i'd got the information out of the database, and had opened up the next screen, everything else would be extremely simple! I really feel it's just changing a few things very slightly in the .kv file. I just don't know where to start!!!
Thanks in advance!
After messing around quite a lot with the ".kv" file, and the main python program, I came up with a solution, taking a slightly different approach.
I dispensed with the listview, and stuck with a simple modal pop-up consisting of a label. I then was able to follow much of the advice in this youtube video, to make the label wrap and scroll effectively.

Label not affected by AnchorLayout - Kivy

I have just started learning kivy and am stuck with this layout problem.
I used the Anchor Layout to center a button using:
Button:
text: "A button"
anchor_x:'center'
anchor_y:'center'
However when I try this:
Label:
text: "Hello World!"
size_hint: 1, 1
anchor_x:'center'
anchor_y:'top'
It just shows "Hello World!" in the default center position but I want it above my centered button.
I have tried everything and looked at the docs but have found nothing to explain this.
Edit:
Here are both files:
py file:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
class Layout(AnchorLayout):
pass
class LayoutApp(App):
def build(self):
return Layout()
if __name__ == '__main__':
LayoutApp().run()
kv file:
<Layout>:
Label:
text: "Hello World!"
font_size: 50
size_hint: None, None
height: 100
anchor_x:'center'
anchor_y:'top'
Button:
text: "Button"
background_colour: (0,0,1,1)
size_hint: None, None
size: 50, 50
anchor_x:'center'
anchor_y:'top'
size_hint: 1, 1
This is your problem, the size_hint means the Label is the same size as the parent layout, with the text in the middle...i.e. the middle of the screen. It doesn't matter where the label is anchored, because there's no room for it to move.
You should instead manually set the size you want, e.g.
size_hint_y: None
height: 50

How to unbind a property automatically binded in Kivy language?

The Kivy Language automatically creates internal binds in properties. For example, if we assign the position of the parent to the position of the child, then the position of the child is going to be updated automatically:
Widget:
Label:
pos: self.parent.pos
In this case, if we move the parent Widget, then the child is also going to move. How do I unbind the property pos from the child? I know how to unbind (properties)[http://kivy.org/docs/api-kivy.uix.widget.html#using-properties] that I bind myself but how do I unbind them if I don't know the name of the method it is bound.
Here is a small example to show what I mean. The Button Up moves the GridLayout to the top and Down to the Bottom. The Button Center center itself in the middle of the screen. My problem is that when I click Up or Down my Centered button is not anymore.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
GridLayout:
id: _box
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.y = 0
text: "Down"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: self.center_y = root.height/2
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.top = root.height
text: "Up"
""")
class Example(FloatLayout):
pass
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Why do I want to do that in any case? I am using an animations on the GridLayout that constantly updates the position. The normal position of the buttons should be inside the gridlayout but once in a while one of the buttons flies over the screen and come back to the same position. The problem is that I cannot make them fly while my gridlayout is also moving because the property is bound and as soon as the button try to fly it goes back to the grid. That also means that the binding is sometimes desirable. What I want is have control of the bind and unbind.
Comments don't seem to be working right now so I'll post this as a answer.
You already have a FloatLayout(your root widget). Use that instead of
creating a new FloatLayout.
Before removing the widget from the grid.
store it's size,
set size_hint to None, None
set pos_hint to position the widget in the center.
When adding the widget to grid do the reverse.
Here's your code with these fixes::
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
_size = self.center_button.size
self.center_widget.remove_widget(self.center_button)
self.center_button.size_hint = (None, None)
self.add_widget(self.center_button)
self.center_button.pos_hint = {'center_x': .5, 'center_y':.5}
else:
self.remove_widget(self.center_button)
self.center_button.size_hint = (1, 1)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Update 1:
If for whatever reason you still need to unbind the properties bound by kvlang you can do so using introspection to get a list of observers for the property. so for your case it would be something like this::
observers = self.center_widget.get_property_observers('pos')
print('list of observers before unbinding: {}'.format(observers))
for observer in observers:
self.center_widget.unbind(pos=observer)
print('list of observers after unbinding: {}'.format(self.center_widget.get_property_observers('pos')))
You would need to use the latest master for this. I should fore-warn you to be extremely careful with this though you'd need to reset the bindings set in kvlanguage, but then you loose the advantage of kv language... Only use this If you really understand what you are doing.
Following #qua-non suggestion, I am temporarily moving the child to another parent. It actually unbinds it, or maybe, rebinds it to the new parent. This is a partial solution because of whatever reason, it doesn't update the position automatically when I took it out of the GridLayout (i.e. when I press enter) and put it into the new parent. I need to press 'Up' (or 'Down') after the 'Out of the Box' button.
However, it does go back immediately. When you click again on the 'Out of the box' button the 2nd time, it goes back to its original position. This part works perfectly. And it continue obeying to its parent instructions.
In other words, it doesn't work immediately when I take out of the grid but it does when I put it back.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
float: _float
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
FloatLayout:
id: _float
size_hint: None,None
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
self.center_widget.remove_widget(self.center_button)
self.float.add_widget(self.center_button)
self.float.size = self.center_button.size
self.float.x = self.center_button.x
self.float.center_y = self.center_y
else:
self.float.remove_widget(self.center_button)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Here is something very similar to what I was trying. The difference is that I ended binding the properties manually so I can unbind them. Basically if I uncomment the line #pos: self.parent.pos of the 'out of the box' button, then I cannot unbind them unless I assign the Button to another parent as #qua-non suggested.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
GridLayout:
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
Button:
on_press: self.parent.y = 0
text: "Down"
Widget:
Button:
id: _center_button
size: self.parent.size
#pos: self.parent.pos
on_press: root.centerize(*args)
text: "Out of the Grid"
Button:
on_press: self.parent.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def __init__(self, **kwargs):
super(Example, self).__init__(**kwargs)
self.center_button.parent.bind(pos=self.binder)
self.centered = False
def binder(self, instance, value):
self.center_button.pos = instance.pos
def centerize(self, instance):
if self.centered:
self.center_button.parent.bind(pos=self.binder)
self.center_button.y = self.center_button.parent.y
self.centered = False
else:
self.center_button.parent.unbind(pos=self.binder)
self.center_button.center_y = self.height/2
self.centered = True
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()

Categories