From 622a2103f277643b391358599f14734c3f9196f1 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Thu, 15 May 2025 21:26:28 +0200 Subject: [PATCH] update toolkit --- application/Makefile | 2 +- ui/cocoa/text.h | 17 +++++ ui/cocoa/text.m | 156 +++++++++++++++++++++++++++++++++++++++++ ui/common/properties.c | 4 ++ ui/gtk/list.c | 8 +++ ui/qt/list.cpp | 40 +++++++++++ ui/qt/model.cpp | 111 ++++++++++++++++++++++++++++- ui/qt/model.h | 32 +++++++++ ui/win32/objs.mk | 3 +- ui/win32/toolkit.c | 4 ++ ui/win32/window.c | 99 ++++++++++++++++++++++++++ ui/win32/window.h | 51 ++++++++++++++ 12 files changed, 524 insertions(+), 3 deletions(-) create mode 100644 ui/win32/window.c create mode 100644 ui/win32/window.h diff --git a/application/Makefile b/application/Makefile index 89823cb..ec1f0e7 100644 --- a/application/Makefile +++ b/application/Makefile @@ -56,7 +56,7 @@ TEST_BIN = ../build/bin/notetest$(APP_EXT) all: $(APP_BIN) $(TEST_BIN) -$(APP_BIN): $(MAIN_OBJ) $(OBJ) +$(APP_BIN): $(MAIN_OBJ) $(OBJ) $(BUILD_ROOT)/build/lib/libuitk.a $(LD) -o $(APP_BIN) $(MAIN_OBJ) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx -lidav -ldbutils -lmd4c $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(DBU_LDFLAGS) $(TEST_BIN): $(OBJ) $(TEST_OBJ) $(BUILD_ROOT)/build/lib/libuitk.a diff --git a/ui/cocoa/text.h b/ui/cocoa/text.h index 9bd685f..23bc438 100644 --- a/ui/cocoa/text.h +++ b/ui/cocoa/text.h @@ -30,3 +30,20 @@ #import "../ui/text.h" + +void ui_textarea_save(UiText *text); +void ui_textarea_destroy(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_set(UiText *text, const char *str); +char* ui_textarea_get(UiText *text); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_setselection(UiText *text, int begin, int end); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); +void ui_textarea_remove(UiText *text, int begin, int end); + +char* ui_textfield_get(UiString *s); +void ui_textfield_set(UiString *s, const char *value); diff --git a/ui/cocoa/text.m b/ui/cocoa/text.m index 37d04a8..7b9ba6e 100644 --- a/ui/cocoa/text.m +++ b/ui/cocoa/text.m @@ -44,5 +44,161 @@ UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) { UiLayout layout = UI_INIT_LAYOUT(args); ui_container_add(obj, scrollview, &layout, TRUE); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_TEXT); + if(var) { + UiText *text = var->value; + text->obj = (__bridge void*)textview; + ui_textarea_restore(text); + + text->save = ui_textarea_save; + text->destroy = ui_textarea_destroy; + text->restore = ui_textarea_restore; + text->set = ui_textarea_set; + text->get = ui_textarea_get; + text->getsubstr = ui_textarea_getsubstr; + text->insert = ui_textarea_insert; + text->setposition = ui_textarea_setposition; + text->position = ui_textarea_position; + text->setselection = ui_textarea_setselection; + text->selection = ui_textarea_selection; + text->length = ui_textarea_length; + text->remove = ui_textarea_remove; + } + return (__bridge void*)scrollview; } + + + + +void ui_textarea_save(UiText *text) { + +} + +void ui_textarea_destroy(UiText *text) { + (void)(__bridge_transfer NSTextStorage*)text->data1; +} + +void ui_textarea_restore(UiText *text) { + NSTextView *textview = (__bridge NSTextView*)text->obj; + NSTextStorage *textStorage; + if(text->data1) { + textStorage = (__bridge NSTextStorage*)text->data1; + + } else { + textStorage = [[NSTextStorage alloc] init]; + } + [textview.layoutManager replaceTextStorage:textStorage]; + text->data1 = (__bridge_retained void*)textStorage; +} + +void ui_textarea_set(UiText *text, const char *str) { + +} + +char* ui_textarea_get(UiText *text) { + return NULL; +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + return NULL; +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + +} + +void ui_textarea_setposition(UiText *text, int pos) { + +} + +int ui_textarea_position(UiText *text) { + return 0; +} + +void ui_textarea_setselection(UiText *text, int begin, int end) { + +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + +} + +int ui_textarea_length(UiText *text) { + return 0; +} + +void ui_textarea_remove(UiText *text, int begin, int end) { + +} + + + +/* -------------------------- TextField -------------------------- */ + +static UIWIDGET textfield_create(UiObject *obj, UiTextFieldArgs args, BOOL password, BOOL frameless) { + NSTextField *textfield; + if(password) { + textfield = [[NSSecureTextField alloc] init]; + } else { + textfield = [[NSTextField alloc] init]; + } + + if(frameless) { + [textfield setBezeled: NO]; + } + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, textfield, &layout, FALSE); + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.value, args.varname, UI_VAR_STRING); + if(var) { + UiString *s = var->value; + if(s->value.ptr) { + textfield.stringValue = [[NSString alloc] initWithUTF8String:s->value.ptr]; + if(s->value.free) { + s->value.free(s->value.ptr); + } + } + s->obj = (__bridge void*)textfield; + s->get = ui_textfield_get; + s->set = ui_textfield_set; + } + + return (__bridge void*)textfield; +} + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) { + return textfield_create(obj, args, FALSE, FALSE); +} + +UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) { + return textfield_create(obj, args, FALSE, TRUE); +} + +UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) { + return textfield_create(obj, args, TRUE, FALSE); +} + +char* ui_textfield_get(UiString *s) { + NSTextField *textfield = (__bridge NSTextField*)s->obj; + NSString *str = textfield.stringValue; + const char *cstr = str.UTF8String; + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = strdup(cstr); + s->value.free = free; + return s->value.ptr; +} + +void ui_textfield_set(UiString *s, const char *value) { + if(s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = NULL; + s->value.free = NULL; + NSTextField *textfield = (__bridge NSTextField*)s->obj; + textfield.stringValue = [[NSString alloc] initWithUTF8String:value]; +} diff --git a/ui/common/properties.c b/ui/common/properties.c index 1d7311a..629112f 100644 --- a/ui/common/properties.c +++ b/ui/common/properties.c @@ -32,6 +32,10 @@ #include #include +#ifdef _WIN32 +#include +#endif + #include "../ui/toolkit.h" diff --git a/ui/gtk/list.c b/ui/gtk/list.c index e5ffa58..978ab53 100644 --- a/ui/gtk/list.c +++ b/ui/gtk/list.c @@ -269,6 +269,10 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { // is ignored g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), listview); } + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view); + ui_widget_set_contextmenu(view, menu); + } // add widget to parent GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); @@ -438,6 +442,10 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { if(args.onactivate) { g_signal_connect(view, "activate", G_CALLBACK(ui_columnview_activate), tableview); } + if(args.contextmenu) { + UIMENU menu = ui_contextmenu_create(args.contextmenu, obj, view); + ui_widget_set_contextmenu(view, menu); + } // add widget to parent GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); diff --git a/ui/qt/list.cpp b/ui/qt/list.cpp index 3253ca7..6ab288a 100644 --- a/ui/qt/list.cpp +++ b/ui/qt/list.cpp @@ -73,3 +73,43 @@ UIWIDGET ui_listview_create(UiObject* obj, UiListArgs args) { return view; } + +UIWIDGET ui_table_create(UiObject* obj, UiListArgs args) { + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + + QTreeView *view = new QTreeView(); + view->setItemsExpandable(false); + view->setRootIsDecorated(false); + if(args.multiselection) { + view->setSelectionMode(QAbstractItemView::ExtendedSelection); + } + + UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args.list, args.varname, UI_VAR_LIST); + + TableModel *model = new TableModel(obj, view, var, args.model); + 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); + + QItemSelectionModel *s = view->selectionModel(); + QObject::connect( + s, + SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), + model, + SLOT(selectionChanged(const QItemSelection &, const QItemSelection &))); + + + ctn->add(view, false); + + return view; +} diff --git a/ui/qt/model.cpp b/ui/qt/model.cpp index 1ac4574..f3a4b3b 100644 --- a/ui/qt/model.cpp +++ b/ui/qt/model.cpp @@ -70,7 +70,9 @@ QVariant ListModel::data(const QModelIndex &index, int role) const { void *rowData = ls->get(ls, index.row()); if(rowData && getvalue) { void *value = getvalue(rowData, 0); - return QString::fromUtf8((char*)value); + if(value) { + return QString::fromUtf8((char*)value); + } } } return QVariant(); @@ -99,6 +101,113 @@ void ListModel::selectionChanged(const QItemSelection& selected, const QItemSele +TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model){ + this->obj = obj; + this->view = view; + this->var = var; + this->model = model; + this->onactivate = nullptr; + this->onactivatedata = nullptr; + this->onselection = nullptr; + this->onselectiondata = nullptr; +} + +void TableModel::setActivationCallback(ui_callback f, void *userdata) { + onactivate = f; + onactivatedata = userdata; +} + +void TableModel::setSelectionCallback(ui_callback f, void *userdata) { + onselection = f; + onselectiondata = userdata; +} + +void TableModel::update(int row) { + if(row >= 0) { + this->update(row); + } else { + this->beginResetModel(); + this->endResetModel(); + } +} + +int TableModel::rowCount(const QModelIndex& parent) const { + UiList *list = (UiList*)var->value; + return ui_list_count(list); +} + +int TableModel::columnCount(const QModelIndex &parent) const { + return model->columns; +} + +QVariant TableModel::data(const QModelIndex &index, int role) const { + if(role == Qt::DisplayRole) { + UiList *ls = (UiList*)var->value; + void *rowData = ls->get(ls, index.row()); + if(rowData && model->getvalue) { + int col = index.column(); + void *value = model->getvalue(rowData, col); + if(value) { + UiModelType type = model->types[col]; + switch(type) { + case UI_STRING: { + return QString::fromUtf8((char*)value); + } + case UI_STRING_FREE: { + QString s = QString::fromUtf8((char*)value); + free(value); + return s; + } + case UI_INTEGER: { + intptr_t i = (intptr_t)value; + return QString::number(i); + } + case UI_ICON: { + break; + } + case UI_ICON_TEXT: { + break; + } + case UI_ICON_TEXT_FREE: { + break; + } + } + } + } + } + return QVariant(); +} + +QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if(role == Qt::DisplayRole) { + char *label = model->titles[section]; + return QString::fromUtf8(label); + } + return QVariant(); +} + +void TableModel::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { + UiListSelection sel = ui_selection_model_to_selection(view->selectionModel()); + + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = &sel; + event.intval = sel.count; + event.set = ui_get_setop(); + + if(onactivate) { + onactivate(&event, onactivatedata); + } + if(onselection) { + onselection(&event, onselectiondata); + } + + free(sel.rows); +} + + UiListSelection ui_selection_model_to_selection(QItemSelectionModel *model) { UiListSelection sel; diff --git a/ui/qt/model.h b/ui/qt/model.h index e2c2e59..839b38e 100644 --- a/ui/qt/model.h +++ b/ui/qt/model.h @@ -71,6 +71,38 @@ public slots: const QItemSelection & deselected); }; +class TableModel : public QAbstractListModel { + Q_OBJECT + + UiModel *model; + ui_callback onactivate; + void *onactivatedata; + ui_callback onselection; + void *onselectiondata; + +public: + UiObject *obj; + UiVar *var; + QTreeView *view; + + TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model); + + void setActivationCallback(ui_callback f, void *userdata); + void setSelectionCallback(ui_callback f, void *userdata); + + void update(int row); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + +public slots: + void selectionChanged( + const QItemSelection & selected, + const QItemSelection & deselected); +}; + UiListSelection ui_selection_model_to_selection(QItemSelectionModel *model); diff --git a/ui/win32/objs.mk b/ui/win32/objs.mk index e74d9c6..0dce885 100644 --- a/ui/win32/objs.mk +++ b/ui/win32/objs.mk @@ -29,7 +29,8 @@ WIN32_SRC_DIR = ui/win32/ WIN32_OBJPRE = $(OBJ_DIR)$(WIN32_SRC_DIR) -WIN32OBJ = toolkit.obj +WIN32OBJ = toolkit.obj +WIN32OBJ += window.obj TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%) TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c) diff --git a/ui/win32/toolkit.c b/ui/win32/toolkit.c index 4afbc73..cd1f1ba 100644 --- a/ui/win32/toolkit.c +++ b/ui/win32/toolkit.c @@ -29,6 +29,8 @@ #include "toolkit.h" #include "Windows.h" +#include "window.h" + #include "../common/properties.h" #include @@ -45,6 +47,8 @@ void *exit_data; void ui_init(const char *appname, int argc, char **argv) { application_name = appname; + + ui_window_init(); } const char* ui_appname() { diff --git a/ui/win32/window.c b/ui/win32/window.c new file mode 100644 index 0000000..184f7fd --- /dev/null +++ b/ui/win32/window.c @@ -0,0 +1,99 @@ +/* + * 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 "window.h" +#include "Windows.h" + + +#include +#include +#include + + +static HINSTANCE hInstance; + +static const wchar_t *mainWindowClass = L"UiMainWindow"; + +LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch(uMsg) { + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + return 0; +} + +void ui_window_init(void) { + hInstance = GetModuleHandle(NULL); + + WNDCLASSEX wc = { sizeof(WNDCLASSEX) }; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = mainWindowClass; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + + if(!RegisterClassEx(&wc)) { + MessageBox(NULL, "RegisterClassEx failed", "Error", MB_ICONERROR); + exit(-1); + } +} + +static UiObject* create_window(const char *title, void *window_data, bool simple) { + CxMempool *mp = cxMempoolCreateSimple(256); + const CxAllocator *a = mp->allocator; + UiObject *obj = cxCalloc(a, 1, sizeof(UiObject)); + obj->ctx = uic_context(obj, mp); + obj->window = window_data; + + HWND hwnd = CreateWindowEx( + 0, + L"UiMainWindow", + "Test", + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, + CW_USEDEFAULT, + 800, + 600, + NULL, + NULL, + hInstance, + NULL); + + ShowWindow(hwnd, SW_SHOWNORMAL); + UpdateWindow(hwnd); + + return obj; +} + +UiObject *ui_window(const char *title, void *window_data) { + return create_window(title, window_data, FALSE); +} + diff --git a/ui/win32/window.h b/ui/win32/window.h new file mode 100644 index 0000000..305f40a --- /dev/null +++ b/ui/win32/window.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include +#include "../ui/window.h" +#include "../common/context.h" +#include "../common/object.h" + +#include "toolkit.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +void ui_window_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* WINDOW_H */ + -- 2.43.5