This commit is contained in:
yumoqing 2022-11-13 08:55:49 +08:00
parent ebfbfb115f
commit 1352d23245
12 changed files with 635 additions and 237 deletions

View File

@ -33,6 +33,7 @@ from .orientationlayout import OrientationLayout
from .threadcall import HttpClient
from .register import *
from .script import Script, set_script_env
from .mixin import filter_mixin, get_mixins, mixin_behaviors
class WidgetNotFoundById(Exception):
def __init__(self, id):
@ -237,18 +238,22 @@ class Blocks(EventDispatcher):
return self.dictValueExpr(obj,localnamespace)
return obj
def w_build(self,desc) -> Widget:
def w_build(self, desc:dict) -> Widget:
widgetClass = desc.get('widgettype',None)
if not widgetClass:
Logger.info("Block: w_build(), desc invalid", desc)
raise Exception(desc)
widgetClass = desc['widgettype']
opts = self.valueExpr(desc.get('options',{}).copy())
opts_org = self.valueExpr(desc.get('options',{}).copy())
opts = filter_mixin(opts_org)
bopts = get_mixins(opts_org)
widget = None
try:
klass = Factory.get(widgetClass)
widget = klass(**opts)
mixin_behaviors(widget, bopts)
except Exception as e:
print('Error:',widgetClass,'contructon error')
print_exc()

27
kivyblocks/mixin.py Normal file
View File

@ -0,0 +1,27 @@
from kivy.factory import Factory
_mix_ins = {}
def filter_mixin(kwargs):
return {k:v for k, v in kwargs.items() if not k.endswith('behavior')}
def get_mixins(kwargs):
return {k:v for k, v in kwargs.items() if k.endswith('behavior')}
def register_mixin(name, klass):
_mix_ins[name] = klass
def mix_other(inst, other):
for k,v in other.__dict__.items():
setattr(inst, k, v)
def mixin_behaviors(inst, kwargs):
behaviors_kw = get_mixins(kwargs)
for name, dic in behaviors_kw.items():
klass = _mix_ins.get(name)
if klass:
other = Factory.Blocks().widgetBuild(dic)
if other:
mix_other(inst, other)

118
kivyblocks/modalbehavior.py Normal file
View File

@ -0,0 +1,118 @@
from kivy.properties import DictProperty, BooleanProperty, \
StringProperty, OptionProperty, \
NumericProperty
from mixin import register_mixin
class ModalBehavior(object):
auto_open = BooleanProperty(True)
auto_dismiss = BooleanProperty(True)
target = StringProperty(None)
show_time = NumericProperty(0)
position = OptionProperty('cc',options=['tl', 'tc', 'tr',
'cl', 'cc', 'cr',
'bl', 'bc', 'br'])
def __init__(self, **kw):
self.time_task = None
self._target = None
super(Modal, self).__init__(**kw)
self.set_size_position()
self._target.bind(size=self.set_size_position)
self.register_event_type('on_open')
self.register_event_type('on_pre_open')
self.register_event_type('on_pre_dismiss')
self.register_event_type('on_dismiss')
self.bind(on_touch_down=self.on_touchdown)
if self.auto_open:
self.open()
def on_touchdown(self, o, touch):
if not self.collide_point(touch.x, touch.y):
if self.auto_dismiss:
self.dispatch('on_pre_dismiss')
self.dismiss()
return True
def on_target(self, *args):
self._target = None
self.set_target(self)
def set_target(self):
if self._target is None:
if self.target is None:
w = Window
else:
w = Factory.Blocks.getWidgetById(self.target, from_target=self)
if w is None:
w = Window
self._target = w
def set_size_position(self, *args):
self.set_target()
if self.size_hint_x:
self.width = self.size_hint_x * self._target.width
if self.size_hint_y:
self.height = self.size_hint_y * self._target.height
self.set_modal_position()
def set_modal_position(self):
self.set_target()
xn = self.position[1]
yn = self.position[0]
x, y = 0, 0
if xn == 'c':
x = (self._target.width - self.width) / 2
elif xn == 'r':
x = self._target.width - self.width
if x < 0:
x = 0
if yn == 'c':
y = (self._target.height - self.height) / 2
elif yn == 't':
y = self._target.height - self.height
if y < 0:
y = 0
if self._target == Window:
self.pos = x, y
else:
self.pos = self._target.pos[0] + x, self._target.pos[1] + y
def open(self):
if self.time_task is not None:
self.time_task.cancel()
self.time_task = None
if self.show_time > 0:
self.time_task = \
Clock.schedule_once(self.dismiss, self.show_time)
if self.parent:
self.parent.remove_widget(self)
self.dispatch('on_pre_open')
Window.add_widget(self)
self.dispatch('on_open')
if self._target != Window:
self._target.disabled = True
def dismiss(self, *args):
if self.time_task:
self.time_task.cancel()
self.time_task = None
self.dispatch('on_pre_dismiss')
self.dispatch('on_dismiss')
Window.remove_widget(self)
if self._target != Window:
self._target.enabled = False
def on_open(self, *args):
pass
def on_dismiss(self, *args):
pass
def on_pre_open(self, *args):
pass
def on_pre_dismiss(self, *args):
pass
register_mixin('modalbehavior', ModalBehavior)

