master
yumoqing 2022-11-04 03:21:12 -04:00
commit d65729b431
31 changed files with 1612 additions and 249 deletions

View File

@ -5,3 +5,5 @@ add script to support local .tmpl and .dspy file translation. it can build dynam
## version 0.3.1
* uses weakref to collect all the i18n widgets in i18n.py
* show video play position

View File

View File

@ -0,0 +1,46 @@
from kivy.app import App
from jnius import autoclass
import kivy
from android.broadcast import BroadcastReceiver
import sys
from kivyblocks.baseWidget import VBox
BluetoothManager = autoclass('android.bluetooth.BluetoothManager')
BluetoothAdapter = autoclass('android.bluetooth.BluetoothAdapter')
BluetoothDevice = autoclass('android.bluetooth.BluetoothDevice')
class BluetoothFinder(VBox):
data = ListProperty([])
def __init__(self, **kw):
super().__init__(**kw)
arg = context.getSystemService(context.BLUETOOTH_SERVICE)
self.bt_manager = BluetoothManager(arg)
self.bt_adapter = self.bt_manager.getAdaper()
self.unpairedBT()
def get_paired_bt(self):
return self.bt_adapter.getBondedDevice()
def __del__(self):
self.adapter.cancelDiscovery()
def unpairedBT(self):
self.adapter = BluetoothAdapter.getDefaultAdapter()
# Search foir unpaired devices
print("Unpaired devices")
self.data=[{"text": "Unpaired Devices"}]
myReceiver = BroadcastReceiver(self.onReceive, actions = [BluetoothDevice.ACTION_FOUND, BluetoothAdapter.ACTION_DISCOVERY_STARTED, BluetoothAdapter.ACTION_DISCOVERY_FINISHED])
myReceiver.start()
self.adapter.startDiscovery()
# Called by Broadcastreceiver
def onReceive(self, context, intent):
print(f"*BT* On receive context{context}", flush = True)
print(f"*BT* On receive intent {intent}", flush = True)
sys.stdout.flush()
self.myData.append({"text": f"Context {context}, intent {intent}"})
self.layout.data = [item for item in self.myData]

View File

