Mercurial > geeqie.yaz
view src/collect-table.c @ 497:a33badd85f16
Allow the copy of file paths to clipboard.
This feature is disabled by default, it can be set through
Preferences > Advanced > Behavior > Show "Copy path" ...
When enabled, it adds a menu entry "Copy path" that let the
user copies current selection's paths to X clipboard.
It is very convenient to paste paths to xterm for example.
Patch by Carles Pina i Estany and me.
author | zas_ |
---|---|
date | Wed, 23 Apr 2008 22:17:21 +0000 |
parents | c7a2471e5c4e |
children | fc9c8a3e1a8b |
line wrap: on
line source
/* * Geeqie * (C) 2004 John Ellis * Copyright (C) 2008 The Geeqie Team * * Author: John Ellis * * This software is released under the GNU General Public License (GNU GPL). * Please read the included file COPYING for more information. * This software comes with no warranty of any kind, use at your own risk! */ #include "main.h" #include "collect-table.h" #include "cellrenderericon.h" #include "collect-dlg.h" #include "collect-io.h" #include "dnd.h" #include "dupe.h" #include "editors.h" #include "filelist.h" #include "img-view.h" #include "info.h" #include "layout.h" #include "layout_image.h" #include "menu.h" #include "print.h" #include "utilops.h" #include "ui_bookmark.h" #include "ui_fileops.h" #include "ui_menu.h" #include "ui_tree_edit.h" #include "icons/marker.xpm" #define MARKER_WIDTH 26 #define MARKER_HEIGHT 32 #include <gdk/gdkkeysyms.h> /* for keyboard values */ /* between these, the icon width is increased by thumb_max_width / 2 */ #define THUMB_MIN_ICON_WIDTH 128 #define THUMB_MAX_ICON_WIDTH 150 #define COLLECT_TABLE_MAX_COLUMNS 32 #define THUMB_BORDER_PADDING 2 #define COLLECT_TABLE_TIP_DELAY 500 #define COLLECT_TABLE_TIP_DELAY_PATH (COLLECT_TABLE_TIP_DELAY * 1.7) enum { CTABLE_COLUMN_POINTER = 0, CTABLE_COLUMN_COUNT }; typedef enum { SELECTION_NONE = 0, SELECTION_SELECTED = 1 << 0, SELECTION_PRELIGHT = 1 << 1, SELECTION_FOCUS = 1 << 2 } SelectionType; #define INFO_SELECTED(x) (x->flag_mask & SELECTION_SELECTED) static void collection_table_populate_at_new_size(CollectTable *ct, gint w, gint h, gint force); /* *------------------------------------------------------------------- * more misc *------------------------------------------------------------------- */ static gint collection_table_find_position(CollectTable *ct, CollectInfo *info, gint *row, gint *col) { gint n; n = g_list_index(ct->cd->list, info); if (n < 0) return FALSE; *row = n / ct->columns; *col = n - (*row * ct->columns); return TRUE; } static gint collection_table_find_iter(CollectTable *ct, CollectInfo *info, GtkTreeIter *iter, gint *column) { GtkTreeModel *store; gint row, col; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); if (!collection_table_find_position(ct, info, &row, &col)) return FALSE; if (!gtk_tree_model_iter_nth_child(store, iter, NULL, row)) return FALSE; if (column) *column = col; return TRUE; } static CollectInfo *collection_table_find_data(CollectTable *ct, gint row, gint col, GtkTreeIter *iter) { GtkTreeModel *store; GtkTreeIter p; if (row < 0 || col < 0) return NULL; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); if (gtk_tree_model_iter_nth_child(store, &p, NULL, row)) { GList *list; gtk_tree_model_get(store, &p, CTABLE_COLUMN_POINTER, &list, -1); if (!list) return NULL; if (iter) *iter = p; return g_list_nth_data(list, col); } return NULL; } static CollectInfo *collection_table_find_data_by_coord(CollectTable *ct, gint x, gint y, GtkTreeIter *iter) { GtkTreePath *tpath; GtkTreeViewColumn *column; if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(ct->listview), x, y, &tpath, &column, NULL, NULL)) { GtkTreeModel *store; GtkTreeIter row; GList *list; gint n; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); gtk_tree_model_get_iter(store, &row, tpath); gtk_tree_path_free(tpath); gtk_tree_model_get(store, &row, CTABLE_COLUMN_POINTER, &list, -1); n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number")); if (list) { if (iter) *iter = row; return g_list_nth_data(list, n); } } return NULL; } static void collection_table_update_status(CollectTable *ct) { if (ct->status_label) { gchar *buf; if (!ct->cd->list) { buf = g_strdup(_("Empty")); } else if (ct->selection) { buf = g_strdup_printf(_("%d images (%d)"), g_list_length(ct->cd->list), g_list_length(ct->selection)); } else { buf = g_strdup_printf(_("%d images"), g_list_length(ct->cd->list)); } gtk_label_set_text(GTK_LABEL(ct->status_label), buf); g_free(buf); } } static void collection_table_update_extras(CollectTable *ct, gint loading, gdouble value) { if (ct->extra_label) { gchar *text; if (loading) text = _("Loading thumbs..."); else text = " "; gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ct->extra_label), value); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ct->extra_label), text); } } static void collection_table_toggle_filenames(CollectTable *ct) { ct->show_text = !ct->show_text; options->show_icon_names = ct->show_text; collection_table_populate_at_new_size(ct, ct->listview->allocation.width, ct->listview->allocation.height, TRUE); } static gint collection_table_get_icon_width(CollectTable *ct) { gint width; if (!ct->show_text) return options->thumbnails.max_width; width = options->thumbnails.max_width + options->thumbnails.max_width / 2; if (width < THUMB_MIN_ICON_WIDTH) width = THUMB_MIN_ICON_WIDTH; if (width > THUMB_MAX_ICON_WIDTH) width = options->thumbnails.max_width; return width; } /* *------------------------------------------------------------------- * cell updates *------------------------------------------------------------------- */ static void collection_table_selection_set(CollectTable *ct, CollectInfo *info, SelectionType value, GtkTreeIter *iter) { GtkTreeModel *store; GList *list; if (!info) return; if (info->flag_mask == value) return; info->flag_mask = value; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); if (iter) { gtk_tree_model_get(store, iter, CTABLE_COLUMN_POINTER, &list, -1); if (list) gtk_list_store_set(GTK_LIST_STORE(store), iter, CTABLE_COLUMN_POINTER, list, -1); } else { GtkTreeIter row; if (collection_table_find_iter(ct, info, &row, NULL)) { gtk_tree_model_get(store, &row, CTABLE_COLUMN_POINTER, &list, -1); if (list) gtk_list_store_set(GTK_LIST_STORE(store), &row, CTABLE_COLUMN_POINTER, list, -1); } } } static void collection_table_selection_add(CollectTable *ct, CollectInfo *info, SelectionType mask, GtkTreeIter *iter) { if (!info) return; collection_table_selection_set(ct, info, info->flag_mask | mask, iter); } static void collection_table_selection_remove(CollectTable *ct, CollectInfo *info, SelectionType mask, GtkTreeIter *iter) { if (!info) return; collection_table_selection_set(ct, info, info->flag_mask & ~mask, iter); } /* *------------------------------------------------------------------- * selections *------------------------------------------------------------------- */ static void collection_table_verify_selections(CollectTable *ct) { GList *work; work = ct->selection; while (work) { CollectInfo *info = work->data; work = work->next; if (!g_list_find(ct->cd->list, info)) { ct->selection = g_list_remove(ct->selection, info); } } } void collection_table_select_all(CollectTable *ct) { GList *work; g_list_free(ct->selection); ct->selection = NULL; work = ct->cd->list; while(work) { ct->selection = g_list_append(ct->selection, work->data); collection_table_selection_add(ct, work->data, SELECTION_SELECTED, NULL); work = work->next; } collection_table_update_status(ct); } void collection_table_unselect_all(CollectTable *ct) { GList *work; work = ct->selection; while (work) { collection_table_selection_remove(ct, work->data, SELECTION_SELECTED, NULL); work = work->next; } g_list_free(ct->selection); ct->selection = NULL; collection_table_update_status(ct); } static void collection_table_select(CollectTable *ct, CollectInfo *info) { ct->prev_selection = info; if (!info || INFO_SELECTED(info)) return; ct->selection = g_list_append(ct->selection, info); collection_table_selection_add(ct, info, SELECTION_SELECTED, NULL); collection_table_update_status(ct); } static void collection_table_unselect(CollectTable *ct, CollectInfo *info) { ct->prev_selection = info; if (!info || !INFO_SELECTED(info) ) return; ct->selection = g_list_remove(ct->selection, info); collection_table_selection_remove(ct, info, SELECTION_SELECTED, NULL); collection_table_update_status(ct); } static void collection_table_select_util(CollectTable *ct, CollectInfo *info, gint select) { if (select) { collection_table_select(ct, info); } else { collection_table_unselect(ct, info); } } static void collection_table_select_region_util(CollectTable *ct, CollectInfo *start, CollectInfo *end, gint select) { gint row1, col1; gint row2, col2; gint t; gint i, j; if (!collection_table_find_position(ct, start, &row1, &col1) || !collection_table_find_position(ct, end, &row2, &col2) ) return; ct->prev_selection = end; if (!options->collections.rectangular_selection) { GList *work; CollectInfo *info; if (g_list_index(ct->cd->list, start) > g_list_index(ct->cd->list, end)) { info = start; start = end; end = info; } work = g_list_find(ct->cd->list, start); while (work) { info = work->data; collection_table_select_util(ct, info, select); if (work->data != end) work = work->next; else work = NULL; } return; } if (row2 < row1) { t = row1; row1 = row2; row2 = t; } if (col2 < col1) { t = col1; col1 = col2; col2 = t; } DEBUG_1("table: %d x %d to %d x %d\n", row1, col1, row2, col2); for (i = row1; i <= row2; i++) { for (j = col1; j <= col2; j++) { CollectInfo *info = collection_table_find_data(ct, i, j, NULL); if (info) collection_table_select_util(ct, info, select); } } } GList *collection_table_selection_get_list(CollectTable *ct) { return collection_list_to_filelist(ct->selection); } static GList *collection_table_get_list(CollectTable *ct) { return collection_list_to_filelist(ct->cd->list); } /* *------------------------------------------------------------------- * tooltip type window *------------------------------------------------------------------- */ static void tip_show(CollectTable *ct) { GtkWidget *label; gint x, y; if (ct->tip_window) return; gdk_window_get_pointer(ct->listview->window, &x, &y, NULL); ct->tip_info = collection_table_find_data_by_coord(ct, x, y, NULL); if (!ct->tip_info) return; ct->tip_window = gtk_window_new(GTK_WINDOW_POPUP); gtk_window_set_resizable(GTK_WINDOW(ct->tip_window), FALSE); gtk_container_set_border_width(GTK_CONTAINER(ct->tip_window), 2); label = gtk_label_new(ct->show_text ? ct->tip_info->fd->path : ct->tip_info->fd->name); g_object_set_data(G_OBJECT(ct->tip_window), "tip_label", label); gtk_container_add(GTK_CONTAINER(ct->tip_window), label); gtk_widget_show(label); gdk_window_get_pointer(NULL, &x, &y, NULL); if (!GTK_WIDGET_REALIZED(ct->tip_window)) gtk_widget_realize(ct->tip_window); gtk_window_move(GTK_WINDOW(ct->tip_window), x + 16, y + 16); gtk_widget_show(ct->tip_window); } static void tip_hide(CollectTable *ct) { if (ct->tip_window) gtk_widget_destroy(ct->tip_window); ct->tip_window = NULL; } static gint tip_schedule_cb(gpointer data) { CollectTable *ct = data; if (ct->tip_delay_id == -1) return FALSE; tip_show(ct); ct->tip_delay_id = -1; return FALSE; } static void tip_schedule(CollectTable *ct) { tip_hide(ct); if (ct->tip_delay_id != -1) { g_source_remove(ct->tip_delay_id); ct->tip_delay_id = -1; } ct->tip_delay_id = g_timeout_add(ct->show_text ? COLLECT_TABLE_TIP_DELAY_PATH : COLLECT_TABLE_TIP_DELAY, tip_schedule_cb, ct); } static void tip_unschedule(CollectTable *ct) { tip_hide(ct); if (ct->tip_delay_id != -1) g_source_remove(ct->tip_delay_id); ct->tip_delay_id = -1; } static void tip_update(CollectTable *ct, CollectInfo *info) { tip_schedule(ct); if (ct->tip_window) { gint x, y; gdk_window_get_pointer(NULL, &x, &y, NULL); gtk_window_move(GTK_WINDOW(ct->tip_window), x + 16, y + 16); if (info != ct->tip_info) { GtkWidget *label; ct->tip_info = info; if (!ct->tip_info) { return; } label = g_object_get_data(G_OBJECT(ct->tip_window), "tip_label"); gtk_label_set_text(GTK_LABEL(label), ct->show_text ? ct->tip_info->fd->path : ct->tip_info->fd->name); } } } /* *------------------------------------------------------------------- * popup menus *------------------------------------------------------------------- */ static void collection_table_popup_save_as_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_dialog_save_as(NULL, ct->cd); } static void collection_table_popup_save_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; if (!ct->cd->path) { collection_table_popup_save_as_cb(widget, data); return; } if (!collection_save(ct->cd, ct->cd->path)) { printf("failed saving to collection path: %s\n", ct->cd->path); } } static GList *collection_table_popup_file_list(CollectTable *ct) { if (!ct->click_info) return NULL; if (INFO_SELECTED(ct->click_info)) { return collection_table_selection_get_list(ct); } return g_list_append(NULL, g_strdup(ct->click_info->fd->path)); } static void collection_table_popup_edit_cb(GtkWidget *widget, gpointer data) { CollectTable *ct; gint n; GList *list; ct = submenu_item_get_data(widget); if (!ct) return; n = GPOINTER_TO_INT(data); list = collection_table_popup_file_list(ct); if (list) { start_editor_from_filelist(n, list); filelist_free(list); } } static void collection_table_popup_info_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; info_window_new(NULL, collection_table_popup_file_list(ct), NULL); } static void collection_table_popup_copy_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; file_util_copy(NULL, collection_table_popup_file_list(ct), NULL, ct->listview); } static void collection_table_popup_move_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; file_util_move(NULL, collection_table_popup_file_list(ct), NULL, ct->listview); } static void collection_table_popup_rename_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; file_util_rename(NULL, collection_table_popup_file_list(ct), ct->listview); } static void collection_table_popup_delete_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; file_util_delete(NULL, collection_table_popup_file_list(ct), ct->listview); } static void collection_table_popup_copy_path_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; file_util_copy_path_list_to_clipboard(collection_table_popup_file_list(ct)); } static void collection_table_popup_sort_cb(GtkWidget *widget, gpointer data) { CollectTable *ct; SortType type; ct = submenu_item_get_data(widget); if (!ct) return; type = (SortType)GPOINTER_TO_INT(data); collection_set_sort_method(ct->cd, type); } static void collection_table_popup_view_new_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; if (ct->click_info && g_list_find(ct->cd->list, ct->click_info)) { view_window_new_from_collection(ct->cd, ct->click_info); } } static void collection_table_popup_view_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; if (ct->click_info && g_list_find(ct->cd->list, ct->click_info)) { layout_image_set_collection(NULL, ct->cd, ct->click_info); } } static void collection_table_popup_selectall_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_table_select_all(ct); ct->prev_selection= ct->click_info; } static void collection_table_popup_unselectall_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_table_unselect_all(ct); ct->prev_selection= ct->click_info; } static void collection_table_popup_remove_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; GList *list; if (!ct->click_info) return; if (INFO_SELECTED(ct->click_info)) { list = g_list_copy(ct->selection); } else { list = g_list_append(NULL, ct->click_info); } collection_remove_by_info_list(ct->cd, list); g_list_free(list); } static void collection_table_popup_add_filelist_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; GList *list; list = layout_list(NULL); if (list) { collection_table_add_filelist(ct, list); filelist_free(list); } } static void collection_table_popup_add_collection_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_dialog_append(NULL, ct->cd); } static void collection_table_popup_find_dupes_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; DupeWindow *dw; dw = dupe_window_new(DUPE_MATCH_NAME); dupe_window_add_collection(dw, ct->cd); } static void collection_table_popup_print_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; FileData *fd; fd = (ct->click_info) ? ct->click_info->fd : NULL; print_window_new(fd, collection_table_selection_get_list(ct), collection_table_get_list(ct), ct->listview); } static void collection_table_popup_show_names_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_table_toggle_filenames(ct); } static void collection_table_popup_destroy_cb(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_table_selection_remove(ct, ct->click_info, SELECTION_PRELIGHT, NULL); ct->click_info = NULL; ct->popup = NULL; path_list_free(ct->drop_list); ct->drop_list = NULL; ct->drop_info = NULL; } static GtkWidget *collection_table_popup_menu(CollectTable *ct, gint over_icon) { GtkWidget *menu; GtkWidget *item; menu = popup_menu_short_lived(); g_signal_connect(G_OBJECT(menu), "destroy", G_CALLBACK(collection_table_popup_destroy_cb), ct); menu_item_add_sensitive(menu, _("_View"), over_icon, G_CALLBACK(collection_table_popup_view_cb), ct); menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, over_icon, G_CALLBACK(collection_table_popup_view_new_cb), ct); menu_item_add_divider(menu); menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, over_icon, G_CALLBACK(collection_table_popup_remove_cb), ct); menu_item_add_stock(menu, _("Append from file list"), GTK_STOCK_ADD, G_CALLBACK(collection_table_popup_add_filelist_cb), ct); menu_item_add_stock(menu, _("Append from collection..."), GTK_STOCK_OPEN, G_CALLBACK(collection_table_popup_add_collection_cb), ct); menu_item_add_divider(menu); menu_item_add(menu, _("Select all"), G_CALLBACK(collection_table_popup_selectall_cb), ct); menu_item_add(menu, _("Select none"), G_CALLBACK(collection_table_popup_unselectall_cb), ct); menu_item_add_divider(menu); submenu_add_edit(menu, &item, G_CALLBACK(collection_table_popup_edit_cb), ct); gtk_widget_set_sensitive(item, over_icon); menu_item_add_sensitive(menu, _("_Properties"), over_icon, G_CALLBACK(collection_table_popup_info_cb), ct); menu_item_add_divider(menu); menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, over_icon, G_CALLBACK(collection_table_popup_copy_cb), ct); menu_item_add_sensitive(menu, _("_Move..."), over_icon, G_CALLBACK(collection_table_popup_move_cb), ct); menu_item_add_sensitive(menu, _("_Rename..."), over_icon, G_CALLBACK(collection_table_popup_rename_cb), ct); menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, over_icon, G_CALLBACK(collection_table_popup_delete_cb), ct); if (options->show_copy_path) menu_item_add_sensitive(menu, _("_Copy path"), over_icon, G_CALLBACK(collection_table_popup_copy_path_cb), ct); menu_item_add_divider(menu); submenu_add_sort(menu, G_CALLBACK(collection_table_popup_sort_cb), ct, FALSE, TRUE, FALSE, 0); menu_item_add_check(menu, _("Show filename _text"), ct->show_text, G_CALLBACK(collection_table_popup_show_names_cb), ct); menu_item_add_divider(menu); menu_item_add_stock(menu, _("_Save collection"), GTK_STOCK_SAVE, G_CALLBACK(collection_table_popup_save_cb), ct); menu_item_add_stock(menu, _("Save collection _as..."), GTK_STOCK_SAVE_AS, G_CALLBACK(collection_table_popup_save_as_cb), ct); menu_item_add_divider(menu); menu_item_add_stock(menu, _("_Find duplicates..."), GTK_STOCK_FIND, G_CALLBACK(collection_table_popup_find_dupes_cb), ct); menu_item_add_stock_sensitive(menu, _("Print..."), GTK_STOCK_PRINT, over_icon, G_CALLBACK(collection_table_popup_print_cb), ct); return menu; } /* *------------------------------------------------------------------- * keyboard callbacks *------------------------------------------------------------------- */ static void collection_table_set_focus(CollectTable *ct, CollectInfo *info) { GtkTreeIter iter; gint row, col; if (g_list_find(ct->cd->list, ct->focus_info)) { if (info == ct->focus_info) { /* ensure focus row col are correct */ collection_table_find_position(ct, ct->focus_info, &ct->focus_row, &ct->focus_column); return; } collection_table_selection_remove(ct, ct->focus_info, SELECTION_FOCUS, NULL); } if (!collection_table_find_position(ct, info, &row, &col)) { ct->focus_info = NULL; ct->focus_row = -1; ct->focus_column = -1; return; } ct->focus_info = info; ct->focus_row = row; ct->focus_column = col; collection_table_selection_add(ct, ct->focus_info, SELECTION_FOCUS, NULL); if (collection_table_find_iter(ct, ct->focus_info, &iter, NULL)) { GtkTreePath *tpath; GtkTreeViewColumn *column; GtkTreeModel *store; tree_view_row_make_visible(GTK_TREE_VIEW(ct->listview), &iter, FALSE); store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); tpath = gtk_tree_model_get_path(store, &iter); /* focus is set to an extra column with 0 width to hide focus, we draw it ourself */ column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), COLLECT_TABLE_MAX_COLUMNS); gtk_tree_view_set_cursor(GTK_TREE_VIEW(ct->listview), tpath, column, FALSE); gtk_tree_path_free(tpath); } } static void collection_table_move_focus(CollectTable *ct, gint row, gint col, gint relative) { gint new_row; gint new_col; if (relative) { new_row = ct->focus_row; new_col = ct->focus_column; new_row += row; if (new_row < 0) new_row = 0; if (new_row >= ct->rows) new_row = ct->rows - 1; while(col != 0) { if (col < 0) { new_col--; col++; } else { new_col++; col--; } if (new_col < 0) { if (new_row > 0) { new_row--; new_col = ct->columns - 1; } else { new_col = 0; } } if (new_col >= ct->columns) { if (new_row < ct->rows - 1) { new_row++; new_col = 0; } else { new_col = ct->columns - 1; } } } } else { new_row = row; new_col = col; if (new_row >= ct->rows) { if (ct->rows > 0) new_row = ct->rows - 1; else new_row = 0; new_col = ct->columns - 1; } if (new_col >= ct->columns) new_col = ct->columns - 1; } if (new_row == ct->rows - 1) { gint l; /* if we moved beyond the last image, go to the last image */ l = g_list_length(ct->cd->list); if (ct->rows > 1) l -= (ct->rows - 1) * ct->columns; if (new_col >= l) new_col = l - 1; } if (new_row == -1 || new_col == -1) { if (!ct->cd->list) return; new_row = new_col = 0; } collection_table_set_focus(ct, collection_table_find_data(ct, new_row, new_col, NULL)); } static void collection_table_update_focus(CollectTable *ct) { gint new_row = 0; gint new_col = 0; if (ct->focus_info && collection_table_find_position(ct, ct->focus_info, &new_row, &new_col)) { /* first find the old focus, if it exists and is valid */ } else { /* (try to) stay where we were */ new_row = ct->focus_row; new_col = ct->focus_column; } collection_table_move_focus(ct, new_row, new_col, FALSE); } /* used to figure the page up/down distances */ static gint page_height(CollectTable *ct) { GtkAdjustment *adj; gint page_size; gint row_height; gint ret; adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(ct->listview)); page_size = (gint)adj->page_increment; row_height = options->thumbnails.max_height + THUMB_BORDER_PADDING * 2; if (ct->show_text) row_height += options->thumbnails.max_height / 3; ret = page_size / row_height; if (ret < 1) ret = 1; return ret; } static void collection_table_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) { CollectTable *ct = data; GtkTreeModel *store; GtkTreeIter iter; gint column; GtkTreePath *tpath; gint cw, ch; if (!collection_table_find_iter(ct, ct->click_info, &iter, &column)) return; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); tpath = gtk_tree_model_get_path(store, &iter); tree_view_get_cell_clamped(GTK_TREE_VIEW(ct->listview), tpath, column, FALSE, x, y, &cw, &ch); gtk_tree_path_free(tpath); *y += ch; popup_menu_position_clamp(menu, x, y, 0); } static gint collection_table_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) { CollectTable *ct = data; gint focus_row = 0; gint focus_col = 0; CollectInfo *info; gint stop_signal; stop_signal = TRUE; switch (event->keyval) { case GDK_Left: case GDK_KP_Left: focus_col = -1; break; case GDK_Right: case GDK_KP_Right: focus_col = 1; break; case GDK_Up: case GDK_KP_Up: focus_row = -1; break; case GDK_Down: case GDK_KP_Down: focus_row = 1; break; case GDK_Page_Up: case GDK_KP_Page_Up: focus_row = -page_height(ct); break; case GDK_Page_Down: case GDK_KP_Page_Down: focus_row = page_height(ct); break; case GDK_Home: case GDK_KP_Home: focus_row = -ct->focus_row; focus_col = -ct->focus_column; break; case GDK_End: case GDK_KP_End: focus_row = ct->rows - 1 - ct->focus_row; focus_col = ct->columns - 1 - ct->focus_column; break; case GDK_space: info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL); if (info) { ct->click_info = info; if (event->state & GDK_CONTROL_MASK) { collection_table_select_util(ct, info, !INFO_SELECTED(info)); } else { collection_table_unselect_all(ct); collection_table_select(ct, info); } } break; case 'T': case 't': if (event->state & GDK_CONTROL_MASK) collection_table_toggle_filenames(ct); break; case GDK_Menu: case GDK_F10: info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL); ct->click_info = info; collection_table_selection_add(ct, ct->click_info, SELECTION_PRELIGHT, NULL); tip_unschedule(ct); ct->popup = collection_table_popup_menu(ct, (info != NULL)); gtk_menu_popup(GTK_MENU(ct->popup), NULL, NULL, collection_table_menu_pos_cb, ct, 0, GDK_CURRENT_TIME); break; default: stop_signal = FALSE; break; } if (focus_row != 0 || focus_col != 0) { CollectInfo *new_info; CollectInfo *old_info; old_info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL); collection_table_move_focus(ct, focus_row, focus_col, TRUE); new_info = collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL); if (new_info != old_info) { if (event->state & GDK_SHIFT_MASK) { if (!options->collections.rectangular_selection) { collection_table_select_region_util(ct, old_info, new_info, FALSE); } else { collection_table_select_region_util(ct, ct->click_info, old_info, FALSE); } collection_table_select_region_util(ct, ct->click_info, new_info, TRUE); } else if (event->state & GDK_CONTROL_MASK) { ct->click_info = new_info; } else { ct->click_info = new_info; collection_table_unselect_all(ct); collection_table_select(ct, new_info); } } } if (stop_signal) { #if 0 g_signal_stop_emission_by_name(GTK_OBJECT(widget), "key_press_event"); #endif tip_unschedule(ct); } return stop_signal; } /* *------------------------------------------------------------------- * insert marker *------------------------------------------------------------------- */ static CollectInfo *collection_table_insert_find(CollectTable *ct, CollectInfo *source, gint *after, GdkRectangle *cell, gint use_coord, gint x, gint y) { CollectInfo *info = NULL; GtkTreeModel *store; GtkTreeIter iter; GtkTreePath *tpath; GtkTreeViewColumn *column; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); if (!use_coord) gdk_window_get_pointer(ct->listview->window, &x, &y, NULL); if (source) { gint col; if (collection_table_find_iter(ct, source, &iter, &col)) { tpath = gtk_tree_model_get_path(store, &iter); column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), col); gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell); gtk_tree_path_free(tpath); info = source; *after = (x > cell->x + (cell->width / 2)); } return info; } if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(ct->listview), x, y, &tpath, &column, NULL, NULL)) { GList *list; gint n; gtk_tree_model_get_iter(store, &iter, tpath); gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1); n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_number")); info = g_list_nth_data(list, n); if (info) { gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell); *after = (x > cell->x + (cell->width / 2)); } gtk_tree_path_free(tpath); } if (info == NULL) { GList *work; work = g_list_last(ct->cd->list); if (work) { gint col; info = work->data; *after = TRUE; if (collection_table_find_iter(ct, info, &iter, &col)) { tpath = gtk_tree_model_get_path(store, &iter); column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), col); gtk_tree_view_get_background_area(GTK_TREE_VIEW(ct->listview), tpath, column, cell); gtk_tree_path_free(tpath); } } } return info; } static CollectInfo *collection_table_insert_point(CollectTable *ct, gint x, gint y) { CollectInfo *info; GdkRectangle cell; gint after = FALSE; info = collection_table_insert_find(ct, NULL, &after, &cell, TRUE, x, y); if (info && after) { GList *work; work = g_list_find(ct->cd->list, info); if (work && work->next) { info = work->next->data; } else { info = NULL; } } return info; } static void collection_table_insert_marker(CollectTable *ct, CollectInfo *info, gint enable) { gint row, col; gint after = FALSE; GdkRectangle cell; if (!enable) { if (ct->marker_window) gdk_window_destroy(ct->marker_window); ct->marker_window = NULL; return; } info = collection_table_insert_find(ct, info, &after, &cell, FALSE, 0, 0); /* this setting does not take into account (after), but since it is not really used... */ ct->marker_info = info; row = -1; col = -1; if (!ct->marker_window) { GdkWindow *parent; GdkWindowAttr attributes; gint attributes_mask; GdkPixmap *pixmap; GdkBitmap *mask; GdkPixbuf *pb; gint w, h; parent = gtk_tree_view_get_bin_window(GTK_TREE_VIEW(ct->listview)); pb = gdk_pixbuf_new_from_xpm_data((const char **)marker_xpm); gdk_pixbuf_render_pixmap_and_mask(pb, &pixmap, &mask, 128); gdk_pixbuf_unref(pb); gdk_drawable_get_size(pixmap, &w, &h); attributes.window_type = GDK_WINDOW_CHILD; attributes.wclass = GDK_INPUT_OUTPUT; attributes.width = w; attributes.height = h; attributes.event_mask = gtk_widget_get_events(ct->listview); attributes_mask = 0; ct->marker_window = gdk_window_new(parent, &attributes, attributes_mask); gdk_window_set_back_pixmap(ct->marker_window, pixmap, FALSE); gdk_window_shape_combine_mask(ct->marker_window, mask, 0, 0); g_object_unref(pixmap); if (mask) g_object_unref(mask); } if (info) { gint x, y; gint w, h; gdk_drawable_get_size(ct->marker_window, &w, &h); if (!after) { x = cell.x; } else { x = cell.x + cell.width; } x -= (w / 2); y = cell.y + (cell.height / 2) - (h / 2); gdk_window_move(ct->marker_window, x, y); gdk_window_clear(ct->marker_window); if (!gdk_window_is_visible(ct->marker_window)) gdk_window_show(ct->marker_window); } else { if (gdk_window_is_visible(ct->marker_window)) gdk_window_hide(ct->marker_window); } } /* *------------------------------------------------------------------- * mouse drag auto-scroll *------------------------------------------------------------------- */ static void collection_table_motion_update(CollectTable *ct, gint x, gint y, gint drop_event) { CollectInfo *info; info = collection_table_find_data_by_coord(ct, x, y, NULL); if (drop_event) { tip_unschedule(ct); collection_table_insert_marker(ct, info, TRUE); } else { tip_update(ct, info); } } static gint collection_table_auto_scroll_idle_cb(gpointer data) { CollectTable *ct = data; GdkWindow *window; gint x, y; gint w, h; if (ct->drop_idle_id == -1) return FALSE; window = ct->listview->window; gdk_window_get_pointer(window, &x, &y, NULL); gdk_drawable_get_size(window, &w, &h); if (x >= 0 && x < w && y >= 0 && y < h) { collection_table_motion_update(ct, x, y, TRUE); } ct->drop_idle_id = -1; return FALSE; } static gint collection_table_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data) { CollectTable *ct = data; if (ct->drop_idle_id == -1) ct->drop_idle_id = g_idle_add(collection_table_auto_scroll_idle_cb, ct); return TRUE; } static void collection_table_scroll(CollectTable *ct, gint scroll) { if (!scroll) { if (ct->drop_idle_id != -1) { g_source_remove(ct->drop_idle_id); ct->drop_idle_id = -1; } widget_auto_scroll_stop(ct->listview); collection_table_insert_marker(ct, NULL, FALSE); } else { GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(ct->listview)); widget_auto_scroll_start(ct->listview, adj, -1, options->thumbnails.max_height / 2, collection_table_auto_scroll_notify_cb, ct); } } /* *------------------------------------------------------------------- * mouse callbacks *------------------------------------------------------------------- */ static gint collection_table_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { CollectTable *ct = data; collection_table_motion_update(ct, (gint)bevent->x, (gint)bevent->y, FALSE); return FALSE; } static gint collection_table_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { CollectTable *ct = data; GtkTreeIter iter; CollectInfo *info; tip_unschedule(ct); info = collection_table_find_data_by_coord(ct, (gint)bevent->x, (gint)bevent->y, &iter); ct->click_info = info; collection_table_selection_add(ct, ct->click_info, SELECTION_PRELIGHT, &iter); switch (bevent->button) { case MOUSE_BUTTON_LEFT: if (bevent->type == GDK_2BUTTON_PRESS) { if (info) { layout_image_set_collection(NULL, ct->cd, info); } } else if (!GTK_WIDGET_HAS_FOCUS(ct->listview)) { gtk_widget_grab_focus(ct->listview); } break; case MOUSE_BUTTON_RIGHT: ct->popup = collection_table_popup_menu(ct, (info != NULL)); gtk_menu_popup(GTK_MENU(ct->popup), NULL, NULL, NULL, NULL, bevent->button, bevent->time); break; default: break; } return TRUE; } static gint collection_table_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { CollectTable *ct = data; GtkTreeIter iter; CollectInfo *info = NULL; tip_schedule(ct); if ((gint)bevent->x != 0 || (gint) bevent->y != 0) { info = collection_table_find_data_by_coord(ct, (gint)bevent->x, (gint)bevent->y, &iter); } if (ct->click_info) { collection_table_selection_remove(ct, ct->click_info, SELECTION_PRELIGHT, NULL); } if (bevent->button == MOUSE_BUTTON_LEFT && info && ct->click_info == info) { collection_table_set_focus(ct, info); if (bevent->state & GDK_CONTROL_MASK) { gint select; select = !INFO_SELECTED(info); if ((bevent->state & GDK_SHIFT_MASK) && ct->prev_selection) { collection_table_select_region_util(ct, ct->prev_selection, info, select); } else { collection_table_select_util(ct, info, select); } } else { collection_table_unselect_all(ct); if ((bevent->state & GDK_SHIFT_MASK) && ct->prev_selection) { collection_table_select_region_util(ct, ct->prev_selection, info, TRUE); } else { collection_table_select_util(ct, info, TRUE); } } } else if (bevent->button == MOUSE_BUTTON_MIDDLE && info && ct->click_info == info) { collection_table_select_util(ct, info, !INFO_SELECTED(info)); } return TRUE; } static gint collection_table_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data) { CollectTable *ct = data; tip_unschedule(ct); return FALSE; } /* *------------------------------------------------------------------- * populate, add, insert, etc. *------------------------------------------------------------------- */ static gboolean collection_table_destroy_node_cb(GtkTreeModel *store, GtkTreePath *tpath, GtkTreeIter *iter, gpointer data) { GList *list; gtk_tree_model_get(store, iter, CTABLE_COLUMN_POINTER, &list, -1); g_list_free(list); return FALSE; } static void collection_table_clear_store(CollectTable *ct) { GtkTreeModel *store; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); gtk_tree_model_foreach(store, collection_table_destroy_node_cb, NULL); gtk_list_store_clear(GTK_LIST_STORE(store)); } static GList *collection_table_add_row(CollectTable *ct, GtkTreeIter *iter) { GtkListStore *store; GList *list = NULL; gint i; for (i = 0; i < ct->columns; i++) list = g_list_prepend(list, NULL); store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview))); gtk_list_store_append(store, iter); gtk_list_store_set(store, iter, CTABLE_COLUMN_POINTER, list, -1); return list; } static void collection_table_populate(CollectTable *ct, gint resize) { gint row; GList *work; collection_table_verify_selections(ct); collection_table_clear_store(ct); if (resize) { gint i; gint thumb_width; thumb_width = collection_table_get_icon_width(ct); for (i = 0; i < COLLECT_TABLE_MAX_COLUMNS; i++) { GtkTreeViewColumn *column; GtkCellRenderer *cell; GList *list; column = gtk_tree_view_get_column(GTK_TREE_VIEW(ct->listview), i); gtk_tree_view_column_set_visible(column, (i < ct->columns)); gtk_tree_view_column_set_fixed_width(column, thumb_width + (THUMB_BORDER_PADDING * 6)); list = gtk_tree_view_column_get_cell_renderers(column); cell = (list) ? list->data : NULL; g_list_free(list); if (cell && GQV_IS_CELL_RENDERER_ICON(cell)) { g_object_set(G_OBJECT(cell), "fixed_width", thumb_width, "fixed_height", options->thumbnails.max_height, "show_text", ct->show_text, NULL); } } if (GTK_WIDGET_REALIZED(ct->listview)) gtk_tree_view_columns_autosize(GTK_TREE_VIEW(ct->listview)); } row = -1; work = ct->cd->list; while (work) { GList *list; GtkTreeIter iter; row++; list = collection_table_add_row(ct, &iter); while (work && list) { list->data = work->data; list = list->next; work = work->next; } } ct->rows = row + 1; collection_table_update_focus(ct); collection_table_update_status(ct); } static void collection_table_populate_at_new_size(CollectTable *ct, gint w, gint h, gint force) { gint new_cols; gint thumb_width; thumb_width = collection_table_get_icon_width(ct); new_cols = w / (thumb_width + (THUMB_BORDER_PADDING * 6)); if (new_cols < 1) new_cols = 1; if (!force && new_cols == ct->columns) return; ct->columns = new_cols; collection_table_populate(ct, TRUE); DEBUG_1("col tab pop cols=%d rows=%d\n", ct->columns, ct->rows); } static void collection_table_sync(CollectTable *ct) { GtkTreeModel *store; GtkTreeIter iter; GList *work; gint r, c; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); r = -1; c = 0; work = ct->cd->list; while (work) { GList *list; r++; c = 0; if (gtk_tree_model_iter_nth_child(store, &iter, NULL, r)) { gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1); gtk_list_store_set(GTK_LIST_STORE(store), &iter, CTABLE_COLUMN_POINTER, list, -1); } else { list = collection_table_add_row(ct, &iter); } while (list) { CollectInfo *info; if (work) { info = work->data; work = work->next; c++; } else { info = NULL; } if (list) { list->data = info; list = list->next; } } } r++; while (gtk_tree_model_iter_nth_child(store, &iter, NULL, r)) { GList *list; gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1); gtk_list_store_remove(GTK_LIST_STORE(store), &iter); g_list_free(list); } ct->rows = r; collection_table_update_focus(ct); collection_table_update_status(ct); } static gint collection_table_sync_idle_cb(gpointer data) { CollectTable *ct = data; if (ct->sync_idle_id == -1) return FALSE; ct->sync_idle_id = -1; collection_table_sync(ct); return FALSE; } static void collection_table_sync_idle(CollectTable *ct) { if (ct->sync_idle_id == -1) { /* high priority, the view needs to be resynced before a redraw * may contain invalid pointers at this time */ ct->sync_idle_id = g_idle_add_full(G_PRIORITY_HIGH, collection_table_sync_idle_cb, ct, NULL); } } void collection_table_add_filelist(CollectTable *ct, GList *list) { GList *work; if (!list) return; work = list; while (work) { collection_add(ct->cd, (FileData *)work->data, FALSE); work = work->next; } } static void collection_table_insert_filelist(CollectTable *ct, GList *list, CollectInfo *insert_info) { GList *work; if (!list) return; work = list; while (work) { collection_insert(ct->cd, (FileData *)work->data, insert_info, FALSE); work = work->next; } collection_table_sync_idle(ct); } static void collection_table_move_by_info_list(CollectTable *ct, GList *info_list, gint row, gint col) { GList *work; GList *insert_pos = NULL; GList *temp; CollectInfo *info; if (!info_list) return; info = collection_table_find_data(ct, row, col, NULL); if (!info_list->next && info_list->data == info) return; if (info) insert_pos = g_list_find(ct->cd->list, info); /* FIXME: this may get slow for large lists */ work = info_list; while (insert_pos && work) { if (insert_pos->data == work->data) { insert_pos = insert_pos->next; work = info_list; } else { work = work->next; } } work = info_list; while (work) { ct->cd->list = g_list_remove(ct->cd->list, work->data); work = work->next; } /* place them back in */ temp = g_list_copy(info_list); if (insert_pos) { ct->cd->list = uig_list_insert_list(ct->cd->list, insert_pos, temp); } else if (info) { ct->cd->list = g_list_concat(temp, ct->cd->list); } else { ct->cd->list = g_list_concat(ct->cd->list, temp); } ct->cd->changed = TRUE; collection_table_sync_idle(ct); } /* *------------------------------------------------------------------- * updating *------------------------------------------------------------------- */ void collection_table_file_update(CollectTable *ct, CollectInfo *info) { GtkTreeIter iter; gint row, col; gdouble value; if (!info) { collection_table_update_extras(ct, FALSE, 0.0); return; } if (!collection_table_find_position(ct, info, &row, &col)) return; if (ct->columns != 0 && ct->rows != 0) { value = (gdouble)(row * ct->columns + col) / (ct->columns * ct->rows); } else { value = 0.0; } collection_table_update_extras(ct, TRUE, value); if (collection_table_find_iter(ct, info, &iter, NULL)) { GtkTreeModel *store; GList *list; store = gtk_tree_view_get_model(GTK_TREE_VIEW(ct->listview)); gtk_tree_model_get(store, &iter, CTABLE_COLUMN_POINTER, &list, -1); gtk_list_store_set(GTK_LIST_STORE(store), &iter, CTABLE_COLUMN_POINTER, list, -1); } } void collection_table_file_add(CollectTable *ct, CollectInfo *info) { collection_table_sync_idle(ct); } void collection_table_file_insert(CollectTable *ct, CollectInfo *ci) { collection_table_sync_idle(ct); } void collection_table_file_remove(CollectTable *ct, CollectInfo *ci) { if (ci && INFO_SELECTED(ci)) { ct->selection = g_list_remove(ct->selection, ci); } collection_table_sync_idle(ct); } void collection_table_refresh(CollectTable *ct) { collection_table_populate(ct, FALSE); } /* *------------------------------------------------------------------- * dnd *------------------------------------------------------------------- */ static void collection_table_add_dir_recursive(CollectTable *ct, gchar *path, gint recursive) { GList *d = NULL; GList *f = NULL; if (filelist_read(path, &f, recursive ? &d : NULL)) { GList *work; f = filelist_filter(f, FALSE); d = filelist_filter(d, TRUE); f = filelist_sort_path(f); d = filelist_sort_path(d); collection_table_insert_filelist(ct, f, ct->marker_info); work = g_list_last(d); while (work) { collection_table_add_dir_recursive(ct, ((FileData *)work->data)->path, TRUE); work = work->prev; } filelist_free(f); filelist_free(d); } } static void confirm_dir_list_do(CollectTable *ct, GList *list, gint recursive) { GList *work = list; while (work) { FileData *fd = work->data; work = work->next; if (isdir(fd->path)) collection_table_add_dir_recursive(ct, fd->path, recursive); } collection_table_insert_filelist(ct, list, ct->marker_info); } static void confirm_dir_list_add(GtkWidget *widget, gpointer data) { CollectTable *ct = data; confirm_dir_list_do(ct, ct->drop_list, FALSE); } static void confirm_dir_list_recurse(GtkWidget *widget, gpointer data) { CollectTable *ct = data; confirm_dir_list_do(ct, ct->drop_list, TRUE); } static void confirm_dir_list_skip(GtkWidget *widget, gpointer data) { CollectTable *ct = data; collection_table_insert_filelist(ct, ct->drop_list, ct->marker_info); } static GtkWidget *collection_table_drop_menu(CollectTable *ct) { GtkWidget *menu; menu = popup_menu_short_lived(); g_signal_connect(G_OBJECT(menu), "destroy", G_CALLBACK(collection_table_popup_destroy_cb), ct); menu_item_add_stock(menu, _("Dropped list includes folders."), GTK_STOCK_DND_MULTIPLE, NULL, NULL); menu_item_add_divider(menu); menu_item_add_stock(menu, _("_Add contents"), GTK_STOCK_OK, G_CALLBACK(confirm_dir_list_add), ct); menu_item_add_stock(menu, _("Add contents _recursive"), GTK_STOCK_ADD, G_CALLBACK(confirm_dir_list_recurse), ct); menu_item_add_stock(menu, _("_Skip folders"), GTK_STOCK_REMOVE, G_CALLBACK(confirm_dir_list_skip), ct); menu_item_add_divider(menu); menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, ct); return menu; } /* *------------------------------------------------------------------- * dnd *------------------------------------------------------------------- */ static GtkTargetEntry collection_drag_types[] = { { TARGET_APP_COLLECTION_MEMBER_STRING, 0, TARGET_APP_COLLECTION_MEMBER }, { "text/uri-list", 0, TARGET_URI_LIST }, { "text/plain", 0, TARGET_TEXT_PLAIN } }; static gint n_collection_drag_types = 3; static GtkTargetEntry collection_drop_types[] = { { TARGET_APP_COLLECTION_MEMBER_STRING, 0, TARGET_APP_COLLECTION_MEMBER }, { "text/uri-list", 0, TARGET_URI_LIST } }; static gint n_collection_drop_types = 2; static void collection_table_dnd_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, gpointer data) { CollectTable *ct = data; gint selected; GList *list = NULL; gchar *uri_text = NULL; gint total; if (!ct->click_info) return; selected = INFO_SELECTED(ct->click_info); switch (info) { case TARGET_APP_COLLECTION_MEMBER: if (selected) { uri_text = collection_info_list_to_dnd_data(ct->cd, ct->selection, &total); } else { list = g_list_append(NULL, ct->click_info); uri_text = collection_info_list_to_dnd_data(ct->cd, list, &total); g_list_free(list); } break; case TARGET_URI_LIST: case TARGET_TEXT_PLAIN: default: if (selected) { list = collection_table_selection_get_list(ct); } else { list = g_list_append(NULL, file_data_ref(ct->click_info->fd)); } if (!list) return; uri_text = uri_text_from_filelist(list, &total, (info == TARGET_TEXT_PLAIN)); filelist_free(list); break; } gtk_selection_data_set(selection_data, selection_data->target, 8, (guchar *)uri_text, total); g_free(uri_text); } static void collection_table_dnd_receive(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, guint time, gpointer data) { CollectTable *ct = data; GList *list = NULL; GList *info_list = NULL; CollectionData *source; CollectInfo *drop_info; GList *work; DEBUG_1("%s\n", selection_data->data); collection_table_scroll(ct, FALSE); collection_table_insert_marker(ct, NULL, FALSE); drop_info = collection_table_insert_point(ct, x, y); switch (info) { case TARGET_APP_COLLECTION_MEMBER: source = collection_from_dnd_data((gchar *)selection_data->data, &list, &info_list); if (source) { if (source == ct->cd) { gint row = -1; gint col = -1; /* it is a move within a collection */ filelist_free(list); list = NULL; if (!drop_info) { collection_table_move_by_info_list(ct, info_list, -1, -1); } else if (collection_table_find_position(ct, drop_info, &row, &col)) { collection_table_move_by_info_list(ct, info_list, row, col); } } else { /* it is a move/copy across collections */ if (context->action == GDK_ACTION_MOVE) { collection_remove_by_info_list(source, info_list); } } g_list_free(info_list); } break; case TARGET_URI_LIST: list = uri_filelist_from_text((gchar *)selection_data->data, TRUE); work = list; while (work) { FileData *fd = work->data; if (isdir(fd->path)) { GtkWidget *menu; ct->drop_list = list; ct->drop_info = drop_info; menu = collection_table_drop_menu(ct); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 0, time); return; } work = work->next; } break; default: list = NULL; break; } if (list) { collection_table_insert_filelist(ct, list, drop_info); filelist_free(list); } } static void collection_table_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data) { CollectTable *ct = data; if (ct->click_info && ct->click_info->pixbuf) { gint items; if (INFO_SELECTED(ct->click_info)) items = g_list_length(ct->selection); else items = 1; dnd_set_drag_icon(widget, context, ct->click_info->pixbuf, items); } } static void collection_table_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data) { CollectTable *ct = data; /* apparently a leave event is not generated on a drop */ tip_unschedule(ct); collection_table_scroll(ct, FALSE); } static gint collection_table_dnd_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer data) { CollectTable *ct = data; collection_table_motion_update(ct, x, y, TRUE); collection_table_scroll(ct, TRUE); return FALSE; } static void collection_table_dnd_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data) { CollectTable *ct = data; collection_table_scroll(ct, FALSE); } static void collection_table_dnd_init(CollectTable *ct) { gtk_drag_source_set(ct->listview, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK, collection_drag_types, n_collection_drag_types, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK); g_signal_connect(G_OBJECT(ct->listview), "drag_data_get", G_CALLBACK(collection_table_dnd_get), ct); g_signal_connect(G_OBJECT(ct->listview), "drag_begin", G_CALLBACK(collection_table_dnd_begin), ct); g_signal_connect(G_OBJECT(ct->listview), "drag_end", G_CALLBACK(collection_table_dnd_end), ct); gtk_drag_dest_set(ct->listview, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, collection_drop_types, n_collection_drop_types, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK); g_signal_connect(G_OBJECT(ct->listview), "drag_motion", G_CALLBACK(collection_table_dnd_motion), ct); g_signal_connect(G_OBJECT(ct->listview), "drag_leave", G_CALLBACK(collection_table_dnd_leave), ct); g_signal_connect(G_OBJECT(ct->listview), "drag_data_received", G_CALLBACK(collection_table_dnd_receive), ct); } /* *----------------------------------------------------------------------------- * draw, etc. *----------------------------------------------------------------------------- */ typedef struct _ColumnData ColumnData; struct _ColumnData { CollectTable *ct; gint number; }; static void collection_table_cell_data_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { ColumnData *cd = data; CollectTable *ct; GtkStyle *style; GList *list; CollectInfo *info; GdkColor color_fg; GdkColor color_bg; ct = cd->ct; gtk_tree_model_get(tree_model, iter, CTABLE_COLUMN_POINTER, &list, -1); info = g_list_nth_data(list, cd->number); style = gtk_widget_get_style(ct->listview); if (info && (info->flag_mask & SELECTION_SELECTED) ) { memcpy(&color_fg, &style->text[GTK_STATE_SELECTED], sizeof(color_fg)); memcpy(&color_bg, &style->base[GTK_STATE_SELECTED], sizeof(color_bg)); } else { memcpy(&color_fg, &style->text[GTK_STATE_NORMAL], sizeof(color_fg)); memcpy(&color_bg, &style->base[GTK_STATE_NORMAL], sizeof(color_bg)); } if (info && (info->flag_mask & SELECTION_PRELIGHT)) { #if 0 shift_color(&color_fg, -1, 0); #endif shift_color(&color_bg, -1, 0); } if (GQV_IS_CELL_RENDERER_ICON(cell)) { if (info) { g_object_set(cell, "pixbuf", info->pixbuf, "text", info->fd->name, "cell-background-gdk", &color_bg, "cell-background-set", TRUE, "foreground-gdk", &color_fg, "foreground-set", TRUE, "has-focus", (ct->focus_info == info), NULL); } else { g_object_set(cell, "pixbuf", NULL, "text", NULL, "cell-background-set", FALSE, "foreground-set", FALSE, "has-focus", FALSE, NULL); } } } static void collection_table_append_column(CollectTable *ct, gint n) { ColumnData *cd; GtkTreeViewColumn *column; GtkCellRenderer *renderer; column = gtk_tree_view_column_new(); gtk_tree_view_column_set_min_width(column, 0); gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_alignment(column, 0.5); renderer = gqv_cell_renderer_icon_new(); gtk_tree_view_column_pack_start(column, renderer, FALSE); g_object_set(G_OBJECT(renderer), "xpad", THUMB_BORDER_PADDING * 2, "ypad", THUMB_BORDER_PADDING, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL); g_object_set_data(G_OBJECT(column), "column_number", GINT_TO_POINTER(n)); cd = g_new0(ColumnData, 1); cd->ct = ct; cd->number = n; gtk_tree_view_column_set_cell_data_func(column, renderer, collection_table_cell_data_cb, cd, g_free); gtk_tree_view_append_column(GTK_TREE_VIEW(ct->listview), column); } /* *------------------------------------------------------------------- * init, destruction *------------------------------------------------------------------- */ static void collection_table_destroy(GtkWidget *widget, gpointer data) { CollectTable *ct = data; if (ct->popup) { g_signal_handlers_disconnect_matched(GTK_OBJECT(ct->popup), G_SIGNAL_MATCH_DATA, 0, 0, 0, NULL, ct); gtk_widget_destroy(ct->popup); } if (ct->sync_idle_id != -1) g_source_remove(ct->sync_idle_id); tip_unschedule(ct); collection_table_scroll(ct, FALSE); g_free(ct); } static void collection_table_sized(GtkWidget *widget, GtkAllocation *allocation, gpointer data) { CollectTable *ct = data; collection_table_populate_at_new_size(ct, allocation->width, allocation->height, FALSE); } CollectTable *collection_table_new(CollectionData *cd) { CollectTable *ct; GtkListStore *store; GtkTreeSelection *selection; gint i; ct = g_new0(CollectTable, 1); ct->cd = cd; ct->columns = 0; ct->rows = 0; ct->selection = NULL; ct->prev_selection = NULL; ct->tip_window = NULL; ct->tip_delay_id = -1; ct->marker_window = NULL; ct->marker_info = NULL; ct->status_label = NULL; ct->extra_label = NULL; ct->focus_row = 0; ct->focus_column = 0; ct->focus_info = NULL; ct->show_text = options->show_icon_names; ct->sync_idle_id = -1; ct->drop_idle_id = -1; ct->popup = NULL; ct->drop_info = NULL; ct->drop_list = NULL; ct->scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(ct->scrolled), GTK_SHADOW_IN); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ct->scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); store = gtk_list_store_new(1, G_TYPE_POINTER); ct->listview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); g_object_unref(store); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ct->listview)); gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_NONE); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ct->listview), FALSE); gtk_tree_view_set_enable_search(GTK_TREE_VIEW(ct->listview), FALSE); for (i = 0; i < COLLECT_TABLE_MAX_COLUMNS; i++) { collection_table_append_column(ct, i); } /* zero width column to hide tree view focus, we draw it ourselves */ collection_table_append_column(ct, i); /* end column to fill white space */ collection_table_append_column(ct, i); g_signal_connect(G_OBJECT(ct->listview), "destroy", G_CALLBACK(collection_table_destroy), ct); g_signal_connect(G_OBJECT(ct->listview), "size_allocate", G_CALLBACK(collection_table_sized), ct); g_signal_connect(G_OBJECT(ct->listview), "key_press_event", G_CALLBACK(collection_table_press_key_cb), ct); gtk_container_add(GTK_CONTAINER(ct->scrolled), ct->listview); gtk_widget_show(ct->listview); collection_table_dnd_init(ct); gtk_widget_set_events(ct->listview, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(ct->listview),"button_press_event", G_CALLBACK(collection_table_press_cb), ct); g_signal_connect(G_OBJECT(ct->listview),"button_release_event", G_CALLBACK(collection_table_release_cb), ct); g_signal_connect(G_OBJECT(ct->listview),"motion_notify_event", G_CALLBACK(collection_table_motion_cb), ct); g_signal_connect(G_OBJECT(ct->listview), "leave_notify_event", G_CALLBACK(collection_table_leave_cb), ct); return ct; } void collection_table_set_labels(CollectTable *ct, GtkWidget *status, GtkWidget *extra) { ct->status_label = status; ct->extra_label = extra; collection_table_update_status(ct); collection_table_update_extras(ct, FALSE, 0.0); } CollectInfo *collection_table_get_focus_info(CollectTable *ct) { return collection_table_find_data(ct, ct->focus_row, ct->focus_column, NULL); }