View File

@ -1,235 +1,241 @@
import os
from traceback import print_exc
from traceback import print_exc
from kivy.app import App
from kivy.logger import Logger
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 AsyncImage
from appPublic.dictObject import DictObject
from .kivysize import KivySizes
class NeedLogin(Exception):
pass
class InsufficientPrivilege(Exception):
pass
class HTTPError(Exception):
def __init__(self,resp_code):
self.resp_code = resp_code
Exception.__init__(self)
def __expr__(self):
return f'Exception:return code={self.resp_code}'
def __str__(self):
return f'Exception:return code={self.resp_code}'
alert_widget= None
def kwarg_pop(obj, kw):
keys = [k for k in kw.keys()]
for k in keys:
if hasattr(obj, k):
setattr(obj, k, kw.pop(k))
def SUPER(klass, obj, kw):
keys = [ k for k in kw.keys() ]
dic = { k:kw.pop(k) for k in keys if hasattr(obj, k) }
super(klass, obj).__init__(**kw)
for k,v in dic.items():
try:
setattr(obj, k, v)
except Exception as e:
print(f'obj={obj}, setattr(obj, "{k}","{v}") error')
print_exc()
raise e
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 = AsyncImage(source=blockImage('loading1.gif'), \
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 set_widget_width(self, width):
if width <= 1:
self.size_hint_x = width
else:
self.size_hint_x = None
self.width = width
def set_widget_height(self, height):
if height <= 1:
self.size_hint_y = height
else:
self.size_hint_y = None
self.height = height
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)
alert_widget.title = str(title)
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)
def show_widget_info(w, tag='DEBUG'):
id = getattr(w, 'widget_id', 'null')
msg=f"""{tag}:size_hint={w.size_hint},size={w.size},pos={w.pos},widget_id={id},{w}"""
Logger.info(msg)
import os
from traceback import print_exc
from traceback import print_exc
from kivy.app import App
from kivy.logger import Logger
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 AsyncImage
from appPublic.dictObject import DictObject
from .mixin import filter_mixin, get_mixins, mixin_behaviors
from .kivysize import KivySizes
class NeedLogin(Exception):
pass
class InsufficientPrivilege(Exception):
pass
class HTTPError(Exception):
def __init__(self,resp_code):
self.resp_code = resp_code
Exception.__init__(self)
def __expr__(self):
return f'Exception:return code={self.resp_code}'
def __str__(self):
return f'Exception:return code={self.resp_code}'
alert_widget= None
def kwarg_pop(obj, kw):
keys = [k for k in kw.keys()]
for k in keys:
if hasattr(obj, k):
setattr(obj, k, kw.pop(k))
def SUPER(klass, obj, kw):
mixins_kw = get_mixins(kw)
kw = filter_mixin(kw)
keys = [ k for k in kw.keys() ]
dic = { k:kw.pop(k) for k in keys if hasattr(obj, k) }
super(klass, obj).__init__(**kw)
for k,v in dic.items():
try:
if v is not None:
setattr(obj, k, v)
except Exception as e:
print(f'obj={obj}, setattr(obj, "{k}","{v}") error')
print_exc()
raise e
mixin_behaviors(obj, mixins_kw)
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 = AsyncImage(source=blockImage('loading1.gif'), \
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 set_widget_width(self, width):
if width <= 1:
self.size_hint_x = width
else:
self.size_hint_x = None
self.width = width
def set_widget_height(self, height):
if height <= 1:
self.size_hint_y = height
else:
self.size_hint_y = None
self.height = height
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)
alert_widget.title = str(title)
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)
def show_widget_info(w, tag='DEBUG'):
id = getattr(w, 'widget_id', 'null')
msg=f"""{tag}:size_hint={w.size_hint},size={w.size},pos={w.pos},widget_id={id},{w}"""
Logger.info(msg)

View File