@ -2,6 +2,7 @@ import sys
import math
from traceback import print_exc
from kivy.resources import resource_find
from kivy.properties import ObjectProperty, StringProperty, \
NumericProperty, BooleanProperty, OptionProperty
from kivy.properties import DictProperty
@ -67,7 +68,7 @@ from .widgetExt.inputext import FloatInput,IntegerInput, \
StrInput,SelectInput, BoolInput, Password
from .widgetExt.messager import Messager
from .bgcolorbehavior import BGColorBehavior
from .utils import NeedLogin, InsufficientPrivilege, HTTPError
from .utils import NeedLogin, InsufficientPrivilege, HTTPError, blockImage
from .login import LoginForm
from .tab import TabsPanel
from .threadcall import HttpClient
@ -76,6 +77,27 @@ from .widget_css import WidgetCSS
from .ready import WidgetReady
from .utils import CSize, SUPER
from .swipebehavior import SwipeBehavior
from .widgetExt.inputext import MyDropDown
font_names = {
'text':resource_find('DroidSansFallback.ttf'),
'title6':resource_find('TsangerYuYangT_W01_W01.ttf'),
'title5':resource_find('TsangerYuYangT_W01_W02.ttf'),
'title4':resource_find('TsangerYuYangT_W01_W03.ttf'),
'title3':resource_find('TsangerYuYangT_W01_W04.ttf'),
'title2':resource_find('TsangerYuYangT_W01_W05.ttf'),
'title1':resource_find('Alimama_ShuHeiTi_Bold.ttf')
}
font_sizes = {
'text':CSize(1),
'title6':CSize(1.1),
'title5':CSize(1.3),
'title4':CSize(1.5),
'title3':CSize(1.7),
'title2':CSize(1.9),
'title1':CSize(2.1)
}
if platform == 'android':
from .widgetExt.phonebutton import PhoneButton
@ -145,33 +167,23 @@ class Text(Label):
def __init__(self,i18n=False, texttype='text', wrap=False,
fgcolor=None, **kw):
fontsize={'font_size':CSize(1)}
offset={
'text':0,
'title1':CSize(0.6),
'title2':CSize(0.5),
'title3':CSize(0.4),
'title4':CSize(0.3),
'title5':CSize(0.2),
'title6':CSize(0.1),
}
fontsize = {'font_size': CSize(1) + offset.get(texttype,0)}
fontsize = font_sizes.get(texttype)
fontname = font_names.get(texttype)
self._i18n = i18n
self.i18n = I18n()
self.bgcolor = fgcolor
kwargs = kw.copy()
config = getConfig()
self.wrap = wrap
if kwargs.get('font_size') and texttype=='text':
pass
else:
kwargs.update(fontsize)
kwargs.update({
'font_size':fontsize,
'font_name':fontname
})
if not kwargs.get('text'):
kwargs['text'] = kwargs.get('otext','')
SUPER(Text, self, kwargs)
if self._i18n:
self.i18n = I18n()
self.i18n.addI18nWidget(self)
if self.wrap:
self.size_hint_y = None
@ -220,7 +232,8 @@ class Text(Label):
self.lang = lang
def on_lang(self,o,lang):
self.text = self.i18n(self.otext)
if self._i18n and self.otext:
self.text = self.i18n(self.otext)
class Title1(Text):
def __init__(self, **kw):
@ -256,12 +269,19 @@ class Modal(VBox):
content = DictProperty(None)
auto_open = BooleanProperty(True)
auto_dismiss = BooleanProperty(True)
position = OptionProperty('tc',options=['tl', 'tc', 'tr',
target = StringProperty(None)
position = OptionProperty('cc',options=['tl', 'tc', 'tr',
'cl', 'cc', 'cr',
'bl', 'bc', 'br'])
def __init__(self, **kw):
SUPER(Modal, self, kw)
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')
if self.content:
blocks = Factory.Blocks()
self.content_w = blocks.widgetBuild(self.content)
@ -269,10 +289,6 @@ class Modal(VBox):
self.add_widget(self.content_w)
else:
print(content,':cannot build widget')
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')
def on_touch_down(self, touch):
if not self.collide_point(touch.x, touch.y):
@ -283,41 +299,74 @@ class Modal(VBox):
return super().on_touch_down(touch)
def set_modal_position(self, w):
def on_target(self):
w = Window
if self.target is not None:
w = Factory.Blocks.getWidgetById(self.target)
if w is None:
w = Window
if w != self._target:
self._target = w
def set_target(self):
if self._target is None:
if self.target is None:
w = Window
else:
w = Factory.Blocks.getWidgetById(self.target)
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
print(self.width, self.height,
self.size_hint_x, self.size_hint_y,
self._target.size
)
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 = (w.width - self.width) / 2
x = (self._target.width - self.width) / 2
elif xn == 'r':
x = w.width - self.width
x = self._target.width - self.width
if x < 0:
x = 0
if yn == 'c':
y = (w.height - self.height) / 2
elif yn == 'b':
y = w.height - self.height
y = (self._target.height - self.height) / 2
elif yn == 't':
y = self._target.height - self.height
if y < 0:
y = 0
if w == Window:
if self._target == Window:
self.pos = x, y
else:
self.pos = w.pos[0] + x, w.pos[1] + y
self.pos = self._target.pos[0] + x, self._target.pos[1] + y
def open(self, widget=None):
def open(self):
if self.parent:
return
self.parent.remove_widget(self)
self.dispatch('on_pre_open')
if widget is None:
widget = Window
self.set_modal_position(widget)
Window.add_widget(self)
self.dispatch('on_open')
if self._target != Window:
self._target.disabled = True
def dismiss(self, *args):
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
@ -333,7 +382,6 @@ class Modal(VBox):
def add_widget(self, w, *args, **kw):
super().add_widget(w, *args, **kw)
# super().add_widget(Label(text='1111'))
if self.auto_open:
self.open()
@ -358,6 +406,32 @@ class TimedModal(Modal):
self.time_task = None
super().dismiss()
class Running(AsyncImage):
def __init__(self, widget, **kw):
super().__init__(**kw)
self.host_widget = widget
self.source = blockImage('running.gif')
self.host_widget.bind(size=self.set_size)
self.set_size()
self.open()
def open(self):
if self.parent:
self.parent.remove_widget(self)
Window.add_widget(self)
self.host_widget.disabled = True
def dismiss(self):
if self.parent:
self.parent.remove_widget(self)
self.host_widget.disabled = False
def set_size(self, *args):
self.size_hint = (None, None)
self.width = CSize(2)
self.height = CSize(2)
self.center = self.host_widget.center
class PressableImage(ButtonBehavior,AsyncImage):
def on_press(self):
pass
@ -366,6 +440,10 @@ class PressableLabel(ButtonBehavior, Text):
def on_press(self):
pass
class PressableText(ButtonBehavior, Text):
def on_press(self):
pass
class FILEDataHandler(EventDispatcher):
def __init__(self, url, suffixs=[],params={}):
self.url = url
@ -566,3 +644,29 @@ class Slider(Carousel):
w = bk.widgetBuild(desc)
self.add_widget(w)
class I18nWidget(PressableText):
lang = StringProperty(None)
def __init__(self, **kw):
super().__init__(**kw)
i18n = I18n()
self.lang = i18n.lang
def on_lang(self, *args):
self.otext = self.lang
def on_press(self, *args):
i18n = I18n()
langs = i18n.get_languages()
data = [ {'lang':l} for l in langs ]
mdd = MyDropDown(textField='lang', valueField='lang',
value=self.lang,
data=data)
mdd.bind(on_select=self.selected_lang)
mdd.showme(self)
def selected_lang(self, o, v):
lang = v[0]
self.lang = lang
i18n = I18n()
i18n.changeLang(self.lang)

View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
import os
import sys
import codecs
@ -818,3 +819,822 @@ class {{ classname }}({% for b in bases -%}{{b}}{% endfor %})
Factory.register('Blocks',Blocks)
Factory.register('Video',Video)
Factory.register('OrientationLayout', OrientationLayout)
=======
import os
import sys
import codecs
# import ujson as json
try:
import ujson as json
except:
import json
from traceback import print_exc
from functools import partial
from appPublic.dictExt import dictExtend
from appPublic.folderUtils import ProgramPath
from appPublic.dictObject import DictObject
from appPublic.Singleton import SingletonDecorator, GlobalEnv
from appPublic.datamapping import keyMapping
from appPublic.registerfunction import RegisterFunction
from kivy.logger import Logger
from kivy.config import Config
from kivy.metrics import sp,dp,mm
from kivy.core.window import WindowBase, Window
from kivy.properties import BooleanProperty
from kivy.uix.widget import Widget
from kivy.clock import mainthread
from kivy.uix.modalview import ModalView
from kivy.app import App
from kivy.factory import Factory
from kivy.uix.video import Video
from .utils import *
from .newvideo import Video
from .orientationlayout import OrientationLayout
from .threadcall import HttpClient
from .register import *
from .script import Script, set_script_env
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:str):
super().__init__()
self.widget_name = name
def __str__(self):
s = 'not reigsted widget(%s)' % self.name
return s
def __expr__(self):
return self.__str__()
def registerWidget(name:str,widget):
globals()[name] = widget
class Blocks(EventDispatcher):
def __init__(self):
EventDispatcher.__init__(self)
self.action_id = 0
self.register_event_type('on_built')
self.register_event_type('on_failed')
self.env = GlobalEnv()
config = getConfig()
self.script = Script()
def set(self, k:str, v):
self.env[k] = v
def register_widget(self, name:str, widget:Widget):
globals()[name] = widget
def buildAction(self, widget:Widget, desc):
conform_desc = desc.get('conform')
blocks = Blocks()
if not conform_desc:
return partial(blocks.uniaction, widget, desc)
func =partial(blocks.conform_action,widget, desc)
return func
def eval(self, s:str, l:dict):
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__']
g['__builtins__']['__import__'] = None
g['__builtins__']['__loader__'] = None
g['__builtins__']['open'] = None
"""
g.update(self.env)
return eval(s,g,l)
def getUrlData(self, url:str, method:str='GET',
params:dict={}, files:dict={},
callback=None,
errback=None,**kw):
if url is None:
if errback:
errback(None,Exception('url is None'))
else:
return None
if url.startswith('file://'):
return self.script.dispatch(url, **params)
elif url.startswith('http://') or url.startswith('https://'):
try:
hc = HttpClient()
resp=hc(url,method=method,params=params,files=files)
# print('Blocks.py :resp=',resp)
return resp
except Exception as e:
print_exc()
if errback:
return errback(None,e)
return None
else:
config = getConfig()
url = config.uihome + url
return self.getUrlData(url,method=method,
params=params,
files=files,
**kw)
def strValueExpr(self,s:str,localnamespace:dict={}):
if not s.startswith('py::'):
return s
s = s[4:]
try:
v = self.eval(s,localnamespace)
return v
except Exception as e:
print('Exception .... ',e,'script=',s)
print_exc()
return s
def arrayValueExpr(self,arr:list,localnamespace:dict={}) -> list:
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={}) -> 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:dict={}):
if type(obj) == type(''):
return self.strValueExpr(obj,localnamespace)
if type(obj) == type([]):
return self.arrayValueExpr(obj,localnamespace)
if type(obj) == type({}):
return self.dictValueExpr(obj,localnamespace)
if isinstance(obj,DictObject):
return self.dictValueExpr(obj,localnamespace)
return obj
def w_build(self,desc) -> 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())
widget = None
try:
klass = Factory.get(widgetClass)
widget = klass(**opts)
except Exception as e:
print('Error:',widgetClass,'contructon error')
print_exc()
raise NotExistsObject(widgetClass)
if desc.get('id'):
id = desc.get('id')
if id.startswith('app.'):
app = App.get_running_app()
id = id[4:]
setattr(app, id, widget)
if id.startswith('root.'):
app = App.get_running_app()
id = id[5:]
setattr(app.root, id, widget)
if '.' in id:
Logger.info('widget id(%s) can not contain "."', id)
else:
widget.widget_id = id
widget.build_desc = desc
self.build_attributes(widget, desc)
self.build_rest(widget, desc)
self.buildBinds(widget, desc)
return widget
def build_attributes(self, widget:Widget,desc,t=None):
excludes = ['widgettype','options','subwidgets','binds']
for k,v in [(k,v) for k,v in desc.items() if k not in excludes]:
if isinstance(v,dict) and v.get('widgettype'):
b = Blocks()
v = self.valueExpr(v, localnamespace={'self':widget})
w = b.widgetBuild(v)
if hasattr(widget,k):
aw = getattr(widget,k)
if isinstance(aw,Layout):
aw.add_widget(w)
continue
setattr(widget,k,w)
continue
setattr(widget,k,self.valueExpr(v,\
localnamespace={'self':widget}))
def build_rest(self, widget:Widget,desc,t=None):
self.subwidget_total = len(desc.get('subwidgets',[]))
self.subwidgets = [ None for i in range(self.subwidget_total)]
pos = 0
for pos,sw in enumerate(desc.get('subwidgets',[])):
b = Blocks()
if isinstance(sw, str):
# w = Blocks.getWidgetById(sw, from_widget=widget)
w = Blocks.findWidget(sw, from_widget=widget)
if w:
widget.add_widget(w)
continue
kw = self.valueExpr(sw.copy(),
localnamespace={'self':widget})
w = b.widgetBuild(kw)
if w:
widget.add_widget(w)
def buildBinds(self, widget:Widget, desc:dict):
for b in desc.get('binds',[]):
kw = self.valueExpr(b.copy(), \
localnamespace={'self':widget})
self.buildBind(widget,kw)
def buildBind(self, widget:Widget, desc:dict):
wid = desc.get('wid','self')
# w = Blocks.getWidgetById(desc.get('wid','self'),from_widget=widget)
w = Blocks.findWidget(desc.get('wid','self'),from_widget=widget)
if not w:
Logger.info('Block: id(%s) %s',desc.get('wid','self'),
'not found via Blocks.getWidgetById()')
return
event = desc.get('event')
if event is None:
Logger.info('Block: binds desc miss event, desc=%s',str(desc))
return
f = self.buildAction(widget,desc)
if f is None:
Logger.info('Block: get a null function,%s',str(desc))
return
w.bind(**{event:f})
def multipleAction(self, widget:Widget, desc, *args):
desc1 = {k:v for k, v in desc.items() if k != 'actions'}
mydesc = desc1.copy()
for a in desc['actions']:
new_desc = mydesc.copy()
new_desc.update(a)
self.uniaction(widget,new_desc, *args)
def conform_action(self, widget:Widget, desc, *args):
conform_desc = desc.get('conform')
blocks = Blocks()
if not conform_desc:
blocks.uniaction(widget, desc,*args, **kw)
return
w = blocks.widgetBuild({
"widgettype":"Conform",
"options":conform_desc
})
w.bind(on_conform=partial(blocks.uniaction, widget, desc))
w.open()
def uniaction(self, widget:Widget, desc, *args):
acttype = desc.get('actiontype')
if acttype=='blocks':
return self.blocksAction(widget,desc, *args)
if acttype=='urlwidget':
return self.urlwidgetAction(widget,desc, *args)
if acttype == 'registedfunction':
return self.registedfunctionAction(widget,desc, *args)
if acttype == 'script':
return self.scriptAction(widget, desc, *args)
if acttype == 'method':
return self.methodAction(widget, desc, *args)
if acttype == 'event':
return self.eventAction(widget, desc, *args)
if acttype == 'multiple':
return self.multipleAction(widget, desc, *args)
alert("actiontype(%s) invalid" % acttype,title='error')
def eventAction(self, widget:Widget, desc, *args):
target = self.get_target(widget, desc)
event = desc.get('dispatch_event')
if not event:
Logger.info('Block: eventAction():desc(%s) miss dispatch_event',
str(desc))
return
params = desc.get('params',{})
d = self.getActionData(widget,desc, *args)
if d:
params.update(d)
try:
target.dispatch(event, params)
except Exception as e:
Logger.info(f'Block: eventAction():dispatch {event} error')
print_exc()
return
def get_target(self, widget:Widget, desc):
if not desc.get('target'):
return None
# return Blocks.getWidgetById(desc.get('target'),from_widget=widget)
return Blocks.findWidget(desc.get('target'),from_widget=widget)
def blocksAction(self, widget:Widget, desc, *args):
target = self.get_target(widget, desc)
add_mode = desc.get('mode','replace')
opts = desc.get('options').copy()
d = self.getActionData(widget,desc, *args)
p = opts.get('options',{}).copy()
if d:
p.update(d)
opts['options'] = p
def doit(target:Widget, add_mode:str, o, w:Widget):
if isinstance(w, Modal):
return
if target and not w.parent:
if add_mode == 'replace':
target.clear_widgets()
target.add_widget(w)
def doerr(o,e):
Logger.info('Block: blocksAction(): desc=%s widgetBuild error'
,str(desc))
raise e
b = Blocks()
b.bind(on_built=partial(doit,target,add_mode))
b.bind(on_failed=doerr)
b.widgetBuild(opts)
def urlwidgetAction(self, widget:Widget, desc, *args):
target = self.get_target(widget, desc)
add_mode = desc.get('mode','replace')
opts = desc.get('options', {}).copy()
p1 = opts.get('params',{})
p = {}
if p1:
p.update(p1)
if len(args) >= 1 and isinstance(args[0],dict):
p.update(args[0])
d = self.getActionData(widget, desc, *args)
if d:
p.update(d)
opts['params'] = p
d = {
'widgettype' : 'urlwidget',
'options': opts
}
def doit(target:Widget, add_mode:str, o, w:Widget):
if isinstance(w, ModalView):
return
if target and not w.parent:
if add_mode == 'replace':
target.clear_widgets()
target.add_widget(w)
def doerr(o,e):
Logger.info('Block: urlwidgetAction(): desc=%s widgetBuild error'
,str(desc))
b = Blocks()
b.bind(on_built=partial(doit,target,add_mode))
b.bind(on_failed=doerr)
b.widgetBuild(d)
def getActionData(self, widget:Widget, desc, *args):
data = {}
rtdesc = self.build_rtdesc(desc)
if rtdesc:
rt = self.get_rtdata(widget, rtdesc, *args)
if rt:
data.update(rt)
if desc.get('keymapping'):
data = keyMapping(data, desc.get('keymapping'))
Logger.info('getActionData():rtdesc=%s, data=%s', rtdesc, data)
return data
def registedfunctionAction(self, widget:Widget, desc, *args):
target = self.get_target(widget, desc)
rf = RegisterFunction()
name = desc.get('rfname')
func = rf.get(name)
if func is None:
Logger.info('Block: desc=%s rfname(%s) not found',
str(desc), name)
raise Exception('rfname(%s) not found' % name)
params = desc.get('params',{}).copy()
d = self.getActionData(widget,desc, *args)
if d:
params.update(d)
func(target, *args, **params)
def scriptAction(self, widget:Widget, desc, *args):
script = desc.get('script')
if not script:
Logger.info('Block: scriptAction():desc(%s) target not found',
str(desc))
return
target = self.get_target(widget, desc)
d = self.getActionData(widget,desc, *args)
ns = {
"self":target,
"args":args,
"kwargs":d
}
if d:
ns.update(d)
try:
self.eval(script, ns)
except Exception as e:
print_exc()
print(e)
def build_rtdesc(self, desc):
rtdesc = desc.get('rtdata')
if not rtdesc:
if desc.get('datawidget'):
rtdesc = {}
rtdesc['target'] = desc['datawidget']
if desc.get('datascript'):
rtdesc['script'] = desc.get('datacript')
else:
rtdesc['method'] = desc.get('datamethod', 'getValue')
rtdesc['kwargs'] = desc.get('datakwargs', {})
return rtdesc
def get_rtdata(self, widget:Widget, desc, *args):
"""
desc descript follow attributes
{
"target", a target name from widget
"method", a method name, default is 'getValue',
"script", script to handle the data
"kwargs":" a dict arguments, default is {}
}
"""
if desc is None or desc == {}:
print('get_rtdata():desc is None')
return {}
kwargs = desc.get('kwargs', {})
if not isinstance(kwargs, dict):
kwargs = {}
w = None
if len(args) > 0:
w = args[0]
target = desc.get('target')
if target:
# w = Blocks.getWidgetById(target, from_widget=widget)
w = Blocks.findWidget(target, from_widget=widget)
if w is None:
print('get_rtdata():w is None', desc)
return {}
script = desc.get('script')
if script:
ns = {
"self":w,
"args":args,
"kwargs":kwargs
}
d = self.eval(script, ns)
return d
method = desc.get('method', 'getValue')
if not hasattr(w, method):
print('get_rtdata():method is None', desc, type(w))
return {}
f = getattr(w, method)
try:
r = f(**kwargs)
if isinstance(r, dict):
return r
if isinstance(r, DictObject):
return r.to_dict()
print('get_rtdata():method return is not dict', desc, 'ret=', r, type(r))
return {}
except:
print_exc()
return {}
def methodAction(self, widget:Widget, desc, *args):
method = desc.get('method')
target = self.get_target(widget, desc)
if target is None:
Logger.info('Block: methodAction():desc(%s) target not found',
str(desc))
return
if method is None:
Logger.info('Block: methodAction():desc(%s) method not found',
str(desc))
return
if hasattr(target,method):
f = getattr(target, method)
kwargs = desc.get('options',{}).copy()
d = self.getActionData(widget,desc, *args)
if d:
kwargs.update(d)
f(*args, **kwargs)
else:
alert('%s method not found' % method)
def widgetBuild(self, desc):
"""
desc format:
{
widgettype:<registered widget>,
id:widget id,
options:{}
subwidgets:[
]
binds:[
]
}
"""
def doit(desc):
if isinstance(desc,DictObject):
desc = desc.to_dict()
if not isinstance(desc,dict):
Logger.info('Block: desc(%s) must be a dict object(%s)',
desc,type(desc))
return None
# desc = self.valueExpr(desc)
try:
widget = self.w_build(desc)
self.dispatch('on_built',widget)
if hasattr(widget,'ready'):
widget.ready = True
return widget
except Exception as e:
print_exc()
self.dispatch('on_failed',e)
return None
if isinstance(desc,DictObject):
desc = desc.to_dict()
if not isinstance(desc, dict):
print('widgetBuild1: desc must be a dict object',
desc,type(desc))
self.dispatch('on_failed',Exception('miss url'))
return
widgettype = desc.get('widgettype')
while widgettype == "urlwidget":
opts = desc.get('options',{}).copy()
extend = desc.get('extend')
addon = None
if desc.get('extend'):
addon = desc.get('extend').copy()
url = opts.get('url')
if url is None:
self.dispatch('on_failed',Exception('miss url'))
return
if opts.get('url'):
del opts['url']
rtdesc = self.build_rtdesc(opts)
if rtdesc:
rtdata = self.get_rtdata(None, rtdesc)
params = opts.get('params', {})
params.update(rtdata)
opts['params'] = params
desc = self.getUrlData(url,**opts)
if not (isinstance(desc, DictObject) or \
isinstance(desc, dict)):
print('Block2: desc must be a dict object',
desc,type(desc))
self.dispatch('on_failed',Exception('miss url'))
return
if addon:
desc = dictExtend(desc,addon)
widgettype = desc.get('widgettype')
if widgettype is None:
print('Block3: desc must be a dict object not None')
return None
return doit(desc)
@classmethod
def findWidget(self, id:str, from_widget:Widget=None) -> Widget:
"""
-:find up direct, find widget in ancestor
+ or empty: find down direct, find widget in descendant
@:find widget by class
$ or empty:find widget by id
.:more than one step.
"""
def find_widget_by_id(id, from_widget):
if id=='self':
return from_widget
if hasattr(from_widget,'widget_id'):
if from_widget.widget_id == id:
return from_widget
if hasattr(from_widget, id):
w = getattr(from_widget,id)
return w
return None
def find_widget_by_class(klass, from_widget):
if from_widget.__class__.__name__ == klass:
return from_widget
for k, v in from_widget.__dict__.items():
if isinstance(v, Widget):
if v.__class__.__name__ == klass:
return v
return None
def _find_widget(name, from_widget, dir='down',
find_func=find_widget_by_id):
w = find_func(name, from_widget)
if w:
return w
if dir == 'down':
children = [i for i in from_widget.children]
if hasattr(from_widget, 'get_subwidgets'):
children = from_widget.get_subwidgets()
# Logger.info('children=%s', str(children))
for c in children:
ret = _find_widget(name, from_widget=c, dir=dir)
if ret:
return ret
else:
if isinstance(from_widget, WindowBase):
return None
if from_widget.parent:
return _find_widget(name,
from_widget=from_widget.parent,
dir=dir)
return None
def find_widget(step, from_widget):
dir = 'down'
if step[0:1] == '-':
dir = 'up'
step = step[1:]
find_func = find_widget_by_id
if step[0:1] == '@':
step = step[1:]
find_func = find_widget_by_class
return _find_widget(step, from_widget=from_widget,
dir=dir, find_func=find_func)
steps = id.split('.')
w = from_widget
for i, s in enumerate(steps):
if i==0:
app = App.get_running_app()
fid = s
if fid == '/self' or fid == 'root':
w = app.root
if len(steps) == 1:
return from_widget
continue
if fid == 'Window':
w = Window
if len(steps) == 1:
return w
continue
if fid == 'app':
return app
w = find_widget(s, w)
if not w:
return None
return w
@classmethod
def getWidgetById(self, id:str, from_widget:Widget=None) -> Widget:
return Blocks.findWidget(id, from_widget=from_widget)
def on_built(self,v=None):
return
def on_failed(self,e=None):
return
def buildKlass(self, desc):
"""
desc = {
"classname":"MyClass",
"base":["Box", "WidgetReady"],
"properties":[
{
"name":"aaa",
"type":"str",
"default":1
}
]
"subwidgets":[
],
"""
codes = """
{% for b in bases %}
{{b}} = Factory.{{b}}
{% endfor %}
class {{ classname }}({% for b in bases -%}{{b}}{% endfor %})
{% for p in properties %}
{{p.name}} {{p.type}}({{p.default}})
{% endfor %}
def __init__(self, **kw):
super({{classname}}, self).__init__(**kw)
"""
Factory.register('Blocks',Blocks)
Factory.register('Video',Video)
Factory.register('OrientationLayout', OrientationLayout)
>>>>>>> cab6a2818466e5d32880a6a01e7afe16ad192675

View File

@ -2,12 +2,6 @@
import os
import sys
from kivy.resources import resource_add_path
from kivy.config import Config
resource_add_path(os.path.join(os.path.dirname(__file__),'./ttf'))
Config.set('kivy', 'default_font', [
'msgothic',
'DroidSansFallback.ttf'])
import signal
import codecs
import json
@ -23,6 +17,7 @@ from kivy.core.window import WindowBase, Window
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.utils import platform
from kivy.metrics import Metrics
from kivy.app import App
import plyer
@ -31,20 +26,25 @@ from .register import *
from .threadcall import HttpClient,Workers
from .utils import *
from .widget_css import register_css
from .version import __version__
if platform == 'android':
from android.storage import app_storage_path
from jnius import autoclass
from .android_rotation import get_rotation
from .android_rotation import get_rotation
Logger.info(f'KivyBlocks:version={__version__}')
def signal_handler(signal, frame):
app = App.get_running_app()
if app is None:
return
app.workers.running = False
app.stop()
print('Singal handled .........')
signal.signal(signal.SIGINT, signal_handler)
class BlocksApp(App):
def get_rotation(self):
return get_rotation()
@ -80,6 +80,8 @@ class BlocksApp(App):
self.public_headers = {
"platform":self.platform
}
# Window.borderless = True
print('Window.dpi=', Window.dpi, 'Metrics.dpi=', Metrics.dpi)
Window.bind(on_request_close=self.on_close)
Window.bind(on_rotate=self.on_rotate)
Window.bind(size=self.device_info)
@ -104,23 +106,19 @@ class BlocksApp(App):
def get_user_data_path(self):
if platform == 'android':
Environment = autoclass('android.os.Environment')
sdpath = Environment.getExternalStorageDirectory()
return str(sdpath)
# Environment = autoclass('android.os.Environment')
# sdpath = Environment.getExternalStorageDirectory()
# return str(sdpath)
return str(app_storage_path())
sdpath = App.get_running_app().user_data_dir
return str(sdpath)
def get_profile_name(self):
fname = os.path.join(self.user_data_dir,'.profile.json')
fname = os.path.join(self.get_user_data_path(),'.profile.json')
print('profile_path=', fname)
return fname
def write_profile(self, dic):
fname = self.get_profile_name()
with codecs.open(fname,'w','utf-8') as f:
json.dump(dic,f)
def write_default_profile(self):
def default_profile(self):
device_id = getID()
try:
device_id = plyer.uniqueid.id
@ -132,6 +130,15 @@ class BlocksApp(App):
d = {
'device_id': device_id
}
return d
def write_profile(self, dic):
fname = self.get_profile_name()
with codecs.open(fname,'w','utf-8') as f:
json.dump(dic,f)
def write_default_profile(self):
d = self.default_profile()
self.write_profile(d)
def read_profile(self):

View File

@ -16,6 +16,11 @@ from kivyblocks.widget_css import WidgetCSS
from .uitype.factory import UiFactory, get_value
from .command_action import cmd_action
def on_disabled(self, *args):
pass
TouchRippleButtonBehavior.on_disabled = on_disabled
class TinyText(Text):
def __init__(self, **kw):
SUPER(TinyText, self, kw)
@ -26,6 +31,46 @@ class TinyText(Text):
self.texture_update()
self.size = self.texture_size
class PressableBox(TouchRippleButtonBehavior, Box):
normal_css = StringProperty("default")
actived_css = StringProperty("default")
box_actived = BooleanProperty(False)
def __init__(self,
border_width=1,
user_data=None,
radius=[],
**kw):
super(PressableBox, self).__init__(
padding=[border_width,
border_width,
border_width,
border_width],
radius=radius,
**kw)
self.border_width = border_width
self.user_data = user_data
self.actived = False
self.csscls = self.normal_css
def active(self, flag):
self.box_actived = flag
def on_box_actived(self, o, v):
if self.box_actived:
self.csscls = self.actived_css
else:
self.csscls = self.normal_css
def on_press(self,o=None):
self.box_actived = True
def setValue(self,d):
self.user_data = d
def getValue(self):
return self.user_data
class ClickableBox(TouchRippleButtonBehavior, Box):
def __init__(self,
border_width=1,

View File

@ -15,7 +15,6 @@ def get_cmd_desc(cmd_desc):
desc['datamethod'] = v['datamethod']
keys = v.keys()
if 'url' in keys:
Logger.info('get_cmd_desc():cmd_desc=%s', cmd_desc)
desc['actiontype'] = 'urlwidget'
desc['mode'] = 'replace'
options = {
@ -24,18 +23,22 @@ def get_cmd_desc(cmd_desc):
}
desc['options'] = options
return desc
if 'rfname' in keys:
desc['actiontype'] = 'registedfunction'
desc['params'] = v.get('params',{})
return desc
if 'script' in keys:
desc['actiontype'] = 'script'
desc['script'] = v.get('script')
return desc
if 'method' in keys:
desc['actiontype'] = 'method'
desc['method'] = v.get('method')
return desc
if 'actions' in keys:
desc['actiontype'] = 'multiple'
desc['actions'] = v.get('actions')
@ -61,6 +64,6 @@ def cmd_action(cmd_desc, widget):
})
w.bind(on_conform=partial(blocks.uniaction, widget, desc))
w.open()
Logger.info('cmd_action():desc=%s, conform and action', desc)
print('cmd_action():desc=', desc, ' conform and action', desc)

View File

@ -1,3 +1,4 @@
<<<<<<< HEAD
from kivy.event import EventDispatcher
from kivy.app import App
@ -191,3 +192,187 @@ class RegisterFunctionDataLoader(DataLoader):
self.dispatch('on_error', e)
=======
from kivy.event import EventDispatcher
from kivy.app import App
from kivy.factory import Factory
from .threadcall import HttpClient
from .utils import absurl
from appPublic.registerfunction import RegisterFunction
class DataGraber(EventDispatcher):
"""
Graber format
{
"widgettype":"DataGraber",
"options":{
"dataurl":"first",
"datarfname":"second",
"target":"third",
"params":
"method":
"pagging":"default false"
}
}
if dataurl present, the DataGraber using this dataurl to get and
return data
else if datarfname present, it find a registered function named
by 'datarfname' to return data
else if datatarget present, it find the widget and uses target
method(default is 'getValue') to return data
else it return None
"""
def __init__(self, **kw):
super().__init__()
self.options = kw
self.register_event_type('on_success')
self.register_event_type('on_error')
def load(self, *args, **kw):
ret = None
while True:
try:
dataurl = self.options.get('dataurl')
if dataurl:
ret = self.loadUrlData(*args, **kw)
break
rfname = self.options.get('datarfname')
if rfname:
ret = self.loadRFData(*args, **kw)
break
target = self.options.get('datatarget')
if target:
ret = self.loadTargetData(*args, **kw)
break
except Exception as e:
self.dispatch('on_error', e)
return
if ret:
self.dispatch('on_success',ret)
else:
e = Exception('Not method to do load')
self.dispatch('on_error', e)
def loadUrlData(self, *args, **kw):
dataurl = self.options.get('dataurl')
hc = HttpClient()
params = self.options.get('params',{}).copy()
params.update(kw)
method = self.options.get('method','GET')
d = hc(dataurl, params=params,method=method)
return d
def loadRFData(self, *args, **kw):
rfname = self.options.get('datarfname')
rf = RegisterFunction()
f = rf.get(rfname)
if not f:
return None
params = self.options.get('params',{}).copy()
params.update(kw)
try:
d = f(**params)
return d
except Exception as e:
Logger.info('blocks : Exception:%s',e)
print_exc()
return None
def loadTargetData(self, *args, **kw):
target = self.options.get('datatarget')
w = Factory.Blocks.getWidgetById(target)
if not w:
return None
params = self.options.get('params',{}).copy()
params.update(kw)
method = params.get('method', 'getValue')
if not has(w, method):
return None
try:
f = getattr(w, method)
d = f()
return d
except Exception as e:
Logger.info('blocks : Exception %s', e)
print_exc()
return None
def on_success(self,d):
pass
def on_error(self,e):
pass
class DataLoader(EventDispatcher):
def __init__(self,data_user):
self.data_user = data_user
EventDispatcher.__init__(self)
self.register_event_type('on_success')
self.register_event_type('on_error')
def on_success(self,d):
pass
def on_error(self,e):
pass
def load(self):
pass
class HttpDataLoader(DataLoader):
def load(self, *args, **kw):
app = App.get_running_app()
url = self.data_user.url
method = self.data_user.method
params = self.data_user.params.copy()
params.update({
"page":self.data_user.curpage,
"rows":self.data_user.page_rows
})
hc = HttpClient()
try:
r = hc(url, method=method, params=params)
self.dispatch('on_success', r)
return r
except Exception as e:
self.dispatch('on_error', e)
class ListDataLoader(DataLoader):
def load(self, *args, **kw):
p = self.data_user.curpage
r = self.data_user.page_rows
try:
s = self.data_user.data[(p-1)*r:p*r]
d = {
"total":len(self.data_user.data),
"rows":s
}
self.dispatch('on_success', d)
return d
except Exception as e:
self.dispatch('on_error', e)
class RegisterFunctionDataLoader(DataLoader):
def load(self, *args, **kw):
rf = RegisterFunction()
try:
rfname = self.data_user.rfname
func = rf.get(rfname)
if func is None:
raise Exception('%s is not a registerfunction' % rfname)
params = {k:v for k,v in self.user_data.params.items()}
params.update({
"page":self.data_user.curpage,
"rows":self.data_user.page_rows
})
s = func(**params)
self.dispatch('on_success', s)
return s
except Exception as e:
self.dispatch('on_error', e)
>>>>>>> cab6a2818466e5d32880a6a01e7afe16ad192675

View File

@ -25,7 +25,7 @@ from appPublic.uniqueID import getID
from appPublic.myTE import string_template_render
from .utils import *
from .baseWidget import Text, HBox, VBox
from .baseWidget import Text, HBox, VBox, Running
from .scrollpanel import ScrollPanel
from .paging import Paging, RelatedLoader
from .ready import WidgetReady
@ -64,7 +64,7 @@ class Cell(ButtonBehavior, WidgetCSS, BoxLayout):
if self.row.header:
self.csscls=self.row.part.datagrid.header_css
if desc['header']:
bl = Text(i18n=True, text=str(desc['value']),
bl = Text(i18n=True, otext=str(desc['value']),
font_size=CSize(1),wrap=True,
halign='left', valign='middle'
)
@ -432,7 +432,7 @@ class DataGrid(VBox):
desc = {
"widgettype":"Text",
"options":{
"text":n,
"otext":n,
"i18n":True,
}
}

View File

@ -0,0 +1,7 @@
from kivy.properties import StringProperty
class DownloadFile(ClickableIconText):
def __init__(self, **kw):
pass

View File

@ -6,12 +6,14 @@ from ffpyplayer.tools import set_log_callback
from kivy.factory import Factory
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.properties import StringProperty, BooleanProperty, \
OptionProperty, NumericProperty
from kivy.graphics.texture import Texture
from kivy.graphics import Color, Line
from kivyblocks.ready import WidgetReady
@ -26,17 +28,17 @@ class FFVideo(WidgetReady, Image):
duration = NumericProperty(-1)
position = NumericProperty(-1)
volume = NumericProperty(-1)
framerate = NumericProperty(180)
in_center_focus = BooleanProperty(False)
def __init__(self, **kwargs):
self._player = None
self._update_task = None
self._texture = None
self.vh_task = None
self.is_black = False
self.videosize = None
self.timeperiod = 1.0 / self.framerate
self.ff_opts = {}
self.ff_opts = {
"framedrop":True
}
self.lib_opts = {}
self.headers_pattern = {}
super(FFVideo, self).__init__(**kwargs)
@ -46,6 +48,7 @@ class FFVideo(WidgetReady, Image):
self.register_event_type('on_leave_focus')
self.register_event_type('on_enter_focus')
self.register_event_type('on_load_success')
self.register_event_type('on_startplay')
def on_open_failed(self, *args):
pass
@ -101,10 +104,14 @@ class FFVideo(WidgetReady, Image):
def on_status(self, *args):
print('on_status called, ', self.status)
if self._player is None:
print('no _player, do nothing')
return
Window.allow_screensaver = True
if self.status == 'play':
Window.allow_screensaver = False
self._player.set_pause(False)
self.video_handle()
elif self.status == 'pause':
self._player.set_pause(True)
elif self.status == 'stop':
@ -112,20 +119,35 @@ class FFVideo(WidgetReady, Image):
else:
pass
def on_parent(self, *args):
if self.parent:
return
self._play_stop()
def on_startplay(self, *args):
pass
def on_frame(self, *args):
if self._player is None:
return
if self.audio_id is None:
return
self._player.request_channel(self, 'audio', 'open', self.audio_id)
if not self.playing:
self.dispatch('on_startplay')
self._player.request_channel( \
'audio', 'open', self.audio_id)
self.seek(self.position)
self.playing = True
if self.duration > 0:
p = self.position / self.duration * self.width
self.canvas.after.clear()
with self.canvas.after:
Color(1,1,1,1)
Line()
Line(points=[0, 0, self.width, 0], width=1)
Color(1,0,0,1)
Line(points=[0,1,p,1], width=1)
def __del__(self):
if self._update_task:
self._update_task.cancel()
self._update_task = None
if self._player:
self._player.close_player()
self._player = None
self._play_stop()
def set_volume(self, v):
if self.play_mode == 'preview':
@ -141,23 +163,27 @@ class FFVideo(WidgetReady, Image):
self.volume = v
def seek(self, pts):
self.vsync = False
if self.play_mode == 'preview':
return
if self._player is None:
return
if self.status != 'play':
return
self._player.seek(pts)
self._player.seek(pts, relative=False)
self.position = self._player.get_pts()
self.last_val = None
def mute(self, flag):
def mute(self, flag=None):
if self.play_mode == 'preview':
return
if self._player is None:
return
if self.status != 'play':
return
self._player.set_mute(flag)
x = self._player.get_mute()
print('Video(), mute=', x)
self._player.set_mute(not x)
def switch_audio(self):
if self.play_mode == 'preview':
@ -169,7 +195,9 @@ class FFVideo(WidgetReady, Image):
self._player.request_channel('audio', action='cycle')
def on_v_src(self, o, src):
self._play_stop()
self.status = 'stop'
self.playing = False
ff_opts = {
'pause':False
}
@ -194,7 +222,8 @@ class FFVideo(WidgetReady, Image):
# self._player = MediaPlayer(self.v_src)
self._player = MediaPlayer(self.v_src, ff_opts=ff_opts, \
lib_opts=lib_opts)
self._play_start()
# self._play_start()
self.status = 'play'
def file_opened(self, files):
self.v_src = files[0]
@ -202,7 +231,6 @@ class FFVideo(WidgetReady, Image):
def play(self):
if self._player is None:
return
# self._player.set_pause(False)
self.status = 'play'
def pause(self):
@ -212,33 +240,32 @@ class FFVideo(WidgetReady, Image):
self.status = 'pause'
def _play_start(self):
self.timepass = 0.0
self.last_frame = None
self.is_black = False
self.first_play = True
self._update_task = Clock.schedule_interval(self._update, self.timeperiod)
self.vsync = False
def _get_video_info(self):
if self.first_play:
if not self.playing:
self.playing = True
meta = self._player.get_metadata()
self.duration = meta['duration']
self.position = 0
self._out_fmt = meta['src_pix_fmt']
self.frame_rate = meta['frame_rate']
self.videosize = meta['src_vid_size']
def _play_stop(self):
if self._player is None:
return
self._update_task.cancel()
self._update_task = None
self._player.close_player()
self._player = None
self.timepass = 0
if self._player:
self._player.close_player()
self._player = None
if self._update_task:
self._update_task.cancel()
self._update_task = None
if self.vh_task:
self.vh_task.cancel()
self.vh_task = None
self.next_frame = None
self.duration = -1
self.position = -1
self.frame_rate = None
self.position = 0
self.videosize = None
def on_size(self, *args):
@ -283,7 +310,6 @@ class FFVideo(WidgetReady, Image):
fbo['tex_y'] = 0
fbo['tex_u'] = 1
fbo['tex_v'] = 2
self._texture = fbo.texture
dy, du, dv, _ = img.to_memoryview()
if dy and du and dv:
self._tex_y.blit_buffer(dy, colorfmt='luminance')
@ -304,35 +330,44 @@ class FFVideo(WidgetReady, Image):
self.texture = texture
# print('img_size=', w, h, 'window size=', self.size)
def _update(self, dt):
if self.last_frame is None:
frame, val = self._player.get_frame()
if val == 'eof':
print('*****EOF******')
self.status = 'stop'
self.set_black()
return
if val == 'pause':
self.status = 'pause'
return
if frame is None:
# print('video null', time.time())
self.set_black()
return
self.last_frame = frame
self.video_ts = val
self._get_video_info()
self.timepass += self.timeperiod
def video_handle(self, *args):
if self._update_task:
self._update_task.cancel()
self._update_task = None
if self._player is None:
return
frame, val = self._player.get_frame()
if val == 'eof':
self.status = 'stop'
self.set_black()
self.last_val = None
return
if val == 'pause':
self.status = 'pause'
self.last_val = None
return
if frame is None:
self.set_black()
self.last_val = None
self.vh_task = Clock.schedule_once(self.video_handle, 0.1)
return
self. _get_video_info()
self.last_frame = frame
self.video_ts = val
if self.last_val is None:
self.last_val = val
self._update_task = Clock.schedule_once(self.do_update, 0)
else:
t = val - self.last_val
if t > 0:
self._update_task = Clock.schedule_once(self.do_update, t)
else:
self._update_task = Clock.schedule_once(self.do_update, 0)
def do_update(self, *args):
self.position = self._player.get_pts()
if self.timepass < self.video_ts:
return
if self.timepass > self.video_ts +0.2:
self.last_frame = None
return
self.status = 'play'
self.volume = self._player.get_volume()
img, t = self.last_frame
if self._out_fmt == 'yuv420p':
self.show_yuv420(img)
@ -340,5 +375,5 @@ class FFVideo(WidgetReady, Image):
self.show_others(img)
self.dispatch('on_frame', self.last_frame)
self.last_frame = None
Factory.register('FFVideo', FFVideo)
self.vh_task = Clock.schedule_once(self.video_handle, 0)

View File

@ -13,31 +13,34 @@ from .scrollpanel import ScrollPanel
from .clickable import SingleCheckBox
from .baseWidget import Text
from .utils import CSize
from .command_action import cmd_action
from appPublic.registerfunction import getRegisterFunctionByName
class TreeViewComplexNode(BoxLayout, TreeViewLabel):
class TreeViewComplexNode(BoxLayout, TreeViewNode):
otext = StringProperty(None)
font_size_c = NumericProperty(1)
node_height = NumericProperty(2)
checkbox = BooleanProperty(False)
icon = StringProperty(None)
data = DictProperty(None)
def __init__(self, **kw):
super(TreeViewComplexNode, self).__init__(**kw)
self.orientation = 'horizontal'
self.size_hint_x = None
if self.checkbox:
cb = SingleCheckBox(size_hint=(None,None))
cb.bind(on_press=self.set_checked)
self.add_widget(cb)
if self.icon:
img = AsyncImage(source=self.icon, size_hint=(None,None))
img.size = CSize(1,1)
img.size = CSize(self.font_size_c,self.font_size_c)
self.add_widget(img)
txt = Text(otext=self.otext, i18n=True)
txt.texture_update()
txt.size_hint = (None, None)
txt.size = txt.texture_size
txt = Text(otext=self.otext, i18n=True, wrap=True,
font_size=CSize(self.font_size_c),halign='left')
self.add_widget(txt)
self.size_hint_x = 1
self.size_hint_y = None
self.height = CSize(self.node_height)
def set_checked(self, o):
if not self.data:
@ -52,6 +55,8 @@ class Hierarchy(ScrollPanel):
params = DictProperty(None)
method = StringProperty('get')
idField = StringProperty(None)
node_height = NumericProperty(2)
font_size_c = NumericProperty(1)
textField = StringProperty(None)
data = ListProperty(None)
checkbox = BooleanProperty(False)
@ -60,13 +65,17 @@ class Hierarchy(ScrollPanel):
def __init__(self, **kw):
self.register_event_type('on_press')
self.tree = TreeView(hide_root=True)
self.tree.size_hint = (None, None)
self.tree.size_hint = (1, None)
self.tree.bind(on_node_expand=self.check_load_subnodes)
self.tree.bind(selected_node=self.node_selected)
super(Hierarchy, self).__init__(inner=self.tree, **kw)
if self.url:
self.data = self.get_remote_data()
def on_size(self, *args):
self.tree.size_hint_x = 1
self.tree.width = self.width
def on_press(self, node):
print('selected node=', node)
@ -95,12 +104,14 @@ class Hierarchy(ScrollPanel):
def create_new_node(self, data, node=None):
n = TreeViewComplexNode(otext=data[self.textField],
checkbox=self.checkbox,
node_height=self.node_height,
font_size_c=self.font_size_c,
icon=data.get('icon') or self.icon
)
n.data = data
n.width = self.tree.indent_start + \
self.tree.indent_level * n.level \
+ sum([i.width for i in n.children])
# n.width = self.tree.indent_start + \
# self.tree.indent_level * n.level \
# + sum([i.width for i in n.children])
if node:
self.tree.add_node(n, node)
else:
@ -148,50 +159,10 @@ class Menu(Hierarchy):
def on_press(self, node):
self.tree.deselect_node()
data = {}
dw = node.data.get('datawidget')
if dw:
data_widget = Factory.Blocks.getWidgetById(dw)
if data_widget:
vn = node.data.get('datamethod', 'getValue')
if hasattr(data_widget, vn):
f = getattr(data_widget, vn)
data = f()
if not isinstance(data, dict):
data = {}
url = node.data.get('url')
target = Factory.Blocks.getWidgetById(node.data.get('target',self.target),self)
if url:
params = node.data.get('params',{})
params.update(data)
blocks = Factory.Blocks()
desc = {
"widgettype":"urlwidget",
"options":{
"url":url,
"params":params
}
}
w = blocks.widgetBuild(desc)
if w and target:
target.add_widget(w)
return
rfname = node.data.get('rfname')
if rfname:
f = getRegisterFunctionByName(rfname)
if f:
f(self, **data)
return
script = node.data.get('script')
if script:
target_name = node.data.get('target', self.target)
target = Factory.Blocks.getWidgetById(target_name, self)
data.update({'self':target})
if target:
eval(script,data)
return
data = node.data.copy()
if self.target and data.get('target') is None:
data['target'] = self.target
return cmd_action(data, self)
Factory.register('Hierarchy', Hierarchy)
Factory.register('Menu', Menu)

View File

@ -14,6 +14,9 @@ class I18n:
def __init__(self):
self.kvlang={}
self.lang = locale.getdefaultlocale()[0]
if self.lang is None:
self.lang = 'en_US'
self.languages = None
self.loadI18n(self.lang)
self.i18nWidgets = []
@ -21,8 +24,9 @@ class I18n:
self.i18nWidgets.append(ref(w))
def loadI18nFromI18nFolder(self, lang):
config = gtConfig()
config = getConfig()
fpath = os.path.join(config.i18n_folder,lang,'msg.txt')
print('fpath=', fpath, type(fpath))
with codecs.open(fpath,'r','utf-8') as f:
line = f.readline()
d = {}
@ -32,15 +36,39 @@ class I18n:
continue
k,v = line.split(':',1)
d.update({k:v})
line = readline()
line = f.readline()
return d
def get_languages(self):
if self.languages:
return self.languages
self.languages = self.getLanguages()
return self.languages
def getLanguages(self):
config = getConfig()
if config.i18n_folder:
langs = []
for f in os.listdir(config.i18n_folder):
p = os.path.join(config.i18n_folder, f)
if os.path.isdir(p):
langs.append(os.path.basename(f))
return langs
if config.i18n_url:
url = '%s%s' % (config.uihome, config.i18n_url)
hc = HttpClient()
d = hc.get(url)
if isinstance(d, list):
return d
return []
def loadI18n(self,lang):
app = App.get_running_app()
config = getConfig()
self.kvlang[lang] = {}
if config.i18n_folder:
self.kvlang[lang] = self.loadI18nFromFolder(lang)
self.kvlang[lang] = self.loadI18nFromI18nFolder(lang)
return
if config.i18n_url:
@ -63,8 +91,8 @@ class I18n:
if not d:
self.loadI18n(lang)
self.lang = lang
ws = [ w for w in self.i18nWidgets if w is not None ]
ws = [ w for w in self.i18nWidgets if w() is not None ]
for w in ws:
w().changeLang(lang)
self.i18nWidgets = ws
for w in self.i18nWidgets:
w.changeLang(lang)

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -191,7 +191,7 @@ class MarkdownBody(VBox):
self.md_obj = md_obj
super().__init__(**kw)
self.padding=padding
self.size_hint = None,None
self.size_hint = 1,None
self.bind(parent=self.resize)
self.resize()
@ -205,6 +205,7 @@ class MarkdownBody(VBox):
f(v)
def resize(self, *args):
return
Logger.info('MDBody:resize called')
if self.parent:
ps = [0,0,0,0]

View File

@ -2,8 +2,10 @@
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.app import App
from kivy.clock import Clock
from kivy.factory import Factory
from .baseWidget import VBox, HBox
from kivy.utils import platform
from .baseWidget import VBox, HBox, I18nWidget
from .toggleitems import PressableBox
from .utils import *
from .swipebehavior import SwipeBehavior
@ -56,8 +58,10 @@ in control bar, there is a optional left menu icon, page title, right menu icon,
PagePanel description file format
```
{
"bar_autohide": true when page is idle
"bar_size": bar size in CSize unit
"bar_at": "top" or "bottom"
"i18n": true of false
"bar_css":
"panel_css":
"left_menu": if defined, it must be a widget instance or a dict
@ -133,16 +137,24 @@ sub-widget's description file format
"""
def __init__(self, bar_size=2,
bar_css='default',
i18n=False,
csscls='default',
singlepage=False,
fixed_before=None,
bar_autohide=False,
fixed_after=None,
bar_at='top',
enable_on_close=False,
left_menu=None, **kw):
self.bar_size = bar_size
self.bar_autohide = bar_autohide
self.bar_at = bar_at
self.i18n = i18n
self.singlepage = singlepage
self.idle_status = False
self.idle_threshold = 10
self.bar_show = True
self.idle_task = None
self.swipe_buffer = []
self.swipe_right = False
self.fixed_before = None
@ -209,6 +221,10 @@ sub-widget's description file format
})
self.bar.add_widget(self.bar_left_menu)
self.bar_left_menu.bind(on_press=self.show_left_menu)
if self.i18n:
self.i18n_w = I18nWidget(size_hint_x=None, width=CSize(5))
self.bar.add_widget(self.i18n_w)
self.bar_title = HBox(csscls=bar_css)
self.bar.add_widget(self.bar_title)
self.bar_right_menu = VBox(size_hint=(None,None),size=CSize(bcsize,bcsize))
@ -229,15 +245,47 @@ sub-widget's description file format
})
self.bar.add_widget(self.bar_right_menu)
self.bar_right_menu_w.bind(on_press=self.show_right_menu)
self.construct()
if self.bar_autohide:
Window.bind(on_touch_down=self.set_normal_bar)
self.idle_task = Clock.schedule_once(self.set_idle_bar, \
self.idle_threshold)
if bar_at == 'top':
def set_idle_bar(self, *args):
if not self.bar_show:
return
try:
self.bar_pos = self.children.index(self.bar)
print('self.bar_pos=', self.bar_pos, '......................')
super().remove_widget(self.bar)
if platform in ['win', 'macosx','linux']:
Window.borderless = True
except:
pass
self.bar_show = False
def set_normal_bar(self, *args):
if self.idle_task:
self.idle_task.cancel()
self.idle_task = Clock.schedule_once(self.set_idle_bar, \
self.idle_threshold)
if self.bar_show:
return
super().add_widget(self.bar, index=self.bar_pos)
if platform in ['win', 'macosx','linux']:
Window.borderless = False
self.bar_show = True
def construct(self):
self.clear_widgets()
if self.bar_show and self.bar_at == 'top':
super().add_widget(self.bar)
if self.fixed_before:
super().add_widget(self.fixed_before)
super().add_widget(self.content)
if self.fixed_after:
super().add_widget(self.fixed_after)
if bar_at != 'top':
if self.bar_show and self.bar_at != 'top':
super().add_widget(self.bar)
self.left_menu_showed = False
self.right_menu_showed = False

