#define MZ_APPLICATION_H
#include <ui/ui.h>
+#include <cx/list.h>
+
+#include <inttypes.h>
+#include <sys/types.h>
#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
*/
* 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)
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);
#include "filebrowser.h"
+#include <unistd.h>
+#include <dirent.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <cx/array_list.h>
+
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);
+}
#include "application.h"
+#include <time.h>
+#include <cx/list.h>
+
#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
#include "gtk-filesview.h"
-
+#include <sys/stat.h>
+#include <sys/types.h>
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;i<view->numitems;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;i<numsections;i++) {
+ view->sections[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;i<numsections;i++) {
+ MzViewSection sec = view->sections[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));
}
#define MZ_GTK_FILEVIEW_H
#include "application.h"
+#include "filebrowser.h"
+
+#include <cx/array_list.h>
#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 {
void mz_files_view_snapshot(GtkWidget *widget, GtkSnapshot *snapshot);
+void mz_update_files_view(MzFilesView *view, FilesSection *sections, size_t numsections);
+
#ifdef __cplusplus
}
#endif
#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);
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);
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);
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") {
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);
}
/*
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);
+}
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
}