Change contents of a tab from a button in Kivy - python

I tried to access TabbedPanel.content.children[0] and remove the child and recrete a new child but it does not work. A note is that TabbedPanel.content is a TabbedPanelContent and not the ListView (in my case).
What I want to do is edit the contents of my listView. The app creates a button to generate data and two tabs, where the first tab create a listView of the data. If button pressed again it will remove previous ListView and insert new one.
How do I update contents of a tab? The code:
#test tabs
import kivy
kivy.require('1.0.6') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelHeader
from kivy.properties import ListProperty
from kivy.properties import DictProperty
from kivy.uix.listview import ListView, ListItemButton
from kivy.adapters.dictadapter import DictAdapter
from kivy.adapters.models import SelectableDataItem
from kivy.uix.selectableview import SelectableView
from kivy.uix.listview import ListView, ListItemButton
from kivy.factory import Factory
from kivy.lang import Builder
import random
#templates kv for SelectableView+BoxLayout called CustomListItem
Builder.load_string('''
[CustomListItem#SelectableView+BoxLayout]:
size_hint_y: ctx.size_hint_y
height: ctx.height
ListItemButton:
text: ctx.text
is_selected: ctx.is_selected
''')
class testTabs(BoxLayout):
data = ListProperty([1,2,3,4,5])
def __init__(self, *args, **kwargs):
super(testTabs, self).__init__(**kwargs)
self.listViewDict = {}
#layout = GridLayout(rows = 2)
self.layout = BoxLayout(orientation = "vertical")
#buttonLayout = GridLayout(cols = 4)
dataButton = Button(text = "press to load random data to tab 1")
dataButton.bind(on_release = self.randData)
self.layout.add_widget(dataButton)
#create list
list_item_args_converter = \
lambda row_index, rec: {'text': rec['text'],
'is_selected': rec['is_selected'],
'size_hint_y': None,
'height': 35}
entry_dict = \
{str(i): {'text': str(self.data[i]), 'is_selected': False} \
for i in xrange(len(self.data) ) }
self.listViewDict = entry_dict
sortedDateEntriesList = sorted(self.listViewDict)
dict_adapter = DictAdapter(sorted_keys = sortedDateEntriesList,
data = self.listViewDict,
args_converter=list_item_args_converter,
template = 'CustomListItem')
self.list_view = ListView(adapter=dict_adapter)
### Create tabs ###
self.tabbedPanel = TabbedPanel()
self.tabbedPanel.default_tab_text = "data tab"
self.tabbedPanel.tab_pos = "top_left"
self.tabbedPanel.default_tab_content = self.list_view
tabbedPanelHeader = TabbedPanelHeader(text = "tab 2")
tabbedPanelHeader.content = Label(text = "Hello world")
self.tabbedPanel.add_widget(tabbedPanelHeader)
self.layout.add_widget(self.tabbedPanel)
self.add_widget(self.layout)
#self.tabbedPanel.content.bind(children = self.foo)
def foo(self, *args):
print "############################in foo args:"
print args
tabbedPanelHeader = args[0]
print tabbedPanelHeader.children
def printContent(self, object):
print "object:" +str(object) +"'s content: " +str(object.content)
def printChildren(self, object):
for child in object.children:
print "object:" +str(object) +"'s child: " +str(child)
#create list view
def randData(self, *args):
print args
self.tabbedPanel.content.children[0].remove_widget(self.list_view)
print "content tabbedPanel children:"
print self.tabbedPanel.content.children
tempData = []
numValues = random.randint(10,20)-1
for i in xrange(numValues):
tempData.append(random.randint(1,30))
self.data = tempData
list_item_args_converter = \
lambda row_index, rec: {'text': rec['text'],
'is_selected': rec['is_selected'],
'size_hint_y': None,
'height': 35}
entry_dict = \
{str(i): {'text': str(self.data[i]), 'is_selected': False} \
for i in xrange(len(self.data) ) }
self.listViewDict = entry_dict
sortedDateEntriesList = sorted(self.listViewDict)
dict_adapter = DictAdapter(sorted_keys = sortedDateEntriesList,
data = self.listViewDict,
args_converter=list_item_args_converter,
template = 'CustomListItem')
self.list_view = ListView(adapter=dict_adapter)
self.tabbedPanel.content.children[0].add_widget(self.list_view)
def on_data(self, *args):
print "on_data func. Data created:"
print args
class MyApp(App):
def build(self):
return testTabs()
if __name__ == '__main__':
MyApp().run()

