]> uap-core.de Git - note.git/commitdiff
implement actions
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 19 Apr 2026 13:33:38 +0000 (15:33 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 19 Apr 2026 13:33:38 +0000 (15:33 +0200)
14 files changed:
application/src/main.rs
ui-rs-derive/Cargo.toml
ui-rs-derive/src/lib.rs
ui-rs/src/ui/event.rs
ui-rs/src/ui/toolkit.rs
ui-rs/src/ui/window.rs
ui/common/context.c
ui/common/document.c
ui/common/toolbar.c
ui/gtk/button.c
ui/gtk/button.h
ui/gtk/headerbar.c
ui/gtk/window.c
ui/ui/toolkit.h

index f845c5004777073da4548dd454f9057e3526a8c3..b68af8b0642869656943708fd0b69c8ed976bc73 100644 (file)
@@ -1,4 +1,4 @@
-use ui_rs::{ui, UiModel};
+use ui_rs::{action, ui, ui_actions, UiModel};
 use ui_rs::ui::*;
 
 fn main() {
@@ -14,9 +14,17 @@ struct App;
 struct TestData {
     i: i32,
     #[bind("list")]
-    list: UiList<i32>
+    list: UiList<usize>
 }
 
+#[ui_actions]
+impl TestData {
+    #[action]
+    pub fn my_action(&mut self, _event: &ActionEvent) {
+        println!("my action {}", self.i);
+        self.i += 1;
+    }
+}
 
 fn create_window() {
     let testdata = TestData::default();
@@ -31,6 +39,7 @@ fn create_window() {
             println!("Button clicked: {}", e.data.i);
             e.data.i += 1;
         }).create();
+        obj.button_builder().label("Action").action("my_action").create();
         let mut model = TableModel::new();
         model.add_column("Name", ColumnType::String, -1);
         obj.tableview_builder::<i32>().fill(true).varname("list").model(&model).getvalue(|elm, _row, _col| {
index 6306e726c693c1eb53903e39f249e6dca8dde791..65ad52292ef3b424cd879e79ca7fefe58c74106d 100644 (file)
@@ -7,6 +7,6 @@ edition = "2021"
 proc-macro = true
 
 [dependencies]
-syn = "2"
+syn = { version = "2", features = ["full"] }
 quote = "1"
 proc-macro2 = "1"
\ No newline at end of file
index 9f427d214dfc56c5af5901d38e3a8fe1038a08f9..51f043c1e69a7d177214fc60f0884b7c32c99f12 100644 (file)
@@ -1,6 +1,6 @@
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Lit, Meta};
+use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, ImplItem, ItemImpl, Lit, Meta};
 
 #[proc_macro_derive(UiModel, attributes(bind))]
 pub fn derive_ui_model(input: TokenStream) -> TokenStream {
@@ -66,4 +66,133 @@ pub fn derive_ui_model(input: TokenStream) -> TokenStream {
     //eprintln!("{}", expanded);
 
     TokenStream::from(expanded)
+}
+
+/// Derives a no-op implementation of [`UiActions`].
+///
+/// This macro generates an empty implementation of the [`UiActions`] trait
+/// for the annotated type. It is intended for use with UI models that do not
+/// define any actions via [`ui_actions`], but still need to satisfy trait
+/// bounds such as `T: UiModel + UiActions`.
+///
+/// If you define actions using the [`ui_actions`] attribute macro, you should
+/// **not** also derive `UiActions`, as both macros generate an implementation
+/// of the same trait and will conflict.
+///
+/// # Generated code
+///
+/// This derive expands to:
+///
+/// ```ignore
+/// impl UiActions for YourType {}
+/// ```
+///
+/// # When to use
+///
+/// Use `#[derive(UiActions)]` when:
+///
+/// - Your type implements [`UiModel`]
+/// - You do **not** define any actions using `#[ui_actions]`
+/// - You still need to satisfy a `UiActions` trait bound
+///
+/// # See also
+///
+/// - [`ui_actions`] — Defines UI actions for a type and generates a non-empty
+///   implementation of [`UiActions`].
+#[proc_macro_derive(UiActions)]
+pub fn derive_ui_actions(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let name = input.ident;
+
+    let expanded = quote! {
+        impl UiActions for #name {
+
+        }
+    };
+
+    TokenStream::from(expanded)
+}
+
+/// Generates an implementation of [`UiActions`] for an `impl` block containing
+/// methods marked with the `#[action]` attribute.
+///
+/// This macro scans the provided `impl` block, collects all methods annotated
+/// with `#[action]`, and generates a corresponding implementation of
+/// [`UiActions`] that registers these methods as UI actions in the provided
+/// [`UiContext`].
+///
+/// The original `impl` block is preserved and emitted unchanged.
+///
+/// # Requirements
+///
+/// - Must be applied to an `impl` block
+/// - Methods intended as actions must be annotated with `#[action]`
+/// - The type must be compatible with [`UiActions`]
+///
+/// # Example
+///
+/// ```
+/// #[ui_actions]
+/// impl TestData {
+///     #[action]
+///     pub fn my_action(&mut self, event: &ActionEvent) {
+///         println!("action triggered");
+///     }
+/// }
+/// ```
+///
+/// # Notes
+///
+/// - Only methods marked with `#[action]` are registered
+/// - Methods without `#[action]` are ignored
+/// - Action names are derived from method names via `stringify!`
+///
+/// # See also
+///
+/// - [`UiActions`]
+/// - [`UiContext`]
+/// - `#[action]` attribute macro
+#[proc_macro_attribute]
+pub fn ui_actions(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let impl_block = parse_macro_input!(item as ItemImpl);
+    let self_ty = &impl_block.self_ty;
+
+    let mut handlers = vec![];
+
+    for item in &impl_block.items {
+        if let ImplItem::Fn(method) = item {
+            if has_handler_attr(&method.attrs) {
+                handlers.push(method.sig.ident.clone());
+            }
+        }
+    }
+
+    let expanded = quote! {
+        #impl_block
+        impl UiActions for #self_ty {
+            fn init_actions(&mut self, ctx: &mut UiContext) {
+                unsafe {
+                    #(
+                        ctx.add_action(
+                            stringify!(#handlers),
+                            |target: &mut Self, event| {
+                                target.#handlers(event);
+                            }
+                        );
+                    )*
+                }
+            }
+        }
+    };
+    //eprintln!("{}", expanded);
+    TokenStream::from(expanded)
+}
+
+fn has_handler_attr(attrs: &[Attribute]) -> bool {
+    attrs.iter().any(|attr| attr.path().is_ident("action"))
+}
+
+#[proc_macro_attribute]
+pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    item
 }
