first commit

This commit is contained in:
yumoqing 2019-12-19 11:13:47 +08:00
commit f8c06c9e8e
76 changed files with 5212 additions and 0 deletions

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# KivyBlocks
Can you ever image build a gui application like play Lego blocks? kivyblocks it try to provide programmer a tool to build a application
## Requirement
see the [requirements.txt]('./requirements.txt')
## Principle
There is a blocksapp(inherited from App) contains a toppage widget to hold all the top widget create by Blocks, and the Blocks can create widgets which is specified in a dictionary data, and a HttpClient to get data or weiget spec file for web server.
the widget specifiction file
## How to use
see the simple example below:
```
```

0
kivyblocks/__init__.py Normal file
View File

215
kivyblocks/aplayer.py Normal file
View File

@ -0,0 +1,215 @@
import sys
import os
from kivy.app import App
from kivy.core.audio import SoundLoader
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.progressbar import ProgressBar
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.clock import Clock
from kivy.uix.filechooser import FileChooserListView
from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, \
NumericProperty, DictProperty, OptionProperty
class APlayer(BoxLayout):
def __init__(self,afile=None,loop=False,
can_openfile=False,
can_changevolume=False,
can_cut = False,
can_replay = False,
can_move=False):
super().__init__(orientation='vertical')
self.loop = loop
self.old_path = os.getcwd()
self.can_openfile = can_openfile
self.can_cut = can_cut
self.can_replay = can_replay
self.can_move = can_move
self.can_changevolume = can_changevolume
self._popup = None
self.pb = None
self.ap = None
if afile is None:
self.playlist = []
else:
if type(afile) == type([]):
self.playlist = afile
else:
self.playlist = [afile]
self.curplay = -1
self.addMenu()
if len(self.playlist) > 0:
self.curplay = 0
self.createSound(self.playlist[self.curplay])
def totime(self,dur):
h = dur / 3600
m = dur % 3600 / 60
s = dur % 60
return '%02d:%02d:%02d' % (h,m,s)
def createSound(self,source):
self.ap = SoundLoader.load(source)
self.ap.bind(on_play=self.begin_play)
self.ap.bind(on_stop=self.end_play)
self.ap.play()
def begin_play(self,obj):
if self.pb:
return
self.pb = BoxLayout(orientation='horizontal',
size_hint = (0.99,None),height=40)
self.curposition = Label(text=self.totime(self.ap.get_pos()),width=60,
size_hint_x=None)
self.curposition.align='right'
self.maxposition = Label(text=self.totime(self.ap.length),
width=60,size_hint_x=None)
self.maxposition.align = 'left'
max = int(self.ap.length*1000)
self.progressbar = ProgressBar(value=0,max=max)
self.add_widget(self.pb)
self.pb.add_widget(self.curposition)
self.pb.add_widget(self.progressbar)
self.pb.add_widget(self.maxposition)
self.pb.pos = (0,0)
Clock.schedule_interval(self.update_progressbar,1)
def update_progressbar(self,t):
pos = self.ap.get_pos()
max = self.ap.length
self.curposition.text = self.totime(pos)
self.progressbar.value = int(pos*1000)
self.progressbar.max = int(max*1000)
self.maxposition.text = self.totime(max)
def end_play(self,obj):
self.curplay += 1
if not self.loop and self.curplay >= len(self.playlist):
self.parent.remove_widget(self)
return
del self.ap
self.curplay = self.curplay % len(self.playlist)
self.createSound(self.playlist[self.curplay])
def addMenu(self):
self.menubar = BoxLayout(orientation='horizontal',
size_hint_y=None,height=40)
if self.can_openfile:
btn_open = Button(text='open')
btn_open.bind(on_press=self.openfile)
self.menubar.add_widget(btn_open)
if self.can_move:
btn_back = Button(text='<<')
btn_forward = Button(text='>>')
btn_forward.bind(on_press=self.moveforward)
btn_back.bind(on_press=self.moveback)
self.menubar.add_widget(btn_back)
self.menubar.add_widget(btn_forward)
if self.can_changevolume:
btn_volumeinc = Button(text='+')
btn_volumedec = Button(text='-')
btn_volumeinc.bind(on_press=self.volumeinc)
btn_volumedec.bind(on_press=self.volumedec)
self.menubar.add_widget(btn_volumedec)
self.menubar.add_widget(btn_volumeinc)
if self.can_cut:
btn_cut = Button(text='>>|')
btn_cut.bind(on_press=self.endplay)
self.menubar.add_widget(btn_cut)
if self.can_replay:
btn_replay = Button(text='replay')
btn_replay.bind(on_press=self.replay)
self.menubar.add_widget(btn_replay)
self.menubar.pos = 0,45
self.add_widget(self.menubar)
def openfile(self,t):
def afilter(path,filename):
aexts = [
'.wav',
'.mp3',
'.ape',
'.flac'
]
for ext in aexts:
if filename.endswith(ext):
return True
return False
if self._popup is None:
c = BoxLayout(orientation='vertical')
self.file_chooser = FileChooserListView()
self.file_chooser.filters = [afilter]
self.file_chooser.multiselect = True
self.file_chooser.path = self.old_path
#self.file_chooser.bind(on_submit=self.loadFilepath)
c.add_widget(self.file_chooser)
b = BoxLayout(size_hint_y=None,height=35)
c.add_widget(b)
#cancel = Button(text='Cancel')
#cancel.bind(on_press=self.cancelopen)
load = Button(text='load')
load.bind(on_press=self.playfile)
b.add_widget(load)
#b.add_widget(cancel)
self._popup = Popup(title='Open file',content=c,size_hint=(0.9,0.9))
self._popup.open()
def endplay(self,btn):
self.ap.seek(self.ap.length - 0.01 )
def replay(self,btn):
self.ap.seek(0)
def volumeinc(self,btn):
self.ap.volume += 0.05
if self.ap.volume > 1.0:
self.ap.volume = 1.0
def volumedec(self,btn):
self.ap.volume -= 0.05
if self.ap.volume < 0.0:
self.ap.volume = 0.0
def moveback(self,btn):
f = self.ap.get_pos()
self.ap.seek(f - 2)
def moveforward(self,btn):
f = self.ap.get_pos()
self.ap.seek(f + 2)
def playfile(self,object):
self.playlist = []
for f in self.file_chooser.selection:
fp = os.path.join(self.file_chooser.path,f)
self.playlist.append(fp)
if len(self.playlist) == 0:
return
self._popup.dismiss()
if self.ap is not None:
self.ap.stop()
return
self.curplay = 0
self.createSound(self.playlist[self.curplay])
if __name__ == '__main__':
class MyApp(App):
def build(self):
af = None
if len(sys.argv) > 1:
af = sys.argv[1:]
return APlayer(afile=af,
loop=True,
can_openfile=True,
can_move = True,
can_cut=True,
can_replay=True,
can_changevolume = True
)
MyApp().run()

86
kivyblocks/baseWidget.py Executable file
View File

@ -0,0 +1,86 @@
import sys
from kivy.utils import platform
from kivy.uix.button import Button, ButtonBehavior
from kivy.uix.accordion import Accordion,AccordionItem
from kivy.uix.label import Label
from kivy.uix.actionbar import ActionBar,ActionView,ActionPrevious,ActionItem,ActionButton
from kivy.uix.actionbar import ActionToggleButton, ActionCheck,ActionSeparator,ActionGroup
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.scatterlayout import ScatterLayout
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.switch import Switch
from kivy.uix.textinput import TextInput
from kivy.uix.dropdown import DropDown
from kivy.uix.tabbedpanel import TabbedPanel,TabbedPanelContent,TabbedPanelHeader,TabbedPanelItem
from kivy.uix.treeview import TreeView
from kivy.uix.image import AsyncImage,Image
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.scrollview import ScrollView
from kivy.uix.splitter import Splitter
from kivy.uix.spinner import Spinner
from kivy.uix.slider import Slider
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.sandbox import Sandbox
from kivy.uix.progressbar import ProgressBar
from kivy.uix.popup import Popup
from kivy.uix.modalview import ModalView
from kivy.uix.filechooser import FileChooser
from kivy.uix.effectwidget import EffectWidget
from kivy.uix.colorpicker import ColorPicker
from kivy.uix.carousel import Carousel
from kivy.uix.camera import Camera
from kivy.uix.bubble import Bubble
from kivy.uix.codeinput import CodeInput
from kivy.graphics import Color, Rectangle
from kivy.properties import ListProperty
from kivycalendar import DatePicker
from appPublic.dictObject import DictObject
from .widgetExt.scrollwidget import ScrollWidget
from .widgetExt.binstateimage import BinStateImage
from .widgetExt.jsoncodeinput import JsonCodeInput
from .widgetExt.inputext import FloatInput,IntegerInput, \
StrInput,SelectInput, BoolInput, AmountInput
from .widgetExt.messager import Messager
if platform == 'android':
from .widgetExt.phonebutton import PhoneButton
from .widgetExt.androidwebview import AWebView
class PressableImage(ButtonBehavior,AsyncImage):
def on_press(self):
print
class Text(Label):
bgColor = ListProperty([0.5,0.5,0.5,1])
def __init__(self,**kw):
self.options = DictObject(**kw)
kwargs = kw.copy()
self.bind(pos=self._update,size=self._update)
if kwargs.get('bgColor'):
self.bgColor = kwargs['bgColor']
del kwargs['bgColor']
super().__init__(**kwargs)
self.bind(pos=self.sizeChange,size=self.sizeChange)
def _update(self,t,v):
self.pos = t.pos
self.size = t.size
def sizeChange(self,o,t=None):
self.canvas.before.clear()
with self.canvas.before:
Color(*self.bgColor)
Rectangle(pos=self.pos,size=self.size)

505
kivyblocks/blocks.py Executable file
View File

@ -0,0 +1,505 @@
import os
import sys
import codecs
import json
from traceback import print_exc
from functools import partial
from appPublic.dictExt import dictExtend
from appPublic.jsonConfig import getConfig
from appPublic.folderUtils import ProgramPath
from appPublic.dictObject import DictObject
from appPublic.Singleton import SingletonDecorator
from kivy.config import Config
from kivy.metrics import sp,dp,mm
from kivy.core.window import WindowBase
from kivy.properties import BooleanProperty
from kivy.uix.widget import Widget
from kivy.app import App
from .baseWidget import *
from .widgetExt import Messager
from .externalwidgetmanager import ExternalWidgetManager
from .threadcall import HttpClient
from .toolbar import *
from .dg import DataGrid
from .utils import *
from .ready import WidgetReady
from .serverImageViewer import ServerImageViewer
from .vplayer import VPlayer
from .form import InputBox, Form, StrSearchForm
from .boxViewer import BoxViewer
def showError(e):
print('error',e)
class WidgetNotFoundById(Exception):
def __init__(self, id):
super().__init__()
self.idstr = id
def __str__(self):
return "Widget not found by id:" + self.idstr + ':'
def __expr__(self):
return str(self)
class ClassMethodNotFound(Exception):
def __init__(self,k,m):
super().__init__()
self.kname = k
self.mname = m
def __str__(self):
s = 'Method(%s) not found in class(%s)' % (self.mname,
str(self.kname.__classname__))
return s
def __expr__(self):
return self.__str__()
class NotExistsObject(Exception):
def __init__(self,name):
super().__init__()
self.name = name
def __str__(self):
s = 'not exists widget(%s)' % self.name
return s
def __expr__(self):
return self.__str__()
class ArgumentError(Exception):
def __init__(self,argument,desc):
super().__init__()
self.argument = argument
self.desc = desc
def __str__(self):
s = 'argument(%s) missed:%s' % (self.argument,self.desc)
return s
def __expr__(self):
return self.__str__()
class NotRegistedWidget(Exception):
def __init__(self,name):
super().__init__()
self.widget_name = name
def __str__(self):
s = 'not reigsted widget(%s)' % self.name
return s
def __expr__(self):
return self.__str__()
@SingletonDecorator
class RegistedFunction:
def __init__(self):
self.rf_list = {}
def register(self,name,func):
self.rf_list[name] = func
def get(self,name):
return self.rf_list.get(name)
def registerWidget(name,widget):
globals()[name] = widget
class Blocks:
registedWidgets = {}
def __init__(self):
self.action_id = 0
def register_widget(self,name,widget):
globals()[name] = widget
def buildAction(self,widget,desc):
self.action_id += 1
fname = 'action%d' % self.action_id
l = {
}
body="""def %s(widget,obj=None, v=None):
jsonstr='''%s'''
print(type(widget), type(obj),v,'action():desc=',jsonstr)
desc = json.loads(jsonstr)
app = App.get_running_app()
app.blocks.uniaction(widget, desc)
print('finished')
""" % (fname, json.dumps(desc))
exec(body,globals(),l)
f = l.get(fname,None)
if f is None:
raise Exception('None Function')
func =partial(f,widget)
return func
setattr(widget,fname,f)
return getattr(widget,fname)
def eval(self,s,l):
g = {}
forbidens = [
"os",
"sys",
"codecs",
"json",
]
for k,v in globals().copy().items():
if k not in forbidens:
g[k] = v
g['__builtins__'] = globals()['__builtins__'].copy()
g['__builtins__']['__import__'] = None
g['__builtins__']['__loader__'] = None
g['__builtins__']['open'] = None
return eval(s,g,l)
def getUrlData(self,kw,parenturl=None):
url = kw.get('url')
method = kw.get('method','GET')
params = kw.get('params',{})
if url is None:
print('kw=',kw)
raise ArgumentError('url','getUrlData() miss a url argument')
url = absurl(url,parenturl)
if url.startswith('file://'):
filename = url[7:]
print(filename)
with codecs.open(filename,'r','utf-8') as f:
b = f.read()
dic = json.loads(b)
else:
dic = App.get_running_app().hc(url,method=method,params=params)
return dic, url
def register(self,name : str ,base:str=None,
defaultOpts:dict={},optkeys:list=[],
properties:list=[]):
"""
"""
self.registedWidgets[name] = {
"defaultOpts":defaultOpts,
"base":base,
"properties":properties, # property: 0 name, 1 type
"optkeys":optkeys,
"infos":{}
}
def buildRegistedWidgetInfo(self, widgettype:str):
"""
setup registed widget defaultOpts and optkeys
by merge its and it's bases widgets
"""
rw = self.registedWidgets.get(widgettype)
if not rw:
raise NotRegistedWidget(widgettype)
rw['infos']['defaultOpts'] = rw['defaultOpts']
rw['infos']['optkeys'] = rw['optkeys']
for b in base:
brw = self.registedWidgets.get(b)
if not brw:
raise NotRegistedWidget(b)
opts = self.getWidgetOptions(b)
opts.update(rw['defaultOpts'])
rw['infos']['defaultOpts'].update({'defaultOpts':opts})
rw['infos']['optkeys'] = list(set(self.getWidgetOptkeys(b) + rw['infos']['optkeys']))
return rw['infos']
def getWidgetOptkeys(self,widgettype:str):
"""
"""
if widgettype not in self.registedWidgets.keys():
raise NotRegistedWidget(widgettype)
infos = self.registedWidgets.get(widgettype)['infos']
if infos == {}:
infos = self.buildRegistedWidgetInfo(widgettype)
return infos['optkeys']
def getWidgetOptions(self,widgettype:str, opts:dict={}):
"""
get widget type valid options, from it's registed info
and it given argument:opts
"""
if widgettype not in self.registedWidgets.keys():
raise NotRegistedWidget(widgettype)
infos = self.registedWidgets.get(widgettype)['infos']
if infos == {}:
infos = self.buildRegistedWidgetInfo(widgettype)
opt = infos['defaultOpts']
keys = opts.keys()
for k in infos['optkeys']:
if k in keys:
opt.update({k:opts[k]})
return opt
def getObject(self,klass:str ,bases:list=[],
properties:list=[]):
pstrs = [ "%s = %s\n" % (n,t) for n, t in properties ]
if klass not in globals().keys():
script="""
class %s(%s):
%s
def __init__(self):
super().__init__()
""" % (klass, '\t'.join(pstrs),','.join(bases))
exec(script,globals(),globals())
return globals().get(klass)()
def isBaseWidget(self,widgettype):
rw = self.registedWidgets.get(widgettype)
if not rw:
raise NotRegistedWidget(widgettype)
return rw['bases'] == []
def getBases(self, widgettype:str):
rw = self.registedWidgets.get(widgettype)
if not rw:
raise NotRegistedWidget(widgettype)
return rw['bases']
def strValueExpr(self,s:str,localnamespace:dict={}):
if not s.startswith('py::'):
return s
try:
v = self.eval(s[4:],localnamespace)
return v
except Exception as e:
if s.startswith('CSize'):
print('Exception .... ',e,s)
print_exc()
return s
def arrayValueExpr(self,arr:list,localnamespace:dict={}):
d = []
for v in arr:
if type(v) == type(''):
d.append(self.strValueExpr(v,localnamespace))
continue
if type(v) == type([]):
d.append(self.arrayValueExpr(v,localnamespace))
continue
if type(v) == type({}):
d.append(self.dictValueExpr(v,localnamespace))
continue
if type(v) == type(DictObject):
d.append(self.dictValueExpr(v,localnamespace))
continue
d.append(v)
return d
def dictValueExpr(self,dic:dict,localnamespace:dict={}):
d = {}
for k,v in dic.items():
if type(v) == type(''):
d[k] = self.strValueExpr(v,localnamespace)
continue
if type(v) == type([]):
d[k] = self.arrayValueExpr(v,localnamespace)
continue
if type(v) == type({}):
d[k] = self.dictValueExpr(v,localnamespace)
continue
if type(v) == type(DictObject):
d[k] = self.dictValueExpr(v,localnamespace)
continue
d[k] = v
return d
def valueExpr(self,obj,localnamespace={}):
if type(obj) == type(''):
return self.strValueExpr(obj,localnamespace)
if type(obj) == type([]):
return self.arrayValueExpr(obj,localnamespace)
if type(obj) in [ type({}), type(DictObject) ]:
return self.dictValueExpr(obj,localnamespace)
return obj
def __build(self,desc:dict,ancestor=None):
def checkReady(w,o):
print('checkReady():w=',w,'o=',o)
w.children_ready[o] = True
if all(w.children_ready.values()):
w.ready = True
widgetClass = desc.get('widgettype',None)
if not widgetClass:
print("__build(), desc invalid", desc)
raise Exception(desc)
widgetClass = desc['widgettype']
opts = desc.get('options',{})
klass = globals().get(widgetClass)
if not klass:
raise NotExistsObject(widgetClass)
widget = klass(**opts)
if desc.get('parenturl'):
widget.parenturl = desc.get('parenturl')
ancestor = widget
if desc.get('id'):
myid = desc.get('id')
holder = ancestor
if ancestor == widget:
app = App.get_running_app()
holder = app.root
if not hasattr(holder,'widget_ids'):
setattr(holder,'widget_ids',{})
holder.widget_ids[myid] = widget
widget.build_desc = desc
self.build_rest(widget,desc,ancestor)
# f = partial(self.build_rest,widget,desc,ancestor)
# event = Clock.schedule_once(f,0)
return widget
def build_rest(self, widget,desc,ancestor,t=None):
for sw in desc.get('subwidgets',[]):
w = self.widgetBuild(sw, ancestor=ancestor)
if w is None:
continue
w.parenturl = widget.parenturl
widget.add_widget(w)
if hasattr(widget,'addChild'):
widget.addChild(w)
for b in desc.get('binds',[]):
self.buildBind(widget,b)
if hasattr(widget, 'built'):
widget.built()
def buildBind(self,widget,desc):
body="""
"""
wid = desc.get('wid','self')
w = self.getWidgetByIdPath(widget,wid)
event = desc.get('event')
if event is None:
return
f = self.buildAction(widget,desc)
w.bind(**{event:f})
def uniaction(self,widget,desc):
acttype = desc.get('actiontype')
if acttype=='urlwidget':
return self.urlwidgetAction(widget,desc)
if acttype == 'registedfunction':
return self.registedfunctionAction(widget,desc)
if acttype == 'script':
return self.scriptAction(widget, desc)
if acttype == 'method':
return self.methodAction(widget, desc)
alert(msg="actiontype invalid",title=acttype)
def urlwidgetAction(self,widget,desc):
target = self.getWidgetByIdPath(widget, desc.get('target','self'))
add_mode = desc.get('mode','replace')
opts = desc.get('options')
d = self.getActionData(widget,desc)
p = opts.get('params',{})
p.update(d)
opts['params'] = p
d = {
'widgettype' : 'urlwidget'
}
d['options'] = opts
x = self.widgetBuild(d,ancestor=widget)
if x is None:
return
if add_mode == 'replace':
target.clear_widgets()
target.add_widget(x)
def getActionData(self,widget,desc):
data = {}
if desc.get('datawidget',False):
dwidget = self.getWidgetByIdPath(widget,
desc.get('datawidget'))
data = dwidget.getData()
return data
def registedfunctionAction(self, widget, desc):
rf = RegistedFunction()
name = desc.get('rfname')
func = rf.get(name)
params = desc.get(params,{})
d = self.getActionData(widget,desc)
params.update(d)
func(params)
def scriptAction(self, widget, desc):
script = desc.get('script')
if script:
self.eval(script, {'self': widget})
def methodAction(self, widget, desc):
method = desc.get('method')
target = self.getWidgetByIdPath(widget, desc.get('target','self'))
if hasattr(target,method):
f = getattr(target, method)
kwargs = desc.get('options',{})
d = self.getActionData(widget,desc)
kwargs.update(d)
f(**kwargs)
else:
alert('%s method not found' % method)
def widgetBuild(self,desc,ancestor=None):
"""
desc format:
{
widgettype:<registered widget>,
id:widget id,
options:
subwidgets:[
]
binds:[
]
}
"""
name = desc['widgettype']
if name == 'urlwidget':
opts = desc.get('options')
parenturl = None
if ancestor:
parenturl = ancestor.parenturl
desc, parenturl = self.getUrlData(opts,parenturl)
if not isinstance(desc,dict):
print("getUrlData() return error data",opts,parenturl)
alert(str(dict) + 'format error')
return None
desc = self.valueExpr(desc)
desc['parenturl'] = parenturl
try:
widget = self.__build(desc,ancestor=ancestor)
return widget
except:
print('widgetBuild() Error',desc)
print_exc()
alert(str(desc))
return None
def getWidgetByIdPath(self,widget,path):
ids = path.split('/')
if ids[0] == '':
app = App.get_running_app()
widget = app.root
ids = ids[1:]
for id in ids:
if id == 'self':
return widget
widget = widget.widget_ids.get(id,None)
if widget is None:
print('widget not found,path=',path,'id=',id,'ids=',ids)
raise WidgetNotFoundById(id)
return widget