You seem to want to change the data given to the listview, you shouldn't have to change te entire ListView for that. Look here https://github.com/kivy/kivy/issues/1321 this mentions a workaround till the data_changed branch for listview is merged in master. Eessentially just add this to your kv::
<ListView>:
on_adapter: self.adapter.bind_triggers_to_view(self._trigger_reset_populate)
simply updating the data after that should show your changes in the listview.
For your issue with TabbedPanel all you need to do is ::
self.tabbedPanel.clear_widgets()
self.tabbedPanel.add_widget(self.list_view)
When you add widgets to the tabbed panel, it actually adds it to the content area. Similarly clear_widgets() clears the content area.
You could also do::
self.tabbedPanel.default_tab.content = self.list_view
self.tabbedPanel.switch_to(self.tabbedPanel.default_tab)
This is better as the linked content is updated and thus every time you change the tab appropriate changed content will be displayed.
The relevant Documentation in the docs::
Tabs and content can be removed in several ways:
tp.remove_widget(Widget/TabbedPanelHeader)
or
tp.clear_widgets() # to clear all the widgets in the content area
or
tp.clear_tabs() # to remove the TabbedPanelHeaders
Warning
To access the children of the tabbed panel, use content.children:
tp.content.children
To access the list of tabs:
tp.tab_list

Rather than mucking around with tabbedpanel.content, you can just keep a reference to your listview (or anything else you put in there) and modify it later.
For instance, in this example you store the listview as self.list_view. That means you can have a method that creates a new adapter then simply replace the old one to update the list. You can also do stuff like modify the data of the original adapter via the same reference.
Some (untested) example code would be something like:
def my_update_method(self, *args):
new_dict_adapter = DictAdapter(data = whatever
# Obviously you have to make thi
args_converter = list_item_args_converter
# You have to make this too, not included in
# the example
)
self.list_view.adapter = new_dict_adapter

Related

Pytube/Pytube3 not working with kivy after installed on Android device [Compiled with Buildozer]

I am trying to make an app that downloads YouTube videos for my first project with kivy. The program works just fine on windows 10, however, it doesn't operate on Android. I believe my problem has come down to whether or not I am using the correct pytube library (whether that be "pytube" or "pytube3"). Also, would anyone know how to correctly set up the buildozer.spec file for the specified imports?
P.S. How would I include the DownloadedVideos folder into the buildozer.spec?
This is a test model BTW, so code improvements to come ;)
from pytube import YouTube
import pytube
import os
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.checkbox import CheckBox
import certifi
import os
# Here's all the magic !
os.environ['SSL_CERT_FILE'] = certifi.where()
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.add_widget(Label(text="Paste your YouTube link into the first box on the right. \nOnce you have done so,"
" hit the submit button. \nThe submit button will allow the app to search for the"
" video and confirm its existence. \nThen, a download button with options"
" will appear. \nSelect the options you wish to have downloaded (keeping in mind"
" that audio files need 'kbps' and '.mp3'), and they will be downloaded.",
halign="left", valign="top", font_size=12))
self.inside = GridLayout()
self.inside.cols = 2
self.inside.add_widget(Label(text="Paste YouTube Link:"))
self.link = TextInput(multiline=False)
self.inside.add_widget(self.link)
self.button1 = Button(text="Submit", on_press=self.callback)
self.button1.bind(on_press=self.callback)
self.inside.add_widget(self.button1)
self.outText = TextInput(multiline=True, readonly=True)
self.inside.add_widget(self.outText)
self.inside.add_widget(Label(text="Media:"))
self.ytTypes = TextInput(multiline=True, readonly=True)
self.inside.add_widget(self.ytTypes)
self.add_widget(self.inside)
def downloadMedia(self, instance):
global stream
print(f"Downloading... {stream}")
self.ytTypes.text = f"Downloading... {stream}"
out_file = stream.download("DownloadedVideos")
print(f"Downloaded {stream}")
global yt
self.ytTypes.text = f"Downloaded \n{yt.title}!"
try:
print("here")
self.remove_widget(self.download)
except:
print("Error")
def callback(self, instance):
youtubeLink = self.link.text
print("pressed", youtubeLink)
try:
global yt
yt = YouTube(youtubeLink)
print("Views: ", yt.title)
print("Length: ", yt.length)
print("Views", yt.views)
#Rounds the length of the YouTube video in minutes down to 2 decimal places
res = "{:.2f}".format(yt.length/60)
self.outText.text = f"Title: {yt.title}, \nLength: {res} minutes, \nViews: {yt.views}"
#Grabs video itags and displays them
global stream
stream = yt.streams.filter(progressive=True).last()
print(stream)
print("pass")
print(stream.itag)
self.ytTypes.text = f"Streams: {stream}"
self.download = Button(text="Download:")
self.download.bind(on_press=self.downloadMedia)
self.add_widget(self.download)
except:
print("error")
self.outText.text = "ERR"
self.link.text = ""
class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()