\ No newline at end of file
index bbf822c4645bc020f4894fd89eaef2aab2c52dcf..0ad7c8f0956c45f73bc717965b6dcfe071cd9e93 100644 (file)
@@ -1,7 +1,7 @@
 #![allow(dead_code)]
 
 use std::ffi::c_void;
-use crate::ui::{ffi, UiDouble, UiInteger, UiString, UiText};
+use crate::ui::{event, ffi, UiDouble, UiInteger, UiString, UiText};
 use crate::ui::ffi::{UiEvent, UiRange};
 
 pub struct Event<'a, T> {
@@ -11,6 +11,12 @@ pub struct Event<'a, T> {
     pub set: bool
 }
 
+pub struct ActionEvent<'a> {
+    pub event_type: EventType<'a>,
+    pub intval: i32,
+    pub set: bool
+}
+
 pub enum EventType<'a> {
     Null,
     Pointer,
@@ -31,6 +37,13 @@ pub struct EventWrapper<T> {
     pub callback: Box<dyn FnMut(&mut Event<T>)>
 }
 
+impl<T> EventWrapper<T> {
+    pub fn new<F>(f: F) -> EventWrapper<T>
+    where F: FnMut(&mut event::Event<T>) + 'static {
+        EventWrapper { callback: Box::new(f) }
+    }
+}
+
 pub extern "C" fn event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_void) {
     unsafe {
         let wrapper = &mut *(data as *mut EventWrapper<T>);
@@ -53,6 +66,28 @@ pub extern "C" fn event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_void) {
     }
 }
 