77
kivyblocks/blocksapp.py Normal file
View File

@ -0,0 +1,77 @@
import os
import sys
import signal
from appPublic.jsonConfig import getConfig
from appPublic.folderUtils import ProgramPath
from kivy.config import Config
from kivy.metrics import sp,dp,mm
from kivy.core.window import WindowBase, Window
from kivy.clock import Clock
import kivy
from kivy.resources import resource_add_path
resource_add_path(os.path.join(os.path.dirname(__file__),'./ttf'))
Config.set('kivy', 'default_font', [
'msgothic',
'DroidSansFallback.ttf'])
from kivy.app import App
# from .baseWidget import baseWidgets
# from .widgetExt import Messager
# from .externalwidgetmanager import ExternalWidgetManager
from .threadcall import HttpClient,Workers
# from .derivedWidget import buildWidget, loadUserDefinedWidget
from .utils import *
from .pagescontainer import PageContainer
from .widgetExt.messager import Messager
from .blocks import Blocks
def signal_handler(signal, frame):
app = App.get_running_app()
app.workers.running = False
app.stop()
print('Singal handled .........')
signal.signal(signal.SIGINT, signal_handler)
class BlocksApp(App):
def build(self):
x = PageContainer()
self.title = 'Test Title'
self.blocks = Blocks()
config = getConfig()
self.config = config
self.workers = Workers(maxworkers=config.maxworkers or 80)
Window.bind(on_request_close=self.on_close)
self.workers.start()
self.hc = HttpClient()
WindowBase.softinput_mode='below_target'
Clock.schedule_once(self.build1)
return x
def build1(self,t):
x = None
x = self.blocks.widgetBuild(self.config.root)
if x is None:
alert(str(self.config.root)+': cannt build widget')
return
self.root.add_widget(x)
return
def on_close(self,o,v=None):
"""
catch the "x" button's event of window
"""
self.workers.running = False
def on_pause(self,o,v=None):
"""
to avoid app start from beginening when user exit and reenter this app
"""
return True
def __del__(self):
self.workers.running = False

106
kivyblocks/boxViewer.py Normal file
View File

@ -0,0 +1,106 @@
"""
BoxViewer options:
{
"dataloader":{
"url",
"method",
"params"
"filter":{
}
}
"boxwidth",
"boxheight",
"viewer"
"toolbar":{
}
}
"""
from kivy.app import App
from kivy.utils import platform
from kivy.uix.boxlayout import BoxLayout
from .responsivelayout import VResponsiveLayout
from .toolbar import Toolbar
from .paging import Paging, RelatedLoader
from .utils import CSize
class BoxViewer(BoxLayout):
def __init__(self, **options):
self.toolbar = None
self.parenturl = None
self.dataloader = None
self.initflag = False
BoxLayout.__init__(self, orientation='vertical')
self.selected_data = None
self.options = options
self.box_width = CSize(options['boxwidth'])
self.box_height = CSize(options['boxheight'])
self.viewContainer = VResponsiveLayout(cols=2,box_width=self.box_width)
if options.get('toolbar'):
self.toolbar = Toolbar(options['toolbar'])
lopts = options['dataloader'].copy()
lopts['options']['adder'] = self.showObject
lopts['options']['remover'] = self.viewContainer.remove_widget
lopts['options']['clearer'] = self.viewContainer.clear_widgets
lopts['options']['target'] = self
lopts['options']['locater'] = self.locater
LClass = RelatedLoader
if lopts['widgettype'] == 'paging':
LClass = Paging
self.dataloader = LClass(**lopts['options'])
if self.toolbar:
self.add_widget(self.toolbar)
if self.dataloader.widget:
self.add_widget(self.dataloader.widget)
self.add_widget(self.viewContainer)
self.register_event_type('on_selected')
self.viewContainer.bind(size=self.resetCols,
pos=self.resetCols)
self.viewContainer.bind(on_scroll_stop=self.on_scroll_stop)
def on_selected(self, o, v=None):
print('BoxViewer(): on_selected fired....')
def locater(self, pos):
self.viewContainer.scroll_y = pos
def resetCols(self,o=None, v=None):
h = 0
if self.toolbar:
h += self.toolbar.height
if self.dataloader.widget:
h += self.dataloader.widget.height
self.viewContainer.height = self.height - h
self.viewContainer.setCols()
if not self.initflag:
self.dataloader.loadPage(1)
self.initflag = True
def showObject(self, rec,**kw):
blocks = App.get_running_app().blocks
options = self.options['viewer'].copy()
options['options']['record'] = rec
options['options']['ancestor'] = self
options['options']['size_hint'] = None,None
options['options']['width'] = self.box_width
options['options']['height'] = self.box_height
w = blocks.widgetBuild(options, ancestor=self)
if w is None:
return None
w.bind(on_press=self.select_record)
self.viewContainer.add_widget(w,**kw)
return w
def on_scroll_stop(self,o,v=None):
if o.scroll_y <= 0.001:
self.dataloader.loadNextPage()
if o.scroll_y >= 0.999:
self.dataloader.loadPreviousPage()
def select_record(self,o,v=None):
self.selected_data = o.getRecord()
self.dispatch('on_selected',o.getRecord())
def getData(self):
return self.selected_data

414
kivyblocks/dg.py Normal file
View File

@ -0,0 +1,414 @@
import time
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import ButtonBehavior
from kivy.clock import Clock
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.graphics import Color, Rectangle
from kivy.app import App
from appPublic.dictObject import DictObject
from appPublic.timecost import TimeCost
from appPublic.uniqueID import getID
from .utils import CSize, setSizeOptions, loading, loaded, absurl, alert
from .baseWidget import Text
from .widgetExt import ScrollWidget
from .paging import Paging, RelatedLoader
from .ready import WidgetReady
from .toolbar import Toolbar
from .i18n import I18nText
class BLabel(ButtonBehavior, Text):
def __init__(self, **kw):
ButtonBehavior.__init__(self)
Text.__init__(self,**kw)
class Cell(BoxLayout):
def __init__(self,row,desc):
"""
desc:{
width,
datatype:fff
value:
format:
on_press:
}
"""
self.desc = desc
self.row = row
super().__init__(size_hint=(None,None),
width = self.desc['width'],
height = self.row.part.datagrid.rowHeight()
)
if not self.row.header and self.desc.get('viewer'):
viewer = self.desc.get('viewer')
blocks = App.get_running_app().blocks
if isinstance(viewer,str):
l = self.desc.copy()
l['row'] = self.row
viewer = blocks.eval(viewer,l)
if isinstance(viewer,dict):
print('viewer = ', viewer)
w = blocks.widgetBuild(viewer,ancestor=self.row.part.datagrid)
self.add_widget(w)
return
if desc['header']:
bl = I18nText(otext=str(desc['value']),
font_size=CSize(1),
halign='left',
bgColor=self.row.part.datagrid.header_bgcolor
)
else:
bl = BLabel(text = str(desc['value']),
font_size=CSize(1),
halign='left',
bgColor=self.row.part.datagrid.body_bgcolor
)
self.add_widget(bl)
bl.bind(on_press=self.cell_press)
def cell_press(self,obj):
self.row.selected()
class Row(GridLayout):
def __init__(self,part, rowdesc,header=False,data=None):
"""
rowdesc=[
{
width,
name
value
on_press
}
"""
self.part = part
self.header = header
self.row_data = data
self.row_id = None
self.linewidth = 1
self.rowdesc = rowdesc
super().__init__(cols=len(self.rowdesc),spacing=self.linewidth)
# Clock.schedule_once(self.init,0)
self.init(0)
def init(self,t):
w = 0
h = 0
for c in self.rowdesc:
c['header'] = self.header
cell = Cell(self,c)
self.add_widget(cell)
w += cell.width
if cell.height > h:
h = cell.height
self.size_hint = None,None
self.width = w + self.linewidth * 2 * len(self.rowdesc)
self.height = h #self.part.datagrid.rowHeight()
def selected(self):
if not hasattr(self,'row_data'):
return # header
print('row selected',self.row_id, self.row_data)
self.part.datagrid.row_selected = True
self.part.datagrid.select_rowid = self.row_id
self.part.datagrid.dispatch('on_selected',self)
class Header(WidgetReady, ScrollWidget):
def __init__(self,part,**kw):
self.part = part
ScrollWidget.__init__(self,**kw)
WidgetReady.__init__(self)
self.init(1)
self.bind(on_scroll_stop=self.part.datagrid.on_scrollstop)
def init(self,t):
rd = [ f.copy() for f in self.part.rowdesc ]
[ f.update({'value':self.part.fields[i]['label']}) for i,f in enumerate(rd) ]
self.header = Row(self.part,rd,header=True)
self.add_widget(self.header)
self.addChild(self.header)
self.height = self.header.height
self.built()
class Body(ScrollWidget):
def __init__(self,part,**kw):
self.part = part
ScrollWidget.__init__(self,**kw)
self.idRow = {}
self.bind(on_scroll_stop=self.part.datagrid.on_scrollstop)
def addRow(self,id, data,index=0):
rd = [ f.copy() for f in self.part.rowdesc ]
[ f.update({'value':data.get(f['name'])}) for f in rd ]
row = Row(self.part,rd,data=data)
row.row_id = id
self.add_widget(row,index=index)
self.idRow[id] = row
def clearRows(self):
self.idRow = {}
self.clear_widgets()
def delRowById(self,id):
row = self.idRow[id]
self.remove_widget(row)
del self.idRow[id]
def getRowData(self,rowid):
return self.idRow[rowid].row_data
def getRowHeight(self):
return self.part.datagrid.rowHeight()
class DataGridPart(WidgetReady, BoxLayout):
def __init__(self,dg, freeze_flag, fields):
self.datagrid = dg
self.fields = fields
self.freeze_flag = freeze_flag
self.fields_width = None
BoxLayout.__init__(self, orientation='vertical')
WidgetReady.__init__(self)
# Clock.schedule_once(self.init,0.1)
self.init(0)
def setWidth(self):
if self.freeze_flag:
self.size_hint_x = None
self.width = self.getFieldsWidth()
def getFieldsWidth(self):
if not self.fields_width:
width = 0
for f in self.rowdesc:
width += f['width']
self.fields_width = width
return self.fields_width
def init(self,t):
rd = []
for f in self.fields:
r = f.copy()
r['width'] = CSize(f.get('width',10))
rd.append(r)
self.rowdesc = rd
self.setWidth()
kw = {}
if self.freeze_flag:
kw['width'] = self.fields_width
kw['size_hint'] = (None,None)
else:
kw['size_hint'] = (1,None)
kw['height'] = self.datagrid.rowHeight()
if not self.datagrid.noheader:
self.header = Header(self,**kw)
self.add_widget(self.header)
self.addChild(self.header)
self.body = Body(self)
self.add_widget(self.body)
self.addChild(self.body)
if not self.freeze_flag:
self.body.bind(pos=self.datagrid.bodyOnSize,
size=self.datagrid.bodyOnSize)
self.built()
def clearRows(self):
return self.body.clearRows()
def addRow(self,id, data):
self.body.addRow(id, data)
class DataGrid(WidgetReady, BoxLayout):
row_selected = BooleanProperty(False)
def __init__(self,**options):
kw = DictObject()
kw = setSizeOptions(options,kw)
kw.orientation = 'vertical'
WidgetReady.__init__(self)
BoxLayout.__init__(self,**kw)
self.parenturl = options.get('parenturl',None)
self.options = options
self.noheader = options.get('noheader',False)
self.header_bgcolor = options.get('header_bgcolor',[0.29,0.29,0.29,1])
self.body_bgcolor = options.get('body_bgcolor',[0.25,0.25,0.25,1])
self.color = options.get('color',[0.91,0.91,0.91,1])
self.widget_ids = {}
self.row_height = None
self.selected_rowid = None
self.dataUrl = self.options.get('dataurl')
self.show_rows = 0
self.toolbar = None
self.freeze_part = None
self.normal_part = None
self.page_rows = self.options.get('page_rows', 60)
self.params = self.options.get('params',{})
self.total_cnt = 0
self.max_row = 0
self.curpage = 0
self.loading = False
self.freeze_fields = self.getPartFields(freeze_flag=True)
self.normal_fields = self.getPartFields(freeze_flag=False)
self.createDataGridPart()
self.createToolbar()
if self.toolbar:
self.add_widget(self.toolbar)
self.addChild(self.toolbar)
b = BoxLayout(orientation='horizontal')
if self.freeze_part:
b.add_widget(self.freeze_part)
self.addChild(self.freeze_part)
if self.normal_part:
b.add_widget(self.normal_part)
self.addChild(self.normal_part)
self.add_widget(b)
if self.options.get('paging',False):
self.loader = Paging(adder=self.addRow,
clearer=self.clearRows,
dataurl=self.dataUrl,
target=self,
params=self.params,
method=self.options.get('method','GET')
)
self.add_widget(self.loader.widget)
else:
self.loader = RelatedLoader(adder=self.addRow,
remover=self.delRow,
locater=self.setScrollPosition,
page_rows=self.page_rows,
dataurl=self.dataUrl,
target=self,
params=self.params,
method=self.options.get('method','GET')
)
self.on_sizeTask = None
self.register_event_type('on_selected')
self.register_event_type('on_scrollstop')
self.built()
def setScrollPosition(self,pos):
self.normal_part.body.scroll_y = pos
if self.freeze_part:
self.freeze_part.body.scroll_y = pos
def on_scrollstop(self,o,v=None):
if not self.noheader and o == self.normal_part.header:
self.normal_part.body.scroll_x = o.scroll_x
return
if o == self.normal_part.body:
if not self.noheader:
self.normal_part.header.scroll_x = o.scroll_x
if self.freeze_part:
self.freeze_part.body.scroll_y = o.scroll_y
if self.freeze_part and o == self.freeze_part.body:
self.normal_part.body.scroll_y = o.scroll_y
if self.options.get('paging'):
return
if o.scroll_y <= 0.001:
self.loader.loadNextPage()
if o.scroll_y >= 0.999:
self.loader.loadPreviousPage()
def getData(self):
if not self.row_selected:
return None
return self._getRowData(self.select_rowid)
def _getRowData(self, rowid):
d = {}
if self.freeze_part:
d.update(self.freeze_part.body.getRowData(rowid))
d.update(self.normal_part.body.getRowData(rowid))
print('getData() return=',d)
return DictObject(**d)
def bodyOnSize(self,o,s):
if self.on_sizeTask is not None:
self.on_sizeTask.cancel()
self.on_sizeTask = Clock.schedule_once(self.calculateShowRows,0.3)
def rowHeight(self):
if not self.row_height:
self.row_height = CSize(self.options.get('row_height',1.8))
return self.row_height
def calculateShowRows(self,t):
print('body height=',self.normal_part.body.height
,'row_height=',self.rowHeight()
)
self.show_rows = int(self.normal_part.body.height/self.rowHeight())
self.loader.setPageRows(self.show_rows)
def getShowRows(self):
if self.show_rows == 0:
return 60
return self.show_rows
def init(self,t):
if self.options.get('dataurl'):
self.loader.loadPage(1)
else:
data = self.options.get('data',[])
if len(data) > 0:
loading(self)
for d in data:
self.addRow()
loaded(self)
def clearRows(self):
if self.freeze_part:
self.freeze_part.body.clearRows()
self.normal_part.body.clearRows()
def addRow(self,data, **kw):
id = getID()
if self.freeze_part:
self.freeze_part.body.addRow(id, data, **kw)
self.normal_part.body.addRow(id, data, **kw)
return id
def delRow(self,id,**kw):
if self.freeze_part:
self.freeze_part.body.delRowById(id)
self.normal_part.body.delRowById(id)
def createToolbar(self):
if 'toolbar' in self.options.keys():
tb = self.options['toolbar']
self.toolbar = Toolbar(ancestor=self,**tb)
def on_selected(self,row):
self.selected_row = row
def createDataGridPart(self):
self.freeze_part = None
self.normal_part = None
if self.freeze_fields:
self.freeze_part = DataGridPart(self,True, self.freeze_fields)
if self.normal_fields:
self.normal_part = DataGridPart(self, False, self.normal_fields)
def getPartFields(self,freeze_flag:bool=False) -> list:
fs = []
for f in self.options['fields']:
if freeze_flag:
if f.get('freeze',False):
fs.append(f)
else:
if not f.get('freeze',False):
fs.append(f)
return fs
def on_ready(self,o,v=None):
print('***********onRadey*************')