Run Flask app in Kivy app when a button is pressed

Is there a way that I could run Kivy and Flask at the same time in a Kivy app? Furthermore, I need the app so once you click a button in the Kivy app, that triggers a function where a Flask web page is started. Then, using the Python builtin webbrowser module, I need it to automatically open the web page in the default browser.
When this code is ran, I get no error. Just the Kivy app freezes and does not respond anymore.
My code so far:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from flask import Flask
from werkzeug.serving import run_simple
import webbrowser
Builder.load_file('design.kv')
answers = []
class CalcScreen(Screen):
def list_view(self):
self.manager.current = "list_screen"
def cround_view(self):
self.manager.current = "round_calc_screen"
def calculate(self):
LengthVal = float(self.ids.length.text)
WidthVal = float(self.ids.width.text)
ThicknessVal = float(self.ids.thickness.text)
FinalCalc = LengthVal * WidthVal * ThicknessVal / 144
FinalCalc = round(FinalCalc,1)
answers.append(FinalCalc)
self.ids.board_feet.text = str(FinalCalc)
class ListScreen(Screen):
def calc_view(self):
self.manager.current = "calc_screen"
def UpdateInfo(self):
tot = 0
for num in answers:
tot += num
self.ids.total_board_feet.text = str(round(tot,1))
self.ids.total_boards.text = str(len(answers))
self.ids.list.text = str(', '.join(map(str, answers)))
def ClearListAnswers(self):
answers.clear()
def printerview(self):
app = Flask(__name__)
#app.route('/')
def home():
return f"<h1>BFCalc Printer Friendly View</h1>\n{self.ids.list.text}"
run_simple('localhost',5000,app)
webbrowser.open_new('localhost:5000')
class RoundCalcScreen(Screen):
def calc_view(self):
self.manager.current = "calc_screen"
def rc_calculate(self):
RC_DiameterVal = float(self.ids.rc_diameter.text)
RC_RadiusVal = RC_DiameterVal / 2
RC_ThicknessVal = float(self.ids.rc_thickness.text)
RC_FinalCalc = (3.14 * (RC_RadiusVal * RC_RadiusVal) * RC_ThicknessVal) / 144
RC_FinalCalc = round(RC_FinalCalc,1)
answers.append(RC_FinalCalc)
self.ids.rc_board_feet.text = str(RC_FinalCalc)
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
self.icon = 'icon.ico'
return RootWidget()
if __name__ == "__main__":
MainApp().run()
It is a bad practice to use backend frameworks in that way (they are not used that way at all). It depends on your needs, you can try to use pure HTML instead.
def printerview(self):
import webbrowser
file_name = "my_html.html"
html = f"""<h1>BFCalc Printer Friendly View</h1>\n{self.ids.list.text}"""
with open(file_name, "w+") as f:
f.write(html)
# open html in a browser
webbrowser.open(file_name)

