From 7a785272129cc11a1a8342803676ce1d121a4acf Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Wed, 2 Apr 2025 21:27:44 +0200 Subject: [PATCH] add attachment window --- application/application.h | 9 ++++++ application/attachment.c | 66 +++++++++++++++++++++++++++++++++++++-- application/attachment.h | 7 ++++- application/gtk-text.c | 2 +- ui/gtk/image.c | 64 ++++++++++++++++++++++++++++++++++--- ui/gtk/image.h | 20 ++++++++++++ ui/qt/label.cpp | 2 +- ui/qt/qt5.pro | 2 ++ ui/qt/widget.cpp | 61 ++++++++++++++++++++++++++++++++++++ ui/qt/widget.h | 38 ++++++++++++++++++++++ ui/ui/image.h | 5 +++ 11 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 ui/qt/widget.cpp create mode 100644 ui/qt/widget.h diff --git a/application/application.h b/application/application.h index 23ff571..03a4fe4 100644 --- a/application/application.h +++ b/application/application.h @@ -168,6 +168,15 @@ typedef struct AttachmentModel { UiGeneric *img; } AttachmentModel; +typedef struct AttachmentWindow { + UiObject *obj; + + Resource *resource; + UiList *attachments; + + UiGeneric *image; +} AttachmentWindow; + void application_init(); void application_startup(UiEvent *event, void *data); diff --git a/application/attachment.c b/application/attachment.c index b51678f..501c483 100644 --- a/application/attachment.c +++ b/application/attachment.c @@ -28,6 +28,10 @@ #include "attachment.h" +#include "note.h" + +#include + Attachment* attachment_create( const CxAllocator *a, int64_t parent, @@ -41,7 +45,7 @@ Attachment* attachment_create( return attachment; } -void attachment_create_ui_model(UiContext *ctx, Attachment *attachment) { +void attachment_create_ui_model(UiContext *ctx, Attachment *attachment, Resource *resource) { if(attachment->ui) { return; } @@ -54,6 +58,8 @@ void attachment_create_ui_model(UiContext *ctx, Attachment *attachment) { model->img = ui_generic_new(ctx, NULL); } model->ctx = ctx; + + model->parent_note = resource; } void attachment_set_image(Attachment *attachment, void *img) { @@ -72,7 +78,63 @@ void attachment_item(UiObject *obj, int index, void *elm, void *userdata) { Attachment *attachment = elm; if(attachment->type == NOTE_ATTACHMENT_IMAGE) { - UIWIDGET imgviewer = ui_imageviewer(obj, .value = attachment->ui->img, .scrollarea = FALSE, .autoscale = TRUE); + UIWIDGET imgviewer = ui_imageviewer(obj, + .value = attachment->ui->img, + .scrollarea = FALSE, + .autoscale = TRUE, + .onbuttonpress = action_attachment_clicked, + .onbuttonpressdata = attachment); ui_widget_set_size(imgviewer, 100, 80); } } + +void action_attachment_clicked(UiEvent *event, void *userdata) { + Attachment *attachment = userdata; + + UiObject *attachment_window = attachment_window_create(attachment->ui->parent_note, attachment); + ui_show(attachment_window); +} + + +UiObject* attachment_window_create(Resource *resource, Attachment *selected_attachment) { + cxmutstr title = cx_asprintf("%s - attachments", note_get_title(resource)); + UiObject *obj = ui_simple_window(title.ptr, NULL); + free(title.ptr); + + AttachmentWindow *wdata = attachment_window_create_data(obj, resource); + obj->window = wdata; + + ui_headerbar(obj) { + ui_headerbar_start(obj) { + ui_button(obj, .icon = UI_ICON_GO_BACK); + ui_button(obj, .icon = UI_ICON_GO_FORWARD); + } + } + + ui_imageviewer(obj, .value = wdata->image, .autoscale = TRUE, .scrollarea = TRUE, .useradjustable = TRUE, .fill = UI_ON); + + + return obj; +} + +static void update_attachments(AttachmentWindow *wdata, NoteModel *model) { + void *elm = ui_list_first(model->attachments); + while(elm) { + ui_list_append(wdata->attachments, elm); + elm = ui_list_next(model->attachments); + } +} + +AttachmentWindow* attachment_window_create_data(UiObject *obj, Resource *resource) { + AttachmentWindow *wdata = ui_malloc(obj->ctx, sizeof(AttachmentWindow)); + memset(wdata, 0, sizeof(AttachmentWindow)); + + wdata->obj = obj; + wdata->resource = resource; + wdata->attachments = ui_list_new(obj->ctx, NULL); + wdata->image = ui_generic_new(obj->ctx, NULL); + + update_attachments(wdata, resource->model); + + return wdata; +} diff --git a/application/attachment.h b/application/attachment.h index fcc2d5e..d10fb16 100644 --- a/application/attachment.h +++ b/application/attachment.h @@ -40,7 +40,7 @@ Attachment* attachment_create( AttachmentType type, const char *name); -void attachment_create_ui_model(UiContext *ctx, Attachment *attachment); +void attachment_create_ui_model(UiContext *ctx, Attachment *attachment, Resource *resource); void attachment_set_image(Attachment *attachment, void *img); @@ -49,6 +49,11 @@ void attachment_set_image(Attachment *attachment, void *img); */ void attachment_item(UiObject *obj, int index, void *elm, void *userdata); +void action_attachment_clicked(UiEvent *event, void *userdata); + +UiObject* attachment_window_create(Resource *resource, Attachment *selected_attachment); + +AttachmentWindow* attachment_window_create_data(UiObject *obj, Resource *resource); #ifdef __cplusplus } diff --git a/application/gtk-text.c b/application/gtk-text.c index 3794565..8d9777b 100644 --- a/application/gtk-text.c +++ b/application/gtk-text.c @@ -275,7 +275,7 @@ static void editor_attach_image(NoteEditor *editor, GdkPixbuf *pixbuf, char *att // create attachment Attachment *attachment = attachment_create(model->note_allocator, note->resource_id, NOTE_ATTACHMENT_IMAGE, util_resource_name(attachment_path)); - attachment_create_ui_model(model->ctx, attachment); + attachment_create_ui_model(model->ctx, attachment, note); g_object_ref(pixbuf); attachment_set_image(attachment, pixbuf); diff --git a/ui/gtk/image.c b/ui/gtk/image.c index 41c9b44..04cb75c 100644 --- a/ui/gtk/image.c +++ b/ui/gtk/image.c @@ -88,6 +88,11 @@ UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) { UiImageViewer *imgviewer = malloc(sizeof(UiImageViewer)); memset(imgviewer, 0, sizeof(UiImageViewer)); + imgviewer->obj = obj; + imgviewer->onbuttonpress = args.onbuttonpress; + imgviewer->onbuttonpressdata = args.onbuttonpressdata; + imgviewer->onbuttonrelease = args.onbuttonrelease; + imgviewer->onbuttonreleasedata = args.onbuttonreleasedata; if(args.image_padding > 0) { imgviewer->padding_left = args.image_padding; imgviewer->padding_right = args.image_padding; @@ -144,6 +149,11 @@ UIWIDGET ui_imageviewer_create(UiObject *obj, UiImageViewerArgs args) { g_signal_connect(drag, "drag-update", G_CALLBACK(ui_imageviewer_drag_update_cb), imgviewer); gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(drag)); + GtkGesture *click = gtk_gesture_click_new(); + g_signal_connect(click, "pressed", G_CALLBACK(ui_imageviewer_pressed_cb), imgviewer); + g_signal_connect(click, "released", G_CALLBACK(ui_imageviewer_released_cb), imgviewer); + gtk_widget_add_controller(GTK_WIDGET(drawingarea), GTK_EVENT_CONTROLLER(click)); + #elif GTK_MAJOR_VERSION == 3 g_signal_connect( drawingarea, @@ -351,7 +361,7 @@ gboolean ui_imageviewer_scroll( } void ui_imageviewer_drag_begin_cb( - GtkGestureDrag* self, + GtkGestureDrag *self, gdouble start_x, gdouble start_y, gpointer userdata) @@ -371,7 +381,7 @@ void ui_imageviewer_drag_end_cb( } void ui_imageviewer_drag_update_cb( - GtkGestureDrag* self, + GtkGestureDrag *self, gdouble x, gdouble y, gpointer userdata) @@ -384,6 +394,48 @@ void ui_imageviewer_drag_update_cb( } } +static void imgviewer_button_event( + GtkGestureClick *gesture, + UiImageViewer *imgviewer, + ui_callback callback, + void *userdata) +{ + UiEvent event; + event.obj = imgviewer->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture)); + event.set = 0; + callback(&event, userdata); +} + +void ui_imageviewer_pressed_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonpress) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonpress, imgviewer->onbuttonpressdata); + } +} + +void ui_imageviewer_released_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata) +{ + UiImageViewer *imgviewer = userdata; + if(imgviewer->onbuttonrelease) { + imgviewer_button_event(self, imgviewer, imgviewer->onbuttonrelease, imgviewer->onbuttonreleasedata); + } +} + #else gboolean ui_imageviewer_scroll_event( @@ -391,7 +443,7 @@ gboolean ui_imageviewer_scroll_event( GdkEventScroll event, gpointer userdata) { - printf("scroll event\n"); + // TODO return FALSE; } @@ -400,7 +452,8 @@ gboolean ui_imageviewer_button_press_event( GdkEventButton event, gpointer userdata) { - printf("button pressed\n"); + // TODO + return FALSE; } gboolean ui_imageviewer_button_release_event( @@ -408,7 +461,8 @@ gboolean ui_imageviewer_button_release_event( GdkEventButton event, gpointer userdata) { - printf("button released\n"); + // TODO + return FALSE; } #endif diff --git a/ui/gtk/image.h b/ui/gtk/image.h index 7e9bf9f..987c73b 100644 --- a/ui/gtk/image.h +++ b/ui/gtk/image.h @@ -37,6 +37,7 @@ extern "C" { #endif typedef struct UiImageViewer { + UiObject *obj; GtkWidget *widget; UiVar *var; int padding_left; @@ -56,6 +57,11 @@ typedef struct UiImageViewer { UiBool isautoscaled; double user_scale; double scale; + + ui_callback onbuttonpress; + void *onbuttonpressdata; + ui_callback onbuttonrelease; + void *onbuttonreleasedata; } UiImageViewer; void ui_cairo_draw_image(UiImageViewer *imgviewer, cairo_t *cr, int width, int height); @@ -90,6 +96,20 @@ void ui_imageviewer_drag_update_cb( gdouble y, gpointer userdata); +void ui_imageviewer_pressed_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata); + +void ui_imageviewer_released_cb( + GtkGestureClick *self, + gint n_press, + gdouble x, + gdouble y, + gpointer userdata); + #else gboolean ui_imageviewer_scroll_event( diff --git a/ui/qt/label.cpp b/ui/qt/label.cpp index ef7a9fc..49917d0 100644 --- a/ui/qt/label.cpp +++ b/ui/qt/label.cpp @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2015 Olaf Wintermann. All rights reserved. + * 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: diff --git a/ui/qt/qt5.pro b/ui/qt/qt5.pro index 60cf702..43d9637 100644 --- a/ui/qt/qt5.pro +++ b/ui/qt/qt5.pro @@ -49,6 +49,7 @@ SOURCES += tree.cpp SOURCES += button.cpp SOURCES += label.cpp SOURCES += graphics.cpp +SOURCES += widget.cpp HEADERS += toolkit.h HEADERS += window.h @@ -62,4 +63,5 @@ HEADERS += tree.h HEADERS += button.h HEADERS += label.h HEADERS += graphics.h +HEADERS += widget.h diff --git a/ui/qt/widget.cpp b/ui/qt/widget.cpp new file mode 100644 index 0000000..283043b --- /dev/null +++ b/ui/qt/widget.cpp @@ -0,0 +1,61 @@ +/* + * 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 "widget.h" + +#include "container.h" +#include "../common/context.h" + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs args) { + UIWIDGET widget = create_widget(obj, args, userdata); + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, args); + ctn->add(widget, false); + return widget; +} + +UIWIDGET ui_separator_create(UiObject *obj, UiWidgetArgs *args) { + QFrame *separator = new QFrame(); + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Sunken); + + UiContainerPrivate *ctn = ui_obj_container(obj); + UI_APPLY_LAYOUT(ctn->layout, (*args)); + + ctn->add(separator, false); + + return separator; +} + +void ui_widget_set_size(UIWIDGET w, int width, int height) { + w->resize(width >= 0 ? width : w->width(), height >= 0 ? w->height()); +} + +void ui_widget_redraw(UIWIDGET w) { + w->repaint(); +} diff --git a/ui/qt/widget.h b/ui/qt/widget.h new file mode 100644 index 0000000..f9ed0bf --- /dev/null +++ b/ui/qt/widget.h @@ -0,0 +1,38 @@ +/* + * 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 WIDGET_H +#define WIDGET_H + +#include "toolkit.h" + +#include "../ui/widget.h" + + +#endif /* WIDGET_H */ + diff --git a/ui/ui/image.h b/ui/ui/image.h index d54c9e9..b0a9e91 100644 --- a/ui/ui/image.h +++ b/ui/ui/image.h @@ -61,6 +61,11 @@ typedef struct UiImageViewerArgs { UiGeneric *value; const char *varname; UiMenuBuilder *contextmenu; + + ui_callback onbuttonpress; + void *onbuttonpressdata; + ui_callback onbuttonrelease; + void *onbuttonreleasedata; } UiImageViewerArgs; #define ui_imageviewer(obj, ...) ui_imageviewer_create(obj, (UiImageViewerArgs){ __VA_ARGS__ } ) -- 2.43.5