kivyblocks/kivyblocks/baseWidget.py
2023-10-26 17:55:34 +08:00

672 lines
17 KiB
Python
Executable File

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
from kivy.app import App
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.metrics import dp
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.actionbar import ActionBar,ActionView,ActionPrevious,ActionItem,ActionButton
from kivy.uix.actionbar import ActionToggleButton, ActionCheck,ActionSeparator,ActionGroup
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.scatterlayout import ScatterLayout
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.switch import Switch
from kivy.uix.textinput import TextInput
from kivy.uix.dropdown import DropDown
from kivy.uix.tabbedpanel import TabbedPanel,TabbedPanelContent,TabbedPanelHeader,TabbedPanelItem
from kivy.uix.treeview import TreeView
from kivy.uix.image import AsyncImage,Image
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.scrollview import ScrollView
from kivy.uix.splitter import Splitter
from kivy.uix.spinner import Spinner
from kivy.uix.slider import Slider
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.sandbox import Sandbox
from kivy.uix.progressbar import ProgressBar
from kivy.uix.popup import Popup
from kivy.uix.modalview import ModalView
from kivy.uix.filechooser import FileChooser
from kivy.uix.effectwidget import EffectWidget
from kivy.uix.colorpicker import ColorPicker
from kivy.uix.carousel import Carousel
from kivy.uix.bubble import Bubble
from kivy.uix.codeinput import CodeInput
from kivy.graphics import Color, Rectangle
from kivy.properties import ListProperty, DictProperty
from kivy.factory import Factory
from appPublic.dictObject import DictObject
from appPublic.jsonConfig import getConfig
from appPublic.folderUtils import listFile
from .widgetExt.scrollwidget import ScrollWidget
from .widgetExt.binstateimage import BinStateImage
from .widgetExt.jsoncodeinput import JsonCodeInput
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, blockImage
from .login import LoginForm
from .tab import TabsPanel
from .threadcall import HttpClient
from .i18n import I18n
from .widget_css import WidgetCSS
from .ready import WidgetReady
from .utils import CSize
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')
}
if platform == 'android':
from .widgetExt.phonebutton import PhoneButton
from .widgetExt.androidwebview import AWebView
class WrapText(Label):
def __init__(self, **kw):
Label.__init__(self, **kw)
self.bind(width=lambda *x: self.setter('text_size')(self, (self.width, None)),
texture_size=lambda *x: self.setter('height')(self, self.texture_size[1]))
class StackBox(WidgetCSS, WidgetReady, StackLayout):
def __init__(self, **kw):
super().__init__(**kw)
class AnchorBox(WidgetCSS, WidgetReady, AnchorLayout):
def __init__(self, **kw):
super().__init__(**kw)
def FloatBox(WidgetCSS, WidgetReady, FloatLayout):
def __init__(self, **kw):
super().__init__(**kw)
def RelativeBox(WidgetCSS, WidgetReady, RelativeLayout):
def __init__(self, **kw):
super().__init__(**kw)
def GridBox(WidgetCSS, WidgetReady, GridLayout):
def __init__(self, **kw):
super().__init__(**kw)
def PageBox(WidgetCSS, WidgetReady, PageLayout):
def __init__(self, **kw):
super().__init__(**kw)
def ScatterBox(WidgetCSS, WidgetReady, ScatterLayout):
def __init__(self, **kw):
super().__init__(**kw)
class Box(WidgetCSS, WidgetReady, BoxLayout):
def __init__(self, **kw):
try:
super(Box, self).__init__(**kw)
except Exception as e:
print('Box(',kw,') Error')
raise e
class HBox(Box):
def __init__(self,**kw):
kw.update({'orientation':'horizontal'})
Box.__init__(self, **kw)
class VBox(Box):
def __init__(self,**kw):
kw.update({'orientation':'vertical'})
Box.__init__(self, **kw)
class SwipeBox(SwipeBehavior, Box):
pass
class Text(Label):
lang=StringProperty('')
otext = StringProperty('')
def __init__(self,i18n=False, texttype='text', wrap=False, rate=1,
fgcolor=None, **kw):
app = App.get_running_app()
self.rate = 1
fontsize = app.get_font_size(texttype) * self.rate
fontname = font_names.get(texttype)
self._i18n = i18n
self.i18n = I18n()
self.bgcolor = fgcolor
kwargs = kw.copy()
config = getConfig()
self.wrap = wrap
kwargs.update({
'font_size':fontsize,
'font_name':fontname
})
if not kwargs.get('text'):
kwargs['text'] = kwargs.get('otext','')
super(Text, self).__init__(**kwargs)
app.bind(font_size=self.reset_font_size)
# self.bind(texture_size=self.setter('size'))
if self._i18n:
self.i18n.addI18nWidget(self)
if self.wrap:
self.size_hint_y = None
self.text_size = self.width, None
self.bind(width=self.set_widget_height)
if self.bgcolor:
self.color = self.bgcolor
def reset_font_size(self, app, fsize):
self.font_size = fsize * self.rate
def resize(self, *args):
if not self.size_hint_y:
ps = [0,0,0,0]
if hasattr(self.parent,'padding'):
ps = self.parent.padding
self.width = self.parent.width - ps[0] - ps[2]
if self.width > 0:
self.set_widget_height()
def set_widget_height(self, *args):
if self.width == 0:
return
self.text_size = self.width, None
rows = len(self.text) * (self.font_size * 0.621) / self.width
rows = math.ceil(rows)
self.height = rows * self.font_size * 1.5
def get_wraped_size(self):
if self.text:
self._label.refresh()
return self._label.size
return (None,None)
def on_size(self,o,size):
# super().on_size(o,size)
if self.wrap:
font_size = self.font_size
self.text_size = self.width, None
def on_otext(self,o,v=None):
if self._i18n:
self.text = self.i18n(self.otext)
else:
self.text = self.otext
def changeLang(self,lang):
self.lang = lang
def on_lang(self,o,lang):
if self._i18n and self.otext:
self.text = self.i18n(self.otext)
class Title1(Text):
def __init__(self, **kw):
kw.update({'texttype':'title1','bold':True})
Text.__init__(self, **kw)
class Title2(Text):
def __init__(self, **kw):
kw.update({'texttype':'title2','bold':True})
Text.__init__(self, **kw)
class Title3(Text):
def __init__(self, **kw):
kw.update({'texttype':'title3','bold':True})
Text.__init__(self, **kw)
class Title4(Text):
def __init__(self, **kw):
kw.update({'texttype':'title4','bold':True})
Text.__init__(self, **kw)
class Title5(Text):
def __init__(self, **kw):
kw.update({'texttype':'title5','bold':True})
Text.__init__(self, **kw)
class Title6(Text):
def __init__(self, **kw):
kw.update({'texttype':'title6','bold':True})
Text.__init__(self, **kw)
class Modal(VBox):
content = DictProperty(None)
auto_open = BooleanProperty(True)
auto_dismiss = BooleanProperty(True)
target = StringProperty(None)
position = OptionProperty('cc',options=['tl', 'tc', 'tr',
'cl', 'cc', 'cr',
'bl', 'bc', 'br'])
def __init__(self, **kw):
self._target = None
super(Modal, self).__init__(**kw)
self.set_size_position()
print('size_hint=', self.size_hint, kw)
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)
if self.content_w:
self.add_widget(self.content_w)
else:
print(content,':cannot build widget')
def on_touch_down(self, touch):
if not self.collide_point(touch.x, touch.y):
if self.auto_dismiss:
self.dispatch('on_pre_dismiss')
self.dismiss()
return True
return super().on_touch_down(touch)
def on_target(self, o, target):
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("size with target:", self.width, self.height,
self.size_hint, self.size_hint,
"target size=", 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 = (self._target.width - self.width) / 2
elif xn == 'r':
x = self._target.width - self.width
if x < 0:
x = 0
if yn == 'c':
y = (self._target.height - self.height) / 2
elif yn == 'b':
y = self._target.height - self.height
if y < 0:
y = 0
if self._target == Window:
self.pos = x, y
else:
self.pos = self._target.pos[0] + x, self._target.pos[1] + y
Mx = Window.width
My = Window.height
self.pos_hint = {'x':self.pos[0]/Mx, 'y':self.pos[1]/My}
def open(self):
if self.parent:
self.parent.remove_widget(self)
self.dispatch('on_pre_open')
Window.add_widget(self)
self.dispatch('on_open')
if self._target != Window:
self._target.disabled = True
def dismiss(self, *args):
self.dispatch('on_pre_dismiss')
self.dispatch('on_dismiss')
Window.remove_widget(self)
if self._target != Window:
self._target.enabled = False
def on_open(self, *args):
pass
def on_dismiss(self, *args):
pass
def on_pre_open(self, *args):
pass
def on_pre_dismiss(self, *args):
pass
def add_widget(self, w, *args, **kw):
super().add_widget(w, *args, **kw)
if self.auto_open:
self.open()
class TimedModal(Modal):
show_time = NumericProperty(0)
def __init__(self, **kw):
self.time_task = None
super(TimedModal, self).__init__(**kw)
def open(self, *args, **kw):
if self.time_task is not None:
self.time_task.cancel()
self.time_task = None
if self.show_time > 0:
self.time_task = \
Clock.schedule_once(self.dismiss, self.show_time)
super().open(*args, **kw)
def dismiss(self, *args, **kw):
if self.time_task:
self.time_task.cancel()
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):
Window.remove_widget(self)
self.host_widget.disabled = False
print('dismiss() called ..')
def set_size(self, *args):
self.size_hint = (None, None)
self.width = CSize(3)
self.height = CSize(3)
self.center = self.host_widget.center
class PressableImage(ButtonBehavior,AsyncImage):
def on_press(self):
pass
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
self.subfixes=subfixes
self.params = params
self.page_rows = self.params.get('page_rows',60)
self.page = self.params.get('page',1)
if not url.startswith('file://'):
raise Exception('%s is not a file url' % url)
self.files = [i for i in listFile(url[7:],suffixs=suffixs, \
rescursive=self.params.get('rescursive',False)) ]
self.total_files = len(self.files)
x = 0 if self.total_files % self.page_rows == 0 else 1
self.total_pages = self.total_files / self.page_rows + x
self.register_event_type('on_success')
self.register_event_type('on_error')
def on_success(self,*args):
return
def on_error(self, *args):
return
def handle(self,params={}):
d = {}
d['total'] = self.total_files
d['rows'] = []
p = self.params.copy()
p.update(params)
page = p.get('page')
for i in range(self.page_rows):
try:
r = self.files[page * self.page_rows + i]
d['rows'].append({'filename':r,
'id':r,
'url':r
})
except:
break
self.dispatch('on_success',d)
class HTTPDataHandler(EventDispatcher):
def __init__(self, url='',method='GET',params={},
headers={},
files={},
stream=False
):
EventDispatcher.__init__(self)
self.url = url
self.method = method
self.params = params
self.headers = headers
self.files=files
self.stream=stream
self.register_event_type('on_success')
self.register_event_type('on_error')
def on_success(self,*args):
print('HTTPDataHandler():',self.url,'finished...')
pass
def on_error(self,*args):
pass
def onSuccess(self,o,v):
print(self.url,'onSuccess() called')
self.dispatch('on_success',v)
def onError(self,o,e):
print(self.url,'onError():v=',e)
self.dispatch('on_error',e)
print_exc()
print('[****][*********] onError Called',e)
def redo(self,o,v=None):
self.handle()
def handle(self,params={},headers={}):
p = self.params
p.update(params)
h = self.headers
h.update(headers)
hc = HttpClient()
print('HTTPDataHandler():',self.url,'handling....')
hc(self.url,method=self.method,
params=p,
headers=h,
files=self.files,
stream=self.stream,
callback=self.onSuccess,
errback=self.onError)
class HTTPSeriesData(HTTPDataHandler):
def __init__(self, rows=20, page=1, **kw):
super(HTTPSeriesData, self).__init__(**kw)
self.rows = rows
self.page = page
self.maxpage = 999999
def load(self, page=1, **params):
self.params.update(params)
if page == 1:
self.maxpage = 999999
self.page = page
params = {
'page': self.page,
'rows': self.rows
}
self.handle(params=params)
def loadPrevious(self, **params):
self.params.update(params)
if self.page > 0:
self.load(page=self.page - 1)
def loadNext(self, **params):
self.params.update(params)
if self.page < self.maxpage:
self.load(page=self.page + 1)
def getDataHandler(url,**kwargs):
if url.startswith('file://'):
return FILEDataHandler(url,**kwargs)
return HTTPDataHandler(url, **kwargs)
def device_type():
if platform != "android" and platform != "ios":
return "desktop"
elif Window.width >= dp(600) and Window.height >= dp(600):
return "tablet"
else:
return "mobile"
class ExAccordion(WidgetCSS, WidgetReady, Accordion):
"""
{
"widgettype":"Accordion",
"options":{
"csscls",
"items":[{
"title":"titl1",
"active":false,
"widget":{
}
}
]
}
}
"""
items = ListProperty(None)
def __init__(self, **kw):
print('ExAccordion() init...')
super().__init__(**kw)
self.width = self.height = 800
# Clock.schedule_once(self._build_children, 0.1)
self._build_children()
def _build_children(self, *args):
for i in self.items:
bk = Factory.Blocks()
w = bk.widgetBuild(i['widget'])
if w is None:
continue
tw = AccordionItem()
if isinstance(i['title'], str):
tw.title = i['title']
else:
tw.title = bk.widgetBuild(i['title'])
tw.add_widget(w)
if i.get('active'):
self.collapse = False
else:
self.collapse = True
self.add_widget(tw)
class Slider(Carousel):
"""
{
"widgettype":"Slider",
"options":{
"direction":"right",
"loop":true,
"items":[
{
"widgettype":....
}
]
}
}
"""
items = ListProperty(None)
def __init__(self, **kw):
print('Carousel pro=', dir(Carousel))
super().__init__(**kw)
bk = Factory.Blocks()
for desc in self.items:
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)