View File

@ -0,0 +1,63 @@
# -*- utf-8 -*-
"""
模板文件以wrdesc结尾
界面描述文件以uidesc结尾
"""
import os
import sys
import codecs
from kivy.app import App
from appPublic.folderUtils import ProgramPath,listFile
from appPublic.jsonConfig import getConfig
import json
class ExternalWidgetManager:
def __init__(self):
#self.register_root = os.path.join(ProgramPath(),'widgets')
#self.ui_root = os.path.join(ProgramPath(),'ui')
self.ui_root = './ui'
self.register_root = './widgets'
def loadJson(self,filepath):
with codecs.open(filepath,'r','utf-8') as f:
return json.load(f)
def travalRegisterDesc(self,func):
return
for f in listFile(self.register_root,suffixs=['.wrdesc'],rescursive=True):
desc = self.loadJson(f)
return func(desc)
def loadWidgetDesc(self,desc):
def text2Json(d):
j = json.loads(d)
return j
# print(desc)
if desc.get('filename'):
path = desc.get('filename')
if path.endswith('.uidesc'):
f = FileDataLoader()
f.bind(on_dataloaded=text2Json)
if path.startswith('/'):
path = path[1:]
fn = os.path.join(self.ui_root,path)
return text2Json(f.loadData(fn))
raise Exception('file error',path)
if desc.get('url'):
url = desc.get('url')
headers = desc.get('headers',{})
params = desc.get('params',{})
app = App.get_running_app()
resp = app.hc.sync_get(url,params=params,headers=headers)
if resp.status_code == 200:
d = resp.json()
if d.get('status') == 'OK':
return d['data']
raise Exception('ui desc loaded failed %s' % url)
raise Exception('ui desc loaded failed' , desc)

260
kivyblocks/form.py Normal file
View File

@ -0,0 +1,260 @@
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from .widgetExt.inputext import FloatInput,IntegerInput, \
StrInput,SelectInput, BoolInput, AmountInput, Password
from .baseWidget import *
from kivycalendar import DatePicker
from .utils import *
from .i18n import getI18n
"""
form options
{
"toolbar":
"dataloader":{
"dataurl":
"params":
"method"
}
"cols":"1"
"labelwidth":
"textsize":
"inputheight":
"inline":True,False
"fields":[
{
"name":
"label":
"datatype",
"uitype",
"uiparams",
"default",
"required"
},
]
"binds":[
]
}
"""
uitypes = {
"string":{
"orientation":"horizontal",
"wclass":StrInput,
},
"number":{
"orientation":"horizontal",
"wclass":IntegerInput,
},
"float":{
"orientation":"horizontal",
"wclass":FloatInput,
},
"amount":{
"orientation":"horizontal",
"wclass":AmountInput,
},
"date":{
"orientation":"horizontal",
"wclass":DatePicker,
},
"time":{
"orientation":"horizontal",
"wclass":StrInput,
},
"bool":{
"orientation":"horizontal",
"wclass":BoolInput,
},
"code":{
"orientation":"horizontal",
"wclass":SelectInput,
},
"text":{
"orientation":"vertical",
"wclass":StrInput,
"options":{
"multiline":True,
"height":CSize(6),
}
},
"teleno":{
"orientation":"horizontal",
"wclass":StrInput,
},
"email":{
"orientation":"horizontal",
"wclass":StrInput,
},
}
class InputBox(BoxLayout):
def __init__(self, form, **options):
self.form = form
self.options = options
self.uitype = options.get('uityoe','string')
self.uidef = uitypes[self.uitype]
orientation = self.uidef['orientation']
width = self.form.inputwidth
height = self.form.inputheight
self.labelwidth = self.form.options['labelwidth']
kwargs = {
"orientation":orientation,
"size_hint_y":None,
"height":CSzie(height)
}
if width <= 1:
kwargs['size_hint_x'] = width
else:
kwargs['size_hint_x'] = None
kwargs['width'] = CSize(width)
super().__init__(**kwargs)
self.initflag = False
self.bind(on_size=self.setSize,
pos=self.setSize)
self.register_event_type("on_datainput")
def init(self):
i18n = getI18n()
if self.initflag:
return
label = self.options.get('label',self.options.get('name'))
if self.options.get('required'):
label = label + '*'
kwargs = {
"otext":label,
"font_size":CSize(1)
}
if self.labelwidth<=1:
kwargs['size_hint_x'] = self.labelwidth
else:
kwargs['size_hint_x'] = None
kwargs['width'] = self.labelwidth
self.labeltext = I18nText(**kwargs)
self.add_widget(self.labeltext)
options = self.uidef.get('options',{}).copy()
options.update(self.options.get('uiparams',{}))
options['allow_copy'] = True
if self.options.get('tip'):
options['hint_text'] = i18n(self.options.get('tip'))
self.input_widget = self.uidef['wclass'](**options)
self.form[self.options['name']] = self.input_widget
self.add_widget(self.input_widget)
self.initflag = True
self.input_widget.bind(on_focus=self.on_focus)
if self.options.get('default'):
self.input_widget.setValue(self.options.get('default'))
def on_focus(self,o,v):
if v:
self.old_value = o.text
else:
if self.old_value != o.text:
self.dispatch('on_datainput',o.text)
def setSize(self,o,v=None):
self.init()
def setValue(self,v):
self.input_widget.setValue(v)
def getValue(self):
return self.input_widget.getValue()
def defaultToolbar():
return {
img_size:1.5,
text_size:0.7,
tools:[
{
"name":"__submit",
"img_src":blockImage("clear.png"),
"label":"submit"
},
{
"name":"__clear",
"img_src":blockImage("clear.png"),
"label":"clear"
}
]
}
class Form(BoxLayout):
def __init__(self, **options):
self.options = options
BoxLayout.__init__(self, orientation='vertical')
self.widget_ids = {}
self.cols = self.options_cols = self.options['cols']
if isHandHold() and Window.width < Window.height:
self.cols = 1
self.inputwidth = Window.width / self.cols
self.inputheight = CSize(self.options.get('inputheight',3))
self.toolbar = Toolbar(options.get('toolbar',defaultToolbar()))
self.fsc = VResponsiveLayout(cols=self.cols,
box_width=self.inputwidth)
self.add_widget(self.toolbar)
self.add_widget(self.fsc)
self.register_event_type('on_submit')
self.initflag = False
self.bind(size=self.on_size,
pos=self.on_size)
def init(self):
if self.initflag:
return
self.fieldWidgets=[]
for f in self.options['fields']:
w = InputBox(self, self, **f)
self.fsc.add_widget(w)
self.fieldWidgets.append(w)
blocks = App.get_running_app().blocks
wid = getWidgetById(self,'__submit')
wid.bind(on_press=self.on_submit_button)
wid = getWidgetById(self,'__clear')
wid.bind(on_press=self.on_clear_button)
self.initflag = True
def on_submit_button(self,o,v=None):
self.dispatch('on_submit')
def on_clear_button(self,o,v=None):
for fw in self.fieldWidgets:
fw.clear()
def on_size(self,o, v=None):
self.init()
class StrSearchForm(BoxLayout):
def __init__(self,img_url=None,**options):
print('here ---------------')
self.name = options.get('name','search_string')
BoxLayout.__init__(self,orientation='horizontal',size_hint_y=None,height=CSize(3))
self.inputwidget = TextInput(
multiline=False,
font_size=CSize(1),
size_hint_y=None,
height=CSize(3))
self.add_widget(self.inputwidget)
imgsrc = img_url if img_url else blockImage('search.png')
self.search = PressableImage(source=imgsrc,
size_hint=(None,None),
size=CSize(3,3)
)
self.add_widget(self.search)
self.register_event_type('on_submit')
self.search.bind(on_press=self.submit_input)
self.inputwidget.bind(on_text_validate=self.submit_input)
def submit_input(self,o,v=None):
print('StrSearchForm():submit_input() called')
text = self.inputwidget.text
if text != '':
d = {
self.name:text
}
self.dispatch('on_submit',d)
def on_submit(self,o,v=None):
print('StrSearchForm():on_submit fired ..........')

31
kivyblocks/httpvplayer.py Normal file
View File

@ -0,0 +1,31 @@
import requests
headers = {
'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/72.0.3626.109 Safari/537.36",
}
def read_m3u8(m3u8Url):
baseurl = '/'.join(m3u8Url.split('/')[:-1])
m3u8_res = requests.get(m3u8Url,headers)
if m3u8_res.status_code != 200:
print('URL不能正常访问', m3u8_res.status_code)
m3u8_content = m3u8_res.content.decode('utf8')
print(m3u8_content)
'''获取m3u8中的下载连接'''
media_url_list = []
lines_list = m3u8_content.strip().split('\r\n')
if len(lines_list) < 3:
lines_list = m3u8_content.strip().split('\n')
if '#EXTM3U' not in m3u8_content:
raise BaseException('非M3U8连接')
for index,line in enumerate(lines_list):
# print(index,line)
if '#EXTINF' in line:
media_url_list.append('%s/%s' %(baseurl,lines_list[index+1]))
return media_url_list
if __name__ == '__main__':
vfile = read_m3u8("http://pili-live-hdl.qhmywl.com/dsdtv/6d735caeaf70f8901aa00f86aefc4dde.m3u8")
print(vfile)

68
kivyblocks/i18n.py Normal file
View File

@ -0,0 +1,68 @@
import locale
from kivy.app import App
from kivy.properties import StringProperty
from appPublic.Singleton import SingletonDecorator
from appPublic.jsonConfig import getConfig
from .baseWidget import Text
@SingletonDecorator
class I18n:
def __init__(self,url):
self.kvlang={}
self.loadUrl = url
self.lang = locale.getdefaultlocale()[0]
self.loadI18n(self.lang)
self.i18nWidgets = []
def addI18nWidget(self,w):
self.i18nWidgets.append(w)
def loadI18n(self,lang):
if not self.loadUrl:
self.kvlang[lang] = {}
return
app = App.get_running_app()
d = app.hc.get('%s?lang=%s' % (self.loadUrl,lang))
self.kvlang[lang] = d
def __call__(self,msg,lang=None):
if lang is None:
lang = self.lang
d = self.kvlang.get(lang,{})
return d.get(msg,msg)
def changeLang(self,lang):
d = self.kvlang.get(lang)
if not d:
self.loadI18n(lang)
self.lang = lang
for w in self.i18nWidgets:
w.changeLang(lang)
def getI18n(url=None):
i18n=I18n(url)
return i18n
class I18nText(Text):
lang=StringProperty('')
otext=StringProperty('')
def __init__(self,**kw):
self.options = kw.copy()
otext = kw.get('otext',kw.get('text'))
if kw.get('otext'):
del kw['otext']
super().__init__(**kw)
self.i18n = getI18n()
self.i18n.addI18nWidget(self)
self.otext = otext
def on_otext(self,o,v=None):
self.text = self.i18n(self.otext)
def changeLang(self,lang):
self.lang = lang
def on_lang(self,o,lang):
self.text = self.i18n(self.otext)

BIN
kivyblocks/imgs/Cycle.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

BIN
kivyblocks/imgs/clear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
kivyblocks/imgs/doing.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
kivyblocks/imgs/folder.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

BIN
kivyblocks/imgs/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
kivyblocks/imgs/mute.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
kivyblocks/imgs/next.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
kivyblocks/imgs/origin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
kivyblocks/imgs/pause.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
kivyblocks/imgs/picture_empty.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

BIN
kivyblocks/imgs/play.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
kivyblocks/imgs/replay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
kivyblocks/imgs/running.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
kivyblocks/imgs/save.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
kivyblocks/imgs/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
kivyblocks/imgs/volume.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

59
kivyblocks/kivysize.py Normal file
View File

