From 2490b46b46f76a247a196e15395f1b0d85efda12 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sat, 18 Apr 2020 11:49:13 +0800 Subject: [PATCH] bugfix --- kivyblocks/baseWidget.py | 36 ++++++ kivyblocks/bgcolorbehavior.py | 26 ++-- kivyblocks/tree.py | 201 +++++++++++++++++++++++++------ kivyblocks/widgetExt/inputext.py | 80 +++++++----- 4 files changed, 266 insertions(+), 77 deletions(-) diff --git a/kivyblocks/baseWidget.py b/kivyblocks/baseWidget.py index caad591..c918e35 100755 --- a/kivyblocks/baseWidget.py +++ b/kivyblocks/baseWidget.py @@ -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) + diff --git a/kivyblocks/bgcolorbehavior.py b/kivyblocks/bgcolorbehavior.py index 48538b0..8916940 100644 --- a/kivyblocks/bgcolorbehavior.py +++ b/kivyblocks/bgcolorbehavior.py @@ -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) diff --git a/kivyblocks/tree.py b/kivyblocks/tree.py index e635d2b..7177f4e 100644 --- a/kivyblocks/tree.py +++ b/kivyblocks/tree.py @@ -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 diff --git a/kivyblocks/widgetExt/inputext.py b/kivyblocks/widgetExt/inputext.py index ed7088e..a0d9f99 100755 --- a/kivyblocks/widgetExt/inputext.py +++ b/kivyblocks/widgetExt/inputext.py @@ -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