ADDED python/cffcall.h Index: python/cffcall.h ================================================================== --- /dev/null +++ python/cffcall.h @@ -0,0 +1,10 @@ +#ifndef _cffcall_h_ +#define _cffcall_h_ + +#include + +#define va_start_voidp(frame) va_start_ptr((frame), void *) +#define va_return_voidp(frame, val) va_return_ptr((frame), void *, (val)) +#define va_arg_voidp(frame) va_arg_ptr((frame), void *) + +#endif /* _cffcall_h_ */ ADDED python/cffcall.pxd Index: python/cffcall.pxd ================================================================== --- /dev/null +++ python/cffcall.pxd @@ -0,0 +1,30 @@ +cdef extern from "cffcall.h": + ctypedef struct va_alist: + pass + + void va_start_uchar(va_alist frame) + void va_return_uchar(va_alist frame, unsigned char val) + unsigned char va_arg_uchar(va_alist frame) + + void va_start_int(va_alist frame) + void va_return_int(va_alist frame, int val) + int va_arg_int(va_alist frame) + + void va_start_float(va_alist frame) + void va_return_float(va_alist frame, float val) + float va_arg_float(va_alist frame) + + void va_start_double(va_alist frame) + void va_return_double(va_alist frame, double val) + double va_arg_double(va_alist frame) + + void va_start_voidp(va_alist frame) + void va_return_voidp(va_alist frame, void *val) + void *va_arg_voidp(va_alist frame) + + ctypedef void (* __VA_function)(void *data, va_alist frame) + + void *alloc_callback(__VA_function fun, void *data) + void free_callback(void *callback) + __VA_function callback_address(void *callback) + void *callback_data(void *callback) ADDED python/ciup.h Index: python/ciup.h ================================================================== --- /dev/null +++ python/ciup.h @@ -0,0 +1,72 @@ +#ifndef _ciup_h_ +#define _ciup_h_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int InativeType; +typedef struct InativeHandle_ InativeHandle; +typedef struct IcontrolData_ IcontrolData; + +typedef struct Itable_ Itable; + +typedef struct Iclass_ Iclass; + +struct Iclass_ { + char *name; + char *format; + InativeType nativetype; + int childtype; + int is_interactive; + int has_attrib_id; + Iclass *parent; + Itable *attrib_func; + Iclass *(* New)(void); + void (* Release)(Iclass *ic); + int (* Create)(Ihandle *ih, void **params); + int (* Map)(Ihandle *ih); + void (* UnMap)(Ihandle *ih); + void (* Destroy)(Ihandle *ih); + Ihandle *(* GetInnerContainer)(Ihandle *ih); + void *(* GetInnerNativeContainerHandle)(Ihandle *ih, Ihandle *child); + void (* ChildAdded)(Ihandle *ih, Ihandle *child); + void (* ChildRemoved)(Ihandle *ih, Ihandle *child); + void (* LayoutUpdate)(Ihandle *ih); + void (* ComputeNaturalSize)(Ihandle *ih, int *w, int *h, int *children_expand); + void (* SetChildrenCurrentSize)(Ihandle *ih, int shrink); + void (* SetChildrenPosition)(Ihandle *ih, int x, int y); + int (* DlgPopup)(Ihandle *ih, int x, int y); +} ; + +struct Ihandle_ { + char sig [4]; + Iclass *iclass; + Itable *attrib; + int serial; + InativeHandle *handle; + int expand; + int flags; + int x; + int y; + int userwidth; + int userheight; + int naturalwidth; + int naturalheight; + int currentwidth; + int currentheight; + Ihandle *parent; + Ihandle *firstchild; + Ihandle *brother; + IcontrolData *data; +} ; + +char *iupClassCallbackGetFormat(Iclass *ic, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* _ciup_h_ */ ADDED python/ciup.pxd Index: python/ciup.pxd ================================================================== --- /dev/null +++ python/ciup.pxd @@ -0,0 +1,57 @@ +cdef extern from "ciup.h": + ctypedef struct Iclass: + pass + ctypedef struct Ihandle: + Iclass *iclass + + int IupOpen(int *nargs, char ***args) + void IupClose() + void IupImageLibOpen() + + char *IupVersion() + char *IupVersionDate() + int IupVersionNumber() + + int IupMainLoop() nogil + int IupLoopStep() nogil + int IupLoopStepWait() nogil + int IupMainLoopLevel() + void IupFlush() + void IupExitLoop() + + int IupHelp(char *url) + char *IupLoad(char *filename) + char *IupLoadBuffer(char *buffer) + + Ihandle *IupCreate(char *classname) + void IupDestroy(Ihandle *ih) + + Ihandle *IupGetHandle(char *name) + Ihandle *IupSetHandle(char *name, Ihandle *ih) + char *IupGetName(Ihandle *ih) + + char *IupGetClassName(Ihandle *ih) + char *IupGetClassType(Ihandle *ih) + + void *IupGetAttribute(Ihandle *ih, char *name) + void IupSetAttribute(Ihandle *ih, char *name, void *value) + void IupStoreAttribute(Ihandle *ih, char *name, char *value) + void IupSetAttributeHandle(Ihandle *ih, char *name, Ihandle *value) + + char *iupClassCallbackGetFormat(Iclass *ic, char *name) + void *IupGetCallback(Ihandle *ih, char *name) + void IupSetCallback(Ihandle *ih, char *name, void *value) + + char *IupSaveClassAttributes(Ihandle *ih) + + void IupUpdate(Ihandle *ih) + void IupUpdateChildren(Ihandle *ih) + void IupRedraw(Ihandle *ih, int children) + void IupRefresh(Ihandle *ih) + void IupRefreshChildren(Ihandle *ih) + + int IupPopup(Ihandle* ih, int x, int y) nogil + int IupShowXY(Ihandle* ih, int x, int y) + int IupHide(Ihandle* ih) + int IupMap(Ihandle *ih) + void IupUnmap(Ihandle *ih) ADDED python/iup.pyx Index: python/iup.pyx ================================================================== --- /dev/null +++ python/iup.pyx @@ -0,0 +1,443 @@ +'''Bindings to the IUP graphical user interface library.''' + +cimport cython +from ciup cimport * +from cffcall cimport * +from cpython.ref cimport * +from warnings import warn + + +IupOpen(NULL, NULL) +IupImageLibOpen() + +IUP_VERSION = IupVersion().decode() +IUP_VERSION_DATE = IupVersionDate().decode() +IUP_VERSION_NUMBER = IupVersionNumber() + + +ERROR = +1 +NOERROR = 0 +OPENED = -1 +INVALID = -1 +IGNORE = -1 +DEFAULT = -2 +CLOSE = -3 +CONTINUE = -4 + +class IupError(Exception): + def __init__(self, status, message = 'Operation failed'): + Exception.__init__(self, status, message) + self.status = status + self.message = message + + def __str__(self): + return '[Status {}] {}'.format(self.status, self.message) + +cdef object __check_status(int status): + if status == NOERROR: + return None + elif status == ERROR: + raise IupError(status) + else: + return status + +cdef int __make_status(object status): + if status is None: + return NOERROR + elif isinstance(status, IupError): + return __make_status(status.status) + elif isinstance(status, Exception): + return ERROR + elif isinstance(status, int): + return status + else: + return INVALID + + +def main_loop(): + '''Runs an IUP event loop.''' + cdef int status + with nogil: + status = IupMainLoop() + return __check_status(status) + +def main_loop_step(int wait = False): + '''Runs one step of the IUP event loop, blocking for events if desired.''' + cdef int status + with nogil: + if wait: + status = IupLoopStepWait() + else: + status = IupLoopStep() + return __check_status(status) + +def main_loop_level(): + '''Returns the nesting level of running IUP event loops.''' + return IupMainLoopLevel() + +def main_loop_exit(): + '''Exits the innermost IUP event loop.''' + IupExitLoop() + +def main_loop_flush(): + '''Flushes events for processing in the IUP event loop.''' + IupFlush() + + +def send_url(url): + '''Opens a URL with some suitable external program.''' + cdef int status + if isinstance(url, str): url = url.encode() + status = IupHelp(url) + if status == -1: + raise IupError(status) + elif status == -2: + raise IupError(status, 'No such file or directory') + +def load_led(filename): + '''Loads a LED IUP GUI description file.''' + cdef char *status + if isinstance(filename, str): filename = filename.encode() + status = IupLoad(filename) + if status: raise IupError(ERROR, status.strip().decode()) + +def load_led_str(data): + '''Loads a LED IUP GUI description from a string.''' + cdef char *status + if isinstance(data, str): data = data.encode() + status = IupLoadBuffer(data) + if status: raise IupError(ERROR, status.strip().decode()) + + +cdef void __frame_callback(void *data, va_alist frame) with gil: + (<_Callback>data).__call_with_frame__(frame) + +cdef class _Callback: + '''Callback invocation wrapper.''' + + cdef void *_cb + cdef readonly object format + cdef public object target + + def __cinit__(self): + self._cb = alloc_callback(__frame_callback, self) + self.format = b'h=i' + self.target = lambda: DEFAULT + + def __destroy__(self): + free_callback(self._cb) + + def __init__(self, format): + if not format: + format = b'h=i' + elif len(format) < 2 or format[-2] != ord(b'='): + format += b'=i' + self.format = format + + @property + def __address__(self): + '''The memory address of the C callback entry point.''' + return self._cb + + def __call__(self, *args): + '''Performs a Python call to the callback.''' + return self.target(*args) + + cdef __call_with_frame__(self, va_alist frame): + '''Performs a C call to the callback.''' + cdef char ret + cdef char arg + cdef object args = [] + cdef object result + cdef Ihandle *ih + + try: + ret = self.format[-1] + if ret == ord(b'b'): + va_start_uchar(frame) + elif ret == ord(b'i'): + va_start_int(frame) + elif ret == ord(b'f'): + va_start_float(frame) + elif ret == ord(b'd'): + va_start_double(frame) + elif ret in b'vh': + va_start_voidp(frame) + else: + raise ValueError('Bad result format {!r}'.format(chr(ret))) + + for arg in self.format[:-2]: + if arg == ord(b'b'): + args.append(va_arg_uchar(frame)) + elif arg == ord(b'i'): + args.append(va_arg_int(frame)) + elif arg == ord(b'f'): + args.append(va_arg_float(frame)) + elif arg == ord(b'd'): + args.append(va_arg_double(frame)) + elif arg == ord(b's'): + args.append(va_arg_voidp(frame)) + elif arg == ord(b'v'): + args.append(va_arg_voidp(frame)) + elif arg == ord(b'h'): + args.append(Handle.wrap(va_arg_voidp(frame))) + else: + raise ValueError('Bad argument format {!r}'.format(chr(arg))) + + result = self.target(*args) + except KeyboardInterrupt: + result = CLOSE + except Exception as error: + warn('Callback failed: {}'.format(error)) + result = error + + if ret == ord(b'b'): + va_return_uchar(frame, result) + elif ret == ord(b'i'): + va_return_int(frame, __make_status(result)) + elif ret == ord(b'f'): + va_return_float(frame, result) + elif ret == ord(b'd'): + va_return_double(frame, result) + elif ret == ord(b'v'): + va_return_voidp(frame, result) + elif ret == ord(b'h'): + va_return_voidp(frame, result.__address__) + + +cdef class _BaseHandle: + '''Base class of IUP attribute containers.''' + + cdef Ihandle *_ih + cdef object _registry + + def __cinit__(self): + self._ih = NULL + self._registry = {} + + cdef object _get_attribute(self, char *name): + cdef char *ptr = IupGetAttribute(self._ih, name) + if ptr: + return ptr.decode() + else: + return None + + cdef void _set_attribute(self, char *name, object value): + cdef unsigned long address + if isinstance(value, str): + value = value.encode() + IupStoreAttribute(self._ih, name, value) + elif isinstance(value, bytes): + IupStoreAttribute(self._ih, name, value) + elif isinstance(value, Handle): + address = value.__address__ + IupSetAttributeHandle(self._ih, name, address) + elif isinstance(value, bool) or value is None: + if value: + value = b'YES' + else: + value = b'NO' + IupStoreAttribute(self._ih, name, value) + else: + value = str(value).encode() + IupStoreAttribute(self._ih, name, value) + + cdef object _get_callback(self, char *name, object table): + cdef object target = None + cdef unsigned long address + if table: + address = IupGetCallback(self._ih, name) + if address in table: target = table[address].target + return target + + cdef void _set_callback(self, char *name, object table, object target): + cdef _Callback callback + cdef unsigned long address = IupGetCallback(self._ih, name) + if address in table: + callback = table[address] + else: + callback = _Callback(iupClassCallbackGetFormat(self._ih.iclass, name)) + address = callback.__address__ + table[address] = callback + IupSetCallback(self._ih, name, address) + callback.target = target + + +@cython.final +cdef class _GlobalHandle(_BaseHandle): + '''Class of the global attribute container.''' + + def __getattr__(self, name): + '''Retrieves a callback or attribute.''' + if isinstance(name, str): name = name.encode() + return \ + self._get_callback(name, self._registry) or \ + self._get_attribute(name) + + def __setattr__(self, name, value): + '''Sets a callback or attribute.''' + if isinstance(name, str): name = name.encode() + if callable(value): + self._set_callback(name, self._registry, value) + else: + self._set_attribute(name, value) + +IUP_GLOBAL = _GlobalHandle() + + +CENTER = 0xFFFF +LEFT = 0xFFFE +RIGHT = 0xFFFD +MOUSEPOS = 0xFFFC +CURRENT = 0xFFFB +CENTERPARENT = 0xFFFA +TOP = LEFT +BOTTOM = RIGHT + +cdef class Handle(_BaseHandle): + '''Class of IUP handles.''' + + def __init__(self, unsigned long address): + '''Wraps a memory address into a new handle.''' + if not address: raise ValueError('Handle may not be NULL') + self._ih = address + Py_INCREF(self) + IupSetAttribute(self._ih, b'PYTHON_COMPANION', self) + + def destroy(self): + '''Destroys the underlying object of this handle.''' + cdef void *ptr + cdef object companion + if self._ih: + ptr = IupGetAttribute(self._ih, b'PYTHON_COMPANION') + if ptr: + companion = ptr + if companion is self: Py_DECREF(companion) + IupDestroy(self._ih) + self._ih = NULL + + @classmethod + def wrap(Self, unsigned long address): + '''Wraps a memory address into a handle, reusing existing ones.''' + cdef void *ptr + cdef object self = None + if address: + ptr = IupGetAttribute(address, b'PYTHON_COMPANION') + if ptr: + self = ptr + else: + self = Self(address) + return self + + @classmethod + def create(Self, name, **attrs): + '''Wraps a new instance of an IUP object into a handle.''' + cdef unsigned long address + cdef Handle self + if isinstance(name, str): name = name.encode() + + address = IupCreate(name) + if address: + self = Self(address) + else: + raise IupError(ERROR, 'Failed to create {}'.format(name)) + + try: + for name, value in attrs.items(): self.__setattr__(name, value) + except: + self.destroy() + raise + + return self + + @classmethod + def by_name(Self, name): + '''Retrieves an IUP object instance by name.''' + if isinstance(name, str): name = name.encode() + return Self.wrap(IupGetHandle(name)) + + @property + def __address__(self): + '''The memory address of the underlying object.''' + return self._ih + + def _check_destroyed(self): + '''Ensures that the underlying object is still alive.''' + if not self._ih: raise ReferenceError('Handle is destroyed') + + @property + def name(self): + '''Global name for the handle.''' + self._check_destroyed() + cdef char *name = IupGetName(self._ih) + if name: + return name.decode() + else: + return None + + cdef void _set_name(self, object value): + cdef char *name = IupGetName(self._ih) + if isinstance(value, str): value = value.encode() + if name: IupSetHandle(name, NULL) + IupSetHandle(value, self._ih) + + @property + def class_name(self): + '''Name of the handle's IUP object class.''' + self._check_destroyed() + return IupGetClassName(self._ih).decode() + + @property + def class_type(self): + '''Name of the handle's IUP native resource type.''' + self._check_destroyed() + return IupGetClassType(self._ih).decode() + + def __getattr__(self, name): + '''Retrieves a callback or attribute.''' + self._check_destroyed() + if isinstance(name, str): name = name.encode() + return \ + self._get_callback(name, self._registry) or \ + self._get_attribute(name) + + def __setattr__(self, name, value): + '''Sets a name, callback or attribute.''' + self._check_destroyed() + if isinstance(name, str): name = name.encode() + if name == b'name': + self._set_name(value) + elif callable(value): + self._set_callback(name, self._registry, value) + else: + self._set_attribute(name, value) + + def save_attributes(self): + '''Ensures that attributes stored in native resources are mirrorred.''' + self._check_destroyed() + IupSaveClassAttributes(self._ih) + + def show(self, modal = False, x = CURRENT, y = CURRENT): + '''Shows a dialog.''' + cdef int status + self._check_destroyed() + if modal: + status = IupPopup(self._ih, x, y) + else: + status = IupShowXY(self._ih, x, y) + return __check_status(status) + + def hide(self): + '''Hides a dialog.''' + self._check_destroyed() + __check_status(IupHide(self._ih)) + + def map_peer(self): + '''Ensures that native resources for the object are allocated.''' + self._check_destroyed() + __check_status(IupMap(self._ih)) + + def unmap_peer(self): + '''Releases native resources associated with the object.''' + self._check_destroyed() + IupUnmap(self._ih) ADDED python/setup.py Index: python/setup.py ================================================================== --- /dev/null +++ python/setup.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext + +setup( + cmdclass = {'build_ext': build_ext}, + ext_modules = [Extension('iup', ['iup.pyx'], + libraries = ['callback', 'iup', 'iupimglib'])])