Python onclick button widget return object

I am trying to build a file/data selector in jupyter notebooks with python. The idea is that I select some files and data channels in the files with the multipleSelect widget and then with a button return a dataFrame.
How can I access the df_object?
#stack example
from ipywidgets import widgets
from IPython.display import display
from IPython.display import clear_output
import pandas as pd
import numpy as np
filenames = ["file1", "file2"]
file_dict = {
"file1":pd.DataFrame(np.arange(5)),
"file2":pd.DataFrame(np.arange(10,15))
}
def data_selection():
sel_file = widgets.SelectMultiple(description="Files",
options=filenames)
display(sel_file)
button = widgets.Button(description="OK")
display(button)
def on_button_clicked(button):
clear_output(wait=True) #clears the previous output
display(sel_file) #displays new selection window
display(button) #displays new button
for f in sel_file.value:
print (f)
display (file_dict[f])
#global df_object #would be a solution but not recommended for sure
df_object = file_dict[f]
return df_object #doesn't work
button.on_click(on_button_clicked)
data_selection()
You really should be using a class for this, and then define all your functions as acting on an instance of that class. Not all of them need to be publicly accessible as well. You can also store the df_objects in a separate attribute like a dictionary and access the dictionary using a separate function. Check out the code below:
class foo(object):
def __init__(self, file1, file2):
self.filenames = [file1, file2]
self.file_dict = {
file1:pd.DataFrame(np.arange(5)),
file2:pd.DataFrame(np.arange(10,15))
}
def _create_widgets(self):
self.sel_file = widgets.SelectMultiple(description='Files',
options=self.filenames,
value=[self.filenames[0]],
)
self.button = widgets.Button(description="OK")
self.button.on_click(self._on_button_clicked)
def _on_button_clicked(self, change):
self.out.clear_output()
self.df_objects = {}
with self.out:
for f in self.sel_file.value:
print(f)
display(self.file_dict[f])
self.df_objects[f] = self.file_dict[f]
def display_widgets(self):
self._create_widgets()
self.out = widgets.Output() # this is the output widget in which the df is displayed
display(widgets.VBox(
[
self.sel_file,
self.button,
self.out
]
)
)
def get_df_objects(self):
return self.df_objects
Then you can create instances and display the widgets like so:
something = foo('a', 'b')
something.display_widgets()
something.get_df_objects() will return a dictionary with the required 'file:dataframe_of_file' key-value pairs.
Hope this helps :)

kivy - button executing function

I'm using Kivy example code to get file path from two different files.
My goal is to use the file path to open and manipulate data from the file.
My problem is to pass the file path into the open file command in the test function below.
Here is my Code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
import re
import pandas as pd
class DropFile(Button):
def __init__(self, **kwargs):
super(DropFile, self).__init__(**kwargs)
# get app instance to add function from widget
app = App.get_running_app()
# add function to the list
app.drops.append(self.on_dropfile)
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path
def test(self):
minimum_wage = open(**FILE PATH HERE**)
LinesToString = ''
for line in minimum_wage:
LinesToString += line
patFinder = re.compile('\d{5}\s+\d{5,9}')
findPat = re.findall(patFinder, LinesToString)
empno_list = []
pattern = '(\d{5})\s+(\d{5})'
for string in findPat:
match = re.search(pattern, string)
empno = match.group(2)
empno_list.append(empno)
MinimumWage = pd.DataFrame({'EMPNO': empno_list})
MinimumWage.set_index('EMPNO')
print MinimumWage.head()
print MinimumWage.shape
class DropApp(App):
def build(self):
# set an empty list that will be later populated
# with functions from widgets themselves
self.drops = []
# bind handling function to 'on_dropfile'
Window.bind(on_dropfile=self.handledrops)
box = BoxLayout(orientation='vertical')
top_label = Label(text='Data manipulation', font_size=45)
box.add_widget(top_label)
run_button = Button(text='Run', size_hint=(1, 0.5))
run_button.bind(on_press=DropFile.test)
box.add_widget(run_button)
two_buttons = BoxLayout(orientation='horizontal')
dropleft = DropFile(text='Drag & Drop File here')
# dropright = DropFile(text='right')
two_buttons.add_widget(dropleft)
# two_buttons.add_widget(dropright)
box.add_widget(two_buttons)
return box
def handledrops(self, *args):
# this will execute each function from list with arguments from
# Window.on_dropfile
#
# make sure `Window.on_dropfile` works on your system first,
# otherwise the example won't work at all
for func in self.drops:
func(*args)
DropApp().run()
Thanks
You can call test() method at the last line of on_dropfile() e.g.:
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path
self.test(path)
def test(self, path):
minimum_wage = open(path)
LinesToString = ''
...
or launch already from the existing thing e.g. if you run test() separately from the on_dropfile() function and you won't change self.text property after changing the text:
def on_dropfile(self, widget, path):
# a function catching a dropped file
# if it's dropped in the widget's area
if self.collide_point(*Window.mouse_pos):
self.text = path # path is assigned to self.text <--
def test(self):
minimum_wage = open(self.text) # <-- and you can use it
LinesToString = ''
...
Or at the end of on_dropfile put it into a separate variable and use that in open().