@ -0,0 +1,59 @@
from kivy.app import App
from kivy.metrics import mm
from kivy.core.window import Window
from appPublic.Singleton import SingletonDecorator
from appPublic.jsonConfig import getConfig
@SingletonDecorator
class KivySizes:
myFontSizes = {
"smallest":1.5,
"small":2.0,
"normal":2.5,
"large":3.0,
"huge":3.5,
"hugest":4.0,
}
separatorsize = 2
def getFontSize(self,name=None):
config = getConfig()
if config.font_sizes:
self.myFontSizes = config.font_sizes
if name is None:
name = getConfig().font_name
x = self.myFontSizes.get(name,None)
if x == None:
x = self.myFontSizes.get('normal')
return x
def namedSize(self,cnt=1,name=None):
return mm(cnt * self.getFontSize(name=name))
def unitedSize(self,x,y=None,name=None):
xr = self.namedSize(cnt=x,name=name)
if y is None:
return xr
return (xr,self.namedSize(cnt=y,name=name))
def CSize(self,x,y=None,name=None):
return self.unitedSize(x,y=y,name=name)
def getScreenSize(self):
#root = App.get_running_app().root
#return root.width, root.height
return Window.width, Window.height
def getWindowPhysicalSize(self,w):
h_phy = float(w.height) / mm(1)
w_phy = float(w.width) / mm(1)
return w_phy, h_phy
def getScreenPhysicalSize(self):
return self.getWindowPhysicalSize(App.get_running_app().root)
return self.getWindowPhysicalSize(Window)
def isHandHold(self):
return min(self.getScreenPhysicalSize()) <= 75.0

76
kivyblocks/login.py Normal file
View File

@ -0,0 +1,76 @@
from kivy.app import App
from kivy.uix.popup import Popup
from appPublic.Singleton import SingletonDecorator
from appPublic.registerfunction import RegisterFunction
logformdesc = {
"widgettype":"Form",
"options":{
"cols":1,
"labelwidth":0.3,
"textsize":1,
"inputheight":3,
"fields":[
{
"name":"userid",
"label":"user name",
"datatype":"str",
"uitype":"str"
},
{
"name":"passwd",
"label":"Password",
"datatype":"str",
"uitype":"password"
}
]
}
"binds":[
"wid":"self",
"event":"on_submit",
"datawidegt":"self",
"actiontype":"registedfunction",
"rfname":"setupUserInfo"
}
}
@SingletonDecorator
class LoginForm(Popup):
def __init__(self):
super().__init__()
app = App.get_running_app()
self.content = app.blocks.widgetBuild(logformdesc)
self.title = 'login'
self.blockCalls = []
self.open_status = False
self.content.bind(on_submit=self.on_submit)
def needlogin(self, url,method,params,headers,callback=None,errback=None):
self.blockCalls.append([url, method, params,headers,callback,errback])
if self.open_status:
return
self.open_status = True
self.open()
def recall(self):
app = App.get_running_app()
for url, method, params, headers,callback, errback in self.blockCalls:
app.hc(url, method=pethod,params=params,headers=headers,
callback=callback,errback=errback)
def close(self):
self.open_status = False
self.dismiss()
def on_submit(self,o,v):
self.dismiss()
def setupUserInfo(userinfo):
app = App.get_running_app()
app.setUserInfo(userinfo)
lf = LoginForm()
lf.recall()
rf = RegisterFunction()
rf.register('setupUserInfo',setupUserInfo)

View File

@ -0,0 +1,71 @@
from kivy.utils import platform
from .responsivelayout import VResponsiveLayout
from .utils import isHandHold
from .paging import RelatedLoader
"""
options format
{
box_width:optional, if set, the child width
dataurl: url to get data
page_rows:records per read
viewer:{
widgettype:an class name to show the object
options:{
}
},
binds:[
bind info
]
}
"""
class ObjectViewer(VResponsiveLayout):
def __init__(self, dataurl=None, viewer={},
page_rows=25,params={},**options):
super().__init__(**options)
self.options = options
self.dataUrl = dataurl
self.params = params
self.viewer = viewer
self.page_rows = page_rows
self.initflag = False
def setScrollPos(self,pos):
print('setScrollPos(),pos=',pos)
self.scroll_y = pos
def sizeChangedWork(self,v=None):
super().sizeChangedWork()
if not self.initflag:
self.initflag = True
self.loader = RelatedLoader(
adder=self.showObject,
remover=self.remove_widget,
locater=self.setScrollPos,
page_rows=self.page_rows,
dataurl=self.dataUrl,
target=self,
params=self.params,
method=self.options.get('method','GET')
)
self.loader.loadPage(1)
print('ObjectViewer:::::::::::::self.size=',self.size)
def on_scroll_stop(self,o,v=None):
return
print('on_scroll_stop(), self.scroll_y=', self.scroll_y,
self._inner.pos,
self._inner.size,
self.size)
if self.scroll_y >=0.999:
print('on_scroll_stop(), move_up')
self.loader.loadPreviousPage()
if self.scroll_y <= 0.001:
print('on_scroll_stop(), move_down')
self.loader.loadNextPage()
def showObject(self,rec,**kw):
pass

View File

@ -0,0 +1,97 @@
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
from .utils import CSize
class PageContainer(FloatLayout):
def __init__(self,**kw):
super().__init__(**kw)
self.show_back = True
self.pageWidgets = []
self.backButton = Button(text='<',size_hint=(None,None),
font_size=CSize(1),
height=CSize(1.8),width=CSize(1.8))
self.backButton.bind(on_press=self.previous)
Window.bind(size=self.on_window_size)
# self.bind(size=self.reshowBackButton,
# on_orientation=self.reshowBackButton)
def on_window_size(self,o,v=None):
print('on_window_size event fired ....',Window.size)
self.size = Window.size
self.reshowBackButton()
def reshowBackButton(self,o=None,v=None):
print(self.size)
self.showBackButton()
def hideBack(self):
if not self.show_back:
return
super(PageContainer,self).remove_widget(self.backButton)
self.show_back = False
def showBack(self):
if self.show_back:
return
super(PageContainer,self).add_widget(self.backButton)
self.show_back = True
def showLastPage(self):
super(PageContainer,self).clear_widgets()
if len(self.pageWidgets) < 1:
return
w = self.pageWidgets[-1]
w.pos = 0,0
w.size = self.size
super(PageContainer,self).add_widget(w)
self.showBackButton()
def previous(self,v=None):
if len(self.pageWidgets) <= 1:
return
w = self.pageWidgets[-1]
if hasattr(w, 'beforeDestroy'):
x = w.beforeDestroy()
if not x:
return
self.pageWidgets = self.pageWidgets[:-1]
del w
self.showLastPage()
def add_widget(self,widget):
self.pageWidgets.append(widget)
self.showLastPage()
def showBackButton(self):
if len(self.pageWidgets) <= 1:
return
print('size event fired .....',self.size)
super(PageContainer,self).remove_widget(self.backButton)
self.show_back = False
self.backButton.pos = (4,self.height - 4 - self.backButton.height)
self.showBack()
if __name__ == '__main__':
class Page(Button):
def __init__(self,container,page_cnt = 1):
self.container = container
self.page_cnt = page_cnt
Button.__init__(self,text = 'page' + str(page_cnt))
self.bind(on_press=self.newpage)
def newpage(self,v):
p = Page(self.container,self.page_cnt + 1)
self.container.add(p)
class MyApp(App):
def build(self):
x = PageContainer()
p = Page(x)
x.add(p)
return x
MyApp().run()

286
kivyblocks/paging.py Normal file
View File

@ -0,0 +1,286 @@
import traceback
import math
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.app import App
from functools import partial
from appPublic.dictObject import DictObject
from appPublic.jsonConfig import getConfig
from .baseWidget import Text
from .utils import CSize, absurl, alert
from .form import StrSearchForm
class PagingButton(Button):
def __init__(self, **kw):
super().__init__(**kw)
self.size_hint = (None,None)
self.size = CSize(2,1.8)
self.font_size = CSize(1)
"""
{
dataurl
params
method
locater
filter
}
"""
class PageLoader:
def __init__(self, **options):
self.loading = False
self.filter = None
if options.get('filter'):
self.filter = StrSearchForm(**options['filter'])
self.filter.bind(on_submit=self.do_search)
self.locater = options.get('locater')
self.params = options.get('params')
self.method = options.get('method','GET')
self.url = options.get('dataurl')
if not self.url:
raise Exception('dataurl must be present:options=%s' % str(options))
self.total_cnt = 0
self.total_page = 0
self.page_rows = options.get('page_rows',0)
self.curpage = 0
def do_search(self,o,params):
print('PageLoader().do_search(), on_submit handle....',params)
self.clearer()
self.params.update(params)
print('do_search():,params=',self.params)
self.loadPage(1)
def calculateTotalPage(self):
self.total_page = math.floor(self.total_cnt / self.page_rows)
if self.total_cnt % self.page_rows != 0:
self.total_page += 1
def setPageRows(self,row_cnt):
if row_cnt == 0:
return
self.page_rows = row_cnt
if self.total_cnt != 0:
self.calculateTotalPage()
self.reload()
def reload(self):
if self.curpage == 0:
self.curpage = 1
self.loadPage(self.curpage)
def setDir(self):
pages = self.objectPages.keys()
if len(pages)==0:
self.dir = 'down'
return
if self.curpage >= max(self.objectPages.keys()):
self.dir = 'down'
else:
self.dir = 'up'
def show_page(self,o,d):
pass
def loadPage(self,p):
self.curpage = p
self.loading = True
params = self.params.copy()
params.update({
"page":self.curpage,
"rows":self.page_rows
})
hc = App.get_running_app().hc
url = absurl(self.url,self.target.parenturl)
# print('loadPage():url=',url,'params=',params)
x = hc(url,method=self.method,
params=params,callback=self.show_page,
errback=self.httperror)
def httperror(self,o,e):
traceback.print_exc()
alert(str(e),title='alert')
"""
{
adder,
remover
target,
locater,
dataurl,
params,
method,
filter
}
"""
class RelatedLoader(PageLoader):
def __init__(self, **options):
super().__init__(**options)
self.adder = options.get('adder')
self.remover = options.get('remover')
self.clearer = options.get('clearer')
self.target = options.get('target')
self.objectPages = {}
self.totalObj = 0
self.MaxbufferPage = 3
if self.filter:
self.widget = self.filter
else:
self.widget = None
def setPageRows(self,row_cnt):
if self.total_cnt != 0:
self.calculateTotalPage()
self.reload()
pass
def doBufferMaintain(self):
siz = len(self.objectPages.keys())
if siz >= self.MaxbufferPage:
if self.dir == 'up':
p = max(self.objectPages.keys())
else:
p = min(self.objectPages.keys())
self.deleteBuffer(p)
def deleteBuffer(self,page):
for w in self.objectPages[page]:
self.remover(w)
self.totalObj -= len(self.objectPages[page])
del self.objectPages[page]
def show_page(self,o,data):
if self.objectPages.get(self.curpage):
self.deleteBuffer(self.curpage)
else:
self.setDir()
self.doBufferMaintain()
self.total_cnt = data['total']
self.calculateTotalPage()
widgets = []
rows = data['rows']
if self.dir == 'up':
rows.reverse()
for r in rows:
if self.dir == 'up':
w = self.adder(r,index=self.totalObj)
else:
w = self.adder(r)
widgets.append(w)
self.totalObj += 1
self.objectPages[self.curpage] = widgets
pages = len(self.objectPages.keys())
loc = 1.0 / float(pages)
if pages == 1:
self.locater(1)
elif self.dir == 'up':
self.locater(1 - loc)
else:
self.locater(loc)
self.loading = False
print('buffer pages=',len(self.objectPages.keys()),'pages=',self.objectPages.keys())
def loadPreviousPage(self):
if self.loading:
return
pages = self.objectPages.keys()
if len(pages) < 1:
return
self.curpage = min(pages)
if self.curpage <= 1:
return
self.curpage -= 1
self.loadPage(self.curpage)
def loadNextPage(self):
if self.loading:
return
pages = self.objectPages.keys()
if len(pages) == 0:
return
self.curpage = max(pages)
if self.curpage>=self.total_page:
return
self.curpage += 1
self.loadPage(self.curpage)
"""
{
adder,
clearer
target,
dataurl
params,
method
}
"""
class Paging(PageLoader):
def __init__(self,**options):
PageLoader.__init__(self,**options)
self.adder = options.get('adder')
self.clearer = options.get('clearer')
self.target = options.get('target')
self.init()
def init(self):
kwargs = {}
kwargs['size_hint_y'] = None
kwargs['height'] = CSize(2)
kwargs['orientation'] = 'horizontal'
self.widget = BoxLayout(**kwargs)
self.b_f = PagingButton(text="|<")
self.b_p = PagingButton(text="<")
self.b_n = PagingButton(text=">")
self.b_l = PagingButton(text=">|")
self.b_f.bind(on_press=self.loadFirstPage)
self.b_p.bind(on_press=self.loadPreviousPage)
self.b_n.bind(on_press=self.loadNextPage)
self.b_l.bind(on_press=self.loadLastPage)
self.widget.add_widget(self.b_f)
self.widget.add_widget(self.b_p)
self.widget.add_widget(self.b_n)
self.widget.add_widget(self.b_l)
if self.filter:
self.widget.add_widget(self.filter)
def show_page(self,o,d):
d = DictObject(**d)
self.total_cnt = d.total
self.calculateTotalPage()
self.clearer()
for r in d.rows:
self.adder(r)
self.loading = False
def loadFirstPage(self,o=None):
if self.curpage == 1:
print('return not loading')
return
self.loadPage(1)
def loadPreviousPage(self,o=None):
if self.curpage < 2:
print('return not loading')
return
self.loadPage(self.curpage-1)
def loadNextPage(self,o=None):
if self.curpage >= self.total_page:
print('return not loading')
return
self.loadPage(self.curpage+1)
def loadLastPage(self,o=None):
if self.curpage >= self.total_page:
print('return not loading')
return
self.loadPage(self.total_page)

60
kivyblocks/ready.py Normal file
View File

@ -0,0 +1,60 @@
from kivy.uix.widget import Widget
from kivy.properties import BooleanProperty
from kivy.clock import Clock
from kivy.graphics import Color, Rectangle
class WidgetReady(Widget):
ready = BooleanProperty(False)
kwargs_keys = ['bg_color']
def __init__(self, **kw):
self.bg_color = kw.get('bg_color',None)
self.all_ready = {}
self.all_ready[self] = False
self.backgroundcolorTask = None
if self.bg_color is not None:
self.setupBackgroundColor()
def setupBackgroundColor(self):
self.bind(pos=self.backgroundcolorfunc)
self.bind(size=self.backgroundcolorfunc)
def backgroundcolorfunc(self,o,v):
if self.backgroundcolorTask is not None:
self.backgroundcolorTask.cancel()
self.backgroundcolorTask = Clock.schedule_once(self._setBackgroundColor)
def _setBackgroundColor(self,t=0):
self.canvas.before.clear()
with self.canvas.before:
Color(*self.bg_color)
Rectangle(size=self.size,pos=self.pos)
def addChild(self,w):
"""
need to call addChild to make widget to wait the subwidget on_ready event
w the subwidget of self
"""
if hasattr(w,'ready'):
self.all_ready[w] = False
w.bind(on_ready=self.childReady)
def childReady(self,c,v=None):
self.all_ready[c] = True
if all(self.all_ready.values()):
self.ready = True
def on_ready(self,o,v=None):
print(self,'on_ready')
def built(self):
"""
when self built, call it at end to issue the on_ready event
"""
self.all_ready[self] = True
if all(self.all_ready.values()):
self.ready = True
def setBackgroundColor(self,bgcolor):
self.bg_color = bgcolor
self._setBackgroundColor(0)

View File

@ -0,0 +1,62 @@
from kivy.utils import platform
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, Ellipse,Rectangle
from kivy.clock import Clock
from .utils import CSize, isHandHold
class VResponsiveLayout(ScrollView):
def __init__(self,box_width,cols, **kw):
self.org_box_width = box_width
self.org_cols = cols
self.box_width = box_width
self.box_cols = cols
super(VResponsiveLayout, self).__init__(**kw)
self.options = kw
self._inner = GridLayout(cols=self.org_cols, padding=2,
spacing=2,size_hint=(1,None))
super(VResponsiveLayout,self).add_widget(self._inner)
self._inner.bind(
minimum_height=self._inner.setter('height'))
self.sizeChangedTask = None
self.bind(pos=self.sizeChanged,size=self.sizeChanged)
def sizeChanged(self,o,v=None):
if self.sizeChangedTask:
self.sizeChangedTask.cancel()
self.sizeChangedTask = Clock.schedule_once(self.sizeChangedWork,0.1)
def sizeChangedWork(self,t=None):
self.setCols()
def on_orientation(self,o):
self.setCols()
def add_widget(self,widget,**kw):
width = self.box_width
if hasattr(widget, 'cols'):
width = widget.cols * self.box_width
widget.width = width
a = self._inner.add_widget(widget,**kw)
return a
def clear_widgets(self,**kw):
a = self._inner.clear_widgets(**kw)
def remove_widget(self,widget,**kw):
a = self._inner.remove_widget(widget,**kw)
return a
def setCols(self,t=None):
self.box_width = self.org_box_width
self.cols = int(self.width / self.box_width)
if isHandHold():
w,h = self.size
if w < h:
self.box_width = w / self.org_cols - 2
self.cols = self.org_cols
self._inner.cols = self.cols
for w in self._inner.children:
w.width = self.box_width

