Updating Button's background_color not reflected on the UI sometimes - kivy

Well, I'm creating a Kivy app in which you can select only one of many statuses. Every status has his own Button and the selected status has a different background_color from the others.
The problem is that sometimes (apparently random) after clicking a button, two of them stays with his background changed at the same time. The strange thing is that I'm checking the background_color of those elements and it doesn't match with the result that I'm seeing on the screen.
So, the background_color property has one color but another one is being rendered on the screen.
Relevant kv file section:
<StatusButtonsContainer>:
cols: 2
spacing: 8
padding: 0,16,0,0
<StatusButton>:
selected: False
text: self.status_name
on_release: app.on_change_status_click(self.status_name)
font_size: '16'
background_color: self.back_color if self.selected else (0.259, 0.259, 0.259,1)
background_normal: ''
background_down: ''
background_disabled_normal: ''
This is how I'm creating the Button widgets dinamically:
class StatusButtonsContainer(GridLayout):
def __init__(self, **kwargs):
super(StatusButtonsContainer, self).__init__(**kwargs)
for name, color in config.statuses.items():
button = StatusButton(status_name=name, back_color=color)
self.add_widget(button)
class StatusButton(Button):
status_name = StringProperty()
back_color = ListProperty()
And this the function that is executed when the button is pressed:
class ControlsScreen(Screen):
def change_selected_status(self, status):
for button in self.ids.buttons_container.children:
if button.status_name == status:
button.selected = True
button.disabled = True
print('Status ' + button.status_name + ' was selected.')
print('background_color:' + str(button.background_color))
else:
button.selected = False
button.disabled = status in ['printing', 'preparing', 'paused']
print('Status ' + button.status_name + ' was NOT selected.')
print('background_color:' + str(button.background_color))
It's even more weird that this is happening on a Raspberry Pi 3 with Raspbian, but I'm not able to reproduce it on a Windows machine... I double checked that the [input] section in the config is correct and the buttons are being pressed only once.
Versions
Python: 3.6.0
OS: Raspbian GNU/Linux 9 (stretch)
Kivy: 1.10.1

Finally, the problem was that I was updating the UI in a thread different from the main thread.
In my application, the button status can be changed either from the UI directly or from a web socket message. So, when the button's background_color was changed through web socket, the UI update was called by another thread and, for some reason, that was causing the issue.
I've solved it with the #mainthread decorator:
#mainthread
def change_status_ui(self, status):
self.get_screen('Controls').change_selected_status(status)
self.get_screen('Status').change_status(status)
Now, it doesn't matter which thread call this method, it will be executed on the main thread.

Related

I need to add more than one app.root.current in kivy

I am creating an app in kivy in which basically it gives you a poem once you enter a date and password. For this I have created several windows for each poem. My problem is that i made it so that on_release of the button there is a selective-like structure to show the right poem. The problem is that this only works for the last sentence and not all of them at the same time as supposed to.
Button:
font_name: "Georgiai"
font_size: 20
text: "Enter"
size_hint: 0.5, 0.5
background_color: "#00FFCE"
on_release:
app.root.current = "fifth" if date.text == "01.02.2023" and password.text == "Si" else "fourth"
app.root.current = "seis" if date.text == "02.02.2023" and password.text == "te" else "fourth"
I have tried using multiple on_release: app.root.current but it also does not work. This is the only thing left for it to function so I hope someone can help me. I am quite new in programming
You should move the program logic into Python and not include logic in your kivy file. Arguments can be passed like this:
Button:
on_release: app.give_poem(_date.text, _password.text, self)
# or
# on_release: root.give_poem(_date.text, _password.text, self)
I changed your variables by adding an underscore to avoid confusing your variables with other common objects. I am assuming date and password were ids of other kivy objects.
in your python app or top level widget you can employ your logic. Here is the basic signature that your method should have within your app or your top level widget object. This one just prints the arguments sent and you can decide what to do from there.
def give_poem(self, _date, _password, my_button):
print(f"{self}: {_date}: {_password}: {my_button}")

Segfault when using `lgi.Pango.AttrList.change` in AwesomeWM

