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 .threadcall import HttpClient
from .register import * from .register import *
from .script import Script, set_script_env from .script import Script, set_script_env
from .mixin import filter_mixin, get_mixins, mixin_behaviors
class WidgetNotFoundById(Exception): class WidgetNotFoundById(Exception):
def __init__(self, id): def __init__(self, id):
@ -237,18 +238,22 @@ class Blocks(EventDispatcher):
return self.dictValueExpr(obj,localnamespace) return self.dictValueExpr(obj,localnamespace)
return obj return obj
def w_build(self,desc) -> Widget: def w_build(self, desc:dict) -> Widget:
widgetClass = desc.get('widgettype',None) widgetClass = desc.get('widgettype',None)
if not widgetClass: if not widgetClass:
Logger.info("Block: w_build(), desc invalid", desc) Logger.info("Block: w_build(), desc invalid", desc)
raise Exception(desc) raise Exception(desc)
widgetClass = desc['widgettype'] 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 widget = None
try: try:
klass = Factory.get(widgetClass) klass = Factory.get(widgetClass)
widget = klass(**opts) widget = klass(**opts)
mixin_behaviors(widget, bopts)
except Exception as e: except Exception as e:
print('Error:',widgetClass,'contructon error') print('Error:',widgetClass,'contructon error')
print_exc() 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 import os
from traceback import print_exc from traceback import print_exc
from traceback import print_exc from traceback import print_exc
from kivy.app import App from kivy.app import App
from kivy.logger import Logger from kivy.logger import Logger
from appPublic.jsonConfig import getConfig from appPublic.jsonConfig import getConfig
from kivy.uix.popup import Popup from kivy.uix.popup import Popup
from kivy.uix.button import Button from kivy.uix.button import Button
from kivy.uix.label import Label from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.modalview import ModalView from kivy.uix.modalview import ModalView
from kivy.uix.image import AsyncImage from kivy.uix.image import AsyncImage
from appPublic.dictObject import DictObject from appPublic.dictObject import DictObject
from .mixin import filter_mixin, get_mixins, mixin_behaviors
from .kivysize import KivySizes
from .kivysize import KivySizes
class NeedLogin(Exception):
pass class NeedLogin(Exception):
pass
class InsufficientPrivilege(Exception):
pass class InsufficientPrivilege(Exception):
pass
class HTTPError(Exception):
def __init__(self,resp_code): class HTTPError(Exception):
self.resp_code = resp_code def __init__(self,resp_code):
Exception.__init__(self) self.resp_code = resp_code
Exception.__init__(self)
def __expr__(self):
return f'Exception:return code={self.resp_code}' def __expr__(self):
return f'Exception:return code={self.resp_code}'
def __str__(self):
return f'Exception:return code={self.resp_code}' def __str__(self):
return f'Exception:return code={self.resp_code}'
alert_widget= None
alert_widget= None
def kwarg_pop(obj, kw):
keys = [k for k in kw.keys()] def kwarg_pop(obj, kw):
for k in keys: keys = [k for k in kw.keys()]
if hasattr(obj, k): for k in keys:
setattr(obj, k, kw.pop(k)) if hasattr(obj, k):
setattr(obj, k, kw.pop(k))
def SUPER(klass, obj, kw):
keys = [ k for k in kw.keys() ] def SUPER(klass, obj, kw):
dic = { k:kw.pop(k) for k in keys if hasattr(obj, k) } mixins_kw = get_mixins(kw)
super(klass, obj).__init__(**kw) kw = filter_mixin(kw)
for k,v in dic.items(): keys = [ k for k in kw.keys() ]
try: dic = { k:kw.pop(k) for k in keys if hasattr(obj, k) }
setattr(obj, k, v) super(klass, obj).__init__(**kw)
except Exception as e: for k,v in dic.items():
print(f'obj={obj}, setattr(obj, "{k}","{v}") error') try:
print_exc() if v is not None:
raise e setattr(obj, k, v)
except Exception as e:
def blockImage(name): print(f'obj={obj}, setattr(obj, "{k}","{v}") error')
p = os.path.dirname(os.path.abspath(__file__)) print_exc()
return os.path.join(p,'imgs',name) raise e
def loaded(widget): mixin_behaviors(obj, mixins_kw)
widget.loadingwidget.dismiss()
# widget.remove_widget(widget.loadingwidget) def blockImage(name):
del widget.loadingwidget p = os.path.dirname(os.path.abspath(__file__))
widget.loadingwidget = None return os.path.join(p,'imgs',name)
def loading(parent): def loaded(widget):
fp = os.path.join(os.path.dirname(__file__),'imgs','loading1.gif') widget.loadingwidget.dismiss()
image = AsyncImage(source=blockImage('loading1.gif'), \ # widget.remove_widget(widget.loadingwidget)
width=CSize(2), height=CSize(2), del widget.loadingwidget
size_hint=(None,None)) widget.loadingwidget = None
view = ModalView(auto_dismiss=False)
view.add_widget(image) def loading(parent):
view.center = parent.center fp = os.path.join(os.path.dirname(__file__),'imgs','loading1.gif')
parent.loadingwidget = view image = AsyncImage(source=blockImage('loading1.gif'), \
# parent.add_widget(view) width=CSize(2), height=CSize(2),
view.open() size_hint=(None,None))
return view view = ModalView(auto_dismiss=False)
view.add_widget(image)
def set_widget_width(self, width): view.center = parent.center
if width <= 1: parent.loadingwidget = view
self.size_hint_x = width # parent.add_widget(view)
else: view.open()
self.size_hint_x = None return view
self.width = width
def set_widget_width(self, width):
def set_widget_height(self, height): if width <= 1:
if height <= 1: self.size_hint_x = width
self.size_hint_y = height else:
else: self.size_hint_x = None
self.size_hint_y = None self.width = width
self.height = height
def set_widget_height(self, height):
def setSizeOptions(desc,kw): if height <= 1:
""" self.size_hint_y = height
desc's width, and height to setup a widget's size options else:
if width or height is not set, kw add not thing self.size_hint_y = None
if width or height <= 1, using present rate of size self.height = height
else use CSize to tims width or height
""" def setSizeOptions(desc,kw):
if not isinstance(kw, DictObject): """
kw = DictObject(**kw) desc's width, and height to setup a widget's size options
if width or height is not set, kw add not thing
width = desc.get('width',0) if width or height <= 1, using present rate of size
if width > 1.01: else use CSize to tims width or height
kw.width = CSize(width) """
kw.size_hint_x = None if not isinstance(kw, DictObject):
elif width > 0.00: kw = DictObject(**kw)
kw.size_hint_x = width
width = desc.get('width',0)
height = desc.get('height',0) if width > 1.01:
if height > 1.01: kw.width = CSize(width)
kw.height = CSize(height) kw.size_hint_x = None
kw.size_hint_y = None elif width > 0.00:
elif height > 0.00: kw.size_hint_x = width
kw.size_hint_y = height
return kw height = desc.get('height',0)
if height > 1.01:
def alert(text,title='alert'): kw.height = CSize(height)
global alert_widget kw.size_hint_y = None
def close_alert(obj): elif height > 0.00:
alert_widget.dismiss() kw.size_hint_y = height
return kw
charsize = CSize(1)
if alert_widget is None: def alert(text,title='alert'):
bl = BoxLayout(orientation='horizontal') global alert_widget
msg = Label(font_size=charsize) def close_alert(obj):
bl.add_widget(msg) alert_widget.dismiss()
button = Button(size_hint_y=None,height=1.4*charsize,font_size=charsize,text='OK')
button.bind(on_press=close_alert) charsize = CSize(1)
bl.add_widget(button) if alert_widget is None:
alert_widget = Popup(content=bl, size_hint=(0.9,0.6)) bl = BoxLayout(orientation='horizontal')
alert_widget.msg_widget = msg msg = Label(font_size=charsize)
alert_widget.msg_widget.text = str(text) bl.add_widget(msg)
alert_widget.title = str(title) button = Button(size_hint_y=None,height=1.4*charsize,font_size=charsize,text='OK')
alert_widget.open() button.bind(on_press=close_alert)
bl.add_widget(button)
def StrConvert(s): alert_widget = Popup(content=bl, size_hint=(0.9,0.6))
if not s.startswith('py::'): alert_widget.msg_widget = msg
return s alert_widget.msg_widget.text = str(text)
s = s[4:] alert_widget.title = str(title)
try: alert_widget.open()
ns = {}
exec('_n_=' + s,globals(),ns) def StrConvert(s):
return ns['_n_'] if not s.startswith('py::'):
except Exception as e: return s
print('----e=',e,'------------s=',s) s = s[4:]
return s try:
ns = {}
def ArrayConvert(a): exec('_n_=' + s,globals(),ns)
s = [] return ns['_n_']
for i in a: except Exception as e:
s.append(JDConvert(i)) print('----e=',e,'------------s=',s)
return s return s
def DictConvert(dic): def ArrayConvert(a):
d = {} s = []
for k,v in dic.items(): for i in a:
if k == 'widgettype': s.append(JDConvert(i))
d[k] = v return s
else:
d[k] = JDConvert(v) def DictConvert(dic):
return d d = {}
for k,v in dic.items():
def JDConvert(dic): if k == 'widgettype':
nd = {} d[k] = v
if type(dic) == type(''): else:
return StrConvert(dic) d[k] = JDConvert(v)
if type(dic) == type([]): return d
return ArrayConvert(dic)
if type(dic) == type({}): def JDConvert(dic):
return DictConvert(dic) nd = {}
return dic if type(dic) == type(''):
return StrConvert(dic)
def getWidgetById(w,id): if type(dic) == type([]):
if id[0] == '/': return ArrayConvert(dic)
app = App.get_running_ap() if type(dic) == type({}):
if not hasattr('ids'): return DictConvert(dic)
return None return dic
return app.ids.get(id[1:])
if id in ['self', '.' ]: def getWidgetById(w,id):
return w if id[0] == '/':
if not hasattr(w,'ids'): app = App.get_running_ap()
return None if not hasattr('ids'):
return w.ids.get(id) return None
return app.ids.get(id[1:])
def CSize(x,y=None,name=None): if id in ['self', '.' ]:
ks = KivySizes() return w
return ks.CSize(x,y=y,name=name) if not hasattr(w,'ids'):
return None
def screenSize(): return w.ids.get(id)
ks = KivySizes()
return ks.getScreenSize() def CSize(x,y=None,name=None):
ks = KivySizes()
def screenPhysicalSize(): return ks.CSize(x,y=y,name=name)
ks = KivySizes()
return ks.getScreenPhysicalSize() def screenSize():
ks = KivySizes()
def isHandHold(): return ks.getScreenSize()
ks = KivySizes()
return ks.isHandHold() def screenPhysicalSize():
ks = KivySizes()
def absurl(url,parent): return ks.getScreenPhysicalSize()
if parent is None:
parent = '' def isHandHold():
config = getConfig() ks = KivySizes()
if url.startswith('http://'): return ks.isHandHold()
return url
if url.startswith('https://'): def absurl(url,parent):
return url if parent is None:
if url.startswith('file:///'): parent = ''
return url config = getConfig()
if url.startswith('/'): if url.startswith('http://'):
return config.uihome + url return url
if url.startswith(config.uihome): if url.startswith('https://'):
return url return url
if parent == '': if url.startswith('file:///'):
print('url=',url) return url
raise Exception('related url(%s) need a parent url' % url) if url.startswith('/'):
return config.uihome + url
if parent.startswith(config.uihome): if url.startswith(config.uihome):
parent = parent[len(config.uihome):] return url
paths = parent.split('/') if parent == '':
paths.pop() print('url=',url)
for i in url.split('/'): raise Exception('related url(%s) need a parent url' % url)
if i in [ '.', '' ]:
continue if parent.startswith(config.uihome):
if i == '..': parent = parent[len(config.uihome):]
if len(paths) > 1: paths = parent.split('/')
paths.pop() paths.pop()
continue for i in url.split('/'):
paths.append(i) if i in [ '.', '' ]:
return config.uihome + '/'.join(paths) continue
if i == '..':
def show_widget_info(w, tag='DEBUG'): if len(paths) > 1:
id = getattr(w, 'widget_id', 'null') paths.pop()
msg=f"""{tag}:size_hint={w.size_hint},size={w.size},pos={w.pos},widget_id={id},{w}""" continue
Logger.info(msg) 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
}
}