View File

@ -0,0 +1,29 @@
from appPublic.jsonConfig import getConfig
from kivy.uix.button import ButtonBehavior
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from .baseWidget import *
from .objectViewer import ObjectViewer
class ServerImageViewer(ObjectViewer):
def showObject(self,rec,**kw):
blocks = App.get_running_app().blocks
config = getConfig()
url = '%s/%s/%s' % ( config.uihome,'thumb',rec['id'])
desc = self.viewer.copy()
desc['options'].update({
"size_hint_x":None,
"width":self.box_width,
"keep_ratio":True,
"source":url
})
w = blocks.widgetBuild(desc,ancestor=self)
if w is None:
print('Error desc=',desc)
return
bl = BoxLayout(size_hint=(None,None),size=(self.box_width,self.box_width))
bl.add_widget(w)
self.add_widget(bl,**kw)
print('showObject():add widget',desc,w.size)
return bl

196
kivyblocks/threadcall.py Normal file
View File

@ -0,0 +1,196 @@
import time
from threading import Thread, Lock, BoundedSemaphore
import requests
from kivy.event import EventDispatcher
from kivy.clock import Clock
from kivy.app import App
class NeedLogin(Exception):
pass
class ThreadCall(Thread,EventDispatcher):
def __init__(self,target, args=(), kwargs={}):
Thread.__init__(self)
EventDispatcher.__init__(self)
self.register_event_type('on_result')
self.register_event_type('on_error')
self.rez = None
self.daemon = False
self.target = target
self.args = args
self.timing = None
self.kwargs = kwargs
def start(self):
Thread.start(self)
self.timing = Clock.schedule_once(self.checkStop,0)
def run(self):
try:
self.rez = self.target(*self.args,**self.kwargs)
self.dispatch('on_result',self.rez)
except NeedLogin as e:
lf = LoginForm()
lf.needlogin(url,method,params,headers,callback,errback)
except Exception as e:
self.dispatch('on_error',e)
def on_result(self, v):
pass
def on_error(self,e):
pass
def checkStop(self,timestamp):
x = self.join(timeout=0.0001)
if self.is_alive():
self.timing = Clock.schedule_once(self.checkStop,0)
return
class Workers(Thread):
def __init__(self,maxworkers):
super().__init__()
self.max_workers = maxworkers
self.tasks = []
# task = [callee,callback,kwargs]
self.lock = Lock()
self.work_sema = BoundedSemaphore(value=self.max_workers)
self.running = False
def run(self):
self.running = True
while self.running:
if len(self.tasks) == 0:
time.sleep(0.001)
continue
task = None
with self.lock:
task = self.tasks.pop()
if task is None:
continue
with self.work_sema:
callee,callback,errback,kwargs = task
x = ThreadCall(callee,kwargs=kwargs)
x.bind(on_result=callback)
if errback:
x.bind(on_error=errback)
x.start()
def add(self,callee,callback,errback=None,kwargs={}):
with self.lock:
self.tasks.insert(0,[callee,callback,errback,kwargs])
class HttpClient:
def __init__(self):
self.s = requests.Session()
self.workers = App.get_running_app().workers
def webcall(self,url,method="GET",params={},headers={}):
if method in ['GET']:
req = requests.Request(method,url,
params=params,headers=headers)
else:
req = requests.Request(method,url,
data=params,headers=headers)
prepped = self.s.prepare_request(req)
resp = self.s.send(prepped)
if resp.status_code == 200:
try:
data = resp.json()
if type(data) != type({}):
return data
status = data.get('status',None)
if status is None:
return data
if status == 'OK':
return data['data']
return data
except:
return resp.text
if resp.status_code == 401:
raise NeedLogin
print('Error', url, method, params, resp.status_code,type(resp.status_code))
raise Exception('error:%s' % url)
def __call__(self,url,method="GET",params={},headers={},callback=None,errback=None):
def cb(t,resp):
return resp
if callback is None:
try:
resp = self.webcall(url, method=method,
params=params, headers=headers)
return cb(None,resp)
except NeedLogin as e:
lf = LoginForm()
lf.needlogin(url,method,params,headers,callback,errback)
return None
except Exception as e:
if errback is not None:
errback(e)
kwargs = {
"url":url,
"method":method,
"params":params,
"headers":headers
}
self.workers.add(self.webcall,callback,errback,kwargs=kwargs)
def get(self, url, params={}, headers={}, callback=None, errback=None):
return self.__call__(url,method='GET',params=params,
headers=headers, callback=callback,
errback=errback)
def post(self, url, params={}, headers={}, callback=None, errback=None):
return self.__call__(url,method='POST',params=params,
headers=headers, callback=callback,
errback=errback)
def put(self, url, params={}, headers={}, callback=None, errback=None):
return self.__call__(url,method='PUT',params=params,
headers=headers, callback=callback,
errback=errback)
def delete(self, url, params={}, headers={}, callback=None, errback=None):
return self.__call__(url,method='DELETE',params=params,
headers=headers, callback=callback,
errback=errback)
def option(self, url, params={}, headers={}, callback=None, errback=None):
return self.__call__(url,method='OPTION',params=params,
headers=headers, callback=callback, errback=errback)
if __name__ == '__main__':
from kivy.uix.textinput import TextInput
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class MyApp(App):
def build(self):
self.hc = HttpClient()
x = BoxLayout(orientation='vertical')
y = BoxLayout(orientation='horizontal',size_hint_y=0.07)
self.ti = TextInput(size_hint_x=0.95,multiline=False)
btn = Button(size_hint_x=0.05,text='go')
y.add_widget(self.ti)
y.add_widget(btn)
btn.bind(on_press=self.getHtml)
self.texti = TextInput(multiline=True,readonly=True)
x.add_widget(y)
x.add_widget(self.texti)
return x
def getHtml(self,v=None):
url = self.ti.text
self.hc.get(url,callback=self.showResult)
self.texti.text = 'loading...'
def showResult(self,target,resp):
if resp.status_code==200:
self.texti.text = resp.text
else:
print(reps.status_code,'...............')
MyApp().run()

206
kivyblocks/toolbar.py Normal file
View File

@ -0,0 +1,206 @@
from kivy.graphics import Color, Rectangle
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import AsyncImage
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.app import App
from kivy.clock import Clock
from appPublic.dictObject import DictObject
from .widgetExt.scrollwidget import ScrollWidget
from .utils import *
from .kivysize import KivySizes
from .ready import WidgetReady
from .i18n import I18nText
"""
toobar={
"mode":"icon", "icontext","text"
img_size=1.5,
text_size=0.7,
"tools":[
]
}
tool options
{
name:'',
label:''
img=''
}
"""
class Tool(ButtonBehavior, WidgetReady,BoxLayout):
normal_bgColor=[0.1,0,0,1]
active_bgColor=[0.4,0.4,0.4,1]
def __init__(self,ancestor=None,**opts):
if ancestor is None:
ancestor = App.get_running_app().root
ancestor.widget_ids[opts['name']] = self
ButtonBehavior.__init__(self)
bc = opts.get('bg_color',self.normal_bgColor)
WidgetReady.__init__(self,bg_color=self.normal_bgColor)
BoxLayout.__init__(self,
orientation='vertical',size_hint=(None,None))
self.opts = DictObject(**opts)
if not self.opts.img_size:
self.opts.img_size = 2
if not self.opts.text_size:
self.opts.text_size = 0.7
app = App.get_running_app()
ks = KivySizes()
size = ks.unitedSize(self.opts.img_size or 2)
img = AsyncImage(source=self.opts.img_src,size_hint=(None,None),
size=(size,size))
tsize = ks.unitedSize(self.opts.text_size)
label = self.opts.label or self.opts.name
lbl = I18nText(otext=label,font_size=int(tsize))
lbl.text_size = (size, 1.3 * tsize)
self.add_widget(img)
self.add_widget(lbl)
self.size = (size * 1.1, (size + 2 * tsize)*1.1)
def on_size(self,obj,size):
if self.parent:
print('********center*dd**********')
self.center = self.parent.center
def on_press(self):
print('Tool(). pressed ...............')
def setActive(self,flag):
if flag:
self.setBackgroundColor(self.active_bgColor)
else:
self.setBackgroundColor(self.normal_bgColor)
"""
toolbar options
{
img_size=1.5,
text_size=0.7,
tools:[
{
"name":"myid",
"img_src":"gggggg",
"label":"gggggg"
},
...
]
}
"""
class Toolbar(GridLayout):
def __init__(self, ancestor=None,**opts):
self.opts = DictObject(**opts)
self.tool_widgets={}
super().__init__(cols = len(self.opts.tools))
self.size_hint = (1,None)
first = True
for opt in self.opts.tools:
opt.img_size = self.opts.img_size
opt.text_size = self.opts.text_size
purl = None
if ancestor:
purl = ancestor.parenturl
opt.img_src = absurl(opt.img_src,purl)
tool = Tool(ancestor=ancestor, **opt)
if first:
first = False
h = ancestor
if not ancestor:
h = App.get_runnung_app().root
h.widget_ids['_home_'] = tool
self.tool_widgets[opt.name] = tool
box = BoxLayout()
box.add_widget(tool)
self.add_widget(box)
tool.bind(on_press=self.tool_press)
self.height = tool.height * 1.1
def on_size(self,obj,size):
with self.canvas.before:
Color(0.3,0.3,0.3,1)
Rectangle(pos=self.pos,size=self.size)
def tool_press(self,o,v=None):
o.background_color = [0.3,1,1,0.5]
for n,w in self.tool_widgets.items():
active = False
if w == o:
active = True
w.setActive(active)
"""
Toolpage options
{
img_size=1.5,
text_size=0.7,
tool_at:"left","right","top","bottom",
tools:[
{
"name":"myid",
"img_src":"gggggg",
"text":"gggggg"
"url":"ggggggggg"
},
...
]
"""
class ToolPage(BoxLayout):
def __init__(self,**opts):
self.opts = DictObject(**opts)
self.parenturl = opts.get('parenturl',None)
self.widget_ids = {}
if self.opts.tool_at in [ 'top','bottom']:
orient = 'vertical'
else:
orient = 'horizontal'
super().__init__(orientation=orient)
self.content = None
self.toolbar = None
self.init()
self.show_firstpage()
def on_size(self,obj,size):
if self.content is None:
return
x,y = size
self.toolbar.width = x
self.content.width = x
self.content.height = y - self.toolbar.height
def showPage(self,obj):
self._show_page(obj.opts)
def show_firstpage(self,t=None):
d = self.widget_ids['_home_']
d.dispatch('on_press')
def init(self):
self.initFlag = True
self.mywidgets = {}
self.content = BoxLayout()
self.widget_ids['content'] = self.content
for t in self.opts.tools:
parenturl = None
if hasattr(self,'parenturl'):
parenturl = self.parenturl
t.img_src = absurl(t.img_src,parenturl)
opts = self.opts
self.toolbar = Toolbar(ancestor=self, **self.opts)
if self.opts.tool_at in ['top','left']:
self.add_widget(self.toolbar)
self.add_widget(self.content)
else:
self.add_widget(self.content)
self.add_widget(self.toolbar)
Clock.schedule_once(self.show_firstpage,0.5)
if __name__ == '__main__':
from blocksapp import BlocksApp
app = BlocksApp()
app.run()

Binary file not shown.

10
kivyblocks/uploadfile.py Normal file
View File

@ -0,0 +1,10 @@
import requests
url = "http://localhost:8080/uploadfile.dspy"
files = {
"afile":("uploadfile.py",open("uploadfile.py","rb"),"application/python")
}
r = requests.post(url,files=files)
print(r.text)

181
kivyblocks/utils.py Normal file
View File

@ -0,0 +1,181 @@
import os
from kivy.app import App
from appPublic.jsonConfig import getConfig
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView
from kivy.uix.image import Image
from appPublic.dictObject import DictObject
from .kivysize import KivySizes
alert_widget= None
def blockImage(name):
p = os.path.dirname(os.path.abspath(__file__))
return os.path.join(p,'imgs',name)
def loaded(widget):
widget.loadingwidget.dismiss()
# widget.remove_widget(widget.loadingwidget)
del widget.loadingwidget
widget.loadingwidget = None
def loading(parent):
fp = os.path.join(os.path.dirname(__file__),'imgs','loading1.gif')
image = Image(source=fp,width=CSize(2),height=CSize(2),
size_hint=(None,None))
view = ModalView(auto_dismiss=False)
view.add_widget(image)
view.center = parent.center
parent.loadingwidget = view
# parent.add_widget(view)
view.open()
return view
def setSizeOptions(desc,kw):
"""
desc's width, and height to setup a widget's size options
if width or height is not set, kw add not thing
if width or height <= 1, using present rate of size
else use CSize to tims width or height
"""
if not isinstance(kw, DictObject):
kw = DictObject(**kw)
width = desc.get('width',0)
if width > 1.01:
kw.width = CSize(width)
kw.size_hint_x = None
elif width > 0.00:
kw.size_hint_x = width
height = desc.get('height',0)
if height > 1.01:
kw.height = CSize(height)
kw.size_hint_y = None
elif height > 0.00:
kw.size_hint_y = height
return kw
def alert(text,title='alert'):
global alert_widget
def close_alert(obj):
alert_widget.dismiss()
charsize = CSize(1)
if alert_widget is None:
bl = BoxLayout(orientation='horizontal')
msg = Label(font_size=charsize)
bl.add_widget(msg)
button = Button(size_hint_y=None,height=1.4*charsize,font_size=charsize,text='OK')
button.bind(on_press=close_alert)
bl.add_widget(button)
alert_widget = Popup(content=bl, size_hint=(0.9,0.6))
alert_widget.msg_widget = msg
alert_widget.msg_widget.text = str(text)
x='(%d,%d)mm' % screenPhysicalSize()
if isHandHold():
x = x + ':hh'
alert_widget.title = str(title) + x
alert_widget.open()
def StrConvert(s):
if not s.startswith('py::'):
return s
s = s[4:]
try:
ns = {}
exec('_n_=' + s,globals(),ns)
return ns['_n_']
except Exception as e:
print('----e=',e,'------------s=',s)
return s
def ArrayConvert(a):
s = []
for i in a:
s.append(JDConvert(i))
return s
def DictConvert(dic):
d = {}
for k,v in dic.items():
if k == 'widgettype':
d[k] = v
else:
d[k] = JDConvert(v)
return d
def JDConvert(dic):
nd = {}
if type(dic) == type(''):
return StrConvert(dic)
if type(dic) == type([]):
return ArrayConvert(dic)
if type(dic) == type({}):
return DictConvert(dic)
return dic
def getWidgetById(w,id):
if id[0] == '/':
app = App.get_running_ap()
if not hasattr('ids'):
return None
return app.ids.get(id[1:])
if id in ['self', '.' ]:
return w
if not hasattr(w,'ids'):
return None
return w.ids.get(id)
def CSize(x,y=None,name=None):
ks = KivySizes()
return ks.CSize(x,y=y,name=name)
def screenSize():
ks = KivySizes()
return ks.getScreenSize()
def screenPhysicalSize():
ks = KivySizes()
return ks.getScreenPhysicalSize()
def isHandHold():
ks = KivySizes()
return ks.isHandHold()
def absurl(url,parent):
if parent is None:
parent = ''
config = getConfig()
if url.startswith('http://'):
return url
if url.startswith('https://'):
return url
if url.startswith('file:///'):
return url
if url.startswith('/'):
return config.uihome + url
if url.startswith(config.uihome):
return url
if parent == '':
print('url=',url)
raise Exception('related url(%s) need a parent url' % url)
if parent.startswith(config.uihome):
parent = parent[len(config.uihome):]
paths = parent.split('/')
paths.pop()
for i in url.split('/'):
if i in [ '.', '' ]:
continue
if i == '..':
if len(paths) > 1:
paths.pop()
continue
paths.append(i)
return config.uihome + '/'.join(paths)

431
kivyblocks/vplayer.py Normal file
View File