Some context:
I am writing a font-previewing application, and one of the things I have to do very often is change the size of the text. As you can see in the image, someone drags the "Size" slider, and the application has to change the size of all of those pieces of text.
NOTE:
keep in mind I am not using beautiful.load_font and these examples are happening in my own text element I wrote, NOT in "wibox/widget/textbox.lua". So no caching happens from there, and I don't do any caching of fonts myself currently.
Initially, I tried to write something like:
-- in my "draw" function which would be called every time someone would move
-- the "Size" slider:
local tf = self.family .. ' ' .. self.weight .. ' ' .. self.size
local new_desc = lgi.Pango.FontDescription.from_string(tf)
text_layout:set_font_description(new_desc)
cr:update_layout(self._text_layout)
cr:show_layout(self._text_layout)
Using this was very slow, down to about 10 - 30 fps as I moved my mouse on the "Size" slider. To try to make this faster, I thought about NOT setting the font description on the text_layout every time, but only set it once when the text element was created, and try to only change the size every time I had to redraw.
So I read the Pango docs, and tried this:
when the text element is created:
local ctx = lgi.PangoCairo.font_map_get_default():create_context()
local text_layout = lgi.Pango.Layout.new(ctx)
local typeface_description = font_family .. " " .. font_weight
local desc = lgi.Pango.FontDescription.from_string(typeface_description)
text_layout:set_font_description(desc)
local pango_attrs = lgi.Pango.AttrList.new()
local pango_size = lgi.Pango.AttrSize.new(font_size * lgi.Pango.SCALE)
lgi.Pango.AttrList.insert(pango_attrs, pango_size)
text_layout:set_attributes(pango_attrs)
self.pango_attrs = pango_attrs
In my "draw" function:
local pango_size = lgi.Pango.AttrSize.new(self.size * lgi.Pango.SCALE)
lgi.Pango.AttrList.change(self.pango_attrs, pango_size)
And when I do this, and use "Xephyr" for testing, The application works well until I start moving the "Size" knob. Then, as I move it, the application still works well for 1-3 seconds. Then, I get a blank screen, and I get this in my terminal:
kill: not enough arguments
kill: not enough arguments
Calling callback function on subscribed signal 'TimeChanged' failed: ./wonderful/bar.lua:148: Pango.Attribute: no `format'
Calling callback function on subscribed signal 'TimeChanged' failed: ./apps/RibbonPanel/LateForLunch/layout.lua:49: Pango.Attribute: no `format'
2022-11-20 09:06:50 E: awesome: signal_fatal:497: signal 11, dumping backtrace
awesome(backtrace_get+0x5f) [0x563e0a3984f2]
awesome(+0x1343e) [0x563e0a38243e]
/usr/lib/libc.so.6(+0x38a00) [0x7fbfdcaa3a00]
/usr/lib/libpango-1.0.so.0(pango_attribute_destroy+0xc) [0x7fbfdb10c36c]
/usr/lib/lua/5.1/lgi/corelgilua51.so(+0x12dc2) [0x7fbfdb386dc2]
/usr/lib/lua/5.1/lgi/corelgilua51.so(+0x132a8) [0x7fbfdb3872a8]
/usr/lib/libluajit-5.1.so.2(+0x9ef6) [0x7fbfdccb9ef6]
/usr/lib/libluajit-5.1.so.2(+0xfcd6) [0x7fbfdccbfcd6]
/usr/lib/libluajit-5.1.so.2(+0x16eac) [0x7fbfdccc6eac]
/usr/lib/libluajit-5.1.so.2(+0x174cd) [0x7fbfdccc74cd]
/usr/lib/libluajit-5.1.so.2(lua_pushstring+0x95) [0x7fbfdccd1955]
awesome(+0x1cc21) [0x563e0a38bc21]
/usr/lib/libluajit-5.1.so.2(+0x9ef6) [0x7fbfdccb9ef6]
/usr/lib/libluajit-5.1.so.2(lua_getfield+0xb1) [0x7fbfdccd7d51]
/usr/lib/lua/5.1/lgi/corelgilua51.so(lgi_marshal_access+0x2c) [0x7fbfdb38585c]
/usr/lib/libluajit-5.1.so.2(+0x9ef6) [0x7fbfdccb9ef6]
/usr/lib/libluajit-5.1.so.2(lua_pcall+0xb3) [0x7fbfdcccc873]
/usr/lib/lua/5.1/lgi/corelgilua51.so(+0x82ee) [0x7fbfdb37c2ee]
/usr/lib/libffi.so.8(+0x70d2) [0x7fbfdc7320d2]
/usr/lib/libffi.so.8(+0x7718) [0x7fbfdc732718]
/usr/lib/libglib-2.0.so.0(+0x560a2) [0x7fbfdd1bd0a2]
/usr/lib/libglib-2.0.so.0(g_main_context_dispatch+0x19b) [0x7fbfdd1bc87b]
/usr/lib/libglib-2.0.so.0(+0xac279) [0x7fbfdd213279]
/usr/lib/libglib-2.0.so.0(g_main_loop_run+0x6f) [0x7fbfdd1bbddf]
awesome(main+0x166c) [0x563e0a383eb0]
/usr/lib/libc.so.6(+0x23290) [0x7fbfdca8e290]
/usr/lib/libc.so.6(__libc_start_main+0x8a) [0x7fbfdca8e34a]
awesome(_start+0x25) [0x563e0a382045]
I decided to ask this because it seems to me like the error has something to do with pango_attribute_destroy. I know awesome is using "lgi", which also includes Pango, so my question is: Am I doing something wrong? Or is this a bug in another library like "lgi"?

