kuuko pushed a commit to branch master. http://git.enlightenment.org/bindings/python/python-efl.git/commit/?id=3661a78c96a6a264f131d5b9d344248180b68c6b
commit 3661a78c96a6a264f131d5b9d344248180b68c6b Author: Kai Huuhko <kai.huu...@gmail.com> Date: Tue Feb 2 20:29:21 2016 +0200 Elm.MultiButtonEntry: Attempt to fix leaks, add more docs --- efl/elementary/multibuttonentry.pxi | 150 +++++++++++++++++++++------ efl/elementary/multibuttonentry_cdef.pxi | 4 +- examples/elementary/test_multibuttonentry.py | 46 +++++--- 3 files changed, 153 insertions(+), 47 deletions(-) diff --git a/efl/elementary/multibuttonentry.pxi b/efl/elementary/multibuttonentry.pxi index aa874bc..13c28e3 100644 --- a/efl/elementary/multibuttonentry.pxi +++ b/efl/elementary/multibuttonentry.pxi @@ -18,24 +18,51 @@ include "multibuttonentry_cdef.pxi" +class MultiButtonEntryFilterOut(Exception): + """An exception you may raise in an item filter callback if you wish to prevent addition of the item""" + pass + cdef Eina_Bool _multibuttonentry_filter_callback(Evas_Object *obj, \ - const char *item_label, void *item_data, void *data) with gil: + char *item_label, void *item_data, void *data) with gil: cdef: MultiButtonEntry mbe = object_from_instance(obj) - bint ret - - (callback, a, ka) = <object>data - - try: - ret = callback(mbe, _ctouni(item_label), *a, **ka) - except Exception: - traceback.print_exc() - - return ret + object ret + list callbacks = mbe._item_filters + + for func, args, kargs in callbacks: + try: + # raise MultiButtonEntryFilterOut -> cancels item add + # ret is None -> no change, continue to next filter + # ret is not None -> change label to value of ret, continue to next filter + ret = func(mbe, _ctouni(item_label), *args, **kargs) + except MultiButtonEntryFilterOut: + #free(item_label) # FIXME: This will result in a double free, find out if it's bad elm documentation + # or wrong ref handling on our side. + + #item_label = NULL + return 0 + except Exception: + traceback.print_exc() + continue + + if ret: + if not isinstance(ret, basestring): + EINA_LOG_DOM_WARN(PY_EFL_ELM_LOG_DOMAIN, + "Ignoring invalid return value from MultiButtonEntry item filter callback!", NULL) + continue + if isinstance(ret, unicode): ret = PyUnicode_AsUTF8String(ret) + #free(item_label) # FIXME: This will result in a double free, find out if it's bad elm documentation + # or wrong ref handling on our side. + + item_label = strdup(ret) # Elm will manage the string + # FIXME: This doesn't apply, why? + + return 1 cdef char * _multibuttonentry_format_cb(int count, void *data) with gil: - (callback, a, ka) = <object>data + cdef MultiButtonEntry obj = <MultiButtonEntry>data + (callback, a, ka) = obj.internal_data["multibuttonentry_format_cb"] try: s = callback(count, *a, **ka) @@ -44,8 +71,7 @@ cdef char * _multibuttonentry_format_cb(int count, void *data) with gil: traceback.print_exc() return NULL - # FIXME: leak here - return strdup(<char *>s) + return strdup(s) # Elm will manage the string cdef class MultiButtonEntryItem(ObjectItem): @@ -198,6 +224,8 @@ cdef class MultiButtonEntry(Object): """ + cdef list _item_filters + def __init__(self, evasObject parent, *args, **kwargs): """MultiButtonEntry(...) @@ -213,6 +241,7 @@ cdef class MultiButtonEntry(Object): _py_elm_mbe_item_added_cb, NULL ) self._set_properties_from_keyword_args(kwargs) + self._item_filters = list() property entry: """The Entry object child of the multibuttonentry. @@ -227,7 +256,10 @@ cdef class MultiButtonEntry(Object): return object_from_instance(elm_multibuttonentry_entry_get(self.obj)) property expanded: - """The expanded state of the multibuttonentry. + """Control the multibuttonentry to expanded state. + + In expanded state, the complete entry will be displayed. + Otherwise, only single line of the entry will be displayed. :type: bool @@ -342,6 +374,11 @@ cdef class MultiButtonEntry(Object): return None property items: + """List of items in the multibuttonentry + + :type: list + + """ def __get__(self): return _object_item_list_to_python(elm_multibuttonentry_items_get(self.obj)) @@ -349,6 +386,11 @@ cdef class MultiButtonEntry(Object): return _object_item_list_to_python(elm_multibuttonentry_items_get(self.obj)) property first_item: + """The first item in the multibuttonentry + + :type: :class:`MultiButtonEntryItem` + + """ def __get__(self): return _object_item_to_python(elm_multibuttonentry_first_item_get(self.obj)) @@ -356,6 +398,11 @@ cdef class MultiButtonEntry(Object): return _object_item_to_python(elm_multibuttonentry_first_item_get(self.obj)) property last_item: + """The last item in the multibuttonentry + + :type: :class:`MultiButtonEntryItem` + + """ def __get__(self): return _object_item_to_python(elm_multibuttonentry_last_item_get(self.obj)) @@ -363,6 +410,11 @@ cdef class MultiButtonEntry(Object): return _object_item_to_python(elm_multibuttonentry_last_item_get(self.obj)) property selected_item: + """The selected item in the multibuttonentry + + :type: :class:`MultiButtonEntryItem` + + """ def __get__(self): return _object_item_to_python(elm_multibuttonentry_selected_item_get(self.obj)) @@ -370,29 +422,66 @@ cdef class MultiButtonEntry(Object): return _object_item_to_python(elm_multibuttonentry_selected_item_get(self.obj)) def clear(self): + """Remove all items in the multibuttonentry""" elm_multibuttonentry_clear(self.obj) def filter_append(self, func, *args, **kwargs): - cbdata = (func, args, kwargs) + """Append an item filter function for text inserted in the Multibuttonentry + + Append the given callback to the list. This function will be called + whenever any text is inserted into the Multibuttonentry, with the text to be inserted + as a parameter. The callback function is free to alter the text in any way + it wants with the modified text passed back as return value. + If the new text is to be left intact, the function can return ``None``. + If the new text is to be discarded, the function must raise + :class:`MultiButtonEntryFilterOut`. + This will also prevent any following filters from being called. - elm_multibuttonentry_item_filter_append(self.obj, - _multibuttonentry_filter_callback, <void *>cbdata) + Callback signature:: - # FIXME: leak here - Py_INCREF(cbdata) + func(obj, text, *args, **kwargs) -> modified text or None + + """ + if not self._item_filters: + elm_multibuttonentry_item_filter_append( + self.obj, + _multibuttonentry_filter_callback, + NULL) + + cbdata = (func, args, kwargs) + self._item_filters.append(cbdata) def filter_prepend(self, func, *args, **kwargs): + """Prepend a filter function for text inserted in the Multibuttonentry + + Prepend the given callback to the list. See :meth:`filter_append` + for more information + + """ + if not self._item_filters: + elm_multibuttonentry_item_filter_append( + self.obj, + _multibuttonentry_filter_callback, + NULL) + cbdata = (func, args, kwargs) + self._item_filters[0] = cbdata - elm_multibuttonentry_item_filter_prepend(self.obj, - _multibuttonentry_filter_callback, <void *>cbdata) + def filter_remove(self, func, *args, **kwargs): + """Remove a filter from the list - # FIXME: leak here - Py_INCREF(cbdata) + Removes the given callback from the filter list. See :meth:`filter_append` + for more information. - #TODO - #def filter_remove(self, func, *args, **kwargs): - #pass + """ + cbdata = (func, args, kwargs) + self._item_filters.remove(cbdata) + + if not self._item_filters: + elm_multibuttonentry_item_filter_remove( + self.obj, + _multibuttonentry_filter_callback, + NULL) property editable: """Whether the multibuttonentry is to be editable or not. @@ -428,15 +517,16 @@ cdef class MultiButtonEntry(Object): """ if func is None: + self.internal_data["multibuttonentry_format_cb"] = None elm_multibuttonentry_format_function_set(self.obj, NULL, NULL) return cbdata = (func, args, kwargs) + self.internal_data["multibuttonentry_format_cb"] = cbdata + elm_multibuttonentry_format_function_set(self.obj, _multibuttonentry_format_cb, - <void *>cbdata) - # FIXME: leak here - Py_INCREF(cbdata) + <void *>self) def callback_item_selected_add(self, func, *args, **kwargs): self._callback_add_full("item,selected", _cb_object_item_conv, func, args, kwargs) diff --git a/efl/elementary/multibuttonentry_cdef.pxi b/efl/elementary/multibuttonentry_cdef.pxi index 1168b48..5b2aa34 100644 --- a/efl/elementary/multibuttonentry_cdef.pxi +++ b/efl/elementary/multibuttonentry_cdef.pxi @@ -1,6 +1,6 @@ cdef extern from "Elementary.h": - ctypedef Eina_Bool (*Elm_Multibuttonentry_Item_Filter_Cb)(Evas_Object *obj, const char *item_label, void *item_data, void *data) + ctypedef Eina_Bool (*Elm_Multibuttonentry_Item_Filter_Cb)(Evas_Object *obj, char *item_label, void *item_data, void *data) ctypedef char * (*Elm_Multibuttonentry_Format_Cb)(int count, void *data) Evas_Object *elm_multibuttonentry_add(Evas_Object *parent) @@ -22,7 +22,7 @@ cdef extern from "Elementary.h": Elm_Object_Item *elm_multibuttonentry_item_next_get(const Elm_Object_Item *it) void elm_multibuttonentry_item_filter_append(Evas_Object *obj, Elm_Multibuttonentry_Item_Filter_Cb func, void *data) void elm_multibuttonentry_item_filter_prepend(Evas_Object *obj, Elm_Multibuttonentry_Item_Filter_Cb func, void *data) - # TODO: void elm_multibuttonentry_item_filter_remove(Evas_Object *obj, Elm_Multibuttonentry_Item_Filter_Cb func, void *data) + void elm_multibuttonentry_item_filter_remove(Evas_Object *obj, Elm_Multibuttonentry_Item_Filter_Cb func, void *data) void elm_multibuttonentry_editable_set(Evas_Object *obj, Eina_Bool editable) Eina_Bool elm_multibuttonentry_editable_get(const Evas_Object *obj) void elm_multibuttonentry_format_function_set(Evas_Object *obj, Elm_Multibuttonentry_Format_Cb f_func, const void *data) diff --git a/examples/elementary/test_multibuttonentry.py b/examples/elementary/test_multibuttonentry.py index b8ad5b6..989598f 100644 --- a/examples/elementary/test_multibuttonentry.py +++ b/examples/elementary/test_multibuttonentry.py @@ -1,16 +1,13 @@ #!/usr/bin/env python # encoding: utf-8 -from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, \ - EXPAND_BOTH, FILL_BOTH, EXPAND_HORIZ, FILL_HORIZ +from efl.evas import EXPAND_BOTH, FILL_BOTH, EXPAND_HORIZ, FILL_HORIZ from efl import elementary -from efl.elementary.window import StandardWindow -from efl.elementary.box import Box -from efl.elementary.button import Button -from efl.elementary.entry import Entry -from efl.elementary.multibuttonentry import MultiButtonEntry -from efl.elementary.scroller import Scroller, ELM_SCROLLER_POLICY_OFF, \ - ELM_SCROLLER_POLICY_AUTO +from efl.elementary import StandardWindow +from efl.elementary import Box +from efl.elementary import Button +from efl.elementary import MultiButtonEntry, MultiButtonEntryFilterOut +from efl.elementary import Scroller, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO SCROLL_POLICY_VERT = ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO @@ -30,27 +27,27 @@ def cb_btn_item_prepend(btn, mbe): global counter counter += 1 - item = mbe.item_prepend("item #%d" % (counter), cb_item_selected) + mbe.item_prepend("item #%d" % (counter), cb_item_selected) def cb_btn_item_append(btn, mbe): global counter counter += 1 - item = mbe.item_append("item #%d" % (counter), cb_item_selected) + mbe.item_append("item #%d" % (counter), cb_item_selected) def cb_btn_item_insert_after(btn, mbe): global counter counter += 1 after = mbe.selected_item - item = mbe.item_insert_after(after, "item #%d" % (counter), cb_item_selected) + mbe.item_insert_after(after, "item #%d" % (counter), cb_item_selected) def cb_btn_item_insert_before(btn, mbe): global counter counter += 1 before = mbe.selected_item - item = mbe.item_insert_before(before, "item #%d" % (counter), cb_item_selected) + mbe.item_insert_before(before, "item #%d" % (counter), cb_item_selected) def cb_btn_clear2(btn, mbe): for item in mbe.items: @@ -58,7 +55,13 @@ def cb_btn_clear2(btn, mbe): def cb_filter1(mbe, text): print(text) - return True + +def cb_filter2(mbe, text): + return text[:-2] + +def cb_filter3(mbe, text): + print(text) + #raise MultiButtonEntryFilterOut def cb_print(btn, mbe): for i in mbe.items: @@ -85,6 +88,8 @@ def multibuttonentry_clicked(obj, item=None): mbe.callback_item_longpressed_add(cb_item_longpressed) mbe.part_text_set("guide", "Tap to add recipient") mbe.filter_append(cb_filter1) + mbe.filter_append(cb_filter2) + mbe.filter_append(cb_filter3) mbe.show() sc = Scroller(win, bounce=(False, True), policy=SCROLL_POLICY_VERT, @@ -181,7 +186,18 @@ def multibuttonentry_clicked(obj, item=None): if __name__ == "__main__": + import logging + elog = logging.getLogger("efl") + elog.setLevel(logging.INFO) + + elog_form = logging.Formatter( + "[%(name)s] %(levelname)s in %(funcName)s:%(lineno)d - %(message)s" + ) + elog_hdlr = logging.StreamHandler() + elog_hdlr.setFormatter(elog_form) + + elog.addHandler(elog_hdlr) + multibuttonentry_clicked(None) elementary.run() - --