SRC += types.c
SRC += store.c
SRC += store_sqlite.c
+SRC += notebook.c
OBJ = $(SRC:%.c=../build/application/%.$(OBJ_EXT))
#define APPLICATION_H
#include <ui/ui.h>
+#include "types.h"
+
+#include <cx/mempool.h>
#ifdef __cplusplus
extern "C" {
#define APP_STATE_NOTE_SELECTED 100
+typedef struct NotebookModel NotebookModel;
+
typedef struct MainWindow {
+ UiObject *obj;
+
UiList *notebooks;
+ NotebookModel *current_notebook;
+
+ /*
+ * key: collection_id
+ * value: NotebookModel*
+ */
+ CxMap *notebook_cache;
UiList *test1;
UiList *test2;
} MainWindow;
+
+struct NotebookModel {
+ /*
+ * Document context
+ */
+ UiContext *ctx;
+
+ /*
+ * The window, this notebook model is attached to.
+ */
+ MainWindow *window;
+
+ /*
+ * Pointer to NoteStore Collection
+ *
+ * After a NoteStore refresh, this pointer is invalid and must be renewed.
+ */
+ Collection *collection;
+
+ /*
+ * same as collection->collection_id, however Collection* may be an invalid
+ * pointer, therefore the collection_id is required separately.
+ */
+ int64_t collection_id;
+
+ /*
+ * list of Note*
+ *
+ * name: notes
+ */
+ UiList *notes;
+
+ /*
+ * The mempool used to allocate the current list of notes
+ */
+ CxMempool *current_notes_pool;
+};
void application_init();
void application_startup(UiEvent *event, void *data);
-
void action_note_new(UiEvent *event, void *data);
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#include "menu.h"
+
+void menu_init() {
+
+}
+
+void toolbar_init() {
+ ui_toolbar_item("AddNote", .icon = UI_ICON_ADD, .onclick = action_note_new);
+
+
+ ui_toolbar_add_default("AddNote", UI_TOOLBAR_LEFT);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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 MENU_H
+#define MENU_H
+
+#include <ui/ui.h>
+
+#include "application.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void menu_init();
+void toolbar_init();
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MENU_H */
+
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#include "notebook.h"
+
+#include "store.h"
+
+NotebookModel* notebookmodel_create() {
+ NotebookModel *model = ui_document_new(sizeof(NotebookModel));
+ model->ctx = ui_document_context(model);
+
+ model->notes = ui_list_new(model->ctx, "notes");
+
+
+ return model;
+}
+
+void notebookmodel_attach(MainWindow *window, NotebookModel *model) {
+ if(window->current_notebook) {
+ notebookmodel_detach(window->current_notebook);
+ }
+ ui_attach_document(window->obj->ctx, model);
+ window->current_notebook = model;
+ model->window = window;
+}
+
+void notebookmodel_detach(NotebookModel *model) {
+ if(model->window) {
+ ui_detach_document2(model->window->obj->ctx, model);
+ model->window->current_notebook = NULL;
+ }
+}
+
+void notebookmodel_set_collection(NotebookModel *model, Collection *collection) {
+ model->collection = collection;
+ model->collection_id = collection->collection_id;
+}
+
+
+static void notebook_loaded(UiEvent *event, AsyncListResult *result, void *data) {
+ NotebookModel *model = data;
+ if(result->list) {
+ ui_list_clear(model->notes);
+ CxIterator i = cxListIterator(result->list);
+ cx_foreach(Note *, note, i) {
+ ui_list_append(model->notes, note);
+ }
+ ui_list_update(model->notes);
+ } else {
+ fprintf(stderr, "Error: get notes failed\n");
+ }
+}
+
+void notebookmodel_reload(UiObject *obj, NotebookModel *model) {
+ note_store_get_notes_async(obj, model->collection_id, notebook_loaded, model);
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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 NOTEBOOK_H
+#define NOTEBOOK_H
+
+#include "application.h"
+#include "types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+NotebookModel* notebookmodel_create();
+
+void notebookmodel_attach(MainWindow *window, NotebookModel *model);
+
+void notebookmodel_detach(NotebookModel *model);
+
+void notebookmodel_set_collection(NotebookModel *model, Collection *collection);
+
+void notebookmodel_reload(UiObject *obj, NotebookModel *model);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NOTEBOOK_H */
+
"select * from cols\n" \
"order by path;"
-
+#define SQL_NOTEBOOK_GET_NOTES "select note_id, parent_id, name, title, lastmodified, creationdate, contenttype, created_by from notes where parent_id = ? ;"
static DBUConnection *connection;
+static UiThreadpool *queue;
+
static char *settings_host;
static char *settings_user;
static char *settings_profile_name;
}
}
+ queue = ui_threadpool_create(1);
+
return 0;
}
NoteStore* note_store_get() {
return current_store;
}
+
+
+CxList* note_store_get_notes(const CxAllocator *a, int64_t parent_collection_id) {
+ DBUQuery *q = connection->createQuery(connection, NULL);
+ dbuQuerySetSQL(q, SQL_NOTEBOOK_GET_NOTES);
+ dbuQuerySetParamInt64(q, 1, parent_collection_id);
+ DBUObjectBuilder *builder = dbuObjectBuilder(notes_class, q, a);
+ CxList *notes = dbuObjectBuilderGetList(builder);
+ dbuObjectBuilderDestroy(builder);
+ return notes;
+}
+
+typedef struct JobGetNotes {
+ int64_t collection_id;
+ listresult_func resultcb;
+ void *userdata;
+ AsyncListResult result;
+} JobGetNotes;
+
+static void uithr_get_notes_finished(UiEvent *event, JobGetNotes *job) {
+ if(job->resultcb) {
+ job->resultcb(event, &job->result, job->userdata);
+ } else {
+ cxMempoolFree(job->result.mp);
+ }
+ free(job);
+}
+
+static int qthr_get_notes(JobGetNotes *job) {
+ job->result.list = note_store_get_notes(job->result.mp->allocator, job->collection_id);
+ if(!job->result.list) {
+ cxMempoolFree(job->result.mp);
+ job->result.mp = NULL;
+ }
+ return 0;
+}
+
+void note_store_get_notes_async(UiObject* obj, int64_t parent_collection_id, listresult_func resultcb, void *userdata) {
+ JobGetNotes *job = malloc(sizeof(JobGetNotes));
+ job->result.mp = cxMempoolCreate(128, NULL);
+ job->result.list = NULL;
+ job->collection_id = parent_collection_id;
+ job->resultcb = resultcb;
+ job->userdata = userdata;
+ ui_threadpool_job(queue, obj, (ui_threadfunc)qthr_get_notes, job, (ui_callback)uithr_get_notes_finished, job);
+}
Collection *root;
} NoteStore;
+typedef struct AsyncListResult {
+ CxMempool *mp;
+ CxList *list;
+} AsyncListResult;
+
+typedef void (*listresult_func)(UiEvent *event, AsyncListResult *result, void *userdata);
+
int init_note_store();
CxList* note_store_get_user_settings(const CxAllocator *a, const char *host, const char *user, const char *profile);
NoteStore* note_store_get();
+CxList* note_store_get_notes(const CxAllocator *a, int64_t parent_collection_id);
+void note_store_get_notes_async(UiObject* obj, int64_t parent_collection_id, listresult_func resultcb, void *userdata);
+
#ifdef __cplusplus
}
"foreign key (default_collection_id) references collections(collection_id), " \
"unique (host, user, profile_name)" \
");"
-#define SQL_CREATE_TABLE_RESOURCE "create table resources( " \
- "resource_id integer primary key, " \
+#define SQL_CREATE_TABLE_NOTES "create table notes( " \
+ "note_id integer primary key, " \
"parent_id integer, " \
"name text, " \
"title text, " \
"lastmodified text, " \
"creationdate text, " \
- "contentlength integer, " \
+ "contenttype text, " \
"content text, " \
"bin_content blob, " \
- "note_item integer, " \
"created_by text, " \
"foreign key (parent_id) references collections(collection_id), " \
"unique (parent_id, name) " \
SQL_CREATE_TABLE_REPOSITORIES,
SQL_CREATE_TABLE_COLLECTIONS,
SQL_CREATE_TABLE_USER_SETTINGS,
- SQL_CREATE_TABLE_RESOURCE
+ SQL_CREATE_TABLE_NOTES
};
int nsql = sizeof(sql) / sizeof(char*);
DBUClass *usersettings_class;
DBUClass *repository_class;
DBUClass *collection_class;
-DBUClass *resource_class;
+DBUClass *notes_class;
DBUContext* get_dbu_context() {
return ctx;
dbuClassAdd(collection_class, Collection, display_name);
dbuClassAdd(collection_class, Collection, type);
- resource_class = dbuRegisterClass(ctx, "resources", Resource, resource_id);
- dbuClassAdd(resource_class, Resource, resource_id);
- dbuClassAdd(resource_class, Resource, parent_id);
- dbuClassAdd(resource_class, Resource, name);
- dbuClassAdd(resource_class, Resource, title);
- dbuClassAdd(resource_class, Resource, lastmodified);
- dbuClassAdd(resource_class, Resource, creationdate);
- dbuClassAdd(resource_class, Resource, contentlength);
- dbuClassAdd(resource_class, Resource, content);
- dbuClassAdd(resource_class, Resource, bin_content);
- dbuClassAdd(resource_class, Resource, note_item);
- dbuClassAdd(resource_class, Resource, created_by);
+ notes_class = dbuRegisterClass(ctx, "notes", Note, note_id);
+ dbuClassAdd(notes_class, Note, parent_id);
+ dbuClassAdd(notes_class, Note, name);
+ dbuClassAdd(notes_class, Note, title);
+ dbuClassAdd(notes_class, Note, lastmodified);
+ dbuClassAdd(notes_class, Note, creationdate);
+ dbuClassAdd(notes_class, Note, contenttype);
+ dbuClassAdd(notes_class, Note, contentlength);
+ dbuClassAdd(notes_class, Note, content);
+ dbuClassAdd(notes_class, Note, bin_content);
+ dbuClassAdd(notes_class, Note, created_by);
}
\ No newline at end of file
typedef struct UserSettings UserSettings;
typedef struct Repository Repository;
typedef struct Collection Collection;
-typedef struct Resource Resource;
+typedef struct Note Note;
struct UserSettings {
char *host;
CxList *children;
};
-struct Resource {
- int64_t resource_id;
+struct Note {
+ int64_t note_id;
int64_t parent_id;
char *name;
char *title;
char *lastmodified;
char *creationdate;
+ char *contenttype;
uint64_t contentlength;
cxmutstr content;
cxmutstr bin_content;
- bool note_item;
char *created_by;
};
extern DBUClass *usersettings_class;
extern DBUClass *repository_class;
extern DBUClass *collection_class;
-extern DBUClass *resource_class;
+extern DBUClass *notes_class;
void register_types();
#include "window.h"
#include "application.h"
#include "store.h"
+#include "notebook.h"
#include <cx/array_list.h>
+#include <cx/hash_map.h>
void window_create() {
};
*/
ui_sidebar(obj) {
- ui_sourcelist(obj, .dynamic_sublist = wdata->notebooks, .getvalue = window_sidebar_getvalue, .fill = UI_ON);
+ ui_sourcelist(obj, .dynamic_sublist = wdata->notebooks, .getvalue = window_sidebar_getvalue, .onactivate = action_notebook_selected, .fill = UI_ON);
ui_hbox(obj, .spacing = 2, .fill = UI_OFF) {
ui_button(obj, .icon = "folder-new-symbolic", .style_class = "flat");
}
// splitpane left: table
UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING_FREE, "Last Modified", -1);
model->columnsize[0] = -1;
- ui_table(obj, .model = model, .varname = "notes");
+ model->getvalue = window_notelist_getvalue;
+ ui_table(obj, .model = model, .varname = "notes",);
// splitpane right: content
ui_grid(obj, .columnspacing = 10, .rowspacing = 10, .def_vfill = TRUE) {
MainWindow* window_init_data(UiObject *obj) {
MainWindow *wdata = ui_calloc(obj->ctx, 1, sizeof(MainWindow));
obj->window = wdata;
+ wdata->obj = obj;
wdata->notebooks = ui_list_new(obj->ctx, NULL);
update_sublists(obj->ctx, wdata->notebooks);
+ wdata->notebook_cache = cxHashMapCreateSimple(CX_STORE_POINTERS);
+
return wdata;
}
delete_list->collection.destructor_data = ctx;
cxListFree(delete_list);
}
+
+void* window_notelist_getvalue(void *data, int col) {
+ Note *note = data;
+ switch(col) {
+ case 0: {
+ return note->title ? note->title : note->name;
+ }
+ case 1: {
+ return note->lastmodified ? strdup(note->lastmodified) : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+void action_notebook_selected(UiEvent *event, void *userdata) {
+ UiSubListEventData *data = event->eventdata;
+ MainWindow *window = event->window;
+ Collection *collection = data->row_data;
+
+ printf("notebook selected: %s\n", collection->name);
+
+ if(window->current_notebook && window->current_notebook->collection == collection) {
+ return; // notebook already selected
+ }
+
+ CxHashKey key = cx_hash_key(&collection->collection_id, sizeof(collection->collection_id));
+ NotebookModel *notebook = cxMapGet(window->notebook_cache, key);
+ if(!notebook) {
+ printf("notebook not in cache\n");
+ notebook = notebookmodel_create();
+ notebookmodel_set_collection(notebook, collection);
+ notebookmodel_reload(event->obj, notebook);
+ cxMapPut(window->notebook_cache, key, notebook);
+ } else {
+ printf("notebook in cache\n");
+ }
+ notebookmodel_attach(window, notebook);
+}
void update_sublists(UiContext *ctx, UiList *sublists);
+void* window_notelist_getvalue(void *data, int col);
+
+void action_notebook_selected(UiEvent *event, void *userdata);
+
#ifdef __cplusplus
}