@ -0,0 +1,431 @@
import os
import sys
from traceback import print_exc
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.video import Video
from kivy.uix.slider import Slider
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.label import Label
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, \
NumericProperty, DictProperty, OptionProperty
from .utils import *
from .baseWidget import PressableImage
class VPlayer(FloatLayout):
fullscreen = BooleanProperty(False)
exit = BooleanProperty(False)
stoped_play = BooleanProperty(False)
paused_play = BooleanProperty(False)
def __init__(self,vfile=None,loop=False,
openfile_img=None,
exit_img = None,
pause_img = None,
play_img = None,
mute_img = None,
track_img = None,
next_img = None,
replay_img = None,
can_openfile=False,
can_cut=False,
can_replay=False,
can_changevolume=True
):
super().__init__()
self.allow_screensaver = False
print(self,vfile)
self._video = Video(allow_stretch=True,pos_hint={'x': 0, 'y': 0},size_hint=(1,1))
self.add_widget(self._video)
self.loop = loop
self.openfile_img = openfile_img
self.can_openfile = can_openfile
self.can_replay = can_replay
self.can_cut = can_cut
self.can_changevolume = can_changevolume
if self.openfile_img:
self.can_openfile = True
self.exit_img = exit_img
self.play_img = play_img
self.pause_img = pause_img
self.mute_img = mute_img
self.track_img = track_img
self.next_img = next_img
self.replay_img = replay_img
self.ffplayer = None
self.menubar = None
self._popup = None
self.manualMode = False
self.old_path = os.getcwd()
self.pb = None
if vfile:
if type(vfile) == type([]):
self.playlist = vfile
else:
self.playlist = [vfile]
self.curplay = 0
self.play()
else:
self.playlist = []
self.curplay = -1
self._video.bind(eos=self.video_end)
self._video.bind(state=self.on_state)
self._video.bind(loaded=self.createProgressbar)
self._video.bind(on_touch_down=self.buildMenu)
self.register_event_type('on_playend')
def play(self,o=None,v=None):
if self.curplay >= 0:
self._video.source = self.playlist[self.curplay]
self._video.state = 'play'
def on_playend(self,o=None,v=None):
pass
def addPlaylist(self,lst):
self.playlist += lst
def video_end(self,t,v):
self.curplay += 1
if not self.loop and self.curplay >= len(self.playlist):
self.dispatch('on_playend')
return
self.curplay = self.curplay % len(self.playlist)
self._video.source = self.playlist[self.curplay]
self._video.state = 'play'
def totime(self,dur):
h = dur / 3600
m = dur % 3600 / 60
s = dur % 60
return '%02d:%02d:%02d' % (h,m,s)
def createProgressbar(self,obj,v):
if hasattr(self._video._video, '_ffplayer'):
self.ffplayer = self._video._video._ffplayer
if self.pb is None:
self.pb = BoxLayout(orientation='horizontal',
size_hint = (0.99,None),height=CSize(1.4))
btn_menu=Button(text='M',size_hint=(None,None),text_size=CSize(1,1),size=CSize(1.2,1,2))
btn_menu.bind(on_press=self.buildMenu)
btn_volume=Button(text='V',size_hint=(None,None),text_size=CSize(1,1),size=CSize(1.2,1,2))
btn_volume.bind(on_press=self.volumeControl)
self.curposition = Label(text='0',width=CSize(4),
size_hint_x=None)
self.curposition.align='right'
self.maxposition = Label(text=self.totime(self._video.duration),
width=CSize(4),size_hint_x=None)
self.maxposition.align = 'left'
# self.slider = ProgressBar(value=0,max=max)
self.slider = Slider(min=0,
max=self._video.duration,
value=0,
orientation='horizontal',
step=0.01)
self.slider.bind(on_touch_down=self.enterManualMode)
self.slider.bind(on_touch_up=self.endManualMode)
self.manual_mode=False
self.add_widget(self.pb)
self.pb.add_widget(btn_menu)
self.pb.add_widget(self.curposition)
self.pb.add_widget(self.slider)
self.pb.add_widget(self.maxposition)
# self.pb.add_widget(self.btn_volume)
self.pb.pos = (0,0)
Clock.schedule_interval(self.update_slider,1)
def volumeControl(self,obj,v):
self.volumeCtrl = BoxLayout(orientation='vertical',size_hint=(None,None),size=CSize(1.4,10))
self.pos = self.width - self.volumeCtrl.width,CSize(1.4)
self.add_widget(self.volumeCtrl)
btn_mute = Button(text='Mute',size_hint_y=None,height=CSize(1.4))
if self._video.volume <= 0.001:
btn_mute.text = 'Sound'
btn_menu.bind(on_press=self.mute)
self.volumeCtrl.add_widget(btn_mute)
slider = Slider(min=0,
max=1,
value=self._video.volume,
orientation='vertical',
step=0.01)
slider.bind(on_value=self.setVolume)
self.volumeCtrl.add_widegt(slider)
btn_audioswitch = Button(text='track',size_hint_y=None,height=CSize(1.4))
btn_audioswitch.bind(on_press=self.audioswitch)
self.volumeCtrl.add_widget(btn_audioswitch)
def enterManualMode(self,obj,touch):
if not self.slider.collide_point(*touch.pos):
return
self.manualMode = True
def endManualMode(self,obj,touch):
if not self.manualMode:
return
if self._video.duration < 0.0001:
return
self._video.seek(self.slider.value/self._video.duration)
self.manualMode = False
def update_slider(self,t):
self.curposition.text = self.totime(self._video.position)
if not self.manualMode:
self.slider.value = self._video.position
self.slider.max = self._video.duration
self.maxposition.text = self.totime(self._video.duration)
def beforeDestroy(self):
try:
self.pause()
except Exception as e:
print_exc()
return True
def on_state(self,o,v):
print('onstate()',o,v,self._video.state)
def on_fullscreen(self, instance, value):
window = self.get_parent_window()
print('window.size=',window.size)
if not window:
Logger.warning('VideoPlayer: Cannot switch to fullscreen, '
'window not found.')
if value:
self.fullscreen = False
return
if not self.parent:
Logger.warning('VideoPlayer: Cannot switch to fullscreen, '
'no parent.')
if value:
self.fullscreen = False
return
if value:
self._fullscreen_state = state = {
'parent': self.parent,
'pos': self.pos,
'size': self.size,
'pos_hint': self.pos_hint,
'size_hint': self.size_hint,
'window_children': window.children[:]}
# remove all window children
for child in window.children[:]:
window.remove_widget(child)
# put the video in fullscreen
if state['parent'] is not window:
state['parent'].remove_widget(self)
window.add_widget(self)
# ensure the video widget is in 0, 0, and the size will be
# readjusted
self.pos = (0, 0)
self.size = (100, 100)
self.pos_hint = {}
self.size_hint = (1, 1)
else:
state = self._fullscreen_state
window.remove_widget(self)
for child in state['window_children']:
window.add_widget(child)
self.pos_hint = state['pos_hint']
self.size_hint = state['size_hint']
self.pos = state['pos']
self.size = state['size']
if state['parent'] is not window:
state['parent'].add_widget(self)
def buildMenu(self,obj,touch):
if not self.collide_point(*touch.pos):
print('not inside the player')
return
if touch.is_double_tap:
self.fullscreen = False if self.fullscreen else True
if self.menubar:
self.remove_widget(self.menubar)
print('doube_tap')
return
if self.menubar:
print('delete menubar')
self.remove_widget(self.menubar)
self.menubar = None
return
self.menubar = BoxLayout(orientation='horizontal',
size_hint_y=None,height=CSize(1.4))
self.btn_pause = PressableImage(source=blockImage('pause.jpg'),
size_hint=(None,None),
size=CSize(3,3)
)
if self._video.state == 'pause':
self.btn_pause.source = blockImage('play.jpg')
self.btn_pause.bind(on_press=self.pause)
self.menubar.add_widget(self.btn_pause)
self.btn_mute = PressableImage(source=blockImage('mute.jpg'),
size_hint=(None,None),
size=CSize(3,3)
)
self.btn_mute.bind(on_press=self.mute)
self.menubar.add_widget(self.btn_mute)
if self.can_openfile:
btn_open = Button(text='open')
btn_open.bind(on_press=self.openfile)
self.menubar.add_widget(btn_open)
btn_cut = PressableImage(source=blockImage('next.jpg'),
size_hint=(None,None),
size=CSize(3,3)
)
btn_cut.bind(on_press=self.endplay)
self.menubar.add_widget(btn_cut)
btn_replay = PressableImage(source=blockImage('replay.jpg'),
size_hint=(None,None),
size=CSize(3,3)
)
btn_replay.bind(on_press=self.replay)
self.menubar.add_widget(btn_replay)
self.btn_audioswitch = PressableImage( \
source=blockImage('musictrack.jpg'),
size_hint=(None,None),
size=CSize(3,3)
)
self.btn_audioswitch.bind(on_press=self.audioswitch)
self.menubar.add_widget(self.btn_audioswitch)
self.menubar.pos = CSize(0,1.4)
self.add_widget(self.menubar)
def endplay(self,btn):
self._video.seek(1.0,precise=True)
def replay(self,btn):
self._video.seek(0.0,precise=True)
def hideMenu(self):
self._popup.dismiss()
self.remove_widget(self.menubar)
self.menubar = None
def audioswitch(self,btn):
if self.ffplayer is not None:
self.ffplayer.request_channel('audio')
def setVolume(self,obj,v):
self._video.volume = v
def mute(self,btn):
if self._video.volume > 0.001:
self.old_volume = self._video.volume
self._video.volume = 0.0
btn.source = blockImage('volume.jpg')
else:
self._video.volume = self.old_volume
btn.source = blockImage('mute.jpg')
def stop(self):
print(self)
self._video.state = 'stop'
def on_disabled(self,o,v):
if self.disabled:
self.stop()
del self._video
def pause(self,t=None):
if self._video.state == 'play':
self._video.state = 'pause'
self.btn_pause.source = blockImage('play.jpg')
else:
self._video.state = 'play'
self.btn_pause.source = blockImage('pause.jpg')
def openfile(self,t):
if self._popup is None:
def vfilter(path,filename):
vexts = ['.avi',
'.mpg',
'.mpe',
'.mpeg',
'.mlv',
'.dat',
'.mp4',
'.flv',
'.mov',
'.rm',
'.mkv',
'.rmvb',
'.asf',
'.3gp'
]
for ext in vexts:
if filename.endswith(ext):
return True
return False
c = BoxLayout(orientation='vertical')
self.file_chooser = FileChooserListView()
self.file_chooser.filters = [vfilter]
self.file_chooser.multiselect = True
self.file_chooser.path = self.old_path
self.file_chooser.bind(on_submit=self.loadFilepath)
c.add_widget(self.file_chooser)
b = BoxLayout(size_hint_y=None,height=35)
c.add_widget(b)
cancel = Button(text='Cancel')
cancel.bind(on_press=self.cancelopen)
load = Button(text='load')
load.bind(on_press=self.playfile)
b.add_widget(load)
b.add_widget(cancel)
self._popup = Popup(title='Open file',content=c,size_hint=(0.9,0.9))
self._popup.open()
def cancelopen(self,obj):
self.hideMenu()
def loadFilepath(self,obj,fpaths,evt):
print('fp=',fpaths,type(fpaths),'evt=',evt)
self.hideMenu()
self.playlist = fpaths
self.curplay = 0
self._video.source = self.playlist[self.curplay]
self._video.state = 'play'
def playfile(self,obj):
print('obj')
self.hideMenu()
self.playlist = []
for f in self.file_chooser.selection:
fp = os.path.join(self.file_chooser.path,f)
self.playlist.append(fp)
self.curplay = 0
self._video.source = self.playlist[self.curplay]
self._video.state = 'play'
if __name__ == '__main__':
class MyApp(App):
def build(self):
vf = None
if len(sys.argv) > 1:
vf = sys.argv[1:]
self.player = VPlayer(vfile=vf,
loop=True,
can_openfile=True,
can_move = True,
can_cut=True,
can_replay=True,
can_changevolume = True
)
return self.player
MyApp().run()

View File

@ -0,0 +1,25 @@
from kivy.utils import platform
from .binstateimage import BinStateImage
from .jsoncodeinput import JsonCodeInput
from .inputext import FloatInput,IntegerInput,StrInput,SelectInput, BoolInput, AmountInput, Password
from .scrollwidget import ScrollWidget
from .messager import Messager
__all__ = [
BinStateImage,
JsonCodeInput,
FloatInput,
AmountInput,
Password,
BoolInput,
IntegerInput,
StrInput,
SelectInput,
ScrollWidget,
Messager,
]
if platform == 'android':
print('***********************************8')
from .phonebutton import PhoneButton
from .androidwebview import AWebView
__all__ = __all__ + [PhoneButton, AWebView]

View File

@ -0,0 +1,38 @@
from kivy.uix.widget import Widget
from kivy.clock import Clock
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.kivy.android.PythonActivity').mActivity
class AWebView(Widget):
def __init__(self, url=None):
super(AWebView, self).__init__()
self.baseUrl = url
self.create_webview()
# Clock.schedule_once(self.create_webview, 0)
@run_on_ui_thread
def create_webview(self, *args):
print('create_webview() begining')
self.webview = WebView(activity)
settings = self.webview.getSettings()
settings.setJavaScriptEnabled(True)
settings.setUseWideViewPort(True) # enables viewport html meta tags
settings.setLoadWithOverviewMode(True) # uses viewport
settings.setSupportZoom(True) # enables zoom
settings.setBuiltInZoomControls(True) # enables zoom controls
wvc = WebViewClient()
self.webview.setWebViewClient(wvc)
activity.setContentView(self.webview)
if self.baseUrl is not None:
self.webview.loadUrl(self.baseUrl)
print('go to the url=',self.baseUrl)
print('create_webview() finished')
def open(self,url):
self.webview.loadUrl(url)

View File

@ -0,0 +1,43 @@
from kivy.uix.image import Image,AsyncImage
from kivy.uix.button import Button
from kivy.graphics import Color
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class BinStateImage(Button):
binstate = NumericProperty(1)
def __init__(self,source1='../images/bullet_arrow_right.png',source2='../images/bullet_arrow_down.png',**kv):
self.bin_state_images = [
source1,
source2
]
kv.update({'source':source1})
super(BinStateImage,self).__init__()
self.myImgWidget = Image(**kv)
self.size = self.myImgWidget.size
#self.background_color = Color(1,0,0,1)
self.bind(on_release=self.changeState)
def changeState(self,instance):
#print('on_release fired')
if instance.binstate == 1:
instance.binstate = 2
else: instance.binstate = 1
self.myImgWidget.source = self.bin_state_images[instance.binstate-1]
self.myImgWidget.reload()
if __name__ == '__main__':
from kivy.app import App
class MyApp(App):
def build(self):
m = BoxLayout(orientation='vertical')
img = BinStateImage()
img.bind(binstate=self.showState)
m.add_widget(img)
b = Button(text='GGGG')
m.add_widget(b)
return m
def showState(self,img,v):
pass #print('cur state=',img.binstate,v)
MyApp().run()

View File

@ -0,0 +1,89 @@
import android
import android.activity
from os import remove
from jnius import autoclass, cast
from plyer.facades import Camera
from plyer.platforms.android import activity
Intent = autoclass('android.content.Intent')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
MediaStore = autoclass('android.provider.MediaStore')
Uri = autoclass('android.net.Uri')
class AndroidCamera(Camera):
def _take_picture(self, on_complete, filename=None):
assert(on_complete is not None)
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)
android.activity.bind(on_activity_result=self._on_activity_result)
intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
uri = Uri.parse('file://' + filename)
parcelable = cast('android.os.Parcelable', uri)
intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
activity.startActivityForResult(intent, 0x123)
def _take_video(self, on_complete, filename=None):
assert(on_complete is not None)
self.on_complete = on_complete
self.filename = filename
android.activity.unbind(on_activity_result=self._on_activity_result)
android.activity.bind(on_activity_result=self._on_activity_result)
intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
uri = Uri.parse('file://' + filename)
parcelable = cast('android.os.Parcelable', uri)
intent.putExtra(MediaStore.EXTRA_OUTPUT, parcelable)
# 0 = low quality, suitable for MMS messages,
# 1 = high quality
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)
activity.startActivityForResult(intent, 0x123)
def _on_activity_result(self, requestCode, resultCode, intent):
if requestCode != 0x123:
return
android.activity.unbind(on_activity_result=self._on_activity_result)
if self.on_complete(self.filename):
self._remove(self.filename)
def _remove(self, fn):
try:
remove(fn)
except OSError:
pass
def instance():
return AndroidCamera()

View File

View File

@ -0,0 +1,22 @@
from kivy.event import EventDispatcher
class DataLoader(EventDispatcher):
def __init__(self,**kw):
self.register_event_type('on_loaded')
self.register_event_type('on_loaderror')
super(DataLoader,self).__init__(**kw)
def loadData(self):
pass
def dataLoaded(self,d):
self.dispatch('on_loaded',d)
def loadError(self,e):
self.dispatch('on_loaderror',e)
def on_loaded(self,d):
pass #print('on_loaded,data=',d)
def on_loaderror(self,a,e):
pass #print('error:',e)

View File

