This commit is contained in:
yumoqing 2020-04-18 11:49:13 +08:00
parent 211fee42ec
commit 2490b46b46
4 changed files with 266 additions and 77 deletions

View File

@ -3,6 +3,7 @@ from kivy.utils import platform
from kivy.uix.button import Button, ButtonBehavior
from kivy.uix.accordion import Accordion,AccordionItem
from kivy.uix.label import Label
from kivy.event import EventDispatcher
from kivy.uix.actionbar import ActionBar,ActionView,ActionPrevious,ActionItem,ActionButton
from kivy.uix.actionbar import ActionToggleButton, ActionCheck,ActionSeparator,ActionGroup
@ -79,3 +80,38 @@ class PressableLabel(ButtonBehavior, Text):
def on_press(self):
pass
class HTTPDataHandler(EventDispatcher):
def __init__(self, url,method='GET',params={},headers={}):
EventDispatcher.__init__()
self.opts = opts
self.url = url
self.method = method
self.params = params
self.headers = headers
self.hc = App.get_running_app().hc
self.register_event_type('on_sucess')
self.register_event_type('on_error')
def on_success(self,o,v):
pass
def on_error(self,o,e):
pass
def onSuccess(self,o,v):
self.dispatch('on_sucess',v)
def onError(self,o,e):
self.dispatch('on_error',e)
def handle(self,params={},headers={}):
p = self.params
p.update(params)
h = self.headers
h.update(headers)
self.hc(self.url,method=self.method,
params=p,
headers=h,
callback=self.onSuccess,
errback=self.onError)

View File

@ -5,23 +5,23 @@ from kivy.properties import ListProperty
_logcnt = 0
class BGColorBehavior(object):
bgcolor = ListProperty([])
def __init__(self, bgcolor=[1,0,0,1],**kwargs):
def __init__(self, bgcolor=[0,0,0,1],**kwargs):
self.bgcolor = bgcolor
self.bind(size=self.on_bgcolor,pos=self.on_bgcolor)
self.bind(size=self.onSize_bgcolor_behavior,
pos=self.onSize_bgcolor_behavior)
self.on_bgcolor()
def onSize_bgcolor_behavior(self,o,v=None):
if not hasattr(self,'rect'):
self.on_bgcolor()
else:
self.rect.pos = o.pos
self.rect.size = o.size
def on_bgcolor(self,o=None,v=None):
global _logcnt
Logger.info('bgcolorBehavior: on_bgcolor(),o=%s,v=%s,logcnt=%d',\
o.text if hasattr(o,'text') else o,v,_logcnt)
_logcnt += 1
x = abs(self.size[0] - 100.0)
y = abs(self.size[1] - 100.0)
if self.size[0] < 0.0001 or self.size[1] < 0.0001:
return
if x < 0.0001 and y < 0.0001:
return
if self.canvas:
with self.canvas.before:
Color(*self.bgcolor)
Rectangle(pos=self.pos, size=self.size)
self.rect = Rectangle(pos=self.pos,
size=self.size)

View File