View File

@ -42,10 +42,13 @@ from .block_test import BlockTest
from .hierarchy import Hierarchy
from .price import PriceView
from .ffpyplayer_video import FFVideo
from .upload import UploadFile
r = Factory.register
# if kivy.platform in ['win','linux', 'macosx']:
# r('ScreenWithMic', ScreenWithMic)
r('UploadFile', UploadFile)
r('FFVideo', FFVideo)
r('AnchorBox', AnchorBox)
r('FloatBox', FloatBox)
r('RelativeBox', RelativeBox)
@ -144,4 +147,3 @@ def register_blocks(name, value):
except:
Logger.info(f'plugin : register_blocks():{name} register error')

View File

@ -1,3 +1,5 @@
import os
import traceback
try:
import ujson as json
except:
@ -9,6 +11,9 @@ from appPublic.myTE import MyTemplateEngine
from appPublic.Singleton import SingletonDecorator
from appPublic.dictObject import DictObject
from kivy.logger import Logger
from kivy.utils import platform
@SingletonDecorator
class ScriptEnv(DictObject):
pass
@ -18,8 +23,10 @@ def set_script_env(n,v):
env.update({n:v})
class Script:
def __init__(self, root):
self.root = root
def __init__(self):
config = getConfig()
self.root = config.script_root
# print('Script.root=', self.root)
self.env = {}
self.handlers = {}
self.register('.tmpl', TemplateHandler)
@ -31,6 +38,20 @@ class Script:
url = url[7:]
return join(self.root, *url.split('/'))
def show_info(self, env):
workdir = env['workdir']
sfile = env['filepath']
url = env['url']
print(f'script:workdir={workdir}')
print(f'script:script_file={sfile}')
print(f'script:url={url}')
sdir = os.path.join(workdir, 'scripts')
sf_exists = os.path.isdir(sdir)
conf_f = os.path.join(workdir, 'conf', 'config.json')
conf_exists = os.path.isfile(conf_f)
print(f'script:script exists {sf_exists}')
print(f'script:config.json exists {conf_exists}')
def dispatch(self, url, **kw):
filepath = self.url2filepath(url)
for suffix, handler in self.handlers.items():
@ -41,6 +62,10 @@ class Script:
env['root_path'] = self.root
env['url'] = url
env['filepath'] = filepath
# print(f"workdir={env['workdir']}--------")
# if platform == 'android':
# self.show_info(env)
h = handler(env)
d = h.render()
try:
@ -90,7 +115,12 @@ class TemplateHandler(BaseHandler):
self.engine = MyTemplateEngine(paths)
def render(self):
return self.engine.render(self.templ_file, self.env)
try:
return self.engine.render(self.templ_file, self.env)
except Exception as e:
print('Exception:', str(e))
print('filename=', self.env['filepath'])
traceback.print_exc()
class DspyHandler(BaseHandler):
def __init__(self, env):
@ -107,9 +137,15 @@ class DspyHandler(BaseHandler):
return txt
def render(self, params={}):
lenv = self.env.copy()
lenv.update(params)
txt = self.loadScript(self.env['filepath'])
exec(txt,lenv,lenv)
func = lenv['myfunc']
return func(self.env, **lenv)
try:
lenv = self.env.copy()
lenv.update(params)
txt = self.loadScript(self.env['filepath'])
exec(txt,lenv,lenv)
func = lenv['myfunc']
return func(self.env, **lenv)
except Exception as e:
print('Exception:', str(e))
print('filename=', self.env['filepath'])
traceback.print_exc()

