diff --git a/examples/widgets_overview_app.py b/examples/widgets_overview_app.py
index a5df611a..c8d1cc62 100644
--- a/examples/widgets_overview_app.py
+++ b/examples/widgets_overview_app.py
@@ -1,3 +1,4 @@
+#!/usr/bin/python
"""
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -29,9 +30,11 @@ def main(self):
# the margin 0px auto centers the main container
verticalContainer = gui.Widget(width=540, margin='0px auto', style={'display': 'block', 'overflow': 'hidden'})
- horizontalContainer = gui.Widget(width='100%', layout_orientation=gui.Widget.LAYOUT_HORIZONTAL, margin='0px', style={'display': 'block', 'overflow': 'auto'})
-
- subContainerLeft = gui.Widget(width=320, style={'display': 'block', 'overflow': 'auto', 'text-align': 'center'})
+ horizontalContainer = gui.Widget(width='100%', layout_orientation=gui.Widget.LAYOUT_HORIZONTAL, margin='0px',
+ style={'display': 'block', 'overflow': 'auto'})
+
+ self.subContainerLeft = subContainerLeft = gui.Widget(width=320,
+ style={'display': 'block', 'overflow': 'auto', 'text-align': 'center'})
self.img = gui.Image('/res:logo.png', height=100, margin='10px')
self.img.onclick.do(self.on_img_clicked)
@@ -44,7 +47,7 @@ def main(self):
self.table.on_table_row_click.do(self.on_table_row_click)
# the arguments are width - height - layoutOrientationOrizontal
- subContainerRight = gui.Widget(style={'width': '220px', 'display': 'block', 'overflow': 'auto', 'text-align': 'center'})
+ self.subContainerRight = subContainerRight = gui.Widget(style={'width': '220px', 'display': 'block', 'overflow': 'auto', 'text-align': 'center'})
self.count = 0
self.counter = gui.Label('', width=200, height=30, margin='10px')
@@ -96,13 +99,16 @@ def main(self):
self.date = gui.Date('2015-04-13', width=200, height=20, margin='10px')
self.date.onchange.do(self.date_changed)
+ self.radioGroup = gui.RadioHGroup(['radio1', 'radio2'], 0, width=200, height=20, margin='10px')
+ self.radioGroup.onchange.do(self.on_radio_change)
+
self.video = gui.Widget( _type='iframe', width=290, height=200, margin='10px')
self.video.attributes['src'] = "https://drive.google.com/file/d/0B0J9Lq_MRyn4UFRsblR3UTBZRHc/preview"
self.video.attributes['width'] = '100%'
self.video.attributes['height'] = '100%'
self.video.attributes['controls'] = 'true'
self.video.style['border'] = 'none'
-
+
self.tree = gui.TreeView(width='100%', height=300)
ti1 = gui.TreeItem("Item1")
ti2 = gui.TreeItem("Item2")
@@ -117,9 +123,10 @@ def main(self):
self.tree.append([ti1, ti2, ti3])
ti2.append([subti1, subti2, subti3, subti4])
subti4.append([subsubti1, subsubti2, subsubti3])
-
+
# appending a widget to another, the first argument is a string key
- subContainerRight.append([self.counter, self.lbl, self.bt, self.txt, self.spin, self.progress, self.check, self.btInputDiag, self.btFileDiag])
+ subContainerRight.append([self.counter, self.lbl, self.bt, self.txt, self.spin, self.progress, self.check,
+ self.radioGroup, self.btInputDiag, self.btFileDiag])
# use a defined key as we replace this widget later
fdownloader = gui.FileDownloader('download test', '../remi/res/logo.png', width=200, height=30, margin='10px')
subContainerRight.append(fdownloader, key='file_downloader')
@@ -130,10 +137,8 @@ def main(self):
horizontalContainer.append([subContainerLeft, subContainerRight])
- menu = gui.Menu(width='100%', height='30px')
+ self.menu = menu = gui.Menu(width='100%', height='30px')
m1 = gui.MenuItem('File', width=100, height=30)
- m2 = gui.MenuItem('View', width=100, height=30)
- m2.onclick.do(self.menu_view_clicked)
m11 = gui.MenuItem('Save', width=100, height=30)
m12 = gui.MenuItem('Open', width=100, height=30)
m12.onclick.do(self.menu_open_clicked)
@@ -141,12 +146,32 @@ def main(self):
m111.onclick.do(self.menu_save_clicked)
m112 = gui.MenuItem('Save as', width=100, height=30)
m112.onclick.do(self.menu_saveas_clicked)
+ m2 = gui.MenuItem('View', width=100, height=30)
+ m21 = gui.MenuItem('Left panel', width=100, height=30)
+ m211 = gui.MenuItem('Disable', width=100, height=30)
+ m211.onclick.do(self.menu_disable_left_panel_clicked)
+ m212 = gui.MenuItem('Hide', width=100, height=30)
+ m212.onclick.do(self.menu_hide_left_panel_clicked)
+ m22 = gui.MenuItem('Right panel', width=100, height=30)
+ m221 = gui.MenuItem('Disable', width=100, height=30)
+ m221.onclick.do(self.menu_disable_right_panel_clicked)
+ m222 = gui.MenuItem('Hide', width=100, height=30)
+ m222.onclick.do(self.menu_hide_right_panel_clicked)
+ m23 = gui.MenuItem('About', width=100, height=30)
+ m231 = gui.MenuItem('Disable', width=100, height=30)
+ m231.onclick.do(self.menu_disable_about_clicked)
m3 = gui.MenuItem('Dialog', width=100, height=30)
m3.onclick.do(self.menu_dialog_clicked)
+ m4 = gui.MenuItem('About', width=100, height=30)
+ m4.onclick.do(self.menu_about_clicked)
- menu.append([m1, m2, m3])
+ menu.append([m1, m2, m3, {'about':m4}])
m1.append([m11, m12])
m11.append([m111, m112])
+ m2.append([m21, m22, m23])
+ m21.append([m211, m212])
+ m22.append([m221, m222])
+ m23.append([m231])
menubar = gui.MenuBar(width='100%', height='30px')
menubar.append(menu)
@@ -247,6 +272,9 @@ def on_spin_change(self, widget, newValue):
def on_check_change(self, widget, newValue):
self.lbl.set_text('CheckBox changed, new value: ' + str(newValue))
+ def on_radio_change(self, widget, value, idx, oldIdx):
+ self.lbl.set_text('Radio changed, on: radio%i' % (idx+1))
+
def open_input_dialog(self, widget):
self.inputDialog = gui.InputDialog('Input Dialog', 'Your name?',
initial_value='type here',
@@ -305,8 +333,36 @@ def menu_saveas_clicked(self, widget):
def menu_open_clicked(self, widget):
self.lbl.set_text('Menu clicked: Open')
- def menu_view_clicked(self, widget):
- self.lbl.set_text('Menu clicked: View')
+ def menu_disable_left_panel_clicked(self, widget):
+ self.lbl.set_text('Disable left panel')
+ flag = self.subContainerLeft.is_enabled()
+ self.subContainerLeft.set_enabled(not flag)
+ widget.set_text('Enable' if flag else 'Disable')
+
+ def menu_disable_right_panel_clicked(self, widget):
+ self.lbl.set_text('Disable right panel')
+ flag = self.subContainerRight.is_enabled()
+ self.subContainerRight.set_enabled(not flag)
+ widget.set_text('Enable' if flag else 'Disable')
+
+ def menu_hide_left_panel_clicked(self, widget):
+ self.lbl.set_text('Hide left panel')
+ flag = self.subContainerLeft.is_visible()
+ self.subContainerLeft.set_hidden(flag)
+ widget.set_text('Show' if flag else 'Hide')
+
+ def menu_hide_right_panel_clicked(self, widget):
+ self.lbl.set_text('Hide right panel')
+ flag = self.subContainerRight.is_visible()
+ self.subContainerRight.set_hidden(flag)
+ widget.set_text('Show' if flag else 'Hide')
+
+ def menu_disable_about_clicked(self, widget):
+ menu_about = self.menu.get_child('about')
+ flag = menu_about.is_enabled()
+ menu_about.set_enabled(not flag)
+ widget.set_text('Enable' if flag else 'Disable')
+ self.lbl.set_text('%s About' % ('Disable'if flag else 'Enable' ))
def fileupload_on_success(self, widget, filename):
self.lbl.set_text('File upload success: ' + filename)
@@ -314,6 +370,9 @@ def fileupload_on_success(self, widget, filename):
def fileupload_on_failed(self, widget, filename):
self.lbl.set_text('File upload failed: ' + filename)
+ def menu_about_clicked(self, widget):
+ self.lbl.set_text('Menu clicked: About')
+
def on_close(self):
""" Overloading App.on_close event to stop the Timer.
"""
diff --git a/remi/gui.py b/remi/gui.py
index 40a78ac6..15afb32c 100644
--- a/remi/gui.py
+++ b/remi/gui.py
@@ -66,7 +66,7 @@ def jsonize(d):
def load_resource(filename):
""" Convenient function. Given a local path and filename (not in standard remi resource format),
- loads the content and returns a base64 encoded data.
+ loads the content and returns a base64 encoded data.
This method allows to bypass the remi resource file management, accessing directly local disk files.
Args:
@@ -85,7 +85,7 @@ def load_resource(filename):
else:
data = str(data, 'utf-8')
return "data:%(mime)s;base64,%(data)s"%{'mime':mimetype, 'data':data}
-
+
def to_uri(uri_data):
""" Convenient function to encase the resource filename or data in url('') keyword
@@ -102,13 +102,13 @@ def to_uri(uri_data):
class EventSource(object):
def __init__(self, *args, **kwargs):
self.setup_event_methods()
-
+
def setup_event_methods(self):
for (method_name, method) in inspect.getmembers(self, predicate=inspect.ismethod):
_event_info = None
if hasattr(method, "_event_info"):
_event_info = method._event_info
-
+
if hasattr(method, '__is_event'):
e = ClassEventConnector(self, method_name, method)
setattr(self, method_name, e)
@@ -130,7 +130,7 @@ def __init__(self, event_source_instance, event_name, event_method_bound):
self.callback = None
self.userdata = None
self.connect = self.do #for compatibility reasons
-
+
def do(self, callback, *userdata):
""" The callback and userdata gets stored, and if there is some javascript to add
the js code is appended as attribute for the event source
@@ -529,7 +529,6 @@ class Widget(Tag, EventSource):
@decorate_constructor_parameter_types([])
def __init__(self, children = None, style = None, *args, **kwargs):
-
"""
Args:
children (Widget, or iterable of Widgets): The child to be appended. In case of a dictionary,
@@ -555,6 +554,10 @@ def __init__(self, children = None, style = None, *args, **kwargs):
self.set_layout_orientation(kwargs.get('layout_orientation', Widget.LAYOUT_VERTICAL))
self.set_size(kwargs.get('width'), kwargs.get('height'))
self.set_style(style)
+ self._saved_display_style = None
+ self._saved_pointer_events_style = None
+ self._visible = True
+ self._enable = True
if children:
self.append(children)
@@ -572,14 +575,60 @@ def set_style(self, style):
k, v = s.split(':', 1)
self.style[k.strip()] = v.strip()
+ def set_hidden(self, hided):
+ """
+ Args:
+ hided (bool): hide - if True, show - if False
+ """
+# for child in self.children.values():
+# if type(child) not in (type(''), type(u'')):
+# child.set_hidden(hided)
+ if hided:
+ self._saved_display_style = self.style.get('display', None)
+ try:
+ self.style['display'] = 'none'
+ except KeyError:
+ pass
+ else:
+ if self.style['display'] == 'none':
+ if self._saved_display_style is None:
+ del self.style['display']
+ else:
+ self.style['display'] = self._saved_display_style
+ self._visible = not hided
+
def set_enabled(self, enabled):
- if enabled:
+ """
+ Args:
+ enabled (bool): enable - if True, disable - if False
+ """
+ for child in self.children.values():
+ if type(child) not in (type(''), type(u'')):
+ child.set_enabled(enabled)
+ if not enabled:
+ self.attributes['disabled'] = 'true'
+ self._saved_pointer_events_style = self.style.get('pointer-events', None)
try:
- del self.attributes['disabled']
+ self.style['pointer-events'] = 'none'
except KeyError:
pass
else:
- self.attributes['disabled'] = 'True'
+ try:
+ del self.attributes['disabled']
+ except KeyError:
+ pass
+ if self.style['pointer-events'] == 'none':
+ if self._saved_pointer_events_style is None:
+ del self.style['pointer-events']
+ else:
+ self.style['pointer-events'] = self._saved_pointer_events_style
+ self._enable = enabled
+
+ def is_visible(self):
+ return self._visible
+
+ def is_enabled(self):
+ return self._enable
def set_size(self, width, height):
"""Set the widget size.
@@ -884,7 +933,7 @@ def onkeyup(self, key, keycode, ctrl, shift, alt):
"""Called when user types and releases a key.
The widget should be able to receive the focus in order to emit the event.
Assign a 'tabindex' attribute to make it focusable.
-
+
Args:
key (str): the character value
keycode (str): the numeric char code
@@ -903,7 +952,7 @@ def onkeydown(self, key, keycode, ctrl, shift, alt):
"""Called when user types and releases a key.
The widget should be able to receive the focus in order to emit the event.
Assign a 'tabindex' attribute to make it focusable.
-
+
Args:
key (str): the character value
keycode (str): the numeric char code
@@ -913,7 +962,7 @@ def onkeydown(self, key, keycode, ctrl, shift, alt):
@decorate_explicit_alias_for_listener_registration
def set_on_focus_listener(self, callback, *userdata):
self.onfocus.connect(callback, *userdata)
-
+
@decorate_explicit_alias_for_listener_registration
def set_on_blur_listener(self, callback, *userdata):
self.onblur.connect(callback, *userdata)
@@ -961,7 +1010,7 @@ def set_on_touchstart_listener(self, callback, *userdata):
@decorate_explicit_alias_for_listener_registration
def set_on_touchend_listener(self, callback, *userdata):
self.ontouchend.connect(callback, *userdata)
-
+
@decorate_explicit_alias_for_listener_registration
def set_on_touchenter_listener(self, callback, *userdata):
self.ontouchenter.connect(callback, *userdata)
@@ -1010,7 +1059,7 @@ def __init__(self, title, *args, **kwargs):
"""
""")
-
+
self._classes = []
self.set_title(title)
@@ -1026,7 +1075,7 @@ def set_icon_file(self, filename, rel="icon"):
def set_icon_data(self, base64_data, mimetype="image/png", rel="icon"):
""" Allows to define an icon for the App
-
+
Args:
base64_data (str): base64 encoded image data (ie. "data:image/x-icon;base64,AAABAAEAEBA....")
mimetype (str): mimetype of the image ("image/png" or "image/x-icon"...)
@@ -1322,7 +1371,7 @@ def __init__(self, *args, **kwargs):
loading_widget.set_identifier("loading")
self.append(loading_widget)
-
+
@decorate_set_on_listener("(self, emitter)")
@decorate_event_js("""sendCallback('%(emitter_identifier)s','%(event_name)s');
event.stopPropagation();event.preventDefault();
@@ -1396,6 +1445,7 @@ def define_grid(self, matrix):
Args:
matrix (list): list of iterables of strings (lists or something else).
Items in the matrix have to correspond to a key for the children.
+ The key must start with a letter.
"""
self.style['grid-template-areas'] = ''.join("'%s'"%(' '.join(x)) for x in matrix)
@@ -1433,7 +1483,7 @@ def append(self, value, key=''):
value.style['position'] = 'static'
return key
-
+
def remove_child(self, child):
if 'grid-area' in child.style.keys():
del child.style['grid-area']
@@ -1454,7 +1504,7 @@ def set_row_sizes(self, values):
values (iterable of int or str): values are treated as percentage.
"""
self.style['grid-template-rows'] = ' '.join(map(lambda value: (str(value) if str(value).endswith('%') else str(value) + '%') , values))
-
+
def set_column_gap(self, value):
"""Sets the gap value between columns
@@ -1524,11 +1574,11 @@ def set_from_asciiart(self, asciipattern):
i=rows[ri].find("|",i+1)
columns[row_max_width] = row_max_width
-
+
row_sizes = []
for r in row_defs.keys():
row_sizes.append(float(rows.count(rows[r]))/float(len(rows))*100.0)
-
+
column_sizes = []
prev_size = 0.0
for c in columns.values():
@@ -1580,7 +1630,7 @@ def append(self, value, key=''):
for child in value:
keys.append( self.append(child) )
return keys
-
+
key = str(key)
if not isinstance(value, Widget):
raise ValueError('value should be a Widget (otherwise use add_child(key,other)')
@@ -1847,7 +1897,7 @@ def onchange(self, new_value):
sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);""")
def onkeyup(self, new_value, keycode):
"""Called when user types and releases a key into the TextInput
-
+
Note: This event can't be registered together with Widget.onchange.
Args:
@@ -2127,7 +2177,7 @@ class ListView(Widget):
"""
@decorate_constructor_parameter_types([bool])
- def __init__(self, selectable = True, *args, **kwargs):
+ def __init__(self, selectable=True, *args, **kwargs):
"""
Args:
kwargs: See Widget.__init__()
@@ -2395,7 +2445,6 @@ def get_key(self):
def onchange(self, value):
"""Called when a new DropDownItem gets selected.
"""
- log.debug('combo box. selected %s' % value)
self.select_by_value(value)
return (value, )
@@ -2889,6 +2938,124 @@ def get_value(self):
return 'checked' in self.attributes
+class RadioHGroup(Widget):
+
+ @decorate_constructor_parameter_types([[str], int, [str]])
+ def __init__(self, labels, checked=0, user_data=[], **kwargs):
+ """
+ Args:
+ label [(str)]:
+ checked (int) or (str):
+ user_data [(str)]:
+ kwargs: See Widget.__init__()
+ """
+ super(RadioHGroup, self).__init__(**kwargs)
+ self.set_layout_orientation(Widget.LAYOUT_HORIZONTAL)
+ user_data = user_data or [ '%i' % i for i in xrange(len(labels)) ]
+ self._radiogroup = [ RadioBoxLabel(label, False, data) for label, data in zip(labels, user_data) ]
+ if type(checked) in (type(''), type(u'')):
+ checked = int(user_data.index(checked))
+ self._radiogroup[checked].set_value(True)
+ self._idx = checked
+ self.append(self._radiogroup)
+ for radiobox in self._radiogroup:
+ radiobox.onchange.connect(self.onchange)
+
+ @decorate_set_on_listener("(self, emitter, value)")
+ @decorate_event
+ def onchange(self, widget, value):
+ prev_idx = self._idx
+ self._radiogroup[prev_idx].set_value(False)
+ self._idx = self._radiogroup.index(widget)
+ return (value, self._idx, prev_idx)
+
+ @decorate_explicit_alias_for_listener_registration
+ def set_on_change_listener(self, callback, *userdata):
+ self.onchange.connect(callback, *userdata)
+
+# TODO
+#class RadioVGroup(RadioHGroup):
+# def __init__(self, labels, checked=0, user_data=[], **kwargs):
+# """
+# Args:
+# label [(str)]:
+# checked (int) or (str):
+# user_data [(str)]:
+# kwargs: See Widget.__init__()
+# """
+# super(RadioVGroup, self).__init__(labels, checked, user_data, **kwargs)
+# self.set_layout_orientation(Widget.LAYOUT_VERTICAL)
+
+
+class RadioBoxLabel(Widget):
+
+ @decorate_constructor_parameter_types([str, bool, str])
+ def __init__(self, label='', checked=False, user_data='', **kwargs):
+ """
+ Args:
+ label (str):
+ checked (bool):
+ user_data (str):
+ kwargs: See Widget.__init__()
+ """
+ super(RadioBoxLabel, self).__init__(**kwargs)
+ self.set_layout_orientation(Widget.LAYOUT_HORIZONTAL)
+ self._radiobox = RadioBox(checked, user_data)
+ self._label = Label(label)
+ self.append(self._radiobox, key='radiobox')
+ self.append(self._label, key='label')
+
+ self.set_value = self._radiobox.set_value
+ self.get_value = self._radiobox.get_value
+
+ self._radiobox.onchange.connect(self.onchange)
+
+ @decorate_set_on_listener("(self, emitter, value)")
+ @decorate_event
+ def onchange(self, widget, value):
+ return (value, )
+
+
+class RadioBox(Input):
+ """check box widget useful as numeric input field implements the onchange event."""
+
+ @decorate_constructor_parameter_types([bool, str])
+ def __init__(self, checked=False, user_data='', **kwargs):
+ """
+ Args:
+ checked (bool):
+ user_data (str):
+ kwargs: See Widget.__init__()
+ """
+ super(RadioBox, self).__init__('radio', user_data, **kwargs)
+ self.set_value(checked)
+ self.attributes[Widget.EVENT_ONCHANGE] = \
+ "var params={};params['value']=document.getElementById('%(emitter_identifier)s').checked;" \
+ "sendCallbackParam('%(emitter_identifier)s','%(event_name)s',params);"% \
+ {'emitter_identifier':str(self.identifier), 'event_name':Widget.EVENT_ONCHANGE}
+
+ @decorate_set_on_listener("(self, emitter, value)")
+ @decorate_event
+ def onchange(self, value):
+ value = value in ('True', 'true')
+ self.set_value(value)
+ return (self.attributes['value'], )
+
+ def set_value(self, checked, update_ui=1):
+ if checked:
+ self.attributes['checked'] = 'checked'
+ else:
+ if 'checked' in self.attributes:
+ del self.attributes['checked']
+
+ def get_value(self):
+ """
+ Returns:
+ bool:
+ """
+ return 'checked' in self.attributes
+
+
class SpinBox(Input):
"""spin box widget useful as numeric input field implements the onchange event.
"""
@@ -3284,7 +3451,6 @@ def __init__(self, text, *args, **kwargs):
self.set_text(text)
def append(self, value, key=''):
-
return self.sub_container.append(value, key=key)
@@ -3588,7 +3754,7 @@ def set_size(self, w, h):
class SvgImage(SvgRectangle):
- """svg image - a raster image element for svg graphics,
+ """svg image - a raster image element for svg graphics,
this have to be appended into Svg elements."""
@decorate_constructor_parameter_types([str, int, int, int, int])
@@ -3770,4 +3936,3 @@ def set_fill(self, color='black'):
color (str): stroke color
"""
self.attributes['fill'] = color
-