diff options
| author | Mattias Andrée <m@maandree.se> | 2026-10-13 23:00:56 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-10-13 23:00:56 +0200 |
| commit | 0476071164c36b41de5b19e3e8a768feb422f27f (patch) | |
| tree | 09d16e177042b14020a48030176d99e9024bea66 /collection.c | |
| parent | Third commit (diff) | |
| download | gbild-0476071164c36b41de5b19e3e8a768feb422f27f.tar.gz gbild-0476071164c36b41de5b19e3e8a768feb422f27f.tar.bz2 gbild-0476071164c36b41de5b19e3e8a768feb422f27f.tar.xz | |
Fourth commit
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
| -rw-r--r-- | collection.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/collection.c b/collection.c new file mode 100644 index 0000000..c39f80b --- /dev/null +++ b/collection.c @@ -0,0 +1,364 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + +GtkWidget *collection_view; + +static GtkWidget *collection_pane; +static GtkCellRenderer *collection_renderer; +static GtkListStore *collection_model; +static int collection_height = -1; +static size_t popup_file; + +static char url_list_name[] = "text/uri-list"; +static GtkTargetEntry url_list = {url_list_name, 0u, 0u}; + +enum { + COLLECTION_THUMBNAIL, + COLLECTION_INDEX, + COLLECTION_COLUMNS +}; + +static void +free_paths(GList *paths) +{ + GList *path; + for (path = paths; path; path = path->next) + gtk_tree_path_free(path->data); + g_list_free(paths); +} + +static int +collection_thumbnail_size(void) +{ + return MAX(1, collection_view->allocation.height - 16); +} + +static GdkPixbuf * +create_thumbnail(struct file *file, int maximum_size) +{ + GdkPixbuf *thumbnail = NULL; + int width = file->image.width; + int height = file->image.height; + double w = (double)width; + double h = (double)height; + double s = (double)maximum_size / MAX(w, h); + s = MIN(1.0, s); + width = (int)(s * w); + height = (int)(s * h); + width = MAX(1, width); + height = MAX(1, height); + if (file->image.vector) { + thumbnail = rasterise(file->image.vector, width, height); + if (thumbnail) + return thumbnail; + } + if (file->image.raster) + return gdk_pixbuf_scale_simple(file->image.raster, width, height, GDK_INTERP_BILINEAR); + return NULL; +} + +static void +populate_collection(void) +{ + GtkTreeIter iter; + GdkPixbuf *thumbnail; + size_t i; + int thumbnail_size = collection_thumbnail_size(); + int item_size = thumbnail_size + 2 * gtk_icon_view_get_item_padding(GTK_ICON_VIEW(collection_view)); + gtk_icon_view_set_columns(GTK_ICON_VIEW(collection_view), nfiles ? (int)nfiles : 1); + gtk_icon_view_set_item_width(GTK_ICON_VIEW(collection_view), item_size); + gtk_cell_renderer_set_fixed_size(collection_renderer, thumbnail_size, thumbnail_size); + gtk_list_store_clear(collection_model); + for (i = 0u; i < nfiles; i++) { + thumbnail = create_thumbnail(&files[i], thumbnail_size); + if (!thumbnail) + continue; + gtk_list_store_append(collection_model, &iter); + gtk_list_store_set(collection_model, &iter, COLLECTION_THUMBNAIL, + thumbnail, COLLECTION_INDEX, (unsigned long)i, -1); + g_object_unref(thumbnail); + } +} + +static void +select_current_file(void) +{ + GtkTreeIter iter; + unsigned long index; + GtkTreePath *path; + if (!nfiles || !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(collection_model), &iter)) + return; + do { + gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &index, -1); + if ((size_t)index == current_file) { + path = gtk_tree_model_get_path(GTK_TREE_MODEL(collection_model), &iter); + gtk_icon_view_select_path(GTK_ICON_VIEW(collection_view), path); + gtk_icon_view_scroll_to_path(GTK_ICON_VIEW(collection_view), path, 1, 0.5, 0.5); + gtk_tree_path_free(path); + return; + } + } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(collection_model), &iter)); +} + +static int +get_selected_index(size_t *index) +{ + GList *paths; + GtkTreeIter iter; + unsigned long value; + int found = 0; + paths = gtk_icon_view_get_selected_items(GTK_ICON_VIEW(collection_view)); + if (!paths) + return 0; + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(collection_model), &iter, paths->data)) { + gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &value, -1); + *index = (size_t)value; + found = 1; + } + free_paths(paths); + return found; +} + +static void +collection_size_allocated(GtkWidget *widget, GtkAllocation *allocation, void *user) +{ + (void) widget; + (void) user; + if (allocation->height == collection_height) + return; + collection_height = allocation->height; + populate_collection(); + select_current_file(); +} + +static int +collection_index_at(int x, int y, size_t *index) +{ + GtkTreeIter iter; + unsigned long value; + int found = 0; + GtkTreePath *path = gtk_icon_view_get_path_at_pos(GTK_ICON_VIEW(collection_view), x, y); + if (!path) + return 0; + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(collection_model), &iter, path)) { + gtk_tree_model_get(GTK_TREE_MODEL(collection_model), &iter, COLLECTION_INDEX, &value, -1); + *index = (size_t)value; + found = 1; + } + gtk_tree_path_free(path); + return found; +} + +void +navigate_collection_drag(void) +{ + size_t index; + if (get_selected_index(&index) && navigate_without_redraw(index, 0u)) + navigate_redraw(); +} + +static void +collection_selection_changed(GtkIconView *view, void *user) +{ + (void) view; + (void) user; + navigate_collection_drag(); +} + +static void +reorder_file(size_t source, size_t target) +{ + struct file moved; + if (target >= nfiles) + target = nfiles - 1u; + if (!nfiles || source >= nfiles || source == target) + return; + moved = files[source]; + if (source < target) { + memmove(&files[source], &files[source + 1u], (target - source) * sizeof(*files)); + if (current_file == source) + current_file = target; + else if (current_file > source && current_file <= target) + current_file -= 1u; + } else { + memmove(&files[target + 1u], &files[target], (source - target) * sizeof(*files)); + if (current_file == source) + current_file = target; + else if (current_file >= target && current_file < source) + current_file += 1u; + } + files[target] = moved; + collection_files_changed(); + navigate_redraw(); +} + +static void +drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, + unsigned int info, unsigned int time, void *user) +{ + GError *error = NULL; + size_t index; + char *uri; + (void) widget; + (void) context; + (void) info; + (void) time; + (void) user; + if (!get_selected_index(&index) || index >= nfiles || !files[index].path) + return; + uri = g_filename_to_uri(files[index].path, NULL, &error); + if (!uri) { + weprintf("g_filename_to_uri: %s", error->message); + g_error_free(error); + return; + } + gtk_selection_data_set_uris(data, (char *[]){uri, NULL}); + g_free(uri); +} + +static void +drag_data_received(GtkWidget *widget, GdkDragContext *context, int x, int y, + GtkSelectionData *data, unsigned int info, unsigned int time, void *user) +{ + struct file *dropped_files; + char **uris; + size_t source_index, n; + GtkTreePath *path = NULL; + GtkWidget *source = gtk_drag_get_source_widget(context); + size_t target; + (void) info; + (void) user; + if (source == collection_view) { + if (get_selected_index(&source_index)) + goto reorder; + } else if (source == display_area) { + if (source == display_area) + source_index = current_file; + reorder: + if (gtk_icon_view_get_dest_item_at_pos(GTK_ICON_VIEW(widget), x, y, &path, NULL)) + target = (size_t)gtk_tree_path_get_indices(path)[0]; + else + target = nfiles; + if (path) + gtk_tree_path_free(path); + reorder_file(source_index, target); + } else { + uris = gtk_selection_data_get_uris(data); + n = get_dropped_files(&dropped_files, uris); + if (n) { + add_images(dropped_files, files_from_array(dropped_files, n)); + free(dropped_files); + } + if (uris) + g_strfreev(uris); + } + gtk_drag_finish(context, 1, 0, time); +} + +static void +remove_from_collection(GtkMenuItem *item, void *user) +{ + struct file removed; + (void) item; + (void) user; + if (popup_file >= nfiles) + return; + removed = files[popup_file]; + if (popup_file + 1u < nfiles) + memmove(&files[popup_file], &files[popup_file + 1u], (nfiles - (popup_file + 1u)) * sizeof(*files)); + nfiles -= 1u; + destroy_file(&removed); + forget_transformed_raster(); + if (current_file > popup_file) + current_file -= 1u; + else if (current_file >= nfiles) + current_file = nfiles ? nfiles - 1u : 0u; + collection_files_changed(); + navigate_redraw(); + set_actions_sensitive(); +} + +static int +collection_button_pressed(GtkWidget *widget, GdkEventButton *event, void *user) +{ + GtkWidget *menu, *item; + size_t index; + (void) widget; + (void) user; + if (event->button != 3u || !collection_index_at((int)event->x, (int)event->y, &index)) + return 0; + popup_file = index; + menu = gtk_menu_new(); + item = gtk_image_menu_item_new_from_stock(GTK_STOCK_DELETE, NULL); + gtk_menu_item_set_label(GTK_MENU_ITEM(item), "_Remove from collection"); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + g_signal_connect(item, "activate", G_CALLBACK(&remove_from_collection), NULL); + gtk_widget_show_all(menu); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time); + return 1; +} + +GtkWidget * +create_collection_pane(void) +{ + collection_pane = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(collection_pane), GTK_POLICY_ALWAYS, GTK_POLICY_NEVER); + gtk_widget_set_size_request(collection_pane, -1, INITIAL_COLLECTION_PANE_HEIGHT); + gtk_widget_set_no_show_all(collection_pane, 1); + + collection_model = gtk_list_store_new(COLLECTION_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_ULONG); + collection_view = gtk_icon_view_new_with_model(GTK_TREE_MODEL(collection_model)); + gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(collection_view), GTK_SELECTION_SINGLE); + gtk_icon_view_set_orientation(GTK_ICON_VIEW(collection_view), GTK_ORIENTATION_HORIZONTAL); + + collection_renderer = gtk_cell_renderer_pixbuf_new(); + g_object_set(collection_renderer, "xalign", 0.5, "yalign", 0.5, NULL); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(collection_view), collection_renderer, 0); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(collection_view), collection_renderer, "pixbuf", COLLECTION_THUMBNAIL); + gtk_container_add(GTK_CONTAINER(collection_pane), collection_view); + + gtk_widget_add_events(collection_view, GDK_BUTTON_PRESS_MASK); + gtk_drag_source_set(collection_view, GDK_BUTTON1_MASK, &url_list, 1u, GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_set(collection_view, GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + &url_list, 1u, GDK_ACTION_COPY | GDK_ACTION_MOVE); + + g_signal_connect(collection_view, "size-allocate", G_CALLBACK(&collection_size_allocated), NULL); + g_signal_connect(collection_view, "selection-changed", G_CALLBACK(&collection_selection_changed), NULL); + g_signal_connect(collection_view, "button-press-event", G_CALLBACK(&collection_button_pressed), NULL); + g_signal_connect(collection_view, "drag-data-get", G_CALLBACK(&drag_data_get), NULL); + g_signal_connect(collection_view, "drag-data-received", G_CALLBACK(&drag_data_received), NULL); + + return collection_pane; +} + +void +set_collection_pane_visible(int visible) +{ + if (visible) { + gtk_widget_show_all(collection_view); + gtk_widget_show(collection_pane); + populate_collection(); + select_current_file(); + gtk_widget_grab_focus(collection_view); + } else { + gtk_widget_hide(collection_pane); + gtk_widget_grab_focus(display_area); + } + g_idle_add(&recalculate_zoom_mode, NULL); +} + +void +collection_files_changed(void) +{ + if (collection_pane && gtk_widget_get_visible(collection_pane)) { + populate_collection(); + select_current_file(); + } +} + +void +update_collection(void) +{ + if (collection_pane && gtk_widget_get_visible(collection_pane)) + select_current_file(); +} |