View File

@ -121,18 +121,18 @@ class HttpClient(Http_Client):
self.workers.add(self.webcall,callback,errback,kwargs=kwargs)
def get(self, url, params={}, headers={}, callback=None, errback=None):
def get(self, url, params={}, headers={}, stream=False, callback=None, errback=None):
return self.__call__(url,method='GET',params=params,
headers=headers, callback=callback,
headers=headers, stream=stream, callback=callback,
errback=errback)
def post(self, url, params={}, headers={}, files={}, callback=None, errback=None):
def post(self, url, params={}, headers={}, files={}, stream=False, callback=None, errback=None):
return self.__call__(url,method='POST',params=params, files=files,
headers=headers, callback=callback,
headers=headers, stream=stream, callback=callback,
errback=errback)
def put(self, url, params={}, headers={}, callback=None, errback=None):
def put(self, url, params={}, headers={}, stream=False, callback=None, errback=None):
return self.__call__(url,method='PUT',params=params,
headers=headers, callback=callback,
headers=headers, stream=stream, callback=callback,
errback=errback)
def delete(self, url, params={}, headers={}, callback=None, errback=None):

View File

@ -1,6 +1,5 @@
from functools import partial
from kivy.logger import Logger
from kivy.uix.behaviors import TouchRippleButtonBehavior
from kivy.graphics import Color, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.factory import Factory
@ -11,46 +10,7 @@ from kivyblocks.bgcolorbehavior import BGColorBehavior
from kivyblocks.utils import CSize
from kivyblocks.baseWidget import Box
from kivyblocks.widget_css import WidgetCSS
class PressableBox(TouchRippleButtonBehavior, Box):
normal_css = StringProperty("default")
actived_css = StringProperty("default")
box_actived = BooleanProperty(False)
def __init__(self,
border_width=1,
user_data=None,
radius=[],
**kw):
super(PressableBox, self).__init__(
padding=[border_width,
border_width,
border_width,
border_width],
radius=radius,
**kw)
self.border_width = border_width
self.user_data = user_data
self.actived = False
self.csscls = self.normal_css
def active(self, flag):
self.box_actived = flag
def on_box_actived(self, o, v):
if self.box_actived:
self.csscls = self.actived_css
else:
self.csscls = self.normal_css
def on_press(self,o=None):
self.box_actived = True
def setValue(self,d):
self.user_data = d
def getValue(self):
return self.user_data
from kivyblocks.clickable import PressableBox
"""
ToggleItems format:

View File

@ -67,16 +67,22 @@ class Toolbar(ScrollPanel):
self.register_event_type('on_delete_tool')
if self.toolbar_orient == 'H':
self._inner.orientation = 'horizontal'
self.size_hint_x = 1
else:
self._inner.orientation = 'vertical'
self.size_hint_y = 1
self.clear_widgets()
for t in self.tools:
self.add_tool(t)
self.bar_width = 0
if self.toolbar_orient == 'H':
self.size_hint_y = None
self.height = self._inner.height * 1.6
self.do_scroll_y = False
else:
self.size_hint_x = None
self.width = self._inner.width * 1.6
self.do_scroll_x = False
def on_children_size(self, o, size):

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,57 @@
import os
from kivy.properties import StringProperty, ListProperty
from kivyblocks.utils import blockImage, CSize
import requests
from plyer import filechooser
from .i18n import I18n
from .clickable import ClickableIconText
from .baseWidget import Running
from .message import Error
class UploadFile(ClickableIconText):
upload_url = StringProperty(None)
name = StringProperty('upfile')
# suffixes = ListProperty([])
def __init__(self, **kw):
super().__init__(**kw)
self.otext = 'please select file'
self.img_kw = {
"size_hint":[None, None],
"width":CSize(1),
"height":CSize(1)
}
self.source = blockImage('upload.png')
self.file_url = None
self.running = None
def getValue(self):
return {
self.name:self.file_url
}
def on_press(self, *args):
i18n = I18n()
filechooser.open_file(title=i18n('open file'),
on_selection=self.file_selected)
def file_selected(self, files):
running = Running(self)
def readfile(f):
with open(f, 'rb') as o:
d = o.read(102400)
if not d:
return
yield d
fpath = files[0]
fn = os.path.basename(fpath)
print('fn=', fn)
headers={
'Content-Type': 'application/octet-stream',
'Content-Disposition':'attachments;filename={}'.format(fn)
}
r = requests.post(self.upload_url,
data=readfile(files[0]),
headers=headers
)
running.dismiss()

View File

@ -1 +1 @@
__version__ = '0.3.0'
__version__ = '0.4.1'