+pub struct ActionEventWrapper<T> {
+    pub callback: Box<dyn FnMut(&mut T, &ActionEvent)>,
+    pub target: *mut T
+}
+
+pub extern "C" fn action_event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_void) {
+    unsafe {
+        let wrapper = &mut *(data as *mut ActionEventWrapper<T>);
+        let target = &mut *(wrapper.target as *mut T);
+
+        let ev_int = ui_event_get_int(e);
+        let ev_set = ui_event_get_set(e) != 0;
+
+        let event = ActionEvent {
+            event_type: EventType::Null,
+            intval: ev_int,
+            set: ev_set
+        };
+
+        (wrapper.callback)(target, &event);
+    }
+}
 
 extern "C" {
     fn ui_event_get_windowdata(event: *const UiEvent) -> *const c_void;
index f9bed168cc2b89d4199cd156390720314233e269..fecf197680c245b5b19449347b88b7907c4b5016 100644 (file)
@@ -1,10 +1,11 @@
 #![allow(dead_code)]
 
 use std::ffi::{c_char, c_int, c_void, CStr, CString};
-use crate::ui::{ffi, ui_object_get_context, ui_reg_destructor};
+use crate::ui::{action_event_wrapper, event, ffi, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ActionEventWrapper, EventWrapper};
 
 use std::marker::PhantomData;
 use std::mem;
+use crate::ui::ffi::UiCallback;
 
 pub struct UiContext {
     pub ptr: *mut ffi::UiContext
@@ -41,13 +42,13 @@ impl UiContext {
         ptr
     }
 
-    pub fn attach(&mut self, doc: &UiDoc) {
+    pub fn attach<T: UiModel + UiActions>(&mut self, doc: &UiDoc<T>) {
         unsafe {
             ui_attach_document(self.ptr, doc.ptr);
         }
     }
 
-    pub fn detach(&mut self, doc: &UiDoc) {
+    pub fn detach<T: UiModel + UiActions>(&mut self, doc: &UiDoc<T>) {
         unsafe {
             ui_attach_document(self.ptr, doc.ptr);
         }
@@ -58,6 +59,26 @@ impl UiContext {
         ls.init(self, None);
         ls
     }
+
+    pub unsafe fn add_action<T, F>(&mut self, name: &str, f: F)
+    where F: FnMut(&mut T, &event::ActionEvent) + 'static {
+        // if this is a document context, use the document pointer as target
+        // otherwise it is a UiObject context, in that case we use the window_data as target
+        let doc_ptr = ui_context_document(self.ptr) as *mut T;
+        let target_ptr = if !doc_ptr.is_null() {
+            doc_ptr
+        } else {
+            let obj_ptr = ui_context_obj(self.ptr);
+            assert!(!obj_ptr.is_null());
+            let obj_wdata_ptr = ui_object_get_windowdata(obj_ptr);
+            assert!(!obj_wdata_ptr.is_null());
+            obj_wdata_ptr as *mut T
+        };
+        let wrapper = Box::new(ActionEventWrapper { callback: Box::new(f), target: target_ptr });
+        let ptr = self.reg_box(wrapper);
+        let cstr = CString::new(name).unwrap();
+        ui_add_action(self.ptr, cstr.as_ptr(), Some(action_event_wrapper::<T>), ptr as *mut c_void);
+    }
 }
 
 extern "C" fn destroy_boxed<T>(data: *mut c_void) {
@@ -69,24 +90,26 @@ extern "C" fn destroy_boxed<T>(data: *mut c_void) {
     }
 }
 
-pub struct UiDoc {
+pub struct UiDoc<T: UiModel + UiActions> {
     pub ctx: UiContext,
-    ptr: *mut c_void
+    ptr: *mut c_void,
+    _marker: PhantomData<T>
 }
 
-impl Clone for UiDoc {
+impl<T: UiModel + UiActions> Clone for UiDoc<T> {
     fn clone(&self) -> Self {
         unsafe {
             ui_document_ref(self.ptr);
         }
-        UiDoc {
+        UiDoc::<T> {
             ctx: self.ctx.clone(),
-            ptr: self.ptr
+            ptr: self.ptr,
+            _marker: PhantomData
         }
     }
 }
 
-impl Drop for UiDoc {
+impl<T: UiModel + UiActions> Drop for UiDoc<T> {
     fn drop(&mut self) {
         unsafe {
             if !self.ptr.is_null() {
@@ -96,8 +119,8 @@ impl Drop for UiDoc {
     }
 }
 
-impl UiDoc {
-    pub fn new<T: UiModel>(mut data: T) -> UiDoc {
+impl<T: UiModel + UiActions> UiDoc<T> {
+    pub fn new(mut data: T) -> UiDoc<T> {
         unsafe {
             let doc = ui_document_new(mem::size_of::<*mut T>());
             let mut ctx = UiContext { ptr: ui_document_context(doc) };
@@ -105,7 +128,22 @@ impl UiDoc {
             let data_ptr = ctx.reg_box(Box::new(data)); // returns *mut T
             let doc_storage: *mut *mut T = doc.cast();
             *doc_storage = data_ptr;
-            UiDoc { ctx: ctx, ptr: doc }
+            UiDoc::<T> { ctx: ctx, ptr: doc, _marker: PhantomData }
+        }
+    }
+
+    pub unsafe fn get_data_ptr(&self) -> *mut T {
+        let ptr: *mut *mut T = self.ptr.cast();
+        *ptr
+    }
+
+    pub fn add_action<F>(&mut self, name: &str, f: F)
+    where F: FnMut(&mut T, &event::ActionEvent) + 'static {
+        unsafe {
+            let wrapper = Box::new(ActionEventWrapper { callback: Box::new(f), target: self.get_data_ptr() });
+            let ptr = self.ctx.reg_box(wrapper);
+            let cstr = CString::new(name).unwrap();
+            ui_add_action(self.ctx.ptr, cstr.as_ptr(), Some(action_event_wrapper::<T>), ptr as *mut c_void);
         }
     }
 }
@@ -114,6 +152,11 @@ pub trait UiModel {
     fn init(&mut self, ctx: &UiContext);
 }
 
+pub trait UiActions {
+    fn init_actions(&mut self, _ctx: &mut UiContext) {}
+}
+
+
 pub struct UiObject<T> {
     pub ptr: *mut ffi::UiObject,
     pub ctx: UiContext,
@@ -409,6 +452,10 @@ extern "C" {
     fn ui_document_context(doc: *mut c_void) -> *mut ffi::UiContext;
     fn ui_attach_document(ctx: *mut ffi::UiContext, doc: *mut c_void);
     fn ui_detach_document(ctx: *mut ffi::UiContext, doc: *mut c_void);
+    fn ui_context_document(ctx: *mut ffi::UiContext) -> *mut c_void;
+    fn ui_context_obj(ctx: *mut ffi::UiContext) -> *mut ffi::UiObject;
+
+    fn ui_add_action(ctx: *mut ffi::UiContext, name: *const c_char, callback: UiCallback, data: *mut c_void);
 
     fn ui_int_new(ctx: *mut ffi::UiContext, name: *const c_char) -> *mut ffi::UiInteger;
     fn ui_double_new(ctx: *mut ffi::UiContext, name: *const c_char) -> *mut ffi::UiDouble;
index 35782134ad6bdfc3fd2425cac3e7e5c1eaa02620..d61df8aeef215659d9eb53ca9837c7428567b2ec 100644 (file)
@@ -4,7 +4,7 @@
 use std::ffi::{c_char, c_int, c_void};
 use std::ffi::CString;
 use std::marker::PhantomData;
-use crate::ui::{toolkit, UiModel};
+use crate::ui::{toolkit, UiActions, UiModel};
 use crate::ui::ffi::{UiContext, UiDestructor, UiObject};
 
 extern "C" {
@@ -39,7 +39,7 @@ enum WindowType {
     Simple
 }
 
-fn window_create<T: UiModel, F>(title: &str, kind: WindowType, mut data: T, create_ui: F) -> toolkit::UiObject<T>
+fn window_create<T: UiModel + UiActions, F>(title: &str, kind: WindowType, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T) {
     // create the window
     let objptr = unsafe {
@@ -63,40 +63,44 @@ where F: FnOnce(&mut toolkit::UiObject<T>, &mut T) {
         _data: PhantomData
     };
 
-    // init view model
-    data.init(&obj.ctx);
-
-    // call ui building closure
-    create_ui(&mut obj, &mut data);
-
     // store windowdata object in the UiObject
     let window_data = Box::new(data);
     let wdata_ptr = obj.ctx.reg_box(window_data);
     unsafe {
         ui_object_set_windowdata(objptr, wdata_ptr as *mut c_void);
     }
+
+    let wdata = unsafe {&mut *(wdata_ptr) };
+
+    // init view model
+    wdata.init(&obj.ctx);
+    wdata.init_actions(&mut obj.ctx);
+
+    // call ui building closure
+    create_ui(&mut obj, wdata);
+
     obj
 }
 
-pub fn window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn window<T: UiModel + UiActions, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Standard, data, create_ui)
 }
 
-pub fn sidebar_window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn sidebar_window<T: UiModel + UiActions, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Sidebar, data, create_ui)
 }
 
-pub fn splitview_window<T: UiModel, F>(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn splitview_window<T: UiModel + UiActions, F>(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::SplitView(sidebar), data, create_ui)
 }
 
-pub fn simple_window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn simple_window<T: UiModel + UiActions, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Simple, data, create_ui)
index f729978e4c29123e6edc9b6afeea33e50284cf63..581469e60f90bcd2ae6437e8b737a55419c2f84e 100644 (file)
@@ -133,6 +133,7 @@ void uic_context_attach_document(UiContext *ctx, void *document) {
     
     UiContext *doc_ctx = ui_document_context(document);
     doc_ctx->parent = ctx;
+    doc_ctx->ref++;
     
     // if a document variable has the same name as a parent variable,
     // move the bindings to the document
@@ -152,7 +153,7 @@ void uic_context_attach_document(UiContext *ctx, void *document) {
         
         var_ctx = var_ctx->parent;
     }
-    
+      
     ui_update_action_bindings(ctx);
 }
 
@@ -196,6 +197,7 @@ void uic_context_detach_document(UiContext *ctx, void *document) {
     UiContext *docctx = ui_document_context(document);
     uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
     docctx->parent = NULL;
+    ui_document_unref(document);
     
     ui_update_action_bindings(ctx);
 }
index b05d4845a443bb783efec436bc0a942fe3dcd676..b4c9402fab01685bc6773e51d21ac5a59ea13c29 100644 (file)
@@ -78,3 +78,11 @@ UiContext* ui_document_context(void *doc) {
         return NULL;
     }
 }
+
+void* ui_context_document(UiContext *ctx) {
+    return ctx->self_doc;
+}
+
+UiObject* ui_context_obj(UiContext *ctx) {
+    return ctx->obj;
+}
index f87e02a373a8fd97ca16e63ef1b9b3ddf98a47ee..9a389d0f18d6d6c24d21f806619c63668092e2d0 100644 (file)
@@ -57,6 +57,7 @@ static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups,
     newargs.tooltip = nl_strdup(args->tooltip);\r
     newargs.onclick = args->onclick;\r
     newargs.onclickdata = args->onclickdata;\r
+    newargs.action = nl_strdup(args->action);\r
     newargs.states = uic_copy_states(args->states, ngroups);\r
     newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates);\r
     return newargs;\r
@@ -78,6 +79,7 @@ static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args
     newargs.varname = nl_strdup(args->varname);\r
     newargs.onchange = args->onchange;\r
     newargs.onchangedata = args->onchangedata;\r
+    newargs.action = nl_strdup(args->action);\r
     newargs.states = uic_copy_states(args->states, ngroups);\r
     newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates);\r
     return newargs;\r
index 9ce23bf7bf3e2faf5e07f437fe7b60f3ff1cb9fe..00ad370fb5810b6671f0929ea1fa40d66b99f23f 100644 (file)
 
 #include "button.h"
 #include "container.h"
+#include "widget.h"
 #include <cx/allocator.h>
 #include <cx/buffer.h>
 #include <cx/json.h>
 #include "../common/context.h"
 #include "../common/object.h"
+#include "../common/action.h"
 
 void ui_button_set_icon_name(GtkWidget *button, const char *icon) {
     if(!icon) {
@@ -63,6 +65,7 @@ GtkWidget* ui_create_button(
         const char *tooltip,
         ui_callback onclick,
         void *userdata,
+        const char *action,
         int event_value,
         bool activate_event)
 {
@@ -72,12 +75,13 @@ GtkWidget* ui_create_button(
         gtk_widget_set_tooltip_text(button, tooltip);
     }
     
-    if(onclick) {
+    if(onclick || action) {
         UiEventData *event = malloc(sizeof(UiEventData));
         event->obj = obj;
         event->userdata = userdata;
         event->callback = onclick;
         event->value = event_value;
+        event->action = action ? strdup(action) : NULL;
         event->customdata = NULL;
         event->customint = 0;
 
@@ -89,7 +93,7 @@ GtkWidget* ui_create_button(
         g_signal_connect(
                 button,
                 "destroy",
-                G_CALLBACK(ui_destroy_userdata),
+                G_CALLBACK(ui_destroy_eventdata),
                 event);
         if(activate_event) {
             g_signal_connect(
@@ -98,13 +102,21 @@ GtkWidget* ui_create_button(
                 G_CALLBACK(ui_button_clicked),
                 event);
         }
+        
+        if(action) {
+            uic_bind_action(obj->ctx, action, button, (ui_enablefunc)ui_set_enabled);
+            UiAction *ui_action = uic_resolve_action(obj->ctx, action);
+            if(!ui_action) {
+                ui_set_enabled(button, FALSE);
+            }
+        }
     }
     
     return button;
 }
 
 UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args) {
-    GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, args->onclick, args->onclickdata, 0, FALSE);
+    GtkWidget *button = ui_create_button(obj, args->label, args->icon, args->tooltip, args->onclick, args->onclickdata, args->action, 0, FALSE);
     ui_set_name_and_style(button, args->name, args->style_class);
     ui_set_widget_states(obj->ctx, button, args->states);
     UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end;
@@ -124,7 +136,13 @@ void ui_button_clicked(GtkWidget *widget, UiEventData *event) {
     e.eventdatatype = 0;
     e.intval = event->value;
     e.set = ui_get_setop();
-    event->callback(&e, event->userdata);
+    if(event->callback) {
+        event->callback(&e, event->userdata);
+    }
+    
+    if(event->action) {
+        uic_action_callback(&e, event->action);
+    }
 }
 
 void ui_button_set_label(UIWIDGET button, const char *label) {
index ce94b90c7dc04259e087553a2a195d35a949f1b5..9197b8f54cf7a055da4e06a1bb10d369edf4e34c 100644 (file)
@@ -58,6 +58,7 @@ GtkWidget* ui_create_button(
         const char *tooltip,
         ui_callback onclick,
         void *userdata,
+        const char *action,
         int event_value,
         bool activate_event);
 
index ef1d9c7e151fa79cf0203d2906eb32dab19031b0..97e57e6192d6b145b3698706186d3adf279f20e2 100644 (file)
@@ -163,7 +163,7 @@ void ui_add_headerbar_item(
         UiObject *obj,
         enum UiToolbarPos pos)
 {
-    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, 0, FALSE);
+    GtkWidget *button = ui_create_button(obj, item->args.label, item->args.icon, item->args.tooltip, item->args.onclick, item->args.onclickdata, item->args.action, 0, FALSE);
     ui_set_widget_states(obj->ctx, button, item->args.states);
     ui_set_widget_visibility_states(obj->ctx, button, item->args.visibility_states);
     WIDGET_ADD_CSS_CLASS(button, "flat");
index 58af741ff25051a9a9ae334b9b42b2a7f1ff0a6a..0ef809357db028e13a38df748ae524d46920360a 100644 (file)
@@ -987,7 +987,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
             }
             
             if(args->lbutton1) {
-                GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1);
+                GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 1, args->default_button == 1);
                 gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 1) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -995,7 +995,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
                 }
             }
             if(args->lbutton2) {
-                GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2);
+                GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 2, args->default_button == 2);
                 gtk_header_bar_pack_start(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 2) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1004,7 +1004,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
             }
             
             if(args->rbutton4) {
-                GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4);
+                GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 4, args->default_button == 4);
                 gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 4) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1012,7 +1012,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
                 }
             }
             if(args->rbutton3) {
-                GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3);
+                GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 3, args->default_button == 3);
                 gtk_header_bar_pack_end(GTK_HEADER_BAR(headerbar), button);
                 if(args->default_button == 3) {
                     WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1033,7 +1033,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
         gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE); 
         
         if(args->lbutton1) {
-            GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 1, args->default_button == 1);
+            GtkWidget *button = ui_create_button(obj, args->lbutton1, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 1, args->default_button == 1);
             gtk_grid_attach(GTK_GRID(grid), button, 0, 0, 1, 1);
             if(args->default_button == 1) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1041,7 +1041,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
             }
         }
         if(args->lbutton2) {
-            GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 2, args->default_button == 2);
+            GtkWidget *button = ui_create_button(obj, args->lbutton2, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 2, args->default_button == 2);
             gtk_grid_attach(GTK_GRID(grid), button, 1, 0, 1, 1);
             if(args->default_button == 2) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1052,7 +1052,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
         gtk_widget_set_hexpand(space, TRUE);
         gtk_grid_attach(GTK_GRID(grid), space, 2, 0, 1, 1);
         if(args->rbutton3) {
-            GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 3, args->default_button == 3);
+            GtkWidget *button = ui_create_button(obj, args->rbutton3, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 3, args->default_button == 3);
             gtk_grid_attach(GTK_GRID(grid), button, 3, 0, 1, 1);
             if(args->default_button == 3) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
@@ -1060,7 +1060,7 @@ UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) {
             }
         }
         if(args->rbutton4) {
-            GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, 4, args->default_button == 4);
+            GtkWidget *button = ui_create_button(obj, args->rbutton4, NULL, NULL/*tooltip*/, args->onclick, args->onclickdata, NULL, 4, args->default_button == 4);
             gtk_grid_attach(GTK_GRID(grid), button, 4, 0, 1, 1);
             if(args->default_button == 4) {
                 WIDGET_ADD_CSS_CLASS(button, "suggested-action");
index cdb75b73646594fb6a8ebcb95663981289e5b1bb..af1036ef2590ad0e16cb5eb741a5601d14d005db 100644 (file)
@@ -579,6 +579,8 @@ UIEXPORT void  ui_document_destroy(void *doc);
 UIEXPORT void* ui_get_subdocument(void *document);               // deprecated
 
 UIEXPORT UiContext* ui_document_context(void *doc);
+UIEXPORT void* ui_context_document(UiContext *ctx);
+UIEXPORT UiObject* ui_context_obj(UiContext *ctx);
 
 UIEXPORT void* ui_context_get_document(UiContext *ctx);
 UIEXPORT void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable);