From: Olaf Wintermann Date: Sat, 13 Dec 2025 14:43:55 +0000 (+0100) Subject: fix build with newest toolkit version X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fmain;p=note.git fix build with newest toolkit version --- diff --git a/application/attachment.c b/application/attachment.c index c94dfe3..00ad270 100644 --- a/application/attachment.c +++ b/application/attachment.c @@ -168,7 +168,7 @@ void action_attachment_clicked(UiEvent *event, void *userdata) { */ UiObject* attachment_window_create(Note *note, Attachment *selected_attachment) { cxmutstr title = cx_asprintf("%s - attachments", note_get_title(note->resource)); - UiObject *obj = ui_simple_window(title.ptr, NULL); + UiObject *obj = ui_simple_window(title.ptr); free(title.ptr); AttachmentWindow *wdata = attachment_window_create_data(obj, note); diff --git a/application/gtk-text.c b/application/gtk-text.c index dbc0ce7..bd25a2d 100644 --- a/application/gtk-text.c +++ b/application/gtk-text.c @@ -395,7 +395,7 @@ static void editor_attach_image(NoteEditor *editor, GdkPixbuf *pixbuf, char *att ui_list_update(model->attachments); // TODO: can we move this to note_add_attachment or somewhere else? - ui_set_group(wdata->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); + ui_set_state(wdata->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); } static gboolean editor_dnd_add_file(NoteEditor *editor, GFile *file) { diff --git a/application/menu.c b/application/menu.c index f23b4f5..96eb847 100644 --- a/application/menu.c +++ b/application/menu.c @@ -44,7 +44,7 @@ void menu_cleanup() { void toolbar_init() { ui_toolbar_item("GoBack", .icon = UI_ICON_GO_BACK, .onclick = action_go_back); ui_toolbar_item("GoForward", .icon = UI_ICON_GO_FORWARD, .onclick = action_go_forward); - ui_toolbar_item("AddNote", .icon = UI_ICON_ADD, .onclick = action_note_new, .groups = UI_GROUPS(APP_STATE_NOTEBOOK_SELECTED)); + ui_toolbar_item("AddNote", .icon = UI_ICON_ADD, .onclick = action_note_new, .states = UI_GROUPS(APP_STATE_NOTEBOOK_SELECTED)); ui_toolbar_item("GoBack2", .icon = UI_ICON_GO_BACK, .onclick = action_go_back, .visibility_states = UI_GROUPS(APP_STATE_HIDE_NOTELIST)); ui_toolbar_item("GoForward2", .icon = UI_ICON_GO_FORWARD, .onclick = action_go_forward, .visibility_states = UI_GROUPS(APP_STATE_HIDE_NOTELIST)); diff --git a/application/nbconfig.c b/application/nbconfig.c index 5486ac7..c61a108 100644 --- a/application/nbconfig.c +++ b/application/nbconfig.c @@ -398,7 +398,7 @@ void notebook_config_dialog(void) { NoteStore *store = note_store_get(); // TODO: check store->root and show different dialog, when root is missing - UiObject *obj = ui_simple_window("Notebooks", NULL); + UiObject *obj = ui_simple_window("Notebooks"); const CxAllocator *a = ui_allocator(obj->ctx); NotebookConfigDialog *wdata = ui_malloc(obj->ctx, sizeof(NotebookConfigDialog)); @@ -475,11 +475,11 @@ void notebook_config_dialog(void) { ui_newline(obj); ui_rlabel(obj, .label = "Default Repository"); - ui_combobox(obj, .list = wdata->tab1_repositories); + ui_dropdown(obj, .list = wdata->tab1_repositories); ui_newline(obj); ui_rlabel(obj, .label = "Default Notebook Type"); - ui_combobox(obj, .list = wdata->tab1_types); + ui_dropdown(obj, .list = wdata->tab1_types); ui_newline(obj); } } @@ -498,7 +498,7 @@ void notebook_config_dialog(void) { ui_grid(obj, .columnspacing = 10, .rowspacing = 10, .fill = TRUE, .def_vfill = TRUE) { ui_rlabel(obj, .label = "Group"); - ui_combobox(obj, .list = wdata->tab2_groups, .getvalue = reslist_getvalue); + ui_dropdown(obj, .list = wdata->tab2_groups, .getvalue = reslist_getvalue); ui_newline(obj); ui_rlabel(obj, .label = "Name"); @@ -506,11 +506,11 @@ void notebook_config_dialog(void) { ui_newline(obj); ui_rlabel(obj, .label = "Repository"); - ui_combobox(obj, .list = wdata->tab1_repositories); + ui_dropdown(obj, .list = wdata->tab1_repositories); ui_newline(obj); ui_rlabel(obj, .label = "Type"); - ui_combobox(obj, .list = wdata->tab2_types); + ui_dropdown(obj, .list = wdata->tab2_types); ui_newline(obj); } } @@ -543,7 +543,7 @@ void notebook_config_dialog(void) { ui_newline(obj); ui_rlabel(obj, .label = "Encryption Key"); - ui_combobox(obj, .list = wdata->tab3_repo_encryption_key); + ui_dropdown(obj, .list = wdata->tab3_repo_encryption_key); } } diff --git a/application/note.c b/application/note.c index de5214f..372bc4d 100644 --- a/application/note.c +++ b/application/note.c @@ -122,7 +122,7 @@ static void note_loading_completed(UiObject *obj, LoadNoteContent *op) { ui_list_update(note->model->attachments); if(ui_list_count(note->model->attachments) > 0) { - ui_set_group(obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); + ui_set_state(obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); } editor_load_markdown(note, wdata->textview, note->resource->content); diff --git a/application/notebook.c b/application/notebook.c index 2b08f42..79fdca6 100644 --- a/application/notebook.c +++ b/application/notebook.c @@ -146,16 +146,16 @@ void notebookmodel_attach_note(NotebookModel *model, Note *note) { ui_attach_document(model->ctx, note->model); // TODO: this is only a workaround and should be removed when // sub-document groups are supported - ui_set_group(model->window->obj->ctx, APP_STATE_NOTE_SELECTED); + ui_set_state(model->window->obj->ctx, APP_STATE_NOTE_SELECTED); model->current_note = note; ui_list_update(note->model->attachments); // TODO: replace when getting groups from sub-ctx works if(ui_list_count(note->model->attachments) == 0) { - ui_unset_group(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); + ui_unset_state(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); } else { - ui_set_group(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); + ui_set_state(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); } if(!note->resource->content_loaded) { @@ -180,7 +180,7 @@ void notebookmodel_detach_current_note(NotebookModel *model) { } model->current_note = NULL; } - ui_unset_group(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); + ui_unset_state(model->window->obj->ctx, APP_STATE_NOTE_HAS_ATTACHMENTS); } /* diff --git a/application/window.c b/application/window.c index cba82f8..84d272e 100644 --- a/application/window.c +++ b/application/window.c @@ -85,11 +85,11 @@ void window_create() { ui_vbox(obj, .fill = TRUE) { ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .def_vfill = TRUE) { //ui_label(obj, .label = "Title", .vfill = TRUE); - ui_textfield(obj, .varname = "note_title", .onchange = action_note_title_changed, .hexpand = TRUE, .hfill = TRUE, .groups = UI_GROUPS(APP_STATE_NOTE_SELECTED)); + ui_textfield(obj, .varname = "note_title", .onchange = action_note_title_changed, .hexpand = TRUE, .hfill = TRUE, .states = UI_GROUPS(APP_STATE_NOTE_SELECTED)); ui_newline(obj); } ui_hbox(obj, .style_class = "note_toolbar", .margin = 10, .spacing = 4) { - ui_combobox(obj, .varname = "note_textnote_para", .onactivate = action_textnote_paragraph); + ui_dropdown(obj, .varname = "note_textnote_para", .onactivate = action_textnote_paragraph); ui_togglebutton(obj, .icon = "format-text-bold", .varname = "note_textnote_strong", .onchange = action_textnote_style_strong); ui_togglebutton(obj, .icon = "format-text-italic", .varname = "note_textnote_emphasis", .onchange = action_textnote_style_emphasis); ui_togglebutton(obj, .icon = "format-text-underline", .varname = "note_textnote_underline", .onchange = action_textnote_style_underline); @@ -112,8 +112,8 @@ void window_create() { ui_customwidget(obj, editor_gtk4_workaround, wdata, .hfill = TRUE); #endif ui_set_visible(wdata->attachments, FALSE); - ui_widget_set_groups(obj->ctx, wdata->attachments, (ui_enablefunc)ui_set_visible, APP_STATE_NOTE_HAS_ATTACHMENTS, -1); - wdata->textview = ui_textarea(obj, .varname = "note_text", .vfill = TRUE, .hfill = TRUE, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2, .groups = UI_GROUPS(APP_STATE_NOTE_SELECTED), .fill = UI_ON); + ui_widget_set_states(obj->ctx, wdata->attachments, (ui_enablefunc)ui_set_visible, APP_STATE_NOTE_HAS_ATTACHMENTS, -1); + wdata->textview = ui_textarea(obj, .varname = "note_text", .vfill = TRUE, .hfill = TRUE, .hexpand = TRUE, .vexpand = TRUE, .colspan = 2, .states = UI_GROUPS(APP_STATE_NOTE_SELECTED), .fill = UI_ON); editor_init_textview(obj, ui_textarea_gettextwidget(wdata->textview)); } } @@ -143,9 +143,9 @@ void window_notelist_setvisible(MainWindow *window, UiBool visible) { ui_splitview_window_set_visible(window->obj, 0, visible); window->notelist_isvisible = visible; if(visible) { - ui_unset_group(window->obj->ctx, APP_STATE_HIDE_NOTELIST); + ui_unset_state(window->obj->ctx, APP_STATE_HIDE_NOTELIST); } else { - ui_set_group(window->obj->ctx, APP_STATE_HIDE_NOTELIST); + ui_set_state(window->obj->ctx, APP_STATE_HIDE_NOTELIST); } } @@ -350,7 +350,7 @@ void action_notebook_add(UiEvent *event, void *userdata) { // Dialog UI ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .def_vfill = TRUE, .fill = TRUE) { ui_rlabel(obj, .label = "Group"); - ui_combobox(obj, .list = wdata->groups, .getvalue = nnd_group_value, .hfill = TRUE, .hexpand = TRUE); + ui_dropdown(obj, .list = wdata->groups, .getvalue = nnd_group_value, .hfill = TRUE, .hexpand = TRUE); ui_newline(obj); ui_newline(obj); @@ -385,7 +385,7 @@ void action_notebook_selected(UiEvent *event, void *userdata) { Resource *collection = data->row_data; printf("notebook selected: %s\n", collection->nodename); - ui_set_group(event->obj->ctx, APP_STATE_NOTEBOOK_SELECTED); + ui_set_state(event->obj->ctx, APP_STATE_NOTEBOOK_SELECTED); if(window->current_notebook && window->current_notebook->collection == collection) { return; // notebook already selected diff --git a/ucx/allocator.c b/ucx/allocator.c index 323b14b..bb01129 100644 --- a/ucx/allocator.c +++ b/ucx/allocator.c @@ -31,6 +31,35 @@ #include #include +#ifdef _WIN32 +#include +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + ps = (unsigned long) sysinfo.dwPageSize; + } + return ps; +} +#else +#include +unsigned long cx_system_page_size(void) { + static unsigned long ps = 0; + if (ps == 0) { + long sc = sysconf(_SC_PAGESIZE); + if (sc < 0) { + // fallback for systems which do not report a value here + ps = 4096; // LCOV_EXCL_LINE + } else { + ps = (unsigned long) sc; + } + } + return ps; +} +#endif + static void *cx_malloc_stdlib( cx_attr_unused void *d, size_t n @@ -79,6 +108,11 @@ int cx_reallocate_( void **mem, size_t n ) { + if (n == 0) { + free(*mem); + *mem = NULL; + return 0; + } void *nmem = realloc(*mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -93,6 +127,11 @@ int cx_reallocatearray_( size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + free(*mem); + *mem = NULL; + return 0; + } size_t n; if (cx_szmul(nmemb, size, &n)) { errno = EOVERFLOW; @@ -156,6 +195,11 @@ int cxReallocate_( void **mem, size_t n ) { + if (n == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -171,6 +215,11 @@ int cxReallocateArray_( size_t nmemb, size_t size ) { + if (nmemb == 0 || size == 0) { + cxFree(allocator, *mem); + *mem = NULL; + return 0; + } void *nmem = cxReallocArray(allocator, *mem, nmemb, size); if (nmem == NULL) { return 1; // LCOV_EXCL_LINE @@ -194,3 +243,7 @@ void cxFree( ) { allocator->cl->free(allocator->data, mem); } + +void cxFreeDefault(void *mem) { + cxDefaultAllocator->cl->free(cxDefaultAllocator->data, mem); +} diff --git a/ucx/buffer.c b/ucx/buffer.c index a652abc..a727ee4 100644 --- a/ucx/buffer.c +++ b/ucx/buffer.c @@ -32,35 +32,6 @@ #include #include -#ifdef _WIN32 -#include -#include -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - ps = sysinfo.dwPageSize; - } - return ps; -} -#else -#include -static unsigned long system_page_size(void) { - static unsigned long ps = 0; - if (ps == 0) { - long sc = sysconf(_SC_PAGESIZE); - if (sc < 0) { - // fallback for systems which do not report a value here - ps = 4096; // LCOV_EXCL_LINE - } else { - ps = (unsigned long) sc; - } - } - return ps; -} -#endif - static int buffer_copy_on_write(CxBuffer* buffer) { if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0; void *newspace = cxMalloc(buffer->allocator, buffer->capacity); @@ -95,29 +66,18 @@ int cxBufferInit( buffer->bytes = space; } buffer->capacity = capacity; + buffer->max_capacity = SIZE_MAX; buffer->size = 0; buffer->pos = 0; - buffer->flush = NULL; - - return 0; -} - -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -) { - buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig)); - if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE - memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig)); return 0; } void cxBufferDestroy(CxBuffer *buffer) { - if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { + if ((buffer->flags & (CX_BUFFER_FREE_CONTENTS | CX_BUFFER_DO_NOT_FREE)) + == CX_BUFFER_FREE_CONTENTS) { cxFree(buffer->allocator, buffer->bytes); } - cxFreeDefault(buffer->flush); memset(buffer, 0, sizeof(CxBuffer)); } @@ -239,9 +199,12 @@ bool cxBufferEof(const CxBuffer *buffer) { } int cxBufferReserve(CxBuffer *buffer, size_t newcap) { - if (newcap <= buffer->capacity) { + if (newcap == buffer->capacity) { return 0; } + if (newcap > buffer->max_capacity) { + return -1; + } const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; if (buffer->flags & force_copy_flags) { void *newspace = cxMalloc(buffer->allocator, newcap); @@ -254,42 +217,57 @@ int cxBufferReserve(CxBuffer *buffer, size_t newcap) { return 0; } else if (cxReallocate(buffer->allocator, (void **) &buffer->bytes, newcap) == 0) { + buffer->flags |= CX_BUFFER_FREE_CONTENTS; buffer->capacity = newcap; + if (buffer->size > newcap) { + buffer->size = newcap; + } return 0; } else { return -1; // LCOV_EXCL_LINE } } -static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { - unsigned long pagesize = system_page_size(); - // if page size is larger than 64 KB - for some reason - truncate to 64 KB - if (pagesize > 65536) pagesize = 65536; - if (mincap < pagesize) { - // when smaller as one page, map to the next power of two - mincap--; - mincap |= mincap >> 1; - mincap |= mincap >> 2; - mincap |= mincap >> 4; - // last operation only needed for pages larger 4096 bytes - // but if/else would be more expensive than just doing this - mincap |= mincap >> 8; - mincap++; - } else { - // otherwise, map to a multiple of the page size - mincap -= mincap % pagesize; - mincap += pagesize; - // note: if newcap is already page aligned, - // this gives a full additional page (which is good) +int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) { + if (capacity < buffer->capacity) { + return -1; } - return mincap; + buffer->max_capacity = capacity; + return 0; } int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { if (newcap <= buffer->capacity) { return 0; } - newcap = cx_buffer_calculate_minimum_capacity(newcap); + if (newcap > buffer->max_capacity) { + return -1; + } + if (newcap < buffer->max_capacity) { + unsigned long pagesize = cx_system_page_size(); + // if page size is larger than 64 KB - for some reason - truncate to 64 KB + if (pagesize > 65536) pagesize = 65536; + if (newcap < pagesize) { + // when smaller as one page, map to the next power of two + newcap--; + newcap |= newcap >> 1; + newcap |= newcap >> 2; + newcap |= newcap >> 4; + // last operation only needed for pages larger 4096 bytes + // but if/else would be more expensive than just doing this + newcap |= newcap >> 8; + newcap++; + } else { + // otherwise, map to a multiple of the page size + newcap -= newcap % pagesize; + newcap += pagesize; + // note: if newcap is already page aligned, + // this gives a full additional page (which is good) + } + if (newcap > buffer->max_capacity) { + newcap = buffer->max_capacity; + } + } return cxBufferReserve(buffer, newcap); } @@ -315,60 +293,15 @@ void cxBufferShrink( } } -static size_t cx_buffer_flush_helper( - const CxBuffer *buffer, - const unsigned char *src, - size_t size, - size_t nitems -) { - // flush data from an arbitrary source - // does not need to be the buffer's contents - size_t max_items = buffer->flush->blksize / size; - size_t fblocks = 0; - size_t flushed_total = 0; - while (nitems > 0 && fblocks < buffer->flush->blkmax) { - fblocks++; - size_t items = nitems > max_items ? max_items : nitems; - size_t flushed = buffer->flush->wfunc( - src, size, items, buffer->flush->target); - if (flushed > 0) { - flushed_total += flushed; - src += flushed * size; - nitems -= flushed; - } else { - // if no bytes can be flushed out anymore, we give up - break; - } - } - return flushed_total; -} - -static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) { - // flush the current contents of the buffer - unsigned char *space = buffer->bytes; - size_t remaining = buffer->pos / size; - size_t flushed_total = cx_buffer_flush_helper( - buffer, space, size, remaining); - - // shift the buffer left after flushing - // IMPORTANT: up to this point, copy on write must have been - // performed already, because we can't do error handling here - cxBufferShiftLeft(buffer, flushed_total*size); - - return flushed_total; -} - -size_t cxBufferFlush(CxBuffer *buffer) { - if (buffer_copy_on_write(buffer)) return 0; - return cx_buffer_flush_impl(buffer, 1); -} - size_t cxBufferWrite( const void *ptr, size_t size, size_t nitems, CxBuffer *buffer ) { + // trivial case + if (size == 0 || nitems == 0) return 0; + // optimize for easy case if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) { if (buffer_copy_on_write(buffer)) return 0; @@ -380,107 +313,52 @@ size_t cxBufferWrite( return nitems; } - size_t len, total_flushed = 0; -cx_buffer_write_retry: + size_t len; if (cx_szmul(size, nitems, &len)) { errno = EOVERFLOW; - return total_flushed; + return 0; } if (buffer->pos > SIZE_MAX - len) { errno = EOVERFLOW; - return total_flushed; + return 0; } + const size_t required = buffer->pos + len; - size_t required = buffer->pos + len; - bool perform_flush = false; + // check if we need to auto-extend if (required > buffer->capacity) { if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - if (buffer->flush != NULL) { - size_t newcap = cx_buffer_calculate_minimum_capacity(required); - if (newcap > buffer->flush->threshold) { - newcap = buffer->flush->threshold; - } - if (cxBufferReserve(buffer, newcap)) { - return total_flushed; // LCOV_EXCL_LINE - } - if (required > newcap) { - perform_flush = true; - } - } else { - if (cxBufferMinimumCapacity(buffer, required)) { - return total_flushed; // LCOV_EXCL_LINE - } - } - } else { - if (buffer->flush != NULL) { - perform_flush = true; - } else { - // truncate data, if we can neither extend nor flush - len = buffer->capacity - buffer->pos; - if (size > 1) { - len -= len % size; - } - nitems = len / size; + size_t newcap = required < buffer->max_capacity + ? required : buffer->max_capacity; + if (cxBufferMinimumCapacity(buffer, newcap)) { + return 0; // LCOV_EXCL_LINE } } } + // check again and truncate data if capacity is still not enough + if (required > buffer->capacity) { + len = buffer->capacity - buffer->pos; + if (size > 1) { + len -= len % size; + } + nitems = len / size; + } + // check here and not above because of possible truncation if (len == 0) { - return total_flushed; + return 0; } // check if we need to copy if (buffer_copy_on_write(buffer)) return 0; // perform the operation - if (perform_flush) { - size_t items_flushed; - if (buffer->pos == 0) { - // if we don't have data in the buffer, but are instructed - // to flush, it means that we are supposed to relay the data - items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); - if (items_flushed == 0) { - // we needed to relay data, but could not flush anything - // i.e. we have to give up to avoid endless trying - return 0; - } - nitems -= items_flushed; - total_flushed += items_flushed; - if (nitems > 0) { - ptr = ((unsigned char*)ptr) + items_flushed * size; - goto cx_buffer_write_retry; - } - return total_flushed; - } else { - items_flushed = cx_buffer_flush_impl(buffer, size); - if (items_flushed == 0) { - // flush target is full, let's try to truncate - size_t remaining_space; - if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { - remaining_space = buffer->flush->threshold > buffer->pos - ? buffer->flush->threshold - buffer->pos - : 0; - } else { - remaining_space = buffer->capacity > buffer->pos - ? buffer->capacity - buffer->pos - : 0; - } - nitems = remaining_space / size; - if (nitems == 0) { - return total_flushed; - } - } - goto cx_buffer_write_retry; - } - } else { - memcpy(buffer->bytes + buffer->pos, ptr, len); - buffer->pos += len; - if (buffer->pos > buffer->size) { - buffer->size = buffer->pos; - } - return total_flushed + nitems; + memcpy(buffer->bytes + buffer->pos, ptr, len); + buffer->pos += len; + if (buffer->pos > buffer->size) { + buffer->size = buffer->pos; } + return nitems; } size_t cxBufferAppend( @@ -489,20 +367,13 @@ size_t cxBufferAppend( size_t nitems, CxBuffer *buffer ) { - size_t pos = buffer->pos; - size_t append_pos = buffer->size; - buffer->pos = append_pos; - size_t written = cxBufferWrite(ptr, size, nitems, buffer); - // the buffer might have been flushed - // we must compute a possible delta for the position - // expected: pos = append_pos + written - // -> if this is not the case, there is a delta - size_t delta = append_pos + written*size - buffer->pos; - if (delta > pos) { - buffer->pos = 0; - } else { - buffer->pos = pos - delta; - } + // trivial case + if (size == 0 || nitems == 0) return 0; + + const size_t pos = buffer->pos; + buffer->pos = buffer->size; + const size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; return written; } @@ -520,19 +391,35 @@ int cxBufferPut( } int cxBufferTerminate(CxBuffer *buffer) { - if (0 == cxBufferPut(buffer, 0)) { - buffer->size = buffer->pos - 1; - return 0; + // try to extend / shrink the buffer + if (buffer->pos >= buffer->capacity) { + if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == 0) { + return -1; + } + if (cxBufferReserve(buffer, buffer->pos + 1)) { + return -1; // LCOV_EXCL_LINE + } } else { - return -1; + buffer->size = buffer->pos; + cxBufferShrink(buffer, 1); + // set the capacity explicitly, in case shrink was skipped due to CoW + buffer->capacity = buffer->size + 1; } + + // check if we are still on read-only memory + if (buffer_copy_on_write(buffer)) return -1; + + // write the terminator and exit + buffer->space[buffer->pos] = '\0'; + return 0; } -size_t cxBufferPutString( - CxBuffer *buffer, - const char *str -) { - return cxBufferWrite(str, 1, strlen(str), buffer); +size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str) { + return cxBufferWrite(str.ptr, 1, str.length, buffer); +} + +size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str) { + return cxBufferAppend(str.ptr, 1, str.length, buffer); } size_t cxBufferRead( diff --git a/ucx/cx/allocator.h b/ucx/cx/allocator.h index a69ef07..11750f3 100644 --- a/ucx/cx/allocator.h +++ b/ucx/cx/allocator.h @@ -145,6 +145,17 @@ typedef void (*cx_destructor_func2)(void *data, void *memory); typedef void*(cx_clone_func)(void *target, const void *source, const CxAllocator *allocator, void *data); +/** + * Returns the system's memory page size. + * + * If the page size cannot be retrieved from the system, + * a default of 4096 bytes is assumed. + * + * @return the system's memory page size in bytes + */ +cx_attr_nodiscard +CX_EXPORT unsigned long cx_system_page_size(void); + /** * Reallocate a previously allocated block and changes the pointer in-place, * if necessary. @@ -428,9 +439,9 @@ CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n); */ #define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__) /** - * Convenience macro that invokes cxFree() with the cxDefaultAllocator. + * Convenience function that invokes cxFree() with the cxDefaultAllocator. */ -#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__) +CX_EXPORT void cxFreeDefault(void *mem); #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/buffer.h b/ucx/cx/buffer.h index 70b5667..4efa52c 100644 --- a/ucx/cx/buffer.h +++ b/ucx/cx/buffer.h @@ -48,6 +48,7 @@ #include "common.h" #include "allocator.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -88,6 +89,13 @@ extern "C" { */ #define CX_BUFFER_COPY_ON_EXTEND 0x08 +/** + * If this flag is enabled, the buffer will never free its contents regardless of #CX_BUFFER_FREE_CONTENTS. + * + * This is useful, for example, when you want to keep a pointer to the data after destroying the buffer. + */ +#define CX_BUFFER_DO_NOT_FREE 0x10 + /** * Function pointer for cxBufferWrite that is compatible with cx_write_func. * @see cx_write_func @@ -99,59 +107,6 @@ extern "C" { */ #define cxBufferReadFunc ((cx_read_func) cxBufferRead) -/** - * Configuration for automatic flushing. - */ -struct cx_buffer_flush_config_s { - /** - * The buffer may not extend beyond this threshold before starting to flush. - * - * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND. - * The threshold will be the maximum capacity the buffer is extended to - * before flushing. - */ - size_t threshold; - /** - * The block size for the elements to flush. - */ - size_t blksize; - /** - * The maximum number of blocks to flush in one cycle. - * - * @attention While it is guaranteed that cxBufferFlush() will not flush - * more blocks, this is not necessarily the case for cxBufferWrite(). - * After performing a flush cycle, cxBufferWrite() will retry the write - * operation and potentially trigger another flush cycle, until the - * flush target accepts no more data. - */ - size_t blkmax; - - /** - * The target for the write function. - */ - void *target; - - /** - * The write-function used for flushing. - * If NULL, the flushed content gets discarded. - */ - cx_write_func wfunc; -}; - -/** - * Type alias for the flush configuration struct. - * - * @code - * struct cx_buffer_flush_config_s { - * size_t threshold; - * size_t blksize; - * size_t blkmax; - * void *target; - * cx_write_func wfunc; - * }; - * @endcode - */ -typedef struct cx_buffer_flush_config_s CxBufferFlushConfig; /** Structure for the UCX buffer data. */ struct cx_buffer_s { @@ -168,16 +123,12 @@ struct cx_buffer_s { }; /** The allocator to use for automatic memory management. */ const CxAllocator *allocator; - /** - * Optional flush configuration - * - * @see cxBufferEnableFlushing() - */ - CxBufferFlushConfig *flush; /** Current position of the buffer. */ size_t pos; /** Current capacity (i.e. maximum size) of the buffer. */ size_t capacity; + /** Maximum capacity that this buffer may grow to. */ + size_t max_capacity; /** Current size of the buffer content. */ size_t size; /** @@ -230,23 +181,6 @@ cx_attr_nonnull_arg(1) CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity, const CxAllocator *allocator, int flags); -/** - * Configures the buffer for flushing. - * - * Flushing can happen automatically when data is written - * to the buffer (see cxBufferWrite()) or manually when - * cxBufferFlush() is called. - * - * @param buffer the buffer - * @param config the flush configuration - * @retval zero success - * @retval non-zero failure - * @see cxBufferFlush() - * @see cxBufferWrite() - */ -cx_attr_nonnull -CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); - /** * Destroys the buffer contents. * @@ -443,6 +377,8 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); * Ensures that the buffer has the required capacity. * * If the current capacity is not sufficient, the buffer will be extended. + * If the current capacity is larger, the buffer is shrunk and superfluous + * content is discarded. * * This function will reserve no more bytes than requested, in contrast to * cxBufferMinimumCapacity(), which may reserve more bytes to improve the @@ -450,7 +386,7 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); * * @param buffer the buffer * @param capacity the required capacity for this buffer - * @retval zero the capacity was already sufficient or successfully increased + * @retval zero on success * @retval non-zero on allocation failure * @see cxBufferShrink() * @see cxBufferMinimumCapacity() @@ -458,6 +394,25 @@ CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); cx_attr_nonnull CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); +/** + * Limits the buffer's capacity. + * + * If the current capacity is already larger, this function fails and returns + * non-zero. + * + * The capacity limit will affect auto-extension features, as well as future + * calls to cxBufferMinimumCapacity() and cxBufferReserve(). + * + * @param buffer the buffer + * @param capacity the maximum allowed capacity for this buffer + * @retval zero the limit is applied + * @retval non-zero the new limit is smaller than the current capacity + * @see cxBufferReserve() + * @see cxBufferMinimumCapacity() + */ +cx_attr_nonnull +CX_EXPORT int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity); + /** * Ensures that the buffer has a minimum capacity. * @@ -471,6 +426,7 @@ CX_EXPORT int cxBufferReserve(CxBuffer *buffer, size_t capacity); * @param capacity the minimum required capacity for this buffer * @retval zero the capacity was already sufficient or successfully increased * @retval non-zero on allocation failure + * @see cxBufferMaximumCapacity() * @see cxBufferReserve() * @see cxBufferShrink() */ @@ -500,33 +456,13 @@ CX_EXPORT void cxBufferShrink(CxBuffer *buffer, size_t reserve); /** * Writes data to a CxBuffer. * - * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position, and the position of the buffer is increased - * by the number of bytes written. - * - * If flushing is enabled and the buffer needs to flush, the data is flushed to - * the target until the target signals that it cannot take more data by - * returning zero via the respective write function. In that case, the remaining - * data in this buffer is shifted to the beginning of this buffer so that the - * newly available space can be used to append as much data as possible. - * - * This function only stops writing more elements when the flush target and this - * buffer are both incapable of taking more data or all data has been written. + * If auto-extension is enabled, the buffer's capacity is automatically + * increased when it is not large enough to hold all data. + * By default, the capacity grows indefinitely, unless limited with + * cxBufferMaximumCapacity(). + * When auto-extension fails, this function writes no data and returns zero. * - * If, after flushing, the number of items that shall be written still exceeds - * the capacity or flush threshold, this function tries to write all items directly - * to the flush target, if possible. - * - * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer. - * That means it does @em not include the number of items that were already in - * the buffer and were also flushed during the process. - * - * @attention - * When @p size is larger than one and the contents of the buffer are not aligned - * with @p size, flushing stops after all complete items have been flushed, leaving - * the misaligned part in the buffer. - * Afterward, this function only writes as many items as possible to the buffer. + * The position of the buffer is moved alongside the written data. * * @note The signature is compatible with the fwrite() family of functions. * @@ -565,62 +501,6 @@ cx_attr_nonnull CX_EXPORT size_t cxBufferAppend(const void *ptr, size_t size, size_t nitems, CxBuffer *buffer); -/** - * Performs a single flush-run on the specified buffer. - * - * Does nothing when the position in the buffer is zero. - * Otherwise, the data until the current position minus - * one is considered for flushing. - * Note carefully that flushing will never exceed the - * current @em position, even when the size of the - * buffer is larger than the current position. - * - * One flush run will try to flush @c blkmax many - * blocks of size @c blksize until either the @p buffer - * has no more data to flush or the write function - * used for flushing returns zero. - * - * The buffer is shifted left for that many bytes - * the flush operation has successfully flushed. - * - * @par Example 1 - * Assume you have a buffer with size 340 and you are - * at position 200. The flush configuration is - * @c blkmax=4 and @c blksize=64 . - * Assume that the entire flush operation is successful. - * All 200 bytes on the left-hand-side from the current - * position are written. - * That means the size of the buffer is now 140 and the - * position is zero. - * - * @par Example 2 - * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276, and the position is 136. - * - * @par Example 3 - * Same as Example 1, but now assume the flush target - * only accepts 100 bytes before returning zero. - * That means the flush operation manages to flush - * one complete block and one partial block, ending - * up with a buffer with size 240 and position 100. - * - * @remark Just returns zero when flushing was not enabled with - * cxBufferEnableFlushing(). - * - * @remark When the buffer uses copy-on-write, the memory - * is copied first, before attempting any flush. - * This is, however, considered an erroneous use of the - * buffer because it makes little sense to put - * readonly data into an UCX buffer for flushing instead - * of writing it directly to the target. - * - * @param buffer the buffer - * @return the number of successfully flushed bytes - * @see cxBufferEnableFlushing() - */ -cx_attr_nonnull -CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); - /** * Reads data from a CxBuffer. * @@ -645,8 +525,9 @@ CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, * * The least significant byte of the argument is written to the buffer. If the * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled, - * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature - * is disabled or the buffer extension fails, @c EOF is returned. + * the buffer capacity is extended, unless a limit set by + * cxBufferMaximumCapacity() is reached. + * If the feature is disabled or the buffer extension fails, @c EOF is returned. * * On successful writing, the position of the buffer is increased. * @@ -655,8 +536,8 @@ CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, * * @param buffer the buffer to write to * @param c the character to write - * @return the byte that has been written or @c EOF when the end of the stream is - * reached, and automatic extension is not enabled or not possible + * @return the byte that has been written or @c EOF when the end of the + * stream is reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull @@ -665,28 +546,61 @@ CX_EXPORT int cxBufferPut(CxBuffer *buffer, int c); /** * Writes a terminating zero to a buffer at the current position. * - * If successful, sets the size to the current position and advances the position by one. + * If successful, also sets the size to the current position and shrinks the buffer. * * The purpose of this function is to have the written data ready to be used as * a C string with the buffer's size being the length of that string. * * @param buffer the buffer to write to * @return zero, if the terminator could be written, non-zero otherwise + * @see cxBufferShrink() */ cx_attr_nonnull CX_EXPORT int cxBufferTerminate(CxBuffer *buffer); /** - * Writes a string to a buffer. + * Internal function - do not use. * - * This is a convenience function for cxBufferWrite(str, 1, strlen(str), buffer). + * @param buffer the buffer + * @param str the string + * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_put_string(CxBuffer *buffer, cxstring str); + +/** + * Writes a string to a buffer with cxBufferWrite(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferWrite() + * @see cx_strcast() + */ +#define cxBufferPutString(buffer, str) cx_buffer_put_string(buffer, cx_strcast(str)) + +/** + * Internal function - do not use. * * @param buffer the buffer - * @param str the zero-terminated string + * @param str the string * @return the number of bytes written + * @see cxBufferPutString() + */ +cx_attr_nonnull +CX_EXPORT size_t cx_buffer_append_string(CxBuffer *buffer, cxstring str); + +/** + * Appends a string to a buffer with cxBufferAppend(). + * + * @param buffer (@c CxBuffer*) the buffer + * @param str (any string) the zero-terminated string + * @return (@c size_t) the number of bytes written + * @see cxBufferAppend() + * @see cx_strcast() */ -cx_attr_nonnull cx_attr_cstr_arg(2) -CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); +#define cxBufferAppendString(buffer, str) cx_buffer_append_string(buffer, cx_strcast(str)) /** * Gets a character from a buffer. diff --git a/ucx/cx/common.h b/ucx/cx/common.h index 9217585..953edb5 100644 --- a/ucx/cx/common.h +++ b/ucx/cx/common.h @@ -80,10 +80,10 @@ #define UCX_COMMON_H /** Major UCX version as integer constant. */ -#define UCX_VERSION_MAJOR 3 +#define UCX_VERSION_MAJOR 4 /** Minor UCX version as integer constant. */ -#define UCX_VERSION_MINOR 1 +#define UCX_VERSION_MINOR 0 /** Version constant which ensures to increase monotonically. */ #define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR) diff --git a/ucx/cx/json.h b/ucx/cx/json.h index ab5af4f..c70a7f7 100644 --- a/ucx/cx/json.h +++ b/ucx/cx/json.h @@ -41,6 +41,7 @@ #include "string.h" #include "buffer.h" #include "array_list.h" +#include "map.h" #include @@ -188,9 +189,10 @@ typedef struct cx_json_value_s CxJsonValue; */ typedef struct cx_json_array_s CxJsonArray; /** - * Type alias for the JSON object struct. + * Type alias for the map representing a JSON object. + * The map contains pointers of type @c CxJsonValue. */ -typedef struct cx_json_object_s CxJsonObject; +typedef CxMap* CxJsonObject; /** * Type alias for a JSON string. */ @@ -208,11 +210,6 @@ typedef double CxJsonNumber; */ typedef enum cx_json_literal CxJsonLiteral; -/** - * Type alias for a key/value pair in a JSON object. - */ -typedef struct cx_json_obj_value_s CxJsonObjValue; - /** * JSON array structure. */ @@ -220,35 +217,7 @@ struct cx_json_array_s { /** * The array data. */ - CX_ARRAY_DECLARE(CxJsonValue*, array); -}; - -/** - * JSON object structure. - */ -struct cx_json_object_s { - /** - * The key/value entries. - */ - CX_ARRAY_DECLARE(CxJsonObjValue, values); - /** - * The original indices to reconstruct the order in which the members were added. - */ - size_t *indices; -}; - -/** - * Structure for a key/value entry in a JSON object. - */ -struct cx_json_obj_value_s { - /** - * The key (or name in JSON terminology) of the value. - */ - cxmutstr name; - /** - * The value. - */ - CxJsonValue *value; + CX_ARRAY_DECLARE(CxJsonValue*, data); }; /** @@ -295,7 +264,7 @@ struct cx_json_value_s { * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; - } value; + }; }; /** @@ -349,11 +318,11 @@ struct cx_json_s { CxJsonValue *parsed; /** - * A pointer to an intermediate state of a currently parsed object member. + * The name of a not yet completely parsed object member. * * Never access this value manually. */ - CxJsonObjValue uncompleted_member; + cxmutstr uncompleted_member_name; /** * State stack. @@ -438,10 +407,6 @@ struct cx_json_writer_s { * Set true to enable pretty output. */ bool pretty; - /** - * Set false to output the members in the order in which they were added. - */ - bool sort_members; /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. @@ -508,6 +473,33 @@ cx_attr_nonnull_arg(1, 2, 3) CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value, cx_write_func wfunc, const CxJsonWriter* settings); + +/** + * Produces a compact string representation of the specified JSON value. + * + * @param value the JSON value + * @param allocator the allocator for the string + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterCompact() + * @see cxJsonToPrettyString() + */ +cx_attr_nonnull_arg(1) +CX_EXPORT cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator); + +/** + * Produces a pretty string representation of the specified JSON value. + * + * @param value the JSON value + * @param allocator the allocator for the string + * @return the produced string + * @see cxJsonWrite() + * @see cxJsonWriterPretty() + * @see cxJsonToString() + */ +cx_attr_nonnull_arg(1) +CX_EXPORT cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator); + /** * Initializes the JSON interface. * @@ -530,8 +522,8 @@ CX_EXPORT void cxJsonDestroy(CxJson *json); /** * Destroys and re-initializes the JSON interface. * - * You might want to use this to reset the parser after - * encountering a syntax error. + * You must use this to reset the parser after encountering a syntax error + * if you want to continue using it. * * @param json the JSON interface */ @@ -592,6 +584,36 @@ CX_INLINE int cx_json_fill(CxJson *json, cxstring str) { */ #define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) + +/** + * Internal function - use cxJsonFromString() instead. + * + * @param allocator the allocator for the JSON value + * @param str the string to parse + * @param value a pointer where the JSON value shall be stored to + * @return status code + */ +cx_attr_nonnull_arg(3) +CX_EXPORT CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value); + +/** + * Parses a string into a JSON value. + * + * @param allocator (@c CxAllocator*) the allocator for the JSON value + * @param str (any string) the string to parse + * @param value (@c CxJsonValue**) a pointer where the JSON value shall be stored to + * @retval CX_JSON_NO_ERROR success + * @retval CX_JSON_NO_DATA the string was empty or blank + * @retval CX_JSON_INCOMPLETE_DATA the string unexpectedly ended + * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed + * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for the CxJsonValue failed + * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number + * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error + */ +#define cxJsonFromString(allocator, str, value) \ + cx_json_from_string(allocator, cx_strcast(str), value) + /** * Creates a new (empty) JSON object. * @@ -1077,7 +1099,7 @@ CX_INLINE bool cxJsonIsLiteral(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal != CX_JSON_NULL; } /** @@ -1094,7 +1116,7 @@ CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_TRUE; } /** @@ -1111,7 +1133,7 @@ CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_FALSE; } /** @@ -1124,7 +1146,7 @@ CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { - return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; + return cxJsonIsLiteral(value) && value->literal == CX_JSON_NULL; } /** @@ -1202,7 +1224,7 @@ CX_EXPORT int64_t cxJsonAsInteger(const CxJsonValue *value); */ cx_attr_nonnull CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { - return value->value.literal == CX_JSON_TRUE; + return value->literal == CX_JSON_TRUE; } /** @@ -1216,7 +1238,7 @@ CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { */ cx_attr_nonnull CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { - return value->value.array.array_size; + return value->array.data_size; } /** @@ -1277,14 +1299,14 @@ CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); */ cx_attr_nonnull CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { - return value->value.object.values_size; + return cxCollectionSize(value->object); } /** - * Returns an iterator over the JSON object members. + * Returns a map iterator over the JSON object members. * - * The iterator yields values of type @c CxJsonObjValue* which - * contain the name and value of the member. + * The iterator yields values of type @c CxMapEntry* which + * contain the name and the @c CxJsonObjValue* of the member. * * If the @p value is not a JSON object, the behavior is undefined. * @@ -1293,7 +1315,7 @@ CX_INLINE size_t cxJsonObjSize(const CxJsonValue *value) { * @see cxJsonIsObject() */ cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); +CX_EXPORT CxMapIterator cxJsonObjIter(const CxJsonValue *value); /** * Internal function, do not use. diff --git a/ucx/cx/properties.h b/ucx/cx/properties.h index 95a4bbe..8301275 100644 --- a/ucx/cx/properties.h +++ b/ucx/cx/properties.h @@ -41,9 +41,6 @@ #include "map.h" #include "buffer.h" -#include -#include - #ifdef __cplusplus extern "C" { #endif @@ -80,9 +77,6 @@ struct cx_properties_config_s { * The character, when appearing at the end of a line, continues that line. * This is '\' by default. */ - /** - * Reserved for future use. - */ char continuation; }; @@ -141,23 +135,16 @@ enum cx_properties_status { */ CX_PROPERTIES_BUFFER_ALLOC_FAILED, /** - * Initializing the properties source failed. - * - * @see cx_properties_read_init_func + * A file operation failed. + * Only for cxPropertiesLoad(). + * It is system-specific if errno is set. */ - CX_PROPERTIES_READ_INIT_FAILED, + CX_PROPERTIES_FILE_ERROR, /** - * Reading from a properties source failed. - * - * @see cx_properties_read_func + * A map operation failed. + * Only for cxPropertiesLoad(). */ - CX_PROPERTIES_READ_FAILED, - /** - * Sinking a k/v-pair failed. - * - * @see cx_properties_sink_func - */ - CX_PROPERTIES_SINK_FAILED, + CX_PROPERTIES_MAP_ERROR, }; /** @@ -190,134 +177,6 @@ struct cx_properties_s { */ typedef struct cx_properties_s CxProperties; - -/** - * Typedef for a properties sink. - */ -typedef struct cx_properties_sink_s CxPropertiesSink; - -/** - * A function that consumes a k/v-pair in a sink. - * - * The sink could be a map, and the sink function would be calling - * a map function to store the k/v-pair. - * - * @param prop the properties interface that wants to sink a k/v-pair - * @param sink the sink - * @param key the key - * @param value the value - * @retval zero success - * @retval non-zero sinking the k/v-pair failed - */ -typedef int(*cx_properties_sink_func)( - CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -); - -/** - * Defines a sink for k/v-pairs. - */ -struct cx_properties_sink_s { - /** - * The sink object. - */ - void *sink; - /** - * Optional custom data. - */ - void *data; - /** - * A function for consuming k/v-pairs into the sink. - */ - cx_properties_sink_func sink_func; -}; - - -/** - * Typedef for a properties source. - */ -typedef struct cx_properties_source_s CxPropertiesSource; - -/** - * A function that reads data from a source. - * - * When the source is depleted, implementations SHALL provide an empty - * string in the @p target and return zero. - * A non-zero return value is only permitted in case of an error. - * - * The meaning of the optional parameters is implementation-dependent. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @param target a string buffer where the read data shall be stored - * @retval zero success - * @retval non-zero reading the data failed - */ -typedef int(*cx_properties_read_func)( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -); - -/** - * A function that may initialize additional memory for the source. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - * @retval zero initialization was successful - * @retval non-zero otherwise - */ -typedef int(*cx_properties_read_init_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * A function that cleans memory initialized by the read_init_func. - * - * @param prop the properties interface that wants to read from the source - * @param src the source - */ -typedef void(*cx_properties_read_clean_func)( - CxProperties *prop, - CxPropertiesSource *src -); - -/** - * Defines a properties source. - */ -struct cx_properties_source_s { - /** - * The source object. - * - * For example, a file stream or a string. - */ - void *src; - /** - * Optional additional data pointer. - */ - void *data_ptr; - /** - * Optional size information. - */ - size_t data_size; - /** - * A function that reads data from the source. - */ - cx_properties_read_func read_func; - /** - * Optional function that may prepare the source for reading data. - */ - cx_properties_read_init_func read_init_func; - /** - * Optional function that cleans additional memory allocated by the - * read_init_func. - */ - cx_properties_read_clean_func read_clean_func; -}; - /** * Initialize a properties interface. * @@ -465,100 +324,83 @@ cx_attr_nonnull cx_attr_nodiscard CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** - * Creates a properties sink for an UCX map. - * - * The values stored in the map will be pointers to freshly allocated, - * zero-terminated C strings (@c char*), which means the @p map should have been - * created with #CX_STORE_POINTERS. - * - * The cxDefaultAllocator will be used unless you specify a custom - * allocator in the optional @c data field of the returned sink. - * - * @param map the map that shall consume the k/v-pairs. - * @return the sink - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will reserve with `cxPropertiesUseStack()`. */ -cx_attr_nonnull cx_attr_nodiscard -CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); +CX_EXPORT extern const unsigned cx_properties_load_buf_size; /** - * Creates a properties source based on an UCX string. - * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() + * The size of the stack memory that `cxPropertiesLoad()` will use to read contents from the file. */ -cx_attr_nodiscard -CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); +CX_EXPORT extern const unsigned cx_properties_load_fill_size; /** - * Creates a properties source based on C string with the specified length. + * Internal function - use cxPropertiesLoad() instead. * - * @param str the string - * @param len the length - * @return the properties source - * @see cxPropertiesLoad() + * @param config the parser config + * @param allocator the allocator for the values + * @param filename the file name + * @param target the target map + * @return status code */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) -CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +cx_attr_nonnull_arg(4) +CX_EXPORT CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target); /** - * Creates a properties source based on a C string. + * Loads properties from a file and inserts them into a map. * - * The length will be determined with strlen(), so the string MUST be - * zero-terminated. + * Entries are added to the map, possibly overwriting existing entries. * - * @param str the string - * @return the properties source - * @see cxPropertiesLoad() - */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) -CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); - -/** - * Creates a properties source based on an FILE. + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * @param file the file - * @param chunk_size how many bytes may be read in one operation + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. * - * @return the properties source - * @see cxPropertiesLoad() + * @param config the parser config + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file + * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key + * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter + * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoadDefault() */ -cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) -CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); - +#define cxPropertiesLoad(config, allocator, filename, target) \ + cx_properties_load(config, allocator, cx_strcast(filename), target) /** - * Loads properties data from a source and transfers it to a sink. + * Loads properties from a file and inserts them into a map with a default config. * - * This function tries to read as much data from the source as possible. - * When the source was completely consumed and at least on k/v-pair was found, - * the return value will be #CX_PROPERTIES_NO_ERROR. - * When the source was consumed but no k/v-pairs were found, the return value - * will be #CX_PROPERTIES_NO_DATA. - * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA - * is returned. In that case you should call this function again with the same - * sink and either an updated source or the same source if the source is able to - * yield the missing data. + * Entries are added to the map, possibly overwriting existing entries. * - * The other result codes apply, according to their description. + * The map must either store pointers of type @c char*, or elements of type cxmutstr. + * Any other configuration is not supported. * - * @param prop the properties interface - * @param sink the sink - * @param source the source - * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found - * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed - * @retval CX_PROPERTIES_READ_FAILED reading from the source failed - * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed - * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs - * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data + * @note When the parser finds an error, all successfully parsed keys before the error + * are added to the map nonetheless. + * + * @param allocator the allocator for the values that will be stored in the map + * @param filename (any string) the absolute or relative path to the file + * @param target (@c CxMap*) the map where the properties shall be added + * @retval CX_PROPERTIES_NO_ERROR (zero) at least one key/value pair was found + * @retval CX_PROPERTIES_NO_DATA the file is syntactically OK, but does not contain properties + * @retval CX_PROPERTIES_INCOMPLETE_DATA unexpected end of file * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed + * @retval CX_PROPERTIES_FILE_ERROR a file operation failed; depending on the system @c errno might be set + * @retval CX_PROPERTIES_MAP_ERROR storing a key/value pair in the map failed + * @see cxPropertiesLoad() */ -cx_attr_nonnull -CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, - CxPropertiesSink sink, CxPropertiesSource source); +#define cxPropertiesLoadDefault(allocator, filename, target) \ + cx_properties_load(cx_properties_config_default, allocator, cx_strcast(filename), target) + #ifdef __cplusplus } // extern "C" diff --git a/ucx/cx/tree.h b/ucx/cx/tree.h index e4d10a8..23ab58e 100644 --- a/ucx/cx/tree.h +++ b/ucx/cx/tree.h @@ -643,16 +643,12 @@ struct cx_tree_node_base_s { * Structure for holding the base data of a tree. */ struct cx_tree_s { + CX_COLLECTION_BASE; /** * The tree class definition. */ const cx_tree_class *cl; - /** - * Allocator to allocate new nodes. - */ - const CxAllocator *allocator; - /** * A pointer to the root node. * @@ -670,21 +666,6 @@ struct cx_tree_s { */ cx_tree_node_create_func node_create; - /** - * An optional simple destructor for the tree nodes. - */ - cx_destructor_func simple_destructor; - - /** - * An optional advanced destructor for the tree nodes. - */ - cx_destructor_func2 advanced_destructor; - - /** - * The pointer to additional data that is passed to the advanced destructor. - */ - void *destructor_data; - /** * A function to compare two nodes. */ @@ -695,11 +676,6 @@ struct cx_tree_s { */ cx_tree_search_data_func search_data; - /** - * The number of currently stored elements. - */ - size_t size; - /** * Offset in the node struct for the parent pointer. */ diff --git a/ucx/json.c b/ucx/json.c index eb5fc94..967356b 100644 --- a/ucx/json.c +++ b/ucx/json.c @@ -27,6 +27,7 @@ */ #include "cx/json.h" +#include "cx/kv_list.h" #include #include @@ -41,90 +42,10 @@ static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING}; -static int json_cmp_objvalue(const void *l, const void *r) { - const CxJsonObjValue *left = l; - const CxJsonObjValue *right = r; - return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name)); -} - -static size_t json_find_objvalue(const CxJsonValue *obj, cxstring name) { - assert(obj->type == CX_JSON_OBJECT); - CxJsonObjValue kv_dummy; - kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length); - return cx_array_binary_search( - obj->value.object.values, - obj->value.object.values_size, - sizeof(CxJsonObjValue), - &kv_dummy, - json_cmp_objvalue - ); -} - -static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) { - assert(objv->type == CX_JSON_OBJECT); - const CxAllocator * const al = objv->allocator; - CxJsonObject *obj = &(objv->value.object); - - // determine the index where we need to insert the new member - size_t index = cx_array_binary_search_sup( - obj->values, - obj->values_size, - sizeof(CxJsonObjValue), - &member, json_cmp_objvalue - ); - - // is the name already present? - if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) { - // free the original value - cx_strfree_a(al, &obj->values[index].name); - cxJsonValueFree(obj->values[index].value); - // replace the item - obj->values[index] = member; - - // nothing more to do - return 0; - } - - // determine the old capacity and reserve for one more element - CxArrayReallocator arealloc = cx_array_reallocator(al, NULL); - size_t oldcap = obj->values_capacity; - if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1; - - // check the new capacity, if we need to realloc the index array - size_t newcap = obj->values_capacity; - if (newcap > oldcap) { - if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) { - return 1; // LCOV_EXCL_LINE - } - } - - // check if append or insert - if (index < obj->values_size) { - // move the other elements - memmove( - &obj->values[index+1], - &obj->values[index], - (obj->values_size - index) * sizeof(CxJsonObjValue) - ); - // increase indices for the moved elements - for (size_t i = 0; i < obj->values_size ; i++) { - if (obj->indices[i] >= index) { - obj->indices[i]++; - } - } - } - - // insert the element and set the index - obj->values[index] = member; - obj->indices[obj->values_size] = index; - obj->values_size++; - - return 0; -} - static void token_destroy(CxJsonToken *token) { if (token->allocated) { cx_strfree(&token->content); + token->allocated = false; } } @@ -307,7 +228,9 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { } } - if (ttype != CX_JSON_NO_TOKEN) { + if (ttype == CX_JSON_NO_TOKEN) { + return CX_JSON_NO_DATA; + } else { // uncompleted token size_t uncompleted_len = json->buffer.size - token_part_start; if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) { @@ -334,9 +257,8 @@ static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) { } // advance the buffer position - we saved the stuff in the uncompleted token json->buffer.pos += uncompleted_len; + return CX_JSON_INCOMPLETE_DATA; } - - return CX_JSON_INCOMPLETE_DATA; } // converts a Unicode codepoint to utf8 @@ -473,7 +395,7 @@ static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { return result; } -static cxmutstr escape_string(cxmutstr str, bool escape_slash) { +static cxmutstr escape_string(cxstring str, bool escape_slash) { // note: this function produces the string without enclosing quotes // the reason is that we don't want to allocate memory just for that CxBuffer buf = {0}; @@ -519,11 +441,27 @@ static cxmutstr escape_string(cxmutstr str, bool escape_slash) { cxBufferPut(&buf, c); } } - if (!all_printable) { - str = cx_mutstrn(buf.space, buf.size); + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); } cxBufferDestroy(&buf); - return str; + return ret; +} + +static CxJsonObject json_create_object_map(const CxAllocator *allocator) { + // TODO: we might want to add a comparator that is sorting the elements by their key + CxMap *map = cxKvListCreateAsMap(allocator, NULL, CX_STORE_POINTERS); + if (map == NULL) return NULL; // LCOV_EXCL_LINE + cxDefineDestructor(map, cxJsonValueFree); + return map; +} + +static void json_free_object_map(CxJsonObject obj) { + cxMapFree(obj); } static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { @@ -534,14 +472,11 @@ static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { v->type = type; v->allocator = json->allocator; if (type == CX_JSON_ARRAY) { - cx_array_initialize_a(json->allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE + cx_array_initialize_a(json->allocator, v->array.data, 16); + if (v->array.data == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } else if (type == CX_JSON_OBJECT) { - cx_array_initialize_a(json->allocator, v->value.object.values, 16); - v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t)); - if (v->value.object.values == NULL || - v->value.object.indices == NULL) - goto create_json_value_exit_error; // LCOV_EXCL_LINE + v->object = json_create_object_map(json->allocator); + if (v->object == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE } // add the new value to a possible parent @@ -550,17 +485,17 @@ static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) { assert(parent != NULL); if (parent->type == CX_JSON_ARRAY) { CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL); - if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) { + if (cx_array_simple_add_a(&value_realloc, parent->array.data, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } } else if (parent->type == CX_JSON_OBJECT) { // the member was already created after parsing the name - assert(json->uncompleted_member.name.ptr != NULL); - json->uncompleted_member.value = v; - if (json_add_objvalue(parent, json->uncompleted_member)) { + // store the pointer of the uncompleted value in the map + assert(json->uncompleted_member_name.ptr != NULL); + if (cxMapPut(parent->object, json->uncompleted_member_name, v)) { goto create_json_value_exit_error; // LCOV_EXCL_LINE } - json->uncompleted_member.name = (cxmutstr) {NULL, 0}; + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } else { assert(false); // LCOV_EXCL_LINE } @@ -624,10 +559,8 @@ void cxJsonDestroy(CxJson *json) { } cxJsonValueFree(json->parsed); json->parsed = NULL; - if (json->uncompleted_member.name.ptr != NULL) { - cx_strfree_a(json->allocator, &json->uncompleted_member.name); - json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL}; - } + token_destroy(&json->uncompleted); + cx_strfree_a(json->allocator, &json->uncompleted_member_name); } void cxJsonReset(CxJson *json) { @@ -720,7 +653,7 @@ static enum cx_json_status json_parse(CxJson *json) { if (str.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - vbuf->value.string = str; + vbuf->string = str; return_rec(CX_JSON_NO_ERROR); } case CX_JSON_TOKEN_INTEGER: @@ -730,11 +663,11 @@ static enum cx_json_status json_parse(CxJson *json) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (type == CX_JSON_INTEGER) { - if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) { + if (cx_strtoi64(token.content, &vbuf->integer, 10)) { return_rec(CX_JSON_FORMAT_ERROR_NUMBER); } } else { - if (cx_strtod(token.content, &vbuf->value.number)) { + if (cx_strtod(token.content, &vbuf->number)) { // TODO: at the moment this is unreachable, because the tokenizer is already stricter than cx_strtod() return_rec(CX_JSON_FORMAT_ERROR_NUMBER); // LCOV_EXCL_LINE } @@ -746,11 +679,11 @@ static enum cx_json_status json_parse(CxJson *json) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) { - vbuf->value.literal = CX_JSON_TRUE; + vbuf->literal = CX_JSON_TRUE; } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) { - vbuf->value.literal = CX_JSON_FALSE; + vbuf->literal = CX_JSON_FALSE; } else { - vbuf->value.literal = CX_JSON_NULL; + vbuf->literal = CX_JSON_NULL; } return_rec(CX_JSON_NO_ERROR); } @@ -786,8 +719,8 @@ static enum cx_json_status json_parse(CxJson *json) { if (name.ptr == NULL) { return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE } - assert(json->uncompleted_member.name.ptr == NULL); - json->uncompleted_member.name = name; + assert(json->uncompleted_member_name.ptr == NULL); + json->uncompleted_member_name = name; assert(json->vbuf_size > 0); // next state @@ -860,29 +793,54 @@ CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) { return result; } +CxJsonStatus cx_json_from_string(const CxAllocator *allocator, + cxstring str, CxJsonValue **value) { + *value = &cx_json_value_nothing; + CxJson parser; + cxJsonInit(&parser, allocator); + if (cxJsonFill(&parser, str)) { + // LCOV_EXCL_START + cxJsonDestroy(&parser); + return CX_JSON_BUFFER_ALLOC_FAILED; + // LCOV_EXCL_STOP + } + CxJsonStatus status = cxJsonNext(&parser, value); + // check if we consume the total string + CxJsonValue *chk_value = NULL; + CxJsonStatus chk_status = CX_JSON_NO_DATA; + if (status == CX_JSON_NO_ERROR) { + chk_status = cxJsonNext(&parser, &chk_value); + } + cxJsonDestroy(&parser); + if (chk_status == CX_JSON_NO_DATA) { + return status; + } else { + cxJsonValueFree(*value); + // if chk_value is nothing, the free is harmless + cxJsonValueFree(chk_value); + *value = &cx_json_value_nothing; + return CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN; + } + +} + void cxJsonValueFree(CxJsonValue *value) { if (value == NULL || value->type == CX_JSON_NOTHING) return; switch (value->type) { case CX_JSON_OBJECT: { - CxJsonObject obj = value->value.object; - for (size_t i = 0; i < obj.values_size; i++) { - cxJsonValueFree(obj.values[i].value); - cx_strfree_a(value->allocator, &obj.values[i].name); - } - cxFree(value->allocator, obj.values); - cxFree(value->allocator, obj.indices); + json_free_object_map(value->object); break; } case CX_JSON_ARRAY: { - CxJsonArray array = value->value.array; - for (size_t i = 0; i < array.array_size; i++) { - cxJsonValueFree(array.array[i]); + CxJsonArray array = value->array; + for (size_t i = 0; i < array.data_size; i++) { + cxJsonValueFree(array.data[i]); } - cxFree(value->allocator, array.array); + cxFree(value->allocator, array.data); break; } case CX_JSON_STRING: { - cxFree(value->allocator, value->value.string.ptr); + cxFree(value->allocator, value->string.ptr); break; } default: { @@ -898,15 +856,8 @@ CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_OBJECT; - cx_array_initialize_a(allocator, v->value.object.values, 16); - if (v->value.object.values == NULL) { // LCOV_EXCL_START - cxFree(allocator, v); - return NULL; - // LCOV_EXCL_STOP - } - v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t)); - if (v->value.object.indices == NULL) { // LCOV_EXCL_START - cxFree(allocator, v->value.object.values); + v->object = json_create_object_map(allocator); + if (v->object == NULL) { // LCOV_EXCL_START cxFree(allocator, v); return NULL; // LCOV_EXCL_STOP @@ -920,8 +871,8 @@ CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_ARRAY; - cx_array_initialize_a(allocator, v->value.array.array, 16); - if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; } + cx_array_initialize_a(allocator, v->array.data, 16); + if (v->array.data == NULL) { cxFree(allocator, v); return NULL; } return v; } @@ -931,7 +882,7 @@ CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_NUMBER; - v->value.number = num; + v->number = num; return v; } @@ -941,7 +892,7 @@ CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) { if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_INTEGER; - v->value.integer = num; + v->integer = num; return v; } @@ -953,7 +904,7 @@ CxJsonValue* cx_json_create_string(const CxAllocator* allocator, cxstring str) { v->type = CX_JSON_STRING; cxmutstr s = cx_strdup_a(allocator, str); if (s.ptr == NULL) { cxFree(allocator, v); return NULL; } - v->value.string = s; + v->string = s; return v; } @@ -963,7 +914,7 @@ CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit if (v == NULL) return NULL; v->allocator = allocator; v->type = CX_JSON_LITERAL; - v->value.literal = lit; + v->literal = lit; return v; } @@ -1042,24 +993,14 @@ int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL); assert(arr->type == CX_JSON_ARRAY); return cx_array_simple_copy_a(&value_realloc, - arr->value.array.array, - arr->value.array.array_size, + arr->array.data, + arr->array.data_size, val, count ); } int cx_json_obj_put(CxJsonValue* obj, cxstring name, CxJsonValue* child) { - cxmutstr k = cx_strdup_a(obj->allocator, name); - if (k.ptr == NULL) return -1; - CxJsonObjValue kv = {k, child}; - if (json_add_objvalue(obj, kv)) { - // LCOV_EXCL_START - cx_strfree_a(obj->allocator, &k); - return 1; - // LCOV_EXCL_STOP - } else { - return 0; - } + return cxMapPut(obj->object, name, child); } CxJsonValue* cx_json_obj_put_obj(CxJsonValue* obj, cxstring name) { @@ -1105,98 +1046,84 @@ CxJsonValue* cx_json_obj_put_literal(CxJsonValue* obj, cxstring name, CxJsonLite } CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return &cx_json_value_nothing; } - return value->value.array.array[index]; + return value->array.data[index]; } CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index) { - if (index >= value->value.array.array_size) { + if (index >= value->array.data_size) { return NULL; } - CxJsonValue *ret = value->value.array.array[index]; + CxJsonValue *ret = value->array.data[index]; // TODO: replace with a low level cx_array_remove() - size_t count = value->value.array.array_size - index - 1; + size_t count = value->array.data_size - index - 1; if (count > 0) { - memmove(value->value.array.array + index, value->value.array.array + index + 1, count * sizeof(CxJsonValue*)); + memmove(value->array.data + index, value->array.data + index + 1, count * sizeof(CxJsonValue*)); } - value->value.array.array_size--; + value->array.data_size--; return ret; } char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; + return value->string.ptr; } cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); + return cx_strcast(value->string); } cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; + return value->string; } double cxJsonAsDouble(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; + return (double) value->integer; } else { - return value->value.number; + return value->number; } } int64_t cxJsonAsInteger(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { - return value->value.integer; + return value->integer; } else { - return (int64_t) value->value.number; + return (int64_t) value->number; } } CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( - value->value.array.array, - value->value.array.array_size, + value->array.data, + value->array.data_size, true // arrays need to keep order ); } -CxIterator cxJsonObjIter(const CxJsonValue *value) { - return cxIterator( - value->value.object.values, - sizeof(CxJsonObjValue), - value->value.object.values_size, - true // TODO: objects do not always need to keep order - ); +CxMapIterator cxJsonObjIter(const CxJsonValue *value) { + return cxMapIterator(value->object); } CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { + CxJsonValue *v = cxMapGet(value->object, name); + if (v == NULL) { return &cx_json_value_nothing; } else { - return value->value.object.values[index].value; + return v; } } CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { - size_t index = json_find_objvalue(value, name); - if (index >= value->value.object.values_size) { - return NULL; - } else { - CxJsonObjValue kv = value->value.object.values[index]; - cx_strfree_a(value->allocator, &kv.name); - // TODO: replace with cx_array_remove() / cx_array_remove_fast() - value->value.object.values_size--; - memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); - return kv.value; - } + CxJsonValue *v = NULL; + cxMapRemoveAndGet(value->object, name, &v); + return v; } CxJsonWriter cxJsonWriterCompact(void) { return (CxJsonWriter) { false, - true, 6, false, 4, @@ -1206,7 +1133,6 @@ CxJsonWriter cxJsonWriterCompact(void) { CxJsonWriter cxJsonWriterPretty(bool use_spaces) { return (CxJsonWriter) { - true, true, 6, use_spaces, @@ -1272,14 +1198,8 @@ int cx_json_write_rec( expected++; } depth++; - size_t elem_count = value->value.object.values_size; - for (size_t look_idx = 0; look_idx < elem_count; look_idx++) { - // get the member either via index array or directly - size_t elem_idx = settings->sort_members - ? look_idx - : value->value.object.indices[look_idx]; - CxJsonObjValue *member = &value->value.object.values[elem_idx]; - + CxMapIterator member_iter = cxJsonObjIter(value); + cx_foreach(const CxMapEntry *, member, member_iter) { // possible indentation if (settings->pretty) { if (cx_json_writer_indent(target, wfunc, settings, depth)) { @@ -1289,26 +1209,27 @@ int cx_json_write_rec( // the name actual += wfunc("\"", 1, 1, target); - cxmutstr name = escape_string(member->name, settings->escape_slash); + cxstring key = cx_strn(member->key->data, member->key->len); + cxmutstr name = escape_string(key, settings->escape_slash); actual += wfunc(name.ptr, 1, name.length, target); - if (name.ptr != member->name.ptr) { - cx_strfree(&name); - } actual += wfunc("\"", 1, 1, target); const char *obj_name_sep = ": "; if (settings->pretty) { actual += wfunc(obj_name_sep, 1, 2, target); - expected += 4 + member->name.length; + expected += 4 + name.length; } else { actual += wfunc(obj_name_sep, 1, 1, target); - expected += 3 + member->name.length; + expected += 3 + name.length; + } + if (name.ptr != key.ptr) { + cx_strfree(&name); } // the value if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1; // end of object-value - if (look_idx < elem_count - 1) { + if (member_iter.index < member_iter.elem_count - 1) { const char *obj_value_sep = ",\n"; if (settings->pretty) { actual += wfunc(obj_value_sep, 1, 2, target); @@ -1361,13 +1282,14 @@ int cx_json_write_rec( } case CX_JSON_STRING: { actual += wfunc("\"", 1, 1, target); - cxmutstr str = escape_string(value->value.string, settings->escape_slash); + cxmutstr str = escape_string(cx_strcast(value->string), + settings->escape_slash); actual += wfunc(str.ptr, 1, str.length, target); - if (str.ptr != value->value.string.ptr) { + actual += wfunc("\"", 1, 1, target); + expected += 2 + str.length; + if (str.ptr != value->string.ptr) { cx_strfree(&str); } - actual += wfunc("\"", 1, 1, target); - expected += 2 + value->value.string.length; break; } case CX_JSON_NUMBER: { @@ -1375,7 +1297,7 @@ int cx_json_write_rec( // because of the way how %g is defined, we need to // double the precision and truncate ourselves precision = 1 + (precision > 15 ? 30 : 2 * precision); - snprintf(numbuf, 40, "%.*g", precision, value->value.number); + snprintf(numbuf, 40, "%.*g", precision, value->number); char *dot, *exp; unsigned char max_digits; // find the decimal separator and hope that it's one of . or , @@ -1439,17 +1361,17 @@ int cx_json_write_rec( break; } case CX_JSON_INTEGER: { - snprintf(numbuf, 32, "%" PRIi64, value->value.integer); + snprintf(numbuf, 32, "%" PRIi64, value->integer); size_t len = strlen(numbuf); actual += wfunc(numbuf, 1, len, target); expected += len; break; } case CX_JSON_LITERAL: { - if (value->value.literal == CX_JSON_TRUE) { + if (value->literal == CX_JSON_TRUE) { actual += wfunc("true", 1, 4, target); expected += 4; - } else if (value->value.literal == CX_JSON_FALSE) { + } else if (value->literal == CX_JSON_FALSE) { actual += wfunc("false", 1, 5, target); expected += 5; } else { @@ -1487,3 +1409,35 @@ int cxJsonWrite( } return cx_json_write_rec(target, value, wfunc, settings, 0); } + +static cxmutstr cx_json_to_string(CxJsonValue *value, const CxAllocator *allocator, CxJsonWriter *writer) { + if (allocator == NULL) allocator = cxDefaultAllocator; + CxBuffer buffer; + if (cxBufferInit(&buffer, NULL, 128, allocator, + CX_BUFFER_AUTO_EXTEND | CX_BUFFER_DO_NOT_FREE)) { + return (cxmutstr){NULL, 0}; + } + if (cx_json_write_rec(&buffer, value, cxBufferWriteFunc, writer, 0) + || cxBufferTerminate(&buffer)) { + // LCOV_EXCL_START + buffer.flags &= ~CX_BUFFER_DO_NOT_FREE; + cxBufferDestroy(&buffer); + return (cxmutstr){NULL, 0}; + // LCOV_EXCL_STOP + } else { + cxmutstr str = cx_mutstrn(buffer.space, buffer.size); + cxBufferDestroy(&buffer); + return str; + } + +} + +cxmutstr cxJsonToString(CxJsonValue *value, const CxAllocator *allocator) { + CxJsonWriter writer = cxJsonWriterCompact(); + return cx_json_to_string(value, allocator, &writer); +} + +cxmutstr cxJsonToPrettyString(CxJsonValue *value, const CxAllocator *allocator) { + CxJsonWriter writer = cxJsonWriterPretty(true); + return cx_json_to_string(value, allocator, &writer); +} diff --git a/ucx/kv_list.c b/ucx/kv_list.c index 3d1a783..d6375d9 100644 --- a/ucx/kv_list.c +++ b/ucx/kv_list.c @@ -285,6 +285,7 @@ static struct cx_iterator_s cx_kvl_iterator( static void cx_kvl_map_deallocate(struct cx_map_s *map) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); kv_list->map_methods->deallocate(map); kv_list->list_methods->deallocate(&kv_list->list.base); } @@ -296,41 +297,7 @@ static void cx_kvl_map_clear(struct cx_map_s *map) { kv_list->map_methods->clear(map); } -static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { - cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; - // if the hash has not yet been computed, do it now - if (key.hash == 0) { - cx_hash_murmur(&key); - } - - // reserve memory in the map first - void **map_data = kv_list->map_methods->put(map, key, NULL); - if (map_data == NULL) return NULL; // LCOV_EXCL_LINE - - // insert the data into the list (which most likely destroys the sorted property) - kv_list->list.base.collection.sorted = false; - void *node_data = kv_list->list_methods->insert_element( - &kv_list->list.base, kv_list->list.base.collection.size, - kv_list->list.base.collection.store_pointer ? &value : value); - if (node_data == NULL) { // LCOV_EXCL_START - // non-destructively remove the key again - kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); - return NULL; - } // LCOV_EXCL_STOP - - // write the node pointer to the map entry - *map_data = node_data; - - // copy the key to the node data - CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); - *key_ptr = key; - - // we must return node_data here and not map_data, - // because the node_data is the actual element of this collection - return node_data; -} - -void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { +static void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data = kv_list->map_methods->get(map, key); if (node_data == NULL) return NULL; // LCOV_EXCL_LINE @@ -338,7 +305,7 @@ void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; } -int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { +static int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; void *node_data; @@ -381,6 +348,43 @@ int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { return 0; } +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // remove any existing element first + cx_kvl_map_remove(map, key, NULL); + + // now reserve new memory in the map + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + static void *cx_kvl_iter_current_entry(const void *it) { const CxMapIterator *iter = it; return (void*)&iter->entry; @@ -455,7 +459,7 @@ static bool cx_kvl_iter_valid(const void *it) { return iter->elem != NULL; } -CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { +static CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { CxMapIterator iter = {0}; iter.type = type; diff --git a/ucx/properties.c b/ucx/properties.c index 8f15f96..c8cdc5c 100644 --- a/ucx/properties.c +++ b/ucx/properties.c @@ -29,12 +29,15 @@ #include "cx/properties.h" #include +#include +#include +#include const CxPropertiesConfig cx_properties_config_default = { - '=', - '#', - '\0', - '\0', + '=', + '#', + '\0', + '\0', '\\', }; @@ -94,13 +97,30 @@ CxPropertiesStatus cxPropertiesNext( // a pointer to the buffer we want to read from CxBuffer *current_buffer = &prop->input; - + + char comment1 = prop->config.comment1; + char comment2 = prop->config.comment2; + char comment3 = prop->config.comment3; + char delimiter = prop->config.delimiter; + char continuation = prop->config.continuation; + // check if we have rescued data if (!cxBufferEof(&prop->buffer)) { // check if we can now get a complete line cxstring input = cx_strn(prop->input.space + prop->input.pos, prop->input.size - prop->input.pos); cxstring nl = cx_strchr(input, '\n'); + while (nl.length > 0) { + // check for line continuation + char previous = nl.ptr > input.ptr ? nl.ptr[-1] : prop->buffer.space[prop->buffer.size-1]; + if (previous == continuation) { + // this nl is a line continuation, check the next newline + nl = cx_strchr(cx_strsubs(nl, 1), '\n'); + } else { + break; + } + } + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line @@ -127,12 +147,7 @@ CxPropertiesStatus cxPropertiesNext( return CX_PROPERTIES_INCOMPLETE_DATA; } } - - char comment1 = prop->config.comment1; - char comment2 = prop->config.comment2; - char comment3 = prop->config.comment3; - char delimiter = prop->config.delimiter; - + // get one line and parse it while (!cxBufferEof(current_buffer)) { const char *buf = current_buffer->space + current_buffer->pos; @@ -145,6 +160,7 @@ CxPropertiesStatus cxPropertiesNext( size_t delimiter_index = 0; size_t comment_index = 0; bool has_comment = false; + bool has_continuation = false; size_t i = 0; char c = 0; @@ -159,6 +175,9 @@ CxPropertiesStatus cxPropertiesNext( if (delimiter_index == 0 && !has_comment) { delimiter_index = i; } + } else if (delimiter_index > 0 && c == continuation && i+1 < len && buf[i+1] == '\n') { + has_continuation = true; + i++; } else if (c == '\n') { break; } @@ -223,10 +242,53 @@ CxPropertiesStatus cxPropertiesNext( k = cx_strtrim(k); val = cx_strtrim(val); if (k.length > 0) { + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); + assert(current_buffer != &prop->buffer || current_buffer->pos == current_buffer->size); + + if (has_continuation) { + char *ptr = (char*)val.ptr; + if (current_buffer != &prop->buffer) { + // move value to the rescue buffer + if (prop->buffer.space == NULL) { + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } + prop->buffer.size = 0; + prop->buffer.pos = 0; + if (cxBufferWrite(val.ptr, 1, val.length, &prop->buffer) != val.length) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; + } + val.ptr = prop->buffer.space; + ptr = prop->buffer.space; + } + // value.ptr is now inside the rescue buffer and we can + // remove the continuation character from the value + bool trim = false; + size_t x = 0; + for(size_t j=0;j x) { + if (trim) { + if (isspace((unsigned char)c)) { + continue; + } + trim = false; + } + ptr[x] = c; + } + x++; + } + val.length = x; + } *key = k; *value = val; - current_buffer->pos += i + 1; - assert(current_buffer->pos <= current_buffer->size); + return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; @@ -241,180 +303,96 @@ CxPropertiesStatus cxPropertiesNext( return CX_PROPERTIES_NO_DATA; } -static int cx_properties_sink_map( - cx_attr_unused CxProperties *prop, - CxPropertiesSink *sink, - cxstring key, - cxstring value -) { - CxMap *map = sink->sink; - CxAllocator *alloc = sink->data; - cxmutstr v = cx_strdup_a(alloc, value); - int r = cxMapPut(map, key, v.ptr); - if (r != 0) cx_strfree_a(alloc, &v); - return r; -} - -CxPropertiesSink cxPropertiesMapSink(CxMap *map) { - CxPropertiesSink sink; - sink.sink = map; - sink.data = (void*) cxDefaultAllocator; - sink.sink_func = cx_properties_sink_map; - return sink; -} - -static int cx_properties_read_string( - CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - if (prop->input.space == src->src) { - // when the input buffer already contains the string - // we have nothing more to provide - target->length = 0; - } else { - target->ptr = src->src; - target->length = src->data_size; +#ifndef CX_PROPERTIES_LOAD_FILL_SIZE +#define CX_PROPERTIES_LOAD_FILL_SIZE 1024 +#endif +const unsigned cx_properties_load_fill_size = CX_PROPERTIES_LOAD_FILL_SIZE; +#ifndef CX_PROPERTIES_LOAD_BUF_SIZE +#define CX_PROPERTIES_LOAD_BUF_SIZE 256 +#endif +const unsigned cx_properties_load_buf_size = CX_PROPERTIES_LOAD_BUF_SIZE; + +CxPropertiesStatus cx_properties_load(CxPropertiesConfig config, + const CxAllocator *allocator, cxstring filename, CxMap *target) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; } - return 0; -} - -static int cx_properties_read_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src, - cxstring *target -) { - target->ptr = src->data_ptr; - target->length = fread(src->data_ptr, 1, src->data_size, src->src); - return ferror((FILE*)src->src); -} - -static int cx_properties_read_init_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - src->data_ptr = cxMallocDefault(src->data_size); - if (src->data_ptr == NULL) return 1; - return 0; -} -static void cx_properties_read_clean_file( - cx_attr_unused CxProperties *prop, - CxPropertiesSource *src -) { - cxFreeDefault(src->data_ptr); -} - -CxPropertiesSource cxPropertiesStringSource(cxstring str) { - CxPropertiesSource src; - src.src = (void*) str.ptr; - src.data_size = str.length; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = len; - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesCstrSource(const char *str) { - CxPropertiesSource src; - src.src = (void*) str; - src.data_size = strlen(str); - src.data_ptr = NULL; - src.read_func = cx_properties_read_string; - src.read_init_func = NULL; - src.read_clean_func = NULL; - return src; -} - -CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { - CxPropertiesSource src; - src.src = file; - src.data_size = chunk_size; - src.data_ptr = NULL; - src.read_func = cx_properties_read_file; - src.read_init_func = cx_properties_read_init_file; - src.read_clean_func = cx_properties_read_clean_file; - return src; -} + // sanity check for the map + const bool use_cstring = cxCollectionStoresPointers(target); + if (!use_cstring && cxCollectionElementSize(target) != sizeof(cxmutstr)) { + return CX_PROPERTIES_MAP_ERROR; + } -CxPropertiesStatus cxPropertiesLoad( - CxProperties *prop, - CxPropertiesSink sink, - CxPropertiesSource source -) { - assert(source.read_func != NULL); - assert(sink.sink_func != NULL); + // create a duplicate to guarantee zero-termination + cxmutstr fname = cx_strdup(filename); + if (fname.ptr == NULL) { + return CX_PROPERTIES_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE + } - // initialize reader - if (source.read_init_func != NULL) { - if (source.read_init_func(prop, &source)) { - return CX_PROPERTIES_READ_INIT_FAILED; // LCOV_EXCL_LINE - } + // open the file + FILE *f = fopen(fname.ptr, "r"); + if (f == NULL) { + cx_strfree(&fname); + return CX_PROPERTIES_FILE_ERROR; } - // transfer the data from the source to the sink + // initialize the parser + char linebuf[cx_properties_load_buf_size]; + char fillbuf[cx_properties_load_fill_size]; CxPropertiesStatus status; - CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; - bool found = false; + CxProperties parser; + cxPropertiesInit(&parser, config); + cxPropertiesUseStack(&parser, linebuf, cx_properties_load_buf_size); + + // read/fill/parse loop + status = CX_PROPERTIES_NO_DATA; + size_t keys_found = 0; while (true) { - // read input - cxstring input; - if (source.read_func(prop, &source, &input)) { // LCOV_EXCL_START - status = CX_PROPERTIES_READ_FAILED; + size_t r = fread(fillbuf, 1, cx_properties_load_fill_size, f); + if (ferror(f)) { + status = CX_PROPERTIES_FILE_ERROR; break; - } // LCOV_EXCL_STOP - - // no more data - break - if (input.length == 0) { - if (found) { - // something was found, check the last kv_status - if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { - status = CX_PROPERTIES_INCOMPLETE_DATA; - } else { - status = CX_PROPERTIES_NO_ERROR; - } - } else { - // nothing found - status = CX_PROPERTIES_NO_DATA; - } + } + if (r == 0) { break; } - - // set the input buffer and read the k/v-pairs - cxPropertiesFill(prop, input); - - do { - cxstring key, value; - kv_status = cxPropertiesNext(prop, &key, &value); - if (kv_status == CX_PROPERTIES_NO_ERROR) { - found = true; - if (sink.sink_func(prop, &sink, key, value)) { - kv_status = CX_PROPERTIES_SINK_FAILED; // LCOV_EXCL_LINE + if (cxPropertiesFilln(&parser, fillbuf, r)) { + status = CX_PROPERTIES_BUFFER_ALLOC_FAILED; + break; + } + cxstring key, value; + while (true) { + status = cxPropertiesNext(&parser, &key, &value); + if (status != CX_PROPERTIES_NO_ERROR) { + break; + } else { + cxmutstr v = cx_strdup_a(allocator, value); + if (v.ptr == NULL) { + status = CX_PROPERTIES_MAP_ERROR; + break; + } + void *mv = use_cstring ? (void*)v.ptr : &v; + if (cxMapPut(target, key, mv)) { + cx_strfree(&v); + status = CX_PROPERTIES_MAP_ERROR; + break; } + keys_found++; } - } while (kv_status == CX_PROPERTIES_NO_ERROR); - - if (kv_status > CX_PROPERTIES_OK) { - status = kv_status; + } + if (status > CX_PROPERTIES_OK) { break; } } - if (source.read_clean_func != NULL) { - source.read_clean_func(prop, &source); + // cleanup and exit + fclose(f); + cxPropertiesDestroy(&parser); + cx_strfree(&fname); + if (status == CX_PROPERTIES_NO_DATA && keys_found > 0) { + return CX_PROPERTIES_NO_ERROR; + } else { + return status; } - - return status; } diff --git a/ucx/tree.c b/ucx/tree.c index 8be9b6d..3905cc0 100644 --- a/ucx/tree.c +++ b/ucx/tree.c @@ -734,15 +734,15 @@ static int cx_tree_default_insert_element( if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); tree->root = node; - tree->size = 1; + tree->collection.size = 1; return 0; } int result = cx_tree_add(data, tree->search, tree->node_create, tree, &node, tree->root, cx_tree_node_layout(tree)); if (0 == result) { - tree->size++; + tree->collection.size++; } else { - cxFree(tree->allocator, node); + cxFree(tree->collection.allocator, node); } return result; } @@ -767,9 +767,9 @@ static size_t cx_tree_default_insert_many( void *failed; ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create, tree, &failed, tree->root, cx_tree_node_layout(tree)); - tree->size += ins; + tree->collection.size += ins; if (ins < n) { - cxFree(tree->allocator, failed); + cxFree(tree->collection.allocator, failed); } return ins; } @@ -818,24 +818,21 @@ CxTree *cxTreeCreate(const CxAllocator *allocator, assert(search_func != NULL); assert(search_data_func != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; - tree->allocator = allocator; + tree->collection.allocator = allocator; tree->node_create = create_func; tree->search = search_func; tree->search_data = search_data_func; - tree->simple_destructor = NULL; - tree->advanced_destructor = (cx_destructor_func2) cxFree; - tree->destructor_data = (void *) allocator; + tree->collection.advanced_destructor = (cx_destructor_func2) cxFree; + tree->collection.destructor_data = (void *) allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; - tree->root = NULL; - tree->size = 0; return tree; } @@ -845,7 +842,7 @@ void cxTreeFree(CxTree *tree) { if (tree->root != NULL) { cxTreeClear(tree); } - cxFree(tree->allocator, tree); + cxFree(tree->collection.allocator, tree); } CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, @@ -856,39 +853,33 @@ CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, } assert(root != NULL); - CxTree *tree = cxMalloc(allocator, sizeof(CxTree)); + CxTree *tree = cxZalloc(allocator, sizeof(CxTree)); if (tree == NULL) return NULL; // LCOV_EXCL_LINE tree->cl = &cx_tree_default_class; // set the allocator anyway, just in case... - tree->allocator = allocator; - tree->node_create = NULL; - tree->search = NULL; - tree->search_data = NULL; - tree->simple_destructor = NULL; - tree->advanced_destructor = NULL; - tree->destructor_data = NULL; + tree->collection.allocator = allocator; tree->loc_parent = loc_parent; tree->loc_children = loc_children; tree->loc_last_child = loc_last_child; tree->loc_prev = loc_prev; tree->loc_next = loc_next; tree->root = root; - tree->size = cxTreeSubtreeSize(tree, root); + tree->collection.size = cxTreeSubtreeSize(tree, root); return tree; } void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { - tree->size++; + tree->collection.size++; } cx_tree_link(parent, child, cx_tree_node_layout(tree)); } void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) { cx_tree_link(parent, child, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; } int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { @@ -896,7 +887,7 @@ int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { if (node == NULL) return 1; // LCOV_EXCL_LINE cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); cx_tree_link(parent, node, cx_tree_node_layout(tree)); - tree->size++; + tree->collection.size++; return 0; } @@ -948,7 +939,7 @@ size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) { } size_t cxTreeSize(CxTree *tree) { - return tree->size; + return tree->collection.size; } size_t cxTreeDepth(CxTree *tree) { @@ -1002,7 +993,7 @@ int cxTreeRemoveNode( if (loc_last_child >= 0) tree_last_child(node) = NULL; // the tree now has one member less - tree->size--; + tree->collection.size--; return 0; } @@ -1010,12 +1001,12 @@ int cxTreeRemoveNode( void cxTreeRemoveSubtree(CxTree *tree, void *node) { if (node == tree->root) { tree->root = NULL; - tree->size = 0; + tree->collection.size = 0; return; } size_t subtree_size = cxTreeSubtreeSize(tree, node); cx_tree_unlink(node, cx_tree_node_layout(tree)); - tree->size -= subtree_size; + tree->collection.size -= subtree_size; } int cxTreeDestroyNode( @@ -1025,12 +1016,7 @@ int cxTreeDestroyNode( ) { int result = cxTreeRemoveNode(tree, node, relink_func); if (result == 0) { - if (tree->simple_destructor) { - tree->simple_destructor(node); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, node); - } + cx_invoke_destructor(tree, node); return 0; } else { return result; @@ -1045,15 +1031,10 @@ void cxTreeDestroySubtree(CxTree *tree, void *node) { ); cx_foreach(void *, child, iter) { if (iter.exiting) { - if (tree->simple_destructor) { - tree->simple_destructor(child); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, child); - } + cx_invoke_destructor(tree, child); } } - tree->size -= iter.counter; + tree->collection.size -= iter.counter; if (node == tree->root) { tree->root = NULL; } diff --git a/ui/cocoa/ListDataSource.h b/ui/cocoa/ListDataSource.h index 4dc142f..439cae1 100644 --- a/ui/cocoa/ListDataSource.h +++ b/ui/cocoa/ListDataSource.h @@ -27,7 +27,7 @@ */ #import "toolkit.h" -#import "../ui/tree.h" +#import "../ui/list.h" @interface ListDataSource : NSObject diff --git a/ui/cocoa/list.h b/ui/cocoa/list.h index c498de5..6a3b174 100644 --- a/ui/cocoa/list.h +++ b/ui/cocoa/list.h @@ -28,7 +28,7 @@ #import "toolkit.h" #import "Container.h" -#import "../ui/tree.h" +#import "../ui/list.h" #import "ListDataSource.h" diff --git a/ui/cocoa/list.m b/ui/cocoa/list.m index e776eeb..98d4a6f 100644 --- a/ui/cocoa/list.m +++ b/ui/cocoa/list.m @@ -179,7 +179,7 @@ UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata]; if(model) { - dataSource.model = ui_model_copy(obj->ctx, model); + dataSource.model = model; } tableview.dataSource = dataSource; @@ -263,7 +263,7 @@ void ui_tableview_setselection(UiList *list, UiListSelection selection) { @end -UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) { NSComboBox *dropdown = [[NSComboBox alloc] init]; dropdown.editable = NO; diff --git a/ui/cocoa/toolkit.m b/ui/cocoa/toolkit.m index fd97b2c..c0e9edf 100644 --- a/ui/cocoa/toolkit.m +++ b/ui/cocoa/toolkit.m @@ -33,6 +33,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #import "image.h" #import "menu.h" @@ -46,13 +47,6 @@ static const char *application_name; static int app_argc; static const char **app_argv; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static UiBool exit_on_shutdown; /* ------------------- App Init / Event Loop functions ------------------- */ @@ -85,21 +79,6 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } @@ -111,9 +90,7 @@ void ui_cocoa_onstartup(void) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(startup_func) { - startup_func(&e, startup_data); - } + uic_application_startup(&e); } void ui_cocoa_onopen(const char *file) { @@ -123,9 +100,7 @@ void ui_cocoa_onopen(const char *file) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(open_func) { - open_func(&e, open_data); - } + uic_application_open(&e); } void ui_cocoa_onexit(void) { @@ -135,9 +110,7 @@ void ui_cocoa_onexit(void) { e.document = NULL; e.eventdata = NULL; e.intval = 0; - if(exit_func) { - exit_func(&e, exit_data); - } + uic_application_exit(&e); } void ui_main(void) { diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m index c4efb6b..a3fe089 100644 --- a/ui/cocoa/window.m +++ b/ui/cocoa/window.m @@ -67,21 +67,18 @@ static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOO return obj; } -UiObject* ui_window(const char *title, void *window_data) { +UiObject* ui_window(const char *title) { UiObject *obj = create_window(title, FALSE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_simple_window(const char *title, void *window_data) { +UiObject* ui_simple_window(const char *title) { UiObject *obj = create_window(title, TRUE, FALSE, FALSE); - obj->window = window_data; return obj; } -UiObject* ui_sidebar_window(const char *title, void *window_data) { +UiObject* ui_sidebar_window(const char *title) { UiObject *obj = create_window(title, FALSE, TRUE, FALSE); - obj->window = window_data; return obj; } diff --git a/ui/common/app.c b/ui/common/app.c new file mode 100644 index 0000000..9674599 --- /dev/null +++ b/ui/common/app.c @@ -0,0 +1,71 @@ +/* + * 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 "app.h" + +static ui_callback startup_func; +static void *startup_data; +static ui_callback open_func; +void *open_data; +static ui_callback exit_func; +void *exit_data; + + +void ui_onstartup(ui_callback f, void *userdata) { + startup_func = f; + startup_data = userdata; +} + +void ui_onopen(ui_callback f, void *userdata) { + open_func = f; + open_data = userdata; +} + +void ui_onexit(ui_callback f, void *userdata) { + exit_func = f; + exit_data = userdata; +} + + +void uic_application_startup(UiEvent *event) { + if(startup_func) { + startup_func(event, startup_data); + } +} + +void uic_application_open(UiEvent *event) { + if(open_func) { + open_func(event, open_data); + } +} + +void uic_application_exit(UiEvent *event) { + if(exit_func) { + exit_func(event, exit_data); + } +} diff --git a/ui/common/app.h b/ui/common/app.h new file mode 100644 index 0000000..02356a9 --- /dev/null +++ b/ui/common/app.h @@ -0,0 +1,48 @@ +/* + * 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 UIC_APP_H +#define UIC_APP_H + +#include "../ui/toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void uic_application_startup(UiEvent *event); +void uic_application_open(UiEvent *event); +void uic_application_exit(UiEvent *event); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_APP_H */ + diff --git a/ui/common/args.c b/ui/common/args.c index 796919b..2ef732e 100644 --- a/ui/common/args.c +++ b/ui/common/args.c @@ -175,10 +175,10 @@ void ui_dialogwindow_args_free(UiDialogWindowArgs *args) { free((void*)args->lbutton2); free((void*)args->rbutton3); free((void*)args->rbutton4); - free((void*)args->lbutton1_groups); - free((void*)args->lbutton2_groups); - free((void*)args->rbutton3_groups); - free((void*)args->rbutton4_groups); + free((void*)args->lbutton1_states); + free((void*)args->lbutton2_states); + free((void*)args->rbutton3_states); + free((void*)args->rbutton4_states); free(args); } @@ -310,16 +310,16 @@ void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclick args->onclickdata = onclickdata; } -void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -355,10 +355,10 @@ void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, args->onchangedata = onchangedata; } -void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args,int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { @@ -366,7 +366,7 @@ void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args) { free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1383,10 +1383,10 @@ void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata){ args->onclickdata = onclickdata; } -void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_button_args_free(UiButtonArgs *args) { @@ -1395,7 +1395,7 @@ void ui_button_args_free(UiButtonArgs *args) { free((void*)args->label); free((void*)args->icon); free((void*)args->tooltip); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1502,14 +1502,14 @@ void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value) { args->value = value; } -void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group) { - args->enable_group = group; +void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state) { + args->enable_state = state; } -void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_toggle_args_free(UiToggleArgs *args) { @@ -1519,7 +1519,7 @@ void ui_toggle_args_free(UiToggleArgs *args) { free((void*)args->icon); free((void*)args->tooltip); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1633,10 +1633,10 @@ void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value) { args->value = value; } -void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_linkbutton_args_free(UiLinkButtonArgs *args) { @@ -1645,7 +1645,7 @@ void ui_linkbutton_args_free(UiLinkButtonArgs *args) { free((void*)args->label); free((void*)args->uri); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1815,10 +1815,10 @@ void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder) args->contextmenu = menubuilder; } -void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_list_args_set_states(UiListArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_list_args_free(UiListArgs *args) { @@ -1831,7 +1831,7 @@ void ui_list_args_free(UiListArgs *args) { } free(args->static_elements); } - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -1971,7 +1971,7 @@ void ui_sourcelist_args_free(UiSourceListArgs *args) { free((void*)args->style_class); free((void*)args->varname); free((void*)args->sublists); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2071,17 +2071,17 @@ void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value) { args->value = value; } -void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textarea_args_free(UiTextAreaArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2191,17 +2191,17 @@ void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value) { args->value = value; } -void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_textfield_args_free(UiTextFieldArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2314,17 +2314,17 @@ void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value) { args->rangevalue = value; } -void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_spinbox_args_free(UiSpinBoxArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } @@ -2414,17 +2414,17 @@ void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value) { args->value = value; } -void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) { - args->groups = calloc(numstates+1, sizeof(int)); - memcpy((void*)args->groups, states, numstates * sizeof(int)); - ((int*)args->groups)[numstates] = -1; +void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates) { + args->states = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->states, states, numstates * sizeof(int)); + ((int*)args->states)[numstates] = -1; } void ui_webview_args_free(UiWebviewArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); - free((void*)args->groups); + free((void*)args->states); free(args); } diff --git a/ui/common/args.h b/ui/common/args.h index ab66471..41d33f6 100644 --- a/ui/common/args.h +++ b/ui/common/args.h @@ -36,7 +36,7 @@ #include "../ui/entry.h" #include "../ui/menu.h" #include "../ui/toolbar.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../ui/text.h" #include "../ui/webview.h" #include "../ui/widget.h" @@ -107,7 +107,7 @@ UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char UIEXPORT void ui_toolbar_item_args_set_tooltip(UiToolbarItemArgs *args, const char *tooltip); UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); -UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_item_args_set_states(UiToolbarItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args); UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); @@ -117,7 +117,7 @@ UIEXPORT void ui_toolbar_toggleitem_args_set_tooltip(UiToolbarToggleItemArgs *ar UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); -UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates); +UIEXPORT void ui_toolbar_toggleitem_args_set_states(UiToolbarToggleItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args); UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); @@ -346,7 +346,7 @@ UIEXPORT void ui_button_args_set_tooltip(UiButtonArgs *args, const char *tooltip UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); -UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_button_args_set_states(UiButtonArgs *args, int *states, int numstates); UIEXPORT void ui_button_args_free(UiButtonArgs *args); UIEXPORT UiToggleArgs* ui_toggle_args_new(void); @@ -373,8 +373,8 @@ UIEXPORT void ui_toggle_args_set_onchange(UiToggleArgs *args, ui_callback callba UIEXPORT void ui_toggle_args_set_onchangedata(UiToggleArgs *args, void *onchangedata); UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname); UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value); -UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group); -UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates); +UIEXPORT void ui_toggle_args_set_enablestate(UiToggleArgs *args, int state); +UIEXPORT void ui_toggle_args_set_states(UiToggleArgs *args, int *states, int numstates); UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); @@ -401,7 +401,7 @@ UIEXPORT void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); -UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates); +UIEXPORT void ui_linkbutton_args_set_states(UiLinkButtonArgs *args, int *states, int numstates); UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); UIEXPORT UiListArgs* ui_list_args_new(void); @@ -443,7 +443,7 @@ UIEXPORT void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave) UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection); UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder); -UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates); +UIEXPORT void ui_list_args_set_states(UiListArgs *args, int *states, int numstates); UIEXPORT void ui_list_args_free(UiListArgs *args); UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void); @@ -495,7 +495,7 @@ UIEXPORT void ui_textarea_args_set_onchange(UiTextAreaArgs *args, ui_callback ca UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata); UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname); UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value); -UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates); +UIEXPORT void ui_textarea_args_set_states(UiTextAreaArgs *args, int *states, int numstates); UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args); UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void); @@ -520,7 +520,7 @@ UIEXPORT void ui_textfield_args_set_onactivate(UiTextFieldArgs *args, ui_callbac UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata); UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname); UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value); -UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates); +UIEXPORT void ui_textfield_args_set_states(UiTextFieldArgs *args, int *states, int numstates); UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args); UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void); @@ -549,7 +549,7 @@ UIEXPORT void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varna UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value); UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value); UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value); -UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates); +UIEXPORT void ui_spinbox_args_set_states(UiSpinBoxArgs *args, int *states, int numstates); UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args); UIEXPORT UiWebviewArgs* ui_webview_args_new(void); @@ -570,7 +570,7 @@ UIEXPORT void ui_webview_args_set_name(UiWebviewArgs *args, const char *name); UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname); UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname); UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value); -UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates); +UIEXPORT void ui_webview_args_set_states(UiWebviewArgs *args, int *states, int numstates); UIEXPORT void ui_webview_args_free(UiWebviewArgs *args); #ifdef __cplusplus diff --git a/ui/common/context.c b/ui/common/context.c index 40c1544..bd1d855 100644 --- a/ui/common/context.c +++ b/ui/common/context.c @@ -64,8 +64,8 @@ UiContext* uic_context(UiObject *toplevel, CxMempool *mp) { ctx->vars = cxHashMapCreate(mp->allocator, CX_STORE_POINTERS, 16); ctx->documents = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, CX_STORE_POINTERS); - ctx->group_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiGroupWidget)); - ctx->groups = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); + ctx->state_widgets = cxLinkedListCreate(mp->allocator, cx_cmp_ptr, sizeof(UiStateWidget)); + ctx->states = cxArrayListCreate(mp->allocator, cx_cmp_int, sizeof(int), 32); ctx->attach_document = uic_context_attach_document; ctx->detach_document2 = uic_context_detach_document; @@ -92,11 +92,17 @@ void uic_context_add_destructor(UiContext *ctx, cx_destructor_func func, void *d } void uic_context_prepare_close(UiContext *ctx) { - cxListClear(ctx->groups); - cxListClear(ctx->group_widgets); + cxListClear(ctx->states); + cxListClear(ctx->state_widgets); } void uic_context_attach_document(UiContext *ctx, void *document) { + if(ctx->single_document_mode) { + if(ctx->document) { + uic_context_detach_document(ctx, ctx->document); + } + } + cxListAdd(ctx->documents, document); ctx->document = document; @@ -202,6 +208,14 @@ UiVar* uic_get_var(UiContext *ctx, const char *name) { return ctx_getvar(ctx, key); } +UiVar* uic_get_var_t(UiContext *ctx,const char *name, UiVarType type) { + UiVar *var = uic_get_var(ctx, name); + if(var && var->type == type) { + return var; + } + return NULL; +} + UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { UiVar *var = uic_get_var(ctx, name); if(var) { @@ -485,6 +499,14 @@ void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value) // public API +void* ui_context_get_document(UiContext *ctx) { + return ctx->document; +} + +void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable) { + ctx->single_document_mode = enable; +} + void ui_attach_document(UiContext *ctx, void *document) { uic_context_attach_document(ctx, document); } @@ -511,48 +533,48 @@ UiContext* ui_context_parent(UiContext *ctx) { } -void ui_set_group(UiContext *ctx, int group) { - if(!cxListIndexValid(ctx->groups, cxListFind(ctx->groups, &group))) { - cxListAdd(ctx->groups, &group); +void ui_set_state(UiContext *ctx, int state) { + if(!cxListIndexValid(ctx->states, cxListFind(ctx->states, &state))) { + cxListAdd(ctx->states, &state); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -void ui_unset_group(UiContext *ctx, int group) { - int i = cxListFind(ctx->groups, &group); +void ui_unset_state(UiContext *ctx, int state) { + int i = cxListFind(ctx->states, &state); if(i != -1) { - cxListRemove(ctx->groups, i); + cxListRemove(ctx->states, i); } // enable/disable group widgets - uic_check_group_widgets(ctx); + uic_check_state_widgets(ctx); } -int* ui_active_groups(UiContext *ctx, int *ngroups) { - *ngroups = cxListSize(ctx->groups); - return cxListAt(ctx->groups, 0); +int* ui_active_states(UiContext *ctx, int *nstates) { + *nstates = cxListSize(ctx->states); + return cxListAt(ctx->states, 0); } -void uic_check_group_widgets(UiContext *ctx) { +void uic_check_state_widgets(UiContext *ctx) { int ngroups = 0; - int *groups = ui_active_groups(ctx, &ngroups); + int *groups = ui_active_states(ctx, &ngroups); - CxIterator i = cxListIterator(ctx->group_widgets); - cx_foreach(UiGroupWidget *, gw, i) { - char *check = calloc(1, gw->numgroups); + CxIterator i = cxListIterator(ctx->state_widgets); + cx_foreach(UiStateWidget *, gw, i) { + char *check = calloc(1, gw->numstates); for(int i=0;inumgroups;k++) { - if(groups[i] == gw->groups[k]) { + for(int k=0;knumstates;k++) { + if(groups[i] == gw->states[k]) { check[k] = 1; } } } int enable = 1; - for(int i=0;inumgroups;i++) { + for(int i=0;inumstates;i++) { if(check[i] == 0) { enable = 0; break; @@ -563,70 +585,70 @@ void uic_check_group_widgets(UiContext *ctx) { } } -void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { +void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - // get groups - CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); + // get states + CxList *states = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); va_list ap; va_start(ap, enable); - int group; - while((group = va_arg(ap, int)) != -1) { - cxListAdd(groups, &group); + int state; + while((state = va_arg(ap, int)) != -1) { + cxListAdd(states, &state); } va_end(ap); - uic_add_group_widget(ctx, widget, enable, groups); + uic_add_state_widget(ctx, widget, enable, states); - cxListFree(groups); + cxListFree(states); } -void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups) { +void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates) { if(enable == NULL) { enable = (ui_enablefunc)ui_set_enabled; } - CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); - for(int i=0;i= 0;i++) { } + for(i=0;states[i] >= 0;i++) { } return i; } -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) { - uic_add_group_widget_i(ctx, widget, enable, cxListAt(groups, 0), cxListSize(groups)); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states) { + uic_add_state_widget_i(ctx, widget, enable, cxListAt(states, 0), cxListSize(states)); } -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups) { +void uic_add_state_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *states, size_t numstates) { const CxAllocator *a = ctx->allocator; - UiGroupWidget gw; + UiStateWidget gw; gw.widget = widget; gw.enable = enable; - gw.numgroups = numgroups; - gw.groups = cxCalloc(a, numgroups, sizeof(int)); + gw.numstates = numstates; + gw.states = cxCalloc(a, numstates, sizeof(int)); - // copy groups - if(groups) { - memcpy(gw.groups, groups, gw.numgroups * sizeof(int)); + // copy states + if(states) { + memcpy(gw.states, states, gw.numstates * sizeof(int)); } - cxListAdd(ctx->group_widgets, &gw); + cxListAdd(ctx->state_widgets, &gw); } -void uic_remove_group_widget(UiContext *ctx, void *widget) { - (void)cxListFindRemove(ctx->group_widgets, widget); +void uic_remove_state_widget(UiContext *ctx, void *widget) { + (void)cxListFindRemove(ctx->state_widgets, widget); } UIEXPORT void *ui_allocator(UiContext *ctx) { @@ -671,3 +693,33 @@ void ui_reg_destructor(UiContext *ctx, void *data, ui_destructor_func destr) { void ui_set_destructor(void *mem, ui_destructor_func destr) { cxMempoolSetDestructor(mem, (cx_destructor_func)destr); } + +UiInteger* ui_get_int_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_INTEGER); + return var ? var->value : NULL; +} + +UiDouble* ui_get_double_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_DOUBLE); + return var ? var->value : NULL; +} + +UiString* ui_get_string_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_STRING); + return var ? var->value : NULL; +} + +UiText* ui_get_text_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_TEXT); + return var ? var->value : NULL; +} + +UiRange* ui_get_range_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_RANGE); + return var ? var->value : NULL; +} + +UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name) { + UiVar *var = uic_get_var_t(ctx, name, UI_VAR_GENERIC); + return var ? var->value : NULL; +} diff --git a/ui/common/context.h b/ui/common/context.h index 15e7700..5efcc2e 100644 --- a/ui/common/context.h +++ b/ui/common/context.h @@ -43,7 +43,7 @@ extern "C" { typedef struct UiVar UiVar; typedef struct UiListPtr UiListPtr; typedef struct UiListVar UiListVar; -typedef struct UiGroupWidget UiGroupWidget; +typedef struct UiStateWidget UiStateWidget; typedef struct UiDestroyHandler UiDestroyHandler; typedef enum UiVarType { @@ -69,8 +69,8 @@ struct UiContext { CxMap *vars; - CxList *groups; // int list - CxList *group_widgets; // UiGroupWidget list + CxList *states; // int list + CxList *state_widgets; // UiGroupWidget list void (*attach_document)(UiContext *ctx, void *document); void (*detach_document2)(UiContext *ctx, void *document); @@ -84,8 +84,10 @@ struct UiContext { GtkAccelGroup *accel_group; #endif #endif - - + + // allow only one document to be attached + // attaching a document will automatically detach the current document + UiBool single_document_mode; ui_callback close_callback; void *close_data; @@ -100,11 +102,11 @@ struct UiVar { UiBool bound; }; -struct UiGroupWidget { +struct UiStateWidget { void *widget; ui_enablefunc enable; - int *groups; - int numgroups; + int *states; + int numstates; }; struct UiDestroyHandler { @@ -128,6 +130,7 @@ void uic_context_detach_context(UiContext *ctx, UiContext *doc_ctx); // TODO void uic_context_detach_all(UiContext *ctx); UiVar* uic_get_var(UiContext *ctx, const char *name); +UiVar* uic_get_var_t(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type); UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); @@ -143,11 +146,11 @@ const char *uic_type2str(UiVarType type); void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value); -size_t uic_group_array_size(const int *groups); -void uic_check_group_widgets(UiContext *ctx); -void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups); -void uic_add_group_widget_i(UiContext *ctx, void *widget, ui_enablefunc enable, const int *groups, size_t numgroups); -void uic_remove_group_widget(UiContext *ctx, void *widget); +size_t uic_state_array_size(const int *states); +void uic_check_state_widgets(UiContext *ctx); +void uic_add_state_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *states); +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); #ifdef __cplusplus } diff --git a/ui/common/menu.c b/ui/common/menu.c index b0cfc46..64c49c1 100644 --- a/ui/common/menu.c +++ b/ui/common/menu.c @@ -89,20 +89,20 @@ static char* nl_strdup(const char* s) { return s ? strdup(s) : NULL; } -int* uic_copy_groups(const int* groups, size_t *ngroups) { - *ngroups = 0; - if (!groups) { +int* uic_copy_states(const int* states, size_t *nstates) { + *nstates = 0; + if (!states) { return NULL; } size_t n; - for (n = 0; groups[n] > -1; n++) { } + for (n = 0; states[n] > -1; n++) { } - if (ngroups > 0) { + if (nstates > 0) { int* newarray = calloc(n+1, sizeof(int)); - memcpy(newarray, groups, n * sizeof(int)); + memcpy(newarray, states, n * sizeof(int)); newarray[n] = -1; - *ngroups = n; + *nstates = n; return newarray; } return NULL; @@ -152,7 +152,7 @@ void ui_menuitem_create(UiMenuItemArgs *args) { item->icon = nl_strdup(args->icon); item->userdata = args->onclickdata; item->callback = args->onclick; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->states, &item->nstates); add_item((UiMenuItemI*)item); } @@ -179,7 +179,7 @@ void ui_menu_toggleitem_create(UiMenuToggleItemArgs *args) { item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -196,7 +196,7 @@ void ui_menu_radioitem_create(UiMenuToggleItemArgs *args) { item->varname = nl_strdup(args->varname); item->userdata = args->onchangedata; item->callback = args->onchange; - item->groups = uic_copy_groups(args->groups, &item->ngroups); + item->states = uic_copy_states(args->nstates, &item->nstates); add_item((UiMenuItemI*)item); } @@ -296,14 +296,14 @@ static void free_menuitem(UiMenuItemI *item) { } case UI_MENU_ITEM: { UiMenuItem *i = (UiMenuItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); break; } case UI_MENU_CHECK_ITEM: { UiMenuCheckItem *i = (UiMenuCheckItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); @@ -311,7 +311,7 @@ static void free_menuitem(UiMenuItemI *item) { } case UI_MENU_RADIO_ITEM: { UiMenuRadioItem *i = (UiMenuRadioItem*)item; - free(i->groups); + free(i->states); free(i->label); free(i->icon); free(i->varname); diff --git a/ui/common/menu.h b/ui/common/menu.h index f44f6b4..9aea578 100644 --- a/ui/common/menu.h +++ b/ui/common/menu.h @@ -79,8 +79,8 @@ struct UiMenuItem { char *label; char *icon; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuCheckItem { @@ -90,8 +90,8 @@ struct UiMenuCheckItem { char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuRadioItem { @@ -101,8 +101,8 @@ struct UiMenuRadioItem { char *varname; ui_callback callback; void *userdata; - int *groups; - size_t ngroups; + int *states; + size_t nstates; }; struct UiMenuItemList { @@ -129,7 +129,7 @@ UiMenu* uic_get_menu_list(void); void uic_add_menu_to_stack(UiMenu* menu); -int* uic_copy_groups(const int* groups, size_t *ngroups); +int* uic_copy_states(const int* states, size_t *nstates); void uic_set_tmp_eventdata(void *eventdata, int type); void* uic_get_tmp_eventdata(void); diff --git a/ui/common/message.c b/ui/common/message.c index 921b1a6..30ef304 100644 --- a/ui/common/message.c +++ b/ui/common/message.c @@ -26,12 +26,18 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _WIN32 + #include #include #include #include "message.h" +int uic_message_send_(UiMessageHandler *handler, cxstring msg) { + return handler->send(handler, msg); +} + UiMessageHandler* uic_simple_msg_handler(int in, int out, msg_received_callback callback) { UiSimpleMessageHandler *handler = malloc(sizeof(UiSimpleMessageHandler)); handler->handler.start = uic_simple_msg_handler_start; @@ -77,6 +83,9 @@ int uic_simple_msg_handler_stop(UiMessageHandler *handler) { int uic_simple_msg_handler_send(UiMessageHandler *handler, cxstring msg) { UiSimpleMessageHandler *sh = (UiSimpleMessageHandler*)handler; pthread_mutex_lock(&sh->queue_lock); + char header[32]; + snprintf(header, 32, "%zu\n", msg.length); + cxBufferPutString(sh->outbuf, header); cxBufferWrite(msg.ptr, 1, msg.length, sh->outbuf); pthread_cond_signal(&sh->available); pthread_mutex_unlock(&sh->queue_lock); @@ -114,8 +123,9 @@ void* uic_simple_msg_handler_in_thread(void *data) { // message complete //fprintf(stderr, "send: %.*s\n", (int)msg_size, msg); if(handler->handler.callback) { - handler->handler.callback(cx_mutstrn(msg, msg_size)); + handler->handler.callback(cx_strn(msg, msg_size)); } + free(msg); msg = NULL; msg_size = 0; msg_pos = 0; @@ -200,3 +210,5 @@ void* uic_simple_msg_handler_out_thread(void *data) { return NULL; } + +#endif diff --git a/ui/common/message.h b/ui/common/message.h index 00c83b7..83cb135 100644 --- a/ui/common/message.h +++ b/ui/common/message.h @@ -29,11 +29,14 @@ #ifndef UIC_MESSAGE_H #define UIC_MESSAGE_H + #include #include #include +#ifndef _WIN32 #include +#endif #ifdef __cplusplus @@ -43,7 +46,7 @@ extern "C" { typedef struct UiMessageHandler UiMessageHandler; -typedef void(*msg_received_callback)(cxmutstr msg); +typedef void(*msg_received_callback)(cxstring msg); struct UiMessageHandler { int (*start)(UiMessageHandler *handler); @@ -57,15 +60,20 @@ typedef struct UiSimpleMessageHandler { UiMessageHandler handler; int in; int out; +#ifndef _WIN32 pthread_t in_thread; pthread_t out_thread; pthread_mutex_t queue_lock; pthread_mutex_t avlbl_lock; pthread_cond_t available; +#endif CxBuffer *outbuf; int stop; } UiSimpleMessageHandler; +int uic_message_send_(UiMessageHandler *handler, cxstring msg); +#define uic_message_send(handler, msg) uic_message_send_(handler, cx_strcast(msg)) + UiMessageHandler* uic_simple_msg_handler(int in, int out, msg_received_callback callback); int uic_simple_msg_handler_start(UiMessageHandler *handler); int uic_simple_msg_handler_stop(UiMessageHandler *handler); diff --git a/ui/common/objs.mk b/ui/common/objs.mk index 2d01bda..e69125c 100644 --- a/ui/common/objs.mk +++ b/ui/common/objs.mk @@ -34,10 +34,10 @@ COMMON_OBJ += document$(OBJ_EXT) COMMON_OBJ += object$(OBJ_EXT) COMMON_OBJ += container$(OBJ_EXT) COMMON_OBJ += types$(OBJ_EXT) +COMMON_OBJ += app$(OBJ_EXT) COMMON_OBJ += properties$(OBJ_EXT) COMMON_OBJ += menu$(OBJ_EXT) COMMON_OBJ += toolbar$(OBJ_EXT) -COMMON_OBJ += ucx_properties$(OBJ_EXT) COMMON_OBJ += threadpool$(OBJ_EXT) COMMON_OBJ += condvar$(OBJ_EXT) COMMON_OBJ += args$(OBJ_EXT) diff --git a/ui/common/properties.c b/ui/common/properties.c index 28676fb..138be62 100644 --- a/ui/common/properties.c +++ b/ui/common/properties.c @@ -44,7 +44,7 @@ #include #include -#include "ucx_properties.h" +#include static CxMap *application_properties; static CxMap *language; @@ -54,8 +54,27 @@ static CxMap *language; static char *locales_dir; static char *pixmaps_dir; +static UiBool use_xdg_config_home = TRUE; + #endif +static UiBool load_on_startup = TRUE; +static void *properties_data = NULL; +static size_t properties_data_length = 0; + +void ui_load_properties_file_on_startup(UiBool enable) { + load_on_startup = enable; +} + +void ui_set_properties_data(const char *str, size_t len) { + if(str && len > 0) { + properties_data = malloc(len); + memcpy(properties_data, str, len); + } else { + properties_data = NULL; + properties_data_length = 0; + } +} char* ui_getappdir(void) { if(ui_appname() == NULL) { @@ -73,6 +92,8 @@ char* ui_getappdir(void) { #define UI_ENV_HOME "USERPROFILE" #endif +#define UI_XDG_CONFIG_HOME_VAR "XDG_CONFIG_HOME" + char* ui_configfile(const char *name) { const char *appname = ui_appname(); if(!appname) { @@ -102,8 +123,23 @@ char* ui_configfile(const char *name) { // on Windows the app dir is $USERPROFILE/AppData/Local/$APPNAME/ cxBufferPutString(&buf, "AppData\\Local\\"); #else - // app dir is $HOME/.$APPNAME/ - cxBufferPut(&buf, '.'); + if(use_xdg_config_home) { + // app dir is $HOME/.config/$APPNAME/ + char *xdghome = getenv(UI_XDG_CONFIG_HOME_VAR); + size_t xdghomelen = xdghome ? strlen(xdghome) : 0; + if(xdghome && xdghomelen > 0) { + cxBufferSeek(&buf, 0, SEEK_SET); + cxBufferPutString(&buf, xdghome); + if(xdghome[xdghomelen-1] != '/') { + cxBufferPut(&buf, '/'); + } + } else { + cxBufferPutString(&buf, ".config/"); + } + } else { + cxBufferPut(&buf, '.'); + } + #endif cxBufferPutString(&buf, appname); cxBufferPut(&buf, UI_PATH_SEPARATOR); @@ -126,10 +162,53 @@ static int ui_mkdir(char *path) { #endif } +static int load_properties(FILE *file, CxMap *map) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + char buf[8192]; + size_t r; + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + while((r = fread(buf, 1, 8192, file)) > 0) { + cxPropertiesFilln(&prop, buf, r); + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(map, key, cx_strdup(value).ptr); + } + if(status > CX_PROPERTIES_OK) { + break; + } + } + return status <= CX_PROPERTIES_NO_DATA ? 0 : 1; +} + void uic_load_app_properties() { application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); application_properties->collection.simple_destructor = free; + if(!load_on_startup) { + if(properties_data) { + CxProperties prop; + cxPropertiesInitDefault(&prop); + + CxPropertiesStatus status = CX_PROPERTIES_NO_ERROR; + cxPropertiesFilln(&prop, properties_data, properties_data_length); + + cxstring key; + cxstring value; + while((status = cxPropertiesNext(&prop, &key, &value)) == CX_PROPERTIES_NO_ERROR) { + cxMapPut(application_properties, key, cx_strdup(value).ptr); + } + + if(status > CX_PROPERTIES_NO_DATA) { + fprintf(stderr, "Error: cannot load properties: %d\n", (int)status); + } else { + cxMapRehash(application_properties); + } + } + return; + } + if(!ui_appname()) { // applications without name cannot load app properties return; @@ -139,9 +218,37 @@ void uic_load_app_properties() { if(!dir) { return; } + size_t len = strlen(dir); + if(len < 2) { + return; + } if(ui_mkdir(dir)) { + if(errno == ENOENT) { + char *parent = strdup(dir); + for(int i=len-2;i>=0;i--) { + if(parent[i] == '/') { + parent[i] = 0; + break; + } + } + // try creating the parent + int err = ui_mkdir(parent); + if(err) { + fprintf(stderr, "Error: Cannot create directory %s: %s\n", parent, strerror(errno)); + free(parent); + free(dir); + return; + } + free(parent); + + // try dir again + if(!ui_mkdir(dir)) { + errno = EEXIST; // success + } + } + if(errno != EEXIST) { - fprintf(stderr, "Ui Error: Cannot create directory %s\n", dir); + fprintf(stderr, "Error: Cannot create directory %s: %s\n", dir, strerror(errno)); free(dir); return; } @@ -159,8 +266,8 @@ void uic_load_app_properties() { return; } - if(ucx_properties_load(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot load application properties.\n"); + if(load_properties(file, application_properties)) { + fprintf(stderr, "Error: Cannot load application properties.\n"); } fclose(file); @@ -181,11 +288,13 @@ int uic_store_app_properties() { } int ret = 0; - if(ucx_properties_store(application_properties, file)) { - fprintf(stderr, "Ui Error: Cannot store application properties.\n"); - ret = 1; + CxMapIterator i = cxMapIterator(application_properties); + cx_foreach(CxMapEntry *, entry, i) { + fprintf(file, "%.*s = %s\n", (int)entry->key->len, (char*)entry->key->data, (char*)entry->value); } + cxMapRehash(application_properties); + fclose(file); free(path); @@ -313,8 +422,8 @@ int uic_load_language_file(const char *path) { return 1; } - if(ucx_properties_load(lang, file)) { - fprintf(stderr, "Ui Error: Cannot parse language file: %s.\n", path); + if(load_properties(file, lang)) { + fprintf(stderr, "Error: Cannot parse language file: %s.\n", path); } fclose(file); diff --git a/ui/common/threadpool.c b/ui/common/threadpool.c index a66af62..3b4c096 100644 --- a/ui/common/threadpool.c +++ b/ui/common/threadpool.c @@ -156,7 +156,12 @@ UiQueue* ui_queue_create(void) { } void ui_queue_free(UiQueue *queue) { - // TODO + // The queue must be empty, we could free UiQueueElm, + // but not the payload data + pthread_mutex_destroy(&queue->lock); + pthread_mutex_destroy(&queue->avlbl_lock); + pthread_cond_destroy(&queue->available); + free(queue); } void ui_queue_put(UiQueue *queue, void *data) { diff --git a/ui/common/toolbar.c b/ui/common/toolbar.c index f237290..eed1246 100644 --- a/ui/common/toolbar.c +++ b/ui/common/toolbar.c @@ -57,15 +57,15 @@ static UiToolbarItemArgs itemargs_copy(UiToolbarItemArgs *args, size_t *ngroups, newargs.tooltip = nl_strdup(args->tooltip); newargs.onclick = args->onclick; newargs.onclickdata = args->onclickdata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_item_create(const char* name, UiToolbarItemArgs *args) { UiToolbarItem* item = malloc(sizeof(UiToolbarItem)); item->item.type = UI_TOOLBAR_ITEM; - item->args = itemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = itemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -78,15 +78,15 @@ static UiToolbarToggleItemArgs toggleitemargs_copy(UiToolbarToggleItemArgs *args newargs.varname = nl_strdup(args->varname); newargs.onchange = args->onchange; newargs.onchangedata = args->onchangedata; - newargs.groups = uic_copy_groups(args->groups, ngroups); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.states = uic_copy_states(args->states, ngroups); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } void ui_toolbar_toggleitem_create(const char* name, UiToolbarToggleItemArgs *args) { UiToolbarToggleItem* item = malloc(sizeof(UiToolbarToggleItem)); item->item.type = UI_TOOLBAR_TOGGLEITEM; - item->args = toggleitemargs_copy(args, &item->ngroups, &item->nvstates); + item->args = toggleitemargs_copy(args, &item->nstates, &item->nvstates); cxMapPut(toolbar_items, name, item); } @@ -95,7 +95,7 @@ static UiToolbarMenuArgs menuargs_copy(UiToolbarMenuArgs *args, size_t *nvstates newargs.label = nl_strdup(args->label); newargs.icon = nl_strdup(args->icon); newargs.tooltip = nl_strdup(args->tooltip); - newargs.visibility_states = uic_copy_groups(args->visibility_states, nvstates); + newargs.visibility_states = uic_copy_states(args->visibility_states, nvstates); return newargs; } diff --git a/ui/common/toolbar.h b/ui/common/toolbar.h index 1ac1d55..9ae60f0 100644 --- a/ui/common/toolbar.h +++ b/ui/common/toolbar.h @@ -62,14 +62,14 @@ struct UiToolbarItemI { struct UiToolbarItem { UiToolbarItemI item; UiToolbarItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; struct UiToolbarToggleItem { UiToolbarItemI item; UiToolbarToggleItemArgs args; - size_t ngroups; + size_t nstates; size_t nvstates; }; diff --git a/ui/common/types.c b/ui/common/types.c index 53fbbec..e8680b7 100644 --- a/ui/common/types.c +++ b/ui/common/types.c @@ -33,7 +33,7 @@ #include #include -#include "../ui/tree.h" +#include "../ui/list.h" #include "types.h" #include "context.h" #include "../ui/image.h" @@ -212,6 +212,7 @@ typedef struct { UiModel* ui_model(UiContext *ctx, ...) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; va_list ap; va_start(ap, ctx); @@ -251,8 +252,18 @@ UiModel* ui_model(UiContext *ctx, ...) { #define UI_MODEL_DEFAULT_ALLOC_SIZE 16 + +static void model_notify_observer(UiModel *model, int insert, int delete) { + UiModelChangeObserver *obs = model->observer; + while(obs) { + obs->update(model, obs->userdata, insert, delete); + obs = obs->next; + } +} + UiModel* ui_model_new(UiContext *ctx) { UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->ctx = ctx; info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE; info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType)); info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*)); @@ -260,16 +271,21 @@ UiModel* ui_model_new(UiContext *ctx) { return info; } -void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) { +void ui_model_add_column(UiModel *model, UiModelType type, const char *title, int width) { + UiContext *ctx = model->ctx; if(model->columns >= model->alloc) { model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE; model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType)); model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*)); model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int)); } - model->types[model->columns] = type; - model->titles[model->columns] = ui_strdup(ctx, title); - model->columnsize[model->columns] = width; + int index = model->columns; + model->types[index] = type; + model->titles[index] = ui_strdup(ctx, title); + model->columnsize[index] = width; + + model_notify_observer(model, index, -1); + model->columns++; } @@ -277,6 +293,7 @@ UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; UiModel* newmodel = cxMalloc(a, sizeof(UiModel)); + newmodel->ctx = ctx; *newmodel = *model; newmodel->types = cxCalloc(a, model->columns, sizeof(UiModelType)); @@ -292,11 +309,67 @@ UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { return newmodel; } -void ui_model_free(UiContext *ctx, UiModel *mi) { - const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; +void ui_model_ref(UiModel *model) { + model->ref++; +} + +void ui_model_unref(UiModel *model) { + if(--model->ref == 0) { + ui_model_free(model); + } +} + +void ui_model_add_observer(UiModel *model, ui_model_update_func update, void *data) { + UiModelChangeObserver *observer = ui_malloc(model->ctx, sizeof(UiModelChangeObserver)); + observer->update = update; + observer->userdata = data; + observer->next = NULL; + + if(model->observer) { + UiModelChangeObserver *last = model->observer; + while(last->next) { + last = last->next; + } + last->next = observer; + } else { + model->observer = observer; + } +} + +void ui_model_remove_observer(UiModel *model, void *data) { + if(model->observer) { + UiModelChangeObserver *obs = model->observer; + UiModelChangeObserver *prev = NULL; + while(obs) { + if(obs->userdata == data) { + // remove + if(prev) { + prev->next = obs->next; + } else { + model->observer = obs->next; + } + // free + ui_free(model->ctx, obs); + break; + } + prev = obs; + obs = obs->next; + } + } +} + +void ui_model_free(UiModel *mi) { + UiContext *ctx = mi->ctx; + const CxAllocator* a = ctx->allocator; for(int i=0;icolumns;i++) { ui_free(ctx, mi->titles[i]); } + UiModelChangeObserver *obs = mi->observer; + while(obs) { + UiModelChangeObserver *n = obs->next; + cxFree(a, obs); + obs = n; + } cxFree(a, mi->types); cxFree(a, mi->titles); cxFree(a, mi->columnsize); @@ -735,9 +808,7 @@ UIEXPORT void ui_list_setselection(UiList *list, int index) { } UIEXPORT void ui_listselection_free(UiListSelection selection) { - if (selection.rows) { - free(selection.rows); - } + free(selection.rows); } UIEXPORT UiStr ui_str(char *cstr) { diff --git a/ui/common/utils.c b/ui/common/utils.c index 5e01239..8d3b5dd 100644 --- a/ui/common/utils.c +++ b/ui/common/utils.c @@ -29,7 +29,11 @@ #include "utils.h" #include "properties.h" +#include +#include + #include +#include UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { cxstring *pathelms; @@ -83,3 +87,65 @@ void ui_get_window_default_width(int *width, int *height) { } } } + +// from UCX json.c +static cxmutstr escape_string(cxstring str, bool escape_slash) { + // note: this function produces the string without enclosing quotes + // the reason is that we don't want to allocate memory just for that + CxBuffer buf = {0}; + + bool all_printable = true; + for (size_t i = 0; i < str.length; i++) { + unsigned char c = str.ptr[i]; + bool escape = c < 0x20 || c == '\\' || c == '"' + || (escape_slash && c == '/'); + + if (all_printable && escape) { + size_t capa = str.length + 32; + char *space = cxMallocDefault(capa); + if (space == NULL) return cx_mutstrn(NULL, 0); + cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND); + cxBufferWrite(str.ptr, 1, i, &buf); + all_printable = false; + } + if (escape) { + cxBufferPut(&buf, '\\'); + if (c == '\"') { + cxBufferPut(&buf, '\"'); + } else if (c == '\n') { + cxBufferPut(&buf, 'n'); + } else if (c == '\t') { + cxBufferPut(&buf, 't'); + } else if (c == '\r') { + cxBufferPut(&buf, 'r'); + } else if (c == '\\') { + cxBufferPut(&buf, '\\'); + } else if (c == '/') { + cxBufferPut(&buf, '/'); + } else if (c == '\f') { + cxBufferPut(&buf, 'f'); + } else if (c == '\b') { + cxBufferPut(&buf, 'b'); + } else { + char code[6]; + snprintf(code, sizeof(code), "u%04x", (unsigned int) c); + cxBufferPutString(&buf, code); + } + } else if (!all_printable) { + cxBufferPut(&buf, c); + } + } + cxmutstr ret; + if (all_printable) { + // don't copy the string when we don't need to escape anything + ret = cx_mutstrn((char*)str.ptr, str.length); + } else { + ret = cx_mutstrn(buf.space, buf.size); + } + cxBufferDestroy(&buf); + return ret; +} + +cxmutstr ui_escape_string(cxstring str) { + return escape_string(str, FALSE); +} diff --git a/ui/common/utils.h b/ui/common/utils.h index 592ad4b..94f3e89 100644 --- a/ui/common/utils.h +++ b/ui/common/utils.h @@ -31,6 +31,8 @@ #include "../ui/toolkit.h" #include "../ui/text.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -43,6 +45,8 @@ UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* re */ void ui_get_window_default_width(int *width, int *height); +cxmutstr ui_escape_string(cxstring str); + #ifdef __cplusplus } diff --git a/ui/common/wrapper.h b/ui/common/wrapper.h index 6ce7c9e..37f2f38 100644 --- a/ui/common/wrapper.h +++ b/ui/common/wrapper.h @@ -30,7 +30,7 @@ #define UIC_WRAPPER_H #include "../ui/toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #ifdef __cplusplus extern "C" { diff --git a/ui/gtk/button.c b/ui/gtk/button.c index 6db5141..cf911cf 100644 --- a/ui/gtk/button.c +++ b/ui/gtk/button.c @@ -106,7 +106,7 @@ GtkWidget* ui_create_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); ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); @@ -174,9 +174,9 @@ static void ui_toggled_callback(GtkToggleButton *widget, UiEventData *event) { static void ui_togglebutton_enable_state_callback(GtkToggleButton *widget, UiEventData *event) { if(gtk_toggle_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -308,9 +308,9 @@ static UIWIDGET togglebutton_create(UiObject *obj, GtkWidget *widget, UiToggleAr args->value, args->onchange, args->onchangedata, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -351,9 +351,9 @@ static void ui_checkbox_callback(GtkCheckButton *widget, UiEventData *event) { static void ui_checkbox_enable_state(GtkCheckButton *widget, UiEventData *event) { if(gtk_check_button_get_active(widget)) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -370,10 +370,10 @@ UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { args->onchange, args->onchangedata, (ui_toggled_func)ui_checkbox_enable_state, - args->enable_group); + args->enable_state); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); @@ -419,7 +419,7 @@ static void switch_changed( UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { GtkWidget *widget = gtk_switch_new(); ui_set_name_and_style(widget, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, widget, args->groups); + ui_set_widget_states(obj->ctx, widget, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -541,7 +541,7 @@ UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args) { GtkWidget *rbutton = RADIOBUTTON_NEW(rg, args->label); ui_set_name_and_style(rbutton, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, rbutton, args->groups); + ui_set_widget_states(obj->ctx, rbutton, args->states); if(rgroup) { #if GTK_MAJOR_VERSION >= 4 if(rg) { @@ -895,7 +895,7 @@ UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) { } ui_set_name_and_style(button, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_states(obj->ctx, button, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, button, &layout); diff --git a/ui/gtk/container.c b/ui/gtk/container.c index 16f7c21..a8df7e8 100644 --- a/ui/gtk/container.c +++ b/ui/gtk/container.c @@ -112,6 +112,21 @@ GtkWidget* ui_subcontainer_create( return add; } +/* -------------------- Custom Container -------------------- */ + +void ui_custom_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLayout *layout) { + UiCustomContainer *custom = (UiCustomContainer*)ct; + custom->add(custom->obj, ct->widget, widget, custom->userdata); +} + +void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata) { + UiCustomContainer *container = cxZalloc(obj->ctx->allocator, sizeof(UiCustomContainer)); + container->container.add = ui_custom_container_add; + container->container.widget = widget; + container->add = add_child; + container->userdata = userdata; + uic_object_push_container(obj, (UiContainerX*)container); +} /* -------------------- Box Container -------------------- */ UiContainerX* ui_box_container(UiObject *obj, GtkWidget *box, UiSubContainerType type) { @@ -985,6 +1000,8 @@ void ui_headerbar_container_add(UiContainerPrivate *ct, GtkWidget *widget, UiLay GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); hb->centerbox = box; UI_HEADERBAR_SET_TITLE_WIDGET(ct->widget, box); + UI_HEADERBAR_SHOW_TITLE_WIDGET(ct->widget, TRUE); + UI_HEADERBAR_SETTINGS(ct->widget); } BOX_ADD(hb->centerbox, widget); } diff --git a/ui/gtk/container.h b/ui/gtk/container.h index 2f2510c..f224aad 100644 --- a/ui/gtk/container.h +++ b/ui/gtk/container.h @@ -61,6 +61,13 @@ struct UiContainerPrivate { int close; }; +typedef struct UiCustomContainer { + UiContainerPrivate container; + UiObject *obj; + ui_addwidget_func add; + void *userdata; +} UiCustomContainer; + typedef struct UiBoxContainer { UiContainerPrivate container; UiSubContainerType type; diff --git a/ui/gtk/entry.c b/ui/gtk/entry.c index b411191..9e61e4e 100644 --- a/ui/gtk/entry.c +++ b/ui/gtk/entry.c @@ -79,7 +79,7 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { #endif GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args->step); ui_set_name_and_style(spin, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, spin, args->groups); + ui_set_widget_states(obj->ctx, spin, args->states); if(args->width > 0) { gtk_widget_set_size_request(spin, args->width, -1); diff --git a/ui/gtk/graphics.c b/ui/gtk/graphics.c index 33fd186..3ae1025 100644 --- a/ui/gtk/graphics.c +++ b/ui/gtk/graphics.c @@ -107,14 +107,14 @@ UIWIDGET ui_drawingarea_create(UiObject *obj, UiDrawingAreaArgs *args) { widget, "draw", G_CALLBACK(draw_callback), - NULL); + drawingarea); #endif g_signal_connect( - widget, - "destroy", - G_CALLBACK(destroy_drawingarea), - drawingarea); + widget, + "destroy", + G_CALLBACK(destroy_drawingarea), + drawingarea); return widget; } diff --git a/ui/gtk/headerbar.c b/ui/gtk/headerbar.c index 0a9bd3e..ef1d9c7 100644 --- a/ui/gtk/headerbar.c +++ b/ui/gtk/headerbar.c @@ -164,7 +164,7 @@ void ui_add_headerbar_item( 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); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + 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"); headerbar_add(headerbar, box, button, pos); @@ -178,7 +178,7 @@ void ui_add_headerbar_toggleitem( enum UiToolbarPos pos) { GtkWidget *button = gtk_toggle_button_new(); - ui_set_widget_groups(obj->ctx, button, item->args.groups); + 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"); ui_setup_togglebutton(obj, button, item->args.label, item->args.icon, item->args.tooltip, item->args.varname, NULL, item->args.onchange, item->args.onchangedata, 0); diff --git a/ui/gtk/headerbar.h b/ui/gtk/headerbar.h index 93028ea..392eae7 100644 --- a/ui/gtk/headerbar.h +++ b/ui/gtk/headerbar.h @@ -46,15 +46,20 @@ extern "C" { #define UI_HEADERBAR_PACK_START(h, w) adw_header_bar_pack_start(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_PACK_END(h, w) adw_header_bar_pack_end(ADW_HEADER_BAR(h), w) #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) adw_header_bar_set_title_widget(ADW_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) adw_header_bar_set_show_title(ADW_HEADER_BAR(h), b) +#define UI_HEADERBAR_SETTINGS(h) adw_header_bar_set_centering_policy(ADW_HEADER_BAR(h), ADW_CENTERING_POLICY_LOOSE) #else #define UI_HEADERBAR GtkHeaderBar* #define UI_HEADERBAR_CAST(h) GTK_HEADER_BAR(h) #define UI_HEADERBAR_PACK_START(h, w) gtk_header_bar_pack_start(GTK_HEADER_BAR(h), w) #define UI_HEADERBAR_PACK_END(h, w) gtk_header_bar_pack_end(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SETTINGS(h) #if GTK_MAJOR_VERSION >= 4 #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_title_widget(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) gtk_header_bar_set_show_title(GTK_HEADER_BAR(h), b) #else #define UI_HEADERBAR_SET_TITLE_WIDGET(h, w) gtk_header_bar_set_custom_title(GTK_HEADER_BAR(h), w) +#define UI_HEADERBAR_SHOW_TITLE_WIDGET(h, b) gtk_header_bar_set_show_title(GTK_HEADER_BAR(h), b) #endif #endif diff --git a/ui/gtk/image.c b/ui/gtk/image.c index e60dbdd..8181126 100644 --- a/ui/gtk/image.c +++ b/ui/gtk/image.c @@ -30,6 +30,7 @@ #include "container.h" #include "menu.h" +#include "widget.h" #include "../common/context.h" #include "../common/object.h" @@ -67,8 +68,14 @@ UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs *args) { GtkWidget *drawingarea = gtk_drawing_area_new(); GtkWidget *toplevel; GtkWidget *widget = drawingarea; - - gtk_widget_set_size_request(drawingarea, 100, 100); + + int width = args->width; + int height = args->height; + if(width == 0 && height == 0) { + width = 100; + height = 100; + } + ui_widget_size_request(drawingarea, width, height); #if GTK_MAJOR_VERSION < 4 GtkWidget *eventbox = gtk_event_box_new(); diff --git a/ui/gtk/list.c b/ui/gtk/list.c index 8e2d217..7a08ffc 100644 --- a/ui/gtk/list.c +++ b/ui/gtk/list.c @@ -80,6 +80,7 @@ static UiListView* create_listview(UiObject *obj, UiListArgs *args) { memset(tableview, 0, sizeof(UiListView)); tableview->obj = obj; tableview->model = args->model; + tableview->multiselection = args->multiselection; tableview->onactivate = args->onactivate; tableview->onactivatedata = args->onactivatedata; tableview->onselection = args->onselection; @@ -98,6 +99,11 @@ static UiListView* create_listview(UiObject *obj, UiListArgs *args) { tableview->onsave = args->onsave; tableview->onsavedata = args->onsavedata; +#if GTK_CHECK_VERSION(4, 0, 0) + tableview->coldata.listview = tableview; + tableview->coldata.column = 0; +#endif + if(args->getvalue2) { tableview->getvalue = args->getvalue2; tableview->getvaluedata = args->getvalue2data; @@ -200,7 +206,7 @@ static void cell_entry_activate( static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *image = gtk_image_new(); @@ -281,16 +287,17 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g UiColData *col = userdata; UiList *list = col->listview->var ? col->listview->var->value : NULL; UiListView *listview = col->listview; + int datacolumn = listview->columns[col->column]; ObjWrapper *obj = gtk_list_item_get_item(item); UiModel *model = col->listview->model; - UiModelType type = model->types[col->model_column]; + UiModelType type = model->types[col->column]; // cache the GtkListItem CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - if(row->items[col->model_column] == NULL) { + if(row->items[col->column] == NULL) { row->bound++; } } else { @@ -298,10 +305,10 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g cxMapPut(listview->bound_rows, row_key, row); row->bound = 1; } - row->items[col->model_column] = item; + row->items[col->column] = item; UiBool freevalue = FALSE; - void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); + void *data = listview->getvalue(list, obj->data, obj->i, datacolumn, listview->getvaluedata, &freevalue); GtkWidget *child = gtk_list_item_get_child(item); PangoAttrList *attributes = NULL; @@ -319,7 +326,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } } - int style_col = col->data_column; + int style_col = datacolumn; if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { style_col++; // col->data_column is the icon, we need the next col for the label } @@ -363,7 +370,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } case UI_ICON_TEXT_FREE: { - void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue); + void *data2 = listview->getvalue(list, obj->data, obj->i, datacolumn+1, listview->getvaluedata, &freevalue); if(type == UI_ICON_TEXT_FREE) { freevalue = TRUE; } @@ -387,7 +394,7 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g if(entry) { entry->listview = col->listview; entry->row = obj->i; - entry->col = col->data_column; + entry->col = datacolumn; entry->previous_value = strdup(data); } ENTRY_SET_TEXT(child, data); @@ -405,13 +412,13 @@ static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, g } } -static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { +static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { ObjWrapper *obj = gtk_list_item_get_item(item); UiListView *listview = col->listview; CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); UiRowItems *row = cxMapGet(listview->bound_rows, row_key); if(row) { - row->items[col->model_column] = NULL; + row->items[col->column] = NULL; row->bound--; if(row->bound == 0) { cxMapRemove(listview->bound_rows, row_key); @@ -457,17 +464,16 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); + g_signal_connect(factory, "unbind", G_CALLBACK(column_factory_unbind), &listview->coldata); GtkSelectionModel *selection_model = create_selection_model(listview, ls, args->multiselection); GtkWidget *view = gtk_list_view_new(GTK_SELECTION_MODEL(selection_model), factory); @@ -540,7 +546,7 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { return scroll_area; } -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { // to simplify things and share code with ui_tableview_create, we also // use a UiModel for the listview UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); @@ -554,17 +560,15 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { } listview->numcolumns = 1; - listview->columns = malloc(sizeof(UiColData)); - listview->columns->listview = listview; - listview->columns->data_column = 0; - listview->columns->model_column = 0; + listview->columns = malloc(sizeof(int)); + listview->columns[0] = 0; listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), &listview->coldata); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), &listview->coldata); GtkWidget *view = gtk_drop_down_new(G_LIST_MODEL(ls), NULL); gtk_drop_down_set_factory(GTK_DROP_DOWN(view), factory); @@ -591,8 +595,8 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { list->obj = listview; list->update = ui_listview_update2; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; ui_update_liststore(ls, list); } else if (args->static_elements && args->static_nelm > 0) { @@ -619,10 +623,34 @@ void ui_listview_select(UIWIDGET listview, int index) { gtk_selection_model_select_item(model, index, TRUE); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_drop_down_set_selected(GTK_DROP_DOWN(dropdown), index); } +static void add_column(UiListView *tableview, int index) { + UiModel *model = tableview->model; + + UiColData *col = malloc(sizeof(UiColData)); + col->listview = tableview; + col->column = index; + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); + g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); + g_object_set_data_full(G_OBJECT(factory), "coldata", col, (GDestroyNotify)free); + + GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[index], factory); + gtk_column_view_column_set_resizable(column, true); + gtk_column_view_insert_column(GTK_COLUMN_VIEW(tableview->widget), index, column); + + int size = model->columnsize[index]; + if(size > 0) { + gtk_column_view_column_set_fixed_width(column, size); + } else if(size < 0) { + gtk_column_view_column_set_expand(column, TRUE); + } +} + UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { GListStore *ls = g_list_store_new(G_TYPE_OBJECT); //g_list_store_append(ls, v1); @@ -650,9 +678,13 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { // create columns from UiModel UiModel *model = args->model; - int columns = model ? model->columns : 0; + int columns = 0; + if(model) { + columns = model->columns; + ui_model_add_observer(model, ui_listview_update_model, tableview); + } - tableview->columns = calloc(columns, sizeof(UiColData)); + tableview->columns = calloc(columns, sizeof(int)); tableview->numcolumns = columns; tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); @@ -660,30 +692,14 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { int addi = 0; for(int i=0;icolumns[i].listview = tableview; - tableview->columns[i].model_column = i; - tableview->columns[i].data_column = i+addi; + tableview->columns[i] = i+addi; if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { // icon+text has 2 data columns addi++; } - GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); - UiColData *col = &tableview->columns[i]; - g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), col); - g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), col); - - GtkColumnViewColumn *column = gtk_column_view_column_new(model->titles[i], factory); - gtk_column_view_column_set_resizable(column, true); - gtk_column_view_append_column(GTK_COLUMN_VIEW(view), column); - - int size = model->columnsize[i]; - if(size > 0) { - gtk_column_view_column_set_fixed_width(column, size); - } else if(size < 0) { - gtk_column_view_column_set_expand(column, TRUE); - } + add_column(tableview, i); } // bind listview to list @@ -734,6 +750,49 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { return scroll_area; } +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index) { + UiListView *listview = userdata; + if(insert_index >= listview->numcolumns) { + listview->numcolumns = insert_index+1; + listview->columns = realloc(listview->columns, listview->numcolumns * sizeof(UiColData)); + } + + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), NULL); + cxMapClear(listview->bound_rows); + + if(insert_index) { + int prev = 0; + if(insert_index > 0) { + prev = listview->columns[insert_index-1]; + } + listview->columns[insert_index] = prev+1; + add_column(listview, insert_index); + + if(insert_index+1 < listview->numcolumns) { + // the data index of trailing columns must be adjusted + UiModelType type = model->types[insert_index]; + int add = 1; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + add++; + } + for(int i=insert_index+1;inumcolumns;i++) { + listview->columns[i] += add; + } + } + } // TODO: delete_index + + GListStore *ls = g_list_store_new(G_TYPE_OBJECT); + GtkSelectionModel *selection_model = create_selection_model(listview, ls, listview->multiselection); + gtk_column_view_set_model(GTK_COLUMN_VIEW(listview->widget), selection_model); + listview->selectionmodel = selection_model; + listview->liststore = ls; + + if(listview->var) { + UiList *list = listview->var->value; + ui_list_update(list); + } +} + static UiListSelection selectionmodel_get_selection(GtkSelectionModel *model) { UiListSelection sel = { 0, NULL }; GtkBitset *bitset = gtk_selection_model_get_selection(model); @@ -862,6 +921,7 @@ void ui_listview_update2(UiList *list, int i) { UiListView *view = list->obj; view->current_row = -1; if(i < 0) { + cxMapClear(view->bound_rows); ui_update_liststore(view->liststore, list); } else { void *value = list->get(list, i); @@ -874,9 +934,12 @@ void ui_listview_update2(UiList *list, int i) { CxHashKey row_key = cx_hash_key(&i, sizeof(int)); UiRowItems *row = cxMapGet(view->bound_rows, row_key); if(row) { + UiColData coldata; + coldata.listview = view; for(int c=0;cnumcolumns;c++) { if(row->items[c] != NULL) { - column_factory_bind(NULL, row->items[c], &view->columns[c]); + coldata.column = c; + column_factory_bind(NULL, row->items[c], &coldata); } } } @@ -916,7 +979,7 @@ void ui_listview_setselection2(UiList *list, UiListSelection selection) { ui_setop_enable(FALSE); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *view = list->obj; guint selection = gtk_drop_down_get_selected(GTK_DROP_DOWN(view->widget)); UiListSelection sel = { 0, NULL }; @@ -928,7 +991,7 @@ UiListSelection ui_combobox_getselection(UiList *list) { return sel; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *view = list->obj; if(selection.count > 0) { @@ -1157,7 +1220,7 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { // create treeview GtkWidget *view = gtk_tree_view_new(); ui_set_name_and_style(view, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, view, args->groups); + ui_set_widget_states(obj->ctx, view, args->states); GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); @@ -1273,7 +1336,7 @@ void ui_listview_select(UIWIDGET listview, int index) { //g_object_unref(path); } -void ui_combobox_select(UIWIDGET dropdown, int index) { +void ui_dropdown_select(UIWIDGET dropdown, int index) { gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), index); } @@ -1515,16 +1578,16 @@ void ui_listview_setselection(UiList *list, UiListSelection selection) { -/* --------------------------- ComboBox --------------------------- */ +/* --------------------------- Dropdown --------------------------- */ -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { GtkWidget *combobox = gtk_combo_box_new(); if(args->width > 0) { gtk_widget_set_size_request(combobox, args->width, -1); } ui_set_name_and_style(combobox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, combobox, args->groups); + ui_set_widget_states(obj->ctx, combobox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, combobox, &layout); @@ -1545,8 +1608,8 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { if(var) { listview->var = var; list->update = ui_combobox_modelupdate; - list->getselection = ui_combobox_getselection; - list->setselection = ui_combobox_setselection; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; list->obj = listview; list->update(list, -1); } else if(args->static_nelm > 0) { @@ -1623,7 +1686,7 @@ void ui_combobox_modelupdate(UiList *list, int i) { g_object_unref(store); } -UiListSelection ui_combobox_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection(UiList *list) { UiListView *combobox = list->obj; UiListSelection ret; ret.rows = malloc(sizeof(int*)); @@ -1632,7 +1695,7 @@ UiListSelection ui_combobox_getselection(UiList *list) { return ret; } -void ui_combobox_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { ui_setop_enable(TRUE); UiListView *combobox = list->obj; if(selection.count > 0) { @@ -2046,6 +2109,10 @@ void ui_listview_destroy(GtkWidget *w, UiListView *v) { if(v->var) { ui_destroy_boundvar(v->obj->ctx, v->var); } + if(v->model) { + ui_model_remove_observer(v->model, v); + ui_model_unref(v->model); + } if(v->elements) { for(int i=0;inelm;i++) { free(v->elements[i]); @@ -2184,7 +2251,7 @@ UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) { SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); ui_set_name_and_style(listbox, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, listbox, args->groups); + ui_set_widget_states(obj->ctx, listbox, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, scroll_area, &layout); diff --git a/ui/gtk/list.h b/ui/gtk/list.h index 635ac58..ef828d4 100644 --- a/ui/gtk/list.h +++ b/ui/gtk/list.h @@ -26,10 +26,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TREE_H -#define TREE_H +#ifndef LIST_H +#define LIST_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include @@ -38,6 +38,7 @@ extern "C" { #endif +typedef struct UiListView UiListView; typedef struct UiColData UiColData; #if GTK_CHECK_VERSION(4, 10, 0) @@ -47,11 +48,17 @@ typedef struct UiRowItems { } UiRowItems; #endif -typedef struct UiListView { +struct UiColData { + UiListView *listview; + int column; +}; + +struct UiListView { UiObject *obj; GtkWidget *widget; UiVar *var; UiModel *model; + UiBool multiselection; ui_getvaluefunc2 getvalue; void *getvaluedata; ui_getstylefunc getstyle; @@ -65,8 +72,9 @@ typedef struct UiListView { CxMap *bound_rows; GListStore *liststore; GtkSelectionModel *selectionmodel; - UiColData *columns; + int *columns; int numcolumns; + UiColData coldata; PangoAttrList *current_row_attributes; #else int style_offset; @@ -85,12 +93,6 @@ typedef struct UiListView { void *onsavedata; UiListSelection selection; -} UiListView; - -struct UiColData { - UiListView *listview; - int model_column; - int data_column; }; typedef struct UiTreeEventData { @@ -136,6 +138,7 @@ struct UiListBox { void ui_update_liststore(GListStore *liststore, UiList *list); void ui_update_liststore_static(GListStore *liststore, char **elm, size_t nelm); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update2(UiList *list, int i); UiListSelection ui_listview_getselection2(UiList *list); void ui_listview_setselection2(UiList *list, UiListSelection selection); @@ -155,6 +158,7 @@ UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks GtkWidget* ui_get_tree_widget(UIWIDGET widget); +void ui_listview_update_model(UiModel *model, void *userdata, int insert_index, int delete_index); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); void ui_listview_setselection(UiList *list, UiListSelection selection); @@ -186,8 +190,8 @@ UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata); void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); void ui_combobox_modelupdate(UiList *list, int i); -UiListSelection ui_combobox_getselection(UiList *list); -void ui_combobox_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); void ui_listbox_dynamic_update(UiList *list, int i); void ui_listbox_dynamic_setselection(UiList *list, UiListSelection sel); @@ -205,5 +209,5 @@ void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user } #endif -#endif /* TREE_H */ +#endif /* LIST_H */ diff --git a/ui/gtk/menu.c b/ui/gtk/menu.c index 74597ae..44d32ca 100644 --- a/ui/gtk/menu.c +++ b/ui/gtk/menu.c @@ -129,11 +129,11 @@ void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObje gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); - cxListFree(groups); + if(i->states) { + CxList *states = cxArrayListCreateSimple(sizeof(int), i->nstates); + cxListAddArray(states, i->states, i->nstates); + uic_add_state_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, states); + cxListFree(states); } } @@ -462,10 +462,10 @@ void ui_gmenu_add_menuitem(GMenu *parent, int index, UiMenuItemI *item, UiObject g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); g_object_unref(action); - if(i->groups) { - CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); - cxListAddArray(groups, i->groups, i->ngroups); - uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); + if(i->states) { + CxList *groups = cxArrayListCreateSimple(sizeof(int), i->nstates); + cxListAddArray(groups, i->states, i->nstates); + uic_add_state_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); cxListFree(groups); } diff --git a/ui/gtk/text.c b/ui/gtk/text.c index ccace09..cb52e4a 100644 --- a/ui/gtk/text.c +++ b/ui/gtk/text.c @@ -32,6 +32,7 @@ #include "text.h" #include "container.h" +#include "widget.h" #include @@ -53,9 +54,9 @@ static void selection_handler( int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end); if(sel != textview->last_selection_state) { if(sel) { - ui_set_group(textview->ctx, UI_GROUP_SELECTION); + ui_set_state(textview->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textview->ctx, UI_GROUP_SELECTION); + ui_unset_state(textview->ctx, UI_GROUP_SELECTION); } } textview->last_selection_state = sel; @@ -112,7 +113,7 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { GtkWidget *text_area = gtk_text_view_new(); ui_set_name_and_style(text_area, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, text_area, args->groups); + ui_set_widget_states(obj->ctx, text_area, args->states); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_area), GTK_WRAP_WORD_CHAR); g_signal_connect( @@ -144,17 +145,7 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area); - if(args->width > 0 || args->height > 0) { - int width = args->width; - int height = args->height; - if(width == 0) { - width = -1; - } - if(height == 0) { - height = -1; - } - gtk_widget_set_size_request(scroll_area, width, height); - } + ui_widget_size_request(scroll_area, args->width, args->height); // font and padding //PangoFontDescription *font; @@ -612,7 +603,7 @@ void ui_text_redo(UiText *value) { static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs *args) { GtkWidget *textfield = gtk_entry_new(); ui_set_name_and_style(textfield, args->name, args->style_class); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_states(obj->ctx, textfield, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); @@ -966,6 +957,10 @@ UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { pathtf->stack = gtk_stack_new(); gtk_widget_set_name(pathtf->stack, "path-textfield-box"); + if(args->width > 0) { + gtk_widget_set_size_request(pathtf->stack, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, pathtf->stack, &layout); @@ -1137,6 +1132,10 @@ UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { G_CALLBACK(ui_path_textfield_destroy), pathtf); + if(args->width > 0) { + gtk_widget_set_size_request(eventbox, args->width, -1); + } + UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, eventbox, &layout); diff --git a/ui/gtk/toolbar.c b/ui/gtk/toolbar.c index 64b221a..0e195ff 100644 --- a/ui/gtk/toolbar.c +++ b/ui/gtk/toolbar.c @@ -139,7 +139,7 @@ void add_toolitem_widget(GtkToolbar *tb, UiToolbarItem *item, UiObject *obj) { } gtk_tool_item_set_is_important(button, TRUE); - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); if(item->args.onclick) { UiEventData *event = cxMalloc( @@ -181,7 +181,7 @@ void add_toolitem_toggle_widget(GtkToolbar *tb, UiToolbarToggleItem *item, UiObj if(item->args.tooltip) { gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(button), item->args.tooltip); } - ui_set_widget_ngroups(obj->ctx, GTK_WIDGET(button), item->args.groups, item->ngroups); + ui_set_widget_nstates(obj->ctx, GTK_WIDGET(button), item->args.states, item->nstates); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER); if(var) { @@ -394,7 +394,7 @@ void add_headerbar_item_widget(GtkHeaderBar *hb, UiToolbarItem *item, UiObject * if(item->args.icon) { ui_button_set_icon_name(button, item->args.icon); } - ui_set_widget_groups(obj->ctx, button, item->args.groups); + ui_set_widget_states(obj->ctx, button, item->args.states); gtk_header_bar_pack_start(hb, button); diff --git a/ui/gtk/toolkit.c b/ui/gtk/toolkit.c index 3c5557d..e0a30c2 100644 --- a/ui/gtk/toolkit.c +++ b/ui/gtk/toolkit.c @@ -39,6 +39,7 @@ #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/threadpool.h" +#include "../common/app.h" #include #include @@ -51,13 +52,6 @@ UI_APPLICATION app; static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -void *open_data; -static ui_callback exit_func; -void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -95,30 +89,13 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } #ifdef UI_APPLICATION static void app_startup(GtkApplication* app, gpointer userdata) { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); } static void app_activate(GtkApplication* app, gpointer userdata) { @@ -126,9 +103,7 @@ static void app_activate(GtkApplication* app, gpointer userdata) { } static void app_shutdown(GtkApplication *app, gpointer userdata) { - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); } @@ -148,13 +123,9 @@ void ui_main() { free(appid.ptr); #else - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); gtk_main(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); ui_app_save_settings(); #endif if(exit_on_shutdown) { @@ -175,7 +146,7 @@ GtkApplication* ui_get_application() { void ui_show(UiObject *obj) { gboolean visible = gtk_widget_is_visible(obj->widget); - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); #if GTK_MAJOR_VERSION >= 4 gtk_window_present(GTK_WINDOW(obj->widget)); #elif GTK_MAJOR_VERSION <= 3 @@ -268,9 +239,9 @@ void ui_set_visible(UIWIDGET widget, UiBool visible) { gtk_widget_set_visible(widget, visible); #else if(visible) { - gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_show_all(widget); } else { + gtk_widget_set_no_show_all(widget, FALSE); gtk_widget_hide(widget); } #endif @@ -534,17 +505,17 @@ void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *styl } } -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups) { - if(!groups) { +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states) { + if(!states) { return; } - size_t ngroups = uic_group_array_size(groups); - ui_set_widget_ngroups(ctx, widget, groups, ngroups); + size_t nstates = uic_state_array_size(states); + ui_set_widget_nstates(ctx, widget, states, nstates); } -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups) { - if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates) { + if(nstates > 0) { + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, states, nstates); ui_set_enabled(widget, FALSE); } } @@ -553,14 +524,14 @@ void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const in if(!states) { return; } - size_t nstates = uic_group_array_size(states); + size_t nstates = uic_state_array_size(states); ui_set_widget_nvisibility_states(ctx, widget, states, nstates); } void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_visible, states, ngroups); ui_set_visible(widget, FALSE); } } diff --git a/ui/gtk/toolkit.h b/ui/gtk/toolkit.h index cfa25bb..8d1ee57 100644 --- a/ui/gtk/toolkit.h +++ b/ui/gtk/toolkit.h @@ -178,8 +178,8 @@ GtkApplication* ui_get_application(); int ui_get_scalefactor(); void ui_set_name_and_style(GtkWidget *widget, const char *name, const char *style); -void ui_set_widget_groups(UiContext *ctx, GtkWidget *widget, const int *groups); -void ui_set_widget_ngroups(UiContext *ctx, GtkWidget *widget, const int *groups, size_t ngroups); +void ui_set_widget_states(UiContext *ctx, GtkWidget *widget, const int *states); +void ui_set_widget_nstates(UiContext *ctx, GtkWidget *widget, const int *states, size_t nstates); void ui_set_widget_visibility_states(UiContext *ctx, GtkWidget *widget, const int *states); void ui_set_widget_nvisibility_states(UiContext *ctx, GtkWidget *widget, const int *states, size_t ngroups); diff --git a/ui/gtk/webview.c b/ui/gtk/webview.c index f8f5718..6a1f319 100644 --- a/ui/gtk/webview.c +++ b/ui/gtk/webview.c @@ -30,6 +30,7 @@ #include "container.h" #include "webview.h" +#include "widget.h" #ifdef UI_WEBVIEW @@ -38,6 +39,8 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { ui_set_name_and_style(webview, args->name, args->style_class); + ui_widget_size_request(webview, args->width, args->height); + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); if(var) { WebViewData *data = malloc(sizeof(WebViewData)); @@ -57,7 +60,7 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { } } - ui_set_widget_groups(obj->ctx, webview, args->groups); + ui_set_widget_states(obj->ctx, webview, args->states); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; UiLayout layout = UI_ARGS2LAYOUT(args); ct->add(ct, webview, &layout); diff --git a/ui/gtk/widget.c b/ui/gtk/widget.c index 06ef1b2..95cbb12 100644 --- a/ui/gtk/widget.c +++ b/ui/gtk/widget.c @@ -31,7 +31,20 @@ #include "../common/object.h" -UIEXPORT UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { +void ui_widget_size_request(UIWIDGET w, int width, int height) { + if(width > 0 || height > 0) { + if(width == 0) { + width = -1; + } + if(height == 0) { + height = -1; + } + gtk_widget_set_size_request(w, width, height); + } +} + + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { UIWIDGET widget = create_widget(obj, args, userdata); UiContainerPrivate *ct = (UiContainerPrivate*)obj->container_end; diff --git a/ui/gtk/widget.h b/ui/gtk/widget.h index 57c7314..7ee6ed4 100644 --- a/ui/gtk/widget.h +++ b/ui/gtk/widget.h @@ -35,7 +35,12 @@ extern "C" { #endif - +/* + * Sets a widget width/height. + * + * If wdith or height is 0, the dimension is not changed + */ +void ui_widget_size_request(UIWIDGET w, int width, int height); #ifdef __cplusplus diff --git a/ui/gtk/window.c b/ui/gtk/window.c index 4b103c3..2081de2 100644 --- a/ui/gtk/window.c +++ b/ui/gtk/window.c @@ -142,7 +142,7 @@ static void save_window_splitview_pos(GtkWidget *widget, void *unused) { ui_set_property("ui.window.splitview.pos", buf); } -static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) { +static UiObject* create_window(const char *title, UiBool sidebar, UiBool splitview, UiBool simple) { UiObject *obj = uic_object_new_toplevel(); #ifdef UI_LIBADWAITA @@ -153,8 +153,6 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side obj->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); #endif - obj->window = window_data; - #if GTK_CHECK_VERSION(4, 0, 0) obj->ctx->action_map = G_ACTION_MAP(obj->widget); #endif @@ -197,6 +195,21 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side obj); #endif + int splitview_pos = 0; + if(splitview) { + const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); + splitview_pos= splitview_window_default_pos; + if(splitview_pos < 0) { + splitview_pos = window_width / 2; + } + if(splitview_pos_str && splitview_window_use_prop) { + int sv_pos = atoi(splitview_pos_str); + if(sv_pos > 0) { + splitview_pos = sv_pos; + } + } + } + GtkWidget *vbox = ui_gtk_vbox_new(0); #ifdef UI_LIBADWAITA GtkWidget *toolbar_view = adw_toolbar_view_new(); @@ -215,18 +228,7 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side G_CALLBACK(save_window_splitview_pos), NULL); - const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); - int pos = splitview_window_default_pos; - if(pos < 0) { - pos = window_width / 2; - } - if(splitview_pos_str && splitview_window_use_prop) { - int splitview_pos = atoi(splitview_pos_str); - if(splitview_pos > 0) { - pos = splitview_pos; - } - } - gtk_paned_set_position(GTK_PANED(content), pos); + gtk_paned_set_position(GTK_PANED(content), splitview_pos); GtkWidget *right_panel = adw_toolbar_view_new(); GtkWidget *right_vbox = ui_gtk_vbox_new(0); @@ -348,6 +350,7 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side if(splitview) { GtkWidget *content_paned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_position(GTK_PANED(content_paned), splitview_pos); gtk_paned_add2(GTK_PANED(paned), content_paned); GtkWidget *right_content_box = ui_gtk_vbox_new(0); @@ -389,20 +392,20 @@ static UiObject* create_window(const char *title, void *window_data, UiBool side } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE, FALSE, FALSE); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE, FALSE, FALSE); +UiObject *ui_sidebar_window(const char *title) { + return create_window(title, TRUE, FALSE, FALSE); } -UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) { - return create_window(title, NULL, sidebar, TRUE, FALSE); +UiObject *ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, sidebar, TRUE, FALSE); } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE, TRUE); +UiObject* ui_simple_window(const char *title) { + return create_window(title, FALSE, FALSE, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { diff --git a/ui/motif/button.c b/ui/motif/button.c index 6393e76..54b5ed2 100644 --- a/ui/motif/button.c +++ b/ui/motif/button.c @@ -61,7 +61,7 @@ UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); if(args->onclick) { UiEventData *eventdata = malloc(sizeof(UiEventData)); @@ -118,9 +118,9 @@ UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -146,9 +146,9 @@ UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); - ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_group); + ui_bind_togglebutton(obj, button, args->varname, args->value, args->onchange, args->onchangedata, args->enable_state); XmStringFree(label); return button; @@ -162,9 +162,9 @@ static void togglebutton_changed(Widget w, UiVarEventData *event, XmToggleButton if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -249,9 +249,9 @@ static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonC if(event->value > 0) { // button in configured to enable/disable states if(tb->set) { - ui_set_group(event->obj->ctx, event->value); + ui_set_state(event->obj->ctx, event->value); } else { - ui_unset_group(event->obj->ctx, event->value); + ui_unset_state(event->obj->ctx, event->value); } } @@ -365,7 +365,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { XtManageChild(button); ui_container_add(ctn, button); - ui_set_widget_groups(obj->ctx, button, args->groups); + ui_set_widget_groups(obj->ctx, button, args->states); UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER); if(var) { @@ -402,7 +402,7 @@ UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args) { event->userdata = args->onchangedata; event->observers = NULL; event->var = var; - event->value = args->enable_group; + event->value = args->enable_state; XtAddCallback( button, XmNvalueChangedCallback, diff --git a/ui/motif/entry.c b/ui/motif/entry.c index d21a400..e8ce311 100644 --- a/ui/motif/entry.c +++ b/ui/motif/entry.c @@ -86,7 +86,7 @@ UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { XtManageChild(spinbox); ui_container_add(ctn, spinbox); - ui_set_widget_groups(obj->ctx, spinbox, args->groups); + ui_set_widget_groups(obj->ctx, spinbox, args->states); WidgetList children; Cardinal num_children; diff --git a/ui/motif/list.c b/ui/motif/list.c index b1e96ee..619b48c 100644 --- a/ui/motif/list.c +++ b/ui/motif/list.c @@ -95,6 +95,8 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { listview->onselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; @@ -102,6 +104,23 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { list->getselection = ui_listview_getselection; list->setselection = ui_listview_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -118,19 +137,36 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { XtAddCallback( widget, XmNextendedSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); XtAddCallback( widget, XmNsingleSelectionCallback, - (XtCallbackProc)ui_listview_selection, + (XtCallbackProc)ui_listview_selection_changed, listview); return widget; } +void ui_listview_select(UIWIDGET listview, int index) { + XmListDeselectAllItems(listview); + XmListSelectPos(listview, index+1, False); +} + +int ui_listview_selection(UIWIDGET listview) { + int *selpositions = NULL; + int numpos = 0; + XtVaGetValues(listview, XmNselectedPositions, &selpositions, XmNselectedPositionCount, &numpos, NULL); + return numpos > 0 ? selpositions[0] : -1; +} + void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d) { - // TODO + ui_listselection_free(listview->current_selection); + if(listview->model) { + ui_model_remove_observer(listview->model, listview); + ui_model_unref(listview->model); + } + free(listview); } static void list_callback(UiObject *obj, UiListSelection sel, ui_callback callback, void *userdata) { @@ -167,7 +203,7 @@ void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct * } } -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb) { +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb) { listview_save_selection(listview, cb); if(listview->onselection) { list_callback(listview->obj, listview->current_selection, listview->onselection, listview->onselectiondata); @@ -249,7 +285,7 @@ void* ui_strmodel_getvalue(void *elm, int column) { /* ------------------------------- Drop Down ------------------------------- */ -static void ui_dropdown_selection( +static void ui_dropdown_selection_changed( Widget w, UiListView *listview, XmComboBoxCallbackStruct *cb) @@ -276,7 +312,7 @@ static void ui_dropdown_selection( } } -UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject* obj, UiListArgs *args) { Arg xargs[16]; int n = 0; @@ -311,6 +347,8 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { listview->onselection = args->onselection; listview->onselectiondata = args->onselectiondata; + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; if(var) { UiList *list = var->value; list->obj = listview; @@ -318,6 +356,23 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { list->getselection = ui_dropdown_getselection; list->setselection = ui_dropdown_setselection; ui_listview_update(list, 0); + } else if(static_elements && static_nelm > 0) { + XmStringTable items = calloc(static_nelm, sizeof(XmString)); + for(int i=0;iwidget, + XmNitems, items, + XmNitemCount, + static_nelm, + NULL); + for (int i=0;igetvalue = getvalue_wrapper; + listview->getvaluedata = ui_strmodel_getvalue; } XtAddCallback( @@ -328,7 +383,7 @@ UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) { XtAddCallback( widget, XmNselectionCallback, - (XtCallbackProc)ui_dropdown_selection, + (XtCallbackProc)ui_dropdown_selection_changed, listview); return widget; @@ -355,3 +410,12 @@ UiListSelection ui_dropdown_getselection(UiList *list) { } return sel; } + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + XtVaSetValues(dropdown, XmNselectedPosition, index, NULL); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + int pos = -1; + XtVaGetValues(dropdown, XmNselectedPosition, &pos, NULL); +} diff --git a/ui/motif/list.h b/ui/motif/list.h index 04756a2..328f6dc 100644 --- a/ui/motif/list.h +++ b/ui/motif/list.h @@ -30,7 +30,7 @@ #define LIST_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #ifdef __cplusplus @@ -63,7 +63,7 @@ typedef struct UiListView { void ui_listview_destroy(Widget w, UiListView *listview, XtPointer d); void ui_listview_activate(Widget w, UiListView *listview, XmListCallbackStruct *cb); -void ui_listview_selection(Widget w, UiListView *listview, XmListCallbackStruct *cb); +void ui_listview_selection_changed(Widget w, UiListView *listview, XmListCallbackStruct *cb); void ui_listview_update(UiList *list, int i); UiListSelection ui_listview_getselection(UiList *list); diff --git a/ui/motif/menu.c b/ui/motif/menu.c index f3e1f41..b6f68ba 100644 --- a/ui/motif/menu.c +++ b/ui/motif/menu.c @@ -33,7 +33,6 @@ #include "menu.h" #include "button.h" #include "toolkit.h" -#include "stock.h" #include "container.h" #include "../common/context.h" #include "../common/menu.h" @@ -150,7 +149,7 @@ void add_menuitem_widget(Widget parent, int i, UiMenuItemI *item, UiObject *obj) eventdata); } - ui_set_widget_groups(obj->ctx, mitem, it->groups); + ui_set_widget_groups(obj->ctx, mitem, it->states); } void add_menuseparator_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { @@ -182,7 +181,7 @@ void add_checkitem_widget(Widget p, int i, UiMenuItemI *item, UiObject *obj) { ui_bind_togglebutton(obj, checkbox, it->varname, NULL, it->callback, it->userdata, 0); - ui_set_widget_groups(obj->ctx, checkbox, it->groups); + ui_set_widget_groups(obj->ctx, checkbox, it->states); } void add_radioitem_widget(Widget p, int index, UiMenuItemI *item, UiObject *obj) { diff --git a/ui/motif/objs.mk b/ui/motif/objs.mk index 8d5d0fd..d4d2dd6 100644 --- a/ui/motif/objs.mk +++ b/ui/motif/objs.mk @@ -30,7 +30,6 @@ MOTIF_SRC_DIR = ui/motif/ MOTIF_OBJPRE = $(OBJ_DIR)$(MOTIF_SRC_DIR) MOTIFOBJ = toolkit.o -MOTIFOBJ += stock.o MOTIFOBJ += window.o MOTIFOBJ += widget.o MOTIFOBJ += container.o diff --git a/ui/motif/text.c b/ui/motif/text.c index 52f4f16..b2c5389 100644 --- a/ui/motif/text.c +++ b/ui/motif/text.c @@ -213,9 +213,9 @@ void ui_text_selection_callback( int sel = left < right ? 1 : 0; if(sel != textarea->last_selection_state) { if(sel) { - ui_set_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_set_state(textarea->obj->ctx, UI_GROUP_SELECTION); } else { - ui_unset_group(textarea->obj->ctx, UI_GROUP_SELECTION); + ui_unset_state(textarea->obj->ctx, UI_GROUP_SELECTION); } } textarea->last_selection_state = sel; @@ -408,7 +408,7 @@ static UIWIDGET create_textfield(UiObject *obj, UiTextFieldArgs *args, int frame XtManageChild(textfield); ui_container_add(ctn, textfield); - ui_set_widget_groups(obj->ctx, textfield, args->groups); + ui_set_widget_groups(obj->ctx, textfield, args->states); UiEventDataExt *eventdata = malloc(sizeof(UiEventDataExt)); memset(eventdata, 0, sizeof(UiEventDataExt)); diff --git a/ui/motif/toolbar.c b/ui/motif/toolbar.c index 2860f13..e9ca682 100644 --- a/ui/motif/toolbar.c +++ b/ui/motif/toolbar.c @@ -33,7 +33,6 @@ #include "toolbar.h" #include "button.h" -#include "stock.h" #include "list.h" #include diff --git a/ui/motif/toolkit.c b/ui/motif/toolkit.c index 17b4726..fe49df1 100644 --- a/ui/motif/toolkit.c +++ b/ui/motif/toolkit.c @@ -34,11 +34,11 @@ #include "toolkit.h" #include "toolbar.h" #include "container.h" -#include "stock.h" #include "../common/menu.h" #include "../common/toolbar.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/app.h" #include #include @@ -49,13 +49,6 @@ static Display *display; static Widget active_window; static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static ui_callback appclose_fnc; static void *appclose_udata; @@ -134,33 +127,14 @@ Display* ui_motif_get_display() { return display; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); XtAppMainLoop(app); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); if(exit_on_shutdown) { exit(0); @@ -180,7 +154,7 @@ void ui_secondary_event_loop(int *loop) { } void ui_show(UiObject *obj) { - uic_check_group_widgets(obj->ctx); + uic_check_state_widgets(obj->ctx); if(!XtIsRealized(obj->widget)) { XtRealizeWidget(obj->widget); obj->ref++; @@ -357,13 +331,13 @@ void ui_set_widget_groups(UiContext *ctx, Widget widget, const int *groups) { if(!groups) { return; } - size_t ngroups = uic_group_array_size(groups); + size_t ngroups = uic_state_array_size(groups); ui_set_widget_ngroups(ctx, widget, groups, ngroups); } void ui_set_widget_ngroups(UiContext *ctx, Widget widget, const int *groups, size_t ngroups) { if(ngroups > 0) { - uic_add_group_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); + uic_add_state_widget_i(ctx, widget, (ui_enablefunc)ui_set_enabled, groups, ngroups); ui_set_enabled(widget, FALSE); } } diff --git a/ui/motif/window.c b/ui/motif/window.c index 9489f07..c8d5b1d 100644 --- a/ui/motif/window.c +++ b/ui/motif/window.c @@ -74,11 +74,10 @@ static void window_close_handler(Widget window, void *udata, void *cdata) { } -static UiObject* create_window(const char *title, void *window_data, Boolean simple) { +static UiObject* create_window(const char *title, Boolean simple) { CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->destroy = ui_window_widget_destroy; int window_width = window_default_width; @@ -151,12 +150,12 @@ static UiObject* create_window(const char *title, void *window_data, Boolean sim return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject* ui_window(const char *title) { + return create_window(title, FALSE); } -UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE); +UiObject* ui_simple_window(const char *title) { + return create_window(title, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { diff --git a/ui/qt/container.cpp b/ui/qt/container.cpp index 245ba19..36e5b46 100644 --- a/ui/qt/container.cpp +++ b/ui/qt/container.cpp @@ -82,7 +82,14 @@ void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { bool fill = layout.fill; if(hasStretchedWidget && fill) { fill = false; - fprintf(stderr, "UiError: container has 2 filled widgets"); + fprintf(stderr, "UiError: container has 2 filled widgets\n"); + } + + if(singleChild) { + fill = true; + if(hasChild) { + fprintf(stderr, "UiError: single child container already has a child\n"); + } } uic_layout_setup_margin(&layout); @@ -103,6 +110,7 @@ void UiBoxContainer::add(QWidget* widget, UiLayout& layout) { hasStretchedWidget = true; } current = widget; + hasChild = true; } UIWIDGET ui_box(UiObject *obj, UiContainerArgs *args, QBoxLayout::Direction dir) { @@ -239,6 +247,64 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { } +/* ------------------------------ Frame ------------------------------ */ + +UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { + UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + bool createBox = true; + bool singleChild = false; + QBoxLayout::Direction dir = QBoxLayout::TopToBottom; + QGroupBox *widget = new QGroupBox(); + if(args->label) { + widget->setTitle(args->label); + } + + switch(args->subcontainer) { + default: break; + case UI_CONTAINER_HBOX: { + dir = QBoxLayout::LeftToRight; + break; + } + case UI_CONTAINER_GRID: { + createBox = false; + break; + } + case UI_CONTAINER_NO_SUB: { + singleChild = true; + } + } + + if(createBox) { + QBoxLayout *box = new QBoxLayout(dir); + widget->setLayout(box); + + UiBoxContainer *container = new UiBoxContainer(box); + ui_obj_add_container(obj, container); + + container->singleChild = singleChild; + } else { + QGridLayout *grid = new QGridLayout(); + widget->setLayout(grid); + + ui_obj_add_container(obj, new UiGridContainer( + grid, + args->padding, + args->columnspacing, + args->rowspacing, + false, + false, + false, + false)); + } + + + ctn->add(widget, layout); + + return widget; +} + /* ---------------------------- UiSidebar ---------------------------- */ UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { diff --git a/ui/qt/container.h b/ui/qt/container.h index 867fc4c..0adf78a 100644 --- a/ui/qt/container.h +++ b/ui/qt/container.h @@ -39,6 +39,7 @@ #include #include #include +#include #define ui_obj_container(obj) (UiContainerPrivate*)((UiContainerX*)obj->container_end)->container @@ -54,6 +55,8 @@ class UiBoxContainer : public UiContainerPrivate { public: QBoxLayout *box; bool hasStretchedWidget = false; + bool singleChild = false; + bool hasChild = false; QSpacerItem *space; QBoxLayout::Direction direction; diff --git a/ui/qt/list.cpp b/ui/qt/list.cpp index 94d3426..a2bac52 100644 --- a/ui/qt/list.cpp +++ b/ui/qt/list.cpp @@ -32,6 +32,7 @@ #include #include #include +#include extern "C" void* ui_strmodel_getvalue(void *elm, int column) { return column == 0 ? elm : NULL; @@ -46,7 +47,7 @@ static void* null_getvalue(UiList *list, void *elm, int row, int col, void *user return NULL; } -UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QListView *view = new QListView(); @@ -92,7 +93,7 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) { return view; } -UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); QTreeView *view = new QTreeView(); @@ -141,3 +142,42 @@ UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) { return view; } + +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + + QComboBox *view = new QComboBox(); + UiLayout layout = UI_ARGS2LAYOUT(args); + ctn->add(view, layout); + + ui_getvaluefunc2 getvalue = nullptr; + void *getvaluedata = nullptr; + if(args->getvalue2) { + getvalue = args->getvalue2; + getvaluedata = args->getvalue2data; + } else if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = getvalue_wrapper; + getvaluedata = (void*)ui_strmodel_getvalue; + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + ListModel *model = new ListModel(obj, view, var, getvalue, getvaluedata); + view->setModel(model); + + if(var) { + UiList *list = (UiList*)var->value; + list->update = ui_listmodel_update; + list->getselection = ui_listmodel_getselection; + list->setselection = ui_listmodel_setselection; + list->obj = model; + } + + model->setActivationCallback(args->onactivate, args->onactivatedata); + model->setSelectionCallback(args->onselection, args->onselectiondata); + + return view; +} diff --git a/ui/qt/list.h b/ui/qt/list.h index 664b32b..8223214 100644 --- a/ui/qt/list.h +++ b/ui/qt/list.h @@ -29,7 +29,7 @@ #ifndef TREE_H #define TREE_H -#include "../ui/tree.h" +#include "../ui/list.h" #include "model.h" #include diff --git a/ui/qt/menu.cpp b/ui/qt/menu.cpp index 4dbb7e1..f391753 100644 --- a/ui/qt/menu.cpp +++ b/ui/qt/menu.cpp @@ -38,7 +38,6 @@ #include "../common/menu.h" #include "../ui/properties.h" #include "../ui/window.h" -#include "stock.h" #include "container.h" @@ -88,8 +87,8 @@ static UiAction* create_action( } if(states) { - size_t nstates = uic_group_array_size(states); - uic_add_group_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); + size_t nstates = uic_state_array_size(states); + uic_add_state_widget_i(obj->ctx, action, (ui_enablefunc)ui_action_enable, states, nstates); action->setEnabled(false); } @@ -98,7 +97,7 @@ static UiAction* create_action( void add_menuitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuItem *it = (UiMenuItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); QObject::connect(action, SIGNAL(triggered()), action, SLOT(trigger())); } @@ -110,7 +109,7 @@ void add_menuseparator_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj) { UiMenuCheckItem *it = (UiMenuCheckItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_checkableaction_prepare_event; @@ -130,7 +129,7 @@ void add_checkitem_widget(QMenu *parent, int i, UiMenuItemI *item, UiObject *obj void add_radioitem_widget(QMenu *parent, int index, UiMenuItemI *item, UiObject *obj) { UiMenuRadioItem *it = (UiMenuRadioItem*)item; - UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->groups); + UiAction *action = create_action(obj, it->icon, it->label, it->callback, it->userdata, it->states); parent->addAction(action); action->setCheckable(true); action->prepare_event = ui_actiongroup_prepare_event; diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp index 7b99134..361da2f 100644 --- a/ui/qt/model.cpp +++ b/ui/qt/model.cpp @@ -29,9 +29,23 @@ #include "model.h" -ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){ +ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { this->obj = obj; - this->view = view; + this->listview = view; + this->combobox = nullptr; + this->var = var; + this->getvalue = getvalue; + this->getvaluedata = getvaluedata; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +ListModel::ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata) { + this->obj = obj; + this->combobox = view; + this->listview = nullptr; this->var = var; this->getvalue = getvalue; this->getvaluedata = getvaluedata; @@ -85,7 +99,12 @@ QVariant ListModel::data(const QModelIndex &index, int role) const { } void ListModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { - UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + UiListSelection sel; + if(listview) { + sel = ui_selection_model_to_selection(listview->selectionModel()); + } else { + // TODO + } UiEvent event; event.obj = obj; @@ -257,10 +276,19 @@ void ui_listmodel_setselection(UiList *list, UiListSelection sel) { QModelIndex index = model->index(sel.rows[i]); selection.select(index, index); } - model->view->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + if(model->listview) { + model->listview->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + } else if(model->combobox) { + // TODO + } } UiListSelection ui_listmodel_getselection(UiList *list) { ListModel *model = (ListModel*)list->obj; - return ui_selection_model_to_selection(model->view->selectionModel()); + if(model->listview) { + return ui_selection_model_to_selection(model->listview->selectionModel()); + } else { + UiListSelection sel = { 0, NULL }; + return sel; + } } diff --git a/ui/qt/model.h b/ui/qt/model.h index 8fa513e..4a69ced 100644 --- a/ui/qt/model.h +++ b/ui/qt/model.h @@ -30,10 +30,11 @@ #define MODEL_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "../common/context.h" #include #include +#include #include #include #include @@ -54,9 +55,11 @@ class ListModel : public QAbstractListModel { public: UiObject *obj; UiVar *var; - QListView *view; + QListView *listview; + QComboBox *combobox; ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); + ListModel(UiObject *obj, QComboBox *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata); void setActivationCallback(ui_callback f, void *userdata); void setSelectionCallback(ui_callback f, void *userdata); diff --git a/ui/qt/qt5.pro b/ui/qt/qt5.pro index 553ddbd..c3082a5 100644 --- a/ui/qt/qt5.pro +++ b/ui/qt/qt5.pro @@ -43,7 +43,6 @@ SOURCES += toolkit.cpp SOURCES += window.cpp SOURCES += menu.cpp SOURCES += toolbar.cpp -SOURCES += stock.cpp SOURCES += container.cpp SOURCES += text.cpp SOURCES += model.cpp @@ -59,7 +58,6 @@ HEADERS += toolkit.h HEADERS += window.h HEADERS += menu.h HEADERS += toolbar.h -HEADERS += stock.h HEADERS += container.h HEADERS += text.h HEADERS += model.h diff --git a/ui/qt/toolbar.cpp b/ui/qt/toolbar.cpp index 2cb1589..63245b6 100644 --- a/ui/qt/toolbar.cpp +++ b/ui/qt/toolbar.cpp @@ -30,7 +30,6 @@ #include "toolbar.h" #include "menu.h" -#include "stock.h" static void add_items(UiObject *obj, QToolBar *toolbar, CxList *defaults, CxMap *items); static void create_item(UiObject *obj, QToolBar *toolbar, UiToolbarItemI *i); diff --git a/ui/qt/toolkit.cpp b/ui/qt/toolkit.cpp index c1fa90a..bc355d0 100644 --- a/ui/qt/toolkit.cpp +++ b/ui/qt/toolkit.cpp @@ -31,22 +31,15 @@ #include "toolkit.h" #include "window.h" -#include "stock.h" #include "../common/document.h" #include "../common/properties.h" #include "../common/menu.h" #include "../common/toolbar.h" +#include "../common/app.h" static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -static void *open_data; -static ui_callback exit_func; -static void *exit_data; - static int is_toplevel_realized = 0; static int app_argc; @@ -73,33 +66,14 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_app_exit_on_shutdown(UiBool exitapp) { exit_on_shutdown = exitapp; } void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); application->exec(); - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); delete application; diff --git a/ui/qt/window.cpp b/ui/qt/window.cpp index 38570d5..6e4c1c8 100644 --- a/ui/qt/window.cpp +++ b/ui/qt/window.cpp @@ -41,9 +41,8 @@ #include #include -static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) { +static UiObject* create_window(const char *title, bool simple, bool sidebar = false) { UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; obj->next = NULL; QMainWindow *window = new QMainWindow(); @@ -73,16 +72,16 @@ static UiObject* create_window(const char *title, void *window_data, bool simple return obj; } -UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, false); +UiObject* ui_window(const char *title) { + return create_window(title, false); } -UiObject* ui_simplewindow(char *title, void *window_data) { - return create_window(title, window_data, true); +UiObject* ui_simple_window(const char *title) { + return create_window(title, true); } -UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, false, true); +UiObject* ui_sidebar_window(const char *title) { + return create_window(title, false, true); } void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { diff --git a/ui/ui/button.h b/ui/ui/button.h index 693f730..55367f1 100644 --- a/ui/ui/button.h +++ b/ui/ui/button.h @@ -65,7 +65,7 @@ typedef struct UiButtonArgs { ui_callback onclick; void *onclickdata; - const int *groups; + const int *states; } UiButtonArgs; typedef struct UiToggleArgs { @@ -93,9 +93,9 @@ typedef struct UiToggleArgs { const char *varname; ui_callback onchange; void *onchangedata; - int enable_group; + int enable_state; - const int *groups; + const int *states; } UiToggleArgs; typedef struct UiLinkButtonArgs { @@ -124,7 +124,7 @@ typedef struct UiLinkButtonArgs { UiBool nofollow; UiLinkType type; - const int *groups; + const int *states; } UiLinkButtonArgs; #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } ) diff --git a/ui/ui/container.h b/ui/ui/container.h index 0bc42b7..dc2aa29 100644 --- a/ui/ui/container.h +++ b/ui/ui/container.h @@ -312,13 +312,14 @@ struct UiContainerX { #define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) - #define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_grid_w(obj, w, ...) for(w = ui_grid_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_tabview_w(obj, w, ...) for(w = ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_scrolledwindow_w(obj, w, ...) for(w = ui_scrolledwindow_create(obj, &(UiFrameArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_custom_container(ob, widget, addfunc, data) for(ui_custom_container_create(obj, widget, addfunc, data);ui_container_finish(obj);ui_container_begin_close(obj)) + #define ui_hsplitpane(obj, ...) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_vsplitpane(obj, ...) for(ui_vsplitpane_create(obj, &(UiSplitPaneArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hsplitpane0(obj) for(ui_hsplitpane_create(obj, &(UiSplitPaneArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) @@ -369,6 +370,9 @@ UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBo UIEXPORT void ui_newline(UiObject *obj); +typedef void(*ui_addwidget_func)(UiObject *obj, UIWIDGET parent, UIWIDGET child, void *userdata); +UIEXPORT void ui_custom_container_create(UiObject *obj, UIWIDGET widget, ui_addwidget_func add_child, void *userdata); + // TODO UIEXPORT UiTabbedPane* ui_tabbed_document_view(UiObject *obj); UIEXPORT UiObject* ui_document_tab(UiTabbedPane *view); diff --git a/ui/ui/entry.h b/ui/ui/entry.h index 6dd6a0a..9ee26d3 100644 --- a/ui/ui/entry.h +++ b/ui/ui/entry.h @@ -65,7 +65,7 @@ typedef struct UiSpinBoxArgs { ui_callback onchange; void* onchangedata; - const int *groups; + const int *states; } UiSpinBoxArgs; diff --git a/ui/ui/image.h b/ui/ui/image.h index 77d923f..0730f75 100644 --- a/ui/ui/image.h +++ b/ui/ui/image.h @@ -58,6 +58,8 @@ typedef struct UiImageViewerArgs { int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; diff --git a/ui/ui/list.h b/ui/ui/list.h new file mode 100644 index 0000000..7cfbfbb --- /dev/null +++ b/ui/ui/list.h @@ -0,0 +1,367 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 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 UI_TREE_H +#define UI_TREE_H + +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiModel UiModel; +typedef struct UiListCallbacks UiListCallbacks; +typedef struct UiListDnd UiListDnd; + +typedef struct UiListArgs UiListArgs; +typedef struct UiSourceListArgs UiSourceListArgs; + +typedef struct UiSubList UiSubList; +typedef struct UiSubListItem UiSubListItem; + +typedef enum UiModelType { + UI_STRING = 0, + UI_STRING_FREE, + UI_INTEGER, + UI_ICON, + UI_ICON_TEXT, + UI_ICON_TEXT_FREE, + UI_STRING_EDITABLE, + UI_BOOL_EDITABLE +} UiModelType; + +typedef struct UiCellValue { + union { + const char *string; + int64_t i; + UiBool b; + }; + UiModelType type; +} UiCellValue; + +typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue *value, void *userdata); + +typedef void (*ui_model_update_func)(UiModel *model, void *userdata, int insert_index, int delete_index); + +typedef struct UiModelChangeObserver UiModelChangeObserver; +struct UiModelChangeObserver { + ui_model_update_func update; + void *userdata; + UiModelChangeObserver *next; +}; + +struct UiModel { + UiContext *ctx; + + /* + * number of columns + */ + int columns; + + /* + * current allocation size (internal) + */ + int alloc; + + /* + * array of column types + * array length is the number of columns + */ + UiModelType *types; + + /* + * array of column titles + * array length is the number of columns + */ + char **titles; + + /* + * array of column size hints + */ + int *columnsize; + + /* + * Model change observers, that will be called when + * columns are added or removed + */ + UiModelChangeObserver *observer; + + /* + * reference counter + */ + int ref; +}; + +struct UiListCallbacks { + /* + * selection callback + */ + ui_callback activate; + + /* + * cursor callback + */ + ui_callback selection; + + /* + * userdata for all callbacks + */ + void *userdata; +}; + +struct UiListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + + const char *name; + const char *style_class; + UiList *list; + const char* varname; + UiModel *model; + char **static_elements; + size_t static_nelm; + ui_getvaluefunc getvalue; + ui_getvaluefunc2 getvalue2; + void *getvalue2data; + ui_getstylefunc getstyle; + void *getstyledata; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + ui_callback ondragstart; + void *ondragstartdata; + ui_callback ondragcomplete; + void *ondragcompletedata; + ui_callback ondrop; + void *ondropdata; + UiBool multiselection; + UiMenuBuilder *contextmenu; + ui_list_savefunc onsave; + void *onsavedata; + + const int *states; +}; + +typedef void (*ui_sublist_getvalue_func)(UiList *list, void *sublist_userdata, void *rowdata, int index, UiSubListItem *item, void *userdata); + +struct UiSubList { + UiList *value; + const char *varname; + const char *header; + UiBool separator; + void *userdata; +}; + +typedef struct UiSubListEventData { + UiList *list; + int sublist_index; + int row_index; + void *row_data; + void *sublist_userdata; + void *event_data; +} UiSubListEventData; + +/* + * list item members must be filled by the sublist getvalue func + * all members must be allocated (by malloc, strdup, ...) the pointer + * will be passed to free + */ +struct UiSubListItem { + char *icon; + char *label; + char *button_icon; + char *button_label; + UiMenuBuilder *button_menu; + char *badge; + void *eventdata; +}; + +struct UiSourceListArgs { + UiBool fill; + UiBool hexpand; + UiBool vexpand; + UiBool hfill; + UiBool vfill; + UiBool override_defaults; + int margin; + int margin_left; + int margin_right; + int margin_top; + int margin_bottom; + int colspan; + int rowspan; + int width; + int height; + const char *name; + const char *style_class; + + const int *states; + + /* + * 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; + /* + * optional number of sublists + * if the value is 0, it is assumed, that sublists is null-terminated + * (last item contains only NULL values) + */ + 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 + */ + ui_sublist_getvalue_func getvalue; + + /* + * getvalue_func userdata + */ + void *getvaluedata; + + /* + * is a sublist header a selectable item + */ + UiBool header_is_item; + + /* + * activated when a list item is selected + */ + ui_callback onactivate; + void *onactivatedata; + + /* + * activated, when the additional list item button is clicked + */ + ui_callback onbuttonclick; + void *onbuttonclickdata; + + UiMenuBuilder *contextmenu; +}; + +#define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ } +#define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} } + + +/* + * Creates an UiModel, that specifies columns for a table widget. + * + * For each column a column type (UiModelType) and a title string + * (char*) must be specified. The column list must be terminated + * with -1. + * + * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1); + */ +UIEXPORT UiModel* ui_model(UiContext *ctx, ...); +UIEXPORT UiModel* ui_model_new(UiContext *ctx); +UIEXPORT void ui_model_add_column(UiModel *model, UiModelType type, const char *title, int width); +UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); +UIEXPORT void ui_model_ref(UiModel *model); +UIEXPORT void ui_model_unref(UiModel *model); +UIEXPORT void ui_model_add_observer(UiModel *model, ui_model_update_func update, void *data); +UIEXPORT void ui_model_remove_observer(UiModel *model, void *data); +UIEXPORT void ui_model_free(UiModel *mi); + +#define ui_listview(obj, ...) ui_listview_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_table(obj, ...) ui_table_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_dropdown(obj, ...) ui_dropdown_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, &(UiListArgs) { __VA_ARGS__ } ) +#define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, &(UiSourceListArgs) { __VA_ARGS__ } ) + +UIEXPORT UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args); +UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject *obj, UiListArgs *args); + +UIEXPORT void ui_listview_select(UIWIDGET listview, int index); +UIEXPORT int ui_listview_selection(UIWIDGET listview); +UIEXPORT void ui_dropdown_select(UIWIDGET dropdown, int index); +UIEXPORT int ui_dropdown_selection(UIWIDGET dropdown); + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args); + +UIEXPORT void ui_sublist_item_set_icon(UiSubListItem *item, const char *icon); +UIEXPORT void ui_sublist_item_set_label(UiSubListItem *item, const char *label); +UIEXPORT void ui_sublist_item_set_button_icon(UiSubListItem *item, const char *button_icon); +UIEXPORT void ui_sublist_item_set_button_label(UiSubListItem *item, const char *button_label); +UIEXPORT void ui_sublist_item_set_button_menu(UiSubListItem *item, UiMenuBuilder *menu); +UIEXPORT void ui_sublist_item_set_badge(UiSubListItem *item, const char *badge); +UIEXPORT void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata); + + + +/* + * Only relevant for some language bindings + */ +typedef void(*ui_sourcelist_update_func)(void); + +/* + * The sourcelist update callback is called after any source list + * sublist update is completed + */ +UIEXPORT void ui_sourcelist_set_update_callback(ui_sourcelist_update_func cb); + +#ifdef __cplusplus +} +#endif + +#endif /* UI_TREE_H */ + diff --git a/ui/ui/menu.h b/ui/ui/menu.h index 9dde9b4..43cca7e 100644 --- a/ui/ui/menu.h +++ b/ui/ui/menu.h @@ -43,7 +43,7 @@ typedef struct UiMenuItemArgs { ui_callback onclick; void* onclickdata; - const int* groups; + const int* states; } UiMenuItemArgs; typedef struct UiMenuToggleItemArgs { @@ -54,7 +54,7 @@ typedef struct UiMenuToggleItemArgs { ui_callback onchange; void* onchangedata; - const int* groups; + const int* nstates; } UiMenuToggleItemArgs; typedef struct UiMenuItemListArgs { diff --git a/ui/ui/properties.h b/ui/ui/properties.h index ffb10dd..57112b7 100644 --- a/ui/ui/properties.h +++ b/ui/ui/properties.h @@ -35,6 +35,9 @@ extern "C" { #endif +void ui_load_properties_file_on_startup(UiBool enable); +void ui_set_properties_data(const char *str, size_t len); + const char* ui_get_property(const char *name); void ui_set_property(const char *name, const char *value); const char* ui_set_default_property(const char *name, const char *value); diff --git a/ui/ui/text.h b/ui/ui/text.h index ac5a2c1..c102f0b 100644 --- a/ui/ui/text.h +++ b/ui/ui/text.h @@ -59,7 +59,7 @@ typedef struct UiTextAreaArgs { ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; } UiTextAreaArgs; typedef struct UiTextFieldArgs { @@ -87,7 +87,7 @@ typedef struct UiTextFieldArgs { ui_callback onactivate; void *onactivatedata; - const int *groups; + const int *states; } UiTextFieldArgs; typedef struct UiPathElmRet { @@ -115,6 +115,7 @@ typedef struct UiPathTextFieldArgs { int margin_bottom; int colspan; int rowspan; + int width; const char *name; const char *style_class; diff --git a/ui/ui/toolbar.h b/ui/ui/toolbar.h index 26a79e6..2e230df 100644 --- a/ui/ui/toolbar.h +++ b/ui/ui/toolbar.h @@ -44,7 +44,7 @@ typedef struct UiToolbarItemArgs { ui_callback onclick; void* onclickdata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarItemArgs; @@ -57,7 +57,7 @@ typedef struct UiToolbarToggleItemArgs { ui_callback onchange; void *onchangedata; - const int *groups; + const int *states; const int *visibility_states; } UiToolbarToggleItemArgs; diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 89751d2..628e3e8 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -148,8 +148,10 @@ public: #elif UI_SERVER -#define UIWIDGET void* -#define UIWINDOW void* +typedef struct UiWidget UiWidget; + +#define UIWIDGET UiWidget* +#define UIWINDOW UiWidget* #define UIMENU void* #endif @@ -565,16 +567,18 @@ UIEXPORT void* ui_get_subdocument(void *document); // deprecated UIEXPORT UiContext* ui_document_context(void *doc); +UIEXPORT void* ui_context_get_document(UiContext *ctx); +UIEXPORT void ui_context_single_attachment_mode(UiContext *ctx, UiBool enable); UIEXPORT void ui_attach_document(UiContext *ctx, void *document); UIEXPORT void ui_detach_document(UiContext *ctx, void *document); -UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); -UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *groups, int ngroups); +UIEXPORT void ui_widget_set_states(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_widget_set_states2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, const int *states, int nstates); UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, const int *states, int nstates); -UIEXPORT void ui_set_group(UiContext *ctx, int group); -UIEXPORT void ui_unset_group(UiContext *ctx, int group); -UIEXPORT int* ui_active_groups(UiContext *ctx, int *ngroups); +UIEXPORT void ui_set_state(UiContext *ctx, int state); +UIEXPORT void ui_unset_state(UiContext *ctx, int state); +UIEXPORT int* ui_active_states(UiContext *ctx, int *nstates); UIEXPORT void* ui_allocator(UiContext *ctx); UIEXPORT void* ui_cx_mempool(UiContext *ctx); @@ -633,6 +637,13 @@ UIEXPORT double ui_var_get_double(UiContext *ctx, const char *name); UIEXPORT void ui_var_set_string(UiContext *ctx, const char *name, char *value); UIEXPORT char* ui_var_get_string(UiContext *ctx, const char *name); +UIEXPORT UiInteger* ui_get_int_var(UiContext *ctx, const char *name); +UIEXPORT UiDouble* ui_get_double_var(UiContext *ctx, const char *name); +UIEXPORT UiString* ui_get_string_var(UiContext *ctx, const char *name); +UIEXPORT UiText* ui_get_text_var(UiContext *ctx, const char *name); +UIEXPORT UiRange* ui_get_range_var(UiContext *ctx, const char *name); +UIEXPORT UiGeneric* ui_get_generic_var(UiContext *ctx, const char *name); + UIEXPORT UiObserver* ui_observer_new(ui_callback f, void *data); UIEXPORT UiObserver* ui_obsvlist_add(UiObserver *list, UiObserver *observer); UIEXPORT UiObserver* ui_add_observer(UiObserver *list, ui_callback f, void *data); diff --git a/ui/ui/ui.h b/ui/ui/ui.h index 57c8025..472ca74 100644 --- a/ui/ui/ui.h +++ b/ui/ui/ui.h @@ -35,11 +35,10 @@ #include "menu.h" #include "toolbar.h" #include "window.h" -#include "stock.h" #include "button.h" #include "text.h" #include "properties.h" -#include "tree.h" +#include "list.h" #include "graphics.h" #include "entry.h" #include "range.h" diff --git a/ui/ui/webview.h b/ui/ui/webview.h index 9636165..d5a243d 100644 --- a/ui/ui/webview.h +++ b/ui/ui/webview.h @@ -60,13 +60,15 @@ typedef struct UiWebviewArgs { int margin_bottom; int colspan; int rowspan; + int width; + int height; const char *name; const char *style_class; UiGeneric *value; const char *varname; - const int* groups; + const int* states; } UiWebviewArgs; #define ui_webview(obj, ...) ui_webview_create(obj, &(UiWebviewArgs){ __VA_ARGS__ } ) diff --git a/ui/ui/window.h b/ui/ui/window.h index 3619b3a..d100016 100644 --- a/ui/ui/window.h +++ b/ui/ui/window.h @@ -61,10 +61,10 @@ typedef struct UiDialogWindowArgs { const char *lbutton2; const char *rbutton3; const char *rbutton4; - const int *lbutton1_groups; - const int *lbutton2_groups; - const int *rbutton3_groups; - const int *rbutton4_groups; + const int *lbutton1_states; + const int *lbutton2_states; + const int *rbutton3_states; + const int *rbutton4_states; int default_button; int width; int height; @@ -72,10 +72,10 @@ typedef struct UiDialogWindowArgs { void *onclickdata; } UiDialogWindowArgs; -UIEXPORT UiObject *ui_window(const char *title, void *window_data); -UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_window(const char *title); +UIEXPORT UiObject *ui_sidebar_window(const char *title); UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar); -UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_simple_window(const char *title); UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args); #define ui_dialog_window(parent, ...) ui_dialog_window_create(parent, &(UiDialogWindowArgs){ __VA_ARGS__ }); diff --git a/ui/win32/list.c b/ui/win32/list.c index 5626458..2681a2e 100644 --- a/ui/win32/list.c +++ b/ui/win32/list.c @@ -134,7 +134,7 @@ static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { } } else { UiModel *model = ui_model_new(obj->ctx); - ui_model_add_column(obj->ctx, model, UI_STRING, "Test", -1); + ui_model_add_column(model, UI_STRING, "Test", -1); listview->model = model; numcolumns = 1; } @@ -166,18 +166,30 @@ static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { UiList *list = listview->var->value; list->obj = listview; list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->setselection = ui_listview_setselection; + list->getselection = ui_listview_getselection_impl; + list->setselection = ui_listview_setselection_impl; ui_listview_update(list, -1); + } else if (!table && args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + LVITEM item; + item.mask = LVIF_TEXT; + item.iSubItem = 0; + for (int i=0;igetvalue = strmodel_getvalue; + listview->getvaluedata = NULL; } return (W32Widget*)listview; } -static UiListSelection listview_get_selection(UiListView *listview) { +static UiListSelection listview_get_selection2(HWND hwnd) { UiListSelection sel = { 0, NULL }; - HWND hwnd = listview->widget.hwnd; CX_ARRAY_DECLARE(int, indices); cx_array_initialize(indices, 8); @@ -195,6 +207,11 @@ static UiListSelection listview_get_selection(UiListView *listview) { return sel; } +static UiListSelection listview_get_selection(UiListView *listview) { + HWND hwnd = listview->widget.hwnd; + return listview_get_selection2(hwnd); +} + // listview class event proc int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { UiListView *listview = (UiListView*)widget; @@ -327,12 +344,12 @@ void ui_listview_update(UiList *list, int row) { } } -UiListSelection ui_listview_getselection(UiList *list) { +UiListSelection ui_listview_getselection_impl(UiList *list) { UiListView *listview = (UiListView*)list->obj; return listview_get_selection(listview); } -void ui_listview_setselection(UiList *list, UiListSelection selection) { +void ui_listview_setselection_impl(UiList *list, UiListSelection selection) { } @@ -346,6 +363,20 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { return listview_create(obj, args, TRUE); } +void ui_listview_select(UIWIDGET listview, int index) { + +} + +int ui_listview_selection(UIWIDGET listview) { + W32Widget *w = (W32Widget*)listview; + UiListSelection sel = listview_get_selection2(w->hwnd); + int index = -1; + if (sel.count > 0) { + index = sel.rows[0]; + } + free(sel.rows); + return index; +} /* ------------------------------------ DropDown ------------------------------------*/ @@ -357,7 +388,7 @@ static W32WidgetClass dropdown_widget_class = { .destroy = w32_widget_default_destroy }; -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { +UIWIDGET ui_dropdown_create(UiObject *obj, UiListArgs *args) { HINSTANCE hInstance = GetModuleHandle(NULL); UiContainerPrivate *container = ui_obj_container(obj); HWND parent = ui_container_get_parent(container); @@ -383,13 +414,20 @@ UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { UiList *list = dropdown->var->value; list->obj = dropdown; list->update = ui_dropdown_update; - list->getselection = ui_dropdown_getselection; - list->setselection = ui_dropdown_setselection; + list->getselection = ui_dropdown_getselection_impl; + list->setselection = ui_dropdown_setselection_impl; ui_dropdown_update(list, -1); + } else if (args->static_elements && args->static_nelm > 0) { + char **static_elements = args->static_elements; + size_t static_nelm = args->static_nelm; + for (int i=0;igetvalue = strmodel_getvalue; + dropdown->getvaluedata = NULL; } - return (W32Widget*)dropdown; } @@ -437,7 +475,7 @@ void ui_dropdown_update(UiList *list, int row) { } } -UiListSelection ui_dropdown_getselection(UiList *list) { +UiListSelection ui_dropdown_getselection_impl(UiList *list) { UiListSelection sel = { 0, NULL }; UiListView *listview = (UiListView*)list->obj; int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); @@ -449,7 +487,15 @@ UiListSelection ui_dropdown_getselection(UiList *list) { return sel; } -void ui_dropdown_setselection(UiList *list, UiListSelection selection) { +void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection) { UiListView *listview = (UiListView*)list->obj; SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); } + +void ui_dropdown_select(UIWIDGET dropdown, int index) { + SendMessage(dropdown->hwnd, CB_SETCURSEL, 0, 0); +} + +int ui_dropdown_selection(UIWIDGET dropdown) { + return SendMessage(dropdown->hwnd, CB_GETCURSEL, 0, 0); +} diff --git a/ui/win32/list.h b/ui/win32/list.h index 7b1d36b..3074ed4 100644 --- a/ui/win32/list.h +++ b/ui/win32/list.h @@ -30,7 +30,7 @@ #define LIST_H #include "toolkit.h" -#include "../ui/tree.h" +#include "../ui/list.h" #include "win32.h" #include @@ -66,15 +66,15 @@ int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam W32Size ui_listview_get_preferred_size(W32Widget *widget); void ui_listview_update(UiList *list, int row); -UiListSelection ui_listview_getselection(UiList *list); -void ui_listview_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_listview_getselection_impl(UiList *list); +void ui_listview_setselection_impl(UiList *list, UiListSelection selection); int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); W32Size ui_dropdown_get_preferred_size(W32Widget *widget); void ui_dropdown_update(UiList *list, int row); -UiListSelection ui_dropdown_getselection(UiList *list); -void ui_dropdown_setselection(UiList *list, UiListSelection selection); +UiListSelection ui_dropdown_getselection_impl(UiList *list); +void ui_dropdown_setselection_impl(UiList *list, UiListSelection selection); #ifdef __cplusplus } diff --git a/ui/win32/menu.c b/ui/win32/menu.c new file mode 100644 index 0000000..c825866 --- /dev/null +++ b/ui/win32/menu.c @@ -0,0 +1,97 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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" + +static ui_menu_add_f createMenuItem[] = { + /* UI_MENU */ ui_add_menu, + /* UI_MENU_ITEM */ ui_add_menu_item, + /* UI_MENU_CHECK_ITEM */ ui_add_menu_checkitem, + /* UI_MENU_RADIO_ITEM */ ui_add_menu_radioitem, + /* UI_MENU_ITEM_LIST */ ui_add_menu_list, + /* UI_MENU_CHECKITEM_LIST */ ui_add_menu_checklist, + /* UI_MENU_RADIOITEM_LIST */ ui_add_menu_radiolist, + /* UI_MENU_SEPARATOR */ ui_add_menu_separator +}; + + +HMENU ui_create_main_menu(UiObject *obj) { + UiMenu *menu = uic_get_menu_list(); + if (!menu) { + return NULL; + } + + HMENU hMenu = CreateMenu(); + ui_add_menu(hMenu, 0, &menu->item, obj); + + + + return hMenu; +} + +void ui_add_menu(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + UiMenu *menu = (UiMenu*)item; + HMENU hMenu = CreatePopupMenu(); + AppendMenu(parent, MF_POPUP, (UINT_PTR)hMenu, menu->label); + + int i = 0; + UiMenuItemI *child = menu->items_begin; + while (child) { + createMenuItem[child->type](hMenu, i++, child, obj); + child = child->next; + } +} + +void ui_add_menu_item(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + UiMenuItem *i = (UiMenuItem*)item; + AppendMenu(parent, MF_STRING, 0, i->label); +} + +void ui_add_menu_checkitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_radioitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_radiolist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} + +void ui_add_menu_separator(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + +} diff --git a/ui/win32/menu.h b/ui/win32/menu.h new file mode 100644 index 0000000..13bbf0c --- /dev/null +++ b/ui/win32/menu.h @@ -0,0 +1,57 @@ +/* + * 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 "toolkit.h" +#include "../ui/menu.h" +#include "../common/menu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void(*ui_menu_add_f)(HMENU, int, UiMenuItemI*, UiObject*); + +HMENU ui_create_main_menu(UiObject *obj); + +void ui_add_menu(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_item(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_checkitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_radioitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_radiolist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); +void ui_add_menu_separator(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj); + +#ifdef __cplusplus +} +#endif + +#endif //MENU_H \ No newline at end of file diff --git a/ui/win32/objs.mk b/ui/win32/objs.mk index 85a6fb3..c239354 100644 --- a/ui/win32/objs.mk +++ b/ui/win32/objs.mk @@ -39,6 +39,7 @@ WIN32OBJ += button.obj WIN32OBJ += grid.obj WIN32OBJ += text.obj WIN32OBJ += list.obj +WIN32OBJ += menu.obj TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%) TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c) diff --git a/ui/win32/toolkit.c b/ui/win32/toolkit.c index f73cc5b..2ed1535 100644 --- a/ui/win32/toolkit.c +++ b/ui/win32/toolkit.c @@ -35,6 +35,7 @@ #include "../common/toolbar.h" #include "../common/document.h" #include "../common/properties.h" +#include "../common/app.h" #include "../ui/widget.h" @@ -45,13 +46,6 @@ static const char *application_name; -static ui_callback startup_func; -static void *startup_data; -static ui_callback open_func; -void *open_data; -static ui_callback exit_func; -void *exit_data; - static HFONT ui_font = NULL; void ui_init(const char *appname, int argc, char **argv) { @@ -91,25 +85,8 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void *userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void *userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void *userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_main() { - if(startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); // event loop MSG msg; @@ -118,9 +95,7 @@ void ui_main() { DispatchMessage(&msg); } - if(exit_func) { - exit_func(NULL, exit_data); - } + uic_application_exit(NULL); uic_store_app_properties(); } @@ -161,7 +136,7 @@ LRESULT CALLBACK ui_default_eventproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA case WM_SIZE: { int width = LOWORD(lParam); int height = HIWORD(lParam); - if (widget->layout) { + if (widget && widget->layout) { widget->layout(widget->layoutmanager, width, height); } break; @@ -169,4 +144,8 @@ LRESULT CALLBACK ui_default_eventproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA default: break;//return DefWindowProc(hwnd, uMsg, wParam, lParam); } return DefWindowProc(hwnd, uMsg, wParam, lParam);; -} \ No newline at end of file +} + +void ui_call_mainthread(ui_threadfunc tf, void* td) { + // TODO +} diff --git a/ui/win32/window.c b/ui/win32/window.c index 6fbe7af..1f661d7 100644 --- a/ui/win32/window.c +++ b/ui/win32/window.c @@ -39,6 +39,7 @@ #include #include "win32.h" +#include "menu.h" static W32WidgetClass w32_toplevel_widget_class = { .eventproc = ui_window_widget_event, @@ -54,9 +55,9 @@ static const char *mainWindowClass = "UiMainWindow"; void ui_window_init(void) { - hInstance = GetModuleHandle(NULL); - - WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + hInstance = GetModuleHandle(NULL); + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; wc.lpfnWndProc = ui_default_eventproc; wc.hInstance = hInstance; wc.lpszClassName = mainWindowClass; @@ -69,50 +70,59 @@ void ui_window_init(void) { } } -static UiObject* create_window(const char *title, void *window_data, bool simple) { +static UiObject* create_window(const char *title, bool simple) { UiObject *obj = uic_object_new_toplevel(); - obj->window = window_data; - HWND hwnd = CreateWindowExA( - 0, - "UiMainWindow", - title, - WS_OVERLAPPEDWINDOW, + HWND hwnd = CreateWindowExA( + 0, + "UiMainWindow", + title, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, - 800, - 600, + 800, + 600, + NULL, NULL, - NULL, - hInstance, - NULL); + hInstance, + NULL); + + if (!simple) { + HMENU menubar = ui_create_main_menu(obj); + if (menubar) { + SetMenu(hwnd, menubar); + } + } UpdateWindow(hwnd); - UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO); - uic_object_push_container(obj, container); - UiBoxContainer *box = (UiBoxContainer*)container; + UiContainerX *container = ui_box_container_create(obj, hwnd, UI_BOX_VERTICAL, 0, INSETS_ZERO); + uic_object_push_container(obj, container); + UiBoxContainer *box = (UiBoxContainer*)container; - UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); - widget->obj = obj; - widget->widget.layout = (W32LayoutFunc)ui_grid_layout; - widget->widget.layoutmanager = box->layout; - obj->widget = (W32Widget*)widget; - obj->ref = 1; + UiWindow *widget = w32_widget_create(&w32_toplevel_widget_class, hwnd, sizeof(UiWindow)); + widget->obj = obj; + widget->widget.layout = (W32LayoutFunc)ui_grid_layout; + widget->widget.layoutmanager = box->layout; + obj->widget = (W32Widget*)widget; + obj->ref = 1; - return obj; + return obj; } -UiObject *ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE); +UiObject *ui_window(const char *title) { + return create_window(title, FALSE); } +UiObject *ui_simple_window(const char *title) { + return create_window(title, TRUE); +} int ui_window_widget_event(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - //UiWindow *window = (UiWindow*)widget; - return 0; + //UiWindow *window = (UiWindow*)widget; + return 0; } void ui_window_widget_show(W32Widget *w, BOOLEAN show) { - ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); + ShowWindow(w->hwnd, show ? SW_SHOWNORMAL : SW_HIDE); } diff --git a/ui/winui/list.h b/ui/winui/list.h index bbb63f6..5027368 100644 --- a/ui/winui/list.h +++ b/ui/winui/list.h @@ -28,7 +28,7 @@ #pragma once -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include "../ui/container.h" diff --git a/ui/winui/table.h b/ui/winui/table.h index 51fa97d..c6959ec 100644 --- a/ui/winui/table.h +++ b/ui/winui/table.h @@ -28,7 +28,7 @@ #pragma once -#include "../ui/tree.h" +#include "../ui/list.h" #include "toolkit.h" #include "dnd.h" diff --git a/ui/winui/toolkit.cpp b/ui/winui/toolkit.cpp index 1844cc2..42e2b8d 100644 --- a/ui/winui/toolkit.cpp +++ b/ui/winui/toolkit.cpp @@ -37,6 +37,7 @@ #include "../common/document.h" #include "../common/toolbar.h" #include "../common/properties.h" +#include "../common/app.h" #include "icons.h" @@ -55,15 +56,6 @@ using namespace Windows::UI::Core; static const char* application_name; -static ui_callback startup_func; -static void* startup_data; - -static ui_callback open_func; -void* open_data; - -static ui_callback exit_func; -void* exit_data; - static ui_callback appclose_fnc; static void* appclose_udata; @@ -75,19 +67,14 @@ static winrt::Microsoft::UI::Dispatching::DispatcherQueue uiDispatcherQueue = { void ui_app_run_startup() { uiDispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); - - if (startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); } class App : public ApplicationT { public: void OnLaunched(LaunchActivatedEventArgs const&) { Resources().MergedDictionaries().Append(XamlControlsResources()); - if (startup_func) { - startup_func(NULL, startup_data); - } + uic_application_startup(NULL); //auto window = make(); //window.Activate(); @@ -175,21 +162,6 @@ const char* ui_appname() { return application_name; } -void ui_onstartup(ui_callback f, void* userdata) { - startup_func = f; - startup_data = userdata; -} - -void ui_onopen(ui_callback f, void* userdata) { - open_func = f; - open_data = userdata; -} - -void ui_onexit(ui_callback f, void* userdata) { - exit_func = f; - exit_data = userdata; -} - void ui_main() { /* init_apartment(); diff --git a/ui/winui/window.cpp b/ui/winui/window.cpp index 4285e09..fcb01d1 100644 --- a/ui/winui/window.cpp +++ b/ui/winui/window.cpp @@ -68,89 +68,88 @@ extern "C" static void ui_window_widget_destroy(UiObject *obj) { obj->wobj->window.Close(); } -UiObject* ui_window(const char* title, void* window_data) { - UiObject* obj = ui_simple_window(title, window_data); - - /* - if (uic_get_menu_list()) { - // create/add menubar - MenuBar mb = ui_create_menubar(obj); - mb.VerticalAlignment(VerticalAlignment::Top); - obj->container->Add(mb, false); - } - */ - - if (uic_toolbar_isenabled()) { - // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" - Grid toolbar_grid = Grid(); - GridLength gl; - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; - - ColumnDefinition coldef0 = ColumnDefinition(); - coldef0.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef0); - - gl.Value = 1; - gl.GridUnitType = GridUnitType::Star; - ColumnDefinition coldef1 = ColumnDefinition(); - coldef1.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef1); - - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; - ColumnDefinition coldef2 = ColumnDefinition(); - coldef2.Width(gl); - toolbar_grid.ColumnDefinitions().Append(coldef2); - - // rowdef - gl.Value = 0; - gl.GridUnitType = GridUnitType::Auto; - RowDefinition rowdef = RowDefinition(); - rowdef.Height(gl); - toolbar_grid.RowDefinitions().Append(rowdef); - - - // create commandbar - CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); - CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); - CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); - - bool addappmenu = true; - if (cxListSize(def_r) > 0) { - CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); - toolbar_grid.SetColumn(toolbar_r, 2); - toolbar_grid.SetRow(toolbar_r, 0); - toolbar_grid.Children().Append(toolbar_r); - addappmenu = false; - } - if (cxListSize(def_c) > 0) { - CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); - toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); - toolbar_grid.SetColumn(toolbar_c, 1); - toolbar_grid.SetRow(toolbar_c, 0); - toolbar_grid.Children().Append(toolbar_c); - addappmenu = false; - } - if (cxListSize(def_l) > 0) { - CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); - toolbar_grid.SetColumn(toolbar_l, 0); - toolbar_grid.SetRow(toolbar_l, 0); - toolbar_grid.Children().Append(toolbar_l); - } - - toolbar_grid.VerticalAlignment(VerticalAlignment::Top); - obj->container->Add(toolbar_grid, false); - } - - return obj; +UiObject* ui_window(const char* title) { + UiObject* obj = ui_simple_window(title); + + /* + if (uic_get_menu_list()) { + // create/add menubar + MenuBar mb = ui_create_menubar(obj); + mb.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(mb, false); + } + */ + + if (uic_toolbar_isenabled()) { + // create a grid for the toolbar: ColumnDefinitions="Auto, *, Auto" + Grid toolbar_grid = Grid(); + GridLength gl; + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + + ColumnDefinition coldef0 = ColumnDefinition(); + coldef0.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef0); + + gl.Value = 1; + gl.GridUnitType = GridUnitType::Star; + ColumnDefinition coldef1 = ColumnDefinition(); + coldef1.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef1); + + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + ColumnDefinition coldef2 = ColumnDefinition(); + coldef2.Width(gl); + toolbar_grid.ColumnDefinitions().Append(coldef2); + + // rowdef + gl.Value = 0; + gl.GridUnitType = GridUnitType::Auto; + RowDefinition rowdef = RowDefinition(); + rowdef.Height(gl); + toolbar_grid.RowDefinitions().Append(rowdef); + + + // create commandbar + CxList* def_l = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); + CxList* def_c = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); + CxList* def_r = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); + + bool addappmenu = true; + if (cxListSize(def_r) > 0) { + CommandBar toolbar_r = ui_create_toolbar(obj, def_r, addappmenu); + toolbar_grid.SetColumn(toolbar_r, 2); + toolbar_grid.SetRow(toolbar_r, 0); + toolbar_grid.Children().Append(toolbar_r); + addappmenu = false; + } + if (cxListSize(def_c) > 0) { + CommandBar toolbar_c = ui_create_toolbar(obj, def_c, addappmenu); + toolbar_c.HorizontalAlignment(HorizontalAlignment::Center); + toolbar_grid.SetColumn(toolbar_c, 1); + toolbar_grid.SetRow(toolbar_c, 0); + toolbar_grid.Children().Append(toolbar_c); + addappmenu = false; + } + if (cxListSize(def_l) > 0) { + CommandBar toolbar_l = ui_create_toolbar(obj, def_l, addappmenu); + toolbar_grid.SetColumn(toolbar_l, 0); + toolbar_grid.SetRow(toolbar_l, 0); + toolbar_grid.Children().Append(toolbar_l); + } + + toolbar_grid.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(toolbar_grid, false); + } + + return obj; } -UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { +UIEXPORT UiObject* ui_simple_window(const char *title) { UiObject* obj = uic_object_new_toplevel(); obj->ctx = uic_context(obj, mp); - obj->window = window_data; Window window = Window(); //Window window = make(); diff --git a/ui/winui/winui.vcxproj b/ui/winui/winui.vcxproj index 9f72b38..6caa289 100644 --- a/ui/winui/winui.vcxproj +++ b/ui/winui/winui.vcxproj @@ -142,7 +142,7 @@ - + diff --git a/ui/winui/winui.vcxproj.filters b/ui/winui/winui.vcxproj.filters index 7340b7f..eb8ea01 100644 --- a/ui/winui/winui.vcxproj.filters +++ b/ui/winui/winui.vcxproj.filters @@ -108,7 +108,7 @@ public - + public