PyobjC : NSTextField on NSPopover

I have the same problem mentioned here : Not being able to edit NSTextField on NSPopover even though Editable behavior is set. The solution seems to be to override the canBecomeKeyWindow of NSWindow. I am trying to do the same thing in PyObjC, but I am getting an error Python signature doesn't match implied objective-C signature.
In the following code, if I comment out canBecomeKeyWindow_(), then the app runs as expected, but I am not able to click and edit the textfields.
# from Cocoa import *
from AppKit import NSWindowController, NSApplication, NSApp, NSMaxYEdge, NSImage, NSStatusBar, NSMenu, NSMenuItem, NSVariableStatusItemLength, NSRect
from Cocoa import objc
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject
from PyObjCTools import AppHelper
import webbrowser
import subprocess
import os
global popover
class TestApp(NSApplication):
def finishLaunching(self):
# Make statusbar item
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
self.icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
self.icon.setScalesWhenResized_(True)
self.icon.setSize_((20, 20))
self.statusitem.setImage_(self.icon)
self.statusitem.setHighlightMode_(1)
# make the menu
self.menubarMenu = NSMenu.alloc().init()
self.menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Login', 'loginCallback:', '')
self.menubarMenu.addItem_(self.menuItem)
self.quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
self.menubarMenu.addItem_(self.quit)
# add menu to statusitem
self.statusitem.setMenu_(self.menubarMenu)
def loginCallback_(self, notification):
# Initiate the contrller with a XIB
viewController = SimpleXibDemoController.alloc().initWithWindowNibName_("Login")
# Show the window
viewController.showWindow_(viewController)
rect = self.statusitem.valueForKey_('button').frame()
viewController.popover.showRelativeToRect_ofView_preferredEdge_(rect, self.statusitem.valueForKey_('button'), NSMaxYEdge)
class SimpleXibDemoController(NSWindowController):
popover = objc.IBOutlet()
counterTextField = objc.IBOutlet()
username_field = objc.IBOutlet()
password_field = objc.IBOutlet()
submit_button = objc.IBOutlet()
def canBecomeKeyWindow_(self):
return 1
def windowDidLoad(self):
NSWindowController.windowDidLoad(self)
#objc.IBAction
def submit_(self, sender):
username = self.username_field.stringValue()
password = self.password_field.stringValue()
self.updateDisplay(username + ' ' + password)
def updateDisplay(self, value):
self.counterTextField.setStringValue_(value)
if __name__ == "__main__":
app = TestApp.sharedApplication()
icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
app.setApplicationIconImage_(icon)
AppHelper.runEventLoop()
It looks like you're adding an underscore where you shouldn't. The PyObjC bridge will translate it into a colon. Besides that, the corresponding Python boolean value should be True. Thus, the correct function would look like this:
def canBecomeKeyWindow(self):
return True

Categories

Resources