How can I click all of the node of the category trees by playwright?

I want to use playwright to automatically click and expand all the child nodes. But my code only expands part of the nodes. How should I fix the code? Thank you.
Current:
What I want:
import json
import time
from playwright.sync_api import sync_playwright
p = sync_playwright().start()
browser = p.chromium.launch(headless=False, slow_mo=2000)
context = browser.new_context()
page = context.new_page()
try:
# page.add_init_script(js);
page.goto("https://keepa.com/#!categorytree", timeout=10000)
# Click text=Log in / register now to subscribe
page.click("text=Log in / register now to subscribe")
# Click input[name="username"]
page.click("input[name=\"username\"]")
# Fill input[name="username"]
page.fill("input[name=\"username\"]", "tylrr123#outlook.com")
# Click input[name="password"]
page.click("input[name=\"password\"]")
# Fill input[name="password"]
page.fill("input[name=\"password\"]", "adnCgL#f$krY9Q9")
# Click input:has-text("Log in")
page.click("input:has-text(\"Log in\")")
page.wait_for_timeout(2000)
page.goto("https://keepa.com/#!categorytree", timeout=10000)
while(True):
#loc.first.click()
loc = page.locator(".ag-icon.ag-icon-expanded")
print(loc.count())
loc.first.click(timeout=5000)
page.wait_for_timeout(2000)
except Exception as err:
print(err)
finally:
print("finished")`
My code only expands part of the nodes. How should I fix the code? Thank you.
Sometimes I try to do some scripts, but being honest, this was one of the most harder ones. It has been a real challenge.
I think it is finished.
# Import needed libs
import time
from playwright.sync_api import sync_playwright
import datetime
# We save the time when script starts
init = datetime.datetime.now()
print(f"{datetime.datetime.now()} - Script starts")
# We initiate the playwright page
p = sync_playwright().start()
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
# Navigate to Keepa and login
page.goto("https://keepa.com/#!categorytree")
page.click("text=Log in / register now to subscribe")
page.fill("#username", "tylrr123#outlook.com")
page.fill("#password", "adnCgL#f$krY9Q9")
page.click("#submitLogin", delay=200)
# We wait for the selector of the profile user, that means that we are already logged in
page.wait_for_selector("#panelUsername")
# Navigate to the categorytree url
page.goto("https://keepa.com/#!categorytree")
time.sleep(1)
#This function try to click on the arrow for expanding an subtree
def try_click():
# We save the number of elements that are closed trees
count = page.locator(f"//span[#class='ag-group-contracted']").count()
# We iterate the number of elements we had
for i in range(0, count):
# If the last element is visible, then we go inside the "if" statement. Why the last element instead of the first one? Because I don't know why the last element is usually the frist one...Keepa things, don't ask
if page.locator(f"(//span[#class='ag-group-contracted'])[{count-i}]").is_visible():
# Element was visible, so we try to click on it (Expand it). I wrapped the click inside a try/except block because sometimes playwright says that click failed, but actually does not fail and element is clicked. I don't know why
try:
# Clicking the element
page.click(f"(//span[#class='ag-group-contracted'])[{count-i}]", timeout=200)
print(f"Clicking Correct {count-i}. Wheel up")
# If element is clicked, we do scroll up, and we return true
page.mouse.wheel(0, -500)
return True
except:
# As I said, sometimes click fails but is actually clicked, so we return also true. The only way of returning False is if the elements are not visible
print(f"Error Clicking {count-i} but probably was clicked")
return True
# This function basically checks that there are closed trees
def there_is_still_closed_trees():
try:
page.wait_for_selector(selector=f"//span[#class='ag-group-contracted']", state='attached')
return True
except:
print("No more trees closed")
return False
# When we navigated to categorytree page a pop up appears, and you have to move the mouse to make it disappear, so I move the mouse and I keep it on the list, because later we will need to do scroll up and scroll down over the list
page.mouse.move(400, 1000)
page.mouse.move(400, 400)
# Var to count how many times we made scroll down
wheel_down_times = 0
# We will do this loop until there are not more closed trees
while there_is_still_closed_trees():
# If we could not make click (The closed trees were not visibles in the page) we will do scroll down to find them out
if not try_click():
# We do scroll down, and we sum one to the scroll down counter
print("Wheel down")
page.mouse.wheel(0, 400)
wheel_down_times = wheel_down_times + 1
print(f"Wheel down times = {wheel_down_times}")
# Sometimes if we do a lot of scrolls, page can crash, so we sleep the script 10 secs every 100 scrolls
if wheel_down_times % 100 == 0:
print("Sleeping 10 secs in order to avoid page crashes")
time.sleep(10)
# This "if" checks that the latest element of the whole tree is visible and we did more than 5 scroll down. That means that we are at the end of the list and we forget some closed trees, so we do scroll up till we arrive at the top of the list and we will make scroll down trying to find the pending closed trees
if page.locator(f"//span[text()='Walkthroughs & Tutorials']").is_visible() and wheel_down_times > 5:
page.mouse.wheel(0, -5000000)
else:
print(f"Wheel down times from {wheel_down_times} to 0")
wheel_down_times = 0
# Script finishes and show a summary of time
end = datetime.datetime.now()
print(f"{datetime.datetime.now()} - Script finished")
print(f"Script started at: {init}")
print(f"Script ended at: {end}")
print("There should not be any more closed trees")
# This sleeps the script if you want to see the screen. But actually you can remove and page will be closed
time.sleep(10000)
The scripts takes almost 3 hours. I don't know how keepa has a so many categories. Awesome...

kivy: have tcp event open popup

I'm new to kivy, so apologies if I've missed something really basic here but I am struggling to understand both the behavior I'm getting as well as my inability to get the outcome I'm after.
The goal
The kivy app should be listening for TCP events from an external server, with certain triggers causing various stuffs in the kivy app. In this case, I want the external server to be able to open a popup in the app.
Illustrative Code
I have adopted the following (working) demo code from another SO answer that I can no longer seem to find, so apologies for the plagiarism but it is not intentional.
server.py
import socket
serversocket = socket.socket()
host = 'localhost'
port = 54545
serversocket.bind(('', port))
serversocket.listen(1)
clientsocket,addr = serversocket.accept()
print("got a connection from %s" % str(addr))
while True:
msg = input("> ")
clientsocket.send(msg.encode('utf-8'))
client.py
import socket
class MySocket:
def __init__(self,host="localhost",port=54545):
self.sock = socket.socket()
self.sock.connect((host, port))
def get_data(self):
return self.sock.recv(1024)
main.py
import kivy
from kivy.app import App
from kivy.event import EventDispatcher
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from threading import Thread
from client import *
kivy.require('1.9.1')
class MyEventDispatcher(EventDispatcher):
def __init__(self, **kwargs):
self.register_event_type('on_test')
super(MyEventDispatcher, self).__init__(**kwargs)
def do_something(self, value):
self.dispatch('on_test', value)
def on_test(self, *args):
print('I am dispatched', args)
CustomPopup().open()
class CustomPopup(Popup):
pass
class MainScreen(Label):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.sock = MySocket()
Thread(target=self.get_data).start()
def get_data(self):
while True:
self.text = self.sock.get_data().decode('utf-8').strip()
if self.text == 'click':
MyEventDispatcher().do_something(self.text)
class MyApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
MyApp().run()
my.kv
<MainScreen>:
Button:
text: 'I do nothing'
<CustomPopup>:
title: 'Popup window'
size_hint: .5, .5
auto_dismiss: False
GridLayout:
cols: 1
Label:
size_hint: .9, .9
halign: 'center'
valign: 'middle'
text: 'message text goes here'
text_size: self.width, None
Button:
text: 'A: Close'
on_release: root.dismiss()
Button:
text: 'B: Close'
on_release: root.dismiss()
This all works in that I can send the trigger word click from the server and the popup will open, it has multiple placeholder buttons that work. Hooray.
The problem, however, is that I seem to require the dummy button on the main app screen, which I do not want. If I simply eliminate the <MainScreen> bit from the .kv file, sending the click keyword leads to:
I am dispatched ('click',)
Segmentation fault (core dumped)
but I can send any other string and it will display on screen as expected.
So, the basic questions are:
How do I get this to work as desired and
Why does placing a non-functional button on the screen make the popup work?
Thanks!

Kivy_Text_INPUT_Multiline_app (frames)

I am developing some app in kivy,I need to display mulitple line of string in a text input.How to do it?
If any examples please do post it.
An example is right here together with description. You didn't provide any code or basically anything I could use, so I'll do it this way:
<Box>:
TextInput:
id: mytextinput
multiline: True ## defaults to True, but so you could see how it works
text: 'something'
Button:
on_release: root.update_text('new value')
this will be your kv file/string for a TextInput no matter where you'll put it and id is identificator of how to access that widget, then in python
class Box(BoxLayout):
def update_text(self, value):
self.ids.mytextinput.text = value
It means that whatever widget you have and TextInput is its child in kv file/string, you will access it through ids dictionary and change its variable text to your desired value with calling custom update_text(<string>) in your class.

Resources