From: Olaf Wintermann Date: Mon, 12 Jan 2026 20:33:00 +0000 (+0100) Subject: add test_note_store_new_resource_async X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=HEAD;p=note.git add test_note_store_new_resource_async --- diff --git a/application/gtk-image.c b/application/gtk-image.c index 9f796b3..1b01e41 100644 --- a/application/gtk-image.c +++ b/application/gtk-image.c @@ -94,4 +94,4 @@ GtkWidget* embedded_image_create(EmbeddedWidget *em, GdkPixbuf *pix) { } -#endif \ No newline at end of file +#endif diff --git a/application/tests/test-store.c b/application/tests/test-store.c index 33e9b2c..ad931e2 100644 --- a/application/tests/test-store.c +++ b/application/tests/test-store.c @@ -137,3 +137,45 @@ CX_TEST(test_note_store_reload) { } } +static void test_new_resource_result(UiEvent *event, int64_t newid, int error, void *userdata) { + int64_t *ptr = userdata; + ptr[0] = newid; + ptr[1] = error; +} + +CX_TEST(test_note_store_new_resource_async) { + CX_TEST_DO { + UiObject *obj = ui_dummy_object(); + + NoteStore *store = note_store_get(); + CX_TEST_ASSERT(store); + + Resource res0 = { 0 }; + res0.nodename = "test_note_store_new_resource_async"; + res0.parent_id = store->root->resource_id; + res0.iscollection = TRUE; + + int64_t result[2] = { 0, -1 }; + note_store_new_resource_async(obj, &res0, test_new_resource_result, result); + ui_exec_buffered_mainthread_calls_wait(3); + + CX_TEST_ASSERT(result[0] > 0); + CX_TEST_ASSERT(result[1] == 0); + + CX_TEST_ASSERT(!note_store_reload()); + store = note_store_get(); + + CX_TEST_ASSERT(store && store->root && store->root->children); + int res_found = 0; + CxIterator i = cxListIterator(store->root->children); + cx_foreach(Resource *, resource, i) { + if(!cx_strcmp(resource->nodename, res0.nodename)) { + res_found = 1; + break; + } + } + CX_TEST_ASSERT(res_found); + + ui_close(obj); + } +} diff --git a/application/tests/test-store.h b/application/tests/test-store.h index 92dabd5..f4c1b31 100644 --- a/application/tests/test-store.h +++ b/application/tests/test-store.h @@ -40,7 +40,7 @@ CX_TEST(test_note_store_create_default); CX_TEST(test_user_settings_is_valid); CX_TEST(test_note_store_reload); - +CX_TEST(test_note_store_new_resource_async); #ifdef __cplusplus diff --git a/application/tests/testmain.c b/application/tests/testmain.c index bc272e1..79a4207 100644 --- a/application/tests/testmain.c +++ b/application/tests/testmain.c @@ -48,12 +48,16 @@ int main(int argc, char **argv) { ui_init(NULL, 0, NULL); register_types(); + // required for testing async functions, that use ui_call_mainthread + ui_buffer_mainthread_calls(TRUE); + CxTestSuite *suite = cx_test_suite_new("note"); cx_test_register(suite, test_init_note_store); cx_test_register(suite, test_note_store_create_default); cx_test_register(suite, test_user_settings_is_valid); cx_test_register(suite, test_note_store_reload); + cx_test_register(suite, test_note_store_new_resource_async); cx_test_register(suite, test_parse_markdown_para); cx_test_register(suite, test_parse_markdown_formatting_simple); diff --git a/ui/common/object.c b/ui/common/object.c index f158230..9b197d7 100644 --- a/ui/common/object.c +++ b/ui/common/object.c @@ -60,6 +60,12 @@ void ui_register_object_destruction_callback(ui_object_callback func, void *user cxListAdd(destruction_callbacks, &cb); } +UiObject* ui_dummy_object(void) { + UiObject *obj = uic_object_new_toplevel(); + obj->ref = 1; + return obj; +} + void uic_object_created(UiObject *obj) { CxIterator i = cxListIterator(creation_callbacks); cx_foreach(objcallback *, cb, i) { diff --git a/ui/common/properties.c b/ui/common/properties.c index bedc918..29c8ce6 100644 --- a/ui/common/properties.c +++ b/ui/common/properties.c @@ -234,7 +234,6 @@ void uic_load_app_properties() { return; } - char *dir = ui_configfile(NULL); if(!dir) { return; diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c index 5d3b72a..0710dcd 100644 --- a/ui/common/threadpool.c +++ b/ui/common/threadpool.c @@ -42,6 +42,7 @@ static threadpool_job kill_job; static pthread_mutex_t mc_buffer_mutex; +static pthread_cond_t mc_buffer_available; static CxList *mainthread_call_buffer; static volatile int mainthread_call_buffered = 0; @@ -52,6 +53,7 @@ typedef struct UiMainCall { void uic_init_threads(void) { pthread_mutex_init(&mc_buffer_mutex, NULL); + pthread_cond_init(&mc_buffer_available, NULL); mainthread_call_buffer = cxLinkedListCreate(NULL, sizeof(UiMainCall)); } @@ -65,6 +67,7 @@ void uic_add_buffered_mainthread_call(ui_threadfunc func, void *data) { call.func = func; call.data = data; cxListAdd(mainthread_call_buffer, &call); + pthread_cond_signal(&mc_buffer_available); pthread_mutex_unlock(&mc_buffer_mutex); } @@ -75,8 +78,8 @@ void ui_buffer_mainthread_calls(UiBool enable_buffering) { ui_exec_buffered_mainthread_calls(); } } -void ui_exec_buffered_mainthread_calls(void) { - pthread_mutex_lock(&mc_buffer_mutex); + +static void exec_buffered_calls(void) { CxIterator i = cxListIterator(mainthread_call_buffer); cx_foreach(UiMainCall *, call, i) { if(call->func) { @@ -84,10 +87,34 @@ void ui_exec_buffered_mainthread_calls(void) { } } cxListClear(mainthread_call_buffer); - pthread_mutex_unlock(&mc_buffer_mutex); } +void ui_exec_buffered_mainthread_calls(void) { + pthread_mutex_lock(&mc_buffer_mutex); + exec_buffered_calls(); + pthread_mutex_unlock(&mc_buffer_mutex); +} +UIEXPORT void ui_exec_buffered_mainthread_calls_wait(int timeout) { + struct timespec ts; + if(timeout > 0) { + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout; + } + + pthread_mutex_lock(&mc_buffer_mutex); + while(cxListSize(mainthread_call_buffer) == 0) { + if(timeout > 0) { + if(pthread_cond_timedwait(&mc_buffer_available, &mc_buffer_mutex, &ts)) { + break; + } + } else { + pthread_cond_wait(&mc_buffer_available, &mc_buffer_mutex); + } + } + exec_buffered_calls(); + pthread_mutex_unlock(&mc_buffer_mutex); +} UiThreadpool* threadpool_new(int min, int max) { UiThreadpool *pool = malloc(sizeof(UiThreadpool)); diff --git a/ui/gtk/text.c b/ui/gtk/text.c index 0bcd54d..0c35219 100644 --- a/ui/gtk/text.c +++ b/ui/gtk/text.c @@ -687,6 +687,41 @@ UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args) { return create_textfield(obj, FALSE, TRUE, args); } +void ui_textfield_focus(UIWIDGET textfield) { + gtk_widget_grab_focus(textfield); +} + +void ui_textfield_focus_without_selecting(UIWIDGET textfield) { +#if GTK_CHECK_VERSION(3, 16, 0) + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(textfield)); +#else + gtk_widget_grab_focus(textfield); +#endif +} + +void ui_textfield_set_selection(UIWIDGET textfield, int begin, int end) { + ENTRY_SET_SELECTION(textfield, begin, end); +} + +void ui_textfield_select_all(UIWIDGET textfield) { + ENTRY_SET_SELECTION(textfield, 0, -1); +} + +void ui_textfield_set_editable(UIWIDGET textfield, UiBool editable) { + ENTRY_SET_EDITABLE(textfield, editable); +} + +UiBool ui_textfield_is_editable(UIWIDGET textfield) { + ENTRY_IS_EDITABLE(textfield); +} + +void ui_textfield_set_position(UIWIDGET textfield, int pos) { + ENTRY_SET_POSITION(textfield, pos); +} + +int ui_textfield_get_position(UIWIDGET textfield) { + return ENTRY_GET_POSITION(textfield); +} void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) { free(textfield); diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c index d775f3b..9009bca 100644 --- a/ui/gtk/toolkit.c +++ b/ui/gtk/toolkit.c @@ -33,6 +33,7 @@ #include "toolkit.h" #include "toolbar.h" +#include "window.h" #include "icon.h" #include "../common/document.h" #include "../common/properties.h" @@ -145,27 +146,33 @@ GtkApplication* ui_get_application() { #endif void ui_show(UiObject *obj) { - gboolean visible = gtk_widget_is_visible(obj->widget); - + gboolean visible = FALSE; uic_check_state_widgets(obj->ctx); + if(obj->widget) { + visible = gtk_widget_is_visible(obj->widget); #if GTK_MAJOR_VERSION >= 4 - gtk_window_present(GTK_WINDOW(obj->widget)); + gtk_window_present(GTK_WINDOW(obj->widget)); #elif GTK_MAJOR_VERSION <= 3 - gtk_widget_show_all(obj->widget); + gtk_widget_show_all(obj->widget); #endif - + } + if(!visible) { obj->ref++; } } void ui_close(UiObject *obj) { - uic_context_prepare_close(obj->ctx); + uic_context_prepare_close(obj->ctx); // TODO: should this be moved to the close event handler? + if(obj->widget) { #if GTK_CHECK_VERSION(4, 0, 0) - gtk_window_close(GTK_WINDOW(obj->widget)); + gtk_window_close(GTK_WINDOW(obj->widget)); #else - gtk_widget_destroy(obj->widget); + gtk_widget_destroy(obj->widget); #endif + } else { + ui_window_close_request(obj); + } } diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h index 0e421b9..e16f361 100644 --- a/ui/gtk/toolkit.h +++ b/ui/gtk/toolkit.h @@ -65,6 +65,11 @@ extern "C" { #define BOX_REMOVE(box, child) gtk_box_remove(GTK_BOX(box), child) #define ENTRY_SET_TEXT(entry, text) gtk_editable_set_text(GTK_EDITABLE(entry), text) #define ENTRY_GET_TEXT(entry) gtk_editable_get_text(GTK_EDITABLE(entry)) +#define ENTRY_SET_SELECTION(entry, begin, end) gtk_editable_select_region(GTK_EDITABLE(entry), begin, end) +#define ENTRY_SET_EDITABLE(entry, editable) gtk_editable_set_editable(GTK_EDITABLE(entry), editable) +#define ENTRY_IS_EDITABLE(entry) gtk_editable_get_editable(GTK_EDITABLE(entry)) +#define ENTRY_SET_POSITION(entry, pos) gtk_editable_set_position(GTK_EDITABLE(entry), pos) +#define ENTRY_GET_POSITION(entry) gtk_editable_get_position(GTK_EDITABLE(entry)) #define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new() #define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), child) #define SCROLLEDWINDOW_GET_CHILD(sw) gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw)) @@ -90,6 +95,11 @@ extern "C" { #define BOX_REMOVE(box, child) gtk_container_remove(GTK_CONTAINER(box), child) #define ENTRY_SET_TEXT(entry, text) gtk_entry_set_text(GTK_ENTRY(entry), text) #define ENTRY_GET_TEXT(entry) gtk_entry_get_text(GTK_ENTRY(entry)) +#define ENTRY_SET_SELECTION(entry, begin, end) gtk_editable_select_region(GTK_EDITABLE(entry), begin, end) +#define ENTRY_SET_EDITABLE(entry, editable) gtk_editable_set_editable(GTK_EDITABLE(entry), editable) +#define ENTRY_IS_EDITABLE(entry) gtk_editable_get_editable(GTK_EDITABLE(entry)) +#define ENTRY_SET_POSITION(entry, pos) gtk_editable_set_position(GTK_EDITABLE(entry), pos) +#define ENTRY_GET_POSITION(entry) gtk_editable_get_position(GTK_EDITABLE(entry)) #define SCROLLEDWINDOW_NEW() gtk_scrolled_window_new(NULL, NULL) #define SCROLLEDWINDOW_SET_CHILD(sw, child) gtk_container_add(GTK_CONTAINER(sw), child) #define SCROLLEDWINDOW_GET_CHILD(sw) gtk_bin_get_child(GTK_BIN(sw)) diff --git a/ui/gtk/window.c b/ui/gtk/window.c index 1046391..fc5e7b2 100644 --- a/ui/gtk/window.c +++ b/ui/gtk/window.c @@ -44,6 +44,7 @@ #include "container.h" #include "headerbar.h" #include "button.h" +#include "window.h" static int nwindows = 0; @@ -80,7 +81,7 @@ void ui_exit_event(GtkWidget *widget, gpointer data) { g_idle_add(ui_window_destroy, data); } -static gboolean ui_window_close_request(UiObject *obj) { +gboolean ui_window_close_request(UiObject *obj) { if(obj->widget) { void *appwindow = g_object_get_data(G_OBJECT(obj->widget), "ui.appwindow"); if(appwindow) { @@ -111,7 +112,6 @@ static gboolean ui_window_close_request(UiObject *obj) { } } - uic_context_prepare_close(obj->ctx); obj->ref--; if(obj->ref > 0) { #if GTK_CHECK_VERSION(2, 18, 0) @@ -121,6 +121,7 @@ static gboolean ui_window_close_request(UiObject *obj) { #endif return TRUE; } else { + uic_context_prepare_close(obj->ctx); return FALSE; } } diff --git a/ui/gtk/window.h b/ui/gtk/window.h new file mode 100644 index 0000000..1b85fd9 --- /dev/null +++ b/ui/gtk/window.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + + +#ifndef WINDOW_H +#define WINDOW_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +gboolean ui_window_close_request(UiObject *obj); + + +#ifdef __cplusplus +} +#endif + +#endif /* WINDOW_H */ + diff --git a/ui/motif/text.c b/ui/motif/text.c index b2c5389..09841df 100644 --- a/ui/motif/text.c +++ b/ui/motif/text.c @@ -492,6 +492,39 @@ UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args) { return create_textfield(obj, args, FALSE, FALSE); } +void ui_textfield_focus(UIWIDGET textfield) { + ui_textfield_focus_without_selecting(textfield); + ui_textfield_select_all(textfield); +} + +void ui_textfield_focus_without_selecting(UIWIDGET textfield) { + XmProcessTraversal(textfield, XmTRAVERSE_CURRENT); +} + +void ui_textfield_set_selection(UIWIDGET textfield, int begin, int end) { + XmTextSetSelection(textfield, begin, end, 0); +} + +void ui_textfield_select_all(UIWIDGET textfield) { + XmTextSetSelection(textfield, 0, XmTextGetLastPosition(textfield), 0); +} + +void ui_textfield_set_editable(UIWIDGET textfield, UiBool editable) { + XmTextFieldSetEditable(textfield, editable); +} + +UiBool ui_textfield_is_editable(UIWIDGET textfield) { + return XmTextFieldGetEditable(textfield); +} + +void ui_textfield_set_position(UIWIDGET textfield, int pos) { + XmTextFieldSetInsertionPosition(textfield, pos); +} + +int ui_textfield_get_position(UIWIDGET textfield) { + return (int)XmTextFieldGetInsertionPosition(textfield); +} + char* ui_textfield_get(UiString *str) { if(str->value.free) { str->value.free(str->value.ptr); diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c index da7ab6e..d70c25f 100644 --- a/ui/motif/toolkit.c +++ b/ui/motif/toolkit.c @@ -39,6 +39,7 @@ #include "../common/document.h" #include "../common/properties.h" #include "../common/app.h" +#include "../common/threadpool.h" #include #include @@ -155,8 +156,12 @@ void ui_secondary_event_loop(int *loop) { void ui_show(UiObject *obj) { uic_check_state_widgets(obj->ctx); - if(!XtIsRealized(obj->widget)) { - XtRealizeWidget(obj->widget); + if(obj->widget) { + if(!XtIsRealized(obj->widget)) { + XtRealizeWidget(obj->widget); + obj->ref++; + } + } else { obj->ref++; } } diff --git a/ui/ui/text.h b/ui/ui/text.h index c102f0b..4e92659 100644 --- a/ui/ui/text.h +++ b/ui/ui/text.h @@ -155,6 +155,15 @@ UIEXPORT UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs * UIEXPORT UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs *args); UIEXPORT UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args); + +UIEXPORT void ui_textfield_focus(UIWIDGET textfield); +UIEXPORT void ui_textfield_focus_without_selecting(UIWIDGET textfield); +UIEXPORT void ui_textfield_set_selection(UIWIDGET textfield, int begin, int end); +UIEXPORT void ui_textfield_select_all(UIWIDGET textfield); +UIEXPORT void ui_textfield_set_editable(UIWIDGET textfield, UiBool editable); +UIEXPORT UiBool ui_textfield_is_editable(UIWIDGET textfield); +UIEXPORT void ui_textfield_set_position(UIWIDGET textfield, int pos); +UIEXPORT int ui_textfield_get_position(UIWIDGET textfield); #ifdef __cplusplus } diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 6c0152b..fff2dda 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -554,11 +554,14 @@ UIEXPORT void ui_app_quit(void); UIEXPORT void ui_show(UiObject *obj); UIEXPORT void ui_close(UiObject *obj); +UIEXPORT UiObject* ui_dummy_object(void); + UIEXPORT void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd); UIEXPORT void ui_call_mainthread(ui_threadfunc tf, void* td); UIEXPORT void ui_buffer_mainthread_calls(UiBool enable_buffering); UIEXPORT void ui_exec_buffered_mainthread_calls(void); +UIEXPORT void ui_exec_buffered_mainthread_calls_wait(int timeout); UIEXPORT UiThreadpool* ui_threadpool_create(int nthreads); UIEXPORT void ui_threadpool_destroy(UiThreadpool* pool);