From ffb8aefa77967b7df4efd58ebde51aad869ca6ee Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Mon, 20 Jan 2025 21:55:15 +0100 Subject: [PATCH] add basic construct for gtk-filesview --- mizunara/application.h | 79 +++++++++++++++++-- mizunara/filebrowser.c | 120 ++++++++++++++++++++++++++++- mizunara/filebrowser.h | 43 ++++++++++- mizunara/gtk-filesview.c | 159 ++++++++++++++++++++++++++++++++++++--- mizunara/gtk-filesview.h | 25 +++++- mizunara/window.c | 44 ++++++++--- mizunara/window.h | 3 +- 7 files changed, 441 insertions(+), 32 deletions(-) diff --git a/mizunara/application.h b/mizunara/application.h index dfa9c89..af25e8b 100644 --- a/mizunara/application.h +++ b/mizunara/application.h @@ -30,15 +30,32 @@ #define MZ_APPLICATION_H #include +#include + +#include +#include #ifdef __cplusplus extern "C" { #endif +typedef struct MainWindow MainWindow; +typedef struct FileBrowser FileBrowser; +typedef struct FilesSection FilesSection; +typedef struct FileInfo FileInfo; + +typedef void (*update_files_view_func)( + FileBrowser *browser, + FilesSection *sections, + size_t numsections, + void *userdata); + /* * main window data */ -typedef struct MainWindow { +struct MainWindow { + UiObject *obj; + /* * sidebar default directories */ @@ -48,14 +65,39 @@ typedef struct MainWindow { * sidebar xdg user directories */ UiList *user_dirs; -} MainWindow; + + /* + * files icon/grid view + */ + UIWIDGET files_gridview; +}; /* * main window document object * later, the MainWindow will contain multiple documents (document tabview) */ -typedef struct FileBrowser { +struct FileBrowser { UiContext *ctx; + MainWindow *window; + + /* + * current operation number + * + * Each background operation (directory loading) gets a new opnum + * increased by 1. When the operation is finished, the result is only + * added, when the operation opnum is bigger than the FileBrowser opnum. + * + * This makes sure, an older operation, that does take much longer for + * some reason, doesn't override the result of a newer operation, that was + * initiated later. + */ + uint64_t opnum; + + /* + * current path + * used by the path textfield + */ + UiString *path; /* * view tabview (icon-grid, list) @@ -63,15 +105,40 @@ typedef struct FileBrowser { UiInteger *view; /* - * gridview file list + * update_grid(FileBrowser *, FilesSection *,size_t, void *) + * + * file gridview update function + */ + update_files_view_func update_grid; + + /* + * update_grid userdata */ - UiList *grid_files; + void *update_grid_data; /* * file listview list */ UiList *list_files; -} FileBrowser; +}; + +/* + * When a directory is loaded, the files are grouped into one or multiple + * sections + */ +struct FilesSection { + CxList *files; + size_t num_bytes; + time_t mtime_from; + time_t mtime_to; +}; + +struct FileInfo { + char *name; + size_t size; + time_t mtime; + mode_t mode; +}; void application_init(void); diff --git a/mizunara/filebrowser.c b/mizunara/filebrowser.c index 1a7b216..2787172 100644 --- a/mizunara/filebrowser.c +++ b/mizunara/filebrowser.c @@ -28,16 +28,134 @@ #include "filebrowser.h" +#include +#include +#include +#include +#include + +#include + FileBrowser* filebrowser_new(const char *url) { FileBrowser *browser = ui_document_new(sizeof(FileBrowser)); UiContext *ctx = ui_document_context(browser); browser->ctx = ctx; + browser->path = ui_string_new(ctx, "path"); browser->view = ui_int_new(ctx, "view"); - browser->grid_files = ui_list_new(ctx, "grid_files"); browser->list_files = ui_list_new(ctx, "list_files"); return browser; } + +void filebrowser_load(FileBrowser *browser, const char *opt_newurl) { + const char *url; + if(opt_newurl) { + url = opt_newurl; + } else { + url = ui_get(browser->path); + if(strlen(url) == 0) { + url = "/"; + ui_set(browser->path, url); + } + } + + FileBrowserOp *op = filebrowser_op_new(browser, url); + ui_job(browser->window->obj, jobthr_filebrowser_op_load, op, filebrowser_op_finished, op); +} + +FileBrowserOp* filebrowser_op_new(FileBrowser *browser, const char *url) { + FileBrowserOp *op = malloc(sizeof(FileBrowserOp)); + op->browser = browser; + op->url = strdup(url); + op->opnum = browser->opnum+1; + op->result = NULL; + op->error = 0; + return op; +} + +static void cleanup_file_info(FileInfo *f) { + free(f->name); +} + +/* + * This function must only be used with ui_job(). + * + * Always return 0 to make sure, filebrowser_op_finished is called. + */ +int jobthr_filebrowser_op_load(void *data) { + FileBrowserOp *op = data; + printf("jobthr_filebrowser_op_load\n"); + + int fd = open(op->url, O_RDONLY); + if(fd < 0) { + op->error = errno; + return 0; + } + + DIR *dir = fdopendir(fd); + if(!dir) { + op->error = errno; + close(fd); + return 0; + } + + op->result = cxArrayListCreateSimple(sizeof(FileInfo), 1024); + op->result->collection.simple_destructor = (cx_destructor_func)cleanup_file_info; + struct dirent *ent; + struct stat s; + while((ent = readdir(dir)) != NULL) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + FileInfo f; + f.name = strdup(ent->d_name); + + if(!fstatat(fd, ent->d_name, &s, 0)) { + f.size = s.st_size; + f.mtime = s.st_mtime; + f.mode = s.st_mode; + } else { + f.size = 0; + f.mtime = 0; + f.mode = 0; + } + + cxListAdd(op->result, &f); + } + + closedir(dir); + return 0; +} + +void filebrowser_op_finished(UiEvent *event, void *data) { + FileBrowserOp *op = data; + printf("filebrowser_op_finished\n"); + + if(op->result) { + int view = ui_get(op->browser->view); + if(view == 0 && op->browser->update_grid) { + // TODO: group feature + // for now, just create a single section and move the result list + FilesSection *section = malloc(sizeof(FilesSection)); + memset(section, 0, sizeof(FilesSection)); + section->files = op->result; + op->result = NULL; + + // update_grid takes ownership of section + op->browser->update_grid(op->browser, section, 1, op->browser->update_grid_data); + } else if(view == 1) { + + } + } else { + // TODO: error msg + } + + cxListFree(op->result); + + free(op->url); + free(op); +} diff --git a/mizunara/filebrowser.h b/mizunara/filebrowser.h index 482c78a..c5a76af 100644 --- a/mizunara/filebrowser.h +++ b/mizunara/filebrowser.h @@ -31,13 +31,54 @@ #include "application.h" +#include +#include + #ifdef __cplusplus extern "C" { #endif - + +/* + * FileBrowser directory loading operation + * + * Make sure to update filebrowser_op_new when changing this struct! + */ +typedef struct FileBrowserOp { + FileBrowser *browser; + char *url; + + /* + * array type: FileInfo + */ + CxList *result; + + /* + * error number in case result is NULL + */ + int error; + + /* + * see FileBrowser opnum + */ + uint64_t opnum; +} FileBrowserOp; + + FileBrowser* filebrowser_new(const char *url); +FileBrowserOp* filebrowser_op_new(FileBrowser *browser, const char *url); + +/* + * Load the current (or new) url + */ +void filebrowser_load(FileBrowser *browser, const char *opt_newurl); + +int jobthr_filebrowser_op_load(void *data); + +void filebrowser_op_finished(UiEvent *event, void *data); + + #ifdef __cplusplus } #endif diff --git a/mizunara/gtk-filesview.c b/mizunara/gtk-filesview.c index 0ce526c..5299b5a 100644 --- a/mizunara/gtk-filesview.c +++ b/mizunara/gtk-filesview.c @@ -28,34 +28,173 @@ #include "gtk-filesview.h" - +#include +#include G_DEFINE_TYPE(MzFilesView, mz_files_view, GTK_TYPE_WIDGET) + +static void drag_begin_cb( + GtkGestureDrag* self, + gdouble start_x, + gdouble start_y, + gpointer user_data); +static void drag_end_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer user_data); +static void drag_update_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer user_data); + + static void mz_files_view_class_init(MzFilesViewClass *klass) { printf("mz_files_view_class_init\n"); - klass->parent_class.snapshot = mz_files_view_snapshot; + klass->parent_class.snapshot = mz_files_view_snapshot; } static void mz_files_view_init(MzFilesView *self) { - self->data = NULL; + self->sections = NULL; + self->numsections = 0; + + self->items = NULL; + self->numitems = 0; + + // event handler + GtkGesture *drag = gtk_gesture_drag_new(); + g_signal_connect(drag, "drag-begin", G_CALLBACK(drag_begin_cb), self); + g_signal_connect(drag, "drag-end", G_CALLBACK(drag_end_cb), self); + g_signal_connect(drag, "drag-update", G_CALLBACK(drag_update_cb), self); + gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(drag)); } MzFilesView* mz_files_view_new(void) { MzFilesView *obj = g_object_new(mz_files_view_get_type(), NULL); - obj->data = NULL; return obj; } void mz_files_view_snapshot(GtkWidget *widget, GtkSnapshot *snapshot) { - printf("mz_files_view_snapshot\n"); + MzFilesView *view = (MzFilesView*)widget; + printf("MzFilesView snapshot\n"); + + int width = gtk_widget_get_width(widget); + int height = gtk_widget_get_height(widget); + + int item_width = 80; + int item_height = 120; + + int items_per_line = width / item_width; - GdkRGBA red; - gdk_rgba_parse (&red, "red"); + if(view->numitems == 0) { + return; + } + + GtkAllocation img_alloc; + GtkAllocation label_alloc; + img_alloc.width = 64; + img_alloc.height = 64; + label_alloc.width = 76; + label_alloc.height = 48; + + int x = 0; + int y = 0; + for(size_t i=0;inumitems;i++) { + img_alloc.x = 20 + x * item_width; + img_alloc.y = 20 + y * item_height; + gtk_widget_size_allocate(view->items[i].image, &img_alloc, -1); + label_alloc.x = img_alloc.x -1; + label_alloc.y = img_alloc.y + 64 + 4; + gtk_widget_size_allocate(view->items[i].label, &label_alloc, -1); + + x++; + if(x >= items_per_line) { + x = 0; + y++; + } + } + + //gtk_widget_size_allocate(view->items[0].image, &child_allocation, -1); + + // draw children + GtkWidgetClass *cls = mz_files_view_parent_class; + cls->snapshot(widget, snapshot); +} + - int w = gtk_widget_get_width(widget); - int h = gtk_widget_get_height(widget); +/* -------------------------- event handler -------------------------- */ - gtk_snapshot_append_color(snapshot, &red, &GRAPHENE_RECT_INIT(0, 0, w, h)); +static void drag_begin_cb( + GtkGestureDrag* self, + gdouble start_x, + gdouble start_y, + gpointer user_data) +{ + +} + +static void drag_end_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer user_data) +{ + +} + +static void drag_update_cb( + GtkGestureDrag* self, + gdouble x, + gdouble y, + gpointer user_data) +{ + +} + + +/* -------------------------- public -------------------------- */ + +void mz_update_files_view(MzFilesView *view, FilesSection *sections, size_t numsections) { + printf("mz_update_files_view\n"); + + // TODO: free previous files + free(view->items); + + + view->sections = calloc(numsections, sizeof(MzFilesView)); + size_t nchildren = 0; + for(size_t i=0;isections[i].section = sections[i]; + nchildren += cxListSize(sections[i].files); + } + view->numsections = numsections; + + GtkIconTheme *icontheme = gtk_icon_theme_get_for_display(gdk_display_get_default()); + GtkIconPaintable *dir_icon = gtk_icon_theme_lookup_icon(icontheme, "folder", NULL, 64, 1, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR); + GtkIconPaintable *file_icon = gtk_icon_theme_lookup_icon(icontheme, "folder", NULL, 64, 1, GTK_TEXT_DIR_LTR, GTK_ICON_LOOKUP_FORCE_REGULAR); + + // create widgets + view->items = calloc(nchildren, sizeof(MzIconGadget)); + view->numitems = nchildren; + + size_t w = 0; + for(size_t i=0;isections[i]; + CxIterator f = cxListIterator(sec.section.files); + cx_foreach(FileInfo *, finfo, f) { + MzIconGadget gadget; + gadget.image = gtk_image_new_from_paintable(GDK_PAINTABLE(S_ISDIR(finfo->mode) ? dir_icon : file_icon)); + gadget.label = gtk_label_new(finfo->name); + + gtk_widget_set_parent(gadget.image, GTK_WIDGET(view)); + gtk_widget_set_parent(gadget.label, GTK_WIDGET(view)); + + view->items[w++] = gadget; + } + } + + gtk_widget_queue_draw(GTK_WIDGET(view)); } diff --git a/mizunara/gtk-filesview.h b/mizunara/gtk-filesview.h index cb51006..f315b22 100644 --- a/mizunara/gtk-filesview.h +++ b/mizunara/gtk-filesview.h @@ -30,14 +30,35 @@ #define MZ_GTK_FILEVIEW_H #include "application.h" +#include "filebrowser.h" + +#include #ifdef __cplusplus extern "C" { #endif + + +typedef struct MzIconGadget { + GtkWidget *image; + GtkWidget *label; +} MzIconGadget; + +typedef struct MzViewSection { + FilesSection section; + GtkWidget *heading; + int x; + int height; +} MzViewSection; + typedef struct MzFilesView { GtkWidget widget; - void *data; + MzIconGadget *items; + size_t numitems; + + MzViewSection *sections; + size_t numsections; } MzFilesView; typedef struct MzFilesViewClass { @@ -48,6 +69,8 @@ MzFilesView* mz_files_view_new(void); void mz_files_view_snapshot(GtkWidget *widget, GtkSnapshot *snapshot); +void mz_update_files_view(MzFilesView *view, FilesSection *sections, size_t numsections); + #ifdef __cplusplus } #endif diff --git a/mizunara/window.c b/mizunara/window.c index 8293e5d..adb29ac 100644 --- a/mizunara/window.c +++ b/mizunara/window.c @@ -34,8 +34,23 @@ #ifdef GTK_MAJOR_VERSION #include "gtk-filesview.h" #include "bookmarks.h" +#include "common/context.h" #endif + +/* + * maybe the update_files_view_func should be replaced with a UiGeneric wrapper + */ +void window_update_gridview( + FileBrowser *browser, + FilesSection *sections, + size_t numsections, + void *userdata) +{ + MainWindow *win = userdata; + mz_update_files_view((MzFilesView*)win->files_gridview, sections, numsections); +} + UiObject* window_create(const char *url) { // toplevel window object UiObject *obj = ui_sidebar_window("Mizunara", NULL); @@ -45,16 +60,20 @@ UiObject* window_create(const char *url) { memset(wdata, 0, sizeof(MainWindow)); windowdata_init(obj->ctx, wdata); obj->window = wdata; + wdata->obj = obj; // create browser document // TODO: in the future we want multiple documents per window (tabs) FileBrowser *browser = filebrowser_new(url); ui_attach_document(obj->ctx, browser); + browser->window = wdata; + browser->update_grid = window_update_gridview; + browser->update_grid_data = wdata; // add UI ui_sidebar0(obj) { UiSubList sublists[] = { - { .value = wdata->default_dirs, .userdata = window_sidebar_default_dirs_item }, + { .value = wdata->default_dirs, .userdata = window_sidebar_user_dirs_item }, { .value = wdata->user_dirs, .userdata = window_sidebar_user_dirs_item, .separator = TRUE } }; ui_sourcelist(obj, .sublists = sublists, .numsublists = 2, .getvalue = window_sidebar_getvalue); @@ -63,7 +82,7 @@ UiObject* window_create(const char *url) { ui_hbox(obj, .spacing = 4, .fill = UI_OFF) { ui_button(obj, .style_class = "flat", .icon = UI_ICON_GO_BACK); ui_button(obj, .style_class = "flat", .icon = UI_ICON_GO_FORWARD); - ui_path_textfield(obj, .varname = "path", .fill = UI_ON); + ui_path_textfield(obj, .varname = "path", .fill = UI_ON, .onactivate = action_pathbar_activate); } window_create_browser_view(obj, wdata); @@ -84,7 +103,7 @@ static UIWIDGET create_filesview(UiObject *obj, UiWidgetArgs args, void *userdat void window_create_browser_view(UiObject *obj, MainWindow *win) { ui_tabview(obj, .tabview = UI_TABVIEW_INVISIBLE, .varname = "view") { ui_tab(obj, "iconview") { - ui_customwidget(obj, create_filesview, NULL, .fill = UI_ON); + win->files_gridview = ui_customwidget(obj, create_filesview, NULL, .fill = UI_ON); } ui_tab(obj, "listview") { @@ -98,10 +117,7 @@ void windowdata_init(UiContext *ctx, MainWindow *win) { win->default_dirs = ui_list_new(ctx, NULL); win->user_dirs = ui_list_new(ctx, NULL); - bookmarks_init_windowdata(win->user_dirs); - - // TODO: init lists - ui_list_append(win->default_dirs, "Test"); + bookmarks_init_windowdata(win->default_dirs, win->user_dirs); } /* @@ -114,13 +130,17 @@ void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, U getvalue(NULL, rowdata, index, item); } -void window_sidebar_default_dirs_item(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item) { - item->icon = strdup("user-home-symbolic"); - item->label = strdup("Home"); -} - void window_sidebar_user_dirs_item(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item) { MZBookmark *bookmark = rowdata; item->icon = strdup(bookmark->icon); item->label = strdup(bookmark->name); } + + + +void action_pathbar_activate(UiEvent *event, void *userdata) { + printf("action_pathbar_activate: %s\n", event->eventdata); + char *path = event->eventdata; + FileBrowser *browser = event->document; + filebrowser_load(browser, path); +} diff --git a/mizunara/window.h b/mizunara/window.h index d3dd96a..d803d17 100644 --- a/mizunara/window.h +++ b/mizunara/window.h @@ -43,9 +43,10 @@ void windowdata_init(UiContext *ctx, MainWindow *win); void window_sidebar_getvalue(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); -void window_sidebar_default_dirs_item(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); void window_sidebar_user_dirs_item(void *sublist_userdata, void *rowdata, int index, UiSubListItem *item); +void action_pathbar_activate(UiEvent *event, void *userdata); + #ifdef __cplusplus } -- 2.43.5