From 476cb37ccd0a248d2219aa484133115705291f62 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Mon, 17 Feb 2025 22:32:33 +0100 Subject: [PATCH] implement loader for notebook structure and use the sidebar to show all available notebooks --- application/application.c | 2 + application/application.h | 7 ++- application/store.c | 81 ++++++++++++++++++++++++++++ application/store.h | 10 ++++ application/types.c | 6 +++ application/types.h | 2 + application/window.c | 92 +++++++++++++++++++++++++++++-- application/window.h | 2 + ui/gtk/list.c | 111 +++++++++++++++++++++++++++----------- ui/gtk/list.h | 1 + ui/ui/toolkit.h | 1 + ui/ui/tree.h | 16 +++++- 12 files changed, 293 insertions(+), 38 deletions(-) diff --git a/application/application.c b/application/application.c index 65f2757..340f796 100644 --- a/application/application.c +++ b/application/application.c @@ -86,5 +86,7 @@ void application_startup(UiEvent *event, void *data) { cxMempoolFree(mp); + note_store_reload(); + window_create(); } diff --git a/application/application.h b/application/application.h index acae80e..ee8806c 100644 --- a/application/application.h +++ b/application/application.h @@ -40,9 +40,12 @@ extern "C" { typedef struct MainWindow { UiList *notebooks; - UiList *sources; -} MainWindow; + + UiList *test1; + UiList *test2; +} MainWindow; + void application_startup(UiEvent *event, void *data); diff --git a/application/store.c b/application/store.c index 3e64e96..7405480 100644 --- a/application/store.c +++ b/application/store.c @@ -35,12 +35,27 @@ #include #include +#include +#include #include "types.h" #include "store_sqlite.h" #include "../dbutils/dbutils/db.h" #include "../dbutils/dbutils/sqlite.h" +#include "cx/mempool.h" + +#define SQL_NOTEBOOK_STRUCTURE "with recursive cols as (\n" \ + " select 1 as depth, '' as path, * from collections where collection_id = ?\n" \ + " union all\n" \ + " select p.depth+1 as depth, concat(p.path, '/', c.name), c.* from collections c\n" \ + " inner join cols p on c.parent_id = p.collection_id\n" \ + " where p.depth < 3\n" \ + ")\n" \ + "select * from cols\n" \ + "order by path;" + + static DBUConnection *connection; @@ -50,6 +65,8 @@ static char *settings_profile_name; static int64_t settings_default_repository_id; static int64_t settings_default_node_id; +static NoteStore *current_store; + /* * Creates a connection to the note database and initializes tables if necessary * @@ -256,3 +273,67 @@ int note_store_create_default(const char *host, const char *user) { return err; } + + +/* + * Reloads the NoteStore structure. The previous NoteStore* pointer will be + * invalid after this call + */ +void note_store_reload() { + // load notebook/collection structure + CxMempool *mp = cxMempoolCreate(64, NULL); + const CxAllocator *a = mp->allocator; + DBUQuery *q = connection->createQuery(connection, NULL); + dbuQuerySetSQL(q, SQL_NOTEBOOK_STRUCTURE); + dbuQuerySetParamInt64(q, 1, settings_default_node_id); + DBUObjectBuilder *builder = dbuObjectBuilder(collection_class, q, mp->allocator); + CxList *collections = dbuObjectBuilderGetList(builder); + dbuObjectBuilderDestroy(builder); + + if(!collections) { + fprintf(stderr, "Error: cannot query note store\n"); + cxMempoolFree(mp); + return; + } + + // NoteStore root object + NoteStore *store = cxMalloc(a, sizeof(NoteStore)); + store->mp = mp; + store->root = NULL; + + // key: collection_id value: Collection* + CxMap *collection_map = cxHashMapCreate(NULL, CX_STORE_POINTERS, cxListSize(collections) + 16); + + // convert result list to tree + CxIterator i = cxListIterator(collections); + cx_foreach(Collection *, col, i) { + CxHashKey key = cx_hash_key(&col->collection_id, sizeof(int64_t)); + cxMapPut(collection_map, key, col); + if(i.index == 0) { + store->root = col; + continue; + } + if(col->parent_id != 0) { + CxHashKey parent_key = cx_hash_key(&col->parent_id, sizeof(int64_t)); + Collection *parent = cxMapGet(collection_map, parent_key); + if(parent) { + if(!parent->children) { + parent->children = cxArrayListCreate(a, NULL, CX_STORE_POINTERS, 8); + } + cxListAdd(parent->children, col); + } + } + } + + cxMapFree(collection_map); + + if(current_store) { + cxMempoolFree(current_store->mp); + } + + current_store = store; +} + +NoteStore* note_store_get() { + return current_store; +} diff --git a/application/store.h b/application/store.h index 6913631..a556989 100644 --- a/application/store.h +++ b/application/store.h @@ -32,6 +32,7 @@ #include "application.h" #include "types.h" #include +#include #ifdef __cplusplus extern "C" { @@ -39,6 +40,11 @@ extern "C" { #define NOTES_DB_FILE "notes.db" +typedef struct NoteStore { + CxMempool *mp; + Collection *root; +} NoteStore; + int init_note_store(); CxList* note_store_get_user_settings(const CxAllocator *a, const char *host, const char *user, const char *profile); @@ -60,6 +66,10 @@ void note_store_set_settings( int note_store_create_default(const char *host, const char *user); +void note_store_reload(); + +NoteStore* note_store_get(); + #ifdef __cplusplus } diff --git a/application/types.c b/application/types.c index 2498b45..a6fc2ee 100644 --- a/application/types.c +++ b/application/types.c @@ -49,4 +49,10 @@ void register_types() { dbuClassAdd(usersettings_class, UserSettings, default_collection_id); dbuClassAdd(usersettings_class, UserSettings, created_by); + collection_class = dbuRegisterClass(ctx, "collections", Collection, collection_id); + dbuClassAdd(collection_class, Collection, parent_id); + dbuClassAdd(collection_class, Collection, repository_id); + dbuClassAdd(collection_class, Collection, name); + dbuClassAdd(collection_class, Collection, display_name); + dbuClassAdd(collection_class, Collection, type); } \ No newline at end of file diff --git a/application/types.h b/application/types.h index c82629c..f6de2d8 100644 --- a/application/types.h +++ b/application/types.h @@ -66,6 +66,8 @@ struct Collection { char *name; char *display_name; char *type; + + CxList *children; }; extern DBUClass *usersettings_class; diff --git a/application/window.c b/application/window.c index a152bb5..a5a44aa 100644 --- a/application/window.c +++ b/application/window.c @@ -28,18 +28,23 @@ #include "window.h" #include "application.h" +#include "store.h" + +#include void window_create() { UiObject *obj = ui_sidebar_window("note", NULL); MainWindow *wdata = window_init_data(obj); + /* UiSubList sublists[] = { { .value = wdata->notebooks, .header = "Notebooks" }, { .value = wdata->sources, .header = "Sources", .separator = TRUE } }; + */ ui_sidebar(obj) { - ui_sourcelist(obj, .sublists = sublists, .numsublists = 2, .getvalue = window_sidebar_getvalue, .fill = UI_ON); + ui_sourcelist(obj, .dynamic_sublist = wdata->notebooks, .getvalue = window_sidebar_getvalue, .fill = UI_ON); ui_hbox(obj, .spacing = 2, .fill = UI_OFF) { ui_button(obj, .icon = "folder-new-symbolic", .style_class = "flat"); } @@ -68,12 +73,93 @@ MainWindow* window_init_data(UiObject *obj) { MainWindow *wdata = ui_calloc(obj->ctx, 1, sizeof(MainWindow)); obj->window = wdata; - wdata->notebooks = ui_list_new(obj->ctx, "notebooks"); - wdata->sources = ui_list_new(obj->ctx, "source"); + wdata->notebooks = ui_list_new(obj->ctx, NULL); + wdata->test1 = ui_list_new(obj->ctx, NULL); + wdata->test2 = ui_list_new(obj->ctx, NULL); + + ui_list_append(wdata->test1, "Test1"); + ui_list_append(wdata->test1, "Test2"); + ui_list_append(wdata->test1, "Test3"); + + ui_list_append(wdata->test2, "Test2 X1"); + ui_list_append(wdata->test2, "Test2 X2"); + + UiSubList *s1 = calloc(1, sizeof(UiSubList)); + UiSubList *s2 = calloc(1, sizeof(UiSubList)); + + s1->value = wdata->test1; + s2->value = wdata->test2; + + s1->header = "Test 1"; + s2->header = "Test 2"; + + //ui_list_append(wdata->notebooks, s1); + //ui_list_append(wdata->notebooks, s2); + update_sublists(obj->ctx, wdata->notebooks); return wdata; } void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item) { + Collection *notebook = rowdata; + item->label = strdup(notebook->display_name ? notebook->display_name : notebook->name); + +} + +static void sublist_free(UiContext *ctx, UiSubList *sublist) { + ui_list_free(sublist->value); + free((char*)sublist->header); + free(sublist); +} + +/* + * Converts the notestore to a list of sublists + * The sublists list will be cleared and filled with UiSubList elements + */ +void update_sublists(UiContext *ctx, UiList *sublists) { + NoteStore *notestore = note_store_get(); + // The NoteStore is a tree in theory, however, currently the depth + // is limited to 3 + // All direct children of root will be converted to a UiSubList + + CxList *list = sublists->data; + + // save previous elements + CxList *delete_list = cxArrayListCreateSimple(CX_STORE_POINTERS, cxListSize(list)); + CxIterator i = cxListIterator(list); + cx_foreach(UiSubList *, sublist, i) { + cxListAdd(delete_list, sublist); + } + cxListClear(list); + + if(!notestore->root) { + fprintf(stderr, "Error: no NoteStore root\n"); + return; // weird, no root, shouldn't happen + } + + if(!notestore->root->children) { + return; + } + + i = cxListIterator(notestore->root->children); + cx_foreach(Collection *, col, i) { + UiSubList *sublist = calloc(1, sizeof(UiSubList)); + sublist->header = strdup(col->display_name ? col->display_name : col->name); + sublist->value = ui_list_new(ctx, NULL); + sublist->userdata = col; + ui_list_append(sublists, sublist); + + if(col->children) { + CxIterator j = cxListIterator(col->children); + cx_foreach(Collection *, nb, j) { + ui_list_append(sublist->value, nb); + } + } + } + + // now we can free the list + delete_list->collection.advanced_destructor = (cx_destructor_func2)sublist_free; + delete_list->collection.destructor_data = ctx; + cxListFree(delete_list); } diff --git a/application/window.h b/application/window.h index 71594a4..9373298 100644 --- a/application/window.h +++ b/application/window.h @@ -42,6 +42,8 @@ MainWindow* window_init_data(UiObject *obj); void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); +void update_sublists(UiContext *ctx, UiList *sublists); + #ifdef __cplusplus } diff --git a/ui/gtk/list.c b/ui/gtk/list.c index a434791..c0e5467 100644 --- a/ui/gtk/list.c +++ b/ui/gtk/list.c @@ -1566,6 +1566,34 @@ static void ui_sidebar_list_box_init(UiSidebarListBox *self) { } #endif + +static void add_sublist(UiListBox *uilistbox, CxList *sublists, UiSubList *sublist) { + UiListBoxSubList uisublist; + uisublist.var = uic_widget_var( + uilistbox->obj->ctx, + uilistbox->obj->ctx, + sublist->value, + sublist->varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist->header ? strdup(sublist->header) : NULL; + uisublist.separator = sublist->separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist->userdata; + uisublist.index = cxListSize(sublists); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1); + UiList *list = uisublist.var->value; + if(list) { + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } + + cxListAdd(sublists, &uisublist); +} + UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { UiObject* current = uic_current_obj(obj); @@ -1603,42 +1631,32 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { uilistbox->sublists->collection.destructor_data = obj; uilistbox->first_row = NULL; - if(args.numsublists == 0 && args.sublists) { - args.numsublists = INT_MAX; - } - for(int i=0;isublists, &sublist); } - UiListBoxSubList uisublist; - uisublist.var = uic_widget_var( - obj->ctx, - current->ctx, - sublist.value, - sublist.varname, - UI_VAR_LIST); - uisublist.numitems = 0; - uisublist.header = sublist.header ? strdup(sublist.header) : NULL; - uisublist.separator = sublist.separator; - uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); - uisublist.listbox = uilistbox; - uisublist.userdata = sublist.userdata; - uisublist.index = i; - - cxListAdd(uilistbox->sublists, &uisublist); - - // bind UiList - UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); - UiList *list = uisublist.var->value; - if(list) { - list->obj = sublist_ptr; - list->update = ui_listbox_list_update; + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + } else { + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.dynamic_sublist, args.varname, UI_VAR_LIST); + if(var) { + UiList *list = var->value; + list->obj = uilistbox; + list->update = ui_listbox_dynamic_update; + + ui_listbox_dynamic_update(list, 0); } } - // fill items - ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); // register uilistbox for both widgets, so it doesn't matter which // widget is used later @@ -1663,6 +1681,35 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { return scroll_area; } +void ui_listbox_dynamic_update(UiList *list, int x) { + UiListBox *uilistbox = list->obj; + + // unbind/free previous list vars + CxIterator i = cxListIterator(uilistbox->sublists); + cx_foreach(UiListBoxSubList *, s, i) { + if(s->var) { + UiList *sl = s->var->value; + sl->obj = NULL; + sl->update = NULL; + if(s->var->type == UI_VAR_SPECIAL) { + ui_free(s->var->from_ctx, s->var); + } + } + } + + cxListFree(uilistbox->sublists); + CxList *new_sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), list->count(list)); + uilistbox->sublists = new_sublists; + + UiSubList *sublist = list->first(list); + while(sublist) { + add_sublist(uilistbox, new_sublists, sublist); + sublist = list->next(list); + } + + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); +} + void ui_listbox_update(UiListBox *listbox, int from, int to) { CxIterator i = cxListIterator(listbox->sublists); size_t pos = 0; diff --git a/ui/gtk/list.h b/ui/gtk/list.h index d9ba1b2..1ff4029 100644 --- a/ui/gtk/list.h +++ b/ui/gtk/list.h @@ -161,6 +161,7 @@ void ui_combobox_modelupdate(UiList *list, int i); UiListSelection ui_combobox_getselection(UiList *list); void ui_combobox_setselection(UiList *list, UiListSelection selection); +void ui_listbox_dynamic_update(UiList *list, int i); void ui_listbox_update(UiListBox *listbox, int from, int to); void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index); void ui_listbox_list_update(UiList *list, int i); diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 66973c3..dd4a7d1 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -534,6 +534,7 @@ UIEXPORT void ui_notify_evt(UiObserver *observer, UiEvent *event); UIEXPORT UiList* ui_list_new(UiContext *ctx, char *name); +UIEXPORT void ui_list_free(UiList *list); UIEXPORT void* ui_list_first(UiList *list); UIEXPORT void* ui_list_next(UiList *list); UIEXPORT void* ui_list_get(UiList *list, int i); diff --git a/ui/ui/tree.h b/ui/ui/tree.h index eb652f0..589d2a1 100644 --- a/ui/ui/tree.h +++ b/ui/ui/tree.h @@ -183,11 +183,14 @@ struct UiSourceListArgs { const int *groups; /* - * list of sublists + * static list of sublists * a sublist must have a varname or a value * * the last entry in the list must contain all NULL values or numsublists * must contain the number of sublists + * + * sublists can be NULL, in which case sublists are dynamically loaded + * from dynamic_sublist/varname */ UiSubList *sublists; /* @@ -197,6 +200,17 @@ struct UiSourceListArgs { */ size_t numsublists; + /* + * list value, that contains UiSubList* elements + */ + UiList *dynamic_sublist; + + /* + * load sublists dynamically from a variable with the specified name + */ + const char *varname; + + /* * callback for each list item, that should fill all necessary * UiSubListItem fields -- 2.43.5