@ -0,0 +1,13 @@
import codecs
from .dataloader import DataLoader
class FileDataLoader(DataLoader):
def loadData(self,filename):
try:
with codecs.open(filename,'r','utf8') as f:
self.dataLoaded(text)
return f.read()
except Exception as e:
self.loadError(e)

View File

@ -0,0 +1,47 @@
import sys
sys.path.append('..')
sys.path.append('.')
import time
class HttpDataLoader(DataLoader):
def __init__(self):
super(HttpDataLoader,self).__init__()
self.hc = HttpClient()
async def loadData(self,url,method='GET',params={},headers={}):
try:
resp = None
if method=='GET':
resp = await self.hc.get(url,params=params,headers=headers)
else:
resp = await self.hc.post(url,data=params,headers=headers)
return resp
except Exception as e:
print('loadData(%s) Error ' % url,e)
self.loadError(e)
if __name__ == '__main__':
import sys
from async_app import AsyncApp, wait_coro
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MyApp(AsyncApp):
def build(self):
root = BoxLayout(orientation='vertical')
btn = Button(text='get Remote data',size_hint_y=None,height=44)
btn.bind(on_release=self.getData)
root.add_widget(btn)
self.txt = TextInput(multiline=True,readonly=True)
root.add_widget(self.txt)
return root
def getData(self,btn):
url = sys.argv[1] if len(sys.argv)>1 else 'https://www.baidu.com'
hdl = HttpDataLoader()
hdl.bind(on_loaded=self.showData)
wait_coro(hdl.loadData,url)
self.loop.call_later(0.001,hdl.loadData(url))
def showData(self,instance,x):
self.txt.text = x
MyApp().run()

242
kivyblocks/widgetExt/inputext.py Executable file
View File

@ -0,0 +1,242 @@
import sys
import re
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.switch import Switch
from kivy.metrics import sp,dp
from kivy.app import App
from kivy.properties import BooleanProperty
from ..threadcall import HttpClient
from ..utils import CSize
class BoolInput(Switch):
change = BooleanProperty(False)
def __init__(self,**kw):
a = DictObject()
if kw.get('defaultvalue',None) is None:
a.active = False
else:
a.active = kw.get('defaultvalue')
if kw.get('value',None) is not None:
a.active = kw.get('value')
super().__init__(**a)
self.bind(active=on_active)
self.bind(change=self.on_change)
def on_change(self,t,v):
pass
def on_active(self,t,v):
change = True
def getValue(self):
return self.active
def setValue(self,v):
self.active = v
class StrInput(TextInput):
change = BooleanProperty(False)
def __init__(self,**kv):
if kv is None:
kv = {}
a = {
# "allow_copy":True,
"font_size":CSize(1),
"multiline":False,
"halign":"left",
"hint_text":"",
"size_hint_y":None,
"height":CSize(2)
}
if kv.get('tip'):
a['hint_text'] = kv['tip']
a.update(kv)
a['multiline'] = False
super(StrInput,self).__init__(**a)
self.bind(focus=self.on_focus)
self.bind(text=self.on_text)
self.bind(change=self.on_change)
def on_change(self,t,v):
if v:
pass
def on_text(self,t,v):
self.change = True
def on_focus(self,t,v):
self.change = False
def getValue(self):
return self.text
def setValue(self,v):
self.text = v
class Password(StrInput):
def __init__(self, **kw):
kw['password'] = True
super().__init__(**kw)
class IntegerInput(StrInput):
def __init__(self,**kw):
a = {}
a.update(kw)
a['halign'] = 'right'
super().__init__(**a)
pat = re.compile('[^0-9]')
def insert_text(self, substring, from_undo=False):
pat = self.pat
s = re.sub(pat, '', substring)
return StrInput.insert_text(self,s, from_undo=from_undo)
class FloatInput(IntegerInput):
pat = re.compile('[^0-9]')
def filter(self,substring):
pat = self.pat
if '.' in self.text:
s = re.sub(pat, '', substring)
else:
s = '.'.join([re.sub(pat, '', s) for s in substring.split('.', 1)])
return s
def insert_text(self, substring, from_undo=False):
s = self.filter(substring)
return StrInput.insert_text(self,s, from_undo=from_undo)
class AmountInput(FloatInput):
def filter(self,substring):
s = super(AmountInput,self).filter(substring)
a = s.split('.')
b = a[0]
if len(b)>3:
k = []
while len(b)>3:
x = b[-3:]
k.insert(0,x)
b = b[:-3]
a[0] = ','.join(k)
s = '.'.join(a)
return '.'.join(a)
def insert_text(self, substring, from_undo=False):
s = self.filter(substring)
return StrInput.insert_text(self,s, from_undo=from_undo)
class MyDropDown(DropDown):
def __init__(self,**kw):
super(MyDropDown,self).__init__()
self.options = kw
self.textField = kw.get('textField',None)
self.valueField = kw.get('valueField',None)
if kw.get('url') is not None:
self.url = kw.get('url')
self.setDataByUrl(self.url)
else:
self.si_data = kw.get('data')
self.setData(self.si_data)
self.bind(on_select=lambda instance, x: self.selectfunc(x))
def selectfunc(self,v):
f = self.options.get('on_select')
if f is not None:
return f(v)
def getTextByValue(self,v):
for d in self.si_data:
if d[self.valueField] == v:
return d[self.textField]
return str(v)
def getValueByText(self,v):
for d in self.si_data:
if d[self.textField] == v:
return d[self.valueField]
return ''
def setData(self,data):
self.si_data = data
self.clear_widgets()
for d in data:
dd = (d[self.valueField],d[self.textField])
b = Button(text=d[self.textField],font_size=CSize(1),
size_hint_y=None,
height=CSize(1.8))
setattr(b,'kw_data',dd)
b.bind(on_release=lambda btn: self.select(btn.kw_data))
self.add_widget(b)
#print(dd)
def setDataByUrl(self,url,params={}):
def x(obj,resp):
if resp.status_code == 200:
d = resp.json()
self.setData(d)
app = App.get_running_app()
app.hc.get(url,params=params,callback=x)
def showme(self,w):
#print('show it ',w)
self.target = w
self.open(w)
class SelectInput(BoxLayout):
def __init__(self,**kw):
super(SelectInput,self).__init__(orientation='horizontal', size_hint_y=None,height=CSize(1.8))
self.tinp = StrInput()
self.tinp.readonly = True
newkw = {}
newkw.update(kw)
newkw.update({'on_select':self.setData})
self.dropdown = MyDropDown(**newkw)
if kw.get('value'):
self.si_data = kw.get('value')
self.text = self.dropdown.getTextByValue(self.si_data)
else:
self.si_data = ''
self.text = ''
self.add_widget(self.tinp)
self.tinp.bind(focus=self.showDropdown)
def showDropdown(self,instance,yn):
# if self.collide_point(*touch.pos):
if yn:
self.tinp.focus = False
self.dropdown.showme(self)
def setData(self,d):
self.tinp.si_data = d[0]
self.tinp.text = d[1]
def setValue(self,v):
self.tinp.si_value = v
self.tinp.text = self.dropdown.getTextByValue(v)
def getValue(self):
return self.tinp.si_value
if __name__ == '__main__':
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class MyApp(App):
def build(self):
root = BoxLayout(orientation='vertical')
x = SelectInput(width=CSize(15),value='1',data=[{'value':'1','text':'ban'},{'value':'0','text':'nu'}],textField='text',valueField='value')
root.add_widget(x)
b = Button(text='drop', size_hint=(None, None))
root.add_widget(b)
dd = MyDropDown(width=CSize(15),value='1',data=[{'value':'1','text':'nan'},{'value':'0','text':'nu'}],textField='text',valueField='value',on_select=x.setData)
b.bind(on_release=dd.showme)
return root
MyApp().run()

View File

@ -0,0 +1,6 @@
from pygments.lexers.data import JsonLexer
from kivy.uix.codeinput import CodeInput
class JsonCodeInput(CodeInput):
def __init__(self,**kw):
super(JsonCodeInput,self).__init__(lexer = JsonLexer())

View File

@ -0,0 +1,27 @@
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from appPublic.Singleton import SingletonDecorator
@SingletonDecorator
class Messager:
def __init__(self):
self.w = Popup(content=BoxLayout(orientation='vertical'),
title="Error info",size_hint=(0.8,0.8))
self.messager = TextInput(size=self.w.content.size,
multiline=True,readonly=True)
self.w.content.add_widget(self.messager)
def show_error(self,e):
self.w.title = "error"
self.messager.text = str(e)
self.w.open()
def show_info(self,info):
self.w.title = "info"
self.messager.text = str(info)
self.w.open()
def hide_error(self):
self.w.dismiss()

View File

@ -0,0 +1,31 @@
from kivy.uix.button import Button
from jnius import autoclass
from jnius import cast
from .messager import Messager
class PhoneButton(Button):
def __init__(self,**kw):
self.options = kw
self.phone_number = kw.get('phone_number')
if self.phone_number is None:
raise Exception('PhoneButton,miss phone_number')
del self.options['phone_number']
self.options.update({'text':'call'})
super(PhoneButton,self).__init__(**self.options)
self.bind(on_release=self.makecall)
def makecall(self,inst):
try:
self.disabled = True
Intent = autoclass('android.content.Intent')
Uri = autoclass('android.net.Uri')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
intent = Intent(Intent.ACTION_CALL)
intent.setData(Uri.parse("tel:" + self.phone_number))
currentActivity = cast('android.app.Activity',
PythonActivity.mActivity)
currentActivity.startActivity(intent)
except Exception as e:
msger = Messager()
msger.show_error(e)

View File

@ -0,0 +1,46 @@
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import Color, Ellipse,Rectangle
class ScrollWidget(ScrollView):
def __init__(self,**kw):
self._inner = BoxLayout(orientation='vertical',padding=2,
spacing=2,size_hint=(None,None))
super(ScrollWidget,self).__init__(**kw)
self._inner.bind(
minimum_height=self._inner.setter('height'))
self._inner.bind(
minimum_width=self._inner.setter('width'))
super(ScrollWidget,self).add_widget(self._inner)
def add_widget(self,widget,**kw):
a = self._inner.add_widget(widget,**kw)
return a
def clear_widgets(self,**kw):
a = self._inner.clear_widgets(**kw)
def remove_widget(self,widget,**kw):
a = self._inner.remove_widget(widget,**kw)
return a
if __name__ == '__main__':
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
import codecs
class MyApp(App):
def build(self):
root = ScrollWidget(size=(400,400),
pos_hint={'center_x': .5, 'center_y': .5}
)
with codecs.open(__file__,'r','utf-8') as f:
txt = f.read()
lines = txt.split('\n')
for l in lines:
root.add_widget(Label(text=l,color=(1,1,1,1),size_hint=(None,None),size=(1200,40)))
return root
MyApp().run()

View File

@ -0,0 +1,32 @@
from .dataloader import DataLoader
from appPublic.sqlorAPI import runSQLIterator,DBPools,runSQLPaging
from twisted.internet import defer, reactor
class SQLDataLoader(DataLoader):
def __init__(self,dbdesc,**kw):
self.dbdesc = dbdesc
super(DataLoader,self).__init__(**kw)
def _run(self,ns):
@runSQLIterator
def _sql(db,ns):
return self.dbdesc['sqldesc']
d = [ i for i in self._sql(self.dbdesc['db'],ns) ]
return d
def loadData(self,ns):
d = defer.maybeDeferred(self._run,ns)
d.callback(self.dataLaoded)
return d
class SQLPaggingLoader(SQLDataLoader):
def _run(self,ns):
@runSQLIterator
def _sql(db,ns):
return self.dbdesc['sqldesc']
d = self._sql(self.dbdesc['db'],ns)
return d

View File

@ -0,0 +1,24 @@
"""
tree description json format:
{
"url":a http(s) url for get data,
"checkbox":boolean, show a checkbox before text
"data":if undefined url,use data to construct the tree
"params":parameters attached to the http(s) request via url
"headers":headers need to set for the http request via url
"height":widget 's height
"width":widget's width
}
data structure :
{
"id":identified field,
"widgettype":widget type,
...other data...
"__children__":[
]
}
"""
from .scrollwidget import ScrollWidget
from kivy.uix.treeview import TreeView

9
requirements.txt Normal file
View File

@ -0,0 +1,9 @@
jinja2
kivy
ffpyplayer
pillow
requests
git+https://github.com/yumoqing/kivycalendar
git+https://github.com/yumoqing/appPublic
git+https://github.com/yumoqing/sqlor
git+https://github.com/yumoqing/ahserver

47
setup.py Executable file
View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from distutils.core import setup
from setuptools import setup, find_packages
# usage:
# python setup.py bdist_wininst generate a window executable file
# python setup.py bdist_egg generate a egg file
# Release information about eway
version = "0.0.4"
description = "kivy blocks is a tool to build kivy ui with json format uidesc files"
author = "yumoqing"
email = "yumoqing@icloud.com"
packages=find_packages()
package_data = {
"kivyblocks":['imgs/*.png', 'imgs/*.gif','imgs/*.jpg','ttf/*.ttf', 'ui/*.uidesc' ],
}
setup(
name="kivyblocks",
version=version,
# uncomment the following lines if you fill them out in release.py
description=description,
author=author,
author_email=email,
install_requires=[
"kivy",
"appPublic",
"sqlor"
],
packages=packages,
package_data=package_data,
keywords = [
],
classifiers = [
'Development Status :: 3 - Alpha',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.5',
'Topic :: Software Development :: Libraries :: Python Modules'
],
platforms= 'any'
)

305
test/buildozer.spec Normal file
View File

