+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2026 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
use std::future::Future;
use std::pin::Pin;
use sea_orm::{ActiveModelTrait, Database, DatabaseConnection, DbErr, EntityTrait, QueryFilter, ColumnTrait, Set, QueryOrder};
use entity::collection::{create_notebook_hierarchy, CollectionType, Entity as Collection, Node};
use entity::note::{Entity as Note};
use entity::notecontent::{Entity as NoteContent};
+use crate::lockmanager::LockManager;
pub struct Backend {
rt: Arc<Runtime>,
db: DatabaseConnection,
+ pub lockmgr: Arc<LockManager>,
+
pub current_profile: profile::Model,
pub broadcast: tokio::sync::broadcast::Sender<BroadcastMessage>,
}
}
+impl NoteId {
+ pub fn is_new(&self) -> bool {
+ matches!(self, NoteId::TmpId(_))
+ }
+}
+
#[derive(Clone, Default)]
pub struct NoteTitleUpdate {
pub collection_id: i32,
Ok(Self {
rt,
db,
+ lockmgr: Arc::new(LockManager::new()),
current_profile: profile,
broadcast: tx,
broadcast_notify: None
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2026 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+use std::collections::HashMap;
+use std::sync::Arc;
+
+pub struct LockManager {
+ locks: Arc<std::sync::Mutex<HashMap<i32, bool>>>,
+}
+
+impl LockManager {
+ pub fn new() -> Self {
+ LockManager {
+ locks: Arc::new(std::sync::Mutex::new(HashMap::new())),
+ }
+ }
+
+ fn locks(&self) -> std::sync::MutexGuard<'_, HashMap<i32, bool>> {
+ self.locks.lock().unwrap_or_else(|e| {
+ // Very unlikely that the mutex gets poisoned (probably only by OOM), and if it does, who cares
+ println!("Error: LockManager poisoned");
+ self.locks.clear_poison();
+ e.into_inner()
+ })
+ }
+
+ pub fn lock_note(&self, note_id: i32) -> Option<NoteLock> {
+ let mut locks = self.locks();
+ if locks.contains_key(¬e_id) {
+ println!("note {} already locked", note_id);
+ return None
+ }
+ println!("lock note {}", note_id);
+ locks.insert(note_id, true);
+
+ let note_lock = NoteLock {
+ locks: self.locks.clone(),
+ note_id: note_id
+ };
+ Some(note_lock)
+ }
+}
+
+pub struct NoteLock {
+ locks: Arc<std::sync::Mutex<HashMap<i32, bool>>>,
+ note_id: i32
+}
+
+impl Drop for NoteLock {
+ fn drop(&mut self) {
+ println!("note {} unlocked", self.note_id);
+ let mut locks = self.locks.lock().unwrap_or_else(|e|{ e.into_inner()});
+ locks.remove(&self.note_id);
+ }
+}
\ No newline at end of file
mod newnotebook;
mod notebook;
mod note;
+mod lockmanager;
use std::env;
use entity::collection::{create_notebook_hierarchy, Node};
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2026 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use sea_orm::sea_query::prelude::Utc;
use ui_rs::{action, ui_actions, UiModel};
use ui_rs::ui::*;
use crate::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate};
+use crate::lockmanager::NoteLock;
use crate::window::NoteTypeTabView;
static TMP_ID: AtomicU64 = AtomicU64::new(1);
pub backend: Rc<BackendHandle>,
pub id: NoteId,
+ pub lock: Option<NoteLock>,
pub collection_id: i32,
pub content_id: i32,
collection_id: collection_id,
id: id,
+ lock: None,
content_id: 0,
kind: NoteType::PlainTextNote,
extract_title: false,
}
}
+ pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<Note> {
+ UiDoc::new2(self, |n,d| {
+ n.doc = d.doc_ref();
+ if n.id.is_new() {
+ n.init_new();
+ } else {
+ n.init_from_model(model);
+ n.load_content(d);
+ }
+
+ d.ctx.onattach_action("attached");
+ d.ctx.ondetach_action("detached");
+ })
+ }
+
+ #[action]
+ pub fn attached(&mut self, _event: &ActionEvent) {
+ if let NoteId::Id(note_id) = self.id {
+ self.lock = self.backend.backend.lockmgr.lock_note(note_id);
+ if self.lock.is_none() {
+ self.text.set_readonly(true);
+ }
+ }
+ }
+
+ #[action]
+ pub fn detached(&mut self, _event: &ActionEvent) {
+ self.lock = None;
+ self.text.set_readonly(false);
+ }
+
pub fn init_from_model(&mut self, model: &entity::note::Model) {
self.id = NoteId::Id(model.note_id);
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2026 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
use std::any::Any;
use std::rc::Rc;
use ui_rs::{action, ui_actions, UiModel};
} else {
// Create the new note
let note_data = crate::note::Note::new(note.id.clone(), self.collection_id, self.backend.clone());
- // Create the note document object
- UiDoc::new2(note_data, |n,d| {
- n.doc = d.doc_ref();
- if note.is_new() {
- n.init_new();
- } else {
- n.init_from_model(¬e.data);
- n.load_content(d);
- }
- })
+ note_data.into_doc(¬e.data)
};
note.model = Some(doc.clone());
}
pub fn is_new(&self) -> bool {
- return matches!(self.id, NoteId::TmpId(_))
+ self.id.is_new()
}
}
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
+
use std::any::Any;
use ui_rs::{action, ui_actions, UiModel};
use ui_rs::ui::*;
let notebook_doc = nb.get_doc(&e.data.backend);
e.obj.ctx.attach(¬ebook_doc);
e.data.selected_notebook = Some(notebook_doc);
+ e.obj.splitview_set_visible(0, !nav.note_maximized);
e.data.navigation.push(nav);
}
pub fn onattach_action(&self, action: &str) {
let cstr = CString::new(action).unwrap();
unsafe {
- ui_onattach_action(self.ptr, cstr.as_ptr());
+ ui_context_onattach_action(self.ptr, cstr.as_ptr());
}
}
pub fn ondetach_action(&self, action: &str) {
let cstr = CString::new(action).unwrap();
unsafe {
- ui_ondetach_action(self.ptr, cstr.as_ptr());
+ ui_context_ondetach_action(self.ptr, cstr.as_ptr());
}
}
ui_text_remove(self.ptr, begin as c_int, end as c_int);
}
}
+
+ pub fn set_readonly(&mut self, readonly: bool) {
+ unsafe {
+ ui_text_setreadonly(self.ptr, readonly as c_int);
+ }
+ }
}
+
impl UiString {
pub fn init(&mut self, ctx: &UiContext, name: Option<&str>) {
let c_string = name.map(|n| CString::new(n).unwrap());
fn ui_detach_document(ctx: *mut ffi::UiContext, doc: *mut c_void);
fn ui_onattach(ctx: *mut ffi::UiContext, callback: UiCallback, data: *mut c_void);
fn ui_ondetach(ctx: *mut ffi::UiContext, callback: UiCallback, data: *mut c_void);
- fn ui_onattach_action(ctx: *mut ffi::UiContext, action: *const c_char);
- fn ui_ondetach_action(ctx: *mut ffi::UiContext, action: *const c_char);
+ fn ui_context_onattach_action(ctx: *mut ffi::UiContext, action: *const c_char);
+ fn ui_context_ondetach_action(ctx: *mut ffi::UiContext, action: *const c_char);
fn ui_context_document(ctx: *mut ffi::UiContext) -> *mut c_void;
fn ui_context_obj(ctx: *mut ffi::UiContext) -> *mut ffi::UiObject;
fn ui_text_selection(text: *const ffi::UiText, begin: *mut c_int, end: *mut c_int);
fn ui_text_length(text: *const ffi::UiText) -> c_int;
fn ui_text_remove(text: *mut ffi::UiText, begin: c_int, end: c_int);
+ fn ui_text_setreadonly(text: *mut ffi::UiText, readonly: c_int);
fn ui_call_action(ctx: *mut ffi::UiContext, action: *const c_char) -> c_int;
fn ui_call_action3(ctx: *mut ffi::UiContext, action: *const c_char, data: *mut c_void, typeid: u64) -> c_int;
broadcast_action(action_name, eventdata, UI_EVENT_DATA_POINTER, intval);
}
+void ui_call_action_on(UiContext *ctx, const char *action) {
+ UiEvent event;
+ memset(&event, 0, sizeof(UiEvent));
+ event.obj = ctx->obj;
+ event.window = event.obj ? event.obj->window : NULL;
+ event.document = ctx->self_doc ? ctx->self_doc : ctx->document;
+
+ UiAction *a = NULL;
+ if(ctx->actions) {
+ a = cxMapGet(ctx->actions, action);
+ }
+ if(a && a->callback) {
+ a->callback(&event, a->userdata);
+ }
+}
+
typedef struct UiActionBroadcast {
char *action;
void *eventdata;
ev.intval = 0;
ev.set = 0;
- if(ctx->close_callback) {
- ctx->close_callback(&ev, ctx->close_data);
+ if(ctx->onclose) {
+ ctx->onclose(&ev, ctx->onclosedata);
}
CxIterator i = cxListIterator(ctx->destroy_handler);
doc_ctx->ref++;
uic_context_update_bindings(doc_ctx);
+ if(doc_ctx->onattach) {
+ UiEvent event;
+ memset(&event, 0, sizeof(UiEvent));
+ event.document = document;
+ doc_ctx->onattach(&event, doc_ctx->onattachdata);
+ }
}
static void uic_context_unbind_vars(UiContext *ctx) {
cxListRemove(ctx->documents, docIndex);
ctx->document = cxListAt(ctx->documents, 0);
- UiContext *docctx = ui_document_context(document);
- uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
- docctx->parent = NULL;
+ UiContext *doc_ctx = ui_document_context(document);
+ uic_context_unbind_vars(doc_ctx); // unbind all doc/subdoc vars from the parent
+ doc_ctx->parent = NULL;
ui_document_unref(document);
ui_update_action_bindings(ctx);
+ if(doc_ctx->ondetach) {
+ UiEvent event;
+ memset(&event, 0, sizeof(UiEvent));
+ event.document = document;
+ doc_ctx->ondetach(&event, doc_ctx->ondetachdata);
+ }
}
void uic_context_detach_all(UiContext *ctx) {
}
void ui_context_closefunc(UiContext *ctx, ui_callback fnc, void *udata) {
- ctx->close_callback = fnc;
- ctx->close_data = udata;
+ ctx->onclose = fnc;
+ ctx->onclosedata = udata;
}
UiContext* ui_context_parent(UiContext *ctx) {
UiVar *var = uic_get_var_t(ctx, name, UI_VAR_GENERIC);
return var ? var->value : NULL;
}
+
+
+
+void ui_context_onattach(UiContext *ctx, ui_callback cb, void *data) {
+ ctx->onattach = cb;
+ ctx->onattachdata = data;
+}
+
+void ui_context_ondetach(UiContext *ctx, ui_callback cb, void *data) {
+ ctx->ondetach = cb;
+ ctx->ondetachdata = data;
+}
+
+static void attachment_action_callback(UiEvent *event, void *action) {
+ if(event->document) {
+ UiContext *ctx = ui_document_context(event->document);
+ ui_call_action_on(ctx, action);
+ }
+}
+
+void ui_context_onattach_action(UiContext *ctx, const char *action) {
+ ctx->onattach = attachment_action_callback;
+ ctx->onattachdata = ui_strdup(ctx, action);
+}
+
+void ui_context_ondetach_action(UiContext *ctx, const char *action) {
+ ctx->ondetach = attachment_action_callback;
+ ctx->ondetachdata = ui_strdup(ctx, action);
+}
// attaching a document will automatically detach the current document
UiBool single_document_mode;
- ui_callback close_callback;
- void *close_data;
+ ui_callback onattach;
+ void *onattachdata;
+
+ ui_callback ondetach;
+ void *ondetachdata;
+
+ ui_callback onclose;
+ void *onclosedata;
unsigned int ref;
};
void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates);
void uic_remove_state_widget(UiContext *ctx, void *widget);
+UIEXPORT void ui_context_onattach(UiContext *ctx, ui_callback cb, void *data);
+UIEXPORT void ui_context_ondetach(UiContext *ctx, ui_callback cb, void *data);
+UIEXPORT void ui_context_onattach_action(UiContext *ctx, const char *action);
+UIEXPORT void ui_context_ondetach_action(UiContext *ctx, const char *action);
+
#ifdef __cplusplus
}
#endif
UiObject* ui_context_obj(UiContext *ctx) {
return ctx->obj;
}
+
+void ui_document_onattach(void *doc, ui_callback cb, void *data) {
+ UiContext *ctx = ui_document_context(doc);
+ ui_context_onattach(ctx, cb, data);
+}
+
+void ui_document_ondetach(void *doc, ui_callback cb, void *data) {
+ UiContext *ctx = ui_document_context(doc);
+ ui_context_ondetach(ctx, cb, data);
+}
+
+void ui_document_onattach_action(void *doc, const char *action) {
+ UiContext *ctx = ui_document_context(doc);
+ ui_context_onattach_action(ctx, action);
+}
+
+void ui_document_ondetach_action(void *doc, const char *action) {
+ UiContext *ctx = ui_document_context(doc);
+ ui_context_ondetach_action(ctx, action);
+}
+
}
static void text_destroy(UiText *t) {
+ if(t->value.free) {
+ t->value.free(t->value.ptr);
+ }
if(t->destroy) {
t->destroy(t);
}
to->selection = from->selection;
to->length = from->length;
to->remove = from->remove;
+ to->setreadonly = from->setreadonly;
to->restore = from->restore;
to->save = from->save;
to->destroy = from->destroy;
t->replace = NULL;
t->setposition = NULL;
t->position = NULL;
+ t->showposition = NULL;
t->selection = NULL;
t->setselection = NULL;
t->length = NULL;
t->remove = NULL;
+ t->setreadonly = NULL;
}
void uic_range_unbind(UiRange *r) {
text->remove(text, begin, end);
}
}
+
+void ui_text_setreadonly(UiText *text, int readonly) {
+ if(text->setreadonly) {
+ text->setreadonly(text, readonly);
+ } else {
+ text->readonly = readonly;
+ }
+}
value->value.free(value->value.ptr);
}
}
+
+ if(value->readonly) {
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(text_area), FALSE);
+ }
+
gtk_text_view_set_buffer(GTK_TEXT_VIEW(text_area), buf);
value->obj = text_area;
value->save = ui_textarea_save;
value->selection = ui_textarea_selection;
value->length = ui_textarea_length;
value->remove = ui_textarea_remove;
+ value->setreadonly = ui_textarea_setreadonly;
value->data1 = buf;
value->data2 = NULL;
value->datatype == UI_TEXT_TYPE_BUFFER;
text->datatype = UI_TEXT_TYPE_BUFFER;
}
gtk_text_view_set_buffer(GTK_TEXT_VIEW(textarea), text->data1);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(textarea), !text->readonly);
}
void ui_textarea_text_destroy(UiText *text) {
gtk_text_buffer_delete(buf, &ib, &ie);
}
+void ui_textarea_setreadonly(UiText *text, int readonly) {
+ if(text->obj) {
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(text->obj), !readonly);
+ }
+ text->readonly = readonly;
+}
+
void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
gtk_widget_grab_focus(widget);
}
void ui_textarea_selection(UiText *text, int *begin, int *end);
int ui_textarea_length(UiText *text);
void ui_textarea_remove(UiText *text, int begin, int end);
+void ui_textarea_setreadonly(UiText *text, int readonly);
void ui_textarea_realize_event(GtkWidget *widget, gpointer data);
//void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea);
void (*selection)(UiText*, int*, int*); /* text, begin, end */
int (*length)(UiText*);
void (*remove)(UiText*, int, int); /* text, begin, end */
+ void (*setreadonly)(UiText*, int);
UiStr value;
int pos;
+ int readonly;
void *obj;
int datatype;
void *data1;
UIEXPORT void ui_document_ref(void *doc);
UIEXPORT void ui_document_unref(void *doc);
UIEXPORT void ui_document_destroy(void *doc);
+UIEXPORT void ui_document_onattach(void *doc, ui_callback cb, void *data);
+UIEXPORT void ui_document_ondetach(void *doc, ui_callback cb, void *data);
+UIEXPORT void ui_document_onattach_action(void *doc, const char *action);
+UIEXPORT void ui_document_ondetach_action(void *doc, const char *action);
UIEXPORT UiContext* ui_document_context(void *doc);
UIEXPORT void* ui_context_document(UiContext *ctx);
UIEXPORT void ui_mainthread_broadcast(const char *action_name);
UIEXPORT void ui_mainthread_broadcast2(const char *action_name, void *eventdata, int intval);
UIEXPORT void ui_mainthread_broadcast3(const char *action_name, void *ptr, uint64_t type_id);
+UIEXPORT void ui_call_action_on(UiContext *ctx, const char *action);
UIEXPORT void ui_set_state(UiContext *ctx, int state);
UIEXPORT void ui_unset_state(UiContext *ctx, int state);
UIEXPORT void ui_text_selection(UiText *text, int *begin, int *end);
UIEXPORT int ui_text_length(UiText *text);
UIEXPORT void ui_text_remove(UiText *text, int begin, int end);
+UIEXPORT void ui_text_setreadonly(UiText *text, int readonly);
UIEXPORT UiStr ui_str(char *cstr);
UIEXPORT UiStr ui_str_free(char *str, void (*free)(void *v));