@ -1,5 +1,8 @@
import traceback
from kivy.app import App
from kivy.logger import logging
from kivy.core.window import Window
from kivy.logger import Logger
from kivy.graphics import Color, Rectangle, Triangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
@ -8,9 +11,11 @@ from kivy.uix.button import ButtonBehavior
from kivyblocks.widgetExt import ScrollWidget
from kivyblocks.utils import CSize
from appPublic.dictObject import DictObject
from appPublic.jsonConfig import getConfig
from .baseWidget import PressableLabel
from .color_definitions import getColors
from .bgcolorbehavior import BGColorBehavior
from .utils import alert,absurl
class EmptyBox(Label):
def __init__(self,size_cnt=1):
@ -80,8 +85,11 @@ class TreeNode(BoxLayout):
self.data = data
self.content = None
self.children_open = False
self.subNodes = []
self.node_box = BoxLayout(orientation='horizontal',size_hint_y=None)
self.nodes = []
self.initChildren = False
self.node_box = BoxLayout(orientation='horizontal',
spacing=CSize(0.5),
size_hint_y=None)
self.node_box1 = BoxLayout(orientation='horizontal')
n = data.get('children')
self.hasChildren_nodes = True if not n is None else False
@ -122,7 +130,7 @@ class TreeNode(BoxLayout):
self.node_box.width = width
if self.node_box1.width < width:
self.node_box1.width = width
for n in self.subNodes:
for n in self.nodes:
n.setMinWidth(width)
def buildContent(self):
@ -134,28 +142,32 @@ class TreeNode(BoxLayout):
self.node_box.height = self.content.height
self.node_box.width = self.trigger.width + \
self.content.width
logging.info('Tree : content=(%d,%d),box=(%d,%d)', \
Logger.info('Tree : content=(%d,%d),box=(%d,%d)', \
self.content.width,self.content.height,
self.node_box.width,self.node_box.height)
def buildChildren(self):
if self.initChildren == True:
return
if self.data.children is None:
logging.info('Tree : is a leaf node')
Logger.info('Tree : is a leaf node')
return
if self.data.children == []:
self.treeObj.getUrlData(self.addChildren,self.data)
return
if len(self.subNodes) == 0:
logging.info('Tree : add subnodes')
if len(self.nodes) == 0:
Logger.info('Tree : add subnodes')
self.addChildren(self.data.children)
else:
logging.info('Tree : not need add subnodes')
Logger.info('Tree : not need add subnodes')
self.initChildren = True
def childrenSizeChange(self,tn,v):
h = 0
w = 0
for n in self.subNodes:
for n in self.nodes:
h += n.height
w = max(w,n.width)
@ -176,13 +188,72 @@ class TreeNode(BoxLayout):
options['data'] = c
tn = self.treeObj.NodeKlass(**options)
tn.bind(size=self.childrenSizeChange)
self.subNodes.append(tn)
self.nodes.append(tn)
self.children_box.add_widget(tn)
self.children_box.height += tn.height
self.children_box.width = max(self.children_box.width,tn.width)
self.node_box1.height = self.children_box.height
self.node_box1.width = self.trigger.width + self.children_box.width
def addNodeByData(self,data):
options = {}
options['tree'] = self.treeObj
options['parentNode'] = self
if isinstance(data,dict):
options['data'] = DictObject(**data)
else:
options['data'] = data
w = self.treeObj.NodeKlass(**options)
self.nodes.append(w)
self.children_box.add_widget(w)
self.childrenSizeChange(None,None)
def addNode(self,node):
node.parentNode = self
self.nodes.append(node)
self.children_box.add_widget(node)
self.childrenSizeChange(None,None)
def deleteNode(self,node):
self.children_box.remove_widget(node)
self.nodes = [ i for i in self.nodes if i != node ]
self.childrenSizeChange(None,None)
def delete(self):
parentNode = self.parentNode if self.parentNode else self.treeObj
parentNode.deleteNode(self)
def expand(self):
if self.children_open:
return
self.toggleChildren(None)
def collapse(self):
if not self.children_open:
return
self.toggleChildren(None)
def expandall(self):
if not self.children_open:
self.toggleChildren(None)
for n in self.nodes:
n.expandall()
def collapseall(self):
if self.children_open:
self.toggleChildren(None)
for n in self.nodes:
n.collapseall(None)
def moveNode(self,node,newParent=None):
old_parent = node.parent
if old_parent == None:
old_parent = node.treeObj
old_parent.deleteNode(node)
if newParent == None:
newParent = node.treeObj
newParent.addNode(node)
def toggleChildren(self,o):
self.treeObj.unselect_row()
self.children_open = True if not self.children_open else False
@ -195,16 +266,14 @@ class TreeNode(BoxLayout):
# when a widget remove from its parent, the get_parent_window()
# will return a None
# w1 = self.children_box.get_parent_window()
# logging.info('Tree :get_parent_window() return=%s',str(type(w1)))
# Logger.info('Tree :get_parent_window() return=%s',str(type(w1)))
"""
tree options
{
"url":
"params",
"bg_color",
"color_level",
"color",
"checkbox",
"multplecheck",
"idField",
@ -230,14 +299,14 @@ class Tree(BGColorBehavior, ScrollWidget):
def unselect_row(self):
if self.selected_node:
logging.info('selected node unselected')
Logger.info('selected node unselected')
self.selected_node.unselected()
self.selected_node = None
def onSize(self,o,v=None):
if not self.initflag:
self.initflag = True
self.buildTree(self)
self.buildTree()
for n in self.nodes:
n.setMinWidth(self.width)
@ -246,13 +315,23 @@ class Tree(BGColorBehavior, ScrollWidget):
def getUrlData(self,callback,kv=None):
hc = App.get_running_app().hc
params = self.options.get(params,{}).copy()
if not data is None:
for k,v in kv:
params = self.options.get('params',{}).copy()
if not kv is None:
for k,v in kv.items():
if k in params.keys():
params[k] = v
params['id'] = kv[self.options.idField]
hc.get(self.url,params=params,callback=callback)
config = getConfig()
url = absurl(self.options.url,None)
Logger.info('Tree: getUrlData(),url=%s',url)
hc.get(url,params=params,
callback=callback,
errback=self.showError)
def showError(self,o,e):
traceback.print_exc()
Logger.info('Tree: showError() o=%s,e=%s',o,e)
alert(e,title='error')
def buildTree(self,kv=None):
if not hasattr(self,'NodeKlass'):
@ -260,16 +339,18 @@ class Tree(BGColorBehavior, ScrollWidget):
if self.options.url:
return self.getUrlData(self.dataLoaded,kv)
data = self.options.data
logging.info("Tree : buildTree,data=%s",data)
self.dataLoaded(data)
data = self.options.data or []
Logger.info("Tree : buildTree,data=%s",data)
self.dataLoaded(None,data)
self.color, self.bgcolor = getColors(self.color_level)
def dataLoaded(self,d):
def dataLoaded(self,o,d):
Logger.info("Tree: dataLoaded,d=%s",d)
self.data = d
self.addNodes()
def addNodes(self):
Logger.info("Tree: addNodes()")
for c in self.data:
options = {}
options['tree'] = self
@ -278,7 +359,22 @@ class Tree(BGColorBehavior, ScrollWidget):
w = self.NodeKlass(**options)
self.nodes.append(w)
self.add_widget(w)
logging.info('Tree : node=%s',type(w))
Logger.info('Tree : node=%s',type(w))
def addNode(self,data,parentNode=None):
options = {}
options['tree'] = self
options['parentNode'] = None
options['data'] = DictObject(**data)
w = self.NodeKlass(**options)
self.nodes.append(w)
self.add_widget(w)
def deleteNode(self,node):
if self.selected_node == node:
self.selected_node = None
self.remove_widget(node)
self.nodes = [ i for i in self.nodes if i != node ]
class TextContent(PressableLabel):
def __init__(self,level=0,**options):
@ -309,22 +405,21 @@ class TextTreeNode(TreeNode):
def onPress(self,o,v=None):
if self.hasChildren_nodes:
v = True if not self.children_open else False
self.toggleChildren(self)
self.trigger.on_press()
return
logging.info('select the leaf node')
Logger.info('select the leaf node')
self.treeObj.select_row(self)
def selected(self):
logging.info('content selected ........')
Logger.info('content selected ........')
color, bgcolor = getColors(self.treeObj.color_level,
selected=True)
self.content.bgcolor = bgcolor
self.content.color = color
def unselected(self):
logging.info('content unselected ........')
Logger.info('content unselected ........')
color, bgcolor = getColors(self.treeObj.color_level,
selected=False)
self.content.bgcolor = bgcolor
@ -336,13 +431,45 @@ class TextTree(Tree):
super().__init__(**options)
self.register_event_type('on_press')
def onPress(self,o,v=None):
if self.selectNode:
self.selectNode.unselected()
self.selectNode = o
o.selected()
self.dispatch(on_press,o,o.data)
def select_row(self, node):
super().select_row(node)
self.dispatch('on_press',node.data)
def on_press(self,o,v):
def on_press(self,o,v=None):
print('TextTree():on_press(),o=',o,'v=',v)
class PopupMenu(BoxLayout):
def __init__(self,target,menudesc,**opts):
self.target = target
self.menudesc = menudesc
BoxLayout.__init__(self, size_hint=(0.5,0.5))
self.menu_tree = TextTree(**menudesc)
self.add_widget(self.menu_tree)
self.menu_tree.bind(on_press=self.onMenuItemTouch)
self.register_event_type('on_press')
def on_press(self,o,v=None):
Logger.info('PopupMenu: on_press fired')
def onMenuItemTouch(self,o,d=None,v=None):
Logger.info('MenuTree: on_press fired,o=%s,d=%s,v=%s',o,d,v)
data = {
'target':self.target,
'menudata':d
}
self.dispatch('on_press',data)
self.dismiss()
def open(self):
Window.add_widget(self)
self.center = Window.center
def dismiss(self):
Window.remove_widget(self)
def on_touch_down(self,touch):
if not self.collide_point(*touch.pos):
self.dismiss()
return False
super().on_touch_down(touch)
return True

View File

@ -1,5 +1,6 @@
import sys
import re
from kivy.logger import Logger
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
@ -25,14 +26,14 @@ class BoolInput(Switch):
a.active = kw.get('value')
super().__init__(**a)
self.register_event_type('on_changed')
self.bind(active=on_active)
self.bind(change=self.on_change)
def on_change(self,t,v):
def on_changed(self,v=None):
pass
def on_active(self,t,v):
change = True
self.dispatch('on_changed',v)
def getValue(self):
return self.active
@ -41,43 +42,53 @@ class BoolInput(Switch):
self.active = v
class StrInput(TextInput):
change = BooleanProperty(False)
def __init__(self,**kv):
if kv is None:
kv = {}
a = {
# "allow_copy":True,
"font_size":CSize(1),
"allow_copy":True,
"font_size":1,
"multiline":False,
"halign":"left",
"hint_text":"",
"size_hint_y":None,
"height":CSize(2)
"width":20,
"height":2.5
}
if kv.get('tip'):
a['hint_text'] = kv['tip']
a.update(kv)
a['multiline'] = False
super(StrInput,self).__init__(**a)
self.bind(focus=self.on_focus)
self.bind(text=self.on_text)
self.bind(change=self.on_change)
a['width'] = CSize(kv.get('width',20))
a['height'] = CSize(kv.get('height',2.5))
a['font_size'] = CSize(kv.get('font_size',1))
def on_change(self,t,v):
if v:
pass
super(StrInput,self).__init__(**a)
self.old_value = None
self.register_event_type('on_changed')
self.bind(focus=self.on_focus)
self.bind(on_text_validate=self.checkChange)
def on_changed(self,v=None):
pass
def on_text(self,t,v):
self.change = True
def checkChange(self,o,v=None):
v = self.getValue()
if v != self.old_value:
self.dispatch('on_changed',v)
def on_focus(self,t,v):
self.change = False
if v:
self.old_value = self.getValue()
else:
self.checkChange(None)
def getValue(self):
return self.text
def setValue(self,v):
self.text = v
if v is None:
v = ''
self.text = str(v)
class Password(StrInput):
def __init__(self, **kw):
@ -97,6 +108,10 @@ class IntegerInput(StrInput):
s = re.sub(pat, '', substring)
return StrInput.insert_text(self,s, from_undo=from_undo)
def on_focus(self,t,v):
super().on_focus(t,v)
self.cursor = (0,len(self.text))
class FloatInput(IntegerInput):
pat = re.compile('[^0-9]')
def filter(self,substring):
@ -167,18 +182,17 @@ class MyDropDown(DropDown):
for d in data:
dd = (d[self.valueField],d[self.textField])
b = Button(text=d[self.textField],font_size=CSize(1),
size_hint_y=None,
height=CSize(1.8))
size_hint=(None,None),
width=CSize(10),
height=CSize(2.5))
setattr(b,'kw_data',dd)
b.bind(on_release=lambda btn: self.select(btn.kw_data))
self.add_widget(b)
#print(dd)
def setDataByUrl(self,url,params={}):
def x(obj,resp):
if resp.status_code == 200:
d = resp.json()
self.setData(d)
def x(obj,d):
self.setData(d)
app = App.get_running_app()
app.hc.get(url,params=params,callback=x)
@ -191,8 +205,11 @@ class MyDropDown(DropDown):
class SelectInput(BoxLayout):
def __init__(self,**kw):
super(SelectInput,self).__init__(orientation='horizontal', size_hint_y=None,height=CSize(1.8))
self.tinp = StrInput()
super(SelectInput,self).__init__(orientation='horizontal',\
size_hint=(None,None),
width=CSize(kw.get('width',10)),
height=CSize(kw.get('height',2.5)))
self.tinp = StrInput(size_hint_y=None,height=kw.get('height',2.5))
self.tinp.readonly = True
newkw = {}
newkw.update(kw)
@ -205,17 +222,26 @@ class SelectInput(BoxLayout):
self.si_data = ''
self.text = ''
self.add_widget(self.tinp)
self.old_value = self.si_data
self.tinp.bind(focus=self.showDropdown)
self.register_event_type('on_changed')
def on_changed(self,v=None):
pass
def showDropdown(self,instance,yn):
# if self.collide_point(*touch.pos):
if yn:
self.tinp.focus = False
self.dropdown.showme(self)
self.old_value = self.getValue()
def setData(self,d):
self.tinp.si_data = d[0]
self.tinp.text = d[1]
v = self.getValue()
if v != self.old_value:
self.dispatch('on_changed',v)
def setValue(self,v):
self.tinp.si_value = v