@ -0,0 +1,305 @@
[app]
# (str) Title of your application
title = UI blocks
# (str) Package name
package.name = blocks
# (str) Package domain (needed for android/ios packaging)
package.domain = com.bsppo
# (str) Source code where the main.py live
source.dir = .
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,json
# (list) List of inclusions using pattern matching
source.include_patterns = conf/*.json
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.2
# (str) Application versioning (method 2)
# version.regex = __version__ = ['"](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = pillow, ffmpeg, ffpyplayer, pygments,markupsafe,jinja2,requests,python3,kivy,git+https://github.com/yumoqing/appPublic,git+https://github.com/yumoqing/kivycalendar,git+https://github.com/yumoqing/kivyblocks
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (list) Garden requirements
#garden_requirements =
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = all
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 1
# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (list) Permissions
android.permissions = INTERNET, CAMERA, VIBRATE, CALL_PHONE, CALL_PRIVILEGED
# (int) Target Android API, should be as high as possible.
#android.api = 27
# (int) Minimum API your APK will support.
#android.minapi = 21
# (int) Android SDK version to use
#android.sdk = 20
# (str) Android NDK version to use
#android.ndk = 17c
# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =
# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =
# (list) Java classes to add as activities to the manifest.
#android.add_activites = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
build_dir = /home/ymq/.buildozer_build
# (str) Path to build output (i.e. .apk, .ipa) storage
bin_dir = /home/ymq/pydev/github/blockserver/wwwroot/android_apks
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

21
test/conf/config.json Normal file
View File

@ -0,0 +1,21 @@
{
"font_sizes":{
"smallest":1.5,
"small":2.5,
"normal":3.5,
"large":4.5,
"huge":5.5,
"hugest":6.5
},
"font_name":"normal",
"uihome":"http://www.bsppo.com:10080",
"udws":[
"/udw/udws.uidesc"
],
"root":{
"widgettype":"urlwidget",
"options":{
"url":"/toolpage.ui"
}
}
}

19
test/main.py Normal file
View File

@ -0,0 +1,19 @@
import sys
import os
from appPublic.folderUtils import ProgramPath
from appPublic.jsonConfig import getConfig
from kivyblocks.blocksapp import BlocksApp
if __name__ == '__main__':
pp = ProgramPath()
workdir = pp
if len(sys.argv) > 1:
workdir = sys.argv[1]
print('ProgramPath=',pp,'workdir=',workdir)
config = getConfig(workdir,NS={'workdir':workdir,'ProgramPath':pp})
myapp = BlocksApp()
myapp.run()
myapp.workers.running = False

76
test/rv.py Normal file
View File

@ -0,0 +1,76 @@
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivyblocks.utils import CSize
from kivyblocks.widgetExt.scrollwidget import ScrollWidget
from kivy.uix.scrollview import ScrollView
from kivy.clock import Clock
from kivyblocks.responsivelayout import VResponsiveLayout
class VGridLayout(GridLayout):
def __init__(self,**kw):
kwargs = kw.copy()
if kwargs.get('box_width'):
del kwargs['box_width']
if not kwargs.get('cols'):
kwargs['cols'] = 1
self.box_width = CSize(kw.get('box_width',0))
self.options = kw
super().__init__(**kwargs)
self.setColsTask = None
def on_size(self,o,s):
if self.setColsTask is not None:
self.setColsTask.cancel()
self.setColsTask = Clock.schedule_once(self.setCols,0.2)
def setCols(self,t):
if self.box_width == 0:
self.box_width = self.width / self.options.get('cols',1)
else:
self.cols = int(self.width / self.box_width)
def setBoxWidth(self,w):
self.box_width = w
class ResponsiveLayout(ScrollView):
def __init__(self, cols=2, box_width=15, **options):
self.options = options
super().__init__(**options)
self._inner = VGridLayout(cols=cols,box_width=box_width)
class Box(BoxLayout):
def __init__(self,**kw):
super().__init__(**kw)
self.size_hint = (None, None)
self.height = CSize(kw.get('height',6))
self.width = CSize(kw.get('width',8))
class MyApp(App):
def build(self):
r = BoxLayout(orientation='vertical')
s = ResponsiveLayout(box_width=15,size_hint=(1,1))
b = Button(text='add box', font_size=CSize(2),
size_hint_y=None,
height=CSize(2.8)
)
r.add_widget(b)
r.add_widget(s)
self.l = s
self.box_cnt = 0
b.bind(on_press=self.add_box)
return r
def add_box(self,o):
box = Box(width=15)
t = Label(text='box ' + str(self.box_cnt))
box.add_widget(t)
self.l.add_widget(box)
self.box_cnt += 1
if __name__ == '__main__':
MyApp().run()

61
test/scroll.py Normal file
View File

@ -0,0 +1,61 @@
from kivy.app import App
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
text="""
The Widget class is the base class required for creating Widgets. This widget class was designed with a couple of principles in mind:
Event Driven
Widget interaction is built on top of events that occur. If a property changes, the widget can respond to the change in the on_<propname> callback. If nothing changes, nothing will be done. Thats the main goal of the Property class.
Separation Of Concerns (the widget and its graphical representation)
Widgets dont have a draw() method. This is done on purpose: The idea is to allow you to create your own graphical representation outside the widget class. Obviously you can still use all the available properties to do that, so that your representation properly reflects the widgets current state. Every widget has its own Canvas that you can use to draw. This separation allows Kivy to run your application in a very efficient manner.
Bounding Box / Collision
Often you want to know if a certain point is within the bounds of your widget. An example would be a button widget where you only want to trigger an action when the button itself is actually touched. For this, you can use the collide_point() method, which will return True if the point you pass to it is inside the axis-aligned bounding box defined by the widgets position and size. If a simple AABB is not sufficient, you can override the method to perform the collision checks with more complex shapes, e.g. a polygon. You can also check if a widget collides with another widget with collide_widget().
We also have some default values and behaviors that you should be aware of:
A Widget is not a Layout: it will not change the position or the size of its children. If you want control over positioning or sizing, use a Layout.
The default size of a widget is (100, 100). This is only changed if the parent is a Layout. For example, if you add a Label inside a Button, the label will not inherit the buttons size or position because the button is not a Layout: its just another Widget.
The default size_hint is (1, 1). If the parent is a Layout, then the widget size will be the parent layouts size.
on_touch_down(), on_touch_move(), on_touch_up() dont do any sort of collisions. If you want to know if the touch is inside your widget, use collide_point().
"""
class MyApp(App):
def build(self):
x = ScrollView()
b = BoxLayout(orientation="vertical",
size_hint=(None,None)
)
b.bind(minimum_height=b.setter('height'))
b.bind(minimum_width=b.setter('width'))
self.textout(b)
x.do_scroll_x = True
x.do_scroll_y = True
x.scroll_x = 1
x.scroll_y = 1
print(b.size,x.size)
x.add_widget(b)
return x
def textout(self,b):
txt = text * 50
maxwidth = 0
for l in txt.split('\n'):
l = Label(text=l,text_size=(None,40),size_hint=(None,None),height=40)
l.width = l.texture_size[0]
l.valign = "middle"
l.align = "left"
print(l.width)
if maxwidth < l.width:
maxwidth = l.width
b.add_widget(l)
b.width = 4500
MyApp().run()

384
test/ttdg.py Normal file
View File

@ -0,0 +1,384 @@
import os
import sys
from functools import partial
from kivyblocks.dg import DataGrid
from kivy.app import App
from kivy.clock import Clock
from appPublic.folderUtils import ProgramPath
from appPublic.jsonConfig import getConfig
from appPublic.timecost import TimeCost
from kivyblocks.blocksapp import BlocksApp
if __name__ == '__main__':
pp = ProgramPath()
workdir = pp
if len(sys.argv) > 1:
workdir = sys.argv[1]
print('ProgramPath=',pp,'workdir=',workdir)
config = getConfig(workdir,NS={'workdir':workdir,'ProgramPath':pp})
desc = {
"paging":True,
"fields":[
{
"freeze":True,
"width":8,
"name":"name",
"label":"Name",
"datatype":"str"
},
{
"width":50,
"name":"subject",
"label":"Subject",
"datatype":"str"
},
{
"width":10,
"name":"age",
"label":"Age",
"datatype":"int"
},
{
"width":10,
"name":"gender",
"label":"Gender",
"datatype":"int"
},
{
"width":10,
"name":"grade",
"label":"Grade",
"datatype":"str"
},
{
"width":10,
"name":"since",
"label":"Since",
"datatype":"date"
}
]
}
data = """name1 subject1 34 1 1 1992
name2 subject2 34 1 1 1992
name3 subject3 34 1 1 1992
name4 subject4 34 1 1 1992
name5 subject5 34 1 1 1992
name6 subject6 34 1 1 1992
name7 subject7 34 1 1 1992
name8 subject8 34 1 1 1992
name9 subject9 34 1 1 1992
name10 subject10 34 1 1 1992
name11 subject11 34 1 1 1992
name12 subject12 34 1 1 1992
name13 subject13 34 1 1 1992
name14 subject14 34 1 1 1992
name15 subject15 34 1 1 1992
name16 subject16 34 1 1 1992
name17 subject17 34 1 1 1992
name18 subject18 34 1 1 1992
name19 subject19 34 1 1 1992
name20 subject20 34 1 1 1992
name21 subject21 34 1 1 1992
name22 subject22 34 1 1 1992
name23 subject23 34 1 1 1992
name24 subject24 34 1 1 1992
name25 subject25 34 1 1 1992
name26 subject26 34 1 1 1992
name27 subject27 34 1 1 1992
name28 subject28 34 1 1 1992
name29 subject29 34 1 1 1992
name30 subject30 34 1 1 1992
name31 subject31 34 1 1 1992
name32 subject32 34 1 1 1992
name33 subject33 34 1 1 1992
name34 subject34 34 1 1 1992
name35 subject35 34 1 1 1992
name36 subject36 34 1 1 1992
name37 subject37 34 1 1 1992
name38 subject38 34 1 1 1992
name39 subject39 34 1 1 1992
name40 subject40 34 1 1 1992
name41 subject41 34 1 1 1992
name42 subject42 34 1 1 1992
name43 subject43 34 1 1 1992
name44 subject44 34 1 1 1992
name45 subject45 34 1 1 1992
name46 subject46 34 1 1 1992
name47 subject47 34 1 1 1992
name48 subject48 34 1 1 1992
name49 subject49 34 1 1 1992
name50 subject50 34 1 1 1992
name51 subject51 34 1 1 1992
name52 subject52 34 1 1 1992
name53 subject53 34 1 1 1992
name54 subject54 34 1 1 1992
name55 subject55 34 1 1 1992
name56 subject56 34 1 1 1992
name57 subject57 34 1 1 1992
name58 subject58 34 1 1 1992
name59 subject59 34 1 1 1992
name60 subject60 34 1 1 1992
name61 subject61 34 1 1 1992
name62 subject62 34 1 1 1992
name63 subject63 34 1 1 1992
name64 subject64 34 1 1 1992
name65 subject65 34 1 1 1992
name66 subject66 34 1 1 1992
name67 subject67 34 1 1 1992
name68 subject68 34 1 1 1992
name69 subject69 34 1 1 1992
name70 subject70 34 1 1 1992
name71 subject71 34 1 1 1992
name72 subject72 34 1 1 1992
name73 subject73 34 1 1 1992
name74 subject74 34 1 1 1992
name75 subject75 34 1 1 1992
name76 subject76 34 1 1 1992
name77 subject77 34 1 1 1992
name78 subject78 34 1 1 1992
name79 subject79 34 1 1 1992
name80 subject80 34 1 1 1992
name81 subject81 34 1 1 1992
name82 subject82 34 1 1 1992
name83 subject83 34 1 1 1992
name84 subject84 34 1 1 1992
name85 subject85 34 1 1 1992
name86 subject86 34 1 1 1992
name87 subject87 34 1 1 1992
name88 subject88 34 1 1 1992
name89 subject89 34 1 1 1992
name90 subject90 34 1 1 1992
name91 subject91 34 1 1 1992
name92 subject92 34 1 1 1992
name93 subject93 34 1 1 1992
name94 subject94 34 1 1 1992
name95 subject95 34 1 1 1992
name96 subject96 34 1 1 1992
name97 subject97 34 1 1 1992
name98 subject98 34 1 1 1992
name99 subject99 34 1 1 1992
name100 subject100 34 1 1 1992
name101 subject101 34 1 1 1992
name102 subject102 34 1 1 1992
name103 subject103 34 1 1 1992
name104 subject104 34 1 1 1992
name105 subject105 34 1 1 1992
name106 subject106 34 1 1 1992
name107 subject107 34 1 1 1992
name108 subject108 34 1 1 1992
name109 subject109 34 1 1 1992
name110 subject110 34 1 1 1992
name111 subject111 34 1 1 1992
name112 subject112 34 1 1 1992
name113 subject113 34 1 1 1992
name114 subject114 34 1 1 1992
name115 subject115 34 1 1 1992
name116 subject116 34 1 1 1992
name117 subject117 34 1 1 1992
name118 subject118 34 1 1 1992
name119 subject119 34 1 1 1992
name120 subject120 34 1 1 1992
name121 subject121 34 1 1 1992
name122 subject122 34 1 1 1992
name123 subject123 34 1 1 1992
name124 subject124 34 1 1 1992
name125 subject125 34 1 1 1992
name126 subject126 34 1 1 1992
name127 subject127 34 1 1 1992
name128 subject128 34 1 1 1992
name129 subject129 34 1 1 1992
name130 subject130 34 1 1 1992
name131 subject131 34 1 1 1992
name132 subject132 34 1 1 1992
name133 subject133 34 1 1 1992
name134 subject134 34 1 1 1992
name135 subject135 34 1 1 1992
name136 subject136 34 1 1 1992
name137 subject137 34 1 1 1992
name138 subject138 34 1 1 1992
name139 subject139 34 1 1 1992
name140 subject140 34 1 1 1992
name141 subject141 34 1 1 1992
name142 subject142 34 1 1 1992
name143 subject143 34 1 1 1992
name144 subject144 34 1 1 1992
name145 subject145 34 1 1 1992
name146 subject146 34 1 1 1992
name147 subject147 34 1 1 1992
name148 subject148 34 1 1 1992
name149 subject149 34 1 1 1992
name150 subject150 34 1 1 1992
name151 subject151 34 1 1 1992
name152 subject152 34 1 1 1992
name153 subject153 34 1 1 1992
name154 subject154 34 1 1 1992
name155 subject155 34 1 1 1992
name156 subject156 34 1 1 1992
name157 subject157 34 1 1 1992
name158 subject158 34 1 1 1992
name159 subject159 34 1 1 1992
name160 subject160 34 1 1 1992
name161 subject161 34 1 1 1992
name162 subject162 34 1 1 1992
name163 subject163 34 1 1 1992
name164 subject164 34 1 1 1992
name165 subject165 34 1 1 1992
name166 subject166 34 1 1 1992
name167 subject167 34 1 1 1992
name168 subject168 34 1 1 1992
name169 subject169 34 1 1 1992
name170 subject170 34 1 1 1992
name171 subject171 34 1 1 1992
name172 subject172 34 1 1 1992
name173 subject173 34 1 1 1992
name174 subject174 34 1 1 1992
name175 subject175 34 1 1 1992
name176 subject176 34 1 1 1992
name177 subject177 34 1 1 1992
name178 subject178 34 1 1 1992
name179 subject179 34 1 1 1992
name180 subject180 34 1 1 1992
name181 subject181 34 1 1 1992
name182 subject182 34 1 1 1992
name183 subject183 34 1 1 1992
name184 subject184 34 1 1 1992
name185 subject185 34 1 1 1992
name186 subject186 34 1 1 1992
name187 subject187 34 1 1 1992
name188 subject188 34 1 1 1992
name189 subject189 34 1 1 1992
name190 subject190 34 1 1 1992
name191 subject191 34 1 1 1992
name192 subject192 34 1 1 1992
name193 subject193 34 1 1 1992
name194 subject194 34 1 1 1992
name195 subject195 34 1 1 1992
name196 subject196 34 1 1 1992
name197 subject197 34 1 1 1992
name198 subject198 34 1 1 1992
name199 subject199 34 1 1 1992
name200 subject200 34 1 1 1992
name201 subject201 34 1 1 1992
name202 subject202 34 1 1 1992
name203 subject203 34 1 1 1992
name204 subject204 34 1 1 1992
name205 subject205 34 1 1 1992
name206 subject206 34 1 1 1992
name207 subject207 34 1 1 1992
name208 subject208 34 1 1 1992
name209 subject209 34 1 1 1992
name210 subject210 34 1 1 1992
name211 subject211 34 1 1 1992
name212 subject212 34 1 1 1992
name213 subject213 34 1 1 1992
name214 subject214 34 1 1 1992
name215 subject215 34 1 1 1992
name216 subject216 34 1 1 1992
name217 subject217 34 1 1 1992
name218 subject218 34 1 1 1992
name219 subject219 34 1 1 1992
name220 subject220 34 1 1 1992
name221 subject221 34 1 1 1992
name222 subject222 34 1 1 1992
name223 subject223 34 1 1 1992
name224 subject224 34 1 1 1992
name225 subject225 34 1 1 1992
name226 subject226 34 1 1 1992
name227 subject227 34 1 1 1992
name228 subject228 34 1 1 1992
name229 subject229 34 1 1 1992
name230 subject230 34 1 1 1992
name231 subject231 34 1 1 1992
name232 subject232 34 1 1 1992
name233 subject233 34 1 1 1992
name234 subject234 34 1 1 1992
name235 subject235 34 1 1 1992
name236 subject236 34 1 1 1992
name237 subject237 34 1 1 1992
name238 subject238 34 1 1 1992
name239 subject239 34 1 1 1992
name240 subject240 34 1 1 1992
name241 subject241 34 1 1 1992
name242 subject242 34 1 1 1992
name243 subject243 34 1 1 1992
name244 subject244 34 1 1 1992
name245 subject245 34 1 1 1992
name246 subject246 34 1 1 1992
name247 subject247 34 1 1 1992
name248 subject248 34 1 1 1992
name249 subject249 34 1 1 1992
name250 subject250 34 1 1 1992
name251 subject251 34 1 1 1992
name252 subject252 34 1 1 1992
name253 subject253 34 1 1 1992
name254 subject254 34 1 1 1992
name255 subject255 34 1 1 1992
name256 subject256 34 1 1 1992
name257 subject257 34 1 1 1992
name258 subject258 34 1 1 1992
name259 subject259 34 1 1 1992
name260 subject260 34 1 1 1992
name261 subject261 34 1 1 1992
name262 subject262 34 1 1 1992
name263 subject263 34 1 1 1992
name264 subject264 34 1 1 1992
name265 subject265 34 1 1 1992
name266 subject266 34 1 1 1992
name267 subject267 34 1 1 1992
name268 subject268 34 1 1 1992
name269 subject269 34 1 1 1992
name270 subject270 34 1 1 1992
name271 subject271 34 1 1 1992
name272 subject272 34 1 1 1992
name273 subject273 34 1 1 1992
name274 subject274 34 1 1 1992
name275 subject275 34 1 1 1992
name276 subject276 34 1 1 1992
name277 subject277 34 1 1 1992
name278 subject278 34 1 1 1992
name279 subject279 34 1 1 1992
name280 subject280 34 1 1 1992
name281 subject281 34 1 1 1992
name282 subject282 34 1 1 1992
name283 subject283 34 1 1 1992
name284 subject284 34 1 1 1992
name285 subject285 34 1 1 1992
name286 subject286 34 1 1 1992
name287 subject287 34 1 1 1992
name288 subject288 34 1 1 1992
name289 subject289 34 1 1 1992
name290 subject290 34 1 1 1992
name291 subject291 34 1 1 1992
name292 subject292 34 1 1 1992
name293 subject293 34 1 1 1992"""
class MyApp(App):
def build(self):
with TimeCost('create widget') as tc:
dg = DataGrid(**desc)
Clock.schedule_once(self.loadData,1)
return dg
def loadData(self,t=None):
d = []
for t in data.split('\n'):
r = self.text2rec(t)
d.append(r)
with TimeCost('setData()') as tc:
self.root.setData(d)
return
def text2rec(self,text):
d = text.split('\t')
r = {}
for i,f in enumerate(desc['fields']):
r[f['name']] = d[i]
return r
MyApp().run()
tc = TimeCost('show')
tc.show()