@ -0,0 +1,30 @@
{
"id":"mainctrl",
"widgettype":"PagePanel",
"options":{
"bar_autohide":true,
"i18n":true,
"bar_size":2,
"bar_at":"top",
"csscls":"mainctrl",
"singlepage":true,
"left_menu":{
"widgettype":"urlwidget",
"options":{
"params":{
"userid":"testuser"
},
"url":"{{entire_url('mainmenu.ui')}}"
}
}
},
"subwidgets":[
{
"widgettype":"urlwidget",
"options":{
"url":"{{entire_url('t.ui')}}"
}
}
]
}

View File

@ -0,0 +1,106 @@
{
"id":"m3u_modal",
"widgettype":"Modal",
"options":{
"auto_open":true,
"position":"tl",
"csscls":"modal_panel",
"size_hint":[0.7,0.7],
"content":{
"id":"modal_content",
"widgettype":"VBox",
"options":{
},
"subwidgets":[
{
"id":"m3u_form",
"widgettype":"Form",
"options":{
"cols":1,
"labelwidth":0.3,
"inputheight":2,
"fields":[
{
"name":"url",
"label":"Url",
"required":true,
"datatype":"str"
},
{
"name":"name",
"label":"Name",
"datatype":"str",
"required":true,
"uitype":"str"
}
]
}
},
{
"widgettype":"HBox",
"options":{},
"subwidgets":[
{
"widgettype":"Text",
"options":{
"otext":"total channels",
"size_hint_x":null,
"width":"py::CSize(14)",
"wrap":true,
"halign":"right"
}
},
{
"id":"tc_w",
"widgettype":"Text",
"options":{
"text":"",
"size_hint_x":null,
"width":"py::CSize(14)",
"wrap":true,
"halign":"left"
}
}
]
},
{
"widgettype":"HBox",
"options":{},
"subwidgets":[
{
"widgettype":"Text",
"options":{
"otext":"good channels",
"size_hint_x":null,
"width":"py::CSize(14)",
"wrap":true,
"halign":"right"
}
},
{
"id":"gc_w",
"widgettype":"Text",
"options":{
"text":"",
"size_hint_x":null,
"width":"py::CSize(14)",
"wrap":true,
"halign":"left"
}
}
]
}
]
}
},
"binds":[
{
"wid":"m3u_form",
"event":"on_submit",
"actiontype":"script",
"datawidget":"m3u_form",
"target":"app",
"script":"self.open_m3u(**kwargs)"
}
]
}

View File

@ -0,0 +1,64 @@
{
"widgettype":"Modal",
"options":{
"auto_open":true,
"position":"tl",
"csscls":"modal_panel",
"size_hint":[0.7,0.7],
"content":{
"id":"url_form",
"widgettype":"Form",
"options":{
"cols":1,
"labelwidth":0.3,
"inputheight":2,
"fields":[
{
"name":"url",
"label":"Url",
"required":true,
"datatype":"str"
},
{
"name":"urltype",
"label":"urltype",
"datatype":"str",
"uitype":"code",
"uiparams":{
"valueField":"ut",
"textField":"ut",
"data":[
{
"ut":"video"
},
{
"ut":"m3u"
},
{
"ut":"ebook"
}
]
}
}
]
}
}
},
"binds":[
{
"wid":"url_form",
"event":"on_submit",
"actiontype":"script",
"target":"self",
"script":"self.dismiss()"
},
{
"wid":"url_form",
"event":"on_submit",
"actiontype":"script",
"datawidget":"url_form",
"target":"app",
"script":"self.open_url(**kwargs)"
}
]
}

View File

@ -0,0 +1,20 @@
{
"widgettype":"Menu",
"options":{
"single_expand":true,
"idField":"id",
"font_size_c":1,
"node_height":2.5,
"textField":"label",
"bgcolor":[0.2,0.2,0.2,1],
"target":"root.mainctrl",
"data":[
{
"id":"setting",
"label":"Setting",
"icon":"{{entire_url('/imgs/setting.png')}}",
"url":"{{entire_url('t.ui')}}"
}
]
}
}

View File

@ -0,0 +1,5 @@
{
"widgettype":"PyInterpreter",
"options":{
}
}

View File

@ -0,0 +1,4 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=898222,RESOLUTION=1047x576,CODECS="avc1.77.30,mp4a.40.2"
chunklist_w891111828.m3u8

6
test/script/scripts/t.ui Normal file
View File

@ -0,0 +1,6 @@
{
"widgettype":"Text",
"options":{
"text":"To Be Implements"
}
}

View File

@ -0,0 +1,7 @@
{
"widgettype":"Text",
"options":{
"text":"Welcome to Kivyblocks",
"i18n":true
}
}